Commit graph

2214 commits

Author SHA1 Message Date
Gille
f29c28d6d0 docs(kanban): clarify unblock status routing 2026-07-17 15:47:39 -07:00
Dusk1e
9a7a43b5d6 fix(tools/kanban): sync kanban_unblock response status with DB state 2026-07-17 15:47:39 -07:00
Teknium
71252f0dcb
fix(terminal): fall back when the configured cwd is unenterable, not just missing (#66306)
A root-launched CLI session can leak /root into the terminal cwd state a
non-root gateway/cron process later resolves (#65583). os.path.isdir('/root')
is True for a non-root user — stat only needs search permission on / — so
_resolve_safe_cwd returned it and subprocess.Popen(cwd='/root') died with
PermissionError: [Errno 13], failing EVERY cron job's terminal/file/search
tool on every command until restart.

_resolve_safe_cwd now requires X_OK (new _cwd_usable helper) and climbs to
the nearest enterable ancestor, logging a WARNING that names the leak class
when an existing-but-denied cwd is skipped. Missing-cwd recovery (#17558)
behavior unchanged.

E2E-verified: LocalEnvironment constructed with an unenterable cwd now runs
commands from the fallback directory instead of raising.
2026-07-17 06:54:51 -07:00
Hermes Agent
a8ec41533c fix(mcp): treat non-string nextCursor as end of pagination
Per the MCP spec the cursor is an opaque string; anything else
(including MagicMock auto-attributes in tests) means no more pages.
Fixes test_mcp_tool_session_expired mock-session runaway.
2026-07-17 04:57:12 -07:00
Hermes Agent
6030ca8cea fix(mcp): follow nextCursor pagination in tools/resources/prompts discovery
Port from anomalyco/opencode#35439/#35500: preserve full MCP catalogs
across paginated tools/list responses.

The MCP spec allows servers to paginate tools/list, resources/list, and
prompts/list via an opaque nextCursor token. The Python SDK's
ClientSession.list_* methods fetch exactly one page per call, and hermes
never passed the cursor back — on a paginated server every tool,
resource, and prompt past page 1 was silently invisible to the agent.

Adds _paginate_full_list() (cursor-draining helper with a 50-page
runaway cap and spec-correct opaque-string cursor validation) and
applies it at all three discovery sites: _discover_tools(), the
tools/list_changed refresh handler, and the list_resources/list_prompts
utility handlers. The keepalive probe intentionally keeps its
single-page call (liveness only).

E2E: real stdio MCP server serving 3 pages (2/1/1 tools) — old code
discovered 2 tools, new code discovers all 4.
2026-07-17 04:57:12 -07:00
Teknium
17485cbcd2 fix(cli): sanitize terminal escapes when replaying stored history (/resume recap, /status recap)
Port from openai/codex#31494: user-visible history replay must strip CSI
sequences and control characters. Stored conversation history can carry
raw terminal escapes (pasted content, gateway-origin text, model output
echoing injected tool results). Replaying it via /resume's recap panel or
build_recap (/status on CLI + gateway) wrote those bytes straight to the
terminal — an injected message could clear the screen, retitle the window,
move the cursor, or restyle the recap UI. Rich's Text() does not neutralize
raw escape bytes.

- tools/ansi_strip.py: add sanitize_display_text() — strip_ansi() plus
  bare C0/C1 control removal, preserving \n and \t, normalizing \r to \n
  (adapted to Python from Codex's sanitize_user_text; reuses the existing
  ECMA-48 stripper instead of transcribing their char-walk)
- hermes_cli/cli_agent_setup_mixin.py: sanitize user + assistant text in
  _display_resumed_history() before building the Rich recap panel
- hermes_cli/session_recap.py: sanitize preview lines in build_recap()
  (_truncate choke point) so /status recaps are clean on every platform
- tests: 10 new sanitize_display_text cases (incl. the exact codex#31494
  fixture), recap + resume-display leak assertions
2026-07-17 04:53:38 -07:00
Teknium
b90dbac1d6 fix(approval): unify execution-bearing option detection
Co-authored-by: MorAlekss <mor.aleksandr@yahoo.com>
2026-07-17 04:53:04 -07:00
Teknium
780e098077 fix: widen UTF-8 BOM tolerance to all sibling frontmatter parsers
The previous commit fixes the canonical agent/skill_utils.parse_frontmatter.
Six more modules reimplement the '---' fence check locally and had the
same bug:

- tools/skill_manager_tool.py _validate_frontmatter — rejected BOM'd
  skill_manage create/edit content outright
- tools/skills_hub.py GitHubSource._parse_frontmatter_quick and
  OptionalSkillSource._parse_frontmatter — hub browse/install metadata
- hermes_cli/skills_hub.py — local skill install validation
- gateway/run.py — skill slug discovery for disabled-skill hints
- agent/prompt_builder.py _strip_yaml_frontmatter — BOM'd context files
  (AGENTS.md) leaked raw frontmatter into the system prompt
- tools/blueprints.py _split_frontmatter — str.lstrip() does not strip
  U+FEFF (not whitespace), so the existing lstrip never covered it

Sibling-surface regression tests added.
Bug class also fixed upstream in cline/cline#12218 (found by the weekly
Cline PR scout).
2026-07-17 04:52:02 -07:00
Teknium
cf3ae7c59c fix(mcp): preserve live OAuth state during reauth 2026-07-17 04:50:47 -07:00
Teknium
ebd737f4d9 fix(mcp): close hosted OAuth lifecycle gaps 2026-07-17 04:50:47 -07:00
Teknium
6045529724 fix(mcp): harden hosted OAuth across profiles and clients 2026-07-17 04:50:47 -07:00
Ben Barclay
11eaa77daf fix(mcp): serialize hosted oauth reauthorization 2026-07-17 04:50:47 -07:00
Ben Barclay
b09f1ba770 fix(mcp): reject invalid dashboard oauth callbacks 2026-07-17 04:50:47 -07:00
Ben Barclay
05dea7be04 fix(mcp): complete OAuth through hosted dashboards 2026-07-17 04:50:47 -07:00
Teknium
0f102fa4dc
feat(browser): store full snapshots on truncation; make eval denylist opt-in (#65923)
* feat(browser): store full snapshots on truncation; make eval denylist opt-in

Two harness fixes motivated by BU_Bench results where fixed-verb + lossy
observation cost Hermes heavily vs code-driven browser agents:

1. Snapshot truncation no longer loses content. When a snapshot exceeds
   the 8000-char threshold, the complete accessibility tree is saved to
   cache/web (same truncate-and-store pattern as web_extract) and the
   truncated view / LLM summary includes the file path plus a ready-made
   read_file call. Element refs beyond the cut are recoverable without
   re-snapshotting. Stored copies are force-redacted and capped at 2MB;
   content-hash filenames dedupe repeated snapshots of the same page.

2. The browser_console(expression=...) sensitive-primitive denylist is
   now opt-in via browser.restrict_evaluate (default false). The
   names-based denylist blocked legitimate DOM extraction — any selector
   or expression containing 'fetch', 'cookie', 'input', etc. — which
   crippled the agent's only programmatic page-inspection path. The
   SSRF/private-URL egress guards in _browser_eval are independent of
   this policy and remain always-on. browser.allow_unsafe_evaluate keeps
   its meaning (bypass the denylist) for configs that already set it.

* test: update None-guard test for stored-snapshot pointer in _extract_relevant_content

test_normal_content_returned pinned the exact return value; the summary
now carries a pointer to the stored full snapshot. Assert the summary
passes through and the pointer is present instead.

* feat(browser): align snapshot threshold with web_extract's 15k char budget

SNAPSHOT_SUMMARIZE_THRESHOLD 8000 -> 15000, matching
web_tools.DEFAULT_EXTRACT_CHAR_LIMIT so the snapshot and web_extract
truncate-and-store paths give the model the same per-page budget.
_truncate_snapshot's default max_chars now follows the constant.
Invariant test added; docs (en+zh) and CLI tip updated.
2026-07-16 23:41:26 -07:00
cresslank
34837597d2 fix(auth): make xAI OAuth pools multi-account resilient
Keep each xAI OAuth auth-add login as an independent manual device-code pool entry and recognize xAI personal-team spending-limit 403 responses as billing exhaustion. Preserve the structured top-level error message so the failed credential is quarantined and the next healthy account is selected without attempting a pointless token refresh.

Route direct xAI HTTP consumers through the credential pool as well. Proactive and 401-reactive refreshes update the exact issuing manual entry, preserve validated xAI base URL overrides, and serialize single-use refresh-token rotation across concurrent pool instances.
2026-07-17 11:37:25 +05:30
Gille
1f7d2be22f
fix(install): detect Git Bash Mandatory ASLR failures (#64651) 2026-07-16 22:46:46 -04:00
Erosika
29e0471708 fix(honcho): update SDK and restore CI coverage 2026-07-16 12:48:48 -07:00
Teknium
23526148d1 fix(terminal): bridge terminal.backend config in serve/desktop processes lacking a launcher env bridge
terminal_tool reads all settings from TERMINAL_* env vars, bridged from
config.yaml by the CLI, gateway, and TUI-PTY launchers. Processes that
skip every launcher bridge — hermes serve / the Desktop app backend's
in-process agents, the desktop cron ticker — saw an unset TERMINAL_ENV
and silently ran every command on the host even when config.yaml selects
terminal.backend: docker. A user who configured Docker isolation got
unsandboxed host execution with no warning.

Two layers:
- _ensure_terminal_env_bridged() in _get_env_config(): when TERMINAL_ENV
  is unset, backfill TERMINAL_* from config.yaml via
  apply_terminal_config_to_env(override=False). Explicit env always wins
  (honor explicit choice; only fix the accidental fallback). One-shot,
  fail-open to the historical local default.
- cmd_dashboard/serve: run the same bridge at startup so every consumer
  in the backend process (in-process agents, desktop cron ticker,
  tui_gateway cwd resolution) sees the bridged env directly.

Fixes #63141, #54449, #61115, #65696.
2026-07-16 08:55:10 -07:00
Tim Roth
58010c8b3d fix(mcp): reuse cached oauth redirect port 2026-07-16 07:56:46 -07:00
Teknium
c2e11bf418 fix(kanban): guard kanban_attach_url against SSRF via tools.url_safety
_download_url_with_cap called urlopen() after only a scheme check, so a
model-controlled URL could reach loopback services, RFC1918/CGNAT hosts,
or cloud metadata endpoints (169.254.169.254), and a public host could
302 to any of those unvalidated.

Route the fetch through the repo's canonical SSRF guard instead:
validate every hop with tools.url_safety.is_safe_url() and follow
redirects manually (httpx, follow_redirects=False, 5-hop limit) so each
Location target is re-checked before it is fetched — the same pattern
as tools/skills_hub._guarded_http_get. The streaming size cap is
unchanged. Local-fixture tests opt in via HERMES_ALLOW_PRIVATE_URLS
(the guard's documented escape hatch); new tests pin rejection of
loopback, cloud-metadata, and private-range URLs, a mocked
public→loopback redirect, and a mocked public happy path.
2026-07-16 07:33:14 -07:00
Teknium
f3cbe45605 refactor(kanban): unify attachment size cap on KANBAN_ATTACHMENT_MAX_BYTES
The salvaged attachment-toolset commit predated main centralizing the
25 MB cap as kanban_db.KANBAN_ATTACHMENT_MAX_BYTES and re-introduced a
private _MAX_ATTACHMENT_BYTES alias. Drop the duplicate: kanban_db's
store_attachment_bytes(), the dashboard upload endpoint, and the
kanban_attach_url tool all reference the one shared constant now, and
the tests monkeypatch that same name.
2026-07-16 07:33:14 -07:00
otsune
3fccd698fd feat(kanban): attachment toolset + CLI to match the dashboard surface
The kanban board has had full attachment storage and a dashboard HTTP
API (upload/list/download/delete) since #35338, but there was no agent
toolset tool and no `hermes kanban` CLI verb for attachments. Agents and
scripts that don't go through the dashboard server (or can't touch the DB
directly) had no way to create or read real attachments — only links in
comments.

Close that gap by mirroring the existing comment surface:

- `kanban_db.store_attachment_bytes()` — one shared write path (validate
  name, enforce the 25 MB cap, write the blob under the per-task dir with
  collision-free naming, insert the metadata row, clean up an orphan blob
  if the insert fails). `_MAX_ATTACHMENT_BYTES`, `_safe_attachment_name`,
  and a new `_collision_free_path` move here so the dashboard, the tool,
  and the CLI all share one implementation and can't drift.
- Tools (`tools/kanban_tools.py`): `kanban_attach` (inline base64),
  `kanban_attach_url` (server-side http/https fetch with the same cap),
  `kanban_attachments` (list). Write tools respect worker task-ownership;
  list is read-only. Registered in the `kanban` toolset.
- CLI (`hermes_cli/kanban.py`): `attach <id> <path>`, `attachments <id>`,
  `attach-rm <attachment_id>`.
- Dashboard `upload_task_attachment` now imports the shared helpers and
  uses `_collision_free_path` — behavior identical (still streams to disk
  with the cap, still 413 on overflow).
- Docs (AGENTS.md, kanban-worker skill) and toolset membership updated.

Tests: tool round-trip + oversize + bad base64 + ownership; attach_url
against a local HTTP fixture incl. oversize-mid-stream and non-http
scheme rejection; CLI attach/attachments/attach-rm; shared-helper unit
tests; dashboard parity preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 07:33:14 -07:00
Teknium
a6d9d1d2cf fix(security): widen non-ASCII compare_digest crash fix to all sibling sites
Same bug class as the salvaged #65305/#65307: hmac.compare_digest (and
secrets.compare_digest) raise TypeError when given a str containing
non-ASCII characters, and these call sites feed it raw request input.
Compare as UTF-8 bytes everywhere:

- gateway/platforms/msgraph_webhook.py: clientState from request body
- gateway/platforms/whatsapp_cloud.py: hub.verify_token query param +
  X-Hub-Signature-256 header (comment claimed 'works on str' — it
  doesn't for non-ASCII)
- plugins/platforms/feishu: verification token + x-lark-signature
- plugins/platforms/raft: bridge token header
- plugins/platforms/line: X-Line-Signature
- plugins/platforms/sms: X-Twilio-Signature
- tools/code_execution_tool.py: sandbox RPC token (both loops)

Regression tests for the two gateway-core sites (msgraph, whatsapp).
2026-07-16 07:22:24 -07:00
teknium1
d34cc4093a fix(mcp): per-flow callback waiters so concurrent OAuth flows cannot cross ports
_wait_for_callback still read the legacy module-level _oauth_port, so
with two concurrent OAuth flows, flow A's callback wait bound flow B's
port while A's redirect URI pointed at A's port — the callback-side
half of the cross-flow collision that #65622 fixed on the redirect
side. _make_callback_waiter(port) closes over each flow's resolved
port; both provider construction sites (build_oauth_auth and
MCPOAuthManager._build_provider) now wire per-flow waiters. The legacy
_wait_for_callback delegates for backwards compatibility.

Direction credit to @LeonSGP43 (#34280) and the #34260 analysis.
2026-07-16 07:11:21 -07:00
doxe0x
454d553d34 fix(mcp): report a clear error when the OAuth callback port is in use
_wait_for_callback catches OSError on bind with a comment claiming the port is
held by a server build_oauth_auth started, and promising to fall back to polling
it. build_oauth_auth never starts a callback server (this is the only listener),
so there is nothing to poll: the branch just raised a misleading "OAuth callback
timed out" when the real cause is a busy port (a concurrent login, a leftover
listener, or a fixed oauth.redirect_port that collided).

Fix the stale comment and raise an accurate, actionable message (names the port,
suggests freeing it or setting a free oauth.redirect_port), chained from the
original OSError. Behavior is otherwise unchanged. Adds a regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 07:11:21 -07:00
Peter Skaronis
dc419d6e80 mcp_oauth: configurable redirect_host (WAF-safe localhost redirect URIs)
Reclaim.ai's AWS API Gateway WAF 403s any /oauth2/authorize request whose
query string contains a literal 127.0.0.1, so the SDK's hardcoded
redirect_uri made the browser flow impossible. New optional oauth config
key redirect_host (default 127.0.0.1, unchanged behavior) lets a server
entry use localhost instead.

Integrated into _resolve_redirect_uri so it composes with redirect_uri:
an explicit redirect_uri wins; redirect_host only rewrites the loopback
default's hostname.
2026-07-16 06:14:34 -07:00
Florian Burka
d0afcb125c test(mcp-oauth): cover configurable redirect_uri + fix misleading SSH hint
The PR added a configurable `redirect_uri` (proxy/Funnel callbacks) but
shipped without tests, and the loopback SSH-tunnel hint stayed hardcoded —
actively misleading the exact proxy user the feature targets.

- Extract `_resolve_redirect_uri(cfg, port)` so the client-metadata and
  pre-registration paths derive an identical callback (a mismatch makes the
  authorization server reject the redirect).
- Make `_redirect_handler` redirect_uri-aware: a configured proxy callback
  reaches this machine on its own, so it no longer prints the `ssh -N -L`
  loopback guidance. Wired via `functools.partial` — no new global state.
- Document `redirect_uri` in the config block.
- 14 new tests (red/green TDD): helper resolution + empty-string fallback,
  metadata + pre-registration for configured/default, AnyUrl normalization,
  no-client_id skip, client_secret combo, and both SSH-hint branches.

ruff clean · 81 passed (tests/tools/test_mcp_oauth.py) · ty baseline unchanged

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 06:14:34 -07:00
Florian Burka
6297634d22 fix(mcp-oauth): allow configurable redirect_uri for MCP OAuth flows 2026-07-16 06:14:34 -07:00
liuhao1024
6160a80253 fix(gateway/platforms): migrate all Weixin fallbacks to get_secret() for consistent profile-scoped resolution
Per egilewski's security review, WEIXIN_BASE_URL and WEIXIN_CDN_BASE_URL
were still resolved from process-global environment variables, leaving
mixed-scope bypasses in multiplex mode.

Changed files:
- gateway/platforms/weixin.py: Added get_secret import, replaced os.getenv()
  with get_secret() for WEIXIN_ACCOUNT_ID, WEIXIN_TOKEN, WEIXIN_BASE_URL,
  WEIXIN_CDN_BASE_URL in WeixinAdapter.__init__() and send_weixin_direct()
- tools/send_message_tool.py: Added get_secret import, replaced os.getenv()
  with get_secret() for all WEIXIN_* fallbacks in _handle_send()

All runtime Weixin send paths now resolve both credentials and endpoint
configuration from the same profile-scoped source.
2026-07-16 05:39:58 -07:00
teknium1
95a0f9c836 fix(mcp): close select-to-bind TOCTOU on the OAuth callback port
_find_free_port() closed its probe socket before HTTPServer re-bound
the port minutes later, leaving a window where another process could
steal it (#22161 by @amathxbt). _reserve_callback_port() now keeps the
selected socket bound (bounded FIFO pool) until _wait_for_callback
adopts it via bind_and_activate=False. Also sets allow_reuse_address
BEFORE binding — the cherry-picked #44872 set it after the constructor
had already bound, where it is a no-op.

Also updates the three #57836 non-interactive-guard tests to the
closure-factory API from #44872.
2026-07-16 05:39:52 -07:00
XuefeiLi
f4c7caa70c fix(mcp): remove unreachable dead code after return in _make_redirect_handler 2026-07-16 05:39:52 -07:00
Code-suphub
13e19a9092 fix(mcp): use per-provider closures and allow_reuse_address for OAuth (#44588, #44590)
Two related OAuth fixes:

1. Replace module-level _redirect_handler with _make_redirect_handler()
   closure factory that closes over the resolved port. This prevents
   cross-server state pollution when multiple MCP servers run OAuth
   concurrently (#44588).

2. Set server.allow_reuse_address = True on the ephemeral callback
   HTTPServer so the socket doesn't stay in TIME_WAIT after the flow
   completes. This prevents 'Address already in use' errors on the
   next OAuth flow for the same port (#44590).

Fixes #44588
Fixes #44590
2026-07-16 05:39:52 -07:00
szafranski
d73a6f5ac2 fix(telegram): include duration in standalone sends 2026-07-16 04:40:35 -07:00
nima20002000
193871f1a6 fix(code-exec): expose truncated stdout metadata 2026-07-16 04:25:19 -07:00
Gaurav Saxena
4d6686c18a fix(execute-code): honor session cwd overrides 2026-07-16 04:24:07 -07:00
AlexFucuson9
298a94926f fix(agent): resolve execute_code cwd from per-session override (#56047)
execute_code's _resolve_child_cwd() only checked the process-global
TERMINAL_CWD env var and os.getcwd(), ignoring the per-session cwd
override registered via session.cwd.set → register_task_env_overrides.

This caused execute_code to write to the process launch directory
while sibling tools (write_file, read_file, patch, terminal) correctly
resolved the session workspace — two file-writing paths in one turn
silently disagreed on the working directory.

Fix: pass task_id to _resolve_child_cwd() and check
_registered_task_cwd_override(task_id) before falling back to
TERMINAL_CWD and os.getcwd(), matching the lookup order used by
file_tools._resolve_base_dir and terminal_tool._resolve_command_cwd.
2026-07-16 04:24:07 -07:00
Teknium
49a8c3f836 fix(terminal): stop writing the cwd temp file entirely
Follow-up for salvaged PR #63255: with LocalEnvironment._update_cwd
delegating to the stdout marker parser, the cwd temp file has zero
readers left. Drop the 'pwd -P > file' writes from the bootstrap and
_wrap_command so every command stops paying a pointless file write
(and stops littering temp dirs with hermes-cwd-*.txt).
2026-07-16 01:28:24 -07:00
LeonSGP43
0ccd05b78a fix(local): stop re-reading cwd marker file 2026-07-16 01:28:24 -07:00
Teknium
5330b2cfad fix: use Windows-aware isabs for native paths under patched _IS_WINDOWS
Follow-up for salvaged PR #26790: on a POSIX host with _IS_WINDOWS
patched (test simulation), os.path.isabs rejects C:\Users\x and the
new relative-cwd recovery would mangle a perfectly absolute native
Windows path. Check ntpath.isabs first on the Windows branch.
2026-07-16 01:28:13 -07:00
threadhoard-vps
5458c76566 fix: normalize local terminal relative cwd 2026-07-16 01:28:13 -07:00
ethernet
c80b244b52 refactor(terminal,file-tools): delete legacy env-side cwd tracking (step 4)
The per-session record store is now the ONLY cwd mechanism. Deleted:

- env.cwd_owner stamping + prev_owner threading (terminal_tool): the
  shared env no longer carries ownership metadata at all
- _resolve_command_cwd's env/prev_owner params: resolution is
  workdir > session record > config/override default
- file_tools._live_cwd_if_owned + _get_live_tracking_cwd: path
  resolution never consults the shared env's live cwd
- file_tools._last_known_cwd + _remember_last_known_cwd +
  _last_known_cwd_for: the #26211 preserved-anchor registry is
  subsumed by the session record, which never lived on the env and
  therefore cannot be lost to env cleanup. The _get_file_ops
  stale-cache rescue now writes the record instead.
- env recreation (both _get_file_ops and terminal_tool) seeds the
  fresh env from override > session record > config

Why no transition fallback: the legacy state was process-local and
in-memory exactly like the record store — after a restart both start
empty, and within a running process every legacy write site has been
dual-writing the record since step 1. There is no populated-legacy/
empty-record state to fall back for.

Tests updated to drive the record store instead of the deleted
mechanism; the cross-session isolation suite now asserts the same
behavior contracts (no leak, cd isolation, #26211 persistence)
against the new architecture, plus a new "session C inherits nothing"
case that the old ownership guard could not express.
2026-07-16 00:18:38 -07:00
ethernet
4d30b05d6d chore(terminal): drop reference to untracked local plan file 2026-07-16 00:18:38 -07:00
ethernet
fd3d9c63d0 refactor(terminal): resolve command cwd from per-session records (step 3)
Third step of the cwd rearchitecture: _resolve_command_cwd now prefers
the session's own cwd record over the shared env's live cwd.

New resolution order: workdir > session record > legacy env.cwd
(ownership-gated, transition-only) > config/override default.

The record is written after every completed command for the session, so
it IS the session's cd state — another session's cd lands in another
record and cannot affect this session's commands. The legacy env.cwd
branch only fires for a session with no record yet (no command has
completed since this code loaded); it keeps the prev_owner ownership
guard for that transition window and is deleted in step 4 along with
env.cwd_owner stamping and file_tools' _last_known_cwd machinery.

Adds command-path regression tests including the terminal sibling of
the leak-A scenario (unowned shared env cwd vs session record) and an
E2E cd round-trip through terminal_tool.
2026-07-16 00:18:38 -07:00
ethernet
5461e0e098 refactor(file-tools): resolve paths against per-session cwd records (step 2)
Flips the read side of the cwd rearchitecture onto the _session_cwd
store introduced in the previous commit.

_authoritative_workspace_root now resolves:
  1. the session's own cwd record (get_session_cwd) — per-session by
     construction, so one session's cd can never leak into another
     session's file resolution, with no ownership heuristics at all
  2. registered override (fallback for cleared/never-written records)
  3. legacy shared-env live cwd + preserved anchor (transition-only,
     for commands that ran before this code loaded)
  4. sentinel-free absolute TERMINAL_CWD

delegate_task children get their record seeded from the parent's at
spawn: they keep starting in the parent's directory (current behavior)
but their subsequent cds stay isolated in their own record instead of
bleeding back through the shared env.

The wrong-worktree leak class is now solved structurally on this path —
there is no shared cwd for sessions to inherit. The legacy env-side
tracking (cwd_owner, _live_cwd_if_owned, _last_known_cwd) remains only
as a transition fallback and is deleted in the next step.
2026-07-16 00:18:38 -07:00
ethernet
be2a1290de refactor(terminal): introduce per-session cwd records (step 1: dual-write)
First step of the cwd rearchitecture (see PR #65185 for the targeted
leak fixes this will eventually supersede, and
.hermes/plans/cwd-rearch-audit.md for the full audit + sequencing).

The root cause of the wrong-worktree bug class is that cwd lives on the
SHARED terminal env — a global mutable timeshared between sessions.
env.cwd_owner stamping, _last_known_cwd, and file_tools' ownership
ladder are all patches over that misplacement.

This adds the replacement store: _session_cwd, keyed by the raw
session/task key, with record/get/clear accessors. Step 1 is dual-write
only — every site that learns a session's live cwd also records it:

- terminal_tool foreground path: after env.execute() the env's own
  post-command tracking has updated env.cwd; mirror it under the
  session key that drove the command
- register_task_env_overrides: a registered workspace cwd (ACP/TUI/
  desktop) seeds the session record
- clear_task_env_overrides: drops the record on teardown

Readers are untouched — behavior is identical. Later steps flip
file_tools resolution and _resolve_command_cwd to read this store,
then delete env-side tracking, cwd_owner, and _last_known_cwd.

Also hardens terminal_tool's env acquisition with an explicit
env-is-None guard (previously implicitly unbound on an unreachable
branch, flagged by pyright once the dual-write read env post-loop).
2026-07-16 00:18:38 -07:00
2751738943
54d0948d38 fix(tui): route post-turn completions by owner
Apply positive-proof routing to every addressed notification in the registry and TUI poller while preserving ownerless legacy behavior and TUI delivery for poll-observed completions.

Remove the unused exact-key drain helper and cover ordinary success and failure, origin, compression-lineage, orphan, and poll-observed paths.

Complements NousResearch/hermes-agent#54785.
2026-07-15 23:51:43 -07:00
kshitij
2b6897f982
fix(computer-use): target Linux app windows reliably (#63725)
* fix(computer-use): target Linux app windows reliably

Resolve app filters through the canonical cua-driver MCP app metadata and join running app PIDs back to windows. Preserve an exact selected window across capture_after, support direct capture by pid/window_id, and send the active window ID for coordinate pointer actions on Linux.

Co-authored-by: annguyenNous <annguyenNous@users.noreply.github.com>
Co-authored-by: grimmjoww578 <willies578@gmail.com>
Co-authored-by: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>

* fix(computer-use): address review on PR #63725

Address three review comments from @f-trycua:

1. type_text, press_key, and hotkey now carry _active_window_id and
   fail closed when it is missing. Previously they sent only the PID,
   so CUA Driver fell back to the first window for that PID — input
   could reach the wrong window in multi-window apps.

2. Coordinate scroll x/y are now capability-gated behind
   input.scroll.coordinates. CUA Driver 0.7.1 Linux schema rejects
   x/y on scroll; omitting them when the driver doesn't advertise
   support avoids the schema rejection while still routing via
   window_id.

3. Windows are sorted by z_index descending (higher = front, per CUA
   Driver semantics) instead of ascending. Null z_index (Wayland) is
   coerced to 0 in _ingest_windows so it doesn't crash the sort and
   sorts to the back instead of being selected as the capture target.

---------

Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
Co-authored-by: annguyenNous <annguyenNous@users.noreply.github.com>
Co-authored-by: grimmjoww578 <willies578@gmail.com>
Co-authored-by: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
2026-07-16 00:34:20 +05:30
Teknium
47d853fdf2 fix(delegation): fail closed on restored completions + stamp CLI dispatch identity
Three-layer companion to the salvaged CLI drain-ownership fix (#64240):

1. restore_undelivered_completions stamps restored=True (in-memory only)
   on every durable completion re-enqueued at process start.
2. drain_notifications' legacy unfiltered branch re-queues restored
   events instead of consuming them — a fresh process can no longer
   adopt a dead session's delegation results (#64484). Same-process
   keyless events keep the legacy behavior.
3. delegate_tool's async dispatch now falls back to the parent agent's
   durable session_id when the approval-context key resolves empty (the
   CLI case), so the CLI's new positive-ownership drain can actually
   claim its own completions instead of failing closed on ''.
2026-07-14 22:14:10 -07:00
Teknium
cab457d722 fix(terminal): make bounded capture opt-in for the foreground terminal path only
Review finding on the salvaged collector: _wait_for_process is the shared
drain for EVERY env.execute() consumer, not just the terminal tool. Applying
tool_output.max_bytes there silently truncated file-operation cat reads
(read_file_raw feeds the patch engine — read-modify-write on any file >50KB
would corrupt it), paginated read_file, code-execution RPC reads, and log
reads.

bounded_capture is now an explicit opt-in on execute()/_wait_for_process,
set only by the foreground terminal tool. Default preserves the historical
full-fidelity capture via an effectively-unbounded collector (single code
path). Modal transports accept the kwarg for signature parity.

New regression test: default execute() returns a 200KB payload complete and
untruncated. E2E: 20MB internal read intact; ShellFileOperations
read_file_raw round-trips byte-exact; terminal path still bounded at 50KB.
2026-07-14 22:00:28 -07:00