@rynx-ai/runtime 0.1.0 → 0.1.10-beta.2

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 (56) hide show
  1. package/dist/claude/executor.d.ts +3 -5
  2. package/dist/claude/executor.js +3 -5
  3. package/dist/claude/native-bridge.d.ts +74 -17
  4. package/dist/claude/native-bridge.js +225 -30
  5. package/dist/claude/native-hook-main.js +327 -38
  6. package/dist/claude/native-hooks.d.ts +3 -2
  7. package/dist/claude/native-hooks.js +15 -6
  8. package/dist/claude/native-integration.d.ts +123 -16
  9. package/dist/claude/native-integration.js +624 -81
  10. package/dist/claude/settings.d.ts +8 -0
  11. package/dist/claude/settings.js +50 -0
  12. package/dist/claude/transcript.d.ts +2 -2
  13. package/dist/claude/transcript.js +14 -3
  14. package/dist/codex/rollout-synth.d.ts +8 -3
  15. package/dist/codex/rollout-synth.js +65 -32
  16. package/dist/codex-app-server/client.d.ts +27 -40
  17. package/dist/codex-app-server/client.js +1134 -99
  18. package/dist/codex-app-server/forwarder.d.ts +36 -10
  19. package/dist/codex-app-server/forwarder.js +146 -28
  20. package/dist/codex-app-server/mapping.d.ts +1 -1
  21. package/dist/codex-app-server/mapping.js +64 -5
  22. package/dist/codex-app-server/protocol.d.ts +269 -4
  23. package/dist/codex-app-server/transport.d.ts +20 -5
  24. package/dist/codex-app-server/transport.js +93 -40
  25. package/dist/codex-app-server/ws-channel.d.ts +3 -3
  26. package/dist/codex-app-server/ws-channel.js +23 -7
  27. package/dist/codex-child-env.js +33 -0
  28. package/dist/codex-home.d.ts +16 -6
  29. package/dist/codex-home.js +46 -15
  30. package/dist/codex-session-store.d.ts +2 -1
  31. package/dist/host.d.ts +38 -38
  32. package/dist/host.js +626 -121
  33. package/dist/index.d.ts +4 -3
  34. package/dist/index.js +1 -1
  35. package/dist/input-resources.d.ts +13 -0
  36. package/dist/input-resources.js +67 -0
  37. package/dist/interactions.d.ts +61 -0
  38. package/dist/interactions.js +236 -0
  39. package/dist/models-catalog.d.ts +5 -13
  40. package/dist/models-catalog.js +60 -9
  41. package/dist/runner/child.d.ts +9 -1
  42. package/dist/runner/child.js +100 -19
  43. package/dist/runner/manager.d.ts +79 -11
  44. package/dist/runner/manager.js +423 -43
  45. package/dist/runner/protocol.d.ts +30 -11
  46. package/dist/runner-main.js +9 -6
  47. package/dist/runtime-status.js +1 -1
  48. package/dist/terminal/claude-tui.d.ts +8 -3
  49. package/dist/terminal/claude-tui.js +6 -2
  50. package/dist/terminal/codex-tui.d.ts +3 -3
  51. package/dist/terminal/codex-tui.js +1 -1
  52. package/dist/terminal/registry.d.ts +1 -1
  53. package/dist/terminal/registry.js +1 -1
  54. package/dist/terminal/tmux.d.ts +6 -6
  55. package/dist/terminal/tmux.js +10 -10
  56. package/package.json +8 -3
@@ -58,7 +58,8 @@ export interface GetAuthStatusResponse {
58
58
  }
59
59
  export type AskForApproval = "untrusted" | "on-failure" | "on-request" | "never";
60
60
  export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";
61
- export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
61
+ /** Open since Codex App Server 0.144; values are advertised by `model/list`. */
62
+ export type ReasoningEffort = string;
62
63
  export interface ThreadStartParams {
63
64
  model?: string | null;
64
65
  modelProvider?: string | null;
@@ -75,13 +76,15 @@ export interface ThreadResumeParams extends ThreadStartParams {
75
76
  /** When true, the resume response omits the thread's `turns` backlog (used to
76
77
  * SUBSCRIBE without re-replaying history). When false/absent, the response
77
78
  * carries `thread.turns[].items[]` — the backfill the forwarder replays for a
78
- * fresh thread's first turn (omnigent's `_replay_resume_response`). */
79
+ * fresh thread's first turn (reference implementation's `_replay_resume_response`). */
79
80
  excludeTurns?: boolean;
80
81
  }
81
82
  /** One turn in a resumed thread's backlog (`thread/resume` response). */
82
83
  export interface ResumedTurn {
83
84
  id?: string;
84
85
  turnId?: string;
86
+ status: TurnStatus;
87
+ error?: unknown;
85
88
  items?: ThreadItem[];
86
89
  }
87
90
  /** The `thread` object a `thread/resume` response carries (id + optional backlog). */
@@ -197,6 +200,20 @@ export interface DynamicToolCallItem extends ThreadItemBase {
197
200
  export interface WebSearchItem extends ThreadItemBase {
198
201
  type: "webSearch";
199
202
  query: string;
203
+ action?: {
204
+ type: "search";
205
+ query: string | null;
206
+ queries: string[] | null;
207
+ } | {
208
+ type: "openPage";
209
+ url: string | null;
210
+ } | {
211
+ type: "findInPage";
212
+ url: string | null;
213
+ pattern: string | null;
214
+ } | {
215
+ type: "other";
216
+ } | null;
200
217
  }
201
218
  export type ThreadItem = UserMessageItem | AgentMessageItem | ReasoningItem | PlanItem | CommandExecutionItem | FileChangeItem | McpToolCallItem | DynamicToolCallItem | WebSearchItem | (ThreadItemBase & Record<string, unknown>);
202
219
  export interface ThreadSummary {
@@ -256,6 +273,11 @@ export interface ModelInfo {
256
273
  hidden?: boolean;
257
274
  isDefault?: boolean;
258
275
  supportsPersonality?: boolean;
276
+ supportedReasoningEfforts?: Array<{
277
+ reasoningEffort: ReasoningEffort;
278
+ description?: string;
279
+ }>;
280
+ defaultReasoningEffort?: ReasoningEffort;
259
281
  [key: string]: unknown;
260
282
  }
261
283
  export interface ModelListResponse {
@@ -398,6 +420,15 @@ export interface ErrorNotificationParams {
398
420
  threadId: string;
399
421
  turnId: string;
400
422
  }
423
+ /** Traex backend-capacity queue update. */
424
+ export interface QueueStatusNotificationParams {
425
+ threadId: string;
426
+ turnId: string;
427
+ /** `queued` | `waiting` | `ready`. */
428
+ state: string;
429
+ position: number | null;
430
+ message: string | null;
431
+ }
401
432
  export type ServerNotification = {
402
433
  method: "thread/started";
403
434
  params: ThreadStartedNotificationParams;
@@ -437,6 +468,9 @@ export type ServerNotification = {
437
468
  } | {
438
469
  method: "thread/tokenUsage/updated";
439
470
  params: ThreadTokenUsageUpdatedNotificationParams;
471
+ } | {
472
+ method: "queue/status";
473
+ params: QueueStatusNotificationParams;
440
474
  } | {
441
475
  method: "error";
442
476
  params: ErrorNotificationParams;
@@ -450,11 +484,26 @@ export interface CommandExecutionRequestApprovalParams {
450
484
  itemId: string;
451
485
  startedAtMs: number;
452
486
  approvalId?: string | null;
487
+ environmentId: string | null;
453
488
  reason?: string | null;
489
+ networkApprovalContext?: NetworkApprovalContext | null;
454
490
  command?: string | null;
455
491
  cwd?: string | null;
456
- }
457
- export type CommandExecutionApprovalDecision = "accept" | "acceptForSession" | "decline" | "cancel";
492
+ commandActions?: Array<Record<string, unknown>> | null;
493
+ additionalPermissions?: AdditionalPermissionProfile | null;
494
+ proposedExecpolicyAmendment?: string[] | null;
495
+ proposedNetworkPolicyAmendments?: NetworkPolicyAmendment[] | null;
496
+ availableDecisions?: CommandExecutionApprovalDecision[] | null;
497
+ }
498
+ export type CommandExecutionApprovalDecision = "accept" | "acceptForSession" | {
499
+ acceptWithExecpolicyAmendment: {
500
+ execpolicy_amendment: string[];
501
+ };
502
+ } | {
503
+ applyNetworkPolicyAmendment: {
504
+ network_policy_amendment: NetworkPolicyAmendment;
505
+ };
506
+ } | "decline" | "cancel";
458
507
  export interface CommandExecutionRequestApprovalResponse {
459
508
  decision: CommandExecutionApprovalDecision;
460
509
  }
@@ -470,3 +519,219 @@ export type FileChangeApprovalDecision = "accept" | "acceptForSession" | "declin
470
519
  export interface FileChangeRequestApprovalResponse {
471
520
  decision: FileChangeApprovalDecision;
472
521
  }
522
+ /** Legacy/experimental app-server approval methods still emitted by current
523
+ * Codex builds alongside the v2 item approval methods. */
524
+ export interface ExecCommandApprovalParams {
525
+ conversationId: string;
526
+ callId: string;
527
+ approvalId: string | null;
528
+ command: string[];
529
+ cwd: string;
530
+ reason: string | null;
531
+ }
532
+ export interface ApplyPatchApprovalParams {
533
+ conversationId: string;
534
+ callId: string;
535
+ fileChanges: Record<string, {
536
+ type: "add" | "delete";
537
+ content: string;
538
+ } | {
539
+ type: "update";
540
+ unified_diff: string;
541
+ move_path: string | null;
542
+ }>;
543
+ reason: string | null;
544
+ grantRoot: string | null;
545
+ }
546
+ export type LegacyReviewDecision = "approved" | "approved_for_session" | "denied" | "timed_out" | "abort";
547
+ export interface LegacyApprovalResponse {
548
+ decision: LegacyReviewDecision;
549
+ }
550
+ /** EXPERIMENTAL request_user_input question advertised by Codex app-server. */
551
+ export interface ToolRequestUserInputQuestion {
552
+ id: string;
553
+ header: string;
554
+ question: string;
555
+ isOther: boolean;
556
+ isSecret: boolean;
557
+ /** Traex extension; absent in Codex 0.144. */
558
+ multiSelect?: boolean;
559
+ options: Array<{
560
+ label: string;
561
+ description: string;
562
+ }> | null;
563
+ }
564
+ export interface ToolRequestUserInputParams {
565
+ threadId: string;
566
+ turnId: string;
567
+ itemId: string;
568
+ questions: ToolRequestUserInputQuestion[];
569
+ /** Codex supplies an optional auto-resolution deadline. */
570
+ autoResolutionMs?: number | null;
571
+ /** Traex 0.200 supplies blocking semantics instead of a deadline. */
572
+ isBlocking?: boolean;
573
+ }
574
+ export interface ToolRequestUserInputResponse {
575
+ answers: Record<string, {
576
+ answers: string[];
577
+ }>;
578
+ }
579
+ export interface McpElicitationConstOption {
580
+ const: string;
581
+ title: string;
582
+ }
583
+ export type McpElicitationPrimitiveSchema = {
584
+ type: "string";
585
+ title?: string;
586
+ description?: string;
587
+ minLength?: number;
588
+ maxLength?: number;
589
+ format?: "email" | "uri" | "date" | "date-time";
590
+ default?: string;
591
+ } | {
592
+ type: "string";
593
+ title?: string;
594
+ description?: string;
595
+ enum: string[];
596
+ enumNames?: string[];
597
+ default?: string;
598
+ } | {
599
+ type: "string";
600
+ title?: string;
601
+ description?: string;
602
+ oneOf: McpElicitationConstOption[];
603
+ default?: string;
604
+ } | {
605
+ type: "array";
606
+ title?: string;
607
+ description?: string;
608
+ minItems?: number;
609
+ maxItems?: number;
610
+ items: {
611
+ type: "string";
612
+ enum: string[];
613
+ } | {
614
+ anyOf: McpElicitationConstOption[];
615
+ };
616
+ default?: string[];
617
+ } | {
618
+ type: "number" | "integer";
619
+ title?: string;
620
+ description?: string;
621
+ minimum?: number;
622
+ maximum?: number;
623
+ default?: number;
624
+ } | {
625
+ type: "boolean";
626
+ title?: string;
627
+ description?: string;
628
+ default?: boolean;
629
+ };
630
+ export interface McpElicitationSchema {
631
+ $schema?: string;
632
+ type: "object";
633
+ properties: Record<string, McpElicitationPrimitiveSchema>;
634
+ required?: string[];
635
+ }
636
+ /** MCP elicitation request. `openai/form` deliberately keeps its schema open. */
637
+ export type McpServerElicitationRequestParams = {
638
+ threadId: string;
639
+ turnId: string | null;
640
+ serverName: string;
641
+ } & ({
642
+ mode: "form";
643
+ message: string;
644
+ requestedSchema: McpElicitationSchema;
645
+ _meta: unknown;
646
+ } | {
647
+ mode: "openai/form";
648
+ message: string;
649
+ requestedSchema: Record<string, unknown>;
650
+ _meta: unknown;
651
+ } | {
652
+ mode: "url";
653
+ message: string;
654
+ url: string;
655
+ elicitationId: string;
656
+ _meta: unknown;
657
+ });
658
+ export interface McpServerElicitationRequestResponse {
659
+ action: "accept" | "decline" | "cancel";
660
+ content: unknown | null;
661
+ _meta: unknown | null;
662
+ }
663
+ export interface AdditionalNetworkPermissions {
664
+ enabled: boolean | null;
665
+ }
666
+ export type FileSystemSpecialPath = {
667
+ kind: "root";
668
+ } | {
669
+ kind: "minimal";
670
+ } | {
671
+ kind: "project_roots";
672
+ subpath: string | null;
673
+ } | {
674
+ kind: "tmpdir";
675
+ } | {
676
+ kind: "slash_tmp";
677
+ } | {
678
+ kind: "unknown";
679
+ path: string;
680
+ subpath: string | null;
681
+ };
682
+ export type FileSystemPath = {
683
+ type: "path";
684
+ path: string;
685
+ } | {
686
+ type: "glob_pattern";
687
+ pattern: string;
688
+ } | {
689
+ type: "special";
690
+ value: FileSystemSpecialPath;
691
+ };
692
+ export interface FileSystemSandboxEntry {
693
+ path: FileSystemPath;
694
+ access: "read" | "write" | "deny";
695
+ }
696
+ export interface AdditionalFileSystemPermissions {
697
+ read: string[] | null;
698
+ write: string[] | null;
699
+ globScanMaxDepth?: number;
700
+ entries?: FileSystemSandboxEntry[];
701
+ }
702
+ export interface AdditionalPermissionProfile {
703
+ network: AdditionalNetworkPermissions | null;
704
+ fileSystem: AdditionalFileSystemPermissions | null;
705
+ }
706
+ export interface NetworkApprovalContext {
707
+ host: string;
708
+ protocol: "http" | "https" | "socks5Tcp" | "socks5Udp";
709
+ }
710
+ export interface NetworkPolicyAmendment {
711
+ host: string;
712
+ action: "allow" | "deny";
713
+ }
714
+ export type RequestPermissionProfile = AdditionalPermissionProfile;
715
+ export interface PermissionsRequestApprovalParams {
716
+ threadId: string;
717
+ turnId: string;
718
+ itemId: string;
719
+ environmentId: string | null;
720
+ startedAtMs: number;
721
+ cwd: string;
722
+ reason: string | null;
723
+ permissions: RequestPermissionProfile;
724
+ }
725
+ export interface PermissionsRequestApprovalResponse {
726
+ permissions: {
727
+ network?: AdditionalNetworkPermissions;
728
+ fileSystem?: AdditionalFileSystemPermissions;
729
+ };
730
+ scope: "turn" | "session";
731
+ strictAutoReview?: boolean;
732
+ }
733
+ /** A different app-server client (usually the native TUI) answered first. */
734
+ export interface ServerRequestResolvedNotificationParams {
735
+ threadId?: string;
736
+ requestId: RequestId;
737
+ }
@@ -1,5 +1,5 @@
1
1
  import { EventEmitter } from "node:events";
2
- import type { SandboxMode } from "./protocol.js";
2
+ import type { RequestId, SandboxMode } from "./protocol.js";
3
3
  export declare function buildAppServerSpawnArgs(sandboxMode: SandboxMode, listen?: string): string[];
4
4
  /** app-server args WITHOUT `--listen` — for a channel that appends its own
5
5
  * transport endpoint (e.g. {@link import("./ws-channel.js").WsRpcChannel}). */
@@ -29,7 +29,7 @@ export interface RpcChannel {
29
29
  /** Establish the channel (connect / await readiness). */
30
30
  start(): Promise<void>;
31
31
  /** Write one framed message (already newline-terminated). */
32
- send(line: string): void;
32
+ send(line: string): void | Promise<void>;
33
33
  /** Register the inbound-line handler (one framed message per call). */
34
34
  onLine(cb: (line: string) => void): void;
35
35
  /** Register the close/error handler. */
@@ -64,8 +64,17 @@ export declare class CodexTransportError extends Error {
64
64
  readonly data?: unknown;
65
65
  constructor(message: string, code?: number, data?: unknown);
66
66
  }
67
- export type ServerRequestHandler = (method: string, params: unknown) => Promise<unknown> | unknown;
67
+ export type ServerRequestHandler = (method: string, params: unknown, requestId: RequestId) => Promise<unknown | typeof NO_SERVER_RESPONSE> | unknown | typeof NO_SERVER_RESPONSE;
68
+ /** A server request was resolved by another app-server client. In that case the
69
+ * local handler must settle without sending a second JSON-RPC response. */
70
+ export declare const NO_SERVER_RESPONSE: unique symbol;
68
71
  export type ServerNotificationHandler = (method: string, params: unknown) => void;
72
+ export type ServerRequestResponseDeliveryHandler = (requestId: RequestId, result: {
73
+ delivered: true;
74
+ } | {
75
+ delivered: false;
76
+ error: Error;
77
+ }) => void;
69
78
  export interface CodexAppServerTransportOptions {
70
79
  /** Default stdio transport: spawns `app-server --listen stdio://`. Mutually
71
80
  * exclusive with {@link channel}; provide exactly one. */
@@ -83,6 +92,10 @@ export interface CodexAppServerTransportOptions {
83
92
  * propagated back to the server.
84
93
  */
85
94
  onNotification?: ServerNotificationHandler;
95
+ /** Reports whether a server-request response was actually handed to the
96
+ * transport. Provider acknowledgement still arrives separately as
97
+ * `serverRequest/resolved`. */
98
+ onServerRequestResponseDelivery?: ServerRequestResponseDeliveryHandler;
86
99
  }
87
100
  interface TransportEvents {
88
101
  exit: [code: number | null, signal: NodeJS.Signals | null];
@@ -109,6 +122,7 @@ export declare class CodexAppServerTransport extends EventEmitter {
109
122
  private readonly logger;
110
123
  private readonly onServerRequest?;
111
124
  private readonly onNotification?;
125
+ private readonly onServerRequestResponseDelivery?;
112
126
  private child;
113
127
  private rl;
114
128
  private stderrRl;
@@ -116,14 +130,14 @@ export declare class CodexAppServerTransport extends EventEmitter {
116
130
  private readonly pending;
117
131
  private startPromise;
118
132
  private exited;
119
- constructor({ spawner, channel, logger, onServerRequest, onNotification, }: CodexAppServerTransportOptions);
133
+ constructor({ spawner, channel, logger, onServerRequest, onNotification, onServerRequestResponseDelivery, }: CodexAppServerTransportOptions);
120
134
  emit<K extends keyof TransportEvents>(event: K, ...args: TransportEvents[K]): boolean;
121
135
  on<K extends keyof TransportEvents>(event: K, listener: (...args: TransportEvents[K]) => void): this;
122
136
  off<K extends keyof TransportEvents>(event: K, listener: (...args: TransportEvents[K]) => void): this;
123
137
  isRunning(): boolean;
124
138
  ensureStarted(): Promise<void>;
125
139
  sendRequest<TResult = unknown>(method: string, params?: unknown): Promise<TResult>;
126
- sendNotification(method: string, params?: unknown): void;
140
+ sendNotification(method: string, params?: unknown): Promise<void>;
127
141
  stop(signal?: NodeJS.Signals): Promise<void>;
128
142
  private startTransport;
129
143
  private startChild;
@@ -134,6 +148,7 @@ export declare class CodexAppServerTransport extends EventEmitter {
134
148
  private handleErrorEnvelope;
135
149
  private handleServerRequest;
136
150
  private respondToServerRequest;
151
+ private notifyServerRequestResponseDelivery;
137
152
  private handleNotification;
138
153
  }
139
154
  export {};
@@ -67,6 +67,9 @@ export class CodexTransportError extends Error {
67
67
  this.data = data;
68
68
  }
69
69
  }
70
+ /** A server request was resolved by another app-server client. In that case the
71
+ * local handler must settle without sending a second JSON-RPC response. */
72
+ export const NO_SERVER_RESPONSE = Symbol("NO_SERVER_RESPONSE");
70
73
  /**
71
74
  * Wraps the Codex `app-server` subprocess and exposes a typed JSON-RPC API
72
75
  * over its stdio. Frames are line-delimited NDJSON (no Content-Length
@@ -88,6 +91,7 @@ export class CodexAppServerTransport extends EventEmitter {
88
91
  logger;
89
92
  onServerRequest;
90
93
  onNotification;
94
+ onServerRequestResponseDelivery;
91
95
  child = null;
92
96
  rl = null;
93
97
  stderrRl = null;
@@ -95,7 +99,7 @@ export class CodexAppServerTransport extends EventEmitter {
95
99
  pending = new Map();
96
100
  startPromise = null;
97
101
  exited = false;
98
- constructor({ spawner, channel, logger = defaultLogger, onServerRequest, onNotification, }) {
102
+ constructor({ spawner, channel, logger = defaultLogger, onServerRequest, onNotification, onServerRequestResponseDelivery, }) {
99
103
  super();
100
104
  if (!spawner && !channel) {
101
105
  throw new Error("CodexAppServerTransport requires a spawner or a channel");
@@ -105,6 +109,7 @@ export class CodexAppServerTransport extends EventEmitter {
105
109
  this.logger = logger;
106
110
  this.onServerRequest = onServerRequest;
107
111
  this.onNotification = onNotification;
112
+ this.onServerRequestResponseDelivery = onServerRequestResponseDelivery;
108
113
  }
109
114
  emit(event, ...args) {
110
115
  return super.emit(event, ...args);
@@ -145,39 +150,46 @@ export class CodexAppServerTransport extends EventEmitter {
145
150
  async sendRequest(method, params) {
146
151
  await this.ensureStarted();
147
152
  const id = this.nextId++;
148
- return await new Promise((resolve, reject) => {
153
+ const envelope = {
154
+ jsonrpc: "2.0",
155
+ id,
156
+ method,
157
+ ...(params === undefined ? {} : { params }),
158
+ };
159
+ let rejectResponse;
160
+ const response = new Promise((resolve, reject) => {
161
+ rejectResponse = reject;
149
162
  this.pending.set(id, {
150
163
  resolve: resolve,
151
164
  reject,
152
165
  method,
153
166
  });
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
167
  });
168
+ try {
169
+ await this.writeLine(envelope);
170
+ }
171
+ catch (error) {
172
+ this.pending.delete(id);
173
+ rejectResponse(error instanceof Error ? error : new Error(String(error)));
174
+ }
175
+ return await response;
168
176
  }
169
- sendNotification(method, params) {
177
+ async sendNotification(method, params) {
170
178
  const envelope = {
171
179
  jsonrpc: "2.0",
172
180
  method,
173
181
  ...(params === undefined ? {} : { params }),
174
182
  };
175
- this.writeLine(envelope);
183
+ await this.writeLine(envelope);
176
184
  }
177
185
  async stop(signal = "SIGTERM") {
178
186
  if (this.channel) {
179
- this.exited = true;
180
187
  await this.channel.stop(signal);
188
+ // Channel implementations may report close asynchronously (or not at all).
189
+ // Run the same exit path synchronously so pending RPCs reject and clients
190
+ // clear pending native interactions before stop() resolves.
191
+ if (!this.exited)
192
+ this.handleExit(null, signal, null);
181
193
  return;
182
194
  }
183
195
  const child = this.child;
@@ -268,25 +280,29 @@ export class CodexAppServerTransport extends EventEmitter {
268
280
  });
269
281
  this.emit("exit", code, signal);
270
282
  }
271
- writeLine(envelope) {
283
+ async writeLine(envelope) {
272
284
  const line = `${JSON.stringify(envelope)}\n`;
273
285
  if (this.channel) {
274
286
  if (this.exited) {
275
287
  throw new CodexTransportError("Codex app-server is not running");
276
288
  }
277
- this.channel.send(line);
289
+ await this.channel.send(line);
278
290
  return;
279
291
  }
280
292
  const child = this.child;
281
293
  if (!child || this.exited) {
282
294
  throw new CodexTransportError("Codex app-server is not running");
283
295
  }
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
- }
296
+ await new Promise((resolve, reject) => {
297
+ const ok = child.stdin.write(line, (error) => {
298
+ if (error)
299
+ reject(error);
300
+ else
301
+ resolve();
302
+ });
303
+ if (!ok)
304
+ this.logger.log({ event: "transport.stdin_backpressure" });
305
+ });
290
306
  }
291
307
  handleLine(line) {
292
308
  if (!line.trim()) {
@@ -352,34 +368,71 @@ export class CodexAppServerTransport extends EventEmitter {
352
368
  }
353
369
  async handleServerRequest(envelope) {
354
370
  if (!this.onServerRequest) {
355
- this.respondToServerRequest(envelope.id, undefined, {
356
- code: -32601,
357
- message: `No handler registered for ${envelope.method}`,
358
- });
371
+ try {
372
+ await this.respondToServerRequest(envelope.id, undefined, {
373
+ code: -32601,
374
+ message: `No handler registered for ${envelope.method}`,
375
+ });
376
+ }
377
+ catch (error) {
378
+ this.logger.log({
379
+ event: "transport.server_request_error_write_failed",
380
+ error: error instanceof Error ? error.message : String(error),
381
+ });
382
+ }
359
383
  return;
360
384
  }
385
+ let result;
361
386
  try {
362
- const result = await this.onServerRequest(envelope.method, envelope.params);
363
- this.respondToServerRequest(envelope.id, result, null);
387
+ result = await this.onServerRequest(envelope.method, envelope.params, envelope.id);
364
388
  }
365
389
  catch (error) {
366
- this.respondToServerRequest(envelope.id, undefined, {
367
- code: -32000,
368
- message: error instanceof Error ? error.message : String(error),
390
+ try {
391
+ await this.respondToServerRequest(envelope.id, undefined, {
392
+ code: -32000,
393
+ message: error instanceof Error ? error.message : String(error),
394
+ });
395
+ }
396
+ catch (writeError) {
397
+ this.logger.log({
398
+ event: "transport.server_request_error_write_failed",
399
+ error: writeError instanceof Error ? writeError.message : String(writeError),
400
+ });
401
+ }
402
+ return;
403
+ }
404
+ if (result === NO_SERVER_RESPONSE)
405
+ return;
406
+ try {
407
+ await this.respondToServerRequest(envelope.id, result, null);
408
+ this.notifyServerRequestResponseDelivery(envelope.id, { delivered: true });
409
+ }
410
+ catch (writeError) {
411
+ const error = writeError instanceof Error ? writeError : new Error(String(writeError));
412
+ this.logger.log({
413
+ event: "transport.server_request_write_failed",
414
+ error: error.message,
415
+ });
416
+ this.notifyServerRequestResponseDelivery(envelope.id, {
417
+ delivered: false,
418
+ error,
369
419
  });
370
420
  }
371
421
  }
372
- respondToServerRequest(id, result, error) {
422
+ async respondToServerRequest(id, result, error) {
373
423
  const envelope = error
374
424
  ? { jsonrpc: "2.0", id, error }
375
425
  : { jsonrpc: "2.0", id, result };
426
+ await this.writeLine(envelope);
427
+ }
428
+ notifyServerRequestResponseDelivery(requestId, result) {
376
429
  try {
377
- this.writeLine(envelope);
430
+ this.onServerRequestResponseDelivery?.(requestId, result);
378
431
  }
379
- catch (writeError) {
432
+ catch (error) {
380
433
  this.logger.log({
381
- event: "transport.server_request_write_failed",
382
- error: writeError instanceof Error ? writeError.message : String(writeError),
434
+ event: "transport.server_request_delivery_handler_failed",
435
+ error: error instanceof Error ? error.message : String(error),
383
436
  });
384
437
  }
385
438
  }
@@ -28,7 +28,7 @@ export declare class WsRpcChannel implements RpcChannel {
28
28
  }) => void): void;
29
29
  isOpen(): boolean;
30
30
  start(): Promise<void>;
31
- send(line: string): void;
31
+ send(line: string): Promise<void>;
32
32
  stop(signal?: NodeJS.Signals): Promise<void>;
33
33
  private emitClose;
34
34
  private connectWithRetry;
@@ -37,7 +37,7 @@ export declare class WsRpcChannel implements RpcChannel {
37
37
  /**
38
38
  * Connect-only {@link RpcChannel}: attaches an ADDITIONAL client to an app-server
39
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
40
+ * is how rynx runs reference implementation's multi-connection codex-native model: the backend
41
41
  * client owns the app-server + drives injection, while a SEPARATE forwarder
42
42
  * connection `thread/resume`s the same thread to subscribe to its item/turn
43
43
  * notifications (verified: codex delivers a thread's items to every connection
@@ -65,7 +65,7 @@ export declare class ExternalWsChannel implements RpcChannel {
65
65
  }) => void): void;
66
66
  isOpen(): boolean;
67
67
  start(): Promise<void>;
68
- send(line: string): void;
68
+ send(line: string): Promise<void>;
69
69
  stop(): Promise<void>;
70
70
  private emitClose;
71
71
  private connect;