@rynx-ai/runtime 0.1.11-beta.4 → 0.1.11-beta.41

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.
Files changed (65) hide show
  1. package/dist/claude/executor.d.ts +19 -5
  2. package/dist/claude/executor.js +56 -12
  3. package/dist/claude/models.d.ts +0 -5
  4. package/dist/claude/models.js +1 -7
  5. package/dist/claude/native-bridge.d.ts +103 -1
  6. package/dist/claude/native-bridge.js +445 -30
  7. package/dist/claude/native-hook-main.js +81 -1
  8. package/dist/claude/native-hooks.js +7 -0
  9. package/dist/claude/native-integration.d.ts +178 -26
  10. package/dist/claude/native-integration.js +1528 -170
  11. package/dist/claude/session-status.d.ts +39 -0
  12. package/dist/claude/session-status.js +163 -0
  13. package/dist/claude/transcript-clone.d.ts +18 -0
  14. package/dist/claude/transcript-clone.js +497 -0
  15. package/dist/claude/transcript.d.ts +27 -4
  16. package/dist/claude/transcript.js +158 -47
  17. package/dist/codex-app-server/client.d.ts +10 -6
  18. package/dist/codex-app-server/client.js +67 -15
  19. package/dist/codex-app-server/forwarder.d.ts +92 -3
  20. package/dist/codex-app-server/forwarder.js +532 -57
  21. package/dist/codex-app-server/mapping.d.ts +3 -6
  22. package/dist/codex-app-server/mapping.js +206 -36
  23. package/dist/codex-app-server/mcp-startup.d.ts +13 -0
  24. package/dist/codex-app-server/mcp-startup.js +63 -0
  25. package/dist/codex-app-server/process-registry.d.ts +36 -0
  26. package/dist/codex-app-server/process-registry.js +320 -0
  27. package/dist/codex-app-server/protocol.d.ts +64 -7
  28. package/dist/codex-app-server/ws-channel.d.ts +7 -0
  29. package/dist/codex-app-server/ws-channel.js +104 -28
  30. package/dist/codex-home.d.ts +35 -3
  31. package/dist/codex-home.js +323 -18
  32. package/dist/codex-session-store.d.ts +23 -0
  33. package/dist/codex-session-store.js +21 -0
  34. package/dist/host.d.ts +103 -46
  35. package/dist/host.js +1988 -634
  36. package/dist/index.d.ts +3 -3
  37. package/dist/index.js +1 -1
  38. package/dist/input-resources.d.ts +4 -0
  39. package/dist/input-resources.js +21 -5
  40. package/dist/models-catalog.d.ts +2 -1
  41. package/dist/models-catalog.js +94 -6
  42. package/dist/runner/child.d.ts +97 -28
  43. package/dist/runner/child.js +1486 -100
  44. package/dist/runner/manager.d.ts +110 -29
  45. package/dist/runner/manager.js +1481 -246
  46. package/dist/runner/protocol.d.ts +212 -24
  47. package/dist/runner/protocol.js +5 -0
  48. package/dist/runner/startup-policy.d.ts +7 -0
  49. package/dist/runner/startup-policy.js +10 -0
  50. package/dist/runner/transport.d.ts +18 -2
  51. package/dist/runner/transport.js +82 -3
  52. package/dist/runner-main.js +8 -3
  53. package/dist/terminal/claude-tui.d.ts +3 -1
  54. package/dist/terminal/claude-tui.js +3 -1
  55. package/dist/terminal/codex-tui.d.ts +4 -0
  56. package/dist/terminal/codex-tui.js +5 -0
  57. package/dist/terminal/control-parser.d.ts +39 -0
  58. package/dist/terminal/control-parser.js +172 -0
  59. package/dist/terminal/registry.d.ts +18 -15
  60. package/dist/terminal/registry.js +44 -23
  61. package/dist/terminal/spool.d.ts +47 -0
  62. package/dist/terminal/spool.js +231 -0
  63. package/dist/terminal/tmux.d.ts +126 -74
  64. package/dist/terminal/tmux.js +807 -211
  65. package/package.json +4 -4
@@ -1,33 +1,43 @@
1
- /** A live attach handle: the pane's bytes flow through `onData`; caller input
2
- * goes through `write`; `resize` reflows the pane; `kill` ends this client
3
- * (not the server). Shape mirrors the node-pty surface the WS bridge needs. */
4
- export interface TerminalAttachment {
5
- onData(listener: (chunk: string) => void): void;
6
- onExit(listener: (info: {
7
- exitCode: number;
1
+ import { type TerminalSpoolRead } from "./spool.js";
2
+ export type TerminalReaderDoneReason = "terminal_exited" | "client_closed" | "backpressure" | "internal";
3
+ export interface TerminalReaderDone {
4
+ reason: TerminalReaderDoneReason;
5
+ finalOffset: number;
6
+ exitCode: number;
7
+ }
8
+ /** Live half of a prepared control attachment. Output is pulled by exact
9
+ * offset; reader completion is published independently of pull activity. */
10
+ export interface PullTerminalAttachment {
11
+ readonly dimensions?: {
12
+ cols: number;
13
+ rows: number;
14
+ };
15
+ readonly readerDone: Promise<TerminalReaderDone>;
16
+ read(offset: number, maxBytes: number): Promise<TerminalSpoolRead>;
17
+ onResize(listener: (dimensions: {
18
+ cols: number;
19
+ rows: number;
8
20
  }) => void): void;
9
- write(data: string): void;
10
- resize(cols: number, rows: number): void;
11
- /** Detach this client. The tmux server + pane keep running. */
21
+ write(data: Uint8Array): Promise<void>;
22
+ resize(cols: number, rows: number): Promise<void>;
12
23
  kill(): void;
13
24
  }
14
- /** Minimal node-pty surface (kept local so this module has no type dep on it). */
15
- interface PtyProcess {
16
- onData(cb: (data: string) => void): void;
17
- onExit(cb: (e: {
18
- exitCode: number;
19
- }) => void): void;
20
- write(data: string): void;
21
- resize(cols: number, rows: number): void;
22
- kill(signal?: string): void;
25
+ /** Capture phase. No tmux control client exists until `start` succeeds. */
26
+ export interface PreparedTerminalAttachment {
27
+ readonly seedBytes: number;
28
+ readonly dimensions?: {
29
+ cols: number;
30
+ rows: number;
31
+ };
32
+ readSeed(offset: number, maxBytes: number): Promise<TerminalSpoolRead>;
33
+ start(): Promise<PullTerminalAttachment>;
34
+ kill(): void;
23
35
  }
24
- type PtySpawn = (file: string, args: string[], opts: {
25
- name: string;
26
- cols: number;
27
- rows: number;
28
- cwd: string;
29
- env: Record<string, string>;
30
- }) => PtyProcess;
36
+ /** Result of probing the inner tmux pane. `unknown` is deliberately distinct
37
+ * from `dead`: a timed-out/unexecutable control command is inconclusive;
38
+ * `dead` requires an explicit dead pane. The lifecycle watcher separately uses
39
+ * capture failure to identify a vanished server/session. */
40
+ export type TerminalLiveness = "alive" | "dead" | "unknown";
31
41
  export interface TmuxTerminalOptions {
32
42
  /** Unique name for the tmux session + socket, e.g. `rynx-<sessionId>-<termId>`. */
33
43
  name: string;
@@ -38,19 +48,67 @@ export interface TmuxTerminalOptions {
38
48
  env?: Record<string, string>;
39
49
  cols?: number;
40
50
  rows?: number;
41
- /** Injectable for tests; defaults to the real node-pty spawn. */
42
- ptySpawn?: PtySpawn;
51
+ /** Rows retained by tmux for reconnect-time history. */
52
+ scrollback?: number;
53
+ /** Allow pane escape sequences to pass through tmux. */
54
+ tmuxAllowPassthrough?: boolean;
55
+ /** Delay the inner command until the first client attaches. */
56
+ tmuxStartOnAttach?: boolean;
57
+ /** Preserve a dead pane for diagnostics until lifecycle cleanup runs. */
58
+ keepAliveAfterExit?: boolean;
43
59
  /** Injectable for tests; defaults to the real `tmux` binary path. */
44
60
  tmuxBin?: string;
45
61
  }
62
+ /** Decode tmux's three-digit octal escapes in a `%output` payload. */
63
+ export declare function unescapeControlOutput(value: Uint8Array): Uint8Array;
64
+ /** Byte-exact control commands for one browser input frame. */
65
+ export declare function hexSendKeysCommands(target: string, data: Uint8Array): Buffer[];
66
+ /** Global tmux argv for a control-mode attach. Exported to pin role semantics. */
67
+ export declare function controlAttachArgs(socketPath: string, role: "owner" | "read-only"): string[];
68
+ export interface TmuxManagedOptionSpec {
69
+ scrollback: number;
70
+ allowPassthrough: boolean;
71
+ keepAliveAfterExit: boolean;
72
+ }
73
+ /** Options applied before pane creation so per-pane values take effect. */
74
+ export declare function tmuxManagedOptionCommands(spec: TmuxManagedOptionSpec): string[][];
75
+ /** Extract the pane size from tmux's `%layout-change` notification. */
76
+ export declare function controlLayoutDimensions(line: Uint8Array): {
77
+ cols: number;
78
+ rows: number;
79
+ } | undefined;
80
+ /** Resolve the exact tmux executable selected by the current Rynx distribution. */
81
+ export declare function resolveTmuxBin(explicit?: string, env?: NodeJS.ProcessEnv): string;
46
82
  /** Deterministic private socket owned by one terminal name. Parent-side
47
83
  * shutdown uses the same mapping when the runner child is too wedged to clean
48
84
  * up its own tmux server. */
49
- export declare function tmuxSocketPath(name: string): string;
50
- /** Kill one private tmux server and verify it no longer answers. Missing
51
- * sockets are already stopped; an existing socket with an unusable tmux
52
- * command is unproven and returns false. */
53
- export declare function terminateTmuxServer(name: string, tmuxBin?: string): boolean;
85
+ export declare function tmuxSocketPath(name: string, ownerPid?: number): string;
86
+ export declare function tmuxInstanceDir(name: string, ownerPid?: number): string;
87
+ /** Read tmux's own output-activity clock for a private terminal window.
88
+ *
89
+ * `#{window_activity}` is an epoch timestamp updated by tmux whenever the pane
90
+ * emits bytes. It is deliberately queried out-of-process rather than inferred
91
+ * from Rynx's forwarded PTY stream: the forwarder/status path is exactly what
92
+ * may have stalled when the idle reaper needs an independent liveness signal.
93
+ * Missing servers, command failures, timeouts, and unparseable output are all
94
+ * treated as unknown (`null`). */
95
+ export declare function tmuxWindowActivityAt(name: string, tmuxBin?: string, ownerPid?: number): Promise<number | null>;
96
+ /** Whether tmux itself reports an attached client for this private terminal.
97
+ * This remains authoritative if a parent-side attachment bookkeeping edge was
98
+ * missed during a transport failure. */
99
+ export declare function tmuxHasAttachedClient(name: string, tmuxBin?: string, ownerPid?: number): Promise<boolean>;
100
+ /** Best-effort close of one private tmux server. Attempt `kill-server`, then
101
+ * retire the private socket regardless of command outcome. The registry has
102
+ * already forgotten the resource, so cleanup failure is diagnostic rather than
103
+ * a second lifecycle state. */
104
+ export declare function terminateTmuxServer(name: string, tmuxBin?: string, ownerPid?: number): boolean;
105
+ /** Reap private terminal servers whose owning runner process is gone. Dirs
106
+ * without an owner marker are deliberately ignored. */
107
+ export declare function reapOrphanedTerminals(opts?: {
108
+ root?: string;
109
+ tmuxBin?: string;
110
+ isProcessAlive?: (pid: number) => boolean;
111
+ }): number;
54
112
  /** Is a usable `tmux` on PATH? Cheap probe for the capability gate. */
55
113
  export declare function isTmuxAvailable(tmuxBin?: string): boolean;
56
114
  export declare class TmuxTerminal {
@@ -62,55 +120,50 @@ export declare class TmuxTerminal {
62
120
  private readonly env;
63
121
  private readonly cols;
64
122
  private readonly rows;
123
+ private readonly scrollback;
124
+ private readonly tmuxAllowPassthrough;
125
+ private readonly tmuxStartOnAttach;
126
+ private readonly keepAliveAfterExit;
65
127
  private readonly tmuxBin;
66
- private readonly injectedSpawn?;
67
128
  private started;
129
+ private lastPaneSnapshot;
130
+ /** Shared by every attachment watcher and by the lifecycle watcher's
131
+ * pane-dead stage. One Terminal must never fan the same tmux control probe
132
+ * out once per attached client. */
133
+ private paneLivenessFlight?;
134
+ /** Shared by lifecycle callers so capture + pane-dead remains one ordered
135
+ * observation per Terminal. */
136
+ private lifecycleLivenessFlight?;
68
137
  constructor(opts: TmuxTerminalOptions);
69
138
  /** tmux argv prefix targeting this terminal's private server. */
70
139
  private base;
71
140
  /** Create the private tmux server + detached session running the inner
72
141
  * command. Idempotent: a second call is a no-op once the session exists. */
73
142
  start(): void;
74
- /**
75
- * Apply reference implementation's tmux option suite to the private server (inner/terminal.py).
76
- * These are NOT cosmetic — several fix real co-drive behavior that the tmux
77
- * defaults break:
78
- * - `mouse on`: the web terminal's wheel scrolls the pane's scrollback (and
79
- * mouse events reach a TUI that requests them). Without it, no scrolling.
80
- * - `extended-keys on` + `csi-u`: tmux forwards Kitty Keyboard Protocol / CSI-u
81
- * keys (Ctrl+C, Shift+Enter, modified keys) that codex/claude TUIs request —
82
- * without it tmux downgrades them and the vendor TUI mis-reads modifiers.
83
- * - `escape-time 0`: kills tmux's default 500 ms wait after ESC, which otherwise
84
- * makes arrow keys / Alt-combos / pasted CSI feel laggy or mis-parse.
85
- * - `prefix None` + `prefix2 None` + unbind the prefix table: the user's
86
- * keystrokes (notably C-b) go to the pane, never tmux — a co-drive terminal
87
- * must not intercept a prefix.
88
- * - `focus-events on`, `allow-passthrough on`, `history-limit`: focus reporting,
89
- * passthrough sequences, scrollback depth.
90
- * - `remain-on-exit on` + `exit-empty off`: keep the dead pane + server after the
91
- * inner CLI exits so its last output stays capturable and `#{pane_dead}` reads
92
- * the exit (liveness probe), instead of the server vanishing.
93
- * - `MouseDown3*` unbinds: no right-click menu to spawn extra panes/windows.
94
- * - `status off`: hide tmux chrome. reference implementation keeps the status line only to show
95
- * a conversation link; rynx has none, so the whole line (and its
96
- * `[main] 0:node*` window list) is hidden.
97
- * `-q`/`-gq`/`-sq` keep an older tmux that lacks an option from failing launch.
98
- * Batched into one invocation with `;` command separators (one spawn).
99
- */
100
- private configureSession;
101
143
  /** Whether the terminal's INNER PROCESS is still running. Probes the pane's
102
144
  * `#{pane_dead}` flag rather than mere session existence: with
103
145
  * `remain-on-exit on` the session/server deliberately outlive the inner CLI's
104
146
  * exit (a dead pane shows tmux's "Pane is dead"), so `has-session` succeeding
105
147
  * no longer implies a live process. Alive only when the session exists AND its
106
- * pane process has not exited. Mirrors reference implementation's `_terminal.is_alive`. */
148
+ * pane process has not exited. */
107
149
  isAlive(): boolean;
108
150
  /** Async pane-liveness probe — MUST NOT block the event loop. The attach
109
151
  * pane-death watcher polls this on an interval; a synchronous `execFileSync`
110
152
  * there stalls the runner child's event loop (freezing the PTY stream → the
111
- * terminal appears "stuck"). reference implementation's `_tmux_session_alive` uses an async
112
- * subprocess + timeout for exactly this reason. */
113
- private isAliveAsync;
153
+ * terminal appears "stuck"). Every command error is `unknown`; only
154
+ * `#{pane_dead}=1` is definitive evidence of `dead`. */
155
+ livenessAsync(): Promise<TerminalLiveness>;
156
+ /** The always-on terminal lifecycle watcher first captures the pane:
157
+ * a control command that ran and reports the target missing is terminal exit;
158
+ * a probe that cannot spawn is inconclusive. If capture succeeds, the normal
159
+ * definitive `pane_dead` probe distinguishes live from exited. */
160
+ lifecycleLivenessAsync(): Promise<TerminalLiveness>;
161
+ /** Compatibility boolean for callers that cannot represent an inconclusive
162
+ * probe. Unknown must remain live so a transient tmux failure cannot tear down
163
+ * a healthy native Session. */
164
+ isAliveAsync(): Promise<boolean>;
165
+ /** PID of the process currently owning the pane. */
166
+ panePid(): number | undefined;
114
167
  /** Type literal text into the pane (agent injection / co-drive from a
115
168
  * non-PTY caller). `-l` sends the text literally rather than as key names. */
116
169
  sendKeys(text: string): void;
@@ -127,11 +180,11 @@ export declare class TmuxTerminal {
127
180
  sendEnter(): void;
128
181
  /** Interrupt the pane's running TUI turn with an Escape key — codex/claude both
129
182
  * cancel an in-flight response on a single Esc ("esc to interrupt"). A key NAME
130
- * (no `-l`) so tmux interprets it. Mirrors reference implementation's `inject_interrupt`. */
183
+ * (no `-l`) so tmux interprets it. */
131
184
  interrupt(): void;
132
185
  /** Clear the current input line before an injection so leftover keystrokes
133
186
  * can't prepend to the pasted draft. `C-a` (Home) + `C-k` (kill-to-end) is
134
- * the safe pair — `C-u` only clears backwards from the cursor (reference implementation). */
187
+ * the safe pair because `C-u` only clears backwards from the cursor. */
135
188
  clearInputLine(): void;
136
189
  /** Send one or more tmux key NAMES (e.g. `Enter`, `C-a`) to the pane. */
137
190
  private sendKeyNames;
@@ -144,15 +197,14 @@ export declare class TmuxTerminal {
144
197
  * that would overflow a `send-keys` argv.
145
198
  */
146
199
  paste(text: string, bufferName?: string): void;
147
- /**
148
- * Attach a client. `role: "read-only"` passes tmux `-r` so the viewer cannot
149
- * type (defense-in-depth on top of the WS bridge dropping input frames).
150
- */
151
- attach(role: "owner" | "read-only", dims?: {
200
+ private execTmuxBuffer;
201
+ /** Prepare connect-time history without creating a live tmux client. */
202
+ prepare(role: "owner" | "read-only", dims?: {
152
203
  cols?: number;
153
204
  rows?: number;
154
- }): Promise<TerminalAttachment>;
205
+ }): Promise<PreparedTerminalAttachment>;
206
+ private controlModeSeedSpool;
207
+ private startPullAttachment;
155
208
  /** Kill the tmux server (ends the session and all attaches). */
156
209
  kill(): void;
157
210
  }
158
- export {};