@rynx-ai/runtime 0.1.11-beta.3 → 0.1.11-beta.30

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 (44) 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 +2 -0
  6. package/dist/claude/native-bridge.js +23 -0
  7. package/dist/claude/native-hook-main.js +62 -0
  8. package/dist/claude/native-integration.d.ts +50 -10
  9. package/dist/claude/native-integration.js +262 -37
  10. package/dist/claude/session-status.d.ts +39 -0
  11. package/dist/claude/session-status.js +163 -0
  12. package/dist/claude/transcript.js +27 -17
  13. package/dist/codex-app-server/client.d.ts +10 -6
  14. package/dist/codex-app-server/client.js +67 -15
  15. package/dist/codex-app-server/forwarder.d.ts +92 -3
  16. package/dist/codex-app-server/forwarder.js +509 -56
  17. package/dist/codex-app-server/mapping.d.ts +3 -6
  18. package/dist/codex-app-server/mapping.js +174 -28
  19. package/dist/codex-app-server/mcp-startup.d.ts +13 -0
  20. package/dist/codex-app-server/mcp-startup.js +63 -0
  21. package/dist/codex-app-server/protocol.d.ts +64 -7
  22. package/dist/codex-app-server/ws-channel.js +19 -19
  23. package/dist/codex-home.js +2 -4
  24. package/dist/host.d.ts +64 -21
  25. package/dist/host.js +1330 -441
  26. package/dist/index.d.ts +1 -1
  27. package/dist/input-resources.d.ts +4 -0
  28. package/dist/input-resources.js +21 -5
  29. package/dist/models-catalog.d.ts +2 -1
  30. package/dist/models-catalog.js +94 -6
  31. package/dist/runner/child.d.ts +48 -21
  32. package/dist/runner/child.js +550 -48
  33. package/dist/runner/manager.d.ts +54 -13
  34. package/dist/runner/manager.js +479 -114
  35. package/dist/runner/protocol.d.ts +62 -19
  36. package/dist/runner/protocol.js +5 -0
  37. package/dist/runner/startup-policy.d.ts +7 -0
  38. package/dist/runner/startup-policy.js +10 -0
  39. package/dist/terminal/claude-tui.d.ts +3 -1
  40. package/dist/terminal/claude-tui.js +3 -1
  41. package/dist/terminal/registry.js +3 -2
  42. package/dist/terminal/tmux.d.ts +50 -7
  43. package/dist/terminal/tmux.js +168 -47
  44. package/package.json +4 -3
@@ -6,7 +6,8 @@
6
6
  * (`live.ensure` / `inject` / `live.interrupt`) plus per-thread capabilities and
7
7
  * terminal attachment; the reply channels mirror each request's `reqId`.
8
8
  */
9
- import { AgentRuntimeError, type ResolvedExecutionSnapshot, type RuntimeUserInput, type SessionInteractionResolution, type SessionEvent, type SessionWorkspaceSnapshot } from "@rynx-ai/core";
9
+ import { AgentRuntimeError, type InjectResult, type ResolvedExecutionSnapshot, type RuntimeTurnOptions, type RuntimeUserInput, type SessionCollaborationMode, type SessionInteractionResolution, type SessionEvent, type SessionWorkspaceSnapshot } from "@rynx-ai/core";
10
+ export type { InjectOutcome, InjectResult } from "@rynx-ai/core";
10
11
  import type { ResolveInteractionResult } from "../interactions.js";
11
12
  /** A runtime error flattened for the wire; rebuilt parent-side as `AgentRuntimeError`. */
12
13
  export interface WireError {
@@ -22,16 +23,17 @@ export type TerminalRole = "owner" | "read-only";
22
23
  /** Stable reason for a terminal attach failure. `terminal_not_live` is an
23
24
  * expected, retryable absence; `terminal_open_failed` is an infrastructure error. */
24
25
  export type TerminalOpenErrorCode = "terminal_not_live" | "terminal_open_failed";
25
- /** Outcome of an `inject`. `injected` = the app-server / tmux accepted the turn.
26
- * `notLive` = no live forwarder for this session (caller may use the run path).
27
- * `notReady` = live but not ready within the park window; `failed` = injection
28
- * threw. For BOTH notReady and failed the caller reports an error and MUST NOT
29
- * re-run the turn (re-running would double-write alongside the forwarder).
30
- * Mirrors reference implementation's "inject failure ⇒ response.failed, never re-run locally". */
31
- export type InjectOutcome = "injected" | "notLive" | "notReady" | "failed";
32
26
  /** Parent → child. Terminal PTY bytes ride `term.input` base64-encoded so the
33
27
  * NDJSON line framing stays intact (raw bytes contain newlines). */
34
28
  export type ToChild = {
29
+ t: "mirror.image.ack";
30
+ transferId: string;
31
+ seq: number;
32
+ } | {
33
+ t: "mirror.image.hold";
34
+ transferId: string;
35
+ seq: number;
36
+ } | {
35
37
  t: "cap";
36
38
  capId: string;
37
39
  name: CapName;
@@ -70,8 +72,9 @@ export type ToChild = {
70
72
  }
71
73
  /** Eagerly bring up a session's live codex TUI + forwarder (codex-native), so
72
74
  * its turns mirror to the bus regardless of whether the web has attached the
73
- * terminal. Idempotent; the child replies `live.ready` (echoing `reqId`) once
74
- * the thread is bound. */
75
+ * terminal. Fresh sessions connect the discovery listener before TUI launch;
76
+ * known-thread resumes launch the TUI after backend preload and attach their
77
+ * observer in the background. */
75
78
  | {
76
79
  t: "live.ensure";
77
80
  reqId: string;
@@ -93,6 +96,7 @@ export type ToChild = {
93
96
  t: "inject";
94
97
  reqId: string;
95
98
  localThreadId: string;
99
+ options?: RuntimeTurnOptions;
96
100
  } & ({
97
101
  input: RuntimeUserInput;
98
102
  text?: never;
@@ -100,6 +104,14 @@ export type ToChild = {
100
104
  text: string;
101
105
  input?: never;
102
106
  }))
107
+ /** Apply a Codex-lineage collaboration mode to an already-loaded native
108
+ * thread. This control path never starts a runner or waits for a new Turn. */
109
+ | {
110
+ t: "collaboration.update";
111
+ reqId: string;
112
+ localThreadId: string;
113
+ mode: SessionCollaborationMode;
114
+ }
103
115
  /** Interrupt the session's active turn — the web Stop button. codex: app-server
104
116
  * `turn/interrupt`; claude: an Escape keystroke to the pane. The child replies
105
117
  * `interrupted` (echoing `reqId`). */
@@ -147,6 +159,24 @@ export type FromChild = {
147
159
  t: "mirror";
148
160
  sessionId: string;
149
161
  event: SessionEvent;
162
+ /** Parent-local completion marker used after a chunked image is rebuilt. */
163
+ transferId?: string;
164
+ seq?: number;
165
+ } | {
166
+ t: "mirror.image.begin";
167
+ transferId: string;
168
+ sessionId: string;
169
+ event: SessionEvent;
170
+ totalChars: number;
171
+ } | {
172
+ t: "mirror.image.chunk";
173
+ transferId: string;
174
+ seq: number;
175
+ data: string;
176
+ } | {
177
+ t: "mirror.image.commit";
178
+ transferId: string;
179
+ seq: number;
150
180
  }
151
181
  /** The session rotated to a fresh machine-session (claude `/clear`·`/fork`): the
152
182
  * child re-pointed its mirror to `to` and asks the daemon to alias the runner
@@ -160,24 +190,26 @@ export type FromChild = {
160
190
  execution: ResolvedExecutionSnapshot;
161
191
  parentSessionId?: string;
162
192
  }
163
- /** Result of a `live.ensure`: `ok` once the requested gate is reached (pane
164
- * started for setup, otherwise native thread bound). Echoes `reqId`. */
193
+ /** Result of a `live.ensure`: for Codex-lineage sessions `ok` means backend
194
+ * preload and pane launch completed. A resume observer and fresh-thread
195
+ * discovery may continue in the background. Other Providers retain their
196
+ * own readiness gate. */
165
197
  | {
166
198
  t: "live.ready";
167
199
  reqId: string;
168
200
  localThreadId: string;
169
201
  ok: boolean;
170
- error?: string;
202
+ error?: WireError;
171
203
  }
172
- /** Result of an `inject`: `outcome` distinguishes injected / notLive / notReady /
173
- * failed so the caller never falls back to a second output path on a live
174
- * session. Echoes the request `reqId`. */
204
+ /** Result of an `inject`: `result.outcome` distinguishes a new turn, an
205
+ * active-turn steer, and failures. Successful results also carry the
206
+ * runtime-owned canonical Response identity. Echoes the request `reqId`. */
175
207
  | {
176
208
  t: "injected";
177
209
  reqId: string;
178
210
  localThreadId: string;
179
- outcome: InjectOutcome;
180
- error?: string;
211
+ result: InjectResult;
212
+ error?: WireError;
181
213
  }
182
214
  /** Result of a `live.interrupt`: `ok` when an active turn was interrupted (false
183
215
  * when there was none). Echoes the request `reqId`. */
@@ -186,13 +218,24 @@ export type FromChild = {
186
218
  reqId: string;
187
219
  localThreadId: string;
188
220
  ok: boolean;
189
- error?: string;
221
+ error?: WireError;
222
+ } | {
223
+ t: "collaboration.updated";
224
+ reqId: string;
225
+ localThreadId: string;
226
+ ok: boolean;
227
+ error?: WireError;
190
228
  } | {
191
229
  t: "interaction.resolved";
192
230
  reqId: string;
193
231
  localThreadId: string;
194
232
  result: ResolveInteractionResult;
195
233
  };
234
+ /** Internal runner wire bounds. Generated images are already subject to the
235
+ * Session image byte limit; chunks keep the NDJSON pipe and parser bounded and
236
+ * each chunk is acknowledged before the next is sent. */
237
+ export declare const RUNNER_IMAGE_CHUNK_CHARS: number;
238
+ export declare const RUNNER_IMAGE_MAX_RESULT_CHARS: number;
196
239
  /** Encode a message as a single NDJSON line (newline included). */
197
240
  export declare function encodeMessage(msg: ToChild | FromChild): string;
198
241
  /** Parse one NDJSON line; returns `null` for blank lines or malformed JSON. */
@@ -7,6 +7,11 @@
7
7
  * terminal attachment; the reply channels mirror each request's `reqId`.
8
8
  */
9
9
  import { AgentRuntimeError, } from "@rynx-ai/core";
10
+ /** Internal runner wire bounds. Generated images are already subject to the
11
+ * Session image byte limit; chunks keep the NDJSON pipe and parser bounded and
12
+ * each chunk is acknowledged before the next is sent. */
13
+ export const RUNNER_IMAGE_CHUNK_CHARS = 32 * 1024;
14
+ export const RUNNER_IMAGE_MAX_RESULT_CHARS = 8 * 1024 * 1024;
10
15
  /** Encode a message as a single NDJSON line (newline included). */
11
16
  export function encodeMessage(msg) {
12
17
  return `${JSON.stringify(msg)}\n`;
@@ -0,0 +1,7 @@
1
+ import type { AgentRuntimeId } from "@rynx-ai/core";
2
+ /** Current live providers are native CLI sessions. Keep this explicit so a
3
+ * future non-native provider does not silently inherit their startup budget. */
4
+ export declare function isManagedNativeProvider(provider: AgentRuntimeId | undefined): provider is AgentRuntimeId;
5
+ /** Codex-lineage providers own app-server, thread-discovery, and injection
6
+ * phase errors. Claude instead retains its parent-owned SessionStart gate. */
7
+ export declare function isCodexLineageProvider(provider: AgentRuntimeId | undefined): provider is Exclude<AgentRuntimeId, "claude">;
@@ -0,0 +1,10 @@
1
+ /** Current live providers are native CLI sessions. Keep this explicit so a
2
+ * future non-native provider does not silently inherit their startup budget. */
3
+ export function isManagedNativeProvider(provider) {
4
+ return provider === "codex" || provider === "traex" || provider === "claude";
5
+ }
6
+ /** Codex-lineage providers own app-server, thread-discovery, and injection
7
+ * phase errors. Claude instead retains its parent-owned SessionStart gate. */
8
+ export function isCodexLineageProvider(provider) {
9
+ return provider === "codex" || provider === "traex";
10
+ }
@@ -18,6 +18,8 @@ export interface ClaudeTuiArgs {
18
18
  settingSources?: string;
19
19
  /** Launch model (`--model`); omit to use claude's default. */
20
20
  model?: string;
21
+ /** Native Claude Code effort (`--effort`); omit to use its default. */
22
+ reasoningEffort?: string;
21
23
  /** Agent instructions appended to Claude Code's native system prompt. */
22
24
  appendSystemPrompt?: string;
23
25
  /** Resume a specific prior claude session (`--resume <id>`). */
@@ -36,4 +38,4 @@ export interface ClaudeTuiArgs {
36
38
  * Build the `claude` argv for an interactive, co-drivable TUI:
37
39
  * `[..extra] [--resume <id>] [--model <m>] [--append-system-prompt <text>] --settings <json>`.
38
40
  */
39
- export declare function buildClaudeTuiArgs({ settingsJson, settingSources, model, appendSystemPrompt, resume, forkSession, sessionId, additionalDirs, extraArgs, }: ClaudeTuiArgs): string[];
41
+ export declare function buildClaudeTuiArgs({ settingsJson, settingSources, model, reasoningEffort, appendSystemPrompt, resume, forkSession, sessionId, additionalDirs, extraArgs, }: ClaudeTuiArgs): string[];
@@ -2,7 +2,7 @@
2
2
  * Build the `claude` argv for an interactive, co-drivable TUI:
3
3
  * `[..extra] [--resume <id>] [--model <m>] [--append-system-prompt <text>] --settings <json>`.
4
4
  */
5
- export function buildClaudeTuiArgs({ settingsJson, settingSources, model, appendSystemPrompt, resume, forkSession = false, sessionId, additionalDirs = [], extraArgs = [], }) {
5
+ export function buildClaudeTuiArgs({ settingsJson, settingSources, model, reasoningEffort, appendSystemPrompt, resume, forkSession = false, sessionId, additionalDirs = [], extraArgs = [], }) {
6
6
  const args = [...extraArgs];
7
7
  if (additionalDirs.length > 0)
8
8
  args.push("--add-dir", ...additionalDirs);
@@ -16,6 +16,8 @@ export function buildClaudeTuiArgs({ settingsJson, settingSources, model, append
16
16
  args.push("--session-id", sessionId);
17
17
  if (model)
18
18
  args.push("--model", model);
19
+ if (reasoningEffort)
20
+ args.push("--effort", reasoningEffort);
19
21
  if (appendSystemPrompt)
20
22
  args.push("--append-system-prompt", appendSystemPrompt);
21
23
  args.push("--settings", settingsJson);
@@ -31,13 +31,13 @@ export class TerminalRegistry {
31
31
  return existing;
32
32
  if (existing) {
33
33
  // Dead pane (its TUI exited) — kill the husk + drop it before relaunching.
34
+ this.terminals.delete(id);
34
35
  try {
35
36
  existing.kill();
36
37
  }
37
38
  catch {
38
39
  // already gone — fine
39
40
  }
40
- this.terminals.delete(id);
41
41
  }
42
42
  const terminal = new TmuxTerminal({ ...opts, name: id });
43
43
  terminal.start();
@@ -59,8 +59,9 @@ export class TerminalRegistry {
59
59
  const terminal = this.terminals.get(id);
60
60
  if (!terminal)
61
61
  return;
62
- terminal.kill();
62
+ // reference implementation removes the resource from its registry before best-effort close.
63
63
  this.terminals.delete(id);
64
+ terminal.kill();
64
65
  }
65
66
  /** Kill every terminal (runner shutdown). */
66
67
  closeAll() {
@@ -11,6 +11,11 @@ export interface TerminalAttachment {
11
11
  /** Detach this client. The tmux server + pane keep running. */
12
12
  kill(): void;
13
13
  }
14
+ /** Result of probing the inner tmux pane. `unknown` is deliberately distinct
15
+ * from `dead`: a timed-out/unexecutable control command is inconclusive;
16
+ * `dead` requires an explicit dead pane. The lifecycle watcher separately
17
+ * mirrors reference implementation's capture failure path for a vanished server/session. */
18
+ export type TerminalLiveness = "alive" | "dead" | "unknown";
14
19
  /** Minimal node-pty surface (kept local so this module has no type dep on it). */
15
20
  interface PtyProcess {
16
21
  onData(cb: (data: string) => void): void;
@@ -43,13 +48,30 @@ export interface TmuxTerminalOptions {
43
48
  /** Injectable for tests; defaults to the real `tmux` binary path. */
44
49
  tmuxBin?: string;
45
50
  }
51
+ /** Resolve the exact tmux executable selected by the current Rynx distribution. */
52
+ export declare function resolveTmuxBin(explicit?: string, env?: NodeJS.ProcessEnv): string;
46
53
  /** Deterministic private socket owned by one terminal name. Parent-side
47
54
  * shutdown uses the same mapping when the runner child is too wedged to clean
48
55
  * up its own tmux server. */
49
56
  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. */
57
+ /** Read tmux's own output-activity clock for a private terminal window.
58
+ *
59
+ * `#{window_activity}` is an epoch timestamp updated by tmux whenever the pane
60
+ * emits bytes. It is deliberately queried out-of-process rather than inferred
61
+ * from Rynx's forwarded PTY stream: the forwarder/status path is exactly what
62
+ * may have stalled when the idle reaper needs an independent liveness signal.
63
+ * Missing servers, command failures, timeouts, and unparseable output are all
64
+ * treated as unknown (`null`). */
65
+ export declare function tmuxWindowActivityAt(name: string, tmuxBin?: string): Promise<number | null>;
66
+ /** Whether tmux itself reports an attached client for this private terminal.
67
+ * This remains authoritative if a parent-side attachment bookkeeping edge was
68
+ * missed during a transport failure. */
69
+ export declare function tmuxHasAttachedClient(name: string, tmuxBin?: string): Promise<boolean>;
70
+ /** Best-effort close of one private tmux server. Mirrors reference implementation's bounded
71
+ * `TerminalInstance.close`: attempt `kill-server`, then retire the private
72
+ * socket regardless of command outcome. The registry has already forgotten
73
+ * the resource, so cleanup failure is diagnostic rather than a second
74
+ * lifecycle state. */
53
75
  export declare function terminateTmuxServer(name: string, tmuxBin?: string): boolean;
54
76
  /** Is a usable `tmux` on PATH? Cheap probe for the capability gate. */
55
77
  export declare function isTmuxAvailable(tmuxBin?: string): boolean;
@@ -65,6 +87,14 @@ export declare class TmuxTerminal {
65
87
  private readonly tmuxBin;
66
88
  private readonly injectedSpawn?;
67
89
  private started;
90
+ private lastPaneSnapshot;
91
+ /** Shared by every attachment watcher and by the lifecycle watcher's
92
+ * pane-dead stage. One Terminal must never fan the same tmux control probe
93
+ * out once per attached client. */
94
+ private paneLivenessFlight?;
95
+ /** Shared by lifecycle callers so capture + pane-dead remains one ordered
96
+ * reference implementation-style observation per Terminal. */
97
+ private lifecycleLivenessFlight?;
68
98
  constructor(opts: TmuxTerminalOptions);
69
99
  /** tmux argv prefix targeting this terminal's private server. */
70
100
  private base;
@@ -108,9 +138,20 @@ export declare class TmuxTerminal {
108
138
  /** Async pane-liveness probe — MUST NOT block the event loop. The attach
109
139
  * pane-death watcher polls this on an interval; a synchronous `execFileSync`
110
140
  * 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;
141
+ * terminal appears "stuck"). Exactly like reference implementation's definitive pane probe,
142
+ * every command error is `unknown`; only `#{pane_dead}=1` is `dead`. */
143
+ livenessAsync(): Promise<TerminalLiveness>;
144
+ /** reference implementation's always-on terminal lifecycle watcher first captures the pane:
145
+ * a control command that ran and reports the target missing is terminal exit;
146
+ * a probe that cannot spawn is inconclusive. If capture succeeds, the normal
147
+ * definitive `pane_dead` probe distinguishes live from exited. */
148
+ lifecycleLivenessAsync(): Promise<TerminalLiveness>;
149
+ /** Compatibility boolean for callers that cannot represent an inconclusive
150
+ * probe. Unknown must remain live so a transient tmux failure cannot tear down
151
+ * a healthy native Session. */
152
+ isAliveAsync(): Promise<boolean>;
153
+ /** PID of the process currently owning the pane. */
154
+ panePid(): number | undefined;
114
155
  /** Type literal text into the pane (agent injection / co-drive from a
115
156
  * non-PTY caller). `-l` sends the text literally rather than as key names. */
116
157
  sendKeys(text: string): void;
@@ -146,7 +187,9 @@ export declare class TmuxTerminal {
146
187
  paste(text: string, bufferName?: string): void;
147
188
  /**
148
189
  * 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).
190
+ * type and `ignore-size` so even its initial PTY dimensions cannot resize the
191
+ * owner's pane (defense-in-depth on top of the WS bridge dropping input and
192
+ * resize frames).
150
193
  */
151
194
  attach(role: "owner" | "read-only", dims?: {
152
195
  cols?: number;