Follow-up to the salvaged #35533 fix: instead of parsing 'here [N]' only in
_mirror_slash_side_effects, parse it inside _compress_session_history — the
single helper all three manual-compress routes converge on (session.compress
RPC, command.dispatch /compress|/compact, slash-exec mirror). Every route now
honors the boundary-aware forms (here [N], up to here, --keep N) with the
same head/tail split + rejoin as cli.py and gateway/slash_commands.py, and
keeps the choke point's existing guards (lock-free LLM call, history_version
race check, deferred context-engine notification).
Tests: choke-point unit tests for 'here N', degenerate-split fallback, and
focus-topic passthrough, plus endpoint-level tests on each of the three
routes.
_mirror_slash_side_effects() passed the raw argument after /compress
directly as focus_topic to _compress_session_history. So /compress here
3 silently used "here 3" as a summary focus topic and did a full
compress instead of preserving the last 3 exchanges verbatim.
Fix: call parse_partial_compress_args() on the argument first. When it
detects a boundary-aware form (here, here N, up to here, --keep N),
split the history into head/tail using split_history_for_partial_compress,
compress only the head via agent._compress_context, and rejoin with
rejoin_compressed_head_and_tail — exactly mirroring cli.py and
gateway/run.py's /compress here implementation (PR #35252).
Non-boundary forms (plain /compress, /compress <focus>) fall through
to _compress_session_history unchanged.
Photon (managed iMessage) shipped without a _PLATFORM_DEFAULTS entry, so it
inherited the noisy global ('all') display defaults and narrated tool
progress / heartbeats / busy-ack detail into a permanent-message iMessage
thread. Register it as TIER_LOW alongside BlueBubbles and Signal, plus a
regression test guarding the tier.
Salvaged from #50511 (Photon tier piece only).
The PR's inline toolset resolution (checking 'memory' in cli_toolsets
list) produced wrong results for composite toolsets like 'hermes-cli'
which expand to include the memory tool. Replace with the canonical
_get_platform_tools() from tools_config.py which correctly handles
composite toolsets and all edge cases.
Update tests to mock _get_platform_tools instead of raw config.
The 'Built-in: always active' label was a hardcoded string that never
reflected the user's actual configuration. It now shows three separate
indicators, each reading from the real source of truth:
- Memory injection: reads memory.memory_enabled from config.yaml
- User profile: reads memory.user_profile_enabled from config.yaml
- Memory tool: checks if 'memory' is in platform_toolsets.cli
(or defaults to enabled if no explicit list)
Before:
Built-in: always active
After:
Built-in (MEMORY.md / USER.md):
Memory injection: disabled ✗
User profile: disabled ✗
Memory tool: disabled ✗
Invalid-tool exhaustion and truncated-tool early returns skipped finalize_turn, leaving role=tool transcripts that become tool→user on the next turn for strict providers. Call close_interrupted_tool_sequence before persist on those paths (same as interrupt aborts).
Hoist the duplicated check_info(source_id) call out of both
if/else branches into a single call after the branch. Remove
trailing whitespace on the blank line after the except block.
Follow-up cleanup for PR #69981.
Consolidate the two near-identical warning strings in
_log_wal_reset_bug_once into a single logger.warning call with an
action variable. Remove overengineered defensive tuple-length handling
in is_sqlite_wal_reset_vulnerable (sqlite3.sqlite_version_info always
returns a 3-tuple). Remove extra blank line.
Follow-up cleanup for PR #69981.
Assert the version matrix, fresh-DB DELETE fallback, already-WAL left
alone (no checkpoint/DELETE), fixed-SQLite WAL path, and warn-only
doctor output for vulnerable builds (#69784).
On vulnerable SQLite (e.g. 3.50.4), do not enable WAL for fresh/non-WAL
shared databases — prefer DELETE instead. Leave existing on-disk WAL
alone (no live downgrade under concurrent gateway/cron openers). Surface
Python/SQLite version details as a doctor warning (#69784).
Guard the globalThis gateway-state container and the survivor park/adopt
calls on the import.meta.hot literal (not the runtime hmrActive() helper),
so Vite dead-code-eliminates every HMR path in production. Prod now uses a
plain module-local singleton — no globalThis, no Symbol.for — and the
survivor module drops out of the bundle entirely. Verified: gatewayRegistryState,
gatewaySurvivor, and import.meta.hot are all absent from the prod build.
Removes the now-unused hmrActive() export.
Park the live primary gateway socket on Fast Refresh dispose and re-adopt it
on remount so dev UI edits don't tear down the WebSocket. Hold gateway store
singletons on globalThis + self-accept HMR on store/gateway.ts. Prod strips
import.meta.hot — live unmount unchanged.
Two fixes for desktop hands-free voice:
- The live speech session bound to the first assistant bubble with text, so
a tool-calling turn spoke only the opening narration and silently dropped
every later interim AND the final answer. The conversation selector now
aggregates all unspoken assistant bubbles in order (turn-scoped speech);
auto-speak keeps its latest-reply-only behavior.
- The speak-stream WS producer blocked forever on the text queue, so a
narration line with no trailing whitespace ("Let me check.") sat in the
sentence chunker until end-of-turn — spoken long after the tool finished,
with the UI stuck on "Preparing audio…". Mirror the CLI speaker's idle
flush: sentence-terminated buffers flush after 0.5s of producer silence,
anything else after ~2s; open <think> blocks are never flushed.
* fix(desktop): render agent credit notices as toasts (#69808)
The desktop renderer had no handler for the `notification.show` /
`notification.clear` WS events, so every credit-usage notice the backend
sends (`agent/credits_tracker.py` → `tui_gateway/server.py`) was silently
dropped. Credit warnings like "• Credits 50% used · $220.00 cap" never
appeared, even though the Ink TUI renders them in its status bar.
Add the two missing branches to the gateway-event dispatcher, delegating
to a small, pure-testable module:
- `store/agent-notices.ts` — `noticeToToast()` maps a notice to a toast
(level → toast kind, sticky → durationMs 0, ttl → ttl_ms), and uses the
notice `key` as the toast id. Re-emitting the same key REPLACES the
toast, so the credits 50→75→90 line escalates in place instead of
stacking, and a key-matched `notification.clear` maps straight to
`dismissNotification(key)`.
- The notice `text` already carries its own glyph (• ⚠ ✕ ✓), so no toast
icon is added.
- Notices are account-wide, so the toast shows regardless of which
session is focused.
The Ink TUI (`ui-tui/src/app/turnController.ts`) is the reference for the
latest-wins / sticky-vs-ttl / key-matched-clear behavior.
Export `NotificationInput` so the mapping's return type can be named.
* feat(desktop): native OS credit alerts + billing-page nudge (#69808)
Round out the credit-notice handling from the previous commit with the two
optional pieces from the issue:
- Native OS notification for the urgent pair. `credits.depleted` /
`credits.restored` also fire an Electron notification when Hermes is
backgrounded, via a new `credits` NativeNotificationKind (the existing
five didn't fit) with its own toggle in Settings → Notifications (the
panel is data-driven off NATIVE_NOTIFICATION_KINDS, so the toggle and
i18n are the only additions). The escalating usage line and grant-spent
notice stay in-app toasts only. Dispatch is `global` (account-wide, not
session-bound) and gated by the user's prefs + backgrounded check.
- Billing-page nudge. A `credits.*` crossing invalidates the
`['billing','state']` query so Settings → Billing reflects the change
immediately instead of waiting up to 30s for its poll.
`nativeNoticeInput()` is a pure mapping (urgent-key gate → native input),
unit-tested directly; the gateway-event branch does the localized-title
lookup and gated dispatch. i18n added for all four locales.
* feat(credits): report $used of $cap instead of % in the usage notice
The usage gauge is Nous subscription-cap-only (used_fraction requires a cap;
non-Nous providers emit no headers, so no notice fires). A bare percentage
implied a universal unit that doesn't exist, so report the absolute dollars
used of the cap instead: used = cap - remaining, from micros (money-safe),
clamped to [0, cap]. Still a snapshot at band-crossing (re-emits on band
change, not every turn) to keep the single escalating line and stay quiet on
append-only surfaces (messaging pushes one message per crossing).
* fix(desktop): de-dupe credit toast icon, band-color the figure, split detail
Three fixes to how agent credit notices render as toasts:
- Strip the leading severity glyph (the toast already draws a kind icon, so the
raw text doubled it). Native OS notifications keep the glyph (no icon there).
- Icon top-margin is now 0.42ch (font-relative) instead of a fixed rem.
- Band-color the $used figure (semibold) by $used/$cap: muted <75%,
--ui-orange >=75%, --ui-red >=90% (depleted red, restored green), reusing the
existing --ui-* usage palette. Icon shares the accent.
- Split a trailing '. detail' into a muted secondary line (title+description
convention) instead of an inline middot.
Generic 'accentColor' + 'meta' slots on the notification; degrades gracefully
when a notice has no figure.
* fix(desktop): billing page always fetches fresh state (team-account desync)
The x-nous-credits-* headers are best-effort and can drift out of sync,
notably in team/org accounts where another member's spend moves the shared
balance without touching this client's headers. The billing endpoint is the
source of truth, so the page no longer trusts a cache: staleTime 0 +
refetchOnMount 'always' force a fresh fetch on every open and focus (still
polling 30s while mounted). The credits.* invalidation nudge still pulls a
crossing in immediately.
* chore(desktop): dev-only credit-notice demo hotkey
Ctrl+Shift+C (and window.__creditsDemo()) steps the full credit-notice
lifecycle (usage 50->75->90, grant-spent, depleted/restored) through the real
gateway event fan-out via a new emitLocalGatewayEvent, so the toast/native/
billing-invalidation paths are testable without hitting real usage bands.
Installed only under import.meta.env.DEV, so it's tree-shaken from production.
Ctrl+Shift+C (and window.__creditsDemo()) steps the full credit-notice
lifecycle (usage 50->75->90, grant-spent, depleted/restored) through the real
gateway event fan-out via a new emitLocalGatewayEvent, so the toast/native/
billing-invalidation paths are testable without hitting real usage bands.
Installed only under import.meta.env.DEV, so it's tree-shaken from production.
The x-nous-credits-* headers are best-effort and can drift out of sync,
notably in team/org accounts where another member's spend moves the shared
balance without touching this client's headers. The billing endpoint is the
source of truth, so the page no longer trusts a cache: staleTime 0 +
refetchOnMount 'always' force a fresh fetch on every open and focus (still
polling 30s while mounted). The credits.* invalidation nudge still pulls a
crossing in immediately.
Three fixes to how agent credit notices render as toasts:
- Strip the leading severity glyph (the toast already draws a kind icon, so the
raw text doubled it). Native OS notifications keep the glyph (no icon there).
- Icon top-margin is now 0.42ch (font-relative) instead of a fixed rem.
- Band-color the $used figure (semibold) by $used/$cap: muted <75%,
--ui-orange >=75%, --ui-red >=90% (depleted red, restored green), reusing the
existing --ui-* usage palette. Icon shares the accent.
- Split a trailing '. detail' into a muted secondary line (title+description
convention) instead of an inline middot.
Generic 'accentColor' + 'meta' slots on the notification; degrades gracefully
when a notice has no figure.
The usage gauge is Nous subscription-cap-only (used_fraction requires a cap;
non-Nous providers emit no headers, so no notice fires). A bare percentage
implied a universal unit that doesn't exist, so report the absolute dollars
used of the cap instead: used = cap - remaining, from micros (money-safe),
clamped to [0, cap]. Still a snapshot at band-crossing (re-emits on band
change, not every turn) to keep the single escalating line and stay quiet on
append-only surfaces (messaging pushes one message per crossing).
Round out the credit-notice handling from the previous commit with the two
optional pieces from the issue:
- Native OS notification for the urgent pair. `credits.depleted` /
`credits.restored` also fire an Electron notification when Hermes is
backgrounded, via a new `credits` NativeNotificationKind (the existing
five didn't fit) with its own toggle in Settings → Notifications (the
panel is data-driven off NATIVE_NOTIFICATION_KINDS, so the toggle and
i18n are the only additions). The escalating usage line and grant-spent
notice stay in-app toasts only. Dispatch is `global` (account-wide, not
session-bound) and gated by the user's prefs + backgrounded check.
- Billing-page nudge. A `credits.*` crossing invalidates the
`['billing','state']` query so Settings → Billing reflects the change
immediately instead of waiting up to 30s for its poll.
`nativeNoticeInput()` is a pure mapping (urgent-key gate → native input),
unit-tested directly; the gateway-event branch does the localized-title
lookup and gated dispatch. i18n added for all four locales.
The desktop renderer had no handler for the `notification.show` /
`notification.clear` WS events, so every credit-usage notice the backend
sends (`agent/credits_tracker.py` → `tui_gateway/server.py`) was silently
dropped. Credit warnings like "• Credits 50% used · $220.00 cap" never
appeared, even though the Ink TUI renders them in its status bar.
Add the two missing branches to the gateway-event dispatcher, delegating
to a small, pure-testable module:
- `store/agent-notices.ts` — `noticeToToast()` maps a notice to a toast
(level → toast kind, sticky → durationMs 0, ttl → ttl_ms), and uses the
notice `key` as the toast id. Re-emitting the same key REPLACES the
toast, so the credits 50→75→90 line escalates in place instead of
stacking, and a key-matched `notification.clear` maps straight to
`dismissNotification(key)`.
- The notice `text` already carries its own glyph (• ⚠ ✕ ✓), so no toast
icon is added.
- Notices are account-wide, so the toast shows regardless of which
session is focused.
The Ink TUI (`ui-tui/src/app/turnController.ts`) is the reference for the
latest-wins / sticky-vs-ttl / key-matched-clear behavior.
Export `NotificationInput` so the mapping's return type can be named.
Guard the post-start set_agent_cursor_enabled on _session._started so
call_tool cannot re-enter session.start() (matches the start_session
lifecycle guard).
cua_driver_update_check now short-circuits to None when no driver
resolves; CI has none installed, so pin resolve_cua_driver_cmd in the
update-check and env-sanitization tests.
Cut steady-state Computer Use latency without changing default behavior
or waiting on cua-driver:
- Cap screenshots via set_config(max_image_dimension) on session start
(config: computer_use.max_image_dimension, default 1456)
- Cache aux-vision routing per (provider, model) so captures skip
repeated load_config()
- Add computer_use.capture_after_mode (default som) so users can opt
follow-ups down to ax (elements only) for speed
Auto-detect now disables the cursor overlay on darwin as well as
headless/WSL2 Linux. After start_session, also call
set_agent_cursor_enabled(false) when the policy is on so older drivers
without --no-overlay still tear the overlay down.
Co-authored-by: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com>
Print gh-image stdout and stderr on attachment failures, then replace the
pending inline-evidence marker in the PR review comment with an escaped
failure notice before preserving the failing workflow result.
The hermes-sweeper review #4701565902 (2026-07-15) flagged two
consistency issues in `_cua_driver_supports_no_overlay` and one
additive-config concern:
1. `cua-backend.py:260` — the `cua-driver --help` support probe
inherited the full parent environment. cua-driver is a third-party
binary; every other spawn site in this file (manifest probe at
`:214`, MCP spawn at `:697`, install probe at `:997`) uses
`_sanitize_subprocess_env(cua_driver_child_env())`. The `--help`
probe should match. This was a low-impact leak (only help output
exits), but inconsistency is the wrong default for a third-party
subprocess.
2. `cua_backend.py:238` — when the manifest returned a `command`
different from the input `driver_cmd` parameter (e.g. a relocated
executable at `/opt/relocated/cua-driver` while the system binary
is at `/usr/bin/cua-driver`), the support probe ran against
`_CUA_DRIVER_CMD` (the default) instead of the manifest-discovered
`command`. Two failure modes:
- The wrapper binary supports `--no-overlay` but the system binary
doesn't → probe returns False → overlay kept despite capability.
- The system binary supports `--no-overlay` but the wrapper doesn't
→ probe returns True → MCP spawn crashes on the unknown flag.
3. The original commit bumped `_config_version` 31→32 for an additive
default (`computer_use.no_overlay: None`). AGENTS.md specifies that
additive defaults in existing sections are handled by deep merge
and should NOT trigger a version bump. After cherry-picking onto
current `origin/main` (which is already at 33), the bump is
effectively dropped — resolved to main's 33.
Changes:
- Add `env=_sanitize_subprocess_env(cua_driver_child_env())` to the
`--help` subprocess (with the same import + rationale comment as
the manifest probe).
- Pass `driver_cmd=command` (or `driver_cmd=driver_cmd` for the
fallback path) into `_mcp_args_with_overlay_flag`, so the support
probe runs against the binary that will actually be launched.
Tests (3 new):
- `test_help_probe_passes_sanitized_env` — verifies `subprocess.run`
is called with an `env=` kwarg.
- `test_manifest_command_drives_support_probe` — verifies the probe
runs against the manifest command when it differs from the input
driver_cmd.
- `test_fallback_uses_input_driver_cmd_for_support_probe` — verifies
the fallback path (no command in manifest) uses the input
driver_cmd.
- `test_probe_distinguishes_support_between_binaries` — sanity check
that the lru_cache key on `driver_cmd` prevents cross-binary
cache leakage.
File-revert negative test confirmed all three of the new
"manifest/probe" tests are load-bearing: with the pre-fix code, they
fail (probe runs against the default binary instead of the resolved
one); with the fix, they pass. 20/20 tests in
`tests/computer_use/test_cua_no_overlay.py` green.
`TestMcpInvocationResolution` (8/8) still green.
Refs: sweeper review #4701565902
Address review feedback from cross-vendor review (Flash + GPT-OSS):
1. Auto-detect now checks for headless Linux (no DISPLAY), WSL2
(/proc/version contains 'microsoft'), instead of all Linux.
Desktop Linux with a compositor keeps the overlay.
2. Add _cua_driver_supports_no_overlay() that probes cua-driver --help
to check if the flag is supported. Older drivers (< 0.6.x) reject
unknown flags, so passing --no-overlay would crash the MCP spawn.
3. Update tests to cover headless vs desktop Linux, WSL2 detection,
version probe, and the unsupported-driver fallback path.
- Add autouse fixture to TestMcpInvocationResolution to disable
--no-overlay flag so existing tests assert baseline args
- Make test_config_load_failure_fails_safe and test_missing_section_enables
platform-aware (Linux auto-detect returns True, macOS/Windows False)
cua-driver's cursor overlay rendering loop can consume CPU indefinitely
when idle (#28152, #47032). On Linux/WSL2, the overlay serves no visual
purpose and the rendering path is the primary source of idle CPU usage.
Add computer_use.no_overlay config option (default: auto-detect) that
passes --no-overlay to cua-driver when enabled. Auto-detection disables
the overlay on Linux (covers WSL2, headless, containers) where it has no
benefit, and keeps it enabled on macOS/Windows where it is visually
useful.
Refs: #28152, #47032
Keep thin-PATH resolution while asserting the absolute binary on the
already-installed version check. Map contributor emails for the salvage.
Co-authored-by: Tianqing Yun <yuntianqing@yahoo.com>
Co-authored-by: Trevor Gordon <trevorbgordon@gmail.com>
Co-authored-by: Adrian Soto Mora <adrian.soto6@gmail.com>
Supersedes #52948 and #67402. Closes#52945.
Standalone hermes dashboard/serve was trusting HERMES_WEB_DIST and
HERMES_SERVE_HEADLESS inherited from a Desktop Electron parent, which
could serve the packaged desktop renderer ("Desktop IPC bridge is
unavailable") or disable the SPA. Drop only Electron-packaged WEB_DIST
paths (app.asar*) when HERMES_DESKTOP!=1, and clear inherited headless
for non-serve launches, while preserving caller-managed custom dist
overrides and the desktop-spawned backend path.
Co-authored-by: Bartok9 <danielrpike9@gmail.com>
Co-authored-by: Commander <commander@tianji.local>