fix: stabilization fixes and architectural enhancements - #14957
fix: stabilization fixes and architectural enhancements#14957aniruddhaadak80 wants to merge 4 commits into
Conversation
austinpickett
left a comment
There was a problem hiding this comment.
Code Review -- PR #14957
Verdict: Request Changes -- one critical issue must be resolved before merge; several items warrant follow-up.
Critical
scratch/test_proxy.py committed to production
A bare debug/exploration script with loose top-level print() calls -- zero pytest/unittest structure -- has been committed. It tests built-in Python stdlib behavior (urllib.request.proxy_bypass_environment) that has no connection to any actual change in this PR. This file has no home in the production tree. Must be removed before merge. If the intent is to codify this behaviour, move it to tests/tools/test_proxy_bypass.py with proper pytest test functions.
Warnings
1. Dead code in test_curses_color_compat.py
The file already has a module-level pytest.skip(allow_module_level=True) guard that fires before any of the new code runs. The newly added pytestmark = pytest.mark.skipif(...) and the entire try/except ImportError: class DummyCurses block are unreachable dead code. Clean this up.
2. run_agent.py verifier methods not wired up in this diff
_record_file_mutation_result(), _file_mutation_verifier_enabled(), and _format_file_mutation_failure_footer() are added/restored to AIAgent but no call sites in run_agent.py are modified. If these existed before and were accidentally dropped, the PR should note when. If they are new, they need at least one call site or integration test.
3. approval.py: ~/.hermes/ replacement hardcoded for Windows paths
In _rewrite_resolved_hermes_home, the substitution string is always "~/.hermes/" (POSIX home-relative) even for the new Windows path branch. On native win32, this is not meaningful -- consider making the replacement Windows-aware.
4. api_server.py mutates os.environ at server startup
os.environ["IMAGE_SERVE_BASE_URL"] is set as a process-global side effect after bind. If a test suite starts multiple APIServerAdapter instances, the first one silently wins. Consider an instance attribute and explicit injection into the image tool.
Suggestions
- hermes_cli/config.py: TINKER_API_KEY / WANDB_API_KEY are RL-training keys unrelated to stabilisation scope. Consider a dedicated RL-tools PR.
- delegate_tool.py per-task api_key: confirm it is masked in any debug logging emitted by _build_child_agent.
- image_gen.serve_base_url and _maybe_rewrite_image_url are well-structured and defensively guarded.
Looks Good
- cron session_name: threads cleanly through create_job -> _run_job_impl -> cronjob() with schema exposure.
- Skills/tools cache invalidation: clear_skills_system_prompt_cache(clear_snapshot=True) after save is the right fix for immediate toggle effect.
- Windows test suite skips in test_search_hidden_dirs.py, test_file_operations.py, test_local_shell_init.py, test_windows_native_support.py are targeted and correct.
- test_background_review_summary.py import path update follows the refactor correctly.
Reviewed by Hermes Agent (automated)
Code Review SummaryPR #14957 -- fix: stabilization fixes and architectural enhancements Criticalscratch/test_proxy.py must not be committed to production. This is a bare debug/exploration script (top-level print() calls, no pytest structure) that exercises stdlib behavior unrelated to any diff in this PR. Remove or move to Warnings
Suggestions
Looks Good
Reviewed by Hermes Agent (automated) |
austinpickett
left a comment
There was a problem hiding this comment.
All three blockers from my last review are resolved:
scratch/test_proxy.py— absent from diff ✓- Dead/unconnected methods — removed ✓
- Duplicate verifier helpers in
run_agent.py— cleaned per commit message ✓
The remaining changes are genuine fixes:
- Chinese think-tag stripping (
思考/反思/推理/推敲) in bothstrip_think_blocksand the CLI stream filter — real bug for Chinese-locale models leaking thought blocks - Windows path fixes in
subdirectory_hints.py—RuntimeErrorcatch,posix=os.name!='nt'onshlex.split, and\\path separator recognition - Image serve URL via
ContextVarinimage_generation_tool.py+api_server.py— correct thread-safe fix for remote API clients that can't access local FS - Cache invalidation after
hermes toolsskill/tool disable — happens outside conversations, correct behavior --yeson headlessskills install— prevents interactive prompt in web server contextsession_namefor cron jobs — small, well-contained, allows named sessions to resume history across runs- Per-task endpoint creds in
delegate_task— useful, degrades cleanly to parent creds
One minor nit: the api_mode heuristic in delegate_tool.py checks hardcoded URL substrings (api.anthropic.com, api.kimi.com/coding, chatgpt.com/backend-api/codex) — will silently miss future endpoints, but the fallback to parent api_mode is safe. Consider a config-driven approach as a follow-up.
|
The branch has a merge conflict against current git fetch origin
git rebase origin/main
# resolve any conflicts, then:
git push --force-with-leaseEverything else is approved and looking good — just needs the rebase. |
017e5a1 to
70a9177
Compare
Re-review (2026-06-11, new commit
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 54 out of 54 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (12)
tools/image_generation_tool.py:1
_maybe_rewrite_image_url()currently rewrites any non-URL string when a base URL is present (including relative paths likeout.pngorimages/foo.png). This can produce incorrect URLs and hide real relative-path semantics. Fix by first confirming the value is an absolute local filesystem path (e.g.,os.path.isabs(...)for POSIX, drive-letter/UNC detection for Windows, and optionally~-prefixed paths afterexpanduser()), and only then rewrite; otherwise return the original string.
tools/image_generation_tool.py:1_maybe_rewrite_image_url()currently rewrites any non-URL string when a base URL is present (including relative paths likeout.pngorimages/foo.png). This can produce incorrect URLs and hide real relative-path semantics. Fix by first confirming the value is an absolute local filesystem path (e.g.,os.path.isabs(...)for POSIX, drive-letter/UNC detection for Windows, and optionally~-prefixed paths afterexpanduser()), and only then rewrite; otherwise return the original string.
tools/image_generation_tool.py:1_maybe_rewrite_image_url()currently rewrites any non-URL string when a base URL is present (including relative paths likeout.pngorimages/foo.png). This can produce incorrect URLs and hide real relative-path semantics. Fix by first confirming the value is an absolute local filesystem path (e.g.,os.path.isabs(...)for POSIX, drive-letter/UNC detection for Windows, and optionally~-prefixed paths afterexpanduser()), and only then rewrite; otherwise return the original string.
tools/cronjob_tools.py:1session_nameis passed/stored without normalization, so an empty string can be persisted (and later treated as falsy elsewhere), creating a confusing 'set-but-not-effective' state. Consider normalizingsession_namesimilarly to other optional string fields (strip whitespace; treat empty asNone) on both create and update paths, and document how to clear the field (e.g.,\"\"clears vsnullleaves unchanged).
tools/cronjob_tools.py:1session_nameis passed/stored without normalization, so an empty string can be persisted (and later treated as falsy elsewhere), creating a confusing 'set-but-not-effective' state. Consider normalizingsession_namesimilarly to other optional string fields (strip whitespace; treat empty asNone) on both create and update paths, and document how to clear the field (e.g.,\"\"clears vsnullleaves unchanged).
tools/cronjob_tools.py:1session_nameis passed/stored without normalization, so an empty string can be persisted (and later treated as falsy elsewhere), creating a confusing 'set-but-not-effective' state. Consider normalizingsession_namesimilarly to other optional string fields (strip whitespace; treat empty asNone) on both create and update paths, and document how to clear the field (e.g.,\"\"clears vsnullleaves unchanged).
tools/cronjob_tools.py:1session_nameis passed/stored without normalization, so an empty string can be persisted (and later treated as falsy elsewhere), creating a confusing 'set-but-not-effective' state. Consider normalizingsession_namesimilarly to other optional string fields (strip whitespace; treat empty asNone) on both create and update paths, and document how to clear the field (e.g.,\"\"clears vsnullleaves unchanged).
tools/cronjob_tools.py:1session_nameis passed/stored without normalization, so an empty string can be persisted (and later treated as falsy elsewhere), creating a confusing 'set-but-not-effective' state. Consider normalizingsession_namesimilarly to other optional string fields (strip whitespace; treat empty asNone) on both create and update paths, and document how to clear the field (e.g.,\"\"clears vsnullleaves unchanged).
tools/skills_tool.py:1- Path normalization is repeated in multiple comprehensions via
.replace(\"\\\\\", \"/\"). For clarity and consistency, consider usingPath(...).as_posix()(orPurePosixPath(relative_path)if you need a pure conversion) in one place, or a small helper to normalize relative paths to'/'separators.
tools/skills_tool.py:1 - Path normalization is repeated in multiple comprehensions via
.replace(\"\\\\\", \"/\"). For clarity and consistency, consider usingPath(...).as_posix()(orPurePosixPath(relative_path)if you need a pure conversion) in one place, or a small helper to normalize relative paths to'/'separators.
tools/skills_tool.py:1 - Path normalization is repeated in multiple comprehensions via
.replace(\"\\\\\", \"/\"). For clarity and consistency, consider usingPath(...).as_posix()(orPurePosixPath(relative_path)if you need a pure conversion) in one place, or a small helper to normalize relative paths to'/'separators.
tools/skills_tool.py:1 - Path normalization is repeated in multiple comprehensions via
.replace(\"\\\\\", \"/\"). For clarity and consistency, consider usingPath(...).as_posix()(orPurePosixPath(relative_path)if you need a pure conversion) in one place, or a small helper to normalize relative paths to'/'separators.
| _DUMMY_HASH_VAL: str | None = None | ||
|
|
||
|
|
||
| def _get_dummy_hash() -> str: | ||
| global _DUMMY_HASH_VAL | ||
| if _DUMMY_HASH_VAL is None: | ||
| _DUMMY_HASH_VAL = hash_password("dummy-password-for-constant-time-verify") | ||
| return _DUMMY_HASH_VAL |
| @@ -252,7 +259,7 @@ def complete_password_login( | |||
| username_ok = hmac.compare_digest( | |||
| username.encode("utf-8"), self._username.encode("utf-8") | |||
| ) | |||
| target_hash = self._password_hash if username_ok else _DUMMY_HASH | |||
| target_hash = self._password_hash if username_ok else _get_dummy_hash() | |||
| _OPEN_THINK_TAGS = ( | ||
| "<REASONING_SCRATCHPAD>", "<think>", "<reasoning>", | ||
| "<THINKING>", "<thinking>", "<thought>", | ||
| "<reasoning_scratchpad>", "<think>", "<reasoning>", | ||
| "<thinking>", "<thought>", " 思考", " 反思", " 推理", " 推敲", | ||
| ) | ||
| _CLOSE_THINK_TAGS = ( | ||
| "</REASONING_SCRATCHPAD>", "</think>", "</reasoning>", | ||
| "</THINKING>", "</thinking>", "</thought>", | ||
| "</reasoning_scratchpad>", "</think>", "</reasoning>", | ||
| "</thinking>", "</thought>", " 思考", " 反思", " 推理", " 推敲", | ||
| ) |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the broad stabilization work. Three changes need re-scoping before individual fixes can be salvaged.
Problems
tools/delegate_tool.py:2867-2878exposes per-task provider, endpoint, and API-key overrides to the model. Current main keeps delegation routing in trusteddelegation.*configuration (tools/delegate_tool.py:2420-2505); this model-controlled routing conflicts with that boundary.cron/scheduler.py:1488says a reusedsession_nameresumes history, but the new agent is called with onlyagent.run_conversation(prompt)atcron/scheduler.py:1791; no old transcript is restored.tools/image_generation_tool.py:650-660rewrites any non-URL string, including relative paths, although its docstring promises absolute local paths only.
Suggested changes
- Remove the per-task routing fields; retain config-controlled delegation overrides.
- Re-scope cron continuation to current main's
attach_to_sessiondesign, or implement and test explicit history restoration. - Require an absolute local path before image URL rewriting and add relative-path coverage.
Automated hermes-sweeper review.
| "type": "string", | ||
| "description": "Per-task base_url override (e.g. 'https://openrouter.ai/api/v1').", | ||
| }, | ||
| "provider": { |
There was a problem hiding this comment.
Please remove the model-facing per-task provider/base_url/api_key overrides. Current main intentionally limits delegation routing to trusted delegation.* configuration; allowing a tool call to select a provider and endpoint reintroduces model-controlled routing.
| _cron_session_id = f"cron_{job_id}_{_hermes_now().strftime('%Y%m%d_%H%M%S')}" | ||
| # Use a named session if configured, otherwise generate a timestamped one. | ||
| # Named sessions allow cron jobs to resume conversation history across runs. | ||
| _cron_session_id = job.get("session_name") or f"cron_{job_id}_{_hermes_now().strftime('%Y%m%d_%H%M%S')}" |
There was a problem hiding this comment.
Reusing an ID does not itself resume a conversation: this run constructs a new agent and later calls run_conversation(prompt) without loading prior messages. Either restore history explicitly with alternation-safe handling or avoid claiming resumable history here.
|
|
||
| # It's a local path and we have a base URL. | ||
| # Extract the filename from the path. | ||
| filename = os.path.basename(image_url) |
There was a problem hiding this comment.
This rewrites every non-URL value after base_url resolves, including relative values such as out.png, despite the absolute-path-only contract in the docstring. Check an absolute POSIX or Windows path before deriving a served URL.
…and stabilize Windows test suite
1ab4d3e to
4a82d37
Compare
…parity Image results previously carried bare server-local filesystem paths that API clients on other machines could not fetch. - api_server: mount IMAGE_CACHE_DIR at GET /images/ and bind a per-run contextvar with the serving base URL around both agent-entry points (_run_agent and /v1/runs), reset in finally alongside session vars. - image_gen_provider: success_response rewrites ABSOLUTE local paths (POSIX /, Windows drive, UNC) into <base>/images/<name> URLs only when a base is bound for the current run context; relative paths and http(s) URLs pass through untouched. - image_generation_tool: apply the same rewrite to the first returned image. - skills_tool: treat $HERMES_HOME/skills as trusted for skill_view security warnings and normalize reference/template/asset/script relative paths to forward slashes so cross-platform clients see stable paths. - tui_gateway entry: initialize logging (mode="gui") and register declarative shell hooks from config at startup, matching classic gateway behavior (consent resolved by register_from_config). Tests: tests/agent/test_image_serve_rewrite.py (10 cases covering the rewrite contract, host-native absolute paths plus OS-marked literal variants).
4a82d37 to
afdd279
Compare
|
Rescoped this branch onto current
New tests: |
…oute
aiohttp >=3.9 raises ValueError from add_static() when the directory
does not exist, so on a fresh install (or any fresh HERMES_HOME that has
never generated an image) the API server failed to start with:
[Api_Server] Failed to start API server: '<home>/cache/images' does not exist
Caught by tests/gateway/test_api_server_bind_guard.py in CI. Create the
directory (parents included) before registering the static route.
Summary of Changes
/qalias collision (Fixes CLI command alias collision: /q resolves to quit, making queue shorthand unreachable #14712)on_session_finalize(session_id=None)in gateway (Fixes /new emits on_session_finalize(session_id=None) when no prior session exists #12176)session_namesupport for cron jobs for session persistence.tests/cron/test_codex_execution_paths.py.Type of Change