Skip to content

Releases: m-morino/saikai

saikai 0.6.2 — why it died, and the family it belonged to

Choose a tag to compare

@m-morino m-morino released this 31 Jul 12:13

saikai closed itself mid-session and left the terminal unusable, and nothing on disk
said why. This release is the answer to that: the cause, the two reasons it was
invisible, and — from a first-principles audit of every path by which saikai can
suddenly die — the rest of the family it belonged to.

Fixed

  • A watchdog false positive can no longer kill a healthy session. The
    terminal-death watchdog reaps saikai's own tree when it can no longer find a shell
    ancestor, and "the walk found nothing" and "the walk did not run" were the same
    value: the snapshot helper returns {} for every failure, which the ancestor walk
    reports as 0, which the watchdog reads as "the terminal is gone". A transient
    CreateToolhelp32Snapshot failure — documented under heavy process churn, which is
    what a saikai with eleven panes produces — therefore counted as a confirmed terminal
    death, and two in a row killed every live pane. Failures now raise instead, so the
    watchdog's own "inconclusive → reset the streak" branch finally works; a truncated
    snapshot, or one that does not contain our own pid, counts as a failure too.
  • An abrupt exit no longer leaves the terminal broken. Two separate reasons the
    cleanup never landed: the mode reset wrote to sys.stderr, which Textual replaces
    for the app's whole life with a capture whose isatty() returns True on purpose — so
    the mouse/paste/focus disables went to the app instead of the terminal, and a wheel
    scroll at the recovered prompt sprayed escape sequences. And nothing ever sent
    ?1049l, so the shell prompt was drawn over the alternate screen with the scrollback
    out of reach. The reset writes to sys.__stderr__ now, and the paths that bypass
    Textual's teardown leave the alternate buffer themselves.
  • An abrupt exit explains itself in the log. Unhandled exceptions on the main
    thread AND in background threads (a pty reader, the mirror server, a reap — their
    deaths were completely silent) are written to saikai.log with their traceback,
    interpreter faults go to crash.log via faulthandler, and every clean exit logs
    stop: exiting, so a log that simply stops is now itself a signal. The watchdog
    logs when it arms, every miss, every NEAR miss — a session one poll from being
    reaped — and the kill decision.
  • Blocking work no longer freezes the UI. The Windows clipboard fallback ran on the
    UI thread with no timeout, and it is reached exactly when another process holds the
    clipboard, i.e. when it is certain to block: the event loop stopped dead, with an
    external kill as the only way out. It and the macOS one are bounded now. The
    agent-kill batch (one taskkill per target, up to 10s each) moved to a worker that is
    joined at exit, and the new-session candidate walk (git worktree list plus up to 40
    directory stats) no longer runs inline.
  • A recycled pid can no longer get an unrelated process tree force-killed. Nothing
    records procStart on Windows, so the agent-kill guard fell through to the image
    name — and node.exe is on the accepted list. On the machine this was found on, the
    only node.exe running belonged to Adobe, and the old check accepted it as our agent. A
    node now has to have a claude (or saikai) ancestor, and the pane's own reap records
    the child's start time at spawn and refuses a pid whose start time no longer matches.
  • A single bad frame no longer freezes the mirror for the rest of the session. The
    drain thread is the only consumer of the ingest queue and its body was unguarded, so
    one raise ended it: attached browsers sat on their last frame forever while the local
    UI looked perfectly fine. Mirror problems now also reach saikai.log.
  • A single bad chunk no longer kills a pane and orphans its claude. An exception
    escaping _consume ended the reader loop, which marked the pane dead — and a dead
    pane is forgotten, not killed, so its child outlived saikai untracked.
  • A pane no longer opens at the wrong size. At mount a widget has no layout yet
    (measured: size is 0x0, with the real geometry arriving on the first Resize), so the
    child was spawned into an 80x24 PTY from the fallback: it drew its whole startup
    screen wrapped at 80 columns into a grid that was then resized, and pyte does not
    reflow. The start waits for the geometry now; an inactive tab, which never gets one,
    still starts on the fallback so its child renders and can be classified.
  • A pane closed before its first layout no longer spawns a child nobody reaps.

Changed

  • A session parked on claude's agents view reports "Needs input". The view states
    its own aggregate in the OSC-0 title (1 awaiting input · claude agents), but with no
    title spinner and no permission prompt in the body every existing signal said idle —
    so a session where an agent was waiting for an answer sat in the Idle section. The
    counts in the title now decide: N awaiting input reads as needs-input and outranks
    the spinner, N working as running. Read from the title only, never the body:
    "Agents" occurs thousands of times in ordinary conversation text.
  • The mirror's read token is half as long — 8 url-safe characters instead of 16, so
    the URL is 42 characters to type on a phone and its QR is sparser. The strength comes
    from the hub's own guessing budget rather than the token: a bad read token arms a
    per-source 30s lockout after 50 attempts, with the source normalised to an IPv6 /64 so
    rotation cannot dodge it. The write-key is unchanged and still never appears in a URL,
    file, QR or log.

Known limitations

  • The pid-identity guard is strong on Windows and Linux only. macOS/BSD have no cheap
    process-start-time source (a ps subprocess on the UI thread is exactly what this
    release removed elsewhere), so there it degrades to "cannot verify → proceed", which
    is what every platform did before. Windows is where pids are recycled aggressively
    AND where the reap is a blanket tree kill.

[0.6.1] — 2026-07-30

A cross-terminal audit of the pane's own contract: what it tells a child it is, and
what it actually does. Every item is a place the two disagreed, so the symptom only
appeared on some hosts or under some children.

Fixed

  • A multi-line paste could be submitted line by line. A PTY read that ended on a
    CSI intermediate byte (the $ of \x1b[?2004$p) was released instead of held, so
    saikai's DECRQM scanners matched neither half and the query went unanswered — a child
    that sets bracketed paste and then verifies it concluded the mode was unsupported.
    pyte's own parser spans the split, which is why nothing looked broken.
  • DECRQM no longer claims modes saikai does not implement. The position-accurate
    overlay recorded every private mode a chunk carried, so a set-then-verify in one
    write was told "1 = set" for a mode nothing here honours, while the same query one
    read later correctly got "0 = not recognised".
  • The alt screen is detected in a combined DECSET. A child that writes its whole
    entry at once (\x1b[?1049;1002;1006h) produced no boundary, so pyte's single buffer
    kept the pre-alt frame under the child's new UI. AltScreenTracker had a second copy
    of the same pattern; it now shares one.
  • Arrow keys follow the child's DECCKM state. The pane always sent CSI while the
    mirror replays ?1h into xterm.js, so the same arrow reached the child as \x1bOA
    from a browser and \x1b[A from the pane.
  • A dropped image is reported. The pane answers DA1 byte-identically to Windows
    Terminal — which advertises sixel — and then drops every DCS, so a graphics payload
    vanished silently. It now says so once per pane.
  • Terminal remote-control sockets no longer reach a pane child. kitty and alacritty
    were scrubbed by their identity variables while KITTY_LISTEN_ON and
    ALACRITTY_SOCKET went through, which let a child drive the real outer window
    (kitten @, alacritty msg); KONSOLE_DBUS_* was the same over D-Bus. Whole
    families are stripped now, as WezTerm's already was.
  • The list's viewport no longer flicks to the cursor row under load. The
    cursor-scroll suppression closes on a queued callback, so a rebuild that starts
    before the previous one's callbacks drain opens a second window — and a boolean let
    the first window's end re-arm the second's pending scroll. It counts now.

Install or upgrade: uv tool install saikai / pipx upgrade saikai.
Full changelog: https://github.com/m-morino/saikai/blob/master/CHANGELOG.md

saikai 0.6.1 — a pane that keeps its own promises

Choose a tag to compare

@m-morino m-morino released this 30 Jul 00:13

A cross-terminal audit of the pane's own contract: what it tells a child it is, and
what it actually does. Every item is a place the two disagreed, so the symptom only
appeared on some hosts or under some children.

Fixed

  • A multi-line paste could be submitted line by line. A PTY read that ended on a
    CSI intermediate byte (the $ of \x1b[?2004$p) was released instead of held, so
    saikai's DECRQM scanners matched neither half and the query went unanswered — a child
    that sets bracketed paste and then verifies it concluded the mode was unsupported.
    pyte's own parser spans the split, which is why nothing looked broken.
  • DECRQM no longer claims modes saikai does not implement. The position-accurate
    overlay recorded every private mode a chunk carried, so a set-then-verify in one
    write was told "1 = set" for a mode nothing here honours, while the same query one
    read later correctly got "0 = not recognised".
  • The alt screen is detected in a combined DECSET. A child that writes its whole
    entry at once (\x1b[?1049;1002;1006h) produced no boundary, so pyte's single buffer
    kept the pre-alt frame under the child's new UI. AltScreenTracker had a second copy
    of the same pattern; it now shares one.
  • Arrow keys follow the child's DECCKM state. The pane always sent CSI while the
    mirror replays ?1h into xterm.js, so the same arrow reached the child as \x1bOA
    from a browser and \x1b[A from the pane.
  • A dropped image is reported. The pane answers DA1 byte-identically to Windows
    Terminal — which advertises sixel — and then drops every DCS, so a graphics payload
    vanished silently. It now says so once per pane.
  • Terminal remote-control sockets no longer reach a pane child. kitty and alacritty
    were scrubbed by their identity variables while KITTY_LISTEN_ON and
    ALACRITTY_SOCKET went through, which let a child drive the real outer window
    (kitten @, alacritty msg); KONSOLE_DBUS_* was the same over D-Bus. Whole
    families are stripped now, as WezTerm's already was.
  • The list's viewport no longer flicks to the cursor row under load. The
    cursor-scroll suppression closes on a queued callback, so a rebuild that starts
    before the previous one's callbacks drain opens a second window — and a boolean let
    the first window's end re-arm the second's pending scroll. It counts now.

Install or upgrade: uv tool install saikai / pipx upgrade saikai.
Full changelog: https://github.com/m-morino/saikai/blob/master/CHANGELOG.md

saikai 0.6.0 — faithful, cheap pane rendering

Choose a tag to compare

@m-morino m-morino released this 29 Jul 13:48

Live panes render a real terminal, and this release is mostly about making that
faithful and cheap on Windows Terminal: a pane no longer redraws itself wholesale on
every chunk, a frame can no longer mix two states of the child's screen, and the
widths in the model now match the widths on screen.

Fixed

  • Rows in a pane no longer land out of place. render_line took the pane lock
    once per row while Textual calls it in a per-row loop, and the reader installs a
    whole child frame atomically under that same lock — so one composited image could
    carry rows from two pyte generations (measured: 40 of 40 render passes torn). The
    positions were always right and the contents were stale, which is why nothing in
    the output stream explained it. The visible grid is now pinned once at the frame
    boundary and read lock-free.
  • Emoji no longer shift the rest of their row. is one column but +VS16 is
    an emoji-presentation sequence that Rich — so Textual, so the terminal — draws in
    two, and the zero-width merge left the cluster owning one. Every glyph after it sat
    a column right and the row's tail spilled past the pane. Measured on a captured
    session: the rows carrying ⚠️ handed Textual a 98-cell row for a 97-cell pane.
  • The pane no longer oscillates while scrolling. pyte erases per cell, so
    overwriting half a double-width glyph left the other half behind — a state no
    terminal can display. Rendering it forced a choice between eating a character and
    shifting the row, and a redrawing child alternated between the two. saikai now
    erases the whole glyph when either half is overwritten, as a real terminal does.
  • Copying with the mouse no longer corrupts the pane. The same per-row read let
    _scroll and the freeze snapshot flip mid-frame, mixing pinned and live rows.
  • The session list no longer jumps back while you scroll it. Three separate places
    moved the viewport during a rebuild: the rebuild itself (clear() zeroes scroll_y),
    a deferred restore that re-applied a pre-wheel position a frame later, and
    move_cursor(scroll=False), which did not actually prevent scrolling. The user's
    scroll position is now owned state rather than a snapshot guarded by a timer —
    measured on the real app under constant rebuilds: 0 backwards jumps and full travel,
    where before most of the travel was lost.
  • A resize that changes nothing is ignored. Textual re-posts Resize on any
    relayout, and each one dropped the pane's scrollback offset, dirtied every pyte line
    and sent the child SIGWINCH. A real session logged seven identical 36x97 resizes
    in eight seconds.
  • The native cursor no longer flickers over the list. Frames are bracketed in DEC
    2026 synchronized output on Windows (Textual's Windows driver never probes for it),
    the list's mouse-hover repaint is gone, and the periodic anchor no longer re-asserts
    ?25h on every tick.
  • IME anchoring follows the caret. The anchor rides the repaint, freezes while the
    child is mid-frame, settles on the busy→idle transition, re-anchors when leaving
    scrollback and after a resize, and heals a drifted cursor-visibility state. The pane
    presents itself as Windows Terminal, keeping WT_SESSION so claude tracks the caret.
  • Terminal queries are answered like a terminal. Primary and secondary DA, DECRQM
    with real mode state, DSR at the position the child asked from, in the order asked,
    once per split unit; DCS is scrubbed before pyte without leaking payloads or opening
    a deletion seam.
  • The PTY write path never blocks the UI thread. Oversized writes go to the pane's
    writer thread, the input queue is bounded, a refused write is logged instead of
    vanishing, and a writer that fails to start no longer swallows the data it was given.
  • Panes flagged "needs input" again for claude's resume and permission gates on the
    alt screen.
  • Quitting takes a deliberate double press. A single Esc (or Ctrl+C) on the
    list quit saikai outright — too easy to fire by reflex (Esc interrupts claude in a
    pane; Claude Code itself exits only on a second Ctrl+C). The first press arms and
    shows a hint; a second within ~2.5s quits, and any other key disarms. Esc still
    leaves search / dropdown / pane → list as before.

Changed

  • A pane repaints the rows that changed, not all of them. pyte tracks per-line
    damage and saikai never read it; a spinner frame touches one or two rows out of
    thirty-six. Measured per pane during an agent-mode storm: 115–670 ms of UI thread per
    second before, 8–33 ms after. The traps this has to survive are listed in the
    rendering invariants — a cursor move dirties nothing, resize leaves out-of-range
    indices, a child scroll dirties everything.
  • The web mirror no longer forces a full-screen relayout per frame. Its
    overflow-recovery repaint was the only refresh(layout=True) in the repository and
    fired on essentially every drain during a storm; it is now rate-limited, gated on a
    connected viewer, and asks for a repaint rather than a relayout. The statusbar also
    stopped requesting a layout pass on every update.

Added

  • SAIKAI_DIAG=1 captures the whole display picture in one run — per-pane child
    bytes, what saikai wrote to the terminal, per-second render accounting, a scroll /
    focus / resize trail, rows whose rendered width disagrees with the pane (with the
    row's text), and an automatic model dump at the first such row. One directory per
    launch. SAIKAI_FULL_REPAINT=1 and SAIKAI_NO_SYNC=1 isolate the two riskiest
    behaviours in a single variable.
  • Documented rendering invariants and a debugging guide.
    ARCHITECTURE.md lists the five
    invariants with the symptom each produces when it breaks;
    DEBUGGING.md covers reading the diagnostics, replaying a capture
    offline, attributing a defect to saikai or to the child, and what each instrument
    cannot distinguish.

Known limitations

  • East Asian Ambiguous characters (, and others) can overhang their cell. Their
    width is not defined by the standard: the terminal advances one column while a CJK
    fallback font may draw two. This reproduces with claude in Windows Terminal without
    saikai, and inside saikai the model and the renderer agree on every one of the 639
    distinct non-ASCII characters in a captured session — so there is nothing for saikai
    to fix here.
  • claude's own transient renders show through. Its spinner cycles, and its
    transcript briefly re-renders at a shifted offset; saikai reproduces the child's
    screen faithfully, including those.

Releases 0.4.0 – 0.5.2 were tagged without CHANGELOG entries. This entry covers the
work since 0.5.2 only; git log v0.3.0..v0.5.2 is the record for that gap.

v0.5.2

Choose a tag to compare

@m-morino m-morino released this 12 Jul 20:24

Fixed

Finished sessions no longer sit under "Running" forever on Linux.

On a quiet POSIX pty the busy→idle transition is completed by saikai's
1.5s poll rather than the pane's output reader — and the poll's list
rebuild was deferred for as long as any live pane held focus (the 2s
re-scan was skipped on the same condition). Parking focus in a pane —
the normal working posture, including driving a session from the web
mirror — froze the State groups, so sessions that had finished stayed
grouped under Running until focus returned to the list. Windows never
showed this because ConPTY keeps emitting output after idle, which kept
the reader-side (unconditional) refresh path alive.

Rebuilds are now deferred only while the user is actively typing
into the focused pane (keys, paste, and mirror-injected input all
count), and a deferred rebuild is caught up on the next poll tick
instead of waiting for a focus change.

Verified by a new regression test that drives a real pty child through
busy → idle → total silence with focus parked in the pane
(tests/test_status_regroup.py).

saikai 0.5.1

Choose a tag to compare

@m-morino m-morino released this 05 Jul 12:45

Mirror UX, native-fidelity selection, and claude-agents awareness. All changes are on top of 0.5.0.

Mirror

  • Native selection everywhere. Dragging inside the claude pane now runs the terminal's OWN selection (content-anchored, self-scrolling) instead of a screen-fixed block overlay whose start drifted when the host scrolled. Works in both the app view (delegated over /mouse) and the pane direct view (xterm-native). Copy is automatic on release and lands on the device you're holding (relayed from the host's clipboard). A long-press in the pane starts a one-shot selection drag with no Select mode; the session-list keeps its block selection + row menu.
  • No more flickering cursor. The browser cursor was chasing every repaint because the host hid its hardware cursor before any mirror client connected. The app view now calms the cursor during write bursts and re-shows it at the true position; the pane view implements DEC 2026 (synchronized output) so claude's frames paint atomically.
  • Fit-to-width now fits height too — the grid clears the key bar instead of hiding rows behind it; the key bar hugs the terminal's bottom edge when the canvas fits.
  • https on Windows — the TLS cert is minted in-process (cryptography), so TLS-by-default works where the openssl CLI isn't on PATH; a fallback to HTTP now records WHY in saikai.log.

claude agents

  • Agents-feature sessions (the & marker) get their own Agents list section instead of diluting "Open", with an honest refusal that points at the parent claude's agents view.
  • Lineage awareness: saikai reads the parent link claude records in each agent's transcript, clusters agents under their parent (↳ … ⟨parent⟩), and shows a "spawned by" line in the preview.
  • Shift+K terminates a runaway agent — confirmed and identity-verified (pid + start-time, so a recycled pid is never hit; the parent interactive claude is untouched). On a parent row it bulk-stops all of that parent's live child agents.
  • Desktop-SSH mirror sessions (ssh-*) stay badged and non-resumable-here.

Install / upgrade

uv tool install --force saikai

saikai 0.5.0

Choose a tag to compare

@m-morino m-morino released this 05 Jul 08:27

Pane direct view — the browser becomes the claude pane's real terminal

A new mirror mode streams a live pane's raw PTY bytes straight to the
browser's xterm.js: byte-faithful alt-screen, truecolor, native mouse
reporting, real cursor — instead of a re-render of the whole TUI. Toggle
Pane view ⇄ App view from the key bar's More row. Input goes verbatim to
the child (arrows honor DECCKM, composer pastes are properly bracketed).
Hardened across one high-effort review and three external audit rounds
(29 findings fixed, all with regression tests).

Mirror UX

  • Fit-to-width: the font scales so the whole grid fits the window
    (readability floor 9px — phones keep 1:1 + pan). Fit ⇄ 1:1 toggle,
    persisted per browser.
  • Key bar hugs the terminal's bottom edge when the canvas fits — no more
    dead space between content and keys on tall windows.
  • Region-aware selection edge auto-scroll, composer draft survives view
    switches.

https on Windows

The mirror's TLS certificate is now minted in-process (cryptography),
so TLS-by-default actually works on Windows where the openssl CLI isn't on
PATH — the QR/URL advertise https everywhere. openssl remains a fallback.

Desktop-SSH mirror sessions

Sessions executed on a REMOTE host via Claude Desktop's SSH integration are
mirrored locally under projects/ssh-*. saikai now badges them (s marker)
and refuses a local resume with an explanatory toast — resuming them for real
(an ssh -t pane + remote discovery) is the 0.6 goal on the
feature/remote-roots branch.

Fixes

  • Attention markers no longer freeze until F5 (transcript-growth-aware change
    gate).
  • Host resize / pane switches repaint the mirror correctly (meta-before-seed
    ordering, reseeds carry geometry).
  • Exclusive mouse-protocol tracking matching xterm.js semantics; child
    terminal queries (DA/DA2/DECRQSS/XTGETTCAP) stripped from the pane stream
    with split-DCS reassembly.

Install / upgrade

uv tool install --force saikai

saikai 0.4.2

Choose a tag to compare

@m-morino m-morino released this 05 Jul 00:53

Fixes

  • Attention markers no longer freeze until F5 — the default-on auto-refresh gated on directory mtimes, which do not change when a transcript grows. The change gate now watches project subdirs and every listed transcript, so the "needs input" marker flips within ~2s. (011b53d)
  • Mirror: host terminal resize now reaches the browser — the mirror's xterm size was set once at startup; resizing the host froze the mirror geometry. (80ef848)
  • Mirror: region-aware edge auto-scroll — selection auto-scroll now triggers at the pane's own edges (published via a new regions SSE event), not the canvas edges, so selecting inside a claude split pane scrolls correctly. Two-stage: local pan first, host scroll (control ON) second. &debug=1 overlay for diagnosis. (5e6ad39, 84091a6, 6e22b75)
  • Mirror: TLS on by default (opt-out) — the LAN mirror serves https with a self-signed cert. (296307f)
  • Toast rows self-heal against the Windows Terminal hover artifact. (f9e06f3)
  • b2 handoff prompt forbids identifier truncation and tool use during the handoff. (ee7d07d)

Install / upgrade

uv tool install --force saikai

v0.4.1 — calmer list tints, working edge auto-scroll

Choose a tag to compare

@m-morino m-morino released this 04 Jul 03:48

Changed

  • Muted tint palettes. The project/topic title tints dropped the saturated
    ANSI set (cyan/yellow/magenta/bright_*) for curated low-saturation tones
    (grey-blue, tan, sage, mauve, …): related sessions still bundle by colour
    when the project column is hidden, but the list stops shouting — and cyan
    is banished from the palettes
    , keeping it exclusively the needs-you
    accent. display.color_by = none remains for a fully neutral list. A test
    pins the rule (hex-only, bounded saturation, nothing cyan-ish). README
    screenshots and demo GIFs re-rendered to match.

Fixed

  • Web mirror: select-mode edge auto-scroll never fired. The edge zones
    were measured on a container that spans the full viewport behind the
    fixed key bar, so the bottom zone sat ~150px under the bar — unreachable.
    Zones now hug the visible terminal box, and the select drag rides a
    window-level mousemove so it keeps extending across the fixed overlays.
    Verified in a real browser (scroll posts every 150ms inside the zone,
    stopping cold outside it).

v0.4.0 — Checkpoint, attention-first UI, phone-first mirror

Choose a tag to compare

@m-morino m-morino released this 04 Jul 01:55

Added

  • Checkpoint (Space c, experimental). Reset a bloated session in one
    gesture: saikai has the session write its own handoff, shows you the
    extracted NEW SESSION PROMPT to vet/edit, and only on your confirm sends
    /clear and reseeds a fresh, lean session — with child→parent lineage
    (Shift+F6 jumps back). The whole pipeline is verified end-to-end against a
    real claude: injection settling, transcript freshness, child detection,
    and a reseed that confirms the turn actually started (a silently-swallowed
    Enter can no longer fake success). Each pane shows its real context
    fill
    (ctx 662K/1.0M (66%), the numbers Claude records — not a chars/4
    guess); Shift+F11 still sends a plain /compact, now verified the same way.
  • Attention-first list. One accent colour (cyan) is reserved for "needs
    you right now": the list opens grouped by state with Needs input first,
    the cursor lands on the first session waiting on a human, and Shift+F3
    hops to the next one. Everything else stays calm greyscale.
  • Web mirror: phone-first control. Drag-to-select as a block/rectangle
    (pane-respecting, like a desktop terminal emulator over an alt-screen) with
    edge auto-scroll and an http-safe Copy; a ⌨ composer tray (visible
    textarea) so the OS paste bubble and IME finally work on phones; an
    ergonomic key bar laid out by measured reach (loop keys ☰ List /
    ! Next on a shoulder row, d-pad + tall Enter in the thumb arc, Esc
    isolated on the far side) with hold-to-repeat arrows and a ⇄ Hand
    toggle that mirrors everything for left-handed use; Checkpoint and Compact
    ride the More row.
  • Web mirror: opt-in TLS (SAIKAI_MIRROR_TLS=1) with a cached self-signed
    EC certificate, plus SAIKAI_MIRROR_TLS_CERT/KEY for your own.
  • memory_safety one-knob presets (on / off / strict) for the
    live-pane RAM gate, replacing per-variable tuning for the common cases.
  • A permanent claude-seam probe (scripts/probe_claude_seams.py): one
    command re-audits every assumption saikai makes about the installed claude
    binary (transcript persistence, /clear child minting, /compact
    injection, --resume, the sessions registry, all transcript readers) —
    run it after claude updates.

Changed

  • Marker vocabulary rebuilt around one attention accent and dim tiers,
    with ASCII-safe glyphs (? waiting · ! reply due · ~ working · =
    idle · . recent) so widths never garble on CJK terminals; a contextual
    marker legend explains each row's glyphs in the preview.
  • Preview leads with recognition: your first/last message first, then
    details; the Summary pane is selectable text.
  • The mirror's inline page script is CSP-pinned by hash (script-src 'self' 'sha256-…', no unsafe-inline), refused inputs answer 429 instead of a
    silent 204, and client disconnects no longer spam tracebacks.
  • CLI: saikai --table | head exits cleanly on a closed pipe; --preview
    re-renders when the cache is older than the transcript.

Fixed

  • 40+ defects from a five-round audit campaign (real-claude E2E probes,
    hostile-input batteries, and an external cross-review), including: the
    checkpoint reseed being silently swallowed after /clear; the mirror page
    rendering nothing since the CSP hardening (inline script blocked); user
    content ([wip]-style titles, folder names) being eaten as markup — or
    crashing the app — in toasts, tabs, table cells, modals, and the folder
    picker; a duplicated event handler disabling header-skip; chronological
    sorts comparing mixed-offset ISO strings lexically; same-mtime cache
    staleness across preview/prefs/titles/lineage; non-dict JSONL lines
    aborting scanners (including b2 child detection); corrupt pref/config
    shapes crashing startup; a future mtime reading as "active"; Windows
    watchdog missing modern shells (nu/fish/xonsh); and venv PATH stripping
    missing trailing separators. Regression nets (AST sweeps, CSP-hash and
    markup-safety contracts) now guard each class.

v0.3.0 — Web mirror

Choose a tag to compare

@m-morino m-morino released this 17 Jun 15:28

Added

  • Web mirror (opt-in). saikai can mirror its live UI to a phone or another

    browser over the LAN — off by default and token-authenticated. It is

    read-only until you arm browser control at the terminal with Shift+F12;

    then the browser drives saikai by tap (select a row, sort a column), a

    single-finger swipe to scroll, an on-screen key bar, and a

    terminal-equivalent physical keyboard (arrows, Home/End, F-keys, Ctrl/Alt

    combos, Ctrl+] to leave a pane, Ctrl+C to interrupt claude). Launch shows a

    scannable QR code; press F12 to bring it back.

  • The mirror is safe by construction: only the local Shift+F12 can enable

    control (a browser can never arm itself), control auto-disables after

    inactivity, a LAN bind stays read-only unless you also set

    SAIKAI_MIRROR_ALLOW_LAN_INPUT=1, the per-run write-key for input travels only

    over the authenticated stream (never the URL, QR, or logs), and the status bar

    shows a live connected-browser count with a toast on each connect. New env

    vars: SAIKAI_MIRROR, SAIKAI_MIRROR_HOST, SAIKAI_MIRROR_PORT,

    SAIKAI_MIRROR_ALLOW_LAN_INPUT.

  • A shared fictional demo fixture now generates the public screenshots,

    deterministic headless GIF, and recording workspace without reading the

    caller's real HOME or Claude history.

  • An isolated real-Claude recording guide and asciinema cast auditor reject

    private paths, credentials, identities, and unapproved demo projects before

    conversion.

  • docs/ARCHITECTURE.md is now the canonical contributor reference for module

    boundaries, history semantics, PTY lifecycle, and concurrency invariants.

Changed

  • **The live-pane memory gate now reasons per-OS instead of projecting

    Windows' commit-charge model onto Linux/macOS.** Linux: commit headroom

    (CommitLimit − Committed_AS) is consulted only under strict overcommit

    (vm.overcommit_memory=2) — under the default heuristic mode the limit is

    not enforced and Committed_AS routinely exceeds it on a healthy machine,

    which read as negative headroom and falsely closed the gate. A new

    pressure check (SAIKAI_MAX_MEM_PRESSURE, default 10) reads Linux PSI

    (/proc/pressure/memory some avg10, the stall-time metric systemd-oomd

    acts on) and macOS's kernel pressure level (gates on critical), refusing

    a new pane when tasks are measurably stalling regardless of occupancy

    numbers. The memory-load high-water default is now 95 on Linux/macOS

    (85 stays on Windows): the POSIX load % is derived from the same

    availability figure as the physical floor, so the old shared default

    closed the gate while ~15% of RAM was still genuinely available.

  • The public story now leads with a one-line "Mission control for Claude Code"

    value statement and the hero demo — which opens on the cross-project session

    list — with the claude --resume rationale moved just below.

  • Help, Settings, and the READMEs explain the visual grammar consistently:

    title color groups context and ASCII symbols report state.

  • Contributor-facing agent files are concise entrypoints instead of duplicate

    copies of the concurrency manual.

Removed

  • The incomplete global LLM cluster mode and its dangling UI/CLI controls.

  • Internal launch-marketing notes and completed implementation plans from the

    public repository surface.

Fixed

  • The filter bar no longer mis-sizes: the Sort/Status/Age dropdowns are wide

    enough that long labels (e.g. "Alphabetically", "All time") no longer wrap,

    and the search box is capped so it does not expand to dwarf them.

  • Claude Desktop session sync (--sync-desktop) derives its store location

    per-OS (macOS ~/Library/Application Support, Linux XDG) instead of only the

    Windows path, so it no longer reports "not found" on macOS where Desktop runs.

  • The mirror-URL host-clipboard copy on Linux tries wl-copy (Wayland), then

    xclip, then xsel, instead of assuming xclip is installed.

  • Public docs, in-app marker help, contributor test commands, and CI now agree

    with the current marker semantics, terminal-support boundaries, and complete

    tests/test_*.py suite.

  • **Tree mode no longer chains unrelated same-repo sessions into one long

    list.** Parent assignment treated "same cwd + recent" as kinship and scored

    a mainmain branch match as strong evidence, so in a single-repository

    history every session linked to its nearest predecessor. A parent now

    requires continuation evidence — a shared feature branch (main/master/

    detached HEAD count as no-information), or title/topic overlap — and

    sessions without such evidence stay roots. Genuine continuation chains

    (e.g. a series of sessions iterating on the same prompt) still nest.

  • Help no longer claims that the Textual Last column is color-coded when it is

    rendered as plain text.