From 9dc02184ccbe9c22e09f501b548f989b907aab79 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Tue, 21 Jul 2026 13:15:07 -0700 Subject: [PATCH] Keep Loopboys on direct terminal ttys --- slab/bin/prox-mcp.mjs | 28 +++++++++++-------- .../Sources/SlabMenubar/Ledger.swift | 11 +++++--- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/slab/bin/prox-mcp.mjs b/slab/bin/prox-mcp.mjs index bb8d22340d..f4e1ff39fb 100644 --- a/slab/bin/prox-mcp.mjs +++ b/slab/bin/prox-mcp.mjs @@ -347,12 +347,21 @@ async function toolLaunch({ host, agent, cwd, prompt = "", by, loopboyContact = if (!target) throw new Error(`no cached ledger for host «${host}» — it must be online in prox first.`); if (!target.ip) throw new Error(`no tailnet IP known for ${target.host}.`); const self = (await readJson(LOCAL_FILE))?.host || hostname().split(".")[0]; + const contactKey = String(loopboyContact || "").toLowerCase(); + const existingMarkerIds = new Set(); + if (contactKey) { + for (const dir of MARKER_DIRS) { + let names = []; + try { names = await readdir(dir); } catch {} + for (const name of names) existingMarkerIds.add(name); + } + } const launcher = by || `${self}:prox`; const body = JSON.stringify({ agent: agentName, prompt: String(prompt), ...(cwd ? { cwd: String(cwd) } : {}), - ...(loopboyContact ? { loopboyContact: String(loopboyContact).toLowerCase() } : {}), + ...(contactKey ? { loopboyContact: contactKey } : {}), by: launcher, }); const res = await fetch(`http://${target.ip}:${PORT}/launch`, { @@ -366,13 +375,10 @@ async function toolLaunch({ host, agent, cwd, prompt = "", by, loopboyContact = try { result = JSON.parse(text); } catch { throw new Error(`launch on ${target.host} returned invalid JSON (HTTP ${res.status}).`); } if (!res.ok || !result.ok) throw new Error(`launch on ${target.host} failed: ${result.error || `HTTP ${res.status}`}`); let binding = ""; - if (loopboyContact) { + if (contactKey) { if (String(target.host).toLowerCase() !== String(self).toLowerCase()) { throw new Error("Loopboy contact routes can only be launched on this local iMessage host"); } - if (!result.nudgeScreen) { - throw new Error("Loopboy launch did not return a nudge screen; prompt host needs the updated Slab build"); - } let marker = null; for (let attempt = 0; attempt < 20 && !marker; attempt++) { for (const dir of MARKER_DIRS) { @@ -380,7 +386,8 @@ async function toolLaunch({ host, agent, cwd, prompt = "", by, loopboyContact = try { names = await readdir(dir); } catch {} for (const name of names) { const value = await readJson(join(dir, name)); - if (value?.nudge_screen === result.nudgeScreen) { + const id = value?.session_id || name; + if (!existingMarkerIds.has(id) && value?.loopboy_contact === contactKey) { marker = { id: value.session_id || name, value }; break; } @@ -393,14 +400,13 @@ async function toolLaunch({ host, agent, cwd, prompt = "", by, loopboyContact = await mkdir(join(homedir(), ".config", "slab"), { recursive: true }); const cfg = (await readJson(LOOPBOY_CONFIG)) || { version: 1, loops: {} }; cfg.version = 1; cfg.loops ||= {}; - cfg.loops[String(loopboyContact).toLowerCase()] = { - event: "imessage", contact: String(loopboyContact).toLowerCase(), + cfg.loops[contactKey] = { + event: "imessage", contact: contactKey, sessionId: marker.id, host: result.host || target.host, - name: "pending-ledger-name", agent: agentName, wake: true, - nudgeScreen: result.nudgeScreen, assignedAt: new Date().toISOString(), + agent: agentName, wake: true, assignedAt: new Date().toISOString(), }; await writeFile(LOOPBOY_CONFIG, JSON.stringify(cfg, null, 2) + "\n", { mode: 0o600 }); - binding = ` and bound Loopboy contact ${loopboyContact}`; + binding = ` and bound Loopboy contact ${contactKey}`; } return [{ type: "text", diff --git a/slab/menubar-swift/Sources/SlabMenubar/Ledger.swift b/slab/menubar-swift/Sources/SlabMenubar/Ledger.swift index d4bc1cb8fc..267bcb57ec 100644 --- a/slab/menubar-swift/Sources/SlabMenubar/Ledger.swift +++ b/slab/menubar-swift/Sources/SlabMenubar/Ledger.swift @@ -173,14 +173,17 @@ final class LedgerStore { } var command: String - var nudgeScreen = "" + let nudgeScreen = "" if !loopboyContact.isEmpty { - nudgeScreen = "loopboy-\(UUID().uuidString.prefix(8).lowercased())" + // Keep Loopboys on the real Terminal PTY. GNU Screen forwards + // Terminal focus-report sequences (ESC [ I / ESC [ O) as literal + // Codex input, corrupting the prompt whenever focus changes. Slab + // already knows how to focus the exact tty and type the wake prompt + // through trusted CGEvents, so an extra terminal layer is harmful. command = "cd \(shellQuote(cwd)) && " + "SLAB_TERMINAL_TTY=$(basename \"$(tty)\") " - + "SLAB_NUDGE_SCREEN=\(shellQuote(nudgeScreen)) " + "SLAB_LOOPBOY_CONTACT=\(shellQuote(loopboyContact)) " - + "exec /usr/bin/screen -S \(shellQuote(nudgeScreen)) \(shellQuote(binary))" + + "exec \(shellQuote(binary))" } else { command = "cd \(shellQuote(cwd)) && exec \(shellQuote(binary))" } -- 2.51.2