* fix(daemon): a pane no window shows still answers DA1
The client's emulator answers a pane's queries, so a pane with no client
attached answered nothing, and the replay on attach drops replies. Claude
Code ends its OSC 11 theme query with DA1 and waits for it; started in such
a pane it hung on that query and skipped every later theme check. The
daemon now answers DA1 itself while no client is attached.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(daemon): write the DA1 answer after the state lock is released
A pty whose program is not reading its input blocks the write, and
holding the pane's state lock across it would stall everything else
that needs that pane.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
* fix(titlebar): the title bar's lead moves with the traffic lights
#1139 moved the lights from 9pt to 16.5pt in, but the 80pt `TITLE_BAR_LEAD` the tab strip, editor header and search band start at stayed put, so the gap after the zoom button shrank from about 17pt to 9.5pt. On macOS the lead is now derived from the lights: their inset, their 54pt span (three 14pt buttons on a 20pt pitch), and the inset again, 87pt. The strip-width calculation that hard-coded 80 uses the constant, the search-band tests use it instead of 80, and a test checks the lights end, with their inset, before the lead. Off macOS nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(titlebar): TitleBar pads the strip by the lead, not its own 80pt
gpui-component's TitleBar hard-codes an 80pt left padding on macOS, so
raising TITLE_BAR_LEAD to the traffic lights' 87pt left the tab strip at 80
while the strip width, search band and document headers assumed 87. Set the
padding from TITLE_BAR_LEAD so they all start at the same x.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
send_input_to_a_dead_pane_answers_an_error raced the reaper: the session
ending after Exited lets the server drop the dead pane, so the input can
find no pane at all and answer "no such pane". Both are refusals.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The host menu's Disconnect ran RemoteLinks::disconnect inside an update
of the window's own Tty7App. The disconnect detaches the panes of every
window on that machine by reading each window's app, this one included,
which panicked on the double lease. The panic happened inside the
mouse-up platform callback, where it cannot unwind, so the process
aborted and every window went down with it.
Defer the disconnect until the update is over, and add a regression test
that reproduces the original panic without the fix.
With the inline prompt editor live, every click on the input row went to
the editor, including clicks on the prompt the shell drew ahead of it.
A click left of the input start was mapped to the editor's first
character, so double-clicking the branch in ` tty7 git:(main)` ran a
word select over the editor's own (often empty) text and selected
nothing, and its first click had already thrown the caret to the start
of the line.
A click on the prompt now bypasses the editor and goes to the grid's
selection like a click on any other output: double click selects the
word, triple click the line, and the caret stays where it was. Clicks
on the typed text are unchanged.
Co-authored-by: wenlingang <lingang.wen@longbridge.sg>
Copy Raw is a keymap action (default cmd-alt-c) instead of a chord
hard-coded in the key handler, and `raw` threads through
copy_contextual into the one copy path, which now owns the
Claude-pane policy. copy_unwrap.rs depends only on alacritty_terminal:
the boundary rule splits into a Block that tracks fences and tool
output and a pure `sep` with early returns. The fixture tests run
through a real Term at the fixture's width instead of a hand-built
row model.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(fonts): turn stroke thickening off in gpui, not through AppleFontSmoothing
font_thicken: false pinned AppleFontSmoothing to 0 in the process's
argument domain. That never left the process, but AppKit reads the same
preference, so tty7's native menus and alerts lost their smoothing too,
most visibly dark text in light mode. The terminal's own dark text looked
unchanged, since gpui never thickens it in the first place.
The gpui fork now exposes set_glyph_dilation, which reaches its
rasterizer alone; apply_font_thicken calls that and writes no
preference. A test checks that turning thickening off leaves
AppleFontSmoothing alone (red on the old implementation).
Fixes#1119
* test(fonts): keep the font_thicken test out of config_reload_tests' doc comment
* fix(sftp): say why SFTP would not start instead of "Timeout"
Every way an SFTP channel can fail before the protocol starts ended the
same way: the library's 10s request timeout, shown in the Files panel as
"sftp init failed: Timeout". Each has its own cause and fix on the server:
- sshd without a `Subsystem sftp` line answers the request with
CHANNEL_FAILURE. russh's request_subsystem only sends the request, so
the refusal went unread and the INIT was sent into a channel nobody
was listening on.
- A `Subsystem` line naming an sftp-server that is not installed: the
channel is accepted and then closes without a byte.
- A shell startup file that prints on non-interactive logins puts its
text where the version reply belongs; read as a packet length it asks
for gigabytes that never arrive.
The subsystem reply is now read (bounded at 20s), and the first bytes
back are checked to be the header of an SSH_FXP_VERSION packet before
the stream is handed to the library. Each case is reported at once with
what to change on the server; text ahead of SFTP is quoted.
Fixes#1126
* fix(sftp): let the probe's reason win over the library's end-of-stream error
A long-running full-screen program paints its whole screen once, after
?1049h, and only differences from then on. Once the replay ring has
dropped that first paint, a reattaching client is put into the alternate
screen and handed nothing but differences from a screen it never saw,
painted onto an empty one.
On attach and observe, when the pane is in the alternate screen, the ring
no longer carries the switch into it, a program owns the pane and the
pane is alive, step the pty one row away and back 50 ms later. The
program hears a size change and repaints in full. Only the pty moves:
the ring keeps its size and no client or lease sees a Size, and the size
put back is read when it is put back. One nudge per pane per 2 s.
Fixes#1122
A wheel report carried the cell of the last button press: only a click,
or motion for programs that ask for every move, ever updated it. tmux asks
for button events only (1002), so after clicking its window list every
notch over the panes was reported as being over the window list. The
wheel now resolves the cell under the pointer from the grid geometry of
the last frame.
On Linux the IME candidate window only moves when the caret rectangle is
pushed to the platform, and gpui pushes it on focus and around a preedit,
never because the caret moved. A TUI moves the caret on its own, so the
candidate window stayed where the last composition or the focus-in left
it. The pane now invalidates the IME coordinates whenever its anchor
moves while focused.
Fixes#1131
* feat(terminal): copy rejoins the lines Claude Code wrapped itself
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(terminal): keep copy_unwrap fixtures LF on Windows checkouts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(settings): build the join-wrapped row with its Selection & Clipboard siblings
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
gpui only rasterizes subpixel (ClearType) glyphs when the window's
background appearance is Opaque. tty7 never asked for Opaque: with no
backdrop material the window was always Transparent (or the WCA blur),
even at the default opacity of 1.0 where nothing behind it can show
through. Every glyph on Windows therefore fell back to grayscale
antialiasing, regardless of the system ClearType setting.
On Windows, hand the window Opaque whenever the fill is fully opaque
and the resolved backdrop is plain translucency or the WCA blur. DWM
materials (Mica / Mica Alt / Acrylic) are left alone. Lowering the
opacity switches back to Transparent at runtime as before.
Fixes#1120
On a light theme Settings drew its dropdowns, buttons, menus, chips and switch knobs pure white, which stands out on a tinted page such as a cream theme. They are now the page colour lifted a step and made opaque, so they keep the theme's hue; a white theme still gets white controls, and dark themes are unchanged. The change is the `raised` colour in `settings/kit.rs`. `cargo test --bin tty7-app settings` gives 58 passed.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A new or deleted file has one side only, so in the split view half its card was empty. Such a file now draws unified whatever the view, and the others still split. A drag takes its mode from the row it starts on, so selecting and copying in one of these files works in either view. `file_mode` in `diff_list.rs` picks the mode, with tests; `cargo test --bin tty7-app diff` gives 90 passed, 1 ignored.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The lights sat 9pt in from the left edge, the inset of a plain 28pt title bar, which crowds them into the corner of the 48pt bar. AppKit's own toolbar windows inset them as far from the left as from the top: measured on macOS 26, the close button sits 19pt in and 19pt down in a 52pt unified toolbar, and 12pt in and 13pt down in a 40pt compact one. `traffic_light_position` now uses the top gap it already derives (16.5pt) for `x` as well, and its test asserts the two match. The lights still end well inside the tab strip's 80pt lead. `cargo test --bin tty7-app traffic` passes.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ssh): a link whose peer stops answering is noticed and redialled
A connection whose peer went silent with the TCP socket still established
(a stalled path, a host that rebooted under it) was never noticed: nothing
is sent on an idle link, russh waits on a channel confirmation forever, and
the cache kept handing the connection out. The machine read "connected",
the GUI sat on "Reconnecting…" indefinitely, routed CLI calls hung, and
Retry Now dialled nothing new.
- Every connection now gets an SSH keepalive (15s, 3 misses) unless its
profile sets its own interval or turns it off with 0.
- Channel opens wait at most 20s. A connection that does not answer in time
is marked dead, so the next open dials a fresh one; what already runs on
it is left alone. The remote probes' exec requests are bounded too.
- When direct-streamlocal is refused on a connection whose server came from
the per-connection note, the note is dropped and the server proved again
(which starts it) before falling back to a per-link stdio server. The note
outlives a server that exited, or a host that rebooted, while the link
stayed up.
* fix(ssh): a slow forward target or a failed re-prove no longer costs the link
A direct-tcpip open is answered only once the server's own connect to the
target settles, which for a target that drops SYNs outlasts the 20s open
bound on a healthy link. Marking the connection dead there stopped every
forward on it and sent the next pane to dial and authenticate afresh.
direct-tcpip is unbounded again; the keepalive catches a dead peer.
A re-prove of a stale server note that fails now falls back to the
per-link --stdio server instead of failing the link.
* fix(settings): put the password and key passphrase back on the SSH host editor
The rebuilt settings window drew the host form from scratch and left out
the credential rows. Everything behind them survived — the form still
read the keychain on open, counted a typed secret as a change, and
filed it on save — but with no box on screen there was no way to give a
host a password short of connecting and waiting to be asked.
The editor now shows a masked Password row (with a reveal toggle) after
Port, and a Key passphrase row after IdentityFile. Each appears only when
the host's auth method would actually use it, the same split the
connection path makes: Password for Auto/Password, the passphrase for
Auto/PublicKey once IdentityFile names a key that exists on this machine.
* test(settings): the SSH editor's credential rows follow the auth method
The band falls back to the reconnect supervisor's last error, which
neither button cleared: Dismiss left it on the next frame, and Try Again
started a second connect of its own while the stale reason kept hiding
the Connecting state.
- Try Again on a supervisor failure asks the supervisor to dial now
(backoff reset) instead of racing it with another connect.
- While a connect is in flight the band says Connecting, not the old
reason.
- Dismiss silences that exact failure on the supervisor; a different
one is shown again, and the link coming up forgets it.
- A dismissed parked-route notice stays quiet instead of resurfacing as
the error band, and the band offers no Try Again on a parked route.
- A failed Close on an orphan pane shows why on its own line.
The badges were revealed on a bare secondary modifier — ⌘ on macOS, Ctrl
everywhere else — whatever `ActivateTab1`…`ActivateTab9` were bound to. Off
macOS the nine ship on Alt, so holding Alt raised nothing while Ctrl, which
switches no tab there, lit up every number; and a tab row rebound onto Alt, the
case reported, still had to be held with Ctrl to see the hint.
The hold is read from `ActivateTab1` now, so it follows the binding and is
matched exactly. An unbound action, a chord with no modifier, a key that is not
the digit the badge draws, and the tmux preset's `prefix 1` raise no badges at
all rather than promising numbers no key answers.
Co-authored-by: Du Leilei <duleilei@sailyond.com>
A Full sync used to read every machine tab the window was not showing as
a close. Missing is not closed: a window is missing tabs whenever it is
emptied (Restart Server clears it for the whole handoff), a rebuild fails,
or state outlives a visit. Each of those turned into a plain TabClose for
every tab, nothing remembered, shells left running as orphans, and the
now-empty workspace was then removed as disposable. Restart Server did
exactly this to a workspace of sixteen agents: any delta during the
handoff synced the emptied window.
#554, #579, #672 and #716 each fenced off one way of reaching that diff.
This removes the inference instead. close_tab_inner, the one path every
user close goes through, records the intent; a sync closes only tabs with
a recorded intent and holds the rest where they are.
An agent that animates its title (Claude Code turns a circle one quadrant
at a time) sends a new OSC title every few hundred milliseconds. Each one
rebuilt the whole window for a tab that reads the same, because
TabView::label() strips the mark before drawing it.
Two places now read "same title" the way label() does (trimmed, status
mark stripped, via tab_view::same_title_ignoring_status_mark):
- settle_title treats a frame that only changes the mark as a Revert, so
it queues no 400 ms wait and ends in no cx.notify() on the pane.
- on_layout_delta asks the machine mirror, before applying a PaneFacts
delta, whether the pane's record differs from the mirror's by anything
but such a title, and skips apply_layout_delta (whose only effect for
PaneFacts is the notify of the root view) when it does not. The mirror
is still updated and sync_window still runs.
A title whose words change lands as before, so a ticking counter in a
title repaints as often as it did.
Co-authored-by: kalpak <307643502+kalpakprod@users.noreply.github.com>
A program that asks for the kitty keyboard protocol pushes its flags once,
at startup. A long session pushes that push out of the 8 MB replay ring, and
the mode tracker that re-sends what the ring lost only knew DEC private
modes, so a client that reattached rebuilt the pane with no keyboard flags
and sent Escape as a bare ESC to a program still parsing the kitty form.
TerminalModes now keeps the keyboard flag stacks the way alacritty does: one
per screen, swapped on ?1049, reloaded from the top on every push, pop and
swap, with CSI = changing only the flags in effect. They cannot be restored
as the state they ended in, because the pushes and pops the ring still holds
act on the stack relative to where it stood, so the ring folds every evicted
byte into a head tracker and the replay prelude rebuilds the stacks as they
stood at the ring's front.
Back from the background the app now asks the machine whether the
connection still answers (Session::answers, up to 3s) and keeps its
streams when it does, instead of closing the pane and replaying it every
time. A dead connection is closed and dropped so the next dial starts
fresh rather than waiting out the idle timeout on it.
A dropped connection is retried quietly under the screen that is up; the
"Reconnecting" banner comes after 3s, or at once if a keystroke was lost.
iOS asks for the background task grace period on leaving, so a short
lock comes back to the same connection. Android runs a foreground
service (specialUse, with a "Connected to <host>" notification and a
partial wake lock) while a machine or pane screen is open.
* fix(daemon): credit a replay back, so a phone keeps updating on a long pane
An observer was charged for the whole replay ring when it subscribed, but
the writer only credited Output, Image and ClipboardWrite back, never the
Snapshot frames the replay is made of. The charge stayed for good. A full
ring (8 MiB) is the observer's whole budget, so the first byte of live
output pruned it, and with the refusal sender still holding the channel
open nothing told the gateway: the phone showed the screen it opened on
and never updated until the pane was reopened.
Charging and crediting now share one measure, gated_len, and the replay
reports what it queued. A controller's gate starts at its replay too, set
under the state lock instead of reset after it.
* fix(daemon): room for a full replay, and an observer dropped is let go
Crediting the replay was not enough on a busy pane. The observer budget
was exactly RING_CAP, so while a full ring's replay was still being
written, one byte of live output pushed the gate past it and the observer
was dropped. With a 1 ms ticker in the pane this happened on every open.
The budget is now two rings: the replay, and as much again of live output.
A dropped observer also went silent. stream_observer kept a clone of the
channel's sender for refusals, so dropping the pane's sender never closed
the stream; the client sat on a connection nothing would be sent down
again, and a lease it held was never released. Refusals now go through the
pane (tell_observer), the pane holds the only sender, and a dropped
observer is sent a last Error saying why before its stream ends. The phone
reads that Error as a reason to reconnect.
* fix(sidebar): keep button size and position when toggled
* fix(sidebar): use unfilled icons when collapsed
* docs(changelog): link the sidebar fixes to PR 1099
* perf(github): revalidate the GitHub panel's reads by ETag
HttpTransport keeps each plain GET's last answer and ETag and re-asks
with If-None-Match; a 304 replays the stored answer. A signed-in 304 is
not counted against the rate limit, so revalidations and polls stop
draining the hourly allowance the panel shares with gh. get_full is not
cached because its signed attachment URLs expire.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(github): replay the answer that went with the ETag that was sent
The tag and the stored answer were read under two separate locks. If the
cache started over (or the entry was replaced) between them, a 304 found
no answer, fell through to classify() and surfaced as HTTP 304 in the
panel. Read both as one snapshot and replay that on a 304.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
* perf(github): a pull request's detail fetches its independent requests in parallel
The detail sent its seven requests one after another. Requests that do not
depend on each other now overlap on scoped threads: issue and comments, then
the pull request (whose head sha the checks need) alongside reviews and files,
so a pull request's detail is three round trips. `checks` sends check-runs and
status together. Same request count.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(github): hold each request until the overlap shows, not for 30 ms
A fixed 30 ms per request only shows three in flight if the threads all
start within that window, which a loaded CI runner does not promise. Each
request now waits until three are in flight (a second at most), so the
test passes on any machine and still fails when the requests go one by
one.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
A phone listed two "Mac"s with nothing to tell them apart: the installed
tty7 and a dev build on the same computer, each with its own key. The one
whose key nothing answered to any more just sat on "Connecting…".
- The gateway names itself by macOS's computer name ("thomas's Mac mini")
rather than gethostname(), which is whatever the router's DHCP handed out
("Mac.lan") and changes with the network. Elsewhere it is unchanged.
- Pairing codes carry an optional machine fingerprint: a SHA-256 of the
OS's machine id (gethostuuid, /etc/machine-id, MachineGuid), so the id
itself never leaves the machine. Every tty7 on one computer gives the
same one; older apps ignore the field, older desktops omit it.
- The app keeps the fingerprint and the pairing time with each machine.
Pairing a computer it already holds under another key offers to replace
the older entry ("Keep both" for more than one tty7 on it), and machines
that share a name show when each was paired.
The AppImage runtime exports `ARGV0` (the path the image was launched by)
into tty7's environment, and every pane inherited it. zsh treats an
exported `ARGV0` as the argv[0] of each external command it runs, so in a
pane every program was told it was `tty7-….AppImage`. Volta's shims choose
the tool from argv[0], so `node`, `npm` and every global package failed
with `Could not find executable "tty7-….AppImage"`.
Turning shell integration off would not have helped. `env -u
TTY7_SHELL_INTEGRATION node` only worked because `env` execs node with a
correct argv[0], and `env -u` with any variable name works just as well.
Panes now start without `ARGV0`. The removal happens before the configured
`env` block is applied, so an explicit setting there still wins.
Reproduced on Linux (Debian arm64, zsh, Volta 2.0.2) with tty7-server
started under the environment an AppImage launch leaves behind: the shims
fail with the line removed and work with it in place.
Brings back the round keyboard key beside the message box. Only the
mobile half of 41a247a1 is reverted; the pid-column removal from #1095
that rode along in the same squash stays.
The pid sat in a column of its own on every row, beside CPU% and memory.
It reads as a port number at a glance and is not what the list is read
for; the row's name, CPU% and memory already say which process is which.
Held still for half a second, a finger opens the plain-text page the
Select text menu item opens, with the run of non-blanks under it already
selected and brought into view, as a long press selects text anywhere on
the phone. A swipe, a pinch or a lift before then is what it was.
* fix(panel): drop the pid column from Info → Processes
The pid sat in a column of its own on every row, beside CPU% and memory.
It reads as a port number at a glance and is not what the list is read
for; the row's name, CPU% and memory already say which process is which.
* feat(mobile): drop the keyboard key beside the message box
A tap on the pane already brings up the keyboard for typing straight into
the terminal, so the round key that did the same only crowded the compose
row.
`y` in `traffic_light_position()` is the gap between the window's top edge
and the top of the button frame, and AppKit draws those buttons 14 points
tall, so the lights' centre lands seven points below it. The tiles beside them
are centred in the title bar's content box instead — `TITLE_BAR_HEIGHT` less
the hairline `TitleBar` draws along its bottom edge — which is 23.5 in a 48pt
bar. The constant was still 17, the value the 40-point bar needed (13)
rewritten by hand when the bar grew to 48, without that border in it, so the
lights' centre came out at 24: half a point low, one whole device pixel on a
Retina panel.
It comes from the bar's own height now — `(48 - 1 - 14) / 2 = 16.5` today —
and a test holds the lights to the tile line, so the next change of bar height
has somewhere to move with it.
Measured on macOS 26.3 with the style mask gpui uses
(`fullSizeContentView` and a transparent title bar): the standard buttons are
14×14 points with 9 points of padding, in a 32pt title bar.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
read_frame awaits the header and the payload separately and keeps nothing
between them, so dropping it mid-frame discards the bytes it has read and
leaves the stream inside a payload. The next read takes terminal output for
a length: "Frame of 2086478377 bytes exceeds the limit" is `)"]|` off a
pane running jq, seen on a phone over 4G.
Three callers drop it routinely:
- the phone app batches output with timeout(16ms, PaneReader::next), which
fires whenever a large frame is still arriving on a slow link;
- the gateway's control and pane loops race it in select! against outgoing
events and output, so a paste while a pane prints can lose input bytes.
Add FrameReader to tty7-mobile-proto: it keeps a partial frame in a Decoder
and only awaits a single read(), which takes nothing when dropped. Use it in
PaneReader, ControlReceiver and both gateway loops. read_frame stays for the
one-shot reads whose stream is abandoned on a timeout, and says so.
Nine tests failed locally but passed in CI. The `ui::scm::detail` tests
ran `git tag` under a global `tag.gpgsign true`, which then wants a
message; their git helper now ignores global and system config. The
`terminal::history` tests read the real `HISTFILE`, whose newer entries
pushed the tests' own past `MAX_ENTRIES`; shell history isn't loaded
under `cfg(test)`.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test(spawn): the reap-guard test spawns /bin/sleep
With GNU coreutils first on PATH, `sleep` resolves to `gsleep`, whose
executable name the guard then (correctly) refuses, so
`reap_guard_rejects_a_live_process_of_another_executable` failed on
such machines. It now spawns `/bin/sleep`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(singleton): dup the seat descriptor with CLOEXEC
Two singleton tests `dup` the seat's descriptor to simulate a lingering
reference. A plain `dup` isn't close-on-exec, so a child another test
spawns in that window (the reap test's `sleep 30`) inherits it and holds
the flock for its whole life, failing the clear under the full parallel
run. `F_DUPFD_CLOEXEC` keeps the reference in-process.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Five tty7-core tests and two tty7-server conformance checks failed on a
machine with a global `status.showUntrackedFiles=no`: git hid the files
they had just written. PINS (and so pin_repo_config) now carries
`status.showUntrackedFiles=normal`, the worktree tests' temp_repo pins
its repository, and the host conformance suite's git_repo sets the key
itself, since other crates run it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
simple-dns 0.12.0 reads the EDNS OPT record's TTL at the wrong offsets: it
takes the extended RCODE from the low byte, where RFC 6891 puts the DO/Z
flags. A resolver that sets a Z bit -- a Clash-style fake-IP DNS on the
router does -- turns every NOERROR answer into RCODE 16, so iroh resolves
nothing. The gateway never reaches a relay, pairing codes carry none, and
a phone off the LAN cannot connect ("It didn't answer").
Vendor simple-dns 0.12.0 with the offsets fixed and patch it in for both
the desktop and the mobile workspace. tests/edns_z_flag.rs drives iroh's
resolver against a nameserver that sets the flag, from the root package so
CI's default `cargo test` runs it and fails if the patch stops applying.
Tapping the terminal to type into it showed no cursor: xterm reports its
cursor as shown and in place, but leaves it undrawn on the phone. And an
input method's composing text — pinyin before a candidate is picked,
dictation before it is done — lived only in the hidden field typing goes
through, so nothing showed until it was committed, often only on Return.
While the pane has the keys, the app draws the cursor itself, a blinking
block at the pane's cursor cell, with the composing text underlined in
front of it. A program that hides the terminal's cursor to draw its own
(an agent's input box) gets only the composing text, where it lands.
* feat(agents): recognise Muse Code and jcode
Muse Code's `muse` launcher execs a versioned `muse-bin-<version>`
binary, so detection also maps that name to Muse. Muse resumes with
`muse resume <id>` (root options on either side of the subcommand);
jcode with `jcode --resume <id>`.
* feat(agents): install native Empryo and jcode hooks
* feat(agents): add jcode lifecycle hooks
* fix(agents): preserve jcode hooks and correct Empryo removal contract
* feat(agents): install Muse native plugin hooks
* docs(agents): list Empryo, Muse Code and jcode as hook-backed
* fix(i18n): translate Empryo, Muse Code and jcode search keywords
* fix(agents): read jcode hook payload from env, route its reports like Codex, bound muse CLI calls
jcode runs hooks with stdin closed and puts session_id/cwd in
JCODE_HOOK_PAYLOAD, so reports carried no session and resume never
learned an id. Its shared server also runs every session's hooks with
the first client's TTY7_PANE, so reports go through the same
session/directory routing as Codex. muse plugins calls now time out
after 30s and surface stderr. Adds ru strings for the new keys and
reattaches Kimi's doc comment to KIMI_HOOK_EVENTS.
* docs: count Muse Code and jcode in the README agent totals
---------
Co-authored-by: kalpakprod <307643502+kalpakprod@users.noreply.github.com>
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>