Skip to content

feat(hermes): choose which Telegram thread a Hermes pane follows - #1521

Merged
Juliusolsson05 merged 6 commits into
batch/hermes-gaps-2026-10-15from
feat/hermes-thread-picker
Oct 15, 2026
Merged

Juliusolsson05 merged 6 commits into
batch/hermes-gaps-2026-10-15from
feat/hermes-thread-picker

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

Problem

A new Hermes pane follows the connection's plain Telegram DM (the default lane, #1496). DM topics and groups are separate lanes with their own routing keys. The recorded T8 has agent:main:telegram:dm:111 and …:111:7. Until now there was no way to point a pane at a different thread, or to turn a thread pane into a desktop-only chat.

What it does

  • Change Telegram Thread… (command palette, Hermes panes with a connection only) opens a picker. It lists every messaging thread on the pane's server, most recent first, plus Separate chat, with the current one marked.
  • Choosing a thread reopens the pane on it, the same way Reload Agent reopens a pane. The choice is stored in SessionMeta.hermesLaneKey and survives Reload Agent.
  • Main: HermesConnectionService.listLanes(id) → HermesLaneListing (it never throws; a failure comes back as a value). IPC hermes:list-lanes, preload hermesListLanes. Routing keys and display names reach the renderer; stored conversation ids (headId) stay in main.

Design notes

  • replaceSession takes hermesLaneKey?: string | null. A string follows that thread, null makes a separate chat, and omitting it keeps the pane's current thread. A plain pass-through did not work: the carry re-applied the OLD pane's key after the caller's options, so the picker reopened the thread the user was leaving.
  • replaceSession now spawns nextKind, the kind it records the successor as. Before, a caller that omitted kind launched the default provider (Claude) into a pane whose metadata still said Hermes. Every caller happened to pass kind, so the bug was latent.
  • Labels lead with which thread, not whose (Direct chat topic 7 · Tester). In T8 both lanes carry the same display name.
  • A followed thread missing from the listing (it fell out of the 100 most recent rows) is still shown as the current row, so the pane never looks unattached.

Tests

All tests use real recorded data: the hermes-headless corpus T8, v0.22.0.

  • hermesLaneReplacement.renderer.test.tsx: without a choice, the pane keeps its thread; a chosen thread wins over the carry; null gives a separate chat and never looks the default thread up. Fail-first verified: reverting the override turns 2 tests red, and reverting the nextKind spawn turns all 3 red.
  • hermesThreadChoices.test.ts: the two "Tester" lanes get distinct labels, the current row is marked, Separate chat is current when the pane follows no thread, and a followed thread missing from the listing is kept. The missing-thread case uses a mechanical mutation of T8, labelled as such.
  • hermesCredentials.test.ts: listLanes on the recorded REST body has no headId; a 502 comes back as {ok:false} and does not raise the "new pane opened as a separate chat" flag.
  • npm run typecheck is clean.

Verification boundary

The app was not launched (standing rule). The picker's rendering and a live thread swap against a real Hermes are not exercised here.

Fixes #1501

🤖 Generated with Claude Code

Juliusolsson05 and others added 5 commits October 14, 2026 16:46
Refs #1501

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change Telegram Thread… lists every messaging thread on the pane's server
(plain DM, DM topics, groups) plus a separate chat, and reopens the pane on
the choice. replaceSession gains a three-state hermesLaneKey because its
carry re-applied the old pane's key after the caller's options, and now
spawns the kind it records instead of defaulting a kind-less call to Claude.

Refs #1501

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1521: the picker closes before the swap runs, so a rejected or
uncommitted replaceSession was an unhandled rejection or silence and the pane
quietly stayed on the old thread. It now toasts a fixed sentence on the pane.
Also: the current-thread row no longer claims the thread is gone while the
listing is loading or failed, and the picker closes if its pane disappears.

Refs #1501

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #1501

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Final disposition (merging via batch/hermes-gaps-2026-10-15, owner-approved 2026-10-15). Head d5157209e. One review round (Claude + Codex): 1 medium and 3 low findings, all fixed in b66360488 (a failed or uncommitted swap now shows a pane toast; the current-thread row reads correctly while the listing loads; the picker closes if its pane disappears; a double-submit test). The first CI run failed only because the command-catalog characterization test needed the new command; fixed in d5157209e. Gate: the batch PR's CI against current main, which this head's own CI run also feeds.

@Juliusolsson05
Juliusolsson05 changed the base branch from main to batch/hermes-gaps-2026-10-15 October 15, 2026 00:35
…to feat/hermes-thread-picker

# Conflicts:
#	src/main/hermes/service.ts
@Juliusolsson05
Juliusolsson05 merged commit 02b2c6c into batch/hermes-gaps-2026-10-15 Oct 15, 2026
@Juliusolsson05
Juliusolsson05 deleted the feat/hermes-thread-picker branch October 15, 2026 00:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(hermes): choose which Telegram thread (DM topic or group) a Hermes pane follows

1 participant