@rynx-ai/runtime 0.1.11-beta.33 → 0.1.11-beta.35

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 (36) hide show
  1. package/dist/claude/native-bridge.d.ts +16 -1
  2. package/dist/claude/native-bridge.js +91 -17
  3. package/dist/claude/native-hook-main.js +1 -0
  4. package/dist/claude/native-integration.d.ts +10 -0
  5. package/dist/claude/native-integration.js +100 -6
  6. package/dist/codex-app-server/forwarder.js +23 -1
  7. package/dist/codex-app-server/mapping.js +34 -10
  8. package/dist/codex-app-server/process-registry.d.ts +36 -0
  9. package/dist/codex-app-server/process-registry.js +320 -0
  10. package/dist/codex-app-server/ws-channel.d.ts +7 -0
  11. package/dist/codex-app-server/ws-channel.js +85 -9
  12. package/dist/codex-home.d.ts +35 -3
  13. package/dist/codex-home.js +321 -14
  14. package/dist/host.d.ts +11 -25
  15. package/dist/host.js +147 -132
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +1 -1
  18. package/dist/runner/child.d.ts +25 -3
  19. package/dist/runner/child.js +359 -64
  20. package/dist/runner/manager.d.ts +33 -18
  21. package/dist/runner/manager.js +229 -49
  22. package/dist/runner/protocol.d.ts +79 -7
  23. package/dist/runner/transport.d.ts +9 -2
  24. package/dist/runner/transport.js +55 -3
  25. package/dist/runner-main.js +8 -3
  26. package/dist/terminal/codex-tui.d.ts +4 -0
  27. package/dist/terminal/codex-tui.js +5 -0
  28. package/dist/terminal/control-parser.d.ts +39 -0
  29. package/dist/terminal/control-parser.js +172 -0
  30. package/dist/terminal/registry.d.ts +18 -15
  31. package/dist/terminal/registry.js +43 -23
  32. package/dist/terminal/spool.d.ts +47 -0
  33. package/dist/terminal/spool.js +231 -0
  34. package/dist/terminal/tmux.d.ts +93 -84
  35. package/dist/terminal/tmux.js +675 -200
  36. package/package.json +3 -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
+ }
@@ -8,6 +8,8 @@ export interface WsRpcChannelOptions {
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;
@@ -16,6 +16,7 @@ import { spawn } from "node:child_process";
16
16
  import { createServer } from "node:net";
17
17
  import { WebSocket } from "ws";
18
18
  import { createCodexChildEnv } from "../codex-child-env.js";
19
+ import { newRuntimeProcessTag, reconcileRuntimeProcesses, registerRuntimeProcess, runtimeProcessArgv0, runtimeProcessOwnerIdentity, unregisterRuntimeProcess, withRuntimeProcessStateArg, } from "./process-registry.js";
19
20
  /** Reserve a free loopback TCP port (best-effort; racy but fine for local use). */
20
21
  async function freeLoopbackPort() {
21
22
  return await new Promise((resolve, reject) => {
@@ -36,6 +37,8 @@ export class WsRpcChannel {
36
37
  lineCb = null;
37
38
  closeCb = null;
38
39
  closedEmitted = false;
40
+ processTag = null;
41
+ stopTask = null;
39
42
  readyTimeoutMs;
40
43
  /** The `ws://IP:PORT` the app-server listens on — pass to the TUI's `--remote`. */
41
44
  url = "";
@@ -53,16 +56,48 @@ export class WsRpcChannel {
53
56
  return this.ws?.readyState === WebSocket.OPEN;
54
57
  }
55
58
  async start() {
59
+ this.stopTask = null;
60
+ this.closedEmitted = false;
61
+ if (this.opts.stateDir) {
62
+ reconcileRuntimeProcesses();
63
+ }
56
64
  const port = await freeLoopbackPort();
57
65
  this.url = `ws://127.0.0.1:${port}`;
58
- const args = [...this.opts.baseArgs, "--listen", this.url];
66
+ const baseArgs = this.opts.stateDir
67
+ ? withRuntimeProcessStateArg(this.opts.baseArgs, this.opts.stateDir)
68
+ : this.opts.baseArgs;
69
+ const args = [...baseArgs, "--listen", this.url];
70
+ const processTag = this.opts.stateDir ? newRuntimeProcessTag() : null;
71
+ this.processTag = processTag;
59
72
  this.child = spawn(this.opts.cliPath, args, {
60
73
  stdio: ["ignore", "ignore", "pipe"],
61
74
  env: { ...createCodexChildEnv(process.env), ...(this.opts.extraEnv ?? {}) },
75
+ detached: process.platform !== "win32",
76
+ ...(processTag ? { argv0: runtimeProcessArgv0(this.opts.cliPath, processTag) } : {}),
77
+ });
78
+ const childPid = this.child.pid;
79
+ if (processTag && this.opts.stateDir && childPid) {
80
+ registerRuntimeProcess({
81
+ pid: childPid,
82
+ pgid: childPid,
83
+ ownerPid: process.pid,
84
+ ownerIdentity: runtimeProcessOwnerIdentity(process.pid),
85
+ sessionTag: processTag,
86
+ stateDir: this.opts.stateDir,
87
+ });
88
+ }
89
+ this.child.on("exit", (code, signal) => {
90
+ this.unregisterProcess();
91
+ this.emitClose(code, signal, null);
62
92
  });
63
- this.child.on("exit", (code, signal) => this.emitClose(code, signal, null));
64
93
  this.child.on("error", (error) => this.emitClose(null, null, error));
65
- this.ws = await this.connectWithRetry(this.url);
94
+ try {
95
+ this.ws = await this.connectWithRetry(this.url);
96
+ }
97
+ catch (error) {
98
+ await this.stop();
99
+ throw error;
100
+ }
66
101
  this.ws.on("message", (data) => {
67
102
  // One JSON-RPC object per frame (codex ws framing).
68
103
  this.lineCb?.(data.toString());
@@ -86,6 +121,12 @@ export class WsRpcChannel {
86
121
  });
87
122
  }
88
123
  async stop(signal = "SIGTERM") {
124
+ if (this.stopTask)
125
+ return await this.stopTask;
126
+ this.stopTask = this.stopOwnedProcess(signal);
127
+ return await this.stopTask;
128
+ }
129
+ async stopOwnedProcess(signal) {
89
130
  try {
90
131
  this.ws?.close();
91
132
  }
@@ -93,14 +134,33 @@ export class WsRpcChannel {
93
134
  /* already closing */
94
135
  }
95
136
  const child = this.child;
96
- if (child) {
97
- try {
98
- child.kill(signal);
99
- }
100
- catch {
101
- /* already gone */
137
+ if (child && child.exitCode === null && child.signalCode === null) {
138
+ this.signalChild(child, signal);
139
+ if (!await waitForExit(child, 5_000)) {
140
+ this.signalChild(child, "SIGKILL");
141
+ await waitForExit(child, 2_000);
102
142
  }
103
143
  }
144
+ this.unregisterProcess();
145
+ this.child = null;
146
+ this.ws = null;
147
+ }
148
+ signalChild(child, signal) {
149
+ try {
150
+ if (process.platform !== "win32" && child.pid)
151
+ process.kill(-child.pid, signal);
152
+ else
153
+ child.kill(signal);
154
+ }
155
+ catch {
156
+ // Already gone.
157
+ }
158
+ }
159
+ unregisterProcess() {
160
+ const tag = this.processTag;
161
+ this.processTag = null;
162
+ if (tag)
163
+ unregisterRuntimeProcess(tag);
104
164
  }
105
165
  emitClose(code, signal, error) {
106
166
  if (this.closedEmitted)
@@ -146,6 +206,22 @@ export class WsRpcChannel {
146
206
  });
147
207
  }
148
208
  }
209
+ function waitForExit(child, timeoutMs) {
210
+ if (child.exitCode !== null || child.signalCode !== null)
211
+ return Promise.resolve(true);
212
+ return new Promise((resolve) => {
213
+ const timer = setTimeout(() => {
214
+ child.off("exit", onExit);
215
+ resolve(false);
216
+ }, timeoutMs);
217
+ timer.unref?.();
218
+ const onExit = () => {
219
+ clearTimeout(timer);
220
+ resolve(true);
221
+ };
222
+ child.once("exit", onExit);
223
+ });
224
+ }
149
225
  /**
150
226
  * Connect-only {@link RpcChannel}: attaches an ADDITIONAL client to an app-server
151
227
  * someone else already started (a {@link WsRpcChannel}'s `url`) — no spawn. This
@@ -1,5 +1,16 @@
1
- import { type AgentRuntimeId } from "@rynx-ai/core";
1
+ import { type AgentRuntimeId, type ResolvedRetryPolicy } from "@rynx-ai/core";
2
2
  export type CodexLineageRuntime = Exclude<AgentRuntimeId, "claude">;
3
+ export type CodexAuthMode = "chatgpt" | "apikey" | "pat";
4
+ export type CodexConfigAuth = "provider-ready" | "provider-auth-missing" | "codex-login";
5
+ /** Read the credential mode from Codex's optional-field auth.json shape. */
6
+ export declare function codexAuthEffectiveMode(authPath: string): CodexAuthMode | null;
7
+ /** Mirror Codex's provider-specific auth decision without spawning the CLI. */
8
+ export declare function codexConfigEffectiveAuth(configPath: string, env?: NodeJS.ProcessEnv): CodexConfigAuth;
9
+ /** True only when this exact launch would defer to an absent Codex login. */
10
+ export declare function codexLaunchRequiresLogin(codexHome: string, env?: NodeJS.ProcessEnv): boolean;
11
+ /** Resolve the user's auth/config source without nesting a parent Session's
12
+ * private CODEX_HOME into a child Session. */
13
+ export declare function resolveCodexHomeConfigSource(candidate: string, defaultHome?: string): string;
3
14
  /**
4
15
  * The deterministic private CODEX_HOME path for a rynx session. PER-SESSION,
5
16
  * under the daemon-owned RYNX_HOME runtime tree,
@@ -22,8 +33,8 @@ export declare function legacyCodexHomePath(): string;
22
33
  * `_CODEX_HOME_COPY_FILES`.
23
34
  *
24
35
  * PER-SESSION: one private home per rynx session (matching reference implementation), so concurrent
25
- * agents never share a `skills/` dir. Idempotent: the symlink/copy are refreshed each
26
- * call so a re-login/config change propagates. Returns the private home dir.
36
+ * agents never share a `skills/` dir. Idempotent: existing Session-owned entries stay
37
+ * unchanged; live symlinks still observe source credential updates. Returns the private home dir.
27
38
  */
28
39
  export declare function prepareCodexHome(sessionId: string, realHome?: string): string;
29
40
  /**
@@ -32,6 +43,27 @@ export declare function prepareCodexHome(sessionId: string, realHome?: string):
32
43
  * real home and cannot wedge a managed app-server/TUI pair.
33
44
  */
34
45
  export declare function prepareRuntimeHome(sessionId: string, runtime: CodexLineageRuntime, realHome?: string): string;
46
+ /** Trust a runner-selected workspace only in the Session's private config so
47
+ * Codex cannot stop a detached TUI at its project-trust prompt. */
48
+ export declare function trustCodexProject(codexHome: string, cwd: string): void;
49
+ /** Apply the Session retry budget through Codex's supported provider settings.
50
+ * A config with no provider definitions is left untouched. */
51
+ export declare function applyCodexProviderRetryPolicy(codexHome: string, retry?: ResolvedRetryPolicy | null): void;
52
+ /** Preserve the user's private-home base instructions and append only the raw
53
+ * Agent-authored text. Repeated launches replace the managed suffix instead of
54
+ * accumulating it. Invalid user TOML is intentionally left untouched. */
55
+ export declare function syncCodexDeveloperInstructions(codexHome: string, instructions?: string | null): void;
56
+ export type CodexDeveloperInstructionsState = {
57
+ state: "present";
58
+ value: string;
59
+ } | {
60
+ state: "absent";
61
+ } | {
62
+ state: "unreadable";
63
+ };
64
+ /** Read the active private-home instructions without conflating a definite
65
+ * absence with malformed or temporarily unreadable config. */
66
+ export declare function readCodexDeveloperInstructionsState(codexHome: string): CodexDeveloperInstructionsState;
35
67
  /** Persist immutable skill copies in a native Provider home. Fork descendants
36
68
  * share that home, so entries must outlive every runner's temporary sources. */
37
69
  export declare function populateCodexSkills(codexHome: string, skills: {