@rynx-ai/runtime 0.1.11-beta.20 → 0.1.11-beta.21

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.
@@ -49,6 +49,19 @@ export declare function resolveTmuxBin(explicit?: string, env?: NodeJS.ProcessEn
49
49
  * shutdown uses the same mapping when the runner child is too wedged to clean
50
50
  * up its own tmux server. */
51
51
  export declare function tmuxSocketPath(name: string): string;
52
+ /** Read tmux's own output-activity clock for a private terminal window.
53
+ *
54
+ * `#{window_activity}` is an epoch timestamp updated by tmux whenever the pane
55
+ * emits bytes. It is deliberately queried out-of-process rather than inferred
56
+ * from Rynx's forwarded PTY stream: the forwarder/status path is exactly what
57
+ * may have stalled when the idle reaper needs an independent liveness signal.
58
+ * Missing servers, command failures, timeouts, and unparseable output are all
59
+ * treated as unknown (`null`). */
60
+ export declare function tmuxWindowActivityAt(name: string, tmuxBin?: string): Promise<number | null>;
61
+ /** Whether tmux itself reports an attached client for this private terminal.
62
+ * This remains authoritative if a parent-side attachment bookkeeping edge was
63
+ * missed during a transport failure. */
64
+ export declare function tmuxHasAttachedClient(name: string, tmuxBin?: string): Promise<boolean>;
52
65
  /** Kill one private tmux server and verify it no longer answers. Missing
53
66
  * sockets are already stopped; an existing socket with an unusable tmux
54
67
  * command is unproven and returns false. */
@@ -113,6 +126,8 @@ export declare class TmuxTerminal {
113
126
  * terminal appears "stuck"). reference implementation's `_tmux_session_alive` uses an async
114
127
  * subprocess + timeout for exactly this reason. */
115
128
  isAliveAsync(): Promise<boolean>;
129
+ /** PID of the process currently owning the pane. */
130
+ panePid(): number | undefined;
116
131
  /** Type literal text into the pane (agent injection / co-drive from a
117
132
  * non-PTY caller). `-l` sends the text literally rather than as key names. */
118
133
  sendKeys(text: string): void;
@@ -42,6 +42,43 @@ export function tmuxSocketPath(name) {
42
42
  const key = createHash("sha256").update(name).digest("hex").slice(0, 16);
43
43
  return join(tmpdir(), "rynx-term", `${key}.sock`);
44
44
  }
45
+ /** Read tmux's own output-activity clock for a private terminal window.
46
+ *
47
+ * `#{window_activity}` is an epoch timestamp updated by tmux whenever the pane
48
+ * emits bytes. It is deliberately queried out-of-process rather than inferred
49
+ * from Rynx's forwarded PTY stream: the forwarder/status path is exactly what
50
+ * may have stalled when the idle reaper needs an independent liveness signal.
51
+ * Missing servers, command failures, timeouts, and unparseable output are all
52
+ * treated as unknown (`null`). */
53
+ export function tmuxWindowActivityAt(name, tmuxBin = resolveTmuxBin()) {
54
+ const socketPath = tmuxSocketPath(name);
55
+ return new Promise((resolve) => {
56
+ execFile(tmuxBin, ["-S", socketPath, "display-message", "-p", "-t", TMUX_TARGET, "#{window_activity}"], { timeout: 2_000 }, (error, stdout) => {
57
+ if (error) {
58
+ resolve(null);
59
+ return;
60
+ }
61
+ const raw = stdout.toString().trim();
62
+ if (!raw) {
63
+ resolve(null);
64
+ return;
65
+ }
66
+ const value = Number(raw);
67
+ resolve(Number.isFinite(value) ? value : null);
68
+ });
69
+ });
70
+ }
71
+ /** Whether tmux itself reports an attached client for this private terminal.
72
+ * This remains authoritative if a parent-side attachment bookkeeping edge was
73
+ * missed during a transport failure. */
74
+ export function tmuxHasAttachedClient(name, tmuxBin = resolveTmuxBin()) {
75
+ const socketPath = tmuxSocketPath(name);
76
+ return new Promise((resolve) => {
77
+ execFile(tmuxBin, ["-S", socketPath, "list-clients", "-t", TMUX_TARGET, "-F", "#{client_name}"], { timeout: 2_000 }, (error, stdout) => {
78
+ resolve(!error && stdout.toString().split("\n").some((line) => line.trim().length > 0));
79
+ });
80
+ });
81
+ }
45
82
  /** Kill one private tmux server and verify it no longer answers. Missing
46
83
  * sockets are already stopped; an existing socket with an unusable tmux
47
84
  * command is unproven and returns false. */
@@ -281,6 +318,19 @@ export class TmuxTerminal {
281
318
  });
282
319
  });
283
320
  }
321
+ /** PID of the process currently owning the pane. */
322
+ panePid() {
323
+ if (!this.started)
324
+ return undefined;
325
+ try {
326
+ const value = execFileSync(this.tmuxBin, [...this.base(), "list-panes", "-t", TMUX_TARGET, "-F", "#{pane_pid}"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim().split(/\s+/)[0];
327
+ const pid = Number(value);
328
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : undefined;
329
+ }
330
+ catch {
331
+ return undefined;
332
+ }
333
+ }
284
334
  /** Type literal text into the pane (agent injection / co-drive from a
285
335
  * non-PTY caller). `-l` sends the text literally rather than as key names. */
286
336
  sendKeys(text) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/runtime",
3
- "version": "0.1.11-beta.20",
3
+ "version": "0.1.11-beta.21",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -27,7 +27,7 @@
27
27
  "node-pty": "1.2.0-beta.15",
28
28
  "smol-toml": "1.7.1",
29
29
  "ws": "^8.21.0",
30
- "@rynx-ai/core": "0.1.11-beta.20"
30
+ "@rynx-ai/core": "0.1.11-beta.21"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/ws": "^8.18.1"