Commit graph

14528 commits

Author SHA1 Message Date
Teknium
77700a0ec0 fix(feishu): send WebSocket CLOSE frame on disconnect (#10202)
Feishu adapter's disconnect() cancelled WSS-thread tasks but never
called the lark_oapi client's _disconnect() coroutine, so no
WebSocket CLOSE frame was sent. Feishu's server kept routing
messages to the stale endpoint for minutes (CLOSE-WAIT timeout),
silencing the channel across every shutdown path — systemd restart,
hermes update, hermes gateway restart, and the --replace takeover
during 'hermes dashboard' invocations.

Schedule ws_client._disconnect() on the WSS thread loop via
run_coroutine_threadsafe with a 5s timeout before the existing
task-cancel + loop-stop sequence. Defensive hasattr guard + broad
except keeps disconnect() resilient if lark_oapi's internals shift.

Fixes #10202
2026-07-05 13:49:50 -07:00
Teknium
a6079dd350
feat(providers): GLM-5.2 native reasoning_effort controls (#58884)
Port from Kilo-Org/kilocode#11555: GLM-5.2 exposes a native
reasoning_effort knob with two enabled levels (high / max) on its
OpenAI-compatible endpoints. Previously the zai profile (direct Z.AI
/api/paas/v4) used the base ProviderProfile and emitted nothing, and the
OpenCode Go profile only handled Kimi K2 / DeepSeek — so a user's effort
preference for GLM-5.2 was silently dropped on both routes.

- zai: ZaiProfile maps effort onto high/max (xhigh/max -> max, lower -> high)
- opencode-go: same mapping for GLM-5.2, alongside existing Kimi/DeepSeek
- alias spellings recognized (glm-5.2 / glm-5-2 / glm-5p2, vendor-prefixed)
- disabled / no effort leaves the server default untouched
2026-07-05 13:48:01 -07:00
Teknium
ba31699091
chore(providers): remove dead cloudcode-pa quota-fallback branches (#51489)
The google-antigravity and google-gemini-cli OAuth providers were removed
in #50492. They were the only producers of a cloudcode-pa:// base_url, so
the account-level-quota early-returns in _pool_may_recover_from_rate_limit
and _credential_pool_may_recover_rate_limit are now unreachable.

- Drop the dead cloudcode-pa:// checks and the now-unused provider/base_url
  params on _pool_may_recover_from_rate_limit (only caller updated).
- Prune the obsolete CloudCode-specific regression tests; keep the live
  single/multi-entry pool-rotation invariants (#11314).
2026-07-05 13:44:33 -07:00
Teknium
55e3ee1ab8
fix: remove dead f-string prefixes via ruff F541 (216 sites) (#52336)
ruff check --fix --select F541 . on current main. Pure prefix removals;
adjacent-string concatenations keep the f only on interpolating fragments.
No string content or live placeholder altered.
2026-07-05 13:42:46 -07:00
teknium1
3d0276182a test: update MCP parallel-batch fixture names to mcp__server__tool convention
TestMcpParallelToolBatch seeded provenance under old-style
mcp_<server>_<tool> names, which no longer pass the
is_mcp_tool_parallel_safe() prefix gate after the naming change.
2026-07-05 13:40:21 -07:00
teknium1
e01f58ff1f feat(mcp): adopt mcp__server__tool naming convention
Port from anomalyco/opencode#33533. Native MCP tools now register as
mcp__<server>__<tool> (double-underscore delimiter) instead of
mcp_<server>_<tool>, aligning with the convention used by Claude Code,
Codex, and OpenCode.

The double-underscore delimiter disambiguates the server/tool boundary
even when either component contains underscores (the single-underscore
form was ambiguous, which is why is_mcp_tool_parallel_safe already had to
track provenance in a side-map). It also unifies native registration with
the Anthropic-OAuth wire form (_MCP_TOOL_PREFIX = 'mcp__'), so the
single->double promotion that path performed is now a no-op for native
tools while still handling legacy replayed names.

- tools/mcp_tool.py: add MCP_TOOL_NAME_PREFIX + mcp_prefixed_tool_name()
  helper; route _convert_mcp_schema, utility schemas, refresh stale-set,
  and the parallel-safe prefix gate through it
- agent/transports/codex_event_projector.py: mirror convention in the
  deterministic call_id input for MCP server-executed tool calls
- tests: update produced-name assertions to the new convention
2026-07-05 13:40:21 -07:00
kshitijk4poor
5986cdd380 fix(cron): deliver before tearing down the agent's async clients (#58720)
Defense-in-depth alongside the interpreter-shutdown guard: run_job closed
the cron agent's async resources (agent.close + cleanup_stale_async_clients)
in its finally block BEFORE run_one_job called _deliver_result, so a live
delivery could race a torn-down async client. run_job now accepts an optional
defer_agent_teardown holder; when set it hands the live agent back instead of
closing it, and run_one_job tears it down (via the extracted _teardown_cron_agent
helper) only AFTER delivery — in a finally so a failed run never leaks. Default
path (holder=None) is unchanged, so every existing caller keeps inline teardown.

Reorder approach based on #58777 by @LavyaTandel; reworked to keep a single
delivery site in run_one_job and add regression coverage.

Co-authored-by: LavyaTandel <lavya@loom.local>
2026-07-06 02:05:46 +05:30
HexLab98
6d9eff28b7 test(cron): cover the interpreter-shutdown scheduling guard (#58720)
Pins `_interpreter_shutting_down()` (finalizing flag + shutdown-error-text
fallback) and asserts the standalone delivery path skips gracefully without
scheduling a send when the interpreter is finalizing, while the normal
non-finalizing path still delivers. Source guardrails keep the guard wired
into both the dispatch (`_submit_with_guard`) and standalone-delivery sites.
2026-07-06 02:05:46 +05:30
HexLab98
8aab8be50c fix(cron): skip delivery/dispatch when the interpreter is shutting down
A cron tick can fire while the gateway is tearing down (SIGTERM from
`hermes update` / `hermes gateway stop` / systemd restart, or an OOM-kill).
Once the interpreter is finalizing, `concurrent.futures` refuses new work
with `RuntimeError: cannot schedule new futures after interpreter shutdown`
and asyncio's default executor is gone, so the cron delivery and dispatch
paths crash the tick and spray a traceback into errors.log on every
restart-race. Telegram/live-adapter deliveries surface it as
"Telegram send failed: ... cannot schedule new futures after interpreter
shutdown".

Add `_interpreter_shutting_down()` and consult it at the scheduling sites:
- the standalone delivery path (`asyncio.run` + the fresh-pool fallback),
- the tick dispatch (`_submit_with_guard` `pool.submit`).

When finalizing, skip gracefully with a warning instead of raising; the job
stays due and fires on the next healthy tick. The helper also matches the
RuntimeError text as a fallback, since the concurrent.futures global flag can
be set a hair before `sys.is_finalizing()` flips.

Fixes #58720. Also addresses the cron paths in #55924.
2026-07-06 02:05:46 +05:30
kshitijk4poor
18058c4515 fix(gateway): drain housekeeping thread over its own 30s future on shutdown
Follow-up on the #58818 cron-drain fix. The housekeeping ticker uses the
same loop-scheduled-future pattern as cron — it refreshes the channel
directory via safe_schedule_threadsafe(build_channel_directory(...), loop)
and blocks on fut.result(timeout=30). The original fix swapped its
join(5) for _await_thread_exit(5), which is a strict improvement (the loop
stays alive so the future can run) but the 5s bound is shorter than the
30s future, so a refresh in flight at shutdown was still abandoned. Bound
the housekeeping drain at 35s (30s future + margin) via a dedicated
_HOUSEKEEPING_SHUTDOWN_DRAIN_TIMEOUT constant. Not user-facing (self-heals
next tick) but keeps the cooperative drain honest across both threads.
2026-07-06 01:37:10 +05:30
HexLab98
6b14be0186 test(gateway): cover cron-delivery drain on restart
Assert _await_thread_exit lets a coroutine scheduled onto the running loop by a
blocked worker thread complete (the #58818 deadlock a synchronous join caused),
returns False when the thread outlives the timeout, and handles None/dead
threads.
2026-07-06 01:37:10 +05:30
HexLab98
dcd70c5823 fix(gateway): drain in-flight cron delivery on restart instead of dropping it
A cron delivery uses the live adapter by scheduling the send coroutine onto the
gateway event loop (safe_schedule_threadsafe) and blocking the ticker thread on
future.result(). On shutdown/restart the cleanup ran a synchronous
cron_thread.join(timeout=5), which blocks the event loop — so the pending
delivery coroutine could never execute, the join always timed out, and the
message was silently dropped (#58818). The default agent.restart_drain_timeout
is 0, so this fired on every restart with an in-flight delivery.

Replace the blocking joins with _await_thread_exit(), which polls is_alive()
via await asyncio.sleep so the loop keeps running and finishes the queued
delivery before teardown. The cron wait is bounded by the delivery future's own
60s ceiling (plus margin); housekeeping keeps a short bound. When no delivery is
in flight the ticker exits on stop_event immediately, so shutdown stays snappy.
2026-07-06 01:37:10 +05:30
kshitijk4poor
beaa1a08e6 fix(config): guard xai migration writer + drop gratuitous annotation
Phase-2 review follow-ups on the unreadable-config chokepoint work:

- hermes_cli/xai_retirement.py apply_migration() is a full-file config.yaml
  rewriter (ruamel round-trip + plain open("w")) that lives outside the
  atomic_yaml_write path, so the chokepoint didn't cover it. It reads the
  file first (which already fails closed on an unreadable file), but add
  require_readable_config_before_write() right before the write as a
  backstop for the read-then-write window, and a regression test asserting
  the original bytes survive an unreadable config.
- Drop the unnecessary "Path" string quotes on atomic_config_write's
  annotation — Path is imported eagerly at module top, no forward ref needed.

auth.py _update_config_for_provider / _reset_config_provider intentionally
keep their standalone require_readable_config_before_write guard + bare
atomic_yaml_write: the guard must fire BEFORE the read (fail-fast) at those
read-then-write sites, and a test pins the atomic_yaml_write call. Both are
already fully guarded against the bug; routing them through the wrapper
would move the check to write time for no benefit.
2026-07-05 23:00:34 +05:30
kshitijk4poor
123c6f3a23 fix(config): close unreadable-overwrite bug class at a single chokepoint
The unreadable-config-overwrite bug (an existing config.yaml that reads as
{} on a permission/IO error gets replaced with only defaults or the edited
section) is not limited to save_config / config set / auth. The same
read-then-atomic_yaml_write pattern lives at ~7 other independent write
sites that don't route through those functions:

  - gateway/slash_commands.py: _save_config_key, memory/skills write_approval
    toggles, tool_progress toggle, runtime_footer toggle, personality set
  - hermes_cli/doctor.py --fix (stale root-key migration)
  - gateway/platforms/yuanbao.py auto-sethome
  - plugins/platforms/telegram/adapter.py topic thread_id persistence
  - tui_gateway/server.py _save_cfg
  - agent/onboarding.py mark_seen

Rather than sprinkle require_readable_config_before_write() at each site,
add a single fail-closed chokepoint, atomic_config_write(), that runs the
guard then delegates to atomic_yaml_write, and route every config.yaml
write through it. Root cause remains that read_raw_config() can't tell an
absent file from an unreadable one (returns {} for both) — read-only
callers correctly stay fail-open, but any full-file replacement now fails
closed in one enforced place instead of relying on each caller to remember
the guard.

save_config / set_config_value / auth keep the contributor's original
guard calls (their commit); this commit widens the fix to the sibling
call paths and adds a regression test on the chokepoint (fails closed on
unreadable existing file + still creates a genuinely absent file).
2026-07-05 23:00:34 +05:30
luyifan
b109adede6 fix(config): refuse unreadable config overwrites 2026-07-05 23:00:34 +05:30
kshitijk4poor
5b04a024a5 docs(telegram): clarify fallback-branch limits wiring vs siblings
Follow-up on the #58790 fallback-limits fix: tighten the now-stale
_with_limits docstring and note on the fallback branch why it injects
limits at the transport level (not via _with_limits) so a future editor
does not re-route it through the client-level helper httpx would discard.
2026-07-05 22:08:42 +05:30
liuhao1024
01ee312de6 fix(telegram): forward keepalive limits into fallback transport
httpx ignores the client-level `limits` kwarg when a custom `transport`
is supplied.  The #31599 keepalive fix injected limits via
`httpx_kwargs[limits]`, but the fallback-IP branch also passes a
custom `TelegramFallbackTransport` — so the limits were silently
discarded and the inner AsyncHTTPTransport instances ran with httpx
defaults (keepalive_expiry=5.0), leaking CLOSE_WAIT fds.

Pass the tuned limits directly into `TelegramFallbackTransport`
via `transport_kwargs` so its inner transports honour keepalive_expiry.
Only affects the fallback-IP branch; proxy and direct-DNS branches
continue to use `_with_limits()` as before.

Fixes #58790
2026-07-05 22:08:42 +05:30
kshitij
368e5f197e
Merge pull request #58698 from kshitijk4poor/feat/pre-tool-call-approve-escalation
feat(plugins): pre_tool_call approve action escalates to human gate (closes #51221)
2026-07-05 22:04:23 +05:30
kshitij
abf9638f4e
Merge pull request #58974 from kshitijk4poor/salvage/compressor-zero-user-58753
fix(compressor): keep a user turn when compression would drop the only one (#58753)
2026-07-05 21:57:38 +05:30
kshitijk4poor
b2c55582ef test(compressor): drop source-string guardrail tests
The two TestSourceGuardrail tests asserted the presence of literal
strings ("#58753", "_user_survives") in context_compressor.py. Those
are change-detector tests that break on any refactor without catching a
real regression. The four behavioral tests in
TestCompressAlwaysKeepsAUserTurn already exercise the real compress()
path and fully cover the invariant (user turn survives, summary pinned
to user, no consecutive user roles, surviving tail user untouched).
2026-07-05 21:42:19 +05:30
HexLab98
10ced05676 test(compressor): pin the zero-user-turn compaction guard (#58753)
Regression coverage for the kanban-worker crash where compression left a
transcript with no user-role messages, triggering a non-retryable
`400 No user query found in messages` from vLLM/Qwen.

Exercises the real `compress()` path with the reporter's shape (no system
prompt in the list, a re-compaction with the only user turn in the
compressed middle) and asserts the output always keeps >=1 user turn,
never introduces consecutive user roles, and leaves a surviving tail user
message untouched. A source guardrail pins the guard so a future refactor
cannot silently drop it.
2026-07-05 21:41:45 +05:30
HexLab98
24add1db74 fix(compressor): keep a user turn when compression would drop the last one
Compression could produce a transcript with ZERO user-role messages,
which OpenAI-compatible backends (vLLM/Qwen) reject with a non-retryable
`400 No user query found in messages`. This crashes `hermes kanban`
workers unrecoverably: every resume replays the same poisoned history and
fails on the very first request after a successful compaction.

The existing #52160 guard pins the handoff summary to role="user" only
when `last_head_role == "system"` — i.e. when the system prompt sits
inside `messages` (the gateway `/compress` path). The main
auto-compression path prepends the system prompt at request-build time,
so the list handed to `compress()` starts with a user/assistant turn,
`last_head_role` defaults to "user", and the summary is emitted as
role="assistant". A kanban worker seeded with a single short
`"work kanban task <id>"` prompt followed by nothing but assistant/tool
turns therefore ends up user-less once that early turn is summarised.

Generalise the guard: when no user-role message survives in the protected
head or the preserved tail, force the summary to carry role="user" so the
request always has at least one user turn. When a user does survive
(e.g. in the tail), the guard does not fire, so alternation is preserved.

Fixes #58753.
2026-07-05 21:41:44 +05:30
Teknium
605727e3b4
feat(discord): optional admin-only gate for exec-approval buttons (#51751)
Add an opt-in toggle (require_admin_for_exec_approval, default false) that
restricts who can click Approve/Deny on a dangerous-command prompt to admins
listed in allow_admin_from. Off by default, so the v0.16-restored user-scope
behavior is unchanged. When on, the clicker must pass the normal admission
check AND be an admin; fails closed (logged) when no admins are configured.
Only ExecApprovalView is gated — model picker / clarify / update-prompt stay
user-scope.
2026-07-05 06:42:42 -07:00
Teknium
8a04b516a8
Port from cline/cline#11803: recursively normalize JSON-string tool args by schema (#52220)
coerce_tool_args only repaired the outermost value, so JSON-encoded
*elements* of array properties (and nested object sub-fields) were left
as strings. Three core tools have array<object> schemas — todo.todos,
delegate_task.tasks, memory.operations — so a model emitting
{"todos": ["{...}"]} would pass raw JSON strings into the tool and fail
downstream on item["id"]/item["goal"] access.

Adds a schema-guided recursive pass (_normalize_json_strings_for_schema)
that parses JSON-string array items and nested object fields only when
the matching schema position expects an array/object, preserving
legitimate JSON-looking string fields (type: string).

Adapted from cline/cline#11803 to hermes-agent's existing coercion layer.
2026-07-05 06:42:28 -07:00
teknium1
b3b1e58ad6 fix(codex): stream commentary deltas through the reasoning channel
Follow-up to the salvaged #58696 (devatnull) + #41343 (annguyenNous)
commits: instead of fully suppressing commentary/analysis-phase stream
deltas, fire on_reasoning_delta so the CLI/gateway display them like
thinking text. Matches Codex CLI semantics where commentary is never
the turn's final answer, while keeping the narration visible in the
reasoning display. Adds devatnull to AUTHOR_MAP.
2026-07-05 06:29:45 -07:00
annguyenNous
538173f679 fix(codex): route commentary-phase preamble text to reasoning channel (fixes #41293)
GPT-5.x models on the Codex Responses API emit short pre-tool-call
"preamble" text as message items with phase="commentary". Previously,
_normalize_codex_response() added ALL message items to content_parts
regardless of phase, causing commentary text to leak as visible
assistant content on chat gateways.

Fix: when normalized_phase is "commentary" or "analysis", route the
message text to reasoning_parts instead of content_parts. This keeps
preamble/internal planning in the reasoning channel where it belongs.

Fixes NousResearch/hermes-agent#41293
2026-07-05 06:29:45 -07:00
devatnull
ea125dd62e fix: keep Codex commentary phase out of user-visible text 2026-07-05 06:29:45 -07:00
teknium1
372c0b5f45 chore: add devatnull to AUTHOR_MAP for PR #58700 salvage 2026-07-05 06:29:26 -07:00
devatnull
14c91ade32 fix: normalize display boolean strings 2026-07-05 06:29:26 -07:00
devatnull
b9de7044aa fix: preserve log tool-progress mode with status phrases 2026-07-05 06:29:26 -07:00
devatnull
d111faa3a7 fix: preserve busy steer env override 2026-07-05 06:29:26 -07:00
devatnull
12f03b11ff feat: make busy steer ack configurable 2026-07-05 06:29:26 -07:00
devatnull
46fbd73f66 fix: strip tool progress display modes 2026-07-05 06:29:26 -07:00
devatnull
fddc95f4c2 chore: limit generic status phrases to long-running notifications 2026-07-05 06:29:26 -07:00
devatnull
4bf5b563bd feat: add generic gateway status phrases 2026-07-05 06:29:26 -07:00
teknium1
b0f2bdbe8b fix(whatsapp): gate poll-vote events to Hermes-created polls + salvage follow-ups
- bridge: only enqueue poll_update events for polls Hermes itself created
  (tracked via recentlySentIds when /send-poll returns) so arbitrary human
  polls in group chats don't inject agent-visible messages on every vote
- update test_already_whatsapp_italic for the new markdown-italic mapping
- AUTHOR_MAP entry for @devatnull (PR #58704 salvage)
2026-07-05 06:27:20 -07:00
devatnull
11627fdcb9 feat(whatsapp): native Baileys polls, clarify-as-poll, locations, and rich inbound metadata
Salvaged from PR #58704 by @devatnull, scoped to the WhatsApp surface:
- bridge_helpers.js: pure, tested extraction of inbound Baileys message
  parsing (quoted text, MIME/filename, PTT vs audio, stickers, contacts,
  reactions, polls, locations, GIF playback metadata)
- native poll primitive: /send-poll endpoint, poll messageSecret caching,
  encrypted vote decryption + aggregation via Baileys
- send_clarify() renders multi-choice clarify prompts as native polls;
  votes flow back through the existing clarify text-intercept
- send_location() + /send-location for native WhatsApp location pins
- structured quoted-reply context (fixes duplicated '[Replying to: ...]'
  rendered both by the adapter and gateway/run.py)
- outbound formatting: markdown *italic* -> WhatsApp _italic_, invisible
  unicode sanitization; execSync -> execFileSync hardening; GIF -> mp4
  gifPlayback conversion with truthful image/gif fallback

Out of scope (deliberately not salvaged from #58704): cross-platform
ordered-delivery machinery in gateway/platforms/base.py, LOCATION: and
hermes:poll response-text directives (no prompt wiring exists yet), and
the unconditional WhatsApp reply-anchor suppression.
2026-07-05 06:27:20 -07:00
teknium1
0ca2a927cf chore: add devatnull to AUTHOR_MAP for PR #58697 salvage 2026-07-05 06:12:49 -07:00
Kong
558001307a feat(desktop,docs): surface stt.echo_transcripts in desktop settings and docs
Adapted from PR #53038 (stt.echo) to the stt.echo_transcripts key:
- desktop Voice settings section gains the Echo Transcripts toggle with
  label + description copy
- configuration.md documents stt.enabled / stt.echo_transcripts
2026-07-05 06:12:49 -07:00
devatnull
4be749d151 fix: honor top-level STT transcript echo config 2026-07-05 06:12:49 -07:00
devatnull
406eb719c3 fix: gate interrupt STT transcript echoes 2026-07-05 06:12:49 -07:00
devatnull
bfc5262725 feat: add STT transcript echo toggle 2026-07-05 06:12:49 -07:00
teknium1
95fc3c6b45 chore: add alastraz to AUTHOR_MAP for PR #41383 salvage
Some checks are pending
CI / Detect affected areas (push) Waiting to run
CI / Python tests (push) Blocked by required conditions
CI / Python lints (push) Blocked by required conditions
CI / TypeScript (push) Blocked by required conditions
CI / Docs Site (push) Blocked by required conditions
CI / Deny unrelated histories (push) Blocked by required conditions
CI / Check contributors (push) Blocked by required conditions
CI / Check uv.lock (push) Blocked by required conditions
CI / Lint Docker scripts (push) Blocked by required conditions
CI / Build&Test Docker image (push) Blocked by required conditions
CI / Supply-chain scan (push) Blocked by required conditions
CI / OSV scan (push) Waiting to run
CI / All required checks pass (push) Blocked by required conditions
CI / CI timing report (push) Blocked by required conditions
Deploy Site / deploy-vercel (push) Waiting to run
Deploy Site / deploy-docs (push) Waiting to run
2026-07-05 05:36:31 -07:00
Adrian Lastra
519ec7b3b3 fix(computer_use): parse (label) and = "value" AX element label forms
The SOM/AX element list dropped labels for two extremely common cua-driver
render forms, leaving the model unable to target elements by name:
  - [79] AXButton (Dark)              -> parenthesised label
  - [4]  AXStaticText = "Wi-Fi"       -> = "value" form
  - [92] AXPopUpButton = "Automatic"  -> = "value" form
The old regex only matched quoted "label" and id=Label, so System Settings
buttons/text/popups all surfaced with empty labels. That's why selecting the
macOS Appearance 'Dark' button by element index required guessing — the
labels weren't available to aim with.

Fix: extend _ELEMENT_LINE_RE to capture all four label forms (= "value",
"quoted", (parenthesised), id=Label), skipping a pure-digit (N) order number
in favour of the id= label. Verified live against System Settings: the
Appearance buttons now surface as Auto/Light/Dark.

Adds a regression test covering all label forms. Full suite: 84 passed.
2026-07-05 05:36:31 -07:00
Adrian Lastra
13b75e73ff fix(computer_use): re-fetch via CLI when MCP returns silent-empty captures
The first fix handled the EAGAIN McpError path. But the persistent MCP
session (long-running gateway/desktop worker) has a second failure mode:
list_windows or get_window_state 'succeed' over MCP yet return a
degenerate/empty payload (no windows, or no screenshot + blank tree)
WITHOUT raising — typically when the bridge reconnected mid-call and
dropped the heavy response. That surfaced to the model as a silent 0x0
capture with no error and no fallback firing (0.00s empty return).

Fix: detect empty results in capture() and re-fetch over the CLI
transport before giving up:
  - empty list_windows -> CLI re-fetch the window list
  - empty get_window_state (som/ax) -> CLI re-fetch the AX tree + screenshot
  - empty screenshot (vision) -> CLI re-fetch get_window_state for the PNG

Adds 2 regression tests. Full suite: 83 passed.
2026-07-05 05:36:31 -07:00
Adrian Lastra
7af9abd174 fix(computer_use): fall back to CLI transport when cua-driver MCP bridge hits EAGAIN
The cua-driver MCP stdio bridge intermittently (and on some machines
persistently) fails to forward heavier calls like get_window_state to
the daemon with POSIX EAGAIN — 'daemon transport error forwarding
get_window_state: Resource temporarily unavailable (os error 35)'.
The wrapper surfaced this as an empty 0x0 capture, so computer_use
returned blank screenshots even though the display, permissions, and
the daemon were all healthy (the direct 'cua-driver call' CLI path
worked fine throughout).

Fix: when the MCP path raises the transient/transport error, fall back
to the 'cua-driver call' subprocess transport, which talks to the
daemon over a different socket. The CLI fallback routes get_window_state
screenshots to a temp file via screenshot_out_file (tiny JSON response
instead of a multi-MB base64 blob that congests the socket), reads the
PNG back, retries with backoff, and remaps the JSON into the same
{data, images, structuredContent, isError} shape the MCP path produces
so capture()/_action() are transport-agnostic.

Adds _is_transient_daemon_error() classifier and 3 regression tests.
Verified live: captures that returned 0x0 now return full
1567x905 screenshots with the AX element tree.
2026-07-05 05:36:31 -07:00
Teknium
de4310c8f6
fix(computer-use): report the wedged startup phase in the session ready-timeout error (#58801)
The 'never reached ready' error (issue #57025) was undiagnosable — doctor
and MCP test pass while the wrapper times out, with no hint where startup
stalled. Track a phase marker through _lifecycle_coro (binary-check →
manifest-discovery → mcp-initialize → capability-discovery → ready) and
include it in the timeout RuntimeError plus a pointer to doctor and the
agent.log phase timings.

Complements the 15s→30s bump + success-path phase timing log from #58760.
2026-07-05 05:36:09 -07:00
Teknium
24a7546918
fix(cli): drop shell=True from cua-driver installer — download to mkstemp, exec as argv (#58796)
Replaces the POSIX `/bin/bash -c "$(curl …)"` invocation with a
download-then-exec flow: curl the upstream install.sh into a mkstemp
temp file (unpredictable name, 0600) and run it as a plain argv list.
No shell=True, no command substitution. The temp script is removed in
a finally block; download failures return cleanly without exec.

Salvages the intent of #34974 by @ErnestHysa. His original patch
targeted a fixed /tmp/cua-driver-install.sh path (symlink/TOCTOU-prone
on multi-user hosts) and predates Windows/Linux installer support;
this version uses mkstemp and keeps the powershell path untouched.

Co-authored-by: ErnestHysa <takis312@hotmail.com>
2026-07-05 05:35:44 -07:00
Teknium
2c0820c9ff
feat(cli): autocomplete + ghost text for stacked slash-skill invocations (#58763)
Follow-up to #57987: after /skill-a the completer previously went silent
for a second /skill token. Now, while the leading tokens form an unbroken
skill chain (each token a distinct installed skill, under the 5-cap) and
the word under the cursor starts with '/', the completer keeps offering
the remaining skill commands, and SlashCommandAutoSuggest ghost-suggests
the rest of the next skill name. Instruction text, path-like tokens, and
broken chains get no suggestions. The TUI's complete.slash RPC reuses
SlashCommandCompleter, so it inherits the behavior with no changes.
2026-07-05 04:34:14 -07:00
Teknium
1c156736dc
docs: warn that mid-session model switches break prompt caching (#58747)
/model switches, primary-model fallback, and credential-pool key
rotation all change the prompt-cache key (model and/or account), so
the next turn re-reads the entire conversation at full input price.
Add cost warnings everywhere docs recommend or describe these paths:

- reference/slash-commands.md: cost note on both /model rows
- user-guide/features/fallback-providers.md: warning admonition
- user-guide/features/credential-pools.md: warning admonition
- user-guide/configuring-models.md: mid-session switch warning
- guides/tips.md: expand cache tip + /model tip
- reference/faq.md: warning on the switch-back-and-forth example
- user-guide/desktop.md: composer picker bullet
- developer-guide/context-compression-and-caching.md: new
  cache-aware design pattern (model identity is part of the key)
2026-07-05 04:34:05 -07:00