@rynx-ai/runtime 0.1.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.
Files changed (69) hide show
  1. package/dist/claude/executor.d.ts +17 -0
  2. package/dist/claude/executor.js +28 -0
  3. package/dist/claude/models.d.ts +10 -0
  4. package/dist/claude/models.js +33 -0
  5. package/dist/claude/native-bridge.d.ts +133 -0
  6. package/dist/claude/native-bridge.js +299 -0
  7. package/dist/claude/native-hook-main.d.ts +2 -0
  8. package/dist/claude/native-hook-main.js +74 -0
  9. package/dist/claude/native-hooks.d.ts +41 -0
  10. package/dist/claude/native-hooks.js +73 -0
  11. package/dist/claude/native-integration.d.ts +213 -0
  12. package/dist/claude/native-integration.js +665 -0
  13. package/dist/claude/native-message-display-main.d.ts +2 -0
  14. package/dist/claude/native-message-display-main.js +51 -0
  15. package/dist/claude/native-status-main.d.ts +2 -0
  16. package/dist/claude/native-status-main.js +105 -0
  17. package/dist/claude/status.d.ts +23 -0
  18. package/dist/claude/status.js +118 -0
  19. package/dist/claude/transcript.d.ts +79 -0
  20. package/dist/claude/transcript.js +272 -0
  21. package/dist/claude/trust.d.ts +6 -0
  22. package/dist/claude/trust.js +85 -0
  23. package/dist/codex/rollout-synth.d.ts +37 -0
  24. package/dist/codex/rollout-synth.js +212 -0
  25. package/dist/codex-app-server/client.d.ts +138 -0
  26. package/dist/codex-app-server/client.js +341 -0
  27. package/dist/codex-app-server/forwarder.d.ts +92 -0
  28. package/dist/codex-app-server/forwarder.js +188 -0
  29. package/dist/codex-app-server/mapping.d.ts +19 -0
  30. package/dist/codex-app-server/mapping.js +189 -0
  31. package/dist/codex-app-server/protocol.d.ts +472 -0
  32. package/dist/codex-app-server/protocol.js +12 -0
  33. package/dist/codex-app-server/transport.d.ts +139 -0
  34. package/dist/codex-app-server/transport.js +422 -0
  35. package/dist/codex-app-server/ws-channel.d.ts +72 -0
  36. package/dist/codex-app-server/ws-channel.js +233 -0
  37. package/dist/codex-child-env.d.ts +1 -0
  38. package/dist/codex-child-env.js +27 -0
  39. package/dist/codex-home.d.ts +47 -0
  40. package/dist/codex-home.js +135 -0
  41. package/dist/codex-session-store.d.ts +42 -0
  42. package/dist/codex-session-store.js +126 -0
  43. package/dist/host.d.ts +324 -0
  44. package/dist/host.js +1323 -0
  45. package/dist/index.d.ts +18 -0
  46. package/dist/index.js +17 -0
  47. package/dist/models-catalog.d.ts +18 -0
  48. package/dist/models-catalog.js +27 -0
  49. package/dist/runner/child.d.ts +58 -0
  50. package/dist/runner/child.js +268 -0
  51. package/dist/runner/manager.d.ts +175 -0
  52. package/dist/runner/manager.js +458 -0
  53. package/dist/runner/protocol.d.ts +195 -0
  54. package/dist/runner/protocol.js +41 -0
  55. package/dist/runner/transport.d.ts +36 -0
  56. package/dist/runner/transport.js +72 -0
  57. package/dist/runner-main.d.ts +2 -0
  58. package/dist/runner-main.js +61 -0
  59. package/dist/runtime-status.d.ts +16 -0
  60. package/dist/runtime-status.js +80 -0
  61. package/dist/terminal/claude-tui.d.ts +27 -0
  62. package/dist/terminal/claude-tui.js +13 -0
  63. package/dist/terminal/codex-tui.d.ts +54 -0
  64. package/dist/terminal/codex-tui.js +26 -0
  65. package/dist/terminal/registry.d.ts +42 -0
  66. package/dist/terminal/registry.js +70 -0
  67. package/dist/terminal/tmux.d.ts +150 -0
  68. package/dist/terminal/tmux.js +364 -0
  69. package/package.json +32 -0
@@ -0,0 +1,139 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { SandboxMode } from "./protocol.js";
3
+ export declare function buildAppServerSpawnArgs(sandboxMode: SandboxMode, listen?: string): string[];
4
+ /** app-server args WITHOUT `--listen` — for a channel that appends its own
5
+ * transport endpoint (e.g. {@link import("./ws-channel.js").WsRpcChannel}). */
6
+ export declare function buildAppServerBaseArgs(sandboxMode: SandboxMode): string[];
7
+ export interface TransportLogger {
8
+ log(entry: Record<string, unknown>): void;
9
+ }
10
+ export interface CodexAppServerProcess {
11
+ readonly stdin: NodeJS.WritableStream;
12
+ readonly stdout: NodeJS.ReadableStream;
13
+ readonly stderr: NodeJS.ReadableStream | null;
14
+ kill(signal?: NodeJS.Signals | number): boolean;
15
+ on(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): void;
16
+ on(event: "error", listener: (error: Error) => void): void;
17
+ }
18
+ export interface CodexAppServerProcessSpawner {
19
+ spawn(): CodexAppServerProcess;
20
+ }
21
+ /**
22
+ * A line-delimited JSON-RPC byte channel. Abstracts the transport's I/O so the
23
+ * same framing/dispatch drives either the default stdio child (single client)
24
+ * or a WebSocket connection to an `app-server --listen ws://…` (multi-client,
25
+ * required for the codex-native TUI to co-drive the same thread — see
26
+ * {@link import("./ws-channel.js").WsRpcChannel}).
27
+ */
28
+ export interface RpcChannel {
29
+ /** Establish the channel (connect / await readiness). */
30
+ start(): Promise<void>;
31
+ /** Write one framed message (already newline-terminated). */
32
+ send(line: string): void;
33
+ /** Register the inbound-line handler (one framed message per call). */
34
+ onLine(cb: (line: string) => void): void;
35
+ /** Register the close/error handler. */
36
+ onClose(cb: (info: {
37
+ code: number | null;
38
+ signal: NodeJS.Signals | null;
39
+ error: Error | null;
40
+ }) => void): void;
41
+ stop(signal?: NodeJS.Signals): Promise<void>;
42
+ isOpen(): boolean;
43
+ }
44
+ export declare class DefaultCodexAppServerProcessSpawner implements CodexAppServerProcessSpawner {
45
+ private readonly cliPath;
46
+ private readonly args;
47
+ private readonly extraEnv;
48
+ constructor({ cliPath, args, extraEnv, }: {
49
+ cliPath: string;
50
+ args?: string[];
51
+ /**
52
+ * Extra env merged on top of the allowlisted child env (see
53
+ * {@link createCodexChildEnv}). Used to inject a per-agent retry budget
54
+ * (`OPENAI_MAX_RETRIES` / `OPENAI_TIMEOUT`) into a dedicated app-server.
55
+ * Merged after the allowlist, so it is intentionally exempt from it — only
56
+ * the specific keys the caller computed are added, never arbitrary env.
57
+ */
58
+ extraEnv?: Record<string, string>;
59
+ });
60
+ spawn(): CodexAppServerProcess;
61
+ }
62
+ export declare class CodexTransportError extends Error {
63
+ readonly code: number;
64
+ readonly data?: unknown;
65
+ constructor(message: string, code?: number, data?: unknown);
66
+ }
67
+ export type ServerRequestHandler = (method: string, params: unknown) => Promise<unknown> | unknown;
68
+ export type ServerNotificationHandler = (method: string, params: unknown) => void;
69
+ export interface CodexAppServerTransportOptions {
70
+ /** Default stdio transport: spawns `app-server --listen stdio://`. Mutually
71
+ * exclusive with {@link channel}; provide exactly one. */
72
+ spawner?: CodexAppServerProcessSpawner;
73
+ /** Alternate I/O channel (e.g. a WebSocket to a multi-client app-server). */
74
+ channel?: RpcChannel;
75
+ logger?: TransportLogger;
76
+ /**
77
+ * Called for every server-initiated request. Must return the JSON result.
78
+ * Returning a rejected promise produces a JSON-RPC error response.
79
+ */
80
+ onServerRequest?: ServerRequestHandler;
81
+ /**
82
+ * Called for every server notification. Errors thrown are logged but not
83
+ * propagated back to the server.
84
+ */
85
+ onNotification?: ServerNotificationHandler;
86
+ }
87
+ interface TransportEvents {
88
+ exit: [code: number | null, signal: NodeJS.Signals | null];
89
+ error: [error: Error];
90
+ }
91
+ /**
92
+ * Wraps the Codex `app-server` subprocess and exposes a typed JSON-RPC API
93
+ * over its stdio. Frames are line-delimited NDJSON (no Content-Length
94
+ * headers — matches what the binary emits today).
95
+ *
96
+ * Lifecycle:
97
+ * - `ensureStarted()` spawns the child once and resolves when stdin is
98
+ * usable. Subsequent calls reuse the running child.
99
+ * - `sendRequest(method, params)` returns a typed Promise. Pending requests
100
+ * are rejected if the child exits before responding.
101
+ * - The child exiting unexpectedly marks the transport as down; the next
102
+ * `ensureStarted()` will respawn. Callers that hold long-running
103
+ * subscriptions should listen for the `exit` event.
104
+ */
105
+ export declare class CodexAppServerTransport extends EventEmitter {
106
+ private readonly spawner;
107
+ private readonly channel;
108
+ private channelWired;
109
+ private readonly logger;
110
+ private readonly onServerRequest?;
111
+ private readonly onNotification?;
112
+ private child;
113
+ private rl;
114
+ private stderrRl;
115
+ private nextId;
116
+ private readonly pending;
117
+ private startPromise;
118
+ private exited;
119
+ constructor({ spawner, channel, logger, onServerRequest, onNotification, }: CodexAppServerTransportOptions);
120
+ emit<K extends keyof TransportEvents>(event: K, ...args: TransportEvents[K]): boolean;
121
+ on<K extends keyof TransportEvents>(event: K, listener: (...args: TransportEvents[K]) => void): this;
122
+ off<K extends keyof TransportEvents>(event: K, listener: (...args: TransportEvents[K]) => void): this;
123
+ isRunning(): boolean;
124
+ ensureStarted(): Promise<void>;
125
+ sendRequest<TResult = unknown>(method: string, params?: unknown): Promise<TResult>;
126
+ sendNotification(method: string, params?: unknown): void;
127
+ stop(signal?: NodeJS.Signals): Promise<void>;
128
+ private startTransport;
129
+ private startChild;
130
+ private handleExit;
131
+ private writeLine;
132
+ private handleLine;
133
+ private handleResponse;
134
+ private handleErrorEnvelope;
135
+ private handleServerRequest;
136
+ private respondToServerRequest;
137
+ private handleNotification;
138
+ }
139
+ export {};
@@ -0,0 +1,422 @@
1
+ import { spawn } from "node:child_process";
2
+ import { EventEmitter } from "node:events";
3
+ import readline from "node:readline";
4
+ import { createCodexChildEnv } from "../codex-child-env.js";
5
+ const DEFAULT_SPAWN_ARGS = ["app-server", "--listen", "stdio://"];
6
+ /**
7
+ * Spawn args for `<cli> app-server`, including a codex config override that
8
+ * opens the sandbox network when needed.
9
+ *
10
+ * codex's `workspace-write` sandbox blocks network access by default, and the
11
+ * app-server's per-thread sandbox params do not reliably re-enable it, so we
12
+ * pass codex's NATIVE config override (`-c sandbox_workspace_write.network_access=true`)
13
+ * on the app-server process itself. This lets sandboxed model commands — e.g.
14
+ * skills calling internal HTTP APIs — resolve DNS and reach the network.
15
+ * `read-only` and `danger-full-access` are left untouched (the former has no
16
+ * network by design; the latter is already unrestricted).
17
+ */
18
+ function appServerConfigOverrides(sandboxMode) {
19
+ return sandboxMode === "workspace-write"
20
+ ? ["-c", "sandbox_workspace_write.network_access=true"]
21
+ : [];
22
+ }
23
+ export function buildAppServerSpawnArgs(sandboxMode, listen = "stdio://") {
24
+ return [...appServerConfigOverrides(sandboxMode), "app-server", "--listen", listen];
25
+ }
26
+ /** app-server args WITHOUT `--listen` — for a channel that appends its own
27
+ * transport endpoint (e.g. {@link import("./ws-channel.js").WsRpcChannel}). */
28
+ export function buildAppServerBaseArgs(sandboxMode) {
29
+ return [...appServerConfigOverrides(sandboxMode), "app-server"];
30
+ }
31
+ const defaultLogger = {
32
+ log(entry) {
33
+ console.log(JSON.stringify({
34
+ level: "info",
35
+ type: "codex_app_server",
36
+ ...entry,
37
+ }));
38
+ },
39
+ };
40
+ export class DefaultCodexAppServerProcessSpawner {
41
+ cliPath;
42
+ args;
43
+ extraEnv;
44
+ constructor({ cliPath, args = DEFAULT_SPAWN_ARGS, extraEnv = {}, }) {
45
+ this.cliPath = cliPath;
46
+ this.args = args;
47
+ this.extraEnv = extraEnv;
48
+ }
49
+ spawn() {
50
+ const child = spawn(this.cliPath, this.args, {
51
+ stdio: ["pipe", "pipe", "pipe"],
52
+ env: { ...createCodexChildEnv(process.env), ...this.extraEnv },
53
+ });
54
+ if (!child.stdin || !child.stdout) {
55
+ throw new Error("Codex app-server child process is missing stdio handles");
56
+ }
57
+ return child;
58
+ }
59
+ }
60
+ export class CodexTransportError extends Error {
61
+ code;
62
+ data;
63
+ constructor(message, code = -32000, data) {
64
+ super(message);
65
+ this.name = "CodexTransportError";
66
+ this.code = code;
67
+ this.data = data;
68
+ }
69
+ }
70
+ /**
71
+ * Wraps the Codex `app-server` subprocess and exposes a typed JSON-RPC API
72
+ * over its stdio. Frames are line-delimited NDJSON (no Content-Length
73
+ * headers — matches what the binary emits today).
74
+ *
75
+ * Lifecycle:
76
+ * - `ensureStarted()` spawns the child once and resolves when stdin is
77
+ * usable. Subsequent calls reuse the running child.
78
+ * - `sendRequest(method, params)` returns a typed Promise. Pending requests
79
+ * are rejected if the child exits before responding.
80
+ * - The child exiting unexpectedly marks the transport as down; the next
81
+ * `ensureStarted()` will respawn. Callers that hold long-running
82
+ * subscriptions should listen for the `exit` event.
83
+ */
84
+ export class CodexAppServerTransport extends EventEmitter {
85
+ spawner;
86
+ channel;
87
+ channelWired = false;
88
+ logger;
89
+ onServerRequest;
90
+ onNotification;
91
+ child = null;
92
+ rl = null;
93
+ stderrRl = null;
94
+ nextId = 1;
95
+ pending = new Map();
96
+ startPromise = null;
97
+ exited = false;
98
+ constructor({ spawner, channel, logger = defaultLogger, onServerRequest, onNotification, }) {
99
+ super();
100
+ if (!spawner && !channel) {
101
+ throw new Error("CodexAppServerTransport requires a spawner or a channel");
102
+ }
103
+ this.spawner = spawner ?? null;
104
+ this.channel = channel ?? null;
105
+ this.logger = logger;
106
+ this.onServerRequest = onServerRequest;
107
+ this.onNotification = onNotification;
108
+ }
109
+ emit(event, ...args) {
110
+ return super.emit(event, ...args);
111
+ }
112
+ on(event, listener) {
113
+ return super.on(event, listener);
114
+ }
115
+ off(event, listener) {
116
+ return super.off(event, listener);
117
+ }
118
+ isRunning() {
119
+ if (this.channel) {
120
+ return this.channel.isOpen() && !this.exited;
121
+ }
122
+ return Boolean(this.child) && !this.exited;
123
+ }
124
+ async ensureStarted() {
125
+ if (this.isRunning()) {
126
+ return;
127
+ }
128
+ if (this.startPromise) {
129
+ await this.startPromise;
130
+ return;
131
+ }
132
+ const startPromise = (async () => {
133
+ await this.startTransport();
134
+ })();
135
+ this.startPromise = startPromise;
136
+ try {
137
+ await startPromise;
138
+ }
139
+ finally {
140
+ if (this.startPromise === startPromise) {
141
+ this.startPromise = null;
142
+ }
143
+ }
144
+ }
145
+ async sendRequest(method, params) {
146
+ await this.ensureStarted();
147
+ const id = this.nextId++;
148
+ return await new Promise((resolve, reject) => {
149
+ this.pending.set(id, {
150
+ resolve: resolve,
151
+ reject,
152
+ method,
153
+ });
154
+ const envelope = {
155
+ jsonrpc: "2.0",
156
+ id,
157
+ method,
158
+ ...(params === undefined ? {} : { params }),
159
+ };
160
+ try {
161
+ this.writeLine(envelope);
162
+ }
163
+ catch (error) {
164
+ this.pending.delete(id);
165
+ reject(error instanceof Error ? error : new Error(String(error)));
166
+ }
167
+ });
168
+ }
169
+ sendNotification(method, params) {
170
+ const envelope = {
171
+ jsonrpc: "2.0",
172
+ method,
173
+ ...(params === undefined ? {} : { params }),
174
+ };
175
+ this.writeLine(envelope);
176
+ }
177
+ async stop(signal = "SIGTERM") {
178
+ if (this.channel) {
179
+ this.exited = true;
180
+ await this.channel.stop(signal);
181
+ return;
182
+ }
183
+ const child = this.child;
184
+ if (!child) {
185
+ return;
186
+ }
187
+ try {
188
+ child.kill(signal);
189
+ }
190
+ catch (error) {
191
+ this.logger.log({
192
+ event: "transport.kill_failed",
193
+ error: error instanceof Error ? error.message : String(error),
194
+ });
195
+ }
196
+ }
197
+ async startTransport() {
198
+ if (this.channel) {
199
+ if (!this.channelWired) {
200
+ this.channel.onLine((line) => this.handleLine(line));
201
+ this.channel.onClose(({ code, signal, error }) => this.handleExit(code, signal, error));
202
+ this.channelWired = true;
203
+ }
204
+ await this.channel.start();
205
+ this.exited = false;
206
+ return;
207
+ }
208
+ this.startChild();
209
+ }
210
+ startChild() {
211
+ if (!this.spawner) {
212
+ throw new CodexTransportError("Codex app-server transport has no stdio spawner");
213
+ }
214
+ const child = this.spawner.spawn();
215
+ this.child = child;
216
+ this.exited = false;
217
+ const rl = readline.createInterface({
218
+ input: child.stdout,
219
+ crlfDelay: Infinity,
220
+ });
221
+ rl.on("line", (line) => {
222
+ this.handleLine(line);
223
+ });
224
+ if (child.stderr) {
225
+ const stderrRl = readline.createInterface({
226
+ input: child.stderr,
227
+ crlfDelay: Infinity,
228
+ });
229
+ stderrRl.on("line", (line) => {
230
+ this.logger.log({ event: "transport.stderr", line });
231
+ });
232
+ this.stderrRl = stderrRl;
233
+ }
234
+ child.on("error", (error) => {
235
+ this.logger.log({
236
+ event: "transport.error",
237
+ error: error.message,
238
+ });
239
+ this.emit("error", error);
240
+ this.handleExit(null, null, error);
241
+ });
242
+ child.on("exit", (code, signal) => {
243
+ this.handleExit(code, signal, null);
244
+ });
245
+ this.rl = rl;
246
+ }
247
+ handleExit(code, signal, error) {
248
+ if (this.exited) {
249
+ return;
250
+ }
251
+ this.exited = true;
252
+ this.rl?.close();
253
+ this.rl = null;
254
+ this.stderrRl?.close();
255
+ this.stderrRl = null;
256
+ this.child = null;
257
+ const reason = error?.message ??
258
+ `Codex app-server exited (code=${code ?? "null"}, signal=${signal ?? "null"})`;
259
+ for (const [id, pending] of this.pending) {
260
+ pending.reject(new CodexTransportError(reason));
261
+ this.pending.delete(id);
262
+ }
263
+ this.logger.log({
264
+ event: "transport.exited",
265
+ code,
266
+ signal,
267
+ error: error?.message,
268
+ });
269
+ this.emit("exit", code, signal);
270
+ }
271
+ writeLine(envelope) {
272
+ const line = `${JSON.stringify(envelope)}\n`;
273
+ if (this.channel) {
274
+ if (this.exited) {
275
+ throw new CodexTransportError("Codex app-server is not running");
276
+ }
277
+ this.channel.send(line);
278
+ return;
279
+ }
280
+ const child = this.child;
281
+ if (!child || this.exited) {
282
+ throw new CodexTransportError("Codex app-server is not running");
283
+ }
284
+ const ok = child.stdin.write(line);
285
+ if (!ok) {
286
+ // Don't block — back-pressure on stdin is unlikely with line-sized
287
+ // messages, but we surface it as a log entry if it ever shows up.
288
+ this.logger.log({ event: "transport.stdin_backpressure" });
289
+ }
290
+ }
291
+ handleLine(line) {
292
+ if (!line.trim()) {
293
+ return;
294
+ }
295
+ let parsed;
296
+ try {
297
+ parsed = JSON.parse(line);
298
+ }
299
+ catch (error) {
300
+ this.logger.log({
301
+ event: "transport.invalid_json",
302
+ line,
303
+ error: error instanceof Error ? error.message : String(error),
304
+ });
305
+ return;
306
+ }
307
+ if (isSuccessEnvelope(parsed)) {
308
+ this.handleResponse(parsed);
309
+ return;
310
+ }
311
+ if (isErrorEnvelope(parsed)) {
312
+ this.handleErrorEnvelope(parsed);
313
+ return;
314
+ }
315
+ if (isRequestEnvelope(parsed)) {
316
+ void this.handleServerRequest(parsed);
317
+ return;
318
+ }
319
+ if (isNotificationEnvelope(parsed)) {
320
+ this.handleNotification(parsed);
321
+ return;
322
+ }
323
+ this.logger.log({
324
+ event: "transport.unknown_envelope",
325
+ payload: parsed,
326
+ });
327
+ }
328
+ handleResponse(envelope) {
329
+ const pending = this.pending.get(envelope.id);
330
+ if (!pending) {
331
+ this.logger.log({
332
+ event: "transport.response_without_pending",
333
+ id: envelope.id,
334
+ });
335
+ return;
336
+ }
337
+ this.pending.delete(envelope.id);
338
+ pending.resolve(envelope.result);
339
+ }
340
+ handleErrorEnvelope(envelope) {
341
+ const pending = this.pending.get(envelope.id);
342
+ if (!pending) {
343
+ this.logger.log({
344
+ event: "transport.error_without_pending",
345
+ id: envelope.id,
346
+ error: envelope.error,
347
+ });
348
+ return;
349
+ }
350
+ this.pending.delete(envelope.id);
351
+ pending.reject(new CodexTransportError(envelope.error?.message ?? "Codex app-server returned an error", envelope.error?.code, envelope.error?.data));
352
+ }
353
+ async handleServerRequest(envelope) {
354
+ if (!this.onServerRequest) {
355
+ this.respondToServerRequest(envelope.id, undefined, {
356
+ code: -32601,
357
+ message: `No handler registered for ${envelope.method}`,
358
+ });
359
+ return;
360
+ }
361
+ try {
362
+ const result = await this.onServerRequest(envelope.method, envelope.params);
363
+ this.respondToServerRequest(envelope.id, result, null);
364
+ }
365
+ catch (error) {
366
+ this.respondToServerRequest(envelope.id, undefined, {
367
+ code: -32000,
368
+ message: error instanceof Error ? error.message : String(error),
369
+ });
370
+ }
371
+ }
372
+ respondToServerRequest(id, result, error) {
373
+ const envelope = error
374
+ ? { jsonrpc: "2.0", id, error }
375
+ : { jsonrpc: "2.0", id, result };
376
+ try {
377
+ this.writeLine(envelope);
378
+ }
379
+ catch (writeError) {
380
+ this.logger.log({
381
+ event: "transport.server_request_write_failed",
382
+ error: writeError instanceof Error ? writeError.message : String(writeError),
383
+ });
384
+ }
385
+ }
386
+ handleNotification(envelope) {
387
+ if (!this.onNotification) {
388
+ return;
389
+ }
390
+ try {
391
+ this.onNotification(envelope.method, envelope.params);
392
+ }
393
+ catch (error) {
394
+ this.logger.log({
395
+ event: "transport.notification_handler_failed",
396
+ method: envelope.method,
397
+ error: error instanceof Error ? error.message : String(error),
398
+ });
399
+ }
400
+ }
401
+ }
402
+ function isSuccessEnvelope(value) {
403
+ return (isObject(value) &&
404
+ "id" in value &&
405
+ "result" in value &&
406
+ !("method" in value));
407
+ }
408
+ function isErrorEnvelope(value) {
409
+ return (isObject(value) &&
410
+ "id" in value &&
411
+ "error" in value &&
412
+ !("method" in value));
413
+ }
414
+ function isRequestEnvelope(value) {
415
+ return isObject(value) && "id" in value && "method" in value;
416
+ }
417
+ function isNotificationEnvelope(value) {
418
+ return isObject(value) && "method" in value && !("id" in value);
419
+ }
420
+ function isObject(value) {
421
+ return Boolean(value) && typeof value === "object";
422
+ }
@@ -0,0 +1,72 @@
1
+ import type { RpcChannel, TransportLogger } from "./transport.js";
2
+ export interface WsRpcChannelOptions {
3
+ cliPath: string;
4
+ /** app-server args WITHOUT `--listen` (the channel appends `--listen ws://…`). */
5
+ baseArgs: string[];
6
+ /** Extra env merged over the allowlisted codex child env (retry budget etc.). */
7
+ extraEnv?: Record<string, string>;
8
+ logger?: TransportLogger;
9
+ /** Ms to wait for the app-server to accept a connection. */
10
+ readyTimeoutMs?: number;
11
+ }
12
+ export declare class WsRpcChannel implements RpcChannel {
13
+ private readonly opts;
14
+ private child;
15
+ private ws;
16
+ private lineCb;
17
+ private closeCb;
18
+ private closedEmitted;
19
+ private readonly readyTimeoutMs;
20
+ /** The `ws://IP:PORT` the app-server listens on — pass to the TUI's `--remote`. */
21
+ url: string;
22
+ constructor(opts: WsRpcChannelOptions);
23
+ onLine(cb: (line: string) => void): void;
24
+ onClose(cb: (info: {
25
+ code: number | null;
26
+ signal: NodeJS.Signals | null;
27
+ error: Error | null;
28
+ }) => void): void;
29
+ isOpen(): boolean;
30
+ start(): Promise<void>;
31
+ send(line: string): void;
32
+ stop(signal?: NodeJS.Signals): Promise<void>;
33
+ private emitClose;
34
+ private connectWithRetry;
35
+ private tryConnect;
36
+ }
37
+ /**
38
+ * Connect-only {@link RpcChannel}: attaches an ADDITIONAL client to an app-server
39
+ * someone else already started (a {@link WsRpcChannel}'s `url`) — no spawn. This
40
+ * is how rynx runs omnigent's multi-connection codex-native model: the backend
41
+ * client owns the app-server + drives injection, while a SEPARATE forwarder
42
+ * connection `thread/resume`s the same thread to subscribe to its item/turn
43
+ * notifications (verified: codex delivers a thread's items to every connection
44
+ * subscribed to it, so the forwarder connection sees turns started by the TUI,
45
+ * the backend inject client, and anyone else).
46
+ */
47
+ export declare class ExternalWsChannel implements RpcChannel {
48
+ /** The `ws://IP:PORT` of the already-running app-server to attach to. */
49
+ readonly url: string;
50
+ private ws;
51
+ private lineCb;
52
+ private closeCb;
53
+ private closedEmitted;
54
+ private readonly readyTimeoutMs;
55
+ constructor(
56
+ /** The `ws://IP:PORT` of the already-running app-server to attach to. */
57
+ url: string, opts?: {
58
+ readyTimeoutMs?: number;
59
+ });
60
+ onLine(cb: (line: string) => void): void;
61
+ onClose(cb: (info: {
62
+ code: number | null;
63
+ signal: NodeJS.Signals | null;
64
+ error: Error | null;
65
+ }) => void): void;
66
+ isOpen(): boolean;
67
+ start(): Promise<void>;
68
+ send(line: string): void;
69
+ stop(): Promise<void>;
70
+ private emitClose;
71
+ private connect;
72
+ }