Idle filter: pivot per-distro → per-pane via TILETOPIA_PANE_ID env marker

Per-distro suppression (shipped earlier today) broke tiletopia's primary
use case — multiple claude panes per distro means as soon as one runs
claude, ALL Ubuntu panes go silent. Tested live: user couldn't reproduce
idle on any pane because PID 46848 (their main session) tripped the gate.

New mechanism, per-pane via env-var marker:

1. pty.rs tags every WSL spawn with TILETOPIA_PANE_ID=<id> as a Windows
   env var, plus WSLENV=...TILETOPIA_PANE_ID/u (appended to any pre-
   existing WSLENV) so the var forwards into the distro. Pane id is now
   reserved BEFORE build_command so the tag is available at spawn time.
2. probe.rs rewritten — is_watch_process_running(distro, pane_id) runs
   a bash one-liner that pgreps for each watched name, then for each PID
   checks /proc/<pid>/environ for the matching TILETOPIA_PANE_ID line.
   Env inheritance does the work: shell inherits from wsl.exe, claude
   inherits from shell. Cache keyed by (distro, pane_id).
3. Fail-safe INVERTED: probe failure now returns false (don't suppress)
   instead of true (suppress). A transient error should never silence
   the idle indicator permanently. Frontend catch updated to match.
4. LeafPane tracks PaneId in paneIdRef set by onPaneSpawned; idle ticks
   before spawn-completion pass 0, which won't match any real marker so
   the pane idles normally.

Existing panes won't have the marker until respawned — they'll always
show idle (since probe never matches). User opens fresh panes once after
deploying this. Documented in memory.md follow-ups.

pnpm check clean. Rust validation: cargo test --lib on Windows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-26 17:58:51 +01:00
parent d3474d33b0
commit 6772b8db37
6 changed files with 230 additions and 124 deletions

View file

@ -154,7 +154,32 @@ impl PtyManager {
_ => None,
};
let (cmd, spawn_err) = build_command(&spec)?;
// Reserve the pane id BEFORE spawning so we can tag the shell's
// env with it — see TILETOPIA_PANE_ID below. We still insert into
// the panes map further down, after the reader thread is wired.
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
let (mut cmd, spawn_err) = build_command(&spec)?;
// WSL panes get a TILETOPIA_PANE_ID env marker so the idle-filter
// probe (probe.rs) can tell which descendant processes belong to
// which pane — inheritance does the work: the shell inherits from
// wsl.exe via WSLENV, and every child (e.g. claude) inherits from
// the shell, so checking `/proc/<pid>/environ` for the marker
// answers "is this process running in pane N?" exactly.
if matches!(spec, SpawnSpec::Wsl { .. }) {
cmd.env("TILETOPIA_PANE_ID", id.to_string());
// WSLENV controls which Windows-side env vars are forwarded into
// the distro. Append our marker rather than clobbering — users
// may have their own WSLENV set up. `/u` = always pass through
// as a Unix-style env var.
let existing = std::env::var("WSLENV").unwrap_or_default();
let combined = if existing.is_empty() {
"TILETOPIA_PANE_ID/u".to_string()
} else {
format!("{existing}:TILETOPIA_PANE_ID/u")
};
cmd.env("WSLENV", combined);
}
let child = pair.slave.spawn_command(cmd).context(spawn_err)?;
// We need to keep the master alive (drop = close the PTY), but we
@ -170,8 +195,6 @@ impl PtyManager {
let writer: SharedWriter = Arc::new(Mutex::new(writer_raw));
let ring: Arc<Mutex<PaneRing>> = Arc::new(Mutex::new(PaneRing::new()));
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
self.panes.lock().insert(
id,
PaneHandle {