Commit Graph
8 Commits
Author SHA1 Message Date
liuhao1024 a3fb48b2ce fix(state): keep /branch sessions visible after parent reopen
/branch (aka /fork) sessions vanished from /resume and /sessions. Both
surfaces funnel through list_sessions_rich(include_children=False), which
hid any session with a parent_session_id unless identified as a branch via a
heuristic — parent.end_reason == 'branched' AND child.started_at >=
parent.ended_at.

Two ways that heuristic failed:
1. CLI/gateway branches: once the parent was reopened (e.g. resumed) and
   re-ended with a different end_reason (tui_shutdown overwriting 'branched'),
   the heuristic stopped matching and the branch was hidden permanently.
2. TUI branches (tui_gateway session.branch): the TUI never ends the parent
   as 'branched' — it creates the child while the parent is still live — so
   the heuristic NEVER matched and TUI branches were hidden from the moment
   they were created (this is the macOS desktop app's primary symptom).

Fix: persist a stable '_branched_from' marker in the branch session's
model_config at creation time across ALL THREE branch paths (CLI cli.py,
gateway gateway/run.py, and TUI tui_gateway/server.py), and OR a
json_extract(model_config, '$._branched_from') IS NOT NULL check into the
list_sessions_rich filter. The marker is immutable across the parent's
lifecycle, so the branch stays visible regardless of how/whether the parent
is ended. The legacy end_reason heuristic is kept (OR'd) so pre-existing
branches remain visible. Subagent/compression children (no marker, parent
not 'branched') stay correctly hidden. Fixes #20856.

Approach by liuhao1024 (PR #20864); reimplemented on current main, extended
to the TUI branch path (which the original missed), with regression tests for
the reopen+re-end scenario and the TUI marker persistence.
2026-06-04 10:07:20 -07:00
liuhao1024 ba3c450914 fix(security): block read_file on project-local .env files
get_read_block_error() only blocked internal Hermes cache files but
allowed reading project-local secret-bearing environment files (.env,
.env.production, .env.local, etc.) through both read_file and ACP
fs/read_text_file paths.

Add a basename deny set for common secret-bearing .env variants.
.env.example remains readable as documentation.

Fixes #20734
2026-05-25 03:40:47 -07:00
liuhao1024 4a2fa77c15 fix(cli): pre-check CUA release asset for Intel macOS before install
The upstream cua-driver installer resolves the latest release and attempts
to download an architecture-specific asset. When the release only ships
arm64 builds (as of v0.1.6), the installer fails with a raw 404 on Intel
macOS with no clear path forward.

Add _check_cua_driver_asset_for_arch() that probes the GitHub Releases API
before running the installer. If the latest release has no x86_64/amd64
asset, print a clear warning and link to the upstream issue. On arm64 or
API failure, fail open and let the installer proceed as before.

Fixes #24530
2026-05-21 19:17:45 -07:00
liuhao1024 18cd1e5c72 fix(computer_use): correct type_text MCP tool name and implement drag action
Bug 3: The cua_backend type_text() method called MCP tool 'type_text_chars'
which does not exist in current cua-driver. Changed to 'type_text' which is
the correct MCP tool name.

Bug 4: The drag() method returned a hardcoded 'not supported' error even
though cua-driver exposes a 'drag' MCP tool. Implemented proper drag
dispatching with coordinate-based and element-based targeting.

Added dispatch-level validation for drag to ensure from/to coordinates
or elements are provided before calling any backend.

Fixes #24170 (bugs 3 and 4)
2026-05-21 14:08:28 -07:00
liuhao1024 1a4e8f7041 fix(gateway): make WhatsApp npm install timeout configurable
Default timeout raised from 60s to 300s (5 minutes) to accommodate
slower systems like Unraid NAS. Configurable via WHATSAPP_NPM_INSTALL_TIMEOUT
environment variable.
2026-05-12 18:49:07 -07:00
liuhao1024 f9b4b8af34 fix(mcp): include exception type in error messages when str(exc) is empty
Some exception classes (e.g. anyio.ClosedResourceError) are raised without
a message argument, so str(exc) returns an empty string. The existing error
format f'{type(exc).__name__}: {exc}' would produce messages like
'MCP call failed: ClosedResourceError: ' with nothing after the colon.

Add _exc_str() helper that falls back to repr(exc) when str(exc) is empty,
and apply it to all 6 MCP error formatting sites (5 tool/prompt/resource
handlers + 1 sampling handler).

Fixes #19417
2026-05-07 06:33:57 -07:00
liuhao1024 af98122793 fix(auxiliary): propagate explicit_api_key to _try_openrouter()
When resolve_provider_client() passes explicit_api_key for OpenRouter auxiliary
tasks, _try_openrouter() now accepts and honors this parameter instead of
silently ignoring it and falling back to OPENROUTER_API_KEY env var.

Root cause: _try_openrouter() had no explicit_api_key parameter, so even
when callers wanted to pass a runtime credential pool key, it could not be used.

Fix:
- Add explicit_api_key: str = None parameter to _try_openrouter()
- Prioritize explicit_api_key over pool key and env var
- Update resolve_provider_client() call site to pass explicit_api_key

Regression coverage:
- Test that explicit_api_key is passed to OpenAI client when provided
- Test that fallback to OPENROUTER_API_KEY still works when explicit_api_key is None

Closes #18338
2026-05-02 02:27:49 -07:00
liuhao1024 9bf260472b fix(tools): deduplicate tool names at API boundary for Vertex/Azure/Bedrock
Providers like Google Vertex, Azure, and Amazon Bedrock reject API
requests with duplicate tool names (HTTP 400: 'Tool names must be
unique').  The upstream injection paths in run_agent.py already dedup
after PR #17335, but two API-boundary functions pass tools through
without checking:

- agent/auxiliary_client.py: _build_call_kwargs() (all non-Anthropic
  providers in chat_completions mode)
- agent/anthropic_adapter.py: convert_tools_to_anthropic() (Anthropic
  Messages API path)

Add defensive dedup guards at both sites.  Duplicates are dropped with
a warning log, converting a hard 400 failure into a recoverable
condition.  This is intentionally conservative — the root-cause dedup
in run_agent.py is the primary defense; these guards add resilience
against future injection-path regressions.

Includes 8 new tests covering unique passthrough, duplicate removal,
empty/None edge cases.

Closes #18478
2026-05-02 01:51:51 -07:00