Commit graph

17033 commits

Author SHA1 Message Date
Teknium
a2068c668b chore: map matt-strawbridge contributor email for attribution CI 2026-07-22 17:14:36 -07:00
matt-strawbridge
ccb5cb34a5 fix(gateway): suppress pre-API compression chatter 2026-07-22 17:14:36 -07:00
ethernet
3651627d88
fix(desktop): skip bootstrap for explicit Nix backend (#69688)
Treat HERMES_DESKTOP_HERMES as an authoritative deployment override.
This keeps the Nix desktop package on its matching immutable Hermes CLI
instead of falling through to install.sh when a best-effort version probe
fails or times out.
2026-07-22 20:00:48 -04:00
Teknium
c35fe293a1 test(compress): pin manual-compress-allowed-when-auto-disabled on every surface
Surface audit for #64438 consistency: cli.py and acp_adapter had their
gates removed by the salvaged #63630 commit (each with a behavioral
test). The remaining manual-compress surfaces never gated on
compression.enabled — pin that contract so a gate can't regress in:

- gateway/slash_commands.py _handle_compress_command (new behavioral
  test: compression_enabled=False still compresses, force=True).
- tui_gateway/server.py — all three manual routes (session.compress
  RPC, command.dispatch compress branch, slash.exec mirror) plus the
  compute-host slash.compress/session.compress controls converge on
  _compress_session_history; new test pins the helper ignores
  agent.compression_enabled and passes force=True.
2026-07-22 16:59:29 -07:00
Teknium
7580bc66d5 fix(acp): align salvaged /compact wording and test with current /compress command name
Main renamed the ACP slash command from /compact to /compress after #63630
was written; update the salvaged status line, comment, and behavioral test
to dispatch the command that actually exists.
2026-07-22 16:59:29 -07:00
babak
a007ac55c6 fix(compress): allow manual /compress when auto-compaction is disabled [overflow error directs users there]
compression.enabled: false is documented (agent/conversation_loop.py
overflow path) as disabling *automatic* compaction only — the terminal
context-overflow error explicitly tells users to run /compress manually,
and the gateway handler has never gated on the flag. But the classic CLI
(_manual_compress) and the ACP adapter (/compact) refused with
'Compression is disabled', leaving users at a full context with no
manual escape hatch on those surfaces.

Remove the stale gates (they predate the overflow-path design; the CLI
gate came from the original /compress commit's boilerplate) and unify
force=True across all manual-compaction call sites: ACP /compact and the
TUI's _compress_session_history (manual-only helper) now bypass the
summary-failure cooldown exactly like the CLI and gateway already did.
Also reword the ACP /context status line so a disabled-compression agent
no longer implies /compact is unavailable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 16:59:29 -07:00
Teknium
e907ecccef test(search): prove discovery scope and bookend bounding work together on compacted sessions
Integration test for the two compaction layers landing this sweep:
#63144's discovery-scope fix (archived rows surface from the current
session) and #69334's bookend bounding (summary exclusion + content
caps). A single compacted session exercises both: the archived FTS hit
must surface while the compaction handoff at the session tail stays out
of bookend_end and long messages stay capped.
2026-07-22 16:59:09 -07:00
MacroAnarchy
98c0d8b291 fix(search): require compacted=1 not just active=0, scope delegation-under-compression
Addresses @teknium1's second review round on #63144:

1. _is_compacted_message now checks both active=0 AND compacted=1.
   Previously checked active=0 alone, which also matched rewind/undo rows
   (active=0, compacted=0) that must stay hidden.

2. Added _is_compression_ended() — checks only the session's own
   end_reason, not the lineage-wide has_compression_hop flag. This prevents
   delegation children living under a compression continuation from leaking
   through the lineage filter.

3. _discover lineage skip now uses is_ended_session (session-level check)
   instead of has_compression (lineage-level flag).

New tests:
- TestRewindExclusion: rewind rows stay hidden alongside compacted rows
- TestCompressionEndedHelper: session-level end_reason checks
- TestLegacyContinuationPlusDelegation: delegate child excluded while
  compression ancestors surface

78 passed (71 + 7 new).
2026-07-22 16:59:09 -07:00
MacroAnarchy
711f1c2f1a fix(search): surface compaction-archived and compression-parent sessions in discovery
After context compaction, pre-compaction content was invisible to
session_search — a memory black hole. The _discover() skip logic
filtered both same-session and same-lineage hits unconditionally,
without distinguishing compression-summarised content (gone from live
context) from delegation children (still visible to the parent agent).

Reworked _resolve_to_parent to return (root_id, has_compression_hop),
checking end_reason='compression' on every hop during the same
db.get_session() traversal — zero extra queries.

_discover() now has three compression-aware paths:
- In-place compaction: FTS hits on active=0 (compacted=1) rows pass
  through even when raw_sid == current_session_id
- Legacy rotation: lineage hits pass through when has_compression_hop
  is true on either side of the chain
- Delegation children: still excluded (no compression edge)

18 new tests covering all three scenarios + unit tests for the helpers.

Addresses Teknium's review feedback on #6256.
Closes #13840, #13841.
2026-07-22 16:59:09 -07:00
Yingliang Zhang
8c745314b9 fix(desktop): synchronize context usage and compaction status 2026-07-22 16:58:58 -07:00
Teknium
1f4eaec88a test(cron): assert no-rotation tip resolution is a finalize no-op
With compression.in_place defaulting True the cron session id never
rotates; get_compression_tip returns the input id. Pin that the
salvaged #67188 fix titles/ends the ORIGINAL cron session in that path
(and for falsy tip returns), i.e. zero behavior change when no
compression rotation happened. Also maps colingreig's contributor
email for attribution CI.
2026-07-22 16:58:47 -07:00
Colin Greig
7fa795a674 fix(cron): finalize compressed session tips (86e2darn2) 2026-07-22 16:58:47 -07:00
Teknium
50026fbaa1 test(compression): pin classify_summary_content agreement with summary predicates on live emissions
Hardening for the #59114 salvage: generate real standalone and merged
handoffs with the current compressor and assert classify_summary_content
agrees with _is_context_summary_content and is_compaction_summary_message
on every emitted shape (behavior contract, not a format snapshot), incl.
the flag-stripped DB-reload copy. Also adds the contributor email mapping
for @israellot.
2026-07-22 16:58:35 -07:00
Israel Lot
24e4c6fbf6 fix(acp): flag replayed compaction summaries via _meta
A context-compaction handoff is persisted as an ordinary history message
but is not a real turn. The ACP history replay streamed it as a bare
user/agent message chunk, dropping the in-process _compressed_summary
marker, so ACP frontends (editors, vscode-hermes) rendered the entire
handoff as a regular message.

Tag replayed summary chunks under _meta.hermes (ACP's extensibility
channel), covering all three persistence shapes the compressor emits:

- standalone role="user" handoff -> compactionSummary: true
- standalone role="assistant" handoff (alternation-driven role pick)
  -> compactionSummary: true
- merge-into-tail message (preserved tail content + appended summary)
  -> containsCompactionSummary: true, a distinct key so clients that
  collapse standalone summaries cannot hide the preserved real content

Detection honors the in-process metadata flag and falls back to a new
ContextCompressor.classify_summary_content() content classifier
(standalone/merged/None), so it also works for a DB-reloaded session
that lost the in-memory flag. _is_context_summary_content is now a thin
wrapper over the classifier, keeping existing callers unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 16:58:35 -07:00
ethernet
93c97073d8
fix(desktop): prevent stale optimistic tails after compression (#69682) 2026-07-22 23:32:20 +00:00
ethernet
6283a33a50
fix(desktop): show steer glyph in busy composer (#69612)
* fix(desktop): show steer glyph in busy composer

Restore the steering-wheel glyph when a typed draft redirects a live turn.
Add a real Electron E2E regression test for stop, steer, attachment queueing,
and a paused queue after Stop.

* feat(desktop): add queue action beside steer

Restore an explicit queue action in the former steering slot while a typed
correction is ready. Keep the primary steering-wheel action for redirecting
the active turn.

* fix(desktop): retain dictation beside queued drafts

Keep the dictation microphone visible when a typed busy draft exposes the
separate queue action. Cover the combined controls in the real Electron flow.

* fix(desktop): place queue beside the busy action

Keep the Queue message control after the read-aloud toggle and directly before
the shared Stop/Steer action. Cover the rendered control order in Electron E2E.
2026-07-22 23:29:38 +00:00
ethernet
433673067e
ci: surface E2E screenshots in review comment (#69631)
* ci: surface E2E screenshots in review comment

* ci: mark completed review commits in past tense

* ci: surface approved sensitive-file reviews

* ci: link sensitive files to reviewed changes

* ci: stage desktop E2E visual evidence

Track screenshots newly introduced against main and package visual diffs for a trusted publisher.

* fix(ci): pass E2E evidence output paths

Supply the manifest and staging-directory arguments required by the screenshot status helper.

* fix(ci): download the OSV SARIF artifact

Match the artifact name and result filename emitted by the pinned upstream reusable workflow.
2026-07-22 23:19:53 +00:00
brooklyn!
8d6e045b8f
Merge pull request #69671 from NousResearch/bb/skin-owns-polarity
fix(ui-tui): a skin that authors a background owns its polarity
2026-07-22 18:17:36 -05:00
ethernet
55759cb273
fix(desktop): refresh composer branch after worktree creation (#69657)
* fix(desktop): refresh composer branch after worktree creation

Route new worktree sessions through the shared workspace target handoff so
composer git status follows the created worktree instead of remaining on the
main checkout. Add an Electron E2E regression test for Ctrl+Shift+B.

* fix(desktop): fixme flaky test
2026-07-22 23:13:23 +00:00
brooklyn!
9024835bf2
Merge pull request #69602 from NousResearch/bb/voice-interrupt-note
feat(voice): tell the model when the user interrupts its spoken reply
2026-07-22 18:13:19 -05:00
brooklyn!
0ac07fdafd
Merge pull request #69511 from NousResearch/bb/voice-streaming
feat(voice): streaming, conversational TTS with barge-in across all surfaces
2026-07-22 18:13:08 -05:00
Brooklyn Nicholson
e762ea1741 fix(ui-tui): a skin that authors a background owns its polarity
The theme engine picked light/dark adaptation from the HOST terminal
(detectLightMode) even when the skin authors its own background — which
the TUI then paints onto the terminal via OSC-11. On a light-mode Apple
Terminal without truecolor, a pure-black skin (e.g. Bloomberg) got its
foregrounds ansi256-bucketed *for a light background that no longer
exists*: theme.color.text became 'ansi256(214)', which also fails the
OSC-10 hex gate, so the terminal default fg stayed the light profile's
near-black — markdown body text rendered black-on-black.

fromSkin now resolves polarity from the skin's authored background when
present (skinIsLight), uses that background as the reference canvas for
the derived tone ladder and contrast adaptation, and only falls back to
host detection for skins without a background (they render on the
terminal's own surface). The paired light_colors/dark_colors pick in
themeForSkin follows the same rule.
2026-07-22 18:11:27 -05:00
hermes-seaeye[bot]
54aaff142e
fmt(js): npm run fix on merge (#69669)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-22 23:05:44 +00:00
ethernet
68abf0b33d
test(desktop): add E2E coverage for session lifecycle (#69580)
* test(desktop): e2e test for interim assistant message preservation (#65919)

Adds a Playwright E2E test that reproduces the fix from PR #65919 across
all three layers (agent core → tui_gateway → desktop renderer). The mock
inference server is upgraded with a multi-turn scripted response that
exercises several interleaved patterns:

  1. text + tool_call  → should produce an interim message
  2. text + tool_call  → another interim message
  3. no text + tool_call → NO interim (no visible text alongside tools)
  4. text + tool_call  → another interim message
  5. final answer (stop) → message.complete, different from all interims

Two describe blocks exercise display.interim_assistant_messages both on
(default) and off:
  - ON:  all interim texts + the final answer visible in the transcript
  - OFF: only the final answer visible, all interim texts wiped

Also fixes a footgun: test:e2e now runs `npm run build` as a pretest
hook so the renderer dist/ is always fresh. Previously, running
`npx playwright test` locally would silently load a stale dist/ that
predated renderer fixes — the python backend ran from source (had the
fix) but the renderer was frozen in an old bundle. CI already built
fresh, so the explicit build step there is removed to avoid duplication.

* test(desktop): e2e sidebar states — background dot, subagent, cross-session

Add sidebar-states.spec.ts with three E2E tests exercising the desktop
sidebar's session dot states driven by real gateway events:

1. Background process dot appears during a terminal(background=true)
   call and disappears after auto-dismiss; subagent (delegate_task)
   runs concurrently; final answer is visible in the transcript.

2. Background dot remains visible while a subagent runs concurrently
   (longer sleep 5 background process so the dot is catchable).

3. Cross-session dot transition: start a turn with a background process,
   wait for the turn to complete, open a new session, then verify the
   original session's dot transitions from 'background running' to
   'finished — unread' when the background process exits.

The mock server gains SIDEBAR_SCRIPT and SIDEBAR_CROSS_SCRIPT trigger
keywords that return tool_calls for terminal(background=true) and
delegate_task — the agent executes these for real (real background
process, real subagent), so the tests assert against genuine gateway
events rather than mocked UI state.

Verified: 3 passed (1.2m) under cage headless wlroots.

* test(desktop): e2e tests for tile-unread bug (tab passes, split fails)

Two scenarios for the tile-unread bug where a session that finishes
while visible on-screen gets the green 'finished unread' dot even
though the user is looking right at it.

The unread check in handleTransition (session-states.ts:174) only
compares against $selectedStoredSessionId and ignores $sessionTiles,
so a session visible in a tile gets marked unread even though it's
on screen.

1. TAB (hidden, PASSES): ⌃-click opens the session as a stacked tab
   that is NOT visible on screen. The unread dot IS correct here —
   the user isn't looking at it.

2. SPLIT (visible, FAILS): drag the session row to the workspace's
   right edge to create a side-by-side split tile. Both sessions are
   visible on screen. The unread dot is WRONG — the session is visible
   in the split tile, so it should not be marked 'unread'. This test
   is RED until the fix lands.

Also adds explicit page.screenshot() calls at key assertion points in
sidebar-states.spec.ts so the trace viewer has full-res captures of the
sidebar dot states during the test.

* test(desktop): cover compression and queued stop lifecycle

Add real desktop E2E coverage for session compression continuation and
queue parking after an explicit Stop. Extend the mock server with a
blocking scripted turn and submitted-prompt assertions.

* test(desktop): cover busy composer submit routing

Replace the invalid queued-stop E2E scenario: plain text redirects a busy
turn rather than entering the queue. Add focused submit-routing coverage for
plain text, slash commands, attachments, explicit Stop, and idle submission.
2026-07-22 22:56:13 +00:00
Brooklyn Nicholson
177a57f70c feat(voice): desktop flags interrupted submits
markVoicePlaybackInterrupted() / takeVoicePlaybackInterrupted() mirror
the backend latch in the renderer (the barge happens client-side, where
the audio plays). VAD barges and typing over playback mark it; the next
prompt.submit carries interrupted:true, which the TUI gateway latches
into the model note.
2026-07-22 17:53:06 -05:00
Brooklyn Nicholson
05b3637d8b feat(voice): CLI + TUI tell the model when its spoken reply was cut
CLI: the VAD monitor's playback cut and the record-key interrupt both
mark the latch; the chat path prepends the note via the existing
_prepend_note_to_message channel. TUI: _tts_stream_stop() grows a
user_barge flag (False for /voice off — a mode change isn't an
interruption; no-op when speech already finished), the VAD monitor
marks on cut, prompt.submit accepts interrupted:true from clients, and
_run_prompt_submit pops the latch into the run message.
2026-07-22 17:53:06 -05:00
Brooklyn Nicholson
393c100a92 feat(voice): speech-interrupted latch in the TTS streaming core
mark_speech_interrupted() / take_speech_interrupted(): a one-shot,
TTL'd (120s) latch plus SPEECH_INTERRUPTED_NOTE. Barge-in paths mark
it when they cut live speech; the next turn's submit path pops it and
prepends the note to the model-bound message — API-call local, never
persisted, so history and prompt caching are untouched.
2026-07-22 17:53:06 -05:00
Brooklyn Nicholson
83456d0bc6 docs(voice): streaming TTS + barge-in across surfaces 2026-07-22 17:47:25 -05:00
Brooklyn Nicholson
93e9061f15 feat(voice): desktop speech-stream sessions with barge-in capture
/api/audio/speak-stream WebSocket: one socket + one Web Audio clock per
reply. The renderer feeds raw LLM deltas as they arrive; the server
cuts sentences with the shared chunker and streams int16 PCM back while
generation continues — speech overlaps generation with no per-sentence
connection or synthesis gaps. Falls back to the POST data-URL path for
old backends / non-chunked providers.

Barge-in runs a MediaRecorder on the monitor's stream the whole time
playback is live (rotated while quiet to bound pre-roll); talking over
the agent cuts playback and the complete utterance goes straight to
transcription and submit. /api/audio/transcribe returns 200/"" for
no-speech results so quiet turns re-listen instead of toasting a 400.
2026-07-22 17:47:25 -05:00
Brooklyn Nicholson
68e1fedd2d feat(voice): stream turn deltas through the TUI gateway, with barge-in
Turn deltas feed a per-turn TTS pipeline; the post-complete speak_text
call survives only as a fallback. session.interrupt, /voice toggles,
and new turns cut in-flight speech. VAD barge-in emits
voice.interrupted at detection, then the captured interruption goes out
as voice.transcript — the same event the TUI already submits as a
spoken turn.
2026-07-22 17:47:15 -05:00
Brooklyn Nicholson
b135a8badd feat(voice): stream any provider in the CLI, with barge-in capture
The streaming gate broadens from ElevenLabs-only to any provider that
passes check_tts_requirements(). In continuous voice mode a mic monitor
runs during playback: talking over the agent cuts TTS at detection
while the monitor keeps recording, then the captured interruption is
transcribed and queued as the next turn (process_loop's auto-restart
stands down while the capture owns the mic). New voice.barge_in config
key, default true.
2026-07-22 17:47:15 -05:00
Brooklyn Nicholson
8ce18d2557 feat(voice): barge-in detector with pre-roll utterance capture
listen_for_speech(): sustained-RMS speech detection with the noise
floor calibrated against audible playback (speaker bleed doesn't
self-trigger). With capture=True it keeps a rolling pre-roll buffer and
records through to a silence endpoint, so the interruption is
transcribed from its first syllable — detection alone loses the opening
words to the detector's sustain window plus the mic re-open.
transcribe_recording() maps no_speech provider results to a successful
empty transcript (silence, not an error).
2026-07-22 17:47:05 -05:00
Brooklyn Nicholson
d8f30b1df7 fix(stt): flag empty transcripts as no_speech
An STT provider that hears no words is reporting silence, not failing.
ElevenLabs/xAI empty-transcript errors now carry no_speech so live
voice loops can re-listen quietly instead of surfacing an error on
every pause.
2026-07-22 17:47:04 -05:00
Brooklyn Nicholson
8da98ce082 feat(tts): route the speaker pipeline through the streaming core
stream_tts_to_speaker() drops its hardcoded ElevenLabs client for
resolve_streaming_provider() + SentenceChunker, so any provider speaks
sentence-by-sentence while the model is still generating. Markdown
stripping also drops emoji — providers stall on them or read them out
loud.
2026-07-22 17:46:55 -05:00
Brooklyn Nicholson
592effcb2a feat(tts): provider-agnostic streaming core with a shared sentence chunker
StreamingTTSProvider ABC + registry + resolve_streaming_provider() —
ElevenLabs (pcm_24000) and OpenAI (response_format=pcm) stream chunked
PCM; every other provider keeps its configured voice and falls back to
per-sentence sync synthesis. SentenceChunker is the one incremental
cutter every surface shares: sentence-boundary cuts on the delta
stream, <think> blocks stripped even when split across deltas, short
fragments merged forward so they never stall as tiny clips.
2026-07-22 17:46:55 -05:00
hermes-seaeye[bot]
15dc65eeda
fmt(js): npm run fix on merge (#69651)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-22 22:14:06 +00:00
ethernet
73c7f68456
fix(desktop): avoid duplicate inflight user rows on resume (#69649)
Keep a live session projection from adding its user turn when the latest
persisted row already represents that same inflight prompt. Add real Electron
coverage for fast and cold resume with idle and background-inference sessions.
2026-07-22 18:07:12 -04:00
hermes-seaeye[bot]
1b8e34e945
fmt(js): npm run fix on merge (#69644)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-22 21:56:16 +00:00
ethernet
7055385379
fix(desktop): warm-route resume jitter from double setMessages (#69635)
* test(desktop): e2e for warm-route resume render jitter

Pre-seeds a 32-message session into state.db, boots the app, does a
cold resume (populates warm cache), navigates away, then clicks back
(warm resume). A MutationObserver + innerHTML-length poll detects
whether the transcript is re-rendered after the initial warm-cache
paint — the jitter bug where syncSessionStateToView fires twice
(warm cache paint, then session.activate RPC reconcile).

* fix(desktop): warm-route resume jitter from double setMessages

The warm resume path in resumeSession() calls syncSessionStateToView
twice: once for the warm cache paint, then again after the
session.activate RPC reconciles messages. The second call created new
message objects via toChatMessages (different references, same content),
and flushPendingViewState's sameMessageList guard used reference
equality per slot — so it always failed and setMessages fired a second
time, causing a visible transcript re-render.

Replace sameMessageList (reference equality) with chatMessageArraysEquivalent
(deep content comparison: id, role, parts, pending, error, etc.). This
was already used by the cold path's fast-path guard; the flush guard
was the last holdout using shallow reference equality.

Also updates the e2e test to use textContent polling on the first
message element (instead of innerHTML on the full viewport) to avoid
false positives from metadata-only DOM changes.

* test(desktop): e2e for warm resume after background inference

Extract the render counter (MutationObserver + text-content poll) and
assertion into reusable helpers. Add a second test that sends a message,
waits for the mock response to complete, navigates away, then warm-
resumes — verifying the warm cache already has the completed turn from
message.complete events and no second paint occurs.
2026-07-22 21:43:33 +00:00
brooklyn!
e0d62b509e
Merge pull request #69632 from NousResearch/bb/skin-terminal-default-fg
fix(ui-tui): a skin owns the terminal's DEFAULT foreground (OSC-10) — kills the invisible-text class
2026-07-22 16:34:48 -05:00
Brooklyn Nicholson
1f652214df fix(ui-tui): skin owns BOTH terminal defaults — OSC-10 foreground beside the OSC-11 background
The input fix's sibling, hit immediately after: the composer was themed but
AGENT text went black-on-black the same way. Root cause is the class, not
the call site — markdown body, borders, and every token rendered without an
explicit color falls back to the terminal's DEFAULT foreground, which
belongs to the HOST profile's polarity, not the skin's. A dark skin on a
light terminal repaints the backdrop via OSC-11 while thousands of
default-fg cells stay near-black.

Chasing every <Text> is unwinnable. Instead own the default itself: when a
skin authors a background (the existing opt-in), paint the default
foreground from the resolved theme's text color via OSC-10. Every unthemed
token — present and future — re-bases onto the skin atomically, exactly
like the background.

terminalModes: the OSC-11 slot generalizes to defaultColorSlot(10|11) —
same paint/clear/exit-restore contract, tracked per slot, so a skinless
session still never touches the terminal. reapplyTheme repaints the fg too:
polarity flips swap paired palettes, moving the text tone while the
background stays.

Tests: slot contract runs table-driven over both OSC codes; handler test
pins the invariant (default fg == theme text; dropping the background
releases both defaults). Suite 1344✓, typecheck/lint/prettier clean.
2026-07-22 16:28:57 -05:00
ethernet
2244be2282
feat(devshell): add terminal UI capture tools (#69629) 2026-07-22 17:28:21 -04:00
brooklyn!
d6080d4cf7
Merge pull request #69616 from NousResearch/bb/tui-input-theme-color
fix(ui-tui): input text goes invisible when a live skin flips the terminal's polarity
2026-07-22 16:07:42 -05:00
brooklyn!
0033cffcfd
Merge pull request #69613 from NousResearch/bb/fix-first-response-nudge
fix(desktop): keep first response layout stable
2026-07-22 16:04:01 -05:00
Brooklyn Nicholson
2a3e3157d2 fix(ui-tui): themed input text — typed text tracked the HOST terminal's fg, not the skin's
Live-repaint's composer gap: flip a light terminal to a dark skin and the
input goes black-on-black. The placeholder was already explicit truecolor
(theme muted), but TYPED text rendered with no color at all — the terminal's
default foreground — in both paint paths:

- the Ink render (<Text wrap="wrap">{rendered}</Text>, no color), and
- the fast-echo bypass, which writes raw cells straight to stdout.

The skin owns the background (OSC-11) but the default fg still belongs to
the host terminal's polarity, so any skin/terminal polarity mismatch made
input invisible. Every other transcript line already paints
theme.color.text (the completed inputBuf rows directly above the composer).

Give TextInput a color prop and paint both paths with it: the Ink <Text>
(chalk re-opens the outer color after the placeholder chips' embedded [39m
closes; INV cursor/selection cells never touch fg) and the fast-echo write
via colorizeEcho — same explicit-truecolor-only rule as colorizeHint, so
the bypass cell can't flash terminal-default before the next frame. All six
TextInput sites (composer, prompts, masked, billing ×2, session switcher)
pass theme text; no color ⇒ passthrough, unthemed inputs keep the terminal
default.

Tests: colorizeEcho SGR wrap + passthrough contracts; full ui-tui suite
1338✓; typecheck clean.
2026-07-22 15:56:39 -05:00
Brooklyn Nicholson
b5d82319d8 fix(desktop): keep first response layout stable 2026-07-22 15:53:27 -05:00
ethernet
d84e11af4d
rip out brew + pip/PyPI wheel support (#68217)
Removes Homebrew and PyPI wheel/sdist as Hermes distribution paths while
preserving the supported source, Docker, and Nix workflows.

Changes:
- Removes the Homebrew formula, PyPI publish workflow, sdist manifest
  (MANIFEST.in), and wheel/sdist release-attachment logic from scripts/release.py.
- Keeps setuptools metadata and entry points required by editable installs
  and Docker/Nix builds, but adds a setup.py guard that rejects wheel/sdist
  builds outside a sealed Nix derivation (HERMES_NIX_BUILD=1).
- Removes pip/Homebrew install detection, PyPI update checks, the pip
  self-update path, the deprecation-banner state, the postinstall subcommand,
  wheel data-directory fallbacks in agent/i18n.py and hermes_constants.py,
  and the ACP Registry manifest/version-lockstep release logic.
- Adds /nix/store/ path detection so `nix run` / `nix profile install`
  installs (which don't set HERMES_MANAGED) are correctly identified as
  "nix" rather than falling through to "git"/"unknown".
- Retired install-method values ("pip", "homebrew") in existing
  .install_method stamps (both code-scoped and home-scoped) are ignored by
  the allowlist reader and fall through to "unknown" instead of resurrecting
  a retired enum value.
- Updates Nix packaging to ship bare runtime data (locales, optional-mcps)
  through store symlinks and wrapper env vars instead of wheel data-files.
- Removes the ACP Registry manifest/icon and their version-lockstep tests.
- Deletes or rewrites packaging, pip-update, Homebrew, and ACP Registry
  tests; adds parametrized coverage for the packaging build guard covering
  BOTH sdist and wheel paths (the guards live in separate cmdclass entries
  — a passing sdist test proves nothing about the wheel path).
- Updates installation/platform documentation and related user-facing copy.
- Adjusts the supply-chain scan so deleted install-hook files do not trigger
  a finding, while additions or modifications still require the existing
  ci-reviewed label gate.

Supported installation paths (unchanged):
- git installer (install.sh)
- Docker
- Nix/NixOS
- editable development installs (uv sync, uv pip install -e ., pip install -e .)
2026-07-22 16:51:01 -04:00
rob-maron
0ee05d72f2
Nous portal model pricing (#69579)
* nous portal model pricing

* update top message
2026-07-22 16:47:06 -04:00
ethernet
681abcc897
test(desktop): cover submit drift through e2e (#69599)
Exercise the full Electron, gateway, and mock-provider submit path while
same-chat route query tokens churn during session creation. Assert the mock
provider receives the prompt and its streamed response reaches the transcript.
2026-07-22 16:44:54 -04:00
ethernet
41e00dcf87
fix(desktop): salvage /compress cluster — session.compress RPC + dedicated RPC routing (#68229)
* fix(desktop): route /compress through session.compress RPC with transcript replacement

Salvages #44462, #53755, and #68218 into a single canonical fix for the
desktop /compress cluster.

The desktop routed /compress through slash.exec, which sends it to the
_SlashWorker subprocess. Compressing a large session outlives both the
desktop's 30s WS timeout and the worker's 45s pipe timeout — the client
gives up, runExec's blanket catch swallows the error, and command.dispatch
surfaces a misleading "not a quick/plugin/skill command: compress" (#44456).
Even when compression succeeded via the _mirror_slash_side_effects path,
the desktop never received the post-compress message list, so summarized
bubbles stayed on screen forever — /compress looked like a no-op.

This change routes /compress to the dedicated session.compress RPC (the TUI's
path), combining the best of all three PRs:

- 120s client timeout matching the TUI's HERMES_TUI_RPC_TIMEOUT_MS (#44462)
- Transcript replacement from the response `messages` via toChatMessages,
  the same converter session.resume uses (#68218, teknium1 review on #44462)
- Session-isolation guard: updateSessionState only publishes for the active
  runtime, so a late result after a session switch can't clobber the
  foreground transcript (#53755, teknium1 review on #53755)
- Coalescing: dedup concurrent compress requests per session (#53755)
- Progress toast ("compressing context...") outside the transcript (#53755)
- Error unmasking in runExec: when slash.exec fails and command.dispatch only
  adds "not a quick/plugin/skill command" routing noise, surface the original
  worker error instead (#44462)
- /compact alias + focus_topic forwarding

Co-authored-by: AlliDev <AIalliAI@users.noreply.github.com>
Co-authored-by: PinkEVO <PINKIIILQWQ@users.noreply.github.com>

* feat(desktop): route slash commands with dedicated RPCs to those RPCs

Salvages #63513 — introduces a new `rpc` kind on DesktopCommandSurface so
commands with a first-class gateway @method handler bypass slash.exec /
command.dispatch entirely, and a `renderRpcResult` utility that shapes
each RPC's structured reply into readable transcript text.

Migrates 6 commands from exec() to rpc(...):
  /agents → agents.list
  /save   → session.save
  /status → session.status
  /steer  → session.steer
  /stop   → process.stop
  /usage  → session.usage

/compress stays as action('compress') — it needs transcript replacement
from the response `messages`, which the generic rpc path can't do (per
teknium1 review on #44462/#63513).

Also includes the json-rpc-gateway timeout message improvement: the error
now includes the configured timeout duration ("request timed out after 120s:
session.compress") so a user can tell whether the default 30s fired or a
per-call override.

Co-authored-by: Jelvin <SmallNew2003@users.noreply.github.com>

* fix(desktop): preserve provider choice during config initialization

* fix(desktop): preserve slash command and host compression semantics

Keep commands whose CLI behavior exceeds their current RPC contracts on slash.exec.
Propagate the full compression timeout through compute-host control, return structured
host compression outcomes with metadata, and retain successful compression feedback
in the desktop transcript.

Add regressions for timeout forwarding, host aborts and metadata sync, structured host
control responses, command routing parity, and numeric stop counts.

* fix(desktop): harden compression state handling

Preserve the invoking stored-session binding for delayed compression results,
normalize replacement histories, and serialize provider selection. Stabilize
gateway platform tests and guard the desktop Git facade during renderer teardown.

---------

Co-authored-by: AlliDev <AIalliAI@users.noreply.github.com>
Co-authored-by: PinkEVO <PINKIIILQWQ@users.noreply.github.com>
Co-authored-by: Jelvin <SmallNew2003@users.noreply.github.com>
2026-07-22 16:40:06 -04:00