@rynx-ai/runtime 0.1.11-beta.5 → 0.1.11-beta.51

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 +460 -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 +1571 -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 +183 -46
  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 +96 -3
  20. package/dist/codex-app-server/forwarder.js +542 -57
  21. package/dist/codex-app-server/mapping.d.ts +3 -6
  22. package/dist/codex-app-server/mapping.js +228 -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 +68 -7
  28. package/dist/codex-app-server/transport.d.ts +2 -2
  29. package/dist/codex-app-server/transport.js +4 -0
  30. package/dist/codex-app-server/ws-channel.d.ts +8 -1
  31. package/dist/codex-app-server/ws-channel.js +113 -28
  32. package/dist/codex-child-env.js +3 -32
  33. package/dist/codex-home.d.ts +35 -3
  34. package/dist/codex-home.js +323 -18
  35. package/dist/codex-session-store.d.ts +23 -0
  36. package/dist/codex-session-store.js +21 -0
  37. package/dist/host.d.ts +106 -48
  38. package/dist/host.js +2024 -640
  39. package/dist/index.d.ts +3 -3
  40. package/dist/index.js +1 -1
  41. package/dist/input-resources.d.ts +4 -0
  42. package/dist/input-resources.js +21 -5
  43. package/dist/models-catalog.js +12 -3
  44. package/dist/runner/child.d.ts +105 -28
  45. package/dist/runner/child.js +1625 -100
  46. package/dist/runner/manager.d.ts +114 -46
  47. package/dist/runner/manager.js +1523 -446
  48. package/dist/runner/protocol.d.ts +212 -24
  49. package/dist/runner/protocol.js +5 -0
  50. package/dist/runner/startup-policy.d.ts +7 -0
  51. package/dist/runner/startup-policy.js +10 -0
  52. package/dist/runner/transport.d.ts +18 -2
  53. package/dist/runner/transport.js +82 -3
  54. package/dist/runner-main.js +8 -3
  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
@@ -0,0 +1,320 @@
1
+ import { mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
2
+ import { basename, dirname, join } from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ import { randomUUID } from "node:crypto";
5
+ import { rynxRuntimeDir } from "@rynx-ai/core";
6
+ const REGISTRY_FILE = "process-registry.json";
7
+ const TAG_PREFIX = "rynx_crash_teardown_tag=";
8
+ const STATE_PREFIX = "rynx_crash_teardown_state_dir=";
9
+ export function runtimeProcessRegistryPath() {
10
+ return join(rynxRuntimeDir(), REGISTRY_FILE);
11
+ }
12
+ export function runtimeProcessTagArg(sessionTag) {
13
+ if (!sessionTag)
14
+ throw new Error("sessionTag must be non-empty");
15
+ return `${TAG_PREFIX}${sessionTag}`;
16
+ }
17
+ export function runtimeProcessArgv0(cliPath, sessionTag) {
18
+ return `${basename(cliPath)} ${runtimeProcessTagArg(sessionTag)}`;
19
+ }
20
+ export function withRuntimeProcessStateArg(baseArgs, stateDir) {
21
+ const args = [...baseArgs];
22
+ const marker = ["-c", `${STATE_PREFIX}${JSON.stringify(stateDir)}`];
23
+ const subcommand = args.indexOf("app-server");
24
+ args.splice(subcommand >= 0 ? subcommand : 0, 0, ...marker);
25
+ return args;
26
+ }
27
+ export function registerRuntimeProcess(entry, registryPath = runtimeProcessRegistryPath()) {
28
+ if (!validEntry(entry))
29
+ return;
30
+ mutateRegistry(registryPath, (entries) => [
31
+ ...entries.filter((candidate) => candidate.sessionTag !== entry.sessionTag),
32
+ entry,
33
+ ]);
34
+ }
35
+ export function unregisterRuntimeProcess(sessionTag, registryPath = runtimeProcessRegistryPath()) {
36
+ if (!sessionTag)
37
+ return;
38
+ mutateRegistry(registryPath, (entries) => entries.filter((entry) => entry.sessionTag !== sessionTag));
39
+ }
40
+ export function reconcileRuntimeProcesses(opts = {}) {
41
+ return reconcileEntries(undefined, opts);
42
+ }
43
+ export function reapRuntimeProcessesForStateDir(stateDir, opts = {}) {
44
+ if (process.platform === "win32" && !opts.processListing)
45
+ return 0;
46
+ const listing = opts.processListing?.() ?? listProcesses();
47
+ if (!listing)
48
+ return 0;
49
+ const currentPgid = opts.currentPgid?.() ?? processGroupId(process.pid);
50
+ const signalGroup = opts.signalGroup ?? signalProcessGroup;
51
+ const childAlive = opts.childAlive ?? isProcessAlive;
52
+ const graceMs = opts.graceMs ?? 1_500;
53
+ const victims = new Map();
54
+ for (const line of listing.split("\n")) {
55
+ const match = /^\s*(\d+)\s+(\d+)\s+([\s\S]+)$/.exec(line);
56
+ if (!match)
57
+ continue;
58
+ const pid = Number.parseInt(match[1], 10);
59
+ const pgid = Number.parseInt(match[2], 10);
60
+ const command = match[3];
61
+ if (!command.includes(stateDir) || !command.includes("app-server"))
62
+ continue;
63
+ if (pid === process.pid || pgid <= 0 || pgid === currentPgid)
64
+ continue;
65
+ victims.set(pid, pgid);
66
+ }
67
+ if (victims.size === 0)
68
+ return 0;
69
+ for (const pgid of new Set(victims.values()))
70
+ signalGroup(pgid, "SIGTERM");
71
+ const deadline = Date.now() + graceMs;
72
+ while (Date.now() < deadline && [...victims.keys()].some(childAlive)) {
73
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
74
+ }
75
+ for (const [pid, pgid] of victims) {
76
+ if (childAlive(pid))
77
+ signalGroup(pgid, "SIGKILL");
78
+ }
79
+ return victims.size;
80
+ }
81
+ function reconcileEntries(stateDir, opts) {
82
+ const registryPath = opts.registryPath ?? runtimeProcessRegistryPath();
83
+ const ownerAlive = opts.ownerAlive ?? isProcessAlive;
84
+ const ownerIdentity = opts.ownerIdentity ?? runtimeProcessOwnerIdentity;
85
+ const childAlive = opts.childAlive ?? isProcessAlive;
86
+ const commandLine = opts.commandLine ?? processCommandLine;
87
+ const terminateGroup = opts.terminateGroup ?? terminateProcessGroup;
88
+ let reaped = 0;
89
+ mutateRegistry(registryPath, (entries) => {
90
+ const survivors = [];
91
+ for (const entry of entries) {
92
+ if (stateDir !== undefined && entry.stateDir !== stateDir) {
93
+ survivors.push(entry);
94
+ continue;
95
+ }
96
+ if (ownerAlive(entry.ownerPid)) {
97
+ const currentOwnerIdentity = ownerIdentity(entry.ownerPid);
98
+ // Old registry entries have no start identity. Keep the conservative
99
+ // PID-only behavior for those entries; every newly registered process
100
+ // is protected against PID reuse.
101
+ if (!entry.ownerIdentity ||
102
+ !currentOwnerIdentity ||
103
+ currentOwnerIdentity === entry.ownerIdentity) {
104
+ survivors.push(entry);
105
+ continue;
106
+ }
107
+ }
108
+ if (!childAlive(entry.pid))
109
+ continue;
110
+ if (!commandLine(entry.pid).includes(runtimeProcessTagArg(entry.sessionTag)))
111
+ continue;
112
+ if (!terminateGroup(entry.pgid)) {
113
+ survivors.push(entry);
114
+ continue;
115
+ }
116
+ reaped += 1;
117
+ }
118
+ return survivors;
119
+ });
120
+ return reaped;
121
+ }
122
+ function validEntry(entry) {
123
+ return Number.isSafeInteger(entry.pid) && entry.pid > 0 &&
124
+ Number.isSafeInteger(entry.pgid) && entry.pgid > 0 &&
125
+ Number.isSafeInteger(entry.ownerPid) && entry.ownerPid > 0 &&
126
+ (entry.ownerIdentity === undefined ||
127
+ (typeof entry.ownerIdentity === "string" && entry.ownerIdentity.length > 0)) &&
128
+ Boolean(entry.sessionTag) && Boolean(entry.stateDir);
129
+ }
130
+ function readRegistry(path) {
131
+ let value;
132
+ try {
133
+ value = JSON.parse(readFileSync(path, "utf8"));
134
+ }
135
+ catch {
136
+ return [];
137
+ }
138
+ if (!Array.isArray(value))
139
+ return [];
140
+ return value.filter((entry) => typeof entry === "object" && entry !== null && validEntry(entry));
141
+ }
142
+ function mutateRegistry(path, mutation) {
143
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
144
+ const lockDir = `${path}.lock`;
145
+ const deadline = Date.now() + 2_000;
146
+ while (true) {
147
+ try {
148
+ mkdirSync(lockDir, { mode: 0o700 });
149
+ writeFileSync(join(lockDir, "owner.pid"), JSON.stringify({
150
+ pid: process.pid,
151
+ identity: runtimeProcessOwnerIdentity(process.pid),
152
+ }), { mode: 0o600 });
153
+ break;
154
+ }
155
+ catch (error) {
156
+ if (error.code !== "EEXIST")
157
+ return;
158
+ let ownerPid = 0;
159
+ let ownerIdentity = "";
160
+ try {
161
+ const rawOwner = readFileSync(join(lockDir, "owner.pid"), "utf8");
162
+ try {
163
+ const parsed = JSON.parse(rawOwner);
164
+ ownerPid = typeof parsed.pid === "number" ? parsed.pid : 0;
165
+ ownerIdentity = typeof parsed.identity === "string" ? parsed.identity : "";
166
+ }
167
+ catch {
168
+ // Backward-compatible recovery for a lock written by the earlier
169
+ // PID-only implementation.
170
+ ownerPid = Number.parseInt(rawOwner, 10);
171
+ }
172
+ }
173
+ catch {
174
+ // A creator may be between mkdir and marker write; the bounded retry handles it.
175
+ }
176
+ const currentOwnerIdentity = ownerPid > 0
177
+ ? runtimeProcessOwnerIdentity(ownerPid)
178
+ : "";
179
+ const staleKnownOwner = ownerPid > 0 && (!isProcessAlive(ownerPid) ||
180
+ Boolean(ownerIdentity && currentOwnerIdentity && ownerIdentity !== currentOwnerIdentity));
181
+ let staleUnownedLock = false;
182
+ if (ownerPid <= 0) {
183
+ try {
184
+ staleUnownedLock = Date.now() - statSync(lockDir).mtimeMs >= 1_000;
185
+ }
186
+ catch {
187
+ staleUnownedLock = true;
188
+ }
189
+ }
190
+ if (staleKnownOwner || staleUnownedLock) {
191
+ rmSync(lockDir, { recursive: true, force: true });
192
+ continue;
193
+ }
194
+ if (Date.now() >= deadline)
195
+ return;
196
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
197
+ }
198
+ }
199
+ try {
200
+ const entries = mutation(readRegistry(path));
201
+ const temp = `${path}.${process.pid}.${randomUUID()}.tmp`;
202
+ writeFileSync(temp, `${JSON.stringify(entries)}\n`, { mode: 0o600 });
203
+ renameSync(temp, path);
204
+ }
205
+ finally {
206
+ rmSync(lockDir, { recursive: true, force: true });
207
+ }
208
+ }
209
+ function isProcessAlive(pid) {
210
+ try {
211
+ process.kill(pid, 0);
212
+ return true;
213
+ }
214
+ catch (error) {
215
+ return error.code === "EPERM";
216
+ }
217
+ }
218
+ /** Return an OS process-start identity used to distinguish PID reuse. */
219
+ export function runtimeProcessOwnerIdentity(pid) {
220
+ if (!Number.isSafeInteger(pid) || pid <= 0)
221
+ return "";
222
+ if (process.platform === "linux") {
223
+ try {
224
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
225
+ const commandEnd = stat.lastIndexOf(")");
226
+ if (commandEnd >= 0) {
227
+ // Fields after ')' begin at proc field 3; starttime is field 22.
228
+ const startTicks = stat.slice(commandEnd + 2).trim().split(/\s+/)[19];
229
+ if (startTicks)
230
+ return `linux:${startTicks}`;
231
+ }
232
+ }
233
+ catch {
234
+ return "";
235
+ }
236
+ }
237
+ try {
238
+ const startedAt = execFileSync("ps", ["-p", String(pid), "-ww", "-o", "lstart="], {
239
+ encoding: "utf8",
240
+ timeout: 2_000,
241
+ stdio: ["ignore", "pipe", "ignore"],
242
+ }).trim();
243
+ return startedAt ? `${process.platform}:${startedAt}` : "";
244
+ }
245
+ catch {
246
+ return "";
247
+ }
248
+ }
249
+ function processCommandLine(pid) {
250
+ if (process.platform === "linux") {
251
+ try {
252
+ return readFileSync(`/proc/${pid}/cmdline`)
253
+ .toString("utf8")
254
+ .replaceAll("\0", " ");
255
+ }
256
+ catch {
257
+ // Fall through to ps for macOS and restricted procfs environments.
258
+ }
259
+ }
260
+ try {
261
+ return execFileSync("ps", ["-p", String(pid), "-ww", "-o", "command="], {
262
+ encoding: "utf8",
263
+ timeout: 2_000,
264
+ stdio: ["ignore", "pipe", "ignore"],
265
+ }).trim();
266
+ }
267
+ catch {
268
+ return "";
269
+ }
270
+ }
271
+ function listProcesses() {
272
+ try {
273
+ return execFileSync("ps", ["-axww", "-o", "pid=,pgid=,command="], {
274
+ encoding: "utf8",
275
+ timeout: 5_000,
276
+ stdio: ["ignore", "pipe", "ignore"],
277
+ });
278
+ }
279
+ catch {
280
+ return "";
281
+ }
282
+ }
283
+ function processGroupId(pid) {
284
+ try {
285
+ const value = execFileSync("ps", ["-p", String(pid), "-o", "pgid="], {
286
+ encoding: "utf8",
287
+ timeout: 2_000,
288
+ stdio: ["ignore", "pipe", "ignore"],
289
+ });
290
+ return Number.parseInt(value.trim(), 10) || -1;
291
+ }
292
+ catch {
293
+ return -1;
294
+ }
295
+ }
296
+ function signalProcessGroup(pgid, signal) {
297
+ if (process.platform === "win32")
298
+ return false;
299
+ try {
300
+ process.kill(-pgid, signal);
301
+ return true;
302
+ }
303
+ catch (error) {
304
+ return error.code === "ESRCH";
305
+ }
306
+ }
307
+ function terminateProcessGroup(pgid) {
308
+ if (process.platform === "win32")
309
+ return false;
310
+ try {
311
+ process.kill(-pgid, "SIGTERM");
312
+ return true;
313
+ }
314
+ catch (error) {
315
+ return error.code === "ESRCH";
316
+ }
317
+ }
318
+ export function newRuntimeProcessTag() {
319
+ return `native-${randomUUID().replaceAll("-", "")}`;
320
+ }
@@ -56,6 +56,21 @@ export interface GetAuthStatusResponse {
56
56
  authToken: string | null;
57
57
  requiresOpenaiAuth: boolean;
58
58
  }
59
+ /** Read the Provider's effective configuration through the app-server rather
60
+ * than parsing a runtime-specific config file (Traex has used both YAML and
61
+ * TOML across releases). `cwd` includes project-scoped configuration layers. */
62
+ export interface ConfigReadParams {
63
+ includeLayers: boolean;
64
+ cwd?: string | null;
65
+ }
66
+ export interface ConfigReadResponse {
67
+ config: {
68
+ model: string | null;
69
+ model_reasoning_effort: ReasoningEffort | null;
70
+ [key: string]: unknown;
71
+ };
72
+ [key: string]: unknown;
73
+ }
59
74
  export type AskForApproval = "untrusted" | "on-failure" | "on-request" | "never";
60
75
  export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";
61
76
  export type SandboxPolicy = {
@@ -87,6 +102,16 @@ export interface PermissionProfileModificationParams {
87
102
  export type PermissionSelection = string | PermissionProfileSelectionParams;
88
103
  /** Open since Codex App Server 0.144; values are advertised by `model/list`. */
89
104
  export type ReasoningEffort = string;
105
+ export type CollaborationModeKind = "plan" | "default";
106
+ /** Full Codex-lineage mode snapshot required by the App Server wire protocol. */
107
+ export interface CollaborationMode {
108
+ mode: CollaborationModeKind;
109
+ settings: {
110
+ model: string;
111
+ reasoning_effort: ReasoningEffort | null;
112
+ developer_instructions: string | null;
113
+ };
114
+ }
90
115
  export interface ThreadStartParams {
91
116
  model?: string | null;
92
117
  modelProvider?: string | null;
@@ -102,14 +127,18 @@ export interface ThreadStartParams {
102
127
  baseInstructions?: string | null;
103
128
  developerInstructions?: string | null;
104
129
  ephemeral?: boolean | null;
130
+ /** Native UI intent for a context-clearing fresh thread. */
131
+ sessionStartSource?: "clear" | string;
105
132
  }
106
133
  export interface ThreadResumeParams extends ThreadStartParams {
107
134
  threadId: string;
108
- /** When true, the resume response omits the thread's `turns` backlog (used to
109
- * SUBSCRIBE without re-replaying history). When false/absent, the response
110
- * carries `thread.turns[].items[]` — the backfill the forwarder replays for a
111
- * fresh thread's first turn (reference implementation's `_replay_resume_response`). */
135
+ /** Suppress rollout history when the caller only needs to load/subscribe. */
112
136
  excludeTurns?: boolean;
137
+ initialTurnsPage?: {
138
+ limit?: number | null;
139
+ sortDirection?: "asc" | "desc" | null;
140
+ itemsView?: "notLoaded" | "summary" | "full" | null;
141
+ } | null;
113
142
  }
114
143
  /** One turn in a resumed thread's backlog (`thread/resume` response). */
115
144
  export interface ResumedTurn {
@@ -125,6 +154,13 @@ export interface ResumedThread {
125
154
  turns?: ResumedTurn[];
126
155
  [key: string]: unknown;
127
156
  }
157
+ /** Settings selected by the Provider while starting or resuming a thread.
158
+ * Older app-server builds may omit them, so the bridge treats them as optional
159
+ * and falls back to `config/read` only for a fresh, not-yet-created thread. */
160
+ export interface ThreadRuntimeSettings {
161
+ model?: string;
162
+ reasoningEffort?: ReasoningEffort | null;
163
+ }
128
164
  export interface ThreadDescriptor {
129
165
  id: string;
130
166
  cwd: string;
@@ -155,6 +191,7 @@ export interface TurnStartParams {
155
191
  permissions?: PermissionSelection | null;
156
192
  model?: string | null;
157
193
  effort?: ReasoningEffort | null;
194
+ collaborationMode?: CollaborationMode | null;
158
195
  }
159
196
  export interface TurnInterruptParams {
160
197
  threadId: string;
@@ -169,7 +206,7 @@ export interface TurnSteerParams {
169
206
  expectedTurnId: string;
170
207
  input: UserInput[];
171
208
  }
172
- export type TurnStatus = "completed" | "interrupted" | "failed" | "inProgress";
209
+ export type TurnStatus = "completed" | "interrupted" | "cancelled" | "canceled" | "failed" | "errored" | "inProgress";
173
210
  export interface TurnPlanStep {
174
211
  step: string;
175
212
  status: "pending" | "inProgress" | "completed";
@@ -252,7 +289,20 @@ export interface WebSearchItem extends ThreadItemBase {
252
289
  type: "other";
253
290
  } | null;
254
291
  }
255
- export type ThreadItem = UserMessageItem | AgentMessageItem | ReasoningItem | PlanItem | CommandExecutionItem | FileChangeItem | McpToolCallItem | DynamicToolCallItem | WebSearchItem | (ThreadItemBase & Record<string, unknown>);
292
+ export interface ImageGenerationItem extends ThreadItemBase {
293
+ type: "imageGeneration";
294
+ status: string;
295
+ revisedPrompt: string | null;
296
+ /** Base64 image bytes supplied when no durable saved path is available. */
297
+ result: string;
298
+ /** Codex App Server guarantees this is absolute when present. */
299
+ savedPath?: string;
300
+ }
301
+ export interface ImageViewItem extends ThreadItemBase {
302
+ type: "imageView";
303
+ path: string;
304
+ }
305
+ export type ThreadItem = UserMessageItem | AgentMessageItem | ReasoningItem | PlanItem | CommandExecutionItem | FileChangeItem | McpToolCallItem | DynamicToolCallItem | WebSearchItem | ImageGenerationItem | ImageViewItem | (ThreadItemBase & Record<string, unknown>);
256
306
  export interface ThreadSummary {
257
307
  id?: string;
258
308
  threadId?: string;
@@ -302,7 +352,7 @@ export interface ThreadSettingsUpdateParams {
302
352
  serviceTier?: string | null;
303
353
  effort?: ReasoningEffort | null;
304
354
  summary?: string | null;
305
- collaborationMode?: Record<string, unknown> | null;
355
+ collaborationMode?: CollaborationMode | null;
306
356
  personality?: string | null;
307
357
  }
308
358
  export interface ModelListParams {
@@ -476,6 +526,14 @@ export interface QueueStatusNotificationParams {
476
526
  export type ServerNotification = {
477
527
  method: "thread/started";
478
528
  params: ThreadStartedNotificationParams;
529
+ } | {
530
+ method: "thread/settings/updated";
531
+ params: {
532
+ threadId: string;
533
+ threadSettings: {
534
+ collaborationMode?: CollaborationMode | null;
535
+ } & Record<string, unknown>;
536
+ };
479
537
  } | {
480
538
  method: "turn/started";
481
539
  params: TurnStartedNotificationParams;
@@ -497,6 +555,9 @@ export type ServerNotification = {
497
555
  } | {
498
556
  method: "item/agentMessage/delta";
499
557
  params: AgentMessageDeltaNotificationParams;
558
+ } | {
559
+ method: "item/plan/delta";
560
+ params: AgentMessageDeltaNotificationParams;
500
561
  } | {
501
562
  method: "item/reasoning/summaryTextDelta";
502
563
  params: ReasoningSummaryTextDeltaNotificationParams;
@@ -49,10 +49,10 @@ export declare class DefaultCodexAppServerProcessSpawner implements CodexAppServ
49
49
  cliPath: string;
50
50
  args?: string[];
51
51
  /**
52
- * Extra env merged on top of the allowlisted child env (see
52
+ * Extra env merged on top of the inherited child env (see
53
53
  * {@link createCodexChildEnv}). Used to inject a per-agent retry budget
54
54
  * (`OPENAI_MAX_RETRIES` / `OPENAI_TIMEOUT`) into a dedicated app-server.
55
- * Merged after the allowlist, so it is intentionally exempt from it — only
55
+ * Merged after the inherited environment, so it takes precedence — only
56
56
  * the specific keys the caller computed are added, never arbitrary env.
57
57
  */
58
58
  extraEnv?: Record<string, string>;
@@ -1,6 +1,8 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { EventEmitter } from "node:events";
3
3
  import readline from "node:readline";
4
+ import { join } from "node:path";
5
+ import { teeDiagnosticStream } from "@rynx-ai/core";
4
6
  import { createCodexChildEnv } from "../codex-child-env.js";
5
7
  const DEFAULT_SPAWN_ARGS = ["app-server", "--listen", "stdio://"];
6
8
  /**
@@ -51,6 +53,8 @@ export class DefaultCodexAppServerProcessSpawner {
51
53
  stdio: ["pipe", "pipe", "pipe"],
52
54
  env: { ...createCodexChildEnv(process.env), ...this.extraEnv },
53
55
  });
56
+ if (process.env.RYNX_RUNTIME_LOG_DIR)
57
+ teeDiagnosticStream(child.stderr, join(process.env.RYNX_RUNTIME_LOG_DIR, "app-server.stderr.log"));
54
58
  if (!child.stdin || !child.stdout) {
55
59
  throw new Error("Codex app-server child process is missing stdio handles");
56
60
  }
@@ -3,11 +3,13 @@ export interface WsRpcChannelOptions {
3
3
  cliPath: string;
4
4
  /** app-server args WITHOUT `--listen` (the channel appends `--listen ws://…`). */
5
5
  baseArgs: string[];
6
- /** Extra env merged over the allowlisted codex child env (retry budget etc.). */
6
+ /** Extra env merged over the inherited codex child env (retry budget etc.). */
7
7
  extraEnv?: Record<string, string>;
8
8
  logger?: TransportLogger;
9
9
  /** Ms to wait for the app-server to accept a connection. */
10
10
  readyTimeoutMs?: number;
11
+ /** Durable per-session runtime state used for targeted crash reconciliation. */
12
+ stateDir?: string;
11
13
  }
12
14
  export declare class WsRpcChannel implements RpcChannel {
13
15
  private readonly opts;
@@ -16,6 +18,8 @@ export declare class WsRpcChannel implements RpcChannel {
16
18
  private lineCb;
17
19
  private closeCb;
18
20
  private closedEmitted;
21
+ private processTag;
22
+ private stopTask;
19
23
  private readonly readyTimeoutMs;
20
24
  /** The `ws://IP:PORT` the app-server listens on — pass to the TUI's `--remote`. */
21
25
  url: string;
@@ -30,6 +34,9 @@ export declare class WsRpcChannel implements RpcChannel {
30
34
  start(): Promise<void>;
31
35
  send(line: string): Promise<void>;
32
36
  stop(signal?: NodeJS.Signals): Promise<void>;
37
+ private stopOwnedProcess;
38
+ private signalChild;
39
+ private unregisterProcess;
33
40
  private emitClose;
34
41
  private connectWithRetry;
35
42
  private tryConnect;