From 2776813df3b3f404611c633a9d1f3a087f337f8d Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 3 Sep 2026 17:13:22 -0700 Subject: [PATCH] =?UTF-8?q?compat(plugins):=20temporary=20import-path=20sh?= =?UTF-8?q?ims=20for=20external=20plugins=20=E2=80=94=20ONE=20commit,=20re?= =?UTF-8?q?vert=20on=20schedule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Sep 2026 decomposition (PR #102117) makes internal import paths a non-API: names now live in the focused modules that define them. This commit is the ONLY thing keeping the old paths alive, so external plugins have time to update. It is deliberately a single, unsquashed commit: git revert removes every shim, stub and manifest at once on the announced date. Nothing in-tree may depend on these pointers: scripts/check_compat_pointers.py (wired into lint.yml) fails CI if it does. What it adds (see COMPAT_MANIFEST.md, compat_manifest.json): - 332 facade modules get one delimited `PLUGIN-COMPAT` block appended at the end of the file - 1,172 moved names resolved lazily via a module `__getattr__` (PEP 562) — never a top-level import, so no import cycles; facades that already had `__getattr__` get a chained one - 592 third-party/stdlib names the old modules used to expose, with their original import statements - 266 public definitions that had been deleted as unused, restored byte-for-byte from the pre-decomposition tree (+40 private helpers and 16 imports pulled in only because a restored definition needs them) - 3 deleted modules recreated as re-export stubs (gateway/startup_watchdog, hermes_cli/observability/ relay_runtime, tools/environments/modal_utils) - private names (`_x`) get no pointer: they were never API (3,792 skipped) Verified: all 335 touched modules import under a fresh HERMES_HOME and every manifest name resolves; the lint reports zero in-tree uses; ruff clean; targeted suites unchanged. --- .github/workflows/lint.yml | 6 + COMPAT_MANIFEST.md | 3843 +++++ acp_adapter/auth.py | 11 + acp_adapter/edit_approval.py | 16 + acp_adapter/events.py | 8 + acp_adapter/server.py | 35 + acp_adapter/session.py | 8 + agent/agent_init.py | 20 + agent/agent_runtime_helpers.py | 27 + agent/anthropic_adapter.py | 42 + agent/aux_accounting.py | 11 + agent/auxiliary_client.py | 29 + agent/backend_identity.py | 21 + agent/background_review.py | 36 + agent/bedrock_adapter.py | 106 + agent/bounded_response.py | 23 + agent/browser_provider.py | 9 + agent/browser_registry.py | 23 + agent/codex_runtime.py | 18 + agent/coding_context.py | 31 + agent/context_compressor.py | 20 + agent/conversation_compression.py | 10 + agent/conversation_loop.py | 60 + agent/credential_sources.py | 20 + agent/display.py | 11 + agent/error_surface.py | 9 + agent/estop.py | 8 + agent/file_safety.py | 83 + agent/image_gen_provider.py | 10 + agent/image_gen_registry.py | 23 + agent/learning_graph_render.py | 22 + agent/lsp/eventlog.py | 26 + agent/lsp/protocol.py | 9 + agent/monitoring/cron_health.py | 21 + agent/monitoring/emitter.py | 9 + agent/monitoring/gateway_health.py | 21 + agent/monitoring/gateway_health_export.py | 8 + agent/nous_rate_guard.py | 21 + agent/outbound_webhooks.py | 10 + agent/pet/generate/atlas.py | 21 + agent/prompt_builder.py | 21 + agent/prompt_cache_boundary.py | 12 + agent/proxy_sources/iron_proxy.py | 8 + agent/reasoning_effort.py | 9 + agent/relay_llm.py | 8 + agent/relay_runtime.py | 88 + agent/relay_tools.py | 9 + agent/review_idle_queue.py | 8 + agent/secret_sources/_cache.py | 21 + agent/secret_sources/bitwarden.py | 109 + agent/secret_sources/command.py | 189 + agent/secret_sources/onepassword.py | 21 + agent/shell_hooks.py | 8 + agent/skill_bundles.py | 8 + agent/skill_utils.py | 17 + agent/ssl_guard.py | 16 + agent/system_prompt.py | 20 + agent/terminal_env_registry.py | 22 + agent/transcript_repair.py | 8 + agent/transcription_provider.py | 22 + agent/transcription_registry.py | 24 + agent/transports/chat_completions.py | 8 + agent/transports/codex.py | 10 + agent/transports/codex_app_server.py | 9 + agent/tts_registry.py | 24 + agent/turn_context.py | 24 + agent/turn_retry_state.py | 8 + agent/usage_pricing.py | 9 + agent/video_gen_provider.py | 10 + agent/video_gen_registry.py | 23 + agent/web_search_provider.py | 8 + agent/web_search_registry.py | 23 + cli.py | 66 + compat_manifest.json | 12540 ++++++++++++++++ cron/jobs.py | 11 + cron/scheduler.py | 26 + cron/scheduler_provider.py | 23 + gateway/browser_control_artifacts.py | 10 + gateway/browser_control_broker.py | 13 + gateway/config.py | 8 + gateway/delivery_ledger.py | 30 + gateway/disk_status.py | 21 + gateway/hosted_room_discussion.py | 8 + gateway/hosted_room_driver.py | 11 + gateway/hosted_room_execution_policy.py | 9 + gateway/hosted_room_peer.py | 54 + gateway/hosted_room_replicas.py | 23 + gateway/hosted_rooms.py | 11 + gateway/kanban_watchers.py | 25 + gateway/memory_status.py | 21 + gateway/platforms/helpers.py | 95 + gateway/platforms/qqbot/adapter.py | 9 + gateway/platforms/qqbot/chunked_upload.py | 11 + gateway/platforms/qqbot/keyboards.py | 94 + gateway/platforms/signal.py | 22 + gateway/platforms/weixin.py | 10 + gateway/platforms/yuanbao.py | 202 + gateway/platforms/yuanbao_media.py | 9 + gateway/platforms/yuanbao_proto.py | 76 + gateway/relay/__init__.py | 19 + gateway/relay/adapter.py | 8 + gateway/relay/auth.py | 99 + gateway/response_filters.py | 9 + gateway/restart_loop_guard.py | 34 + gateway/run.py | 70 + gateway/session.py | 27 + gateway/slash_access.py | 8 + gateway/slash_commands.py | 31 + gateway/startup_watchdog.py | 6 + gateway/status.py | 22 + gateway/sticker_cache.py | 9 + gateway/stream_consumer.py | 21 + gateway/stream_dispatch.py | 20 + gateway/systemd_notify.py | 8 + hermes_cli/agent_import.py | 25 + hermes_cli/auth.py | 46 + hermes_cli/backup.py | 28 + hermes_cli/browser_connect.py | 10 + hermes_cli/callbacks.py | 123 + hermes_cli/checkpoints.py | 21 + hermes_cli/commands.py | 255 + hermes_cli/config.py | 53 + hermes_cli/console_engine.py | 8 + hermes_cli/curses_ui.py | 8 + hermes_cli/dashboard_auth/audit.py | 8 + hermes_cli/dashboard_auth/middleware.py | 20 + hermes_cli/dingtalk_auth.py | 21 + hermes_cli/doctor.py | 50 + hermes_cli/doctor_live.py | 17 + hermes_cli/focus_view.py | 22 + hermes_cli/foreign_sessions.py | 63 + hermes_cli/gateway.py | 33 + hermes_cli/gitlock.py | 30 + hermes_cli/heartbeat.py | 8 + hermes_cli/journey.py | 10 + hermes_cli/kanban.py | 8 + hermes_cli/kanban_db.py | 83 + hermes_cli/kanban_decompose.py | 9 + hermes_cli/kanban_diagnostics.py | 19 + hermes_cli/local_runtime/binaries.py | 20 + hermes_cli/local_runtime/bootstrap.py | 20 + hermes_cli/local_runtime/capabilities.py | 9 + hermes_cli/local_runtime/catalog.py | 19 + hermes_cli/local_runtime/hf_browse.py | 8 + hermes_cli/main.py | 26 + hermes_cli/mcp_picker.py | 20 + hermes_cli/mcp_security.py | 10 + hermes_cli/middleware.py | 26 + hermes_cli/moa_config.py | 34 + hermes_cli/model_setup_flows.py | 26 + hermes_cli/model_switch.py | 26 + hermes_cli/models.py | 98 + hermes_cli/nous_billing.py | 9 + hermes_cli/nous_subscription.py | 20 + hermes_cli/observability/relay_runtime.py | 6 + .../observability/relay_shared_metrics.py | 41 + hermes_cli/onepassword_secrets_cli.py | 22 + hermes_cli/platform_actions.py | 8 + hermes_cli/plugins.py | 64 + hermes_cli/plugins_cmd.py | 8 + hermes_cli/profile_describer.py | 8 + hermes_cli/profile_distribution.py | 20 + hermes_cli/profiles.py | 14 + hermes_cli/runtime_provider.py | 22 + hermes_cli/security_advisories.py | 24 + hermes_cli/setup.py | 27 + hermes_cli/slack_cli.py | 8 + hermes_cli/sqlite_safe_read.py | 9 + hermes_cli/status.py | 25 + hermes_cli/telegram_managed_bot.py | 98 + hermes_cli/tools_config.py | 29 + hermes_cli/uninstall.py | 26 + hermes_cli/update_cmd.py | 11 + hermes_cli/update_inventory.py | 9 + hermes_cli/web_deps.py | 23 + hermes_cli/web_routers/cron.py | 8 + hermes_cli/web_routers/mcp.py | 8 + hermes_cli/web_routers/sessions.py | 10 + hermes_cli/web_routers/skills.py | 21 + hermes_cli/web_routers/tools.py | 21 + hermes_cli/web_server.py | 407 + hermes_cli/webhook.py | 22 + hermes_cli/win_pty_bridge.py | 8 + hermes_logging.py | 15 + hermes_state.py | 74 + hermes_state_registry.py | 16 + hermes_state_search.py | 9 + plugins/browser/browser_use/provider.py | 21 + plugins/browser/browserbase/provider.py | 22 + plugins/browser/firecrawl/provider.py | 22 + plugins/context_engine/__init__.py | 9 + plugins/cron_providers/__init__.py | 9 + plugins/cron_providers/chronos/__init__.py | 8 + plugins/cron_providers/chronos/_nas_client.py | 8 + plugins/dashboard_auth/basic/__init__.py | 21 + plugins/dashboard_auth/drain/__init__.py | 20 + plugins/dashboard_auth/nous/__init__.py | 29 + .../dashboard_auth/self_hosted/__init__.py | 26 + plugins/google_meet/audio_bridge.py | 30 + plugins/google_meet/meet_bot.py | 12 + plugins/google_meet/node/cli.py | 8 + plugins/google_meet/node/registry.py | 8 + plugins/image_gen/deepinfra/__init__.py | 23 + plugins/image_gen/fal/__init__.py | 21 + plugins/image_gen/krea/__init__.py | 23 + plugins/image_gen/openai/__init__.py | 24 + plugins/image_gen/xai/__init__.py | 24 + plugins/memory/hindsight/__init__.py | 9 + plugins/memory/honcho/__init__.py | 25 + plugins/memory/honcho/client.py | 20 + plugins/memory/honcho/oauth.py | 8 + plugins/memory/honcho/session.py | 11 + plugins/memory/mem0/__init__.py | 71 + plugins/memory/mem0/_setup.py | 16 + plugins/memory/retaindb/__init__.py | 90 + plugins/memory/supermemory/__init__.py | 35 + plugins/platforms/a2a/protocol.py | 47 + plugins/platforms/a2a/security.py | 83 + plugins/platforms/a2a/tools.py | 8 + plugins/platforms/dingtalk/adapter.py | 22 + plugins/platforms/discord/adapter.py | 20 + plugins/platforms/feishu/feishu_comment.py | 95 + plugins/platforms/google_chat/oauth.py | 22 + plugins/platforms/irc/adapter.py | 8 + plugins/platforms/line/adapter.py | 8 + plugins/platforms/matrix/adapter.py | 40 + plugins/platforms/ntfy/adapter.py | 8 + plugins/platforms/photon/adapter.py | 21 + plugins/platforms/photon/auth.py | 48 + plugins/platforms/photon/cli.py | 21 + plugins/platforms/raft/adapter.py | 8 + plugins/platforms/teams/adapter.py | 22 + plugins/platforms/telegram/adapter.py | 22 + plugins/platforms/telegram/telegram_ids.py | 11 + plugins/platforms/wecom/adapter.py | 47 + plugins/spotify/__init__.py | 26 + plugins/spotify/client.py | 21 + plugins/spotify/tools.py | 21 + plugins/teams_pipeline/cli.py | 24 + plugins/teams_pipeline/meetings.py | 45 + plugins/teams_pipeline/pipeline.py | 8 + plugins/teams_pipeline/subscriptions.py | 27 + plugins/video_gen/fal/__init__.py | 8 + plugins/video_gen/xai/__init__.py | 60 + plugins/web/brave_free/provider.py | 21 + plugins/web/ddgs/provider.py | 20 + plugins/web/exa/provider.py | 21 + plugins/web/firecrawl/provider.py | 23 + plugins/web/keenable/provider.py | 20 + plugins/web/parallel/provider.py | 20 + plugins/web/searxng/provider.py | 21 + plugins/web/tavily/provider.py | 20 + plugins/web/xai/provider.py | 20 + providers/__init__.py | 20 + run_agent.py | 49 + scripts/check_compat_pointers.py | 93 + tools/apply_layout_tool.py | 8 + tools/approval.py | 48 + tools/async_delegation.py | 20 + tools/browser_camofox_state.py | 11 + tools/browser_dialog_tool.py | 21 + tools/browser_supervisor.py | 37 + tools/browser_tool.py | 49 + tools/clarify_gateway.py | 24 + tools/close_preview_tool.py | 47 + tools/code_execution_tool.py | 27 + tools/code_kernel_remote.py | 8 + tools/computer_use/__init__.py | 25 + tools/computer_use/cua_backend.py | 35 + tools/computer_use/permissions.py | 8 + tools/computer_use/tool.py | 8 + tools/cronjob_tools.py | 21 + tools/delegate_tool.py | 36 + tools/delegation_live_log.py | 11 + tools/delegation_output_schema.py | 9 + tools/discord_tool.py | 13 + tools/drive_preview_tool.py | 8 + tools/env_probe.py | 8 + tools/environments/base.py | 28 + tools/environments/managed_modal.py | 8 + tools/environments/modal_utils.py | 6 + tools/environments/vercel_sandbox.py | 8 + tools/feishu_doc_tool.py | 23 + tools/feishu_drive_tool.py | 9 + tools/file_operations.py | 48 + tools/file_tools.py | 26 + tools/focus_pane_tool.py | 28 + tools/fuzzy_match.py | 9 + tools/image_generation_tool.py | 11 + tools/lazy_deps.py | 13 + tools/managed_tool_gateway.py | 205 + tools/mcp_oauth.py | 18 + tools/mcp_schema_cache.py | 17 + tools/mcp_tool.py | 56 + tools/memory_tool.py | 22 + tools/microsoft_graph_client.py | 21 + tools/open_preview_tool.py | 51 + tools/openrouter_client.py | 23 + tools/path_security.py | 21 + tools/preview_tool.py | 11 + tools/process_registry.py | 9 + tools/react_to_message_tool.py | 20 + tools/read_extract.py | 9 + tools/read_preview_tool.py | 51 + tools/read_terminal_tool.py | 8 + tools/read_window_tool.py | 21 + tools/send_message_tool.py | 61 + tools/skill_ledger.py | 13 + tools/skill_linter.py | 14 + tools/skill_manager_tool.py | 20 + tools/skill_usage.py | 63 + tools/skillevaluator_scan.py | 13 + tools/skills_guard.py | 11 + tools/skills_hub.py | 70 + tools/skills_sync.py | 34 + tools/skills_sync_client.py | 68 + tools/skills_tool.py | 25 + tools/slash_confirm.py | 34 + tools/terminal_scope.py | 24 + tools/terminal_tool.py | 38 + tools/tool_result_storage.py | 10 + tools/tool_search.py | 45 + tools/transcription_tools.py | 46 + tools/tts_tool.py | 102 + tools/url_safety.py | 57 + tools/vision_tools.py | 10 + tools/voice_mode.py | 27 + tools/web_result_cache.py | 9 + tools/web_tools.py | 35 + tools/website_policy.py | 13 + tools/write_approval.py | 10 + tools/yuanbao_tools.py | 9 + toolsets.py | 25 + tui_gateway/compute_host.py | 81 + tui_gateway/hosted_room_driver.py | 13 + tui_gateway/hosted_room_service.py | 8 + tui_gateway/mcp_rpc_helpers.py | 28 + tui_gateway/methods_browser_control.py | 22 + tui_gateway/methods_prompt.py | 8 + 339 files changed, 25958 insertions(+) create mode 100644 COMPAT_MANIFEST.md create mode 100644 compat_manifest.json create mode 100644 gateway/startup_watchdog.py create mode 100644 hermes_cli/observability/relay_runtime.py create mode 100644 scripts/check_compat_pointers.py create mode 100644 tools/environments/modal_utils.py diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7a49a62203..bae4299b1b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -178,3 +178,9 @@ jobs: - name: Run footgun checker run: python scripts/check-windows-footguns.py --all + + # The Sep 2026 decomposition kept old import paths alive for external plugins + # (PLUGIN-COMPAT blocks, see COMPAT_MANIFEST.md). They are removed on schedule by + # reverting one commit, so in-tree code must never depend on them. + - name: Forbid in-tree use of plugin-compat pointers + run: python scripts/check_compat_pointers.py diff --git a/COMPAT_MANIFEST.md b/COMPAT_MANIFEST.md new file mode 100644 index 0000000000..8d9abc25b0 --- /dev/null +++ b/COMPAT_MANIFEST.md @@ -0,0 +1,3843 @@ +# Plugin compatibility manifest (temporary) + +The September 2026 decomposition (PR #102117) split the large modules of Hermes Agent into focused +files. **Internal import paths are not a stable API**, and after that PR the names below are no +longer defined where they used to be. To give external plugin authors time to update, every name +is still importable from its OLD module through a `PLUGIN-COMPAT` block appended to that module. + +**This layer is temporary.** It was added as a single commit and will be removed, on the announced +date, by reverting that commit. Update your plugin to import from the `new location` column now. +Nothing inside this repository is allowed to use these pointers (`scripts/check_compat_pointers.py` +fails CI if it does). + +| kind | count | meaning | +|---|---|---| +| moved | 0 | name now defined in `new location`; re-exported from the old module | +| moved-lazy | 1172 | same, resolved lazily via `__getattr__` to avoid an import cycle | +| import | 592 | a third-party/stdlib name the old module used to expose; original import restored | +| restored-def | 266 | public name that was deleted as unused; its pre-decomposition definition is restored verbatim | +| restored-helper | 40 | private helper restored only because a restored-def above depends on it | +| restored-import | 16 | import re-added only because a restored-def above depends on it | +| module-stub | 3 | whole module deleted; stub re-exports from its replacement | +| unrestorable | 34 | not restorable (e.g. leaked loop variables); listed for completeness | + +## Names by old module + + +### `acp_adapter.auth` + +| name | kind | new location | +|---|---|---| +| `has_provider` | restored-def | `(deleted; BASE body restored)` | + +### `acp_adapter.edit_approval` + +| name | kind | new location | +|---|---|---| +| `FutureTimeout` | import | `concurrent.futures` | +| `clear_edit_approval_requester` | restored-def | `(deleted; BASE body restored)` | +| `get_edit_approval_requester` | restored-def | `(deleted; BASE body restored)` | + +### `acp_adapter.events` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | + +### `acp_adapter.server` + +| name | kind | new location | +|---|---|---| +| `ACP_MAX_MODELS_PER_PROVIDER` | moved-lazy | `acp_adapter.model_catalog` | +| `AgentThoughtChunk` | import | `acp.schema` | +| `AudioContentBlock` | import | `acp.schema` | +| `AvailableCommand` | import | `acp.schema` | +| `AvailableCommandsUpdate` | import | `acp.schema` | +| `BlobResourceContents` | import | `acp.schema` | +| `EmbeddedResourceContentBlock` | import | `acp.schema` | +| `ImageContentBlock` | import | `acp.schema` | +| `Path` | import | `pathlib` | +| `ResourceContentBlock` | import | `acp.schema` | +| `TextResourceContents` | import | `acp.schema` | +| `UnstructuredCommandInput` | import | `acp.schema` | +| `base64` | import | `base64` | +| `json` | import | `json` | +| `unquote` | import | `urllib.parse` | +| `urlparse` | import | `urllib.parse` | + +### `acp_adapter.session` + +| name | kind | new location | +|---|---|---| +| `Lock` | import | `threading` | + +### `agent` + +| name | kind | new location | +|---|---|---| +| `message_sanitization` | unrestorable | `no top-level definition on BASE` | + +### `agent.agent_init` + +| name | kind | new location | +|---|---|---| +| `ToolGuardrailDecision` | moved-lazy | `agent.tool_guardrails` | + +### `agent.agent_runtime_helpers` + +| name | kind | new location | +|---|---|---| +| `agent_runtime_owns_post_tool_hook` | restored-def | `(deleted; BASE body restored)` | +| `intent_ack_continuation_enabled` | restored-def | `(deleted; BASE body restored)` | + +### `agent.anthropic_adapter` + +| name | kind | new location | +|---|---|---| +| `CredentialPersistError` | moved-lazy | `agent.anthropic_credentials` | +| `Path` | import | `pathlib` | +| `Tuple` | import | `typing` | +| `base_url_host_matches` | moved-lazy | `utils` | +| `base_url_hostname` | moved-lazy | `utils` | +| `claude_code_credentials_path` | moved-lazy | `agent.anthropic_credentials` | +| `copy` | import | `copy` | +| `get_hermes_home` | moved-lazy | `hermes_constants` | +| `is_claude_code_token_valid` | moved-lazy | `agent.anthropic_credentials` | +| `is_rotation_consumed_uncommitted` | moved-lazy | `agent.anthropic_credentials` | +| `json` | import | `json` | +| `mark_rotation_consumed_uncommitted` | moved-lazy | `agent.anthropic_credentials` | +| `os` | import | `os` | +| `platform` | import | `platform` | +| `read_claude_code_credentials` | moved-lazy | `agent.anthropic_credentials` | +| `read_hermes_oauth_credentials` | moved-lazy | `agent.anthropic_credentials` | +| `refresh_anthropic_oauth_pure` | moved-lazy | `agent.anthropic_credentials` | +| `resolve_anthropic_token` | moved-lazy | `agent.anthropic_credentials` | +| `run_hermes_oauth_login_pure` | moved-lazy | `agent.anthropic_credentials` | +| `run_oauth_setup_token` | moved-lazy | `agent.anthropic_credentials` | +| `secrets` | import | `secrets` | +| `stat` | import | `stat` | +| `urlparse` | import | `urllib.parse` | + +### `agent.aux_accounting` + +| name | kind | new location | +|---|---|---| +| `get_accounting_context` | restored-def | `(deleted; BASE body restored)` | + +### `agent.auxiliary_client` + +| name | kind | new location | +|---|---|---| +| `NOUS_EXTRA_BODY` | restored-def | `(deleted; BASE body restored)` | +| `Path` | import | `pathlib` | +| `copy` | import | `copy` | +| `get_async_text_auxiliary_client` | restored-def | `(deleted; BASE body restored)` | + +### `agent.backend_identity` + +| name | kind | new location | +|---|---|---| +| `_REASON_SCOPES` | restored-helper | `(deleted; restored as a dependency of classify_failure_scope)` | +| `classify_failure_scope` | restored-def | `(deleted; BASE body restored)` | + +### `agent.background_review` + +| name | kind | new location | +|---|---|---| +| `Path` | import | `pathlib` | +| `is_background_review_enabled` | restored-def | `(deleted; BASE body restored)` | + +### `agent.bedrock_adapter` + +| name | kind | new location | +|---|---|---| +| `CONTEXT_OVERFLOW_PATTERNS` | restored-def | `(deleted; BASE body restored)` | +| `OVERLOAD_PATTERNS` | restored-def | `(deleted; BASE body restored)` | +| `THROTTLE_PATTERNS` | restored-def | `(deleted; BASE body restored)` | +| `call_converse_stream` | restored-def | `(deleted; BASE body restored)` | +| `classify_bedrock_error` | restored-def | `(deleted; BASE body restored)` | +| `is_context_overflow_error` | restored-helper | `(deleted; restored as a dependency of classify_bedrock_error)` | +| `is_context_overflow_error` | restored-def | `(deleted; BASE body restored)` | + +### `agent.bounded_response` + +| name | kind | new location | +|---|---|---| +| `Optional` | import | `typing` | +| `read_error_body_or_default` | restored-def | `(deleted; BASE body restored)` | + +### `agent.browser_provider` + +| name | kind | new location | +|---|---|---| +| `Any` | import | `typing` | +| `Optional` | import | `typing` | + +### `agent.browser_registry` + +| name | kind | new location | +|---|---|---| +| `Dict` | import | `typing` | +| `List` | import | `typing` | +| `hermes_home_key` | moved-lazy | `hermes_constants` | +| `threading` | import | `threading` | + +### `agent.codex_runtime` + +| name | kind | new location | +|---|---|---| +| `run_codex_create_stream_fallback` | restored-def | `(deleted; BASE body restored)` | + +### `agent.coding_context` + +| name | kind | new location | +|---|---|---| +| `_PROFILES` | restored-helper | `(deleted; restored as a dependency of get_profile)` | +| `coding_system_blocks` | restored-def | `(deleted; BASE body restored)` | +| `get_profile` | restored-def | `(deleted; BASE body restored)` | + +### `agent.context_compressor` + +| name | kind | new location | +|---|---|---| +| `tool_result_id_variants` | moved-lazy | `agent.message_sanitization` | + +### `agent.conversation_compression` + +| name | kind | new location | +|---|---|---| +| `CompressionExecutorSaturatedError` | restored-def | `(deleted; BASE body restored)` | + +### `agent.conversation_loop` + +| name | kind | new location | +|---|---|---| +| `COMPRESSION_RETRY_CONTEXT_REDUCED_STATUS_TEMPLATE` | moved-lazy | `agent.conversation_compression` | +| `COMPRESSION_RETRY_MESSAGES_STATUS_TEMPLATE` | moved-lazy | `agent.conversation_compression` | +| `COMPRESSION_RETRY_TOKENS_STATUS_TEMPLATE` | moved-lazy | `agent.conversation_compression` | +| `COMPRESSION_RETRY_TOO_LARGE_STATUS_TEMPLATE` | moved-lazy | `agent.conversation_compression` | +| `FailoverReason` | moved-lazy | `agent.error_classifier` | +| `KawaiiSpinner` | moved-lazy | `agent.display` | +| `PARTIAL_STREAM_STUB_ID` | moved-lazy | `hermes_constants` | +| `PRE_API_COMPRESSION_STATUS_TEMPLATE` | moved-lazy | `agent.conversation_compression` | +| `adaptive_rate_limit_backoff` | moved-lazy | `agent.retry_utils` | +| `anchored_context_tokens` | moved-lazy | `agent.model_metadata` | +| `automatic_compaction_status_message` | moved-lazy | `agent.context_engine` | +| `capture_usage_anchor` | moved-lazy | `agent.model_metadata` | +| `classify_api_error` | moved-lazy | `agent.error_classifier` | +| `close_interrupted_tool_sequence` | moved-lazy | `agent.message_sanitization` | +| `coalesce_tool_call_id` | moved-lazy | `agent.message_sanitization` | +| `compose_user_api_content` | moved-lazy | `agent.turn_context` | +| `compression_blocked_transiently` | moved-lazy | `agent.conversation_compression` | +| `compression_skipped_due_to_lock` | moved-lazy | `agent.conversation_compression` | +| `context_compression_timed_out` | moved-lazy | `agent.conversation_compression` | +| `conversation_history_after_compression` | moved-lazy | `agent.conversation_compression` | +| `env_var_enabled` | moved-lazy | `utils` | +| `estimate_messages_tokens_rough` | moved-lazy | `agent.model_metadata` | +| `estimate_request_tokens_rough` | moved-lazy | `agent.model_metadata` | +| `estimate_usage_cost` | moved-lazy | `agent.usage_pricing` | +| `get_context_length_from_provider_error` | moved-lazy | `agent.model_metadata` | +| `has_incomplete_scratchpad` | moved-lazy | `agent.trajectory` | +| `is_output_cap_error` | moved-lazy | `agent.model_metadata` | +| `is_repetition_dominated` | moved-lazy | `agent.repetition_guard` | +| `is_zai_coding_overload_error` | moved-lazy | `agent.retry_utils` | +| `jittered_backoff` | moved-lazy | `agent.retry_utils` | +| `normalize_usage` | moved-lazy | `agent.usage_pricing` | +| `os` | import | `os` | +| `parse_available_output_tokens_from_error` | moved-lazy | `agent.model_metadata` | +| `random` | import | `random` | +| `reanchor_current_turn_user_idx` | moved-lazy | `agent.turn_context` | +| `save_context_length` | moved-lazy | `agent.model_metadata` | +| `serialized_messages_bytes` | moved-lazy | `agent.message_sanitization` | +| `splice_provider_projection` | moved-lazy | `agent.provider_projection` | +| `ssl` | import | `ssl` | +| `sys` | import | `sys` | +| `zai_coding_overload_retry_ceiling` | moved-lazy | `agent.retry_utils` | + +### `agent.credential_sources` + +| name | kind | new location | +|---|---|---| +| `register` | moved-lazy | `tools.slash_confirm` | + +### `agent.display` + +| name | kind | new location | +|---|---|---| +| `get_friendly_tool_labels` | restored-def | `(deleted; BASE body restored)` | + +### `agent.error_surface` + +| name | kind | new location | +|---|---|---| +| `LAYER_RUNTIME` | restored-def | `(deleted; BASE body restored)` | + +### `agent.estop` + +| name | kind | new location | +|---|---|---| +| `os` | import | `os` | + +### `agent.file_safety` + +| name | kind | new location | +|---|---|---| +| `PROFILE_SCOPED_AREAS` | restored-def | `(deleted; BASE body restored)` | +| `classify_cross_profile_target` | restored-def | `(deleted; BASE body restored)` | +| `get_cross_profile_warning` | restored-def | `(deleted; BASE body restored)` | + +### `agent.image_gen_provider` + +| name | kind | new location | +|---|---|---| +| `base64` | import | `base64` | +| `datetime` | import | `datetime` | +| `uuid` | import | `uuid` | + +### `agent.image_gen_registry` + +| name | kind | new location | +|---|---|---| +| `Dict` | import | `typing` | +| `List` | import | `typing` | +| `hermes_home_key` | moved-lazy | `hermes_constants` | +| `threading` | import | `threading` | + +### `agent.learning_graph_render` + +| name | kind | new location | +|---|---|---| +| `Grid` | restored-def | `(deleted; BASE body restored)` | +| `Run` | moved-lazy | `hermes_cli.kanban_db` | + +### `agent.lsp.eventlog` + +| name | kind | new location | +|---|---|---| +| `_announce_once` | restored-helper | `(deleted; restored as a dependency of log_no_server_configured)` | +| `_announced_no_server` | restored-helper | `(deleted; restored as a dependency of log_no_server_configured)` | +| `log_no_server_configured` | restored-def | `(deleted; BASE body restored)` | + +### `agent.lsp.protocol` + +| name | kind | new location | +|---|---|---| +| `ERROR_REQUEST_CANCELLED` | restored-def | `(deleted; BASE body restored)` | + +### `agent.monitoring.cron_health` + +| name | kind | new location | +|---|---|---| +| `GatewayHealthSnapshot` | moved-lazy | `agent.monitoring.gateway_health` | +| `hashlib` | import | `hashlib` | + +### `agent.monitoring.emitter` + +| name | kind | new location | +|---|---|---| +| `TelemetryEmitter` | restored-def | `(deleted; BASE body restored)` | + +### `agent.monitoring.gateway_health` + +| name | kind | new location | +|---|---|---| +| `redact_gateway_message` | restored-def | `(deleted; BASE body restored)` | + +### `agent.monitoring.gateway_health_export` + +| name | kind | new location | +|---|---|---| +| `re` | import | `re` | + +### `agent.nous_rate_guard` + +| name | kind | new location | +|---|---|---| +| `atomic_replace` | moved-lazy | `utils` | +| `tempfile` | import | `tempfile` | + +### `agent.outbound_webhooks` + +| name | kind | new location | +|---|---|---| +| `Path` | import | `pathlib` | +| `datetime` | import | `datetime` | +| `timezone` | import | `datetime` | + +### `agent.pet.generate.atlas` + +| name | kind | new location | +|---|---|---| +| `COLUMNS` | restored-def | `(deleted; BASE body restored)` | +| `FRAME_COUNTS` | restored-def | `(deleted; BASE body restored)` | +| `ROWS` | restored-def | `(deleted; BASE body restored)` | +| `atlas_to_webp_bytes` | restored-def | `(deleted; BASE body restored)` | +| `io` | restored-import | `io` | +| `io` | import | `io` | + +### `agent.prompt_builder` + +| name | kind | new location | +|---|---|---| +| `List` | import | `typing` | +| `org_id_of_path` | moved-lazy | `agent.skill_utils` | + +### `agent.prompt_cache_boundary` + +| name | kind | new location | +|---|---|---| +| `clear_stable_prefixes` | restored-def | `(deleted; BASE body restored)` | + +### `agent.proxy_sources.iron_proxy` + +| name | kind | new location | +|---|---|---| +| `stat` | import | `stat` | + +### `agent.reasoning_effort` + +| name | kind | new location | +|---|---|---| +| `CODEX_RESPONSES_EFFORTS` | restored-def | `(deleted; BASE body restored)` | + +### `agent.relay_llm` + +| name | kind | new location | +|---|---|---| +| `dataclass` | import | `dataclasses` | + +### `agent.relay_runtime` + +| name | kind | new location | +|---|---|---| +| `auto` | import | `enum` | +| `emit_mark` | restored-def | `(deleted; BASE body restored)` | +| `ensure_session` | restored-def | `(deleted; BASE body restored)` | +| `get_host` | restored-def | `(deleted; BASE body restored)` | +| `get_session_handle` | restored-def | `(deleted; BASE body restored)` | +| `run_in_session` | restored-def | `(deleted; BASE body restored)` | +| `run_in_session_async` | restored-def | `(deleted; BASE body restored)` | + +### `agent.relay_tools` + +| name | kind | new location | +|---|---|---| +| `asyncio` | import | `asyncio` | +| `inspect` | import | `inspect` | + +### `agent.review_idle_queue` + +| name | kind | new location | +|---|---|---| +| `List` | import | `typing` | + +### `agent.secret_sources._cache` + +| name | kind | new location | +|---|---|---| +| `FetchResult` | moved-lazy | `agent.secret_sources.base` | +| `is_valid_env_name` | moved-lazy | `agent.secret_sources.base` | + +### `agent.secret_sources.bitwarden` + +| name | kind | new location | +|---|---|---| +| `DiskCache` | moved-lazy | `agent.secret_sources._cache` | +| `apply_bitwarden_secrets` | restored-def | `(deleted; BASE body restored)` | +| `stat` | import | `stat` | + +### `agent.secret_sources.command` + +| name | kind | new location | +|---|---|---| +| `apply_command_secrets` | restored-def | `(deleted; BASE body restored)` | +| `get_command_secret` | restored-def | `(deleted; BASE body restored)` | +| `get_source_environment` | moved-lazy | `agent.secret_sources.base` | +| `list_command_secrets` | restored-def | `(deleted; BASE body restored)` | +| `parse_secret_output` | restored-helper | `(deleted; restored as a dependency of get_command_secret)` | +| `parse_secret_output` | restored-def | `(deleted; BASE body restored)` | + +### `agent.secret_sources.onepassword` + +| name | kind | new location | +|---|---|---| +| `DiskCache` | moved-lazy | `agent.secret_sources._cache` | +| `hashlib` | import | `hashlib` | + +### `agent.shell_hooks` + +| name | kind | new location | +|---|---|---| +| `shlex` | import | `shlex` | + +### `agent.skill_bundles` + +| name | kind | new location | +|---|---|---| +| `re` | import | `re` | + +### `agent.skill_utils` + +| name | kind | new location | +|---|---|---| +| `get_scan_ordered_skills_dirs` | restored-def | `(deleted; BASE body restored)` | + +### `agent.ssl_guard` + +| name | kind | new location | +|---|---|---| +| `verify_ca_bundle_with_fallback` | restored-def | `(deleted; BASE body restored)` | + +### `agent.system_prompt` + +| name | kind | new location | +|---|---|---| +| `OPENAI_MODEL_EXECUTION_GUIDANCE` | moved-lazy | `agent.prompt_builder` | + +### `agent.terminal_env_registry` + +| name | kind | new location | +|---|---|---| +| `Dict` | import | `typing` | +| `hermes_home_key` | moved-lazy | `hermes_constants` | +| `threading` | import | `threading` | + +### `agent.transcript_repair` + +| name | kind | new location | +|---|---|---| +| `Optional` | import | `typing` | + +### `agent.transcription_provider` + +| name | kind | new location | +|---|---|---| +| `List` | import | `typing` | +| `logger` | moved-lazy | `agent.i18n` | +| `logging` | import | `logging` | + +### `agent.transcription_registry` + +| name | kind | new location | +|---|---|---| +| `Dict` | import | `typing` | +| `List` | import | `typing` | +| `Optional` | import | `typing` | +| `hermes_home_key` | moved-lazy | `hermes_constants` | +| `threading` | import | `threading` | + +### `agent.transports.chat_completions` + +| name | kind | new location | +|---|---|---| +| `Dict` | import | `typing` | + +### `agent.transports.codex` + +| name | kind | new location | +|---|---|---| +| `Dict` | import | `typing` | +| `List` | import | `typing` | +| `Tuple` | import | `typing` | + +### `agent.transports.codex_app_server` + +| name | kind | new location | +|---|---|---| +| `field` | import | `dataclasses` | +| `time` | import | `time` | + +### `agent.tts_registry` + +| name | kind | new location | +|---|---|---| +| `Dict` | import | `typing` | +| `List` | import | `typing` | +| `Optional` | import | `typing` | +| `hermes_home_key` | moved-lazy | `hermes_constants` | +| `threading` | import | `threading` | + +### `agent.turn_context` + +| name | kind | new location | +|---|---|---| +| `IDLE_COMPACTION_STATUS_TEMPLATE` | moved-lazy | `agent.conversation_compression` | +| `PREFLIGHT_COMPRESSION_STATUS_TEMPLATE` | moved-lazy | `agent.conversation_compression` | +| `automatic_compaction_status_message` | moved-lazy | `agent.context_engine` | +| `compression_skipped_due_to_lock` | moved-lazy | `agent.conversation_compression` | +| `conversation_history_after_compression` | moved-lazy | `agent.conversation_compression` | + +### `agent.turn_retry_state` + +| name | kind | new location | +|---|---|---| +| `fields` | import | `dataclasses` | + +### `agent.usage_pricing` + +| name | kind | new location | +|---|---|---| +| `DEFAULT_PRICING` | restored-def | `(deleted; BASE body restored)` | + +### `agent.video_gen_provider` + +| name | kind | new location | +|---|---|---| +| `base64` | import | `base64` | +| `datetime` | import | `datetime` | +| `uuid` | import | `uuid` | + +### `agent.video_gen_registry` + +| name | kind | new location | +|---|---|---| +| `Dict` | import | `typing` | +| `List` | import | `typing` | +| `hermes_home_key` | moved-lazy | `hermes_constants` | +| `threading` | import | `threading` | + +### `agent.web_search_provider` + +| name | kind | new location | +|---|---|---| +| `Optional` | import | `typing` | + +### `agent.web_search_registry` + +| name | kind | new location | +|---|---|---| +| `Dict` | import | `typing` | +| `List` | import | `typing` | +| `hermes_home_key` | moved-lazy | `hermes_constants` | +| `threading` | import | `threading` | + +### `cli` + +| name | kind | new location | +|---|---|---| +| `AIAgent` | moved-lazy | `run_agent` | +| `CanonicalUsage` | moved-lazy | `agent.usage_pricing` | +| `CompletionsMenu` | import | `prompt_toolkit.layout.menus` | +| `Condition` | import | `prompt_toolkit.filters` | +| `ConditionalContainer` | import | `prompt_toolkit.layout` | +| `ConditionalProcessor` | import | `prompt_toolkit.layout.processors` | +| `DEFAULT_BROWSER_CDP_URL` | moved-lazy | `hermes_cli.browser_connect` | +| `Dimension` | import | `prompt_toolkit.layout.dimension` | +| `FileHistory` | import | `prompt_toolkit.history` | +| `FormattedTextControl` | import | `prompt_toolkit.layout` | +| `HERMES_AGENT_LOGO` | moved-lazy | `hermes_cli.banner` | +| `HERMES_CADUCEUS` | moved-lazy | `hermes_cli.banner` | +| `HSplit` | import | `prompt_toolkit.layout` | +| `KeyBindings` | import | `prompt_toolkit.key_binding` | +| `Layout` | import | `prompt_toolkit.layout` | +| `PTStyle` | import | `prompt_toolkit.styles` | +| `Panel` | import | `rich.panel` | +| `PasswordProcessor` | import | `prompt_toolkit.layout.processors` | +| `Processor` | import | `prompt_toolkit.layout.processors` | +| `SlashCommandAutoSuggest` | moved-lazy | `hermes_cli.commands_completion` | +| `SlashCommandCompleter` | moved-lazy | `hermes_cli.commands_completion` | +| `TextArea` | import | `prompt_toolkit.widgets` | +| `Transformation` | import | `prompt_toolkit.layout.processors` | +| `Window` | import | `prompt_toolkit.layout` | +| `WindowAlign` | import | `prompt_toolkit.layout` | +| `base64` | import | `base64` | +| `build_welcome_banner` | moved-lazy | `hermes_cli.banner` | +| `concurrent` | import | `concurrent.futures` | +| `copy` | import | `copy` | +| `display_hermes_home` | moved-lazy | `hermes_constants` | +| `estimate_usage_cost` | moved-lazy | `agent.usage_pricing` | +| `get_all_toolsets` | moved-lazy | `toolsets` | +| `get_job` | moved-lazy | `cron.jobs` | +| `get_toolset_for_tool` | moved-lazy | `model_tools` | +| `get_toolset_info` | moved-lazy | `toolsets` | +| `init_skin_from_config` | moved-lazy | `hermes_cli.skin_engine` | +| `is_browser_debug_ready` | moved-lazy | `hermes_cli.browser_connect` | +| `is_table_divider` | moved-lazy | `agent.markdown_tables` | +| `looks_like_table_row` | moved-lazy | `agent.markdown_tables` | +| `manual_chrome_debug_command` | moved-lazy | `hermes_cli.browser_connect` | +| `print_config_warnings` | moved-lazy | `hermes_cli.config` | +| `prompt_for_secret` | moved-lazy | `hermes_cli.callbacks` | +| `rich_box` | import | `rich` | +| `set_friendly_tool_labels` | moved-lazy | `agent.display` | +| `set_tool_preview_max_len` | moved-lazy | `agent.display` | +| `setup_logging` | moved-lazy | `hermes_logging` | +| `tempfile` | import | `tempfile` | +| `try_launch_chrome_debug` | unrestorable | `no top-level definition on BASE` | + +### `cron.jobs` + +| name | kind | new location | +|---|---|---| +| `clear_drift_alerted` | restored-def | `(deleted; BASE body restored)` | + +### `cron.scheduler` + +| name | kind | new location | +|---|---|---| +| `BOT_CHAT_PLATFORM` | moved-lazy | `cron.scheduler_delivery` | +| `SharedRouteAdapters` | moved-lazy | `cron.scheduler_preflight` | +| `asyncio` | import | `asyncio` | +| `cron_delivery_targets` | moved-lazy | `cron.scheduler_delivery` | +| `parse_bot_chat_deliver_token` | moved-lazy | `cron.scheduler_delivery` | +| `shutil` | import | `shutil` | +| `signal` | import | `signal` | + +### `cron.scheduler_provider` + +| name | kind | new location | +|---|---|---| +| `provider_supports_fire_cancel` | restored-def | `(deleted; BASE body restored)` | + +### `gateway.browser_control_artifacts` + +| name | kind | new location | +|---|---|---| +| `ArtifactOverwrite` | restored-def | `(deleted; BASE body restored)` | + +### `gateway.browser_control_broker` + +| name | kind | new location | +|---|---|---| +| `BROWSER_CONTROL_ALL_CAPABILITIES` | restored-def | `(deleted; BASE body restored)` | + +### `gateway.config` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | + +### `gateway.delivery_ledger` + +| name | kind | new location | +|---|---|---| +| `debug_rows` | restored-def | `(deleted; BASE body restored)` | +| `json` | restored-import | `json` | +| `json` | import | `json` | + +### `gateway.disk_status` + +| name | kind | new location | +|---|---|---| +| `logger` | moved-lazy | `gateway.run` | +| `logging` | import | `logging` | + +### `gateway.hosted_room_discussion` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | + +### `gateway.hosted_room_driver` + +| name | kind | new location | +|---|---|---| +| `Iterator` | import | `typing` | +| `Path` | import | `pathlib` | +| `contextmanager` | import | `contextlib` | +| `re` | import | `re` | + +### `gateway.hosted_room_execution_policy` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | +| `re` | import | `re` | + +### `gateway.hosted_room_peer` + +| name | kind | new location | +|---|---|---| +| `RoomLinkProbe` | restored-def | `(deleted; BASE body restored)` | +| `RoomLinkProbe` | restored-helper | `(deleted; restored as a dependency of select_room_link)` | +| `_LINK_PRIORITY` | restored-helper | `(deleted; restored as a dependency of select_room_link)` | +| `select_room_link` | restored-def | `(deleted; BASE body restored)` | +| `time` | import | `time` | + +### `gateway.hosted_room_replicas` + +| name | kind | new location | +|---|---|---| +| `MAX_EVENT_JSON_BYTES` | moved-lazy | `gateway.hosted_rooms` | +| `MAX_ROOM_ID_CHARS` | moved-lazy | `gateway.hosted_rooms` | +| `Path` | import | `pathlib` | +| `time` | import | `time` | + +### `gateway.hosted_rooms` + +| name | kind | new location | +|---|---|---| +| `Iterator` | import | `typing` | +| `NoReturn` | import | `typing` | +| `contextmanager` | import | `contextlib` | +| `time` | import | `time` | + +### `gateway.kanban_watchers` + +| name | kind | new location | +|---|---|---| +| `Callable` | import | `typing` | +| `Context` | import | `contextvars` | +| `logging` | import | `logging` | +| `re` | import | `re` | +| `sqlite3` | import | `sqlite3` | +| `t` | moved-lazy | `agent.i18n` | + +### `gateway.memory_status` + +| name | kind | new location | +|---|---|---| +| `logger` | moved-lazy | `gateway.run` | +| `logging` | import | `logging` | + +### `gateway.platforms.helpers` + +| name | kind | new location | +|---|---|---| +| `Dict` | import | `typing` | +| `TYPE_CHECKING` | import | `typing` | +| `TextBatchAggregator` | restored-def | `(deleted; BASE body restored)` | +| `asyncio` | restored-import | `asyncio` | +| `asyncio` | import | `asyncio` | + +### `gateway.platforms.qqbot` + +| name | kind | new location | +|---|---|---| +| `ApprovalSender` | unrestorable | `no top-level definition on BASE` | + +### `gateway.platforms.qqbot.adapter` + +| name | kind | new location | +|---|---|---| +| `base64` | import | `base64` | +| `mimetypes` | import | `mimetypes` | + +### `gateway.platforms.qqbot.chunked_upload` + +| name | kind | new location | +|---|---|---| +| `ApiRequestFn` | restored-def | `(deleted; BASE body restored)` | +| `Optional` | import | `typing` | +| `functools` | import | `functools` | + +### `gateway.platforms.qqbot.keyboards` + +| name | kind | new location | +|---|---|---| +| `ApprovalSender` | restored-def | `(deleted; BASE body restored)` | +| `Awaitable` | restored-import | `typing` | +| `Awaitable` | import | `typing` | +| `Callable` | restored-import | `typing` | +| `Callable` | import | `typing` | +| `PostMessageFn` | restored-helper | `(deleted; restored as a dependency of ApprovalSender)` | +| `PostMessageFn` | restored-def | `(deleted; BASE body restored)` | +| `logger` | restored-helper | `(deleted; restored as a dependency of ApprovalSender)` | +| `logger` | moved-lazy | `gateway.platforms.qqbot.adapter` | +| `logging` | restored-import | `logging` | +| `logging` | import | `logging` | + +### `gateway.platforms.signal` + +| name | kind | new location | +|---|---|---| +| `DEFAULT_EXT_TO_MIME` | moved-lazy | `gateway.platforms.media_cache` | +| `TYPING_INTERVAL` | restored-def | `(deleted; BASE body restored)` | + +### `gateway.platforms.weixin` + +| name | kind | new location | +|---|---|---| +| `MSG_TYPE_USER` | restored-def | `(deleted; BASE body restored)` | +| `struct` | import | `struct` | + +### `gateway.platforms.yuanbao` + +| name | kind | new location | +|---|---|---| +| `AUTH_FAILED_CODES` | restored-def | `(deleted; BASE body restored)` | +| `AUTH_RETRYABLE_CODES` | restored-def | `(deleted; BASE body restored)` | +| `FileUrlHandler` | restored-def | `(deleted; BASE body restored)` | +| `GroupQueryService` | restored-def | `(deleted; BASE body restored)` | +| `REPLY_REF_TTL_S` | restored-def | `(deleted; BASE body restored)` | +| `get_active_adapter` | restored-def | `(deleted; BASE body restored)` | +| `send_yuanbao_direct` | restored-def | `(deleted; BASE body restored)` | + +### `gateway.platforms.yuanbao_media` + +| name | kind | new location | +|---|---|---| +| `COS_USE_ACCELERATE` | restored-def | `(deleted; BASE body restored)` | + +### `gateway.platforms.yuanbao_proto` + +| name | kind | new location | +|---|---|---| +| `DEBUG_MODE` | restored-def | `(deleted; BASE body restored)` | +| `_encode_forward_msg` | restored-helper | `(deleted; restored as a dependency of encode_forward_msg_data)` | +| `_encode_forward_msg_content` | restored-helper | `(deleted; restored as a dependency of encode_forward_msg_data)` | +| `_encode_forward_multimedia` | restored-helper | `(deleted; restored as a dependency of encode_forward_msg_data)` | +| `encode_forward_msg_data` | restored-def | `(deleted; BASE body restored)` | +| `logger` | moved-lazy | `gateway.platforms.base` | +| `logging` | import | `logging` | + +### `gateway.relay` + +| name | kind | new location | +|---|---|---| +| `relay_bot_username` | restored-def | `(deleted; BASE body restored)` | + +### `gateway.relay.adapter` + +| name | kind | new location | +|---|---|---| +| `cast` | import | `typing` | + +### `gateway.relay.auth` + +| name | kind | new location | +|---|---|---| +| `DELIVERY_SIG_HEADER` | restored-def | `(deleted; BASE body restored)` | +| `DELIVERY_TS_HEADER` | restored-def | `(deleted; BASE body restored)` | +| `Optional` | import | `typing` | +| `Sequence` | import | `typing` | +| `_DEFAULT_MAX_SKEW_SECONDS` | restored-helper | `(deleted; restored as a dependency of verify_delivery_signature)` | +| `_delivery_payload` | restored-helper | `(deleted; restored as a dependency of verify_delivery_signature)` | +| `_hmac_hex` | restored-helper | `(deleted; restored as a dependency of verify_delivery_signature)` | +| `verify_delivery_signature` | restored-def | `(deleted; BASE body restored)` | +| `verify_signature` | restored-helper | `(deleted; restored as a dependency of verify_delivery_signature)` | +| `verify_signature` | restored-def | `(deleted; BASE body restored)` | +| `verify_token` | restored-def | `(deleted; BASE body restored)` | + +### `gateway.response_filters` + +| name | kind | new location | +|---|---|---| +| `SILENT_REPLY_TOKEN` | restored-def | `(deleted; BASE body restored)` | + +### `gateway.restart_loop_guard` + +| name | kind | new location | +|---|---|---| +| `is_restart_loop_tripped` | restored-def | `(deleted; BASE body restored)` | + +### `gateway.run` + +| name | kind | new location | +|---|---|---| +| `Awaitable` | import | `typing` | +| `Context` | import | `contextvars` | +| `DEFAULT_GATEWAY_POST_INTERRUPT_GRACE_TIMEOUT` | moved-lazy | `gateway.restart` | +| `DEFAULT_HEARTBEAT_INTERVAL_S` | moved-lazy | `gateway.shutdown_watchdog` | +| `DEFAULT_LEASE_WAIT` | moved-lazy | `gateway.turn_lease` | +| `DEFAULT_LOOP_WATCHDOG_INTERVAL_S` | moved-lazy | `gateway.shutdown_watchdog` | +| `DEFAULT_LOOP_WATCHDOG_MAX_STRIKES` | moved-lazy | `gateway.shutdown_watchdog` | +| `DEFAULT_LOOP_WATCHDOG_TIMEOUT_S` | moved-lazy | `gateway.shutdown_watchdog` | +| `EphemeralReply` | moved-lazy | `gateway.platforms.base` | +| `GATEWAY_FATAL_CONFIG_EXIT_CODE` | moved-lazy | `gateway.restart` | +| `GATEWAY_SERVICE_RESTART_EXIT_CODE` | moved-lazy | `gateway.restart` | +| `SessionEntry` | moved-lazy | `gateway.session` | +| `TranscriptReadError` | moved-lazy | `gateway.session_transcript` | +| `TurnContext` | moved-lazy | `gateway.turn_context` | +| `TurnLeaseTimeoutError` | moved-lazy | `gateway.turn_lease` | +| `TurnRunner` | moved-lazy | `gateway.run_turn_runner` | +| `Union` | import | `typing` | +| `arm_shutdown_watchdog` | moved-lazy | `gateway.shutdown_watchdog` | +| `atomic_json_write` | moved-lazy | `utils` | +| `base_url_hostname` | moved-lazy | `utils` | +| `build_auto_tts_output_path` | moved-lazy | `gateway.platforms.base` | +| `build_channel_continuity_note` | moved-lazy | `gateway.session` | +| `build_session_context` | moved-lazy | `gateway.session` | +| `build_session_context_prompt` | moved-lazy | `gateway.session` | +| `consume_detached_task_result` | moved-lazy | `agent.async_utils` | +| `faulthandler` | import | `faulthandler` | +| `functools` | import | `functools` | +| `inspect` | import | `inspect` | +| `is_global_startup_conflict` | moved-lazy | `gateway.restart` | +| `is_shared_multi_user_session` | moved-lazy | `gateway.session` | +| `is_truthy_value` | moved-lazy | `utils` | +| `load_dotenv` | import | `dotenv` | +| `looks_like_telegram_private_chat_id` | moved-lazy | `gateway.delivery` | +| `loop_heartbeat_forever` | moved-lazy | `gateway.shutdown_watchdog` | +| `merge_pending_message_event` | moved-lazy | `gateway.platforms.base` | +| `neutralize_untrusted_inline_text` | moved-lazy | `gateway.session` | +| `parse_cron_drain_timeout` | moved-lazy | `gateway.restart` | +| `parse_restart_after_turn_timeout` | moved-lazy | `gateway.restart` | +| `parse_restart_drain_timeout` | moved-lazy | `gateway.restart` | +| `parse_signal_interrupt_grace_timeout` | moved-lazy | `gateway.restart` | +| `project_compaction_message_for_display` | moved-lazy | `agent.compaction_display` | +| `queue` | import | `queue` | +| `repair_explicit_computer_use_media_paths` | moved-lazy | `gateway.media_repair` | +| `resolve_cron_drain_budget` | moved-lazy | `gateway.restart` | +| `resolve_delivery_transport` | moved-lazy | `gateway.delivery` | +| `resolve_shutdown_watchdog_delay` | moved-lazy | `gateway.shutdown_watchdog` | +| `start_loop_liveness_watchdog` | moved-lazy | `gateway.shutdown_watchdog` | +| `t` | moved-lazy | `agent.i18n` | +| `timedelta` | import | `datetime` | +| `timezone` | import | `datetime` | +| `utf16_len` | moved-lazy | `gateway.platforms.base` | + +### `gateway.session` + +| name | kind | new location | +|---|---|---| +| `SessionResetPolicy` | moved-lazy | `gateway.config` | +| `TranscriptReadError` | moved-lazy | `gateway.session_transcript` | +| `atomic_replace` | moved-lazy | `utils` | +| `auto_continue_freshness_window` | moved-lazy | `gateway.session_lifecycle` | +| `extract_api_content_sidecar` | moved-lazy | `agent.turn_context` | +| `normalize_whatsapp_identifier` | moved-lazy | `gateway.whatsapp_identity` | +| `replace` | import | `dataclasses` | +| `uuid` | import | `uuid` | + +### `gateway.slash_access` + +| name | kind | new location | +|---|---|---| +| `Tuple` | import | `typing` | + +### `gateway.slash_commands` + +| name | kind | new location | +|---|---|---| +| `Any` | import | `typing` | +| `HISTORY_UNREADABLE` | moved-lazy | `gateway.slash_commands_status` | +| `MessageType` | moved-lazy | `gateway.platforms.base` | +| `SessionSource` | moved-lazy | `gateway.session` | +| `base_url_host_matches` | moved-lazy | `utils` | +| `build_session_key` | moved-lazy | `gateway.session` | +| `clear_model_endpoint_credentials` | moved-lazy | `hermes_cli.config` | +| `extract_api_content_sidecar` | moved-lazy | `agent.turn_context` | +| `fetch_account_usage` | moved-lazy | `agent.account_usage` | +| `hashlib` | import | `hashlib` | +| `is_shared_multi_user_session` | moved-lazy | `gateway.session` | +| `render_account_usage_lines` | moved-lazy | `agent.account_usage` | + +### `gateway.startup_watchdog` + +| name | kind | new location | +|---|---|---| +| `*` | module-stub | `gateway.shutdown_watchdog` | + +### `gateway.status` + +| name | kind | new location | +|---|---|---| +| `clear_planned_stop_marker` | restored-def | `(deleted; BASE body restored)` | +| `is_gateway_running` | restored-def | `(deleted; BASE body restored)` | + +### `gateway.sticker_cache` + +| name | kind | new location | +|---|---|---| +| `os` | import | `os` | +| `tempfile` | import | `tempfile` | + +### `gateway.stream_consumer` + +| name | kind | new location | +|---|---|---| +| `MEDIA_TAG_CLEANUP_RE` | moved-lazy | `gateway.platforms.base` | +| `escape_code_fences_for_display` | moved-lazy | `gateway.stream_consumer_fences` | + +### `gateway.stream_dispatch` + +| name | kind | new location | +|---|---|---| +| `ToolCallFinished` | moved-lazy | `gateway.stream_events` | + +### `gateway.systemd_notify` + +| name | kind | new location | +|---|---|---| +| `Optional` | import | `typing` | + +### `hermes_cli.agent_import` + +| name | kind | new location | +|---|---|---| +| `backup_memory_file` | restored-def | `(deleted; BASE body restored)` | +| `default_source_dir` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.auth` + +| name | kind | new location | +|---|---|---| +| `BaseHTTPRequestHandler` | import | `http.server` | +| `CODEX_OAUTH_USER_AGENT` | moved-lazy | `hermes_cli.auth_constants` | +| `CODEX_QUOTA_PROBE_MIN_INTERVAL_SECONDS` | moved-lazy | `hermes_cli.auth_codex` | +| `DEFAULT_SPOTIFY_REDIRECT_URI` | moved-lazy | `hermes_cli.auth_constants` | +| `DEVICE_AUTH_POLL_INTERVAL_CAP_SECONDS` | moved-lazy | `hermes_cli.auth_constants` | +| `HTTPServer` | import | `http.server` | +| `MINIMAX_OAUTH_GRANT_TYPE` | moved-lazy | `hermes_cli.auth_constants` | +| `NOUS_INFERENCE_INVOKE_SCOPE` | moved-lazy | `hermes_cli.auth_constants` | +| `NOUS_SHARED_STORE_FILENAME` | moved-lazy | `hermes_cli.auth_nous` | +| `OAUTH_OVER_SSH_DOCS_URL` | moved-lazy | `hermes_cli.auth_constants` | +| `QWEN_OAUTH_CLIENT_ID` | moved-lazy | `hermes_cli.auth_constants` | +| `QWEN_OAUTH_TOKEN_URL` | moved-lazy | `hermes_cli.auth_constants` | +| `SINGLE_USE_OAUTH_SINGLETON_FILES` | moved-lazy | `hermes_cli.auth_oauth_grants` | +| `SPOTIFY_ACCESS_TOKEN_REFRESH_SKEW_SECONDS` | moved-lazy | `hermes_cli.auth_constants` | +| `SPOTIFY_DASHBOARD_URL` | moved-lazy | `hermes_cli.auth_constants` | +| `TYPE_CHECKING` | import | `typing` | +| `XAI_OAUTH_DEVICE_CODE_URL` | moved-lazy | `hermes_cli.auth_constants` | +| `XAI_OAUTH_DISCOVERY_URL` | moved-lazy | `hermes_cli.auth_constants` | +| `XAI_OAUTH_ISSUER` | moved-lazy | `hermes_cli.auth_constants` | +| `base64` | import | `base64` | +| `hashlib` | import | `hashlib` | +| `parse_qs` | import | `urllib.parse` | +| `refresh_nous_oauth_pure` | moved-lazy | `hermes_cli.auth_nous` | +| `ssl` | import | `ssl` | +| `subprocess` | import | `subprocess` | +| `sys` | import | `sys` | +| `urlencode` | import | `urllib.parse` | + +### `hermes_cli.backup` + +| name | kind | new location | +|---|---|---| +| `copy_db_and_verify` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.browser_connect` + +| name | kind | new location | +|---|---|---| +| `try_launch_chrome_debug` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.callbacks` + +| name | kind | new location | +|---|---|---| +| `approval_callback` | restored-def | `(deleted; BASE body restored)` | +| `clarify_callback` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.checkpoints` + +| name | kind | new location | +|---|---|---| +| `cmd_list` | moved-lazy | `hermes_cli.plugins_cmd` | +| `datetime` | import | `datetime` | + +### `hermes_cli.commands` + +| name | kind | new location | +|---|---|---| +| `Any` | import | `typing` | +| `AutoSuggest` | unrestorable | `no top-level definition on BASE` | +| `Callable` | import | `collections.abc` | +| `Completer` | unrestorable | `no top-level definition on BASE` | +| `Completion` | unrestorable | `no top-level definition on BASE` | +| `Dict` | import | `typing` | +| `Mapping` | import | `collections.abc` | +| `Optional` | import | `typing` | +| `Sequence` | import | `collections.abc` | +| `SlashCommandAutoSuggest` | moved-lazy | `hermes_cli.commands_completion` | +| `SlashCommandCompleter` | moved-lazy | `hermes_cli.commands_completion` | +| `Suggestion` | unrestorable | `no top-level definition on BASE` | +| `Tuple` | import | `typing` | +| `_CMD_NAME_LIMIT` | restored-helper | `(deleted; restored as a dependency of discord_skill_commands)` | +| `_clamp_command_names` | restored-helper | `(deleted; restored as a dependency of discord_skill_commands)` | +| `_collect_gateway_skill_entries` | restored-helper | `(deleted; restored as a dependency of discord_skill_commands)` | +| `_requires_argument` | restored-helper | `(deleted; restored as a dependency of discord_skill_commands)` | +| `discord_skill_commands` | restored-def | `(deleted; BASE body restored)` | +| `discord_skill_commands_by_category` | moved-lazy | `hermes_cli.commands_platforms` | +| `field` | import | `dataclasses` | +| `key` | unrestorable | `no top-level definition on BASE` | +| `m` | unrestorable | `no top-level definition on BASE` | +| `os` | import | `os` | +| `shutil` | import | `shutil` | +| `slack_app_manifest` | moved-lazy | `hermes_cli.commands_platforms` | +| `slack_native_slashes` | moved-lazy | `hermes_cli.commands_platforms` | +| `slack_subcommand_map` | moved-lazy | `hermes_cli.commands_platforms` | +| `subprocess` | import | `subprocess` | +| `telegram_bot_commands` | moved-lazy | `hermes_cli.commands_platforms` | +| `telegram_menu_commands` | moved-lazy | `hermes_cli.commands_platforms` | +| `telegram_menu_max_commands` | moved-lazy | `hermes_cli.commands_platforms` | +| `time` | import | `time` | + +### `hermes_cli.config` + +| name | kind | new location | +|---|---|---| +| `_install_method_project_root` | restored-helper | `(deleted; restored as a dependency of stamp_install_method)` | +| `normalize_route_base_url` | moved-lazy | `hermes_cli.route_identity` | +| `stamp_install_method` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.console_engine` + +| name | kind | new location | +|---|---|---| +| `shlex` | import | `shlex` | + +### `hermes_cli.curses_ui` + +| name | kind | new location | +|---|---|---| +| `Protocol` | import | `typing` | + +### `hermes_cli.dashboard_auth.audit` + +| name | kind | new location | +|---|---|---| +| `os` | import | `os` | + +### `hermes_cli.dashboard_auth.middleware` + +| name | kind | new location | +|---|---|---| +| `DashboardAuthProvider` | moved-lazy | `hermes_cli.dashboard_auth.base` | + +### `hermes_cli.dingtalk_auth` + +| name | kind | new location | +|---|---|---| +| `logger` | moved-lazy | `hermes_cli.auth` | +| `logging` | import | `logging` | + +### `hermes_cli.doctor` + +| name | kind | new location | +|---|---|---| +| `FTS_STORAGE_VERSION` | moved-lazy | `hermes_state_common` | +| `OPENROUTER_MODELS_URL` | moved-lazy | `hermes_constants` | +| `Path` | import | `pathlib` | +| `STATE_DB_SIZE_WARN_BYTES` | moved-lazy | `hermes_cli.doctor_state` | +| `agent_browser_runnable` | moved-lazy | `hermes_constants` | +| `base_url_host_matches` | moved-lazy | `utils` | +| `check_certificates` | moved-lazy | `hermes_cli.doctor_platform` | +| `check_fail` | restored-def | `(deleted; BASE body restored)` | +| `check_macos_full_disk_access` | moved-lazy | `hermes_cli.doctor_platform` | +| `check_macos_tcc_anchor` | moved-lazy | `hermes_cli.doctor_platform` | +| `check_macos_tcc_grants` | moved-lazy | `hermes_cli.doctor_platform` | +| `check_ok` | restored-def | `(deleted; BASE body restored)` | +| `check_warn` | restored-def | `(deleted; BASE body restored)` | +| `collect_deprecated_config_keys` | moved-lazy | `hermes_cli.doctor_config` | +| `collect_deprecated_env_vars` | moved-lazy | `hermes_cli.doctor_config` | +| `collect_relay_plugin_cutover_findings` | moved-lazy | `hermes_cli.doctor_config` | +| `describe_vercel_auth` | moved-lazy | `hermes_cli.vercel_auth` | +| `detect_install_method` | moved-lazy | `hermes_cli.config` | +| `importlib` | import | `importlib.util` | +| `is_nix_install_method` | moved-lazy | `hermes_cli.config` | +| `managed_scope_check` | moved-lazy | `hermes_cli.doctor_config` | +| `recommended_update_command_for_method` | moved-lazy | `hermes_cli.config` | +| `report_deprecated_config_and_env` | moved-lazy | `hermes_cli.doctor_config` | +| `shutil` | import | `shutil` | +| `subprocess` | import | `subprocess` | + +### `hermes_cli.doctor_live` + +| name | kind | new location | +|---|---|---| +| `ELEVENLABS_VOICES_URL` | restored-def | `(deleted; BASE body restored)` | +| `FAL_MODELS_URL` | restored-def | `(deleted; BASE body restored)` | +| `FIRECRAWL_HEALTH_URL` | restored-def | `(deleted; BASE body restored)` | +| `GROQ_MODELS_URL` | restored-def | `(deleted; BASE body restored)` | +| `OPENAI_MODELS_URL` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.focus_view` + +| name | kind | new location | +|---|---|---| +| `FOCUS_USAGE` | restored-def | `(deleted; BASE body restored)` | +| `effective_tool_progress_mode` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.foreign_sessions` + +| name | kind | new location | +|---|---|---| +| `list_claude_sessions` | restored-def | `(deleted; BASE body restored)` | +| `list_codex_sessions` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.gateway` + +| name | kind | new location | +|---|---|---| +| `DEFAULT_GATEWAY_RESTART_AFTER_TURN_TIMEOUT` | moved-lazy | `gateway.restart` | +| `print_systemd_linger_guidance` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.gitlock` + +| name | kind | new location | +|---|---|---| +| `is_ancestor_of_head` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.heartbeat` + +| name | kind | new location | +|---|---|---| +| `Dict` | import | `typing` | + +### `hermes_cli.journey` + +| name | kind | new location | +|---|---|---| +| `cmd_journey` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.kanban` + +| name | kind | new location | +|---|---|---| +| `Any` | import | `typing` | + +### `hermes_cli.kanban_db` + +| name | kind | new location | +|---|---|---| +| `DEFAULT_BUSY_TIMEOUT_MS` | moved-lazy | `hermes_cli.kanban_db_connect` | +| `DEFAULT_LOG_BACKUP_COUNT` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `DEFAULT_LOG_ROTATE_BYTES` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `DEFAULT_SPAWN_FAILURE_LIMIT` | restored-def | `(deleted; BASE body restored)` | +| `DERIVED_MAX_IN_PROGRESS_CEILING` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `DERIVED_MAX_IN_PROGRESS_FLOOR` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `KANBAN_TERMINAL_TIMEOUT_GRACE_SECONDS` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `KanbanDbCorruptError` | moved-lazy | `hermes_cli.kanban_db_connect` | +| `MEMORY_GUARD_MB_PER_WORKER` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `Mapping` | import | `typing` | +| `RepairResult` | moved-lazy | `hermes_cli.kanban_db_connect` | +| `add_notify_sub` | moved-lazy | `hermes_cli.kanban_db_notify` | +| `advance_notify_cursor` | moved-lazy | `hermes_cli.kanban_db_notify` | +| `check_respawn_guard` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `claim_unseen_events_for_sub` | moved-lazy | `hermes_cli.kanban_db_notify` | +| `configured_max_in_progress` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `connect` | moved-lazy | `hermes_cli.projects_db` | +| `connect_closing` | moved-lazy | `hermes_cli.projects_db` | +| `count_notify_subs` | moved-lazy | `hermes_cli.kanban_db_notify` | +| `count_running_tasks` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `count_running_tasks_other_boards` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `derive_default_max_in_progress` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `detect_crashed_workers` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `detect_stale_running` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `dispatch_once` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `enforce_max_runtime` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `field` | import | `dataclasses` | +| `has_spawnable_ready` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `has_spawnable_review` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `hashlib` | import | `hashlib` | +| `heartbeat_worker` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `list_notify_subs` | moved-lazy | `hermes_cli.kanban_db_notify` | +| `parent_results` | restored-def | `(deleted; BASE body restored)` | +| `purge_stale_done_notify_subs` | moved-lazy | `hermes_cli.kanban_db_notify` | +| `random` | import | `random` | +| `reap_worker_zombies` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `reconcile_orphaned_running` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `remove_notify_sub` | moved-lazy | `hermes_cli.kanban_db_notify` | +| `repair_db` | moved-lazy | `hermes_cli.kanban_db_connect` | +| `resolve_max_in_progress` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `resolve_workspace` | moved-lazy | `hermes_cli.kanban_db_workspace` | +| `review_dispatch_enabled` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `rewind_notify_cursor` | moved-lazy | `hermes_cli.kanban_db_notify` | +| `run_daemon` | moved-lazy | `hermes_cli.kanban_db_dispatch` | +| `set_branch_name` | moved-lazy | `hermes_cli.kanban_db_workspace` | +| `set_workspace_path` | moved-lazy | `hermes_cli.kanban_db_workspace` | +| `shutil` | import | `shutil` | +| `threading` | import | `threading` | +| `unseen_events_for_sub` | moved-lazy | `hermes_cli.kanban_db_notify` | +| `worker_log_rotation_config` | moved-lazy | `hermes_cli.kanban_db_dispatch` | + +### `hermes_cli.kanban_decompose` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | +| `os` | import | `os` | + +### `hermes_cli.kanban_diagnostics` + +| name | kind | new location | +|---|---|---| +| `DIAGNOSTIC_KINDS` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.local_runtime.binaries` + +| name | kind | new location | +|---|---|---| +| `get_hermes_home` | moved-lazy | `hermes_constants` | + +### `hermes_cli.local_runtime.bootstrap` + +| name | kind | new location | +|---|---|---| +| `get_hermes_home` | moved-lazy | `hermes_constants` | + +### `hermes_cli.local_runtime.capabilities` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | +| `urllib` | import | `urllib.request` | + +### `hermes_cli.local_runtime.catalog` + +| name | kind | new location | +|---|---|---| +| `find_variant` | restored-def | `(deleted; BASE body restored)` | +| `re` | import | `re` | +| `recommended_id` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.local_runtime.hf_browse` + +| name | kind | new location | +|---|---|---| +| `field` | import | `dataclasses` | + +### `hermes_cli.main` + +| name | kind | new location | +|---|---|---| +| `hashlib` | import | `hashlib` | +| `line_input` | moved-lazy | `hermes_cli.cli_output` | +| `shlex` | import | `shlex` | +| `stat` | import | `stat` | +| `tempfile` | import | `tempfile` | + +### `hermes_cli.mcp_picker` + +| name | kind | new location | +|---|---|---| +| `color` | moved-lazy | `hermes_cli.colors` | + +### `hermes_cli.mcp_security` + +| name | kind | new location | +|---|---|---| +| `is_mcp_server_entry_suspicious` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.middleware` + +| name | kind | new location | +|---|---|---| +| `API_EXECUTION_MIDDLEWARE` | restored-def | `(deleted; BASE body restored)` | +| `API_REQUEST_MIDDLEWARE` | restored-def | `(deleted; BASE body restored)` | +| `apply_api_request_middleware` | restored-def | `(deleted; BASE body restored)` | +| `run_api_execution_middleware` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.moa_config` + +| name | kind | new location | +|---|---|---| +| `build_moa_turn_prompt` | restored-def | `(deleted; BASE body restored)` | +| `encode_moa_turn` | restored-helper | `(deleted; restored as a dependency of build_moa_turn_prompt)` | +| `encode_moa_turn` | restored-def | `(deleted; BASE body restored)` | +| `list_moa_presets` | restored-def | `(deleted; BASE body restored)` | +| `set_active_moa_preset` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.model_setup_flows` + +| name | kind | new location | +|---|---|---| +| `BEDROCK_GEO_PREFIXES` | moved-lazy | `hermes_cli.model_setup_flows_bedrock` | +| `bedrock_model_routable_from_region` | moved-lazy | `hermes_cli.model_setup_flows_bedrock` | +| `bedrock_region_geo_prefix` | moved-lazy | `hermes_cli.model_setup_flows_bedrock` | +| `custom_provider_slug` | moved-lazy | `hermes_cli.providers` | +| `line_input` | moved-lazy | `hermes_cli.cli_output` | +| `subprocess` | import | `subprocess` | +| `urllib` | import | `urllib.parse` | + +### `hermes_cli.model_switch` + +| name | kind | new location | +|---|---|---| +| `List` | import | `typing` | +| `base_url_host_matches` | moved-lazy | `utils` | +| `custom_provider_slug` | moved-lazy | `hermes_cli.providers` | +| `http` | import | `http.client` | +| `list_picker_providers` | moved-lazy | `hermes_cli.model_switch_providers` | +| `prewarm_picker_cache_async` | moved-lazy | `hermes_cli.model_switch_providers` | +| `time` | import | `time` | + +### `hermes_cli.models` + +| name | kind | new location | +|---|---|---| +| `LMStudioLoadResult` | moved-lazy | `hermes_cli.models_local` | +| `NamedTuple` | import | `typing` | +| `PROVIDER_GROUPS` | moved-lazy | `hermes_cli.models_catalog_static` | +| `ProviderEntry` | moved-lazy | `hermes_cli.models_catalog_static` | +| `_OPENCODE_KEYLESS_EXTRA_SLUGS` | restored-helper | `(deleted; restored as a dependency of is_opencode_zen_free_model)` | +| `atomic_json_write` | moved-lazy | `utils` | +| `base_url_host_matches` | moved-lazy | `utils` | +| `compute_sale_discount` | moved-lazy | `hermes_cli.models_pricing` | +| `ensure_lmstudio_model_loaded` | moved-lazy | `hermes_cli.models_local` | +| `fetch_ai_gateway_pricing` | moved-lazy | `hermes_cli.models_pricing` | +| `fetch_lmstudio_models` | moved-lazy | `hermes_cli.models_local` | +| `fetch_models_with_pricing` | moved-lazy | `hermes_cli.models_pricing` | +| `fetch_ollama_local_models` | moved-lazy | `hermes_cli.models_local` | +| `get_cached_nous_inference_base_url` | moved-lazy | `hermes_cli.models_pricing` | +| `get_close_matches` | import | `difflib` | +| `get_pricing_for_provider` | moved-lazy | `hermes_cli.models_pricing` | +| `group_providers` | moved-lazy | `hermes_cli.models_catalog_static` | +| `http` | import | `http.client` | +| `is_nous_free_tier` | restored-def | `(deleted; BASE body restored)` | +| `is_opencode_zen_free_model` | restored-def | `(deleted; BASE body restored)` | +| `lmstudio_model_reasoning_options` | moved-lazy | `hermes_cli.models_local` | +| `nous_catalog_url` | moved-lazy | `hermes_cli.models_reasoning_caps` | +| `nous_model_reasoning_capabilities` | moved-lazy | `hermes_cli.models_reasoning_caps` | +| `nous_policy_allowed_ids` | moved-lazy | `hermes_cli.models_pricing` | +| `ollama_model_supports_thinking` | moved-lazy | `hermes_cli.models_local` | +| `openrouter_model_reasoning_capabilities` | moved-lazy | `hermes_cli.models_reasoning_caps` | +| `parse_openrouter_reasoning_capabilities` | moved-lazy | `hermes_cli.models_reasoning_caps` | +| `peek_cached_pricing` | moved-lazy | `hermes_cli.models_pricing` | +| `pricing_cache_scope` | moved-lazy | `hermes_cli.models_pricing` | +| `probe_lmstudio_models` | moved-lazy | `hermes_cli.models_local` | +| `probe_ollama_local_models` | moved-lazy | `hermes_cli.models_local` | +| `provider_group_for_slug` | moved-lazy | `hermes_cli.models_catalog_static` | +| `refresh_reasoning_caps_async` | moved-lazy | `hermes_cli.models_reasoning_caps` | +| `restrict_to_nous_policy` | moved-lazy | `hermes_cli.models_pricing` | +| `should_use_ollama_native_catalog` | moved-lazy | `hermes_cli.models_local` | +| `url_origin` | moved-lazy | `hermes_cli.urllib_security` | +| `validate_requested_model` | moved-lazy | `hermes_cli.models_validate` | +| `warm_nous_reasoning_caps_async` | moved-lazy | `hermes_cli.models_reasoning_caps` | +| `warm_openrouter_reasoning_caps_async` | moved-lazy | `hermes_cli.models_reasoning_caps` | + +### `hermes_cli.nous_billing` + +| name | kind | new location | +|---|---|---| +| `BILLING_MANAGE_SCOPE` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.nous_subscription` + +| name | kind | new location | +|---|---|---| +| `managed_nous_tools_enabled` | moved-lazy | `tools.tool_backend_helpers` | + +### `hermes_cli.observability.relay_runtime` + +| name | kind | new location | +|---|---|---| +| `*` | module-stub | `agent.relay_runtime` | + +### `hermes_cli.observability.relay_shared_metrics` + +| name | kind | new location | +|---|---|---| +| `CLIENT_ACTIVE_MARK` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `MODEL_CALL_PROFILE_MODEL` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `SCHEMA_KEY` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `SCHEMA_VERSION` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `SKILL_LIFECYCLE_MARK` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `SKILL_LOAD_MARK` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `TOOL_APPROVAL_MARK` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `TOOL_CALL_SCOPE` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `model_call_fields` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `prepare_session_start` | restored-def | `(deleted; BASE body restored)` | +| `skill_lifecycle_fields` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `skill_load_fields` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `task_start_fields` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `task_terminal_fields` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `task_terminal_state` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `tool_approval_outcome` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `tool_category` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | +| `tool_terminal_fields` | moved-lazy | `hermes_cli.observability.shared_metrics_contract` | + +### `hermes_cli.onepassword_secrets_cli` + +| name | kind | new location | +|---|---|---| +| `Table` | import | `rich.table` | +| `masked_secret_prompt` | moved-lazy | `hermes_cli.secret_prompt` | +| `sys` | import | `sys` | + +### `hermes_cli.platform_actions` + +| name | kind | new location | +|---|---|---| +| `Optional` | import | `typing` | + +### `hermes_cli.plugins` + +| name | kind | new location | +|---|---|---| +| `CAPABILITY_REGISTRY` | moved-lazy | `hermes_cli.plugin_capabilities` | +| `ENTRY_POINT_CAPABILITIES_GROUP` | moved-lazy | `hermes_cli.plugins_discovery` | +| `Iterable` | import | `typing` | +| `LEGACY_RELAY_PLUGIN_KEYS` | moved-lazy | `hermes_cli.relay_plugin_cutover` | +| `MAX_SYSTEM_PROMPT_SECTIONS` | moved-lazy | `hermes_cli.plugins_dispatch` | +| `OBSERVER_SCHEMA_VERSION` | moved-lazy | `hermes_cli.middleware` | +| `Type` | import | `typing` | +| `VALID_CAPABILITY_IDS` | moved-lazy | `hermes_cli.plugin_capabilities` | +| `cfg_get` | moved-lazy | `hermes_cli.config` | +| `contextmanager` | import | `contextlib` | +| `contextvars` | import | `contextvars` | +| `copy` | import | `copy` | +| `fast_safe_load` | moved-lazy | `utils` | +| `format_system_prompt_section` | moved-lazy | `hermes_cli.plugins_dispatch` | +| `get_plugin_subscriptions` | restored-def | `(deleted; BASE body restored)` | +| `hashlib` | import | `hashlib` | +| `reset_hermes_home_override` | moved-lazy | `hermes_constants` | +| `set_hermes_home_override` | moved-lazy | `hermes_constants` | +| `time` | import | `time` | +| `unload_plugins` | restored-def | `(deleted; BASE body restored)` | +| `wraps` | import | `functools` | +| `yaml` | unrestorable | `no top-level definition on BASE` | + +### `hermes_cli.plugins_cmd` + +| name | kind | new location | +|---|---|---| +| `importlib` | import | `importlib.metadata` | + +### `hermes_cli.profile_describer` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | + +### `hermes_cli.profile_distribution` + +| name | kind | new location | +|---|---|---| +| `is_excluded_skill_path` | moved-lazy | `agent.skill_utils` | + +### `hermes_cli.profiles` + +| name | kind | new location | +|---|---|---| +| `has_bundled_skills_opt_out` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.runtime_provider` + +| name | kind | new location | +|---|---|---| +| `custom_provider_aliases` | moved-lazy | `hermes_cli.providers` | +| `custom_provider_slug` | moved-lazy | `hermes_cli.providers` | +| `os` | import | `os` | + +### `hermes_cli.security_advisories` + +| name | kind | new location | +|---|---|---| +| `render_doctor_section` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.setup` + +| name | kind | new location | +|---|---|---| +| `Any` | import | `typing` | +| `Dict` | import | `typing` | +| `Optional` | import | `typing` | +| `get_nous_subscription_features` | moved-lazy | `hermes_cli.nous_subscription` | +| `get_optional_skills_dir` | moved-lazy | `hermes_constants` | +| `json` | import | `json` | +| `managed_nous_tools_enabled` | moved-lazy | `tools.tool_backend_helpers` | +| `shutil` | import | `shutil` | + +### `hermes_cli.slack_cli` + +| name | kind | new location | +|---|---|---| +| `os` | import | `os` | + +### `hermes_cli.sqlite_safe_read` + +| name | kind | new location | +|---|---|---| +| `SQLITE_HEADER_MAGIC` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.status` + +| name | kind | new location | +|---|---|---| +| `format_nous_portal_entitlement_message` | moved-lazy | `hermes_cli.nous_account` | +| `get_nous_portal_account_info` | moved-lazy | `hermes_cli.nous_account` | +| `get_nous_subscription_features` | moved-lazy | `hermes_cli.nous_subscription` | +| `managed_nous_tools_enabled` | moved-lazy | `tools.tool_backend_helpers` | +| `redact_key` | moved-lazy | `hermes_cli.config` | +| `subprocess` | import | `subprocess` | + +### `hermes_cli.telegram_managed_bot` + +| name | kind | new location | +|---|---|---| +| `DEFAULT_MANAGER_BOT` | restored-def | `(deleted; BASE body restored)` | +| `_USERNAME_SLUG_ALPHABET` | restored-helper | `(deleted; restored as a dependency of generate_bot_username)` | +| `auto_setup_telegram_bot` | restored-def | `(deleted; BASE body restored)` | +| `generate_bot_username` | restored-def | `(deleted; BASE body restored)` | +| `generate_deep_link` | restored-def | `(deleted; BASE body restored)` | +| `generate_pairing_nonce` | restored-def | `(deleted; BASE body restored)` | +| `generate_username_slug` | restored-helper | `(deleted; restored as a dependency of generate_bot_username)` | +| `generate_username_slug` | restored-def | `(deleted; BASE body restored)` | +| `poll_for_token` | moved-lazy | `plugins.platforms.photon.auth` | +| `poll_pairing_once` | restored-def | `(deleted; BASE body restored)` | +| `secrets` | restored-import | `secrets` | +| `secrets` | import | `secrets` | +| `urllib` | restored-import | `urllib.parse` | +| `urllib` | import | `urllib.parse` | + +### `hermes_cli.tools_config` + +| name | kind | new location | +|---|---|---| +| `MANAGED_FEATURE_COVERAGE_CATEGORY` | moved-lazy | `hermes_cli.nous_subscription` | +| `NOUS_MANAGED_PROVIDER` | moved-lazy | `tools.tool_backend_helpers` | +| `base_url_hostname` | moved-lazy | `utils` | +| `fal_key_is_configured` | moved-lazy | `tools.tool_backend_helpers` | +| `format_nous_portal_entitlement_message` | moved-lazy | `hermes_cli.nous_account` | +| `is_truthy_value` | moved-lazy | `utils` | +| `save_env_value` | moved-lazy | `hermes_cli.config` | +| `shutil` | import | `shutil` | +| `subprocess` | import | `subprocess` | +| `sys` | import | `sys` | + +### `hermes_cli.uninstall` + +| name | kind | new location | +|---|---|---| +| `find_shell_configs` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.update_cmd` + +| name | kind | new location | +|---|---|---| +| `Optional` | import | `typing` | +| `datetime` | import | `datetime` | +| `hashlib` | import | `hashlib` | +| `json` | import | `json` | + +### `hermes_cli.update_inventory` + +| name | kind | new location | +|---|---|---| +| `Path` | import | `pathlib` | +| `os` | import | `os` | + +### `hermes_cli.web_deps` + +| name | kind | new location | +|---|---|---| +| `get_dashboard_health` | restored-def | `(deleted; BASE body restored)` | +| `get_session_token` | restored-def | `(deleted; BASE body restored)` | +| `has_valid_session_token` | restored-def | `(deleted; BASE body restored)` | +| `late_attr` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_cli.web_routers.cron` + +| name | kind | new location | +|---|---|---| +| `logging` | import | `logging` | + +### `hermes_cli.web_routers.mcp` + +| name | kind | new location | +|---|---|---| +| `logging` | import | `logging` | + +### `hermes_cli.web_routers.sessions` + +| name | kind | new location | +|---|---|---| +| `Any` | import | `typing` | +| `Dict` | import | `typing` | +| `logging` | import | `logging` | + +### `hermes_cli.web_routers.skills` + +| name | kind | new location | +|---|---|---| +| `LateState` | moved-lazy | `hermes_cli.web_deps` | +| `logging` | import | `logging` | + +### `hermes_cli.web_routers.tools` + +| name | kind | new location | +|---|---|---| +| `LateState` | moved-lazy | `hermes_cli.web_deps` | +| `logging` | import | `logging` | + +### `hermes_cli.web_server` + +| name | kind | new location | +|---|---|---| +| `AudioTranscriptionRequest` | moved-lazy | `hermes_cli.web_models` | +| `AutomationBlueprintInstantiate` | moved-lazy | `hermes_cli.web_models` | +| `BackupRequest` | moved-lazy | `hermes_cli.web_models` | +| `BaseModel` | unrestorable | `no top-level definition on BASE` | +| `BulkDeleteSessions` | moved-lazy | `hermes_cli.web_models` | +| `CONFIG_SCHEMA` | moved-lazy | `hermes_cli.web_server_config` | +| `ChatImageUpload` | moved-lazy | `hermes_cli.web_models` | +| `ConfigUpdate` | moved-lazy | `hermes_cli.web_models` | +| `CredentialPoolAdd` | moved-lazy | `hermes_cli.web_models` | +| `CronJobCreate` | moved-lazy | `hermes_cli.web_models` | +| `CronJobUpdate` | moved-lazy | `hermes_cli.web_models` | +| `CuratorPause` | moved-lazy | `hermes_cli.web_models` | +| `CustomEndpointUpdate` | moved-lazy | `hermes_cli.web_models` | +| `DEFAULT_CONFIG` | moved-lazy | `hermes_cli.config` | +| `DebugShareRequest` | moved-lazy | `hermes_cli.web_models` | +| `EnvVarDelete` | moved-lazy | `hermes_cli.web_models` | +| `EnvVarReveal` | moved-lazy | `hermes_cli.web_models` | +| `EnvVarUpdate` | moved-lazy | `hermes_cli.web_models` | +| `File` | unrestorable | `no top-level definition on BASE` | +| `FileResponse` | unrestorable | `no top-level definition on BASE` | +| `FontSetBody` | moved-lazy | `hermes_cli.web_models` | +| `Form` | unrestorable | `no top-level definition on BASE` | +| `FsWriteText` | moved-lazy | `hermes_cli.web_models` | +| `GitBranchSwitchBody` | moved-lazy | `hermes_cli.web_models` | +| `GitCommitBody` | moved-lazy | `hermes_cli.web_models` | +| `GitFileBody` | moved-lazy | `hermes_cli.web_models` | +| `GitPathBody` | moved-lazy | `hermes_cli.web_models` | +| `GitWorktreeAddBody` | moved-lazy | `hermes_cli.web_models` | +| `GitWorktreeRemoveBody` | moved-lazy | `hermes_cli.web_models` | +| `HTMLResponse` | unrestorable | `no top-level definition on BASE` | +| `HookCreate` | moved-lazy | `hermes_cli.web_models` | +| `HookDelete` | moved-lazy | `hermes_cli.web_models` | +| `ImportRequest` | moved-lazy | `hermes_cli.web_models` | +| `LearningNodeEdit` | moved-lazy | `hermes_cli.web_models` | +| `LearningNodeRef` | moved-lazy | `hermes_cli.web_models` | +| `List` | import | `typing` | +| `Literal` | import | `typing` | +| `MCPCatalogInstall` | moved-lazy | `hermes_cli.web_models` | +| `MCPEnabledToggle` | moved-lazy | `hermes_cli.web_models` | +| `MCPServerCreate` | moved-lazy | `hermes_cli.web_models` | +| `MCPServersReplace` | moved-lazy | `hermes_cli.web_models` | +| `ManagedDirectoryCreate` | moved-lazy | `hermes_cli.web_models` | +| `ManagedFileDelete` | moved-lazy | `hermes_cli.web_models` | +| `ManagedFileUpload` | moved-lazy | `hermes_cli.web_models` | +| `ManagedFilesPolicy` | moved-lazy | `hermes_cli.web_server_files` | +| `MemoryProviderConfigUpdate` | moved-lazy | `hermes_cli.web_models` | +| `MemoryProviderSelect` | moved-lazy | `hermes_cli.web_models` | +| `MemoryProviderSetupRequest` | moved-lazy | `hermes_cli.web_models` | +| `MemoryReset` | moved-lazy | `hermes_cli.web_models` | +| `MessagingPlatformUpdate` | moved-lazy | `hermes_cli.web_models` | +| `MoaConfigPayload` | moved-lazy | `hermes_cli.web_models` | +| `MoaModelSlot` | moved-lazy | `hermes_cli.web_models` | +| `MoaPresetPayload` | moved-lazy | `hermes_cli.web_models` | +| `ModelAssignment` | moved-lazy | `hermes_cli.web_models` | +| `OAuthSubmitBody` | moved-lazy | `hermes_cli.web_models` | +| `OPTIONAL_ENV_VARS` | moved-lazy | `hermes_cli.config` | +| `PairingApprove` | moved-lazy | `hermes_cli.web_models` | +| `PairingRevoke` | moved-lazy | `hermes_cli.web_models` | +| `ProfileActiveUpdate` | moved-lazy | `hermes_cli.web_models` | +| `ProfileCreate` | moved-lazy | `hermes_cli.web_models` | +| `ProfileDescribeAuto` | moved-lazy | `hermes_cli.web_models` | +| `ProfileDescriptionUpdate` | moved-lazy | `hermes_cli.web_models` | +| `ProfileModelUpdate` | moved-lazy | `hermes_cli.web_models` | +| `ProfileRename` | moved-lazy | `hermes_cli.web_models` | +| `ProfileSoulUpdate` | moved-lazy | `hermes_cli.web_models` | +| `ProviderConfigSchema` | moved-lazy | `plugins.memory.config_schema` | +| `ProviderField` | moved-lazy | `plugins.memory.config_schema` | +| `PtyBridge` | moved-lazy | `hermes_cli.pty_bridge` | +| `PtySessionRegistry` | moved-lazy | `hermes_cli.pty_session` | +| `PtyUnavailableError` | moved-lazy | `hermes_cli.pty_bridge` | +| `Query` | unrestorable | `no top-level definition on BASE` | +| `RawConfigUpdate` | moved-lazy | `hermes_cli.web_models` | +| `RegistryFull` | moved-lazy | `hermes_cli.pty_session` | +| `Response` | unrestorable | `no top-level definition on BASE` | +| `STORAGE_HONCHO_HOST_BLOCK` | moved-lazy | `plugins.memory.config_schema` | +| `SecretStr` | unrestorable | `no top-level definition on BASE` | +| `SessionImport` | moved-lazy | `hermes_cli.web_models` | +| `SessionPrune` | moved-lazy | `hermes_cli.web_models` | +| `SessionRename` | moved-lazy | `hermes_cli.web_models` | +| `SkillContentUpdate` | moved-lazy | `hermes_cli.web_models` | +| `SkillCreate` | moved-lazy | `hermes_cli.web_models` | +| `SkillInstallRequest` | moved-lazy | `hermes_cli.web_models` | +| `SkillToggle` | moved-lazy | `hermes_cli.web_models` | +| `SkillUninstallRequest` | moved-lazy | `hermes_cli.web_models` | +| `SkillsUpdateRequest` | moved-lazy | `hermes_cli.web_models` | +| `StaticFiles` | unrestorable | `no top-level definition on BASE` | +| `TTSLeaseRequest` | moved-lazy | `hermes_cli.web_models` | +| `TTSSpeakRequest` | moved-lazy | `hermes_cli.web_models` | +| `TelegramOnboardingApply` | moved-lazy | `hermes_cli.web_models` | +| `TelegramOnboardingStart` | moved-lazy | `hermes_cli.web_models` | +| `TerminalBackendSelect` | moved-lazy | `hermes_cli.web_models` | +| `ThemeSetBody` | moved-lazy | `hermes_cli.web_models` | +| `ToolsetEnvUpdate` | moved-lazy | `hermes_cli.web_models` | +| `ToolsetModelSelect` | moved-lazy | `hermes_cli.web_models` | +| `ToolsetPostSetup` | moved-lazy | `hermes_cli.web_models` | +| `ToolsetProviderSelect` | moved-lazy | `hermes_cli.web_models` | +| `ToolsetToggle` | moved-lazy | `hermes_cli.web_models` | +| `UploadFile` | unrestorable | `no top-level definition on BASE` | +| `WebSocket` | unrestorable | `no top-level definition on BASE` | +| `WebSocketDisconnect` | unrestorable | `no top-level definition on BASE` | +| `WebhookCreate` | moved-lazy | `hermes_cli.web_models` | +| `WebhookEnabledToggle` | moved-lazy | `hermes_cli.web_models` | +| `WhatsAppOnboardingApply` | moved-lazy | `hermes_cli.web_models` | +| `WhatsAppOnboardingStart` | moved-lazy | `hermes_cli.web_models` | +| `activate_custom_endpoint` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `add_credential_pool_entry` | moved-lazy | `hermes_cli.web_routers.ops` | +| `add_mcp_server` | moved-lazy | `hermes_cli.web_routers.mcp` | +| `apply_telegram_onboarding` | moved-lazy | `hermes_cli.web_routers.messaging` | +| `apply_whatsapp_onboarding` | moved-lazy | `hermes_cli.web_routers.messaging` | +| `approve_pairing` | moved-lazy | `hermes_cli.web_routers.ops` | +| `atexit` | import | `atexit` | +| `auth_mcp_server` | moved-lazy | `hermes_cli.web_routers.mcp` | +| `base64` | import | `base64` | +| `binascii` | import | `binascii` | +| `build_cron_model_impact` | moved-lazy | `hermes_cli.config` | +| `bulk_delete_sessions_endpoint` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `cancel_oauth_session` | moved-lazy | `hermes_cli.web_routers.oauth` | +| `cancel_telegram_onboarding` | moved-lazy | `hermes_cli.web_routers.messaging` | +| `cancel_whatsapp_onboarding` | moved-lazy | `hermes_cli.web_routers.messaging` | +| `cfg_get` | moved-lazy | `hermes_cli.config` | +| `check_config_version` | moved-lazy | `hermes_cli.config` | +| `check_hermes_update` | moved-lazy | `hermes_cli.web_routers.actions` | +| `clear_model_endpoint_credentials` | moved-lazy | `hermes_cli.config` | +| `clear_pending_pairing` | moved-lazy | `hermes_cli.web_routers.ops` | +| `coerce_provider_id` | moved-lazy | `hermes_cli.config` | +| `concurrent` | import | `concurrent.futures` | +| `console_ws` | moved-lazy | `hermes_cli.web_routers.chat_ws` | +| `contextlib` | import | `contextlib` | +| `contextmanager` | import | `contextlib` | +| `count_empty_sessions_endpoint` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `create_cron_job` | moved-lazy | `hermes_cli.web_routers.cron` | +| `create_hook` | moved-lazy | `hermes_cli.web_routers.ops` | +| `create_managed_directory` | moved-lazy | `hermes_cli.web_routers.files` | +| `create_profile_endpoint` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `create_skill` | moved-lazy | `hermes_cli.web_routers.skills` | +| `create_webhook` | moved-lazy | `hermes_cli.web_routers.ops` | +| `cron_fire_webhook` | moved-lazy | `hermes_cli.web_routers.cron` | +| `custom_endpoint_key_env` | moved-lazy | `hermes_cli.config` | +| `dataclass` | import | `dataclasses` | +| `datetime` | import | `datetime` | +| `delete_agent_plugin` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `delete_cron_job` | moved-lazy | `hermes_cli.web_routers.cron` | +| `delete_custom_endpoint` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `delete_empty_sessions_endpoint` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `delete_hook` | moved-lazy | `hermes_cli.web_routers.ops` | +| `delete_learning_node` | moved-lazy | `hermes_cli.web_routers.status` | +| `delete_managed_file` | moved-lazy | `hermes_cli.web_routers.files` | +| `delete_profile_endpoint` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `delete_session_endpoint` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `delete_webhook` | moved-lazy | `hermes_cli.web_routers.ops` | +| `derive_gateway_busy` | moved-lazy | `gateway.status` | +| `derive_gateway_drainable` | moved-lazy | `gateway.status` | +| `describe_profile_auto_endpoint` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `detect_install_method` | moved-lazy | `hermes_cli.config` | +| `disconnect_oauth_provider` | moved-lazy | `hermes_cli.web_routers.oauth` | +| `download_dashboard_backup` | moved-lazy | `hermes_cli.web_routers.ops` | +| `download_managed_file` | moved-lazy | `hermes_cli.web_routers.files` | +| `enable_webhooks` | moved-lazy | `hermes_cli.web_routers.ops` | +| `env_var_enabled` | moved-lazy | `utils` | +| `events_ws` | moved-lazy | `hermes_cli.web_routers.chat_ws` | +| `export_session_endpoint` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `field_validator` | unrestorable | `no top-level definition on BASE` | +| `find_provider_entry` | moved-lazy | `hermes_cli.config` | +| `format_docker_update_message` | moved-lazy | `hermes_cli.config` | +| `fs_default_cwd` | moved-lazy | `hermes_cli.web_routers.files` | +| `fs_download` | moved-lazy | `hermes_cli.web_routers.files` | +| `fs_git_root` | moved-lazy | `hermes_cli.web_routers.files` | +| `fs_list` | moved-lazy | `hermes_cli.web_routers.files` | +| `fs_read_data_url` | moved-lazy | `hermes_cli.web_routers.files` | +| `fs_read_text` | moved-lazy | `hermes_cli.web_routers.files` | +| `fs_write_text` | moved-lazy | `hermes_cli.web_routers.files` | +| `functools` | import | `functools` | +| `gateway_drain` | moved-lazy | `hermes_cli.web_routers.actions` | +| `gateway_ws` | moved-lazy | `hermes_cli.web_routers.chat_ws` | +| `get_action_status` | moved-lazy | `hermes_cli.web_routers.actions` | +| `get_active_profile_endpoint` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `get_auxiliary_models` | moved-lazy | `hermes_cli.web_routers.models` | +| `get_client_voice_config` | moved-lazy | `hermes_cli.web_routers.audio` | +| `get_computer_use_status` | moved-lazy | `hermes_cli.web_routers.tools` | +| `get_config` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `get_config_path` | moved-lazy | `hermes_cli.config` | +| `get_config_raw` | moved-lazy | `hermes_cli.web_routers.analytics` | +| `get_cron_delivery_targets` | moved-lazy | `hermes_cli.web_routers.cron` | +| `get_cron_job` | moved-lazy | `hermes_cli.web_routers.cron` | +| `get_curator_status` | moved-lazy | `hermes_cli.web_routers.status` | +| `get_dashboard_font` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `get_dashboard_plugins` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `get_dashboard_themes` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `get_defaults` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `get_egress_status` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `get_elevenlabs_voices` | moved-lazy | `hermes_cli.web_routers.audio` | +| `get_env_path` | moved-lazy | `hermes_cli.config` | +| `get_env_vars` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `get_health` | moved-lazy | `hermes_cli.web_routers.status` | +| `get_hermes_home` | moved-lazy | `hermes_cli.config` | +| `get_learning_graph` | moved-lazy | `hermes_cli.web_routers.status` | +| `get_learning_node` | moved-lazy | `hermes_cli.web_routers.status` | +| `get_logs` | moved-lazy | `hermes_cli.web_routers.status` | +| `get_media` | moved-lazy | `hermes_cli.web_routers.files` | +| `get_memory_provider_config` | moved-lazy | `hermes_cli.web_routers.memory_providers` | +| `get_memory_status` | moved-lazy | `hermes_cli.web_routers.ops` | +| `get_messaging_platforms` | moved-lazy | `hermes_cli.web_routers.messaging` | +| `get_moa_models` | moved-lazy | `hermes_cli.web_routers.models` | +| `get_model_info` | moved-lazy | `hermes_cli.web_routers.models` | +| `get_model_options` | moved-lazy | `hermes_cli.web_routers.models` | +| `get_models_analytics` | moved-lazy | `hermes_cli.web_routers.analytics` | +| `get_plugins_hub` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `get_portal_status` | moved-lazy | `hermes_cli.web_routers.status` | +| `get_process_hermes_home` | moved-lazy | `hermes_cli.config` | +| `get_profile_setup_command` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `get_profile_soul` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `get_profiles_sessions` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `get_profiles_sessions_sidebar` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `get_provider_config_schema` | moved-lazy | `plugins.memory.config_schema` | +| `get_recommended_default_model` | moved-lazy | `hermes_cli.web_routers.models` | +| `get_running_pid` | moved-lazy | `gateway.status` | +| `get_running_pid_cached` | moved-lazy | `gateway.status` | +| `get_runtime_status_running_pid` | moved-lazy | `gateway.status` | +| `get_schema` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `get_session_detail` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `get_session_latest_descendant` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `get_session_messages` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `get_session_stats` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `get_sessions` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `get_skill_content` | moved-lazy | `hermes_cli.web_routers.skills` | +| `get_skills` | moved-lazy | `hermes_cli.web_routers.skills` | +| `get_ssh_ownership` | moved-lazy | `hermes_cli.web_routers.status` | +| `get_status` | moved-lazy | `hermes_cli.web_routers.status` | +| `get_system_stats` | moved-lazy | `hermes_cli.web_routers.status` | +| `get_telegram_onboarding_status` | moved-lazy | `hermes_cli.web_routers.messaging` | +| `get_terminal_backends` | moved-lazy | `hermes_cli.web_routers.tools` | +| `get_toolset_config` | moved-lazy | `hermes_cli.web_routers.tools` | +| `get_toolset_models` | moved-lazy | `hermes_cli.web_routers.tools` | +| `get_toolsets` | moved-lazy | `hermes_cli.web_routers.tools` | +| `get_update_receipt` | moved-lazy | `hermes_cli.web_routers.actions` | +| `get_usage_analytics` | moved-lazy | `hermes_cli.web_routers.analytics` | +| `get_whatsapp_onboarding_status` | moved-lazy | `hermes_cli.web_routers.messaging` | +| `git_base_branches_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_branch_switch_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_branches_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_commit_context_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_commit_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_create_pr_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_file_diff_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_push_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_rev_parse_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_revert_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_review_diff_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_review_list_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_ship_info_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_stage_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_status_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_unstage_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_worktree_add_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_worktree_remove_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `git_worktrees_route` | moved-lazy | `hermes_cli.web_routers.git` | +| `grant_computer_use_permissions` | moved-lazy | `hermes_cli.web_routers.tools` | +| `hashlib` | import | `hashlib` | +| `import_sessions_endpoint` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `importlib` | import | `importlib.util` | +| `inspect` | import | `inspect` | +| `install_mcp_catalog_entry` | moved-lazy | `hermes_cli.web_routers.mcp` | +| `install_skill_hub` | moved-lazy | `hermes_cli.web_routers.skills` | +| `instantiate_blueprint` | moved-lazy | `hermes_cli.web_routers.cron` | +| `ipaddress` | import | `ipaddress` | +| `is_nix_install_method` | moved-lazy | `hermes_cli.config` | +| `json` | import | `json` | +| `list_checkpoints` | moved-lazy | `hermes_cli.web_routers.ops` | +| `list_credential_pool` | moved-lazy | `hermes_cli.web_routers.ops` | +| `list_cron_blueprints` | moved-lazy | `hermes_cli.web_routers.cron` | +| `list_cron_job_runs` | moved-lazy | `hermes_cli.web_routers.cron` | +| `list_cron_jobs` | moved-lazy | `hermes_cli.web_routers.cron` | +| `list_custom_endpoints` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `list_hooks` | moved-lazy | `hermes_cli.web_routers.ops` | +| `list_managed_files` | moved-lazy | `hermes_cli.web_routers.files` | +| `list_mcp_catalog` | moved-lazy | `hermes_cli.web_routers.mcp` | +| `list_mcp_servers` | moved-lazy | `hermes_cli.web_routers.mcp` | +| `list_oauth_providers` | moved-lazy | `hermes_cli.web_routers.oauth` | +| `list_pairing` | moved-lazy | `hermes_cli.web_routers.ops` | +| `list_profiles_endpoint` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `list_skills_hub_sources` | moved-lazy | `hermes_cli.web_routers.skills` | +| `list_webhooks` | moved-lazy | `hermes_cli.web_routers.ops` | +| `load_env` | moved-lazy | `hermes_cli.config` | +| `math` | import | `math` | +| `mcp_oauth_callback` | moved-lazy | `hermes_cli.web_routers.mcp` | +| `mcp_oauth_flow_status` | moved-lazy | `hermes_cli.web_routers.mcp` | +| `mimetypes` | import | `mimetypes` | +| `normalize_updated_at` | moved-lazy | `gateway.status` | +| `open_profile_terminal_endpoint` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `parse_active_agents` | moved-lazy | `gateway.status` | +| `pause_cron_job` | moved-lazy | `hermes_cli.web_routers.cron` | +| `poll_oauth_session` | moved-lazy | `hermes_cli.web_routers.oauth` | +| `post_agent_plugin_disable` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `post_agent_plugin_enable` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `post_agent_plugin_install` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `post_agent_plugin_update` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `post_plugin_visibility` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `preview_skill_hub` | moved-lazy | `hermes_cli.web_routers.skills` | +| `prune_checkpoints` | moved-lazy | `hermes_cli.web_routers.ops` | +| `prune_sessions_endpoint` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `pty_ws` | moved-lazy | `hermes_cli.web_routers.chat_ws` | +| `pub_ws` | moved-lazy | `hermes_cli.web_routers.chat_ws` | +| `put_plugin_providers` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `queue` | import | `queue` | +| `read_managed_file` | moved-lazy | `hermes_cli.web_routers.files` | +| `read_raw_config` | moved-lazy | `hermes_cli.config` | +| `read_runtime_status` | moved-lazy | `gateway.status` | +| `recommended_update_command_for_method` | moved-lazy | `hermes_cli.config` | +| `redact_key` | moved-lazy | `hermes_cli.config` | +| `remove_credential_pool_entry` | moved-lazy | `hermes_cli.web_routers.ops` | +| `remove_env_value` | moved-lazy | `hermes_cli.config` | +| `remove_env_var` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `remove_mcp_server` | moved-lazy | `hermes_cli.web_routers.mcp` | +| `rename_profile_endpoint` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `rename_session_endpoint` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `replace_mcp_servers` | moved-lazy | `hermes_cli.web_routers.mcp` | +| `rescan_dashboard_plugins` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `reset_memory` | moved-lazy | `hermes_cli.web_routers.ops` | +| `resolve_cron_model_drift_defaults` | moved-lazy | `hermes_cli.config` | +| `resolve_gateway_liveness` | moved-lazy | `gateway.status` | +| `restart_gateway` | moved-lazy | `hermes_cli.web_routers.actions` | +| `resume_cron_job` | moved-lazy | `hermes_cli.web_routers.cron` | +| `reveal_env_var` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `revoke_pairing` | moved-lazy | `hermes_cli.web_routers.ops` | +| `run_backup` | moved-lazy | `hermes_cli.backup` | +| `run_config_migrate` | moved-lazy | `hermes_cli.web_routers.status` | +| `run_curator` | moved-lazy | `hermes_cli.web_routers.status` | +| `run_debug_share_endpoint` | moved-lazy | `hermes_cli.web_routers.status` | +| `run_doctor` | moved-lazy | `hermes_cli.doctor` | +| `run_dump` | moved-lazy | `hermes_cli.dump` | +| `run_import` | moved-lazy | `hermes_cli.backup` | +| `run_import_upload` | moved-lazy | `hermes_cli.web_routers.ops` | +| `run_in_threadpool` | unrestorable | `no top-level definition on BASE` | +| `run_prompt_size` | moved-lazy | `hermes_cli.web_routers.status` | +| `run_security_audit` | moved-lazy | `hermes_cli.web_routers.ops` | +| `run_toolset_post_setup` | moved-lazy | `hermes_cli.web_routers.tools` | +| `save_config` | moved-lazy | `hermes_cli.config` | +| `save_env_value` | moved-lazy | `hermes_cli.config` | +| `save_toolset_env` | moved-lazy | `hermes_cli.web_routers.tools` | +| `scan_skill_hub` | moved-lazy | `hermes_cli.web_routers.skills` | +| `search_sessions` | moved-lazy | `hermes_cli.web_routers.sessions` | +| `search_skills_hub` | moved-lazy | `hermes_cli.web_routers.skills` | +| `select_terminal_backend` | moved-lazy | `hermes_cli.web_routers.tools` | +| `select_toolset_model` | moved-lazy | `hermes_cli.web_routers.tools` | +| `select_toolset_provider` | moved-lazy | `hermes_cli.web_routers.tools` | +| `serve_plugin_asset` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `set_active_profile_endpoint` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `set_curator_paused` | moved-lazy | `hermes_cli.web_routers.status` | +| `set_dashboard_font` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `set_dashboard_theme` | moved-lazy | `hermes_cli.web_routers.dashboard_ui` | +| `set_env_var` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `set_mcp_server_enabled` | moved-lazy | `hermes_cli.web_routers.mcp` | +| `set_memory_provider` | moved-lazy | `hermes_cli.web_routers.ops` | +| `set_moa_models` | moved-lazy | `hermes_cli.web_routers.models` | +| `set_model_assignment` | moved-lazy | `hermes_cli.web_routers.models` | +| `set_webhook_enabled` | moved-lazy | `hermes_cli.web_routers.ops` | +| `setup_memory_provider` | moved-lazy | `hermes_cli.web_routers.memory_providers` | +| `shlex` | import | `shlex` | +| `shutil` | import | `shutil` | +| `speak_stream_ws` | moved-lazy | `hermes_cli.web_routers.audio` | +| `speak_text` | moved-lazy | `hermes_cli.voice` | +| `start_gateway` | moved-lazy | `hermes_cli.web_routers.ops` | +| `start_oauth_login` | moved-lazy | `hermes_cli.web_routers.oauth` | +| `start_telegram_onboarding` | moved-lazy | `hermes_cli.web_routers.messaging` | +| `start_whatsapp_onboarding` | moved-lazy | `hermes_cli.web_routers.messaging` | +| `stat` | import | `stat` | +| `stop_gateway` | moved-lazy | `hermes_cli.web_routers.ops` | +| `stream_managed_file` | moved-lazy | `hermes_cli.web_routers.files` | +| `submit_oauth_code` | moved-lazy | `hermes_cli.web_routers.oauth` | +| `tempfile` | import | `tempfile` | +| `test_mcp_server` | moved-lazy | `hermes_cli.web_routers.mcp` | +| `test_messaging_platform` | moved-lazy | `hermes_cli.web_routers.messaging` | +| `timezone` | import | `datetime` | +| `toggle_skill` | moved-lazy | `hermes_cli.web_routers.skills` | +| `toggle_toolset` | moved-lazy | `hermes_cli.web_routers.tools` | +| `transcribe_audio_upload` | moved-lazy | `hermes_cli.web_routers.audio` | +| `trigger_cron_job` | moved-lazy | `hermes_cli.web_routers.cron` | +| `tts_lease` | moved-lazy | `hermes_cli.web_routers.audio` | +| `uninstall_skill_hub` | moved-lazy | `hermes_cli.web_routers.skills` | +| `update_config` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `update_config_raw` | moved-lazy | `hermes_cli.web_routers.analytics` | +| `update_cron_job` | moved-lazy | `hermes_cli.web_routers.cron` | +| `update_hermes` | moved-lazy | `hermes_cli.web_routers.actions` | +| `update_learning_node` | moved-lazy | `hermes_cli.web_routers.status` | +| `update_memory_provider_config` | moved-lazy | `hermes_cli.web_routers.memory_providers` | +| `update_messaging_platform` | moved-lazy | `hermes_cli.web_routers.messaging` | +| `update_profile_description_endpoint` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `update_profile_model_endpoint` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `update_profile_soul` | moved-lazy | `hermes_cli.web_routers.profiles` | +| `update_skill_content` | moved-lazy | `hermes_cli.web_routers.skills` | +| `update_skills_hub` | moved-lazy | `hermes_cli.web_routers.skills` | +| `upload_chat_image` | moved-lazy | `hermes_cli.web_routers.files` | +| `upload_managed_file` | moved-lazy | `hermes_cli.web_routers.files` | +| `upload_managed_file_stream` | moved-lazy | `hermes_cli.web_routers.files` | +| `upsert_custom_endpoint` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `validate_custom_endpoint` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `validate_provider_credential` | moved-lazy | `hermes_cli.web_routers.config_env` | +| `windows_detach_flags` | moved-lazy | `hermes_cli._subprocess_compat` | +| `windows_hide_flags` | moved-lazy | `hermes_cli._subprocess_compat` | +| `write_platform_config_field` | moved-lazy | `hermes_cli.config` | +| `yaml` | import | `yaml` | +| `zipfile` | import | `zipfile` | + +### `hermes_cli.webhook` + +| name | kind | new location | +|---|---|---| +| `atomic_replace` | moved-lazy | `utils` | +| `os` | import | `os` | +| `tempfile` | import | `tempfile` | + +### `hermes_cli.win_pty_bridge` + +| name | kind | new location | +|---|---|---| +| `os` | import | `os` | + +### `hermes_logging` + +| name | kind | new location | +|---|---|---| +| `rotating_file_handlers` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_state` + +| name | kind | new location | +|---|---|---| +| `AUTO_VACUUM_MIN_FREELIST_RATIO` | moved-lazy | `hermes_state_common` | +| `ActivityProvenance` | moved-lazy | `agent.session_activity` | +| `CompressionSessionBusyError` | moved-lazy | `hermes_state_errors` | +| `CompressionSessionClosedError` | moved-lazy | `hermes_state_errors` | +| `DEFERRED_INDEX_SQL` | moved-lazy | `hermes_state_common` | +| `FTS_CJK_STALE_KEY` | moved-lazy | `hermes_state_common` | +| `FTS_CJK_TABLE_SQL` | moved-lazy | `hermes_state_fts` | +| `FTS_CJK_TRIGGER_SQL` | moved-lazy | `hermes_state_fts` | +| `FTS_REBUILD_DEFERRAL_KEY` | moved-lazy | `hermes_state_common` | +| `FTS_SQL` | moved-lazy | `hermes_state_common` | +| `FTS_STALE_KEY` | moved-lazy | `hermes_state_common` | +| `FTS_STORAGE_VERSION` | moved-lazy | `hermes_state_common` | +| `FTS_TRIGRAM_SQL` | moved-lazy | `hermes_state_common` | +| `LEGACY_FTS_SQL` | moved-lazy | `hermes_state_common` | +| `LEGACY_FTS_TRIGRAM_SQL` | moved-lazy | `hermes_state_common` | +| `MAX_FTS5_QUERY_CHARS` | moved-lazy | `hermes_state_common` | +| `MAX_SAFE_EXPORT_MESSAGES` | restored-def | `(deleted; BASE body restored)` | +| `MAX_SAFE_RESUME_MESSAGES` | restored-def | `(deleted; BASE body restored)` | +| `PERSISTENCE_ERROR_CAUSES` | moved-lazy | `hermes_state_errors` | +| `SCHEMA_SQL` | moved-lazy | `hermes_state_common` | +| `SCHEMA_VERSION` | moved-lazy | `hermes_state_common` | +| `SESSION_STATUS_COMPLETE` | moved-lazy | `hermes_state_sessions` | +| `SESSION_STATUS_EMPTY` | moved-lazy | `hermes_state_sessions` | +| `SESSION_STATUS_ERROR` | moved-lazy | `hermes_state_sessions` | +| `SESSION_STATUS_INTERRUPTED` | moved-lazy | `hermes_state_sessions` | +| `SKILL_EXCERPT_JOINT` | moved-lazy | `agent.skill_commands` | +| `SKILL_SCAFFOLD_SQL_LIKE` | moved-lazy | `agent.skill_commands` | +| `SessionTurnLeaseLostError` | moved-lazy | `hermes_state_errors` | +| `Set` | import | `typing` | +| `WalUnsupportedError` | moved-lazy | `hermes_state_wal` | +| `apply_durability_barriers` | moved-lazy | `hermes_state_repair` | +| `classify_session_status` | moved-lazy | `hermes_state_sessions` | +| `close_shared_session_dbs` | unrestorable | `no top-level definition on BASE` | +| `collect_state_db_stats` | moved-lazy | `hermes_state_dbfile` | +| `contextlib` | import | `contextlib` | +| `count_db_holders` | moved-lazy | `hermes_state_dbfile` | +| `describe_skill_invocation` | moved-lazy | `agent.skill_commands` | +| `errno` | import | `errno` | +| `fts5_cjk_so_path` | moved-lazy | `hermes_state_fts` | +| `get_shared_session_db` | unrestorable | `no top-level definition on BASE` | +| `is_advisory_lock_contention` | moved-lazy | `hermes_state_common` | +| `is_automatic_end_reason` | moved-lazy | `hermes_state_common` | +| `is_disk_full_error` | moved-lazy | `hermes_state_errors` | +| `is_sqlite_wal_reset_vulnerable` | moved-lazy | `hermes_state_wal` | +| `is_transient_sqlite_error` | moved-lazy | `hermes_state_errors` | +| `iter_deleted_sqlite_sidecar_holders` | moved-lazy | `hermes_state_dbfile` | +| `release_or_close` | moved-lazy | `hermes_state_registry` | +| `release_shared_session_db` | unrestorable | `no top-level definition on BASE` | +| `report_startup_progress` | moved-lazy | `hermes_startup_watchdog` | +| `resolve_journal_mode` | moved-lazy | `hermes_state_wal` | +| `resolve_synchronous_level` | moved-lazy | `hermes_state_wal` | +| `sanitize_context` | moved-lazy | `agent.memory_manager` | +| `sqlite_source_id` | moved-lazy | `hermes_state_wal` | +| `struct` | import | `struct` | +| `weakref` | import | `weakref` | +| `workspace_key` | moved-lazy | `hermes_state_sessions` | + +### `hermes_state_registry` + +| name | kind | new location | +|---|---|---| +| `close_shared_session_dbs` | restored-def | `(deleted; BASE body restored)` | +| `get_shared_session_db` | restored-def | `(deleted; BASE body restored)` | +| `release_shared_session_db` | restored-def | `(deleted; BASE body restored)` | + +### `hermes_state_search` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | +| `os` | import | `os` | + +### `plugins.browser.browser_use.provider` + +| name | kind | new location | +|---|---|---| +| `BrowserProvider` | moved-lazy | `agent.browser_provider` | +| `os` | import | `os` | + +### `plugins.browser.browserbase.provider` + +| name | kind | new location | +|---|---|---| +| `BrowserProvider` | moved-lazy | `agent.browser_provider` | +| `requests` | import | `requests` | +| `uuid` | import | `uuid` | + +### `plugins.browser.firecrawl.provider` + +| name | kind | new location | +|---|---|---| +| `BrowserProvider` | moved-lazy | `agent.browser_provider` | +| `requests` | import | `requests` | +| `uuid` | import | `uuid` | + +### `plugins.context_engine` + +| name | kind | new location | +|---|---|---| +| `importlib` | import | `importlib.util` | +| `sys` | import | `sys` | + +### `plugins.cron_providers` + +| name | kind | new location | +|---|---|---| +| `importlib` | import | `importlib.util` | +| `sys` | import | `sys` | + +### `plugins.cron_providers.chronos` + +| name | kind | new location | +|---|---|---| +| `Optional` | import | `typing` | + +### `plugins.cron_providers.chronos._nas_client` + +| name | kind | new location | +|---|---|---| +| `Optional` | import | `typing` | + +### `plugins.dashboard_auth.basic` + +| name | kind | new location | +|---|---|---| +| `Any` | import | `typing` | +| `LoginStart` | moved-lazy | `hermes_cli.dashboard_auth` | + +### `plugins.dashboard_auth.drain` + +| name | kind | new location | +|---|---|---| +| `LoginStart` | moved-lazy | `hermes_cli.dashboard_auth` | + +### `plugins.dashboard_auth.nous` + +| name | kind | new location | +|---|---|---| +| `DashboardAuthProvider` | moved-lazy | `hermes_cli.dashboard_auth` | +| `InvalidCodeError` | moved-lazy | `hermes_cli.dashboard_auth` | +| `RefreshExpiredError` | moved-lazy | `hermes_cli.dashboard_auth` | +| `base64` | import | `base64` | +| `classify_jwks_lookup_error` | moved-lazy | `hermes_cli.dashboard_auth` | +| `hashlib` | import | `hashlib` | +| `httpx` | import | `httpx` | +| `os` | import | `os` | +| `secrets` | import | `secrets` | +| `urllib` | import | `urllib.parse` | + +### `plugins.dashboard_auth.self_hosted` + +| name | kind | new location | +|---|---|---| +| `DashboardAuthProvider` | moved-lazy | `hermes_cli.dashboard_auth` | +| `InvalidCodeError` | moved-lazy | `hermes_cli.dashboard_auth` | +| `RefreshExpiredError` | moved-lazy | `hermes_cli.dashboard_auth` | +| `classify_jwks_lookup_error` | moved-lazy | `hermes_cli.dashboard_auth` | +| `hashlib` | import | `hashlib` | +| `os` | import | `os` | +| `secrets` | import | `secrets` | + +### `plugins.google_meet.audio_bridge` + +| name | kind | new location | +|---|---|---| +| `chrome_fake_audio_flags` | restored-def | `(deleted; BASE body restored)` | + +### `plugins.google_meet.meet_bot` + +| name | kind | new location | +|---|---|---| +| `SAY_PCM_FILENAME` | restored-def | `(deleted; BASE body restored)` | +| `SAY_QUEUE_FILENAME` | restored-def | `(deleted; BASE body restored)` | +| `json` | import | `json` | + +### `plugins.google_meet.node.cli` + +| name | kind | new location | +|---|---|---| +| `Any` | import | `typing` | + +### `plugins.google_meet.node.registry` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | + +### `plugins.image_gen.deepinfra` + +| name | kind | new location | +|---|---|---| +| `ImageGenProvider` | moved-lazy | `agent.image_gen_provider` | +| `error_response` | moved-lazy | `agent.image_gen_provider` | +| `save_b64_image` | moved-lazy | `agent.image_gen_provider` | +| `save_url_image` | moved-lazy | `agent.image_gen_provider` | + +### `plugins.image_gen.fal` + +| name | kind | new location | +|---|---|---| +| `ImageGenProvider` | moved-lazy | `agent.image_gen_provider` | +| `os` | import | `os` | + +### `plugins.image_gen.krea` + +| name | kind | new location | +|---|---|---| +| `ImageGenProvider` | moved-lazy | `agent.image_gen_provider` | +| `error_response` | moved-lazy | `agent.image_gen_provider` | +| `normalize_reference_images` | moved-lazy | `agent.image_gen_provider` | +| `os` | import | `os` | + +### `plugins.image_gen.openai` + +| name | kind | new location | +|---|---|---| +| `ImageGenProvider` | moved-lazy | `agent.image_gen_provider` | +| `error_response` | moved-lazy | `agent.image_gen_provider` | +| `normalize_reference_images` | moved-lazy | `agent.image_gen_provider` | +| `save_b64_image` | moved-lazy | `agent.image_gen_provider` | +| `save_url_image` | moved-lazy | `agent.image_gen_provider` | + +### `plugins.image_gen.xai` + +| name | kind | new location | +|---|---|---| +| `ImageGenProvider` | moved-lazy | `agent.image_gen_provider` | +| `error_response` | moved-lazy | `agent.image_gen_provider` | +| `normalize_reference_images` | moved-lazy | `agent.image_gen_provider` | +| `save_b64_image` | moved-lazy | `agent.image_gen_provider` | +| `save_url_image` | moved-lazy | `agent.image_gen_provider` | + +### `plugins.memory.hindsight` + +| name | kind | new location | +|---|---|---| +| `dataclass` | import | `dataclasses` | +| `importlib` | import | `importlib` | + +### `plugins.memory.honcho` + +| name | kind | new location | +|---|---|---| +| `CONCLUDE_SCHEMA` | moved-lazy | `plugins.memory.honcho.tool_schemas` | +| `CONTEXT_SCHEMA` | moved-lazy | `plugins.memory.honcho.tool_schemas` | +| `PROFILE_SCHEMA` | moved-lazy | `plugins.memory.honcho.tool_schemas` | +| `REASONING_SCHEMA` | moved-lazy | `plugins.memory.honcho.tool_schemas` | +| `SEARCH_SCHEMA` | moved-lazy | `plugins.memory.honcho.tool_schemas` | +| `TRIVIAL_PROMPT_RE` | moved-lazy | `agent.memory_provider` | + +### `plugins.memory.honcho.client` + +| name | kind | new location | +|---|---|---| +| `SingletonSlot` | moved-lazy | `plugins.plugin_utils` | + +### `plugins.memory.honcho.oauth` + +| name | kind | new location | +|---|---|---| +| `Callable` | import | `typing` | + +### `plugins.memory.honcho.session` + +| name | kind | new location | +|---|---|---| +| `Callable` | import | `typing` | +| `Path` | import | `pathlib` | +| `hashlib` | import | `hashlib` | +| `re` | import | `re` | + +### `plugins.memory.mem0` + +| name | kind | new location | +|---|---|---| +| `ADD_SCHEMA` | restored-def | `(deleted; BASE body restored)` | +| `DELETE_SCHEMA` | restored-def | `(deleted; BASE body restored)` | +| `SEARCH_SCHEMA` | moved-lazy | `plugins.memory.honcho.tool_schemas` | +| `UPDATE_SCHEMA` | restored-def | `(deleted; BASE body restored)` | + +### `plugins.memory.mem0._setup` + +| name | kind | new location | +|---|---|---| +| `has_oss_flags` | restored-def | `(deleted; BASE body restored)` | + +### `plugins.memory.retaindb` + +| name | kind | new location | +|---|---|---| +| `CONTEXT_SCHEMA` | moved-lazy | `plugins.memory.honcho.tool_schemas` | +| `Dict` | import | `typing` | +| `FILE_DELETE_SCHEMA` | restored-def | `(deleted; BASE body restored)` | +| `FILE_INGEST_SCHEMA` | restored-def | `(deleted; BASE body restored)` | +| `FILE_LIST_SCHEMA` | restored-def | `(deleted; BASE body restored)` | +| `FILE_READ_SCHEMA` | restored-def | `(deleted; BASE body restored)` | +| `FILE_UPLOAD_SCHEMA` | restored-def | `(deleted; BASE body restored)` | +| `FORGET_SCHEMA` | moved-lazy | `plugins.memory.openviking` | +| `List` | import | `typing` | +| `PROFILE_SCHEMA` | moved-lazy | `plugins.memory.honcho.tool_schemas` | +| `REMEMBER_SCHEMA` | moved-lazy | `plugins.memory.openviking` | +| `SEARCH_SCHEMA` | moved-lazy | `plugins.memory.honcho.tool_schemas` | + +### `plugins.memory.supermemory` + +| name | kind | new location | +|---|---|---| +| `FORGET_SCHEMA` | moved-lazy | `plugins.memory.openviking` | +| `PROFILE_SCHEMA` | moved-lazy | `plugins.memory.honcho.tool_schemas` | +| `SEARCH_SCHEMA` | moved-lazy | `plugins.memory.honcho.tool_schemas` | +| `STORE_SCHEMA` | restored-def | `(deleted; BASE body restored)` | + +### `plugins.platforms.a2a.protocol` + +| name | kind | new location | +|---|---|---| +| `ERR_PUSH_NOT_SUPPORTED` | restored-def | `(deleted; BASE body restored)` | +| `STATE_AUTH_REQUIRED` | restored-def | `(deleted; BASE body restored)` | +| `copy` | import | `copy` | +| `data_part` | restored-def | `(deleted; BASE body restored)` | +| `file_part` | restored-def | `(deleted; BASE body restored)` | +| `message_with_parts` | restored-def | `(deleted; BASE body restored)` | +| `stream_message` | restored-def | `(deleted; BASE body restored)` | + +### `plugins.platforms.a2a.security` + +| name | kind | new location | +|---|---|---| +| `Path` | import | `pathlib` | +| `authenticate` | restored-def | `(deleted; BASE body restored)` | +| `get_bearer_token` | restored-def | `(deleted; BASE body restored)` | +| `get_peer_tokens` | restored-def | `(deleted; BASE body restored)` | +| `get_push_secret` | restored-def | `(deleted; BASE body restored)` | +| `get_trusted_peers` | restored-def | `(deleted; BASE body restored)` | +| `is_trusted_peer` | restored-def | `(deleted; BASE body restored)` | +| `resolve_bind_host` | restored-def | `(deleted; BASE body restored)` | +| `sign_push_payload` | restored-def | `(deleted; BASE body restored)` | + +### `plugins.platforms.a2a.tools` + +| name | kind | new location | +|---|---|---| +| `TypedDict` | import | `typing` | + +### `plugins.platforms.dingtalk.adapter` + +| name | kind | new location | +|---|---|---| +| `DINGTALK_TYPE_MAPPING` | moved-lazy | `plugins.platforms.dingtalk.inbound` | +| `EXT_MAP` | moved-lazy | `plugins.platforms.dingtalk.inbound` | +| `MessageType` | moved-lazy | `gateway.platforms.base` | + +### `plugins.platforms.discord.adapter` + +| name | kind | new location | +|---|---|---| +| `env_int` | moved-lazy | `utils` | + +### `plugins.platforms.feishu.feishu_comment` + +| name | kind | new location | +|---|---|---| +| `add_comment_reaction` | restored-def | `(deleted; BASE body restored)` | +| `delete_comment_reaction` | restored-def | `(deleted; BASE body restored)` | + +### `plugins.platforms.google_chat.oauth` + +| name | kind | new location | +|---|---|---| +| `atomic_replace` | moved-lazy | `utils` | +| `secrets` | import | `secrets` | +| `subprocess` | import | `subprocess` | + +### `plugins.platforms.irc.adapter` + +| name | kind | new location | +|---|---|---| +| `os` | import | `os` | + +### `plugins.platforms.line.adapter` + +| name | kind | new location | +|---|---|---| +| `field` | import | `dataclasses` | + +### `plugins.platforms.matrix.adapter` + +| name | kind | new location | +|---|---|---| +| `MAX_MESSAGE_LENGTH` | moved-lazy | `gateway.platforms.signal` | +| `PaginationDirection` | unrestorable | `no top-level definition on BASE` | +| `SyncToken` | unrestorable | `no top-level definition on BASE` | +| `_MATRIX_CAPABILITIES` | restored-helper | `(deleted; restored as a dependency of get_matrix_capabilities)` | +| `get_matrix_capabilities` | restored-def | `(deleted; BASE body restored)` | + +### `plugins.platforms.ntfy.adapter` + +| name | kind | new location | +|---|---|---| +| `os` | import | `os` | + +### `plugins.platforms.photon.adapter` + +| name | kind | new location | +|---|---|---| +| `ProcessingOutcome` | moved-lazy | `gateway.platforms.base` | +| `resolve_sidecar_dir` | moved-lazy | `plugins.platforms.photon.sidecar_paths` | + +### `plugins.platforms.photon.auth` + +| name | kind | new location | +|---|---|---| +| `credential_summary` | restored-def | `(deleted; BASE body restored)` | +| `get_session` | restored-def | `(deleted; BASE body restored)` | + +### `plugins.platforms.photon.cli` + +| name | kind | new location | +|---|---|---| +| `Path` | import | `pathlib` | +| `resolve_sidecar_dir` | moved-lazy | `plugins.platforms.photon.sidecar_paths` | + +### `plugins.platforms.raft.adapter` + +| name | kind | new location | +|---|---|---| +| `asyncio` | import | `asyncio` | + +### `plugins.platforms.teams.adapter` + +| name | kind | new location | +|---|---|---| +| `TeamsSummaryWriter` | moved-lazy | `plugins.platforms.teams.summary_writer` | +| `html` | import | `html` | +| `quote` | import | `urllib.parse` | + +### `plugins.platforms.telegram.adapter` + +| name | kind | new location | +|---|---|---| +| `atomic_replace` | moved-lazy | `utils` | +| `cache_document_from_bytes` | moved-lazy | `gateway.platforms.base` | +| `threading` | import | `threading` | + +### `plugins.platforms.telegram.telegram_ids` + +| name | kind | new location | +|---|---|---| +| `telegram_chat_id_key` | restored-def | `(deleted; BASE body restored)` | + +### `plugins.platforms.wecom.adapter` + +| name | kind | new location | +|---|---|---| +| `ABSOLUTE_MAX_BYTES` | moved-lazy | `plugins.platforms.wecom.media` | +| `APP_CMD_UPLOAD_MEDIA_CHUNK` | moved-lazy | `plugins.platforms.wecom.media` | +| `APP_CMD_UPLOAD_MEDIA_FINISH` | moved-lazy | `plugins.platforms.wecom.media` | +| `APP_CMD_UPLOAD_MEDIA_INIT` | moved-lazy | `plugins.platforms.wecom.media` | +| `FILE_MAX_BYTES` | moved-lazy | `plugins.platforms.wecom.media` | +| `IMAGE_MAX_BYTES` | moved-lazy | `plugins.platforms.wecom.media` | +| `MAX_INTERMEDIATE_FRAMES` | moved-lazy | `plugins.platforms.wecom.streaming` | +| `MAX_UPLOAD_CHUNKS` | moved-lazy | `plugins.platforms.wecom.media` | +| `Path` | import | `pathlib` | +| `ReplyFrame` | moved-lazy | `plugins.platforms.wecom.streaming` | +| `STREAM_EXPIRED_ERRCODE` | moved-lazy | `plugins.platforms.wecom.streaming` | +| `STREAM_REQUEST_EXPIRED_ERRCODE` | moved-lazy | `plugins.platforms.wecom.streaming` | +| `STREAM_VERSION_CONFLICT_ERRCODE` | moved-lazy | `plugins.platforms.wecom.streaming` | +| `UPLOAD_CHUNK_SIZE` | moved-lazy | `plugins.platforms.wecom.media` | +| `VIDEO_MAX_BYTES` | moved-lazy | `plugins.platforms.wecom.media` | +| `VOICE_MAX_BYTES` | moved-lazy | `plugins.platforms.wecom.media` | +| `VOICE_SUPPORTED_MIMES` | moved-lazy | `plugins.platforms.wecom.media` | +| `WeComStreamExpiredError` | moved-lazy | `plugins.platforms.wecom.streaming` | +| `base64` | import | `base64` | +| `cache_document_from_bytes_async` | moved-lazy | `gateway.platforms.base` | +| `cache_image_from_bytes_async` | moved-lazy | `gateway.platforms.base` | +| `dataclass` | import | `dataclasses` | +| `deque` | import | `collections` | +| `hashlib` | import | `hashlib` | +| `mimetypes` | import | `mimetypes` | +| `os` | import | `os` | +| `unquote` | import | `urllib.parse` | +| `urlparse` | import | `urllib.parse` | + +### `plugins.spotify` + +| name | kind | new location | +|---|---|---| +| `SPOTIFY_ALBUMS_SCHEMA` | moved-lazy | `plugins.spotify.tools` | +| `SPOTIFY_DEVICES_SCHEMA` | moved-lazy | `plugins.spotify.tools` | +| `SPOTIFY_LIBRARY_SCHEMA` | moved-lazy | `plugins.spotify.tools` | +| `SPOTIFY_PLAYBACK_SCHEMA` | moved-lazy | `plugins.spotify.tools` | +| `SPOTIFY_PLAYLISTS_SCHEMA` | moved-lazy | `plugins.spotify.tools` | +| `SPOTIFY_QUEUE_SCHEMA` | moved-lazy | `plugins.spotify.tools` | +| `SPOTIFY_SEARCH_SCHEMA` | moved-lazy | `plugins.spotify.tools` | + +### `plugins.spotify.client` + +| name | kind | new location | +|---|---|---| +| `compact_json` | moved-lazy | `gateway.hosted_rooms_common` | +| `json` | import | `json` | + +### `plugins.spotify.tools` + +| name | kind | new location | +|---|---|---| +| `SpotifyAPIError` | moved-lazy | `plugins.spotify.client` | +| `SpotifyAuthRequiredError` | moved-lazy | `plugins.spotify.client` | + +### `plugins.teams_pipeline.cli` + +| name | kind | new location | +|---|---|---| +| `GraphSubscription` | moved-lazy | `plugins.teams_pipeline.models` | +| `Path` | import | `pathlib` | +| `datetime` | import | `datetime` | +| `timedelta` | import | `datetime` | +| `timezone` | import | `datetime` | + +### `plugins.teams_pipeline.meetings` + +| name | kind | new location | +|---|---|---| +| `fetch_call_record_artifact` | restored-def | `(deleted; BASE body restored)` | + +### `plugins.teams_pipeline.pipeline` + +| name | kind | new location | +|---|---|---| +| `os` | import | `os` | + +### `plugins.teams_pipeline.subscriptions` + +| name | kind | new location | +|---|---|---| +| `build_store` | restored-def | `(deleted; BASE body restored)` | +| `resolve_store_path` | restored-helper | `(deleted; restored as a dependency of build_store)` | +| `resolve_store_path` | restored-def | `(deleted; BASE body restored)` | +| `resolve_teams_pipeline_store_path` | restored-import | `plugins.teams_pipeline.store` | +| `resolve_teams_pipeline_store_path` | moved-lazy | `plugins.teams_pipeline.store` | + +### `plugins.video_gen.fal` + +| name | kind | new location | +|---|---|---| +| `os` | import | `os` | + +### `plugins.video_gen.xai` + +| name | kind | new location | +|---|---|---| +| `_run_xai_video_coroutine` | restored-helper | `(deleted; restored as a dependency of run_xai_video_generation)` | +| `run_xai_video_generation` | restored-def | `(deleted; BASE body restored)` | + +### `plugins.web.brave_free.provider` + +| name | kind | new location | +|---|---|---| +| `WebSearchProvider` | moved-lazy | `agent.web_search_provider` | +| `os` | import | `os` | + +### `plugins.web.ddgs.provider` + +| name | kind | new location | +|---|---|---| +| `WebSearchProvider` | moved-lazy | `agent.web_search_provider` | + +### `plugins.web.exa.provider` + +| name | kind | new location | +|---|---|---| +| `WebSearchProvider` | moved-lazy | `agent.web_search_provider` | +| `os` | import | `os` | + +### `plugins.web.firecrawl.provider` + +| name | kind | new location | +|---|---|---| +| `NoReturn` | import | `typing` | +| `TYPE_CHECKING` | import | `typing` | +| `WebSearchProvider` | moved-lazy | `agent.web_search_provider` | +| `os` | import | `os` | + +### `plugins.web.keenable.provider` + +| name | kind | new location | +|---|---|---| +| `WebSearchProvider` | moved-lazy | `agent.web_search_provider` | + +### `plugins.web.parallel.provider` + +| name | kind | new location | +|---|---|---| +| `WebSearchProvider` | moved-lazy | `agent.web_search_provider` | + +### `plugins.web.searxng.provider` + +| name | kind | new location | +|---|---|---| +| `WebSearchProvider` | moved-lazy | `agent.web_search_provider` | +| `os` | import | `os` | + +### `plugins.web.tavily.provider` + +| name | kind | new location | +|---|---|---| +| `WebSearchProvider` | moved-lazy | `agent.web_search_provider` | + +### `plugins.web.xai.provider` + +| name | kind | new location | +|---|---|---| +| `WebSearchProvider` | moved-lazy | `agent.web_search_provider` | + +### `providers` + +| name | kind | new location | +|---|---|---| +| `OMIT_TEMPERATURE` | moved-lazy | `providers.base` | + +### `run_agent` + +| name | kind | new location | +|---|---|---| +| `COMPRESSED_SUMMARY_METADATA_KEY` | moved-lazy | `agent.context_compressor` | +| `ContextCompressor` | moved-lazy | `agent.context_compressor` | +| `DEFAULT_AGENT_IDENTITY` | moved-lazy | `agent.prompt_builder` | +| `FailoverReason` | moved-lazy | `agent.error_classifier` | +| `OpenAI` | moved-lazy | `agent.process_bootstrap` | +| `SimpleNamespace` | import | `types` | +| `asyncio` | import | `asyncio` | +| `atomic_json_write` | moved-lazy | `utils` | +| `base64` | import | `base64` | +| `build_context_files_prompt` | moved-lazy | `agent.prompt_builder` | +| `build_environment_hints` | moved-lazy | `agent.prompt_builder` | +| `build_skills_system_prompt` | moved-lazy | `agent.prompt_builder` | +| `check_toolset_requirements` | moved-lazy | `model_tools` | +| `convert_scratchpad_to_think` | moved-lazy | `agent.trajectory` | +| `copy` | import | `copy` | +| `estimate_request_tokens_rough` | moved-lazy | `agent.model_metadata` | +| `file_mutation_result_landed` | moved-lazy | `agent.tool_result_classification` | +| `flatten_message_text` | moved-lazy | `agent.message_content` | +| `get_tool_definitions` | moved-lazy | `model_tools` | +| `handle_function_call` | moved-lazy | `model_tools` | +| `hashlib` | import | `hashlib` | +| `is_truthy_value` | moved-lazy | `utils` | +| `jittered_backoff` | moved-lazy | `agent.retry_utils` | +| `load_soul_md` | moved-lazy | `agent.prompt_builder` | +| `normalize_usage` | moved-lazy | `agent.usage_pricing` | +| `redact_sensitive_text` | moved-lazy | `agent.redact` | +| `request_hard_interrupt` | moved-lazy | `agent.interrupt_compat` | +| `sanitize_context` | moved-lazy | `agent.memory_manager` | +| `tempfile` | import | `tempfile` | +| `user_originated_turn_view` | moved-lazy | `agent.context_compressor` | + +### `tools` + +| name | kind | new location | +|---|---|---| +| `mcp_tool` | unrestorable | `no top-level definition on BASE` | + +### `tools.apply_layout_tool` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | + +### `tools.approval` + +| name | kind | new location | +|---|---|---| +| `DANGEROUS_PATTERNS` | moved-lazy | `tools.approval_detection` | +| `DANGEROUS_PATTERNS_COMPILED` | moved-lazy | `tools.approval_detection` | +| `HARDLINE_PATTERNS` | moved-lazy | `tools.approval_detection` | +| `HARDLINE_PATTERNS_COMPILED` | moved-lazy | `tools.approval_detection` | +| `HUMAN_WAIT_MARGIN_S` | moved-lazy | `tools.approval_human_wait` | +| `cfg_get` | moved-lazy | `hermes_cli.config` | +| `contextlib` | import | `contextlib` | +| `contextvars` | import | `contextvars` | +| `fnmatch` | import | `fnmatch` | +| `functools` | import | `functools` | +| `get_plugin_manager` | moved-lazy | `tools.approval_prompt` | +| `human_wait_ceiling` | moved-lazy | `tools.approval_human_wait` | +| `human_wait_seconds` | moved-lazy | `tools.approval_human_wait` | +| `human_wait_window` | moved-lazy | `tools.approval_human_wait` | +| `is_interrupted` | moved-lazy | `tools.interrupt` | +| `re` | import | `re` | +| `request_elicitation_consent` | moved-lazy | `tools.approval_prompt` | +| `reset_current_observability_context` | moved-lazy | `tools.approval_context` | +| `reset_current_session_key` | moved-lazy | `tools.approval_context` | +| `reset_hermes_interactive_context` | moved-lazy | `tools.approval_context` | +| `set_current_observability_context` | moved-lazy | `tools.approval_context` | +| `set_current_session_key` | moved-lazy | `tools.approval_context` | +| `set_hermes_interactive_context` | moved-lazy | `tools.approval_context` | +| `shlex` | import | `shlex` | +| `sys` | import | `sys` | +| `tempfile` | import | `tempfile` | +| `time` | import | `time` | +| `unicodedata` | import | `unicodedata` | +| `uuid` | import | `uuid` | + +### `tools.async_delegation` + +| name | kind | new location | +|---|---|---| +| `active_for_session` | restored-def | `(deleted; BASE body restored)` | + +### `tools.browser_camofox_state` + +| name | kind | new location | +|---|---|---| +| `CAMOFOX_STATE_DIR_NAME` | restored-def | `(deleted; BASE body restored)` | +| `CAMOFOX_STATE_SUBDIR` | restored-def | `(deleted; BASE body restored)` | + +### `tools.browser_dialog_tool` + +| name | kind | new location | +|---|---|---| +| `logger` | moved-lazy | `tools.approval` | +| `logging` | import | `logging` | + +### `tools.browser_supervisor` + +| name | kind | new location | +|---|---|---| +| `CONSOLE_HISTORY_MAX` | restored-def | `(deleted; BASE body restored)` | +| `ConsoleEvent` | restored-def | `(deleted; BASE body restored)` | +| `DIALOG_BRIDGE_HOST` | moved-lazy | `tools.browser_supervisor_dialogs` | +| `DIALOG_BRIDGE_URL_PATTERN` | moved-lazy | `tools.browser_supervisor_dialogs` | +| `DIALOG_POLICY_AUTO_ACCEPT` | moved-lazy | `tools.browser_supervisor_dialogs` | +| `DIALOG_POLICY_AUTO_DISMISS` | moved-lazy | `tools.browser_supervisor_dialogs` | +| `DIALOG_POLICY_MUST_RESPOND` | moved-lazy | `tools.browser_supervisor_dialogs` | +| `FRAME_TREE_MAX_ENTRIES` | moved-lazy | `tools.browser_supervisor_frames` | +| `FRAME_TREE_MAX_OOPIF_DEPTH` | moved-lazy | `tools.browser_supervisor_frames` | + +### `tools.browser_tool` + +| name | kind | new location | +|---|---|---| +| `BrowserUseProvider` | moved-lazy | `plugins.browser.browser_use.provider.BrowserUseBrowserProvider` | +| `BrowserbaseProvider` | moved-lazy | `plugins.browser.browserbase.provider.BrowserbaseBrowserProvider` | +| `CloudBrowserProvider` | moved-lazy | `agent.browser_provider.BrowserProvider` | +| `FirecrawlProvider` | moved-lazy | `plugins.browser.firecrawl.provider.FirecrawlBrowserProvider` | +| `List` | import | `typing` | +| `SNAPSHOT_SUMMARIZE_THRESHOLD` | restored-def | `(deleted; BASE body restored)` | +| `Tuple` | import | `typing` | +| `agent_browser_runnable` | moved-lazy | `hermes_constants` | +| `check_browser_requirements` | moved-lazy | `tools.browser_tool_install` | +| `check_browser_vision_requirements` | moved-lazy | `tools.browser_tool_install` | +| `cleanup_all_browsers` | moved-lazy | `tools.browser_tool_lifecycle` | +| `cleanup_browser` | moved-lazy | `tools.browser_tool_lifecycle` | +| `contextlib` | import | `contextlib` | +| `datetime` | import | `datetime` | +| `functools` | import | `functools` | +| `get_hermes_home_override` | moved-lazy | `hermes_constants` | +| `hermes_home_key` | moved-lazy | `hermes_constants` | +| `is_truthy_value` | moved-lazy | `utils` | +| `lightpanda_engine_status` | moved-lazy | `tools.browser_tool_lightpanda_fallback` | +| `node_tool_runnable` | moved-lazy | `hermes_constants` | +| `normalize_browser_cloud_provider` | moved-lazy | `tools.tool_backend_helpers` | +| `re` | import | `re` | +| `reset_hermes_home_override` | moved-lazy | `hermes_constants` | +| `set_hermes_home_override` | moved-lazy | `hermes_constants` | +| `shutil` | import | `shutil` | +| `signal` | import | `signal` | +| `timezone` | import | `datetime` | +| `warm_agent_browser_npx_cache` | moved-lazy | `tools.browser_tool_install` | +| `windows_hide_flags` | moved-lazy | `hermes_cli._subprocess_compat` | + +### `tools.clarify_gateway` + +| name | kind | new location | +|---|---|---| +| `get_notify` | restored-def | `(deleted; BASE body restored)` | +| `register_notify` | restored-def | `(deleted; BASE body restored)` | +| `unregister_notify` | restored-def | `(deleted; BASE body restored)` | + +### `tools.close_preview_tool` + +| name | kind | new location | +|---|---|---| +| `CLOSE_PREVIEW_SCHEMA` | restored-def | `(deleted; BASE body restored)` | +| `json` | import | `json` | +| `registry` | moved-lazy | `tools.registry` | +| `tool_error` | moved-lazy | `tools.registry` | + +### `tools.code_execution_tool` + +| name | kind | new location | +|---|---|---| +| `DEFAULT_KERNEL_MODE` | restored-def | `(deleted; BASE body restored)` | +| `KERNEL_MODES` | restored-def | `(deleted; BASE body restored)` | +| `platform` | import | `platform` | +| `socket` | import | `socket` | +| `sys` | import | `sys` | +| `thread_scoped_silence` | moved-lazy | `agent.thread_scoped_output` | + +### `tools.code_kernel_remote` + +| name | kind | new location | +|---|---|---| +| `base64` | import | `base64` | + +### `tools.computer_use` + +| name | kind | new location | +|---|---|---| +| `annotations` | import | `__future__` | +| `check_computer_use_requirements` | moved-lazy | `tools.computer_use.tool` | +| `get_computer_use_schema` | moved-lazy | `tools.computer_use.tool` | +| `handle_computer_use` | moved-lazy | `tools.computer_use.tool` | +| `release_computer_use_session` | moved-lazy | `tools.computer_use.tool` | +| `set_approval_callback` | moved-lazy | `tools.computer_use.tool` | + +### `tools.computer_use.cua_backend` + +| name | kind | new location | +|---|---|---| +| `CaptureResult` | moved-lazy | `tools.computer_use.backend` | +| `PureWindowsPath` | import | `pathlib` | +| `Tuple` | import | `typing` | +| `UIElement` | moved-lazy | `tools.computer_use.backend` | +| `asyncio` | import | `asyncio` | +| `base64` | import | `base64` | +| `concurrent` | import | `concurrent.futures` | +| `cua_driver_install_hint` | moved-lazy | `tools.computer_use.cua_backend_driver` | +| `cua_driver_update_check` | moved-lazy | `tools.computer_use.cua_backend_driver` | +| `deque` | import | `collections` | +| `functools` | import | `functools` | +| `json` | import | `json` | +| `re` | import | `re` | +| `shutil` | import | `shutil` | +| `tempfile` | import | `tempfile` | +| `time` | import | `time` | + +### `tools.computer_use.permissions` + +| name | kind | new location | +|---|---|---| +| `List` | import | `typing` | + +### `tools.computer_use.tool` + +| name | kind | new location | +|---|---|---| +| `struct` | import | `struct` | + +### `tools.cronjob_tools` + +| name | kind | new location | +|---|---|---| +| `effective_job_state` | moved-lazy | `cron.jobs` | +| `re` | import | `re` | + +### `tools.delegate_tool` + +| name | kind | new location | +|---|---|---| +| `DEFAULT_CHILD_TIMEOUT` | moved-lazy | `tools.delegate_tool_config` | +| `DEFAULT_MAX_SUMMARY_CHARS` | moved-lazy | `tools.delegate_tool_results` | +| `DEFAULT_TOOLSETS` | moved-lazy | `tools.delegate_tool_toolsets` | +| `FuturesTimeoutError` | import | `concurrent.futures` | +| `MAX_DEPTH` | moved-lazy | `tools.delegate_tool_config` | +| `TOOLSETS` | moved-lazy | `toolsets` | +| `base_url_hostname` | moved-lazy | `utils` | +| `contextvars` | import | `contextvars` | +| `enum` | import | `enum` | +| `file_state` | moved-lazy | `tools` | +| `json` | import | `json` | +| `os` | import | `os` | +| `re` | import | `re` | +| `request_hard_interrupt` | moved-lazy | `agent.interrupt_compat` | +| `threading` | import | `threading` | +| `urlsplit` | import | `urllib.parse` | +| `urlunsplit` | import | `urllib.parse` | + +### `tools.delegation_live_log` + +| name | kind | new location | +|---|---|---| +| `new_live_delegation_id` | restored-def | `(deleted; BASE body restored)` | + +### `tools.delegation_output_schema` + +| name | kind | new location | +|---|---|---| +| `MAX_SCHEMA_RETRIES` | restored-def | `(deleted; BASE body restored)` | + +### `tools.discord_tool` + +| name | kind | new location | +|---|---|---| +| `TYPE_CHECKING` | import | `typing` | +| `Tuple` | import | `typing` | +| `get_dynamic_schema` | restored-def | `(deleted; BASE body restored)` | + +### `tools.drive_preview_tool` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | + +### `tools.env_probe` + +| name | kind | new location | +|---|---|---| +| `os` | import | `os` | + +### `tools.environments` + +| name | kind | new location | +|---|---|---| +| `modal_utils` | unrestorable | `no top-level definition on BASE` | + +### `tools.environments.base` + +| name | kind | new location | +|---|---|---| +| `IO` | import | `typing` | +| `Protocol` | import | `typing` | +| `codecs` | import | `codecs` | +| `deque` | import | `collections` | +| `re` | import | `re` | +| `sanitize_task_id_for_path` | moved-lazy | `tools.environments.path_utils` | +| `select` | import | `select` | +| `subprocess` | import | `subprocess` | +| `windows_hide_flags` | moved-lazy | `hermes_cli._subprocess_compat` | + +### `tools.environments.managed_modal` + +| name | kind | new location | +|---|---|---| +| `BaseModalExecutionEnvironment` | unrestorable | `no top-level definition on BASE` | +| `ModalExecStart` | unrestorable | `no top-level definition on BASE` | +| `PreparedModalExec` | unrestorable | `no top-level definition on BASE` | +| `dataclass` | import | `dataclasses` | + +### `tools.environments.modal_utils` + +| name | kind | new location | +|---|---|---| +| `*` | module-stub | `(deleted)` | + +### `tools.environments.vercel_sandbox` + +| name | kind | new location | +|---|---|---| +| `dataclass` | import | `dataclasses` | + +### `tools.feishu_doc_tool` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | +| `logger` | moved-lazy | `tools.approval` | +| `logging` | import | `logging` | +| `threading` | import | `threading` | + +### `tools.feishu_drive_tool` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | +| `threading` | import | `threading` | + +### `tools.file_operations` + +| name | kind | new location | +|---|---|---| +| `Any` | import | `typing` | +| `ClassVar` | import | `typing` | +| `DEFAULT_READ_LIMIT` | moved-lazy | `tools.file_operations_common` | +| `DEFAULT_READ_OFFSET` | moved-lazy | `tools.file_operations_common` | +| `DEFAULT_SEARCH_LIMIT` | moved-lazy | `tools.file_operations_common` | +| `DEFAULT_SEARCH_OFFSET` | moved-lazy | `tools.file_operations_common` | +| `LINTERS` | moved-lazy | `tools.file_operations_lint` | +| `LintResult` | moved-lazy | `tools.file_operations_common` | +| `List` | import | `typing` | +| `MAX_FILE_SIZE` | moved-lazy | `tools.transcription_common` | +| `MAX_LINES` | restored-def | `(deleted; BASE body restored)` | +| `MAX_LINE_LENGTH` | restored-def | `(deleted; BASE body restored)` | +| `SEARCH_PRUNE_DIR_NAMES` | moved-lazy | `agent.search_policy` | +| `SearchMatch` | moved-lazy | `tools.file_operations_common` | +| `WRITE_DENIED_PATHS` | restored-def | `(deleted; BASE body restored)` | +| `WRITE_DENIED_PREFIXES` | restored-def | `(deleted; BASE body restored)` | +| `build_write_denied_paths` | restored-import | `agent.file_safety` | +| `build_write_denied_paths` | moved-lazy | `agent.file_safety` | +| `build_write_denied_prefixes` | restored-import | `agent.file_safety` | +| `build_write_denied_prefixes` | moved-lazy | `agent.file_safety` | +| `dataclass` | import | `dataclasses` | +| `field` | import | `dataclasses` | +| `posixpath` | import | `posixpath` | +| `threading` | import | `threading` | +| `tool_interrupt` | moved-lazy | `tools.interrupt` | + +### `tools.file_tools` + +| name | kind | new location | +|---|---|---| +| `PurePosixPath` | import | `pathlib` | +| `has_opaque_document_extension` | moved-lazy | `tools.binary_extensions` | +| `is_pdf_path` | moved-lazy | `tools.binary_extensions` | +| `notify_other_tool_call` | moved-lazy | `tools.file_tools_read_tracking` | +| `posixpath` | import | `posixpath` | +| `reset_file_dedup` | moved-lazy | `tools.file_tools_read_tracking` | +| `sys` | import | `sys` | + +### `tools.focus_pane_tool` + +| name | kind | new location | +|---|---|---| +| `FOCUS_PANE_SCHEMA` | restored-def | `(deleted; BASE body restored)` | +| `json` | import | `json` | + +### `tools.fuzzy_match` + +| name | kind | new location | +|---|---|---| +| `List` | import | `typing` | +| `Tuple` | import | `typing` | + +### `tools.image_generation_tool` + +| name | kind | new location | +|---|---|---| +| `is_krea_model` | restored-def | `(deleted; BASE body restored)` | + +### `tools.lazy_deps` + +| name | kind | new location | +|---|---|---| +| `feature_specs` | restored-def | `(deleted; BASE body restored)` | + +### `tools.managed_tool_gateway` + +| name | kind | new location | +|---|---|---| +| `_MANAGED_GATEWAY_VENDOR` | restored-helper | `(deleted; restored as a dependency of build_managed_media_uploader)` | +| `_MEDIA_UPLOAD_PRESIGN_TIMEOUT_SECONDS` | restored-helper | `(deleted; restored as a dependency of build_managed_media_uploader)` | +| `_MEDIA_UPLOAD_PUT_READ_TIMEOUT_SECONDS` | restored-helper | `(deleted; restored as a dependency of build_managed_media_uploader)` | +| `_MEDIA_UPLOAD_PUT_WRITE_TIMEOUT_SECONDS` | restored-helper | `(deleted; restored as a dependency of build_managed_media_uploader)` | +| `_describe_media_upload_refusal` | restored-helper | `(deleted; restored as a dependency of build_managed_media_uploader)` | +| `build_managed_media_uploader` | restored-def | `(deleted; BASE body restored)` | +| `is_managed_nous_gateway_url` | restored-helper | `(deleted; restored as a dependency of build_managed_media_uploader)` | +| `is_managed_nous_gateway_url` | restored-def | `(deleted; BASE body restored)` | +| `managed_gateway_auth_headers` | restored-helper | `(deleted; restored as a dependency of build_managed_media_uploader)` | +| `managed_gateway_auth_headers` | restored-def | `(deleted; BASE body restored)` | +| `managed_vendor_base_path` | restored-def | `(deleted; BASE body restored)` | +| `managed_vendor_endpoints` | restored-def | `(deleted; BASE body restored)` | +| `managed_vendor_upload_path` | restored-helper | `(deleted; restored as a dependency of managed_vendor_endpoints)` | +| `managed_vendor_upload_path` | restored-def | `(deleted; BASE body restored)` | +| `urlsplit` | restored-import | `urllib.parse` | +| `urlsplit` | import | `urllib.parse` | + +### `tools.mcp_oauth` + +| name | kind | new location | +|---|---|---| +| `OAuthClientInformationFull` | restored-def | `(deleted; BASE body restored)` | +| `OAuthClientMetadata` | restored-def | `(deleted; BASE body restored)` | +| `OAuthClientProvider` | restored-def | `(deleted; BASE body restored)` | +| `OAuthMetadata` | restored-def | `(deleted; BASE body restored)` | +| `OAuthToken` | restored-def | `(deleted; BASE body restored)` | +| `contextmanager` | import | `contextlib` | + +### `tools.mcp_schema_cache` + +| name | kind | new location | +|---|---|---| +| `clear_cache_entry` | restored-def | `(deleted; BASE body restored)` | +| `has_cached_entry` | restored-def | `(deleted; BASE body restored)` | + +### `tools.mcp_tool` + +| name | kind | new location | +|---|---|---| +| `Coroutine` | import | `typing` | +| `InvalidMcpUrlError` | moved-lazy | `tools.mcp_tool_errors` | +| `MCP_TOOL_NAME_PREFIX` | moved-lazy | `tools.mcp_tool_schema` | +| `NonMcpEndpointError` | moved-lazy | `tools.mcp_tool_errors` | +| `SimpleNamespace` | import | `types` | +| `Tuple` | import | `typing` | +| `asynccontextmanager` | import | `contextlib` | +| `concurrent` | import | `concurrent.futures` | +| `datetime` | import | `datetime` | +| `discover_mcp_tools` | moved-lazy | `tools.mcp_tool_discovery` | +| `errno` | import | `errno` | +| `fnmatch` | import | `fnmatch` | +| `get_mcp_status` | moved-lazy | `tools.mcp_tool_discovery` | +| `get_registered_mcp_server_names` | moved-lazy | `tools.mcp_tool_discovery` | +| `has_registered_mcp_tools` | moved-lazy | `tools.mcp_tool_discovery` | +| `is_mcp_tool_parallel_safe` | moved-lazy | `tools.mcp_tool_discovery` | +| `json` | import | `json` | +| `matches_name_filter` | moved-lazy | `tools.mcp_tool_schema` | +| `math` | import | `math` | +| `mcp_prefixed_tool_name` | moved-lazy | `tools.mcp_tool_schema` | +| `persist_agent_tool_names` | moved-lazy | `tools.mcp_tool_agent` | +| `probe_mcp_server_tools` | moved-lazy | `tools.mcp_tool_discovery` | +| `random` | import | `random` | +| `re` | import | `re` | +| `reconnect_mcp_server` | moved-lazy | `tools.mcp_tool_loop` | +| `refresh_agent_mcp_tools` | moved-lazy | `tools.mcp_tool_agent` | +| `register_mcp_servers` | moved-lazy | `tools.mcp_tool_discovery` | +| `reprobe_tool_availability` | moved-lazy | `tools.mcp_tool_agent` | +| `restore_agent_tool_prefix` | moved-lazy | `tools.mcp_tool_agent` | +| `sanitize_mcp_name_component` | moved-lazy | `tools.mcp_tool_schema` | +| `shutdown_mcp_servers` | moved-lazy | `tools.mcp_tool_lifecycle` | +| `shutil` | import | `shutil` | +| `strip_unicode_tags` | moved-lazy | `tools.ansi_strip` | +| `tool_error` | moved-lazy | `tools.registry` | +| `urlparse` | import | `urllib.parse` | + +### `tools.memory_tool` + +| name | kind | new location | +|---|---|---| +| `atomic_write_text` | moved-lazy | `utils` | +| `contextmanager` | import | `contextlib` | +| `time` | import | `time` | + +### `tools.microsoft_graph_client` + +| name | kind | new location | +|---|---|---| +| `AsyncIterator` | import | `typing` | +| `GraphCredentials` | moved-lazy | `tools.microsoft_graph_auth` | + +### `tools.open_preview_tool` + +| name | kind | new location | +|---|---|---| +| `OPEN_PREVIEW_SCHEMA` | restored-def | `(deleted; BASE body restored)` | +| `json` | import | `json` | +| `registry` | moved-lazy | `tools.registry` | + +### `tools.openrouter_client` + +| name | kind | new location | +|---|---|---| +| `get_async_client` | restored-def | `(deleted; BASE body restored)` | + +### `tools.path_security` + +| name | kind | new location | +|---|---|---| +| `logger` | moved-lazy | `tools.approval` | +| `logging` | import | `logging` | + +### `tools.preview_tool` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | +| `preview_open` | restored-def | `(deleted; BASE body restored)` | + +### `tools.process_registry` + +| name | kind | new location | +|---|---|---| +| `MAX_ACTIVE_PROCESS_AGE` | restored-def | `(deleted; BASE body restored)` | + +### `tools.react_to_message_tool` + +| name | kind | new location | +|---|---|---| +| `env_var_enabled` | moved-lazy | `utils` | + +### `tools.read_extract` + +| name | kind | new location | +|---|---|---| +| `MAX_XLSX_BYTES` | restored-def | `(deleted; BASE body restored)` | + +### `tools.read_preview_tool` + +| name | kind | new location | +|---|---|---| +| `READ_PREVIEW_SCHEMA` | restored-def | `(deleted; BASE body restored)` | +| `json` | import | `json` | +| `registry` | moved-lazy | `tools.registry` | +| `tool_error` | moved-lazy | `tools.registry` | + +### `tools.read_terminal_tool` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | + +### `tools.read_window_tool` + +| name | kind | new location | +|---|---|---| +| `json` | import | `json` | +| `tool_error` | moved-lazy | `tools.registry` | + +### `tools.send_message_tool` + +| name | kind | new location | +|---|---|---| +| `SEND_MESSAGE_SCHEMA` | restored-def | `(deleted; BASE body restored)` | +| `re` | import | `re` | +| `redact_sensitive_text` | moved-lazy | `agent.redact` | +| `time` | import | `time` | + +### `tools.skill_ledger` + +| name | kind | new location | +|---|---|---| +| `ACTOR_AGENT` | restored-def | `(deleted; BASE body restored)` | +| `ACTOR_CURATOR` | restored-def | `(deleted; BASE body restored)` | +| `ACTOR_USER` | restored-def | `(deleted; BASE body restored)` | + +### `tools.skill_linter` + +| name | kind | new location | +|---|---|---| +| `format_findings` | restored-def | `(deleted; BASE body restored)` | +| `has_errors` | restored-def | `(deleted; BASE body restored)` | + +### `tools.skill_manager_tool` + +| name | kind | new location | +|---|---|---| +| `mark_background_review_skill_read` | moved-lazy | `tools.skill_manager_guards` | + +### `tools.skill_usage` + +| name | kind | new location | +|---|---|---| +| `_suppressed_file` | restored-helper | `(deleted; restored as a dependency of add_suppressed_name)` | +| `_write_suppressed_names` | restored-helper | `(deleted; restored as a dependency of add_suppressed_name)` | +| `add_suppressed_name` | restored-def | `(deleted; BASE body restored)` | +| `agent_created_report` | restored-def | `(deleted; BASE body restored)` | +| `os` | restored-import | `os` | +| `os` | import | `os` | +| `remove_suppressed_name` | restored-def | `(deleted; BASE body restored)` | +| `tempfile` | restored-import | `tempfile` | +| `tempfile` | import | `tempfile` | + +### `tools.skillevaluator_scan` + +| name | kind | new location | +|---|---|---| +| `Optional` | import | `typing` | +| `SCANNER_NAME` | restored-def | `(deleted; BASE body restored)` | +| `scanner_available` | restored-def | `(deleted; BASE body restored)` | + +### `tools.skills_guard` + +| name | kind | new location | +|---|---|---| +| `full_content_hash` | restored-def | `(deleted; BASE body restored)` | + +### `tools.skills_hub` + +| name | kind | new location | +|---|---|---| +| `ABC` | import | `abc` | +| `BrowseShSource` | moved-lazy | `tools.skills_hub_sources` | +| `ClawHubSource` | moved-lazy | `tools.skills_hub_clawhub` | +| `GITHUB_TAP_PROVIDERS` | moved-lazy | `tools.skills_hub_github` | +| `GitHubAuth` | moved-lazy | `tools.skills_hub_github` | +| `GitHubSource` | moved-lazy | `tools.skills_hub_github` | +| `HERMES_INDEX_TTL` | moved-lazy | `tools.skills_hub_search` | +| `HERMES_INDEX_URL` | moved-lazy | `tools.skills_hub_search` | +| `HermesIndexSource` | moved-lazy | `tools.skills_hub_official` | +| `LobeHubSource` | moved-lazy | `tools.skills_hub_sources` | +| `OptionalSkillSource` | moved-lazy | `tools.skills_hub_official` | +| `PurePosixPath` | import | `pathlib` | +| `ScanResult` | moved-lazy | `tools.skills_guard` | +| `SkillBundle` | moved-lazy | `tools.skills_hub_models` | +| `SkillMeta` | moved-lazy | `tools.skills_hub_models` | +| `SkillSource` | moved-lazy | `tools.skills_hub_models` | +| `SkillsShSource` | moved-lazy | `tools.skills_hub_skillssh` | +| `TRUSTED_REPOS` | moved-lazy | `tools.skills_guard` | +| `Tuple` | import | `typing` | +| `Union` | import | `typing` | +| `UrlSource` | moved-lazy | `tools.skills_hub_sources` | +| `WellKnownSkillSource` | moved-lazy | `tools.skills_hub_sources` | +| `abstractmethod` | import | `abc` | +| `bundle_content_hash` | moved-lazy | `tools.skills_hub_install` | +| `check_for_skill_updates` | moved-lazy | `tools.skills_hub_install` | +| `content_hash` | moved-lazy | `tools.skills_guard` | +| `create_source_router` | moved-lazy | `tools.skills_hub_search` | +| `dataclass` | import | `dataclasses` | +| `field` | import | `dataclasses` | +| `github_provider_for` | moved-lazy | `tools.skills_hub_github` | +| `hashlib` | import | `hashlib` | +| `install_from_quarantine` | moved-lazy | `tools.skills_hub_install` | +| `is_excluded_skill_path` | moved-lazy | `agent.skill_utils` | +| `os` | import | `os` | +| `parallel_search_sources` | moved-lazy | `tools.skills_hub_search` | +| `quarantine_bundle` | moved-lazy | `tools.skills_hub_install` | +| `quote` | import | `urllib.parse` | +| `re` | import | `re` | +| `shutil` | import | `shutil` | +| `source_url_for_bundle` | moved-lazy | `tools.skills_hub_models` | +| `subprocess` | import | `subprocess` | +| `unified_search` | moved-lazy | `tools.skills_hub_search` | +| `uninstall_skill` | moved-lazy | `tools.skills_hub_install` | +| `unquote` | import | `urllib.parse` | +| `urlparse` | import | `urllib.parse` | +| `urlsplit` | import | `urllib.parse` | +| `urlunparse` | import | `urllib.parse` | +| `windows_hide_flags` | moved-lazy | `hermes_cli._subprocess_compat` | +| `yaml` | import | `yaml` | + +### `tools.skills_sync` + +| name | kind | new location | +|---|---|---| +| `PurePosixPath` | import | `pathlib` | +| `atomic_replace` | moved-lazy | `utils` | +| `datetime` | import | `datetime` | +| `diff_bundled_skill` | moved-lazy | `tools.skills_sync_bundled_ops` | +| `is_bundled_skills_opt_out` | restored-def | `(deleted; BASE body restored)` | +| `json` | import | `json` | +| `list_user_modified_bundled_skills` | moved-lazy | `tools.skills_sync_bundled_ops` | +| `remove_pristine_bundled_skills` | moved-lazy | `tools.skills_sync_bundled_ops` | +| `reset_bundled_skill` | moved-lazy | `tools.skills_sync_bundled_ops` | +| `restore_official_optional_skill` | moved-lazy | `tools.skills_sync_optional` | +| `set_bundled_skills_opt_out` | moved-lazy | `tools.skills_sync_bundled_ops` | +| `timezone` | import | `datetime` | + +### `tools.skills_sync_client` + +| name | kind | new location | +|---|---|---| +| `ARTIFACT_TYPE_SKILL` | moved-lazy | `tools.skills_sync_client_wire` | +| `KIND_COMMIT` | restored-def | `(deleted; BASE body restored)` | +| `KIND_TREE` | restored-def | `(deleted; BASE body restored)` | +| `MODE_DIR` | restored-def | `(deleted; BASE body restored)` | +| `MODE_EXEC` | restored-def | `(deleted; BASE body restored)` | +| `MODE_FILE` | restored-def | `(deleted; BASE body restored)` | +| `SYNC_MANIFEST_ENTRY_NAME` | moved-lazy | `tools.skills_sync_client_wire` | +| `SYNC_MANIFEST_TYPE` | restored-def | `(deleted; BASE body restored)` | +| `SYNC_MANIFEST_VERSION` | moved-lazy | `tools.skills_sync_client_wire` | +| `WIRE_VERSION` | moved-lazy | `tools.skills_sync_client_wire` | +| `canonical_json_bytes` | moved-lazy | `tools.skills_sync_client_wire` | +| `datetime` | import | `datetime` | +| `dev_gate_open` | restored-def | `(deleted; BASE body restored)` | +| `hashlib` | import | `hashlib` | +| `maybe_pull_org_skills` | moved-lazy | `tools.skills_sync_client_org` | +| `org_head_ref` | moved-lazy | `tools.skills_sync_client_org` | +| `org_skill_is_locally_modified` | moved-lazy | `tools.skills_sync_client_org` | +| `org_sync_available` | restored-def | `(deleted; BASE body restored)` | +| `parse_sync_manifest` | moved-lazy | `tools.skills_sync_client_wire` | +| `propose_skill` | moved-lazy | `tools.skills_sync_client_org` | +| `pull_org_skills` | moved-lazy | `tools.skills_sync_client_org` | +| `time` | import | `time` | +| `timezone` | import | `datetime` | +| `user_conflict_ref` | restored-def | `(deleted; BASE body restored)` | +| `wire_address` | moved-lazy | `tools.skills_sync_client_wire` | + +### `tools.skills_tool` + +| name | kind | new location | +|---|---|---| +| `Enum` | import | `enum` | +| `Set` | import | `typing` | +| `display_hermes_home` | moved-lazy | `hermes_constants` | +| `env_var_enabled` | moved-lazy | `utils` | +| `re` | import | `re` | +| `threading` | import | `threading` | + +### `tools.slash_confirm` + +| name | kind | new location | +|---|---|---| +| `asyncio` | import | `asyncio` | +| `resolve_sync_compat` | restored-def | `(deleted; BASE body restored)` | + +### `tools.terminal_scope` + +| name | kind | new location | +|---|---|---| +| `install_refusal_scope` | restored-def | `(deleted; BASE body restored)` | +| `terminal_scope` | restored-def | `(deleted; BASE body restored)` | + +### `tools.terminal_tool` + +| name | kind | new location | +|---|---|---| +| `Path` | import | `pathlib` | +| `cleanup_vm` | moved-lazy | `tools.terminal_tool_lifecycle` | +| `env_var_enabled` | moved-lazy | `utils` | +| `get_active_env` | moved-lazy | `tools.terminal_tool_lifecycle` | +| `has_direct_modal_credentials` | moved-lazy | `tools.tool_backend_helpers` | +| `importlib` | import | `importlib.util` | +| `is_interrupted` | moved-lazy | `tools.interrupt` | +| `is_managed_tool_gateway_ready` | moved-lazy | `tools.managed_tool_gateway` | +| `is_persistent_env` | moved-lazy | `tools.terminal_tool_lifecycle` | +| `nous_tool_gateway_unavailable_message` | moved-lazy | `tools.tool_backend_helpers` | +| `platform` | import | `platform` | +| `re` | import | `re` | +| `resolve_modal_backend_state` | moved-lazy | `tools.tool_backend_helpers` | +| `shlex` | import | `shlex` | +| `shutil` | import | `shutil` | +| `stat` | import | `stat` | +| `strip_inert_heredoc_bodies` | moved-lazy | `tools.shell_heredoc` | +| `subprocess` | import | `subprocess` | +| `sys` | import | `sys` | + +### `tools.tool_result_storage` + +| name | kind | new location | +|---|---|---| +| `HEREDOC_MARKER` | restored-def | `(deleted; BASE body restored)` | +| `uuid` | import | `uuid` | + +### `tools.tool_search` + +| name | kind | new location | +|---|---|---| +| `Literal` | import | `typing` | +| `build_catalog_listing` | restored-def | `(deleted; BASE body restored)` | +| `copy` | import | `copy` | +| `field` | import | `dataclasses` | +| `re` | import | `re` | +| `snowballstemmer` | import | `snowballstemmer` | +| `threading` | import | `threading` | + +### `tools.transcription_tools` + +| name | kind | new location | +|---|---|---| +| `COMMAND_STT_OUTPUT_FORMATS` | moved-lazy | `tools.transcription_command` | +| `COMMON_LOCAL_BIN_DIRS` | moved-lazy | `tools.transcription_common` | +| `DEFAULT_COMMAND_STT_LANGUAGE` | moved-lazy | `tools.transcription_command` | +| `DEFAULT_COMMAND_STT_OUTPUT_FORMAT` | moved-lazy | `tools.transcription_command` | +| `DEFAULT_COMMAND_STT_TIMEOUT_SECONDS` | moved-lazy | `tools.transcription_command` | +| `DEFAULT_LOCAL_STT_LANGUAGE` | moved-lazy | `tools.transcription_common` | +| `ELEVENLABS_STT_BASE_URL` | moved-lazy | `tools.transcription_common` | +| `GROQ_BASE_URL` | moved-lazy | `tools.transcription_common` | +| `GROQ_MODELS` | moved-lazy | `tools.transcription_common` | +| `LOCAL_NATIVE_AUDIO_FORMATS` | moved-lazy | `tools.transcription_common` | +| `MAX_FILE_SIZE` | moved-lazy | `tools.transcription_common` | +| `OPENAI_BASE_URL` | moved-lazy | `tools.transcription_common` | +| `OPENAI_MODELS` | moved-lazy | `tools.transcription_common` | +| `SUPPORTED_FORMATS` | moved-lazy | `tools.transcription_common` | +| `XAI_STT_BASE_URL` | moved-lazy | `tools.transcription_common` | +| `managed_nous_tools_enabled` | moved-lazy | `tools.tool_backend_helpers` | +| `nous_tool_gateway_unavailable_message` | moved-lazy | `tools.tool_backend_helpers` | +| `platform` | import | `platform` | +| `queue` | import | `queue` | +| `re` | import | `re` | +| `resolve_managed_tool_gateway` | moved-lazy | `tools.managed_tool_gateway` | +| `resolve_openai_audio_api_key` | moved-lazy | `tools.tool_backend_helpers` | +| `shlex` | import | `shlex` | +| `subprocess` | import | `subprocess` | +| `tempfile` | import | `tempfile` | +| `urljoin` | import | `urllib.parse` | +| `windows_hide_flags` | moved-lazy | `hermes_cli._subprocess_compat` | + +### `tools.tts_tool` + +| name | kind | new location | +|---|---|---| +| `AudioDeliveryProfile` | moved-lazy | `tools.tts_tool_delivery` | +| `COMMAND_TTS_OUTPUT_FORMATS` | moved-lazy | `tools.tts_command_provider` | +| `DEFAULT_COMMAND_TTS_MAX_TEXT_LENGTH` | moved-lazy | `tools.tts_command_provider` | +| `DEFAULT_COMMAND_TTS_OUTPUT_FORMAT` | moved-lazy | `tools.tts_command_provider` | +| `DEFAULT_COMMAND_TTS_TIMEOUT_SECONDS` | moved-lazy | `tools.tts_command_provider` | +| `DEFAULT_DEEPINFRA_TTS_VOICE` | moved-lazy | `tools.tts_tool_openai` | +| `DEFAULT_EDGE_VOICE` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_ELEVENLABS_MODEL_ID` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_ELEVENLABS_STREAMING_MODEL_ID` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_ELEVENLABS_VOICE_ID` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_GEMINI_AUDIO_TAGS` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_GEMINI_TTS_BASE_URL` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_GEMINI_TTS_MODEL` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_GEMINI_TTS_VOICE` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_KITTENTTS_MODEL` | moved-lazy | `tools.tts_tool_local` | +| `DEFAULT_KITTENTTS_VOICE` | moved-lazy | `tools.tts_tool_local` | +| `DEFAULT_MINIMAX_BASE_URL` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_MINIMAX_CN_BASE_URL` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_MINIMAX_MODEL` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_MINIMAX_VOICE_ID` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_MISTRAL_TTS_MODEL` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_MISTRAL_TTS_VOICE_ID` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_OPENAI_BASE_URL` | moved-lazy | `tools.tts_tool_openai` | +| `DEFAULT_OPENAI_MODEL` | moved-lazy | `tools.tts_tool_openai` | +| `DEFAULT_OPENAI_VOICE` | moved-lazy | `tools.tts_tool_openai` | +| `DEFAULT_PIPER_VOICE` | moved-lazy | `tools.tts_tool_local` | +| `DEFAULT_XAI_AUTO_SPEECH_TAGS` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_XAI_BASE_URL` | moved-lazy | `tools.xai_http` | +| `DEFAULT_XAI_BIT_RATE` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_XAI_LANGUAGE` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_XAI_OPTIMIZE_STREAMING_LATENCY_DEFAULT` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_XAI_SAMPLE_RATE` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_XAI_SPEED_DEFAULT` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_XAI_SPEED_MAX` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_XAI_SPEED_MIN` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_XAI_TEXT_NORMALIZATION_DEFAULT` | moved-lazy | `tools.tts_tool_providers` | +| `DEFAULT_XAI_VOICE_ID` | moved-lazy | `tools.tts_tool_providers` | +| `ELEVENLABS_MODEL_MAX_TEXT_LENGTH` | moved-lazy | `tools.tts_tool_delivery` | +| `FALLBACK_MAX_TEXT_LENGTH` | moved-lazy | `tools.tts_tool_delivery` | +| `Future` | import | `concurrent.futures` | +| `GEMINI_AUDIO_TAG_REWRITE_TASK` | moved-lazy | `tools.tts_tool_providers` | +| `GEMINI_TTS_CHANNELS` | restored-def | `(deleted; BASE body restored)` | +| `GEMINI_TTS_SAMPLE_RATE` | restored-def | `(deleted; BASE body restored)` | +| `GEMINI_TTS_SAMPLE_WIDTH` | restored-def | `(deleted; BASE body restored)` | +| `Iterator` | import | `typing` | +| `MANAGED_OPENAI_TTS_MODELS` | moved-lazy | `tools.tts_tool_openai` | +| `MAX_TEXT_LENGTH` | moved-lazy | `gateway.platforms.bluebubbles` | +| `PROVIDER_MAX_TEXT_LENGTH` | moved-lazy | `tools.tts_tool_delivery` | +| `TTS_RESPONSE_BODY_CHUNK_BYTES` | moved-lazy | `tools.tts_tool_providers` | +| `TTS_RESPONSE_BODY_LIMIT_BYTES` | moved-lazy | `tools.tts_tool_providers` | +| `ThreadPoolExecutor` | import | `concurrent.futures` | +| `Tuple` | import | `typing` | +| `acquire_tts_lease` | moved-lazy | `tools.tts_tool_lifecycle` | +| `base64` | import | `base64` | +| `dataclass` | import | `dataclasses` | +| `field` | import | `dataclasses` | +| `hermes_xai_user_agent` | moved-lazy | `tools.xai_http` | +| `managed_nous_tools_enabled` | moved-lazy | `tools.tool_backend_helpers` | +| `nous_tool_gateway_unavailable_message` | moved-lazy | `tools.tool_backend_helpers` | +| `platform` | import | `platform` | +| `queue` | import | `queue` | +| `re` | import | `re` | +| `read_selection` | moved-lazy | `tools.tool_backend_helpers` | +| `release_tts_lease` | moved-lazy | `tools.tts_tool_lifecycle` | +| `release_tts_provider` | moved-lazy | `tools.tts_tool_lifecycle` | +| `resolve_managed_tool_gateway` | moved-lazy | `tools.managed_tool_gateway` | +| `resolve_openai_audio_api_key` | moved-lazy | `tools.tool_backend_helpers` | +| `selection_error` | moved-lazy | `tools.tool_backend_helpers` | +| `shlex` | import | `shlex` | +| `shutil` | import | `shutil` | +| `stream_tts_to_speaker` | moved-lazy | `tools.tts_tool_speaker` | +| `subprocess` | import | `subprocess` | +| `threading` | import | `threading` | +| `time` | import | `time` | +| `tts_lease_holders` | moved-lazy | `tools.tts_tool_lifecycle` | +| `urljoin` | import | `urllib.parse` | +| `urlparse` | import | `urllib.parse` | +| `uuid` | import | `uuid` | +| `warm_tts_provider` | moved-lazy | `tools.tts_tool_lifecycle` | +| `windows_hide_flags` | moved-lazy | `hermes_cli._subprocess_compat` | + +### `tools.url_safety` + +| name | kind | new location | +|---|---|---| +| `has_sensitive_query_params` | restored-def | `(deleted; BASE body restored)` | +| `ssrf_safe_async_http_transport` | restored-def | `(deleted; BASE body restored)` | +| `ssrf_safe_http_transport` | restored-def | `(deleted; BASE body restored)` | + +### `tools.vision_tools` + +| name | kind | new location | +|---|---|---| +| `contextlib` | import | `contextlib` | +| `sys` | import | `sys` | +| `threading` | import | `threading` | + +### `tools.voice_mode` + +| name | kind | new location | +|---|---|---| +| `DEFAULT_TTS_ECHO_SIMILARITY_THRESHOLD` | moved-lazy | `tools.voice_mode_transcript` | +| `DEFAULT_VOICE_STOP_PHRASES` | moved-lazy | `tools.voice_mode_transcript` | +| `MIN_FRAGMENT_LENGTH_FOR_ECHO` | moved-lazy | `tools.voice_mode_transcript` | +| `WHISPER_HALLUCINATIONS` | moved-lazy | `tools.voice_mode_transcript` | +| `difflib` | import | `difflib` | +| `is_tts_echo` | moved-lazy | `tools.voice_mode_transcript` | +| `re` | import | `re` | +| `voice_stop_hint` | moved-lazy | `tools.voice_mode_transcript` | + +### `tools.web_result_cache` + +| name | kind | new location | +|---|---|---| +| `Any` | import | `typing` | +| `List` | import | `typing` | + +### `tools.web_tools` + +| name | kind | new location | +|---|---|---| +| `DEFAULT_EXTRACT_CHAR_LIMIT` | moved-lazy | `tools.web_tools_truncate` | +| `Dict` | import | `typing` | +| `Firecrawl` | moved-lazy | `plugins.web.firecrawl.provider` | +| `MAX_STORED_TEXT_CHARS` | moved-lazy | `tools.web_tools_truncate` | +| `TYPE_CHECKING` | import | `typing` | +| `asyncio` | import | `asyncio` | +| `build_vendor_gateway_url` | moved-lazy | `tools.managed_tool_gateway` | +| `httpx` | import | `httpx` | +| `managed_nous_tools_enabled` | moved-lazy | `tools.tool_backend_helpers` | +| `normalize_url_for_request` | moved-lazy | `tools.url_safety` | +| `nous_tool_gateway_unavailable_message` | moved-lazy | `tools.tool_backend_helpers` | +| `prefers_gateway` | moved-lazy | `tools.tool_backend_helpers` | +| `re` | import | `re` | +| `resolve_managed_tool_gateway` | moved-lazy | `tools.managed_tool_gateway` | +| `sensitive_query_param_name` | moved-lazy | `tools.url_safety` | +| `sys` | import | `sys` | + +### `tools.website_policy` + +| name | kind | new location | +|---|---|---| +| `invalidate_cache` | restored-def | `(deleted; BASE body restored)` | + +### `tools.write_approval` + +| name | kind | new location | +|---|---|---| +| `is_background` | restored-def | `(deleted; BASE body restored)` | + +### `tools.yuanbao_tools` + +| name | kind | new location | +|---|---|---| +| `List` | import | `typing` | +| `Optional` | import | `typing` | + +### `toolsets` + +| name | kind | new location | +|---|---|---| +| `resolve_multiple_toolsets` | restored-def | `(deleted; BASE body restored)` | + +### `tui_gateway.compute_host` + +| name | kind | new location | +|---|---|---| +| `HostSession` | restored-def | `(deleted; BASE body restored)` | +| `SpikeAgent` | restored-helper | `(deleted; restored as a dependency of HostSession)` | +| `SpikeAgent` | restored-def | `(deleted; BASE body restored)` | +| `dataclass` | restored-import | `dataclasses` | +| `dataclass` | import | `dataclasses` | +| `field` | restored-import | `dataclasses` | +| `field` | import | `dataclasses` | +| `request_hard_interrupt` | moved-lazy | `agent.interrupt_compat` | + +### `tui_gateway.hosted_room_driver` + +| name | kind | new location | +|---|---|---| +| `contextlib` | import | `contextlib` | +| `null_turn_lock` | restored-def | `(deleted; BASE body restored)` | + +### `tui_gateway.hosted_room_service` + +| name | kind | new location | +|---|---|---| +| `hashlib` | import | `hashlib` | + +### `tui_gateway.mcp_rpc_helpers` + +| name | kind | new location | +|---|---|---| +| `Optional` | import | `typing` | +| `Tuple` | import | `typing` | +| `resolve_profile` | restored-def | `(deleted; BASE body restored)` | + +### `tui_gateway.methods_browser_control` + +| name | kind | new location | +|---|---|---| +| `BROWSER_CONTROL_PROTOCOL_VERSION` | moved-lazy | `gateway.browser_control_broker` | +| `browser_control_protocol_supported` | moved-lazy | `gateway.browser_control_broker` | +| `filter_browser_control_capabilities` | moved-lazy | `gateway.browser_control_broker` | + +### `tui_gateway.methods_prompt` + +| name | kind | new location | +|---|---|---| +| `types` | import | `types` | diff --git a/acp_adapter/auth.py b/acp_adapter/auth.py index a1ad9982d4..a56a76179b 100644 --- a/acp_adapter/auth.py +++ b/acp_adapter/auth.py @@ -49,3 +49,14 @@ def build_auth_methods() -> list[Any]: "Use this when Hermes has not been configured on this machine yet."), )) return methods + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def has_provider() -> bool: + """Return True if Hermes can resolve any runtime provider credentials.""" + return detect_provider() is not None +# ---- END PLUGIN-COMPAT ---- diff --git a/acp_adapter/edit_approval.py b/acp_adapter/edit_approval.py index 5c38efc9e0..0e78915848 100644 --- a/acp_adapter/edit_approval.py +++ b/acp_adapter/edit_approval.py @@ -226,3 +226,19 @@ def make_acp_edit_approval_requester( return getattr(outcome, "outcome", None) == "selected" and getattr(outcome, "option_id", None) == "allow_once" return _requester + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from concurrent.futures import TimeoutError as FutureTimeout # noqa: F401,E402 + +def clear_edit_approval_requester() -> None: + """Clear the current requester; primarily used by tests.""" + + _EDIT_APPROVAL_REQUESTER.set(None) + +def get_edit_approval_requester() -> EditApprovalRequester | None: + return _EDIT_APPROVAL_REQUESTER.get() +# ---- END PLUGIN-COMPAT ---- diff --git a/acp_adapter/events.py b/acp_adapter/events.py index 020a0b1b40..88d1b351ac 100644 --- a/acp_adapter/events.py +++ b/acp_adapter/events.py @@ -174,3 +174,11 @@ def make_step_cb( tool_call_ids.pop(tool_name, None) return _step + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/acp_adapter/server.py b/acp_adapter/server.py index 083b1c45bc..c8dceaf94f 100644 --- a/acp_adapter/server.py +++ b/acp_adapter/server.py @@ -972,3 +972,38 @@ class HermesACPAgent(SlashCommandsMixin, acp.Agent): self.session_manager.save_session(session_id) logger.info("Session %s: config option %s updated", session_id, config_id) return SetSessionConfigOptionResponse(config_options=[]) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from acp.schema import AgentThoughtChunk # noqa: F401,E402 +from acp.schema import AudioContentBlock # noqa: F401,E402 +from acp.schema import AvailableCommand # noqa: F401,E402 +from acp.schema import AvailableCommandsUpdate # noqa: F401,E402 +from acp.schema import BlobResourceContents # noqa: F401,E402 +from acp.schema import EmbeddedResourceContentBlock # noqa: F401,E402 +from acp.schema import ImageContentBlock # noqa: F401,E402 +from pathlib import Path # noqa: F401,E402 +from acp.schema import ResourceContentBlock # noqa: F401,E402 +from acp.schema import TextResourceContents # noqa: F401,E402 +from acp.schema import UnstructuredCommandInput # noqa: F401,E402 +import base64 # noqa: F401,E402 +import json # noqa: F401,E402 +from urllib.parse import unquote # noqa: F401,E402 +from urllib.parse import urlparse # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'ACP_MAX_MODELS_PER_PROVIDER': ('acp_adapter.model_catalog', 'ACP_MAX_MODELS_PER_PROVIDER'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/acp_adapter/session.py b/acp_adapter/session.py index de52eeb628..4f28742011 100644 --- a/acp_adapter/session.py +++ b/acp_adapter/session.py @@ -422,3 +422,11 @@ class SessionManager: # ACP stdio: stdout is protocol-only JSON-RPC; agent chatter goes to stderr. agent._print_fn = _acp_stderr_print return agent + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from threading import Lock # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/agent_init.py b/agent/agent_init.py index cb393b21cc..5039763e36 100644 --- a/agent/agent_init.py +++ b/agent/agent_init.py @@ -2304,3 +2304,23 @@ def init_agent( __all__ = ["init_agent"] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'ToolGuardrailDecision': ('agent.tool_guardrails', 'ToolGuardrailDecision'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/agent_runtime_helpers.py b/agent/agent_runtime_helpers.py index 613a9b03e7..1d7e287c30 100644 --- a/agent/agent_runtime_helpers.py +++ b/agent/agent_runtime_helpers.py @@ -3210,3 +3210,30 @@ __all__ = [ "extract_api_error_context", "apply_pending_steer_to_tool_results", "_iter_pool_sockets", "force_close_tcp_sockets", ] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def agent_runtime_owns_post_tool_hook(agent: Any, function_name: str) -> bool: + """Return True when an agent-level tool path emits its own post hook.""" + if function_name in AGENT_RUNTIME_POST_HOOK_TOOL_NAMES: + return True + if getattr(agent, "_context_engine_tool_names", None) and function_name in agent._context_engine_tool_names: + return True + memory_manager = getattr(agent, "_memory_manager", None) + return bool(memory_manager and memory_manager.has_tool(function_name)) + +def intent_ack_continuation_enabled(agent) -> bool: + """Whether intent-ack continuation should fire at all for this turn. + + The ``codex_ack_continuations < 2`` per-turn cap and the + ``looks_like_codex_intermediate_ack`` detector are applied by the caller; + this only decides the on/off gate. Callers that also need to know whether + the workspace requirement applies should use ``intent_ack_continuation_mode`` + directly (``"codex_only"`` ⇒ require_workspace=True, ``"all"`` ⇒ False). + """ + return intent_ack_continuation_mode(agent) != "off" +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/anthropic_adapter.py b/agent/anthropic_adapter.py index 4db2e2ea9b..9bfd6952c7 100644 --- a/agent/anthropic_adapter.py +++ b/agent/anthropic_adapter.py @@ -673,3 +673,45 @@ def create_anthropic_message( "%sAnthropic Messages stream unavailable; falling back to messages.create(): %s", log_prefix, exc ) return messages_api.create(**{k: v for k, v in api_kwargs.items() if k != "stream"}) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import Path # noqa: F401,E402 +from typing import Tuple # noqa: F401,E402 +import copy # noqa: F401,E402 +import json # noqa: F401,E402 +import os # noqa: F401,E402 +import platform # noqa: F401,E402 +import secrets # noqa: F401,E402 +import stat # noqa: F401,E402 +from urllib.parse import urlparse # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'CredentialPersistError': ('agent.anthropic_credentials', 'CredentialPersistError'), + 'base_url_host_matches': ('utils', 'base_url_host_matches'), + 'base_url_hostname': ('utils', 'base_url_hostname'), + 'claude_code_credentials_path': ('agent.anthropic_credentials', 'claude_code_credentials_path'), + 'get_hermes_home': ('hermes_constants', 'get_hermes_home'), + 'is_claude_code_token_valid': ('agent.anthropic_credentials', 'is_claude_code_token_valid'), + 'is_rotation_consumed_uncommitted': ('agent.anthropic_credentials', 'is_rotation_consumed_uncommitted'), + 'mark_rotation_consumed_uncommitted': ('agent.anthropic_credentials', 'mark_rotation_consumed_uncommitted'), + 'read_claude_code_credentials': ('agent.anthropic_credentials', 'read_claude_code_credentials'), + 'read_hermes_oauth_credentials': ('agent.anthropic_credentials', 'read_hermes_oauth_credentials'), + 'refresh_anthropic_oauth_pure': ('agent.anthropic_credentials', 'refresh_anthropic_oauth_pure'), + 'resolve_anthropic_token': ('agent.anthropic_credentials', 'resolve_anthropic_token'), + 'run_hermes_oauth_login_pure': ('agent.anthropic_credentials', 'run_hermes_oauth_login_pure'), + 'run_oauth_setup_token': ('agent.anthropic_credentials', 'run_oauth_setup_token'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/aux_accounting.py b/agent/aux_accounting.py index 052c026afc..81d648128a 100644 --- a/agent/aux_accounting.py +++ b/agent/aux_accounting.py @@ -89,3 +89,14 @@ def record_aux_usage( ) except Exception: logger.debug("Aux usage recording failed (non-fatal)", exc_info=True) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def get_accounting_context() -> Optional[tuple]: + """Return ``(session_db, session_id)`` for the active turn, or ``None``.""" + return _accounting.get() +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 4172e4f515..b62809ab4c 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -7319,3 +7319,32 @@ async def _async_call_llm_impl( if result is _RERAISE_ORIGINAL: raise return result + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import Path # noqa: F401,E402 +import copy # noqa: F401,E402 + +NOUS_EXTRA_BODY = _nous_extra_body() + +def get_async_text_auxiliary_client(task: str = "", *, main_runtime: Optional[Dict[str, Any]] = None): + """Return (async_client, model_slug) for async consumers. + + For standard providers returns (AsyncOpenAI, model). For Codex returns + (AsyncCodexAuxiliaryClient, model) which wraps the Responses API. + Returns (None, None) when no provider is available. + """ + provider, model, base_url, api_key, api_mode = _resolve_task_provider_model(task or None) + return resolve_provider_client( + provider, + model=model, + async_mode=True, + explicit_base_url=base_url, + explicit_api_key=api_key, + api_mode=api_mode, + main_runtime=main_runtime, + ) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/backend_identity.py b/agent/backend_identity.py index 5ba5556068..ecf9f2f66b 100644 --- a/agent/backend_identity.py +++ b/agent/backend_identity.py @@ -128,3 +128,24 @@ def should_skip_candidate( True when it is the same backend as ``failed`` along the axis ``scope`` invalidated. Every fallback/dedup/skip site must call this.""" return _SCOPE_PREDICATES.get(scope, same_deployment)(candidate, failed) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +_REASON_SCOPES = { + "auth error": FailureScope.CREDENTIAL, + "payment error": FailureScope.CREDENTIAL, + "rate limit": FailureScope.MODEL, + "model incompatible with route": FailureScope.MODEL, + "invalid provider response": FailureScope.MODEL, + "connection error": FailureScope.MODEL, + "timeout": FailureScope.MODEL, +} + +def classify_failure_scope(reason: Optional[str]) -> FailureScope: + """Map a human-readable failure reason to the identity axis it kills.""" + return _REASON_SCOPES.get((reason or "").strip().lower(), FailureScope.MODEL) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/background_review.py b/agent/background_review.py index 2daf9ced40..6342c17579 100644 --- a/agent/background_review.py +++ b/agent/background_review.py @@ -1120,3 +1120,39 @@ __all__ = [ "_MEMORY_REVIEW_PROMPT", "_SKILL_REVIEW_PROMPT", "_COMBINED_REVIEW_PROMPT", "load_background_review_settings", "spawn_background_review_thread", "summarize_background_review_actions", "build_memory_write_metadata", ] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import Path # noqa: F401,E402 + +def is_background_review_enabled( + task_cfg: Optional[Dict[str, Any]] = None, +) -> bool: + """Return whether automatic post-turn background review may spawn. + + Controlled by ``auxiliary.background_review.enabled`` (default ``true``). + Explicit ``/refine`` (``focus`` set) bypasses this gate — same contract as + zeroing the nudge intervals, which stops automatic forks but leaves manual + refine working (issue #87250). + + Prefer :func:`load_background_review_settings` at the spawn call site so + the task block is not re-read on the same turn. + """ + if task_cfg is not None: + try: + from utils import is_truthy_value + + return is_truthy_value(task_cfg.get("enabled"), default=True) + except Exception: + logger.warning( + "Failed to interpret background_review.enabled; leaving " + "automatic review enabled (fail-open)", + exc_info=True, + ) + return True + enabled, _ = load_background_review_settings() + return enabled +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/bedrock_adapter.py b/agent/bedrock_adapter.py index f02ecffe30..9c19a8a789 100644 --- a/agent/bedrock_adapter.py +++ b/agent/bedrock_adapter.py @@ -998,3 +998,109 @@ def get_bedrock_context_length(model_id: str, region: str = "", probe: bool = Tr return probed matches = [key for key in BEDROCK_CONTEXT_LENGTHS if key in model_id.lower()] return BEDROCK_CONTEXT_LENGTHS[max(matches, key=len)] if matches else BEDROCK_DEFAULT_CONTEXT_LENGTH + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +CONTEXT_OVERFLOW_PATTERNS = [ + re.compile(r"ValidationException.*(?:input is too long|max input token|input token.*exceed)", re.IGNORECASE), + re.compile(r"ValidationException.*(?:exceeds? the (?:maximum|max) (?:number of )?(?:input )?tokens)", re.IGNORECASE), + re.compile(r"ModelStreamErrorException.*(?:Input is too long|too many input tokens)", re.IGNORECASE), +] + +OVERLOAD_PATTERNS = [ + re.compile(r"ModelNotReadyException", re.IGNORECASE), + re.compile(r"ModelTimeoutException", re.IGNORECASE), + re.compile(r"InternalServerException", re.IGNORECASE), +] + +THROTTLE_PATTERNS = [ + re.compile(r"ThrottlingException", re.IGNORECASE), + re.compile(r"Too many concurrent requests", re.IGNORECASE), + re.compile(r"ServiceQuotaExceededException", re.IGNORECASE), +] + +def call_converse_stream( + region: str, + model: str, + messages: List[Dict], + tools: Optional[List[Dict]] = None, + max_tokens: Optional[int] = 4096, + temperature: Optional[float] = None, + top_p: Optional[float] = None, + stop_sequences: Optional[List[str]] = None, + guardrail_config: Optional[Dict] = None, +) -> SimpleNamespace: + """Call Bedrock ConverseStream API and return an OpenAI-compatible response. + + Consumes the full stream and returns the assembled response. For true + streaming with delta callbacks, use ``iter_converse_stream()`` instead. + """ + client = _get_bedrock_runtime_client(region) + kwargs = build_converse_kwargs( + model=model, + messages=messages, + tools=tools, + max_tokens=max_tokens, + temperature=temperature, + top_p=top_p, + stop_sequences=stop_sequences, + guardrail_config=guardrail_config, + ) + + try: + response = client.converse_stream(**kwargs) + except Exception as exc: + retry_kwargs = recover_from_cache_point_rejection(exc, kwargs) + if retry_kwargs is not None: + return normalize_converse_stream_events( + client.converse_stream(**retry_kwargs) + ) + if is_streaming_access_denied_error(exc): + # IAM allows bedrock:InvokeModel but not + # InvokeModelWithResponseStream — permanent for this session. + # Fall back to the non-streaming converse() path. + logger.info( + "bedrock: converse_stream denied by IAM on (region=%s, model=%s) — " + "falling back to non-streaming converse().", + region, model, + ) + return normalize_converse_response(client.converse(**kwargs)) + if is_stale_connection_error(exc): + logger.warning( + "bedrock: stale-connection error on converse_stream(region=%s, " + "model=%s): %s — evicting cached client so the next call reconnects.", + region, model, type(exc).__name__, + ) + invalidate_runtime_client(region) + raise + return normalize_converse_stream_events(response) + +def is_context_overflow_error(error_message: str) -> bool: + """Return True if the error indicates the input context was too large. + + When this returns True, the agent should compress context and retry + rather than treating it as a fatal error. + """ + return any(p.search(error_message) for p in CONTEXT_OVERFLOW_PATTERNS) + +def classify_bedrock_error(error_message: str) -> str: + """Classify a Bedrock error for retry/failover decisions. + + Returns: + - ``"context_overflow"`` — input too long, compress and retry + - ``"rate_limit"`` — throttled, backoff and retry + - ``"overloaded"`` — model temporarily unavailable, retry with delay + - ``"unknown"`` — unclassified error + """ + if is_context_overflow_error(error_message): + return "context_overflow" + if any(p.search(error_message) for p in THROTTLE_PATTERNS): + return "rate_limit" + if any(p.search(error_message) for p in OVERLOAD_PATTERNS): + return "overloaded" + return "unknown" +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/bounded_response.py b/agent/bounded_response.py index d0d5ac8ed8..8114b8ff7c 100644 --- a/agent/bounded_response.py +++ b/agent/bounded_response.py @@ -77,3 +77,26 @@ def read_streaming_error_body( "bounded error-body read: capped at %d bytes (max=%d)", sum(len(c) for c in chunks), max_bytes, ) return b"".join(chunks).decode("utf-8", errors="replace") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Optional # noqa: F401,E402 + +def read_error_body_or_default( + response: httpx.Response, + *, + max_bytes: int = DEFAULT_ERROR_BODY_MAX_BYTES, + timeout_s: float = DEFAULT_ERROR_BODY_TIMEOUT_S, +) -> Optional[str]: + """Like ``read_streaming_error_body`` but returns ``None`` on empty body. + + Convenience for callers that distinguish "no body" from "empty string". + """ + text = read_streaming_error_body( + response, max_bytes=max_bytes, timeout_s=timeout_s + ) + return text or None +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/browser_provider.py b/agent/browser_provider.py index cf9989bcd3..62180f2c23 100644 --- a/agent/browser_provider.py +++ b/agent/browser_provider.py @@ -55,3 +55,12 @@ class BrowserProvider(ProviderBase): def emergency_cleanup(self, session_id: str) -> None: """Best-effort teardown from atexit / signal handlers. Must tolerate missing credentials and network errors; must not raise.""" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Any # noqa: F401,E402 +from typing import Optional # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/browser_registry.py b/agent/browser_registry.py index cde417c5f0..51ae1117d0 100644 --- a/agent/browser_registry.py +++ b/agent/browser_registry.py @@ -59,3 +59,26 @@ def _resolve(configured: Optional[str]) -> Optional[BrowserProvider]: ): return provider return None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +from typing import List # noqa: F401,E402 +import threading # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'hermes_home_key': ('hermes_constants', 'hermes_home_key'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/codex_runtime.py b/agent/codex_runtime.py index 12460c2132..e5b2820458 100644 --- a/agent/codex_runtime.py +++ b/agent/codex_runtime.py @@ -969,3 +969,21 @@ __all__ = [ "run_codex_app_server_turn", "run_codex_stream", "_consume_codex_event_stream", "make_codex_app_server_event_bridge", ] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def run_codex_create_stream_fallback(agent, api_kwargs: dict, client: Any = None): + """Backward-compatible alias for the unified event-driven path. + + Historically this was the fallback when the SDK's high-level + ``responses.stream(...)`` helper raised on shape drift. The primary + path now does exactly what the fallback did, so this just forwards. + Kept as a public symbol because tests and a small number of call sites + still reference it by name. + """ + return run_codex_stream(agent, api_kwargs, client=client) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/coding_context.py b/agent/coding_context.py index dfdf546a4e..5a6b3be08d 100644 --- a/agent/coding_context.py +++ b/agent/coding_context.py @@ -554,3 +554,34 @@ def build_coding_workspace_block(cwd: Optional[str | Path] = None) -> str: if f.context_files: lines.append(f"- Context files: {', '.join(f.context_files)}") return "\n".join(lines) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def coding_system_blocks( + *, + platform: Optional[str] = None, + cwd: Optional[str | Path] = None, + config: Optional[dict[str, Any]] = None, + model: Optional[str] = None, +) -> list[str]: + """Stable system-prompt blocks for the current posture (empty when general). + + ``model`` steers the brief's edit-format nudge toward the model's family. + """ + return resolve_runtime_mode( + platform=platform, cwd=cwd, config=config, model=model + ).system_blocks() + +_PROFILES: dict[str, ContextProfile] = { + GENERAL_PROFILE.name: GENERAL_PROFILE, + CODING_PROFILE.name: CODING_PROFILE, +} + +def get_profile(name: str) -> ContextProfile: + """Return a registered profile, falling back to ``general``.""" + return _PROFILES.get(name, GENERAL_PROFILE) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/context_compressor.py b/agent/context_compressor.py index d53770436b..a151697c0b 100644 --- a/agent/context_compressor.py +++ b/agent/context_compressor.py @@ -4894,3 +4894,23 @@ def reference_handoff_would_drive_next_model_call(messages: Optional[List[Dict[s def is_user_originated_turn(message: Any) -> bool: """True for human-authored user turns (not compaction scaffolding); dispatchers must use this, not a bare role check.""" return user_originated_turn_view(message) is not None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'tool_result_id_variants': ('agent.message_sanitization', 'tool_result_id_variants'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/conversation_compression.py b/agent/conversation_compression.py index e691015e72..18465dc71f 100644 --- a/agent/conversation_compression.py +++ b/agent/conversation_compression.py @@ -4013,3 +4013,13 @@ __all__ = [ "check_compression_model_feasibility", "replay_compression_warning", "compress_context", "try_shrink_image_parts_in_messages", ] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +class CompressionExecutorSaturatedError(RuntimeError): + """All compression pool slots are occupied; submission was refused.""" +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index f855c28fc5..a7776cd5c7 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -1536,3 +1536,63 @@ def run_conversation( __all__ = ["run_conversation"] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 +import random # noqa: F401,E402 +import ssl # noqa: F401,E402 +import sys # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'COMPRESSION_RETRY_CONTEXT_REDUCED_STATUS_TEMPLATE': ('agent.conversation_compression', 'COMPRESSION_RETRY_CONTEXT_REDUCED_STATUS_TEMPLATE'), + 'COMPRESSION_RETRY_MESSAGES_STATUS_TEMPLATE': ('agent.conversation_compression', 'COMPRESSION_RETRY_MESSAGES_STATUS_TEMPLATE'), + 'COMPRESSION_RETRY_TOKENS_STATUS_TEMPLATE': ('agent.conversation_compression', 'COMPRESSION_RETRY_TOKENS_STATUS_TEMPLATE'), + 'COMPRESSION_RETRY_TOO_LARGE_STATUS_TEMPLATE': ('agent.conversation_compression', 'COMPRESSION_RETRY_TOO_LARGE_STATUS_TEMPLATE'), + 'FailoverReason': ('agent.error_classifier', 'FailoverReason'), + 'KawaiiSpinner': ('agent.display', 'KawaiiSpinner'), + 'PARTIAL_STREAM_STUB_ID': ('hermes_constants', 'PARTIAL_STREAM_STUB_ID'), + 'PRE_API_COMPRESSION_STATUS_TEMPLATE': ('agent.conversation_compression', 'PRE_API_COMPRESSION_STATUS_TEMPLATE'), + 'adaptive_rate_limit_backoff': ('agent.retry_utils', 'adaptive_rate_limit_backoff'), + 'anchored_context_tokens': ('agent.model_metadata', 'anchored_context_tokens'), + 'automatic_compaction_status_message': ('agent.context_engine', 'automatic_compaction_status_message'), + 'capture_usage_anchor': ('agent.model_metadata', 'capture_usage_anchor'), + 'classify_api_error': ('agent.error_classifier', 'classify_api_error'), + 'close_interrupted_tool_sequence': ('agent.message_sanitization', 'close_interrupted_tool_sequence'), + 'coalesce_tool_call_id': ('agent.message_sanitization', 'coalesce_tool_call_id'), + 'compose_user_api_content': ('agent.turn_context', 'compose_user_api_content'), + 'compression_blocked_transiently': ('agent.conversation_compression', 'compression_blocked_transiently'), + 'compression_skipped_due_to_lock': ('agent.conversation_compression', 'compression_skipped_due_to_lock'), + 'context_compression_timed_out': ('agent.conversation_compression', 'context_compression_timed_out'), + 'conversation_history_after_compression': ('agent.conversation_compression', 'conversation_history_after_compression'), + 'env_var_enabled': ('utils', 'env_var_enabled'), + 'estimate_messages_tokens_rough': ('agent.model_metadata', 'estimate_messages_tokens_rough'), + 'estimate_request_tokens_rough': ('agent.model_metadata', 'estimate_request_tokens_rough'), + 'estimate_usage_cost': ('agent.usage_pricing', 'estimate_usage_cost'), + 'get_context_length_from_provider_error': ('agent.model_metadata', 'get_context_length_from_provider_error'), + 'has_incomplete_scratchpad': ('agent.trajectory', 'has_incomplete_scratchpad'), + 'is_output_cap_error': ('agent.model_metadata', 'is_output_cap_error'), + 'is_repetition_dominated': ('agent.repetition_guard', 'is_repetition_dominated'), + 'is_zai_coding_overload_error': ('agent.retry_utils', 'is_zai_coding_overload_error'), + 'jittered_backoff': ('agent.retry_utils', 'jittered_backoff'), + 'normalize_usage': ('agent.usage_pricing', 'normalize_usage'), + 'parse_available_output_tokens_from_error': ('agent.model_metadata', 'parse_available_output_tokens_from_error'), + 'reanchor_current_turn_user_idx': ('agent.turn_context', 'reanchor_current_turn_user_idx'), + 'save_context_length': ('agent.model_metadata', 'save_context_length'), + 'serialized_messages_bytes': ('agent.message_sanitization', 'serialized_messages_bytes'), + 'splice_provider_projection': ('agent.provider_projection', 'splice_provider_projection'), + 'zai_coding_overload_retry_ceiling': ('agent.retry_utils', 'zai_coding_overload_retry_ceiling'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/credential_sources.py b/agent/credential_sources.py index 0d728c4813..1dc2947700 100644 --- a/agent/credential_sources.py +++ b/agent/credential_sources.py @@ -259,3 +259,23 @@ _REGISTRY: List[RemovalStep] = [ description="Custom provider config.yaml api_key field", ), ] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'register': ('tools.slash_confirm', 'register'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/display.py b/agent/display.py index 598553bfae..26aff9c146 100644 --- a/agent/display.py +++ b/agent/display.py @@ -1083,3 +1083,14 @@ def get_cute_tool_message(tool_name: str, args: dict, duration: float, result: s safe_name = tool_name[:9] if isinstance(tool_name, str) and tool_name else "tool" safe_duration = f"{duration:.1f}s" if isinstance(duration, (int, float)) else "done" return f"┊ ⚡ {safe_name:9} completed {safe_duration}" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def get_friendly_tool_labels() -> bool: + """Return whether friendly tool labels are enabled.""" + return _friendly_tool_labels +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/error_surface.py b/agent/error_surface.py index 089c649ced..77e7cd6393 100644 --- a/agent/error_surface.py +++ b/agent/error_surface.py @@ -155,3 +155,12 @@ def build_error_surface_from_exception(exc: BaseException, provider: str = "", m except Exception: # pragma: no cover — never break the error path logger.debug("error_surface: exception classification failed", exc_info=True) return None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +LAYER_RUNTIME = "runtime" +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/estop.py b/agent/estop.py index 86c5d585ee..b7cc51cc5e 100644 --- a/agent/estop.py +++ b/agent/estop.py @@ -136,3 +136,11 @@ def check_paused(component: str, logger: logging.Logger) -> bool: component, suffix, sentinel_path(), ) return True + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/file_safety.py b/agent/file_safety.py index d3a981d090..937a73cc09 100644 --- a/agent/file_safety.py +++ b/agent/file_safety.py @@ -358,3 +358,86 @@ def get_container_mirror_warning(path: str, mirror_prefix: str | None = None) -> "the real HERMES_HOME.", "after explicit user direction, retry", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +PROFILE_SCOPED_AREAS = ("skills", "plugins", "cron", "memories") + +def classify_cross_profile_target(path: str) -> Optional[dict]: + """Classify a write target as cross-profile if it lands in another + profile's scoped area (skills/plugins/cron/memories). + + Returns ``None`` when the target is outside Hermes scope, or is inside + the ACTIVE profile, or doesn't hit a profile-scoped area. Otherwise + returns a dict with: + + * ``active_profile``: name of the profile the agent is running as + * ``target_profile``: name of the profile the path belongs to + * ``area``: which scoped area (``"skills"``, ``"plugins"``, etc.) + * ``target_path``: the resolved path string + + The caller decides what to do with the result — surface a warning to + the model, prompt the user, or (with explicit consent / + ``cross_profile=True``) proceed anyway. + """ + try: + target = Path(os.path.expanduser(str(path))).resolve() + root_real = _hermes_root_path().resolve() + except (OSError, RuntimeError): + return None + + target_profile: Optional[str] = None + area: Optional[str] = None + + try: + rel = target.relative_to(root_real) + except ValueError: + return None + + parts = rel.parts + if not parts: + return None + + if parts[0] in PROFILE_SCOPED_AREAS: + # ``//...`` → default profile. + target_profile = "default" + area = parts[0] + elif ( + parts[0] == "profiles" + and len(parts) >= 3 + and parts[2] in PROFILE_SCOPED_AREAS + ): + # ``/profiles///...`` → named profile. + target_profile = parts[1] + area = parts[2] + else: + return None + + active_profile = _resolve_active_profile_name() + if target_profile == active_profile: + # In-profile write — not a cross-profile event. + return None + + return { + "active_profile": active_profile, + "target_profile": target_profile, + "area": area, + "target_path": str(target), + } + +def get_cross_profile_warning(path: str) -> Optional[str]: + """RETIRED (maintainer decision): always returns ``None``. + + The cross-profile write guard was removed — profiles were never + isolated (same OS user; the terminal tool writes anywhere), so the + block was ceremony that cost every schema real tokens and taught a + bypass arg. The system prompt's active-profile hint remains the only + steering; the classifier below survives for that hint and for + diagnostics. Kept as a stub so external callers/plugins fail soft. + """ + return None +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/image_gen_provider.py b/agent/image_gen_provider.py index 250c825ad0..4b49e899c0 100644 --- a/agent/image_gen_provider.py +++ b/agent/image_gen_provider.py @@ -129,3 +129,13 @@ def error_response( "success": False, "image": None, "error": error, "error_type": error_type, "model": model, "prompt": prompt, "aspect_ratio": aspect_ratio, "provider": provider, } + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import base64 # noqa: F401,E402 +import datetime # noqa: F401,E402 +import uuid # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/image_gen_registry.py b/agent/image_gen_registry.py index 7c6d2b3365..87d66c5c60 100644 --- a/agent/image_gen_registry.py +++ b/agent/image_gen_registry.py @@ -45,3 +45,26 @@ def get_active_provider() -> Optional[ImageGenProvider]: return available[0] fal = snapshot.get("fal") return fal if fal is not None and _available(fal) else None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +from typing import List # noqa: F401,E402 +import threading # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'hermes_home_key': ('hermes_constants', 'hermes_home_key'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/learning_graph_render.py b/agent/learning_graph_render.py index dd3fe3eaba..8200680c8d 100644 --- a/agent/learning_graph_render.py +++ b/agent/learning_graph_render.py @@ -417,3 +417,25 @@ def render_frames(payload: dict[str, Any], *, cols: int = 80, rows: int = 16, fr "buckets": _bucket_rows(buckets, payload), "summary": build_summary(payload), "axis": axis_labels(payload), "count": len(payload.get("nodes", [])), "cols": cols, "rows": rows, } + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +Grid = list # list[Row] + + +_PLUGIN_COMPAT_LAZY = { + 'Run': ('hermes_cli.kanban_db', 'Run'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/lsp/eventlog.py b/agent/lsp/eventlog.py index d963c5f948..69d0b9f22a 100644 --- a/agent/lsp/eventlog.py +++ b/agent/lsp/eventlog.py @@ -128,3 +128,29 @@ __all__ = [ "log_server_unavailable", "log_timeout", "log_server_error", "log_spawn_failed", "log_reaped", "reset_announce_caches", ] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +_announced_no_server: set = set() # keys: (server_id,) + +def _announce_once(bucket: set, key: Tuple) -> bool: + """Return True if *key* has not been announced for *bucket* yet. + + Atomically marks the key as announced so concurrent callers + cannot both win the race and double-log. + """ + with _announce_lock: + if key in bucket: + return False + bucket.add(key) + return True + +def log_no_server_configured(server_id: str) -> None: + """No spawn recipe for this language. WARNING once.""" + if _announce_once(_announced_no_server, (server_id,)): + _emit(server_id, logging.WARNING, "no server configured") +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/lsp/protocol.py b/agent/lsp/protocol.py index 9596e26b82..dc2d7380a0 100644 --- a/agent/lsp/protocol.py +++ b/agent/lsp/protocol.py @@ -136,3 +136,12 @@ __all__ = [ "encode_message", "read_message", "make_request", "make_notification", "make_response", "make_error_response", "classify_message", ] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +ERROR_REQUEST_CANCELLED = -32800 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/monitoring/cron_health.py b/agent/monitoring/cron_health.py index ecd9e739fd..8d87e0c6f5 100644 --- a/agent/monitoring/cron_health.py +++ b/agent/monitoring/cron_health.py @@ -168,3 +168,24 @@ __all__ = [ "CronHealthSnapshot", "build_cron_health_snapshot", "classify_cron_error", "emit_execution_state", "project_execution_event", ] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import hashlib # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'GatewayHealthSnapshot': ('agent.monitoring.gateway_health', 'GatewayHealthSnapshot'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/monitoring/emitter.py b/agent/monitoring/emitter.py index d3cf44a27d..326e83570a 100644 --- a/agent/monitoring/emitter.py +++ b/agent/monitoring/emitter.py @@ -165,3 +165,12 @@ def reset_emitter_for_tests(emitter: Optional[MonitoringEmitter] = None) -> None __all__ = ["MonitoringEmitter", "get_emitter", "emit", "reset_emitter_for_tests"] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +TelemetryEmitter = MonitoringEmitter +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/monitoring/gateway_health.py b/agent/monitoring/gateway_health.py index 6e6e016dc1..bd042e0b9a 100644 --- a/agent/monitoring/gateway_health.py +++ b/agent/monitoring/gateway_health.py @@ -329,3 +329,24 @@ __all__ = [ "GatewayMetric", "GatewayHealthSnapshot", "GatewayDiagnosticLogHandler", "build_gateway_health_snapshot", "classify_gateway_error", "source_logger_for_export", ] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def redact_gateway_message(message: Any) -> str: + """Redact gateway diagnostic free text for operator-owned export. + + Single scrub path: everything goes through + ``agent.monitoring.redaction.redact_for_export`` (unconditional + secrets + PII), then is length-bounded. + """ + try: + from agent.monitoring.redaction import redact_for_export + redacted = redact_for_export(str(message or "")) or "" + except Exception: + redacted = "[redaction-unavailable]" + return redacted[:500] +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/monitoring/gateway_health_export.py b/agent/monitoring/gateway_health_export.py index e0f7a3cdf5..338d24b294 100644 --- a/agent/monitoring/gateway_health_export.py +++ b/agent/monitoring/gateway_health_export.py @@ -334,3 +334,11 @@ def start_gateway_health_export(config: Dict[str, Any]) -> GatewayHealthExportRu __all__ = ["GatewayHealthExportRuntime", "start_gateway_health_export"] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import re # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/nous_rate_guard.py b/agent/nous_rate_guard.py index 3b87c8677a..3747dcb9ae 100644 --- a/agent/nous_rate_guard.py +++ b/agent/nous_rate_guard.py @@ -162,3 +162,24 @@ def _has_exhausted_bucket_in_object(state: Any) -> bool: if _is_exhausted(remaining, reset): return True return False + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import tempfile # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'atomic_replace': ('utils', 'atomic_replace'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/outbound_webhooks.py b/agent/outbound_webhooks.py index 14cb117e8e..2a637bae24 100644 --- a/agent/outbound_webhooks.py +++ b/agent/outbound_webhooks.py @@ -335,3 +335,13 @@ def _deliver(delivery: Dict[str, Any]) -> None: "outbound webhook delivery failed after %d attempt(s) (event=%s target=%s): %s", MAX_DELIVERY_ATTEMPTS, event, label, last_error, ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import Path # noqa: F401,E402 +from datetime import datetime # noqa: F401,E402 +from datetime import timezone # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/pet/generate/atlas.py b/agent/pet/generate/atlas.py index 0e456b65f9..f94ee5f399 100644 --- a/agent/pet/generate/atlas.py +++ b/agent/pet/generate/atlas.py @@ -619,3 +619,24 @@ def _check_atlas_cells(atlas) -> tuple[list[str], list[str], list[str]]: if residue: errors.append(f"{residue} transparent pixels retain RGB residue") return errors, warnings, filled_states + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import io # noqa: F401,E402 +import io # noqa: F401,E402 + +COLUMNS = max(count for _, _, count in ROW_SPECS) + +FRAME_COUNTS: dict[str, int] = {state: count for state, _, count in ROW_SPECS} + +ROWS = len(ROW_SPECS) + +def atlas_to_webp_bytes(atlas) -> bytes: + """Encode an atlas image to lossless WebP bytes (the on-disk pet format).""" + buf = io.BytesIO() + atlas.save(buf, format="WEBP", lossless=True, quality=100, method=6, exact=True) + return buf.getvalue() +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/prompt_builder.py b/agent/prompt_builder.py index 340547f973..a328c652e8 100644 --- a/agent/prompt_builder.py +++ b/agent/prompt_builder.py @@ -1586,3 +1586,24 @@ def build_context_files_prompt( return "" return ("# Project Context\n\nThe following project context files have been loaded and should be followed:\n\n" + "\n".join(sections)) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import List # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'org_id_of_path': ('agent.skill_utils', 'org_id_of_path'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/prompt_cache_boundary.py b/agent/prompt_cache_boundary.py index 6d0fa76f54..dde1f0750f 100644 --- a/agent/prompt_cache_boundary.py +++ b/agent/prompt_cache_boundary.py @@ -56,3 +56,15 @@ def find_stable_prefix(content: str) -> Optional[str]: if best is not None: _prefixes.move_to_end(best) # after the scan: never mutate mid-iteration return best + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def clear_stable_prefixes() -> None: + """Test isolation helper.""" + with _lock: + _prefixes.clear() +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/proxy_sources/iron_proxy.py b/agent/proxy_sources/iron_proxy.py index f7c76df9d4..5807ef76e8 100644 --- a/agent/proxy_sources/iron_proxy.py +++ b/agent/proxy_sources/iron_proxy.py @@ -1036,3 +1036,11 @@ __all__ = [ "merge_mappings", "mint_proxy_token", "reload_proxy", "start_proxy", "stop_proxy", "write_mappings", "write_proxy_config", ] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import stat # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/reasoning_effort.py b/agent/reasoning_effort.py index 8c67bbd1aa..711517f8fe 100644 --- a/agent/reasoning_effort.py +++ b/agent/reasoning_effort.py @@ -133,3 +133,12 @@ def requested_effort(reasoning_config: Optional[dict]) -> Optional[str]: if not isinstance(reasoning_config, dict) or reasoning_config.get("enabled") is False: return None return str(reasoning_config.get("effort") or "").strip().lower() or None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +CODEX_RESPONSES_EFFORTS: tuple[str, ...] = CODEX_GPT56_EFFORTS +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/relay_llm.py b/agent/relay_llm.py index d61b2d84f8..01a52e4208 100644 --- a/agent/relay_llm.py +++ b/agent/relay_llm.py @@ -880,3 +880,11 @@ def _run_awaitable( if _has_running_event_loop(): raise RuntimeError(loop_error) return asyncio.run(value) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from dataclasses import dataclass # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/relay_runtime.py b/agent/relay_runtime.py index 3102f61fed..a204ec5b76 100644 --- a/agent/relay_runtime.py +++ b/agent/relay_runtime.py @@ -1248,3 +1248,91 @@ def _reset_for_tests() -> None: HOST_REGISTRY.shutdown_all() _PLUGIN_CONFIGURATION.reset_for_tests() _PROFILE_KEY_CACHE.clear() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from enum import auto # noqa: F401,E402 + +def emit_mark( + name: str, + *, + session_id: str, + data: Any = None, + metadata: Any = None, +) -> bool: + """Emit a fail-open Relay mark under a Hermes session.""" + runtime = get_runtime(create=False) + if runtime is None: + return False + try: + return runtime.emit_mark( + name, + {"session_id": session_id}, + data=data, + metadata=metadata, + ) + except Exception: + logger.warning("Hermes Relay mark failed: %s", name, exc_info=True) + return False + +def ensure_session(*, session_id: str, **context: Any) -> RelaySession | None: + """Create or return the shared Relay session used by Hermes core.""" + runtime = get_runtime() + if runtime is None: + return None + try: + return runtime.ensure_session({"session_id": session_id, **context}) + except Exception: + logger.warning("Hermes Relay session initialization failed", exc_info=True) + return None + +def get_host( + *, + create: bool = True, + profile_key: str | None = None, +) -> RelayHost | None: + """Return the explicit real or reduced-capability host for a profile.""" + return HOST_REGISTRY.for_profile(profile_key, create=create) + +def get_session_handle(session_id: str) -> Any: + """Return the shared Relay handle for direct core instrumentation.""" + runtime = get_runtime(create=False) + return None if runtime is None else runtime.get_session_handle(session_id) + +def run_in_session( + session_id: str, + callback: Callable[..., Any], + *args: Any, + **kwargs: Any, +) -> Any: + """Run a scope, LLM, or tool API against a shared Hermes session.""" + runtime = get_runtime() + if runtime is None: + raise RuntimeError("Hermes Relay runtime is unavailable") + session = runtime.get_session(session_id) + if session is None: + session = runtime.ensure_session({"session_id": session_id}) + if session is None: + raise RuntimeError("Hermes Relay session is unavailable") + return runtime.run_in_session(session, callback, *args, **kwargs) + +async def run_in_session_async( + session_id: str, + callback: Callable[..., Any], + *args: Any, + **kwargs: Any, +) -> Any: + """Await a Relay operation inside a shared Hermes session context.""" + runtime = get_runtime() + if runtime is None: + raise RuntimeError("Hermes Relay runtime is unavailable") + session = runtime.get_session(session_id) + if session is None: + session = runtime.ensure_session({"session_id": session_id}) + if session is None: + raise RuntimeError("Hermes Relay session is unavailable") + return await runtime.run_in_session_async(session, callback, *args, **kwargs) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/relay_tools.py b/agent/relay_tools.py index ce00788b99..44d06a1653 100644 --- a/agent/relay_tools.py +++ b/agent/relay_tools.py @@ -104,3 +104,12 @@ def _run_awaitable(value: Any) -> Any: return relay_llm._run_awaitable( value, loop_error="Synchronous Hermes Relay tool execution cannot run on an active event-loop thread", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import asyncio # noqa: F401,E402 +import inspect # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/review_idle_queue.py b/agent/review_idle_queue.py index 8d3fb52f4c..8a4d35328b 100644 --- a/agent/review_idle_queue.py +++ b/agent/review_idle_queue.py @@ -201,3 +201,11 @@ def _managed_server_idle() -> bool: # Module singleton — one queue per process, like the load-progress watcher. QUEUE = ReviewIdleQueue() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import List # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/secret_sources/_cache.py b/agent/secret_sources/_cache.py index aaddb87bd3..33cdd826ef 100644 --- a/agent/secret_sources/_cache.py +++ b/agent/secret_sources/_cache.py @@ -186,3 +186,24 @@ class SecretCache(Generic[K]): def clear(self, home_path: Optional[Path] = None) -> None: self.memory.clear() self.disk.clear(home_path) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'FetchResult': ('agent.secret_sources.base', 'FetchResult'), + 'is_valid_env_name': ('agent.secret_sources.base', 'is_valid_env_name'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/secret_sources/bitwarden.py b/agent/secret_sources/bitwarden.py index a219a30d13..118f370765 100644 --- a/agent/secret_sources/bitwarden.py +++ b/agent/secret_sources/bitwarden.py @@ -526,3 +526,112 @@ def clear_caches(home_path: Optional[Path] = None) -> None: _reset_cache_for_tests = clear_caches + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import stat # noqa: F401,E402 + +def apply_bitwarden_secrets( + *, + enabled: bool, + access_token_env: str = "BWS_ACCESS_TOKEN", + project_id: str = "", + override_existing: bool = False, + cache_ttl_seconds: float = 300, + auto_install: bool = True, + server_url: str = "", + home_path: Optional[Path] = None, + encrypted_cache_enabled: bool = False, + encrypted_cache_max_stale_seconds: float = 0, +) -> FetchResult: + """Pull secrets from BSM and set them on ``os.environ``. + + This is the function ``load_hermes_dotenv()`` calls after the .env + files have loaded. It is intentionally defensive — any failure + returns a :class:`FetchResult` with ``error`` set; it never raises. + + ``server_url`` selects the Bitwarden region or self-hosted endpoint + (e.g. ``https://vault.bitwarden.eu`` for EU Cloud). Empty string + means use ``bws``'s default (US Cloud). + + Parameters mirror the ``secrets.bitwarden.*`` config keys so the + caller can just splat the dict in. + """ + result = FetchResult() + + if not enabled: + return result + + access_token = os.environ.get(access_token_env, "").strip() + if not access_token: + result.error = ( + f"secrets.bitwarden.enabled is true but {access_token_env} is " + "not set. Run `hermes secrets bitwarden setup`." + ) + return result + + if not project_id: + result.error = ( + "secrets.bitwarden.project_id is empty. " + "Run `hermes secrets bitwarden setup`." + ) + return result + + binary = find_bws(install_if_missing=auto_install) + result.binary_path = binary + if binary is None: + result.error = ( + "bws binary not available and auto-install is disabled. " + "Run `hermes secrets bitwarden setup` to install." + ) + return result + + try: + secrets, warnings = fetch_bitwarden_secrets( + access_token=access_token, + project_id=project_id, + binary=binary, + cache_ttl_seconds=cache_ttl_seconds, + server_url=server_url, + home_path=home_path, + encrypted_cache_enabled=encrypted_cache_enabled, + encrypted_cache_max_stale_seconds=encrypted_cache_max_stale_seconds, + ) + except RuntimeError as exc: + result.error = str(exc) + return result + + result.secrets = secrets + result.warnings.extend(warnings) + + for key, value in secrets.items(): + if key == access_token_env: + # Don't let BSM clobber the very token we used to fetch + # itself — that would be a footgun if someone stored the + # token as a BSM secret too. + result.skipped.append(key) + continue + if not override_existing and os.environ.get(key): + result.skipped.append(key) + continue + os.environ[key] = value + result.applied.append(key) + + return result + + +_PLUGIN_COMPAT_LAZY = { + 'DiskCache': ('agent.secret_sources._cache', 'DiskCache'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/secret_sources/command.py b/agent/secret_sources/command.py index 43af583071..f2043f5277 100644 --- a/agent/secret_sources/command.py +++ b/agent/secret_sources/command.py @@ -190,3 +190,192 @@ class CommandSource(SecretSource): return result result.secrets = secrets return result + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def apply_command_secrets( + *, + command: str, + override_existing: bool = False, + timeout_seconds: float = _COMMAND_TIMEOUT_SECONDS, + max_output_bytes: int = _MAX_OUTPUT_BYTES, + home_path: Optional[Path] = None, +) -> FetchResult: + """Run the helper once at startup and set its KEY=VALUE output on + ``os.environ``. + + LEGACY shim retained for API symmetry with ``apply_bitwarden_secrets``; + the startup path goes through :class:`CommandSource` + the registry + orchestrator instead (which owns precedence and the environ writes). + """ + result = FetchResult() + + command = (command or "").strip() + if not command: + result.error = ( + "secrets.command.enabled is true but secrets.command.command is " + "empty. Set the helper command in config.yaml." + ) + return result + + if _is_windows(): + result.warnings.append( + "the 'command' secret source is POSIX-only (needs /bin/sh); " + "skipping on Windows" + ) + return result + + # The list/enumerate path: run the helper exactly ONCE with an empty + # HERMES_SECRET_KEY and parse its stdout as a dotenv blob. + stdout = _run_helper(command, "", timeout_seconds, max_output_bytes) + if stdout is None: + # _run_helper already logged structured fields to stderr. + result.warnings.append( + "helper command failed at startup; no secrets applied " + "(process env / .env values remain in effect)" + ) + return result + + secrets = _parse_dotenv_map(stdout) + result.secrets = secrets + if not secrets: + result.warnings.append( + "helper output was not a KEY=VALUE map; nothing applied at " + "startup (a bare-value helper still resolves single keys on demand)" + ) + return result + + for key, value in secrets.items(): + if value.strip() == "": + # Whitespace-only placeholder entries are "no value" — applying + # them would flow into an Authorization header → guaranteed 401. + result.skipped.append(key) + continue + if not override_existing and os.environ.get(key): + # Process env / .env win — same precedence as bitwarden. + result.skipped.append(key) + continue + os.environ[key] = value + result.applied.append(key) + + return result + +def parse_secret_output(stdout: str, wanted_key: str) -> Optional[str]: + """Parse a secret-fetch helper's stdout. Supports BOTH shapes: + + * a bare value (single secret): the whole trimmed stdout is the value. + * a dotenv blob (KEY=VALUE lines): parse them and return the entry for + ``wanted_key``. + + Mirrors the TS ``parseSecretOutput`` exactly, including the cross-key + misroute guard and the base64-padding disambiguation. + """ + text = stdout.replace("\r\n", "\n") + lines = text.split("\n") + + # 1. Exact dotenv match wins: scan for a `wanted_key=...` line. This + # is deterministic and never returns another key's value. + dotenv_lines = [ + line + for line in (raw.strip() for raw in lines) + if line and not line.startswith("#") and _ENV_LINE.match(line) + ] + for line in dotenv_lines: + m = _ENV_LINE.match(line) + assert m is not None # filtered above + if m.group(1) == wanted_key: + value = unquote_dotenv_value(m.group(2)) + # Whitespace-only (e.g. a quoted `K=" "` placeholder) is "no + # value": it would otherwise flow into an Authorization header + # → guaranteed 401. + return value if value.strip() != "" else None + + # 2. The output is a multi-key dotenv dump that does NOT contain the + # wanted key → None, rather than mis-returning an unrelated line as + # a bare value. Only >=2 env-shaped lines count as a dump: a SINGLE + # non-matching env-shaped line falls through to the bare-value + # branch, because a bare secret can itself match the KEY=VALUE shape + # (e.g. base64 with '=' padding, "dGVzdA==") and must not be + # misclassified as a dump. + if len(dotenv_lines) > 1: + return None + + # 3. Otherwise treat the whole output as a single bare value (a per-key + # helper that printed just the secret). Trim first so whitespace-only + # output (a ' '/'\t' placeholder entry) resolves to None, never a "key". + value = text.strip() + if value == "": + return None + + # SECURITY (S2): a single env-shaped line for a DIFFERENT key must not + # be returned as the wanted secret. A sloppy helper (e.g. `head -1 + # env-file`, or a grep that matched the wrong line) emitting + # `OTHER_KEY=realvalue` would otherwise flow — key name, '=' and the + # OTHER key's value — into an Authorization header sent to the WANTED + # key's endpoint: cross-provider credential leakage, not just a 401. + # Disambiguation from a bare base64 secret: base64 padding only ever + # produces an env-shaped line whose "value" part is empty or all '=' + # (`dGVzdA==` → key `dGVzdA`, value `=`), so a non-trivial value part + # after a non-matching key means a misrouted dotenv entry → None. + env_shaped = _ENV_LINE.match(value) + if ( + env_shaped + and env_shaped.group(1) != wanted_key + and re.fullmatch(r"=*", env_shaped.group(2).strip()) is None + ): + return None + return value + +def get_command_secret( + *, + command: str, + key: str, + timeout_seconds: float = _COMMAND_TIMEOUT_SECONDS, + max_output_bytes: int = _MAX_OUTPUT_BYTES, +) -> Optional[str]: + """Resolve a single secret by running the helper with the key in + ``HERMES_SECRET_KEY``. Returns None on any failure — never raises.""" + command = (command or "").strip() + if not command: + return None + stdout = _run_helper(command, key, timeout_seconds, max_output_bytes) + if stdout is None: + return None + return parse_secret_output(stdout, key) + +def list_command_secrets( + *, + command: str, + timeout_seconds: float = _COMMAND_TIMEOUT_SECONDS, + max_output_bytes: int = _MAX_OUTPUT_BYTES, +) -> Dict[str, str]: + """Enumerate secrets by running the helper ONCE with an empty key. + + Returns the dotenv map ONLY when the helper emits a KEY=VALUE blob; + a bare-value helper returns ``{}``. Never raises. + """ + command = (command or "").strip() + if not command: + return {} + stdout = _run_helper(command, "", timeout_seconds, max_output_bytes) + if stdout is None: + return {} + return _parse_dotenv_map(stdout) + + +_PLUGIN_COMPAT_LAZY = { + 'get_source_environment': ('agent.secret_sources.base', 'get_source_environment'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/secret_sources/onepassword.py b/agent/secret_sources/onepassword.py index ed39bb63e9..4a10914b37 100644 --- a/agent/secret_sources/onepassword.py +++ b/agent/secret_sources/onepassword.py @@ -335,3 +335,24 @@ def clear_caches(home_path: Optional[Path] = None) -> None: _reset_cache_for_tests = clear_caches + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import hashlib # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'DiskCache': ('agent.secret_sources._cache', 'DiskCache'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/shell_hooks.py b/agent/shell_hooks.py index e552740cf6..38304130cd 100644 --- a/agent/shell_hooks.py +++ b/agent/shell_hooks.py @@ -590,3 +590,11 @@ def run_once(spec: ShellHookSpec, kwargs: Dict[str, Any]) -> Dict[str, Any]: result = _spawn(spec, _serialize_payload(spec.event, kwargs)) result["parsed"] = _evaluate_result(spec, result) return result + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import shlex # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/skill_bundles.py b/agent/skill_bundles.py index 8fcd581834..b1c922050e 100644 --- a/agent/skill_bundles.py +++ b/agent/skill_bundles.py @@ -204,3 +204,11 @@ def delete_bundle(name: str) -> Path: def get_bundle(name: str) -> Optional[Dict[str, Any]]: """Look up a bundle by name (slug-normalized).""" return get_skill_bundles().get(f"/{_slugify(name)}") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import re # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/skill_utils.py b/agent/skill_utils.py index 7c33210e7f..fe0b06c69f 100644 --- a/agent/skill_utils.py +++ b/agent/skill_utils.py @@ -767,3 +767,20 @@ def parse_qualified_name(name: str) -> Tuple[Optional[str], str]: def is_valid_namespace(candidate: Optional[str]) -> bool: """Check whether *candidate* is a valid namespace (``[a-zA-Z0-9_-]+``).""" return bool(candidate) and bool(_NAMESPACE_RE.match(candidate)) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def get_scan_ordered_skills_dirs() -> List[Path]: + """All skill dirs in precedence order: project → local → external. + + First-wins name deduplication over this order gives project skills + priority over profile-local and external ones. + """ + dirs = list(get_project_skills_dirs()) + dirs.extend(get_all_skills_dirs()) + return dirs +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/ssl_guard.py b/agent/ssl_guard.py index 79b2b4e765..8b045a5577 100644 --- a/agent/ssl_guard.py +++ b/agent/ssl_guard.py @@ -60,3 +60,19 @@ def verify_ca_bundle() -> None: except Exception as exc: raise _ssl_err(f"certifi is not importable: {exc}") from exc _validate_bundle_path("certifi", str(certifi.where()), require_substantial=True) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def verify_ca_bundle_with_fallback() -> None: + """Backward-compatible wrapper for older call sites. + + The old PR name mentioned a platform fallback, but allowing startup with a + broken certifi bundle still leaves httpx/OpenAI and requests call sites + failing later. Keep the wrapper name but enforce the same check. + """ + verify_ca_bundle() +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/system_prompt.py b/agent/system_prompt.py index 3c47a66340..125aadaba0 100644 --- a/agent/system_prompt.py +++ b/agent/system_prompt.py @@ -709,3 +709,23 @@ def format_tools_for_system_message(agent: Any) -> str: __all__ = ["build_system_prompt_parts", "build_system_prompt", "invalidate_system_prompt", "restore_plugin_prompt_sections", "format_tools_for_system_message"] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'OPENAI_MODEL_EXECUTION_GUIDANCE': ('agent.prompt_builder', 'OPENAI_MODEL_EXECUTION_GUIDANCE'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/terminal_env_registry.py b/agent/terminal_env_registry.py index e2619448c9..4b7b974800 100644 --- a/agent/terminal_env_registry.py +++ b/agent/terminal_env_registry.py @@ -81,3 +81,25 @@ def plugin_strip_env_keys() -> frozenset: except Exception: logger.debug("Terminal environment provider strip_env_keys raised", exc_info=True) return frozenset(keys) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +import threading # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'hermes_home_key': ('hermes_constants', 'hermes_home_key'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/transcript_repair.py b/agent/transcript_repair.py index 3b1c7ae3da..a1bed300f3 100644 --- a/agent/transcript_repair.py +++ b/agent/transcript_repair.py @@ -84,3 +84,11 @@ def sync_flushed_message_markers(batch_msgs: List[Dict[str, Any]], batch_rows: L written["_row_id"] = row["_row_id"] if "_canonical_content" in row: written["content"] = row["_canonical_content"] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Optional # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/transcription_provider.py b/agent/transcription_provider.py index 647dbe644d..a9cf75bc29 100644 --- a/agent/transcription_provider.py +++ b/agent/transcription_provider.py @@ -34,3 +34,25 @@ class TranscriptionProvider(CatalogProviderBase): ``prompt`` (from ``stt.prompt`` or a ``pre_transcription`` hook) as a vocabulary hint; unknown keys must be ignored. """ + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import List # noqa: F401,E402 +import logging # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'logger': ('agent.i18n', 'logger'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/transcription_registry.py b/agent/transcription_registry.py index 365f91eb8e..68abb6e07a 100644 --- a/agent/transcription_registry.py +++ b/agent/transcription_registry.py @@ -43,3 +43,27 @@ _registry: ProviderRegistry[TranscriptionProvider] = ProviderRegistry( on_builtin_collision=_warn_builtin_collision, ) _registry.export(globals()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +from typing import List # noqa: F401,E402 +from typing import Optional # noqa: F401,E402 +import threading # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'hermes_home_key': ('hermes_constants', 'hermes_home_key'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/transports/chat_completions.py b/agent/transports/chat_completions.py index 3000dda5ee..723174f87d 100644 --- a/agent/transports/chat_completions.py +++ b/agent/transports/chat_completions.py @@ -565,3 +565,11 @@ class ChatCompletionsTransport(ProviderTransport): from agent.transports import register_transport # noqa: E402 register_transport("chat_completions", ChatCompletionsTransport) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/transports/codex.py b/agent/transports/codex.py index 874aac658a..183c7f3a30 100644 --- a/agent/transports/codex.py +++ b/agent/transports/codex.py @@ -674,3 +674,13 @@ class ResponsesApiTransport(ProviderTransport): from agent.transports import register_transport # noqa: E402 register_transport("codex_responses", ResponsesApiTransport) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +from typing import List # noqa: F401,E402 +from typing import Tuple # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/transports/codex_app_server.py b/agent/transports/codex_app_server.py index 1d2d348057..def380dd42 100644 --- a/agent/transports/codex_app_server.py +++ b/agent/transports/codex_app_server.py @@ -276,3 +276,12 @@ def check_codex_binary( if version < min_version: return False, f"codex {have} is older than required {'.'.join(map(str, min_version))}. Run: npm i -g @openai/codex" return True, have + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from dataclasses import field # noqa: F401,E402 +import time # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/tts_registry.py b/agent/tts_registry.py index 9fbbfdff39..4a785e5f6f 100644 --- a/agent/tts_registry.py +++ b/agent/tts_registry.py @@ -43,3 +43,27 @@ _registry: ProviderRegistry[TTSProvider] = ProviderRegistry( builtin_names=_BUILTIN_NAMES, on_builtin_collision=_warn_builtin_collision, ) _registry.export(globals()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +from typing import List # noqa: F401,E402 +from typing import Optional # noqa: F401,E402 +import threading # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'hermes_home_key': ('hermes_constants', 'hermes_home_key'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/turn_context.py b/agent/turn_context.py index df86b2746f..52d053ec88 100644 --- a/agent/turn_context.py +++ b/agent/turn_context.py @@ -1001,3 +1001,27 @@ def build_api_messages( if effective_system: api_messages = [{"role": "system", "content": effective_system}] + api_messages return api_messages, effective_system + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'IDLE_COMPACTION_STATUS_TEMPLATE': ('agent.conversation_compression', 'IDLE_COMPACTION_STATUS_TEMPLATE'), + 'PREFLIGHT_COMPRESSION_STATUS_TEMPLATE': ('agent.conversation_compression', 'PREFLIGHT_COMPRESSION_STATUS_TEMPLATE'), + 'automatic_compaction_status_message': ('agent.context_engine', 'automatic_compaction_status_message'), + 'compression_skipped_due_to_lock': ('agent.conversation_compression', 'compression_skipped_due_to_lock'), + 'conversation_history_after_compression': ('agent.conversation_compression', 'conversation_history_after_compression'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/turn_retry_state.py b/agent/turn_retry_state.py index 47ce71245d..24a28bffbe 100644 --- a/agent/turn_retry_state.py +++ b/agent/turn_retry_state.py @@ -50,3 +50,11 @@ class TurnRetryState: # A user correction cancelled the in-flight request: append a role-safe checkpoint + # user message, rebuild the payload, and retry the same logical iteration. restart_with_redirected_messages: bool = False + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from dataclasses import fields # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/usage_pricing.py b/agent/usage_pricing.py index 9d60c1a070..0f73ce36f9 100644 --- a/agent/usage_pricing.py +++ b/agent/usage_pricing.py @@ -618,3 +618,12 @@ def format_token_count_compact(value: int) -> str: if "." in text: text = text.rstrip("0").rstrip(".") return f"{sign}{text}{suffix}" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +DEFAULT_PRICING = {"input": 0.0, "output": 0.0} +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/video_gen_provider.py b/agent/video_gen_provider.py index 14b0092286..31ec67e105 100644 --- a/agent/video_gen_provider.py +++ b/agent/video_gen_provider.py @@ -262,3 +262,13 @@ class OpenAICompatibleVideoGenProvider(VideoGenProvider): close = getattr(client, "close", None) if callable(close): close() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import base64 # noqa: F401,E402 +import datetime # noqa: F401,E402 +import uuid # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/video_gen_registry.py b/agent/video_gen_registry.py index 04738f7a45..935aa69f61 100644 --- a/agent/video_gen_registry.py +++ b/agent/video_gen_registry.py @@ -44,3 +44,26 @@ def get_active_provider() -> Optional[VideoGenProvider]: if is_available_safe(p, logger, "video_gen provider %s.is_available() raised %s") ] return available[0] if len(available) == 1 else None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +from typing import List # noqa: F401,E402 +import threading # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'hermes_home_key': ('hermes_constants', 'hermes_home_key'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/web_search_provider.py b/agent/web_search_provider.py index 48b24c7f1b..4c5e393241 100644 --- a/agent/web_search_provider.py +++ b/agent/web_search_provider.py @@ -78,3 +78,11 @@ class WebSearchProvider(ProviderBase): raise NotImplementedError( f"{self.name} does not support extract (override supports_extract)" ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Optional # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/agent/web_search_registry.py b/agent/web_search_registry.py index a62c5f26da..df1643193b 100644 --- a/agent/web_search_registry.py +++ b/agent/web_search_registry.py @@ -199,3 +199,26 @@ def get_active_search_provider() -> Optional[WebSearchProvider]: def get_active_extract_provider() -> Optional[WebSearchProvider]: """Resolve the currently-active web extract provider.""" return _resolve(_configured_backend("extract"), capability="extract") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +from typing import List # noqa: F401,E402 +import threading # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'hermes_home_key': ('hermes_constants', 'hermes_home_key'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/cli.py b/cli.py index 6ee26558a6..240f795b4b 100644 --- a/cli.py +++ b/cli.py @@ -4574,3 +4574,69 @@ if __name__ == "__main__": import fire fire.Fire(main) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from prompt_toolkit.layout.menus import CompletionsMenu # noqa: F401,E402 +from prompt_toolkit.filters import Condition # noqa: F401,E402 +from prompt_toolkit.layout import ConditionalContainer # noqa: F401,E402 +from prompt_toolkit.layout.processors import ConditionalProcessor # noqa: F401,E402 +from prompt_toolkit.layout.dimension import Dimension # noqa: F401,E402 +from prompt_toolkit.history import FileHistory # noqa: F401,E402 +from prompt_toolkit.layout import FormattedTextControl # noqa: F401,E402 +from prompt_toolkit.layout import HSplit # noqa: F401,E402 +from prompt_toolkit.key_binding import KeyBindings # noqa: F401,E402 +from prompt_toolkit.layout import Layout # noqa: F401,E402 +from prompt_toolkit.styles import Style as PTStyle # noqa: F401,E402 +from rich.panel import Panel # noqa: F401,E402 +from prompt_toolkit.layout.processors import PasswordProcessor # noqa: F401,E402 +from prompt_toolkit.layout.processors import Processor # noqa: F401,E402 +from prompt_toolkit.widgets import TextArea # noqa: F401,E402 +from prompt_toolkit.layout.processors import Transformation # noqa: F401,E402 +from prompt_toolkit.layout import Window # noqa: F401,E402 +from prompt_toolkit.layout import WindowAlign # noqa: F401,E402 +import base64 # noqa: F401,E402 +import concurrent.futures # noqa: F401,E402 +import copy # noqa: F401,E402 +from rich import box as rich_box # noqa: F401,E402 +import tempfile # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'AIAgent': ('run_agent', 'AIAgent'), + 'CanonicalUsage': ('agent.usage_pricing', 'CanonicalUsage'), + 'DEFAULT_BROWSER_CDP_URL': ('hermes_cli.browser_connect', 'DEFAULT_BROWSER_CDP_URL'), + 'HERMES_AGENT_LOGO': ('hermes_cli.banner', 'HERMES_AGENT_LOGO'), + 'HERMES_CADUCEUS': ('hermes_cli.banner', 'HERMES_CADUCEUS'), + 'SlashCommandAutoSuggest': ('hermes_cli.commands_completion', 'SlashCommandAutoSuggest'), + 'SlashCommandCompleter': ('hermes_cli.commands_completion', 'SlashCommandCompleter'), + 'build_welcome_banner': ('hermes_cli.banner', 'build_welcome_banner'), + 'display_hermes_home': ('hermes_constants', 'display_hermes_home'), + 'estimate_usage_cost': ('agent.usage_pricing', 'estimate_usage_cost'), + 'get_all_toolsets': ('toolsets', 'get_all_toolsets'), + 'get_job': ('cron.jobs', 'get_job'), + 'get_toolset_for_tool': ('model_tools', 'get_toolset_for_tool'), + 'get_toolset_info': ('toolsets', 'get_toolset_info'), + 'init_skin_from_config': ('hermes_cli.skin_engine', 'init_skin_from_config'), + 'is_browser_debug_ready': ('hermes_cli.browser_connect', 'is_browser_debug_ready'), + 'is_table_divider': ('agent.markdown_tables', 'is_table_divider'), + 'looks_like_table_row': ('agent.markdown_tables', 'looks_like_table_row'), + 'manual_chrome_debug_command': ('hermes_cli.browser_connect', 'manual_chrome_debug_command'), + 'print_config_warnings': ('hermes_cli.config', 'print_config_warnings'), + 'prompt_for_secret': ('hermes_cli.callbacks', 'prompt_for_secret'), + 'set_friendly_tool_labels': ('agent.display', 'set_friendly_tool_labels'), + 'set_tool_preview_max_len': ('agent.display', 'set_tool_preview_max_len'), + 'setup_logging': ('hermes_logging', 'setup_logging'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/compat_manifest.json b/compat_manifest.json new file mode 100644 index 0000000000..724d740760 --- /dev/null +++ b/compat_manifest.json @@ -0,0 +1,12540 @@ +{ + "schema": 1, + "note": "Plugin-compat pointers kept for external plugins; removed on schedule by reverting the commit that added them. scripts/check_compat_pointers.py forbids in-tree use.", + "entries": [ + { + "facade": "acp_adapter.auth", + "name": "has_provider", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "acp_adapter.edit_approval", + "name": "FutureTimeout", + "kind": "import", + "target": "concurrent.futures" + }, + { + "facade": "acp_adapter.edit_approval", + "name": "clear_edit_approval_requester", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "acp_adapter.edit_approval", + "name": "get_edit_approval_requester", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "acp_adapter.events", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "acp_adapter.server", + "name": "ACP_MAX_MODELS_PER_PROVIDER", + "kind": "moved-lazy", + "target": "acp_adapter.model_catalog" + }, + { + "facade": "acp_adapter.server", + "name": "AgentThoughtChunk", + "kind": "import", + "target": "acp.schema" + }, + { + "facade": "acp_adapter.server", + "name": "AudioContentBlock", + "kind": "import", + "target": "acp.schema" + }, + { + "facade": "acp_adapter.server", + "name": "AvailableCommand", + "kind": "import", + "target": "acp.schema" + }, + { + "facade": "acp_adapter.server", + "name": "AvailableCommandsUpdate", + "kind": "import", + "target": "acp.schema" + }, + { + "facade": "acp_adapter.server", + "name": "BlobResourceContents", + "kind": "import", + "target": "acp.schema" + }, + { + "facade": "acp_adapter.server", + "name": "EmbeddedResourceContentBlock", + "kind": "import", + "target": "acp.schema" + }, + { + "facade": "acp_adapter.server", + "name": "ImageContentBlock", + "kind": "import", + "target": "acp.schema" + }, + { + "facade": "acp_adapter.server", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "acp_adapter.server", + "name": "ResourceContentBlock", + "kind": "import", + "target": "acp.schema" + }, + { + "facade": "acp_adapter.server", + "name": "TextResourceContents", + "kind": "import", + "target": "acp.schema" + }, + { + "facade": "acp_adapter.server", + "name": "UnstructuredCommandInput", + "kind": "import", + "target": "acp.schema" + }, + { + "facade": "acp_adapter.server", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "acp_adapter.server", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "acp_adapter.server", + "name": "unquote", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "acp_adapter.server", + "name": "urlparse", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "acp_adapter.session", + "name": "Lock", + "kind": "import", + "target": "threading" + }, + { + "facade": "agent.agent_init", + "name": "ToolGuardrailDecision", + "kind": "moved-lazy", + "target": "agent.tool_guardrails" + }, + { + "facade": "agent.agent_runtime_helpers", + "name": "agent_runtime_owns_post_tool_hook", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.agent_runtime_helpers", + "name": "intent_ack_continuation_enabled", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.anthropic_adapter", + "name": "CredentialPersistError", + "kind": "moved-lazy", + "target": "agent.anthropic_credentials" + }, + { + "facade": "agent.anthropic_adapter", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "agent.anthropic_adapter", + "name": "Tuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.anthropic_adapter", + "name": "base_url_host_matches", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "agent.anthropic_adapter", + "name": "base_url_hostname", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "agent.anthropic_adapter", + "name": "claude_code_credentials_path", + "kind": "moved-lazy", + "target": "agent.anthropic_credentials" + }, + { + "facade": "agent.anthropic_adapter", + "name": "copy", + "kind": "import", + "target": "copy" + }, + { + "facade": "agent.anthropic_adapter", + "name": "get_hermes_home", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "agent.anthropic_adapter", + "name": "is_claude_code_token_valid", + "kind": "moved-lazy", + "target": "agent.anthropic_credentials" + }, + { + "facade": "agent.anthropic_adapter", + "name": "is_rotation_consumed_uncommitted", + "kind": "moved-lazy", + "target": "agent.anthropic_credentials" + }, + { + "facade": "agent.anthropic_adapter", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "agent.anthropic_adapter", + "name": "mark_rotation_consumed_uncommitted", + "kind": "moved-lazy", + "target": "agent.anthropic_credentials" + }, + { + "facade": "agent.anthropic_adapter", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "agent.anthropic_adapter", + "name": "platform", + "kind": "import", + "target": "platform" + }, + { + "facade": "agent.anthropic_adapter", + "name": "read_claude_code_credentials", + "kind": "moved-lazy", + "target": "agent.anthropic_credentials" + }, + { + "facade": "agent.anthropic_adapter", + "name": "read_hermes_oauth_credentials", + "kind": "moved-lazy", + "target": "agent.anthropic_credentials" + }, + { + "facade": "agent.anthropic_adapter", + "name": "refresh_anthropic_oauth_pure", + "kind": "moved-lazy", + "target": "agent.anthropic_credentials" + }, + { + "facade": "agent.anthropic_adapter", + "name": "resolve_anthropic_token", + "kind": "moved-lazy", + "target": "agent.anthropic_credentials" + }, + { + "facade": "agent.anthropic_adapter", + "name": "run_hermes_oauth_login_pure", + "kind": "moved-lazy", + "target": "agent.anthropic_credentials" + }, + { + "facade": "agent.anthropic_adapter", + "name": "run_oauth_setup_token", + "kind": "moved-lazy", + "target": "agent.anthropic_credentials" + }, + { + "facade": "agent.anthropic_adapter", + "name": "secrets", + "kind": "import", + "target": "secrets" + }, + { + "facade": "agent.anthropic_adapter", + "name": "stat", + "kind": "import", + "target": "stat" + }, + { + "facade": "agent.anthropic_adapter", + "name": "urlparse", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "agent.aux_accounting", + "name": "get_accounting_context", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.auxiliary_client", + "name": "NOUS_EXTRA_BODY", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.auxiliary_client", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "agent.auxiliary_client", + "name": "copy", + "kind": "import", + "target": "copy" + }, + { + "facade": "agent.auxiliary_client", + "name": "get_async_text_auxiliary_client", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.backend_identity", + "name": "_REASON_SCOPES", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of classify_failure_scope)" + }, + { + "facade": "agent.backend_identity", + "name": "classify_failure_scope", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.background_review", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "agent.background_review", + "name": "is_background_review_enabled", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.bedrock_adapter", + "name": "CONTEXT_OVERFLOW_PATTERNS", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.bedrock_adapter", + "name": "OVERLOAD_PATTERNS", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.bedrock_adapter", + "name": "THROTTLE_PATTERNS", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.bedrock_adapter", + "name": "call_converse_stream", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.bedrock_adapter", + "name": "is_context_overflow_error", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of classify_bedrock_error)" + }, + { + "facade": "agent.bedrock_adapter", + "name": "classify_bedrock_error", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.bedrock_adapter", + "name": "is_context_overflow_error", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.bounded_response", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.bounded_response", + "name": "read_error_body_or_default", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.browser_provider", + "name": "Any", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.browser_provider", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.browser_registry", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.browser_registry", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.browser_registry", + "name": "hermes_home_key", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "agent.browser_registry", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "agent.codex_runtime", + "name": "run_codex_create_stream_fallback", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.coding_context", + "name": "coding_system_blocks", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.coding_context", + "name": "_PROFILES", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of get_profile)" + }, + { + "facade": "agent.coding_context", + "name": "get_profile", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.context_compressor", + "name": "tool_result_id_variants", + "kind": "moved-lazy", + "target": "agent.message_sanitization" + }, + { + "facade": "agent.conversation_compression", + "name": "CompressionExecutorSaturatedError", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.conversation_loop", + "name": "COMPRESSION_RETRY_CONTEXT_REDUCED_STATUS_TEMPLATE", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.conversation_loop", + "name": "COMPRESSION_RETRY_MESSAGES_STATUS_TEMPLATE", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.conversation_loop", + "name": "COMPRESSION_RETRY_TOKENS_STATUS_TEMPLATE", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.conversation_loop", + "name": "COMPRESSION_RETRY_TOO_LARGE_STATUS_TEMPLATE", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.conversation_loop", + "name": "FailoverReason", + "kind": "moved-lazy", + "target": "agent.error_classifier" + }, + { + "facade": "agent.conversation_loop", + "name": "KawaiiSpinner", + "kind": "moved-lazy", + "target": "agent.display" + }, + { + "facade": "agent.conversation_loop", + "name": "PARTIAL_STREAM_STUB_ID", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "agent.conversation_loop", + "name": "PRE_API_COMPRESSION_STATUS_TEMPLATE", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.conversation_loop", + "name": "adaptive_rate_limit_backoff", + "kind": "moved-lazy", + "target": "agent.retry_utils" + }, + { + "facade": "agent.conversation_loop", + "name": "anchored_context_tokens", + "kind": "moved-lazy", + "target": "agent.model_metadata" + }, + { + "facade": "agent.conversation_loop", + "name": "automatic_compaction_status_message", + "kind": "moved-lazy", + "target": "agent.context_engine" + }, + { + "facade": "agent.conversation_loop", + "name": "capture_usage_anchor", + "kind": "moved-lazy", + "target": "agent.model_metadata" + }, + { + "facade": "agent.conversation_loop", + "name": "classify_api_error", + "kind": "moved-lazy", + "target": "agent.error_classifier" + }, + { + "facade": "agent.conversation_loop", + "name": "close_interrupted_tool_sequence", + "kind": "moved-lazy", + "target": "agent.message_sanitization" + }, + { + "facade": "agent.conversation_loop", + "name": "coalesce_tool_call_id", + "kind": "moved-lazy", + "target": "agent.message_sanitization" + }, + { + "facade": "agent.conversation_loop", + "name": "compose_user_api_content", + "kind": "moved-lazy", + "target": "agent.turn_context" + }, + { + "facade": "agent.conversation_loop", + "name": "compression_blocked_transiently", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.conversation_loop", + "name": "compression_skipped_due_to_lock", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.conversation_loop", + "name": "context_compression_timed_out", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.conversation_loop", + "name": "conversation_history_after_compression", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.conversation_loop", + "name": "env_var_enabled", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "agent.conversation_loop", + "name": "estimate_messages_tokens_rough", + "kind": "moved-lazy", + "target": "agent.model_metadata" + }, + { + "facade": "agent.conversation_loop", + "name": "estimate_request_tokens_rough", + "kind": "moved-lazy", + "target": "agent.model_metadata" + }, + { + "facade": "agent.conversation_loop", + "name": "estimate_usage_cost", + "kind": "moved-lazy", + "target": "agent.usage_pricing" + }, + { + "facade": "agent.conversation_loop", + "name": "get_context_length_from_provider_error", + "kind": "moved-lazy", + "target": "agent.model_metadata" + }, + { + "facade": "agent.conversation_loop", + "name": "has_incomplete_scratchpad", + "kind": "moved-lazy", + "target": "agent.trajectory" + }, + { + "facade": "agent.conversation_loop", + "name": "is_output_cap_error", + "kind": "moved-lazy", + "target": "agent.model_metadata" + }, + { + "facade": "agent.conversation_loop", + "name": "is_repetition_dominated", + "kind": "moved-lazy", + "target": "agent.repetition_guard" + }, + { + "facade": "agent.conversation_loop", + "name": "is_zai_coding_overload_error", + "kind": "moved-lazy", + "target": "agent.retry_utils" + }, + { + "facade": "agent.conversation_loop", + "name": "jittered_backoff", + "kind": "moved-lazy", + "target": "agent.retry_utils" + }, + { + "facade": "agent.conversation_loop", + "name": "normalize_usage", + "kind": "moved-lazy", + "target": "agent.usage_pricing" + }, + { + "facade": "agent.conversation_loop", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "agent.conversation_loop", + "name": "parse_available_output_tokens_from_error", + "kind": "moved-lazy", + "target": "agent.model_metadata" + }, + { + "facade": "agent.conversation_loop", + "name": "random", + "kind": "import", + "target": "random" + }, + { + "facade": "agent.conversation_loop", + "name": "reanchor_current_turn_user_idx", + "kind": "moved-lazy", + "target": "agent.turn_context" + }, + { + "facade": "agent.conversation_loop", + "name": "save_context_length", + "kind": "moved-lazy", + "target": "agent.model_metadata" + }, + { + "facade": "agent.conversation_loop", + "name": "serialized_messages_bytes", + "kind": "moved-lazy", + "target": "agent.message_sanitization" + }, + { + "facade": "agent.conversation_loop", + "name": "splice_provider_projection", + "kind": "moved-lazy", + "target": "agent.provider_projection" + }, + { + "facade": "agent.conversation_loop", + "name": "ssl", + "kind": "import", + "target": "ssl" + }, + { + "facade": "agent.conversation_loop", + "name": "sys", + "kind": "import", + "target": "sys" + }, + { + "facade": "agent.conversation_loop", + "name": "zai_coding_overload_retry_ceiling", + "kind": "moved-lazy", + "target": "agent.retry_utils" + }, + { + "facade": "agent.credential_sources", + "name": "register", + "kind": "moved-lazy", + "target": "tools.slash_confirm" + }, + { + "facade": "agent.display", + "name": "get_friendly_tool_labels", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.error_surface", + "name": "LAYER_RUNTIME", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.estop", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "agent.file_safety", + "name": "PROFILE_SCOPED_AREAS", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.file_safety", + "name": "classify_cross_profile_target", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.file_safety", + "name": "get_cross_profile_warning", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.image_gen_provider", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "agent.image_gen_provider", + "name": "datetime", + "kind": "import", + "target": "datetime" + }, + { + "facade": "agent.image_gen_provider", + "name": "uuid", + "kind": "import", + "target": "uuid" + }, + { + "facade": "agent.image_gen_registry", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.image_gen_registry", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.image_gen_registry", + "name": "hermes_home_key", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "agent.image_gen_registry", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "agent.learning_graph_render", + "name": "Grid", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.learning_graph_render", + "name": "Run", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db" + }, + { + "facade": "agent.lsp.eventlog", + "name": "_announced_no_server", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of log_no_server_configured)" + }, + { + "facade": "agent.lsp.eventlog", + "name": "_announce_once", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of log_no_server_configured)" + }, + { + "facade": "agent.lsp.eventlog", + "name": "log_no_server_configured", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.lsp.protocol", + "name": "ERROR_REQUEST_CANCELLED", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.monitoring.cron_health", + "name": "GatewayHealthSnapshot", + "kind": "moved-lazy", + "target": "agent.monitoring.gateway_health" + }, + { + "facade": "agent.monitoring.cron_health", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "agent.monitoring.emitter", + "name": "TelemetryEmitter", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.monitoring.gateway_health", + "name": "redact_gateway_message", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.monitoring.gateway_health_export", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "agent.nous_rate_guard", + "name": "atomic_replace", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "agent.nous_rate_guard", + "name": "tempfile", + "kind": "import", + "target": "tempfile" + }, + { + "facade": "agent.outbound_webhooks", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "agent.outbound_webhooks", + "name": "datetime", + "kind": "import", + "target": "datetime" + }, + { + "facade": "agent.outbound_webhooks", + "name": "timezone", + "kind": "import", + "target": "datetime" + }, + { + "facade": "agent.pet.generate.atlas", + "name": "COLUMNS", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.pet.generate.atlas", + "name": "FRAME_COUNTS", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.pet.generate.atlas", + "name": "ROWS", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.pet.generate.atlas", + "name": "io", + "kind": "restored-import", + "target": "io" + }, + { + "facade": "agent.pet.generate.atlas", + "name": "atlas_to_webp_bytes", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.pet.generate.atlas", + "name": "io", + "kind": "import", + "target": "io" + }, + { + "facade": "agent.prompt_builder", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.prompt_builder", + "name": "org_id_of_path", + "kind": "moved-lazy", + "target": "agent.skill_utils" + }, + { + "facade": "agent.prompt_cache_boundary", + "name": "clear_stable_prefixes", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.proxy_sources.iron_proxy", + "name": "stat", + "kind": "import", + "target": "stat" + }, + { + "facade": "agent.reasoning_effort", + "name": "CODEX_RESPONSES_EFFORTS", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.relay_llm", + "name": "dataclass", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "agent.relay_runtime", + "name": "auto", + "kind": "import", + "target": "enum" + }, + { + "facade": "agent.relay_runtime", + "name": "emit_mark", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.relay_runtime", + "name": "ensure_session", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.relay_runtime", + "name": "get_host", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.relay_runtime", + "name": "get_session_handle", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.relay_runtime", + "name": "run_in_session", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.relay_runtime", + "name": "run_in_session_async", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.relay_tools", + "name": "asyncio", + "kind": "import", + "target": "asyncio" + }, + { + "facade": "agent.relay_tools", + "name": "inspect", + "kind": "import", + "target": "inspect" + }, + { + "facade": "agent.review_idle_queue", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.secret_sources._cache", + "name": "FetchResult", + "kind": "moved-lazy", + "target": "agent.secret_sources.base" + }, + { + "facade": "agent.secret_sources._cache", + "name": "is_valid_env_name", + "kind": "moved-lazy", + "target": "agent.secret_sources.base" + }, + { + "facade": "agent.secret_sources.bitwarden", + "name": "DiskCache", + "kind": "moved-lazy", + "target": "agent.secret_sources._cache" + }, + { + "facade": "agent.secret_sources.bitwarden", + "name": "apply_bitwarden_secrets", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.secret_sources.bitwarden", + "name": "stat", + "kind": "import", + "target": "stat" + }, + { + "facade": "agent.secret_sources.command", + "name": "apply_command_secrets", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.secret_sources.command", + "name": "parse_secret_output", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of get_command_secret)" + }, + { + "facade": "agent.secret_sources.command", + "name": "get_command_secret", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.secret_sources.command", + "name": "get_source_environment", + "kind": "moved-lazy", + "target": "agent.secret_sources.base" + }, + { + "facade": "agent.secret_sources.command", + "name": "list_command_secrets", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.secret_sources.command", + "name": "parse_secret_output", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.secret_sources.onepassword", + "name": "DiskCache", + "kind": "moved-lazy", + "target": "agent.secret_sources._cache" + }, + { + "facade": "agent.secret_sources.onepassword", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "agent.shell_hooks", + "name": "shlex", + "kind": "import", + "target": "shlex" + }, + { + "facade": "agent.skill_bundles", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "agent.skill_utils", + "name": "get_scan_ordered_skills_dirs", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.ssl_guard", + "name": "verify_ca_bundle_with_fallback", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.system_prompt", + "name": "OPENAI_MODEL_EXECUTION_GUIDANCE", + "kind": "moved-lazy", + "target": "agent.prompt_builder" + }, + { + "facade": "agent.terminal_env_registry", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.terminal_env_registry", + "name": "hermes_home_key", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "agent.terminal_env_registry", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "agent.transcript_repair", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.transcription_provider", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.transcription_provider", + "name": "logger", + "kind": "moved-lazy", + "target": "agent.i18n" + }, + { + "facade": "agent.transcription_provider", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "agent.transcription_registry", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.transcription_registry", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.transcription_registry", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.transcription_registry", + "name": "hermes_home_key", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "agent.transcription_registry", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "agent.transports.chat_completions", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.transports.codex", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.transports.codex", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.transports.codex", + "name": "Tuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.transports.codex_app_server", + "name": "field", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "agent.transports.codex_app_server", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "agent.tts_registry", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.tts_registry", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.tts_registry", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.tts_registry", + "name": "hermes_home_key", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "agent.tts_registry", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "agent.turn_context", + "name": "IDLE_COMPACTION_STATUS_TEMPLATE", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.turn_context", + "name": "PREFLIGHT_COMPRESSION_STATUS_TEMPLATE", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.turn_context", + "name": "automatic_compaction_status_message", + "kind": "moved-lazy", + "target": "agent.context_engine" + }, + { + "facade": "agent.turn_context", + "name": "compression_skipped_due_to_lock", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.turn_context", + "name": "conversation_history_after_compression", + "kind": "moved-lazy", + "target": "agent.conversation_compression" + }, + { + "facade": "agent.turn_retry_state", + "name": "fields", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "agent.usage_pricing", + "name": "DEFAULT_PRICING", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "agent.video_gen_provider", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "agent.video_gen_provider", + "name": "datetime", + "kind": "import", + "target": "datetime" + }, + { + "facade": "agent.video_gen_provider", + "name": "uuid", + "kind": "import", + "target": "uuid" + }, + { + "facade": "agent.video_gen_registry", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.video_gen_registry", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.video_gen_registry", + "name": "hermes_home_key", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "agent.video_gen_registry", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "agent.web_search_provider", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.web_search_registry", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.web_search_registry", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "agent.web_search_registry", + "name": "hermes_home_key", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "agent.web_search_registry", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "cli", + "name": "AIAgent", + "kind": "moved-lazy", + "target": "run_agent" + }, + { + "facade": "cli", + "name": "CanonicalUsage", + "kind": "moved-lazy", + "target": "agent.usage_pricing" + }, + { + "facade": "cli", + "name": "CompletionsMenu", + "kind": "import", + "target": "prompt_toolkit.layout.menus" + }, + { + "facade": "cli", + "name": "Condition", + "kind": "import", + "target": "prompt_toolkit.filters" + }, + { + "facade": "cli", + "name": "ConditionalContainer", + "kind": "import", + "target": "prompt_toolkit.layout" + }, + { + "facade": "cli", + "name": "ConditionalProcessor", + "kind": "import", + "target": "prompt_toolkit.layout.processors" + }, + { + "facade": "cli", + "name": "DEFAULT_BROWSER_CDP_URL", + "kind": "moved-lazy", + "target": "hermes_cli.browser_connect" + }, + { + "facade": "cli", + "name": "Dimension", + "kind": "import", + "target": "prompt_toolkit.layout.dimension" + }, + { + "facade": "cli", + "name": "FileHistory", + "kind": "import", + "target": "prompt_toolkit.history" + }, + { + "facade": "cli", + "name": "FormattedTextControl", + "kind": "import", + "target": "prompt_toolkit.layout" + }, + { + "facade": "cli", + "name": "HERMES_AGENT_LOGO", + "kind": "moved-lazy", + "target": "hermes_cli.banner" + }, + { + "facade": "cli", + "name": "HERMES_CADUCEUS", + "kind": "moved-lazy", + "target": "hermes_cli.banner" + }, + { + "facade": "cli", + "name": "HSplit", + "kind": "import", + "target": "prompt_toolkit.layout" + }, + { + "facade": "cli", + "name": "KeyBindings", + "kind": "import", + "target": "prompt_toolkit.key_binding" + }, + { + "facade": "cli", + "name": "Layout", + "kind": "import", + "target": "prompt_toolkit.layout" + }, + { + "facade": "cli", + "name": "PTStyle", + "kind": "import", + "target": "prompt_toolkit.styles" + }, + { + "facade": "cli", + "name": "Panel", + "kind": "import", + "target": "rich.panel" + }, + { + "facade": "cli", + "name": "PasswordProcessor", + "kind": "import", + "target": "prompt_toolkit.layout.processors" + }, + { + "facade": "cli", + "name": "Processor", + "kind": "import", + "target": "prompt_toolkit.layout.processors" + }, + { + "facade": "cli", + "name": "SlashCommandAutoSuggest", + "kind": "moved-lazy", + "target": "hermes_cli.commands_completion" + }, + { + "facade": "cli", + "name": "SlashCommandCompleter", + "kind": "moved-lazy", + "target": "hermes_cli.commands_completion" + }, + { + "facade": "cli", + "name": "TextArea", + "kind": "import", + "target": "prompt_toolkit.widgets" + }, + { + "facade": "cli", + "name": "Transformation", + "kind": "import", + "target": "prompt_toolkit.layout.processors" + }, + { + "facade": "cli", + "name": "Window", + "kind": "import", + "target": "prompt_toolkit.layout" + }, + { + "facade": "cli", + "name": "WindowAlign", + "kind": "import", + "target": "prompt_toolkit.layout" + }, + { + "facade": "cli", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "cli", + "name": "build_welcome_banner", + "kind": "moved-lazy", + "target": "hermes_cli.banner" + }, + { + "facade": "cli", + "name": "concurrent", + "kind": "import", + "target": "concurrent.futures" + }, + { + "facade": "cli", + "name": "copy", + "kind": "import", + "target": "copy" + }, + { + "facade": "cli", + "name": "display_hermes_home", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "cli", + "name": "estimate_usage_cost", + "kind": "moved-lazy", + "target": "agent.usage_pricing" + }, + { + "facade": "cli", + "name": "get_all_toolsets", + "kind": "moved-lazy", + "target": "toolsets" + }, + { + "facade": "cli", + "name": "get_job", + "kind": "moved-lazy", + "target": "cron.jobs" + }, + { + "facade": "cli", + "name": "get_toolset_for_tool", + "kind": "moved-lazy", + "target": "model_tools" + }, + { + "facade": "cli", + "name": "get_toolset_info", + "kind": "moved-lazy", + "target": "toolsets" + }, + { + "facade": "cli", + "name": "init_skin_from_config", + "kind": "moved-lazy", + "target": "hermes_cli.skin_engine" + }, + { + "facade": "cli", + "name": "is_browser_debug_ready", + "kind": "moved-lazy", + "target": "hermes_cli.browser_connect" + }, + { + "facade": "cli", + "name": "is_table_divider", + "kind": "moved-lazy", + "target": "agent.markdown_tables" + }, + { + "facade": "cli", + "name": "looks_like_table_row", + "kind": "moved-lazy", + "target": "agent.markdown_tables" + }, + { + "facade": "cli", + "name": "manual_chrome_debug_command", + "kind": "moved-lazy", + "target": "hermes_cli.browser_connect" + }, + { + "facade": "cli", + "name": "print_config_warnings", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "cli", + "name": "prompt_for_secret", + "kind": "moved-lazy", + "target": "hermes_cli.callbacks" + }, + { + "facade": "cli", + "name": "rich_box", + "kind": "import", + "target": "rich" + }, + { + "facade": "cli", + "name": "set_friendly_tool_labels", + "kind": "moved-lazy", + "target": "agent.display" + }, + { + "facade": "cli", + "name": "set_tool_preview_max_len", + "kind": "moved-lazy", + "target": "agent.display" + }, + { + "facade": "cli", + "name": "setup_logging", + "kind": "moved-lazy", + "target": "hermes_logging" + }, + { + "facade": "cli", + "name": "tempfile", + "kind": "import", + "target": "tempfile" + }, + { + "facade": "cron.jobs", + "name": "clear_drift_alerted", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "cron.scheduler", + "name": "BOT_CHAT_PLATFORM", + "kind": "moved-lazy", + "target": "cron.scheduler_delivery" + }, + { + "facade": "cron.scheduler", + "name": "SharedRouteAdapters", + "kind": "moved-lazy", + "target": "cron.scheduler_preflight" + }, + { + "facade": "cron.scheduler", + "name": "asyncio", + "kind": "import", + "target": "asyncio" + }, + { + "facade": "cron.scheduler", + "name": "cron_delivery_targets", + "kind": "moved-lazy", + "target": "cron.scheduler_delivery" + }, + { + "facade": "cron.scheduler", + "name": "parse_bot_chat_deliver_token", + "kind": "moved-lazy", + "target": "cron.scheduler_delivery" + }, + { + "facade": "cron.scheduler", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "cron.scheduler", + "name": "signal", + "kind": "import", + "target": "signal" + }, + { + "facade": "cron.scheduler_provider", + "name": "provider_supports_fire_cancel", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.browser_control_artifacts", + "name": "ArtifactOverwrite", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.browser_control_broker", + "name": "BROWSER_CONTROL_ALL_CAPABILITIES", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.config", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "gateway.delivery_ledger", + "name": "json", + "kind": "restored-import", + "target": "json" + }, + { + "facade": "gateway.delivery_ledger", + "name": "debug_rows", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.delivery_ledger", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "gateway.disk_status", + "name": "logger", + "kind": "moved-lazy", + "target": "gateway.run" + }, + { + "facade": "gateway.disk_status", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "gateway.hosted_room_discussion", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "gateway.hosted_room_driver", + "name": "Iterator", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.hosted_room_driver", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "gateway.hosted_room_driver", + "name": "contextmanager", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "gateway.hosted_room_driver", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "gateway.hosted_room_execution_policy", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "gateway.hosted_room_execution_policy", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "gateway.hosted_room_peer", + "name": "RoomLinkProbe", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.hosted_room_peer", + "name": "_LINK_PRIORITY", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of select_room_link)" + }, + { + "facade": "gateway.hosted_room_peer", + "name": "RoomLinkProbe", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of select_room_link)" + }, + { + "facade": "gateway.hosted_room_peer", + "name": "select_room_link", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.hosted_room_peer", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "gateway.hosted_room_replicas", + "name": "MAX_EVENT_JSON_BYTES", + "kind": "moved-lazy", + "target": "gateway.hosted_rooms" + }, + { + "facade": "gateway.hosted_room_replicas", + "name": "MAX_ROOM_ID_CHARS", + "kind": "moved-lazy", + "target": "gateway.hosted_rooms" + }, + { + "facade": "gateway.hosted_room_replicas", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "gateway.hosted_room_replicas", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "gateway.hosted_rooms", + "name": "Iterator", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.hosted_rooms", + "name": "NoReturn", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.hosted_rooms", + "name": "contextmanager", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "gateway.hosted_rooms", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "gateway.kanban_watchers", + "name": "Callable", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.kanban_watchers", + "name": "Context", + "kind": "import", + "target": "contextvars" + }, + { + "facade": "gateway.kanban_watchers", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "gateway.kanban_watchers", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "gateway.kanban_watchers", + "name": "sqlite3", + "kind": "import", + "target": "sqlite3" + }, + { + "facade": "gateway.kanban_watchers", + "name": "t", + "kind": "moved-lazy", + "target": "agent.i18n" + }, + { + "facade": "gateway.memory_status", + "name": "logger", + "kind": "moved-lazy", + "target": "gateway.run" + }, + { + "facade": "gateway.memory_status", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "gateway.platforms.helpers", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.platforms.helpers", + "name": "TYPE_CHECKING", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.platforms.helpers", + "name": "asyncio", + "kind": "restored-import", + "target": "asyncio" + }, + { + "facade": "gateway.platforms.helpers", + "name": "TextBatchAggregator", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.helpers", + "name": "asyncio", + "kind": "import", + "target": "asyncio" + }, + { + "facade": "gateway.platforms.qqbot.adapter", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "gateway.platforms.qqbot.adapter", + "name": "mimetypes", + "kind": "import", + "target": "mimetypes" + }, + { + "facade": "gateway.platforms.qqbot.chunked_upload", + "name": "ApiRequestFn", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.qqbot.chunked_upload", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.platforms.qqbot.chunked_upload", + "name": "functools", + "kind": "import", + "target": "functools" + }, + { + "facade": "gateway.platforms.qqbot.keyboards", + "name": "logging", + "kind": "restored-import", + "target": "logging" + }, + { + "facade": "gateway.platforms.qqbot.keyboards", + "name": "Awaitable", + "kind": "restored-import", + "target": "typing" + }, + { + "facade": "gateway.platforms.qqbot.keyboards", + "name": "Callable", + "kind": "restored-import", + "target": "typing" + }, + { + "facade": "gateway.platforms.qqbot.keyboards", + "name": "logger", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of ApprovalSender)" + }, + { + "facade": "gateway.platforms.qqbot.keyboards", + "name": "PostMessageFn", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of ApprovalSender)" + }, + { + "facade": "gateway.platforms.qqbot.keyboards", + "name": "ApprovalSender", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.qqbot.keyboards", + "name": "Awaitable", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.platforms.qqbot.keyboards", + "name": "Callable", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.platforms.qqbot.keyboards", + "name": "PostMessageFn", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.qqbot.keyboards", + "name": "logger", + "kind": "moved-lazy", + "target": "gateway.platforms.qqbot.adapter" + }, + { + "facade": "gateway.platforms.qqbot.keyboards", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "gateway.platforms.signal", + "name": "DEFAULT_EXT_TO_MIME", + "kind": "moved-lazy", + "target": "gateway.platforms.media_cache" + }, + { + "facade": "gateway.platforms.signal", + "name": "TYPING_INTERVAL", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.weixin", + "name": "MSG_TYPE_USER", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.weixin", + "name": "struct", + "kind": "import", + "target": "struct" + }, + { + "facade": "gateway.platforms.yuanbao", + "name": "AUTH_FAILED_CODES", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.yuanbao", + "name": "AUTH_RETRYABLE_CODES", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.yuanbao", + "name": "FileUrlHandler", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.yuanbao", + "name": "GroupQueryService", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.yuanbao", + "name": "REPLY_REF_TTL_S", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.yuanbao", + "name": "get_active_adapter", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.yuanbao", + "name": "send_yuanbao_direct", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.yuanbao_media", + "name": "COS_USE_ACCELERATE", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.yuanbao_proto", + "name": "DEBUG_MODE", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.yuanbao_proto", + "name": "_encode_forward_multimedia", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of encode_forward_msg_data)" + }, + { + "facade": "gateway.platforms.yuanbao_proto", + "name": "_encode_forward_msg_content", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of encode_forward_msg_data)" + }, + { + "facade": "gateway.platforms.yuanbao_proto", + "name": "_encode_forward_msg", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of encode_forward_msg_data)" + }, + { + "facade": "gateway.platforms.yuanbao_proto", + "name": "encode_forward_msg_data", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.platforms.yuanbao_proto", + "name": "logger", + "kind": "moved-lazy", + "target": "gateway.platforms.base" + }, + { + "facade": "gateway.platforms.yuanbao_proto", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "gateway.relay", + "name": "relay_bot_username", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.relay.adapter", + "name": "cast", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.relay.auth", + "name": "DELIVERY_SIG_HEADER", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.relay.auth", + "name": "DELIVERY_TS_HEADER", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.relay.auth", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.relay.auth", + "name": "Sequence", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.relay.auth", + "name": "_DEFAULT_MAX_SKEW_SECONDS", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of verify_delivery_signature)" + }, + { + "facade": "gateway.relay.auth", + "name": "_hmac_hex", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of verify_delivery_signature)" + }, + { + "facade": "gateway.relay.auth", + "name": "verify_signature", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of verify_delivery_signature)" + }, + { + "facade": "gateway.relay.auth", + "name": "_delivery_payload", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of verify_delivery_signature)" + }, + { + "facade": "gateway.relay.auth", + "name": "verify_delivery_signature", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.relay.auth", + "name": "verify_signature", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.relay.auth", + "name": "verify_token", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.response_filters", + "name": "SILENT_REPLY_TOKEN", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.restart_loop_guard", + "name": "is_restart_loop_tripped", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.run", + "name": "Awaitable", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.run", + "name": "Context", + "kind": "import", + "target": "contextvars" + }, + { + "facade": "gateway.run", + "name": "DEFAULT_GATEWAY_POST_INTERRUPT_GRACE_TIMEOUT", + "kind": "moved-lazy", + "target": "gateway.restart" + }, + { + "facade": "gateway.run", + "name": "DEFAULT_HEARTBEAT_INTERVAL_S", + "kind": "moved-lazy", + "target": "gateway.shutdown_watchdog" + }, + { + "facade": "gateway.run", + "name": "DEFAULT_LEASE_WAIT", + "kind": "moved-lazy", + "target": "gateway.turn_lease" + }, + { + "facade": "gateway.run", + "name": "DEFAULT_LOOP_WATCHDOG_INTERVAL_S", + "kind": "moved-lazy", + "target": "gateway.shutdown_watchdog" + }, + { + "facade": "gateway.run", + "name": "DEFAULT_LOOP_WATCHDOG_MAX_STRIKES", + "kind": "moved-lazy", + "target": "gateway.shutdown_watchdog" + }, + { + "facade": "gateway.run", + "name": "DEFAULT_LOOP_WATCHDOG_TIMEOUT_S", + "kind": "moved-lazy", + "target": "gateway.shutdown_watchdog" + }, + { + "facade": "gateway.run", + "name": "EphemeralReply", + "kind": "moved-lazy", + "target": "gateway.platforms.base" + }, + { + "facade": "gateway.run", + "name": "GATEWAY_FATAL_CONFIG_EXIT_CODE", + "kind": "moved-lazy", + "target": "gateway.restart" + }, + { + "facade": "gateway.run", + "name": "GATEWAY_SERVICE_RESTART_EXIT_CODE", + "kind": "moved-lazy", + "target": "gateway.restart" + }, + { + "facade": "gateway.run", + "name": "SessionEntry", + "kind": "moved-lazy", + "target": "gateway.session" + }, + { + "facade": "gateway.run", + "name": "TranscriptReadError", + "kind": "moved-lazy", + "target": "gateway.session_transcript" + }, + { + "facade": "gateway.run", + "name": "TurnContext", + "kind": "moved-lazy", + "target": "gateway.turn_context" + }, + { + "facade": "gateway.run", + "name": "TurnLeaseTimeoutError", + "kind": "moved-lazy", + "target": "gateway.turn_lease" + }, + { + "facade": "gateway.run", + "name": "TurnRunner", + "kind": "moved-lazy", + "target": "gateway.run_turn_runner" + }, + { + "facade": "gateway.run", + "name": "Union", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.run", + "name": "arm_shutdown_watchdog", + "kind": "moved-lazy", + "target": "gateway.shutdown_watchdog" + }, + { + "facade": "gateway.run", + "name": "atomic_json_write", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "gateway.run", + "name": "base_url_hostname", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "gateway.run", + "name": "build_auto_tts_output_path", + "kind": "moved-lazy", + "target": "gateway.platforms.base" + }, + { + "facade": "gateway.run", + "name": "build_channel_continuity_note", + "kind": "moved-lazy", + "target": "gateway.session" + }, + { + "facade": "gateway.run", + "name": "build_session_context", + "kind": "moved-lazy", + "target": "gateway.session" + }, + { + "facade": "gateway.run", + "name": "build_session_context_prompt", + "kind": "moved-lazy", + "target": "gateway.session" + }, + { + "facade": "gateway.run", + "name": "consume_detached_task_result", + "kind": "moved-lazy", + "target": "agent.async_utils" + }, + { + "facade": "gateway.run", + "name": "faulthandler", + "kind": "import", + "target": "faulthandler" + }, + { + "facade": "gateway.run", + "name": "functools", + "kind": "import", + "target": "functools" + }, + { + "facade": "gateway.run", + "name": "inspect", + "kind": "import", + "target": "inspect" + }, + { + "facade": "gateway.run", + "name": "is_global_startup_conflict", + "kind": "moved-lazy", + "target": "gateway.restart" + }, + { + "facade": "gateway.run", + "name": "is_shared_multi_user_session", + "kind": "moved-lazy", + "target": "gateway.session" + }, + { + "facade": "gateway.run", + "name": "is_truthy_value", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "gateway.run", + "name": "load_dotenv", + "kind": "import", + "target": "dotenv" + }, + { + "facade": "gateway.run", + "name": "looks_like_telegram_private_chat_id", + "kind": "moved-lazy", + "target": "gateway.delivery" + }, + { + "facade": "gateway.run", + "name": "loop_heartbeat_forever", + "kind": "moved-lazy", + "target": "gateway.shutdown_watchdog" + }, + { + "facade": "gateway.run", + "name": "merge_pending_message_event", + "kind": "moved-lazy", + "target": "gateway.platforms.base" + }, + { + "facade": "gateway.run", + "name": "neutralize_untrusted_inline_text", + "kind": "moved-lazy", + "target": "gateway.session" + }, + { + "facade": "gateway.run", + "name": "parse_cron_drain_timeout", + "kind": "moved-lazy", + "target": "gateway.restart" + }, + { + "facade": "gateway.run", + "name": "parse_restart_after_turn_timeout", + "kind": "moved-lazy", + "target": "gateway.restart" + }, + { + "facade": "gateway.run", + "name": "parse_restart_drain_timeout", + "kind": "moved-lazy", + "target": "gateway.restart" + }, + { + "facade": "gateway.run", + "name": "parse_signal_interrupt_grace_timeout", + "kind": "moved-lazy", + "target": "gateway.restart" + }, + { + "facade": "gateway.run", + "name": "project_compaction_message_for_display", + "kind": "moved-lazy", + "target": "agent.compaction_display" + }, + { + "facade": "gateway.run", + "name": "queue", + "kind": "import", + "target": "queue" + }, + { + "facade": "gateway.run", + "name": "repair_explicit_computer_use_media_paths", + "kind": "moved-lazy", + "target": "gateway.media_repair" + }, + { + "facade": "gateway.run", + "name": "resolve_cron_drain_budget", + "kind": "moved-lazy", + "target": "gateway.restart" + }, + { + "facade": "gateway.run", + "name": "resolve_delivery_transport", + "kind": "moved-lazy", + "target": "gateway.delivery" + }, + { + "facade": "gateway.run", + "name": "resolve_shutdown_watchdog_delay", + "kind": "moved-lazy", + "target": "gateway.shutdown_watchdog" + }, + { + "facade": "gateway.run", + "name": "start_loop_liveness_watchdog", + "kind": "moved-lazy", + "target": "gateway.shutdown_watchdog" + }, + { + "facade": "gateway.run", + "name": "t", + "kind": "moved-lazy", + "target": "agent.i18n" + }, + { + "facade": "gateway.run", + "name": "timedelta", + "kind": "import", + "target": "datetime" + }, + { + "facade": "gateway.run", + "name": "timezone", + "kind": "import", + "target": "datetime" + }, + { + "facade": "gateway.run", + "name": "utf16_len", + "kind": "moved-lazy", + "target": "gateway.platforms.base" + }, + { + "facade": "gateway.session", + "name": "SessionResetPolicy", + "kind": "moved-lazy", + "target": "gateway.config" + }, + { + "facade": "gateway.session", + "name": "TranscriptReadError", + "kind": "moved-lazy", + "target": "gateway.session_transcript" + }, + { + "facade": "gateway.session", + "name": "atomic_replace", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "gateway.session", + "name": "auto_continue_freshness_window", + "kind": "moved-lazy", + "target": "gateway.session_lifecycle" + }, + { + "facade": "gateway.session", + "name": "extract_api_content_sidecar", + "kind": "moved-lazy", + "target": "agent.turn_context" + }, + { + "facade": "gateway.session", + "name": "normalize_whatsapp_identifier", + "kind": "moved-lazy", + "target": "gateway.whatsapp_identity" + }, + { + "facade": "gateway.session", + "name": "replace", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "gateway.session", + "name": "uuid", + "kind": "import", + "target": "uuid" + }, + { + "facade": "gateway.slash_access", + "name": "Tuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.slash_commands", + "name": "Any", + "kind": "import", + "target": "typing" + }, + { + "facade": "gateway.slash_commands", + "name": "HISTORY_UNREADABLE", + "kind": "moved-lazy", + "target": "gateway.slash_commands_status" + }, + { + "facade": "gateway.slash_commands", + "name": "MessageType", + "kind": "moved-lazy", + "target": "gateway.platforms.base" + }, + { + "facade": "gateway.slash_commands", + "name": "SessionSource", + "kind": "moved-lazy", + "target": "gateway.session" + }, + { + "facade": "gateway.slash_commands", + "name": "base_url_host_matches", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "gateway.slash_commands", + "name": "build_session_key", + "kind": "moved-lazy", + "target": "gateway.session" + }, + { + "facade": "gateway.slash_commands", + "name": "clear_model_endpoint_credentials", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "gateway.slash_commands", + "name": "extract_api_content_sidecar", + "kind": "moved-lazy", + "target": "agent.turn_context" + }, + { + "facade": "gateway.slash_commands", + "name": "fetch_account_usage", + "kind": "moved-lazy", + "target": "agent.account_usage" + }, + { + "facade": "gateway.slash_commands", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "gateway.slash_commands", + "name": "is_shared_multi_user_session", + "kind": "moved-lazy", + "target": "gateway.session" + }, + { + "facade": "gateway.slash_commands", + "name": "render_account_usage_lines", + "kind": "moved-lazy", + "target": "agent.account_usage" + }, + { + "facade": "gateway.status", + "name": "clear_planned_stop_marker", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.status", + "name": "is_gateway_running", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "gateway.sticker_cache", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "gateway.sticker_cache", + "name": "tempfile", + "kind": "import", + "target": "tempfile" + }, + { + "facade": "gateway.stream_consumer", + "name": "MEDIA_TAG_CLEANUP_RE", + "kind": "moved-lazy", + "target": "gateway.platforms.base" + }, + { + "facade": "gateway.stream_consumer", + "name": "escape_code_fences_for_display", + "kind": "moved-lazy", + "target": "gateway.stream_consumer_fences" + }, + { + "facade": "gateway.stream_dispatch", + "name": "ToolCallFinished", + "kind": "moved-lazy", + "target": "gateway.stream_events" + }, + { + "facade": "gateway.systemd_notify", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.agent_import", + "name": "backup_memory_file", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.agent_import", + "name": "default_source_dir", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.auth", + "name": "BaseHTTPRequestHandler", + "kind": "import", + "target": "http.server" + }, + { + "facade": "hermes_cli.auth", + "name": "CODEX_OAUTH_USER_AGENT", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "CODEX_QUOTA_PROBE_MIN_INTERVAL_SECONDS", + "kind": "moved-lazy", + "target": "hermes_cli.auth_codex" + }, + { + "facade": "hermes_cli.auth", + "name": "DEFAULT_SPOTIFY_REDIRECT_URI", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "DEVICE_AUTH_POLL_INTERVAL_CAP_SECONDS", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "HTTPServer", + "kind": "import", + "target": "http.server" + }, + { + "facade": "hermes_cli.auth", + "name": "MINIMAX_OAUTH_GRANT_TYPE", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "NOUS_INFERENCE_INVOKE_SCOPE", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "NOUS_SHARED_STORE_FILENAME", + "kind": "moved-lazy", + "target": "hermes_cli.auth_nous" + }, + { + "facade": "hermes_cli.auth", + "name": "OAUTH_OVER_SSH_DOCS_URL", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "QWEN_OAUTH_CLIENT_ID", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "QWEN_OAUTH_TOKEN_URL", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "SINGLE_USE_OAUTH_SINGLETON_FILES", + "kind": "moved-lazy", + "target": "hermes_cli.auth_oauth_grants" + }, + { + "facade": "hermes_cli.auth", + "name": "SPOTIFY_ACCESS_TOKEN_REFRESH_SKEW_SECONDS", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "SPOTIFY_DASHBOARD_URL", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "TYPE_CHECKING", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.auth", + "name": "XAI_OAUTH_DEVICE_CODE_URL", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "XAI_OAUTH_DISCOVERY_URL", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "XAI_OAUTH_ISSUER", + "kind": "moved-lazy", + "target": "hermes_cli.auth_constants" + }, + { + "facade": "hermes_cli.auth", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "hermes_cli.auth", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "hermes_cli.auth", + "name": "parse_qs", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "hermes_cli.auth", + "name": "refresh_nous_oauth_pure", + "kind": "moved-lazy", + "target": "hermes_cli.auth_nous" + }, + { + "facade": "hermes_cli.auth", + "name": "ssl", + "kind": "import", + "target": "ssl" + }, + { + "facade": "hermes_cli.auth", + "name": "subprocess", + "kind": "import", + "target": "subprocess" + }, + { + "facade": "hermes_cli.auth", + "name": "sys", + "kind": "import", + "target": "sys" + }, + { + "facade": "hermes_cli.auth", + "name": "urlencode", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "hermes_cli.backup", + "name": "copy_db_and_verify", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.browser_connect", + "name": "try_launch_chrome_debug", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.callbacks", + "name": "approval_callback", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.callbacks", + "name": "clarify_callback", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.checkpoints", + "name": "cmd_list", + "kind": "moved-lazy", + "target": "hermes_cli.plugins_cmd" + }, + { + "facade": "hermes_cli.checkpoints", + "name": "datetime", + "kind": "import", + "target": "datetime" + }, + { + "facade": "hermes_cli.commands", + "name": "Any", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.commands", + "name": "Callable", + "kind": "import", + "target": "collections.abc" + }, + { + "facade": "hermes_cli.commands", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.commands", + "name": "Mapping", + "kind": "import", + "target": "collections.abc" + }, + { + "facade": "hermes_cli.commands", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.commands", + "name": "Sequence", + "kind": "import", + "target": "collections.abc" + }, + { + "facade": "hermes_cli.commands", + "name": "SlashCommandAutoSuggest", + "kind": "moved-lazy", + "target": "hermes_cli.commands_completion" + }, + { + "facade": "hermes_cli.commands", + "name": "SlashCommandCompleter", + "kind": "moved-lazy", + "target": "hermes_cli.commands_completion" + }, + { + "facade": "hermes_cli.commands", + "name": "Tuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.commands", + "name": "_requires_argument", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of discord_skill_commands)" + }, + { + "facade": "hermes_cli.commands", + "name": "_CMD_NAME_LIMIT", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of discord_skill_commands)" + }, + { + "facade": "hermes_cli.commands", + "name": "_clamp_command_names", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of discord_skill_commands)" + }, + { + "facade": "hermes_cli.commands", + "name": "_collect_gateway_skill_entries", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of discord_skill_commands)" + }, + { + "facade": "hermes_cli.commands", + "name": "discord_skill_commands", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.commands", + "name": "discord_skill_commands_by_category", + "kind": "moved-lazy", + "target": "hermes_cli.commands_platforms" + }, + { + "facade": "hermes_cli.commands", + "name": "field", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "hermes_cli.commands", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "hermes_cli.commands", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "hermes_cli.commands", + "name": "slack_app_manifest", + "kind": "moved-lazy", + "target": "hermes_cli.commands_platforms" + }, + { + "facade": "hermes_cli.commands", + "name": "slack_native_slashes", + "kind": "moved-lazy", + "target": "hermes_cli.commands_platforms" + }, + { + "facade": "hermes_cli.commands", + "name": "slack_subcommand_map", + "kind": "moved-lazy", + "target": "hermes_cli.commands_platforms" + }, + { + "facade": "hermes_cli.commands", + "name": "subprocess", + "kind": "import", + "target": "subprocess" + }, + { + "facade": "hermes_cli.commands", + "name": "telegram_bot_commands", + "kind": "moved-lazy", + "target": "hermes_cli.commands_platforms" + }, + { + "facade": "hermes_cli.commands", + "name": "telegram_menu_commands", + "kind": "moved-lazy", + "target": "hermes_cli.commands_platforms" + }, + { + "facade": "hermes_cli.commands", + "name": "telegram_menu_max_commands", + "kind": "moved-lazy", + "target": "hermes_cli.commands_platforms" + }, + { + "facade": "hermes_cli.commands", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "hermes_cli.config", + "name": "normalize_route_base_url", + "kind": "moved-lazy", + "target": "hermes_cli.route_identity" + }, + { + "facade": "hermes_cli.config", + "name": "_install_method_project_root", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of stamp_install_method)" + }, + { + "facade": "hermes_cli.config", + "name": "stamp_install_method", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.console_engine", + "name": "shlex", + "kind": "import", + "target": "shlex" + }, + { + "facade": "hermes_cli.curses_ui", + "name": "Protocol", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.dashboard_auth.audit", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "hermes_cli.dashboard_auth.middleware", + "name": "DashboardAuthProvider", + "kind": "moved-lazy", + "target": "hermes_cli.dashboard_auth.base" + }, + { + "facade": "hermes_cli.dingtalk_auth", + "name": "logger", + "kind": "moved-lazy", + "target": "hermes_cli.auth" + }, + { + "facade": "hermes_cli.dingtalk_auth", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "hermes_cli.doctor", + "name": "FTS_STORAGE_VERSION", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_cli.doctor", + "name": "OPENROUTER_MODELS_URL", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "hermes_cli.doctor", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "hermes_cli.doctor", + "name": "STATE_DB_SIZE_WARN_BYTES", + "kind": "moved-lazy", + "target": "hermes_cli.doctor_state" + }, + { + "facade": "hermes_cli.doctor", + "name": "agent_browser_runnable", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "hermes_cli.doctor", + "name": "base_url_host_matches", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "hermes_cli.doctor", + "name": "check_certificates", + "kind": "moved-lazy", + "target": "hermes_cli.doctor_platform" + }, + { + "facade": "hermes_cli.doctor", + "name": "check_fail", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.doctor", + "name": "check_macos_full_disk_access", + "kind": "moved-lazy", + "target": "hermes_cli.doctor_platform" + }, + { + "facade": "hermes_cli.doctor", + "name": "check_macos_tcc_anchor", + "kind": "moved-lazy", + "target": "hermes_cli.doctor_platform" + }, + { + "facade": "hermes_cli.doctor", + "name": "check_macos_tcc_grants", + "kind": "moved-lazy", + "target": "hermes_cli.doctor_platform" + }, + { + "facade": "hermes_cli.doctor", + "name": "check_ok", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.doctor", + "name": "check_warn", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.doctor", + "name": "collect_deprecated_config_keys", + "kind": "moved-lazy", + "target": "hermes_cli.doctor_config" + }, + { + "facade": "hermes_cli.doctor", + "name": "collect_deprecated_env_vars", + "kind": "moved-lazy", + "target": "hermes_cli.doctor_config" + }, + { + "facade": "hermes_cli.doctor", + "name": "collect_relay_plugin_cutover_findings", + "kind": "moved-lazy", + "target": "hermes_cli.doctor_config" + }, + { + "facade": "hermes_cli.doctor", + "name": "describe_vercel_auth", + "kind": "moved-lazy", + "target": "hermes_cli.vercel_auth" + }, + { + "facade": "hermes_cli.doctor", + "name": "detect_install_method", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.doctor", + "name": "importlib", + "kind": "import", + "target": "importlib.util" + }, + { + "facade": "hermes_cli.doctor", + "name": "is_nix_install_method", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.doctor", + "name": "managed_scope_check", + "kind": "moved-lazy", + "target": "hermes_cli.doctor_config" + }, + { + "facade": "hermes_cli.doctor", + "name": "recommended_update_command_for_method", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.doctor", + "name": "report_deprecated_config_and_env", + "kind": "moved-lazy", + "target": "hermes_cli.doctor_config" + }, + { + "facade": "hermes_cli.doctor", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "hermes_cli.doctor", + "name": "subprocess", + "kind": "import", + "target": "subprocess" + }, + { + "facade": "hermes_cli.doctor_live", + "name": "ELEVENLABS_VOICES_URL", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.doctor_live", + "name": "FAL_MODELS_URL", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.doctor_live", + "name": "FIRECRAWL_HEALTH_URL", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.doctor_live", + "name": "GROQ_MODELS_URL", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.doctor_live", + "name": "OPENAI_MODELS_URL", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.focus_view", + "name": "FOCUS_USAGE", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.focus_view", + "name": "effective_tool_progress_mode", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.foreign_sessions", + "name": "list_claude_sessions", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.foreign_sessions", + "name": "list_codex_sessions", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.gateway", + "name": "DEFAULT_GATEWAY_RESTART_AFTER_TURN_TIMEOUT", + "kind": "moved-lazy", + "target": "gateway.restart" + }, + { + "facade": "hermes_cli.gateway", + "name": "print_systemd_linger_guidance", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.gitlock", + "name": "is_ancestor_of_head", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.heartbeat", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.journey", + "name": "cmd_journey", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.kanban", + "name": "Any", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "DEFAULT_BUSY_TIMEOUT_MS", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_connect" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "DEFAULT_LOG_BACKUP_COUNT", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "DEFAULT_LOG_ROTATE_BYTES", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "DEFAULT_SPAWN_FAILURE_LIMIT", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "DERIVED_MAX_IN_PROGRESS_CEILING", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "DERIVED_MAX_IN_PROGRESS_FLOOR", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "KANBAN_TERMINAL_TIMEOUT_GRACE_SECONDS", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "KanbanDbCorruptError", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_connect" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "MEMORY_GUARD_MB_PER_WORKER", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "Mapping", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "RepairResult", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_connect" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "add_notify_sub", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_notify" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "advance_notify_cursor", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_notify" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "check_respawn_guard", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "claim_unseen_events_for_sub", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_notify" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "configured_max_in_progress", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "connect", + "kind": "moved-lazy", + "target": "hermes_cli.projects_db" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "connect_closing", + "kind": "moved-lazy", + "target": "hermes_cli.projects_db" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "count_notify_subs", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_notify" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "count_running_tasks", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "count_running_tasks_other_boards", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "derive_default_max_in_progress", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "detect_crashed_workers", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "detect_stale_running", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "dispatch_once", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "enforce_max_runtime", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "field", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "has_spawnable_ready", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "has_spawnable_review", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "heartbeat_worker", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "list_notify_subs", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_notify" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "parent_results", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "purge_stale_done_notify_subs", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_notify" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "random", + "kind": "import", + "target": "random" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "reap_worker_zombies", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "reconcile_orphaned_running", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "remove_notify_sub", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_notify" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "repair_db", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_connect" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "resolve_max_in_progress", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "resolve_workspace", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_workspace" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "review_dispatch_enabled", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "rewind_notify_cursor", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_notify" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "run_daemon", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "set_branch_name", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_workspace" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "set_workspace_path", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_workspace" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "unseen_events_for_sub", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_notify" + }, + { + "facade": "hermes_cli.kanban_db", + "name": "worker_log_rotation_config", + "kind": "moved-lazy", + "target": "hermes_cli.kanban_db_dispatch" + }, + { + "facade": "hermes_cli.kanban_decompose", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "hermes_cli.kanban_decompose", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "hermes_cli.kanban_diagnostics", + "name": "DIAGNOSTIC_KINDS", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.local_runtime.binaries", + "name": "get_hermes_home", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "hermes_cli.local_runtime.bootstrap", + "name": "get_hermes_home", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "hermes_cli.local_runtime.capabilities", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "hermes_cli.local_runtime.capabilities", + "name": "urllib", + "kind": "import", + "target": "urllib.request" + }, + { + "facade": "hermes_cli.local_runtime.catalog", + "name": "find_variant", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.local_runtime.catalog", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "hermes_cli.local_runtime.catalog", + "name": "recommended_id", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.local_runtime.hf_browse", + "name": "field", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "hermes_cli.main", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "hermes_cli.main", + "name": "line_input", + "kind": "moved-lazy", + "target": "hermes_cli.cli_output" + }, + { + "facade": "hermes_cli.main", + "name": "shlex", + "kind": "import", + "target": "shlex" + }, + { + "facade": "hermes_cli.main", + "name": "stat", + "kind": "import", + "target": "stat" + }, + { + "facade": "hermes_cli.main", + "name": "tempfile", + "kind": "import", + "target": "tempfile" + }, + { + "facade": "hermes_cli.mcp_picker", + "name": "color", + "kind": "moved-lazy", + "target": "hermes_cli.colors" + }, + { + "facade": "hermes_cli.mcp_security", + "name": "is_mcp_server_entry_suspicious", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.middleware", + "name": "API_EXECUTION_MIDDLEWARE", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.middleware", + "name": "API_REQUEST_MIDDLEWARE", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.middleware", + "name": "apply_api_request_middleware", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.middleware", + "name": "run_api_execution_middleware", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.moa_config", + "name": "encode_moa_turn", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of build_moa_turn_prompt)" + }, + { + "facade": "hermes_cli.moa_config", + "name": "build_moa_turn_prompt", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.moa_config", + "name": "encode_moa_turn", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.moa_config", + "name": "list_moa_presets", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.moa_config", + "name": "set_active_moa_preset", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.model_setup_flows", + "name": "BEDROCK_GEO_PREFIXES", + "kind": "moved-lazy", + "target": "hermes_cli.model_setup_flows_bedrock" + }, + { + "facade": "hermes_cli.model_setup_flows", + "name": "bedrock_model_routable_from_region", + "kind": "moved-lazy", + "target": "hermes_cli.model_setup_flows_bedrock" + }, + { + "facade": "hermes_cli.model_setup_flows", + "name": "bedrock_region_geo_prefix", + "kind": "moved-lazy", + "target": "hermes_cli.model_setup_flows_bedrock" + }, + { + "facade": "hermes_cli.model_setup_flows", + "name": "custom_provider_slug", + "kind": "moved-lazy", + "target": "hermes_cli.providers" + }, + { + "facade": "hermes_cli.model_setup_flows", + "name": "line_input", + "kind": "moved-lazy", + "target": "hermes_cli.cli_output" + }, + { + "facade": "hermes_cli.model_setup_flows", + "name": "subprocess", + "kind": "import", + "target": "subprocess" + }, + { + "facade": "hermes_cli.model_setup_flows", + "name": "urllib", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "hermes_cli.model_switch", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.model_switch", + "name": "base_url_host_matches", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "hermes_cli.model_switch", + "name": "custom_provider_slug", + "kind": "moved-lazy", + "target": "hermes_cli.providers" + }, + { + "facade": "hermes_cli.model_switch", + "name": "http", + "kind": "import", + "target": "http.client" + }, + { + "facade": "hermes_cli.model_switch", + "name": "list_picker_providers", + "kind": "moved-lazy", + "target": "hermes_cli.model_switch_providers" + }, + { + "facade": "hermes_cli.model_switch", + "name": "prewarm_picker_cache_async", + "kind": "moved-lazy", + "target": "hermes_cli.model_switch_providers" + }, + { + "facade": "hermes_cli.model_switch", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "hermes_cli.models", + "name": "LMStudioLoadResult", + "kind": "moved-lazy", + "target": "hermes_cli.models_local" + }, + { + "facade": "hermes_cli.models", + "name": "NamedTuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.models", + "name": "PROVIDER_GROUPS", + "kind": "moved-lazy", + "target": "hermes_cli.models_catalog_static" + }, + { + "facade": "hermes_cli.models", + "name": "ProviderEntry", + "kind": "moved-lazy", + "target": "hermes_cli.models_catalog_static" + }, + { + "facade": "hermes_cli.models", + "name": "atomic_json_write", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "hermes_cli.models", + "name": "base_url_host_matches", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "hermes_cli.models", + "name": "compute_sale_discount", + "kind": "moved-lazy", + "target": "hermes_cli.models_pricing" + }, + { + "facade": "hermes_cli.models", + "name": "ensure_lmstudio_model_loaded", + "kind": "moved-lazy", + "target": "hermes_cli.models_local" + }, + { + "facade": "hermes_cli.models", + "name": "fetch_ai_gateway_pricing", + "kind": "moved-lazy", + "target": "hermes_cli.models_pricing" + }, + { + "facade": "hermes_cli.models", + "name": "fetch_lmstudio_models", + "kind": "moved-lazy", + "target": "hermes_cli.models_local" + }, + { + "facade": "hermes_cli.models", + "name": "fetch_models_with_pricing", + "kind": "moved-lazy", + "target": "hermes_cli.models_pricing" + }, + { + "facade": "hermes_cli.models", + "name": "fetch_ollama_local_models", + "kind": "moved-lazy", + "target": "hermes_cli.models_local" + }, + { + "facade": "hermes_cli.models", + "name": "get_cached_nous_inference_base_url", + "kind": "moved-lazy", + "target": "hermes_cli.models_pricing" + }, + { + "facade": "hermes_cli.models", + "name": "get_close_matches", + "kind": "import", + "target": "difflib" + }, + { + "facade": "hermes_cli.models", + "name": "get_pricing_for_provider", + "kind": "moved-lazy", + "target": "hermes_cli.models_pricing" + }, + { + "facade": "hermes_cli.models", + "name": "group_providers", + "kind": "moved-lazy", + "target": "hermes_cli.models_catalog_static" + }, + { + "facade": "hermes_cli.models", + "name": "http", + "kind": "import", + "target": "http.client" + }, + { + "facade": "hermes_cli.models", + "name": "is_nous_free_tier", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.models", + "name": "_OPENCODE_KEYLESS_EXTRA_SLUGS", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of is_opencode_zen_free_model)" + }, + { + "facade": "hermes_cli.models", + "name": "is_opencode_zen_free_model", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.models", + "name": "lmstudio_model_reasoning_options", + "kind": "moved-lazy", + "target": "hermes_cli.models_local" + }, + { + "facade": "hermes_cli.models", + "name": "nous_catalog_url", + "kind": "moved-lazy", + "target": "hermes_cli.models_reasoning_caps" + }, + { + "facade": "hermes_cli.models", + "name": "nous_model_reasoning_capabilities", + "kind": "moved-lazy", + "target": "hermes_cli.models_reasoning_caps" + }, + { + "facade": "hermes_cli.models", + "name": "nous_policy_allowed_ids", + "kind": "moved-lazy", + "target": "hermes_cli.models_pricing" + }, + { + "facade": "hermes_cli.models", + "name": "ollama_model_supports_thinking", + "kind": "moved-lazy", + "target": "hermes_cli.models_local" + }, + { + "facade": "hermes_cli.models", + "name": "openrouter_model_reasoning_capabilities", + "kind": "moved-lazy", + "target": "hermes_cli.models_reasoning_caps" + }, + { + "facade": "hermes_cli.models", + "name": "parse_openrouter_reasoning_capabilities", + "kind": "moved-lazy", + "target": "hermes_cli.models_reasoning_caps" + }, + { + "facade": "hermes_cli.models", + "name": "peek_cached_pricing", + "kind": "moved-lazy", + "target": "hermes_cli.models_pricing" + }, + { + "facade": "hermes_cli.models", + "name": "pricing_cache_scope", + "kind": "moved-lazy", + "target": "hermes_cli.models_pricing" + }, + { + "facade": "hermes_cli.models", + "name": "probe_lmstudio_models", + "kind": "moved-lazy", + "target": "hermes_cli.models_local" + }, + { + "facade": "hermes_cli.models", + "name": "probe_ollama_local_models", + "kind": "moved-lazy", + "target": "hermes_cli.models_local" + }, + { + "facade": "hermes_cli.models", + "name": "provider_group_for_slug", + "kind": "moved-lazy", + "target": "hermes_cli.models_catalog_static" + }, + { + "facade": "hermes_cli.models", + "name": "refresh_reasoning_caps_async", + "kind": "moved-lazy", + "target": "hermes_cli.models_reasoning_caps" + }, + { + "facade": "hermes_cli.models", + "name": "restrict_to_nous_policy", + "kind": "moved-lazy", + "target": "hermes_cli.models_pricing" + }, + { + "facade": "hermes_cli.models", + "name": "should_use_ollama_native_catalog", + "kind": "moved-lazy", + "target": "hermes_cli.models_local" + }, + { + "facade": "hermes_cli.models", + "name": "url_origin", + "kind": "moved-lazy", + "target": "hermes_cli.urllib_security" + }, + { + "facade": "hermes_cli.models", + "name": "validate_requested_model", + "kind": "moved-lazy", + "target": "hermes_cli.models_validate" + }, + { + "facade": "hermes_cli.models", + "name": "warm_nous_reasoning_caps_async", + "kind": "moved-lazy", + "target": "hermes_cli.models_reasoning_caps" + }, + { + "facade": "hermes_cli.models", + "name": "warm_openrouter_reasoning_caps_async", + "kind": "moved-lazy", + "target": "hermes_cli.models_reasoning_caps" + }, + { + "facade": "hermes_cli.nous_billing", + "name": "BILLING_MANAGE_SCOPE", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.nous_subscription", + "name": "managed_nous_tools_enabled", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "CLIENT_ACTIVE_MARK", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "MODEL_CALL_PROFILE_MODEL", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "SCHEMA_KEY", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "SCHEMA_VERSION", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "SKILL_LIFECYCLE_MARK", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "SKILL_LOAD_MARK", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "TOOL_APPROVAL_MARK", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "TOOL_CALL_SCOPE", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "model_call_fields", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "prepare_session_start", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "skill_lifecycle_fields", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "skill_load_fields", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "task_start_fields", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "task_terminal_fields", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "task_terminal_state", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "tool_approval_outcome", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "tool_category", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.observability.relay_shared_metrics", + "name": "tool_terminal_fields", + "kind": "moved-lazy", + "target": "hermes_cli.observability.shared_metrics_contract" + }, + { + "facade": "hermes_cli.onepassword_secrets_cli", + "name": "Table", + "kind": "import", + "target": "rich.table" + }, + { + "facade": "hermes_cli.onepassword_secrets_cli", + "name": "masked_secret_prompt", + "kind": "moved-lazy", + "target": "hermes_cli.secret_prompt" + }, + { + "facade": "hermes_cli.onepassword_secrets_cli", + "name": "sys", + "kind": "import", + "target": "sys" + }, + { + "facade": "hermes_cli.platform_actions", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.plugins", + "name": "CAPABILITY_REGISTRY", + "kind": "moved-lazy", + "target": "hermes_cli.plugin_capabilities" + }, + { + "facade": "hermes_cli.plugins", + "name": "ENTRY_POINT_CAPABILITIES_GROUP", + "kind": "moved-lazy", + "target": "hermes_cli.plugins_discovery" + }, + { + "facade": "hermes_cli.plugins", + "name": "Iterable", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.plugins", + "name": "LEGACY_RELAY_PLUGIN_KEYS", + "kind": "moved-lazy", + "target": "hermes_cli.relay_plugin_cutover" + }, + { + "facade": "hermes_cli.plugins", + "name": "MAX_SYSTEM_PROMPT_SECTIONS", + "kind": "moved-lazy", + "target": "hermes_cli.plugins_dispatch" + }, + { + "facade": "hermes_cli.plugins", + "name": "OBSERVER_SCHEMA_VERSION", + "kind": "moved-lazy", + "target": "hermes_cli.middleware" + }, + { + "facade": "hermes_cli.plugins", + "name": "Type", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.plugins", + "name": "VALID_CAPABILITY_IDS", + "kind": "moved-lazy", + "target": "hermes_cli.plugin_capabilities" + }, + { + "facade": "hermes_cli.plugins", + "name": "cfg_get", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.plugins", + "name": "contextmanager", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "hermes_cli.plugins", + "name": "contextvars", + "kind": "import", + "target": "contextvars" + }, + { + "facade": "hermes_cli.plugins", + "name": "copy", + "kind": "import", + "target": "copy" + }, + { + "facade": "hermes_cli.plugins", + "name": "fast_safe_load", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "hermes_cli.plugins", + "name": "format_system_prompt_section", + "kind": "moved-lazy", + "target": "hermes_cli.plugins_dispatch" + }, + { + "facade": "hermes_cli.plugins", + "name": "get_plugin_subscriptions", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.plugins", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "hermes_cli.plugins", + "name": "reset_hermes_home_override", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "hermes_cli.plugins", + "name": "set_hermes_home_override", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "hermes_cli.plugins", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "hermes_cli.plugins", + "name": "unload_plugins", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.plugins", + "name": "wraps", + "kind": "import", + "target": "functools" + }, + { + "facade": "hermes_cli.plugins_cmd", + "name": "importlib", + "kind": "import", + "target": "importlib.metadata" + }, + { + "facade": "hermes_cli.profile_describer", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "hermes_cli.profile_distribution", + "name": "is_excluded_skill_path", + "kind": "moved-lazy", + "target": "agent.skill_utils" + }, + { + "facade": "hermes_cli.profiles", + "name": "has_bundled_skills_opt_out", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.runtime_provider", + "name": "custom_provider_aliases", + "kind": "moved-lazy", + "target": "hermes_cli.providers" + }, + { + "facade": "hermes_cli.runtime_provider", + "name": "custom_provider_slug", + "kind": "moved-lazy", + "target": "hermes_cli.providers" + }, + { + "facade": "hermes_cli.runtime_provider", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "hermes_cli.security_advisories", + "name": "render_doctor_section", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.setup", + "name": "Any", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.setup", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.setup", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.setup", + "name": "get_nous_subscription_features", + "kind": "moved-lazy", + "target": "hermes_cli.nous_subscription" + }, + { + "facade": "hermes_cli.setup", + "name": "get_optional_skills_dir", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "hermes_cli.setup", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "hermes_cli.setup", + "name": "managed_nous_tools_enabled", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "hermes_cli.setup", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "hermes_cli.slack_cli", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "hermes_cli.sqlite_safe_read", + "name": "SQLITE_HEADER_MAGIC", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.status", + "name": "format_nous_portal_entitlement_message", + "kind": "moved-lazy", + "target": "hermes_cli.nous_account" + }, + { + "facade": "hermes_cli.status", + "name": "get_nous_portal_account_info", + "kind": "moved-lazy", + "target": "hermes_cli.nous_account" + }, + { + "facade": "hermes_cli.status", + "name": "get_nous_subscription_features", + "kind": "moved-lazy", + "target": "hermes_cli.nous_subscription" + }, + { + "facade": "hermes_cli.status", + "name": "managed_nous_tools_enabled", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "hermes_cli.status", + "name": "redact_key", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.status", + "name": "subprocess", + "kind": "import", + "target": "subprocess" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "DEFAULT_MANAGER_BOT", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "auto_setup_telegram_bot", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "secrets", + "kind": "restored-import", + "target": "secrets" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "_USERNAME_SLUG_ALPHABET", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of generate_bot_username)" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "generate_username_slug", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of generate_bot_username)" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "generate_bot_username", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "urllib", + "kind": "restored-import", + "target": "urllib.parse" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "generate_deep_link", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "generate_pairing_nonce", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "generate_username_slug", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "poll_for_token", + "kind": "moved-lazy", + "target": "plugins.platforms.photon.auth" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "poll_pairing_once", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "secrets", + "kind": "import", + "target": "secrets" + }, + { + "facade": "hermes_cli.telegram_managed_bot", + "name": "urllib", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "hermes_cli.tools_config", + "name": "MANAGED_FEATURE_COVERAGE_CATEGORY", + "kind": "moved-lazy", + "target": "hermes_cli.nous_subscription" + }, + { + "facade": "hermes_cli.tools_config", + "name": "NOUS_MANAGED_PROVIDER", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "hermes_cli.tools_config", + "name": "base_url_hostname", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "hermes_cli.tools_config", + "name": "fal_key_is_configured", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "hermes_cli.tools_config", + "name": "format_nous_portal_entitlement_message", + "kind": "moved-lazy", + "target": "hermes_cli.nous_account" + }, + { + "facade": "hermes_cli.tools_config", + "name": "is_truthy_value", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "hermes_cli.tools_config", + "name": "save_env_value", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.tools_config", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "hermes_cli.tools_config", + "name": "subprocess", + "kind": "import", + "target": "subprocess" + }, + { + "facade": "hermes_cli.tools_config", + "name": "sys", + "kind": "import", + "target": "sys" + }, + { + "facade": "hermes_cli.uninstall", + "name": "find_shell_configs", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.update_cmd", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.update_cmd", + "name": "datetime", + "kind": "import", + "target": "datetime" + }, + { + "facade": "hermes_cli.update_cmd", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "hermes_cli.update_cmd", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "hermes_cli.update_inventory", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "hermes_cli.update_inventory", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "hermes_cli.web_deps", + "name": "get_dashboard_health", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.web_deps", + "name": "get_session_token", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.web_deps", + "name": "has_valid_session_token", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.web_deps", + "name": "late_attr", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_cli.web_routers.cron", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "hermes_cli.web_routers.mcp", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "hermes_cli.web_routers.sessions", + "name": "Any", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.web_routers.sessions", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.web_routers.sessions", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "hermes_cli.web_routers.skills", + "name": "LateState", + "kind": "moved-lazy", + "target": "hermes_cli.web_deps" + }, + { + "facade": "hermes_cli.web_routers.skills", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "hermes_cli.web_routers.tools", + "name": "LateState", + "kind": "moved-lazy", + "target": "hermes_cli.web_deps" + }, + { + "facade": "hermes_cli.web_routers.tools", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "hermes_cli.web_server", + "name": "AudioTranscriptionRequest", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "AutomationBlueprintInstantiate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "BackupRequest", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "BulkDeleteSessions", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "CONFIG_SCHEMA", + "kind": "moved-lazy", + "target": "hermes_cli.web_server_config" + }, + { + "facade": "hermes_cli.web_server", + "name": "ChatImageUpload", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ConfigUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "CredentialPoolAdd", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "CronJobCreate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "CronJobUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "CuratorPause", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "CustomEndpointUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "DEFAULT_CONFIG", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "DebugShareRequest", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "EnvVarDelete", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "EnvVarReveal", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "EnvVarUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "FontSetBody", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "FsWriteText", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "GitBranchSwitchBody", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "GitCommitBody", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "GitFileBody", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "GitPathBody", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "GitWorktreeAddBody", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "GitWorktreeRemoveBody", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "HookCreate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "HookDelete", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ImportRequest", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "LearningNodeEdit", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "LearningNodeRef", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.web_server", + "name": "Literal", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_cli.web_server", + "name": "MCPCatalogInstall", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "MCPEnabledToggle", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "MCPServerCreate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "MCPServersReplace", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ManagedDirectoryCreate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ManagedFileDelete", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ManagedFileUpload", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ManagedFilesPolicy", + "kind": "moved-lazy", + "target": "hermes_cli.web_server_files" + }, + { + "facade": "hermes_cli.web_server", + "name": "MemoryProviderConfigUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "MemoryProviderSelect", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "MemoryProviderSetupRequest", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "MemoryReset", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "MessagingPlatformUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "MoaConfigPayload", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "MoaModelSlot", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "MoaPresetPayload", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ModelAssignment", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "OAuthSubmitBody", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "OPTIONAL_ENV_VARS", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "PairingApprove", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "PairingRevoke", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ProfileActiveUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ProfileCreate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ProfileDescribeAuto", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ProfileDescriptionUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ProfileModelUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ProfileRename", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ProfileSoulUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ProviderConfigSchema", + "kind": "moved-lazy", + "target": "plugins.memory.config_schema" + }, + { + "facade": "hermes_cli.web_server", + "name": "ProviderField", + "kind": "moved-lazy", + "target": "plugins.memory.config_schema" + }, + { + "facade": "hermes_cli.web_server", + "name": "PtyBridge", + "kind": "moved-lazy", + "target": "hermes_cli.pty_bridge" + }, + { + "facade": "hermes_cli.web_server", + "name": "PtySessionRegistry", + "kind": "moved-lazy", + "target": "hermes_cli.pty_session" + }, + { + "facade": "hermes_cli.web_server", + "name": "PtyUnavailableError", + "kind": "moved-lazy", + "target": "hermes_cli.pty_bridge" + }, + { + "facade": "hermes_cli.web_server", + "name": "RawConfigUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "RegistryFull", + "kind": "moved-lazy", + "target": "hermes_cli.pty_session" + }, + { + "facade": "hermes_cli.web_server", + "name": "STORAGE_HONCHO_HOST_BLOCK", + "kind": "moved-lazy", + "target": "plugins.memory.config_schema" + }, + { + "facade": "hermes_cli.web_server", + "name": "SessionImport", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "SessionPrune", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "SessionRename", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "SkillContentUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "SkillCreate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "SkillInstallRequest", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "SkillToggle", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "SkillUninstallRequest", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "SkillsUpdateRequest", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "TTSLeaseRequest", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "TTSSpeakRequest", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "TelegramOnboardingApply", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "TelegramOnboardingStart", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "TerminalBackendSelect", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ThemeSetBody", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ToolsetEnvUpdate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ToolsetModelSelect", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ToolsetPostSetup", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ToolsetProviderSelect", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "ToolsetToggle", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "WebhookCreate", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "WebhookEnabledToggle", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "WhatsAppOnboardingApply", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "WhatsAppOnboardingStart", + "kind": "moved-lazy", + "target": "hermes_cli.web_models" + }, + { + "facade": "hermes_cli.web_server", + "name": "activate_custom_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "add_credential_pool_entry", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "add_mcp_server", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.mcp" + }, + { + "facade": "hermes_cli.web_server", + "name": "apply_telegram_onboarding", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.messaging" + }, + { + "facade": "hermes_cli.web_server", + "name": "apply_whatsapp_onboarding", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.messaging" + }, + { + "facade": "hermes_cli.web_server", + "name": "approve_pairing", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "atexit", + "kind": "import", + "target": "atexit" + }, + { + "facade": "hermes_cli.web_server", + "name": "auth_mcp_server", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.mcp" + }, + { + "facade": "hermes_cli.web_server", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "hermes_cli.web_server", + "name": "binascii", + "kind": "import", + "target": "binascii" + }, + { + "facade": "hermes_cli.web_server", + "name": "build_cron_model_impact", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "bulk_delete_sessions_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "cancel_oauth_session", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.oauth" + }, + { + "facade": "hermes_cli.web_server", + "name": "cancel_telegram_onboarding", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.messaging" + }, + { + "facade": "hermes_cli.web_server", + "name": "cancel_whatsapp_onboarding", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.messaging" + }, + { + "facade": "hermes_cli.web_server", + "name": "cfg_get", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "check_config_version", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "check_hermes_update", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.actions" + }, + { + "facade": "hermes_cli.web_server", + "name": "clear_model_endpoint_credentials", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "clear_pending_pairing", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "coerce_provider_id", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "concurrent", + "kind": "import", + "target": "concurrent.futures" + }, + { + "facade": "hermes_cli.web_server", + "name": "console_ws", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.chat_ws" + }, + { + "facade": "hermes_cli.web_server", + "name": "contextlib", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "hermes_cli.web_server", + "name": "contextmanager", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "hermes_cli.web_server", + "name": "count_empty_sessions_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "create_cron_job", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "create_hook", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "create_managed_directory", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "create_profile_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "create_skill", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.skills" + }, + { + "facade": "hermes_cli.web_server", + "name": "create_webhook", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "cron_fire_webhook", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "custom_endpoint_key_env", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "dataclass", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "hermes_cli.web_server", + "name": "datetime", + "kind": "import", + "target": "datetime" + }, + { + "facade": "hermes_cli.web_server", + "name": "delete_agent_plugin", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "delete_cron_job", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "delete_custom_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "delete_empty_sessions_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "delete_hook", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "delete_learning_node", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "delete_managed_file", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "delete_profile_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "delete_session_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "delete_webhook", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "derive_gateway_busy", + "kind": "moved-lazy", + "target": "gateway.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "derive_gateway_drainable", + "kind": "moved-lazy", + "target": "gateway.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "describe_profile_auto_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "detect_install_method", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "disconnect_oauth_provider", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.oauth" + }, + { + "facade": "hermes_cli.web_server", + "name": "download_dashboard_backup", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "download_managed_file", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "enable_webhooks", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "env_var_enabled", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "hermes_cli.web_server", + "name": "events_ws", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.chat_ws" + }, + { + "facade": "hermes_cli.web_server", + "name": "export_session_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "find_provider_entry", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "format_docker_update_message", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "fs_default_cwd", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "fs_download", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "fs_git_root", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "fs_list", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "fs_read_data_url", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "fs_read_text", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "fs_write_text", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "functools", + "kind": "import", + "target": "functools" + }, + { + "facade": "hermes_cli.web_server", + "name": "gateway_drain", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.actions" + }, + { + "facade": "hermes_cli.web_server", + "name": "gateway_ws", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.chat_ws" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_action_status", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.actions" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_active_profile_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_auxiliary_models", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.models" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_client_voice_config", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.audio" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_computer_use_status", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.tools" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_config", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_config_path", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_config_raw", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.analytics" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_cron_delivery_targets", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_cron_job", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_curator_status", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_dashboard_font", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_dashboard_plugins", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_dashboard_themes", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_defaults", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_egress_status", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_elevenlabs_voices", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.audio" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_env_path", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_env_vars", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_health", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_hermes_home", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_learning_graph", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_learning_node", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_logs", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_media", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_memory_provider_config", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.memory_providers" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_memory_status", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_messaging_platforms", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.messaging" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_moa_models", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.models" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_model_info", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.models" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_model_options", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.models" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_models_analytics", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.analytics" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_plugins_hub", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_portal_status", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_process_hermes_home", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_profile_setup_command", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_profile_soul", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_profiles_sessions", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_profiles_sessions_sidebar", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_provider_config_schema", + "kind": "moved-lazy", + "target": "plugins.memory.config_schema" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_recommended_default_model", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.models" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_running_pid", + "kind": "moved-lazy", + "target": "gateway.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_running_pid_cached", + "kind": "moved-lazy", + "target": "gateway.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_runtime_status_running_pid", + "kind": "moved-lazy", + "target": "gateway.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_schema", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_session_detail", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_session_latest_descendant", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_session_messages", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_session_stats", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_sessions", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_skill_content", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.skills" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_skills", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.skills" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_ssh_ownership", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_status", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_system_stats", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_telegram_onboarding_status", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.messaging" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_terminal_backends", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.tools" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_toolset_config", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.tools" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_toolset_models", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.tools" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_toolsets", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.tools" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_update_receipt", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.actions" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_usage_analytics", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.analytics" + }, + { + "facade": "hermes_cli.web_server", + "name": "get_whatsapp_onboarding_status", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.messaging" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_base_branches_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_branch_switch_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_branches_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_commit_context_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_commit_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_create_pr_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_file_diff_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_push_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_rev_parse_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_revert_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_review_diff_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_review_list_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_ship_info_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_stage_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_status_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_unstage_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_worktree_add_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_worktree_remove_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "git_worktrees_route", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.git" + }, + { + "facade": "hermes_cli.web_server", + "name": "grant_computer_use_permissions", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.tools" + }, + { + "facade": "hermes_cli.web_server", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "hermes_cli.web_server", + "name": "import_sessions_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "importlib", + "kind": "import", + "target": "importlib.util" + }, + { + "facade": "hermes_cli.web_server", + "name": "inspect", + "kind": "import", + "target": "inspect" + }, + { + "facade": "hermes_cli.web_server", + "name": "install_mcp_catalog_entry", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.mcp" + }, + { + "facade": "hermes_cli.web_server", + "name": "install_skill_hub", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.skills" + }, + { + "facade": "hermes_cli.web_server", + "name": "instantiate_blueprint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "ipaddress", + "kind": "import", + "target": "ipaddress" + }, + { + "facade": "hermes_cli.web_server", + "name": "is_nix_install_method", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_checkpoints", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_credential_pool", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_cron_blueprints", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_cron_job_runs", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_cron_jobs", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_custom_endpoints", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_hooks", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_managed_files", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_mcp_catalog", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.mcp" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_mcp_servers", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.mcp" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_oauth_providers", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.oauth" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_pairing", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_profiles_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_skills_hub_sources", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.skills" + }, + { + "facade": "hermes_cli.web_server", + "name": "list_webhooks", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "load_env", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "math", + "kind": "import", + "target": "math" + }, + { + "facade": "hermes_cli.web_server", + "name": "mcp_oauth_callback", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.mcp" + }, + { + "facade": "hermes_cli.web_server", + "name": "mcp_oauth_flow_status", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.mcp" + }, + { + "facade": "hermes_cli.web_server", + "name": "mimetypes", + "kind": "import", + "target": "mimetypes" + }, + { + "facade": "hermes_cli.web_server", + "name": "normalize_updated_at", + "kind": "moved-lazy", + "target": "gateway.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "open_profile_terminal_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "parse_active_agents", + "kind": "moved-lazy", + "target": "gateway.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "pause_cron_job", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "poll_oauth_session", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.oauth" + }, + { + "facade": "hermes_cli.web_server", + "name": "post_agent_plugin_disable", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "post_agent_plugin_enable", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "post_agent_plugin_install", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "post_agent_plugin_update", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "post_plugin_visibility", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "preview_skill_hub", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.skills" + }, + { + "facade": "hermes_cli.web_server", + "name": "prune_checkpoints", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "prune_sessions_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "pty_ws", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.chat_ws" + }, + { + "facade": "hermes_cli.web_server", + "name": "pub_ws", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.chat_ws" + }, + { + "facade": "hermes_cli.web_server", + "name": "put_plugin_providers", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "queue", + "kind": "import", + "target": "queue" + }, + { + "facade": "hermes_cli.web_server", + "name": "read_managed_file", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "read_raw_config", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "read_runtime_status", + "kind": "moved-lazy", + "target": "gateway.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "recommended_update_command_for_method", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "redact_key", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "remove_credential_pool_entry", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "remove_env_value", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "remove_env_var", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "remove_mcp_server", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.mcp" + }, + { + "facade": "hermes_cli.web_server", + "name": "rename_profile_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "rename_session_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "replace_mcp_servers", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.mcp" + }, + { + "facade": "hermes_cli.web_server", + "name": "rescan_dashboard_plugins", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "reset_memory", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "resolve_cron_model_drift_defaults", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "resolve_gateway_liveness", + "kind": "moved-lazy", + "target": "gateway.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "restart_gateway", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.actions" + }, + { + "facade": "hermes_cli.web_server", + "name": "resume_cron_job", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "reveal_env_var", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "revoke_pairing", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "run_backup", + "kind": "moved-lazy", + "target": "hermes_cli.backup" + }, + { + "facade": "hermes_cli.web_server", + "name": "run_config_migrate", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "run_curator", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "run_debug_share_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "run_doctor", + "kind": "moved-lazy", + "target": "hermes_cli.doctor" + }, + { + "facade": "hermes_cli.web_server", + "name": "run_dump", + "kind": "moved-lazy", + "target": "hermes_cli.dump" + }, + { + "facade": "hermes_cli.web_server", + "name": "run_import", + "kind": "moved-lazy", + "target": "hermes_cli.backup" + }, + { + "facade": "hermes_cli.web_server", + "name": "run_import_upload", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "run_prompt_size", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "run_security_audit", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "run_toolset_post_setup", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.tools" + }, + { + "facade": "hermes_cli.web_server", + "name": "save_config", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "save_env_value", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "save_toolset_env", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.tools" + }, + { + "facade": "hermes_cli.web_server", + "name": "scan_skill_hub", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.skills" + }, + { + "facade": "hermes_cli.web_server", + "name": "search_sessions", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.sessions" + }, + { + "facade": "hermes_cli.web_server", + "name": "search_skills_hub", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.skills" + }, + { + "facade": "hermes_cli.web_server", + "name": "select_terminal_backend", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.tools" + }, + { + "facade": "hermes_cli.web_server", + "name": "select_toolset_model", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.tools" + }, + { + "facade": "hermes_cli.web_server", + "name": "select_toolset_provider", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.tools" + }, + { + "facade": "hermes_cli.web_server", + "name": "serve_plugin_asset", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "set_active_profile_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "set_curator_paused", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "set_dashboard_font", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "set_dashboard_theme", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.dashboard_ui" + }, + { + "facade": "hermes_cli.web_server", + "name": "set_env_var", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "set_mcp_server_enabled", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.mcp" + }, + { + "facade": "hermes_cli.web_server", + "name": "set_memory_provider", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "set_moa_models", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.models" + }, + { + "facade": "hermes_cli.web_server", + "name": "set_model_assignment", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.models" + }, + { + "facade": "hermes_cli.web_server", + "name": "set_webhook_enabled", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "setup_memory_provider", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.memory_providers" + }, + { + "facade": "hermes_cli.web_server", + "name": "shlex", + "kind": "import", + "target": "shlex" + }, + { + "facade": "hermes_cli.web_server", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "hermes_cli.web_server", + "name": "speak_stream_ws", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.audio" + }, + { + "facade": "hermes_cli.web_server", + "name": "speak_text", + "kind": "moved-lazy", + "target": "hermes_cli.voice" + }, + { + "facade": "hermes_cli.web_server", + "name": "start_gateway", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "start_oauth_login", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.oauth" + }, + { + "facade": "hermes_cli.web_server", + "name": "start_telegram_onboarding", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.messaging" + }, + { + "facade": "hermes_cli.web_server", + "name": "start_whatsapp_onboarding", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.messaging" + }, + { + "facade": "hermes_cli.web_server", + "name": "stat", + "kind": "import", + "target": "stat" + }, + { + "facade": "hermes_cli.web_server", + "name": "stop_gateway", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.ops" + }, + { + "facade": "hermes_cli.web_server", + "name": "stream_managed_file", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "submit_oauth_code", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.oauth" + }, + { + "facade": "hermes_cli.web_server", + "name": "tempfile", + "kind": "import", + "target": "tempfile" + }, + { + "facade": "hermes_cli.web_server", + "name": "test_mcp_server", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.mcp" + }, + { + "facade": "hermes_cli.web_server", + "name": "test_messaging_platform", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.messaging" + }, + { + "facade": "hermes_cli.web_server", + "name": "timezone", + "kind": "import", + "target": "datetime" + }, + { + "facade": "hermes_cli.web_server", + "name": "toggle_skill", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.skills" + }, + { + "facade": "hermes_cli.web_server", + "name": "toggle_toolset", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.tools" + }, + { + "facade": "hermes_cli.web_server", + "name": "transcribe_audio_upload", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.audio" + }, + { + "facade": "hermes_cli.web_server", + "name": "trigger_cron_job", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "tts_lease", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.audio" + }, + { + "facade": "hermes_cli.web_server", + "name": "uninstall_skill_hub", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.skills" + }, + { + "facade": "hermes_cli.web_server", + "name": "update_config", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "update_config_raw", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.analytics" + }, + { + "facade": "hermes_cli.web_server", + "name": "update_cron_job", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.cron" + }, + { + "facade": "hermes_cli.web_server", + "name": "update_hermes", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.actions" + }, + { + "facade": "hermes_cli.web_server", + "name": "update_learning_node", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.status" + }, + { + "facade": "hermes_cli.web_server", + "name": "update_memory_provider_config", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.memory_providers" + }, + { + "facade": "hermes_cli.web_server", + "name": "update_messaging_platform", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.messaging" + }, + { + "facade": "hermes_cli.web_server", + "name": "update_profile_description_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "update_profile_model_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "update_profile_soul", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.profiles" + }, + { + "facade": "hermes_cli.web_server", + "name": "update_skill_content", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.skills" + }, + { + "facade": "hermes_cli.web_server", + "name": "update_skills_hub", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.skills" + }, + { + "facade": "hermes_cli.web_server", + "name": "upload_chat_image", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "upload_managed_file", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "upload_managed_file_stream", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.files" + }, + { + "facade": "hermes_cli.web_server", + "name": "upsert_custom_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "validate_custom_endpoint", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "validate_provider_credential", + "kind": "moved-lazy", + "target": "hermes_cli.web_routers.config_env" + }, + { + "facade": "hermes_cli.web_server", + "name": "windows_detach_flags", + "kind": "moved-lazy", + "target": "hermes_cli._subprocess_compat" + }, + { + "facade": "hermes_cli.web_server", + "name": "windows_hide_flags", + "kind": "moved-lazy", + "target": "hermes_cli._subprocess_compat" + }, + { + "facade": "hermes_cli.web_server", + "name": "write_platform_config_field", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "hermes_cli.web_server", + "name": "yaml", + "kind": "import", + "target": "yaml" + }, + { + "facade": "hermes_cli.web_server", + "name": "zipfile", + "kind": "import", + "target": "zipfile" + }, + { + "facade": "hermes_cli.webhook", + "name": "atomic_replace", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "hermes_cli.webhook", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "hermes_cli.webhook", + "name": "tempfile", + "kind": "import", + "target": "tempfile" + }, + { + "facade": "hermes_cli.win_pty_bridge", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "hermes_logging", + "name": "rotating_file_handlers", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_state", + "name": "AUTO_VACUUM_MIN_FREELIST_RATIO", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "ActivityProvenance", + "kind": "moved-lazy", + "target": "agent.session_activity" + }, + { + "facade": "hermes_state", + "name": "CompressionSessionBusyError", + "kind": "moved-lazy", + "target": "hermes_state_errors" + }, + { + "facade": "hermes_state", + "name": "CompressionSessionClosedError", + "kind": "moved-lazy", + "target": "hermes_state_errors" + }, + { + "facade": "hermes_state", + "name": "DEFERRED_INDEX_SQL", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "FTS_CJK_STALE_KEY", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "FTS_CJK_TABLE_SQL", + "kind": "moved-lazy", + "target": "hermes_state_fts" + }, + { + "facade": "hermes_state", + "name": "FTS_CJK_TRIGGER_SQL", + "kind": "moved-lazy", + "target": "hermes_state_fts" + }, + { + "facade": "hermes_state", + "name": "FTS_REBUILD_DEFERRAL_KEY", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "FTS_SQL", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "FTS_STALE_KEY", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "FTS_STORAGE_VERSION", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "FTS_TRIGRAM_SQL", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "LEGACY_FTS_SQL", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "LEGACY_FTS_TRIGRAM_SQL", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "MAX_FTS5_QUERY_CHARS", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "MAX_SAFE_EXPORT_MESSAGES", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_state", + "name": "MAX_SAFE_RESUME_MESSAGES", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_state", + "name": "PERSISTENCE_ERROR_CAUSES", + "kind": "moved-lazy", + "target": "hermes_state_errors" + }, + { + "facade": "hermes_state", + "name": "SCHEMA_SQL", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "SCHEMA_VERSION", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "SESSION_STATUS_COMPLETE", + "kind": "moved-lazy", + "target": "hermes_state_sessions" + }, + { + "facade": "hermes_state", + "name": "SESSION_STATUS_EMPTY", + "kind": "moved-lazy", + "target": "hermes_state_sessions" + }, + { + "facade": "hermes_state", + "name": "SESSION_STATUS_ERROR", + "kind": "moved-lazy", + "target": "hermes_state_sessions" + }, + { + "facade": "hermes_state", + "name": "SESSION_STATUS_INTERRUPTED", + "kind": "moved-lazy", + "target": "hermes_state_sessions" + }, + { + "facade": "hermes_state", + "name": "SKILL_EXCERPT_JOINT", + "kind": "moved-lazy", + "target": "agent.skill_commands" + }, + { + "facade": "hermes_state", + "name": "SKILL_SCAFFOLD_SQL_LIKE", + "kind": "moved-lazy", + "target": "agent.skill_commands" + }, + { + "facade": "hermes_state", + "name": "SessionTurnLeaseLostError", + "kind": "moved-lazy", + "target": "hermes_state_errors" + }, + { + "facade": "hermes_state", + "name": "Set", + "kind": "import", + "target": "typing" + }, + { + "facade": "hermes_state", + "name": "WalUnsupportedError", + "kind": "moved-lazy", + "target": "hermes_state_wal" + }, + { + "facade": "hermes_state", + "name": "apply_durability_barriers", + "kind": "moved-lazy", + "target": "hermes_state_repair" + }, + { + "facade": "hermes_state", + "name": "classify_session_status", + "kind": "moved-lazy", + "target": "hermes_state_sessions" + }, + { + "facade": "hermes_state", + "name": "collect_state_db_stats", + "kind": "moved-lazy", + "target": "hermes_state_dbfile" + }, + { + "facade": "hermes_state", + "name": "contextlib", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "hermes_state", + "name": "count_db_holders", + "kind": "moved-lazy", + "target": "hermes_state_dbfile" + }, + { + "facade": "hermes_state", + "name": "describe_skill_invocation", + "kind": "moved-lazy", + "target": "agent.skill_commands" + }, + { + "facade": "hermes_state", + "name": "errno", + "kind": "import", + "target": "errno" + }, + { + "facade": "hermes_state", + "name": "fts5_cjk_so_path", + "kind": "moved-lazy", + "target": "hermes_state_fts" + }, + { + "facade": "hermes_state", + "name": "is_advisory_lock_contention", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "is_automatic_end_reason", + "kind": "moved-lazy", + "target": "hermes_state_common" + }, + { + "facade": "hermes_state", + "name": "is_disk_full_error", + "kind": "moved-lazy", + "target": "hermes_state_errors" + }, + { + "facade": "hermes_state", + "name": "is_sqlite_wal_reset_vulnerable", + "kind": "moved-lazy", + "target": "hermes_state_wal" + }, + { + "facade": "hermes_state", + "name": "is_transient_sqlite_error", + "kind": "moved-lazy", + "target": "hermes_state_errors" + }, + { + "facade": "hermes_state", + "name": "iter_deleted_sqlite_sidecar_holders", + "kind": "moved-lazy", + "target": "hermes_state_dbfile" + }, + { + "facade": "hermes_state", + "name": "release_or_close", + "kind": "moved-lazy", + "target": "hermes_state_registry" + }, + { + "facade": "hermes_state", + "name": "report_startup_progress", + "kind": "moved-lazy", + "target": "hermes_startup_watchdog" + }, + { + "facade": "hermes_state", + "name": "resolve_journal_mode", + "kind": "moved-lazy", + "target": "hermes_state_wal" + }, + { + "facade": "hermes_state", + "name": "resolve_synchronous_level", + "kind": "moved-lazy", + "target": "hermes_state_wal" + }, + { + "facade": "hermes_state", + "name": "sanitize_context", + "kind": "moved-lazy", + "target": "agent.memory_manager" + }, + { + "facade": "hermes_state", + "name": "sqlite_source_id", + "kind": "moved-lazy", + "target": "hermes_state_wal" + }, + { + "facade": "hermes_state", + "name": "struct", + "kind": "import", + "target": "struct" + }, + { + "facade": "hermes_state", + "name": "weakref", + "kind": "import", + "target": "weakref" + }, + { + "facade": "hermes_state", + "name": "workspace_key", + "kind": "moved-lazy", + "target": "hermes_state_sessions" + }, + { + "facade": "hermes_state_registry", + "name": "close_shared_session_dbs", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_state_registry", + "name": "get_shared_session_db", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_state_registry", + "name": "release_shared_session_db", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "hermes_state_search", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "hermes_state_search", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.browser.browser_use.provider", + "name": "BrowserProvider", + "kind": "moved-lazy", + "target": "agent.browser_provider" + }, + { + "facade": "plugins.browser.browser_use.provider", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.browser.browserbase.provider", + "name": "BrowserProvider", + "kind": "moved-lazy", + "target": "agent.browser_provider" + }, + { + "facade": "plugins.browser.browserbase.provider", + "name": "requests", + "kind": "import", + "target": "requests" + }, + { + "facade": "plugins.browser.browserbase.provider", + "name": "uuid", + "kind": "import", + "target": "uuid" + }, + { + "facade": "plugins.browser.firecrawl.provider", + "name": "BrowserProvider", + "kind": "moved-lazy", + "target": "agent.browser_provider" + }, + { + "facade": "plugins.browser.firecrawl.provider", + "name": "requests", + "kind": "import", + "target": "requests" + }, + { + "facade": "plugins.browser.firecrawl.provider", + "name": "uuid", + "kind": "import", + "target": "uuid" + }, + { + "facade": "plugins.context_engine", + "name": "importlib", + "kind": "import", + "target": "importlib.util" + }, + { + "facade": "plugins.context_engine", + "name": "sys", + "kind": "import", + "target": "sys" + }, + { + "facade": "plugins.cron_providers", + "name": "importlib", + "kind": "import", + "target": "importlib.util" + }, + { + "facade": "plugins.cron_providers", + "name": "sys", + "kind": "import", + "target": "sys" + }, + { + "facade": "plugins.cron_providers.chronos", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "plugins.cron_providers.chronos._nas_client", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "plugins.dashboard_auth.basic", + "name": "Any", + "kind": "import", + "target": "typing" + }, + { + "facade": "plugins.dashboard_auth.basic", + "name": "LoginStart", + "kind": "moved-lazy", + "target": "hermes_cli.dashboard_auth" + }, + { + "facade": "plugins.dashboard_auth.drain", + "name": "LoginStart", + "kind": "moved-lazy", + "target": "hermes_cli.dashboard_auth" + }, + { + "facade": "plugins.dashboard_auth.nous", + "name": "DashboardAuthProvider", + "kind": "moved-lazy", + "target": "hermes_cli.dashboard_auth" + }, + { + "facade": "plugins.dashboard_auth.nous", + "name": "InvalidCodeError", + "kind": "moved-lazy", + "target": "hermes_cli.dashboard_auth" + }, + { + "facade": "plugins.dashboard_auth.nous", + "name": "RefreshExpiredError", + "kind": "moved-lazy", + "target": "hermes_cli.dashboard_auth" + }, + { + "facade": "plugins.dashboard_auth.nous", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "plugins.dashboard_auth.nous", + "name": "classify_jwks_lookup_error", + "kind": "moved-lazy", + "target": "hermes_cli.dashboard_auth" + }, + { + "facade": "plugins.dashboard_auth.nous", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "plugins.dashboard_auth.nous", + "name": "httpx", + "kind": "import", + "target": "httpx" + }, + { + "facade": "plugins.dashboard_auth.nous", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.dashboard_auth.nous", + "name": "secrets", + "kind": "import", + "target": "secrets" + }, + { + "facade": "plugins.dashboard_auth.nous", + "name": "urllib", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "plugins.dashboard_auth.self_hosted", + "name": "DashboardAuthProvider", + "kind": "moved-lazy", + "target": "hermes_cli.dashboard_auth" + }, + { + "facade": "plugins.dashboard_auth.self_hosted", + "name": "InvalidCodeError", + "kind": "moved-lazy", + "target": "hermes_cli.dashboard_auth" + }, + { + "facade": "plugins.dashboard_auth.self_hosted", + "name": "RefreshExpiredError", + "kind": "moved-lazy", + "target": "hermes_cli.dashboard_auth" + }, + { + "facade": "plugins.dashboard_auth.self_hosted", + "name": "classify_jwks_lookup_error", + "kind": "moved-lazy", + "target": "hermes_cli.dashboard_auth" + }, + { + "facade": "plugins.dashboard_auth.self_hosted", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "plugins.dashboard_auth.self_hosted", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.dashboard_auth.self_hosted", + "name": "secrets", + "kind": "import", + "target": "secrets" + }, + { + "facade": "plugins.google_meet.audio_bridge", + "name": "chrome_fake_audio_flags", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.google_meet.meet_bot", + "name": "SAY_PCM_FILENAME", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.google_meet.meet_bot", + "name": "SAY_QUEUE_FILENAME", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.google_meet.meet_bot", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "plugins.google_meet.node.cli", + "name": "Any", + "kind": "import", + "target": "typing" + }, + { + "facade": "plugins.google_meet.node.registry", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "plugins.image_gen.deepinfra", + "name": "ImageGenProvider", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.deepinfra", + "name": "error_response", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.deepinfra", + "name": "save_b64_image", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.deepinfra", + "name": "save_url_image", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.fal", + "name": "ImageGenProvider", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.fal", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.image_gen.krea", + "name": "ImageGenProvider", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.krea", + "name": "error_response", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.krea", + "name": "normalize_reference_images", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.krea", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.image_gen.openai", + "name": "ImageGenProvider", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.openai", + "name": "error_response", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.openai", + "name": "normalize_reference_images", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.openai", + "name": "save_b64_image", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.openai", + "name": "save_url_image", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.xai", + "name": "ImageGenProvider", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.xai", + "name": "error_response", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.xai", + "name": "normalize_reference_images", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.xai", + "name": "save_b64_image", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.image_gen.xai", + "name": "save_url_image", + "kind": "moved-lazy", + "target": "agent.image_gen_provider" + }, + { + "facade": "plugins.memory.hindsight", + "name": "dataclass", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "plugins.memory.hindsight", + "name": "importlib", + "kind": "import", + "target": "importlib" + }, + { + "facade": "plugins.memory.honcho", + "name": "CONCLUDE_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.honcho.tool_schemas" + }, + { + "facade": "plugins.memory.honcho", + "name": "CONTEXT_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.honcho.tool_schemas" + }, + { + "facade": "plugins.memory.honcho", + "name": "PROFILE_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.honcho.tool_schemas" + }, + { + "facade": "plugins.memory.honcho", + "name": "REASONING_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.honcho.tool_schemas" + }, + { + "facade": "plugins.memory.honcho", + "name": "SEARCH_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.honcho.tool_schemas" + }, + { + "facade": "plugins.memory.honcho", + "name": "TRIVIAL_PROMPT_RE", + "kind": "moved-lazy", + "target": "agent.memory_provider" + }, + { + "facade": "plugins.memory.honcho.client", + "name": "SingletonSlot", + "kind": "moved-lazy", + "target": "plugins.plugin_utils" + }, + { + "facade": "plugins.memory.honcho.oauth", + "name": "Callable", + "kind": "import", + "target": "typing" + }, + { + "facade": "plugins.memory.honcho.session", + "name": "Callable", + "kind": "import", + "target": "typing" + }, + { + "facade": "plugins.memory.honcho.session", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "plugins.memory.honcho.session", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "plugins.memory.honcho.session", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "plugins.memory.mem0", + "name": "ADD_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.memory.mem0", + "name": "DELETE_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.memory.mem0", + "name": "SEARCH_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.honcho.tool_schemas" + }, + { + "facade": "plugins.memory.mem0", + "name": "UPDATE_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.memory.mem0._setup", + "name": "has_oss_flags", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.memory.retaindb", + "name": "CONTEXT_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.honcho.tool_schemas" + }, + { + "facade": "plugins.memory.retaindb", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "plugins.memory.retaindb", + "name": "FILE_DELETE_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.memory.retaindb", + "name": "FILE_INGEST_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.memory.retaindb", + "name": "FILE_LIST_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.memory.retaindb", + "name": "FILE_READ_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.memory.retaindb", + "name": "FILE_UPLOAD_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.memory.retaindb", + "name": "FORGET_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.openviking" + }, + { + "facade": "plugins.memory.retaindb", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "plugins.memory.retaindb", + "name": "PROFILE_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.honcho.tool_schemas" + }, + { + "facade": "plugins.memory.retaindb", + "name": "REMEMBER_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.openviking" + }, + { + "facade": "plugins.memory.retaindb", + "name": "SEARCH_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.honcho.tool_schemas" + }, + { + "facade": "plugins.memory.supermemory", + "name": "FORGET_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.openviking" + }, + { + "facade": "plugins.memory.supermemory", + "name": "PROFILE_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.honcho.tool_schemas" + }, + { + "facade": "plugins.memory.supermemory", + "name": "SEARCH_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.memory.honcho.tool_schemas" + }, + { + "facade": "plugins.memory.supermemory", + "name": "STORE_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.protocol", + "name": "ERR_PUSH_NOT_SUPPORTED", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.protocol", + "name": "STATE_AUTH_REQUIRED", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.protocol", + "name": "copy", + "kind": "import", + "target": "copy" + }, + { + "facade": "plugins.platforms.a2a.protocol", + "name": "data_part", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.protocol", + "name": "file_part", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.protocol", + "name": "message_with_parts", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.protocol", + "name": "stream_message", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.security", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "plugins.platforms.a2a.security", + "name": "authenticate", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.security", + "name": "get_bearer_token", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.security", + "name": "get_peer_tokens", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.security", + "name": "get_push_secret", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.security", + "name": "get_trusted_peers", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.security", + "name": "is_trusted_peer", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.security", + "name": "resolve_bind_host", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.security", + "name": "sign_push_payload", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.a2a.tools", + "name": "TypedDict", + "kind": "import", + "target": "typing" + }, + { + "facade": "plugins.platforms.dingtalk.adapter", + "name": "DINGTALK_TYPE_MAPPING", + "kind": "moved-lazy", + "target": "plugins.platforms.dingtalk.inbound" + }, + { + "facade": "plugins.platforms.dingtalk.adapter", + "name": "EXT_MAP", + "kind": "moved-lazy", + "target": "plugins.platforms.dingtalk.inbound" + }, + { + "facade": "plugins.platforms.dingtalk.adapter", + "name": "MessageType", + "kind": "moved-lazy", + "target": "gateway.platforms.base" + }, + { + "facade": "plugins.platforms.discord.adapter", + "name": "env_int", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "plugins.platforms.feishu.feishu_comment", + "name": "add_comment_reaction", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.feishu.feishu_comment", + "name": "delete_comment_reaction", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.google_chat.oauth", + "name": "atomic_replace", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "plugins.platforms.google_chat.oauth", + "name": "secrets", + "kind": "import", + "target": "secrets" + }, + { + "facade": "plugins.platforms.google_chat.oauth", + "name": "subprocess", + "kind": "import", + "target": "subprocess" + }, + { + "facade": "plugins.platforms.irc.adapter", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.platforms.line.adapter", + "name": "field", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "plugins.platforms.matrix.adapter", + "name": "MAX_MESSAGE_LENGTH", + "kind": "moved-lazy", + "target": "gateway.platforms.signal" + }, + { + "facade": "plugins.platforms.matrix.adapter", + "name": "_MATRIX_CAPABILITIES", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of get_matrix_capabilities)" + }, + { + "facade": "plugins.platforms.matrix.adapter", + "name": "get_matrix_capabilities", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.ntfy.adapter", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.platforms.photon.adapter", + "name": "ProcessingOutcome", + "kind": "moved-lazy", + "target": "gateway.platforms.base" + }, + { + "facade": "plugins.platforms.photon.adapter", + "name": "resolve_sidecar_dir", + "kind": "moved-lazy", + "target": "plugins.platforms.photon.sidecar_paths" + }, + { + "facade": "plugins.platforms.photon.auth", + "name": "credential_summary", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.photon.auth", + "name": "get_session", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.photon.cli", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "plugins.platforms.photon.cli", + "name": "resolve_sidecar_dir", + "kind": "moved-lazy", + "target": "plugins.platforms.photon.sidecar_paths" + }, + { + "facade": "plugins.platforms.raft.adapter", + "name": "asyncio", + "kind": "import", + "target": "asyncio" + }, + { + "facade": "plugins.platforms.teams.adapter", + "name": "TeamsSummaryWriter", + "kind": "moved-lazy", + "target": "plugins.platforms.teams.summary_writer" + }, + { + "facade": "plugins.platforms.teams.adapter", + "name": "html", + "kind": "import", + "target": "html" + }, + { + "facade": "plugins.platforms.teams.adapter", + "name": "quote", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "plugins.platforms.telegram.adapter", + "name": "atomic_replace", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "plugins.platforms.telegram.adapter", + "name": "cache_document_from_bytes", + "kind": "moved-lazy", + "target": "gateway.platforms.base" + }, + { + "facade": "plugins.platforms.telegram.adapter", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "plugins.platforms.telegram.telegram_ids", + "name": "telegram_chat_id_key", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "ABSOLUTE_MAX_BYTES", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.media" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "APP_CMD_UPLOAD_MEDIA_CHUNK", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.media" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "APP_CMD_UPLOAD_MEDIA_FINISH", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.media" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "APP_CMD_UPLOAD_MEDIA_INIT", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.media" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "FILE_MAX_BYTES", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.media" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "IMAGE_MAX_BYTES", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.media" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "MAX_INTERMEDIATE_FRAMES", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.streaming" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "MAX_UPLOAD_CHUNKS", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.media" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "ReplyFrame", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.streaming" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "STREAM_EXPIRED_ERRCODE", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.streaming" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "STREAM_REQUEST_EXPIRED_ERRCODE", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.streaming" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "STREAM_VERSION_CONFLICT_ERRCODE", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.streaming" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "UPLOAD_CHUNK_SIZE", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.media" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "VIDEO_MAX_BYTES", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.media" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "VOICE_MAX_BYTES", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.media" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "VOICE_SUPPORTED_MIMES", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.media" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "WeComStreamExpiredError", + "kind": "moved-lazy", + "target": "plugins.platforms.wecom.streaming" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "cache_document_from_bytes_async", + "kind": "moved-lazy", + "target": "gateway.platforms.base" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "cache_image_from_bytes_async", + "kind": "moved-lazy", + "target": "gateway.platforms.base" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "dataclass", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "deque", + "kind": "import", + "target": "collections" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "mimetypes", + "kind": "import", + "target": "mimetypes" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "unquote", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "plugins.platforms.wecom.adapter", + "name": "urlparse", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "plugins.spotify", + "name": "SPOTIFY_ALBUMS_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.spotify.tools" + }, + { + "facade": "plugins.spotify", + "name": "SPOTIFY_DEVICES_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.spotify.tools" + }, + { + "facade": "plugins.spotify", + "name": "SPOTIFY_LIBRARY_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.spotify.tools" + }, + { + "facade": "plugins.spotify", + "name": "SPOTIFY_PLAYBACK_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.spotify.tools" + }, + { + "facade": "plugins.spotify", + "name": "SPOTIFY_PLAYLISTS_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.spotify.tools" + }, + { + "facade": "plugins.spotify", + "name": "SPOTIFY_QUEUE_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.spotify.tools" + }, + { + "facade": "plugins.spotify", + "name": "SPOTIFY_SEARCH_SCHEMA", + "kind": "moved-lazy", + "target": "plugins.spotify.tools" + }, + { + "facade": "plugins.spotify.client", + "name": "compact_json", + "kind": "moved-lazy", + "target": "gateway.hosted_rooms_common" + }, + { + "facade": "plugins.spotify.client", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "plugins.spotify.tools", + "name": "SpotifyAPIError", + "kind": "moved-lazy", + "target": "plugins.spotify.client" + }, + { + "facade": "plugins.spotify.tools", + "name": "SpotifyAuthRequiredError", + "kind": "moved-lazy", + "target": "plugins.spotify.client" + }, + { + "facade": "plugins.teams_pipeline.cli", + "name": "GraphSubscription", + "kind": "moved-lazy", + "target": "plugins.teams_pipeline.models" + }, + { + "facade": "plugins.teams_pipeline.cli", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "plugins.teams_pipeline.cli", + "name": "datetime", + "kind": "import", + "target": "datetime" + }, + { + "facade": "plugins.teams_pipeline.cli", + "name": "timedelta", + "kind": "import", + "target": "datetime" + }, + { + "facade": "plugins.teams_pipeline.cli", + "name": "timezone", + "kind": "import", + "target": "datetime" + }, + { + "facade": "plugins.teams_pipeline.meetings", + "name": "fetch_call_record_artifact", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.teams_pipeline.pipeline", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.teams_pipeline.subscriptions", + "name": "resolve_teams_pipeline_store_path", + "kind": "restored-import", + "target": "plugins.teams_pipeline.store" + }, + { + "facade": "plugins.teams_pipeline.subscriptions", + "name": "resolve_store_path", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of build_store)" + }, + { + "facade": "plugins.teams_pipeline.subscriptions", + "name": "build_store", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.teams_pipeline.subscriptions", + "name": "resolve_store_path", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.teams_pipeline.subscriptions", + "name": "resolve_teams_pipeline_store_path", + "kind": "moved-lazy", + "target": "plugins.teams_pipeline.store" + }, + { + "facade": "plugins.video_gen.fal", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.video_gen.xai", + "name": "_run_xai_video_coroutine", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of run_xai_video_generation)" + }, + { + "facade": "plugins.video_gen.xai", + "name": "run_xai_video_generation", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "plugins.web.brave_free.provider", + "name": "WebSearchProvider", + "kind": "moved-lazy", + "target": "agent.web_search_provider" + }, + { + "facade": "plugins.web.brave_free.provider", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.web.ddgs.provider", + "name": "WebSearchProvider", + "kind": "moved-lazy", + "target": "agent.web_search_provider" + }, + { + "facade": "plugins.web.exa.provider", + "name": "WebSearchProvider", + "kind": "moved-lazy", + "target": "agent.web_search_provider" + }, + { + "facade": "plugins.web.exa.provider", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.web.firecrawl.provider", + "name": "NoReturn", + "kind": "import", + "target": "typing" + }, + { + "facade": "plugins.web.firecrawl.provider", + "name": "TYPE_CHECKING", + "kind": "import", + "target": "typing" + }, + { + "facade": "plugins.web.firecrawl.provider", + "name": "WebSearchProvider", + "kind": "moved-lazy", + "target": "agent.web_search_provider" + }, + { + "facade": "plugins.web.firecrawl.provider", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.web.keenable.provider", + "name": "WebSearchProvider", + "kind": "moved-lazy", + "target": "agent.web_search_provider" + }, + { + "facade": "plugins.web.parallel.provider", + "name": "WebSearchProvider", + "kind": "moved-lazy", + "target": "agent.web_search_provider" + }, + { + "facade": "plugins.web.searxng.provider", + "name": "WebSearchProvider", + "kind": "moved-lazy", + "target": "agent.web_search_provider" + }, + { + "facade": "plugins.web.searxng.provider", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "plugins.web.tavily.provider", + "name": "WebSearchProvider", + "kind": "moved-lazy", + "target": "agent.web_search_provider" + }, + { + "facade": "plugins.web.xai.provider", + "name": "WebSearchProvider", + "kind": "moved-lazy", + "target": "agent.web_search_provider" + }, + { + "facade": "providers", + "name": "OMIT_TEMPERATURE", + "kind": "moved-lazy", + "target": "providers.base" + }, + { + "facade": "run_agent", + "name": "COMPRESSED_SUMMARY_METADATA_KEY", + "kind": "moved-lazy", + "target": "agent.context_compressor" + }, + { + "facade": "run_agent", + "name": "ContextCompressor", + "kind": "moved-lazy", + "target": "agent.context_compressor" + }, + { + "facade": "run_agent", + "name": "DEFAULT_AGENT_IDENTITY", + "kind": "moved-lazy", + "target": "agent.prompt_builder" + }, + { + "facade": "run_agent", + "name": "FailoverReason", + "kind": "moved-lazy", + "target": "agent.error_classifier" + }, + { + "facade": "run_agent", + "name": "OpenAI", + "kind": "moved-lazy", + "target": "agent.process_bootstrap" + }, + { + "facade": "run_agent", + "name": "SimpleNamespace", + "kind": "import", + "target": "types" + }, + { + "facade": "run_agent", + "name": "asyncio", + "kind": "import", + "target": "asyncio" + }, + { + "facade": "run_agent", + "name": "atomic_json_write", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "run_agent", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "run_agent", + "name": "build_context_files_prompt", + "kind": "moved-lazy", + "target": "agent.prompt_builder" + }, + { + "facade": "run_agent", + "name": "build_environment_hints", + "kind": "moved-lazy", + "target": "agent.prompt_builder" + }, + { + "facade": "run_agent", + "name": "build_skills_system_prompt", + "kind": "moved-lazy", + "target": "agent.prompt_builder" + }, + { + "facade": "run_agent", + "name": "check_toolset_requirements", + "kind": "moved-lazy", + "target": "model_tools" + }, + { + "facade": "run_agent", + "name": "convert_scratchpad_to_think", + "kind": "moved-lazy", + "target": "agent.trajectory" + }, + { + "facade": "run_agent", + "name": "copy", + "kind": "import", + "target": "copy" + }, + { + "facade": "run_agent", + "name": "estimate_request_tokens_rough", + "kind": "moved-lazy", + "target": "agent.model_metadata" + }, + { + "facade": "run_agent", + "name": "file_mutation_result_landed", + "kind": "moved-lazy", + "target": "agent.tool_result_classification" + }, + { + "facade": "run_agent", + "name": "flatten_message_text", + "kind": "moved-lazy", + "target": "agent.message_content" + }, + { + "facade": "run_agent", + "name": "get_tool_definitions", + "kind": "moved-lazy", + "target": "model_tools" + }, + { + "facade": "run_agent", + "name": "handle_function_call", + "kind": "moved-lazy", + "target": "model_tools" + }, + { + "facade": "run_agent", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "run_agent", + "name": "is_truthy_value", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "run_agent", + "name": "jittered_backoff", + "kind": "moved-lazy", + "target": "agent.retry_utils" + }, + { + "facade": "run_agent", + "name": "load_soul_md", + "kind": "moved-lazy", + "target": "agent.prompt_builder" + }, + { + "facade": "run_agent", + "name": "normalize_usage", + "kind": "moved-lazy", + "target": "agent.usage_pricing" + }, + { + "facade": "run_agent", + "name": "redact_sensitive_text", + "kind": "moved-lazy", + "target": "agent.redact" + }, + { + "facade": "run_agent", + "name": "request_hard_interrupt", + "kind": "moved-lazy", + "target": "agent.interrupt_compat" + }, + { + "facade": "run_agent", + "name": "sanitize_context", + "kind": "moved-lazy", + "target": "agent.memory_manager" + }, + { + "facade": "run_agent", + "name": "tempfile", + "kind": "import", + "target": "tempfile" + }, + { + "facade": "run_agent", + "name": "user_originated_turn_view", + "kind": "moved-lazy", + "target": "agent.context_compressor" + }, + { + "facade": "tools.apply_layout_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.approval", + "name": "DANGEROUS_PATTERNS", + "kind": "moved-lazy", + "target": "tools.approval_detection" + }, + { + "facade": "tools.approval", + "name": "DANGEROUS_PATTERNS_COMPILED", + "kind": "moved-lazy", + "target": "tools.approval_detection" + }, + { + "facade": "tools.approval", + "name": "HARDLINE_PATTERNS", + "kind": "moved-lazy", + "target": "tools.approval_detection" + }, + { + "facade": "tools.approval", + "name": "HARDLINE_PATTERNS_COMPILED", + "kind": "moved-lazy", + "target": "tools.approval_detection" + }, + { + "facade": "tools.approval", + "name": "HUMAN_WAIT_MARGIN_S", + "kind": "moved-lazy", + "target": "tools.approval_human_wait" + }, + { + "facade": "tools.approval", + "name": "cfg_get", + "kind": "moved-lazy", + "target": "hermes_cli.config" + }, + { + "facade": "tools.approval", + "name": "contextlib", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "tools.approval", + "name": "contextvars", + "kind": "import", + "target": "contextvars" + }, + { + "facade": "tools.approval", + "name": "fnmatch", + "kind": "import", + "target": "fnmatch" + }, + { + "facade": "tools.approval", + "name": "functools", + "kind": "import", + "target": "functools" + }, + { + "facade": "tools.approval", + "name": "get_plugin_manager", + "kind": "moved-lazy", + "target": "tools.approval_prompt" + }, + { + "facade": "tools.approval", + "name": "human_wait_ceiling", + "kind": "moved-lazy", + "target": "tools.approval_human_wait" + }, + { + "facade": "tools.approval", + "name": "human_wait_seconds", + "kind": "moved-lazy", + "target": "tools.approval_human_wait" + }, + { + "facade": "tools.approval", + "name": "human_wait_window", + "kind": "moved-lazy", + "target": "tools.approval_human_wait" + }, + { + "facade": "tools.approval", + "name": "is_interrupted", + "kind": "moved-lazy", + "target": "tools.interrupt" + }, + { + "facade": "tools.approval", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.approval", + "name": "request_elicitation_consent", + "kind": "moved-lazy", + "target": "tools.approval_prompt" + }, + { + "facade": "tools.approval", + "name": "reset_current_observability_context", + "kind": "moved-lazy", + "target": "tools.approval_context" + }, + { + "facade": "tools.approval", + "name": "reset_current_session_key", + "kind": "moved-lazy", + "target": "tools.approval_context" + }, + { + "facade": "tools.approval", + "name": "reset_hermes_interactive_context", + "kind": "moved-lazy", + "target": "tools.approval_context" + }, + { + "facade": "tools.approval", + "name": "set_current_observability_context", + "kind": "moved-lazy", + "target": "tools.approval_context" + }, + { + "facade": "tools.approval", + "name": "set_current_session_key", + "kind": "moved-lazy", + "target": "tools.approval_context" + }, + { + "facade": "tools.approval", + "name": "set_hermes_interactive_context", + "kind": "moved-lazy", + "target": "tools.approval_context" + }, + { + "facade": "tools.approval", + "name": "shlex", + "kind": "import", + "target": "shlex" + }, + { + "facade": "tools.approval", + "name": "sys", + "kind": "import", + "target": "sys" + }, + { + "facade": "tools.approval", + "name": "tempfile", + "kind": "import", + "target": "tempfile" + }, + { + "facade": "tools.approval", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "tools.approval", + "name": "unicodedata", + "kind": "import", + "target": "unicodedata" + }, + { + "facade": "tools.approval", + "name": "uuid", + "kind": "import", + "target": "uuid" + }, + { + "facade": "tools.async_delegation", + "name": "active_for_session", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.browser_camofox_state", + "name": "CAMOFOX_STATE_DIR_NAME", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.browser_camofox_state", + "name": "CAMOFOX_STATE_SUBDIR", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.browser_dialog_tool", + "name": "logger", + "kind": "moved-lazy", + "target": "tools.approval" + }, + { + "facade": "tools.browser_dialog_tool", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "tools.browser_supervisor", + "name": "CONSOLE_HISTORY_MAX", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.browser_supervisor", + "name": "ConsoleEvent", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.browser_supervisor", + "name": "DIALOG_BRIDGE_HOST", + "kind": "moved-lazy", + "target": "tools.browser_supervisor_dialogs" + }, + { + "facade": "tools.browser_supervisor", + "name": "DIALOG_BRIDGE_URL_PATTERN", + "kind": "moved-lazy", + "target": "tools.browser_supervisor_dialogs" + }, + { + "facade": "tools.browser_supervisor", + "name": "DIALOG_POLICY_AUTO_ACCEPT", + "kind": "moved-lazy", + "target": "tools.browser_supervisor_dialogs" + }, + { + "facade": "tools.browser_supervisor", + "name": "DIALOG_POLICY_AUTO_DISMISS", + "kind": "moved-lazy", + "target": "tools.browser_supervisor_dialogs" + }, + { + "facade": "tools.browser_supervisor", + "name": "DIALOG_POLICY_MUST_RESPOND", + "kind": "moved-lazy", + "target": "tools.browser_supervisor_dialogs" + }, + { + "facade": "tools.browser_supervisor", + "name": "FRAME_TREE_MAX_ENTRIES", + "kind": "moved-lazy", + "target": "tools.browser_supervisor_frames" + }, + { + "facade": "tools.browser_supervisor", + "name": "FRAME_TREE_MAX_OOPIF_DEPTH", + "kind": "moved-lazy", + "target": "tools.browser_supervisor_frames" + }, + { + "facade": "tools.browser_tool", + "name": "BrowserUseProvider", + "kind": "moved-lazy", + "target": "plugins.browser.browser_use.provider.BrowserUseBrowserProvider" + }, + { + "facade": "tools.browser_tool", + "name": "BrowserbaseProvider", + "kind": "moved-lazy", + "target": "plugins.browser.browserbase.provider.BrowserbaseBrowserProvider" + }, + { + "facade": "tools.browser_tool", + "name": "CloudBrowserProvider", + "kind": "moved-lazy", + "target": "agent.browser_provider.BrowserProvider" + }, + { + "facade": "tools.browser_tool", + "name": "FirecrawlProvider", + "kind": "moved-lazy", + "target": "plugins.browser.firecrawl.provider.FirecrawlBrowserProvider" + }, + { + "facade": "tools.browser_tool", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.browser_tool", + "name": "SNAPSHOT_SUMMARIZE_THRESHOLD", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.browser_tool", + "name": "Tuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.browser_tool", + "name": "agent_browser_runnable", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "tools.browser_tool", + "name": "check_browser_requirements", + "kind": "moved-lazy", + "target": "tools.browser_tool_install" + }, + { + "facade": "tools.browser_tool", + "name": "check_browser_vision_requirements", + "kind": "moved-lazy", + "target": "tools.browser_tool_install" + }, + { + "facade": "tools.browser_tool", + "name": "cleanup_all_browsers", + "kind": "moved-lazy", + "target": "tools.browser_tool_lifecycle" + }, + { + "facade": "tools.browser_tool", + "name": "cleanup_browser", + "kind": "moved-lazy", + "target": "tools.browser_tool_lifecycle" + }, + { + "facade": "tools.browser_tool", + "name": "contextlib", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "tools.browser_tool", + "name": "datetime", + "kind": "import", + "target": "datetime" + }, + { + "facade": "tools.browser_tool", + "name": "functools", + "kind": "import", + "target": "functools" + }, + { + "facade": "tools.browser_tool", + "name": "get_hermes_home_override", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "tools.browser_tool", + "name": "hermes_home_key", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "tools.browser_tool", + "name": "is_truthy_value", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "tools.browser_tool", + "name": "lightpanda_engine_status", + "kind": "moved-lazy", + "target": "tools.browser_tool_lightpanda_fallback" + }, + { + "facade": "tools.browser_tool", + "name": "node_tool_runnable", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "tools.browser_tool", + "name": "normalize_browser_cloud_provider", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.browser_tool", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.browser_tool", + "name": "reset_hermes_home_override", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "tools.browser_tool", + "name": "set_hermes_home_override", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "tools.browser_tool", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "tools.browser_tool", + "name": "signal", + "kind": "import", + "target": "signal" + }, + { + "facade": "tools.browser_tool", + "name": "timezone", + "kind": "import", + "target": "datetime" + }, + { + "facade": "tools.browser_tool", + "name": "warm_agent_browser_npx_cache", + "kind": "moved-lazy", + "target": "tools.browser_tool_install" + }, + { + "facade": "tools.browser_tool", + "name": "windows_hide_flags", + "kind": "moved-lazy", + "target": "hermes_cli._subprocess_compat" + }, + { + "facade": "tools.clarify_gateway", + "name": "get_notify", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.clarify_gateway", + "name": "register_notify", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.clarify_gateway", + "name": "unregister_notify", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.close_preview_tool", + "name": "CLOSE_PREVIEW_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.close_preview_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.close_preview_tool", + "name": "registry", + "kind": "moved-lazy", + "target": "tools.registry" + }, + { + "facade": "tools.close_preview_tool", + "name": "tool_error", + "kind": "moved-lazy", + "target": "tools.registry" + }, + { + "facade": "tools.code_execution_tool", + "name": "DEFAULT_KERNEL_MODE", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.code_execution_tool", + "name": "KERNEL_MODES", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.code_execution_tool", + "name": "platform", + "kind": "import", + "target": "platform" + }, + { + "facade": "tools.code_execution_tool", + "name": "socket", + "kind": "import", + "target": "socket" + }, + { + "facade": "tools.code_execution_tool", + "name": "sys", + "kind": "import", + "target": "sys" + }, + { + "facade": "tools.code_execution_tool", + "name": "thread_scoped_silence", + "kind": "moved-lazy", + "target": "agent.thread_scoped_output" + }, + { + "facade": "tools.code_kernel_remote", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "tools.computer_use", + "name": "annotations", + "kind": "import", + "target": "__future__" + }, + { + "facade": "tools.computer_use", + "name": "check_computer_use_requirements", + "kind": "moved-lazy", + "target": "tools.computer_use.tool" + }, + { + "facade": "tools.computer_use", + "name": "get_computer_use_schema", + "kind": "moved-lazy", + "target": "tools.computer_use.tool" + }, + { + "facade": "tools.computer_use", + "name": "handle_computer_use", + "kind": "moved-lazy", + "target": "tools.computer_use.tool" + }, + { + "facade": "tools.computer_use", + "name": "release_computer_use_session", + "kind": "moved-lazy", + "target": "tools.computer_use.tool" + }, + { + "facade": "tools.computer_use", + "name": "set_approval_callback", + "kind": "moved-lazy", + "target": "tools.computer_use.tool" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "CaptureResult", + "kind": "moved-lazy", + "target": "tools.computer_use.backend" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "PureWindowsPath", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "Tuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "UIElement", + "kind": "moved-lazy", + "target": "tools.computer_use.backend" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "asyncio", + "kind": "import", + "target": "asyncio" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "concurrent", + "kind": "import", + "target": "concurrent.futures" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "cua_driver_install_hint", + "kind": "moved-lazy", + "target": "tools.computer_use.cua_backend_driver" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "cua_driver_update_check", + "kind": "moved-lazy", + "target": "tools.computer_use.cua_backend_driver" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "deque", + "kind": "import", + "target": "collections" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "functools", + "kind": "import", + "target": "functools" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "tempfile", + "kind": "import", + "target": "tempfile" + }, + { + "facade": "tools.computer_use.cua_backend", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "tools.computer_use.permissions", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.computer_use.tool", + "name": "struct", + "kind": "import", + "target": "struct" + }, + { + "facade": "tools.cronjob_tools", + "name": "effective_job_state", + "kind": "moved-lazy", + "target": "cron.jobs" + }, + { + "facade": "tools.cronjob_tools", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.delegate_tool", + "name": "DEFAULT_CHILD_TIMEOUT", + "kind": "moved-lazy", + "target": "tools.delegate_tool_config" + }, + { + "facade": "tools.delegate_tool", + "name": "DEFAULT_MAX_SUMMARY_CHARS", + "kind": "moved-lazy", + "target": "tools.delegate_tool_results" + }, + { + "facade": "tools.delegate_tool", + "name": "DEFAULT_TOOLSETS", + "kind": "moved-lazy", + "target": "tools.delegate_tool_toolsets" + }, + { + "facade": "tools.delegate_tool", + "name": "FuturesTimeoutError", + "kind": "import", + "target": "concurrent.futures" + }, + { + "facade": "tools.delegate_tool", + "name": "MAX_DEPTH", + "kind": "moved-lazy", + "target": "tools.delegate_tool_config" + }, + { + "facade": "tools.delegate_tool", + "name": "TOOLSETS", + "kind": "moved-lazy", + "target": "toolsets" + }, + { + "facade": "tools.delegate_tool", + "name": "base_url_hostname", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "tools.delegate_tool", + "name": "contextvars", + "kind": "import", + "target": "contextvars" + }, + { + "facade": "tools.delegate_tool", + "name": "enum", + "kind": "import", + "target": "enum" + }, + { + "facade": "tools.delegate_tool", + "name": "file_state", + "kind": "moved-lazy", + "target": "tools" + }, + { + "facade": "tools.delegate_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.delegate_tool", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "tools.delegate_tool", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.delegate_tool", + "name": "request_hard_interrupt", + "kind": "moved-lazy", + "target": "agent.interrupt_compat" + }, + { + "facade": "tools.delegate_tool", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "tools.delegate_tool", + "name": "urlsplit", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "tools.delegate_tool", + "name": "urlunsplit", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "tools.delegation_live_log", + "name": "new_live_delegation_id", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.delegation_output_schema", + "name": "MAX_SCHEMA_RETRIES", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.discord_tool", + "name": "TYPE_CHECKING", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.discord_tool", + "name": "Tuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.discord_tool", + "name": "get_dynamic_schema", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.drive_preview_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.env_probe", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "tools.environments.base", + "name": "IO", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.environments.base", + "name": "Protocol", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.environments.base", + "name": "codecs", + "kind": "import", + "target": "codecs" + }, + { + "facade": "tools.environments.base", + "name": "deque", + "kind": "import", + "target": "collections" + }, + { + "facade": "tools.environments.base", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.environments.base", + "name": "sanitize_task_id_for_path", + "kind": "moved-lazy", + "target": "tools.environments.path_utils" + }, + { + "facade": "tools.environments.base", + "name": "select", + "kind": "import", + "target": "select" + }, + { + "facade": "tools.environments.base", + "name": "subprocess", + "kind": "import", + "target": "subprocess" + }, + { + "facade": "tools.environments.base", + "name": "windows_hide_flags", + "kind": "moved-lazy", + "target": "hermes_cli._subprocess_compat" + }, + { + "facade": "tools.environments.managed_modal", + "name": "dataclass", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "tools.environments.vercel_sandbox", + "name": "dataclass", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "tools.feishu_doc_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.feishu_doc_tool", + "name": "logger", + "kind": "moved-lazy", + "target": "tools.approval" + }, + { + "facade": "tools.feishu_doc_tool", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "tools.feishu_doc_tool", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "tools.feishu_drive_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.feishu_drive_tool", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "tools.file_operations", + "name": "Any", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.file_operations", + "name": "ClassVar", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.file_operations", + "name": "DEFAULT_READ_LIMIT", + "kind": "moved-lazy", + "target": "tools.file_operations_common" + }, + { + "facade": "tools.file_operations", + "name": "DEFAULT_READ_OFFSET", + "kind": "moved-lazy", + "target": "tools.file_operations_common" + }, + { + "facade": "tools.file_operations", + "name": "DEFAULT_SEARCH_LIMIT", + "kind": "moved-lazy", + "target": "tools.file_operations_common" + }, + { + "facade": "tools.file_operations", + "name": "DEFAULT_SEARCH_OFFSET", + "kind": "moved-lazy", + "target": "tools.file_operations_common" + }, + { + "facade": "tools.file_operations", + "name": "LINTERS", + "kind": "moved-lazy", + "target": "tools.file_operations_lint" + }, + { + "facade": "tools.file_operations", + "name": "LintResult", + "kind": "moved-lazy", + "target": "tools.file_operations_common" + }, + { + "facade": "tools.file_operations", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.file_operations", + "name": "MAX_FILE_SIZE", + "kind": "moved-lazy", + "target": "tools.transcription_common" + }, + { + "facade": "tools.file_operations", + "name": "MAX_LINES", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.file_operations", + "name": "MAX_LINE_LENGTH", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.file_operations", + "name": "SEARCH_PRUNE_DIR_NAMES", + "kind": "moved-lazy", + "target": "agent.search_policy" + }, + { + "facade": "tools.file_operations", + "name": "SearchMatch", + "kind": "moved-lazy", + "target": "tools.file_operations_common" + }, + { + "facade": "tools.file_operations", + "name": "build_write_denied_paths", + "kind": "restored-import", + "target": "agent.file_safety" + }, + { + "facade": "tools.file_operations", + "name": "WRITE_DENIED_PATHS", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.file_operations", + "name": "build_write_denied_prefixes", + "kind": "restored-import", + "target": "agent.file_safety" + }, + { + "facade": "tools.file_operations", + "name": "WRITE_DENIED_PREFIXES", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.file_operations", + "name": "build_write_denied_paths", + "kind": "moved-lazy", + "target": "agent.file_safety" + }, + { + "facade": "tools.file_operations", + "name": "build_write_denied_prefixes", + "kind": "moved-lazy", + "target": "agent.file_safety" + }, + { + "facade": "tools.file_operations", + "name": "dataclass", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "tools.file_operations", + "name": "field", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "tools.file_operations", + "name": "posixpath", + "kind": "import", + "target": "posixpath" + }, + { + "facade": "tools.file_operations", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "tools.file_operations", + "name": "tool_interrupt", + "kind": "moved-lazy", + "target": "tools.interrupt" + }, + { + "facade": "tools.file_tools", + "name": "PurePosixPath", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "tools.file_tools", + "name": "has_opaque_document_extension", + "kind": "moved-lazy", + "target": "tools.binary_extensions" + }, + { + "facade": "tools.file_tools", + "name": "is_pdf_path", + "kind": "moved-lazy", + "target": "tools.binary_extensions" + }, + { + "facade": "tools.file_tools", + "name": "notify_other_tool_call", + "kind": "moved-lazy", + "target": "tools.file_tools_read_tracking" + }, + { + "facade": "tools.file_tools", + "name": "posixpath", + "kind": "import", + "target": "posixpath" + }, + { + "facade": "tools.file_tools", + "name": "reset_file_dedup", + "kind": "moved-lazy", + "target": "tools.file_tools_read_tracking" + }, + { + "facade": "tools.file_tools", + "name": "sys", + "kind": "import", + "target": "sys" + }, + { + "facade": "tools.focus_pane_tool", + "name": "FOCUS_PANE_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.focus_pane_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.fuzzy_match", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.fuzzy_match", + "name": "Tuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.image_generation_tool", + "name": "is_krea_model", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.lazy_deps", + "name": "feature_specs", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "urlsplit", + "kind": "restored-import", + "target": "urllib.parse" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "_MANAGED_GATEWAY_VENDOR", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of build_managed_media_uploader)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "is_managed_nous_gateway_url", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of build_managed_media_uploader)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "managed_gateway_auth_headers", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of build_managed_media_uploader)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "_MEDIA_UPLOAD_PRESIGN_TIMEOUT_SECONDS", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of build_managed_media_uploader)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "_MEDIA_UPLOAD_PUT_READ_TIMEOUT_SECONDS", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of build_managed_media_uploader)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "_MEDIA_UPLOAD_PUT_WRITE_TIMEOUT_SECONDS", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of build_managed_media_uploader)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "_describe_media_upload_refusal", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of build_managed_media_uploader)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "build_managed_media_uploader", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "is_managed_nous_gateway_url", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "managed_gateway_auth_headers", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "managed_vendor_base_path", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "managed_vendor_upload_path", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of managed_vendor_endpoints)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "managed_vendor_endpoints", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "managed_vendor_upload_path", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.managed_tool_gateway", + "name": "urlsplit", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "tools.mcp_oauth", + "name": "OAuthClientInformationFull", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.mcp_oauth", + "name": "OAuthClientMetadata", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.mcp_oauth", + "name": "OAuthClientProvider", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.mcp_oauth", + "name": "OAuthMetadata", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.mcp_oauth", + "name": "OAuthToken", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.mcp_oauth", + "name": "contextmanager", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "tools.mcp_schema_cache", + "name": "clear_cache_entry", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.mcp_schema_cache", + "name": "has_cached_entry", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.mcp_tool", + "name": "Coroutine", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.mcp_tool", + "name": "InvalidMcpUrlError", + "kind": "moved-lazy", + "target": "tools.mcp_tool_errors" + }, + { + "facade": "tools.mcp_tool", + "name": "MCP_TOOL_NAME_PREFIX", + "kind": "moved-lazy", + "target": "tools.mcp_tool_schema" + }, + { + "facade": "tools.mcp_tool", + "name": "NonMcpEndpointError", + "kind": "moved-lazy", + "target": "tools.mcp_tool_errors" + }, + { + "facade": "tools.mcp_tool", + "name": "SimpleNamespace", + "kind": "import", + "target": "types" + }, + { + "facade": "tools.mcp_tool", + "name": "Tuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.mcp_tool", + "name": "asynccontextmanager", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "tools.mcp_tool", + "name": "concurrent", + "kind": "import", + "target": "concurrent.futures" + }, + { + "facade": "tools.mcp_tool", + "name": "datetime", + "kind": "import", + "target": "datetime" + }, + { + "facade": "tools.mcp_tool", + "name": "discover_mcp_tools", + "kind": "moved-lazy", + "target": "tools.mcp_tool_discovery" + }, + { + "facade": "tools.mcp_tool", + "name": "errno", + "kind": "import", + "target": "errno" + }, + { + "facade": "tools.mcp_tool", + "name": "fnmatch", + "kind": "import", + "target": "fnmatch" + }, + { + "facade": "tools.mcp_tool", + "name": "get_mcp_status", + "kind": "moved-lazy", + "target": "tools.mcp_tool_discovery" + }, + { + "facade": "tools.mcp_tool", + "name": "get_registered_mcp_server_names", + "kind": "moved-lazy", + "target": "tools.mcp_tool_discovery" + }, + { + "facade": "tools.mcp_tool", + "name": "has_registered_mcp_tools", + "kind": "moved-lazy", + "target": "tools.mcp_tool_discovery" + }, + { + "facade": "tools.mcp_tool", + "name": "is_mcp_tool_parallel_safe", + "kind": "moved-lazy", + "target": "tools.mcp_tool_discovery" + }, + { + "facade": "tools.mcp_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.mcp_tool", + "name": "matches_name_filter", + "kind": "moved-lazy", + "target": "tools.mcp_tool_schema" + }, + { + "facade": "tools.mcp_tool", + "name": "math", + "kind": "import", + "target": "math" + }, + { + "facade": "tools.mcp_tool", + "name": "mcp_prefixed_tool_name", + "kind": "moved-lazy", + "target": "tools.mcp_tool_schema" + }, + { + "facade": "tools.mcp_tool", + "name": "persist_agent_tool_names", + "kind": "moved-lazy", + "target": "tools.mcp_tool_agent" + }, + { + "facade": "tools.mcp_tool", + "name": "probe_mcp_server_tools", + "kind": "moved-lazy", + "target": "tools.mcp_tool_discovery" + }, + { + "facade": "tools.mcp_tool", + "name": "random", + "kind": "import", + "target": "random" + }, + { + "facade": "tools.mcp_tool", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.mcp_tool", + "name": "reconnect_mcp_server", + "kind": "moved-lazy", + "target": "tools.mcp_tool_loop" + }, + { + "facade": "tools.mcp_tool", + "name": "refresh_agent_mcp_tools", + "kind": "moved-lazy", + "target": "tools.mcp_tool_agent" + }, + { + "facade": "tools.mcp_tool", + "name": "register_mcp_servers", + "kind": "moved-lazy", + "target": "tools.mcp_tool_discovery" + }, + { + "facade": "tools.mcp_tool", + "name": "reprobe_tool_availability", + "kind": "moved-lazy", + "target": "tools.mcp_tool_agent" + }, + { + "facade": "tools.mcp_tool", + "name": "restore_agent_tool_prefix", + "kind": "moved-lazy", + "target": "tools.mcp_tool_agent" + }, + { + "facade": "tools.mcp_tool", + "name": "sanitize_mcp_name_component", + "kind": "moved-lazy", + "target": "tools.mcp_tool_schema" + }, + { + "facade": "tools.mcp_tool", + "name": "shutdown_mcp_servers", + "kind": "moved-lazy", + "target": "tools.mcp_tool_lifecycle" + }, + { + "facade": "tools.mcp_tool", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "tools.mcp_tool", + "name": "strip_unicode_tags", + "kind": "moved-lazy", + "target": "tools.ansi_strip" + }, + { + "facade": "tools.mcp_tool", + "name": "tool_error", + "kind": "moved-lazy", + "target": "tools.registry" + }, + { + "facade": "tools.mcp_tool", + "name": "urlparse", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "tools.memory_tool", + "name": "atomic_write_text", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "tools.memory_tool", + "name": "contextmanager", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "tools.memory_tool", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "tools.microsoft_graph_client", + "name": "AsyncIterator", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.microsoft_graph_client", + "name": "GraphCredentials", + "kind": "moved-lazy", + "target": "tools.microsoft_graph_auth" + }, + { + "facade": "tools.open_preview_tool", + "name": "OPEN_PREVIEW_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.open_preview_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.open_preview_tool", + "name": "registry", + "kind": "moved-lazy", + "target": "tools.registry" + }, + { + "facade": "tools.openrouter_client", + "name": "get_async_client", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.path_security", + "name": "logger", + "kind": "moved-lazy", + "target": "tools.approval" + }, + { + "facade": "tools.path_security", + "name": "logging", + "kind": "import", + "target": "logging" + }, + { + "facade": "tools.preview_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.preview_tool", + "name": "preview_open", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.process_registry", + "name": "MAX_ACTIVE_PROCESS_AGE", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.react_to_message_tool", + "name": "env_var_enabled", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "tools.read_extract", + "name": "MAX_XLSX_BYTES", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.read_preview_tool", + "name": "READ_PREVIEW_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.read_preview_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.read_preview_tool", + "name": "registry", + "kind": "moved-lazy", + "target": "tools.registry" + }, + { + "facade": "tools.read_preview_tool", + "name": "tool_error", + "kind": "moved-lazy", + "target": "tools.registry" + }, + { + "facade": "tools.read_terminal_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.read_window_tool", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.read_window_tool", + "name": "tool_error", + "kind": "moved-lazy", + "target": "tools.registry" + }, + { + "facade": "tools.send_message_tool", + "name": "SEND_MESSAGE_SCHEMA", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.send_message_tool", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.send_message_tool", + "name": "redact_sensitive_text", + "kind": "moved-lazy", + "target": "agent.redact" + }, + { + "facade": "tools.send_message_tool", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "tools.skill_ledger", + "name": "ACTOR_AGENT", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skill_ledger", + "name": "ACTOR_CURATOR", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skill_ledger", + "name": "ACTOR_USER", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skill_linter", + "name": "format_findings", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skill_linter", + "name": "has_errors", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skill_manager_tool", + "name": "mark_background_review_skill_read", + "kind": "moved-lazy", + "target": "tools.skill_manager_guards" + }, + { + "facade": "tools.skill_usage", + "name": "tempfile", + "kind": "restored-import", + "target": "tempfile" + }, + { + "facade": "tools.skill_usage", + "name": "os", + "kind": "restored-import", + "target": "os" + }, + { + "facade": "tools.skill_usage", + "name": "_suppressed_file", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of add_suppressed_name)" + }, + { + "facade": "tools.skill_usage", + "name": "_write_suppressed_names", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of add_suppressed_name)" + }, + { + "facade": "tools.skill_usage", + "name": "add_suppressed_name", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skill_usage", + "name": "agent_created_report", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skill_usage", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "tools.skill_usage", + "name": "remove_suppressed_name", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skill_usage", + "name": "tempfile", + "kind": "import", + "target": "tempfile" + }, + { + "facade": "tools.skillevaluator_scan", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.skillevaluator_scan", + "name": "SCANNER_NAME", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skillevaluator_scan", + "name": "scanner_available", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skills_guard", + "name": "full_content_hash", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skills_hub", + "name": "ABC", + "kind": "import", + "target": "abc" + }, + { + "facade": "tools.skills_hub", + "name": "BrowseShSource", + "kind": "moved-lazy", + "target": "tools.skills_hub_sources" + }, + { + "facade": "tools.skills_hub", + "name": "ClawHubSource", + "kind": "moved-lazy", + "target": "tools.skills_hub_clawhub" + }, + { + "facade": "tools.skills_hub", + "name": "GITHUB_TAP_PROVIDERS", + "kind": "moved-lazy", + "target": "tools.skills_hub_github" + }, + { + "facade": "tools.skills_hub", + "name": "GitHubAuth", + "kind": "moved-lazy", + "target": "tools.skills_hub_github" + }, + { + "facade": "tools.skills_hub", + "name": "GitHubSource", + "kind": "moved-lazy", + "target": "tools.skills_hub_github" + }, + { + "facade": "tools.skills_hub", + "name": "HERMES_INDEX_TTL", + "kind": "moved-lazy", + "target": "tools.skills_hub_search" + }, + { + "facade": "tools.skills_hub", + "name": "HERMES_INDEX_URL", + "kind": "moved-lazy", + "target": "tools.skills_hub_search" + }, + { + "facade": "tools.skills_hub", + "name": "HermesIndexSource", + "kind": "moved-lazy", + "target": "tools.skills_hub_official" + }, + { + "facade": "tools.skills_hub", + "name": "LobeHubSource", + "kind": "moved-lazy", + "target": "tools.skills_hub_sources" + }, + { + "facade": "tools.skills_hub", + "name": "OptionalSkillSource", + "kind": "moved-lazy", + "target": "tools.skills_hub_official" + }, + { + "facade": "tools.skills_hub", + "name": "PurePosixPath", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "tools.skills_hub", + "name": "ScanResult", + "kind": "moved-lazy", + "target": "tools.skills_guard" + }, + { + "facade": "tools.skills_hub", + "name": "SkillBundle", + "kind": "moved-lazy", + "target": "tools.skills_hub_models" + }, + { + "facade": "tools.skills_hub", + "name": "SkillMeta", + "kind": "moved-lazy", + "target": "tools.skills_hub_models" + }, + { + "facade": "tools.skills_hub", + "name": "SkillSource", + "kind": "moved-lazy", + "target": "tools.skills_hub_models" + }, + { + "facade": "tools.skills_hub", + "name": "SkillsShSource", + "kind": "moved-lazy", + "target": "tools.skills_hub_skillssh" + }, + { + "facade": "tools.skills_hub", + "name": "TRUSTED_REPOS", + "kind": "moved-lazy", + "target": "tools.skills_guard" + }, + { + "facade": "tools.skills_hub", + "name": "Tuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.skills_hub", + "name": "Union", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.skills_hub", + "name": "UrlSource", + "kind": "moved-lazy", + "target": "tools.skills_hub_sources" + }, + { + "facade": "tools.skills_hub", + "name": "WellKnownSkillSource", + "kind": "moved-lazy", + "target": "tools.skills_hub_sources" + }, + { + "facade": "tools.skills_hub", + "name": "abstractmethod", + "kind": "import", + "target": "abc" + }, + { + "facade": "tools.skills_hub", + "name": "bundle_content_hash", + "kind": "moved-lazy", + "target": "tools.skills_hub_install" + }, + { + "facade": "tools.skills_hub", + "name": "check_for_skill_updates", + "kind": "moved-lazy", + "target": "tools.skills_hub_install" + }, + { + "facade": "tools.skills_hub", + "name": "content_hash", + "kind": "moved-lazy", + "target": "tools.skills_guard" + }, + { + "facade": "tools.skills_hub", + "name": "create_source_router", + "kind": "moved-lazy", + "target": "tools.skills_hub_search" + }, + { + "facade": "tools.skills_hub", + "name": "dataclass", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "tools.skills_hub", + "name": "field", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "tools.skills_hub", + "name": "github_provider_for", + "kind": "moved-lazy", + "target": "tools.skills_hub_github" + }, + { + "facade": "tools.skills_hub", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "tools.skills_hub", + "name": "install_from_quarantine", + "kind": "moved-lazy", + "target": "tools.skills_hub_install" + }, + { + "facade": "tools.skills_hub", + "name": "is_excluded_skill_path", + "kind": "moved-lazy", + "target": "agent.skill_utils" + }, + { + "facade": "tools.skills_hub", + "name": "os", + "kind": "import", + "target": "os" + }, + { + "facade": "tools.skills_hub", + "name": "parallel_search_sources", + "kind": "moved-lazy", + "target": "tools.skills_hub_search" + }, + { + "facade": "tools.skills_hub", + "name": "quarantine_bundle", + "kind": "moved-lazy", + "target": "tools.skills_hub_install" + }, + { + "facade": "tools.skills_hub", + "name": "quote", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "tools.skills_hub", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.skills_hub", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "tools.skills_hub", + "name": "source_url_for_bundle", + "kind": "moved-lazy", + "target": "tools.skills_hub_models" + }, + { + "facade": "tools.skills_hub", + "name": "subprocess", + "kind": "import", + "target": "subprocess" + }, + { + "facade": "tools.skills_hub", + "name": "unified_search", + "kind": "moved-lazy", + "target": "tools.skills_hub_search" + }, + { + "facade": "tools.skills_hub", + "name": "uninstall_skill", + "kind": "moved-lazy", + "target": "tools.skills_hub_install" + }, + { + "facade": "tools.skills_hub", + "name": "unquote", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "tools.skills_hub", + "name": "urlparse", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "tools.skills_hub", + "name": "urlsplit", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "tools.skills_hub", + "name": "urlunparse", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "tools.skills_hub", + "name": "windows_hide_flags", + "kind": "moved-lazy", + "target": "hermes_cli._subprocess_compat" + }, + { + "facade": "tools.skills_hub", + "name": "yaml", + "kind": "import", + "target": "yaml" + }, + { + "facade": "tools.skills_sync", + "name": "PurePosixPath", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "tools.skills_sync", + "name": "atomic_replace", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "tools.skills_sync", + "name": "datetime", + "kind": "import", + "target": "datetime" + }, + { + "facade": "tools.skills_sync", + "name": "diff_bundled_skill", + "kind": "moved-lazy", + "target": "tools.skills_sync_bundled_ops" + }, + { + "facade": "tools.skills_sync", + "name": "is_bundled_skills_opt_out", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skills_sync", + "name": "json", + "kind": "import", + "target": "json" + }, + { + "facade": "tools.skills_sync", + "name": "list_user_modified_bundled_skills", + "kind": "moved-lazy", + "target": "tools.skills_sync_bundled_ops" + }, + { + "facade": "tools.skills_sync", + "name": "remove_pristine_bundled_skills", + "kind": "moved-lazy", + "target": "tools.skills_sync_bundled_ops" + }, + { + "facade": "tools.skills_sync", + "name": "reset_bundled_skill", + "kind": "moved-lazy", + "target": "tools.skills_sync_bundled_ops" + }, + { + "facade": "tools.skills_sync", + "name": "restore_official_optional_skill", + "kind": "moved-lazy", + "target": "tools.skills_sync_optional" + }, + { + "facade": "tools.skills_sync", + "name": "set_bundled_skills_opt_out", + "kind": "moved-lazy", + "target": "tools.skills_sync_bundled_ops" + }, + { + "facade": "tools.skills_sync", + "name": "timezone", + "kind": "import", + "target": "datetime" + }, + { + "facade": "tools.skills_sync_client", + "name": "ARTIFACT_TYPE_SKILL", + "kind": "moved-lazy", + "target": "tools.skills_sync_client_wire" + }, + { + "facade": "tools.skills_sync_client", + "name": "KIND_COMMIT", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skills_sync_client", + "name": "KIND_TREE", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skills_sync_client", + "name": "MODE_DIR", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skills_sync_client", + "name": "MODE_EXEC", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skills_sync_client", + "name": "MODE_FILE", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skills_sync_client", + "name": "SYNC_MANIFEST_ENTRY_NAME", + "kind": "moved-lazy", + "target": "tools.skills_sync_client_wire" + }, + { + "facade": "tools.skills_sync_client", + "name": "SYNC_MANIFEST_TYPE", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skills_sync_client", + "name": "SYNC_MANIFEST_VERSION", + "kind": "moved-lazy", + "target": "tools.skills_sync_client_wire" + }, + { + "facade": "tools.skills_sync_client", + "name": "WIRE_VERSION", + "kind": "moved-lazy", + "target": "tools.skills_sync_client_wire" + }, + { + "facade": "tools.skills_sync_client", + "name": "canonical_json_bytes", + "kind": "moved-lazy", + "target": "tools.skills_sync_client_wire" + }, + { + "facade": "tools.skills_sync_client", + "name": "datetime", + "kind": "import", + "target": "datetime" + }, + { + "facade": "tools.skills_sync_client", + "name": "dev_gate_open", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skills_sync_client", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "tools.skills_sync_client", + "name": "maybe_pull_org_skills", + "kind": "moved-lazy", + "target": "tools.skills_sync_client_org" + }, + { + "facade": "tools.skills_sync_client", + "name": "org_head_ref", + "kind": "moved-lazy", + "target": "tools.skills_sync_client_org" + }, + { + "facade": "tools.skills_sync_client", + "name": "org_skill_is_locally_modified", + "kind": "moved-lazy", + "target": "tools.skills_sync_client_org" + }, + { + "facade": "tools.skills_sync_client", + "name": "org_sync_available", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skills_sync_client", + "name": "parse_sync_manifest", + "kind": "moved-lazy", + "target": "tools.skills_sync_client_wire" + }, + { + "facade": "tools.skills_sync_client", + "name": "propose_skill", + "kind": "moved-lazy", + "target": "tools.skills_sync_client_org" + }, + { + "facade": "tools.skills_sync_client", + "name": "pull_org_skills", + "kind": "moved-lazy", + "target": "tools.skills_sync_client_org" + }, + { + "facade": "tools.skills_sync_client", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "tools.skills_sync_client", + "name": "timezone", + "kind": "import", + "target": "datetime" + }, + { + "facade": "tools.skills_sync_client", + "name": "user_conflict_ref", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.skills_sync_client", + "name": "wire_address", + "kind": "moved-lazy", + "target": "tools.skills_sync_client_wire" + }, + { + "facade": "tools.skills_tool", + "name": "Enum", + "kind": "import", + "target": "enum" + }, + { + "facade": "tools.skills_tool", + "name": "Set", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.skills_tool", + "name": "display_hermes_home", + "kind": "moved-lazy", + "target": "hermes_constants" + }, + { + "facade": "tools.skills_tool", + "name": "env_var_enabled", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "tools.skills_tool", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.skills_tool", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "tools.slash_confirm", + "name": "asyncio", + "kind": "import", + "target": "asyncio" + }, + { + "facade": "tools.slash_confirm", + "name": "resolve_sync_compat", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.terminal_scope", + "name": "install_refusal_scope", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.terminal_scope", + "name": "terminal_scope", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.terminal_tool", + "name": "Path", + "kind": "import", + "target": "pathlib" + }, + { + "facade": "tools.terminal_tool", + "name": "cleanup_vm", + "kind": "moved-lazy", + "target": "tools.terminal_tool_lifecycle" + }, + { + "facade": "tools.terminal_tool", + "name": "env_var_enabled", + "kind": "moved-lazy", + "target": "utils" + }, + { + "facade": "tools.terminal_tool", + "name": "get_active_env", + "kind": "moved-lazy", + "target": "tools.terminal_tool_lifecycle" + }, + { + "facade": "tools.terminal_tool", + "name": "has_direct_modal_credentials", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.terminal_tool", + "name": "importlib", + "kind": "import", + "target": "importlib.util" + }, + { + "facade": "tools.terminal_tool", + "name": "is_interrupted", + "kind": "moved-lazy", + "target": "tools.interrupt" + }, + { + "facade": "tools.terminal_tool", + "name": "is_managed_tool_gateway_ready", + "kind": "moved-lazy", + "target": "tools.managed_tool_gateway" + }, + { + "facade": "tools.terminal_tool", + "name": "is_persistent_env", + "kind": "moved-lazy", + "target": "tools.terminal_tool_lifecycle" + }, + { + "facade": "tools.terminal_tool", + "name": "nous_tool_gateway_unavailable_message", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.terminal_tool", + "name": "platform", + "kind": "import", + "target": "platform" + }, + { + "facade": "tools.terminal_tool", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.terminal_tool", + "name": "resolve_modal_backend_state", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.terminal_tool", + "name": "shlex", + "kind": "import", + "target": "shlex" + }, + { + "facade": "tools.terminal_tool", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "tools.terminal_tool", + "name": "stat", + "kind": "import", + "target": "stat" + }, + { + "facade": "tools.terminal_tool", + "name": "strip_inert_heredoc_bodies", + "kind": "moved-lazy", + "target": "tools.shell_heredoc" + }, + { + "facade": "tools.terminal_tool", + "name": "subprocess", + "kind": "import", + "target": "subprocess" + }, + { + "facade": "tools.terminal_tool", + "name": "sys", + "kind": "import", + "target": "sys" + }, + { + "facade": "tools.tool_result_storage", + "name": "HEREDOC_MARKER", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.tool_result_storage", + "name": "uuid", + "kind": "import", + "target": "uuid" + }, + { + "facade": "tools.tool_search", + "name": "Literal", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.tool_search", + "name": "build_catalog_listing", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.tool_search", + "name": "copy", + "kind": "import", + "target": "copy" + }, + { + "facade": "tools.tool_search", + "name": "field", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "tools.tool_search", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.tool_search", + "name": "snowballstemmer", + "kind": "import", + "target": "snowballstemmer" + }, + { + "facade": "tools.tool_search", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "tools.transcription_tools", + "name": "COMMAND_STT_OUTPUT_FORMATS", + "kind": "moved-lazy", + "target": "tools.transcription_command" + }, + { + "facade": "tools.transcription_tools", + "name": "COMMON_LOCAL_BIN_DIRS", + "kind": "moved-lazy", + "target": "tools.transcription_common" + }, + { + "facade": "tools.transcription_tools", + "name": "DEFAULT_COMMAND_STT_LANGUAGE", + "kind": "moved-lazy", + "target": "tools.transcription_command" + }, + { + "facade": "tools.transcription_tools", + "name": "DEFAULT_COMMAND_STT_OUTPUT_FORMAT", + "kind": "moved-lazy", + "target": "tools.transcription_command" + }, + { + "facade": "tools.transcription_tools", + "name": "DEFAULT_COMMAND_STT_TIMEOUT_SECONDS", + "kind": "moved-lazy", + "target": "tools.transcription_command" + }, + { + "facade": "tools.transcription_tools", + "name": "DEFAULT_LOCAL_STT_LANGUAGE", + "kind": "moved-lazy", + "target": "tools.transcription_common" + }, + { + "facade": "tools.transcription_tools", + "name": "ELEVENLABS_STT_BASE_URL", + "kind": "moved-lazy", + "target": "tools.transcription_common" + }, + { + "facade": "tools.transcription_tools", + "name": "GROQ_BASE_URL", + "kind": "moved-lazy", + "target": "tools.transcription_common" + }, + { + "facade": "tools.transcription_tools", + "name": "GROQ_MODELS", + "kind": "moved-lazy", + "target": "tools.transcription_common" + }, + { + "facade": "tools.transcription_tools", + "name": "LOCAL_NATIVE_AUDIO_FORMATS", + "kind": "moved-lazy", + "target": "tools.transcription_common" + }, + { + "facade": "tools.transcription_tools", + "name": "MAX_FILE_SIZE", + "kind": "moved-lazy", + "target": "tools.transcription_common" + }, + { + "facade": "tools.transcription_tools", + "name": "OPENAI_BASE_URL", + "kind": "moved-lazy", + "target": "tools.transcription_common" + }, + { + "facade": "tools.transcription_tools", + "name": "OPENAI_MODELS", + "kind": "moved-lazy", + "target": "tools.transcription_common" + }, + { + "facade": "tools.transcription_tools", + "name": "SUPPORTED_FORMATS", + "kind": "moved-lazy", + "target": "tools.transcription_common" + }, + { + "facade": "tools.transcription_tools", + "name": "XAI_STT_BASE_URL", + "kind": "moved-lazy", + "target": "tools.transcription_common" + }, + { + "facade": "tools.transcription_tools", + "name": "managed_nous_tools_enabled", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.transcription_tools", + "name": "nous_tool_gateway_unavailable_message", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.transcription_tools", + "name": "platform", + "kind": "import", + "target": "platform" + }, + { + "facade": "tools.transcription_tools", + "name": "queue", + "kind": "import", + "target": "queue" + }, + { + "facade": "tools.transcription_tools", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.transcription_tools", + "name": "resolve_managed_tool_gateway", + "kind": "moved-lazy", + "target": "tools.managed_tool_gateway" + }, + { + "facade": "tools.transcription_tools", + "name": "resolve_openai_audio_api_key", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.transcription_tools", + "name": "shlex", + "kind": "import", + "target": "shlex" + }, + { + "facade": "tools.transcription_tools", + "name": "subprocess", + "kind": "import", + "target": "subprocess" + }, + { + "facade": "tools.transcription_tools", + "name": "tempfile", + "kind": "import", + "target": "tempfile" + }, + { + "facade": "tools.transcription_tools", + "name": "urljoin", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "tools.transcription_tools", + "name": "windows_hide_flags", + "kind": "moved-lazy", + "target": "hermes_cli._subprocess_compat" + }, + { + "facade": "tools.tts_tool", + "name": "AudioDeliveryProfile", + "kind": "moved-lazy", + "target": "tools.tts_tool_delivery" + }, + { + "facade": "tools.tts_tool", + "name": "COMMAND_TTS_OUTPUT_FORMATS", + "kind": "moved-lazy", + "target": "tools.tts_command_provider" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_COMMAND_TTS_MAX_TEXT_LENGTH", + "kind": "moved-lazy", + "target": "tools.tts_command_provider" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_COMMAND_TTS_OUTPUT_FORMAT", + "kind": "moved-lazy", + "target": "tools.tts_command_provider" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_COMMAND_TTS_TIMEOUT_SECONDS", + "kind": "moved-lazy", + "target": "tools.tts_command_provider" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_DEEPINFRA_TTS_VOICE", + "kind": "moved-lazy", + "target": "tools.tts_tool_openai" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_EDGE_VOICE", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_ELEVENLABS_MODEL_ID", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_ELEVENLABS_STREAMING_MODEL_ID", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_ELEVENLABS_VOICE_ID", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_GEMINI_AUDIO_TAGS", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_GEMINI_TTS_BASE_URL", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_GEMINI_TTS_MODEL", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_GEMINI_TTS_VOICE", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_KITTENTTS_MODEL", + "kind": "moved-lazy", + "target": "tools.tts_tool_local" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_KITTENTTS_VOICE", + "kind": "moved-lazy", + "target": "tools.tts_tool_local" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_MINIMAX_BASE_URL", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_MINIMAX_CN_BASE_URL", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_MINIMAX_MODEL", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_MINIMAX_VOICE_ID", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_MISTRAL_TTS_MODEL", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_MISTRAL_TTS_VOICE_ID", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_OPENAI_BASE_URL", + "kind": "moved-lazy", + "target": "tools.tts_tool_openai" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_OPENAI_MODEL", + "kind": "moved-lazy", + "target": "tools.tts_tool_openai" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_OPENAI_VOICE", + "kind": "moved-lazy", + "target": "tools.tts_tool_openai" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_PIPER_VOICE", + "kind": "moved-lazy", + "target": "tools.tts_tool_local" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_XAI_AUTO_SPEECH_TAGS", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_XAI_BASE_URL", + "kind": "moved-lazy", + "target": "tools.xai_http" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_XAI_BIT_RATE", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_XAI_LANGUAGE", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_XAI_OPTIMIZE_STREAMING_LATENCY_DEFAULT", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_XAI_SAMPLE_RATE", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_XAI_SPEED_DEFAULT", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_XAI_SPEED_MAX", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_XAI_SPEED_MIN", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_XAI_TEXT_NORMALIZATION_DEFAULT", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "DEFAULT_XAI_VOICE_ID", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "ELEVENLABS_MODEL_MAX_TEXT_LENGTH", + "kind": "moved-lazy", + "target": "tools.tts_tool_delivery" + }, + { + "facade": "tools.tts_tool", + "name": "FALLBACK_MAX_TEXT_LENGTH", + "kind": "moved-lazy", + "target": "tools.tts_tool_delivery" + }, + { + "facade": "tools.tts_tool", + "name": "Future", + "kind": "import", + "target": "concurrent.futures" + }, + { + "facade": "tools.tts_tool", + "name": "GEMINI_AUDIO_TAG_REWRITE_TASK", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "GEMINI_TTS_CHANNELS", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.tts_tool", + "name": "GEMINI_TTS_SAMPLE_RATE", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.tts_tool", + "name": "GEMINI_TTS_SAMPLE_WIDTH", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.tts_tool", + "name": "Iterator", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.tts_tool", + "name": "MANAGED_OPENAI_TTS_MODELS", + "kind": "moved-lazy", + "target": "tools.tts_tool_openai" + }, + { + "facade": "tools.tts_tool", + "name": "MAX_TEXT_LENGTH", + "kind": "moved-lazy", + "target": "gateway.platforms.bluebubbles" + }, + { + "facade": "tools.tts_tool", + "name": "PROVIDER_MAX_TEXT_LENGTH", + "kind": "moved-lazy", + "target": "tools.tts_tool_delivery" + }, + { + "facade": "tools.tts_tool", + "name": "TTS_RESPONSE_BODY_CHUNK_BYTES", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "TTS_RESPONSE_BODY_LIMIT_BYTES", + "kind": "moved-lazy", + "target": "tools.tts_tool_providers" + }, + { + "facade": "tools.tts_tool", + "name": "ThreadPoolExecutor", + "kind": "import", + "target": "concurrent.futures" + }, + { + "facade": "tools.tts_tool", + "name": "Tuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.tts_tool", + "name": "acquire_tts_lease", + "kind": "moved-lazy", + "target": "tools.tts_tool_lifecycle" + }, + { + "facade": "tools.tts_tool", + "name": "base64", + "kind": "import", + "target": "base64" + }, + { + "facade": "tools.tts_tool", + "name": "dataclass", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "tools.tts_tool", + "name": "field", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "tools.tts_tool", + "name": "hermes_xai_user_agent", + "kind": "moved-lazy", + "target": "tools.xai_http" + }, + { + "facade": "tools.tts_tool", + "name": "managed_nous_tools_enabled", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.tts_tool", + "name": "nous_tool_gateway_unavailable_message", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.tts_tool", + "name": "platform", + "kind": "import", + "target": "platform" + }, + { + "facade": "tools.tts_tool", + "name": "queue", + "kind": "import", + "target": "queue" + }, + { + "facade": "tools.tts_tool", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.tts_tool", + "name": "read_selection", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.tts_tool", + "name": "release_tts_lease", + "kind": "moved-lazy", + "target": "tools.tts_tool_lifecycle" + }, + { + "facade": "tools.tts_tool", + "name": "release_tts_provider", + "kind": "moved-lazy", + "target": "tools.tts_tool_lifecycle" + }, + { + "facade": "tools.tts_tool", + "name": "resolve_managed_tool_gateway", + "kind": "moved-lazy", + "target": "tools.managed_tool_gateway" + }, + { + "facade": "tools.tts_tool", + "name": "resolve_openai_audio_api_key", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.tts_tool", + "name": "selection_error", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.tts_tool", + "name": "shlex", + "kind": "import", + "target": "shlex" + }, + { + "facade": "tools.tts_tool", + "name": "shutil", + "kind": "import", + "target": "shutil" + }, + { + "facade": "tools.tts_tool", + "name": "stream_tts_to_speaker", + "kind": "moved-lazy", + "target": "tools.tts_tool_speaker" + }, + { + "facade": "tools.tts_tool", + "name": "subprocess", + "kind": "import", + "target": "subprocess" + }, + { + "facade": "tools.tts_tool", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "tools.tts_tool", + "name": "time", + "kind": "import", + "target": "time" + }, + { + "facade": "tools.tts_tool", + "name": "tts_lease_holders", + "kind": "moved-lazy", + "target": "tools.tts_tool_lifecycle" + }, + { + "facade": "tools.tts_tool", + "name": "urljoin", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "tools.tts_tool", + "name": "urlparse", + "kind": "import", + "target": "urllib.parse" + }, + { + "facade": "tools.tts_tool", + "name": "uuid", + "kind": "import", + "target": "uuid" + }, + { + "facade": "tools.tts_tool", + "name": "warm_tts_provider", + "kind": "moved-lazy", + "target": "tools.tts_tool_lifecycle" + }, + { + "facade": "tools.tts_tool", + "name": "windows_hide_flags", + "kind": "moved-lazy", + "target": "hermes_cli._subprocess_compat" + }, + { + "facade": "tools.url_safety", + "name": "has_sensitive_query_params", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.url_safety", + "name": "ssrf_safe_async_http_transport", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.url_safety", + "name": "ssrf_safe_http_transport", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.vision_tools", + "name": "contextlib", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "tools.vision_tools", + "name": "sys", + "kind": "import", + "target": "sys" + }, + { + "facade": "tools.vision_tools", + "name": "threading", + "kind": "import", + "target": "threading" + }, + { + "facade": "tools.voice_mode", + "name": "DEFAULT_TTS_ECHO_SIMILARITY_THRESHOLD", + "kind": "moved-lazy", + "target": "tools.voice_mode_transcript" + }, + { + "facade": "tools.voice_mode", + "name": "DEFAULT_VOICE_STOP_PHRASES", + "kind": "moved-lazy", + "target": "tools.voice_mode_transcript" + }, + { + "facade": "tools.voice_mode", + "name": "MIN_FRAGMENT_LENGTH_FOR_ECHO", + "kind": "moved-lazy", + "target": "tools.voice_mode_transcript" + }, + { + "facade": "tools.voice_mode", + "name": "WHISPER_HALLUCINATIONS", + "kind": "moved-lazy", + "target": "tools.voice_mode_transcript" + }, + { + "facade": "tools.voice_mode", + "name": "difflib", + "kind": "import", + "target": "difflib" + }, + { + "facade": "tools.voice_mode", + "name": "is_tts_echo", + "kind": "moved-lazy", + "target": "tools.voice_mode_transcript" + }, + { + "facade": "tools.voice_mode", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.voice_mode", + "name": "voice_stop_hint", + "kind": "moved-lazy", + "target": "tools.voice_mode_transcript" + }, + { + "facade": "tools.web_result_cache", + "name": "Any", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.web_result_cache", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.web_tools", + "name": "DEFAULT_EXTRACT_CHAR_LIMIT", + "kind": "moved-lazy", + "target": "tools.web_tools_truncate" + }, + { + "facade": "tools.web_tools", + "name": "Dict", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.web_tools", + "name": "Firecrawl", + "kind": "moved-lazy", + "target": "plugins.web.firecrawl.provider" + }, + { + "facade": "tools.web_tools", + "name": "MAX_STORED_TEXT_CHARS", + "kind": "moved-lazy", + "target": "tools.web_tools_truncate" + }, + { + "facade": "tools.web_tools", + "name": "TYPE_CHECKING", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.web_tools", + "name": "asyncio", + "kind": "import", + "target": "asyncio" + }, + { + "facade": "tools.web_tools", + "name": "build_vendor_gateway_url", + "kind": "moved-lazy", + "target": "tools.managed_tool_gateway" + }, + { + "facade": "tools.web_tools", + "name": "httpx", + "kind": "import", + "target": "httpx" + }, + { + "facade": "tools.web_tools", + "name": "managed_nous_tools_enabled", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.web_tools", + "name": "normalize_url_for_request", + "kind": "moved-lazy", + "target": "tools.url_safety" + }, + { + "facade": "tools.web_tools", + "name": "nous_tool_gateway_unavailable_message", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.web_tools", + "name": "prefers_gateway", + "kind": "moved-lazy", + "target": "tools.tool_backend_helpers" + }, + { + "facade": "tools.web_tools", + "name": "re", + "kind": "import", + "target": "re" + }, + { + "facade": "tools.web_tools", + "name": "resolve_managed_tool_gateway", + "kind": "moved-lazy", + "target": "tools.managed_tool_gateway" + }, + { + "facade": "tools.web_tools", + "name": "sensitive_query_param_name", + "kind": "moved-lazy", + "target": "tools.url_safety" + }, + { + "facade": "tools.web_tools", + "name": "sys", + "kind": "import", + "target": "sys" + }, + { + "facade": "tools.website_policy", + "name": "invalidate_cache", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.write_approval", + "name": "is_background", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tools.yuanbao_tools", + "name": "List", + "kind": "import", + "target": "typing" + }, + { + "facade": "tools.yuanbao_tools", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "toolsets", + "name": "resolve_multiple_toolsets", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tui_gateway.compute_host", + "name": "field", + "kind": "restored-import", + "target": "dataclasses" + }, + { + "facade": "tui_gateway.compute_host", + "name": "dataclass", + "kind": "restored-import", + "target": "dataclasses" + }, + { + "facade": "tui_gateway.compute_host", + "name": "SpikeAgent", + "kind": "restored-helper", + "target": "(deleted; restored as a dependency of HostSession)" + }, + { + "facade": "tui_gateway.compute_host", + "name": "HostSession", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tui_gateway.compute_host", + "name": "SpikeAgent", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tui_gateway.compute_host", + "name": "dataclass", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "tui_gateway.compute_host", + "name": "field", + "kind": "import", + "target": "dataclasses" + }, + { + "facade": "tui_gateway.compute_host", + "name": "request_hard_interrupt", + "kind": "moved-lazy", + "target": "agent.interrupt_compat" + }, + { + "facade": "tui_gateway.hosted_room_driver", + "name": "contextlib", + "kind": "import", + "target": "contextlib" + }, + { + "facade": "tui_gateway.hosted_room_driver", + "name": "null_turn_lock", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tui_gateway.hosted_room_service", + "name": "hashlib", + "kind": "import", + "target": "hashlib" + }, + { + "facade": "tui_gateway.mcp_rpc_helpers", + "name": "Optional", + "kind": "import", + "target": "typing" + }, + { + "facade": "tui_gateway.mcp_rpc_helpers", + "name": "Tuple", + "kind": "import", + "target": "typing" + }, + { + "facade": "tui_gateway.mcp_rpc_helpers", + "name": "resolve_profile", + "kind": "restored-def", + "target": "(deleted; BASE body restored)" + }, + { + "facade": "tui_gateway.methods_browser_control", + "name": "BROWSER_CONTROL_PROTOCOL_VERSION", + "kind": "moved-lazy", + "target": "gateway.browser_control_broker" + }, + { + "facade": "tui_gateway.methods_browser_control", + "name": "browser_control_protocol_supported", + "kind": "moved-lazy", + "target": "gateway.browser_control_broker" + }, + { + "facade": "tui_gateway.methods_browser_control", + "name": "filter_browser_control_capabilities", + "kind": "moved-lazy", + "target": "gateway.browser_control_broker" + }, + { + "facade": "tui_gateway.methods_prompt", + "name": "types", + "kind": "import", + "target": "types" + }, + { + "facade": "gateway.startup_watchdog", + "name": "*", + "kind": "module-stub", + "target": "gateway.shutdown_watchdog" + }, + { + "facade": "hermes_cli.observability.relay_runtime", + "name": "*", + "kind": "module-stub", + "target": "agent.relay_runtime" + }, + { + "facade": "tools.environments.modal_utils", + "name": "*", + "kind": "module-stub", + "target": "(deleted)" + } + ] +} \ No newline at end of file diff --git a/cron/jobs.py b/cron/jobs.py index 34a484f990..044d0bee54 100644 --- a/cron/jobs.py +++ b/cron/jobs.py @@ -3123,3 +3123,14 @@ def rewrite_skill_refs( save_jobs(jobs) logger.info("Curator rewrote skill references in %d cron job(s)", len(rewrites)) return {"rewrites": rewrites, "jobs_updated": len(rewrites), "jobs_scanned": len(jobs)} + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def clear_drift_alerted(job_id: str) -> None: + """Clear the drift alert-dedup marker (resolution matches again).""" + _set_alert_flag(job_id, "drift_alerted", False) +# ---- END PLUGIN-COMPAT ---- diff --git a/cron/scheduler.py b/cron/scheduler.py index 9fcbdeea42..b2db71db52 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -3865,3 +3865,29 @@ if __name__ == "__main__": 0 if _run_external_worker_payload(args.external_worker_file, args.ack_file) else 1 ) tick(verbose=True) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import asyncio # noqa: F401,E402 +import shutil # noqa: F401,E402 +import signal # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'BOT_CHAT_PLATFORM': ('cron.scheduler_delivery', 'BOT_CHAT_PLATFORM'), + 'SharedRouteAdapters': ('cron.scheduler_preflight', 'SharedRouteAdapters'), + 'cron_delivery_targets': ('cron.scheduler_delivery', 'cron_delivery_targets'), + 'parse_bot_chat_deliver_token': ('cron.scheduler_delivery', 'parse_bot_chat_deliver_token'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/cron/scheduler_provider.py b/cron/scheduler_provider.py index 1a1acd60d4..fc02d14fce 100644 --- a/cron/scheduler_provider.py +++ b/cron/scheduler_provider.py @@ -543,3 +543,26 @@ class InProcessCronScheduler(CronScheduler): if ok: consecutive_failures = 0 stop_event.wait(_backoff_wait_seconds(interval, consecutive_failures)) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def provider_supports_fire_cancel(provider: Any) -> bool: + """Return whether ``fire_claimed`` accepts a ``cancel_event`` kwarg.""" + try: + parameters = inspect.signature(provider.fire_claimed).parameters.values() + except (TypeError, ValueError): + return False + return any( + parameter.kind is inspect.Parameter.VAR_KEYWORD + or ( + parameter.name == "cancel_event" + and parameter.kind + in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY) + ) + for parameter in parameters + ) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/browser_control_artifacts.py b/gateway/browser_control_artifacts.py index cbd0c3c49d..8dcb51a5a0 100644 --- a/gateway/browser_control_artifacts.py +++ b/gateway/browser_control_artifacts.py @@ -321,3 +321,13 @@ class ArtifactRateLimiter: """Drop the recorded hits for ``key`` (tests/diagnostics).""" with self._lock: self._hits.pop(key, None) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +class ArtifactOverwrite(ArtifactError): + """An artifact id already exists and the store refuses to overwrite it.""" +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/browser_control_broker.py b/gateway/browser_control_broker.py index 58e2d7c672..20b0b79f09 100644 --- a/gateway/browser_control_broker.py +++ b/gateway/browser_control_broker.py @@ -510,3 +510,16 @@ _GLOBAL_BROKER = BrowserControlBroker() def get_browser_control_broker() -> BrowserControlBroker: """Process-local broker shared by API and dashboard Gateway transports.""" return _GLOBAL_BROKER + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +BROWSER_CONTROL_ALL_CAPABILITIES = frozenset( + BROWSER_CONTROL_CAPABILITIES + | BROWSER_CONTROL_ARTIFACT_CAPABILITIES + | BROWSER_CONTROL_DEVELOPER_CAPABILITIES +) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/config.py b/gateway/config.py index 0a7f36a605..f75324d67c 100644 --- a/gateway/config.py +++ b/gateway/config.py @@ -863,3 +863,11 @@ def _apply_env_overrides(config: GatewayConfig) -> None: """Apply environment variable overrides to config (see ``gateway.config_env``).""" from gateway.config_env import _apply_env_overrides as _impl _impl(config) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/delivery_ledger.py b/gateway/delivery_ledger.py index 585290555e..fdcfc232a4 100644 --- a/gateway/delivery_ledger.py +++ b/gateway/delivery_ledger.py @@ -352,3 +352,33 @@ def ledger_enabled(config: Optional[Dict[str, Any]] = None) -> bool: return value.strip().lower() not in {"false", "0", "no", "off"} if isinstance(value, str) else bool(value) except Exception: return True + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +import json # noqa: F401,E402 + +def debug_rows(limit: int = 20) -> str: + """Human-readable dump for ad-hoc inspection (sqlite3-free path).""" + with _DB_LOCK, _transaction() as conn: + rows = conn.execute( + """SELECT obligation_id, session_key, state, attempts, + created_at, updated_at, last_error + FROM delivery_obligations + ORDER BY updated_at DESC LIMIT ?""", + (limit,), + ).fetchall() + return json.dumps( + [ + { + "id": r[0], "session": r[1], "state": r[2], "attempts": r[3], + "created_at": r[4], "updated_at": r[5], "last_error": r[6], + } + for r in rows + ], + indent=2, + ) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/disk_status.py b/gateway/disk_status.py index 897a251bc4..a5cda3a7f8 100644 --- a/gateway/disk_status.py +++ b/gateway/disk_status.py @@ -58,3 +58,24 @@ def collect_disk_status(home: Optional[Path] = None) -> Dict[str, Any]: status.update(total_mb=total_mb, free_mb=free_mb, used_percent=round((usage.used / usage.total) * 100, 1), pressure=classify_disk_pressure(free_mb, total_mb)) return status + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import logging # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'logger': ('gateway.run', 'logger'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/hosted_room_discussion.py b/gateway/hosted_room_discussion.py index 0dc4e9b225..559583991e 100644 --- a/gateway/hosted_room_discussion.py +++ b/gateway/hosted_room_discussion.py @@ -782,3 +782,11 @@ def plan_publication( payload={**_turn_coordinates(task), "seen_through_seq": task.seen_through_seq, **extra}, authority_gateway_id=room.gateway_id, authority_epoch=room.authority_epoch)) return PublicationPlan(task_id=task.identity.task_id, terminal_kind=terminal_kind, events=tuple(effects)) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/hosted_room_driver.py b/gateway/hosted_room_driver.py index fb7268767a..7cf44a075b 100644 --- a/gateway/hosted_room_driver.py +++ b/gateway/hosted_room_driver.py @@ -857,3 +857,14 @@ def prune_published_terminal_tasks( f"DELETE FROM hosted_room_driver_tasks WHERE room_id=? AND task_id IN ({','.join('?' * len(candidates))})", (room_id, *candidates)) return max(0, int(deleted.rowcount)) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Iterator # noqa: F401,E402 +from pathlib import Path # noqa: F401,E402 +from contextlib import contextmanager # noqa: F401,E402 +import re # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/hosted_room_execution_policy.py b/gateway/hosted_room_execution_policy.py index 29fbf66496..c91cb7b54f 100644 --- a/gateway/hosted_room_execution_policy.py +++ b/gateway/hosted_room_execution_policy.py @@ -110,3 +110,12 @@ __all__ = [ "MAX_POLICY_ITERATIONS", "POLICY_VERSION", "RoomExecutionPolicy", "RoomExecutionPolicyError", "bind_room_execution_policy", "current_room_execution_policy", "execution_policy_mapping", "reset_room_execution_policy"] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +import re # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/hosted_room_peer.py b/gateway/hosted_room_peer.py index 4264ea0ec1..fdc8c7d36a 100644 --- a/gateway/hosted_room_peer.py +++ b/gateway/hosted_room_peer.py @@ -488,3 +488,57 @@ def room_grant_needs_dispatch_refresh(token: str, *, now: float | None = None, l return clock(now) + max(0.0, float(leeway_seconds)) >= expires_at except Exception: return True + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import time # noqa: F401,E402 + +@dataclass(frozen=True) +class RoomLinkProbe: + """One gateway-verified route candidate.""" + + mode: LinkMode + verified: bool + encrypted: bool + latency_ms: float + +_LINK_PRIORITY = { + "direct": 0, + "overlay": 1, + "relay": 2, + "pull": 3, + "desktop": 4, +} + +def select_room_link( + probes: Iterable[RoomLinkProbe], + *, + desktop_available: bool, +) -> RoomLinkProbe | None: + """Choose the fastest safe route without weakening encryption.""" + candidates = [ + probe + for probe in probes + if probe.verified + and probe.encrypted + and probe.mode != "desktop" + and math.isfinite(probe.latency_ms) + and probe.latency_ms >= 0 + ] + if candidates: + return min( + candidates, + key=lambda item: (_LINK_PRIORITY[item.mode], item.latency_ms), + ) + if desktop_available: + return RoomLinkProbe( + mode="desktop", + verified=True, + encrypted=True, + latency_ms=0, + ) + return None +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/hosted_room_replicas.py b/gateway/hosted_room_replicas.py index 3a786b2f31..384fcf03be 100644 --- a/gateway/hosted_room_replicas.py +++ b/gateway/hosted_room_replicas.py @@ -284,3 +284,26 @@ def demote_room( return { "room_id": room_id, "authority_gateway_id": observed_gateway_id, "authority_epoch": observed_epoch, "idempotent": False} + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import Path # noqa: F401,E402 +import time # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'MAX_EVENT_JSON_BYTES': ('gateway.hosted_rooms', 'MAX_EVENT_JSON_BYTES'), + 'MAX_ROOM_ID_CHARS': ('gateway.hosted_rooms', 'MAX_ROOM_ID_CHARS'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/hosted_rooms.py b/gateway/hosted_rooms.py index 4a823bf0fd..5246eb1d9f 100644 --- a/gateway/hosted_rooms.py +++ b/gateway/hosted_rooms.py @@ -1158,3 +1158,14 @@ def read_events( if not fits(events): raise HostedRoomError("hosted room event exceeds replay page limit") return build_page(events) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Iterator # noqa: F401,E402 +from typing import NoReturn # noqa: F401,E402 +from contextlib import contextmanager # noqa: F401,E402 +import time # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/kanban_watchers.py b/gateway/kanban_watchers.py index 84967ad559..1aa3d2ac3d 100644 --- a/gateway/kanban_watchers.py +++ b/gateway/kanban_watchers.py @@ -304,3 +304,28 @@ class GatewayKanbanWatchersMixin: await self._sleep_between_ticks(interval) self._release_kanban_dispatcher_lock() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Callable # noqa: F401,E402 +from contextvars import Context # noqa: F401,E402 +import logging # noqa: F401,E402 +import re # noqa: F401,E402 +import sqlite3 # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 't': ('agent.i18n', 't'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/memory_status.py b/gateway/memory_status.py index e6a4600a64..f600286113 100644 --- a/gateway/memory_status.py +++ b/gateway/memory_status.py @@ -110,3 +110,24 @@ def collect_memory_status( status["boot_id"] = started_at if isinstance(started_at, str) and started_at else None return status + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import logging # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'logger': ('gateway.run', 'logger'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/platforms/helpers.py b/gateway/platforms/helpers.py index 4d6ed53fe6..32717f83c5 100644 --- a/gateway/platforms/helpers.py +++ b/gateway/platforms/helpers.py @@ -511,3 +511,98 @@ def _chunk_newline_preferred(text, limit, len_fn): if remaining: chunks.append(remaining) return chunks + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +from typing import TYPE_CHECKING # noqa: F401,E402 +import asyncio # noqa: F401,E402 +import asyncio # noqa: F401,E402 + +class TextBatchAggregator: + """Aggregates rapid-fire text events into single messages. + + Replaces the ``_enqueue_text_event`` / ``_flush_text_batch`` pattern + previously duplicated in telegram, discord, matrix, wecom, and feishu. + + Usage:: + + self._text_batcher = TextBatchAggregator( + handler=self._message_handler, + batch_delay=0.6, + split_threshold=1900, + ) + + # In message dispatch: + if msg_type == MessageType.TEXT and self._text_batcher.is_enabled(): + self._text_batcher.enqueue(event, session_key) + return + """ + + def __init__( + self, + handler, + *, + batch_delay: float = 0.6, + split_delay: float = 2.0, + split_threshold: int = 4000, + ): + self._handler = handler + self._batch_delay = batch_delay + self._split_delay = split_delay + self._split_threshold = split_threshold + self._pending: Dict[str, "MessageEvent"] = {} + self._pending_tasks: Dict[str, asyncio.Task] = {} + + def is_enabled(self) -> bool: + """Return True if batching is active (delay > 0).""" + return self._batch_delay > 0 + + def enqueue(self, event: "MessageEvent", key: str) -> None: + """Add *event* to the pending batch for *key*.""" + chunk_len = len(event.text or "") + existing = self._pending.get(key) + if not existing: + event._last_chunk_len = chunk_len # type: ignore[attr-defined] + self._pending[key] = event + else: + existing.text = f"{existing.text}\n{event.text}" + existing._last_chunk_len = chunk_len # type: ignore[attr-defined] + + # Cancel prior flush timer, start a new one + prior = self._pending_tasks.get(key) + if prior and not prior.done(): + prior.cancel() + self._pending_tasks[key] = asyncio.create_task(self._flush(key)) + + async def _flush(self, key: str) -> None: + """Wait then dispatch the batched event for *key*.""" + current_task = self._pending_tasks.get(key) + pending = self._pending.get(key) + last_len = getattr(pending, "_last_chunk_len", 0) if pending else 0 + + # Use longer delay when the last chunk looks like a split message + delay = self._split_delay if last_len >= self._split_threshold else self._batch_delay + await asyncio.sleep(delay) + + event = self._pending.pop(key, None) + if event: + try: + await self._handler(event) + except Exception: + logger.exception("[TextBatchAggregator] Error dispatching batched event for %s", key) + + if self._pending_tasks.get(key) is current_task: + self._pending_tasks.pop(key, None) + + def cancel_all(self) -> None: + """Cancel all pending flush tasks.""" + for task in self._pending_tasks.values(): + if not task.done(): + task.cancel() + self._pending_tasks.clear() + self._pending.clear() +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/platforms/qqbot/adapter.py b/gateway/platforms/qqbot/adapter.py index 5596b83eef..87f4c04857 100644 --- a/gateway/platforms/qqbot/adapter.py +++ b/gateway/platforms/qqbot/adapter.py @@ -1695,3 +1695,12 @@ class QQAdapter(BasePlatformAdapter): return True self._seen_messages[msg_id] = now return False + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import base64 # noqa: F401,E402 +import mimetypes # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/platforms/qqbot/chunked_upload.py b/gateway/platforms/qqbot/chunked_upload.py index 874a844819..2091d6aff1 100644 --- a/gateway/platforms/qqbot/chunked_upload.py +++ b/gateway/platforms/qqbot/chunked_upload.py @@ -290,3 +290,14 @@ def _compute_file_hashes(file_path: str, file_size: int) -> Dict[str, str]: bytes_read += len(chunk) full_md5 = md5.hexdigest() return {"md5": full_md5, "sha1": sha1.hexdigest(), "md5_10m": md5_10m.hexdigest() if need_10m else full_md5} + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Optional # noqa: F401,E402 +import functools # noqa: F401,E402 + +ApiRequestFn = Callable[..., Awaitable[Dict[str, Any]]] +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/platforms/qqbot/keyboards.py b/gateway/platforms/qqbot/keyboards.py index 490b8e6ae7..9d31e2b517 100644 --- a/gateway/platforms/qqbot/keyboards.py +++ b/gateway/platforms/qqbot/keyboards.py @@ -189,3 +189,97 @@ def parse_interaction_event(raw: Dict[str, Any]) -> InteractionEvent: channel_id=str(raw.get("channel_id", "")), guild_id=str(raw.get("guild_id", "")), button_data=str(resolved.get("button_data", "")), button_id=str(resolved.get("button_id", "")), resolver_user_id=str(resolved.get("user_id", ""))) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import logging # noqa: F401,E402 +from typing import Awaitable # noqa: F401,E402 +from typing import Callable # noqa: F401,E402 +from typing import Awaitable # noqa: F401,E402 +from typing import Callable # noqa: F401,E402 +import logging # noqa: F401,E402 + +logger = logging.getLogger(__name__) + +PostMessageFn = Callable[..., Awaitable[Dict[str, Any]]] + +class ApprovalSender: + """Send an approval-request message with an inline keyboard. + + Decoupled from the adapter via callables so it can be unit-tested in + isolation. Pass the adapter's ``_send_message_with_keyboard`` helper + (or any equivalent) as ``post_message``. + """ + + def __init__( + self, + post_c2c: PostMessageFn, + post_group: PostMessageFn, + log_tag: str = "QQBot", + ) -> None: + self._post_c2c = post_c2c + self._post_group = post_group + self._log_tag = log_tag + + async def send( + self, + chat_type: str, + chat_id: str, + req: ApprovalRequest, + msg_id: Optional[str] = None, + ) -> bool: + """Send an approval message to *chat_id*. + + :param chat_type: ``'c2c'`` or ``'group'``. + :param chat_id: User openid or group openid. + :param req: :class:`ApprovalRequest`. + :param msg_id: Reply-to message id (required for passive messages). + :returns: ``True`` on success, ``False`` on failure. + """ + text = build_approval_text(req) + keyboard = build_approval_keyboard(req.session_key) + + logger.info( + "[%s] Sending approval request to %s:%s (session=%.20s…)", + self._log_tag, chat_type, chat_id, req.session_key, + ) + + try: + if chat_type == "c2c": + await self._post_c2c(chat_id, text, msg_id, keyboard) + elif chat_type == "group": + await self._post_group(chat_id, text, msg_id, keyboard) + else: + logger.warning( + "[%s] Approval: unsupported chat_type %r", + self._log_tag, chat_type, + ) + return False + logger.info( + "[%s] Approval message sent to %s:%s", + self._log_tag, chat_type, chat_id, + ) + return True + except Exception as exc: + logger.error( + "[%s] Failed to send approval message to %s:%s: %s", + self._log_tag, chat_type, chat_id, exc, + ) + return False + + +_PLUGIN_COMPAT_LAZY = { + 'logger': ('gateway.platforms.qqbot.adapter', 'logger'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/platforms/signal.py b/gateway/platforms/signal.py index e9ac35529d..254de671b7 100644 --- a/gateway/platforms/signal.py +++ b/gateway/platforms/signal.py @@ -974,3 +974,25 @@ class SignalAdapter(BasePlatformAdapter): result = await self._rpc("getContact", {"account": self.account, "contactAddress": chat_id}) name = (result.get("name") or result.get("profileName")) if isinstance(result, dict) else None return {"name": name or chat_id, "type": "dm", "chat_id": chat_id} + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +TYPING_INTERVAL = 8.0 # seconds between typing indicator refreshes + + +_PLUGIN_COMPAT_LAZY = { + 'DEFAULT_EXT_TO_MIME': ('gateway.platforms.media_cache', 'DEFAULT_EXT_TO_MIME'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/platforms/weixin.py b/gateway/platforms/weixin.py index 1316af4c2d..2f4e97f53a 100644 --- a/gateway/platforms/weixin.py +++ b/gateway/platforms/weixin.py @@ -1265,3 +1265,13 @@ async def send_weixin_direct( adapter._send_session = adapter._session = session adapter._token_store = token_store return await _deliver_direct(adapter, chat_id, message, media_files, context_token) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import struct # noqa: F401,E402 + +MSG_TYPE_USER = 1 +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/platforms/yuanbao.py b/gateway/platforms/yuanbao.py index cd913d2b14..02093b501d 100644 --- a/gateway/platforms/yuanbao.py +++ b/gateway/platforms/yuanbao.py @@ -2802,3 +2802,205 @@ class YuanbaoAdapter(BasePlatformAdapter): """Current valid sign token (module-level cache).""" return await SignManager.get_token(self._app_key, self._app_secret, self._api_domain, route_env=self._route_env) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +AUTH_FAILED_CODES = {4001, 4002, 4003} # permanent auth failure, re-sign token + +AUTH_RETRYABLE_CODES = {4010, 4011, 4099} # transient, can retry with same token + +class FileUrlHandler(MediaSendHandler): + """Strategy: send file from a URL (download → COS → TIMFileElem).""" + + async def acquire_file(self, adapter, **kwargs): + file_url: str = kwargs["file_url"] + logger.info("[%s] FileUrlHandler: downloading %s", adapter.name, file_url) + file_bytes, content_type = await media_download_url( + file_url, max_size_mb=adapter.MEDIA_MAX_SIZE_MB, + ) + filename = kwargs.get("filename") + if not filename: + path_part = file_url.split("?")[0] + filename = os.path.basename(path_part) or "file" + if not content_type or content_type == "application/octet-stream": + content_type = guess_mime_type(filename) or "application/octet-stream" + return file_bytes, filename, content_type + + def build_msg_body(self, upload_result, **kwargs): + return build_file_msg_body( + url=upload_result["url"], + filename=kwargs["filename"], + uuid=kwargs["file_uuid"], + size=upload_result["size"], + ) + +class GroupQueryService: + """Encapsulates all group query operations (both low-level WS calls and + higher-level AI-tool-facing wrappers). + + Responsibilities: + - Low-level WS encode/decode for group info and member list queries + - Chat-id parsing, error wrapping and result filtering for AI tools + - Member cache population on the adapter + """ + + def __init__(self, adapter: "YuanbaoAdapter") -> None: + self._adapter = adapter + + # ------------------------------------------------------------------ + # Low-level WS query methods + # ------------------------------------------------------------------ + + async def query_group_info_raw(self, group_code: str) -> Optional[dict]: + """Query group info via WS (group name, owner, member count, etc.). + + Returns: + Decoded dict or None on failure. + """ + adapter = self._adapter + if adapter._connection.ws is None: + return None + encoded = encode_query_group_info(group_code) + from gateway.platforms.yuanbao_proto import decode_conn_msg as _decode + decoded = _decode(encoded) + req_id = decoded["head"]["msg_id"] + try: + response = await adapter._connection.send_biz_request(encoded, req_id=req_id) + head = response.get("head", {}) + status = head.get("status", 0) + if status != 0: + logger.warning("[%s] query_group_info failed: status=%d", adapter.name, status) + return None + biz_data = response.get("data", b"") or response.get("body", b"") + if biz_data and isinstance(biz_data, bytes): + return decode_query_group_info_rsp(biz_data) + return {"group_code": group_code} + except asyncio.TimeoutError: + logger.warning("[%s] query_group_info timeout: group=%s", adapter.name, group_code) + return None + except Exception as exc: + logger.warning("[%s] query_group_info failed: %s", adapter.name, exc) + return None + + async def get_group_member_list_raw( + self, group_code: str, offset: int = 0, limit: int = 200 + ) -> Optional[dict]: + """Query group member list via WS. + + Returns: + Decoded dict or None on failure. Also populates adapter._member_cache. + """ + adapter = self._adapter + if adapter._connection.ws is None: + return None + encoded = encode_get_group_member_list(group_code, offset=offset, limit=limit) + from gateway.platforms.yuanbao_proto import decode_conn_msg as _decode + decoded = _decode(encoded) + req_id = decoded["head"]["msg_id"] + try: + response = await adapter._connection.send_biz_request(encoded, req_id=req_id) + head = response.get("head", {}) + status = head.get("status", 0) + if status != 0: + logger.warning("[%s] get_group_member_list failed: status=%d", adapter.name, status) + return None + biz_data = response.get("data", b"") or response.get("body", b"") + if biz_data and isinstance(biz_data, bytes): + result = decode_get_group_member_list_rsp(biz_data) + else: + result = {"members": [], "next_offset": 0, "is_complete": True} + if result and result.get("members"): + adapter._member_cache[group_code] = (time.time(), result["members"]) + return result + except asyncio.TimeoutError: + logger.warning("[%s] get_group_member_list timeout: group=%s", adapter.name, group_code) + return None + except Exception as exc: + logger.warning("[%s] get_group_member_list failed: %s", adapter.name, exc) + return None + + # ------------------------------------------------------------------ + # AI-tool-facing wrappers (chat_id parsing + filtering) + # ------------------------------------------------------------------ + + async def query_group_info(self, chat_id: str) -> dict: + """AI tool: Query current group info. + + No parameters needed (group_code extracted from session context). + Returns group name, owner, member count, etc. + """ + if not chat_id.startswith("group:"): + return {"error": "This command is only available in group chats"} + group_code = chat_id[len("group:"):] + result = await self.query_group_info_raw(group_code) + if result is None: + return {"error": "Failed to query group info"} + return result + + async def query_session_members( + self, + chat_id: str, + action: str = "list_all", + name: Optional[str] = None, + ) -> dict: + """AI tool: Query group member list. + + Args: + chat_id: Chat ID (extracted from session context) + action: 'find' (search by name) | 'list_bots' (list bots) | 'list_all' (list all) + name: Search keyword when action='find' + + Returns: + {"members": [...], "total": int, "mentionHint": str} + """ + if not chat_id.startswith("group:"): + return {"error": "This command is only available in group chats"} + group_code = chat_id[len("group:"):] + result = await self.get_group_member_list_raw(group_code) + if result is None: + return {"error": "Failed to query group members"} + + members = result.get("members", []) + + if action == "find" and name: + query = name.lower() + members = [ + m for m in members + if query in (m.get("nickname", "") or "").lower() + or query in (m.get("name_card", "") or "").lower() + or query in (m.get("user_id", "") or "").lower() + ] + elif action == "list_bots": + members = [m for m in members if "bot" in (m.get("nickname", "") or "").lower()] + + # Construct mentionHint + mention_hint = "" + if members and len(members) <= 10: + names = [m.get("name_card") or m.get("nickname") or m.get("user_id", "") for m in members] + mention_hint = "Mention with @name: " + ", ".join(names) + + return { + "members": members[:50], # Limit return count + "total": len(members), + "mentionHint": mention_hint, + } + +REPLY_REF_TTL_S = 300.0 # Reference dedup TTL (5 minutes) + +def get_active_adapter() -> Optional["YuanbaoAdapter"]: + """Delegate to ``YuanbaoAdapter.get_active()``.""" + return YuanbaoAdapter.get_active() + +async def send_yuanbao_direct( + adapter: "YuanbaoAdapter", + chat_id: str, + message: str, + media_files: Optional[List[Tuple[str, bool]]] = None, +) -> Dict[str, Any]: + """Delegate to ``OutboundManager.send_direct``.""" + return await adapter._outbound.send_direct(chat_id, message, media_files) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/platforms/yuanbao_media.py b/gateway/platforms/yuanbao_media.py index 9371e0868a..946e331cc3 100644 --- a/gateway/platforms/yuanbao_media.py +++ b/gateway/platforms/yuanbao_media.py @@ -317,3 +317,12 @@ def _basename_from_url(url: str) -> str: return os.path.basename(urllib.parse.urlparse(url).path) except Exception: return "" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +COS_USE_ACCELERATE = True +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/platforms/yuanbao_proto.py b/gateway/platforms/yuanbao_proto.py index 7937f29662..cb138c1e4b 100644 --- a/gateway/platforms/yuanbao_proto.py +++ b/gateway/platforms/yuanbao_proto.py @@ -538,3 +538,79 @@ def decode_get_group_member_list_rsp(data: bytes) -> Optional[dict]: } except Exception: return None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import logging # noqa: F401,E402 + +DEBUG_MODE = False + +def _encode_forward_multimedia(media: dict) -> bytes: + buf = b"" + for fn, key in [(1, "type"), (2, "url"), (4, "file_name"), (15, "media_id")]: + v = media.get(key, "") + if v: + buf += _encode_field(fn, WT_LEN, _encode_string(str(v))) + for fn, key in [(5, "file_size"), (6, "width"), (7, "height")]: + v = media.get(key, 0) + if v: + buf += _encode_field(fn, WT_VARINT, _encode_varint(int(v))) + return buf + +def _encode_forward_msg_content(content: dict) -> bytes: + buf = _encode_field(1, WT_VARINT, _encode_varint(int(content.get("type", 0)))) + text = content.get("text", "") + if text: + buf += _encode_field(2, WT_LEN, _encode_string(str(text))) + for media in content.get("multimedia") or []: + buf += _encode_field(3, WT_LEN, _encode_message(_encode_forward_multimedia(media))) + return buf + +def _encode_forward_msg(msg: dict) -> bytes: + buf = b"" + sender = msg.get("sender", "") + if sender: + buf += _encode_field(1, WT_LEN, _encode_string(str(sender))) + time_val = msg.get("time", 0) + if time_val: + buf += _encode_field(2, WT_VARINT, _encode_varint(int(time_val))) + plain = msg.get("plainText", "") + if plain: + buf += _encode_field(3, WT_LEN, _encode_string(str(plain))) + for mc in msg.get("msgContent") or []: + buf += _encode_field(4, WT_LEN, _encode_message(_encode_forward_msg_content(mc))) + return buf + +def encode_forward_msg_data(data: dict) -> bytes: + """Encode ForwardMsgData protobuf bytes (inverse of ``decode_forward_msg_data``). + + Mainly used to build mock / test data; production code never needs to encode this. + """ + buf = _encode_field(1, WT_VARINT, _encode_varint(int(data.get("sub_type", 0)))) + for fn, key in [(2, "begin_time"), (3, "end_time")]: + v = data.get(key, 0) + if v: + buf += _encode_field(fn, WT_VARINT, _encode_varint(int(v))) + nick = data.get("nick_name", "") + if nick: + buf += _encode_field(4, WT_LEN, _encode_string(str(nick))) + for msg in data.get("msg") or []: + buf += _encode_field(5, WT_LEN, _encode_message(_encode_forward_msg(msg))) + return buf + + +_PLUGIN_COMPAT_LAZY = { + 'logger': ('gateway.platforms.base', 'logger'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/relay/__init__.py b/gateway/relay/__init__.py index b49a240787..2366cf943c 100644 --- a/gateway/relay/__init__.py +++ b/gateway/relay/__init__.py @@ -650,3 +650,22 @@ def register_relay_adapter(force: bool = False, url: Optional[str] = None) -> bo ) ) return True + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def relay_bot_username(platform: str) -> Optional[str]: + """The bot's deep-link username/handle for a platform (e.g. Telegram's + ``@handle`` for ``t.me/``), read from the per-platform entry in + ``GATEWAY_RELAY_BOT_IDS``. None when absent (most platforms don't need one). + """ + entry = _relay_bot_ids_map().get(platform) + if isinstance(entry, dict): + username = entry.get("username") + if username: + return str(username).lstrip("@") + return None +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/relay/adapter.py b/gateway/relay/adapter.py index 021d723764..c680236deb 100644 --- a/gateway/relay/adapter.py +++ b/gateway/relay/adapter.py @@ -2139,3 +2139,11 @@ _PROMPT_RESOLVERS = { "slash_confirm": RelayAdapter._resolve_slash_confirm, "clarify": RelayAdapter._resolve_clarify, } + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import cast # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/relay/auth.py b/gateway/relay/auth.py index ca23ab1c5d..8a2e069a6d 100644 --- a/gateway/relay/auth.py +++ b/gateway/relay/auth.py @@ -38,3 +38,102 @@ def make_upgrade_token( ) -> str: """WS-upgrade bearer: ``payload = gateway_id`` (the connector peeks it to index its verify list).""" return make_token(gateway_id, secret, ttl_seconds) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Optional # noqa: F401,E402 +from typing import Sequence # noqa: F401,E402 + +DELIVERY_SIG_HEADER = "x-relay-signature" + +DELIVERY_TS_HEADER = "x-relay-timestamp" + +_DEFAULT_MAX_SKEW_SECONDS = 300 + +def _hmac_hex(payload: str, secret: str) -> str: + """HMAC-SHA256 hex digest of ``payload`` under ``secret`` (UTF-8).""" + return hmac.new(secret.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256).hexdigest() + +def verify_signature(payload: str, sig_hex: str, secrets: Sequence[str]) -> bool: + """Constant-time check that ``sig_hex`` is a valid HMAC of ``payload`` under + ANY of ``secrets`` (rotation window). Length-mismatched candidates are + skipped without a timing leak. Mirrors ``verifySignature``. + """ + try: + sig_buf = bytes.fromhex(sig_hex) + except (ValueError, TypeError): + return False + if len(sig_buf) == 0: + return False + for secret in secrets: + if not secret: + continue + expected = bytes.fromhex(_hmac_hex(payload, secret)) + if len(expected) != len(sig_buf): + continue + if hmac.compare_digest(sig_buf, expected): + return True + return False + +def _delivery_payload(ts: int, body_json: str) -> str: + """Signed material for an inbound delivery: ``f"{ts}.{body_json}"``.""" + return f"{ts}.{body_json}" + +def verify_delivery_signature( + body_json: str, + timestamp: Optional[str], + signature: Optional[str], + verify_keys: Sequence[str], + max_skew_seconds: int = _DEFAULT_MAX_SKEW_SECONDS, + *, + now: Optional[int] = None, +) -> bool: + """Verify a connector→gateway inbound delivery signature. + + ``body_json`` MUST be the exact request body bytes decoded as UTF-8 — the + connector signs over the literal serialized body, so the gateway verifies + over the literal received body (no re-serialization). Checks the timestamp + is within ``max_skew_seconds`` of now and the HMAC matches any key in the + rotation verify list. Mirrors the connector's ``verifyDeliverySignature``. + """ + if not timestamp or not signature: + return False + try: + ts = int(timestamp) + except (ValueError, TypeError): + return False + current = now if now is not None else int(time.time()) + if abs(current - ts) > max_skew_seconds: + return False + return verify_signature(_delivery_payload(ts, body_json), signature, verify_keys) + +def verify_token(token: str, secrets: Sequence[str]) -> Optional[str]: + """Verify a token built by ``make_token``; return the payload or None. + + Splits from the right so a payload may itself contain colons (mirrors the + connector's ``verifyToken``). Rejects an expired token and any signature + that doesn't match a secret in the verify list. + """ + try: + # base64url decode with padding restored. + padded = token + "=" * (-len(token) % 4) + decoded = base64.urlsafe_b64decode(padded.encode("ascii")).decode("utf-8") + except (ValueError, TypeError): + return None + parts = decoded.split(":") + if len(parts) < 3: + return None + sig = parts[-1] + try: + exp = int(parts[-2]) + except ValueError: + return None + payload = ":".join(parts[:-2]) + if exp != 0 and int(time.time()) > exp: + return None + signed = f"{payload}:{exp}" + return payload if verify_signature(signed, sig, secrets) else None +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/response_filters.py b/gateway/response_filters.py index 6f29393544..b465053248 100644 --- a/gateway/response_filters.py +++ b/gateway/response_filters.py @@ -92,3 +92,12 @@ def is_partial_silence_marker(text: Any) -> bool: c and any(marker.startswith(c) for marker in LIVE_GATEWAY_SILENT_MARKERS) for c in _canonical_silence_candidates(text) ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +SILENT_REPLY_TOKEN = "NO_REPLY" +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/restart_loop_guard.py b/gateway/restart_loop_guard.py index 09f47b3d4a..627ad0404a 100644 --- a/gateway/restart_loop_guard.py +++ b/gateway/restart_loop_guard.py @@ -112,3 +112,37 @@ def check_and_record( len(boots), int(_chain_gap(window_seconds, max_gap_seconds)), max_restarts, _state_path(), ) return tripped + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def is_restart_loop_tripped( + max_restarts: int = DEFAULT_MAX_RESTARTS, + window_seconds: int = DEFAULT_WINDOW_SECONDS, + *, + now: Optional[float] = None, + max_gap_seconds: int = DEFAULT_MAX_GAP_SECONDS, +) -> bool: + """Return True if the gateway has restarted ``>= max_restarts`` times with + restart-interrupted sessions in one unbroken chain ending at ``now``. + + Reads the persisted boot log written by + ``record_restart_interrupted_boot`` and counts the boots that still chain + together (consecutive gaps within ``max_gap_seconds``), so the verdict does + not depend on how fast the crash cycle happens to be. + Fails OPEN (returns False) on any error — a broken breaker must never + wedge a healthy gateway. + """ + if max_restarts <= 0: + return False + ts = time.time() if now is None else now + gap = _chain_gap(window_seconds, max_gap_seconds) + try: + recent = _chain_ending_at(_load_boots(), ts, gap) + except Exception: # pragma: no cover — _load_boots already guards + return False + return len(recent) >= max_restarts +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/run.py b/gateway/run.py index b45327d6ce..c3a2d89b04 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -5438,3 +5438,73 @@ def _exit_after_graceful_shutdown(exit_code: int) -> None: if __name__ == "__main__": main() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Awaitable # noqa: F401,E402 +from contextvars import Context # noqa: F401,E402 +from typing import Union # noqa: F401,E402 +import faulthandler # noqa: F401,E402 +import functools # noqa: F401,E402 +import inspect # noqa: F401,E402 +from dotenv import load_dotenv # noqa: F401,E402 +import queue # noqa: F401,E402 +from datetime import timedelta # noqa: F401,E402 +from datetime import timezone # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'DEFAULT_GATEWAY_POST_INTERRUPT_GRACE_TIMEOUT': ('gateway.restart', 'DEFAULT_GATEWAY_POST_INTERRUPT_GRACE_TIMEOUT'), + 'DEFAULT_HEARTBEAT_INTERVAL_S': ('gateway.shutdown_watchdog', 'DEFAULT_HEARTBEAT_INTERVAL_S'), + 'DEFAULT_LEASE_WAIT': ('gateway.turn_lease', 'DEFAULT_LEASE_WAIT'), + 'DEFAULT_LOOP_WATCHDOG_INTERVAL_S': ('gateway.shutdown_watchdog', 'DEFAULT_LOOP_WATCHDOG_INTERVAL_S'), + 'DEFAULT_LOOP_WATCHDOG_MAX_STRIKES': ('gateway.shutdown_watchdog', 'DEFAULT_LOOP_WATCHDOG_MAX_STRIKES'), + 'DEFAULT_LOOP_WATCHDOG_TIMEOUT_S': ('gateway.shutdown_watchdog', 'DEFAULT_LOOP_WATCHDOG_TIMEOUT_S'), + 'EphemeralReply': ('gateway.platforms.base', 'EphemeralReply'), + 'GATEWAY_FATAL_CONFIG_EXIT_CODE': ('gateway.restart', 'GATEWAY_FATAL_CONFIG_EXIT_CODE'), + 'GATEWAY_SERVICE_RESTART_EXIT_CODE': ('gateway.restart', 'GATEWAY_SERVICE_RESTART_EXIT_CODE'), + 'SessionEntry': ('gateway.session', 'SessionEntry'), + 'TranscriptReadError': ('gateway.session_transcript', 'TranscriptReadError'), + 'TurnContext': ('gateway.turn_context', 'TurnContext'), + 'TurnLeaseTimeoutError': ('gateway.turn_lease', 'TurnLeaseTimeoutError'), + 'TurnRunner': ('gateway.run_turn_runner', 'TurnRunner'), + 'arm_shutdown_watchdog': ('gateway.shutdown_watchdog', 'arm_shutdown_watchdog'), + 'atomic_json_write': ('utils', 'atomic_json_write'), + 'base_url_hostname': ('utils', 'base_url_hostname'), + 'build_auto_tts_output_path': ('gateway.platforms.base', 'build_auto_tts_output_path'), + 'build_channel_continuity_note': ('gateway.session', 'build_channel_continuity_note'), + 'build_session_context': ('gateway.session', 'build_session_context'), + 'build_session_context_prompt': ('gateway.session', 'build_session_context_prompt'), + 'consume_detached_task_result': ('agent.async_utils', 'consume_detached_task_result'), + 'is_global_startup_conflict': ('gateway.restart', 'is_global_startup_conflict'), + 'is_shared_multi_user_session': ('gateway.session', 'is_shared_multi_user_session'), + 'is_truthy_value': ('utils', 'is_truthy_value'), + 'looks_like_telegram_private_chat_id': ('gateway.delivery', 'looks_like_telegram_private_chat_id'), + 'loop_heartbeat_forever': ('gateway.shutdown_watchdog', 'loop_heartbeat_forever'), + 'merge_pending_message_event': ('gateway.platforms.base', 'merge_pending_message_event'), + 'neutralize_untrusted_inline_text': ('gateway.session', 'neutralize_untrusted_inline_text'), + 'parse_cron_drain_timeout': ('gateway.restart', 'parse_cron_drain_timeout'), + 'parse_restart_after_turn_timeout': ('gateway.restart', 'parse_restart_after_turn_timeout'), + 'parse_restart_drain_timeout': ('gateway.restart', 'parse_restart_drain_timeout'), + 'parse_signal_interrupt_grace_timeout': ('gateway.restart', 'parse_signal_interrupt_grace_timeout'), + 'project_compaction_message_for_display': ('agent.compaction_display', 'project_compaction_message_for_display'), + 'repair_explicit_computer_use_media_paths': ('gateway.media_repair', 'repair_explicit_computer_use_media_paths'), + 'resolve_cron_drain_budget': ('gateway.restart', 'resolve_cron_drain_budget'), + 'resolve_delivery_transport': ('gateway.delivery', 'resolve_delivery_transport'), + 'resolve_shutdown_watchdog_delay': ('gateway.shutdown_watchdog', 'resolve_shutdown_watchdog_delay'), + 'start_loop_liveness_watchdog': ('gateway.shutdown_watchdog', 'start_loop_liveness_watchdog'), + 't': ('agent.i18n', 't'), + 'utf16_len': ('gateway.platforms.base', 'utf16_len'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/session.py b/gateway/session.py index 4cebdf9156..72a7c8e88f 100644 --- a/gateway/session.py +++ b/gateway/session.py @@ -1191,3 +1191,30 @@ def build_session_context( context.session_id = session_entry.session_id context.created_at, context.updated_at = session_entry.created_at, session_entry.updated_at return context + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from dataclasses import replace # noqa: F401,E402 +import uuid # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'SessionResetPolicy': ('gateway.config', 'SessionResetPolicy'), + 'TranscriptReadError': ('gateway.session_transcript', 'TranscriptReadError'), + 'atomic_replace': ('utils', 'atomic_replace'), + 'auto_continue_freshness_window': ('gateway.session_lifecycle', 'auto_continue_freshness_window'), + 'extract_api_content_sidecar': ('agent.turn_context', 'extract_api_content_sidecar'), + 'normalize_whatsapp_identifier': ('gateway.whatsapp_identity', 'normalize_whatsapp_identifier'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/slash_access.py b/gateway/slash_access.py index ed7b096f11..07204201e4 100644 --- a/gateway/slash_access.py +++ b/gateway/slash_access.py @@ -118,3 +118,11 @@ def policy_for_source(gateway_config: Any, source: Any) -> SlashAccessPolicy: __all__ = ["SlashAccessPolicy", "policy_from_extra", "policy_for_source"] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Tuple # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/slash_commands.py b/gateway/slash_commands.py index bb215473b6..32121ca61f 100644 --- a/gateway/slash_commands.py +++ b/gateway/slash_commands.py @@ -1240,3 +1240,34 @@ class GatewaySlashCommandsMixin( return t("gateway.update.start_failed", error=e) self._schedule_update_notification_watch() return t("gateway.update.starting") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Any # noqa: F401,E402 +import hashlib # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'HISTORY_UNREADABLE': ('gateway.slash_commands_status', 'HISTORY_UNREADABLE'), + 'MessageType': ('gateway.platforms.base', 'MessageType'), + 'SessionSource': ('gateway.session', 'SessionSource'), + 'base_url_host_matches': ('utils', 'base_url_host_matches'), + 'build_session_key': ('gateway.session', 'build_session_key'), + 'clear_model_endpoint_credentials': ('hermes_cli.config', 'clear_model_endpoint_credentials'), + 'extract_api_content_sidecar': ('agent.turn_context', 'extract_api_content_sidecar'), + 'fetch_account_usage': ('agent.account_usage', 'fetch_account_usage'), + 'is_shared_multi_user_session': ('gateway.session', 'is_shared_multi_user_session'), + 'render_account_usage_lines': ('agent.account_usage', 'render_account_usage_lines'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/startup_watchdog.py b/gateway/startup_watchdog.py new file mode 100644 index 0000000000..b9f629446e --- /dev/null +++ b/gateway/startup_watchdog.py @@ -0,0 +1,6 @@ +"""PLUGIN-COMPAT stub (revert-scheduled; see COMPAT_MANIFEST.md). + +``gateway.startup_watchdog`` was folded into ``gateway.shutdown_watchdog`` in the Sep 2026 decomposition. This stub keeps the +old import path alive for external plugins only; internal code must import ``gateway.shutdown_watchdog``. +""" +from gateway.shutdown_watchdog import * # noqa: F401,F403 diff --git a/gateway/status.py b/gateway/status.py index 440093e6a9..b356a938b6 100644 --- a/gateway/status.py +++ b/gateway/status.py @@ -1540,3 +1540,25 @@ def get_running_pid_cached( with _gateway_running_pid_cache_lock: _gateway_running_pid_cache[key] = (time.monotonic(), refreshed_signature, pid) return pid + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def clear_planned_stop_marker() -> None: + """Remove the planned-stop marker unconditionally.""" + try: + _get_planned_stop_marker_path().unlink(missing_ok=True) + except OSError: + pass + +def is_gateway_running( + pid_path: Optional[Path] = None, + *, + cleanup_stale: bool = True, +) -> bool: + """Check if the gateway daemon is currently running.""" + return get_running_pid(pid_path, cleanup_stale=cleanup_stale) is not None +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/sticker_cache.py b/gateway/sticker_cache.py index 2760ef030c..cc2a4f3d5a 100644 --- a/gateway/sticker_cache.py +++ b/gateway/sticker_cache.py @@ -61,3 +61,12 @@ def build_animated_sticker_injection(emoji: str = "") -> str: return (f"[The user sent an animated sticker {emoji}~ " f"I can't see animated ones yet, but the emoji suggests: {emoji}]") return "[The user sent an animated sticker~ I can't see animated ones yet]" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 +import tempfile # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/stream_consumer.py b/gateway/stream_consumer.py index 603f1735b9..6d06275376 100644 --- a/gateway/stream_consumer.py +++ b/gateway/stream_consumer.py @@ -935,3 +935,24 @@ class GatewayStreamConsumer(StreamTransportMixin, StreamFallbackMixin, StreamThi def _clean_for_display(text: str) -> str: """Hide MEDIA: / [[audio_as_voice]] directives; media is delivered post-stream.""" return _BasePlatformAdapter.strip_media_directives_for_display(text) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'MEDIA_TAG_CLEANUP_RE': ('gateway.platforms.base', 'MEDIA_TAG_CLEANUP_RE'), + 'escape_code_fences_for_display': ('gateway.stream_consumer_fences', 'escape_code_fences_for_display'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/stream_dispatch.py b/gateway/stream_dispatch.py index 140dd11b05..fad35e71b0 100644 --- a/gateway/stream_dispatch.py +++ b/gateway/stream_dispatch.py @@ -82,3 +82,23 @@ class GatewayEventDispatcher: __all__ = ["GatewayEventDispatcher"] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'ToolCallFinished': ('gateway.stream_events', 'ToolCallFinished'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/gateway/systemd_notify.py b/gateway/systemd_notify.py index da66753533..4fbefeb65a 100644 --- a/gateway/systemd_notify.py +++ b/gateway/systemd_notify.py @@ -117,3 +117,11 @@ class SystemdWatchdog: if self.enabled and not self._stopping_notified: notify("STOPPING=1") self._stopping_notified = True + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Optional # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/agent_import.py b/hermes_cli/agent_import.py index 5b01e0fff3..aacb59361b 100644 --- a/hermes_cli/agent_import.py +++ b/hermes_cli/agent_import.py @@ -639,3 +639,28 @@ def print_import_report(report: Dict[str, Any], dry_run: bool) -> None: parts = [f"{summary[k]} {label}" for k, _, _, label in groups if summary.get(k)] if parts: print_info(f"Summary: {', '.join(parts)}") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def backup_memory_file(path: Path) -> Optional[Path]: + """Snapshot ``path`` before a destructive rewrite; return the backup path. + + Restores parity with the openclaw migration script this module was ported + from, which calls ``maybe_backup(destination)`` before rewriting a memory + store. Uses the same ``.bak.`` naming as + ``MemoryStore._backup_drifted_file``. Returns None when there is nothing + to back up. + """ + if not path.exists(): + return None + backup = path.with_suffix(path.suffix + f".bak.{int(time.time())}") + shutil.copy2(path, backup) + return backup + +def default_source_dir(agent: str) -> Path: + return Path.home() / _AGENT_DEFAULT_DIRS[agent] +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 7a7cf5880f..e8c711a219 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -2185,3 +2185,49 @@ def logout_command(args) -> None: print("Hermes will use OpenRouter for inference.") else: print("Run `hermes model` or configure an API key to use Hermes.") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from http.server import BaseHTTPRequestHandler # noqa: F401,E402 +from http.server import HTTPServer # noqa: F401,E402 +from typing import TYPE_CHECKING # noqa: F401,E402 +import base64 # noqa: F401,E402 +import hashlib # noqa: F401,E402 +from urllib.parse import parse_qs # noqa: F401,E402 +import ssl # noqa: F401,E402 +import subprocess # noqa: F401,E402 +import sys # noqa: F401,E402 +from urllib.parse import urlencode # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'CODEX_OAUTH_USER_AGENT': ('hermes_cli.auth_constants', 'CODEX_OAUTH_USER_AGENT'), + 'CODEX_QUOTA_PROBE_MIN_INTERVAL_SECONDS': ('hermes_cli.auth_codex', 'CODEX_QUOTA_PROBE_MIN_INTERVAL_SECONDS'), + 'DEFAULT_SPOTIFY_REDIRECT_URI': ('hermes_cli.auth_constants', 'DEFAULT_SPOTIFY_REDIRECT_URI'), + 'DEVICE_AUTH_POLL_INTERVAL_CAP_SECONDS': ('hermes_cli.auth_constants', 'DEVICE_AUTH_POLL_INTERVAL_CAP_SECONDS'), + 'MINIMAX_OAUTH_GRANT_TYPE': ('hermes_cli.auth_constants', 'MINIMAX_OAUTH_GRANT_TYPE'), + 'NOUS_INFERENCE_INVOKE_SCOPE': ('hermes_cli.auth_constants', 'NOUS_INFERENCE_INVOKE_SCOPE'), + 'NOUS_SHARED_STORE_FILENAME': ('hermes_cli.auth_nous', 'NOUS_SHARED_STORE_FILENAME'), + 'OAUTH_OVER_SSH_DOCS_URL': ('hermes_cli.auth_constants', 'OAUTH_OVER_SSH_DOCS_URL'), + 'QWEN_OAUTH_CLIENT_ID': ('hermes_cli.auth_constants', 'QWEN_OAUTH_CLIENT_ID'), + 'QWEN_OAUTH_TOKEN_URL': ('hermes_cli.auth_constants', 'QWEN_OAUTH_TOKEN_URL'), + 'SINGLE_USE_OAUTH_SINGLETON_FILES': ('hermes_cli.auth_oauth_grants', 'SINGLE_USE_OAUTH_SINGLETON_FILES'), + 'SPOTIFY_ACCESS_TOKEN_REFRESH_SKEW_SECONDS': ('hermes_cli.auth_constants', 'SPOTIFY_ACCESS_TOKEN_REFRESH_SKEW_SECONDS'), + 'SPOTIFY_DASHBOARD_URL': ('hermes_cli.auth_constants', 'SPOTIFY_DASHBOARD_URL'), + 'XAI_OAUTH_DEVICE_CODE_URL': ('hermes_cli.auth_constants', 'XAI_OAUTH_DEVICE_CODE_URL'), + 'XAI_OAUTH_DISCOVERY_URL': ('hermes_cli.auth_constants', 'XAI_OAUTH_DISCOVERY_URL'), + 'XAI_OAUTH_ISSUER': ('hermes_cli.auth_constants', 'XAI_OAUTH_ISSUER'), + 'refresh_nous_oauth_pure': ('hermes_cli.auth_nous', 'refresh_nous_oauth_pure'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/backup.py b/hermes_cli/backup.py index 55f7ccc366..9c4d4fe4da 100644 --- a/hermes_cli/backup.py +++ b/hermes_cli/backup.py @@ -1623,3 +1623,31 @@ def create_pre_migration_backup( found or the write failed. Never raises.""" return _create_prefixed_full_backup( hermes_home, _PRE_MIGRATION_PREFIX, max(keep, 0), "pre-migration", "pre-migration backup") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def copy_db_and_verify(src: Path, dst: Path) -> bool: + """Like :func:`_safe_copy_db` but verifies the destination after copy. + + Returns True only when the copy succeeded AND the destination is valid + SQLite (header + integrity check). Verification honours the default + size ceiling — a multi-GB destination gets the header + schema probe + rather than a full ``PRAGMA integrity_check`` that would page through + the whole file. + """ + if not _safe_copy_db(src, dst): + return False + integrity = verify_sqlite_integrity(dst, run_pragma=True) + if not integrity.get("valid"): + try: + dst.unlink(missing_ok=True) + except OSError: + pass + logger.warning("Backup of %s failed integrity verification: %s", src, integrity.get("message")) + return False + return True +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/browser_connect.py b/hermes_cli/browser_connect.py index fed82e3b20..c75f9890ad 100644 --- a/hermes_cli/browser_connect.py +++ b/hermes_cli/browser_connect.py @@ -920,3 +920,13 @@ def launch_chrome_debug( candidate, attempt.returncode, port, f"; stderr tail: {attempt.stderr_tail}" if attempt.stderr_tail else "") return result + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def try_launch_chrome_debug(port: int = DEFAULT_BROWSER_CDP_PORT, system: str | None = None) -> bool: + return launch_chrome_debug(port, system).launched +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/callbacks.py b/hermes_cli/callbacks.py index d7fa16fa2f..5c6bf21f75 100644 --- a/hermes_cli/callbacks.py +++ b/hermes_cli/callbacks.py @@ -92,3 +92,126 @@ def prompt_for_secret(cli, var_name: str, prompt: str, metadata=None) -> dict: _invalidate(cli) cprint(f"\n{_DIM} ⏱ Timeout — secret capture cancelled{_RST}") return _skipped(var_name, "timeout", "Secret setup timed out and was skipped.") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def approval_callback(cli, command: str, description: str) -> str: + """Prompt for dangerous command approval through the TUI. + + Shows a selection UI with choices: once / session / always / deny. + When the command is longer than 70 characters, a "view" option is + included so the user can reveal the full text before deciding. + + Uses cli._approval_lock to serialize concurrent requests (e.g. from + parallel delegation subtasks) so each prompt gets its own turn. + """ + lock = getattr(cli, "_approval_lock", None) + if lock is None: + import threading + cli._approval_lock = threading.Lock() + lock = cli._approval_lock + + with lock: + from cli import CLI_CONFIG + timeout = CLI_CONFIG.get("approvals", {}).get("timeout", 300) + response_queue = queue.Queue() + choices = ["once", "session", "always", "deny"] + if len(command) > 70: + choices.append("view") + + cli._approval_state = { + "command": command, + "description": description, + "choices": choices, + "selected": 0, + "response_queue": response_queue, + } + cli._approval_deadline = _time.monotonic() + timeout + + if hasattr(cli, "_app") and cli._app: + cli._app.invalidate() + + while True: + try: + result = response_queue.get(timeout=1) + cli._approval_state = None + cli._approval_deadline = 0 + if hasattr(cli, "_app") and cli._app: + cli._app.invalidate() + return result + except queue.Empty: + remaining = cli._approval_deadline - _time.monotonic() + if remaining <= 0: + break + if hasattr(cli, "_app") and cli._app: + cli._app.invalidate() + + cli._approval_state = None + cli._approval_deadline = 0 + if hasattr(cli, "_app") and cli._app: + cli._app.invalidate() + cprint(f"\n{_DIM} ⏱ Timeout — denying command{_RST}") + return "timeout" + +def clarify_callback(cli, question, choices, multi_select=False): + """Prompt for clarifying question through the TUI. + + Sets up the interactive selection UI, then blocks until the user + responds. Returns the user's choice or a timeout message. + + When ``multi_select`` is True, shows checkboxes and the user can + select multiple options with Space, confirming with Enter. + """ + from cli import CLI_CONFIG + from tools.clarify_gateway import resolve_clarify_timeout + + # Canonical clarify timeout, shared with the gateway/TUI path. `<= 0` + # means unlimited (never auto-skip mid-think) → a null deadline. + timeout = resolve_clarify_timeout(CLI_CONFIG) + response_queue = queue.Queue() + is_open_ended = not choices + effective_multi = multi_select and not is_open_ended + + cli._clarify_state = { + "question": question, + "choices": choices if not is_open_ended else [], + "selected": 0, + "multi_select": effective_multi, + "selected_indices": set() if effective_multi else None, + "response_queue": response_queue, + } + cli._clarify_deadline = None if timeout <= 0 else _time.monotonic() + timeout + cli._clarify_freetext = is_open_ended + + if hasattr(cli, "_app") and cli._app: + cli._app.invalidate() + + while True: + try: + result = response_queue.get(timeout=1) + cli._clarify_deadline = None + return result + except queue.Empty: + # None deadline = unlimited: never auto-skip, just keep polling. + if cli._clarify_deadline is not None: + remaining = cli._clarify_deadline - _time.monotonic() + if remaining <= 0: + break + if hasattr(cli, "_app") and cli._app: + cli._app.invalidate() + + cli._clarify_state = None + cli._clarify_freetext = False + cli._clarify_deadline = None + if hasattr(cli, "_app") and cli._app: + cli._app.invalidate() + cprint(f"\n{_DIM}(clarify timed out after {timeout}s — agent will decide){_RST}") + return ( + "The user did not provide a response within the time limit. " + "Use your best judgement to make the choice and proceed." + ) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/checkpoints.py b/hermes_cli/checkpoints.py index 72388936da..403a130ed0 100644 --- a/hermes_cli/checkpoints.py +++ b/hermes_cli/checkpoints.py @@ -215,3 +215,24 @@ def register_cli(parser: argparse.ArgumentParser) -> None: p_clear = subs.add_parser(name, help=help_text) p_clear.add_argument("-f", "--force", action="store_true", help="Skip confirmation prompt") p_clear.set_defaults(func=func) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from datetime import datetime # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'cmd_list': ('hermes_cli.plugins_cmd', 'cmd_list'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/commands.py b/hermes_cli/commands.py index 7441606d0f..8dfa0f3716 100644 --- a/hermes_cli/commands.py +++ b/hermes_cli/commands.py @@ -467,3 +467,258 @@ def _iter_plugin_command_entries() -> list[tuple[str, str, str]]: return [(name, str(meta.get("description") or f"Run /{name}"), str(meta.get("args_hint") or "").strip()) for name, meta in commands.items() if isinstance(name, str) and isinstance(meta, dict)] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Any # noqa: F401,E402 +from collections.abc import Callable # noqa: F401,E402 +from typing import Dict # noqa: F401,E402 +from collections.abc import Mapping # noqa: F401,E402 +from typing import Optional # noqa: F401,E402 +from collections.abc import Sequence # noqa: F401,E402 +from typing import Tuple # noqa: F401,E402 +from dataclasses import field # noqa: F401,E402 +import os # noqa: F401,E402 +import shutil # noqa: F401,E402 +import subprocess # noqa: F401,E402 +import time # noqa: F401,E402 + +def _requires_argument(args_hint: str) -> bool: + """Return True when selecting a command without text would be incomplete.""" + return args_hint.strip().startswith("<") + +_CMD_NAME_LIMIT = 32 + +def _clamp_command_names( + entries: Sequence[tuple[str, ...]], + reserved: set[str], +) -> list[tuple[str, ...]]: + """Enforce 32-char command name limit with collision avoidance. + + Both Telegram and Discord cap slash command names at 32 characters. + Names exceeding the limit are truncated. If truncation creates a duplicate + (against *reserved* names or earlier entries in the same batch), the name is + shortened to 31 chars and a digit ``0``-``9`` is appended to differentiate. + If all 10 digit slots are taken the entry is silently dropped. + + Accepts tuples of any length >= 2. Extra elements beyond ``(name, desc)`` + (e.g. ``cmd_key``) are passed through unchanged, so callers can attach + metadata that survives the rename. + """ + used: set[str] = set(reserved) + result: list[tuple] = [] + for entry in entries: + name, desc, *extra = entry + if len(name) > _CMD_NAME_LIMIT: + candidate = name[:_CMD_NAME_LIMIT] + if candidate in used: + prefix = name[:_CMD_NAME_LIMIT - 1] + for digit in range(10): + candidate = f"{prefix}{digit}" + if candidate not in used: + break + else: + # All 10 digit slots exhausted — skip entry + continue + name = candidate + if name in used: + continue + used.add(name) + result.append((name, desc, *extra)) + return result + +def _collect_gateway_skill_entries( + platform: str, + max_slots: int | None, + reserved_names: set[str], + desc_limit: int = 100, + sanitize_name: "Callable[[str], str] | None" = None, +) -> tuple[list[tuple[str, str, str, str]], int]: + """Collect plugin + skill entries for a gateway platform. + + Priority order: + 1. Plugin slash commands (take precedence over skills) + 2. Built-in skill commands (fill remaining slots, alphabetical) + + Only skills are trimmed when the cap is reached. + Hub-installed skills are excluded. Per-platform disabled skills are + excluded. + + Args: + platform: Platform identifier for per-platform skill filtering + (``"telegram"``, ``"discord"``, etc.). + max_slots: Maximum number of entries to return (remaining slots after + built-in/core commands), or ``None`` to return every eligible + plugin and skill candidate for a caller that applies a global cap. + reserved_names: Names already taken by built-in commands. Mutated + in-place as new names are added. + desc_limit: Max description length (40 for Telegram, 100 for Discord). + sanitize_name: Optional name transform applied before clamping, e.g. + :func:`_sanitize_telegram_name` for Telegram. May return an + empty string to signal "skip this entry". + + Returns: + ``(entries, hidden_count)`` where *entries* contains + ``(name, description, cmd_key, raw_name)`` tuples. ``cmd_key`` is the + original skill key (empty for plugins); ``raw_name`` is the sanitized + pre-clamp name used for configured priority matching. + """ + all_entries: list[tuple[str, str, str, str]] = [] + + # --- Tier 1: Plugin slash commands (never trimmed) --------------------- + plugin_pairs: list[tuple[str, str, str]] = [] + try: + from hermes_cli.plugins import get_plugin_commands + plugin_cmds = get_plugin_commands() + for cmd_name in sorted(plugin_cmds): + if platform == "telegram": + args_hint = str(plugin_cmds[cmd_name].get("args_hint") or "").strip() + if _requires_argument(args_hint): + continue + name = sanitize_name(cmd_name) if sanitize_name else cmd_name + if not name: + continue + desc = plugin_cmds[cmd_name].get("description", "Plugin command") + if len(desc) > desc_limit: + desc = desc[:desc_limit - 3] + "..." + plugin_pairs.append((name, desc, name)) + except Exception: + pass + + plugin_pairs = [ + (name, desc, raw_name) + for name, desc, raw_name in _clamp_command_names(plugin_pairs, reserved_names) + ] + reserved_names.update(n for n, _d, _raw_name in plugin_pairs) + # Plugins have no cmd_key — use empty string as placeholder. + for name, desc, raw_name in plugin_pairs: + all_entries.append((name, desc, "", raw_name)) + + # --- Tier 2: Built-in skill commands (trimmed at cap) ----------------- + _platform_disabled: set[str] = set() + try: + from agent.skill_utils import get_disabled_skill_names + _platform_disabled = get_disabled_skill_names(platform=platform) + except Exception: + pass + + skill_entries: list[tuple[str, str, str, str]] = [] + try: + from agent.skill_commands import get_skill_commands + from tools.skills_tool import SKILLS_DIR + from agent.skill_utils import get_external_skills_dirs, get_project_skills_dirs + _skills_dir = str(SKILLS_DIR.resolve()) + _hub_dir = str((SKILLS_DIR / ".hub").resolve()).rstrip("/") + "/" + # Build set of allowed directory prefixes: local skills dir + any + # user-configured ``skills.external_dirs`` + trusted project dirs. + # Ensure each prefix ends + # with ``/`` so ``/my-skills`` does not also match ``/my-skills-extra``. + # Without this widening, external skills are visible in + # ``hermes skills list`` and the agent's ``/skill-name`` dispatch but + # silently excluded from gateway slash menus (#8110). + _allowed_prefixes = [_skills_dir.rstrip("/") + "/"] + _allowed_prefixes.extend( + str(d).rstrip("/") + "/" for d in get_external_skills_dirs() + ) + _allowed_prefixes.extend( + str(d).rstrip("/") + "/" for d in get_project_skills_dirs() + ) + skill_cmds = get_skill_commands() + for cmd_key in sorted(skill_cmds): + info = skill_cmds[cmd_key] + skill_path = info.get("skill_md_path", "") + if not skill_path: + continue + if not any(skill_path.startswith(prefix) for prefix in _allowed_prefixes): + continue + if skill_path.startswith(_hub_dir): + continue + skill_name = info.get("name", "") + if skill_name in _platform_disabled: + continue + raw_name = cmd_key.lstrip("/") + name = sanitize_name(raw_name) if sanitize_name else raw_name + if not name: + continue + desc = info.get("description", "") + if len(desc) > desc_limit: + desc = desc[:desc_limit - 3] + "..." + skill_entries.append((name, desc, cmd_key, name)) + except Exception: + pass + + # Clamp names; cmd_key and raw_name survive any clamp-induced rename. + skill_entries = [ + (name, desc, cmd_key, raw_name) + for name, desc, cmd_key, raw_name in _clamp_command_names( + skill_entries, reserved_names + ) + ] + + if max_slots is None: + return all_entries + skill_entries, 0 + + # Skills fill remaining slots — only tier that gets trimmed + remaining = max(0, max_slots - len(all_entries)) + hidden_count = max(0, len(skill_entries) - remaining) + for name, desc, cmd_key, raw_name in skill_entries[:remaining]: + all_entries.append((name, desc, cmd_key, raw_name)) + + return all_entries[:max_slots], hidden_count + +def discord_skill_commands( + max_slots: int, + reserved_names: set[str], +) -> tuple[list[tuple[str, str, str]], int]: + """Return skill entries for Discord slash command registration. + + Same priority and filtering logic as :func:`telegram_menu_commands` + (plugins > skills, hub excluded, per-platform disabled excluded), but + adapted for Discord's constraints: + + - Hyphens are allowed in names (no ``-`` → ``_`` sanitization) + - Descriptions capped at 100 chars (Discord's per-field max) + + Args: + max_slots: Available command slots (100 minus existing built-in count). + reserved_names: Names of already-registered built-in commands. + + Returns: + ``(entries, hidden_count)`` where *entries* is a list of + ``(discord_name, description, cmd_key)`` triples. ``cmd_key`` is + the original ``/skill-name`` key needed for the slash handler callback. + """ + entries, hidden_count = _collect_gateway_skill_entries( + platform="discord", + max_slots=max_slots, + reserved_names=set(reserved_names), # copy — don't mutate caller's set + desc_limit=100, + ) + return [ + (name, desc, cmd_key) for name, desc, cmd_key, _raw_name in entries + ], hidden_count + + +_PLUGIN_COMPAT_LAZY = { + 'SlashCommandAutoSuggest': ('hermes_cli.commands_completion', 'SlashCommandAutoSuggest'), + 'SlashCommandCompleter': ('hermes_cli.commands_completion', 'SlashCommandCompleter'), + 'discord_skill_commands_by_category': ('hermes_cli.commands_platforms', 'discord_skill_commands_by_category'), + 'slack_app_manifest': ('hermes_cli.commands_platforms', 'slack_app_manifest'), + 'slack_native_slashes': ('hermes_cli.commands_platforms', 'slack_native_slashes'), + 'slack_subcommand_map': ('hermes_cli.commands_platforms', 'slack_subcommand_map'), + 'telegram_bot_commands': ('hermes_cli.commands_platforms', 'telegram_bot_commands'), + 'telegram_menu_commands': ('hermes_cli.commands_platforms', 'telegram_menu_commands'), + 'telegram_menu_max_commands': ('hermes_cli.commands_platforms', 'telegram_menu_max_commands'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 78848329ac..21c780b9ea 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -3854,3 +3854,56 @@ def _inject_platform_plugin_env_vars() -> None: _inject_platform_plugin_env_vars() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def _install_method_project_root(project_root: Optional[Path] = None) -> Path: + """Resolve the directory that holds the *running code* (the install tree). + + This is the parent of ``hermes_cli/`` — i.e. the git checkout for source + installs, ``/opt/hermes`` inside the published image. It is a property of + the running interpreter, NOT of ``$HERMES_HOME``, which is why a + code-scoped stamp here is immune to two installs sharing one data + directory. + """ + if project_root is not None: + return project_root + return Path(__file__).parent.parent.resolve() + +def stamp_install_method(method: str, project_root: Optional[Path] = None) -> None: + """Write the install method next to the running code (code-scoped stamp). + + The stamp lives in the install tree (``/.install_method``), + not in ``$HERMES_HOME``, so that two installs sharing one data directory + do not overwrite each other's marker. See ``detect_install_method`` for + the full rationale. + + Best-effort: if the install tree is read-only (e.g. the immutable + ``/opt/hermes`` in the published image, which instead bakes the stamp at + build time) the write silently no-ops and detection falls back to its + other signals. + """ + root = _install_method_project_root(project_root) + try: + root.mkdir(parents=True, exist_ok=True) + (root / ".install_method").write_text(method + "\n", encoding="utf-8") + except OSError: + pass + + +_PLUGIN_COMPAT_LAZY = { + 'normalize_route_base_url': ('hermes_cli.route_identity', 'normalize_route_base_url'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/console_engine.py b/hermes_cli/console_engine.py index 30ae31e606..db658435bb 100644 --- a/hermes_cli/console_engine.py +++ b/hermes_cli/console_engine.py @@ -853,3 +853,11 @@ def run_console_repl( print(result.output, file=stderr if result.status == "error" else stdout) if result.status == "exit": return 0 + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import shlex # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/curses_ui.py b/hermes_cli/curses_ui.py index 2ab74b8086..4c23c8d6e3 100644 --- a/hermes_cli/curses_ui.py +++ b/hermes_cli/curses_ui.py @@ -769,3 +769,11 @@ def _numbered_fallback( if 0 <= idx < len(items): chosen.symmetric_difference_update({idx}) print() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Protocol # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/dashboard_auth/audit.py b/hermes_cli/dashboard_auth/audit.py index 7ebe401f53..101dfa40e4 100644 --- a/hermes_cli/dashboard_auth/audit.py +++ b/hermes_cli/dashboard_auth/audit.py @@ -63,3 +63,11 @@ def audit_log(event: AuditEvent, **fields: Any) -> None: f.write(line) except Exception as e: _log.warning("dashboard-auth audit log write failed: %s", e) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/dashboard_auth/middleware.py b/hermes_cli/dashboard_auth/middleware.py index 80f63a61bb..a48a36008a 100644 --- a/hermes_cli/dashboard_auth/middleware.py +++ b/hermes_cli/dashboard_auth/middleware.py @@ -225,3 +225,23 @@ def _attempt_refresh(request: Request, *, refresh_token, provider_hint: str | No provider_hint, _refresh, phase="refresh", log=_log, swallow=(RefreshExpiredError,), on_swallow=_audit_failure("refresh_expired"), on_unreachable=_audit_failure("provider_unreachable")) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'DashboardAuthProvider': ('hermes_cli.dashboard_auth.base', 'DashboardAuthProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/dingtalk_auth.py b/hermes_cli/dingtalk_auth.py index 0c9cd1f9e3..0de0a58c71 100644 --- a/hermes_cli/dingtalk_auth.py +++ b/hermes_cli/dingtalk_auth.py @@ -179,3 +179,24 @@ def dingtalk_qr_auth() -> Optional[Tuple[str, str]]: print_success(f" Client ID: {client_id}") print_success(f" Client Secret: {client_secret[:8]}{'*' * (len(client_secret) - 8)}") return client_id, client_secret + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import logging # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'logger': ('hermes_cli.auth', 'logger'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/doctor.py b/hermes_cli/doctor.py index 6057b2b034..ffc61b13f0 100644 --- a/hermes_cli/doctor.py +++ b/hermes_cli/doctor.py @@ -180,3 +180,53 @@ def run_doctor(args): from hermes_cli.doctor_live import maybe_run_live_checks maybe_run_live_checks(args, total.manual_issues) _print_summary(should_fix, total) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import Path # noqa: F401,E402 +import importlib.util # noqa: F401,E402 +import shutil # noqa: F401,E402 +import subprocess # noqa: F401,E402 + +def check_fail(text: str, detail: str = ""): + print(f" {color('✗', Colors.RED)} {text}" + (f" {color(detail, Colors.DIM)}" if detail else "")) + +def check_ok(text: str, detail: str = ""): + print(f" {color('✓', Colors.GREEN)} {text}" + (f" {color(detail, Colors.DIM)}" if detail else "")) + +def check_warn(text: str, detail: str = ""): + print(f" {color('⚠', Colors.YELLOW)} {text}" + (f" {color(detail, Colors.DIM)}" if detail else "")) + + +_PLUGIN_COMPAT_LAZY = { + 'FTS_STORAGE_VERSION': ('hermes_state_common', 'FTS_STORAGE_VERSION'), + 'OPENROUTER_MODELS_URL': ('hermes_constants', 'OPENROUTER_MODELS_URL'), + 'STATE_DB_SIZE_WARN_BYTES': ('hermes_cli.doctor_state', 'STATE_DB_SIZE_WARN_BYTES'), + 'agent_browser_runnable': ('hermes_constants', 'agent_browser_runnable'), + 'base_url_host_matches': ('utils', 'base_url_host_matches'), + 'check_certificates': ('hermes_cli.doctor_platform', 'check_certificates'), + 'check_macos_full_disk_access': ('hermes_cli.doctor_platform', 'check_macos_full_disk_access'), + 'check_macos_tcc_anchor': ('hermes_cli.doctor_platform', 'check_macos_tcc_anchor'), + 'check_macos_tcc_grants': ('hermes_cli.doctor_platform', 'check_macos_tcc_grants'), + 'collect_deprecated_config_keys': ('hermes_cli.doctor_config', 'collect_deprecated_config_keys'), + 'collect_deprecated_env_vars': ('hermes_cli.doctor_config', 'collect_deprecated_env_vars'), + 'collect_relay_plugin_cutover_findings': ('hermes_cli.doctor_config', 'collect_relay_plugin_cutover_findings'), + 'describe_vercel_auth': ('hermes_cli.vercel_auth', 'describe_vercel_auth'), + 'detect_install_method': ('hermes_cli.config', 'detect_install_method'), + 'is_nix_install_method': ('hermes_cli.config', 'is_nix_install_method'), + 'managed_scope_check': ('hermes_cli.doctor_config', 'managed_scope_check'), + 'recommended_update_command_for_method': ('hermes_cli.config', 'recommended_update_command_for_method'), + 'report_deprecated_config_and_env': ('hermes_cli.doctor_config', 'report_deprecated_config_and_env'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/doctor_live.py b/hermes_cli/doctor_live.py index d42bf54cab..08057cf1a7 100644 --- a/hermes_cli/doctor_live.py +++ b/hermes_cli/doctor_live.py @@ -213,3 +213,20 @@ def maybe_run_live_checks(args, issues: List[str]): except Exception as exc: # catch-all: doctor must survive check_warn("Live backend probes crashed", f"({exc})") return None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +ELEVENLABS_VOICES_URL = "https://api.elevenlabs.io/v1/voices" + +FAL_MODELS_URL = "https://fal.ai/api/models?page=1" + +FIRECRAWL_HEALTH_URL = "https://api.firecrawl.dev/v2/team/credit-usage" + +GROQ_MODELS_URL = "https://api.groq.com/openai/v1/models" + +OPENAI_MODELS_URL = "https://api.openai.com/v1/models" +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/focus_view.py b/hermes_cli/focus_view.py index 6bcd6201b3..0f7799ac55 100644 --- a/hermes_cli/focus_view.py +++ b/hermes_cli/focus_view.py @@ -80,3 +80,25 @@ def format_focus_toggle_message(enabled: bool, configured_mode: object) -> str: if enabled: return "Focus view enabled — just your prompt and the final response" return f"Focus view disabled — tool progress: {normalize_tool_progress_mode(configured_mode).upper()}" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +FOCUS_USAGE = "Usage: /focus [on|off|status]" + +def effective_tool_progress_mode(focus_enabled: bool, configured_mode: object) -> str: + """Return the tool-progress mode that should actually be in force. + + Focus view wins while it is on (it *is* "tool progress off" plus reporting). + When focus is off the user's configured mode is returned untouched — this is + what makes ``/focus off`` restore ``/verbose verbose`` rather than clobbering + it to ``all``. + """ + normalized = normalize_tool_progress_mode(configured_mode) + if focus_enabled: + return FOCUS_TOOL_PROGRESS_MODE + return normalized +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/foreign_sessions.py b/hermes_cli/foreign_sessions.py index 696650417e..e34fca7505 100644 --- a/hermes_cli/foreign_sessions.py +++ b/hermes_cli/foreign_sessions.py @@ -293,3 +293,66 @@ def run_sessions_import(args, db=None) -> Optional[str]: print(f"✓ Imported {_SOURCE_LABELS.get(source, source)} session as {session_id}") print(f" Continue it with: hermes --resume {session_id}") return session_id + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def list_claude_sessions(root: Optional[Path] = None) -> List[ForeignSession]: + """Discover Claude Code sessions under ``~/.claude/projects``.""" + root = Path(root) if root else Path.home() / ".claude" / "projects" + results: List[ForeignSession] = [] + if not root.is_dir(): + return results + for jsonl in sorted(root.glob("*/*.jsonl")): + try: + mtime = jsonl.stat().st_mtime + except OSError: + continue + parsed = parse_claude_session(jsonl) + if not parsed["turns"]: + continue + results.append( + ForeignSession( + source="claude", + path=jsonl, + mtime=mtime, + cwd=parsed["cwd"], + title_guess=parsed["title_guess"], + turn_count=len(parsed["turns"]), + session_id=parsed["session_id"], + ) + ) + results.sort(key=lambda s: s.mtime, reverse=True) + return results + +def list_codex_sessions(root: Optional[Path] = None) -> List[ForeignSession]: + """Discover Codex CLI rollouts under ``~/.codex/sessions``.""" + root = Path(root) if root else Path.home() / ".codex" / "sessions" + results: List[ForeignSession] = [] + if not root.is_dir(): + return results + for jsonl in sorted(root.rglob("rollout-*.jsonl")): + try: + mtime = jsonl.stat().st_mtime + except OSError: + continue + parsed = parse_codex_session(jsonl) + if not parsed["turns"]: + continue + results.append( + ForeignSession( + source="codex", + path=jsonl, + mtime=mtime, + cwd=parsed["cwd"], + title_guess=parsed["title_guess"], + turn_count=len(parsed["turns"]), + session_id=parsed["session_id"], + ) + ) + results.sort(key=lambda s: s.mtime, reverse=True) + return results +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index 66a8f9aba4..a098154ff3 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -6159,3 +6159,36 @@ def _gateway_command_inner(args): handler = _GATEWAY_SUBCOMMANDS.get(getattr(args, "gateway_command", None)) if handler is not None: handler(args) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def print_systemd_linger_guidance() -> None: + """Print the current linger status and the fix when it is disabled.""" + linger_enabled, linger_detail = get_systemd_linger_status() + if linger_enabled is True: + print("✓ Systemd linger is enabled (service survives logout)") + elif linger_enabled is False: + print("⚠ Systemd linger is disabled (gateway may stop when you log out)") + print(" Run: sudo loginctl enable-linger $USER") + else: + print(f"⚠ Could not verify systemd linger ({linger_detail})") + print(" If you want the gateway user service to survive logout, run:") + print(" sudo loginctl enable-linger $USER") + + +_PLUGIN_COMPAT_LAZY = { + 'DEFAULT_GATEWAY_RESTART_AFTER_TURN_TIMEOUT': ('gateway.restart', 'DEFAULT_GATEWAY_RESTART_AFTER_TURN_TIMEOUT'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/gitlock.py b/hermes_cli/gitlock.py index 850cbfdd12..3f714ad834 100644 --- a/hermes_cli/gitlock.py +++ b/hermes_cli/gitlock.py @@ -96,3 +96,33 @@ def clear_stale_tmp_packs(repo_root: Path, *, min_age_seconds: Optional[int] = N skip_msg="git process running; skipping tmp-pack sweep", log_removed=lambda p, size: logger.info("Removed aborted-fetch pack debris %s (%d bytes)", p, size), ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def is_ancestor_of_head(repo_root: Path, rev: str) -> bool: + """True when ``rev`` is an ancestor of (or equal to) HEAD. + + Wraps ``git merge-base --is-ancestor HEAD``. This is the correct + question for update checks: a local cherry-pick on top of the remote tip + makes HEAD *different* from ``origin/main`` but still *contains* it, so + the answer to "is there an update?" is no. + + Returns False on any probe failure (missing rev, shallow boundary, git + error) — callers treat that as "can't prove contained", which is the + conservative direction for an update check. + """ + try: + result = subprocess.run( + ["git", "merge-base", "--is-ancestor", rev, "HEAD"], + cwd=str(repo_root), + capture_output=True, text=True, timeout=10, + ) + return result.returncode == 0 + except Exception: + logger.debug("merge-base --is-ancestor probe failed for %s", rev, exc_info=True) + return False +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/heartbeat.py b/hermes_cli/heartbeat.py index a2aa3eb457..ed5dddbace 100644 --- a/hermes_cli/heartbeat.py +++ b/hermes_cli/heartbeat.py @@ -236,3 +236,11 @@ __all__ = [ "HeartbeatState", "HeartbeatManager", "parse_interval", "format_interval", "load_heartbeat", "save_heartbeat", "migrate_heartbeat_to_session", "HEARTBEAT_PROMPT_TEMPLATE", "MIN_INTERVAL_SECONDS", "POLL_SECONDS", ] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/journey.py b/hermes_cli/journey.py index f6d8df1a3f..0fba1c1efd 100644 --- a/hermes_cli/journey.py +++ b/hermes_cli/journey.py @@ -378,3 +378,13 @@ if __name__ == "__main__": register_cli(_p) _a = _p.parse_args() sys.exit(_a.func(_a)) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def cmd_journey(args: argparse.Namespace) -> int: + return _cmd_show(args) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/kanban.py b/hermes_cli/kanban.py index d1734d8a65..a4e76bd2fa 100644 --- a/hermes_cli/kanban.py +++ b/hermes_cli/kanban.py @@ -1322,3 +1322,11 @@ def run_slash(rest: str) -> str: if err and out: return f"{out}\n{err}" return err if err else (out or "(no output)") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Any # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/kanban_db.py b/hermes_cli/kanban_db.py index ca99bd4a0e..ea33f297cf 100644 --- a/hermes_cli/kanban_db.py +++ b/hermes_cli/kanban_db.py @@ -4195,3 +4195,86 @@ from hermes_cli.kanban_db_dispatch import ( # noqa: E402 _worker_survived_termination, _worker_terminal_timeout_env, ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Mapping # noqa: F401,E402 +from dataclasses import field # noqa: F401,E402 +import hashlib # noqa: F401,E402 +import random # noqa: F401,E402 +import shutil # noqa: F401,E402 +import threading # noqa: F401,E402 + +DEFAULT_SPAWN_FAILURE_LIMIT = DEFAULT_FAILURE_LIMIT + +def parent_results(conn: sqlite3.Connection, task_id: str) -> list[tuple[str, Optional[str]]]: + """Return ``(parent_id, result)`` for every done parent of ``task_id``.""" + rows = conn.execute( + """ + SELECT t.id AS id, t.result AS result + FROM tasks t + JOIN task_links l ON l.parent_id = t.id + WHERE l.child_id = ? AND t.status = 'done' + ORDER BY t.completed_at ASC + """, + (task_id,), + ).fetchall() + return [(r["id"], r["result"]) for r in rows] + + +_PLUGIN_COMPAT_LAZY = { + 'DEFAULT_BUSY_TIMEOUT_MS': ('hermes_cli.kanban_db_connect', 'DEFAULT_BUSY_TIMEOUT_MS'), + 'DEFAULT_LOG_BACKUP_COUNT': ('hermes_cli.kanban_db_dispatch', 'DEFAULT_LOG_BACKUP_COUNT'), + 'DEFAULT_LOG_ROTATE_BYTES': ('hermes_cli.kanban_db_dispatch', 'DEFAULT_LOG_ROTATE_BYTES'), + 'DERIVED_MAX_IN_PROGRESS_CEILING': ('hermes_cli.kanban_db_dispatch', 'DERIVED_MAX_IN_PROGRESS_CEILING'), + 'DERIVED_MAX_IN_PROGRESS_FLOOR': ('hermes_cli.kanban_db_dispatch', 'DERIVED_MAX_IN_PROGRESS_FLOOR'), + 'KANBAN_TERMINAL_TIMEOUT_GRACE_SECONDS': ('hermes_cli.kanban_db_dispatch', 'KANBAN_TERMINAL_TIMEOUT_GRACE_SECONDS'), + 'KanbanDbCorruptError': ('hermes_cli.kanban_db_connect', 'KanbanDbCorruptError'), + 'MEMORY_GUARD_MB_PER_WORKER': ('hermes_cli.kanban_db_dispatch', 'MEMORY_GUARD_MB_PER_WORKER'), + 'RepairResult': ('hermes_cli.kanban_db_connect', 'RepairResult'), + 'add_notify_sub': ('hermes_cli.kanban_db_notify', 'add_notify_sub'), + 'advance_notify_cursor': ('hermes_cli.kanban_db_notify', 'advance_notify_cursor'), + 'check_respawn_guard': ('hermes_cli.kanban_db_dispatch', 'check_respawn_guard'), + 'claim_unseen_events_for_sub': ('hermes_cli.kanban_db_notify', 'claim_unseen_events_for_sub'), + 'configured_max_in_progress': ('hermes_cli.kanban_db_dispatch', 'configured_max_in_progress'), + 'connect': ('hermes_cli.projects_db', 'connect'), + 'connect_closing': ('hermes_cli.projects_db', 'connect_closing'), + 'count_notify_subs': ('hermes_cli.kanban_db_notify', 'count_notify_subs'), + 'count_running_tasks': ('hermes_cli.kanban_db_dispatch', 'count_running_tasks'), + 'count_running_tasks_other_boards': ('hermes_cli.kanban_db_dispatch', 'count_running_tasks_other_boards'), + 'derive_default_max_in_progress': ('hermes_cli.kanban_db_dispatch', 'derive_default_max_in_progress'), + 'detect_crashed_workers': ('hermes_cli.kanban_db_dispatch', 'detect_crashed_workers'), + 'detect_stale_running': ('hermes_cli.kanban_db_dispatch', 'detect_stale_running'), + 'dispatch_once': ('hermes_cli.kanban_db_dispatch', 'dispatch_once'), + 'enforce_max_runtime': ('hermes_cli.kanban_db_dispatch', 'enforce_max_runtime'), + 'has_spawnable_ready': ('hermes_cli.kanban_db_dispatch', 'has_spawnable_ready'), + 'has_spawnable_review': ('hermes_cli.kanban_db_dispatch', 'has_spawnable_review'), + 'heartbeat_worker': ('hermes_cli.kanban_db_dispatch', 'heartbeat_worker'), + 'list_notify_subs': ('hermes_cli.kanban_db_notify', 'list_notify_subs'), + 'purge_stale_done_notify_subs': ('hermes_cli.kanban_db_notify', 'purge_stale_done_notify_subs'), + 'reap_worker_zombies': ('hermes_cli.kanban_db_dispatch', 'reap_worker_zombies'), + 'reconcile_orphaned_running': ('hermes_cli.kanban_db_dispatch', 'reconcile_orphaned_running'), + 'remove_notify_sub': ('hermes_cli.kanban_db_notify', 'remove_notify_sub'), + 'repair_db': ('hermes_cli.kanban_db_connect', 'repair_db'), + 'resolve_max_in_progress': ('hermes_cli.kanban_db_dispatch', 'resolve_max_in_progress'), + 'resolve_workspace': ('hermes_cli.kanban_db_workspace', 'resolve_workspace'), + 'review_dispatch_enabled': ('hermes_cli.kanban_db_dispatch', 'review_dispatch_enabled'), + 'rewind_notify_cursor': ('hermes_cli.kanban_db_notify', 'rewind_notify_cursor'), + 'run_daemon': ('hermes_cli.kanban_db_dispatch', 'run_daemon'), + 'set_branch_name': ('hermes_cli.kanban_db_workspace', 'set_branch_name'), + 'set_workspace_path': ('hermes_cli.kanban_db_workspace', 'set_workspace_path'), + 'unseen_events_for_sub': ('hermes_cli.kanban_db_notify', 'unseen_events_for_sub'), + 'worker_log_rotation_config': ('hermes_cli.kanban_db_dispatch', 'worker_log_rotation_config'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/kanban_decompose.py b/hermes_cli/kanban_decompose.py index ab11cf6dbd..d2a93ecacf 100644 --- a/hermes_cli/kanban_decompose.py +++ b/hermes_cli/kanban_decompose.py @@ -336,3 +336,12 @@ def list_triage_ids(*, tenant: Optional[str] = None) -> list[str]: with kbc.connect_closing() as conn: rows = kb.list_tasks(conn, status="triage", tenant=tenant, limit=1000) return [row.id for row in rows] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +import os # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/kanban_diagnostics.py b/hermes_cli/kanban_diagnostics.py index 577339acb1..38ec59b440 100644 --- a/hermes_cli/kanban_diagnostics.py +++ b/hermes_cli/kanban_diagnostics.py @@ -773,3 +773,22 @@ def compute_task_diagnostics( severity_idx = {s: i for i, s in enumerate(SEVERITY_ORDER)} out.sort(key=lambda d: (-severity_idx.get(d.severity, -1), -(d.last_seen_at or 0))) return out + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +DIAGNOSTIC_KINDS = ( + "hallucinated_cards", + "triage_aux_unavailable", + "prose_phantom_refs", + "repeated_failures", + "repeated_crashes", + "review_dependency_deadlock", + "stuck_in_blocked", + "block_unblock_cycling", + "stranded_in_ready", +) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/local_runtime/binaries.py b/hermes_cli/local_runtime/binaries.py index 8c9257bea2..3ee4ef9f13 100644 --- a/hermes_cli/local_runtime/binaries.py +++ b/hermes_cli/local_runtime/binaries.py @@ -299,3 +299,23 @@ def ensure_runtime_installed(tag: str, backend: str, "verified_version": version}, indent=2), encoding="utf-8") logger.info("installed llama.cpp %s (%s): %s", tag, backend, version) return install_dir + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'get_hermes_home': ('hermes_constants', 'get_hermes_home'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/local_runtime/bootstrap.py b/hermes_cli/local_runtime/bootstrap.py index 105dcd29b2..280fdcbfa4 100644 --- a/hermes_cli/local_runtime/bootstrap.py +++ b/hermes_cli/local_runtime/bootstrap.py @@ -281,3 +281,23 @@ def _start_idle_sweeper(sup) -> None: logger.debug("idle sweep skipped: %s", exc) threading.Thread(target=_loop, daemon=True, name="local-runtime-idle-sweep").start() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'get_hermes_home': ('hermes_constants', 'get_hermes_home'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/local_runtime/capabilities.py b/hermes_cli/local_runtime/capabilities.py index f85a8b3253..ccb918483e 100644 --- a/hermes_cli/local_runtime/capabilities.py +++ b/hermes_cli/local_runtime/capabilities.py @@ -77,3 +77,12 @@ def managed_model_supports_vision(model_id: str) -> "bool | None": return (entry.mmproj is not None and (assets_dir() / entry.mmproj.local_name).exists()) return None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +import urllib.request # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/local_runtime/catalog.py b/hermes_cli/local_runtime/catalog.py index b13373fd6a..e73a336bd9 100644 --- a/hermes_cli/local_runtime/catalog.py +++ b/hermes_cli/local_runtime/catalog.py @@ -330,3 +330,22 @@ def find_entry_for_model(model_id: str) -> "tuple[CatalogEntry, QuantVariant] | def entry_for_model(model_id: str) -> "CatalogEntry | None": hit = find_entry_for_model(model_id) return hit[0] if hit is not None else None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import re # noqa: F401,E402 + +def find_variant(entry_id: str, model_id: str) -> QuantVariant | None: + entry = catalog_by_id().get(entry_id) + if entry is None: + return None + return next((v for v in entry.variants if v.model_id == model_id), None) + +def recommended_id(budget: HardwareBudget, + entries: "tuple[CatalogEntry, ...] | None" = None) -> str | None: + picked = recommended_entry(budget, entries) + return picked[0].id if picked is not None else None +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/local_runtime/hf_browse.py b/hermes_cli/local_runtime/hf_browse.py index 2289871e2e..fb8776c03c 100644 --- a/hermes_cli/local_runtime/hf_browse.py +++ b/hermes_cli/local_runtime/hf_browse.py @@ -132,3 +132,11 @@ def rough_fit(total_bytes: int, budget) -> str: def priced_repo_files(repo: str, budget) -> list[HFFileGroup]: return [replace(g, fit=rough_fit(g.total_bytes, budget)) for g in repo_files(repo)] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from dataclasses import field # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/main.py b/hermes_cli/main.py index f624da99bf..813b5235fc 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -3403,3 +3403,29 @@ def main(): if __name__ == "__main__": main() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import hashlib # noqa: F401,E402 +import shlex # noqa: F401,E402 +import stat # noqa: F401,E402 +import tempfile # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'line_input': ('hermes_cli.cli_output', 'line_input'), +} + +_plugin_compat_prev_getattr = __getattr__ + + +def __getattr__(name): # PEP 562 — chained onto the module's own __getattr__ + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + return _plugin_compat_prev_getattr(name) + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/mcp_picker.py b/hermes_cli/mcp_picker.py index ea957a022d..24619a3d82 100644 --- a/hermes_cli/mcp_picker.py +++ b/hermes_cli/mcp_picker.py @@ -218,3 +218,23 @@ def install_by_name(identifier: str) -> int: ) return 1 return 0 if _install(entry, "install") else 1 + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'color': ('hermes_cli.colors', 'color'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/mcp_security.py b/hermes_cli/mcp_security.py index 8365ea42e3..38bd405921 100644 --- a/hermes_cli/mcp_security.py +++ b/hermes_cli/mcp_security.py @@ -133,3 +133,13 @@ def validate_mcp_server_entry(name: str, entry: dict[str, Any]) -> list[str]: f"MCP server" ) return issues + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def is_mcp_server_entry_suspicious(name: str, entry: dict[str, Any]) -> bool: + return bool(validate_mcp_server_entry(name, entry)) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/middleware.py b/hermes_cli/middleware.py index 7f791aa93b..897e4afc07 100644 --- a/hermes_cli/middleware.py +++ b/hermes_cli/middleware.py @@ -211,3 +211,29 @@ def _run_execution_chain(kind: str, terminal_call: Callable[[Any], Any], **kwarg return call_at(index + 1, payload) return call_at(0, kwargs[payload_key]) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +API_EXECUTION_MIDDLEWARE = LLM_EXECUTION_MIDDLEWARE + +API_REQUEST_MIDDLEWARE = LLM_REQUEST_MIDDLEWARE + +def apply_api_request_middleware( + request: Dict[str, Any], + **context: Any, +) -> RequestMiddlewareResult: + """Compatibility wrapper for older ``api_request`` naming.""" + return apply_llm_request_middleware(request, **context) + +def run_api_execution_middleware( + request: Dict[str, Any], + next_call: Callable[[Dict[str, Any]], Any], + **context: Any, +) -> Any: + """Compatibility wrapper for older ``api_execution`` naming.""" + return run_llm_execution_middleware(request, next_call, **context) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/moa_config.py b/hermes_cli/moa_config.py index 0e35a866d0..4749ef0eac 100644 --- a/hermes_cli/moa_config.py +++ b/hermes_cli/moa_config.py @@ -329,3 +329,37 @@ def decode_moa_turn(message: Any) -> tuple[str, dict[str, Any] | None]: def moa_usage() -> str: return "Usage: /moa (runs one prompt through the default MoA preset, then restores your model; pick a preset from the model picker to switch for the session)" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def encode_moa_turn(prompt: str, config: Any = None, preset: str | None = None) -> str: + """Encode a /moa one-shot turn for frontends that can only send text.""" + payload = { + "prompt": str(prompt or ""), + "config": resolve_moa_preset(config or {}, preset), + } + encoded = base64.urlsafe_b64encode( + json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8") + ).decode("ascii") + return f"{MOA_MARKER_PREFIX}{encoded}" + +def build_moa_turn_prompt(user_prompt: str, config: Any = None, preset: str | None = None) -> str: + """Build the hidden one-shot payload used by TUI/gateway routing.""" + return encode_moa_turn(user_prompt, config, preset=preset) + +def list_moa_presets(config: Any) -> list[str]: + cfg = normalize_moa_config(config) + return list(cfg["presets"].keys()) + +def set_active_moa_preset(config: Any, name: str | None) -> dict[str, Any]: + cfg = normalize_moa_config(config) + clean = str(name or "").strip() + if clean and clean not in cfg["presets"]: + raise KeyError(clean) + cfg["active_preset"] = clean + return cfg +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/model_setup_flows.py b/hermes_cli/model_setup_flows.py index 00df4ce120..d046cd41a8 100644 --- a/hermes_cli/model_setup_flows.py +++ b/hermes_cli/model_setup_flows.py @@ -1047,3 +1047,29 @@ def _model_flow_anthropic(config, current_model=""): # Clear base_url: resolve_runtime_provider() always hardcodes Anthropic's URL, and a # stale value can contaminate other providers on a later switch. _finish_model(selected, "anthropic", f"Default model set to: {selected} (via Anthropic)", drop_base_url=True, drop_api_mode=True) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import subprocess # noqa: F401,E402 +import urllib.parse # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'BEDROCK_GEO_PREFIXES': ('hermes_cli.model_setup_flows_bedrock', 'BEDROCK_GEO_PREFIXES'), + 'bedrock_model_routable_from_region': ('hermes_cli.model_setup_flows_bedrock', 'bedrock_model_routable_from_region'), + 'bedrock_region_geo_prefix': ('hermes_cli.model_setup_flows_bedrock', 'bedrock_region_geo_prefix'), + 'custom_provider_slug': ('hermes_cli.providers', 'custom_provider_slug'), + 'line_input': ('hermes_cli.cli_output', 'line_input'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index 6649653f60..02f2939a13 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -1494,3 +1494,29 @@ def _scoped_key_env(name: str) -> str: return (get_secret(name, "") or "").strip() if name else "" except Exception: return "" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import List # noqa: F401,E402 +import http.client # noqa: F401,E402 +import time # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'base_url_host_matches': ('utils', 'base_url_host_matches'), + 'custom_provider_slug': ('hermes_cli.providers', 'custom_provider_slug'), + 'list_picker_providers': ('hermes_cli.model_switch_providers', 'list_picker_providers'), + 'prewarm_picker_cache_async': ('hermes_cli.model_switch_providers', 'prewarm_picker_cache_async'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/models.py b/hermes_cli/models.py index 541d23f1e8..c43a4758b7 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -2372,3 +2372,101 @@ def cached_fetch_api_models( if _cache_entry_valid(entry, fp): return list(entry["models"]) return live + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import NamedTuple # noqa: F401,E402 +from difflib import get_close_matches # noqa: F401,E402 +import http.client # noqa: F401,E402 + +def is_nous_free_tier(account_info: dict[str, Any]) -> bool: + """Return True if the account info indicates a free (unpaid) tier. + + Prefer the Portal's explicit ``paid_service_access.allowed`` entitlement + decision. Legacy payloads fall back to ``subscription.monthly_charge == 0``. + Returns False when both signals are missing or unparseable. + """ + paid_access = account_info.get("paid_service_access") + if isinstance(paid_access, dict): + allowed = paid_access.get("allowed") + if isinstance(allowed, bool): + return not allowed + paid = paid_access.get("paid_access") + if isinstance(paid, bool): + return not paid + + sub = account_info.get("subscription") + if not isinstance(sub, dict): + return False + charge = sub.get("monthly_charge") + if charge is None: + return False + try: + return float(charge) == 0 + except (TypeError, ValueError): + return False + +_OPENCODE_KEYLESS_EXTRA_SLUGS = frozenset({"big-pickle"}) + +def is_opencode_zen_free_model(model_id: Optional[str]) -> bool: + """True when ``model_id`` is an OpenCode Zen free-tier slug. + + Matches the ``*-free`` suffix plus the known unsuffixed free slugs + (``big-pickle``). Tolerates provider-prefixed ids + (``opencode-zen/x-preview-f-free``). The Go catalog serves no free + models (verified 2026-08-21), so this identifies the Zen free tier + across the OpenCode family. + """ + bare = str(model_id or "").strip().rsplit("/", 1)[-1].lower() + if not bare: + return False + return bare.endswith("-free") or bare in _OPENCODE_KEYLESS_EXTRA_SLUGS + + +_PLUGIN_COMPAT_LAZY = { + 'LMStudioLoadResult': ('hermes_cli.models_local', 'LMStudioLoadResult'), + 'PROVIDER_GROUPS': ('hermes_cli.models_catalog_static', 'PROVIDER_GROUPS'), + 'ProviderEntry': ('hermes_cli.models_catalog_static', 'ProviderEntry'), + 'atomic_json_write': ('utils', 'atomic_json_write'), + 'base_url_host_matches': ('utils', 'base_url_host_matches'), + 'compute_sale_discount': ('hermes_cli.models_pricing', 'compute_sale_discount'), + 'ensure_lmstudio_model_loaded': ('hermes_cli.models_local', 'ensure_lmstudio_model_loaded'), + 'fetch_ai_gateway_pricing': ('hermes_cli.models_pricing', 'fetch_ai_gateway_pricing'), + 'fetch_lmstudio_models': ('hermes_cli.models_local', 'fetch_lmstudio_models'), + 'fetch_models_with_pricing': ('hermes_cli.models_pricing', 'fetch_models_with_pricing'), + 'fetch_ollama_local_models': ('hermes_cli.models_local', 'fetch_ollama_local_models'), + 'get_cached_nous_inference_base_url': ('hermes_cli.models_pricing', 'get_cached_nous_inference_base_url'), + 'get_pricing_for_provider': ('hermes_cli.models_pricing', 'get_pricing_for_provider'), + 'group_providers': ('hermes_cli.models_catalog_static', 'group_providers'), + 'lmstudio_model_reasoning_options': ('hermes_cli.models_local', 'lmstudio_model_reasoning_options'), + 'nous_catalog_url': ('hermes_cli.models_reasoning_caps', 'nous_catalog_url'), + 'nous_model_reasoning_capabilities': ('hermes_cli.models_reasoning_caps', 'nous_model_reasoning_capabilities'), + 'nous_policy_allowed_ids': ('hermes_cli.models_pricing', 'nous_policy_allowed_ids'), + 'ollama_model_supports_thinking': ('hermes_cli.models_local', 'ollama_model_supports_thinking'), + 'openrouter_model_reasoning_capabilities': ('hermes_cli.models_reasoning_caps', 'openrouter_model_reasoning_capabilities'), + 'parse_openrouter_reasoning_capabilities': ('hermes_cli.models_reasoning_caps', 'parse_openrouter_reasoning_capabilities'), + 'peek_cached_pricing': ('hermes_cli.models_pricing', 'peek_cached_pricing'), + 'pricing_cache_scope': ('hermes_cli.models_pricing', 'pricing_cache_scope'), + 'probe_lmstudio_models': ('hermes_cli.models_local', 'probe_lmstudio_models'), + 'probe_ollama_local_models': ('hermes_cli.models_local', 'probe_ollama_local_models'), + 'provider_group_for_slug': ('hermes_cli.models_catalog_static', 'provider_group_for_slug'), + 'refresh_reasoning_caps_async': ('hermes_cli.models_reasoning_caps', 'refresh_reasoning_caps_async'), + 'restrict_to_nous_policy': ('hermes_cli.models_pricing', 'restrict_to_nous_policy'), + 'should_use_ollama_native_catalog': ('hermes_cli.models_local', 'should_use_ollama_native_catalog'), + 'url_origin': ('hermes_cli.urllib_security', 'url_origin'), + 'validate_requested_model': ('hermes_cli.models_validate', 'validate_requested_model'), + 'warm_nous_reasoning_caps_async': ('hermes_cli.models_reasoning_caps', 'warm_nous_reasoning_caps_async'), + 'warm_openrouter_reasoning_caps_async': ('hermes_cli.models_reasoning_caps', 'warm_openrouter_reasoning_caps_async'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/nous_billing.py b/hermes_cli/nous_billing.py index ecb37b27b7..a431f72dc2 100644 --- a/hermes_cli/nous_billing.py +++ b/hermes_cli/nous_billing.py @@ -392,3 +392,12 @@ def post_subscription_upgrade( return _post_idempotent( "/api/billing/subscription/upgrade", {"subscriptionTypeId": subscription_type_id}, idempotency_key, "an upgrade", timeout ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +BILLING_MANAGE_SCOPE = "billing:manage" +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/nous_subscription.py b/hermes_cli/nous_subscription.py index f423cb14e2..bff6b08d3c 100644 --- a/hermes_cli/nous_subscription.py +++ b/hermes_cli/nous_subscription.py @@ -723,3 +723,23 @@ def _run_nous_portal_login_only(*, capability: str) -> bool: except Exception as exc: print(f" Nous Portal login failed: {exc}") return False + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'managed_nous_tools_enabled': ('tools.tool_backend_helpers', 'managed_nous_tools_enabled'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/observability/relay_runtime.py b/hermes_cli/observability/relay_runtime.py new file mode 100644 index 0000000000..09810b3499 --- /dev/null +++ b/hermes_cli/observability/relay_runtime.py @@ -0,0 +1,6 @@ +"""PLUGIN-COMPAT stub (revert-scheduled; see COMPAT_MANIFEST.md). + +``hermes_cli.observability.relay_runtime`` was folded into ``agent.relay_runtime`` in the Sep 2026 decomposition. This stub keeps the +old import path alive for external plugins only; internal code must import ``agent.relay_runtime``. +""" +from agent.relay_runtime import * # noqa: F401,F403 diff --git a/hermes_cli/observability/relay_shared_metrics.py b/hermes_cli/observability/relay_shared_metrics.py index 129905743e..e496659a4a 100644 --- a/hermes_cli/observability/relay_shared_metrics.py +++ b/hermes_cli/observability/relay_shared_metrics.py @@ -1046,3 +1046,44 @@ def _reset_for_tests() -> None: for runtime in runtimes: if isinstance(runtime, _Runtime): runtime.shutdown() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def prepare_session_start() -> None: + """Register the subscriber before any producer opens the session scope.""" + if enabled(): + _get_runtime(retry_failed=True) + + +_PLUGIN_COMPAT_LAZY = { + 'CLIENT_ACTIVE_MARK': ('hermes_cli.observability.shared_metrics_contract', 'CLIENT_ACTIVE_MARK'), + 'MODEL_CALL_PROFILE_MODEL': ('hermes_cli.observability.shared_metrics_contract', 'MODEL_CALL_PROFILE_MODEL'), + 'SCHEMA_KEY': ('hermes_cli.observability.shared_metrics_contract', 'SCHEMA_KEY'), + 'SCHEMA_VERSION': ('hermes_cli.observability.shared_metrics_contract', 'SCHEMA_VERSION'), + 'SKILL_LIFECYCLE_MARK': ('hermes_cli.observability.shared_metrics_contract', 'SKILL_LIFECYCLE_MARK'), + 'SKILL_LOAD_MARK': ('hermes_cli.observability.shared_metrics_contract', 'SKILL_LOAD_MARK'), + 'TOOL_APPROVAL_MARK': ('hermes_cli.observability.shared_metrics_contract', 'TOOL_APPROVAL_MARK'), + 'TOOL_CALL_SCOPE': ('hermes_cli.observability.shared_metrics_contract', 'TOOL_CALL_SCOPE'), + 'model_call_fields': ('hermes_cli.observability.shared_metrics_contract', 'model_call_fields'), + 'skill_lifecycle_fields': ('hermes_cli.observability.shared_metrics_contract', 'skill_lifecycle_fields'), + 'skill_load_fields': ('hermes_cli.observability.shared_metrics_contract', 'skill_load_fields'), + 'task_start_fields': ('hermes_cli.observability.shared_metrics_contract', 'task_start_fields'), + 'task_terminal_fields': ('hermes_cli.observability.shared_metrics_contract', 'task_terminal_fields'), + 'task_terminal_state': ('hermes_cli.observability.shared_metrics_contract', 'task_terminal_state'), + 'tool_approval_outcome': ('hermes_cli.observability.shared_metrics_contract', 'tool_approval_outcome'), + 'tool_category': ('hermes_cli.observability.shared_metrics_contract', 'tool_category'), + 'tool_terminal_fields': ('hermes_cli.observability.shared_metrics_contract', 'tool_terminal_fields'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/onepassword_secrets_cli.py b/hermes_cli/onepassword_secrets_cli.py index 6d70bcd941..6accb552f3 100644 --- a/hermes_cli/onepassword_secrets_cli.py +++ b/hermes_cli/onepassword_secrets_cli.py @@ -393,3 +393,25 @@ def _op_whoami(binary: Path, account: str, *, token_value: str = "") -> Optional if res.returncode != 0: return None return (res.stdout or "").strip().replace("\n", " ")[:120] or "authenticated" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from rich.table import Table # noqa: F401,E402 +import sys # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'masked_secret_prompt': ('hermes_cli.secret_prompt', 'masked_secret_prompt'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/platform_actions.py b/hermes_cli/platform_actions.py index 5cd1c36f90..192e2ac8ff 100644 --- a/hermes_cli/platform_actions.py +++ b/hermes_cli/platform_actions.py @@ -201,3 +201,11 @@ class PlatformActions: result.get("ok"), "" if result.get("ok") else f" error={result.get('error')}", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Optional # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/plugins.py b/hermes_cli/plugins.py index 1cf03f9984..cef22a5b4a 100644 --- a/hermes_cli/plugins.py +++ b/hermes_cli/plugins.py @@ -2034,3 +2034,67 @@ def get_plugin_toolsets() -> List[tuple]: desc = (plugin.manifest.description if plugin else "") or ", ".join(sorted(toolset_tools[ts_key])) result.append((ts_key, f"🔌 {ts_key.replace('_', ' ').title()}", desc)) return result + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Iterable # noqa: F401,E402 +from typing import Type # noqa: F401,E402 +from contextlib import contextmanager # noqa: F401,E402 +import contextvars # noqa: F401,E402 +import copy # noqa: F401,E402 +import hashlib # noqa: F401,E402 +import time # noqa: F401,E402 +from functools import wraps # noqa: F401,E402 + +def get_plugin_subscriptions() -> Dict[str, List[Callable]]: + """Return the inter-plugin event bus subscription registry. + + Returns a snapshot mapping each fully-qualified event name + (``:`` or ``hermes:``) to subscriber callbacks in + registration order. Owner ledger metadata stays private to the manager. + Triggers idempotent plugin discovery before reading the snapshot. + """ + manager = _ensure_plugins_discovered() + with manager._event_lock: + return { + event: [entry.callback for entry in entries] + for event, entries in manager._subscriptions.items() + } + +def unload_plugins( + plugin: Union[str, PluginManifest, LoadedPlugin, None] = None, +) -> bool: + """Unload one plugin or all plugins from the process-global manager. + + Wait for background discovery first so teardown cannot race an in-flight + registration sweep introduced by the warm-start discovery path. + """ + _join_background_discovery() + return get_plugin_manager().unload(plugin) + + +_PLUGIN_COMPAT_LAZY = { + 'CAPABILITY_REGISTRY': ('hermes_cli.plugin_capabilities', 'CAPABILITY_REGISTRY'), + 'ENTRY_POINT_CAPABILITIES_GROUP': ('hermes_cli.plugins_discovery', 'ENTRY_POINT_CAPABILITIES_GROUP'), + 'LEGACY_RELAY_PLUGIN_KEYS': ('hermes_cli.relay_plugin_cutover', 'LEGACY_RELAY_PLUGIN_KEYS'), + 'MAX_SYSTEM_PROMPT_SECTIONS': ('hermes_cli.plugins_dispatch', 'MAX_SYSTEM_PROMPT_SECTIONS'), + 'OBSERVER_SCHEMA_VERSION': ('hermes_cli.middleware', 'OBSERVER_SCHEMA_VERSION'), + 'VALID_CAPABILITY_IDS': ('hermes_cli.plugin_capabilities', 'VALID_CAPABILITY_IDS'), + 'cfg_get': ('hermes_cli.config', 'cfg_get'), + 'fast_safe_load': ('utils', 'fast_safe_load'), + 'format_system_prompt_section': ('hermes_cli.plugins_dispatch', 'format_system_prompt_section'), + 'reset_hermes_home_override': ('hermes_constants', 'reset_hermes_home_override'), + 'set_hermes_home_override': ('hermes_constants', 'set_hermes_home_override'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/plugins_cmd.py b/hermes_cli/plugins_cmd.py index 0f40a28095..6505a7f3f1 100644 --- a/hermes_cli/plugins_cmd.py +++ b/hermes_cli/plugins_cmd.py @@ -2035,3 +2035,11 @@ def plugins_command(args) -> None: if handler is None: _fail(_console(), f"[red]Unknown plugins action: {action}[/red]") handler(args) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import importlib.metadata # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/profile_describer.py b/hermes_cli/profile_describer.py index f0eda23821..97f04aa9f9 100644 --- a/hermes_cli/profile_describer.py +++ b/hermes_cli/profile_describer.py @@ -190,3 +190,11 @@ def list_describable_profiles(*, missing_only: bool = True) -> list[str]: p.name for p in profiles_mod.list_profiles() if not (missing_only and (p.description or "").strip() and not p.description_auto) ] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/profile_distribution.py b/hermes_cli/profile_distribution.py index 3c1a8a7f13..984aea9fd3 100644 --- a/hermes_cli/profile_distribution.py +++ b/hermes_cli/profile_distribution.py @@ -458,3 +458,23 @@ def describe_distribution(profile_name: str) -> Dict[str, Any]: """Return a structured view of a profile's distribution metadata ({} if not a distribution).""" manifest = read_manifest(_existing_profile(profile_name)[1]) return {} if manifest is None else manifest.to_dict() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'is_excluded_skill_path': ('agent.skill_utils', 'is_excluded_skill_path'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/profiles.py b/hermes_cli/profiles.py index 035cbfd3f8..e4a0d3b1a3 100644 --- a/hermes_cli/profiles.py +++ b/hermes_cli/profiles.py @@ -1702,3 +1702,17 @@ def resolve_profile_env(profile_name: str) -> str: if not profile_dir.is_dir() or named_profile_is_deleted(profile_dir): raise _missing_profile_error(canon) return str(profile_dir) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def has_bundled_skills_opt_out(profile_dir: Path) -> bool: + """Return True if the profile opted out of bundled-skill seeding.""" + try: + return (profile_dir / NO_BUNDLED_SKILLS_MARKER).exists() + except OSError: + return False +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/runtime_provider.py b/hermes_cli/runtime_provider.py index d1807bdc2c..6d62983e8e 100644 --- a/hermes_cli/runtime_provider.py +++ b/hermes_cli/runtime_provider.py @@ -867,3 +867,25 @@ def _ladder_rungs(requested_provider, explicit_api_key, explicit_base_url, targe def format_runtime_provider_error(error: Exception) -> str: return format_auth_error(error) if isinstance(error, AuthError) else str(error) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'custom_provider_aliases': ('hermes_cli.providers', 'custom_provider_aliases'), + 'custom_provider_slug': ('hermes_cli.providers', 'custom_provider_slug'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/security_advisories.py b/hermes_cli/security_advisories.py index 1d807e99a4..f1c4f6f708 100644 --- a/hermes_cli/security_advisories.py +++ b/hermes_cli/security_advisories.py @@ -273,3 +273,27 @@ def gateway_log_message(hits: list[AdvisoryHit]) -> Optional[str]: f"matches {h.advisory.title}. See {h.advisory.url}") return (f"{len(fresh)} security advisories active (IDs: {', '.join(h.advisory.id for h in fresh)}). " "Run `hermes doctor` on the gateway host for details.") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def render_doctor_section(hits: list[AdvisoryHit]) -> tuple[bool, list[str]]: + """Render the security-advisory section for ``hermes doctor``. + + Returns ``(has_problems, lines)``. Caller is responsible for printing + with whatever color scheme it uses. + """ + fresh = filter_unacked(hits) + if not fresh: + return False, ["No active security advisories. ✓"] + + lines: list[str] = [] + for i, hit in enumerate(fresh): + if i: + lines.append("") + lines.extend(full_remediation_text(hit)) + return True, lines +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index 0b444ba462..f5c0c98e74 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -782,3 +782,30 @@ def _run_setup_wizard_impl(args): "If setup changed a value you customized, restore it with:", f" cp {_backup_path} {config_path}") _print_setup_summary(config, hermes_home) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Any # noqa: F401,E402 +from typing import Dict # noqa: F401,E402 +from typing import Optional # noqa: F401,E402 +import json # noqa: F401,E402 +import shutil # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'get_nous_subscription_features': ('hermes_cli.nous_subscription', 'get_nous_subscription_features'), + 'get_optional_skills_dir': ('hermes_constants', 'get_optional_skills_dir'), + 'managed_nous_tools_enabled': ('tools.tool_backend_helpers', 'managed_nous_tools_enabled'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/slack_cli.py b/hermes_cli/slack_cli.py index 1c89955d8e..fe52b4ddb5 100644 --- a/hermes_cli/slack_cli.py +++ b/hermes_cli/slack_cli.py @@ -145,3 +145,11 @@ def slack_manifest_command(args) -> int: " (xoxb-...) and app token (xapp-...) configured via\n" " `hermes setup`.\n", file=sys.stderr) return 0 + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/sqlite_safe_read.py b/hermes_cli/sqlite_safe_read.py index fecbd03efb..154e43f31c 100644 --- a/hermes_cli/sqlite_safe_read.py +++ b/hermes_cli/sqlite_safe_read.py @@ -237,3 +237,12 @@ def offline_file_access(path: Path | str, *, what: str = "read"): "connection's POSIX advisory locks. Close all database " "handles (stop the gateway/dashboard) and retry.") yield + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +SQLITE_HEADER_MAGIC = b"SQLite format 3\x00" +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/status.py b/hermes_cli/status.py index a16f9520ff..7d6965e3a9 100644 --- a/hermes_cli/status.py +++ b/hermes_cli/status.py @@ -355,3 +355,28 @@ def show_status(args): nous_inference_present=False, nous_account_info=None) for render in _SECTIONS: render(ctx) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import subprocess # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'format_nous_portal_entitlement_message': ('hermes_cli.nous_account', 'format_nous_portal_entitlement_message'), + 'get_nous_portal_account_info': ('hermes_cli.nous_account', 'get_nous_portal_account_info'), + 'get_nous_subscription_features': ('hermes_cli.nous_subscription', 'get_nous_subscription_features'), + 'managed_nous_tools_enabled': ('tools.tool_backend_helpers', 'managed_nous_tools_enabled'), + 'redact_key': ('hermes_cli.config', 'redact_key'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/telegram_managed_bot.py b/hermes_cli/telegram_managed_bot.py index 79054f7c72..c124f49e85 100644 --- a/hermes_cli/telegram_managed_bot.py +++ b/hermes_cli/telegram_managed_bot.py @@ -182,3 +182,101 @@ def auto_setup_telegram_bot_result( print(" The bot may still be created — check Telegram.\n" " You can paste the token manually below, or re-run setup.") return None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import secrets # noqa: F401,E402 +import urllib.parse # noqa: F401,E402 +import secrets # noqa: F401,E402 +import urllib.parse # noqa: F401,E402 + +DEFAULT_MANAGER_BOT = "HermesSetupBot" + +def auto_setup_telegram_bot( + api_url: str | None = None, + manager_bot: str = DEFAULT_MANAGER_BOT, + profile_name: Optional[str] = None, + poll_timeout: float = DEFAULT_POLL_TIMEOUT, +) -> Optional[str]: + """Run automatic Telegram bot creation and return only the bot token.""" + result = auto_setup_telegram_bot_result( + api_url=api_url, + manager_bot=manager_bot, + profile_name=profile_name, + poll_timeout=poll_timeout, + ) + return result.token if result else None + +_USERNAME_SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567" + +def generate_username_slug(length: int = 16) -> str: + """Generate a base32-ish slug for Telegram username correlation. + + Sixteen characters from a 32-symbol alphabet gives 80 bits of entropy while + keeping ``hermes__bot`` under Telegram's 32-character username limit. + """ + return "".join(secrets.choice(_USERNAME_SLUG_ALPHABET) for _ in range(length)) + +def generate_bot_username(profile_name: Optional[str] = None) -> str: + """Generate a secure suggested bot username like ``hermes__bot``. + + ``profile_name`` is accepted for backward compatibility with the original + PoC, but is intentionally not embedded in the username. The username has to + carry enough entropy for backend correlation. + """ + _ = profile_name + return f"hermes_{generate_username_slug()}_bot" + +def generate_deep_link( + manager_bot: str = DEFAULT_MANAGER_BOT, + suggested_username: Optional[str] = None, + suggested_name: Optional[str] = None, +) -> str: + """Build a ``t.me/newbot`` deep link for managed bot creation.""" + manager = manager_bot.lstrip("@") + username = suggested_username or generate_bot_username() + base_url = ( + "https://t.me/newbot/" + f"{urllib.parse.quote(manager)}/" + f"{urllib.parse.quote(username)}" + ) + + if suggested_name: + params = urllib.parse.urlencode({"name": suggested_name}) + return f"{base_url}?{params}" + return base_url + +def generate_pairing_nonce() -> str: + """Generate a legacy-compatible random nonce string. + + The new protocol uses service-created ``pairing_id`` + bearer + ``poll_token`` instead of a path nonce, but this helper is harmless and + still useful for callers/tests that need a generic random id. + """ + return secrets.token_hex(16) + +def poll_pairing_once( + api_url: str | None, + pairing: TelegramPairing, + timeout: float = 10.0, +) -> str | None: + """Poll the onboarding service once. Returns the token when ready.""" + result = poll_pairing_result_once(api_url, pairing, timeout=timeout) + return result.token if result else None + + +_PLUGIN_COMPAT_LAZY = { + 'poll_for_token': ('plugins.platforms.photon.auth', 'poll_for_token'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/tools_config.py b/hermes_cli/tools_config.py index b0dc86d113..8c2ab10d99 100644 --- a/hermes_cli/tools_config.py +++ b/hermes_cli/tools_config.py @@ -1055,3 +1055,32 @@ def tools_command(args=None, first_install: bool = False, config: dict = None): print(color(f" Tool configuration saved to {display_hermes_home()}/config.yaml", Colors.DIM)) print(color(" Changes take effect on next 'hermes' or gateway restart.", Colors.DIM)) print() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import shutil # noqa: F401,E402 +import subprocess # noqa: F401,E402 +import sys # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'MANAGED_FEATURE_COVERAGE_CATEGORY': ('hermes_cli.nous_subscription', 'MANAGED_FEATURE_COVERAGE_CATEGORY'), + 'NOUS_MANAGED_PROVIDER': ('tools.tool_backend_helpers', 'NOUS_MANAGED_PROVIDER'), + 'base_url_hostname': ('utils', 'base_url_hostname'), + 'fal_key_is_configured': ('tools.tool_backend_helpers', 'fal_key_is_configured'), + 'format_nous_portal_entitlement_message': ('hermes_cli.nous_account', 'format_nous_portal_entitlement_message'), + 'is_truthy_value': ('utils', 'is_truthy_value'), + 'save_env_value': ('hermes_cli.config', 'save_env_value'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/uninstall.py b/hermes_cli/uninstall.py index d4940f490e..a17ab7edc2 100644 --- a/hermes_cli/uninstall.py +++ b/hermes_cli/uninstall.py @@ -735,3 +735,29 @@ def main(argv=None) -> int: if __name__ == "__main__": sys.exit(main()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def find_shell_configs() -> list: + """Find shell configuration files that might have PATH entries.""" + home = Path.home() + configs = [] + + candidates = [ + home / ".bashrc", + home / ".bash_profile", + home / ".profile", + home / ".zshrc", + home / ".zprofile", + ] + + for config in candidates: + if config.exists(): + configs.append(config) + + return configs +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/update_cmd.py b/hermes_cli/update_cmd.py index d08f123438..14068d1cdd 100644 --- a/hermes_cli/update_cmd.py +++ b/hermes_cli/update_cmd.py @@ -1340,3 +1340,14 @@ def _cmd_update_impl(args, gateway_mode: bool): _refuse_update_for_contended_shims(e) except subprocess.CalledProcessError as e: _handle_update_called_process_error(e, args, gateway_mode, had_desktop_app_before_update) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Optional # noqa: F401,E402 +from datetime import datetime # noqa: F401,E402 +import hashlib # noqa: F401,E402 +import json # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/update_inventory.py b/hermes_cli/update_inventory.py index 97dbc937f5..912d66dcc3 100644 --- a/hermes_cli/update_inventory.py +++ b/hermes_cli/update_inventory.py @@ -389,3 +389,12 @@ def record_plan_in_receipt(plan: UpdatePlan) -> None: ur._current.data["plan"] = plan.to_dict() except Exception as exc: logger.debug("Could not record plan in receipt: %s", exc) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import Path # noqa: F401,E402 +import os # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/web_deps.py b/hermes_cli/web_deps.py index 5c2b99ad56..c7f464656e 100644 --- a/hermes_cli/web_deps.py +++ b/hermes_cli/web_deps.py @@ -107,3 +107,26 @@ class LateState: def __repr__(self) -> str: # pragma: no cover - debugging aid return f" {self._target()!r}>" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def get_dashboard_health(): + """The ``DASHBOARD_HEALTH`` singleton owned by web_server.""" + return _server().DASHBOARD_HEALTH + +def get_session_token() -> str: + """Current dashboard session token (``web_server._SESSION_TOKEN``).""" + return _server()._SESSION_TOKEN + +def has_valid_session_token(request) -> bool: + """Late-bound alias for ``web_server._has_valid_session_token``.""" + return _server()._has_valid_session_token(request) + +def late_attr(name: str) -> Any: + """Read ``web_server.`` right now (for non-callable state reads).""" + return getattr(_server(), name) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/web_routers/cron.py b/hermes_cli/web_routers/cron.py index bb1e571735..43ed0ba76b 100644 --- a/hermes_cli/web_routers/cron.py +++ b/hermes_cli/web_routers/cron.py @@ -418,3 +418,11 @@ async def instantiate_blueprint(body: AutomationBlueprintInstantiate, profile: s _raise_if_cron_registration_error(e) _log.exception("POST /api/cron/blueprints/instantiate failed") raise HTTPException(status_code=400, detail=str(e)) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import logging # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/web_routers/mcp.py b/hermes_cli/web_routers/mcp.py index 3308d68e1d..8ab90c95fa 100644 --- a/hermes_cli/web_routers/mcp.py +++ b/hermes_cli/web_routers/mcp.py @@ -449,3 +449,11 @@ async def install_mcp_catalog_entry(body: MCPCatalogInstall, profile: Optional[s _log.exception("install_mcp_catalog_entry failed") raise HTTPException(status_code=400, detail=str(exc)) return {"ok": True, "name": name, "background": False} + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import logging # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/web_routers/sessions.py b/hermes_cli/web_routers/sessions.py index 4831d836ed..eb4450c0cc 100644 --- a/hermes_cli/web_routers/sessions.py +++ b/hermes_cli/web_routers/sessions.py @@ -691,3 +691,13 @@ async def export_session_endpoint(session_id: str, profile: Optional[str] = None async def prune_sessions_endpoint(body: SessionPrune): """Delete ended sessions matching filters without blocking the event loop.""" return await asyncio.to_thread(_prune_sessions, body) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Any # noqa: F401,E402 +from typing import Dict # noqa: F401,E402 +import logging # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/web_routers/skills.py b/hermes_cli/web_routers/skills.py index 84829a3212..9c78a1b956 100644 --- a/hermes_cli/web_routers/skills.py +++ b/hermes_cli/web_routers/skills.py @@ -436,3 +436,24 @@ async def update_skill_content(body: SkillContentUpdate): raise HTTPException(status_code=status, detail=err) _clear_skills_prompt_cache() return result + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import logging # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'LateState': ('hermes_cli.web_deps', 'LateState'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/web_routers/tools.py b/hermes_cli/web_routers/tools.py index d09adb2a34..6340b3da01 100644 --- a/hermes_cli/web_routers/tools.py +++ b/hermes_cli/web_routers/tools.py @@ -683,3 +683,24 @@ async def grant_computer_use_permissions(profile: Optional[str] = None): profile, ["computer-use", "permissions", "grant"], "computer-use-grant", log_msg="Failed to spawn computer-use permissions grant", prefix="Failed to request permissions") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import logging # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'LateState': ('hermes_cli.web_deps', 'LateState'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 422c67b8b0..d5fd2a3e13 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -1418,3 +1418,410 @@ def start_server( await server.shutdown() _run_serve(_serve, config, host, port) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import List # noqa: F401,E402 +from typing import Literal # noqa: F401,E402 +import atexit # noqa: F401,E402 +import base64 # noqa: F401,E402 +import binascii # noqa: F401,E402 +import concurrent.futures # noqa: F401,E402 +import contextlib # noqa: F401,E402 +from contextlib import contextmanager # noqa: F401,E402 +from dataclasses import dataclass # noqa: F401,E402 +from datetime import datetime # noqa: F401,E402 +import functools # noqa: F401,E402 +import hashlib # noqa: F401,E402 +import importlib.util # noqa: F401,E402 +import inspect # noqa: F401,E402 +import ipaddress # noqa: F401,E402 +import json # noqa: F401,E402 +import math # noqa: F401,E402 +import mimetypes # noqa: F401,E402 +import queue # noqa: F401,E402 +import shlex # noqa: F401,E402 +import shutil # noqa: F401,E402 +import stat # noqa: F401,E402 +import tempfile # noqa: F401,E402 +from datetime import timezone # noqa: F401,E402 +import yaml # noqa: F401,E402 +import zipfile # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'AudioTranscriptionRequest': ('hermes_cli.web_models', 'AudioTranscriptionRequest'), + 'AutomationBlueprintInstantiate': ('hermes_cli.web_models', 'AutomationBlueprintInstantiate'), + 'BackupRequest': ('hermes_cli.web_models', 'BackupRequest'), + 'BulkDeleteSessions': ('hermes_cli.web_models', 'BulkDeleteSessions'), + 'CONFIG_SCHEMA': ('hermes_cli.web_server_config', 'CONFIG_SCHEMA'), + 'ChatImageUpload': ('hermes_cli.web_models', 'ChatImageUpload'), + 'ConfigUpdate': ('hermes_cli.web_models', 'ConfigUpdate'), + 'CredentialPoolAdd': ('hermes_cli.web_models', 'CredentialPoolAdd'), + 'CronJobCreate': ('hermes_cli.web_models', 'CronJobCreate'), + 'CronJobUpdate': ('hermes_cli.web_models', 'CronJobUpdate'), + 'CuratorPause': ('hermes_cli.web_models', 'CuratorPause'), + 'CustomEndpointUpdate': ('hermes_cli.web_models', 'CustomEndpointUpdate'), + 'DEFAULT_CONFIG': ('hermes_cli.config', 'DEFAULT_CONFIG'), + 'DebugShareRequest': ('hermes_cli.web_models', 'DebugShareRequest'), + 'EnvVarDelete': ('hermes_cli.web_models', 'EnvVarDelete'), + 'EnvVarReveal': ('hermes_cli.web_models', 'EnvVarReveal'), + 'EnvVarUpdate': ('hermes_cli.web_models', 'EnvVarUpdate'), + 'FontSetBody': ('hermes_cli.web_models', 'FontSetBody'), + 'FsWriteText': ('hermes_cli.web_models', 'FsWriteText'), + 'GitBranchSwitchBody': ('hermes_cli.web_models', 'GitBranchSwitchBody'), + 'GitCommitBody': ('hermes_cli.web_models', 'GitCommitBody'), + 'GitFileBody': ('hermes_cli.web_models', 'GitFileBody'), + 'GitPathBody': ('hermes_cli.web_models', 'GitPathBody'), + 'GitWorktreeAddBody': ('hermes_cli.web_models', 'GitWorktreeAddBody'), + 'GitWorktreeRemoveBody': ('hermes_cli.web_models', 'GitWorktreeRemoveBody'), + 'HookCreate': ('hermes_cli.web_models', 'HookCreate'), + 'HookDelete': ('hermes_cli.web_models', 'HookDelete'), + 'ImportRequest': ('hermes_cli.web_models', 'ImportRequest'), + 'LearningNodeEdit': ('hermes_cli.web_models', 'LearningNodeEdit'), + 'LearningNodeRef': ('hermes_cli.web_models', 'LearningNodeRef'), + 'MCPCatalogInstall': ('hermes_cli.web_models', 'MCPCatalogInstall'), + 'MCPEnabledToggle': ('hermes_cli.web_models', 'MCPEnabledToggle'), + 'MCPServerCreate': ('hermes_cli.web_models', 'MCPServerCreate'), + 'MCPServersReplace': ('hermes_cli.web_models', 'MCPServersReplace'), + 'ManagedDirectoryCreate': ('hermes_cli.web_models', 'ManagedDirectoryCreate'), + 'ManagedFileDelete': ('hermes_cli.web_models', 'ManagedFileDelete'), + 'ManagedFileUpload': ('hermes_cli.web_models', 'ManagedFileUpload'), + 'ManagedFilesPolicy': ('hermes_cli.web_server_files', 'ManagedFilesPolicy'), + 'MemoryProviderConfigUpdate': ('hermes_cli.web_models', 'MemoryProviderConfigUpdate'), + 'MemoryProviderSelect': ('hermes_cli.web_models', 'MemoryProviderSelect'), + 'MemoryProviderSetupRequest': ('hermes_cli.web_models', 'MemoryProviderSetupRequest'), + 'MemoryReset': ('hermes_cli.web_models', 'MemoryReset'), + 'MessagingPlatformUpdate': ('hermes_cli.web_models', 'MessagingPlatformUpdate'), + 'MoaConfigPayload': ('hermes_cli.web_models', 'MoaConfigPayload'), + 'MoaModelSlot': ('hermes_cli.web_models', 'MoaModelSlot'), + 'MoaPresetPayload': ('hermes_cli.web_models', 'MoaPresetPayload'), + 'ModelAssignment': ('hermes_cli.web_models', 'ModelAssignment'), + 'OAuthSubmitBody': ('hermes_cli.web_models', 'OAuthSubmitBody'), + 'OPTIONAL_ENV_VARS': ('hermes_cli.config', 'OPTIONAL_ENV_VARS'), + 'PairingApprove': ('hermes_cli.web_models', 'PairingApprove'), + 'PairingRevoke': ('hermes_cli.web_models', 'PairingRevoke'), + 'ProfileActiveUpdate': ('hermes_cli.web_models', 'ProfileActiveUpdate'), + 'ProfileCreate': ('hermes_cli.web_models', 'ProfileCreate'), + 'ProfileDescribeAuto': ('hermes_cli.web_models', 'ProfileDescribeAuto'), + 'ProfileDescriptionUpdate': ('hermes_cli.web_models', 'ProfileDescriptionUpdate'), + 'ProfileModelUpdate': ('hermes_cli.web_models', 'ProfileModelUpdate'), + 'ProfileRename': ('hermes_cli.web_models', 'ProfileRename'), + 'ProfileSoulUpdate': ('hermes_cli.web_models', 'ProfileSoulUpdate'), + 'ProviderConfigSchema': ('plugins.memory.config_schema', 'ProviderConfigSchema'), + 'ProviderField': ('plugins.memory.config_schema', 'ProviderField'), + 'PtyBridge': ('hermes_cli.pty_bridge', 'PtyBridge'), + 'PtySessionRegistry': ('hermes_cli.pty_session', 'PtySessionRegistry'), + 'PtyUnavailableError': ('hermes_cli.pty_bridge', 'PtyUnavailableError'), + 'RawConfigUpdate': ('hermes_cli.web_models', 'RawConfigUpdate'), + 'RegistryFull': ('hermes_cli.pty_session', 'RegistryFull'), + 'STORAGE_HONCHO_HOST_BLOCK': ('plugins.memory.config_schema', 'STORAGE_HONCHO_HOST_BLOCK'), + 'SessionImport': ('hermes_cli.web_models', 'SessionImport'), + 'SessionPrune': ('hermes_cli.web_models', 'SessionPrune'), + 'SessionRename': ('hermes_cli.web_models', 'SessionRename'), + 'SkillContentUpdate': ('hermes_cli.web_models', 'SkillContentUpdate'), + 'SkillCreate': ('hermes_cli.web_models', 'SkillCreate'), + 'SkillInstallRequest': ('hermes_cli.web_models', 'SkillInstallRequest'), + 'SkillToggle': ('hermes_cli.web_models', 'SkillToggle'), + 'SkillUninstallRequest': ('hermes_cli.web_models', 'SkillUninstallRequest'), + 'SkillsUpdateRequest': ('hermes_cli.web_models', 'SkillsUpdateRequest'), + 'TTSLeaseRequest': ('hermes_cli.web_models', 'TTSLeaseRequest'), + 'TTSSpeakRequest': ('hermes_cli.web_models', 'TTSSpeakRequest'), + 'TelegramOnboardingApply': ('hermes_cli.web_models', 'TelegramOnboardingApply'), + 'TelegramOnboardingStart': ('hermes_cli.web_models', 'TelegramOnboardingStart'), + 'TerminalBackendSelect': ('hermes_cli.web_models', 'TerminalBackendSelect'), + 'ThemeSetBody': ('hermes_cli.web_models', 'ThemeSetBody'), + 'ToolsetEnvUpdate': ('hermes_cli.web_models', 'ToolsetEnvUpdate'), + 'ToolsetModelSelect': ('hermes_cli.web_models', 'ToolsetModelSelect'), + 'ToolsetPostSetup': ('hermes_cli.web_models', 'ToolsetPostSetup'), + 'ToolsetProviderSelect': ('hermes_cli.web_models', 'ToolsetProviderSelect'), + 'ToolsetToggle': ('hermes_cli.web_models', 'ToolsetToggle'), + 'WebhookCreate': ('hermes_cli.web_models', 'WebhookCreate'), + 'WebhookEnabledToggle': ('hermes_cli.web_models', 'WebhookEnabledToggle'), + 'WhatsAppOnboardingApply': ('hermes_cli.web_models', 'WhatsAppOnboardingApply'), + 'WhatsAppOnboardingStart': ('hermes_cli.web_models', 'WhatsAppOnboardingStart'), + 'activate_custom_endpoint': ('hermes_cli.web_routers.config_env', 'activate_custom_endpoint'), + 'add_credential_pool_entry': ('hermes_cli.web_routers.ops', 'add_credential_pool_entry'), + 'add_mcp_server': ('hermes_cli.web_routers.mcp', 'add_mcp_server'), + 'apply_telegram_onboarding': ('hermes_cli.web_routers.messaging', 'apply_telegram_onboarding'), + 'apply_whatsapp_onboarding': ('hermes_cli.web_routers.messaging', 'apply_whatsapp_onboarding'), + 'approve_pairing': ('hermes_cli.web_routers.ops', 'approve_pairing'), + 'auth_mcp_server': ('hermes_cli.web_routers.mcp', 'auth_mcp_server'), + 'build_cron_model_impact': ('hermes_cli.config', 'build_cron_model_impact'), + 'bulk_delete_sessions_endpoint': ('hermes_cli.web_routers.sessions', 'bulk_delete_sessions_endpoint'), + 'cancel_oauth_session': ('hermes_cli.web_routers.oauth', 'cancel_oauth_session'), + 'cancel_telegram_onboarding': ('hermes_cli.web_routers.messaging', 'cancel_telegram_onboarding'), + 'cancel_whatsapp_onboarding': ('hermes_cli.web_routers.messaging', 'cancel_whatsapp_onboarding'), + 'cfg_get': ('hermes_cli.config', 'cfg_get'), + 'check_config_version': ('hermes_cli.config', 'check_config_version'), + 'check_hermes_update': ('hermes_cli.web_routers.actions', 'check_hermes_update'), + 'clear_model_endpoint_credentials': ('hermes_cli.config', 'clear_model_endpoint_credentials'), + 'clear_pending_pairing': ('hermes_cli.web_routers.ops', 'clear_pending_pairing'), + 'coerce_provider_id': ('hermes_cli.config', 'coerce_provider_id'), + 'console_ws': ('hermes_cli.web_routers.chat_ws', 'console_ws'), + 'count_empty_sessions_endpoint': ('hermes_cli.web_routers.sessions', 'count_empty_sessions_endpoint'), + 'create_cron_job': ('hermes_cli.web_routers.cron', 'create_cron_job'), + 'create_hook': ('hermes_cli.web_routers.ops', 'create_hook'), + 'create_managed_directory': ('hermes_cli.web_routers.files', 'create_managed_directory'), + 'create_profile_endpoint': ('hermes_cli.web_routers.profiles', 'create_profile_endpoint'), + 'create_skill': ('hermes_cli.web_routers.skills', 'create_skill'), + 'create_webhook': ('hermes_cli.web_routers.ops', 'create_webhook'), + 'cron_fire_webhook': ('hermes_cli.web_routers.cron', 'cron_fire_webhook'), + 'custom_endpoint_key_env': ('hermes_cli.config', 'custom_endpoint_key_env'), + 'delete_agent_plugin': ('hermes_cli.web_routers.dashboard_ui', 'delete_agent_plugin'), + 'delete_cron_job': ('hermes_cli.web_routers.cron', 'delete_cron_job'), + 'delete_custom_endpoint': ('hermes_cli.web_routers.config_env', 'delete_custom_endpoint'), + 'delete_empty_sessions_endpoint': ('hermes_cli.web_routers.sessions', 'delete_empty_sessions_endpoint'), + 'delete_hook': ('hermes_cli.web_routers.ops', 'delete_hook'), + 'delete_learning_node': ('hermes_cli.web_routers.status', 'delete_learning_node'), + 'delete_managed_file': ('hermes_cli.web_routers.files', 'delete_managed_file'), + 'delete_profile_endpoint': ('hermes_cli.web_routers.profiles', 'delete_profile_endpoint'), + 'delete_session_endpoint': ('hermes_cli.web_routers.sessions', 'delete_session_endpoint'), + 'delete_webhook': ('hermes_cli.web_routers.ops', 'delete_webhook'), + 'derive_gateway_busy': ('gateway.status', 'derive_gateway_busy'), + 'derive_gateway_drainable': ('gateway.status', 'derive_gateway_drainable'), + 'describe_profile_auto_endpoint': ('hermes_cli.web_routers.profiles', 'describe_profile_auto_endpoint'), + 'detect_install_method': ('hermes_cli.config', 'detect_install_method'), + 'disconnect_oauth_provider': ('hermes_cli.web_routers.oauth', 'disconnect_oauth_provider'), + 'download_dashboard_backup': ('hermes_cli.web_routers.ops', 'download_dashboard_backup'), + 'download_managed_file': ('hermes_cli.web_routers.files', 'download_managed_file'), + 'enable_webhooks': ('hermes_cli.web_routers.ops', 'enable_webhooks'), + 'env_var_enabled': ('utils', 'env_var_enabled'), + 'events_ws': ('hermes_cli.web_routers.chat_ws', 'events_ws'), + 'export_session_endpoint': ('hermes_cli.web_routers.sessions', 'export_session_endpoint'), + 'find_provider_entry': ('hermes_cli.config', 'find_provider_entry'), + 'format_docker_update_message': ('hermes_cli.config', 'format_docker_update_message'), + 'fs_default_cwd': ('hermes_cli.web_routers.files', 'fs_default_cwd'), + 'fs_download': ('hermes_cli.web_routers.files', 'fs_download'), + 'fs_git_root': ('hermes_cli.web_routers.files', 'fs_git_root'), + 'fs_list': ('hermes_cli.web_routers.files', 'fs_list'), + 'fs_read_data_url': ('hermes_cli.web_routers.files', 'fs_read_data_url'), + 'fs_read_text': ('hermes_cli.web_routers.files', 'fs_read_text'), + 'fs_write_text': ('hermes_cli.web_routers.files', 'fs_write_text'), + 'gateway_drain': ('hermes_cli.web_routers.actions', 'gateway_drain'), + 'gateway_ws': ('hermes_cli.web_routers.chat_ws', 'gateway_ws'), + 'get_action_status': ('hermes_cli.web_routers.actions', 'get_action_status'), + 'get_active_profile_endpoint': ('hermes_cli.web_routers.profiles', 'get_active_profile_endpoint'), + 'get_auxiliary_models': ('hermes_cli.web_routers.models', 'get_auxiliary_models'), + 'get_client_voice_config': ('hermes_cli.web_routers.audio', 'get_client_voice_config'), + 'get_computer_use_status': ('hermes_cli.web_routers.tools', 'get_computer_use_status'), + 'get_config': ('hermes_cli.web_routers.config_env', 'get_config'), + 'get_config_path': ('hermes_cli.config', 'get_config_path'), + 'get_config_raw': ('hermes_cli.web_routers.analytics', 'get_config_raw'), + 'get_cron_delivery_targets': ('hermes_cli.web_routers.cron', 'get_cron_delivery_targets'), + 'get_cron_job': ('hermes_cli.web_routers.cron', 'get_cron_job'), + 'get_curator_status': ('hermes_cli.web_routers.status', 'get_curator_status'), + 'get_dashboard_font': ('hermes_cli.web_routers.dashboard_ui', 'get_dashboard_font'), + 'get_dashboard_plugins': ('hermes_cli.web_routers.dashboard_ui', 'get_dashboard_plugins'), + 'get_dashboard_themes': ('hermes_cli.web_routers.dashboard_ui', 'get_dashboard_themes'), + 'get_defaults': ('hermes_cli.web_routers.config_env', 'get_defaults'), + 'get_egress_status': ('hermes_cli.web_routers.config_env', 'get_egress_status'), + 'get_elevenlabs_voices': ('hermes_cli.web_routers.audio', 'get_elevenlabs_voices'), + 'get_env_path': ('hermes_cli.config', 'get_env_path'), + 'get_env_vars': ('hermes_cli.web_routers.config_env', 'get_env_vars'), + 'get_health': ('hermes_cli.web_routers.status', 'get_health'), + 'get_hermes_home': ('hermes_cli.config', 'get_hermes_home'), + 'get_learning_graph': ('hermes_cli.web_routers.status', 'get_learning_graph'), + 'get_learning_node': ('hermes_cli.web_routers.status', 'get_learning_node'), + 'get_logs': ('hermes_cli.web_routers.status', 'get_logs'), + 'get_media': ('hermes_cli.web_routers.files', 'get_media'), + 'get_memory_provider_config': ('hermes_cli.web_routers.memory_providers', 'get_memory_provider_config'), + 'get_memory_status': ('hermes_cli.web_routers.ops', 'get_memory_status'), + 'get_messaging_platforms': ('hermes_cli.web_routers.messaging', 'get_messaging_platforms'), + 'get_moa_models': ('hermes_cli.web_routers.models', 'get_moa_models'), + 'get_model_info': ('hermes_cli.web_routers.models', 'get_model_info'), + 'get_model_options': ('hermes_cli.web_routers.models', 'get_model_options'), + 'get_models_analytics': ('hermes_cli.web_routers.analytics', 'get_models_analytics'), + 'get_plugins_hub': ('hermes_cli.web_routers.dashboard_ui', 'get_plugins_hub'), + 'get_portal_status': ('hermes_cli.web_routers.status', 'get_portal_status'), + 'get_process_hermes_home': ('hermes_cli.config', 'get_process_hermes_home'), + 'get_profile_setup_command': ('hermes_cli.web_routers.profiles', 'get_profile_setup_command'), + 'get_profile_soul': ('hermes_cli.web_routers.profiles', 'get_profile_soul'), + 'get_profiles_sessions': ('hermes_cli.web_routers.profiles', 'get_profiles_sessions'), + 'get_profiles_sessions_sidebar': ('hermes_cli.web_routers.profiles', 'get_profiles_sessions_sidebar'), + 'get_provider_config_schema': ('plugins.memory.config_schema', 'get_provider_config_schema'), + 'get_recommended_default_model': ('hermes_cli.web_routers.models', 'get_recommended_default_model'), + 'get_running_pid': ('gateway.status', 'get_running_pid'), + 'get_running_pid_cached': ('gateway.status', 'get_running_pid_cached'), + 'get_runtime_status_running_pid': ('gateway.status', 'get_runtime_status_running_pid'), + 'get_schema': ('hermes_cli.web_routers.config_env', 'get_schema'), + 'get_session_detail': ('hermes_cli.web_routers.sessions', 'get_session_detail'), + 'get_session_latest_descendant': ('hermes_cli.web_routers.sessions', 'get_session_latest_descendant'), + 'get_session_messages': ('hermes_cli.web_routers.sessions', 'get_session_messages'), + 'get_session_stats': ('hermes_cli.web_routers.sessions', 'get_session_stats'), + 'get_sessions': ('hermes_cli.web_routers.sessions', 'get_sessions'), + 'get_skill_content': ('hermes_cli.web_routers.skills', 'get_skill_content'), + 'get_skills': ('hermes_cli.web_routers.skills', 'get_skills'), + 'get_ssh_ownership': ('hermes_cli.web_routers.status', 'get_ssh_ownership'), + 'get_status': ('hermes_cli.web_routers.status', 'get_status'), + 'get_system_stats': ('hermes_cli.web_routers.status', 'get_system_stats'), + 'get_telegram_onboarding_status': ('hermes_cli.web_routers.messaging', 'get_telegram_onboarding_status'), + 'get_terminal_backends': ('hermes_cli.web_routers.tools', 'get_terminal_backends'), + 'get_toolset_config': ('hermes_cli.web_routers.tools', 'get_toolset_config'), + 'get_toolset_models': ('hermes_cli.web_routers.tools', 'get_toolset_models'), + 'get_toolsets': ('hermes_cli.web_routers.tools', 'get_toolsets'), + 'get_update_receipt': ('hermes_cli.web_routers.actions', 'get_update_receipt'), + 'get_usage_analytics': ('hermes_cli.web_routers.analytics', 'get_usage_analytics'), + 'get_whatsapp_onboarding_status': ('hermes_cli.web_routers.messaging', 'get_whatsapp_onboarding_status'), + 'git_base_branches_route': ('hermes_cli.web_routers.git', 'git_base_branches_route'), + 'git_branch_switch_route': ('hermes_cli.web_routers.git', 'git_branch_switch_route'), + 'git_branches_route': ('hermes_cli.web_routers.git', 'git_branches_route'), + 'git_commit_context_route': ('hermes_cli.web_routers.git', 'git_commit_context_route'), + 'git_commit_route': ('hermes_cli.web_routers.git', 'git_commit_route'), + 'git_create_pr_route': ('hermes_cli.web_routers.git', 'git_create_pr_route'), + 'git_file_diff_route': ('hermes_cli.web_routers.git', 'git_file_diff_route'), + 'git_push_route': ('hermes_cli.web_routers.git', 'git_push_route'), + 'git_rev_parse_route': ('hermes_cli.web_routers.git', 'git_rev_parse_route'), + 'git_revert_route': ('hermes_cli.web_routers.git', 'git_revert_route'), + 'git_review_diff_route': ('hermes_cli.web_routers.git', 'git_review_diff_route'), + 'git_review_list_route': ('hermes_cli.web_routers.git', 'git_review_list_route'), + 'git_ship_info_route': ('hermes_cli.web_routers.git', 'git_ship_info_route'), + 'git_stage_route': ('hermes_cli.web_routers.git', 'git_stage_route'), + 'git_status_route': ('hermes_cli.web_routers.git', 'git_status_route'), + 'git_unstage_route': ('hermes_cli.web_routers.git', 'git_unstage_route'), + 'git_worktree_add_route': ('hermes_cli.web_routers.git', 'git_worktree_add_route'), + 'git_worktree_remove_route': ('hermes_cli.web_routers.git', 'git_worktree_remove_route'), + 'git_worktrees_route': ('hermes_cli.web_routers.git', 'git_worktrees_route'), + 'grant_computer_use_permissions': ('hermes_cli.web_routers.tools', 'grant_computer_use_permissions'), + 'import_sessions_endpoint': ('hermes_cli.web_routers.sessions', 'import_sessions_endpoint'), + 'install_mcp_catalog_entry': ('hermes_cli.web_routers.mcp', 'install_mcp_catalog_entry'), + 'install_skill_hub': ('hermes_cli.web_routers.skills', 'install_skill_hub'), + 'instantiate_blueprint': ('hermes_cli.web_routers.cron', 'instantiate_blueprint'), + 'is_nix_install_method': ('hermes_cli.config', 'is_nix_install_method'), + 'list_checkpoints': ('hermes_cli.web_routers.ops', 'list_checkpoints'), + 'list_credential_pool': ('hermes_cli.web_routers.ops', 'list_credential_pool'), + 'list_cron_blueprints': ('hermes_cli.web_routers.cron', 'list_cron_blueprints'), + 'list_cron_job_runs': ('hermes_cli.web_routers.cron', 'list_cron_job_runs'), + 'list_cron_jobs': ('hermes_cli.web_routers.cron', 'list_cron_jobs'), + 'list_custom_endpoints': ('hermes_cli.web_routers.config_env', 'list_custom_endpoints'), + 'list_hooks': ('hermes_cli.web_routers.ops', 'list_hooks'), + 'list_managed_files': ('hermes_cli.web_routers.files', 'list_managed_files'), + 'list_mcp_catalog': ('hermes_cli.web_routers.mcp', 'list_mcp_catalog'), + 'list_mcp_servers': ('hermes_cli.web_routers.mcp', 'list_mcp_servers'), + 'list_oauth_providers': ('hermes_cli.web_routers.oauth', 'list_oauth_providers'), + 'list_pairing': ('hermes_cli.web_routers.ops', 'list_pairing'), + 'list_profiles_endpoint': ('hermes_cli.web_routers.profiles', 'list_profiles_endpoint'), + 'list_skills_hub_sources': ('hermes_cli.web_routers.skills', 'list_skills_hub_sources'), + 'list_webhooks': ('hermes_cli.web_routers.ops', 'list_webhooks'), + 'load_env': ('hermes_cli.config', 'load_env'), + 'mcp_oauth_callback': ('hermes_cli.web_routers.mcp', 'mcp_oauth_callback'), + 'mcp_oauth_flow_status': ('hermes_cli.web_routers.mcp', 'mcp_oauth_flow_status'), + 'normalize_updated_at': ('gateway.status', 'normalize_updated_at'), + 'open_profile_terminal_endpoint': ('hermes_cli.web_routers.profiles', 'open_profile_terminal_endpoint'), + 'parse_active_agents': ('gateway.status', 'parse_active_agents'), + 'pause_cron_job': ('hermes_cli.web_routers.cron', 'pause_cron_job'), + 'poll_oauth_session': ('hermes_cli.web_routers.oauth', 'poll_oauth_session'), + 'post_agent_plugin_disable': ('hermes_cli.web_routers.dashboard_ui', 'post_agent_plugin_disable'), + 'post_agent_plugin_enable': ('hermes_cli.web_routers.dashboard_ui', 'post_agent_plugin_enable'), + 'post_agent_plugin_install': ('hermes_cli.web_routers.dashboard_ui', 'post_agent_plugin_install'), + 'post_agent_plugin_update': ('hermes_cli.web_routers.dashboard_ui', 'post_agent_plugin_update'), + 'post_plugin_visibility': ('hermes_cli.web_routers.dashboard_ui', 'post_plugin_visibility'), + 'preview_skill_hub': ('hermes_cli.web_routers.skills', 'preview_skill_hub'), + 'prune_checkpoints': ('hermes_cli.web_routers.ops', 'prune_checkpoints'), + 'prune_sessions_endpoint': ('hermes_cli.web_routers.sessions', 'prune_sessions_endpoint'), + 'pty_ws': ('hermes_cli.web_routers.chat_ws', 'pty_ws'), + 'pub_ws': ('hermes_cli.web_routers.chat_ws', 'pub_ws'), + 'put_plugin_providers': ('hermes_cli.web_routers.dashboard_ui', 'put_plugin_providers'), + 'read_managed_file': ('hermes_cli.web_routers.files', 'read_managed_file'), + 'read_raw_config': ('hermes_cli.config', 'read_raw_config'), + 'read_runtime_status': ('gateway.status', 'read_runtime_status'), + 'recommended_update_command_for_method': ('hermes_cli.config', 'recommended_update_command_for_method'), + 'redact_key': ('hermes_cli.config', 'redact_key'), + 'remove_credential_pool_entry': ('hermes_cli.web_routers.ops', 'remove_credential_pool_entry'), + 'remove_env_value': ('hermes_cli.config', 'remove_env_value'), + 'remove_env_var': ('hermes_cli.web_routers.config_env', 'remove_env_var'), + 'remove_mcp_server': ('hermes_cli.web_routers.mcp', 'remove_mcp_server'), + 'rename_profile_endpoint': ('hermes_cli.web_routers.profiles', 'rename_profile_endpoint'), + 'rename_session_endpoint': ('hermes_cli.web_routers.sessions', 'rename_session_endpoint'), + 'replace_mcp_servers': ('hermes_cli.web_routers.mcp', 'replace_mcp_servers'), + 'rescan_dashboard_plugins': ('hermes_cli.web_routers.dashboard_ui', 'rescan_dashboard_plugins'), + 'reset_memory': ('hermes_cli.web_routers.ops', 'reset_memory'), + 'resolve_cron_model_drift_defaults': ('hermes_cli.config', 'resolve_cron_model_drift_defaults'), + 'resolve_gateway_liveness': ('gateway.status', 'resolve_gateway_liveness'), + 'restart_gateway': ('hermes_cli.web_routers.actions', 'restart_gateway'), + 'resume_cron_job': ('hermes_cli.web_routers.cron', 'resume_cron_job'), + 'reveal_env_var': ('hermes_cli.web_routers.config_env', 'reveal_env_var'), + 'revoke_pairing': ('hermes_cli.web_routers.ops', 'revoke_pairing'), + 'run_backup': ('hermes_cli.backup', 'run_backup'), + 'run_config_migrate': ('hermes_cli.web_routers.status', 'run_config_migrate'), + 'run_curator': ('hermes_cli.web_routers.status', 'run_curator'), + 'run_debug_share_endpoint': ('hermes_cli.web_routers.status', 'run_debug_share_endpoint'), + 'run_doctor': ('hermes_cli.doctor', 'run_doctor'), + 'run_dump': ('hermes_cli.dump', 'run_dump'), + 'run_import': ('hermes_cli.backup', 'run_import'), + 'run_import_upload': ('hermes_cli.web_routers.ops', 'run_import_upload'), + 'run_prompt_size': ('hermes_cli.web_routers.status', 'run_prompt_size'), + 'run_security_audit': ('hermes_cli.web_routers.ops', 'run_security_audit'), + 'run_toolset_post_setup': ('hermes_cli.web_routers.tools', 'run_toolset_post_setup'), + 'save_config': ('hermes_cli.config', 'save_config'), + 'save_env_value': ('hermes_cli.config', 'save_env_value'), + 'save_toolset_env': ('hermes_cli.web_routers.tools', 'save_toolset_env'), + 'scan_skill_hub': ('hermes_cli.web_routers.skills', 'scan_skill_hub'), + 'search_sessions': ('hermes_cli.web_routers.sessions', 'search_sessions'), + 'search_skills_hub': ('hermes_cli.web_routers.skills', 'search_skills_hub'), + 'select_terminal_backend': ('hermes_cli.web_routers.tools', 'select_terminal_backend'), + 'select_toolset_model': ('hermes_cli.web_routers.tools', 'select_toolset_model'), + 'select_toolset_provider': ('hermes_cli.web_routers.tools', 'select_toolset_provider'), + 'serve_plugin_asset': ('hermes_cli.web_routers.dashboard_ui', 'serve_plugin_asset'), + 'set_active_profile_endpoint': ('hermes_cli.web_routers.profiles', 'set_active_profile_endpoint'), + 'set_curator_paused': ('hermes_cli.web_routers.status', 'set_curator_paused'), + 'set_dashboard_font': ('hermes_cli.web_routers.dashboard_ui', 'set_dashboard_font'), + 'set_dashboard_theme': ('hermes_cli.web_routers.dashboard_ui', 'set_dashboard_theme'), + 'set_env_var': ('hermes_cli.web_routers.config_env', 'set_env_var'), + 'set_mcp_server_enabled': ('hermes_cli.web_routers.mcp', 'set_mcp_server_enabled'), + 'set_memory_provider': ('hermes_cli.web_routers.ops', 'set_memory_provider'), + 'set_moa_models': ('hermes_cli.web_routers.models', 'set_moa_models'), + 'set_model_assignment': ('hermes_cli.web_routers.models', 'set_model_assignment'), + 'set_webhook_enabled': ('hermes_cli.web_routers.ops', 'set_webhook_enabled'), + 'setup_memory_provider': ('hermes_cli.web_routers.memory_providers', 'setup_memory_provider'), + 'speak_stream_ws': ('hermes_cli.web_routers.audio', 'speak_stream_ws'), + 'speak_text': ('hermes_cli.voice', 'speak_text'), + 'start_gateway': ('hermes_cli.web_routers.ops', 'start_gateway'), + 'start_oauth_login': ('hermes_cli.web_routers.oauth', 'start_oauth_login'), + 'start_telegram_onboarding': ('hermes_cli.web_routers.messaging', 'start_telegram_onboarding'), + 'start_whatsapp_onboarding': ('hermes_cli.web_routers.messaging', 'start_whatsapp_onboarding'), + 'stop_gateway': ('hermes_cli.web_routers.ops', 'stop_gateway'), + 'stream_managed_file': ('hermes_cli.web_routers.files', 'stream_managed_file'), + 'submit_oauth_code': ('hermes_cli.web_routers.oauth', 'submit_oauth_code'), + 'test_mcp_server': ('hermes_cli.web_routers.mcp', 'test_mcp_server'), + 'test_messaging_platform': ('hermes_cli.web_routers.messaging', 'test_messaging_platform'), + 'toggle_skill': ('hermes_cli.web_routers.skills', 'toggle_skill'), + 'toggle_toolset': ('hermes_cli.web_routers.tools', 'toggle_toolset'), + 'transcribe_audio_upload': ('hermes_cli.web_routers.audio', 'transcribe_audio_upload'), + 'trigger_cron_job': ('hermes_cli.web_routers.cron', 'trigger_cron_job'), + 'tts_lease': ('hermes_cli.web_routers.audio', 'tts_lease'), + 'uninstall_skill_hub': ('hermes_cli.web_routers.skills', 'uninstall_skill_hub'), + 'update_config': ('hermes_cli.web_routers.config_env', 'update_config'), + 'update_config_raw': ('hermes_cli.web_routers.analytics', 'update_config_raw'), + 'update_cron_job': ('hermes_cli.web_routers.cron', 'update_cron_job'), + 'update_hermes': ('hermes_cli.web_routers.actions', 'update_hermes'), + 'update_learning_node': ('hermes_cli.web_routers.status', 'update_learning_node'), + 'update_memory_provider_config': ('hermes_cli.web_routers.memory_providers', 'update_memory_provider_config'), + 'update_messaging_platform': ('hermes_cli.web_routers.messaging', 'update_messaging_platform'), + 'update_profile_description_endpoint': ('hermes_cli.web_routers.profiles', 'update_profile_description_endpoint'), + 'update_profile_model_endpoint': ('hermes_cli.web_routers.profiles', 'update_profile_model_endpoint'), + 'update_profile_soul': ('hermes_cli.web_routers.profiles', 'update_profile_soul'), + 'update_skill_content': ('hermes_cli.web_routers.skills', 'update_skill_content'), + 'update_skills_hub': ('hermes_cli.web_routers.skills', 'update_skills_hub'), + 'upload_chat_image': ('hermes_cli.web_routers.files', 'upload_chat_image'), + 'upload_managed_file': ('hermes_cli.web_routers.files', 'upload_managed_file'), + 'upload_managed_file_stream': ('hermes_cli.web_routers.files', 'upload_managed_file_stream'), + 'upsert_custom_endpoint': ('hermes_cli.web_routers.config_env', 'upsert_custom_endpoint'), + 'validate_custom_endpoint': ('hermes_cli.web_routers.config_env', 'validate_custom_endpoint'), + 'validate_provider_credential': ('hermes_cli.web_routers.config_env', 'validate_provider_credential'), + 'windows_detach_flags': ('hermes_cli._subprocess_compat', 'windows_detach_flags'), + 'windows_hide_flags': ('hermes_cli._subprocess_compat', 'windows_hide_flags'), + 'write_platform_config_field': ('hermes_cli.config', 'write_platform_config_field'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/webhook.py b/hermes_cli/webhook.py index ce5d59179b..fb7c1983e2 100644 --- a/hermes_cli/webhook.py +++ b/hermes_cli/webhook.py @@ -224,3 +224,25 @@ _ACTIONS = { "list": _cmd_list, "ls": _cmd_list, "remove": _cmd_remove, "rm": _cmd_remove, "test": _cmd_test} + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 +import tempfile # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'atomic_replace': ('utils', 'atomic_replace'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_cli/win_pty_bridge.py b/hermes_cli/win_pty_bridge.py index 2e7a65906c..05deb36a5c 100644 --- a/hermes_cli/win_pty_bridge.py +++ b/hermes_cli/win_pty_bridge.py @@ -124,3 +124,11 @@ class WinPtyBridge: def __exit__(self, *_exc) -> None: self.close() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_logging.py b/hermes_logging.py index 49990ff4ec..d94e9f70cb 100644 --- a/hermes_logging.py +++ b/hermes_logging.py @@ -627,3 +627,18 @@ def _read_logging_config(): except Exception: pass return (None, None, None) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def rotating_file_handlers() -> list: + """Return the live rotating file handlers. + + They are attached to the async ``QueueListener`` rather than the root + logger, so callers/tests must use this instead of scanning + ``logging.getLogger().handlers``.""" + return list(_queued_file_handlers) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_state.py b/hermes_state.py index d4bf1a6a90..2bb6c8667f 100644 --- a/hermes_state.py +++ b/hermes_state.py @@ -1292,3 +1292,77 @@ class AsyncSessionDB: async def _offloaded(*args, **kwargs): return await asyncio.to_thread(attr, *args, **kwargs) return _offloaded + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Set # noqa: F401,E402 +import contextlib # noqa: F401,E402 +import errno # noqa: F401,E402 +import struct # noqa: F401,E402 +import weakref # noqa: F401,E402 + +MAX_SAFE_EXPORT_MESSAGES = 20_000 + +MAX_SAFE_RESUME_MESSAGES = 20_000 + + +_PLUGIN_COMPAT_LAZY = { + 'AUTO_VACUUM_MIN_FREELIST_RATIO': ('hermes_state_common', 'AUTO_VACUUM_MIN_FREELIST_RATIO'), + 'ActivityProvenance': ('agent.session_activity', 'ActivityProvenance'), + 'CompressionSessionBusyError': ('hermes_state_errors', 'CompressionSessionBusyError'), + 'CompressionSessionClosedError': ('hermes_state_errors', 'CompressionSessionClosedError'), + 'DEFERRED_INDEX_SQL': ('hermes_state_common', 'DEFERRED_INDEX_SQL'), + 'FTS_CJK_STALE_KEY': ('hermes_state_common', 'FTS_CJK_STALE_KEY'), + 'FTS_CJK_TABLE_SQL': ('hermes_state_fts', 'FTS_CJK_TABLE_SQL'), + 'FTS_CJK_TRIGGER_SQL': ('hermes_state_fts', 'FTS_CJK_TRIGGER_SQL'), + 'FTS_REBUILD_DEFERRAL_KEY': ('hermes_state_common', 'FTS_REBUILD_DEFERRAL_KEY'), + 'FTS_SQL': ('hermes_state_common', 'FTS_SQL'), + 'FTS_STALE_KEY': ('hermes_state_common', 'FTS_STALE_KEY'), + 'FTS_STORAGE_VERSION': ('hermes_state_common', 'FTS_STORAGE_VERSION'), + 'FTS_TRIGRAM_SQL': ('hermes_state_common', 'FTS_TRIGRAM_SQL'), + 'LEGACY_FTS_SQL': ('hermes_state_common', 'LEGACY_FTS_SQL'), + 'LEGACY_FTS_TRIGRAM_SQL': ('hermes_state_common', 'LEGACY_FTS_TRIGRAM_SQL'), + 'MAX_FTS5_QUERY_CHARS': ('hermes_state_common', 'MAX_FTS5_QUERY_CHARS'), + 'PERSISTENCE_ERROR_CAUSES': ('hermes_state_errors', 'PERSISTENCE_ERROR_CAUSES'), + 'SCHEMA_SQL': ('hermes_state_common', 'SCHEMA_SQL'), + 'SCHEMA_VERSION': ('hermes_state_common', 'SCHEMA_VERSION'), + 'SESSION_STATUS_COMPLETE': ('hermes_state_sessions', 'SESSION_STATUS_COMPLETE'), + 'SESSION_STATUS_EMPTY': ('hermes_state_sessions', 'SESSION_STATUS_EMPTY'), + 'SESSION_STATUS_ERROR': ('hermes_state_sessions', 'SESSION_STATUS_ERROR'), + 'SESSION_STATUS_INTERRUPTED': ('hermes_state_sessions', 'SESSION_STATUS_INTERRUPTED'), + 'SKILL_EXCERPT_JOINT': ('agent.skill_commands', 'SKILL_EXCERPT_JOINT'), + 'SKILL_SCAFFOLD_SQL_LIKE': ('agent.skill_commands', 'SKILL_SCAFFOLD_SQL_LIKE'), + 'SessionTurnLeaseLostError': ('hermes_state_errors', 'SessionTurnLeaseLostError'), + 'WalUnsupportedError': ('hermes_state_wal', 'WalUnsupportedError'), + 'apply_durability_barriers': ('hermes_state_repair', 'apply_durability_barriers'), + 'classify_session_status': ('hermes_state_sessions', 'classify_session_status'), + 'collect_state_db_stats': ('hermes_state_dbfile', 'collect_state_db_stats'), + 'count_db_holders': ('hermes_state_dbfile', 'count_db_holders'), + 'describe_skill_invocation': ('agent.skill_commands', 'describe_skill_invocation'), + 'fts5_cjk_so_path': ('hermes_state_fts', 'fts5_cjk_so_path'), + 'is_advisory_lock_contention': ('hermes_state_common', 'is_advisory_lock_contention'), + 'is_automatic_end_reason': ('hermes_state_common', 'is_automatic_end_reason'), + 'is_disk_full_error': ('hermes_state_errors', 'is_disk_full_error'), + 'is_sqlite_wal_reset_vulnerable': ('hermes_state_wal', 'is_sqlite_wal_reset_vulnerable'), + 'is_transient_sqlite_error': ('hermes_state_errors', 'is_transient_sqlite_error'), + 'iter_deleted_sqlite_sidecar_holders': ('hermes_state_dbfile', 'iter_deleted_sqlite_sidecar_holders'), + 'release_or_close': ('hermes_state_registry', 'release_or_close'), + 'report_startup_progress': ('hermes_startup_watchdog', 'report_startup_progress'), + 'resolve_journal_mode': ('hermes_state_wal', 'resolve_journal_mode'), + 'resolve_synchronous_level': ('hermes_state_wal', 'resolve_synchronous_level'), + 'sanitize_context': ('agent.memory_manager', 'sanitize_context'), + 'sqlite_source_id': ('hermes_state_wal', 'sqlite_source_id'), + 'workspace_key': ('hermes_state_sessions', 'workspace_key'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_state_registry.py b/hermes_state_registry.py index 6f8d694f58..2a5614bc16 100644 --- a/hermes_state_registry.py +++ b/hermes_state_registry.py @@ -222,3 +222,19 @@ def release_or_close(db: "SessionDB") -> None: db.close() except Exception: logger.debug("release_or_close fallback close failed", exc_info=True) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def close_shared_session_dbs() -> int: + return close_all() + +def get_shared_session_db(db_path: Optional[Path] = None) -> "SessionDB": + return acquire(db_path) + +def release_shared_session_db(db: "SessionDB") -> bool: + return release(db) +# ---- END PLUGIN-COMPAT ---- diff --git a/hermes_state_search.py b/hermes_state_search.py index 53d0c9fc92..e2c72cfc4e 100644 --- a/hermes_state_search.py +++ b/hermes_state_search.py @@ -1269,3 +1269,12 @@ class SessionSearchMixin: break self._fts_usermerge_floor_applied = True return executed + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +import os # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/browser/browser_use/provider.py b/plugins/browser/browser_use/provider.py index 0c09d019c1..9cbc21d17d 100644 --- a/plugins/browser/browser_use/provider.py +++ b/plugins/browser/browser_use/provider.py @@ -158,3 +158,24 @@ class BrowserUseBrowserProvider(CloudBrowserProvider): "features": {"browser_use": True}, "external_call_id": response.headers.get("x-external-call-id") if managed_mode else None, } + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'BrowserProvider': ('agent.browser_provider', 'BrowserProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/browser/browserbase/provider.py b/plugins/browser/browserbase/provider.py index 99805dea7c..264e3a5ade 100644 --- a/plugins/browser/browserbase/provider.py +++ b/plugins/browser/browserbase/provider.py @@ -113,3 +113,25 @@ class BrowserbaseBrowserProvider(CloudBrowserProvider): "cdp_url": session_data["connectUrl"], "features": features_enabled, } + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import requests # noqa: F401,E402 +import uuid # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'BrowserProvider': ('agent.browser_provider', 'BrowserProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/browser/firecrawl/provider.py b/plugins/browser/firecrawl/provider.py index d3732d7b55..f608842937 100644 --- a/plugins/browser/firecrawl/provider.py +++ b/plugins/browser/firecrawl/provider.py @@ -66,3 +66,25 @@ class FirecrawlBrowserProvider(CloudBrowserProvider): "cdp_url": data["cdpUrl"], "features": {"firecrawl": True}, } + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import requests # noqa: F401,E402 +import uuid # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'BrowserProvider': ('agent.browser_provider', 'BrowserProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/context_engine/__init__.py b/plugins/context_engine/__init__.py index 1a07131679..dee2c5da37 100644 --- a/plugins/context_engine/__init__.py +++ b/plugins/context_engine/__init__.py @@ -82,3 +82,12 @@ class _EngineCollector(_loader.NoopPluginContext): logger.debug("Context engine '%s' registered command: /%s", self._engine_name, clean) except Exception as exc: logger.debug("Context engine '%s' could not register /%s: %s", self._engine_name, clean, exc) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import importlib.util # noqa: F401,E402 +import sys # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/cron_providers/__init__.py b/plugins/cron_providers/__init__.py index 7414097aae..c9416f5811 100644 --- a/plugins/cron_providers/__init__.py +++ b/plugins/cron_providers/__init__.py @@ -94,3 +94,12 @@ class _ProviderCollector(_loader.NoopPluginContext): def register_cron_scheduler(self, provider): self.provider = provider + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import importlib.util # noqa: F401,E402 +import sys # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/cron_providers/chronos/__init__.py b/plugins/cron_providers/chronos/__init__.py index 0ef53944c6..a485d76a6f 100644 --- a/plugins/cron_providers/chronos/__init__.py +++ b/plugins/cron_providers/chronos/__init__.py @@ -158,3 +158,11 @@ class ChronosCronScheduler(CronScheduler): def register(ctx) -> None: """Plugin entrypoint — plugins/cron_providers discovery collects the provider here.""" ctx.register_cron_scheduler(ChronosCronScheduler()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Optional # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/cron_providers/chronos/_nas_client.py b/plugins/cron_providers/chronos/_nas_client.py index ab39c4a763..6dee94c1c6 100644 --- a/plugins/cron_providers/chronos/_nas_client.py +++ b/plugins/cron_providers/chronos/_nas_client.py @@ -63,3 +63,11 @@ class NasCronClient: data = self._request("GET", _LIST_PATH, params={}) items = data.get("armed") if isinstance(data, dict) else None return items if isinstance(items, list) else [] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Optional # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/dashboard_auth/basic/__init__.py b/plugins/dashboard_auth/basic/__init__.py index 6abf5e7f18..749f7f2dcd 100644 --- a/plugins/dashboard_auth/basic/__init__.py +++ b/plugins/dashboard_auth/basic/__init__.py @@ -261,3 +261,24 @@ def register(ctx) -> None: kwargs, LAST_SKIP_REASON = register_provider(ctx, logger, _TAG, BasicAuthProvider, _settings) if kwargs is not None: logger.info("dashboard-auth-basic: registered password provider (username=%s)", kwargs["username"]) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Any # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'LoginStart': ('hermes_cli.dashboard_auth', 'LoginStart'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/dashboard_auth/drain/__init__.py b/plugins/dashboard_auth/drain/__init__.py index de8447855a..de645211d1 100644 --- a/plugins/dashboard_auth/drain/__init__.py +++ b/plugins/dashboard_auth/drain/__init__.py @@ -155,3 +155,23 @@ def register(ctx) -> None: logger.info( "dashboard-auth-drain: registered drain service-credential provider (scope=%s, route=%s)", kwargs["scope"], DRAIN_ROUTE_PATH) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'LoginStart': ('hermes_cli.dashboard_auth', 'LoginStart'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/dashboard_auth/nous/__init__.py b/plugins/dashboard_auth/nous/__init__.py index 3f56be7794..3eb7731408 100644 --- a/plugins/dashboard_auth/nous/__init__.py +++ b/plugins/dashboard_auth/nous/__init__.py @@ -153,3 +153,32 @@ def register(ctx) -> None: if kwargs is not None: logger.info( "dashboard-auth-nous: registered provider (client_id=%s, portal=%s)", kwargs["client_id"], kwargs["portal_url"]) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import base64 # noqa: F401,E402 +import hashlib # noqa: F401,E402 +import httpx # noqa: F401,E402 +import os # noqa: F401,E402 +import secrets # noqa: F401,E402 +import urllib.parse # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'DashboardAuthProvider': ('hermes_cli.dashboard_auth', 'DashboardAuthProvider'), + 'InvalidCodeError': ('hermes_cli.dashboard_auth', 'InvalidCodeError'), + 'RefreshExpiredError': ('hermes_cli.dashboard_auth', 'RefreshExpiredError'), + 'classify_jwks_lookup_error': ('hermes_cli.dashboard_auth', 'classify_jwks_lookup_error'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/dashboard_auth/self_hosted/__init__.py b/plugins/dashboard_auth/self_hosted/__init__.py index 64e2c092c6..09fc38f22c 100644 --- a/plugins/dashboard_auth/self_hosted/__init__.py +++ b/plugins/dashboard_auth/self_hosted/__init__.py @@ -293,3 +293,29 @@ def register(ctx) -> None: logger.info( "dashboard-auth-self-hosted: registered provider (issuer=%s, client_id=%s, scopes=%r, confidential=%s)", kw["issuer"], kw["client_id"], kw["scopes"], bool(kw["client_secret"])) # never log the secret itself + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import hashlib # noqa: F401,E402 +import os # noqa: F401,E402 +import secrets # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'DashboardAuthProvider': ('hermes_cli.dashboard_auth', 'DashboardAuthProvider'), + 'InvalidCodeError': ('hermes_cli.dashboard_auth', 'InvalidCodeError'), + 'RefreshExpiredError': ('hermes_cli.dashboard_auth', 'RefreshExpiredError'), + 'classify_jwks_lookup_error': ('hermes_cli.dashboard_auth', 'classify_jwks_lookup_error'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/google_meet/audio_bridge.py b/plugins/google_meet/audio_bridge.py index 678841a1a3..5cd50d8682 100644 --- a/plugins/google_meet/audio_bridge.py +++ b/plugins/google_meet/audio_bridge.py @@ -109,3 +109,33 @@ class AudioBridge: return int(token) except ValueError as exc: raise RuntimeError(f"could not parse pactl module id from: {stdout!r}") from exc + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def chrome_fake_audio_flags(bridge_info: dict) -> list[str]: + """Return Chrome flags for using the fake audio input. + + The PulseAudio source is selected via the ``PULSE_SOURCE`` env var, + which callers must set in Chrome's environment before launch: + + env["PULSE_SOURCE"] = bridge_info["device_name"] + + On macOS the caller must ensure the system default audio input is + set to the returned BlackHole device (we do not flip that switch). + """ + system = platform.system() + if system == "Linux": + # Chromium on Linux picks up the PulseAudio source selected via + # PULSE_SOURCE env var; the fake-ui flag skips the permission + # prompt so the bot can pick "use my mic" without user input. + return ["--use-fake-ui-for-media-stream"] + if system == "Darwin": + return ["--use-fake-ui-for-media-stream"] + if system == "Windows": + raise RuntimeError("windows not supported in v2") + raise RuntimeError(f"unsupported platform: {system}") +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/google_meet/meet_bot.py b/plugins/google_meet/meet_bot.py index 9deadc79cb..eddc2cc020 100644 --- a/plugins/google_meet/meet_bot.py +++ b/plugins/google_meet/meet_bot.py @@ -479,3 +479,15 @@ def _parse_duration(raw: str) -> Optional[float]: if __name__ == "__main__": # pragma: no cover — subprocess entry point sys.exit(run_bot()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 + +SAY_PCM_FILENAME = "speaker.pcm" + +SAY_QUEUE_FILENAME = "say_queue.jsonl" +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/google_meet/node/cli.py b/plugins/google_meet/node/cli.py index fd171ee98b..04638cea46 100644 --- a/plugins/google_meet/node/cli.py +++ b/plugins/google_meet/node/cli.py @@ -100,3 +100,11 @@ def node_command(args: argparse.Namespace) -> int: return 2 # ``run`` never touches the registry; constructing it is side-effect free. return handler(args, NodeRegistry()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Any # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/google_meet/node/registry.py b/plugins/google_meet/node/registry.py index 2a5fb08cbe..13015098f3 100644 --- a/plugins/google_meet/node/registry.py +++ b/plugins/google_meet/node/registry.py @@ -65,3 +65,11 @@ class NodeRegistry: return self.get(chrome_node) nodes = self.list_all() return nodes[0] if len(nodes) == 1 else None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/image_gen/deepinfra/__init__.py b/plugins/image_gen/deepinfra/__init__.py index dfe36d0165..df90c313e9 100644 --- a/plugins/image_gen/deepinfra/__init__.py +++ b/plugins/image_gen/deepinfra/__init__.py @@ -153,3 +153,26 @@ class DeepInfraImageGenProvider(StaticImageGenProvider): def register(ctx) -> None: """Plugin entry point — wire ``DeepInfraImageGenProvider`` into the registry.""" ctx.register_image_gen_provider(DeepInfraImageGenProvider()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'ImageGenProvider': ('agent.image_gen_provider', 'ImageGenProvider'), + 'error_response': ('agent.image_gen_provider', 'error_response'), + 'save_b64_image': ('agent.image_gen_provider', 'save_b64_image'), + 'save_url_image': ('agent.image_gen_provider', 'save_url_image'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/image_gen/fal/__init__.py b/plugins/image_gen/fal/__init__.py index ba884373c7..b5d3cfc389 100644 --- a/plugins/image_gen/fal/__init__.py +++ b/plugins/image_gen/fal/__init__.py @@ -111,3 +111,24 @@ class FalImageGenProvider(StaticImageGenProvider): def register(ctx) -> None: """Plugin entry point — wire ``FalImageGenProvider`` into the registry.""" ctx.register_image_gen_provider(FalImageGenProvider()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'ImageGenProvider': ('agent.image_gen_provider', 'ImageGenProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/image_gen/krea/__init__.py b/plugins/image_gen/krea/__init__.py index f64b219f14..ff36f004dc 100644 --- a/plugins/image_gen/krea/__init__.py +++ b/plugins/image_gen/krea/__init__.py @@ -517,3 +517,26 @@ class KreaImageGenProvider(StaticImageGenProvider): def register(ctx) -> None: """Plugin entry point — wire ``KreaImageGenProvider`` into the registry.""" ctx.register_image_gen_provider(KreaImageGenProvider()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'ImageGenProvider': ('agent.image_gen_provider', 'ImageGenProvider'), + 'error_response': ('agent.image_gen_provider', 'error_response'), + 'normalize_reference_images': ('agent.image_gen_provider', 'normalize_reference_images'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/image_gen/openai/__init__.py b/plugins/image_gen/openai/__init__.py index 59bb92e1d3..e5b5e3baa0 100644 --- a/plugins/image_gen/openai/__init__.py +++ b/plugins/image_gen/openai/__init__.py @@ -141,3 +141,27 @@ class OpenAIImageGenProvider(StaticImageGenProvider): def register(ctx) -> None: """Plugin entry point — wire ``OpenAIImageGenProvider`` into the registry.""" ctx.register_image_gen_provider(OpenAIImageGenProvider()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'ImageGenProvider': ('agent.image_gen_provider', 'ImageGenProvider'), + 'error_response': ('agent.image_gen_provider', 'error_response'), + 'normalize_reference_images': ('agent.image_gen_provider', 'normalize_reference_images'), + 'save_b64_image': ('agent.image_gen_provider', 'save_b64_image'), + 'save_url_image': ('agent.image_gen_provider', 'save_url_image'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/image_gen/xai/__init__.py b/plugins/image_gen/xai/__init__.py index 32f4e23721..ca27cf7585 100644 --- a/plugins/image_gen/xai/__init__.py +++ b/plugins/image_gen/xai/__init__.py @@ -317,3 +317,27 @@ class XAIImageGenProvider(StaticImageGenProvider): def register(ctx: Any) -> None: """Register this provider with the image gen registry.""" ctx.register_image_gen_provider(XAIImageGenProvider()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'ImageGenProvider': ('agent.image_gen_provider', 'ImageGenProvider'), + 'error_response': ('agent.image_gen_provider', 'error_response'), + 'normalize_reference_images': ('agent.image_gen_provider', 'normalize_reference_images'), + 'save_b64_image': ('agent.image_gen_provider', 'save_b64_image'), + 'save_url_image': ('agent.image_gen_provider', 'save_url_image'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/memory/hindsight/__init__.py b/plugins/memory/hindsight/__init__.py index 613e50fa22..c35b189493 100644 --- a/plugins/memory/hindsight/__init__.py +++ b/plugins/memory/hindsight/__init__.py @@ -1203,3 +1203,12 @@ class HindsightMemoryProvider(MemoryProvider): def register(ctx) -> None: """Register Hindsight as a memory provider plugin.""" ctx.register_memory_provider(HindsightMemoryProvider()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from dataclasses import dataclass # noqa: F401,E402 +import importlib # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/memory/honcho/__init__.py b/plugins/memory/honcho/__init__.py index 93723d9247..bfac07824c 100644 --- a/plugins/memory/honcho/__init__.py +++ b/plugins/memory/honcho/__init__.py @@ -803,3 +803,28 @@ def register(ctx) -> None: from plugins.memory.query_rewrite import rewrite_memory_query ctx.register_memory_provider(HonchoMemoryProvider(query_rewriter=rewrite_memory_query)) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'CONCLUDE_SCHEMA': ('plugins.memory.honcho.tool_schemas', 'CONCLUDE_SCHEMA'), + 'CONTEXT_SCHEMA': ('plugins.memory.honcho.tool_schemas', 'CONTEXT_SCHEMA'), + 'PROFILE_SCHEMA': ('plugins.memory.honcho.tool_schemas', 'PROFILE_SCHEMA'), + 'REASONING_SCHEMA': ('plugins.memory.honcho.tool_schemas', 'REASONING_SCHEMA'), + 'SEARCH_SCHEMA': ('plugins.memory.honcho.tool_schemas', 'SEARCH_SCHEMA'), + 'TRIVIAL_PROMPT_RE': ('agent.memory_provider', 'TRIVIAL_PROMPT_RE'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/memory/honcho/client.py b/plugins/memory/honcho/client.py index 3a48c31f60..55bc707c3e 100644 --- a/plugins/memory/honcho/client.py +++ b/plugins/memory/honcho/client.py @@ -593,3 +593,23 @@ def reset_honcho_client() -> None: with _client_slots_lock: _client_slots.clear() _honcho_json_timeout_memo.clear() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'SingletonSlot': ('plugins.plugin_utils', 'SingletonSlot'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/memory/honcho/oauth.py b/plugins/memory/honcho/oauth.py index 8e80571dd1..bcdac62f12 100644 --- a/plugins/memory/honcho/oauth.py +++ b/plugins/memory/honcho/oauth.py @@ -378,3 +378,11 @@ def apply_token_to_client(client: Any, token: str) -> bool: return False http.api_key = token return True + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Callable # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/memory/honcho/session.py b/plugins/memory/honcho/session.py index ab257f98d4..0d94308124 100644 --- a/plugins/memory/honcho/session.py +++ b/plugins/memory/honcho/session.py @@ -373,3 +373,14 @@ class HonchoSessionManager(SessionAuthMixin, SessionPeersMixin, SessionContextMi """Return and clear the cached context result ({} if none ready yet).""" with self._prefetch_cache_lock: return self._context_cache.pop(session_key, {}) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Callable # noqa: F401,E402 +from pathlib import Path # noqa: F401,E402 +import hashlib # noqa: F401,E402 +import re # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/memory/mem0/__init__.py b/plugins/memory/mem0/__init__.py index d20660a356..ce208c1641 100644 --- a/plugins/memory/mem0/__init__.py +++ b/plugins/memory/mem0/__init__.py @@ -348,3 +348,74 @@ class Mem0MemoryProvider(MemoryProvider): def register(ctx) -> None: """Register Mem0 as a memory provider plugin.""" ctx.register_memory_provider(Mem0MemoryProvider()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +ADD_SCHEMA = { + "name": "mem0_add", + "description": ( + "Store a durable fact about the user, verbatim (no LLM extraction). " + "Call this the moment the user states a lasting preference, correction, " + "decision, or personal detail worth recalling on future turns — don't " + "wait to be asked to remember. Skip transient chit-chat and facts you've " + "already stored." + ), + "parameters": { + "type": "object", + "properties": { + "content": {"type": "string", "description": "The fact to store."}, + }, + "required": ["content"], + }, +} + +DELETE_SCHEMA = { + "name": "mem0_delete", + "description": ( + "Delete a memory by its ID (take the ID from a mem0_search " + "result). Use when a stored fact is obsolete or the user asks you to " + "forget it; prefer mem0_update if the fact merely changed." + ), + "parameters": { + "type": "object", + "properties": { + "memory_id": {"type": "string", "description": "Memory UUID to delete."}, + }, + "required": ["memory_id"], + }, +} + +UPDATE_SCHEMA = { + "name": "mem0_update", + "description": ( + "Replace the text of an existing memory by its ID (take the ID from a " + "mem0_search result). Use when a stored fact has changed " + "or was wrong — correct it in place instead of adding a duplicate." + ), + "parameters": { + "type": "object", + "properties": { + "memory_id": {"type": "string", "description": "Memory UUID to update."}, + "text": {"type": "string", "description": "New text content."}, + }, + "required": ["memory_id", "text"], + }, +} + + +_PLUGIN_COMPAT_LAZY = { + 'SEARCH_SCHEMA': ('plugins.memory.honcho.tool_schemas', 'SEARCH_SCHEMA'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/memory/mem0/_setup.py b/plugins/memory/mem0/_setup.py index 390bf35473..1f0f25d261 100644 --- a/plugins/memory/mem0/_setup.py +++ b/plugins/memory/mem0/_setup.py @@ -508,3 +508,19 @@ def post_setup(hermes_home: str, config: dict) -> None: if handler is None: handler = _MODE_PICKER[_curses_select(" Select mode", _MODE_ITEMS, 0)] handler(hermes_home, config, flags) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def has_oss_flags() -> bool: + """Check if OSS-related flags are present in sys.argv.""" + flags = parse_flags(sys.argv[1:]) + if flags["mode"] == "oss": + return True + if any(flags.get(k) for k in ("oss_llm_key", "oss_vector_path", "oss_vector_url")): + return True + return False +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/memory/retaindb/__init__.py b/plugins/memory/retaindb/__init__.py index fe8f2972c9..cf4b1f4be4 100644 --- a/plugins/memory/retaindb/__init__.py +++ b/plugins/memory/retaindb/__init__.py @@ -488,3 +488,93 @@ _TOOLS: dict[str, tuple[str | None, Callable[..., Any]]] = { def register(ctx) -> None: """Register RetainDB as a memory provider plugin.""" ctx.register_memory_provider(RetainDBMemoryProvider()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +from typing import List # noqa: F401,E402 + +FILE_DELETE_SCHEMA = { + "name": "retaindb_delete_file", + "description": "Delete a stored file.", + "parameters": { + "type": "object", + "properties": { + "file_id": {"type": "string", "description": "File ID to delete."}, + }, + "required": ["file_id"], + }, +} + +FILE_INGEST_SCHEMA = { + "name": "retaindb_ingest_file", + "description": "Chunk, embed, and extract memories from a stored file. Makes its contents searchable.", + "parameters": { + "type": "object", + "properties": { + "file_id": {"type": "string", "description": "File ID to ingest."}, + }, + "required": ["file_id"], + }, +} + +FILE_LIST_SCHEMA = { + "name": "retaindb_list_files", + "description": "List files in the shared file store.", + "parameters": { + "type": "object", + "properties": { + "prefix": {"type": "string", "description": "Path prefix to filter by, e.g. /reports/"}, + "limit": {"type": "integer", "description": "Max results (default: 50)."}, + }, + "required": [], + }, +} + +FILE_READ_SCHEMA = { + "name": "retaindb_read_file", + "description": "Read the text content of a stored file by its file ID.", + "parameters": { + "type": "object", + "properties": { + "file_id": {"type": "string", "description": "File ID returned from upload or list."}, + }, + "required": ["file_id"], + }, +} + +FILE_UPLOAD_SCHEMA = { + "name": "retaindb_upload_file", + "description": "Upload a file to the shared RetainDB file store. Returns an rdb:// URI any agent can reference.", + "parameters": { + "type": "object", + "properties": { + "local_path": {"type": "string", "description": "Local file path to upload."}, + "remote_path": {"type": "string", "description": "Destination path, e.g. /reports/q1.pdf"}, + "scope": {"type": "string", "enum": ["USER", "PROJECT", "ORG"], "description": "Access scope (default: PROJECT)."}, + "ingest": {"type": "boolean", "description": "Also extract memories from file after upload (default: false)."}, + }, + "required": ["local_path"], + }, +} + + +_PLUGIN_COMPAT_LAZY = { + 'CONTEXT_SCHEMA': ('plugins.memory.honcho.tool_schemas', 'CONTEXT_SCHEMA'), + 'FORGET_SCHEMA': ('plugins.memory.openviking', 'FORGET_SCHEMA'), + 'PROFILE_SCHEMA': ('plugins.memory.honcho.tool_schemas', 'PROFILE_SCHEMA'), + 'REMEMBER_SCHEMA': ('plugins.memory.openviking', 'REMEMBER_SCHEMA'), + 'SEARCH_SCHEMA': ('plugins.memory.honcho.tool_schemas', 'SEARCH_SCHEMA'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/memory/supermemory/__init__.py b/plugins/memory/supermemory/__init__.py index 0b339660b5..a5939722ae 100644 --- a/plugins/memory/supermemory/__init__.py +++ b/plugins/memory/supermemory/__init__.py @@ -536,3 +536,38 @@ class SupermemoryMemoryProvider(MemoryProvider): def register(ctx): ctx.register_memory_provider(SupermemoryMemoryProvider()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +STORE_SCHEMA = { + "name": "supermemory_store", + "description": "Store an explicit memory for future recall.", + "parameters": { + "type": "object", + "properties": { + "content": {"type": "string", "description": "The memory content to store."}, + "metadata": {"type": "object", "description": "Optional metadata attached to the memory."}, + }, + "required": ["content"], + }, +} + + +_PLUGIN_COMPAT_LAZY = { + 'FORGET_SCHEMA': ('plugins.memory.openviking', 'FORGET_SCHEMA'), + 'PROFILE_SCHEMA': ('plugins.memory.honcho.tool_schemas', 'PROFILE_SCHEMA'), + 'SEARCH_SCHEMA': ('plugins.memory.honcho.tool_schemas', 'SEARCH_SCHEMA'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/a2a/protocol.py b/plugins/platforms/a2a/protocol.py index 013682510e..1b3c2c5f35 100644 --- a/plugins/platforms/a2a/protocol.py +++ b/plugins/platforms/a2a/protocol.py @@ -470,3 +470,50 @@ def load_conversation(context_id: str, limit: int = 50) -> list[dict]: def list_conversations() -> list[str]: """Context-ids that have persisted conversations.""" return sorted(p.stem for p in (_hermes_home() / "a2a_conversations").glob("*.jsonl")) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import copy # noqa: F401,E402 + +ERR_PUSH_NOT_SUPPORTED = -32003 # A2A spec: PushNotificationNotSupportedError + +STATE_AUTH_REQUIRED = "TASK_STATE_AUTH_REQUIRED" + +def data_part(data: Any, media_type: str = "application/json") -> dict: + """Build a v1.0 data Part (structured data, no ``kind`` field).""" + return {"data": data, "mediaType": media_type} + +def file_part(url: str = "", raw: str = "", filename: str = "", + media_type: str = "application/octet-stream") -> dict: + """Build a v1.0 file Part. + + Either ``url`` (file reference) or ``raw`` (base64-encoded bytes) must be + provided. Discrimination is by member presence — no ``kind`` field. + """ + part: dict[str, Any] = {"mediaType": media_type} + if filename: + part["filename"] = filename + if url: + part["url"] = url + elif raw: + part["raw"] = raw + return part + +def message_with_parts(role: str, parts: list[dict], context_id: str = "") -> dict: + """Build an A2A v1.0 Message with arbitrary Parts (text, file, data).""" + msg: dict[str, Any] = { + "role": role, + "parts": parts, + "messageId": uuid.uuid4().hex, + } + if context_id: + msg["contextId"] = context_id + return msg + +def stream_message(message: dict) -> dict: + """v1.0 StreamResponse with a message member.""" + return {"message": message} +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/a2a/security.py b/plugins/platforms/a2a/security.py index 68df198d7b..d37f7cf810 100644 --- a/plugins/platforms/a2a/security.py +++ b/plugins/platforms/a2a/security.py @@ -214,3 +214,86 @@ def audit(direction: str, peer: str, task_id: str, summary: str) -> None: fh.write(json.dumps(rec, ensure_ascii=False) + "\n") except Exception: logger.debug("A2A: audit write failed", exc_info=True) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import Path # noqa: F401,E402 + +def authenticate(auth_header: Optional[str], client_ip: str = "") -> Optional[str]: + """Authenticate an inbound request; return the peer identity or None. + + - No tokens configured (localhost-only mode): identity is ``ip:``. + - Token matches an A2A_PEER_TOKENS entry: identity is that peer's name. + - Token matches the shared A2A_BEARER_TOKEN: identity is ``ip:``. + - Otherwise: None (reject with 401). + + Comparisons are constant-time (hmac.compare_digest). + """ + return A2ASecurityContext.capture().authenticate(auth_header, client_ip) + +def get_bearer_token() -> str: + """Return the configured shared inbound bearer token (empty if none).""" + return _startup_env("A2A_BEARER_TOKEN") + +def get_peer_tokens() -> dict[str, str]: + """Parse A2A_PEER_TOKENS ("alice:tok1,bob:tok2") into {token: peer_name}. + + Per-peer tokens give each remote agent its own credential, so the identity + used for rate limiting, trust, and audit is authenticated — not whatever + the request body claims. + """ + return _parse_peer_tokens(_startup_env("A2A_PEER_TOKENS")) + +def get_push_secret() -> str: + """Return the secret used for HMAC-SHA256 push notification signing. + + Falls back to the bearer token if no dedicated push secret is set. + If neither is configured, push notifications are unsigned (localhost-only mode). + """ + return A2ASecurityContext.capture().push_secret + +def get_trusted_peers() -> set[str]: + """Return the configured trusted-peer allow-list (empty = no restriction). + + Configured via A2A_TRUSTED_PEERS env var (comma-separated identities) or + config.yaml under a2a.trusted_peers. Identities are the *authenticated* + names from ``authenticate()`` — peer-token names, or ``ip:`` for + shared-token callers. + """ + return set(_configured_trusted_peers()) + +def is_trusted_peer(identity: str) -> bool: + """Check whether an authenticated identity may run tasks. + + Open when A2A_ALLOW_ALL_USERS is set or in localhost-only mode. When a + trusted-peer allow-list is configured, the identity must be on it; + otherwise any *authenticated* identity is allowed (authentication is the + primary gate — the allow-list is an optional restriction on top). + """ + return A2ASecurityContext.capture().is_trusted_peer(identity) + +def resolve_bind_host() -> str: + """Resolve the safe inbound bind host. + + Rule: localhost unless the operator BOTH configured a token (shared or + per-peer) AND explicitly asked for a wider host. A token alone does not + widen the bind — opting into remote exposure must be deliberate. + """ + return A2ASecurityContext.capture().resolve_bind_host() + +def sign_push_payload(payload: dict) -> str: + """HMAC-SHA256 sign a push notification payload. + + Returns hex-encoded signature. Empty string if no secret configured. + Receivers verify by HMAC-ing the JSON body (sorted keys) with the shared + secret and comparing against the X-A2A-Signature header. + """ + secret = get_push_secret() + if not secret: + return "" + body = json.dumps(payload, sort_keys=True, ensure_ascii=False).encode("utf-8") + return hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest() +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/a2a/tools.py b/plugins/platforms/a2a/tools.py index 1190b8f1dc..044d5153d6 100644 --- a/plugins/platforms/a2a/tools.py +++ b/plugins/platforms/a2a/tools.py @@ -361,3 +361,11 @@ def register_tools(ctx) -> None: ctx.register_tool(name=name, toolset="a2a", handler=handler, description=description, schema={"name": name, "description": description, "parameters": parameters}, emoji="\U0001f9e9", check_fn=_a2a_tools_available) # puzzle piece + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import TypedDict # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/dingtalk/adapter.py b/plugins/platforms/dingtalk/adapter.py index 1239870f23..5f5ef7035d 100644 --- a/plugins/platforms/dingtalk/adapter.py +++ b/plugins/platforms/dingtalk/adapter.py @@ -736,3 +736,25 @@ def register(ctx) -> None: allow_all_env="DINGTALK_ALLOW_ALL_USERS", cron_deliver_env_var="DINGTALK_HOME_CHANNEL", standalone_sender_fn=_standalone_send, emoji="🐳", allow_update_command=True, ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'DINGTALK_TYPE_MAPPING': ('plugins.platforms.dingtalk.inbound', 'DINGTALK_TYPE_MAPPING'), + 'EXT_MAP': ('plugins.platforms.dingtalk.inbound', 'EXT_MAP'), + 'MessageType': ('gateway.platforms.base', 'MessageType'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/discord/adapter.py b/plugins/platforms/discord/adapter.py index 09422b9693..a1281e22af 100644 --- a/plugins/platforms/discord/adapter.py +++ b/plugins/platforms/discord/adapter.py @@ -7352,3 +7352,23 @@ def register(ctx) -> None: emoji="🎮", allow_update_command=True, ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'env_int': ('utils', 'env_int'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/feishu/feishu_comment.py b/plugins/platforms/feishu/feishu_comment.py index 3c67f8bc05..0ddf99b439 100644 --- a/plugins/platforms/feishu/feishu_comment.py +++ b/plugins/platforms/feishu/feishu_comment.py @@ -618,3 +618,98 @@ async def handle_drive_comment_event(client: Any, data: Any, *, self_open_id: st if reply_id: # best-effort cleanup of the OK reaction await update_comment_reaction(client, "delete", **reaction_kwargs) logger.info("[Feishu-Comment] ========== handle_drive_comment_event END ==========") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +async def add_comment_reaction( + client: Any, + *, + file_token: str, + file_type: str, + reply_id: str, + reaction_type: str = "OK", +) -> bool: + """Add an emoji reaction to a document comment reply. + + Uses the Drive v2 ``update_reaction`` endpoint:: + + POST /open-apis/drive/v2/files/{file_token}/comments/reaction?file_type=... + + Returns ``True`` on success, ``False`` on failure (errors are logged). + """ + try: + from lark_oapi import AccessTokenType # noqa: F401 + except ImportError: + logger.error("[Feishu-Comment] lark_oapi not available") + return False + + body = { + "action": "add", + "reply_id": reply_id, + "reaction_type": reaction_type, + } + + code, msg, _ = await _exec_request( + client, "POST", _REACTION_URI, + paths={"file_token": file_token}, + queries=[("file_type", file_type)], + body=body, + ) + + succeeded = code == 0 + if succeeded: + logger.info( + "[Feishu-Comment] Reaction '%s' added: file=%s:%s reply=%s", + reaction_type, file_type, file_token, reply_id, + ) + else: + logger.warning( + "[Feishu-Comment] Reaction API failed: code=%s msg=%s " + "file=%s:%s reply=%s", + code, msg, file_type, file_token, reply_id, + ) + return succeeded + +async def delete_comment_reaction( + client: Any, + *, + file_token: str, + file_type: str, + reply_id: str, + reaction_type: str = "OK", +) -> bool: + """Remove an emoji reaction from a document comment reply. + + Best-effort — errors are logged but not raised. + """ + body = { + "action": "delete", + "reply_id": reply_id, + "reaction_type": reaction_type, + } + + code, msg, _ = await _exec_request( + client, "POST", _REACTION_URI, + paths={"file_token": file_token}, + queries=[("file_type", file_type)], + body=body, + ) + + succeeded = code == 0 + if succeeded: + logger.info( + "[Feishu-Comment] Reaction '%s' deleted: file=%s:%s reply=%s", + reaction_type, file_type, file_token, reply_id, + ) + else: + logger.warning( + "[Feishu-Comment] Reaction API failed: code=%s msg=%s " + "file=%s:%s reply=%s", + code, msg, file_type, file_token, reply_id, + ) + return succeeded +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/google_chat/oauth.py b/plugins/platforms/google_chat/oauth.py index 35cd47bf09..1be1399a22 100644 --- a/plugins/platforms/google_chat/oauth.py +++ b/plugins/platforms/google_chat/oauth.py @@ -445,3 +445,25 @@ def main() -> None: if __name__ == "__main__": main() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import secrets # noqa: F401,E402 +import subprocess # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'atomic_replace': ('utils', 'atomic_replace'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/irc/adapter.py b/plugins/platforms/irc/adapter.py index 6435c301b0..8413ff34eb 100644 --- a/plugins/platforms/irc/adapter.py +++ b/plugins/platforms/irc/adapter.py @@ -624,3 +624,11 @@ def register(ctx): "line (long messages are automatically split). In channels, users " "address you by prefixing your nick. Keep responses concise and " "conversational.")) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/line/adapter.py b/plugins/platforms/line/adapter.py index 462e6bab5d..42237b963b 100644 --- a/plugins/platforms/line/adapter.py +++ b/plugins/platforms/line/adapter.py @@ -973,3 +973,11 @@ def register(ctx) -> None: "requires LINE_PUBLIC_URL configured to a publicly reachable HTTPS " "host. Slow responses surface a 'Get answer' button the user taps " "to fetch the reply via a fresh free token.")) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from dataclasses import field # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/matrix/adapter.py b/plugins/platforms/matrix/adapter.py index d97661413d..6ba4a36cda 100644 --- a/plugins/platforms/matrix/adapter.py +++ b/plugins/platforms/matrix/adapter.py @@ -3010,3 +3010,43 @@ def register(ctx) -> None: allow_all_env="MATRIX_ALLOW_ALL_USERS", cron_deliver_env_var="MATRIX_HOME_ROOM", standalone_sender_fn=_standalone_send, max_message_length=DEFAULT_MAX_MESSAGE_LENGTH, emoji="🔐", allow_update_command=True) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +_MATRIX_CAPABILITIES: Dict[str, str] = { + "text": "yes", + "threads": "yes", + "reactions": "yes", + "approvals": "yes", + "model picker": "yes", + "thinking panes": "yes", + "images": "yes", + "multiple images": "yes", + "files": "yes", + "voice/audio": "yes", + "video": "yes", + "E2EE": "off / optional / required", + "diagnostics": "yes", +} + +def get_matrix_capabilities() -> Dict[str, str]: + """Return Matrix gateway capabilities for docs and release checks.""" + return dict(_MATRIX_CAPABILITIES) + + +_PLUGIN_COMPAT_LAZY = { + 'MAX_MESSAGE_LENGTH': ('gateway.platforms.signal', 'MAX_MESSAGE_LENGTH'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/ntfy/adapter.py b/plugins/platforms/ntfy/adapter.py index 7eb318529d..db7d12ea24 100644 --- a/plugins/platforms/ntfy/adapter.py +++ b/plugins/platforms/ntfy/adapter.py @@ -397,3 +397,11 @@ def register(ctx) -> None: "Keep responses concise; ntfy is a push notification service " "with a 4096-character per-message limit." )) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/photon/adapter.py b/plugins/platforms/photon/adapter.py index 3c90c6f97a..faf344c4f7 100644 --- a/plugins/platforms/photon/adapter.py +++ b/plugins/platforms/photon/adapter.py @@ -1614,3 +1614,24 @@ def register(ctx) -> None: ctx.register_cli_command( name="photon", help="Set up and manage the Photon iMessage integration", setup_fn=_cli.register_cli, handler_fn=_cli.dispatch) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'ProcessingOutcome': ('gateway.platforms.base', 'ProcessingOutcome'), + 'resolve_sidecar_dir': ('plugins.platforms.photon.sidecar_paths', 'resolve_sidecar_dir'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/photon/auth.py b/plugins/platforms/photon/auth.py index d402048e7a..198bfa5b43 100644 --- a/plugins/platforms/photon/auth.py +++ b/plugins/platforms/photon/auth.py @@ -713,3 +713,51 @@ def print_credential_summary(emit: Any = print) -> None: " my number : " + (phone if phone else "✗ missing (run `hermes photon setup --phone ...`)"), " assigned number : " + (assigned if assigned else "✗ missing (run `hermes photon setup`)")] emit("\n".join(rows)) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def credential_summary() -> Dict[str, str]: + """Return a fully pre-formatted credential status dict (no raw secrets).""" + def _present_token() -> str: + return ( + "✓ stored" if load_photon_token() + else "✗ missing (run `hermes photon setup`)" + ) + + def _present_project_id() -> str: + sid, _sec = load_project_credentials() + return sid or "✗ missing" + + def _present_secret() -> str: + _sid, sec = load_project_credentials() + return "✓ stored" if sec else "✗ missing" + + def _present_phone() -> str: + phone, _assigned = load_user_numbers() + return phone or "✗ missing (run `hermes photon setup --phone ...`)" + + def _present_assigned_phone() -> str: + _phone, assigned = load_user_numbers() + return assigned or "✗ missing (run `hermes photon setup`)" + + return { + "device_token": _present_token(), + "project_id": _present_project_id(), + "project_key": _present_secret(), + "phone_number": _present_phone(), + "assigned_phone_number": _present_assigned_phone(), + } + +def get_session(token: str) -> Dict[str, Any]: + """GET ``/api/auth/get-session`` — confirm the token + fetch the user.""" + if httpx is None: + raise RuntimeError("httpx is required for Photon") + url = f"{_dashboard_host()}/api/auth/get-session" + resp = httpx.get(url, headers=_bearer(token), timeout=30.0) + resp.raise_for_status() + return resp.json() or {} +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/photon/cli.py b/plugins/platforms/photon/cli.py index 8f704d6053..9f78b80650 100644 --- a/plugins/platforms/photon/cli.py +++ b/plugins/platforms/photon/cli.py @@ -343,3 +343,24 @@ def _prompt(prompt: str, *, secret: bool = False) -> str: except (KeyboardInterrupt, EOFError): print() return "" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import Path # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'resolve_sidecar_dir': ('plugins.platforms.photon.sidecar_paths', 'resolve_sidecar_dir'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/raft/adapter.py b/plugins/platforms/raft/adapter.py index 617a4e07fc..9f38281eb9 100644 --- a/plugins/platforms/raft/adapter.py +++ b/plugins/platforms/raft/adapter.py @@ -571,3 +571,11 @@ def register(ctx) -> None: ("post_llm_call", _on_post_llm_call), ("on_session_end", _on_session_end), ("on_session_finalize", _on_session_finalize)): ctx.register_hook(hook_name, callback) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import asyncio # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/teams/adapter.py b/plugins/platforms/teams/adapter.py index 48a6295377..7dc0f728b8 100644 --- a/plugins/platforms/teams/adapter.py +++ b/plugins/platforms/teams/adapter.py @@ -801,3 +801,25 @@ def register(ctx) -> None: "markdown — bold (**text**), italic (*text*), and inline code " "(`code`) work, but complex tables or raw HTML do not. Keep " "responses clear and professional.")) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import html # noqa: F401,E402 +from urllib.parse import quote # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'TeamsSummaryWriter': ('plugins.platforms.teams.summary_writer', 'TeamsSummaryWriter'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/telegram/adapter.py b/plugins/platforms/telegram/adapter.py index 36da8a65fb..c6406ae721 100644 --- a/plugins/platforms/telegram/adapter.py +++ b/plugins/platforms/telegram/adapter.py @@ -6499,3 +6499,25 @@ def register(ctx) -> None: install_hint="Run `hermes setup` to install Telegram support.", setup_fn=interactive_setup, apply_yaml_config_fn=_apply_yaml_config, allowed_users_env="TELEGRAM_ALLOWED_USERS", allow_all_env="TELEGRAM_ALLOW_ALL_USERS", cron_deliver_env_var="TELEGRAM_HOME_CHANNEL", standalone_sender_fn=_standalone_send, max_message_length=4096, emoji="✈️", allow_update_command=True) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import threading # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'atomic_replace': ('utils', 'atomic_replace'), + 'cache_document_from_bytes': ('gateway.platforms.base', 'cache_document_from_bytes'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/telegram/telegram_ids.py b/plugins/platforms/telegram/telegram_ids.py index 90f4a715e6..97e3d4fdd0 100644 --- a/plugins/platforms/telegram/telegram_ids.py +++ b/plugins/platforms/telegram/telegram_ids.py @@ -29,3 +29,14 @@ def parse_telegram_username_target(target_ref: Any) -> Union[str, None]: """Return the value when it is an ``@username`` target, else ``None``.""" value = str(target_ref).strip() return value if looks_like_telegram_username(value) else None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def telegram_chat_id_key(chat_id: Any) -> str: + """Stable string key for a chat_id (for dict keys / persisted state).""" + return str(normalize_telegram_chat_id(chat_id)) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/platforms/wecom/adapter.py b/plugins/platforms/wecom/adapter.py index b60733b372..289f41dfa8 100644 --- a/plugins/platforms/wecom/adapter.py +++ b/plugins/platforms/wecom/adapter.py @@ -887,3 +887,50 @@ def register(ctx) -> None: required_env=["WECOM_CALLBACK_CORP_ID", "WECOM_CALLBACK_CORP_SECRET"], allowed_users_env="WECOM_CALLBACK_ALLOWED_USERS", allow_all_env="WECOM_CALLBACK_ALLOW_ALL_USERS", **common, ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import Path # noqa: F401,E402 +import base64 # noqa: F401,E402 +from dataclasses import dataclass # noqa: F401,E402 +from collections import deque # noqa: F401,E402 +import hashlib # noqa: F401,E402 +import mimetypes # noqa: F401,E402 +import os # noqa: F401,E402 +from urllib.parse import unquote # noqa: F401,E402 +from urllib.parse import urlparse # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'ABSOLUTE_MAX_BYTES': ('plugins.platforms.wecom.media', 'ABSOLUTE_MAX_BYTES'), + 'APP_CMD_UPLOAD_MEDIA_CHUNK': ('plugins.platforms.wecom.media', 'APP_CMD_UPLOAD_MEDIA_CHUNK'), + 'APP_CMD_UPLOAD_MEDIA_FINISH': ('plugins.platforms.wecom.media', 'APP_CMD_UPLOAD_MEDIA_FINISH'), + 'APP_CMD_UPLOAD_MEDIA_INIT': ('plugins.platforms.wecom.media', 'APP_CMD_UPLOAD_MEDIA_INIT'), + 'FILE_MAX_BYTES': ('plugins.platforms.wecom.media', 'FILE_MAX_BYTES'), + 'IMAGE_MAX_BYTES': ('plugins.platforms.wecom.media', 'IMAGE_MAX_BYTES'), + 'MAX_INTERMEDIATE_FRAMES': ('plugins.platforms.wecom.streaming', 'MAX_INTERMEDIATE_FRAMES'), + 'MAX_UPLOAD_CHUNKS': ('plugins.platforms.wecom.media', 'MAX_UPLOAD_CHUNKS'), + 'ReplyFrame': ('plugins.platforms.wecom.streaming', 'ReplyFrame'), + 'STREAM_EXPIRED_ERRCODE': ('plugins.platforms.wecom.streaming', 'STREAM_EXPIRED_ERRCODE'), + 'STREAM_REQUEST_EXPIRED_ERRCODE': ('plugins.platforms.wecom.streaming', 'STREAM_REQUEST_EXPIRED_ERRCODE'), + 'STREAM_VERSION_CONFLICT_ERRCODE': ('plugins.platforms.wecom.streaming', 'STREAM_VERSION_CONFLICT_ERRCODE'), + 'UPLOAD_CHUNK_SIZE': ('plugins.platforms.wecom.media', 'UPLOAD_CHUNK_SIZE'), + 'VIDEO_MAX_BYTES': ('plugins.platforms.wecom.media', 'VIDEO_MAX_BYTES'), + 'VOICE_MAX_BYTES': ('plugins.platforms.wecom.media', 'VOICE_MAX_BYTES'), + 'VOICE_SUPPORTED_MIMES': ('plugins.platforms.wecom.media', 'VOICE_SUPPORTED_MIMES'), + 'WeComStreamExpiredError': ('plugins.platforms.wecom.streaming', 'WeComStreamExpiredError'), + 'cache_document_from_bytes_async': ('gateway.platforms.base', 'cache_document_from_bytes_async'), + 'cache_image_from_bytes_async': ('gateway.platforms.base', 'cache_image_from_bytes_async'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/spotify/__init__.py b/plugins/spotify/__init__.py index 230367c619..cd01225088 100644 --- a/plugins/spotify/__init__.py +++ b/plugins/spotify/__init__.py @@ -29,3 +29,29 @@ def register(ctx) -> None: """Register all Spotify tools. Called once by the plugin loader.""" for name, schema, handler, emoji in _TOOLS: ctx.register_tool(name=name, toolset="spotify", schema=schema, handler=handler, check_fn=_t._check_spotify_available, emoji=emoji) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'SPOTIFY_ALBUMS_SCHEMA': ('plugins.spotify.tools', 'SPOTIFY_ALBUMS_SCHEMA'), + 'SPOTIFY_DEVICES_SCHEMA': ('plugins.spotify.tools', 'SPOTIFY_DEVICES_SCHEMA'), + 'SPOTIFY_LIBRARY_SCHEMA': ('plugins.spotify.tools', 'SPOTIFY_LIBRARY_SCHEMA'), + 'SPOTIFY_PLAYBACK_SCHEMA': ('plugins.spotify.tools', 'SPOTIFY_PLAYBACK_SCHEMA'), + 'SPOTIFY_PLAYLISTS_SCHEMA': ('plugins.spotify.tools', 'SPOTIFY_PLAYLISTS_SCHEMA'), + 'SPOTIFY_QUEUE_SCHEMA': ('plugins.spotify.tools', 'SPOTIFY_QUEUE_SCHEMA'), + 'SPOTIFY_SEARCH_SCHEMA': ('plugins.spotify.tools', 'SPOTIFY_SEARCH_SCHEMA'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/spotify/client.py b/plugins/spotify/client.py index 50262abc04..b747d95324 100644 --- a/plugins/spotify/client.py +++ b/plugins/spotify/client.py @@ -159,3 +159,24 @@ def normalize_spotify_uris(values: Iterable[str], expected_type: Optional[str] = if not uris: raise SpotifyError("At least one Spotify item is required.") return uris + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'compact_json': ('gateway.hosted_rooms_common', 'compact_json'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/spotify/tools.py b/plugins/spotify/tools.py index f072f2694c..af803b3093 100644 --- a/plugins/spotify/tools.py +++ b/plugins/spotify/tools.py @@ -314,3 +314,24 @@ SPOTIFY_LIBRARY_SCHEMA = _schema("spotify_library", "List, save, or remove the u "action": _enum("list", "save", "remove"), "limit": _INT, "offset": _INT, "market": COMMON_STRING, "uris": _STR_ARRAY, "ids": _STR_ARRAY, "items": _STR_ARRAY, }, ("kind", "action")) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'SpotifyAPIError': ('plugins.spotify.client', 'SpotifyAPIError'), + 'SpotifyAuthRequiredError': ('plugins.spotify.client', 'SpotifyAuthRequiredError'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/teams_pipeline/cli.py b/plugins/teams_pipeline/cli.py index c844e9c25d..b45c20e763 100644 --- a/plugins/teams_pipeline/cli.py +++ b/plugins/teams_pipeline/cli.py @@ -314,3 +314,27 @@ _REQUIRED_ARGS: dict[Callable[[Any], None], tuple[tuple[str, ...], str]] = { _cmd_run: (("job_id",), "job_id is required"), _cmd_renew_subscription: (("subscription_id", "expiration"), "subscription_id and --expiration are required"), _cmd_delete_subscription: (("subscription_id",), "subscription_id is required")} + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import Path # noqa: F401,E402 +from datetime import datetime # noqa: F401,E402 +from datetime import timedelta # noqa: F401,E402 +from datetime import timezone # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'GraphSubscription': ('plugins.teams_pipeline.models', 'GraphSubscription'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/teams_pipeline/meetings.py b/plugins/teams_pipeline/meetings.py index 114f1849fe..6599da48be 100644 --- a/plugins/teams_pipeline/meetings.py +++ b/plugins/teams_pipeline/meetings.py @@ -248,3 +248,48 @@ async def enrich_meeting_with_call_record( artifact_type="call_record", artifact_id=str(payload["id"]), display_name=payload.get("type") or "call_record", source_url=payload.get("webUrl"), created_at=payload.get("startDateTime"), available_at=payload.get("endDateTime"), metadata={"call_record": payload, "metrics": metrics}) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +async def fetch_call_record_artifact( + client: MicrosoftGraphClient, + *, + call_record_id: str, + allow_permission_errors: bool = True, +) -> MeetingArtifact | None: + try: + payload = await client.get_json(f"/communications/callRecords/{quote(call_record_id, safe='')}") + except MicrosoftGraphAPIError as exc: + if exc.status_code in {401, 403} and allow_permission_errors: + return None + if exc.status_code == 404: + return None + raise _wrap_graph_error(exc, missing_message=f"Call record not found: {call_record_id}") from exc + + if not isinstance(payload, dict) or not payload.get("id"): + return None + + metrics = { + "version": payload.get("version"), + "modalities": payload.get("modalities"), + "participant_count": len(payload.get("participants") or []), + "organizer": _parse_organizer_user_id(payload), + } + sessions = payload.get("sessions") or [] + if sessions: + metrics["session_count"] = len(sessions) + + return MeetingArtifact( + artifact_type="call_record", + artifact_id=str(payload["id"]), + display_name=payload.get("type") or "call_record", + source_url=payload.get("webUrl"), + created_at=payload.get("startDateTime"), + available_at=payload.get("endDateTime"), + metadata={"call_record": payload, "metrics": metrics}, + ) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/teams_pipeline/pipeline.py b/plugins/teams_pipeline/pipeline.py index 0b370a1780..aab24a973c 100644 --- a/plugins/teams_pipeline/pipeline.py +++ b/plugins/teams_pipeline/pipeline.py @@ -496,3 +496,11 @@ def _render_summary_markdown(payload: TeamsMeetingSummaryPayload) -> str: lines += ["", f"## {heading}", body or ("No summary available." if heading == "Summary" else "- None")] lines += ["", f"Confidence: {payload.confidence or 'unknown'}", payload.confidence_notes or ""] return "\n".join(lines).strip() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/teams_pipeline/subscriptions.py b/plugins/teams_pipeline/subscriptions.py index 22ffc5f25f..745acda3f8 100644 --- a/plugins/teams_pipeline/subscriptions.py +++ b/plugins/teams_pipeline/subscriptions.py @@ -120,3 +120,30 @@ async def maintain_graph_subscriptions( "threshold_hours": threshold_hours, "extend_hours": extend_hours, "candidates": candidates, "renewed": renewed, "skipped": skipped, } + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from plugins.teams_pipeline.store import resolve_teams_pipeline_store_path # noqa: F401,E402 + +def resolve_store_path(path: str | None) -> str: + return str(resolve_teams_pipeline_store_path(path)) + +def build_store(path: str | None = None) -> TeamsPipelineStore: + return TeamsPipelineStore(resolve_store_path(path)) + + +_PLUGIN_COMPAT_LAZY = { + 'resolve_teams_pipeline_store_path': ('plugins.teams_pipeline.store', 'resolve_teams_pipeline_store_path'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/video_gen/fal/__init__.py b/plugins/video_gen/fal/__init__.py index bc9e2a7f74..9760891966 100644 --- a/plugins/video_gen/fal/__init__.py +++ b/plugins/video_gen/fal/__init__.py @@ -364,3 +364,11 @@ class FALVideoGenProvider(VideoGenProvider): def register(ctx) -> None: """Plugin entry point — wire ``FALVideoGenProvider`` into the registry.""" ctx.register_video_gen_provider(FALVideoGenProvider()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/video_gen/xai/__init__.py b/plugins/video_gen/xai/__init__.py index 7e59fe66f8..8365116bbc 100644 --- a/plugins/video_gen/xai/__init__.py +++ b/plugins/video_gen/xai/__init__.py @@ -316,3 +316,63 @@ async def _submit_xai_video_payload(api_key: str, base_url: str, endpoint: str, def register(ctx) -> None: """Plugin entry point — wire ``XAIVideoGenProvider`` into the registry.""" ctx.register_video_gen_provider(XAIVideoGenProvider()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def run_xai_video_generation( + *, + prompt: str, + model: Optional[str], + explicit_model: bool, + image_url: Optional[str], + reference_image_urls: Optional[List[str]], + duration: Optional[int], + aspect_ratio: str, + resolution: str, +) -> Dict[str, Any]: + return _run_xai_video_coroutine( + _generate_xai_video_async( + prompt=prompt, + model=model, + explicit_model=explicit_model, + image_url=image_url, + reference_image_urls=reference_image_urls, + duration=duration, + aspect_ratio=aspect_ratio, + resolution=resolution, + ), + operation_label="generation", + model=model, + prompt=prompt, + aspect_ratio=aspect_ratio, + ) + +def _run_xai_video_coroutine( + coro, + *, + operation_label: str, + model: Optional[str], + prompt: str, + aspect_ratio: str, +) -> Dict[str, Any]: + try: + loop = asyncio.new_event_loop() + try: + return loop.run_until_complete(coro) + finally: + loop.close() + except Exception as exc: + logger.warning("xAI video %s unexpected failure: %s", operation_label, exc, exc_info=True) + return error_response( + error=f"xAI video {operation_label} failed: {exc}", + error_type="api_error", + provider="xai", + model=model or DEFAULT_MODEL, + prompt=prompt, + aspect_ratio=aspect_ratio, + ) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/web/brave_free/provider.py b/plugins/web/brave_free/provider.py index 924763d4ba..f03488951f 100644 --- a/plugins/web/brave_free/provider.py +++ b/plugins/web/brave_free/provider.py @@ -46,3 +46,24 @@ class BraveFreeWebSearchProvider(BaseWebSearchProvider): "Brave Search (Free)", "free", "Free-tier API key — 2k queries/mo, search only.", "BRAVE_SEARCH_API_KEY", "Brave Search API key (free tier)", "https://brave.com/search/api/", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'WebSearchProvider': ('agent.web_search_provider', 'WebSearchProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/web/ddgs/provider.py b/plugins/web/ddgs/provider.py index 48d7050854..68acbb3add 100644 --- a/plugins/web/ddgs/provider.py +++ b/plugins/web/ddgs/provider.py @@ -217,3 +217,23 @@ class DDGSWebSearchProvider(BaseWebSearchProvider): "DuckDuckGo (ddgs)", "free · no key · search only", "Search via the ddgs Python package — no API key (pair with any extract provider)", post_setup="ddgs", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'WebSearchProvider': ('agent.web_search_provider', 'WebSearchProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/web/exa/provider.py b/plugins/web/exa/provider.py index 982598fc19..f4340fff72 100644 --- a/plugins/web/exa/provider.py +++ b/plugins/web/exa/provider.py @@ -67,3 +67,24 @@ class ExaWebSearchProvider(BaseWebSearchProvider): free_tag="Semantic + neural web search with content extraction on Exa's anonymous free tier. Rate-limited under burst load.", paid_tag="Semantic + neural web search with content extraction via the Exa SDK. Unthrottled, guaranteed service.", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'WebSearchProvider': ('agent.web_search_provider', 'WebSearchProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/web/firecrawl/provider.py b/plugins/web/firecrawl/provider.py index 25f024eb20..1f338ec740 100644 --- a/plugins/web/firecrawl/provider.py +++ b/plugins/web/firecrawl/provider.py @@ -322,3 +322,26 @@ class FirecrawlWebSearchProvider(BaseWebSearchProvider): "Full search + extract; supports keyless cloud, direct API, and Nous tool-gateway routing.", "FIRECRAWL_API_KEY", "Firecrawl API key (optional; blank = keyless cloud or self-hosted)", "https://docs.firecrawl.dev/introduction", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import NoReturn # noqa: F401,E402 +from typing import TYPE_CHECKING # noqa: F401,E402 +import os # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'WebSearchProvider': ('agent.web_search_provider', 'WebSearchProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/web/keenable/provider.py b/plugins/web/keenable/provider.py index df704cb84a..324d1f873a 100644 --- a/plugins/web/keenable/provider.py +++ b/plugins/web/keenable/provider.py @@ -84,3 +84,23 @@ class KeenableWebSearchProvider(BaseWebSearchProvider): free_tag="Independent web index for AI apps — fast search + page fetch on Keenable's anonymous free tier.", paid_tag="Independent web index for AI apps. Keyed access with higher limits and guaranteed service.", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'WebSearchProvider': ('agent.web_search_provider', 'WebSearchProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/web/parallel/provider.py b/plugins/web/parallel/provider.py index 96a9725f59..2be0b0f83c 100644 --- a/plugins/web/parallel/provider.py +++ b/plugins/web/parallel/provider.py @@ -86,3 +86,23 @@ class ParallelWebSearchProvider(BaseWebSearchProvider): free_tag="Objective-tuned search + page extraction on Parallel's anonymous free tier. Rate-limited under burst load.", paid_tag="Objective-tuned search + parallel page extraction via the Parallel SDK. Unthrottled, guaranteed service.", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'WebSearchProvider': ('agent.web_search_provider', 'WebSearchProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/web/searxng/provider.py b/plugins/web/searxng/provider.py index 14f29a4630..187e8598b3 100644 --- a/plugins/web/searxng/provider.py +++ b/plugins/web/searxng/provider.py @@ -43,3 +43,24 @@ class SearXNGWebSearchProvider(BaseWebSearchProvider): "SearXNG", "free · self-hosted", "Free, privacy-respecting metasearch. Point SEARXNG_URL at your instance.", "SEARXNG_URL", "SearXNG instance URL (e.g. http://localhost:8080)", "https://searx.space/", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'WebSearchProvider': ('agent.web_search_provider', 'WebSearchProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/web/tavily/provider.py b/plugins/web/tavily/provider.py index db31fc8566..6e689b450b 100644 --- a/plugins/web/tavily/provider.py +++ b/plugins/web/tavily/provider.py @@ -122,3 +122,23 @@ class TavilyWebSearchProvider(BaseWebSearchProvider): "Tavily", "free · key optional", "Search + extract. Opt-in keyless; set TAVILY_API_KEY for higher limits.", "TAVILY_API_KEY", "Tavily API key (optional — keyless works when Tavily is selected)", "https://app.tavily.com/home", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'WebSearchProvider': ('agent.web_search_provider', 'WebSearchProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/plugins/web/xai/provider.py b/plugins/web/xai/provider.py index 18b12fc09d..30f68bb381 100644 --- a/plugins/web/xai/provider.py +++ b/plugins/web/xai/provider.py @@ -258,3 +258,23 @@ class XAIWebSearchProvider(BaseWebSearchProvider): "xAI Web Search (Grok)", "paid", "Agentic web search via Grok's web_search tool — uses xAI Grok OAuth or XAI_API_KEY.", post_setup="xai_grok", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'WebSearchProvider': ('agent.web_search_provider', 'WebSearchProvider'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/providers/__init__.py b/providers/__init__.py index 2a4ee8c536..c198273e9b 100644 --- a/providers/__init__.py +++ b/providers/__init__.py @@ -412,3 +412,23 @@ def _discover_providers() -> None: # (Pip entry-point providers are discovered in step 0, before the # filesystem plugins, so first-party profiles always win on name # collision — see _discover_entry_point_providers.) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'OMIT_TEMPERATURE': ('providers.base', 'OMIT_TEMPERATURE'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/run_agent.py b/run_agent.py index d64048251d..a3db668990 100644 --- a/run_agent.py +++ b/run_agent.py @@ -1502,3 +1502,52 @@ def main( if __name__ == "__main__": import fire fire.Fire(main) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from types import SimpleNamespace # noqa: F401,E402 +import asyncio # noqa: F401,E402 +import base64 # noqa: F401,E402 +import copy # noqa: F401,E402 +import hashlib # noqa: F401,E402 +import tempfile # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'COMPRESSED_SUMMARY_METADATA_KEY': ('agent.context_compressor', 'COMPRESSED_SUMMARY_METADATA_KEY'), + 'ContextCompressor': ('agent.context_compressor', 'ContextCompressor'), + 'DEFAULT_AGENT_IDENTITY': ('agent.prompt_builder', 'DEFAULT_AGENT_IDENTITY'), + 'FailoverReason': ('agent.error_classifier', 'FailoverReason'), + 'OpenAI': ('agent.process_bootstrap', 'OpenAI'), + 'atomic_json_write': ('utils', 'atomic_json_write'), + 'build_context_files_prompt': ('agent.prompt_builder', 'build_context_files_prompt'), + 'build_environment_hints': ('agent.prompt_builder', 'build_environment_hints'), + 'build_skills_system_prompt': ('agent.prompt_builder', 'build_skills_system_prompt'), + 'check_toolset_requirements': ('model_tools', 'check_toolset_requirements'), + 'convert_scratchpad_to_think': ('agent.trajectory', 'convert_scratchpad_to_think'), + 'estimate_request_tokens_rough': ('agent.model_metadata', 'estimate_request_tokens_rough'), + 'file_mutation_result_landed': ('agent.tool_result_classification', 'file_mutation_result_landed'), + 'flatten_message_text': ('agent.message_content', 'flatten_message_text'), + 'get_tool_definitions': ('model_tools', 'get_tool_definitions'), + 'handle_function_call': ('model_tools', 'handle_function_call'), + 'is_truthy_value': ('utils', 'is_truthy_value'), + 'jittered_backoff': ('agent.retry_utils', 'jittered_backoff'), + 'load_soul_md': ('agent.prompt_builder', 'load_soul_md'), + 'normalize_usage': ('agent.usage_pricing', 'normalize_usage'), + 'redact_sensitive_text': ('agent.redact', 'redact_sensitive_text'), + 'request_hard_interrupt': ('agent.interrupt_compat', 'request_hard_interrupt'), + 'sanitize_context': ('agent.memory_manager', 'sanitize_context'), + 'user_originated_turn_view': ('agent.context_compressor', 'user_originated_turn_view'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/scripts/check_compat_pointers.py b/scripts/check_compat_pointers.py new file mode 100644 index 0000000000..001d753141 --- /dev/null +++ b/scripts/check_compat_pointers.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""Fail when in-tree code depends on a plugin-compat pointer. + +``compat_manifest.json`` lists every name the Sep 2026 decomposition kept importable from its OLD +module purely for external plugins (the `PLUGIN-COMPAT` blocks). Those blocks are removed on a +schedule by reverting the commit that added them, so nothing inside this repository may depend on +them — otherwise the revert breaks the tree. This check walks every first-party Python file (source +AND tests) and flags: + + from import # direct import through the old path + import ; . # attribute access through the old path + patch(".") / monkeypatch.setattr(, "") + +Exit 1 with a file:line list on any hit. Run: python scripts/check_compat_pointers.py +""" +from __future__ import annotations + +import ast +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +MANIFEST = ROOT / "compat_manifest.json" +SKIP_DIRS = {".git", "node_modules", "website", "skills", "optional-skills", "apps", "evals", "build", "MagicMock", ".worktrees", "__pycache__"} + + +def _py_files(): + for p in ROOT.rglob("*.py"): + parts = p.relative_to(ROOT).parts + if parts[0] in SKIP_DIRS or p.name == "check_compat_pointers.py": + continue + yield p + + +def main() -> int: + if not MANIFEST.exists(): + print("compat_manifest.json missing — nothing to check (compat layer already reverted?)") + return 0 + entries = json.loads(MANIFEST.read_text(encoding="utf-8"))["entries"] + compat: dict[str, set[str]] = {} + for e in entries: + compat.setdefault(e["facade"], set()).add(e["name"]) + facades = set(compat) + hits: list[str] = [] + str_pat = re.compile(r"""["']((?:[A-Za-z_][\w]*\.)+[A-Za-z_]\w*)["']""") + for path in _py_files(): + rel = path.relative_to(ROOT) + try: + src = path.read_text(encoding="utf-8", errors="ignore") + tree = ast.parse(src) + except SyntaxError: + continue + # module-level facade import aliases in this file: alias -> facade + aliases: dict[str, str] = {} + for node in ast.walk(tree): + if isinstance(node, ast.ImportFrom) and node.module in facades and node.level == 0: + bad = [a.name for a in node.names if a.name in compat[node.module]] + for b in bad: + hits.append(f"{rel}:{node.lineno}: from {node.module} import {b}") + elif isinstance(node, ast.Import): + for a in node.names: + if a.name in facades: + aliases[a.asname or a.name] = a.name + elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module: + for a in node.names: + full = f"{node.module}.{a.name}" + if full in facades: + aliases[a.asname or a.name] = full + for node in ast.walk(tree): + if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name): + fac = aliases.get(node.value.id) + if fac and node.attr in compat[fac]: + hits.append(f"{rel}:{node.lineno}: {node.value.id}.{node.attr} (via {fac})") + elif isinstance(node, ast.Constant) and isinstance(node.value, str): + m = str_pat.fullmatch(node.value.strip()) + if m: + dotted = m.group(1); fac, _, name = dotted.rpartition(".") + if fac in facades and name in compat[fac]: + hits.append(f"{rel}:{node.lineno}: \"{dotted}\" (string patch target)") + if hits: + print("❌ in-tree code depends on plugin-compat pointers (scheduled for removal):") + for h in sorted(set(hits)): + print(" " + h) + print(f"\n{len(set(hits))} site(s). Import from the defining module instead (see COMPAT_MANIFEST.md).") + return 1 + print(f"✅ no in-tree dependency on the {len(entries)} plugin-compat pointers") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/apply_layout_tool.py b/tools/apply_layout_tool.py index 167bca418c..8395a2d480 100644 --- a/tools/apply_layout_tool.py +++ b/tools/apply_layout_tool.py @@ -50,3 +50,11 @@ registry.register( handler=lambda args, **kw: apply_layout_tool(preset=args.get("preset", "")), emoji="🧱", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/approval.py b/tools/approval.py index ff53ea9c1d..9e1570caa4 100644 --- a/tools/approval.py +++ b/tools/approval.py @@ -1109,3 +1109,51 @@ def check_execute_code_guard(code: str, env_type: str, has_host_access: bool = F # Load permanent allowlist from config on module import load_permanent_allowlist() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import contextlib # noqa: F401,E402 +import contextvars # noqa: F401,E402 +import fnmatch # noqa: F401,E402 +import functools # noqa: F401,E402 +import re # noqa: F401,E402 +import shlex # noqa: F401,E402 +import sys # noqa: F401,E402 +import tempfile # noqa: F401,E402 +import time # noqa: F401,E402 +import unicodedata # noqa: F401,E402 +import uuid # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'DANGEROUS_PATTERNS': ('tools.approval_detection', 'DANGEROUS_PATTERNS'), + 'DANGEROUS_PATTERNS_COMPILED': ('tools.approval_detection', 'DANGEROUS_PATTERNS_COMPILED'), + 'HARDLINE_PATTERNS': ('tools.approval_detection', 'HARDLINE_PATTERNS'), + 'HARDLINE_PATTERNS_COMPILED': ('tools.approval_detection', 'HARDLINE_PATTERNS_COMPILED'), + 'HUMAN_WAIT_MARGIN_S': ('tools.approval_human_wait', 'HUMAN_WAIT_MARGIN_S'), + 'cfg_get': ('hermes_cli.config', 'cfg_get'), + 'get_plugin_manager': ('tools.approval_prompt', 'get_plugin_manager'), + 'human_wait_ceiling': ('tools.approval_human_wait', 'human_wait_ceiling'), + 'human_wait_seconds': ('tools.approval_human_wait', 'human_wait_seconds'), + 'human_wait_window': ('tools.approval_human_wait', 'human_wait_window'), + 'is_interrupted': ('tools.interrupt', 'is_interrupted'), + 'request_elicitation_consent': ('tools.approval_prompt', 'request_elicitation_consent'), + 'reset_current_observability_context': ('tools.approval_context', 'reset_current_observability_context'), + 'reset_current_session_key': ('tools.approval_context', 'reset_current_session_key'), + 'reset_hermes_interactive_context': ('tools.approval_context', 'reset_hermes_interactive_context'), + 'set_current_observability_context': ('tools.approval_context', 'set_current_observability_context'), + 'set_current_session_key': ('tools.approval_context', 'set_current_session_key'), + 'set_hermes_interactive_context': ('tools.approval_context', 'set_hermes_interactive_context'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/async_delegation.py b/tools/async_delegation.py index 4fef9cc7e0..1ef7fbdc27 100644 --- a/tools/async_delegation.py +++ b/tools/async_delegation.py @@ -892,3 +892,23 @@ def _reset_for_tests() -> None: thread.join(timeout=2) with _records_lock: _records.clear() + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def active_for_session(origin_ui_session_id: str) -> int: + """Number of live async delegations owned by one UI session.""" + if not origin_ui_session_id: + return 0 + with _records_lock: + return sum( + 1 + for r in _records.values() + if r.get("status") in {"running", "stalling", "finalizing"} + and str(r.get("origin_ui_session_id") or "") + == origin_ui_session_id + ) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/browser_camofox_state.py b/tools/browser_camofox_state.py index f8517259ee..7f2a4c881e 100644 --- a/tools/browser_camofox_state.py +++ b/tools/browser_camofox_state.py @@ -25,3 +25,14 @@ def get_camofox_identity(task_id: Optional[str] = None) -> Dict[str, str]: user_digest = uuid.uuid5(uuid.NAMESPACE_URL, f"camofox-user:{scope_root}").hex[:10] session_digest = uuid.uuid5(uuid.NAMESPACE_URL, f"camofox-session:{scope_root}:{task_id or 'default'}").hex[:16] return {"user_id": f"hermes_{user_digest}", "session_key": f"task_{session_digest}"} + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +CAMOFOX_STATE_DIR_NAME = "browser_auth" + +CAMOFOX_STATE_SUBDIR = "camofox" +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/browser_dialog_tool.py b/tools/browser_dialog_tool.py index ecd6dffa1e..07873f71d3 100644 --- a/tools/browser_dialog_tool.py +++ b/tools/browser_dialog_tool.py @@ -107,3 +107,24 @@ registry.register( check_fn=_browser_dialog_check, emoji="💬", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import logging # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'logger': ('tools.approval', 'logger'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/browser_supervisor.py b/tools/browser_supervisor.py index 362fb42bb1..9a612cf319 100644 --- a/tools/browser_supervisor.py +++ b/tools/browser_supervisor.py @@ -472,3 +472,40 @@ SUPERVISOR_REGISTRY = _SupervisorRegistry() __all__ = ["CDPSupervisor", "SUPERVISOR_REGISTRY", "SupervisorSnapshot", "_SupervisorRegistry"] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +CONSOLE_HISTORY_MAX = 50 + +@dataclass +class ConsoleEvent: + """Ring buffer entry for console + exception traffic.""" + + ts: float + level: str # "log" | "error" | "warning" | "exception" + text: str + url: Optional[str] = None + + +_PLUGIN_COMPAT_LAZY = { + 'DIALOG_BRIDGE_HOST': ('tools.browser_supervisor_dialogs', 'DIALOG_BRIDGE_HOST'), + 'DIALOG_BRIDGE_URL_PATTERN': ('tools.browser_supervisor_dialogs', 'DIALOG_BRIDGE_URL_PATTERN'), + 'DIALOG_POLICY_AUTO_ACCEPT': ('tools.browser_supervisor_dialogs', 'DIALOG_POLICY_AUTO_ACCEPT'), + 'DIALOG_POLICY_AUTO_DISMISS': ('tools.browser_supervisor_dialogs', 'DIALOG_POLICY_AUTO_DISMISS'), + 'DIALOG_POLICY_MUST_RESPOND': ('tools.browser_supervisor_dialogs', 'DIALOG_POLICY_MUST_RESPOND'), + 'FRAME_TREE_MAX_ENTRIES': ('tools.browser_supervisor_frames', 'FRAME_TREE_MAX_ENTRIES'), + 'FRAME_TREE_MAX_OOPIF_DEPTH': ('tools.browser_supervisor_frames', 'FRAME_TREE_MAX_OOPIF_DEPTH'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/browser_tool.py b/tools/browser_tool.py index 62607b214a..41a047ffc1 100644 --- a/tools/browser_tool.py +++ b/tools/browser_tool.py @@ -1312,3 +1312,52 @@ for _name, _emoji, _check_fn, _defaults, *_extra in _BROWSER_TOOL_TABLE: registry.register(name=_name, toolset="browser", schema=_BROWSER_SCHEMA_MAP[_name], handler=_routed_handler(_name, _fallback_call(_name, _defaults, *_extra)), check_fn=_check_fn, emoji=_emoji) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import List # noqa: F401,E402 +from typing import Tuple # noqa: F401,E402 +import contextlib # noqa: F401,E402 +from datetime import datetime # noqa: F401,E402 +import functools # noqa: F401,E402 +import re # noqa: F401,E402 +import shutil # noqa: F401,E402 +import signal # noqa: F401,E402 +from datetime import timezone # noqa: F401,E402 + +SNAPSHOT_SUMMARIZE_THRESHOLD = DEFAULT_SNAPSHOT_THRESHOLD + + +_PLUGIN_COMPAT_LAZY = { + 'BrowserUseProvider': ('plugins.browser.browser_use.provider', 'BrowserUseBrowserProvider'), + 'BrowserbaseProvider': ('plugins.browser.browserbase.provider', 'BrowserbaseBrowserProvider'), + 'CloudBrowserProvider': ('agent.browser_provider', 'BrowserProvider'), + 'FirecrawlProvider': ('plugins.browser.firecrawl.provider', 'FirecrawlBrowserProvider'), + 'agent_browser_runnable': ('hermes_constants', 'agent_browser_runnable'), + 'check_browser_requirements': ('tools.browser_tool_install', 'check_browser_requirements'), + 'check_browser_vision_requirements': ('tools.browser_tool_install', 'check_browser_vision_requirements'), + 'cleanup_all_browsers': ('tools.browser_tool_lifecycle', 'cleanup_all_browsers'), + 'cleanup_browser': ('tools.browser_tool_lifecycle', 'cleanup_browser'), + 'get_hermes_home_override': ('hermes_constants', 'get_hermes_home_override'), + 'hermes_home_key': ('hermes_constants', 'hermes_home_key'), + 'is_truthy_value': ('utils', 'is_truthy_value'), + 'lightpanda_engine_status': ('tools.browser_tool_lightpanda_fallback', 'lightpanda_engine_status'), + 'node_tool_runnable': ('hermes_constants', 'node_tool_runnable'), + 'normalize_browser_cloud_provider': ('tools.tool_backend_helpers', 'normalize_browser_cloud_provider'), + 'reset_hermes_home_override': ('hermes_constants', 'reset_hermes_home_override'), + 'set_hermes_home_override': ('hermes_constants', 'set_hermes_home_override'), + 'warm_agent_browser_npx_cache': ('tools.browser_tool_install', 'warm_agent_browser_npx_cache'), + 'windows_hide_flags': ('hermes_cli._subprocess_compat', 'windows_hide_flags'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/clarify_gateway.py b/tools/clarify_gateway.py index dd03b6e185..e33a5ff083 100644 --- a/tools/clarify_gateway.py +++ b/tools/clarify_gateway.py @@ -282,3 +282,27 @@ def get_clarify_timeout() -> int: return resolve_clarify_timeout(load_config() or {}) except Exception: return 3600 + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def get_notify(session_key: str) -> Optional[Callable[[_ClarifyEntry], None]]: + with _lock: + return _notify_cbs.get(session_key) + +def register_notify(session_key: str, cb: Callable[[_ClarifyEntry], None]) -> None: + """Register a per-session notify callback used by ``clarify_callback``.""" + with _lock: + _notify_cbs[session_key] = cb + +def unregister_notify(session_key: str) -> None: + """Drop the per-session notify callback and cancel any pending clarify entries.""" + with _lock: + _notify_cbs.pop(session_key, None) + # Cancel any pending entries so blocked threads unwind when the run + # ends (interrupt, completion, gateway shutdown). + clear_session(session_key) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/close_preview_tool.py b/tools/close_preview_tool.py index 61687c76fd..12095c9231 100644 --- a/tools/close_preview_tool.py +++ b/tools/close_preview_tool.py @@ -13,3 +13,50 @@ def close_preview_tool(url: str = "") -> str: "preview.close", {"url": target}, "Failed to close the preview pane: ", "The preview pane is only available in the Hermes desktop app.", {"success": True, "url": target}, ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 + +CLOSE_PREVIEW_SCHEMA = { + "name": "close_preview", + "description": ( + "Close the preview pane beside the chat in the Hermes desktop app, or one " + "tab inside it. Use this when the user asks to close, hide, or dismiss the " + "preview — e.g. \"close the preview pane\", \"close cnn.com\", \"hide the " + "preview\". Omit url to close the whole pane (every tab). Pass a web URL, " + "localhost address, or file path to close only that tab. Counterpart of " + "open_preview." + ), + "parameters": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": ( + "Optional. The tab to close: a web URL (https://… or a bare " + "domain), a localhost URL, or a file path. Omit to close the " + "whole preview pane." + ), + }, + }, + }, +} + + +_PLUGIN_COMPAT_LAZY = { + 'registry': ('tools.registry', 'registry'), + 'tool_error': ('tools.registry', 'tool_error'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/code_execution_tool.py b/tools/code_execution_tool.py index 26f2ab680f..1896fe2ab1 100644 --- a/tools/code_execution_tool.py +++ b/tools/code_execution_tool.py @@ -902,3 +902,30 @@ registry.register( handler=_execute_code_handler, check_fn=check_sandbox_requirements, emoji="🐍", max_result_size_chars=100_000, ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import platform # noqa: F401,E402 +import socket # noqa: F401,E402 +import sys # noqa: F401,E402 + +DEFAULT_KERNEL_MODE = "session" + +KERNEL_MODES = ("per-call", "session") # legacy compat constant + + +_PLUGIN_COMPAT_LAZY = { + 'thread_scoped_silence': ('agent.thread_scoped_output', 'thread_scoped_silence'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/code_kernel_remote.py b/tools/code_kernel_remote.py index 5f5829efa0..47bd24a878 100644 --- a/tools/code_kernel_remote.py +++ b/tools/code_kernel_remote.py @@ -384,3 +384,11 @@ def _run_attached_cell(kernel: RemoteKernel, key: Tuple, code: str, *, env, task if cell_status == "error" and result["traceback"]: result["error"] = result["traceback"].strip().splitlines()[-1] return result + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import base64 # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/computer_use/__init__.py b/tools/computer_use/__init__.py index 44b5ec7bc0..9fa01d3039 100644 --- a/tools/computer_use/__init__.py +++ b/tools/computer_use/__init__.py @@ -10,3 +10,28 @@ Modules: `tool.py` (handler, approval gate, response shaping), `backend.py` (abs `ComputerUseBackend` + result dataclasses), `cua_backend.py` (default MCP-over-stdio backend + `cua_backend_parse`/`_session`/`_daemon` siblings), `schema.py` (byte-frozen). """ + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from __future__ import annotations # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'check_computer_use_requirements': ('tools.computer_use.tool', 'check_computer_use_requirements'), + 'get_computer_use_schema': ('tools.computer_use.tool', 'get_computer_use_schema'), + 'handle_computer_use': ('tools.computer_use.tool', 'handle_computer_use'), + 'release_computer_use_session': ('tools.computer_use.tool', 'release_computer_use_session'), + 'set_approval_callback': ('tools.computer_use.tool', 'set_approval_callback'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/computer_use/cua_backend.py b/tools/computer_use/cua_backend.py index bcf285bb04..109575b141 100644 --- a/tools/computer_use/cua_backend.py +++ b/tools/computer_use/cua_backend.py @@ -374,3 +374,38 @@ class CuaDriverBackend(_CaptureMixin, _InputMixin, ComputerUseBackend): meta = {k: v for part in (data, structured) if isinstance(part, dict) for k, v in part.items()} return _action_result_from(name, not out["isError"], message, meta, structured, requested_delivery=args.get("delivery_mode")) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import PureWindowsPath # noqa: F401,E402 +from typing import Tuple # noqa: F401,E402 +import asyncio # noqa: F401,E402 +import base64 # noqa: F401,E402 +import concurrent.futures # noqa: F401,E402 +from collections import deque # noqa: F401,E402 +import functools # noqa: F401,E402 +import json # noqa: F401,E402 +import re # noqa: F401,E402 +import shutil # noqa: F401,E402 +import tempfile # noqa: F401,E402 +import time # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'CaptureResult': ('tools.computer_use.backend', 'CaptureResult'), + 'UIElement': ('tools.computer_use.backend', 'UIElement'), + 'cua_driver_install_hint': ('tools.computer_use.cua_backend_driver', 'cua_driver_install_hint'), + 'cua_driver_update_check': ('tools.computer_use.cua_backend_driver', 'cua_driver_update_check'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/computer_use/permissions.py b/tools/computer_use/permissions.py index b1d2e5434d..b4a68acd7a 100644 --- a/tools/computer_use/permissions.py +++ b/tools/computer_use/permissions.py @@ -109,3 +109,11 @@ def request_permissions_grant(driver_cmd: Optional[str] = None) -> int: except Exception as exc: # pragma: no cover - defensive print(f"cua-driver permissions grant failed: {exc}", file=sys.stderr) return 2 + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import List # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/computer_use/tool.py b/tools/computer_use/tool.py index 04401721c8..953bfda4b9 100644 --- a/tools/computer_use/tool.py +++ b/tools/computer_use/tool.py @@ -757,3 +757,11 @@ def check_computer_use_requirements() -> bool: def get_computer_use_schema() -> Dict[str, Any]: from tools.computer_use.schema import COMPUTER_USE_SCHEMA return COMPUTER_USE_SCHEMA + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import struct # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/cronjob_tools.py b/tools/cronjob_tools.py index 685bf9ad95..a728873945 100644 --- a/tools/cronjob_tools.py +++ b/tools/cronjob_tools.py @@ -1026,3 +1026,24 @@ registry.register( check_fn=check_cronjob_requirements, emoji="⏰", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import re # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'effective_job_state': ('cron.jobs', 'effective_job_state'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/delegate_tool.py b/tools/delegate_tool.py index 77b54d38a0..9eb979ded6 100644 --- a/tools/delegate_tool.py +++ b/tools/delegate_tool.py @@ -610,3 +610,39 @@ registry.register( emoji="🔀", dynamic_schema_overrides=_build_dynamic_schema_overrides, ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from concurrent.futures import TimeoutError as FuturesTimeoutError # noqa: F401,E402 +import contextvars # noqa: F401,E402 +import enum # noqa: F401,E402 +import json # noqa: F401,E402 +import os # noqa: F401,E402 +import re # noqa: F401,E402 +import threading # noqa: F401,E402 +from urllib.parse import urlsplit # noqa: F401,E402 +from urllib.parse import urlunsplit # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'DEFAULT_CHILD_TIMEOUT': ('tools.delegate_tool_config', 'DEFAULT_CHILD_TIMEOUT'), + 'DEFAULT_MAX_SUMMARY_CHARS': ('tools.delegate_tool_results', 'DEFAULT_MAX_SUMMARY_CHARS'), + 'DEFAULT_TOOLSETS': ('tools.delegate_tool_toolsets', 'DEFAULT_TOOLSETS'), + 'MAX_DEPTH': ('tools.delegate_tool_config', 'MAX_DEPTH'), + 'TOOLSETS': ('toolsets', 'TOOLSETS'), + 'base_url_hostname': ('utils', 'base_url_hostname'), + 'file_state': ('tools', 'file_state'), + 'request_hard_interrupt': ('agent.interrupt_compat', 'request_hard_interrupt'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/delegation_live_log.py b/tools/delegation_live_log.py index 40965d9764..d2d2e0325e 100644 --- a/tools/delegation_live_log.py +++ b/tools/delegation_live_log.py @@ -302,3 +302,14 @@ def prune_stale_live_dirs(max_age_days: int = LIVE_RETENTION_DAYS) -> int: except OSError: continue return removed + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def new_live_delegation_id() -> str: + """Same shape as async_delegation's ids so the dir name matches the handle.""" + return f"deleg_{uuid.uuid4().hex[:8]}" +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/delegation_output_schema.py b/tools/delegation_output_schema.py index 0e1a55ff9f..ea9833b1d8 100644 --- a/tools/delegation_output_schema.py +++ b/tools/delegation_output_schema.py @@ -105,3 +105,12 @@ def build_retry_message(errors: List[str]) -> str: "validator. Validation errors:\n" f"{error_block}\n\n" "Reply with ONLY the corrected JSON object matching the OUTPUT " "CONTRACT schema from your task context. No prose, no explanations.") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +MAX_SCHEMA_RETRIES = 1 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/discord_tool.py b/tools/discord_tool.py index 7e37899c3a..c5fd69af53 100644 --- a/tools/discord_tool.py +++ b/tools/discord_tool.py @@ -631,3 +631,16 @@ for _name, _actions, _handler in ( handler=lambda args, _h=_handler, **kw: _h(**{"action": "", **args}), check_fn=check_discord_tool_requirements, requires_env=["DISCORD_BOT_TOKEN"]) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import TYPE_CHECKING # noqa: F401,E402 +from typing import Tuple # noqa: F401,E402 + +def get_dynamic_schema() -> Optional[Dict[str, Any]]: + """Backward-compat wrapper — returns core schema.""" + return get_dynamic_schema_core() +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/drive_preview_tool.py b/tools/drive_preview_tool.py index 66cef662fe..436bb8febd 100644 --- a/tools/drive_preview_tool.py +++ b/tools/drive_preview_tool.py @@ -140,3 +140,11 @@ registry.register( **{k: args.get(k) for k in ("ref", "selector", "text", "key", "submit", "amount", "to", "full")}, ), emoji="🖱️") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/env_probe.py b/tools/env_probe.py index 75f7205833..6805eb8078 100644 --- a/tools/env_probe.py +++ b/tools/env_probe.py @@ -245,3 +245,11 @@ def _reset_cache_for_tests() -> None: _PROBE_THREAD = None _PROBE_GEN += 1 _WAIT_ALREADY_TIMED_OUT = False + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import os # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/environments/base.py b/tools/environments/base.py index fbb2a05ad1..014bfd1131 100644 --- a/tools/environments/base.py +++ b/tools/environments/base.py @@ -522,3 +522,31 @@ class BaseEnvironment(ABC): """Transform sudo commands if SUDO_PASSWORD is available.""" from tools.terminal_tool_sudo import _transform_sudo_command return _transform_sudo_command(command) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import IO # noqa: F401,E402 +from typing import Protocol # noqa: F401,E402 +import codecs # noqa: F401,E402 +from collections import deque # noqa: F401,E402 +import re # noqa: F401,E402 +import select # noqa: F401,E402 +import subprocess # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'sanitize_task_id_for_path': ('tools.environments.path_utils', 'sanitize_task_id_for_path'), + 'windows_hide_flags': ('hermes_cli._subprocess_compat', 'windows_hide_flags'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/environments/managed_modal.py b/tools/environments/managed_modal.py index 0fd65dbf53..66936ea097 100644 --- a/tools/environments/managed_modal.py +++ b/tools/environments/managed_modal.py @@ -203,3 +203,11 @@ class ManagedModalEnvironment(BaseEnvironment): pass text = response.text.strip() return f"{prefix}: {text}" if text else f"{prefix}: HTTP {response.status_code}" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from dataclasses import dataclass # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/environments/modal_utils.py b/tools/environments/modal_utils.py new file mode 100644 index 0000000000..771d4d75ee --- /dev/null +++ b/tools/environments/modal_utils.py @@ -0,0 +1,6 @@ +"""PLUGIN-COMPAT stub (revert-scheduled; see COMPAT_MANIFEST.md). + +``tools.environments.modal_utils`` was deleted in the Sep 2026 decomposition (its callers were folded into the +execution backends). Importing it no longer provides anything; this stub exists only so an external +plugin's ``import tools.environments.modal_utils`` does not raise at import time. +""" diff --git a/tools/environments/vercel_sandbox.py b/tools/environments/vercel_sandbox.py index 41c492fa52..fc3d3fe2a2 100644 --- a/tools/environments/vercel_sandbox.py +++ b/tools/environments/vercel_sandbox.py @@ -333,3 +333,11 @@ class VercelSandboxEnvironment(BaseEnvironment): # Always stop the sandbox during cleanup to avoid resource leaks (matches Modal/Daytona). self._stop_sandbox(sandbox) self._close_sandbox_client(sandbox) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from dataclasses import dataclass # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/feishu_doc_tool.py b/tools/feishu_doc_tool.py index 5e0694b949..958ecb3874 100644 --- a/tools/feishu_doc_tool.py +++ b/tools/feishu_doc_tool.py @@ -68,3 +68,26 @@ registry.register( name="feishu_doc_read", toolset="feishu_doc", schema=FEISHU_DOC_READ_SCHEMA, handler=_handle_feishu_doc_read, check_fn=_check_feishu, requires_env=[], is_async=False, description="Read Feishu document content", emoji="\U0001f4c4") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +import logging # noqa: F401,E402 +import threading # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'logger': ('tools.approval', 'logger'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/feishu_drive_tool.py b/tools/feishu_drive_tool.py index e1ba075d54..0ae4da6d6f 100644 --- a/tools/feishu_drive_tool.py +++ b/tools/feishu_drive_tool.py @@ -192,3 +192,12 @@ for _schema, _handler, _desc, _emoji in ( registry.register( name=_schema["name"], toolset="feishu_drive", schema=_schema, handler=_handler, check_fn=_check_feishu, requires_env=[], is_async=False, description=_desc, emoji=_emoji) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +import threading # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/file_operations.py b/tools/file_operations.py index b057cd2911..3fb02295c4 100644 --- a/tools/file_operations.py +++ b/tools/file_operations.py @@ -1374,3 +1374,51 @@ class ShellFileOperations(LintMixin, SearchMixin, FileOperations): f"an unattended privacy prompt: {skipped}. Search a protected " "folder directly when access is intentional.") return result + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Any # noqa: F401,E402 +from typing import ClassVar # noqa: F401,E402 +from typing import List # noqa: F401,E402 +from agent.file_safety import build_write_denied_paths # noqa: F401,E402 +from agent.file_safety import build_write_denied_prefixes # noqa: F401,E402 +from dataclasses import dataclass # noqa: F401,E402 +from dataclasses import field # noqa: F401,E402 +import posixpath # noqa: F401,E402 +import threading # noqa: F401,E402 + +MAX_LINES = 2000 + +MAX_LINE_LENGTH = 2000 + +WRITE_DENIED_PATHS = build_write_denied_paths(_HOME) + +WRITE_DENIED_PREFIXES = build_write_denied_prefixes(_HOME) + + +_PLUGIN_COMPAT_LAZY = { + 'DEFAULT_READ_LIMIT': ('tools.file_operations_common', 'DEFAULT_READ_LIMIT'), + 'DEFAULT_READ_OFFSET': ('tools.file_operations_common', 'DEFAULT_READ_OFFSET'), + 'DEFAULT_SEARCH_LIMIT': ('tools.file_operations_common', 'DEFAULT_SEARCH_LIMIT'), + 'DEFAULT_SEARCH_OFFSET': ('tools.file_operations_common', 'DEFAULT_SEARCH_OFFSET'), + 'LINTERS': ('tools.file_operations_lint', 'LINTERS'), + 'LintResult': ('tools.file_operations_common', 'LintResult'), + 'MAX_FILE_SIZE': ('tools.transcription_common', 'MAX_FILE_SIZE'), + 'SEARCH_PRUNE_DIR_NAMES': ('agent.search_policy', 'SEARCH_PRUNE_DIR_NAMES'), + 'SearchMatch': ('tools.file_operations_common', 'SearchMatch'), + 'build_write_denied_paths': ('agent.file_safety', 'build_write_denied_paths'), + 'build_write_denied_prefixes': ('agent.file_safety', 'build_write_denied_prefixes'), + 'tool_interrupt': ('tools', 'interrupt'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/file_tools.py b/tools/file_tools.py index c2f2624d3b..6264044c8b 100644 --- a/tools/file_tools.py +++ b/tools/file_tools.py @@ -1223,3 +1223,29 @@ def _patch_schema_overrides(): registry.register(name="patch", toolset="file", schema=PATCH_SCHEMA, handler=_handle_patch, check_fn=_check_file_reqs, emoji="🔧", max_result_size_chars=100_000, dynamic_schema_overrides=_patch_schema_overrides) registry.register(name="search_files", toolset="file", schema=SEARCH_FILES_SCHEMA, handler=_handle_search_files, check_fn=_check_file_reqs, emoji="🔎", max_result_size_chars=100_000) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import PurePosixPath # noqa: F401,E402 +import posixpath # noqa: F401,E402 +import sys # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'has_opaque_document_extension': ('tools.binary_extensions', 'has_opaque_document_extension'), + 'is_pdf_path': ('tools.binary_extensions', 'is_pdf_path'), + 'notify_other_tool_call': ('tools.file_tools_read_tracking', 'notify_other_tool_call'), + 'reset_file_dedup': ('tools.file_tools_read_tracking', 'reset_file_dedup'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/focus_pane_tool.py b/tools/focus_pane_tool.py index 2ff5477aa8..696681afb5 100644 --- a/tools/focus_pane_tool.py +++ b/tools/focus_pane_tool.py @@ -46,3 +46,31 @@ registry.register( handler=lambda args, **kw: focus_pane_tool(pane=args.get("pane", "")), emoji="🪟", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 + +FOCUS_PANE_SCHEMA = { + "name": "focus_pane", + "description": ( + "Reveal and focus a Hermes desktop pane when the user asks to see it: " + "chat, files, terminal, review (git diff), or sessions. For URLs/" + "files use the desktop_preview tool instead." + ), + "parameters": { + "type": "object", + "properties": { + "pane": { + "type": "string", + "enum": list(PANES), + "description": "Which pane to reveal.", + }, + }, + "required": ["pane"], + }, +} +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/fuzzy_match.py b/tools/fuzzy_match.py index ab9491e36f..5f08358b98 100644 --- a/tools/fuzzy_match.py +++ b/tools/fuzzy_match.py @@ -585,3 +585,12 @@ def format_no_match_hint(error: Optional[str], match_count: int, return "" hint = find_closest_lines(old_string, content) return "\n\nDid you mean one of these sections?\n" + hint if hint else "" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import List # noqa: F401,E402 +from typing import Tuple # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/image_generation_tool.py b/tools/image_generation_tool.py index 49ff3146e2..853f2e44f9 100644 --- a/tools/image_generation_tool.py +++ b/tools/image_generation_tool.py @@ -857,3 +857,14 @@ registry.register( is_async=False, # sync fal_client API to avoid "Event loop is closed" in gateway emoji="🎨", dynamic_schema_overrides=_build_dynamic_image_schema, ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def is_krea_model(model_id: Optional[str]) -> bool: + """True when ``model_id`` is a native Krea plugin id (``krea-2-*``).""" + return _normalize_krea_model(model_id) is not None +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/lazy_deps.py b/tools/lazy_deps.py index 234d61c79f..ffccf19761 100644 --- a/tools/lazy_deps.py +++ b/tools/lazy_deps.py @@ -726,3 +726,16 @@ def ensure_and_bind(feature: str, importer: Callable[[], dict[str, Any]], target logger.warning("Failed to import feature %r after install: %s", feature, exc) return False return True + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def feature_specs(feature: str) -> tuple[str, ...]: + """Return the registered specs for a feature, or raise KeyError.""" + if feature not in LAZY_DEPS: + raise KeyError(f"Unknown lazy feature: {feature!r}") + return LAZY_DEPS[feature] +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/managed_tool_gateway.py b/tools/managed_tool_gateway.py index b53a25f935..a7504d1d4c 100644 --- a/tools/managed_tool_gateway.py +++ b/tools/managed_tool_gateway.py @@ -140,3 +140,208 @@ def is_managed_tool_gateway_ready( :func:`peek_nous_access_token` (no OAuth refresh); callers about to make a real request use :func:`resolve_managed_tool_gateway` instead.""" return resolve_managed_tool_gateway(vendor, gateway_builder=gateway_builder, token_reader=token_reader or peek_nous_access_token) is not None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from urllib.parse import urlsplit # noqa: F401,E402 +from urllib.parse import urlsplit # noqa: F401,E402 + +_MANAGED_GATEWAY_VENDOR = "tool" + +def is_managed_nous_gateway_url( + url: object, + gateway_builder: Optional[Callable[[str], str]] = None, +) -> bool: + """True when ``url`` is on the Nous tool-gateway origin this client builds. + + Anything granting a URL extra trust — our bearer, reading files off disk to + upload — must gate on this rather than on a name, so an arbitrary URL can + never inherit that trust. + """ + if not isinstance(url, str) or not url.strip(): + return False + + builder = gateway_builder or build_vendor_gateway_url + try: + expected = urlsplit(builder(_MANAGED_GATEWAY_VENDOR)) + actual = urlsplit(url.strip()) + except ValueError: + return False + + return bool(actual.scheme) and (actual.scheme, actual.netloc) == (expected.scheme, expected.netloc) + +def managed_gateway_auth_headers( + url: object, + gateway_builder: Optional[Callable[[str], str]] = None, + token_reader: Optional[Callable[[], Optional[str]]] = None, +) -> dict: + """Live auth headers for a managed gateway URL, or ``{}`` when not managed. + + Read fresh on every call rather than cached: a Nous access token expires + within the hour, and a long session would otherwise keep presenting a dead + bearer. Returns ``{}`` rather than raising when no token is available, so a + caller can report "sign in" instead of sending an unauthenticated request. + """ + if not is_managed_nous_gateway_url(url, gateway_builder): + return {} + + resolved_token_reader = token_reader or read_nous_access_token + try: + token = resolved_token_reader() + except Exception as exc: # pragma: no cover — defensive + logger.debug("Managed gateway token read failed for %s: %s", url, exc) + return {} + if not isinstance(token, str) or not token.strip(): + return {} + + return {"Authorization": f"Bearer {token.strip()}"} + +_MEDIA_UPLOAD_PRESIGN_TIMEOUT_SECONDS = 15.0 + +_MEDIA_UPLOAD_PUT_READ_TIMEOUT_SECONDS = 60.0 + +_MEDIA_UPLOAD_PUT_WRITE_TIMEOUT_SECONDS = 300.0 + +def _describe_media_upload_refusal(response) -> str: + """A model-actionable reason from a gateway refusal, or a generic one. + + The gateway's 4xx bodies carry deliberate guidance (rate-limit waits, size + caps, "you could not submit anyway"), so surface `error.message` verbatim + rather than a bare status code. + """ + try: + payload = response.json() + message = payload.get("error", {}).get("message") + if isinstance(message, str) and message.strip(): + return message.strip() + except Exception: + pass + return f"the gateway refused the upload (HTTP {response.status_code})" + +def build_managed_media_uploader( + server_url: object, + upload_path: object, + gateway_builder: Optional[Callable[[str], str]] = None, + token_reader: Optional[Callable[[], Optional[str]]] = None, +) -> Optional[Callable]: + """Async ``(data, mime) -> argument value`` uploader for one managed vendor. + + Returns ``None`` when there is no usable upload endpoint (not a managed + Nous URL, or no ``upload_path``); callers then refuse local paths with a + clear message instead of silently forwarding them. + + The three steps of the protocol: + + 1. POST ``origin + upload_path`` with the declared content type and exact + byte length, using the same live auth headers as the vendor calls. + The gateway answers with a presigned single-object PUT URL (short + expiry; type and length are signed into it) and an upload token. + 2. PUT the bytes to that URL. This goes directly to storage — never + through the gateway — which is what removes the request-size ceiling. + 3. Return ``nous-upload:`` for the tool argument. The token is + bound to this Nous principal and is redeemable only through the + gateway, so it is inert anywhere else it might end up. + """ + if not is_managed_nous_gateway_url(server_url, gateway_builder): + return None + if not isinstance(upload_path, str) or not upload_path.startswith("/"): + return None + + parts = urlsplit(str(server_url).strip()) + origin = f"{parts.scheme}://{parts.netloc}" + presign_url = f"{origin}{upload_path}" + + async def upload(data: bytes, mime: str) -> str: + import httpx + + from tools.url_safety import create_ssrf_safe_async_client + + headers = managed_gateway_auth_headers(server_url, gateway_builder, token_reader) + if not headers: + raise RuntimeError("no Nous credential is available for the upload") + + # Two clients on purpose, split by whose address we are trusting. + # + # The presign POST goes to `presign_url`, which is entirely determined + # by the managed gateway origin (already validated by + # is_managed_nous_gateway_url) plus the pinned upload_path — the same + # first-party host the vendor calls go to freely. SSRF-guarding it + # protects against nothing and would reject a local gateway on + # 127.0.0.1, so it uses a plain client. The PUT target, by contrast, is + # a URL the gateway *returned*, so it keeps the SSRF-safe client as + # defense in depth (real presigned URLs are public R2, which it allows). + presign_timeout = httpx.Timeout(_MEDIA_UPLOAD_PRESIGN_TIMEOUT_SECONDS) + async with httpx.AsyncClient(timeout=presign_timeout) as client: + presign = await client.post( + presign_url, + headers=headers, + json={"contentType": mime, "contentLength": len(data)}, + ) + if presign.status_code != 200: + raise RuntimeError(_describe_media_upload_refusal(presign)) + + try: + payload = presign.json() + except Exception: + payload = None + upload_url = payload.get("uploadUrl") if isinstance(payload, dict) else None + token = payload.get("token") if isinstance(payload, dict) else None + if not (isinstance(upload_url, str) and upload_url and isinstance(token, str) and token): + raise RuntimeError("the gateway's upload response was malformed") + + put_timeout = httpx.Timeout( + _MEDIA_UPLOAD_PRESIGN_TIMEOUT_SECONDS, + read=_MEDIA_UPLOAD_PUT_READ_TIMEOUT_SECONDS, + write=_MEDIA_UPLOAD_PUT_WRITE_TIMEOUT_SECONDS, + ) + async with create_ssrf_safe_async_client(timeout=put_timeout) as client: + # The presigned URL signs the exact Content-Type and Content-Length, + # so this PUT must send precisely what was declared above. + put = await client.put(upload_url, content=data, headers={"Content-Type": mime}) + if put.status_code != 200: + raise RuntimeError(f"storage refused the upload (HTTP {put.status_code})") + + return f"nous-upload:{token}" + + return upload + +def managed_vendor_base_path(vendor: str) -> str: + """Base path for a managed vendor's REST routes on the gateway host.""" + return f"/api/{vendor}" + +def managed_vendor_upload_path(vendor: str) -> str: + """Media upload endpoint for a managed vendor, on the same host.""" + return f"/api/uploads/{vendor}" + +def managed_vendor_endpoints( + vendor: str, + gateway_builder: Optional[Callable[[str], str]] = None, +) -> Optional[dict]: + """Absolute URLs for a managed vendor, or ``None`` when none resolves. + + Address resolution only: entitlement is deliberately not consulted here. + What an account may spend on a managed vendor is the gateway's own + decision, stated in its refusals, and re-deciding it on the client can only + ever disagree with the server. A caller that wants to hide its tools from + users who could not call them at all does that in its ``check_fn``. + + ``None`` means no origin could be resolved — a misconfigured + ``TOOL_GATEWAY_SCHEME`` — so there is nothing to call. + """ + builder = gateway_builder or build_vendor_gateway_url + try: + origin = builder(_MANAGED_GATEWAY_VENDOR).rstrip("/") + except ValueError: + return None + if not origin: + return None + + return { + "origin": origin, + "base_url": f"{origin}{managed_vendor_base_path(vendor)}", + "upload_path": managed_vendor_upload_path(vendor), + } +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/mcp_oauth.py b/tools/mcp_oauth.py index dbd8917de3..99ccb081a1 100644 --- a/tools/mcp_oauth.py +++ b/tools/mcp_oauth.py @@ -986,3 +986,21 @@ def build_oauth_auth(server_name: str, server_url: str, oauth_config: dict | Non "__doc__": "SDK provider plus Hermes' token-endpoint fixes (see ``HermesProviderMixin``).", "__module__": __name__, "_hermes_logger": logger}) return HermesOAuthClientProvider(server_url=server_url, **kwargs) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from contextlib import contextmanager # noqa: F401,E402 + +OAuthClientInformationFull: Any = None + +OAuthClientMetadata: Any = None + +OAuthClientProvider: Any = None + +OAuthMetadata: Any = None + +OAuthToken: Any = None +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/mcp_schema_cache.py b/tools/mcp_schema_cache.py index ba22dcb177..98e150edb1 100644 --- a/tools/mcp_schema_cache.py +++ b/tools/mcp_schema_cache.py @@ -107,3 +107,20 @@ def tools_from_cache_entry(entry: dict) -> List[dict]: def utility_tools_from_cache_entry(entry: dict) -> List[dict]: """Return cached ``{schema, handler_key}`` utility rows.""" return _list_field(entry, "utility_tools") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def clear_cache_entry(server_name: str) -> None: + with _cache_lock: + data = _load_all() + if server_name in data: + del data[server_name] + _save_all(data) + +def has_cached_entry(server_name: str, fingerprint: str) -> bool: + return get_cached_entry(server_name, fingerprint) is not None +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/mcp_tool.py b/tools/mcp_tool.py index 2e0c3c2551..a67cc5a449 100644 --- a/tools/mcp_tool.py +++ b/tools/mcp_tool.py @@ -618,3 +618,59 @@ _LOCK_UNAVAILABLE: Any = object() # sentinel: locking broken/unavailable _MCP_DISCOVERY_LOCK_PATH: Optional[str] = None # resolved lazily # Bounded wait when another process holds the lock. _MCP_DISCOVERY_LOCK_MAX_RETRIES, _MCP_DISCOVERY_LOCK_RETRY_DELAY_S = 240, 0.5 + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Coroutine # noqa: F401,E402 +from types import SimpleNamespace # noqa: F401,E402 +from typing import Tuple # noqa: F401,E402 +from contextlib import asynccontextmanager # noqa: F401,E402 +import concurrent.futures # noqa: F401,E402 +from datetime import datetime # noqa: F401,E402 +import errno # noqa: F401,E402 +import fnmatch # noqa: F401,E402 +import json # noqa: F401,E402 +import math # noqa: F401,E402 +import random # noqa: F401,E402 +import re # noqa: F401,E402 +import shutil # noqa: F401,E402 +from urllib.parse import urlparse # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'InvalidMcpUrlError': ('tools.mcp_tool_errors', 'InvalidMcpUrlError'), + 'MCP_TOOL_NAME_PREFIX': ('tools.mcp_tool_schema', 'MCP_TOOL_NAME_PREFIX'), + 'NonMcpEndpointError': ('tools.mcp_tool_errors', 'NonMcpEndpointError'), + 'discover_mcp_tools': ('tools.mcp_tool_discovery', 'discover_mcp_tools'), + 'get_mcp_status': ('tools.mcp_tool_discovery', 'get_mcp_status'), + 'get_registered_mcp_server_names': ('tools.mcp_tool_discovery', 'get_registered_mcp_server_names'), + 'has_registered_mcp_tools': ('tools.mcp_tool_discovery', 'has_registered_mcp_tools'), + 'is_mcp_tool_parallel_safe': ('tools.mcp_tool_discovery', 'is_mcp_tool_parallel_safe'), + 'matches_name_filter': ('tools.mcp_tool_schema', 'matches_name_filter'), + 'mcp_prefixed_tool_name': ('tools.mcp_tool_schema', 'mcp_prefixed_tool_name'), + 'persist_agent_tool_names': ('tools.mcp_tool_agent', 'persist_agent_tool_names'), + 'probe_mcp_server_tools': ('tools.mcp_tool_discovery', 'probe_mcp_server_tools'), + 'reconnect_mcp_server': ('tools.mcp_tool_loop', 'reconnect_mcp_server'), + 'refresh_agent_mcp_tools': ('tools.mcp_tool_agent', 'refresh_agent_mcp_tools'), + 'register_mcp_servers': ('tools.mcp_tool_discovery', 'register_mcp_servers'), + 'reprobe_tool_availability': ('tools.mcp_tool_agent', 'reprobe_tool_availability'), + 'restore_agent_tool_prefix': ('tools.mcp_tool_agent', 'restore_agent_tool_prefix'), + 'sanitize_mcp_name_component': ('tools.mcp_tool_schema', 'sanitize_mcp_name_component'), + 'shutdown_mcp_servers': ('tools.mcp_tool_lifecycle', 'shutdown_mcp_servers'), + 'strip_unicode_tags': ('tools.ansi_strip', 'strip_unicode_tags'), + 'tool_error': ('tools.registry', 'tool_error'), +} + +_plugin_compat_prev_getattr = __getattr__ + + +def __getattr__(name): # PEP 562 — chained onto the module's own __getattr__ + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + return _plugin_compat_prev_getattr(name) + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/memory_tool.py b/tools/memory_tool.py index 8e42f22e57..79a56efcb6 100644 --- a/tools/memory_tool.py +++ b/tools/memory_tool.py @@ -324,3 +324,25 @@ registry.register( check_fn=check_memory_requirements, emoji="🧠", dynamic_schema_overrides=_build_memory_schema_overrides) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from contextlib import contextmanager # noqa: F401,E402 +import time # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'atomic_write_text': ('utils', 'atomic_write_text'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/microsoft_graph_client.py b/tools/microsoft_graph_client.py index faf29d7acb..6a7921097b 100644 --- a/tools/microsoft_graph_client.py +++ b/tools/microsoft_graph_client.py @@ -180,3 +180,24 @@ class MicrosoftGraphClient: return MicrosoftGraphAPIError( response.status_code, method, url, response.text.strip() or "unknown error" if detail is None else detail, retry_after_seconds=parse_retry_after_seconds(response.headers), payload=payload) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import AsyncIterator # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'GraphCredentials': ('tools.microsoft_graph_auth', 'GraphCredentials'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/open_preview_tool.py b/tools/open_preview_tool.py index 009c194313..7c0b9b760c 100644 --- a/tools/open_preview_tool.py +++ b/tools/open_preview_tool.py @@ -42,3 +42,54 @@ def open_preview_tool(url: str, label: str = "") -> str: "Failed to open the preview pane: ", "The preview pane is only available in the Hermes desktop app.", {"success": True, "url": target, "label": label}) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 + +OPEN_PREVIEW_SCHEMA = { + "name": "open_preview", + "description": ( + "Open something in the preview pane beside the chat in the Hermes desktop " + "app. Use this when the user asks to see a page, dev server, or file in the " + "preview pane — e.g. \"open cnn.com in the preview pane\" or \"preview " + "localhost:3000\". Accepts a web URL (a bare domain like www.cnn.com is fine), " + "a localhost dev-server URL, or a file path (HTML renders live; other files " + "show their contents). The pane opens for the current window only. To close " + "the pane or a tab, use close_preview." + ), + "parameters": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": ( + "What to preview: a web URL (https://… or a bare domain), a " + "localhost URL (localhost:3000), or a file path." + ), + }, + "label": { + "type": "string", + "description": "Optional tab label; defaults to the target's name.", + }, + }, + "required": ["url"], + }, +} + + +_PLUGIN_COMPAT_LAZY = { + 'registry': ('tools.registry', 'registry'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/openrouter_client.py b/tools/openrouter_client.py index 262b4d73c7..9037f3ab0d 100644 --- a/tools/openrouter_client.py +++ b/tools/openrouter_client.py @@ -18,3 +18,26 @@ def check_api_key() -> bool: except Exception: pass return bool(os.getenv("OPENROUTER_API_KEY")) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def get_async_client(): + """Return a shared async OpenAI-compatible client for OpenRouter. + + The client is created lazily on first call and reused thereafter. + Uses the centralized provider router for auth and client construction. + Raises ValueError if OPENROUTER_API_KEY is not set. + """ + global _client + if _client is None: + from agent.auxiliary_client import resolve_provider_client + client, _model = resolve_provider_client("openrouter", async_mode=True) + if client is None: + raise ValueError("OPENROUTER_API_KEY environment variable not set") + _client = client + return _client +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/path_security.py b/tools/path_security.py index 222df08fd0..8fee6dd694 100644 --- a/tools/path_security.py +++ b/tools/path_security.py @@ -16,3 +16,24 @@ def validate_within_dir(path: Path, root: Path) -> Optional[str]: def has_traversal_component(path_str: str) -> bool: """Cheap pre-check for a literal ``..`` component before full resolution.""" return ".." in Path(path_str).parts + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import logging # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'logger': ('tools.approval', 'logger'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/preview_tool.py b/tools/preview_tool.py index cba89ff8a5..a5271a7e04 100644 --- a/tools/preview_tool.py +++ b/tools/preview_tool.py @@ -76,3 +76,14 @@ registry.register( handler=_handle_preview, emoji="🖼️", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 + +def preview_open(url: str, label: str = "") -> str: + return open_preview_tool(url=url, label=label) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/process_registry.py b/tools/process_registry.py index 50ec32044e..75ae003b84 100644 --- a/tools/process_registry.py +++ b/tools/process_registry.py @@ -2089,3 +2089,12 @@ registry.register( handler=_handle_process, emoji="⚙️", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +MAX_ACTIVE_PROCESS_AGE = 86400 # 24h default — see session_reset.bg_process_max_age_hours (#29177) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/react_to_message_tool.py b/tools/react_to_message_tool.py index 0308119ba0..d4c21d638c 100644 --- a/tools/react_to_message_tool.py +++ b/tools/react_to_message_tool.py @@ -116,3 +116,23 @@ registry.register( messages_back=args.get("messages_back")), check_fn=check_react_requirements, emoji="💛", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'env_var_enabled': ('utils', 'env_var_enabled'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/read_extract.py b/tools/read_extract.py index b6ba1ab5fa..0ae70c8ab9 100644 --- a/tools/read_extract.py +++ b/tools/read_extract.py @@ -538,3 +538,12 @@ def _cell_value(cell: ET.Element, shared: list[str], s: str) -> str: # Extension -> stdlib extractor; anydoc formats fall through in extract_document_text. _STDLIB_EXTRACTORS: dict[str, Callable[[str], str]] = { ".ipynb": _extract_notebook, ".docx": _extract_docx, ".xlsx": _extract_xlsx} + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +MAX_XLSX_BYTES = 50 * 1024 * 1024 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/read_preview_tool.py b/tools/read_preview_tool.py index 68e3f63b93..1a491d1325 100644 --- a/tools/read_preview_tool.py +++ b/tools/read_preview_tool.py @@ -21,3 +21,54 @@ def read_preview_tool( "start and count must be integers.", "Failed to read the preview pane: ", "No preview tab is open, or the read timed out.")) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 + +READ_PREVIEW_SCHEMA = { + "name": "read_preview", + "description": ( + "Read what's currently shown in the in-app browser / preview pane of the " + "Hermes desktop GUI (the pane open_preview opens beside this chat). Call " + "with no arguments for the first window of the active tab's content. " + "Returns JSON {kind, url, title, text, start, end, total_chars, note?}: " + "a URL (Browser) tab's text is the rendered page's visible text — page " + "through longer pages with `start`/`count` (character offsets, capped " + "per read); a file tab answers identity only (read the file with " + "read_file); an artifact tab points back at the conversation. Use after " + "open_preview, or whenever the user refers to what's on screen in the " + "browser ('what does this page say?'). To close the pane, use close_preview." + ), + "parameters": { + "type": "object", + "properties": { + "start": { + "type": "integer", + "description": "0-indexed character offset into the page text. Omit for the start.", + }, + "count": { + "type": "integer", + "description": "Characters to return from start. Defaults to (and is capped at) the per-read maximum.", + }, + }, + }, +} + + +_PLUGIN_COMPAT_LAZY = { + 'registry': ('tools.registry', 'registry'), + 'tool_error': ('tools.registry', 'tool_error'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/read_terminal_tool.py b/tools/read_terminal_tool.py index 41087a907d..16de488479 100644 --- a/tools/read_terminal_tool.py +++ b/tools/read_terminal_tool.py @@ -79,3 +79,11 @@ registry.register( ), emoji="🖥️", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/read_window_tool.py b/tools/read_window_tool.py index b41ab6db06..cae4f8cf95 100644 --- a/tools/read_window_tool.py +++ b/tools/read_window_tool.py @@ -46,3 +46,24 @@ registry.register( handler=lambda args, **kw: read_window_below_tool(callback=kw.get("callback")), emoji="🪟", ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import json # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'tool_error': ('tools.registry', 'tool_error'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/send_message_tool.py b/tools/send_message_tool.py index 0a1b08ce2d..f62e773a7c 100644 --- a/tools/send_message_tool.py +++ b/tools/send_message_tool.py @@ -525,3 +525,64 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None, and not last_result.get("media_delivered")): last_result["warnings"] = [*last_result.get("warnings", []), warning] return last_result + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import re # noqa: F401,E402 +import time # noqa: F401,E402 + +SEND_MESSAGE_SCHEMA = { + "name": "send_message", + "description": ( + "Send a message to a connected messaging platform, or list available targets.\n\n" + "IMPORTANT: When the user asks to send to a specific channel or person " + "(not just a bare platform name), call send_message(action='list') FIRST to see " + "available targets, then send to the correct one.\n" + "If the user just says a platform name like 'send to telegram', send directly " + "to the home channel without listing first." + ), + "parameters": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": ["send", "list", "react", "unreact"], + "description": "Action to perform. 'send' (default) sends a message. 'list' returns all available channels/contacts across connected platforms. 'react' attaches an emoji reaction to a message (platforms that support it, e.g. photon/iMessage tapbacks). 'unreact' retracts a previously-added reaction." + }, + "target": { + "type": "string", + "description": "Delivery target. Format: 'platform' (uses home channel), 'platform:#channel-name', 'platform:chat_id', or 'platform:chat_id:thread_id' for Telegram topics and Discord threads. Examples: 'telegram', 'telegram:-1001234567890:17585', 'discord:999888777:555444333', 'discord:#bot-home', 'slack:#engineering', 'signal:+155****4567', 'matrix:!roomid:server.org', 'matrix:@user:server.org', 'ntfy:alerts-channel' (explicit ntfy topic), 'yuanbao:direct:' (DM), 'yuanbao:group:' (group chat)" + }, + "message": { + "type": "string", + "description": "The message text to send. To send an image or file, include MEDIA: (e.g. 'MEDIA:/tmp/report.pdf') in the message — the platform will deliver it as a native media attachment." + }, + "emoji": { + "type": "string", + "description": "For action='react': the emoji to react with (e.g. '❤️'). On iMessage, ❤️👍👎😂‼️❓ render as native tapbacks; other emoji use custom-emoji reactions." + }, + "message_id": { + "type": "string", + "description": "For action='react'/'unreact': id of the message to react to. Omit to target the most recent message received in that chat (usually the one being replied to)." + } + }, + "required": [] + } +} + + +_PLUGIN_COMPAT_LAZY = { + 'redact_sensitive_text': ('agent.redact', 'redact_sensitive_text'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/skill_ledger.py b/tools/skill_ledger.py index 1c852abed4..cbe041ddff 100644 --- a/tools/skill_ledger.py +++ b/tools/skill_ledger.py @@ -400,3 +400,16 @@ def rollback_entry(entry_id: str) -> Tuple[bool, str]: f"rolled back entry {entry_id} ({entry.get('action')} on " f"'{entry.get('skill')}'): {restored} file(s) restored, {removed} removed. " f"Safety entry {safety_id} captured the pre-rollback state.") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +ACTOR_AGENT = "agent" + +ACTOR_CURATOR = "curator" + +ACTOR_USER = "user" +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/skill_linter.py b/tools/skill_linter.py index 31cfb59232..e46de3806d 100644 --- a/tools/skill_linter.py +++ b/tools/skill_linter.py @@ -183,3 +183,17 @@ def lint_skill(skill_md_path: Path) -> List[LintFinding]: skill_md_path = Path(skill_md_path) content = skill_md_path.read_text(encoding="utf-8", errors="ignore") return lint_content(content, skill_dir=skill_md_path.parent) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def format_findings(findings: List[LintFinding]) -> str: + """Render findings as a newline-joined human-readable block.""" + return "\n".join(f.format() for f in findings) + +def has_errors(findings: List[LintFinding]) -> bool: + return any(f.severity == ERROR for f in findings) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/skill_manager_tool.py b/tools/skill_manager_tool.py index 3463f4c330..70cffd3942 100644 --- a/tools/skill_manager_tool.py +++ b/tools/skill_manager_tool.py @@ -875,3 +875,23 @@ registry.register( name="skill_manage", toolset="skills", schema=SKILL_MANAGE_SCHEMA, emoji="📝", handler=lambda args, **kw: _skill_manage_from( args, task_id=kw.get("task_id"), session_id=kw.get("session_id"))) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'mark_background_review_skill_read': ('tools.skill_manager_guards', 'mark_background_review_skill_read'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/skill_usage.py b/tools/skill_usage.py index ebf8128037..48d7edc9d3 100644 --- a/tools/skill_usage.py +++ b/tools/skill_usage.py @@ -677,3 +677,66 @@ def usage_report() -> List[Dict[str, Any]]: data = load_usage() return [_report_row(n, data.get(n), provenance=provenance(n), _persisted=n in data) for n in sorted({name for name, _md in _iter_skill_mds(base, local_only=False)})] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import tempfile # noqa: F401,E402 +import os # noqa: F401,E402 +import os # noqa: F401,E402 +import tempfile # noqa: F401,E402 + +def _suppressed_file() -> Path: + return _skills_dir() / ".curator_suppressed" + +def _write_suppressed_names(names: Set[str]) -> None: + path = _suppressed_file() + try: + path.parent.mkdir(parents=True, exist_ok=True) + data = "\n".join(sorted(names)) + ("\n" if names else "") + fd, tmp = tempfile.mkstemp(dir=str(path.parent), prefix=".curator_suppressed_", suffix=".tmp") + try: + with os.fdopen(fd, "w", encoding="utf-8") as f: + f.write(data) + f.flush() + os.fsync(f.fileno()) + os.replace(tmp, path) + except BaseException: + try: + os.unlink(tmp) + except OSError: + pass + raise + except Exception as e: + logger.debug("Failed to write curator suppression list: %s", e, exc_info=True) + +def add_suppressed_name(skill_name: str) -> None: + """Record that a built-in skill was pruned, so sync won't restore it.""" + if not skill_name: + return + names = read_suppressed_names() + if skill_name not in names: + names.add(skill_name) + _write_suppressed_names(names) + +def agent_created_report() -> List[Dict[str, Any]]: + """DEPRECATED — use :func:`curated_report` instead. + + Used to return everything :func:`curated_report` returns (including bundled + skills when ``curator.prune_builtins`` is enabled), which made the + "agent-created" name misleading. Kept as a compatibility alias for + external callers; new code should call ``curated_report()``. + """ + return curated_report() + +def remove_suppressed_name(skill_name: str) -> None: + """Clear a built-in's suppression entry (e.g. on restore).""" + if not skill_name: + return + names = read_suppressed_names() + if skill_name in names: + names.discard(skill_name) + _write_suppressed_names(names) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/skillevaluator_scan.py b/tools/skillevaluator_scan.py index 73b20440f5..f48142c20a 100644 --- a/tools/skillevaluator_scan.py +++ b/tools/skillevaluator_scan.py @@ -139,3 +139,16 @@ def format_tier1_report(report: Tier1Report, limit: int = 10) -> str: if report.incomplete_checks: lines.append(f" (not run: {', '.join(report.incomplete_checks)} — no opinion from these checks)") return "\n".join(lines) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Optional # noqa: F401,E402 + +SCANNER_NAME = "skillevaluator-tier1" + +def scanner_available() -> bool: + return shutil.which(SCANNER_BIN) is not None +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/skills_guard.py b/tools/skills_guard.py index c228978e20..275e742b01 100644 --- a/tools/skills_guard.py +++ b/tools/skills_guard.py @@ -633,3 +633,14 @@ def _build_summary(name: str, source: str, trust: str, verdict: str, findings: L if not findings: return f"{name}: clean scan, no threats detected" return f"{name}: {verdict} — {len(findings)} finding(s) in {', '.join(sorted({f.category for f in findings}))}" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def full_content_hash(skill_path: Path) -> str: + """Full canonical digest used to bind scanner attestations.""" + return f"sha256:{_content_digest(skill_path)}" +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/skills_hub.py b/tools/skills_hub.py index d0d9f5cd0c..65e8927a15 100644 --- a/tools/skills_hub.py +++ b/tools/skills_hub.py @@ -315,3 +315,73 @@ def ensure_hub_dirs() -> None: ): if not path.exists(): path.write_text(initial, encoding="utf-8") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from abc import ABC # noqa: F401,E402 +from pathlib import PurePosixPath # noqa: F401,E402 +from typing import Tuple # noqa: F401,E402 +from typing import Union # noqa: F401,E402 +from abc import abstractmethod # noqa: F401,E402 +from dataclasses import dataclass # noqa: F401,E402 +from dataclasses import field # noqa: F401,E402 +import hashlib # noqa: F401,E402 +import os # noqa: F401,E402 +from urllib.parse import quote # noqa: F401,E402 +import re # noqa: F401,E402 +import shutil # noqa: F401,E402 +import subprocess # noqa: F401,E402 +from urllib.parse import unquote # noqa: F401,E402 +from urllib.parse import urlparse # noqa: F401,E402 +from urllib.parse import urlsplit # noqa: F401,E402 +from urllib.parse import urlunparse # noqa: F401,E402 +import yaml # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'BrowseShSource': ('tools.skills_hub_sources', 'BrowseShSource'), + 'ClawHubSource': ('tools.skills_hub_clawhub', 'ClawHubSource'), + 'GITHUB_TAP_PROVIDERS': ('tools.skills_hub_github', 'GITHUB_TAP_PROVIDERS'), + 'GitHubAuth': ('tools.skills_hub_github', 'GitHubAuth'), + 'GitHubSource': ('tools.skills_hub_github', 'GitHubSource'), + 'HERMES_INDEX_TTL': ('tools.skills_hub_search', 'HERMES_INDEX_TTL'), + 'HERMES_INDEX_URL': ('tools.skills_hub_search', 'HERMES_INDEX_URL'), + 'HermesIndexSource': ('tools.skills_hub_official', 'HermesIndexSource'), + 'LobeHubSource': ('tools.skills_hub_sources', 'LobeHubSource'), + 'OptionalSkillSource': ('tools.skills_hub_official', 'OptionalSkillSource'), + 'ScanResult': ('tools.skills_guard', 'ScanResult'), + 'SkillBundle': ('tools.skills_hub_models', 'SkillBundle'), + 'SkillMeta': ('tools.skills_hub_models', 'SkillMeta'), + 'SkillSource': ('tools.skills_hub_models', 'SkillSource'), + 'SkillsShSource': ('tools.skills_hub_skillssh', 'SkillsShSource'), + 'TRUSTED_REPOS': ('tools.skills_guard', 'TRUSTED_REPOS'), + 'UrlSource': ('tools.skills_hub_sources', 'UrlSource'), + 'WellKnownSkillSource': ('tools.skills_hub_sources', 'WellKnownSkillSource'), + 'bundle_content_hash': ('tools.skills_hub_install', 'bundle_content_hash'), + 'check_for_skill_updates': ('tools.skills_hub_install', 'check_for_skill_updates'), + 'content_hash': ('tools.skills_guard', 'content_hash'), + 'create_source_router': ('tools.skills_hub_search', 'create_source_router'), + 'github_provider_for': ('tools.skills_hub_github', 'github_provider_for'), + 'install_from_quarantine': ('tools.skills_hub_install', 'install_from_quarantine'), + 'is_excluded_skill_path': ('agent.skill_utils', 'is_excluded_skill_path'), + 'parallel_search_sources': ('tools.skills_hub_search', 'parallel_search_sources'), + 'quarantine_bundle': ('tools.skills_hub_install', 'quarantine_bundle'), + 'source_url_for_bundle': ('tools.skills_hub_models', 'source_url_for_bundle'), + 'unified_search': ('tools.skills_hub_search', 'unified_search'), + 'uninstall_skill': ('tools.skills_hub_install', 'uninstall_skill'), + 'windows_hide_flags': ('hermes_cli._subprocess_compat', 'windows_hide_flags'), +} + +_plugin_compat_prev_getattr = __getattr__ + + +def __getattr__(name): # PEP 562 — chained onto the module's own __getattr__ + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + return _plugin_compat_prev_getattr(name) + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/skills_sync.py b/tools/skills_sync.py index ec6902a0e5..08861d6f87 100644 --- a/tools/skills_sync.py +++ b/tools/skills_sync.py @@ -433,3 +433,37 @@ if __name__ == "__main__": if backfilled := result.get("optional_provenance_backfilled"): parts.append(f"{len(backfilled)} official optional backfilled") print(f"\nDone: {', '.join(parts)}. {result['total_bundled']} total bundled.") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import PurePosixPath # noqa: F401,E402 +from datetime import datetime # noqa: F401,E402 +import json # noqa: F401,E402 +from datetime import timezone # noqa: F401,E402 + +def is_bundled_skills_opt_out() -> bool: + """Return True if the active profile carries the opt-out marker.""" + return (_hermes_home() / NO_BUNDLED_SKILLS_MARKER).exists() + + +_PLUGIN_COMPAT_LAZY = { + 'atomic_replace': ('utils', 'atomic_replace'), + 'diff_bundled_skill': ('tools.skills_sync_bundled_ops', 'diff_bundled_skill'), + 'list_user_modified_bundled_skills': ('tools.skills_sync_bundled_ops', 'list_user_modified_bundled_skills'), + 'remove_pristine_bundled_skills': ('tools.skills_sync_bundled_ops', 'remove_pristine_bundled_skills'), + 'reset_bundled_skill': ('tools.skills_sync_bundled_ops', 'reset_bundled_skill'), + 'restore_official_optional_skill': ('tools.skills_sync_optional', 'restore_official_optional_skill'), + 'set_bundled_skills_opt_out': ('tools.skills_sync_bundled_ops', 'set_bundled_skills_opt_out'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/skills_sync_client.py b/tools/skills_sync_client.py index 20fe644ce3..d5a2ec291e 100644 --- a/tools/skills_sync_client.py +++ b/tools/skills_sync_client.py @@ -496,3 +496,71 @@ def sync_status() -> Dict[str, Any]: except Exception as e: logger.debug("skills_sync_client: sync_status org lookup failed: %s", e) return status + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from datetime import datetime # noqa: F401,E402 +import hashlib # noqa: F401,E402 +import time # noqa: F401,E402 +from datetime import timezone # noqa: F401,E402 + +KIND_COMMIT = "commit" + +KIND_TREE = "tree" + +MODE_DIR = "dir" + +MODE_EXEC = "exec" + +MODE_FILE = "file" + +SYNC_MANIFEST_TYPE = "sync-manifest" + +def dev_gate_open() -> bool: + """Whether the access gate permits sync. Never raises.""" + try: + return bool(resolve_identity().get("nous_admin")) + except SyncInertError: + return False + except Exception as e: + logger.debug("skills_sync_client: dev_gate_open check failed: %s", e) + return False + +def org_sync_available() -> bool: + """True iff this token can see the org-skill surface (multi-member org).""" + try: + resolve_org_identity() + return True + except Exception: + return False + +def user_conflict_ref(owner: str, n: int) -> str: + return f"refs/user/{owner}/conflict/{n}" + + +_PLUGIN_COMPAT_LAZY = { + 'ARTIFACT_TYPE_SKILL': ('tools.skills_sync_client_wire', 'ARTIFACT_TYPE_SKILL'), + 'SYNC_MANIFEST_ENTRY_NAME': ('tools.skills_sync_client_wire', 'SYNC_MANIFEST_ENTRY_NAME'), + 'SYNC_MANIFEST_VERSION': ('tools.skills_sync_client_wire', 'SYNC_MANIFEST_VERSION'), + 'WIRE_VERSION': ('tools.skills_sync_client_wire', 'WIRE_VERSION'), + 'canonical_json_bytes': ('tools.skills_sync_client_wire', 'canonical_json_bytes'), + 'maybe_pull_org_skills': ('tools.skills_sync_client_org', 'maybe_pull_org_skills'), + 'org_head_ref': ('tools.skills_sync_client_org', 'org_head_ref'), + 'org_skill_is_locally_modified': ('tools.skills_sync_client_org', 'org_skill_is_locally_modified'), + 'parse_sync_manifest': ('tools.skills_sync_client_wire', 'parse_sync_manifest'), + 'propose_skill': ('tools.skills_sync_client_org', 'propose_skill'), + 'pull_org_skills': ('tools.skills_sync_client_org', 'pull_org_skills'), + 'wire_address': ('tools.skills_sync_client_wire', 'wire_address'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/skills_tool.py b/tools/skills_tool.py index 431b02bf79..0e9563793b 100644 --- a/tools/skills_tool.py +++ b/tools/skills_tool.py @@ -662,3 +662,28 @@ def _skill_view_with_bump(args, **kw): registry.register( name="skill_view", toolset="skills", schema=SKILL_VIEW_SCHEMA, handler=_skill_view_with_bump, check_fn=check_skills_requirements, emoji="📚") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from enum import Enum # noqa: F401,E402 +from typing import Set # noqa: F401,E402 +import re # noqa: F401,E402 +import threading # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'display_hermes_home': ('hermes_constants', 'display_hermes_home'), + 'env_var_enabled': ('utils', 'env_var_enabled'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/slash_confirm.py b/tools/slash_confirm.py index a44b9d0dab..f3f3e78e17 100644 --- a/tools/slash_confirm.py +++ b/tools/slash_confirm.py @@ -87,3 +87,37 @@ async def resolve(session_key: str, confirm_id: str, choice: str, logger.error("Slash-confirm handler for /%s raised: %s", command, exc, exc_info=True) return f"❌ Error handling confirmation: {exc}" return result if isinstance(result, str) else None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import asyncio # noqa: F401,E402 + +def resolve_sync_compat( + loop: asyncio.AbstractEventLoop, + session_key: str, + confirm_id: str, + choice: str, +) -> Optional[str]: + """Synchronous helper: schedule resolve() on a loop and wait for the result. + + Used by platform callback paths that run on a different thread than the + event loop (e.g. Discord's button click handler in some configurations). + Prefer the async ``resolve()`` from an async context. + """ + try: + from agent.async_utils import safe_schedule_threadsafe + fut = safe_schedule_threadsafe( + resolve(session_key, confirm_id, choice), loop, + logger=logger, + log_message="resolve_sync_compat scheduling failed", + ) + if fut is None: + return None + return fut.result(timeout=30) + except Exception as exc: + logger.error("resolve_sync_compat failed: %s", exc) + return None +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/terminal_scope.py b/tools/terminal_scope.py index 6bef7819a9..64338b8055 100644 --- a/tools/terminal_scope.py +++ b/tools/terminal_scope.py @@ -159,3 +159,27 @@ def install_and_reset_profile_terminal_scope(hermes_home: "Any") -> Iterator[Non yield finally: reset_terminal_scope(token) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def install_refusal_scope(reason: str) -> Token: + """Install a refusal scope after :class:`TerminalPolicyUnavailable`. + + Terminal execution under this scope is rejected (fail closed) instead of + running under the launch process's ambient policy. + """ + return _terminal_scope_var.set(TerminalPolicyRefusal(reason)) + +@contextmanager +def terminal_scope(mapping: Optional[Dict[str, str]]) -> Iterator[None]: + """Context manager form of set/reset_terminal_scope.""" + token = set_terminal_scope(mapping) + try: + yield + finally: + reset_terminal_scope(token) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/terminal_tool.py b/tools/terminal_tool.py index 35b1b6d3ab..ed10535e5d 100644 --- a/tools/terminal_tool.py +++ b/tools/terminal_tool.py @@ -1296,3 +1296,41 @@ registry.register( emoji="💻", max_result_size_chars=100_000, ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from pathlib import Path # noqa: F401,E402 +import importlib.util # noqa: F401,E402 +import platform # noqa: F401,E402 +import re # noqa: F401,E402 +import shlex # noqa: F401,E402 +import shutil # noqa: F401,E402 +import stat # noqa: F401,E402 +import subprocess # noqa: F401,E402 +import sys # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'cleanup_vm': ('tools.terminal_tool_lifecycle', 'cleanup_vm'), + 'env_var_enabled': ('utils', 'env_var_enabled'), + 'get_active_env': ('tools.terminal_tool_lifecycle', 'get_active_env'), + 'has_direct_modal_credentials': ('tools.tool_backend_helpers', 'has_direct_modal_credentials'), + 'is_interrupted': ('tools.interrupt', 'is_interrupted'), + 'is_managed_tool_gateway_ready': ('tools.managed_tool_gateway', 'is_managed_tool_gateway_ready'), + 'is_persistent_env': ('tools.terminal_tool_lifecycle', 'is_persistent_env'), + 'nous_tool_gateway_unavailable_message': ('tools.tool_backend_helpers', 'nous_tool_gateway_unavailable_message'), + 'resolve_modal_backend_state': ('tools.tool_backend_helpers', 'resolve_modal_backend_state'), + 'strip_inert_heredoc_bodies': ('tools.shell_heredoc', 'strip_inert_heredoc_bodies'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/tool_result_storage.py b/tools/tool_result_storage.py index c96619e6f2..216d843b5c 100644 --- a/tools/tool_result_storage.py +++ b/tools/tool_result_storage.py @@ -254,3 +254,13 @@ def enforce_turn_budget(tool_messages: list[dict], env=None, logger.info("Budget enforcement: persisted tool result %s (%d chars)", tool_use_id, size) return tool_messages + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import uuid # noqa: F401,E402 + +HEREDOC_MARKER = "HERMES_PERSIST_EOF" +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/tool_search.py b/tools/tool_search.py index 04fb88b846..ab2af9b0e6 100644 --- a/tools/tool_search.py +++ b/tools/tool_search.py @@ -494,3 +494,48 @@ __all__ = [ "bridge_tool_schemas", "assemble_tool_defs", "is_bridge_tool", "dispatch_tool_search", "dispatch_tool_describe", "resolve_underlying_call", "scoped_deferrable_names", "validate_deferred_call_args"] + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Literal # noqa: F401,E402 +import copy # noqa: F401,E402 +from dataclasses import field # noqa: F401,E402 +import re # noqa: F401,E402 +import snowballstemmer # noqa: F401,E402 +import threading # noqa: F401,E402 + +def build_catalog_listing( + deferrable: List[Dict[str, Any]], + *, + max_tokens: int = 4000, +) -> Optional[str]: + """Render a skills-style manifest of the deferred catalog. + + One line per tool — ``name: short description`` — grouped under a + heading per source (MCP server / plugin toolset), exactly like the + bundled-skills listing in the system prompt: + + github tools: (44) + - create_issue: Open a new issue in a GitHub repository. + - merge_pull_request: Merge an open pull request. + ... + + Ordering is deterministic (groups and tools sorted by name) so the + rendered block is byte-stable across assemblies of the same catalog — + this keeps the request prefix cacheable across turns. + + Token-budget fallbacks (cheap chars/4 estimate, same rule as the + activation gate): + 1. full listing (names + short descriptions) + 2. names-only listing, still grouped + 3. server-level summary — one line per MCP server / plugin toolset + (name + tool count), so the model always knows WHICH domains are + reachable through the bridge even when per-tool names don't fit + 4. ``None`` — only when the summary itself exceeds the budget + """ + text, _form = build_catalog_listing_with_form(deferrable, max_tokens=max_tokens) + return text +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/transcription_tools.py b/tools/transcription_tools.py index 24657938f5..4ccac30161 100644 --- a/tools/transcription_tools.py +++ b/tools/transcription_tools.py @@ -552,3 +552,49 @@ def transcribe_audio_local_fallback(file_path: str, model: Optional[str] = None) if _has_local_command(): return _transcribe_local_command(file_path, _normalize_local_model(local_model)) return _error_result("No installed local STT backend is available.", provider="local") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import platform # noqa: F401,E402 +import queue # noqa: F401,E402 +import re # noqa: F401,E402 +import shlex # noqa: F401,E402 +import subprocess # noqa: F401,E402 +import tempfile # noqa: F401,E402 +from urllib.parse import urljoin # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'COMMAND_STT_OUTPUT_FORMATS': ('tools.transcription_command', 'COMMAND_STT_OUTPUT_FORMATS'), + 'COMMON_LOCAL_BIN_DIRS': ('tools.transcription_common', 'COMMON_LOCAL_BIN_DIRS'), + 'DEFAULT_COMMAND_STT_LANGUAGE': ('tools.transcription_command', 'DEFAULT_COMMAND_STT_LANGUAGE'), + 'DEFAULT_COMMAND_STT_OUTPUT_FORMAT': ('tools.transcription_command', 'DEFAULT_COMMAND_STT_OUTPUT_FORMAT'), + 'DEFAULT_COMMAND_STT_TIMEOUT_SECONDS': ('tools.transcription_command', 'DEFAULT_COMMAND_STT_TIMEOUT_SECONDS'), + 'DEFAULT_LOCAL_STT_LANGUAGE': ('tools.transcription_common', 'DEFAULT_LOCAL_STT_LANGUAGE'), + 'ELEVENLABS_STT_BASE_URL': ('tools.transcription_common', 'ELEVENLABS_STT_BASE_URL'), + 'GROQ_BASE_URL': ('tools.transcription_common', 'GROQ_BASE_URL'), + 'GROQ_MODELS': ('tools.transcription_common', 'GROQ_MODELS'), + 'LOCAL_NATIVE_AUDIO_FORMATS': ('tools.transcription_common', 'LOCAL_NATIVE_AUDIO_FORMATS'), + 'MAX_FILE_SIZE': ('tools.transcription_common', 'MAX_FILE_SIZE'), + 'OPENAI_BASE_URL': ('tools.transcription_common', 'OPENAI_BASE_URL'), + 'OPENAI_MODELS': ('tools.transcription_common', 'OPENAI_MODELS'), + 'SUPPORTED_FORMATS': ('tools.transcription_common', 'SUPPORTED_FORMATS'), + 'XAI_STT_BASE_URL': ('tools.transcription_common', 'XAI_STT_BASE_URL'), + 'managed_nous_tools_enabled': ('tools.tool_backend_helpers', 'managed_nous_tools_enabled'), + 'nous_tool_gateway_unavailable_message': ('tools.tool_backend_helpers', 'nous_tool_gateway_unavailable_message'), + 'resolve_managed_tool_gateway': ('tools.managed_tool_gateway', 'resolve_managed_tool_gateway'), + 'resolve_openai_audio_api_key': ('tools.tool_backend_helpers', 'resolve_openai_audio_api_key'), + 'windows_hide_flags': ('hermes_cli._subprocess_compat', 'windows_hide_flags'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/tts_tool.py b/tools/tts_tool.py index 3d2d189503..2c37e43596 100644 --- a/tools/tts_tool.py +++ b/tools/tts_tool.py @@ -573,3 +573,105 @@ registry.register( **{k: args.get(k) for k in ("output_path", "speed", "instructions", "provider")}), check_fn=check_tts_requirements, emoji="🔊") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from concurrent.futures import Future # noqa: F401,E402 +from typing import Iterator # noqa: F401,E402 +from concurrent.futures import ThreadPoolExecutor # noqa: F401,E402 +from typing import Tuple # noqa: F401,E402 +import base64 # noqa: F401,E402 +from dataclasses import dataclass # noqa: F401,E402 +from dataclasses import field # noqa: F401,E402 +import platform # noqa: F401,E402 +import queue # noqa: F401,E402 +import re # noqa: F401,E402 +import shlex # noqa: F401,E402 +import shutil # noqa: F401,E402 +import subprocess # noqa: F401,E402 +import threading # noqa: F401,E402 +import time # noqa: F401,E402 +from urllib.parse import urljoin # noqa: F401,E402 +from urllib.parse import urlparse # noqa: F401,E402 +import uuid # noqa: F401,E402 + +GEMINI_TTS_CHANNELS = 1 + +GEMINI_TTS_SAMPLE_RATE = 24000 + +GEMINI_TTS_SAMPLE_WIDTH = 2 # 16-bit PCM (L16) + + +_PLUGIN_COMPAT_LAZY = { + 'AudioDeliveryProfile': ('tools.tts_tool_delivery', 'AudioDeliveryProfile'), + 'COMMAND_TTS_OUTPUT_FORMATS': ('tools.tts_command_provider', 'COMMAND_TTS_OUTPUT_FORMATS'), + 'DEFAULT_COMMAND_TTS_MAX_TEXT_LENGTH': ('tools.tts_command_provider', 'DEFAULT_COMMAND_TTS_MAX_TEXT_LENGTH'), + 'DEFAULT_COMMAND_TTS_OUTPUT_FORMAT': ('tools.tts_command_provider', 'DEFAULT_COMMAND_TTS_OUTPUT_FORMAT'), + 'DEFAULT_COMMAND_TTS_TIMEOUT_SECONDS': ('tools.tts_command_provider', 'DEFAULT_COMMAND_TTS_TIMEOUT_SECONDS'), + 'DEFAULT_DEEPINFRA_TTS_VOICE': ('tools.tts_tool_openai', 'DEFAULT_DEEPINFRA_TTS_VOICE'), + 'DEFAULT_EDGE_VOICE': ('tools.tts_tool_providers', 'DEFAULT_EDGE_VOICE'), + 'DEFAULT_ELEVENLABS_MODEL_ID': ('tools.tts_tool_providers', 'DEFAULT_ELEVENLABS_MODEL_ID'), + 'DEFAULT_ELEVENLABS_STREAMING_MODEL_ID': ('tools.tts_tool_providers', 'DEFAULT_ELEVENLABS_STREAMING_MODEL_ID'), + 'DEFAULT_ELEVENLABS_VOICE_ID': ('tools.tts_tool_providers', 'DEFAULT_ELEVENLABS_VOICE_ID'), + 'DEFAULT_GEMINI_AUDIO_TAGS': ('tools.tts_tool_providers', 'DEFAULT_GEMINI_AUDIO_TAGS'), + 'DEFAULT_GEMINI_TTS_BASE_URL': ('tools.tts_tool_providers', 'DEFAULT_GEMINI_TTS_BASE_URL'), + 'DEFAULT_GEMINI_TTS_MODEL': ('tools.tts_tool_providers', 'DEFAULT_GEMINI_TTS_MODEL'), + 'DEFAULT_GEMINI_TTS_VOICE': ('tools.tts_tool_providers', 'DEFAULT_GEMINI_TTS_VOICE'), + 'DEFAULT_KITTENTTS_MODEL': ('tools.tts_tool_local', 'DEFAULT_KITTENTTS_MODEL'), + 'DEFAULT_KITTENTTS_VOICE': ('tools.tts_tool_local', 'DEFAULT_KITTENTTS_VOICE'), + 'DEFAULT_MINIMAX_BASE_URL': ('tools.tts_tool_providers', 'DEFAULT_MINIMAX_BASE_URL'), + 'DEFAULT_MINIMAX_CN_BASE_URL': ('tools.tts_tool_providers', 'DEFAULT_MINIMAX_CN_BASE_URL'), + 'DEFAULT_MINIMAX_MODEL': ('tools.tts_tool_providers', 'DEFAULT_MINIMAX_MODEL'), + 'DEFAULT_MINIMAX_VOICE_ID': ('tools.tts_tool_providers', 'DEFAULT_MINIMAX_VOICE_ID'), + 'DEFAULT_MISTRAL_TTS_MODEL': ('tools.tts_tool_providers', 'DEFAULT_MISTRAL_TTS_MODEL'), + 'DEFAULT_MISTRAL_TTS_VOICE_ID': ('tools.tts_tool_providers', 'DEFAULT_MISTRAL_TTS_VOICE_ID'), + 'DEFAULT_OPENAI_BASE_URL': ('tools.tts_tool_openai', 'DEFAULT_OPENAI_BASE_URL'), + 'DEFAULT_OPENAI_MODEL': ('tools.tts_tool_openai', 'DEFAULT_OPENAI_MODEL'), + 'DEFAULT_OPENAI_VOICE': ('tools.tts_tool_openai', 'DEFAULT_OPENAI_VOICE'), + 'DEFAULT_PIPER_VOICE': ('tools.tts_tool_local', 'DEFAULT_PIPER_VOICE'), + 'DEFAULT_XAI_AUTO_SPEECH_TAGS': ('tools.tts_tool_providers', 'DEFAULT_XAI_AUTO_SPEECH_TAGS'), + 'DEFAULT_XAI_BASE_URL': ('tools.xai_http', 'DEFAULT_XAI_BASE_URL'), + 'DEFAULT_XAI_BIT_RATE': ('tools.tts_tool_providers', 'DEFAULT_XAI_BIT_RATE'), + 'DEFAULT_XAI_LANGUAGE': ('tools.tts_tool_providers', 'DEFAULT_XAI_LANGUAGE'), + 'DEFAULT_XAI_OPTIMIZE_STREAMING_LATENCY_DEFAULT': ('tools.tts_tool_providers', 'DEFAULT_XAI_OPTIMIZE_STREAMING_LATENCY_DEFAULT'), + 'DEFAULT_XAI_SAMPLE_RATE': ('tools.tts_tool_providers', 'DEFAULT_XAI_SAMPLE_RATE'), + 'DEFAULT_XAI_SPEED_DEFAULT': ('tools.tts_tool_providers', 'DEFAULT_XAI_SPEED_DEFAULT'), + 'DEFAULT_XAI_SPEED_MAX': ('tools.tts_tool_providers', 'DEFAULT_XAI_SPEED_MAX'), + 'DEFAULT_XAI_SPEED_MIN': ('tools.tts_tool_providers', 'DEFAULT_XAI_SPEED_MIN'), + 'DEFAULT_XAI_TEXT_NORMALIZATION_DEFAULT': ('tools.tts_tool_providers', 'DEFAULT_XAI_TEXT_NORMALIZATION_DEFAULT'), + 'DEFAULT_XAI_VOICE_ID': ('tools.tts_tool_providers', 'DEFAULT_XAI_VOICE_ID'), + 'ELEVENLABS_MODEL_MAX_TEXT_LENGTH': ('tools.tts_tool_delivery', 'ELEVENLABS_MODEL_MAX_TEXT_LENGTH'), + 'FALLBACK_MAX_TEXT_LENGTH': ('tools.tts_tool_delivery', 'FALLBACK_MAX_TEXT_LENGTH'), + 'GEMINI_AUDIO_TAG_REWRITE_TASK': ('tools.tts_tool_providers', 'GEMINI_AUDIO_TAG_REWRITE_TASK'), + 'MANAGED_OPENAI_TTS_MODELS': ('tools.tts_tool_openai', 'MANAGED_OPENAI_TTS_MODELS'), + 'MAX_TEXT_LENGTH': ('gateway.platforms.bluebubbles', 'MAX_TEXT_LENGTH'), + 'PROVIDER_MAX_TEXT_LENGTH': ('tools.tts_tool_delivery', 'PROVIDER_MAX_TEXT_LENGTH'), + 'TTS_RESPONSE_BODY_CHUNK_BYTES': ('tools.tts_tool_providers', 'TTS_RESPONSE_BODY_CHUNK_BYTES'), + 'TTS_RESPONSE_BODY_LIMIT_BYTES': ('tools.tts_tool_providers', 'TTS_RESPONSE_BODY_LIMIT_BYTES'), + 'acquire_tts_lease': ('tools.tts_tool_lifecycle', 'acquire_tts_lease'), + 'hermes_xai_user_agent': ('tools.xai_http', 'hermes_xai_user_agent'), + 'managed_nous_tools_enabled': ('tools.tool_backend_helpers', 'managed_nous_tools_enabled'), + 'nous_tool_gateway_unavailable_message': ('tools.tool_backend_helpers', 'nous_tool_gateway_unavailable_message'), + 'read_selection': ('tools.tool_backend_helpers', 'read_selection'), + 'release_tts_lease': ('tools.tts_tool_lifecycle', 'release_tts_lease'), + 'release_tts_provider': ('tools.tts_tool_lifecycle', 'release_tts_provider'), + 'resolve_managed_tool_gateway': ('tools.managed_tool_gateway', 'resolve_managed_tool_gateway'), + 'resolve_openai_audio_api_key': ('tools.tool_backend_helpers', 'resolve_openai_audio_api_key'), + 'selection_error': ('tools.tool_backend_helpers', 'selection_error'), + 'stream_tts_to_speaker': ('tools.tts_tool_speaker', 'stream_tts_to_speaker'), + 'tts_lease_holders': ('tools.tts_tool_lifecycle', 'tts_lease_holders'), + 'warm_tts_provider': ('tools.tts_tool_lifecycle', 'warm_tts_provider'), + 'windows_hide_flags': ('hermes_cli._subprocess_compat', 'windows_hide_flags'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/url_safety.py b/tools/url_safety.py index 88dbdc8b83..539362ffa5 100644 --- a/tools/url_safety.py +++ b/tools/url_safety.py @@ -496,3 +496,60 @@ def redirect_target_from_response(response: Any) -> Optional[str]: return urljoin(str(getattr(response, "url", "")), str(location)) next_request = getattr(response, "next_request", None) return str(next_request.url) if next_request else None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def has_sensitive_query_params(url: str) -> bool: + """Return True when ``url`` carries likely credential-bearing query params.""" + return sensitive_query_param_name(url) is not None + +def ssrf_safe_async_http_transport(**kwargs: Any) -> Any: + """Return an httpx async transport that pins direct TCP connects to vetted IPs.""" + import contextvars + import httpx + + schemes_by_origin_var = contextvars.ContextVar("hermes_ssrf_async_origin_schemes") + + class _Transport(httpx.AsyncHTTPTransport): + def __init__(self, **transport_kwargs: Any): + super().__init__(**transport_kwargs) + self._pool._network_backend = _SSRFGuardedAsyncNetworkBackend( # type: ignore[attr-defined] + schemes_by_origin_var + ) + + async def handle_async_request(self, request: Any) -> Any: + token = schemes_by_origin_var.set(_origin_scheme_context(request)) + try: + return await super().handle_async_request(request) + finally: + schemes_by_origin_var.reset(token) + + return _Transport(**kwargs) + +def ssrf_safe_http_transport(**kwargs: Any) -> Any: + """Return an httpx sync transport that pins direct TCP connects to vetted IPs.""" + import contextvars + import httpx + + schemes_by_origin_var = contextvars.ContextVar("hermes_ssrf_origin_schemes") + + class _Transport(httpx.HTTPTransport): + def __init__(self, **transport_kwargs: Any): + super().__init__(**transport_kwargs) + self._pool._network_backend = _SSRFGuardedNetworkBackend( # type: ignore[attr-defined] + schemes_by_origin_var + ) + + def handle_request(self, request: Any) -> Any: + token = schemes_by_origin_var.set(_origin_scheme_context(request)) + try: + return super().handle_request(request) + finally: + schemes_by_origin_var.reset(token) + + return _Transport(**kwargs) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/vision_tools.py b/tools/vision_tools.py index da2d1637f0..f15404af4a 100644 --- a/tools/vision_tools.py +++ b/tools/vision_tools.py @@ -1048,3 +1048,13 @@ registry.register( check_fn=check_vision_requirements, is_async=True, emoji="🎬") + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import contextlib # noqa: F401,E402 +import sys # noqa: F401,E402 +import threading # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/voice_mode.py b/tools/voice_mode.py index 8d50615433..f0f4d0cc20 100644 --- a/tools/voice_mode.py +++ b/tools/voice_mode.py @@ -1512,3 +1512,30 @@ def cleanup_temp_recordings(max_age_seconds: int = 3600) -> int: if deleted: logger.debug("Cleaned up %d old voice recordings", deleted) return deleted + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import difflib # noqa: F401,E402 +import re # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'DEFAULT_TTS_ECHO_SIMILARITY_THRESHOLD': ('tools.voice_mode_transcript', 'DEFAULT_TTS_ECHO_SIMILARITY_THRESHOLD'), + 'DEFAULT_VOICE_STOP_PHRASES': ('tools.voice_mode_transcript', 'DEFAULT_VOICE_STOP_PHRASES'), + 'MIN_FRAGMENT_LENGTH_FOR_ECHO': ('tools.voice_mode_transcript', 'MIN_FRAGMENT_LENGTH_FOR_ECHO'), + 'WHISPER_HALLUCINATIONS': ('tools.voice_mode_transcript', 'WHISPER_HALLUCINATIONS'), + 'is_tts_echo': ('tools.voice_mode_transcript', 'is_tts_echo'), + 'voice_stop_hint': ('tools.voice_mode_transcript', 'voice_stop_hint'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/web_result_cache.py b/tools/web_result_cache.py index 1eda5ff315..1ed9ce9172 100644 --- a/tools/web_result_cache.py +++ b/tools/web_result_cache.py @@ -302,3 +302,12 @@ def extract_cache_put( _save_index(index) except Exception as exc: # noqa: BLE001 — cache writes are best-effort logger.debug("Failed to cache web extract for %s: %s", url, exc) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Any # noqa: F401,E402 +from typing import List # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/web_tools.py b/tools/web_tools.py index fb12f868ca..2895fffe9b 100644 --- a/tools/web_tools.py +++ b/tools/web_tools.py @@ -506,3 +506,38 @@ registry.register( check_fn=check_web_api_key, requires_env=_web_requires_env(), is_async=True, emoji="📄", max_result_size_chars=100_000, ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Dict # noqa: F401,E402 +from typing import TYPE_CHECKING # noqa: F401,E402 +import asyncio # noqa: F401,E402 +import httpx # noqa: F401,E402 +import re # noqa: F401,E402 +import sys # noqa: F401,E402 + + +_PLUGIN_COMPAT_LAZY = { + 'DEFAULT_EXTRACT_CHAR_LIMIT': ('tools.web_tools_truncate', 'DEFAULT_EXTRACT_CHAR_LIMIT'), + 'Firecrawl': ('plugins.web.firecrawl.provider', 'Firecrawl'), + 'MAX_STORED_TEXT_CHARS': ('tools.web_tools_truncate', 'MAX_STORED_TEXT_CHARS'), + 'build_vendor_gateway_url': ('tools.managed_tool_gateway', 'build_vendor_gateway_url'), + 'managed_nous_tools_enabled': ('tools.tool_backend_helpers', 'managed_nous_tools_enabled'), + 'normalize_url_for_request': ('tools.url_safety', 'normalize_url_for_request'), + 'nous_tool_gateway_unavailable_message': ('tools.tool_backend_helpers', 'nous_tool_gateway_unavailable_message'), + 'prefers_gateway': ('tools.tool_backend_helpers', 'prefers_gateway'), + 'resolve_managed_tool_gateway': ('tools.managed_tool_gateway', 'resolve_managed_tool_gateway'), + 'sensitive_query_param_name': ('tools.url_safety', 'sensitive_query_param_name'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/website_policy.py b/tools/website_policy.py index 9710a1d617..be0af25fd3 100644 --- a/tools/website_policy.py +++ b/tools/website_policy.py @@ -183,3 +183,16 @@ def check_website_access(url: str, config_path: Optional[Path] = None) -> Option "message": f"Blocked by website policy: '{host}' matched rule '{pattern}' from {source}", } return None + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def invalidate_cache() -> None: + """Force the next ``check_website_access`` call to re-read config.""" + global _cached_policy + with _cache_lock: + _cached_policy = None +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/write_approval.py b/tools/write_approval.py index 5f84aeb72c..f1e99b46e9 100644 --- a/tools/write_approval.py +++ b/tools/write_approval.py @@ -279,3 +279,13 @@ def skill_pending_diff(record: Dict[str, Any]) -> str: diff = difflib.unified_diff(current.splitlines(keepends=True), new.splitlines(keepends=True), fromfile=f"a/{target_label}", tofile=f"b/{target_label}") return "".join(diff) or "(no textual change)" + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def is_background() -> bool: + return current_origin() == "background_review" +# ---- END PLUGIN-COMPAT ---- diff --git a/tools/yuanbao_tools.py b/tools/yuanbao_tools.py index b7aec9813b..13bf308380 100644 --- a/tools/yuanbao_tools.py +++ b/tools/yuanbao_tools.py @@ -493,3 +493,12 @@ for _schema, _handler, _emoji in _TOOLS: name=_schema["name"], toolset="hermes-yuanbao", schema=_schema, handler=_handler, check_fn=_check_yuanbao, is_async=True, emoji=_emoji, ) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import List # noqa: F401,E402 +from typing import Optional # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/toolsets.py b/toolsets.py index 23a8ab8e2d..7e87f49dd8 100644 --- a/toolsets.py +++ b/toolsets.py @@ -438,3 +438,28 @@ def get_toolset_info(name: str) -> Dict[str, Any]: "resolved_tools": resolved_tools, "tool_count": len(resolved_tools), "is_composite": bool(toolset["includes"]), } + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + +def resolve_multiple_toolsets(toolset_names: List[str]) -> List[str]: + """ + Resolve multiple toolsets and combine their tools. + + Args: + toolset_names (List[str]): List of toolset names to resolve + + Returns: + List[str]: Combined list of all tool names (deduplicated) + """ + all_tools = set() + + for name in toolset_names: + tools = resolve_toolset(name) + all_tools.update(tools) + + return sorted(all_tools) +# ---- END PLUGIN-COMPAT ---- diff --git a/tui_gateway/compute_host.py b/tui_gateway/compute_host.py index b5189353ab..eec7ec24fc 100644 --- a/tui_gateway/compute_host.py +++ b/tui_gateway/compute_host.py @@ -515,3 +515,84 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": # pragma: no cover raise SystemExit(main()) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from dataclasses import field # noqa: F401,E402 +from dataclasses import dataclass # noqa: F401,E402 +from dataclasses import dataclass # noqa: F401,E402 +from dataclasses import field # noqa: F401,E402 + +@dataclass +class SpikeAgent: + """A deterministic AIAgent-shaped object for pipe/interrupt measurements.""" + + session_id: str + history: list[dict[str, str]] = field(default_factory=list) + _interrupt: threading.Event = field(default_factory=threading.Event) + + def clear_interrupt(self) -> None: + self._interrupt.clear() + + def interrupt(self, *, hard_cancel: bool = False) -> None: + self._interrupt.set() + + def run_conversation( + self, + prompt: str, + *, + conversation_history: list[dict[str, str]] | None = None, + stream_callback: Callable[[str], None] | None = None, + delta_count: int = 24, + delay_s: float = 0.001, + ) -> dict[str, Any]: + base_history = list(conversation_history if conversation_history is not None else self.history) + chunks: list[str] = [] + interrupted = False + for index in range(max(0, int(delta_count))): + if self._interrupt.is_set(): + interrupted = True + break + chunk = f"{self.session_id}:{prompt}:{index:04d} " + chunks.append(chunk) + if stream_callback is not None: + stream_callback(chunk) + if delay_s > 0: + time.sleep(delay_s) + if self._interrupt.is_set(): + interrupted = True + final = "".join(chunks) + if interrupted: + final += "[interrupted]" + messages = [ + *base_history, + {"role": "user", "content": prompt}, + {"role": "assistant", "content": final}, + ] + self.history = messages + return {"final_response": final, "messages": messages, "interrupted": interrupted} + +@dataclass +class HostSession: + sid: str + agent: SpikeAgent + history_version: int = 0 + running: bool = False + lock: threading.Lock = field(default_factory=threading.Lock) + + +_PLUGIN_COMPAT_LAZY = { + 'request_hard_interrupt': ('agent.interrupt_compat', 'request_hard_interrupt'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tui_gateway/hosted_room_driver.py b/tui_gateway/hosted_room_driver.py index 1867c02f4b..5e4f5eab46 100644 --- a/tui_gateway/hosted_room_driver.py +++ b/tui_gateway/hosted_room_driver.py @@ -968,3 +968,16 @@ def _info_is_active_for( info: Mapping[str, Any], identity: state.TaskIdentity, *, require_exact: bool = False) -> bool: accepted = (identity.task_id,) if require_exact else (None, identity.task_id) return _info_active(info) and info.get("task_id") in accepted + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import contextlib # noqa: F401,E402 + +@contextlib.contextmanager +def null_turn_lock(_profile: str) -> Any: + """Provide an explicit no-op lock for narrow embedding tests.""" + yield +# ---- END PLUGIN-COMPAT ---- diff --git a/tui_gateway/hosted_room_service.py b/tui_gateway/hosted_room_service.py index 0246d75e89..ca23d955ed 100644 --- a/tui_gateway/hosted_room_service.py +++ b/tui_gateway/hosted_room_service.py @@ -613,3 +613,11 @@ class _RouteStatusPeerClient: self._on_ready() return result return tracked + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import hashlib # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ---- diff --git a/tui_gateway/mcp_rpc_helpers.py b/tui_gateway/mcp_rpc_helpers.py index 34b6e17ca0..aa69566f99 100644 --- a/tui_gateway/mcp_rpc_helpers.py +++ b/tui_gateway/mcp_rpc_helpers.py @@ -44,3 +44,31 @@ def summarize_server(name: str, cfg: dict) -> Dict[str, Any]: "oauth_tokens_present": _oauth_tokens_present(name) if auth == "oauth" else None, "enabled": cfg.get("enabled", True) is not False, "tools": cfg.get("tools")} + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +from typing import Optional # noqa: F401,E402 +from typing import Tuple # noqa: F401,E402 + +def resolve_profile(rid, params, err_fn) -> Tuple[Optional[Any], Optional[dict]]: + """Resolve the optional ``profile`` param to a HERMES_HOME override token. + + Returns ``(token, error)``: ``token`` is None for the launch profile (no + override) or an opaque reset token; ``error`` is a JSON-RPC error dict + (built via ``err_fn``) when the named profile doesn't exist. Callers reset + ``token`` in a finally via :func:`reset_profile`. + """ + profile = str(params.get("profile") or "").strip() + if not profile: + return None, None + from hermes_cli.profiles import get_profile_dir + from hermes_constants import set_hermes_home_override + + profile_dir = get_profile_dir(profile) + if not profile_dir or not profile_dir.is_dir(): + return None, err_fn(rid, 4064, f"profile '{profile}' not found") + return set_hermes_home_override(str(profile_dir)), None +# ---- END PLUGIN-COMPAT ---- diff --git a/tui_gateway/methods_browser_control.py b/tui_gateway/methods_browser_control.py index cff73ec875..e1a12bdc14 100644 --- a/tui_gateway/methods_browser_control.py +++ b/tui_gateway/methods_browser_control.py @@ -204,3 +204,25 @@ def _(rid, params: dict, transport, _identity, _session_id, broker, scope, _sess def register(server) -> None: """Publish helpers/constants onto ``server`` and install handlers (rebound to its globals).""" bind_module(globals(), server, skip=("_",)) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. + + +_PLUGIN_COMPAT_LAZY = { + 'BROWSER_CONTROL_PROTOCOL_VERSION': ('gateway.browser_control_broker', 'BROWSER_CONTROL_PROTOCOL_VERSION'), + 'browser_control_protocol_supported': ('gateway.browser_control_broker', 'browser_control_protocol_supported'), + 'filter_browser_control_capabilities': ('gateway.browser_control_broker', 'filter_browser_control_capabilities'), +} + + +def __getattr__(name): # PEP 562 — lazy so no import cycles + target = _PLUGIN_COMPAT_LAZY.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib + return getattr(importlib.import_module(target[0]), target[1]) +# ---- END PLUGIN-COMPAT ---- diff --git a/tui_gateway/methods_prompt.py b/tui_gateway/methods_prompt.py index 0b4af43ed9..b37588e7b0 100644 --- a/tui_gateway/methods_prompt.py +++ b/tui_gateway/methods_prompt.py @@ -1171,3 +1171,11 @@ def _(rid, params: dict) -> dict: def register(server) -> None: """Publish this module's helpers + handlers onto ``server``, rebound to its globals.""" bind_module(globals(), server, skip=("_",)) + + +# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ---- +# Names external plugins imported from this module before the Sep 2026 decomposition. +# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does). +# The whole block is removed by reverting the commit that added it. +import types # noqa: F401,E402 +# ---- END PLUGIN-COMPAT ----