fix(ssh): auto-push host-local attachments to SSH backend on read_file miss#33455
fix(ssh): auto-push host-local attachments to SSH backend on read_file miss#33455Carry00 wants to merge 3 commits into
Conversation
fb57d6e to
30317ef
Compare
…x_retries Closes NousResearch#30331. When the configured credential pool has nothing to rotate to (single-key deployments — the common pattern of one provider key in ~/.hermes/.env) and an HTTP 401/403 comes back, the existing retry loop would treat the error as transient and hit `jittered_backoff(retry_count, base_delay=5.0, max_delay=120.0)` × `max_retries` times. Each retry hit the same dead key and got the same 401 — up to ~8 minutes of pure latency before the user saw an actionable error. The fix adds a one-shot `single_key_auth_retry_attempted` flag to the per-turn state block. After all provider-specific OAuth refresh paths have had their chance (codex / nous / copilot / anthropic), an auth error with no pool rotation available now: 1. On first occurrence — retries once with a fresh connection (handles genuine transient hiccups), logging a single visible line so the user can see what's happening. 2. On the second occurrence — upgrades the ClassifiedError reason from `auth` to `auth_permanent`. The downstream `is_client_error` branch sees `retryable=False` and takes the existing non-retryable abort path, which already has actionable "your API key was rejected" hints for Codex/xAI OAuth, OpenRouter, and generic providers. status_code / provider / model / error_context are preserved through the upgrade so the abort path's diagnostic output stays accurate.
When a WebUI file upload targets a session using an SSH terminal backend, the file lands in the host attachment inbox but is absent on the remote machine. read_file/read_file_raw would return "File not found" with no recovery path. Fix: - Add SSHEnvironment.push_file() as a public wrapper around _scp_upload(), reusing the existing ControlMaster socket (no extra handshake). - Add ShellFileOperations._push_local_file_if_needed(): when a remote stat fails, checks whether the same path exists on the local host and, if the active environment exposes push_file(), transfers the file and retries. - Update both read_file() and read_file_raw() to call the fallback before returning the suggest-similar-files error. The change is transparent on local/docker backends (no push_file attribute) and adds no overhead on the happy path (fallback only triggers after a failed stat).
…s-profile SSH leakage _resolve_container_task_id always returned "default", so _active_environments shared a single SSHEnvironment across all WebUI sessions. When a user switched from profile A (ssh_host=10.0.0.1) to profile B (ssh_host=10.0.0.2), the new session found _active_environments["default"] already set to A's SSHEnvironment and reused it — silently running every command on the wrong remote host. Fix: when HERMES_SESSION_KEY is present (set per-session by the WebUI streaming layer and per-message by the gateway via contextvars), return "session:<key>" as the cache key instead of "default". Each session now owns its own slot in _active_environments and always creates an environment from its own profile's TERMINAL_SSH_HOST / TERMINAL_ENV config. Behaviour unchanged in CLI mode (no HERMES_SESSION_KEY → still "default"). RL/benchmark task overrides (register_task_env_overrides) are unaffected. Subagent task_ids inside a WebUI session collapse to "session:<key>" so they continue to share the parent session's container. Five new regression tests added to test_shared_container_task_id.py.
30317ef to
b3a0eef
Compare
@file: attachments now work when the desktop is connected to a remote gateway. Previously a referenced file resolved to a client-disk path the gateway couldn't see, so context_references rejected it with "path is outside the allowed workspace" and the agent never saw the file. Adds a file.attach RPC (sibling to the existing image.attach_bytes / pdf.attach byte-upload pipeline): the desktop uploads the file bytes, the gateway stages them into <workspace>/.hermes/desktop-attachments/ and returns a workspace-relative @file: ref that resolves cleanly. Local mode passes the path directly; a gateway-visible file outside the workspace is copied in; an in-workspace file is referenced as-is with no copy. Consolidates the file-sync design from #38615 (LeonSGP43) and the host-file-staging idea from #33455 (Carry00), rebased onto the image/PDF remote-media helpers already on main. Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
|
Superseded by #42634 (commit dbbd1d4 on main), which solves the remote-desktop file-attach gap from the gateway/desktop side. Your host-file-staging idea — detect a file the backend can see but that lives outside the session workspace, and stage it in so the agent can read it — is captured there as the "gateway-visible file outside the workspace → copy into Your PR specifically targets the SSH-backend WebUI-upload path ( |
@file: attachments now work when the desktop is connected to a remote gateway. Previously a referenced file resolved to a client-disk path the gateway couldn't see, so context_references rejected it with "path is outside the allowed workspace" and the agent never saw the file. Adds a file.attach RPC (sibling to the existing image.attach_bytes / pdf.attach byte-upload pipeline): the desktop uploads the file bytes, the gateway stages them into <workspace>/.hermes/desktop-attachments/ and returns a workspace-relative @file: ref that resolves cleanly. Local mode passes the path directly; a gateway-visible file outside the workspace is copied in; an in-workspace file is referenced as-is with no copy. Consolidates the file-sync design from NousResearch#38615 (LeonSGP43) and the host-file-staging idea from NousResearch#33455 (Carry00), rebased onto the image/PDF remote-media helpers already on main. Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
@file: attachments now work when the desktop is connected to a remote gateway. Previously a referenced file resolved to a client-disk path the gateway couldn't see, so context_references rejected it with "path is outside the allowed workspace" and the agent never saw the file. Adds a file.attach RPC (sibling to the existing image.attach_bytes / pdf.attach byte-upload pipeline): the desktop uploads the file bytes, the gateway stages them into <workspace>/.hermes/desktop-attachments/ and returns a workspace-relative @file: ref that resolves cleanly. Local mode passes the path directly; a gateway-visible file outside the workspace is copied in; an in-workspace file is referenced as-is with no copy. Consolidates the file-sync design from NousResearch#38615 (LeonSGP43) and the host-file-staging idea from NousResearch#33455 (Carry00), rebased onto the image/PDF remote-media helpers already on main. Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
@file: attachments now work when the desktop is connected to a remote gateway. Previously a referenced file resolved to a client-disk path the gateway couldn't see, so context_references rejected it with "path is outside the allowed workspace" and the agent never saw the file. Adds a file.attach RPC (sibling to the existing image.attach_bytes / pdf.attach byte-upload pipeline): the desktop uploads the file bytes, the gateway stages them into <workspace>/.hermes/desktop-attachments/ and returns a workspace-relative @file: ref that resolves cleanly. Local mode passes the path directly; a gateway-visible file outside the workspace is copied in; an in-workspace file is referenced as-is with no copy. Consolidates the file-sync design from NousResearch#38615 (LeonSGP43) and the host-file-staging idea from NousResearch#33455 (Carry00), rebased onto the image/PDF remote-media helpers already on main. Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
@file: attachments now work when the desktop is connected to a remote gateway. Previously a referenced file resolved to a client-disk path the gateway couldn't see, so context_references rejected it with "path is outside the allowed workspace" and the agent never saw the file. Adds a file.attach RPC (sibling to the existing image.attach_bytes / pdf.attach byte-upload pipeline): the desktop uploads the file bytes, the gateway stages them into <workspace>/.hermes/desktop-attachments/ and returns a workspace-relative @file: ref that resolves cleanly. Local mode passes the path directly; a gateway-visible file outside the workspace is copied in; an in-workspace file is referenced as-is with no copy. Consolidates the file-sync design from #38615 (LeonSGP43) and the host-file-staging idea from #33455 (Carry00), rebased onto the image/PDF remote-media helpers already on main. Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
Problem
When a user uploads a file via the WebUI in a session that uses an SSH
terminal backend (e.g. a NAS or remote server profile), the file is saved
to the host's WebUI attachment inbox
(
~/.hermes/webui/attachments/<sid>/). The agent then receives the pathas
[Attached files: /path/to/file], callsread_file, and the toolroutes the stat check through SSH — where that path does not exist.
Result: "File not found" on every attachment, regardless of file type.
This affects every user running an SSH-profile session who uploads any
non-image file. The original design comment in the WebUI notes
"Non-image files intentionally stay as text path attachments so the agent
can inspect them with file tools" — a correct design, but one that assumes
the agent's terminal is local.
Fix
Two small, additive changes:
tools/environments/ssh.py— exposes a publicpush_file()methodthat wraps the existing
_scp_upload(), reusing the already-openControlMaster socket (no extra handshake, no new dependency).
tools/file_operations.py— adds_push_local_file_if_needed()andcalls it in both
read_file()andread_file_raw()before returning the"not found" error:
push_file, transfer the file andretry the stat.
modal, …), fall through to the original suggest-similar-files response.
The fallback is entirely transparent on non-SSH backends —
push_fileisabsent on those environments, so
getattr(..., None)short-circuits withno overhead on the happy path.
Tests
tests/tools/test_ssh_attachment_push_fallback.py(8 new tests):_push_local_file_if_neededunit cases: absent local file, missingpush_fileattr, successful push, push raises.read_fileintegration: push called and read succeeds; push raises →fallback to suggest-similar; env without
push_file→ no crash.SSHEnvironment.push_filedelegates to_scp_upload.All existing
test_file_operations.py,test_file_operations_edge_cases.py,and
test_ssh_environment.pycontinue to pass (126 tests, 0 failures).Impact
push_fileabsent, fallback returnsFalseimmediately