mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
* fix(lint): skip per-file shell linter when LSP will handle the file `_check_lint` ran `npx tsc --noEmit FILE.ts` after every `.ts`/`.tsx` edit. `tsc` ignores `tsconfig.json` when given an explicit file argument (documented quirk) and defaults to no-lib / ES5, so every ES2015+ stdlib reference reports as missing: - `Cannot find global value 'Promise'` - `Cannot find name 'Map' / 'Set' / 'ReadonlySet' / 'Iterable'` - `Property 'isFinite' does not exist on type 'NumberConstructor'` - `Module 'phaser' can only be default-imported using esModuleInterop` - `import.meta is only allowed when --module is es2020+` On real TypeScript projects this floods the `lint` field on WriteResult / PatchResult with up to 25K tokens of false positives per edit. The delta filter in `_check_lint_delta` is supposed to mask them, but a tiny edit shifts line numbers and every phantom resurfaces as "introduced by this edit". The result is a 1MB+ phantom-error dump on every patch that eats the agent's context budget. Same shape for `.go` (`go vet` outside a module) and `.rs` (`rustfmt --check` outside a Cargo project). PR #24168 added an LSP tier on top of this — real `tsserver` / `gopls` / `rust-analyzer` diagnostics surface in the separate `lsp_diagnostics` field. But the broken shell linter kept running underneath, so the phantom-error dump kept happening even when LSP was giving us a clean authoritative signal. This change short-circuits the shell linter for the structurally-broken extensions (`.ts`, `.tsx`, `.go`, `.rs`) when an LSP server is active and claims the file via `LSPService.enabled_for(path)`. The LSP tier runs as before and carries the real diagnostics in `lsp_diagnostics`. Other shell linters (`py_compile`, `node --check`) keep running unconditionally — they're fast, file-local, and correct. Default behavior (LSP disabled, LSP misconfigured, remote backend, file outside a workspace) is unchanged — the existing fallback paths trigger when `_lsp_will_handle` returns False, so users who haven't opted into LSP get the same shell-linter behavior they had before. Drive-by: `.tsx` was missing from the `LINTERS` table entirely, so TS React files got no post-edit syntax check at all. Added it for symmetry; in practice it now hits the LSP-skip path. Tests: - `tests/agent/lsp/test_shell_linter_lsp_skip.py` — 14 tests covering: * skip happens for each redundant extension when LSP claims the file (asserted by patching `_exec` to raise on any shell-linter call) * shell linter still runs when LSP is inactive (regression guard) * `.py` / `.js` continue to run unconditionally even with LSP active * `_lsp_will_handle` is exception-safe: returns False on None service, remote backend, or `enabled_for` raising * `.tsx` is in both `LINTERS` and `_SHELL_LINTER_LSP_REDUNDANT` - All pre-existing tests in `tests/agent/lsp/` and `tests/tools/test_file_operations*.py` still pass (233/233). * fix(lint): address Copilot review on #29054 Two fixes from copilot-pull-request-reviewer on PR #29054: 1. `.tsx` regression with LSP disabled (https://github.com/NousResearch/hermes-agent/pull/29054#discussion_r3271017282) The first revision added `.tsx` to the `LINTERS` table so that TypeScript React files would hit the LSP skip path. Side effect: when LSP is *disabled* (the default), `.tsx` edits would suddenly run `npx tsc --noEmit FILE.tsx` and inherit the same phantom-error dump this PR is supposed to fix. Pre-PR behavior was implicit `skipped` (no `LINTERS` entry); restore that. - Remove `.tsx` from `LINTERS`. - Remove `.tsx` from `_SHELL_LINTER_LSP_REDUNDANT` (the skip path is unreachable without a `LINTERS` entry — falls through to `ext not in LINTERS` first). - When LSP IS enabled, `.tsx` is still covered by the LSP tier via `_maybe_lsp_diagnostics` (typescript-language-server's `extensions` tuple includes `.tsx`), so the diagnostics still surface — just on the `lsp_diagnostics` channel, not `lint`. - Update test_shell_linter_lsp_skip.py to reflect this contract (drop `.tsx` from the parametrize lists; add `test_tsx_stays_out_of_linters_table_for_default_compatibility` and `test_tsx_default_check_lint_returns_skipped`). 2. V4A patches dropped `WriteResult.lsp_diagnostics` (https://github.com/NousResearch/hermes-agent/pull/29054#discussion_r3271017295) `tools/patch_parser.py::apply_v4a_operations` calls `file_ops.write_file()` per operation, then calls `_check_lint()` directly afterwards — but never propagates `WriteResult.lsp_diagnostics` to the `PatchResult`. The shell-linter skip introduced in this PR makes the gap visible: a `.ts` / `.go` / `.rs` V4A patch with LSP active would return `lint = {f: {skipped: True}}` and zero diagnostics from any channel. - `_apply_add` and `_apply_update` now return `Tuple[bool, str, Optional[str]]` where the third element is `WriteResult.lsp_diagnostics` (or `None` on failure / no diags). - `_apply_delete` and `_apply_move` stay 2-tuples — they don't produce diagnostics, no write goes through `write_file`. - `apply_v4a_operations` accumulates per-file diagnostics blocks and surfaces a combined block on `PatchResult.lsp_diagnostics`. Each block already carries its `<diagnostics file="...">` header from `LSPService.report_for_file`, so concatenation preserves per-file attribution. Tests added (`test_patch_parser.py::TestV4ALspDiagnosticsPropagation`): - ADD op: `WriteResult.lsp_diagnostics` flows to `PatchResult` - UPDATE op: same - No diagnostics → `PatchResult.lsp_diagnostics is None` (not "") - Multi-file patch: combined block contains every per-file block Verification: - Targeted test scope: 257/257 pass (tests/agent/lsp/, tests/tools/test_file_operations*.py, tests/tools/test_patch_parser.py) - Wider sweep: 5400 pass; 11 failures all pre-existing on origin/main (file_staleness / file_read_guards / file_state_registry — unrelated macOS /var/folders tmp-path sensitivity issues, confirmed by re-running on a clean origin/main checkout) * docs(test): align shell-linter LSP skip docstring with .tsx behavior Copilot review feedback (review #4324947616, comment #3271049036): the test module docstring still listed .tsx alongside .ts/.go/.rs in the skip contract, but .tsx is now intentionally NOT in LINTERS or _SHELL_LINTER_LSP_REDUNDANT. Updated the bullet list to drop .tsx from the skip contract and added a paragraph documenting why .tsx is left out (preserves pre-PR implicit-skip behavior for LSP-disabled users; LSP coverage still happens via _maybe_lsp_diagnostics). * test(lsp): drop unused tmp_path from _make_fops helper Copilot review #3271069484: the helper accepted tmp_path but never used it. Callers still need tmp_path themselves for the file they're asserting against, so we just drop the helper's parameter. |
||
|---|---|---|
| .. | ||
| __init__.py | ||
| test_accretion_caps.py | ||
| test_ansi_strip.py | ||
| test_approval.py | ||
| test_approval_heartbeat.py | ||
| test_approval_plugin_hooks.py | ||
| test_base_environment.py | ||
| test_browser_camofox.py | ||
| test_browser_camofox_persistence.py | ||
| test_browser_camofox_state.py | ||
| test_browser_cdp_override.py | ||
| test_browser_cdp_tool.py | ||
| test_browser_chromium_check.py | ||
| test_browser_cleanup.py | ||
| test_browser_cloud_fallback.py | ||
| test_browser_cloud_provider_cache.py | ||
| test_browser_console.py | ||
| test_browser_content_none_guard.py | ||
| test_browser_eval_supervisor_path.py | ||
| test_browser_hardening.py | ||
| test_browser_homebrew_paths.py | ||
| test_browser_hybrid_routing.py | ||
| test_browser_lightpanda.py | ||
| test_browser_orphan_reaper.py | ||
| test_browser_secret_exfil.py | ||
| test_browser_ssrf_local.py | ||
| test_browser_supervisor.py | ||
| test_browser_supervisor_healthcheck.py | ||
| test_budget_config.py | ||
| test_checkpoint_manager.py | ||
| test_clarify_gateway.py | ||
| test_clarify_tool.py | ||
| test_clipboard.py | ||
| test_code_execution.py | ||
| test_code_execution_modes.py | ||
| test_code_execution_windows_env.py | ||
| test_command_guards.py | ||
| test_computer_use.py | ||
| test_config_null_guard.py | ||
| test_credential_files.py | ||
| test_credential_pool_env_fallback.py | ||
| test_cron_approval_mode.py | ||
| test_cron_prompt_injection.py | ||
| test_cronjob_tools.py | ||
| test_daytona_environment.py | ||
| test_debug_helpers.py | ||
| test_delegate.py | ||
| test_delegate_composite_toolsets.py | ||
| test_delegate_subagent_timeout_diagnostic.py | ||
| test_delegate_toolset_scope.py | ||
| test_discord_tool.py | ||
| test_docker_environment.py | ||
| test_docker_find.py | ||
| test_dockerfile_node_modules_perms.py | ||
| test_dockerfile_pid1_reaping.py | ||
| test_env_passthrough.py | ||
| test_feishu_tools.py | ||
| test_file_operations.py | ||
| test_file_operations_edge_cases.py | ||
| test_file_ops_cwd_tracking.py | ||
| test_file_read_guards.py | ||
| test_file_staleness.py | ||
| test_file_state_registry.py | ||
| test_file_sync.py | ||
| test_file_sync_back.py | ||
| test_file_sync_perf.py | ||
| test_file_tools.py | ||
| test_file_tools_container_config.py | ||
| test_file_tools_live.py | ||
| test_file_write_safety.py | ||
| test_force_dangerous_override.py | ||
| test_fuzzy_match.py | ||
| test_hardline_blocklist.py | ||
| test_heartbeat_stale_thresholds.py | ||
| test_hidden_dir_filter.py | ||
| test_homeassistant_tool.py | ||
| test_image_generation.py | ||
| test_image_generation_env.py | ||
| test_image_generation_plugin_dispatch.py | ||
| test_init_session_cwd_respect.py | ||
| test_interrupt.py | ||
| test_kanban_codex_lane_skill.py | ||
| test_kanban_tools.py | ||
| test_lazy_deps.py | ||
| test_llm_content_none_guard.py | ||
| test_local_background_child_hang.py | ||
| test_local_env_blocklist.py | ||
| test_local_env_cwd_recovery.py | ||
| test_local_env_windows_msys.py | ||
| test_local_interrupt_cleanup.py | ||
| test_local_shell_init.py | ||
| test_local_tempdir.py | ||
| test_managed_browserbase_and_modal.py | ||
| test_managed_media_gateways.py | ||
| test_managed_modal_environment.py | ||
| test_managed_tool_gateway.py | ||
| test_mcp_cancelled_error_propagation.py | ||
| test_mcp_circuit_breaker.py | ||
| test_mcp_dynamic_discovery.py | ||
| test_mcp_empty_error_message.py | ||
| test_mcp_image_content.py | ||
| test_mcp_invalid_url.py | ||
| test_mcp_oauth.py | ||
| test_mcp_oauth_bidirectional.py | ||
| test_mcp_oauth_cold_load_expiry.py | ||
| test_mcp_oauth_integration.py | ||
| test_mcp_oauth_manager.py | ||
| test_mcp_oauth_metadata.py | ||
| test_mcp_probe.py | ||
| test_mcp_reconnect_signal.py | ||
| test_mcp_sse_transport.py | ||
| test_mcp_stability.py | ||
| test_mcp_structured_content.py | ||
| test_mcp_tool.py | ||
| test_mcp_tool_401_handling.py | ||
| test_mcp_tool_issue_948.py | ||
| test_mcp_tool_session_expired.py | ||
| test_mcp_utility_capability_gating.py | ||
| test_memory_tool.py | ||
| test_memory_tool_import_fallback.py | ||
| test_memory_tool_schema.py | ||
| test_microsoft_graph_auth.py | ||
| test_microsoft_graph_client.py | ||
| test_mixture_of_agents_tool.py | ||
| test_modal_bulk_upload.py | ||
| test_modal_sandbox_fixes.py | ||
| test_modal_snapshot_isolation.py | ||
| test_notify_on_complete.py | ||
| test_osv_check.py | ||
| test_parse_env_var.py | ||
| test_patch_parser.py | ||
| test_process_registry.py | ||
| test_read_loop_detection.py | ||
| test_registry.py | ||
| test_resolve_path.py | ||
| test_schema_sanitizer.py | ||
| test_search_hidden_dirs.py | ||
| test_send_message_missing_platforms.py | ||
| test_send_message_telegram_proxy.py | ||
| test_send_message_tool.py | ||
| test_session_search.py | ||
| test_shared_container_task_id.py | ||
| test_signal_media.py | ||
| test_singularity_preflight.py | ||
| test_skill_env_passthrough.py | ||
| test_skill_improvements.py | ||
| test_skill_manager_tool.py | ||
| test_skill_provenance.py | ||
| test_skill_size_limits.py | ||
| test_skill_usage.py | ||
| test_skill_view_path_check.py | ||
| test_skill_view_traversal.py | ||
| test_skills_guard.py | ||
| test_skills_hub.py | ||
| test_skills_hub_browse_sh.py | ||
| test_skills_hub_clawhub.py | ||
| test_skills_sync.py | ||
| test_skills_tool.py | ||
| test_slash_confirm.py | ||
| test_spotify_client.py | ||
| test_ssh_bulk_upload.py | ||
| test_ssh_environment.py | ||
| test_symlink_prefix_confusion.py | ||
| test_sync_back_backends.py | ||
| test_terminal_compound_background.py | ||
| test_terminal_config_env_sync.py | ||
| test_terminal_exit_semantics.py | ||
| test_terminal_foreground_timeout_cap.py | ||
| test_terminal_none_command_guard.py | ||
| test_terminal_output_transform_hook.py | ||
| test_terminal_requirements.py | ||
| test_terminal_task_cwd.py | ||
| test_terminal_timeout_output.py | ||
| test_terminal_tool.py | ||
| test_terminal_tool_pty_fallback.py | ||
| test_terminal_tool_requirements.py | ||
| test_threaded_process_handle.py | ||
| test_tirith_security.py | ||
| test_todo_tool.py | ||
| test_tool_backend_helpers.py | ||
| test_tool_output_limits.py | ||
| test_tool_result_storage.py | ||
| test_transcription.py | ||
| test_transcription_dotenv_fallback.py | ||
| test_transcription_tools.py | ||
| test_tts_command_providers.py | ||
| test_tts_dotenv_fallback.py | ||
| test_tts_gemini.py | ||
| test_tts_kittentts.py | ||
| test_tts_max_text_length.py | ||
| test_tts_mistral.py | ||
| test_tts_opus_routing.py | ||
| test_tts_piper.py | ||
| test_tts_speed.py | ||
| test_url_safety.py | ||
| test_vercel_sandbox_environment.py | ||
| test_video_analyze.py | ||
| test_video_generation_dispatch.py | ||
| test_video_generation_dynamic_schema.py | ||
| test_video_generation_tool_surface_matrix.py | ||
| test_vision_native_fast_path.py | ||
| test_vision_tools.py | ||
| test_voice_cli_integration.py | ||
| test_voice_mode.py | ||
| test_watch_patterns.py | ||
| test_web_providers.py | ||
| test_web_providers_brave_free.py | ||
| test_web_providers_ddgs.py | ||
| test_web_providers_searxng.py | ||
| test_web_providers_xai.py | ||
| test_web_tools_config.py | ||
| test_web_tools_tavily.py | ||
| test_website_policy.py | ||
| test_windows_compat.py | ||
| test_windows_native_support.py | ||
| test_write_deny.py | ||
| test_x_search_tool.py | ||
| test_yolo_mode.py | ||
| test_zombie_process_cleanup.py | ||