Commit Graph

7858 Commits

Author SHA1 Message Date
Yuan Li
7974173708 fix(desktop): defer transcript video loading 2026-10-06 14:59:13 -05:00
alt-glitch
6aef8dfe87 fix(desktop): an install another package manager owns gets package-manager removal steps (review)
nativeRemovalInstructions told an 'external' Linux install with no AppImage path to delete an AppImage or app directory, which leaves the package registered with missing files.
2026-10-07 01:29:02 +05:30
alt-glitch
73133c88c0 fix(bundle): packaged installs run their own hermes and name their own app (review)
The agent's terminal puts a sealed payload's launcher dir first on PATH even when the inherited PATH already lists it behind another install.
The macOS removal instructions name the running .app bundle and its folder instead of a fixed Hermes.app.
A manifest.json with a mistyped runtime, commands, venv or store reads as not a payload, and a missing venv or store is treated as unset, so startup no longer raises.
2026-10-07 01:29:02 +05:30
alt-glitch
237fba22e0 test(desktop): window focus policy calls isFocused as a method
The existing stubs pass isFocused as an arrow function, so they cannot catch
a detached call that loses `this` (Electron then throws "Object has been
destroyed"). The new case uses a window whose isFocused reads `this`. The
fix itself is already on main.
2026-10-07 01:29:02 +05:30
alt-glitch
3d8ce003a4 fix(desktop): packaged installs show how to uninstall instead of an empty page
Settings > About > Uninstall rendered nothing when the OS or a package
manager owns removal (MSIX, bundled, Nix). The summary IPC now adds the
canonical nativeRemovalInstructions text from the main process. The page
shows it, where user data lives, and on Windows an "Open Apps settings"
button backed by a fixed-URL IPC, because the generic openExternal bridge
admits web schemes only.

The uninstall strings move into en_uninstall_section.ts and
types_uninstall_section.ts, main's split i18n layout, which keeps en.ts and
types.ts under their line caps. main.ts offsets its two new lines by
deleting isActiveRuntimeUsable(), which nothing calls.
2026-10-07 01:29:02 +05:30
alt-glitch
3383a1d8b3 fix(bundle): the agent's terminal runs a working hermes inside the desktop payload
Inside a packaged desktop build, bare `hermes` in the terminal tool resolved
to <payload>/venv/Scripts/hermes.exe. That venv was built on CI, and its
Windows redirector reads pyvenv.cfg `home`, which still names the CI path,
so every call failed with "did not find executable at C:\a\hermes-agent\...".
The working launcher in <payload>/bin carries the channel name and was never
on the agent's PATH.

- activate_dependencies puts the payload's own launcher dir (from the
  manifest's runtime.commands) first on PATH, and on Windows no longer adds
  the shipped venv's Scripts dir: every redirector in it names the CI
  interpreter.
- _resolve_hermes_bin_dir answers with that launcher dir in a sealed payload,
  so terminal and cron children do not depend on PATH order.
- stageDesktopLaunchers copies the canonical launchers to their qualified
  names instead of renaming them. Only the qualified names are published.
- The "plugin already exists" hint names the real CLI command.
- Every manifest-relative path (venv, store, hermes launcher) goes through one
  resolve-and-contain check, and a manifest.json that is unreadable or not a
  payload manifest reads as "not a payload" instead of raising, since every
  install now asks.

Source and dev installs are unchanged: they have no payload manifest.
2026-10-07 01:29:02 +05:30
Brooklyn Nicholson
2e332c0e07 test(desktop): model a real multi-profile window in connection-tag fixtures
f6c15b0971's fixtures toggle all-profiles with an empty $profiles cache,
which 87e94805's effective-scope rule now treats as a hidden single-profile
preference. Seed two profiles like projects.test.ts so the suite exercises
the real all-profiles path.
2026-10-06 14:58:46 -05:00
ehz0ah
32c00d30f8 fix(desktop): honor effective project profile scope 2026-10-06 14:58:46 -05:00
Austin Pickett
049d2c2223 fix(desktop): settle each remote profile's sidebar read under its own budget
One unavailable remote profile no longer holds the whole session sidebar
aggregate hostage until the 180s backend readiness deadline. Each remote's
session read now runs under its own bounded budget and settles allSettled-
style: healthy profiles' rows land immediately, the dead one contributes a
named error entry in the response payload instead of being swallowed to
null, and the underlying fetch keeps running so a slow-to-boot remote lands
on a later refresh. No sidebar visuals change — the renderer's existing
errors handling carries the failure.

Fixes #75712
2026-10-06 15:56:52 -04:00
FalconOrtiz
14e941b1a7 test(desktop): pin last-assign appearance hop
Named-profile assign writes the global slot on every pick. After forge=dark then atlas=light, a never-themed profile inherits light. A focused test pins that hop. promoteUnanimousLegacy persists from stored. The write is idempotent and a no-op when records disagree.
2026-10-06 14:56:18 -05:00
FalconOrtiz
f634b08a2c fix(desktop): keep Dark mode across Bot Mode new chat
Named-profile appearance picks now mirror into the global fallback, and a unanimous pre-existing per-profile mode promotes into an empty global slot. Bot Mode gateway hops onto never-themed bots inherit the user's mode instead of resolving system (#101216).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 14:56:18 -05:00
Tom Reinelt
e671fc8b7f fix(desktop): ignore preview subframe load failures 2026-10-06 14:56:06 -05:00
Not Governor
33b8776db6 fix(desktop): drop dead titlebar gutter left of Settings
The right cluster reservation added 24px of pad before Settings.
That pad is a layout spacer with no app-region, so it ate window
drags while the empty tab strip still moved the window.

The 48px data-window-drag-handle is already the clearance; do not
mark the spacer drag (it sits under the z-70 cluster).

Fixes #131729
2026-10-06 14:55:47 -05:00
Yuan Li
fab83ebfcf test(desktop): cover focused duration wording 2026-10-06 14:55:26 -05:00
Yuan Li
27cf75a648 fix(desktop): correct focused duration label 2026-10-06 14:55:26 -05:00
Omid Zaferi
9d0475c493 fix(desktop): use --ui-text-secondary for section labels to avoid false affordance
Replaces --ui-accent with --ui-text-secondary in model-catalog-menu,
model-visibility-dialog, and dropdown-menu. Accent color is reserved for
interactive elements; secondary text is semantically correct for static
provider-group section headers. Addresses AI code review feedback on #103493.
2026-10-06 14:55:06 -05:00
Omid Zaferi
478406c2cd fix(desktop): use --ui-text-secondary for section labels to avoid false affordance
Replaces --ui-accent with --ui-text-secondary in model-catalog-menu,
model-visibility-dialog, and dropdown-menu. Accent color is reserved for
interactive elements; secondary text is semantically correct for static
provider-group section headers. Addresses AI code review feedback on #103493.
2026-10-06 14:55:06 -05:00
Omid Zaferi
82b263c978 fix(desktop): use --ui-text-secondary for section labels to avoid false affordance
Replaces --ui-accent with --ui-text-secondary in model-catalog-menu,
model-visibility-dialog, and dropdown-menu. Accent color is reserved for
interactive elements; secondary text is semantically correct for static
provider-group section headers. Addresses AI code review feedback on #103493.
2026-10-06 14:55:06 -05:00
Omid Zaferi
78d7ef3fa9 fix(desktop): increase contrast for provider group headings in model picker (#103432) 2026-10-06 14:55:06 -05:00
Hukla
b0ea14d456 test(desktop): focus list input before Enter blur 2026-10-06 14:50:58 -05:00
Hukla
94edc03bc2 fix(desktop): preserve commas while editing list settings 2026-10-06 14:50:58 -05:00
Teknium
00adf69d92 feat(stt): live transcription while you speak on CLI, TUI and Desktop (stt.streaming)
Voice input was record-then-upload on every surface: nothing showed while the user
talked, and the transcript arrived only after the whole take uploaded. With
`stt.streaming: true` the openai, xai and elevenlabs providers now stream microphone
PCM to their realtime endpoints while the user speaks. Partial text renders live and
the final transcript is ready at end of speech.

- tools/transcription_streaming.py: one session API (push_audio / end_audio /
  finalize / cancel, an on_partial callback, a worker thread that owns the socket)
  with three wires, all verified live: the OpenAI Realtime `type: transcription`
  session (gpt-live-transcribe, 16->24 kHz resample since the endpoint refuses
  < 24 kHz), xAI wss /v1/stt with interim results, and ElevenLabs Scribe v2
  realtime. Plugin providers opt in through
  TranscriptionProvider.streaming_capable / open_stream_session.
- tools/voice_mode.py: AudioRecorder taps its capture callback into the session and
  parks it under the WAV path. transcribe_recording uses the live transcript and
  falls back to the file on any failure or empty result, so CLI push-to-talk, the
  continuous VAD loop and the TUI gateway gain streaming with no call-site changes.
- Classic CLI and Ink TUI show partial text in the input placeholder while recording
  (new `voice.partial` gateway event, contract regenerated). Desktop dictation
  streams PCM to the new /api/audio/transcribe-stream socket (provider keys stay on
  the host, and the host resamples) and shows partial text in the dictation pill.
  The recorded blob stays the fallback.

The plugin streaming contract (streaming_capable / open_stream_session /
TranscriptionStreamSession) and the transcribe-stream socket shape come from #103173.

Co-authored-by: Vinicius <timelord1025@gmail.com>
2026-10-06 12:50:31 -07:00
alt-glitch
bf81f4b2c9 fix(tour): built-in tour no longer opens over a chat the user switched to (review)
Re-check the active chat after the local-model wait and answer 'Tours only run in the session the user is looking at.' when it changed.
Move TourPreset to tools/tour_presets.py so importing the wire contracts no longer registers gui_tour.
2026-10-07 01:02:34 +05:30
alt-glitch
890976c224 feat(tour): gui_tour start with no steps runs the app's built-in tour
A general look around the desktop app is now one tool call. Before, the
model ran a targets scan and wrote its own steps from raw DOM labels: two
round trips, English-only copy.

- gui_tour action='start' without steps runs the built-in tour on
  surface='app' (refused on 'preview'). preset 'quick' shows four stops:
  chats list, composer, new session, model picker. 'full' (the default)
  adds what the interface mode puts on screen. A stop that is not on
  screen is skipped. The model stop adds a line when this computer can
  run a local model (the local-setup tip's fit check, capped at 2 s).
- TourPreset is defined once, in tools/tour_tool.py. The wire contract
  types tour.preset with it, so the renderer uses the generated
  TourPreset type. Contracts regenerated.
- The stop copy lives in i18n (appTour, en_app_tour.ts); other locales
  fall back to English until the translations PR.
- engine.ts: one shared teardown for every driver exit, which now tells
  the host. The app host clears a reactive $tourActive flag, so the
  usage-stats consent dialog waits until no tour is on screen. A closed
  one-off highlight also clears it.
- en.ts and types.ts are over the size cap, so the existing handoffTour
  copy and type move unchanged into the same siblings to offset appTour.

test_start_validates_its_steps: a bare start is no longer an error; it is
the built-in tour, and start on surface='preview' without steps is.
2026-10-07 01:02:34 +05:30
alt-glitch
901da1cb6a fix(catalog): sessions off the desktop no longer see manage_catalog through tool_search (review)
An agent stores its session platform's gated toolsets in disabled_toolsets at load, so the tool_search listing, the bridge's search, MCP refreshes and delegate children drop manage_catalog too.
InstallPhase moves to the leaf hermes_cli/plugin_install_phase.py; the gateway contracts no longer import the installer.
The per-home enable lock moves from the plugins_cmd facade to plugins_cmd_install._enable_placed.
Reusing an installed catalog tree reads the install record through catalog_install_record, so older top-level catalog_name records match.
A failed row no longer redacts the Advanced choices (profile name, commit); only credentials are redacted.
The profiles.py role comment no longer says a role grants the setup toolset.
2026-10-07 01:01:14 +05:30
alt-glitch
79e6a3b42d fix(desktop): drop the session-teardown import that closed a store import cycle (review)
session-states imported connection-request, which reaches use-session-actions/utils, which imports session-states. The cycle broke the sidebar at load (vitest sidebar suites and the lineage/rename E2E). Kept install cards stay without a teardown: they are bounded by the installs a user approves in one window.
2026-10-07 01:01:14 +05:30
alt-glitch
8ce4d4bf95 fix(catalog): installs use the approved commit and settled retries show their own result (review)
Catalog plugin installs pass the commit approved under Advanced to the clone; a disabled tree on disk is reused only when it is at that commit.
A settled Try again on a plugin row draws the tools, setup notes and enabled state from that retry's install result.
A manage_catalog search row that reuses an install's call id no longer draws the install card.
Connection cards kept for a session are cleared when its runtime is dropped and on a gateway re-home; turnHasReply moves to lib/chat-messages to keep session-states.ts under its size cap.
2026-10-07 01:01:14 +05:30
alt-glitch
c2b13dc3d8 feat(desktop): every desktop chat can install catalog plugins; tools gated by session platform
Toolsets that exist only for one kind of session now say so on their spec:
`platforms=frozenset({"desktop"})`. `TOOLSET_SESSION_PLATFORMS` is derived
from the specs that carry the key, `all` / `*` / no selection skip those
toolsets, and `toolsets.agent_tool_drops(agent)` removes their tools from any
agent whose own session platform is not listed. `agent_init._load_tools` and
`tools/mcp_tool_agent.py` (MCP refresh and prefix restore) call that one
helper. `tool_search._DIRECT_SURFACE_TOOLSETS` is derived from
`CLIENT_SURFACE_TOOLSETS` and `TOOLSET_SESSION_PLATFORMS`.

This replaces the profile-role gate: `profile_role_toolsets`, the role code in
`_with_session_toolsets` and the explicit-pin branch, the strip step in
`_select_tool_names`, and main's `setup` toolset (whose only tool moves out)
are deleted. `manage_catalog` moves into a new `catalog` toolset, which the
desktop client folds into every desktop chat like `project` and `desktop_ui`.
It is deferred behind tool_search by default (`config_defaults` defer list).
The shared-metrics schemas' toolset enum follows (`setup` out, `catalog` in).

The install card works outside the setup chat (#129650, with review fixes):
- Parallel installs: `_set_plugin_enabled` holds a per-home lock around the
  read-then-admit section, so rows enabled at once no longer refuse each other.
- A catalog plugin already on disk and not enabled is enabled in place, so Try
  again after a refused enable can finish.
- A failed catalog row counts as resolved (`contract.resolves`), so it no longer
  holds the turn to the 300 s deadline. The snapshot carries `resolved`, and the
  card reads it instead of a hand copy of the backend's rule.
- Each card draws its own operation by op id, because models reuse call ids.
- The row carries the approved non-secret choices (`approved`: force, enable,
  ref) and the chosen profile, so Try again after the card settled repeats them.
- Install phases are ids (`InstallPhase`, wire `CatalogInstallPhase`) and the
  installed-row notes are fields (`enabled`, `missing_env`, `server_errors`,
  `already_installed`, `requires_hermes`); the card words them in English
  strings (translations follow later). Failure reasons stay raw text, and
  `detail` keeps the English notes for the model.
- Installs and search target the calling chat's own home, carried as a path;
  the row's profile name is only a label, with no `'default'` fallback in
  Python or TypeScript.
- `dashboard_install_plugin` is split into `_resolve_source`, `_place_tree` and
  `_enable_placed`.

Desktop English strings for the card move to `en_catalog_install.ts` /
`types_catalog_install.ts`, which offsets the growth of `en.ts` and `types.ts`.
2026-10-07 01:01:14 +05:30
alt-glitch
c1418f98fc test(release): both bundle-env validators reject the retired HERMES_SKIP_INTRO (review)
Re-adding the key to an allowlist now fails tests/scripts/test_release_build_commit.py and scripts/bundle-env.test.mjs.
2026-10-07 01:00:00 +05:30
alt-glitch
6bcdd6ceea chore(release): retire HERMES_SKIP_INTRO 2026-10-07 01:00:00 +05:30
Austin Pickett
9aaef03b06 test(desktop): give the electron vitest project a 30s per-test timeout
The electron project inherited vitest's 5s default while its tests spawn
real python3/pwsh processes over a 40+ case corpus. On loaded CI runners
pwsh startup alone takes several seconds, so
remote-update-marker-programs.test.ts timed out and turned JS & TS checks
red on unrelated PRs. The react project already carries 15s for the same
reason.
2026-10-06 15:25:36 -04:00
Adolanium
3d304a112b fix(desktop): restore sash preview styles before the release commit, teardown in finally
Two cleanup holes in TreeSplit's sash drag:

1. setPaneWidthOverride early-returns when the released px equals the
   stored override, so a drag released back at its origin is a store
   no-op with no re-render. cleanup relied on that re-render to rewrite
   flex and clear the inline preview, so the flex-basis longhand stayed
   stuck on the seam wrapper (frozen grow / stale basis) until some
   unrelated render. cleanup now restores the captured style attribute
   FIRST, then commits the store; when the store does notify, React
   rewrites the same styles anyway.

2. endSashDrag() ran after applyShift with no try/finally, so a
   throwing store commit left the sash-drag depth stuck above zero,
   freezing every :root geometry-var write for the rest of the session,
   and kept the window pointer listeners attached to a dead drag. The
   whole teardown (endSashDrag, cursor/select restore, pointer capture,
   listener removal, persistTree) now runs in finally.
2026-10-06 15:15:13 -04:00
Teknium
8a7ed8a492 test(desktop): give the PowerShell marker-judge replay the subprocess test budget
pwsh cold start on a loaded CI runner blew the default 5 s vitest timeout; the
Python twin and the other subprocess-driven tests already use 20 s.
2026-10-06 12:08:49 -07:00
Teknium
d53e9e79df refactor(stt): clear code-health blockers on the salvaged xAI pin
STT_XAI_MODEL is read per call (a module constant froze the launch profile env),
the xAI client-direct form builder moves out of transcribeAudioClientDirect (CC 21),
the silencedetect probe catches only what subprocess raises, and the stt.xai
defaults comment folds into one line so config_defaults.py does not grow.
2026-10-06 12:08:49 -07:00
Teknium
29caf43e1e fix(stt): every model picker lists the runtime catalog; Mistral STT is selectable again
STT_MODEL_CATALOG moves to tools/transcription_common.py and becomes the one table
behind the `hermes tools` picker, the dashboard selects and the auto-correction
sets. Before this, the dashboard and Desktop offered models for only four providers,
Mistral and xAI had no select, DeepInfra had no model field, and Groq still listed
distil-whisper-large-v3-en, which Groq shut down on 2025-08-23. A config naming it
now remaps to whisper-large-v3-turbo.

Mistral Voxtral STT comes back to the picker and the dashboard provider list. It had
been hidden since the 2026-05-12 mistralai quarantine, and the pin is a verified-clean
2.4.8 again. DeepInfra's picker reads its live STT catalog.
2026-10-06 12:08:49 -07:00
teknium1
d1152d797c fix(stt): xAI format=true only with a language; accurate model-default notes
xAI's /v1/stt now answers HTTP 400 "Field 'language' is required when
'format' is true", so every transcription with stt.language "" (auto-detect)
failed on both the server path and the Desktop client-direct path. Send the
inverse-text-normalization flag only when a language is resolved; the model
field is still always named. Comments updated: the server default moved from
grok-voice-transcribe-1.0 to 2.0 between Sep 17 and Sep 21, so the pin is
about determinism and a 1.0 rollback knob, not about escaping a 1.0 default.

Live: auto-detect on origin/main -> HTTP 400; with this change -> transcript
returned for 2.0 and pinned 1.0; language=en still sends format=true.

(cherry picked from commit 1ff4887819a12a5e37e9cbea7f31b5cc6b040857)
2026-10-06 12:08:49 -07:00
Julien Talbot
56b0f3cb55 feat(stt): pin xAI transcription to grok-voice-transcribe-2.0
POST /v1/stt still defaults to grok-voice-transcribe-1.0 when model is
omitted. Hermes logged "grok-stt" and never sent the field, so voice notes
stayed on 1.0. Send stt.xai.model (default 2.0, overridable via STT_XAI_MODEL)
on the gateway and the desktop client-direct path.

(cherry picked from commit c869631946e9a9d84b1738bfdd278f926300da00)
2026-10-06 12:08:49 -07:00
kokhlo
9dcab1e440 fix(desktop): retry readiness on the tick while no verdict exists yet
A gateway flap clears the inference status on every gatewayState change, and
a readiness round that answers with no boolean is dropped on purpose because
a transport fallback is not proof that inference lost its credentials. Both
leave the status null, and the only callers of the readiness legs are three
seams the window may never cross again, so the chip can sit on "Checking
inference" while the backend is healthy and nothing ever retries it.

Let the periodic tick carry the readiness legs while the status is still
null. Once a round publishes an authoritative verdict the tick is status-only
again, so a healthy gateway pays no extra RPCs.
2026-10-06 13:54:07 -05:00
chelsealong
c0fad55154 fix(desktop): refresh project tree when Move-to-project submenu opens
MoveToProjectItems read $projectTree without ever populating it itself.
In the default flat Sessions view, the tree is only warmed by a
background timer (PROJECT_TREE_WARM_MS ~2s) or a visit to grouped
view, so opening "Move to project" before either had run showed "No
other projects" even when named projects with folder roots existed.

Fixes #121193
2026-10-06 14:51:33 -04:00
Brooklyn Nicholson
9ab4a4fdbb fix(desktop): extract recovery-request toast handling into useRecoveryRequestToasts hook 2026-10-06 13:39:19 -05:00
Jakub Wolniewicz
5736ea3782 fix(desktop): decouple background queues from foreground connectivity 2026-10-06 13:39:19 -05:00
Brooklyn Nicholson
3f62bc5342 feat(desktop): full-width butterbar above the statusbar 2026-10-06 13:24:06 -05:00
Brooklyn Nicholson
a10faf9e5a feat(desktop): butterbar store — register notices from anywhere 2026-10-06 13:24:06 -05:00
ethernet
fa2dcb1077 fix(desktop): don't use xwayland on nvidia open kernel module
#127048 detected both modern nvidia open kernel drivers AND propetiary
drivers as the proprietary one, disabling wayland.

this fix looks for the "open kernel module" string in
/proc/driver/nvidia/version, and lets Wayland rendering continue to be
used.
2026-10-06 14:11:29 -04:00
Teknium
d3674513c8 docs(telemetry): the send explainer and Desktop consent window name the fresh-install note
The fresh-install receipt is kept from before the consent answer and counted
after an opt-in, so "data from before you opt in stays on this machine" was
false for it on the CLI send explainer and Desktop's consentWindow line in all
9 locales; only the pre-chat offer named the exception. Both now carry the same
"apart from the fresh-install note (noted on this machine and counted only once
you opt in)" clause. No i18n key changes; the CLI lines stay under 80 columns.
2026-10-06 11:10:43 -07:00
Teknium
4e0eae06e6 docs(telemetry): consent copy names install/update failure reasons and the local install note
CLI setup, the pre-chat consent offer and the Desktop collectedReliability line (all 9 locales,
no new key) now say update and install results carry a fixed-list reason and stage, and that a
fresh install is noted on this machine and counted only once the user opts in. The offer's
'data from before you opt in is never sent' sentence names that one exception.
2026-10-06 11:10:43 -07:00
Austin Pickett
950ea4d51e test(desktop): shellSshDouble tolerates a child that exits before reading stdin
The spawn fixtures run commands that never read stdin, so on a fast Linux
runner the shell can exit before shellExec's stdin write lands and the
write fails with EPIPE. The stream had no error listener, so vitest
reported it as an unhandled error and failed the electron project even
though every test passed. The close verdict is the result; the EPIPE is
noise.
2026-10-06 14:07:07 -04:00
xxxigm
db6a9c65e6 test(desktop): prove the relaunch gate survives a transport that refuses big commands
A double that rejects any command over 4 KB - the shape of an OpenSSH mux
socket that cannot pass descriptors behind a large session request - must still
get a verdict, with the judge on stdin and out of argv. Red on base, where the
inlined program makes the gate raise "Could not prove that the remote Hermes
install is clear for SSH startup."

shellSshDouble forwards stdinData the way the real mux exec does, so the live
spawn and v2-marker fixtures now run `python3 -` against a real python3 and keep
proving the argument positions, lock behaviour and verdicts unchanged.
2026-10-06 14:07:07 -04:00
xxxigm
0630e548eb fix(desktop): ship the SSH relaunch marker gate on stdin, not in the ssh command
assertRemoteInstallUpdateClear inlined the ~7.5 KB marker gate program with
`python3 -c`, making the ssh command ~8.1 KB. ssh.exec rides the POSIX
ControlMaster mux socket, where ssh writes the session request and only then
hands our stdio descriptors over with sendmsg(SCM_RIGHTS); a request that still
fills the socket buffer - 8 KB for a unix-stream socket on macOS - leaves no
room for that descriptor message, so the pass fails with EMSGSIZE:

    mm_send_fd: sendmsg(0): Message too long
    mux_client_request_session: send fds failed

ssh exits 255 and the fail-closed gate reads it as "Could not prove that the
remote Hermes install is clear for SSH startup." on every attempt, so no remote
host connects while nothing is wrong on the remote. The window depends on the
client's socket buffer and the command's exact length, which is why a shorter
probe to the same host can still answer.

Hand the program to `python3 -` on stdin instead. Python compiles the whole
program before running it and the gate never reads stdin, so argv keeps the same
`<marker> [payload] [hermes...]` positions while the command stays a few dozen
bytes however the judge grows.
2026-10-06 14:07:07 -04:00
kshitijk4poor
69a12d2203 refactor(desktop,tui): free-tier challenge wiring lives in topical modules, not the facades
- desktop main.ts: the challenge IPC handler moves to
  electron/challenge-window-ipc.ts (registerChallengeWindowIpc), and
  resolvePortalBaseUrl moves next to the portal session that already
  consumed it (portal-session.ts), so the facade ends below its main
  line count; imports are sorted.
- desktop renderer: free_tier.challenge gets its own family handler
  (gateway-event/free-tier.ts) in the dispatcher table instead of a
  branch in handleLifecycleEvent.
- ui-tui: the challenge presenter and the existing billing step-up link
  presenter move to app/gatewayBrowserLinks.ts, keeping the event
  switch at or below its main size; the presenter test moves to its own
  file.

No behaviour change.
2026-10-06 23:14:10 +05:30