@sentropic/h2a-runtime 0.88.0 → 0.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attach.d.ts +21 -0
- package/dist/attach.d.ts.map +1 -1
- package/dist/attach.js +69 -1
- package/dist/attach.js.map +1 -1
- package/dist/file-lock.d.ts +34 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +96 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +492 -50
- package/dist/index.js.map +1 -1
- package/dist/llm-gateway-runtime/accounts.d.ts.map +1 -1
- package/dist/llm-gateway-runtime/accounts.js +3 -0
- package/dist/llm-gateway-runtime/accounts.js.map +1 -1
- package/dist/llm-gateway-runtime/proxy-anthropic.d.ts.map +1 -1
- package/dist/llm-gateway-runtime/proxy-anthropic.js +24 -0
- package/dist/llm-gateway-runtime/proxy-anthropic.js.map +1 -1
- package/dist/llm-gateway-runtime/proxy-openai.d.ts.map +1 -1
- package/dist/llm-gateway-runtime/proxy-openai.js +53 -2
- package/dist/llm-gateway-runtime/proxy-openai.js.map +1 -1
- package/dist/proc-cpu.d.ts +55 -0
- package/dist/proc-cpu.d.ts.map +1 -0
- package/dist/proc-cpu.js +133 -0
- package/dist/proc-cpu.js.map +1 -0
- package/dist/prompt-delivery.d.ts +159 -0
- package/dist/prompt-delivery.d.ts.map +1 -0
- package/dist/prompt-delivery.js +469 -0
- package/dist/prompt-delivery.js.map +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +7 -69
- package/dist/registry.js.map +1 -1
- package/dist/relaunch.d.ts +20 -1
- package/dist/relaunch.d.ts.map +1 -1
- package/dist/relaunch.js +31 -10
- package/dist/relaunch.js.map +1 -1
- package/dist/restore.d.ts +20 -4
- package/dist/restore.d.ts.map +1 -1
- package/dist/restore.js +26 -4
- package/dist/restore.js.map +1 -1
- package/dist/session-lease.d.ts +266 -0
- package/dist/session-lease.d.ts.map +1 -0
- package/dist/session-lease.js +342 -0
- package/dist/session-lease.js.map +1 -0
- package/dist/tmux.d.ts +77 -2
- package/dist/tmux.d.ts.map +1 -1
- package/dist/tmux.js +269 -23
- package/dist/tmux.js.map +1 -1
- package/package.json +1 -1
package/dist/tmux.d.ts
CHANGED
|
@@ -176,7 +176,44 @@ export type ManagedLaunchMetadata = {
|
|
|
176
176
|
terminateOnAgentExit?: boolean;
|
|
177
177
|
/** Refuse an existing name instead of reusing it (structured launch contract). */
|
|
178
178
|
refuseExisting?: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Keep a real, fixed-size PTY client attached while the pane is remotely
|
|
181
|
+
* driven. Required before an initial TUI prompt can be delivered reliably.
|
|
182
|
+
*/
|
|
183
|
+
attachedTerminal?: boolean;
|
|
184
|
+
};
|
|
185
|
+
/** Fixed PTY geometry for a non-human tmux client. */
|
|
186
|
+
export declare const HEADLESS_TERMINAL_SIZE: {
|
|
187
|
+
readonly cols: 160;
|
|
188
|
+
readonly rows: 48;
|
|
189
|
+
};
|
|
190
|
+
export type HeadlessTerminalResult = {
|
|
191
|
+
readonly state: "already-attached";
|
|
192
|
+
readonly attachedClients: number;
|
|
193
|
+
} | {
|
|
194
|
+
readonly state: "headless-attached";
|
|
195
|
+
readonly attachedClients: number;
|
|
196
|
+
readonly cols: number;
|
|
197
|
+
readonly rows: number;
|
|
198
|
+
} | {
|
|
199
|
+
readonly state: "unavailable";
|
|
200
|
+
readonly reason: string;
|
|
201
|
+
};
|
|
202
|
+
export type EnsureHeadlessTerminalOptions = {
|
|
203
|
+
/** How long to wait for tmux to observe the new client. */
|
|
204
|
+
readonly timeoutMs?: number;
|
|
205
|
+
/** Poll cadence while the script-owned client starts. */
|
|
206
|
+
readonly pollMs?: number;
|
|
179
207
|
};
|
|
208
|
+
/**
|
|
209
|
+
* Give a detached tmux session a genuine terminal, without taking over the
|
|
210
|
+
* launcher terminal. `script` allocates a PTY and an intentionally idle pipe
|
|
211
|
+
* keeps its stdin open; without that pipe `script` receives EOF from a
|
|
212
|
+
* headless launcher and the tmux client immediately detaches. A pipe or `tmux
|
|
213
|
+
* send-keys` alone is not a terminal, and both Codex and Claude can drop their
|
|
214
|
+
* initial composer input when started that way.
|
|
215
|
+
*/
|
|
216
|
+
export declare function ensureHeadlessTerminal(session: string, options?: EnsureHeadlessTerminalOptions): HeadlessTerminalResult;
|
|
180
217
|
/**
|
|
181
218
|
* Pure builder for the global tmux options applied at every run/attach. Split
|
|
182
219
|
* out of `ensureScrollConfig` so the option set (scroll/clipboard AND the
|
|
@@ -410,10 +447,48 @@ export declare function sessionAttached(name: string): boolean;
|
|
|
410
447
|
* Paste a LITERAL line into a session's main pane, then submit it with a real
|
|
411
448
|
* Enter key event. Content reaches `tmux load-buffer -` on stdin, then a named
|
|
412
449
|
* buffer is pasted: arbitrary text is never interpreted by a shell and never
|
|
413
|
-
* appears in a process argv. Enter is a separate key-name event. Used
|
|
414
|
-
*
|
|
450
|
+
* appears in a process argv. Enter is a separate key-name event. Used for
|
|
451
|
+
* interactive throttle nudges (single lines).
|
|
415
452
|
*/
|
|
416
453
|
export declare function sendKeysLiteral(name: string, keys: string): boolean;
|
|
454
|
+
/**
|
|
455
|
+
* Paste text as ONE block, WITHOUT submitting it.
|
|
456
|
+
*
|
|
457
|
+
* `-p` wraps the payload in bracketed-paste markers. Measured 2026-07-29 on
|
|
458
|
+
* Claude Code 2.1.220: without `-p`, a multi-line brief is submitted line by
|
|
459
|
+
* line — line 1 left as its own request while line 2 stayed in the composer.
|
|
460
|
+
* With `-p` the whole block lands as a single entry that one Enter submits.
|
|
461
|
+
*/
|
|
462
|
+
export declare function pasteLiteralBlock(name: string, keys: string): boolean;
|
|
463
|
+
/** Submit whatever the composer currently holds (one real Enter key event). */
|
|
464
|
+
export declare function submitPane(name: string): boolean;
|
|
465
|
+
/**
|
|
466
|
+
* Best-effort composer wipe. Ctrl-U only kills to the start of the CURRENT
|
|
467
|
+
* line, so a multi-line composer needs several — measured: one C-u left a
|
|
468
|
+
* multi-line brief in place, and retrying the paste then STACKED copies.
|
|
469
|
+
* Delivery never depends on this succeeding; it types only once.
|
|
470
|
+
*/
|
|
471
|
+
export declare function clearPaneComposer(name: string): boolean;
|
|
472
|
+
/**
|
|
473
|
+
* VISIBLE text of one pane, or undefined when tmux cannot be read — unlike
|
|
474
|
+
* `capturePane`, which flattens a failure into "". Delivery needs the
|
|
475
|
+
* difference: an unreadable pane is not an empty one.
|
|
476
|
+
*/
|
|
477
|
+
export declare function capturePaneVisible(pane: string): string | undefined;
|
|
478
|
+
/**
|
|
479
|
+
* CPU time (ms) of the process tree behind a pane — the liveness signal used to
|
|
480
|
+
* tell a working agent from one parked on its placeholder.
|
|
481
|
+
*/
|
|
482
|
+
export declare function paneTreeCpuMs(pane: string): number | undefined;
|
|
483
|
+
/**
|
|
484
|
+
* The pid actually doing the work behind a pane.
|
|
485
|
+
*
|
|
486
|
+
* The pane's own pid is the launch WRAPPER: measured 2026-07-29, it reported 0s
|
|
487
|
+
* of CPU after 37s while its `codex` child had burned 13s. Anything that judges
|
|
488
|
+
* a lane by the reported pid alone — a supervisor, a status bar, a conductor —
|
|
489
|
+
* is reading the wrong process, and gets a false answer in both directions.
|
|
490
|
+
*/
|
|
491
|
+
export declare function paneWorkerPid(pane: string): number | undefined;
|
|
417
492
|
/**
|
|
418
493
|
* Attach the real terminal straight into the Pod's tmux session via
|
|
419
494
|
* `kubectl exec -it`. The local terminal talks to tmux directly (no WS proxy),
|
package/dist/tmux.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tmux.d.ts","sourceRoot":"","sources":["../src/tmux.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"tmux.d.ts","sourceRoot":"","sources":["../src/tmux.ts"],"names":[],"mappings":"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"}
|
package/dist/tmux.js
CHANGED
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
* the LOCAL terminal owns scrollback + copy (OSC52) with no WS proxy in the
|
|
11
11
|
* middle — this is what fixes "I can't copy the code claude printed".
|
|
12
12
|
*/
|
|
13
|
-
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
14
14
|
import { randomUUID } from "node:crypto";
|
|
15
15
|
import { getTmuxProfileConfig } from "./config.js";
|
|
16
|
-
import { mkdtempSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
16
|
+
import { mkdtempSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
17
17
|
import { basename, join } from "node:path";
|
|
18
18
|
import { homedir, tmpdir } from "node:os";
|
|
19
|
+
import { readProcessTreeCpuMs, readWorkerPid } from "./proc-cpu.js";
|
|
19
20
|
import { LAUNCH_OPTION_PREFIX, buildLaunchContext, launchContextOptions, parseLaunchContext, redactSecrets, } from "./launch-context.js";
|
|
20
21
|
import { SESSION_CLASS_ENV } from "./session-class.js";
|
|
21
22
|
const TMUX = "tmux";
|
|
@@ -352,6 +353,91 @@ export function getLocalSessionDisplayName(session) {
|
|
|
352
353
|
const v = r.stdout.trim();
|
|
353
354
|
return v || undefined;
|
|
354
355
|
}
|
|
356
|
+
/** Fixed PTY geometry for a non-human tmux client. */
|
|
357
|
+
export const HEADLESS_TERMINAL_SIZE = { cols: 160, rows: 48 };
|
|
358
|
+
const HEADLESS_TERMINAL_TIMEOUT_MS = 5_000;
|
|
359
|
+
const HEADLESS_TERMINAL_POLL_MS = 50;
|
|
360
|
+
const HEADLESS_TERMINAL_MARKER = "@h2a_attached_terminal";
|
|
361
|
+
function sleepBlocking(ms) {
|
|
362
|
+
if (ms <= 0)
|
|
363
|
+
return;
|
|
364
|
+
const cell = new Int32Array(new SharedArrayBuffer(4));
|
|
365
|
+
Atomics.wait(cell, 0, 0, ms);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Give a detached tmux session a genuine terminal, without taking over the
|
|
369
|
+
* launcher terminal. `script` allocates a PTY and an intentionally idle pipe
|
|
370
|
+
* keeps its stdin open; without that pipe `script` receives EOF from a
|
|
371
|
+
* headless launcher and the tmux client immediately detaches. A pipe or `tmux
|
|
372
|
+
* send-keys` alone is not a terminal, and both Codex and Claude can drop their
|
|
373
|
+
* initial composer input when started that way.
|
|
374
|
+
*/
|
|
375
|
+
export function ensureHeadlessTerminal(session, options = {}) {
|
|
376
|
+
const attached = sessionAttachedCount(session);
|
|
377
|
+
if (attached !== undefined && attached > 0) {
|
|
378
|
+
return { state: "already-attached", attachedClients: attached };
|
|
379
|
+
}
|
|
380
|
+
if (attached === undefined) {
|
|
381
|
+
return {
|
|
382
|
+
state: "unavailable",
|
|
383
|
+
reason: `tmux could not inspect session ${session}`,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (!commandAvailable("script")) {
|
|
387
|
+
return {
|
|
388
|
+
state: "unavailable",
|
|
389
|
+
reason: "the util-linux `script` command is required to allocate a persistent PTY",
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
let launchError;
|
|
393
|
+
try {
|
|
394
|
+
const child = spawn("/bin/sh", [
|
|
395
|
+
"-c",
|
|
396
|
+
`exec tail -f /dev/null | script -qefc 'export TERM=xterm-256color; stty cols ${HEADLESS_TERMINAL_SIZE.cols} rows ${HEADLESS_TERMINAL_SIZE.rows}; exec tmux -u attach-session -t "$H2A_HEADLESS_TARGET"' /dev/null`,
|
|
397
|
+
], {
|
|
398
|
+
detached: true,
|
|
399
|
+
stdio: "ignore",
|
|
400
|
+
env: {
|
|
401
|
+
...process.env,
|
|
402
|
+
H2A_HEADLESS_TARGET: exactSessionTarget(session),
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
child.on("error", (error) => {
|
|
406
|
+
launchError = error.message;
|
|
407
|
+
});
|
|
408
|
+
child.unref();
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
return {
|
|
412
|
+
state: "unavailable",
|
|
413
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
const timeoutMs = options.timeoutMs ?? HEADLESS_TERMINAL_TIMEOUT_MS;
|
|
417
|
+
const pollMs = options.pollMs ?? HEADLESS_TERMINAL_POLL_MS;
|
|
418
|
+
const deadline = Date.now() + timeoutMs;
|
|
419
|
+
for (;;) {
|
|
420
|
+
const clients = sessionAttachedCount(session);
|
|
421
|
+
if (clients !== undefined && clients > 0) {
|
|
422
|
+
setSessionOption(session, HEADLESS_TERMINAL_MARKER, `headless:${HEADLESS_TERMINAL_SIZE.cols}x${HEADLESS_TERMINAL_SIZE.rows}`);
|
|
423
|
+
return {
|
|
424
|
+
state: "headless-attached",
|
|
425
|
+
attachedClients: clients,
|
|
426
|
+
...HEADLESS_TERMINAL_SIZE,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
if (launchError) {
|
|
430
|
+
return { state: "unavailable", reason: launchError };
|
|
431
|
+
}
|
|
432
|
+
if (Date.now() >= deadline) {
|
|
433
|
+
return {
|
|
434
|
+
state: "unavailable",
|
|
435
|
+
reason: `tmux client did not attach within ${timeoutMs}ms`,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
sleepBlocking(Math.min(pollMs, Math.max(1, deadline - Date.now())));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
355
441
|
/**
|
|
356
442
|
* First clipboard CLI found on PATH, or undefined. With `mouse on`, a mouse
|
|
357
443
|
* drag is captured by tmux (copy-mode) instead of the terminal's NATIVE
|
|
@@ -567,7 +653,7 @@ export function ensureScrollConfig(profile) {
|
|
|
567
653
|
export function startLocalSession(profile, command, cwd, args = [], label, tmuxProfile = getTmuxProfileConfig().profile, metadata = {}) {
|
|
568
654
|
const slug = slugify(label ?? cwd);
|
|
569
655
|
const name = localSessionName(slug);
|
|
570
|
-
const { sessionClass, terminateOnAgentExit = false, refuseExisting = false, ...launchMetadata } = metadata;
|
|
656
|
+
const { sessionClass, terminateOnAgentExit = false, refuseExisting = false, attachedTerminal = false, ...launchMetadata } = metadata;
|
|
571
657
|
ensureScrollConfig(tmuxProfile);
|
|
572
658
|
const existing = resolveLocalSessionSlug(slug);
|
|
573
659
|
if (existing.kind === "ambiguous") {
|
|
@@ -579,12 +665,31 @@ export function startLocalSession(profile, command, cwd, args = [], label, tmuxP
|
|
|
579
665
|
}
|
|
580
666
|
const agentPane = persistAgentPaneMetadata(existing.session.name, profile, cwd);
|
|
581
667
|
persistLaunchContext(existing.session.name, buildLaunchContext({ profile, cwd, label, ...launchMetadata }));
|
|
668
|
+
if (attachedTerminal) {
|
|
669
|
+
const terminal = ensureHeadlessTerminal(existing.session.name);
|
|
670
|
+
if (terminal.state === "unavailable") {
|
|
671
|
+
throw new Error(`could not attach a persistent terminal to ${existing.session.slug}: ${terminal.reason}`);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
582
674
|
return {
|
|
583
675
|
name: existing.session.name,
|
|
584
676
|
slug: existing.session.slug,
|
|
585
677
|
...(agentPane ? { agentPane } : {}),
|
|
586
678
|
};
|
|
587
679
|
}
|
|
680
|
+
const agentCommand = [
|
|
681
|
+
...anthopicEnvUnsetCommandPrefix(),
|
|
682
|
+
"/bin/bash",
|
|
683
|
+
"-lc",
|
|
684
|
+
terminateOnAgentExit ? STRUCTURED_LOCAL_WRAPPER : LOCAL_WRAPPER,
|
|
685
|
+
...(terminateOnAgentExit
|
|
686
|
+
? [command]
|
|
687
|
+
: [
|
|
688
|
+
localRelaunchCommand(profile, cwd, label, metadata.resumeId ? ["--resume", metadata.resumeId] : []),
|
|
689
|
+
command,
|
|
690
|
+
]),
|
|
691
|
+
...args,
|
|
692
|
+
];
|
|
588
693
|
const r = spawnSync(TMUX, [
|
|
589
694
|
"new-session",
|
|
590
695
|
"-d",
|
|
@@ -601,17 +706,13 @@ export function startLocalSession(profile, command, cwd, args = [], label, tmuxP
|
|
|
601
706
|
profile,
|
|
602
707
|
"-c",
|
|
603
708
|
cwd,
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
...(
|
|
609
|
-
? [
|
|
610
|
-
:
|
|
611
|
-
localRelaunchCommand(profile, cwd, label, metadata.resumeId ? ["--resume", metadata.resumeId] : []),
|
|
612
|
-
command,
|
|
613
|
-
]),
|
|
614
|
-
...args,
|
|
709
|
+
// A detached pane has no actual terminal client. Do not start a TUI
|
|
710
|
+
// there: a process that reads stdin before the PTY client attaches sees
|
|
711
|
+
// EOF, while one that starts rendering can lose the first prompt. Keep a
|
|
712
|
+
// benign pane alive, attach and verify the PTY, then respawn the agent.
|
|
713
|
+
...(attachedTerminal
|
|
714
|
+
? ["/bin/bash", "-lc", "exec sleep 86400"]
|
|
715
|
+
: agentCommand),
|
|
615
716
|
], {
|
|
616
717
|
encoding: "utf8",
|
|
617
718
|
stdio: ["inherit", "pipe", "inherit"],
|
|
@@ -622,10 +723,31 @@ export function startLocalSession(profile, command, cwd, args = [], label, tmuxP
|
|
|
622
723
|
const printedPane = r.stdout?.trim();
|
|
623
724
|
const capturedPane = validTmuxPaneId(printedPane) ? printedPane : undefined;
|
|
624
725
|
const agentPane = persistAgentPaneMetadata(name, profile, cwd, capturedPane);
|
|
625
|
-
if ((terminateOnAgentExit || refuseExisting) && !agentPane) {
|
|
726
|
+
if ((terminateOnAgentExit || refuseExisting || attachedTerminal) && !agentPane) {
|
|
626
727
|
killLocalSession(name);
|
|
627
728
|
throw new Error(`tmux did not return a live agent pane for ${slug}`);
|
|
628
729
|
}
|
|
730
|
+
if (attachedTerminal) {
|
|
731
|
+
const terminal = ensureHeadlessTerminal(name);
|
|
732
|
+
if (terminal.state === "unavailable") {
|
|
733
|
+
killLocalSession(name);
|
|
734
|
+
throw new Error(`could not attach a persistent terminal to ${slug}: ${terminal.reason}`);
|
|
735
|
+
}
|
|
736
|
+
const respawned = spawnSync(TMUX, [
|
|
737
|
+
"respawn-pane",
|
|
738
|
+
"-k",
|
|
739
|
+
"-t",
|
|
740
|
+
agentPane,
|
|
741
|
+
"-c",
|
|
742
|
+
cwd,
|
|
743
|
+
...tmuxEnvironmentArgs(sessionClass),
|
|
744
|
+
...agentCommand,
|
|
745
|
+
], { stdio: ["ignore", "ignore", "ignore"] });
|
|
746
|
+
if (respawned.status !== 0) {
|
|
747
|
+
killLocalSession(name);
|
|
748
|
+
throw new Error(`tmux could not start the agent pane for ${slug} after terminal attachment`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
629
751
|
// Record the profile as a session option so `remote ls` can show it.
|
|
630
752
|
spawnSync(TMUX, ["set-option", "-t", name, "@profile", profile], {
|
|
631
753
|
stdio: "ignore",
|
|
@@ -755,7 +877,7 @@ export function cleanupHeadlessPromptFile(promptFile) {
|
|
|
755
877
|
* we never send keys into a session a human is driving. Best-effort.
|
|
756
878
|
*/
|
|
757
879
|
export function sessionAttachedCount(name) {
|
|
758
|
-
const r = spawnSync(TMUX, ["display", "-p", "-t",
|
|
880
|
+
const r = spawnSync(TMUX, ["display", "-p", "-t", exactSessionTarget(name), "#{session_attached}"], { encoding: "utf8" });
|
|
759
881
|
if (r.status !== 0 || r.stdout === undefined)
|
|
760
882
|
return undefined;
|
|
761
883
|
const n = Number.parseInt(r.stdout.trim(), 10);
|
|
@@ -845,8 +967,25 @@ export function commandAvailable(cmd) {
|
|
|
845
967
|
function validTmuxPaneId(value) {
|
|
846
968
|
return value !== undefined && /^%\d+$/.test(value);
|
|
847
969
|
}
|
|
970
|
+
/**
|
|
971
|
+
* Exact, fail-closed SESSION target for `-t`, for both `show-options` and
|
|
972
|
+
* `set-option`.
|
|
973
|
+
*
|
|
974
|
+
* Measured on tmux 3.6:
|
|
975
|
+
* -t <name> bare -> resolves exact THEN unique-PREFIX, so `set-option -t
|
|
976
|
+
* h2a-foo` mutates `h2a-foobar` when h2a-foo is gone.
|
|
977
|
+
* -t =<name> -> `=` marks a PANE target here; both commands miss the
|
|
978
|
+
* session, and `-q` swallows the error so a read returns
|
|
979
|
+
* "" — which is why the status surface silently never
|
|
980
|
+
* installed on this tmux.
|
|
981
|
+
* -t =<name>: -> `=` exact + trailing `:` (the session's target):
|
|
982
|
+
* resolves the EXACT session, fails closed when it is
|
|
983
|
+
* absent, and works for read and write alike.
|
|
984
|
+
* So the correct exact-session form is `=<name>:`.
|
|
985
|
+
*/
|
|
848
986
|
function exactSessionTarget(session) {
|
|
849
|
-
|
|
987
|
+
const bare = session.replace(/^=/, "").replace(/:$/, "");
|
|
988
|
+
return `=${bare}:`;
|
|
850
989
|
}
|
|
851
990
|
function readSessionOption(session, option) {
|
|
852
991
|
const r = spawnSync(TMUX, ["show-options", "-qv", "-t", exactSessionTarget(session), option], { encoding: "utf8" });
|
|
@@ -1422,7 +1561,7 @@ export function relaunchInSession(name, command) {
|
|
|
1422
1561
|
*/
|
|
1423
1562
|
export function sessionAttached(name) {
|
|
1424
1563
|
try {
|
|
1425
|
-
const r = spawnSync(TMUX, ["display", "-p", "-t",
|
|
1564
|
+
const r = spawnSync(TMUX, ["display", "-p", "-t", exactSessionTarget(name), "#{session_attached}"], { encoding: "utf8" });
|
|
1426
1565
|
if (r.status !== 0)
|
|
1427
1566
|
return true; // can't tell → assume attached (never nudge)
|
|
1428
1567
|
const n = Number((r.stdout ?? "").trim());
|
|
@@ -1438,10 +1577,18 @@ export function sessionAttached(name) {
|
|
|
1438
1577
|
* Paste a LITERAL line into a session's main pane, then submit it with a real
|
|
1439
1578
|
* Enter key event. Content reaches `tmux load-buffer -` on stdin, then a named
|
|
1440
1579
|
* buffer is pasted: arbitrary text is never interpreted by a shell and never
|
|
1441
|
-
* appears in a process argv. Enter is a separate key-name event. Used
|
|
1442
|
-
*
|
|
1580
|
+
* appears in a process argv. Enter is a separate key-name event. Used for
|
|
1581
|
+
* interactive throttle nudges (single lines).
|
|
1443
1582
|
*/
|
|
1444
1583
|
export function sendKeysLiteral(name, keys) {
|
|
1584
|
+
if (!loadAndPaste(name, keys, false))
|
|
1585
|
+
return false;
|
|
1586
|
+
const enter = spawnSync(TMUX, ["send-keys", "-t", name, "Enter"], {
|
|
1587
|
+
stdio: "ignore",
|
|
1588
|
+
});
|
|
1589
|
+
return enter.status === 0;
|
|
1590
|
+
}
|
|
1591
|
+
function loadAndPaste(name, keys, bracketed) {
|
|
1445
1592
|
const buffer = `h2a-${process.pid}-${randomUUID()}`;
|
|
1446
1593
|
const loaded = spawnSync(TMUX, ["load-buffer", "-b", buffer, "-"], {
|
|
1447
1594
|
input: keys,
|
|
@@ -1450,15 +1597,114 @@ export function sendKeysLiteral(name, keys) {
|
|
|
1450
1597
|
});
|
|
1451
1598
|
if (loaded.status !== 0)
|
|
1452
1599
|
return false;
|
|
1453
|
-
const pasted = spawnSync(TMUX, [
|
|
1600
|
+
const pasted = spawnSync(TMUX, [
|
|
1601
|
+
"paste-buffer",
|
|
1602
|
+
...(bracketed ? ["-p"] : []),
|
|
1603
|
+
"-b",
|
|
1604
|
+
buffer,
|
|
1605
|
+
"-d",
|
|
1606
|
+
"-t",
|
|
1607
|
+
name,
|
|
1608
|
+
], { stdio: "ignore" });
|
|
1454
1609
|
if (pasted.status !== 0) {
|
|
1455
1610
|
spawnSync(TMUX, ["delete-buffer", "-b", buffer], { stdio: "ignore" });
|
|
1456
1611
|
return false;
|
|
1457
1612
|
}
|
|
1458
|
-
|
|
1613
|
+
return true;
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Paste text as ONE block, WITHOUT submitting it.
|
|
1617
|
+
*
|
|
1618
|
+
* `-p` wraps the payload in bracketed-paste markers. Measured 2026-07-29 on
|
|
1619
|
+
* Claude Code 2.1.220: without `-p`, a multi-line brief is submitted line by
|
|
1620
|
+
* line — line 1 left as its own request while line 2 stayed in the composer.
|
|
1621
|
+
* With `-p` the whole block lands as a single entry that one Enter submits.
|
|
1622
|
+
*/
|
|
1623
|
+
export function pasteLiteralBlock(name, keys) {
|
|
1624
|
+
return loadAndPaste(name, keys, true);
|
|
1625
|
+
}
|
|
1626
|
+
/** Submit whatever the composer currently holds (one real Enter key event). */
|
|
1627
|
+
export function submitPane(name) {
|
|
1628
|
+
const r = spawnSync(TMUX, ["send-keys", "-t", name, "Enter"], {
|
|
1459
1629
|
stdio: "ignore",
|
|
1460
1630
|
});
|
|
1461
|
-
return
|
|
1631
|
+
return r.status === 0;
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Best-effort composer wipe. Ctrl-U only kills to the start of the CURRENT
|
|
1635
|
+
* line, so a multi-line composer needs several — measured: one C-u left a
|
|
1636
|
+
* multi-line brief in place, and retrying the paste then STACKED copies.
|
|
1637
|
+
* Delivery never depends on this succeeding; it types only once.
|
|
1638
|
+
*/
|
|
1639
|
+
export function clearPaneComposer(name) {
|
|
1640
|
+
let ok = true;
|
|
1641
|
+
for (let i = 0; i < 4; i += 1) {
|
|
1642
|
+
const r = spawnSync(TMUX, ["send-keys", "-t", name, "C-u"], {
|
|
1643
|
+
stdio: "ignore",
|
|
1644
|
+
});
|
|
1645
|
+
ok = ok && r.status === 0;
|
|
1646
|
+
}
|
|
1647
|
+
return ok;
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* VISIBLE text of one pane, or undefined when tmux cannot be read — unlike
|
|
1651
|
+
* `capturePane`, which flattens a failure into "". Delivery needs the
|
|
1652
|
+
* difference: an unreadable pane is not an empty one.
|
|
1653
|
+
*/
|
|
1654
|
+
export function capturePaneVisible(pane) {
|
|
1655
|
+
const r = spawnSync(TMUX, ["capture-pane", "-p", "-t", pane], {
|
|
1656
|
+
encoding: "utf8",
|
|
1657
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1658
|
+
});
|
|
1659
|
+
if (r.status !== 0 || r.stdout === undefined)
|
|
1660
|
+
return undefined;
|
|
1661
|
+
return r.stdout;
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* CPU time (ms) of the process tree behind a pane — the liveness signal used to
|
|
1665
|
+
* tell a working agent from one parked on its placeholder.
|
|
1666
|
+
*/
|
|
1667
|
+
export function paneTreeCpuMs(pane) {
|
|
1668
|
+
const pid = localSessionPanePid(pane);
|
|
1669
|
+
if (pid === undefined)
|
|
1670
|
+
return undefined;
|
|
1671
|
+
return readProcessTreeCpuMs(pid, procReaderDeps());
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* The pid actually doing the work behind a pane.
|
|
1675
|
+
*
|
|
1676
|
+
* The pane's own pid is the launch WRAPPER: measured 2026-07-29, it reported 0s
|
|
1677
|
+
* of CPU after 37s while its `codex` child had burned 13s. Anything that judges
|
|
1678
|
+
* a lane by the reported pid alone — a supervisor, a status bar, a conductor —
|
|
1679
|
+
* is reading the wrong process, and gets a false answer in both directions.
|
|
1680
|
+
*/
|
|
1681
|
+
export function paneWorkerPid(pane) {
|
|
1682
|
+
const pid = localSessionPanePid(pane);
|
|
1683
|
+
if (pid === undefined)
|
|
1684
|
+
return undefined;
|
|
1685
|
+
return readWorkerPid(pid, procReaderDeps());
|
|
1686
|
+
}
|
|
1687
|
+
function procReaderDeps() {
|
|
1688
|
+
return {
|
|
1689
|
+
listPids: () => {
|
|
1690
|
+
try {
|
|
1691
|
+
return readdirSync("/proc")
|
|
1692
|
+
.map((entry) => Number.parseInt(entry, 10))
|
|
1693
|
+
.filter((value) => Number.isInteger(value) && value > 0);
|
|
1694
|
+
}
|
|
1695
|
+
catch {
|
|
1696
|
+
return [];
|
|
1697
|
+
}
|
|
1698
|
+
},
|
|
1699
|
+
readStat: (target) => {
|
|
1700
|
+
try {
|
|
1701
|
+
return readFileSync(`/proc/${target}/stat`, "utf8");
|
|
1702
|
+
}
|
|
1703
|
+
catch {
|
|
1704
|
+
return undefined;
|
|
1705
|
+
}
|
|
1706
|
+
},
|
|
1707
|
+
};
|
|
1462
1708
|
}
|
|
1463
1709
|
/**
|
|
1464
1710
|
* Attach the real terminal straight into the Pod's tmux session via
|