@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.
- package/dist/claude/native-bridge.d.ts +16 -1
- package/dist/claude/native-bridge.js +91 -17
- package/dist/claude/native-hook-main.js +1 -0
- package/dist/claude/native-integration.d.ts +10 -0
- package/dist/claude/native-integration.js +100 -6
- package/dist/codex-app-server/forwarder.js +23 -1
- package/dist/codex-app-server/mapping.js +34 -10
- package/dist/codex-app-server/process-registry.d.ts +36 -0
- package/dist/codex-app-server/process-registry.js +320 -0
- package/dist/codex-app-server/ws-channel.d.ts +7 -0
- package/dist/codex-app-server/ws-channel.js +85 -9
- package/dist/codex-home.d.ts +35 -3
- package/dist/codex-home.js +321 -14
- package/dist/host.d.ts +11 -25
- package/dist/host.js +147 -132
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/runner/child.d.ts +25 -3
- package/dist/runner/child.js +359 -64
- package/dist/runner/manager.d.ts +33 -18
- package/dist/runner/manager.js +229 -49
- package/dist/runner/protocol.d.ts +79 -7
- package/dist/runner/transport.d.ts +9 -2
- package/dist/runner/transport.js +55 -3
- package/dist/runner-main.js +8 -3
- package/dist/terminal/codex-tui.d.ts +4 -0
- package/dist/terminal/codex-tui.js +5 -0
- package/dist/terminal/control-parser.d.ts +39 -0
- package/dist/terminal/control-parser.js +172 -0
- package/dist/terminal/registry.d.ts +18 -15
- package/dist/terminal/registry.js +43 -23
- package/dist/terminal/spool.d.ts +47 -0
- package/dist/terminal/spool.js +231 -0
- package/dist/terminal/tmux.d.ts +93 -84
- package/dist/terminal/tmux.js +675 -200
- package/package.json +3 -4
package/dist/codex-home.js
CHANGED
|
@@ -1,26 +1,174 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { copyFileSync, cpSync, existsSync, mkdirSync, renameSync, rmSync, symlinkSync } from "node:fs";
|
|
2
|
+
import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
import { parse, stringify } from "smol-toml";
|
|
5
6
|
import { assertSkillPathComponent, getRuntimeProfile, resolveRuntimeHome, } from "@rynx-ai/core";
|
|
6
7
|
import { adoptLegacyRuntimeDirectory, legacyRuntimeStateRoot, runtimeSessionDigest, runtimeSessionStateDir, } from "./runtime-state-paths.js";
|
|
7
|
-
/**
|
|
8
|
-
const
|
|
8
|
+
/** Codex-owned files that must stay live across every private home. */
|
|
9
|
+
const CODEX_SYMLINK_FILES = [
|
|
10
|
+
"auth.json",
|
|
11
|
+
".credentials.json",
|
|
12
|
+
"memories_1.sqlite",
|
|
13
|
+
"AGENTS.md",
|
|
14
|
+
"AGENTS.override.md",
|
|
15
|
+
"hooks.json",
|
|
16
|
+
];
|
|
17
|
+
/** Large/shared Codex state and cross-process coordination directories. */
|
|
18
|
+
const CODEX_SYMLINK_DIRS = [
|
|
19
|
+
join("plugins", "cache"),
|
|
20
|
+
"mcp-oauth-locks",
|
|
21
|
+
"memories",
|
|
22
|
+
"rules",
|
|
23
|
+
];
|
|
9
24
|
/** Inherit the user's settings by snapshot copy (not the mutable NUX/update state). */
|
|
10
25
|
const COPY_FILES = ["config.toml"];
|
|
11
26
|
const RUNTIME_HOME_FILES = {
|
|
12
|
-
codex: {
|
|
27
|
+
codex: {
|
|
28
|
+
symlink: CODEX_SYMLINK_FILES,
|
|
29
|
+
symlinkDirs: CODEX_SYMLINK_DIRS,
|
|
30
|
+
copy: COPY_FILES,
|
|
31
|
+
},
|
|
13
32
|
traex: {
|
|
14
33
|
// Traex keeps credentials below `cli/`, while its user configuration lives
|
|
15
34
|
// at the TRAE_HOME root. Keep both the current TOML name and the legacy YAML
|
|
16
35
|
// name so existing installations remain usable without an implicit migrate.
|
|
17
36
|
symlink: ["cli/auth.json"],
|
|
37
|
+
symlinkDirs: [],
|
|
18
38
|
copy: ["traecli.toml", "traecli.yaml"],
|
|
19
39
|
},
|
|
20
40
|
};
|
|
41
|
+
const CODEX_BUILTIN_PROVIDERS = new Set([
|
|
42
|
+
"openai",
|
|
43
|
+
"amazon-bedrock",
|
|
44
|
+
"amazon-bedrock-runtime",
|
|
45
|
+
"ollama",
|
|
46
|
+
"lmstudio",
|
|
47
|
+
]);
|
|
48
|
+
function isTomlTable(value) {
|
|
49
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
50
|
+
}
|
|
51
|
+
/** Resolve the effective native Provider, honoring the selected profile first. */
|
|
52
|
+
function effectiveCodexModelProvider(config) {
|
|
53
|
+
let provider = config.model_provider;
|
|
54
|
+
const activeProfile = config.profile;
|
|
55
|
+
if (typeof activeProfile === "string" && activeProfile.trim() && isTomlTable(config.profiles)) {
|
|
56
|
+
const profile = config.profiles[activeProfile];
|
|
57
|
+
if (isTomlTable(profile) && typeof profile.model_provider === "string") {
|
|
58
|
+
provider = profile.model_provider;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return typeof provider === "string" && provider.trim() ? provider : null;
|
|
62
|
+
}
|
|
63
|
+
/** Read the credential mode from Codex's optional-field auth.json shape. */
|
|
64
|
+
export function codexAuthEffectiveMode(authPath) {
|
|
65
|
+
let data;
|
|
66
|
+
try {
|
|
67
|
+
data = JSON.parse(readFileSync(authPath, "utf8"));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
73
|
+
return null;
|
|
74
|
+
const auth = data;
|
|
75
|
+
const tokens = auth.tokens;
|
|
76
|
+
if (tokens && typeof tokens === "object" && !Array.isArray(tokens)) {
|
|
77
|
+
const tokenRecord = tokens;
|
|
78
|
+
for (const field of ["access_token", "refresh_token"]) {
|
|
79
|
+
const value = tokenRecord[field];
|
|
80
|
+
if (typeof value === "string" && value.trim())
|
|
81
|
+
return "chatgpt";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (typeof auth.OPENAI_API_KEY === "string" && auth.OPENAI_API_KEY.trim())
|
|
85
|
+
return "apikey";
|
|
86
|
+
if (typeof auth.personal_access_token === "string" &&
|
|
87
|
+
auth.personal_access_token.trim())
|
|
88
|
+
return "pat";
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
/** Mirror Codex's provider-specific auth decision without spawning the CLI. */
|
|
92
|
+
export function codexConfigEffectiveAuth(configPath, env = process.env) {
|
|
93
|
+
let config;
|
|
94
|
+
try {
|
|
95
|
+
config = parse(readFileSync(configPath, "utf8"));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return "codex-login";
|
|
99
|
+
}
|
|
100
|
+
const providerId = effectiveCodexModelProvider(config);
|
|
101
|
+
if (!providerId)
|
|
102
|
+
return "codex-login";
|
|
103
|
+
if (CODEX_BUILTIN_PROVIDERS.has(providerId)) {
|
|
104
|
+
return providerId === "openai" ? "codex-login" : "provider-ready";
|
|
105
|
+
}
|
|
106
|
+
const provider = isTomlTable(config.model_providers)
|
|
107
|
+
? config.model_providers[providerId]
|
|
108
|
+
: undefined;
|
|
109
|
+
if (!isTomlTable(provider))
|
|
110
|
+
return "provider-auth-missing";
|
|
111
|
+
if (provider.requires_openai_auth === true)
|
|
112
|
+
return "codex-login";
|
|
113
|
+
const envKey = provider.env_key;
|
|
114
|
+
if (typeof envKey !== "string" || !envKey.trim())
|
|
115
|
+
return "provider-ready";
|
|
116
|
+
const value = env[envKey.trim()];
|
|
117
|
+
return typeof value === "string" && value.trim()
|
|
118
|
+
? "provider-ready"
|
|
119
|
+
: "provider-auth-missing";
|
|
120
|
+
}
|
|
121
|
+
/** True only when this exact launch would defer to an absent Codex login. */
|
|
122
|
+
export function codexLaunchRequiresLogin(codexHome, env = process.env) {
|
|
123
|
+
return codexConfigEffectiveAuth(join(codexHome, "config.toml"), env) === "codex-login" &&
|
|
124
|
+
codexAuthEffectiveMode(join(codexHome, "auth.json")) === null;
|
|
125
|
+
}
|
|
126
|
+
function pathExistsOrIsSymlink(path) {
|
|
127
|
+
try {
|
|
128
|
+
lstatSync(path);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function isPrivateCodexHome(candidate) {
|
|
136
|
+
const normalized = resolve(candidate);
|
|
137
|
+
if (basename(normalized) !== "codex-home")
|
|
138
|
+
return false;
|
|
139
|
+
const ownerDir = dirname(normalized);
|
|
140
|
+
const namespace = dirname(ownerDir);
|
|
141
|
+
const root = dirname(namespace);
|
|
142
|
+
return (basename(namespace) === "sessions" && basename(root) === "runtime") || basename(namespace) === "codex-native";
|
|
143
|
+
}
|
|
144
|
+
function privateCodexHomeSource(candidate) {
|
|
145
|
+
const sourceDirs = new Set();
|
|
146
|
+
for (const name of ["auth.json", ".credentials.json", "memories_1.sqlite"]) {
|
|
147
|
+
const file = join(candidate, name);
|
|
148
|
+
try {
|
|
149
|
+
if (lstatSync(file).isSymbolicLink())
|
|
150
|
+
sourceDirs.add(dirname(realpathSync(file)));
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Missing or unreadable links do not identify a source home.
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return sourceDirs.size === 1 ? [...sourceDirs][0] : null;
|
|
157
|
+
}
|
|
158
|
+
/** Resolve the user's auth/config source without nesting a parent Session's
|
|
159
|
+
* private CODEX_HOME into a child Session. */
|
|
160
|
+
export function resolveCodexHomeConfigSource(candidate, defaultHome = join(homedir(), ".codex")) {
|
|
161
|
+
const source = resolve(candidate);
|
|
162
|
+
const fallback = resolve(defaultHome);
|
|
163
|
+
if (source !== fallback && isPrivateCodexHome(source)) {
|
|
164
|
+
return privateCodexHomeSource(source) ?? fallback;
|
|
165
|
+
}
|
|
166
|
+
return source;
|
|
167
|
+
}
|
|
21
168
|
/** The user's real CODEX_HOME (env override, else `~/.codex`). */
|
|
22
169
|
function realCodexHome() {
|
|
23
|
-
|
|
170
|
+
const fallback = join(homedir(), ".codex");
|
|
171
|
+
return resolveCodexHomeConfigSource(process.env.CODEX_HOME?.trim() || fallback, fallback);
|
|
24
172
|
}
|
|
25
173
|
/**
|
|
26
174
|
* The deterministic private CODEX_HOME path for a rynx session. PER-SESSION,
|
|
@@ -52,8 +200,8 @@ export function legacyCodexHomePath() {
|
|
|
52
200
|
* `_CODEX_HOME_COPY_FILES`.
|
|
53
201
|
*
|
|
54
202
|
* PER-SESSION: one private home per rynx session (matching reference implementation), so concurrent
|
|
55
|
-
* agents never share a `skills/` dir. Idempotent:
|
|
56
|
-
*
|
|
203
|
+
* agents never share a `skills/` dir. Idempotent: existing Session-owned entries stay
|
|
204
|
+
* unchanged; live symlinks still observe source credential updates. Returns the private home dir.
|
|
57
205
|
*/
|
|
58
206
|
export function prepareCodexHome(sessionId, realHome = realCodexHome()) {
|
|
59
207
|
return prepareRuntimeHome(sessionId, "codex", realHome);
|
|
@@ -77,13 +225,9 @@ export function prepareRuntimeHome(sessionId, runtime, realHome = runtime === "c
|
|
|
77
225
|
for (const name of files.symlink) {
|
|
78
226
|
const src = join(realHome, name);
|
|
79
227
|
const dst = join(dir, name);
|
|
228
|
+
if (pathExistsOrIsSymlink(dst))
|
|
229
|
+
continue;
|
|
80
230
|
mkdirSync(dirname(dst), { recursive: true, mode: 0o700 });
|
|
81
|
-
try {
|
|
82
|
-
rmSync(dst, { recursive: true, force: true });
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
// absent — fine
|
|
86
|
-
}
|
|
87
231
|
if (existsSync(src)) {
|
|
88
232
|
try {
|
|
89
233
|
symlinkSync(src, dst);
|
|
@@ -93,11 +237,35 @@ export function prepareRuntimeHome(sessionId, runtime, realHome = runtime === "c
|
|
|
93
237
|
}
|
|
94
238
|
}
|
|
95
239
|
}
|
|
240
|
+
for (const name of files.symlinkDirs) {
|
|
241
|
+
const src = join(realHome, name);
|
|
242
|
+
const dst = join(dir, name);
|
|
243
|
+
let sourceIsDirectory = false;
|
|
244
|
+
try {
|
|
245
|
+
sourceIsDirectory = statSync(src).isDirectory();
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// Missing/unreadable shared state is optional.
|
|
249
|
+
}
|
|
250
|
+
if (!sourceIsDirectory)
|
|
251
|
+
continue;
|
|
252
|
+
if (pathExistsOrIsSymlink(dst))
|
|
253
|
+
continue;
|
|
254
|
+
mkdirSync(dirname(dst), { recursive: true, mode: 0o700 });
|
|
255
|
+
try {
|
|
256
|
+
symlinkSync(src, dst, "dir");
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// Best-effort: Codex recreates absent cache/state in its private home.
|
|
260
|
+
}
|
|
261
|
+
}
|
|
96
262
|
for (const name of files.copy) {
|
|
97
263
|
const src = join(realHome, name);
|
|
98
264
|
if (existsSync(src)) {
|
|
99
265
|
try {
|
|
100
266
|
const dst = join(dir, name);
|
|
267
|
+
if (pathExistsOrIsSymlink(dst))
|
|
268
|
+
continue;
|
|
101
269
|
mkdirSync(dirname(dst), { recursive: true, mode: 0o700 });
|
|
102
270
|
copyFileSync(src, dst);
|
|
103
271
|
}
|
|
@@ -108,6 +276,145 @@ export function prepareRuntimeHome(sessionId, runtime, realHome = runtime === "c
|
|
|
108
276
|
}
|
|
109
277
|
return dir;
|
|
110
278
|
}
|
|
279
|
+
/** Trust a runner-selected workspace only in the Session's private config so
|
|
280
|
+
* Codex cannot stop a detached TUI at its project-trust prompt. */
|
|
281
|
+
export function trustCodexProject(codexHome, cwd) {
|
|
282
|
+
const configPath = join(codexHome, "config.toml");
|
|
283
|
+
let config = {};
|
|
284
|
+
if (existsSync(configPath))
|
|
285
|
+
config = parse(readFileSync(configPath, "utf8"));
|
|
286
|
+
const projects = isTomlTable(config.projects) ? config.projects : {};
|
|
287
|
+
config.projects = projects;
|
|
288
|
+
let projectPath;
|
|
289
|
+
try {
|
|
290
|
+
projectPath = realpathSync(cwd);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
projectPath = resolve(cwd);
|
|
294
|
+
}
|
|
295
|
+
const existingProject = projects[projectPath];
|
|
296
|
+
const project = isTomlTable(existingProject) ? existingProject : {};
|
|
297
|
+
project.trust_level = "trusted";
|
|
298
|
+
projects[projectPath] = project;
|
|
299
|
+
writeCodexConfig(configPath, config);
|
|
300
|
+
}
|
|
301
|
+
const DEFAULT_CODEX_MAX_RETRIES = 7;
|
|
302
|
+
const DEFAULT_CODEX_REQUEST_TIMEOUT_SECONDS = 120;
|
|
303
|
+
const DEVELOPER_INSTRUCTIONS_BASE_FILE = ".rynx-developer-instructions-base";
|
|
304
|
+
/** Apply the Session retry budget through Codex's supported provider settings.
|
|
305
|
+
* A config with no provider definitions is left untouched. */
|
|
306
|
+
export function applyCodexProviderRetryPolicy(codexHome, retry) {
|
|
307
|
+
const configPath = join(codexHome, "config.toml");
|
|
308
|
+
if (!existsSync(configPath) || lstatSync(configPath).isSymbolicLink())
|
|
309
|
+
return;
|
|
310
|
+
const config = parse(readFileSync(configPath, "utf8"));
|
|
311
|
+
if (!isTomlTable(config.model_providers))
|
|
312
|
+
return;
|
|
313
|
+
const maxRetries = retry?.maxRetries ?? DEFAULT_CODEX_MAX_RETRIES;
|
|
314
|
+
const timeoutSeconds = retry?.timeoutPerRequestSeconds ??
|
|
315
|
+
DEFAULT_CODEX_REQUEST_TIMEOUT_SECONDS;
|
|
316
|
+
let changed = false;
|
|
317
|
+
for (const provider of Object.values(config.model_providers)) {
|
|
318
|
+
if (!isTomlTable(provider))
|
|
319
|
+
continue;
|
|
320
|
+
provider.request_max_retries = maxRetries;
|
|
321
|
+
provider.stream_max_retries = maxRetries;
|
|
322
|
+
provider.stream_idle_timeout_ms = Math.trunc(timeoutSeconds * 1_000);
|
|
323
|
+
changed = true;
|
|
324
|
+
}
|
|
325
|
+
if (changed)
|
|
326
|
+
writeCodexConfig(configPath, config);
|
|
327
|
+
}
|
|
328
|
+
/** Preserve the user's private-home base instructions and append only the raw
|
|
329
|
+
* Agent-authored text. Repeated launches replace the managed suffix instead of
|
|
330
|
+
* accumulating it. Invalid user TOML is intentionally left untouched. */
|
|
331
|
+
export function syncCodexDeveloperInstructions(codexHome, instructions) {
|
|
332
|
+
mkdirSync(codexHome, { recursive: true, mode: 0o700 });
|
|
333
|
+
const configPath = join(codexHome, "config.toml");
|
|
334
|
+
if (pathExistsOrIsSymlink(configPath) && lstatSync(configPath).isSymbolicLink()) {
|
|
335
|
+
try {
|
|
336
|
+
const source = realpathSync(configPath);
|
|
337
|
+
const content = readFileSync(source, "utf8");
|
|
338
|
+
rmSync(configPath, { force: true });
|
|
339
|
+
writeFileSync(configPath, content, { encoding: "utf8", mode: 0o600 });
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
let config;
|
|
346
|
+
try {
|
|
347
|
+
config = existsSync(configPath) ? parse(readFileSync(configPath, "utf8")) : {};
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const current = config.developer_instructions;
|
|
353
|
+
if (current !== undefined && typeof current !== "string")
|
|
354
|
+
return;
|
|
355
|
+
const addition = instructions?.trim() ?? "";
|
|
356
|
+
const basePath = join(codexHome, DEVELOPER_INSTRUCTIONS_BASE_FILE);
|
|
357
|
+
let base;
|
|
358
|
+
if (existsSync(basePath)) {
|
|
359
|
+
base = readFileSync(basePath, "utf8");
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
base = typeof current === "string" ? current.trim() : "";
|
|
363
|
+
if (addition && base === addition) {
|
|
364
|
+
base = "";
|
|
365
|
+
}
|
|
366
|
+
else if (addition && base.endsWith(`\n\n${addition}`)) {
|
|
367
|
+
base = base.slice(0, -(`\n\n${addition}`).length).trimEnd();
|
|
368
|
+
}
|
|
369
|
+
writeFileSync(basePath, base, { encoding: "utf8", mode: 0o600 });
|
|
370
|
+
}
|
|
371
|
+
const active = base && addition ? `${base}\n\n${addition}` : base || addition;
|
|
372
|
+
if (active)
|
|
373
|
+
config.developer_instructions = active;
|
|
374
|
+
else
|
|
375
|
+
delete config.developer_instructions;
|
|
376
|
+
writeCodexConfig(configPath, config);
|
|
377
|
+
}
|
|
378
|
+
/** Read the active private-home instructions without conflating a definite
|
|
379
|
+
* absence with malformed or temporarily unreadable config. */
|
|
380
|
+
export function readCodexDeveloperInstructionsState(codexHome) {
|
|
381
|
+
const configPath = join(codexHome, "config.toml");
|
|
382
|
+
let source;
|
|
383
|
+
try {
|
|
384
|
+
source = readFileSync(configPath, "utf8");
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
const code = error.code;
|
|
388
|
+
return code === "ENOENT" || code === "ENOTDIR"
|
|
389
|
+
? { state: "absent" }
|
|
390
|
+
: { state: "unreadable" };
|
|
391
|
+
}
|
|
392
|
+
let config;
|
|
393
|
+
try {
|
|
394
|
+
config = parse(source);
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
return { state: "unreadable" };
|
|
398
|
+
}
|
|
399
|
+
if (!Object.prototype.hasOwnProperty.call(config, "developer_instructions")) {
|
|
400
|
+
return { state: "absent" };
|
|
401
|
+
}
|
|
402
|
+
const instructions = config.developer_instructions;
|
|
403
|
+
return typeof instructions === "string" && instructions.trim()
|
|
404
|
+
? { state: "present", value: instructions }
|
|
405
|
+
: { state: "unreadable" };
|
|
406
|
+
}
|
|
407
|
+
function writeCodexConfig(configPath, config) {
|
|
408
|
+
mkdirSync(dirname(configPath), { recursive: true, mode: 0o700 });
|
|
409
|
+
const staged = join(dirname(configPath), `.config.toml.${process.pid}.${randomUUID()}`);
|
|
410
|
+
try {
|
|
411
|
+
writeFileSync(staged, stringify(config), { encoding: "utf8", mode: 0o600 });
|
|
412
|
+
renameSync(staged, configPath);
|
|
413
|
+
}
|
|
414
|
+
finally {
|
|
415
|
+
rmSync(staged, { force: true });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
111
418
|
/** Persist immutable skill copies in a native Provider home. Fork descendants
|
|
112
419
|
* share that home, so entries must outlive every runner's temporary sources. */
|
|
113
420
|
export function populateCodexSkills(codexHome, skills) {
|
package/dist/host.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ResolvedExecutionSnapshot, type
|
|
1
|
+
import { type ResolvedExecutionSnapshot, type RuntimeTurnOptions, type RuntimeUserInput, type SessionCollaborationMode, type LiveSessionFailure, type SessionWorkspaceSnapshot, type SessionInteractionResolution, type SessionEvent } from "@rynx-ai/core";
|
|
2
2
|
import { type AgentRuntimeId } from "@rynx-ai/core";
|
|
3
3
|
import { type AppConfig } from "@rynx-ai/core";
|
|
4
4
|
import { createCodexChildEnv } from "./codex-child-env.js";
|
|
@@ -107,14 +107,6 @@ export interface LiveSessionOpts {
|
|
|
107
107
|
execution: ResolvedExecutionSnapshot;
|
|
108
108
|
retargetMirror?: RetargetMirror;
|
|
109
109
|
}
|
|
110
|
-
/**
|
|
111
|
-
* The `OPENAI_*` retry env for a codex-lineage app-server spawn, or `undefined`
|
|
112
|
-
* when the budget declares no retry or the runtime is claude. claude applies
|
|
113
|
-
* its retry budget per-run in `buildOptions` (see the claude executor), so it
|
|
114
|
-
* never needs a dedicated app-server.
|
|
115
|
-
*/
|
|
116
|
-
export declare function codexRetryEnv(runtime: AgentRuntimeId, budget?: ResolvedExecutionBudget): Record<string, string> | undefined;
|
|
117
|
-
export declare function codexBackendKey(runtime: AgentRuntimeId, budget?: ResolvedExecutionBudget): string;
|
|
118
110
|
export declare class LocalAgentHost implements CodexCapabilities {
|
|
119
111
|
private readonly config;
|
|
120
112
|
private readonly sessionStore;
|
|
@@ -122,8 +114,6 @@ export declare class LocalAgentHost implements CodexCapabilities {
|
|
|
122
114
|
private readonly defaultRuntime;
|
|
123
115
|
private readonly injectedCommandRunner?;
|
|
124
116
|
private readonly injectedAppServerClient?;
|
|
125
|
-
private readonly clock;
|
|
126
|
-
private readonly backendIdleTtlMs;
|
|
127
117
|
private readonly forwarderClientFactory;
|
|
128
118
|
private readonly preloadClientFactory;
|
|
129
119
|
/** Builds one short-lived message/interrupt client. */
|
|
@@ -143,21 +133,18 @@ export declare class LocalAgentHost implements CodexCapabilities {
|
|
|
143
133
|
/** Claude forwarders stopped before their terminal is killed. Runner shutdown
|
|
144
134
|
* finalizes these synchronously afterwards to scrub raw interaction answers. */
|
|
145
135
|
private readonly pendingClaudeFinalizers;
|
|
136
|
+
private readonly pendingCleanupTasks;
|
|
146
137
|
private readonly liveEnsuring;
|
|
147
138
|
private readonly forkingTargets;
|
|
148
139
|
/** Short-lived dedupe for managed fork notifications delivered after the
|
|
149
140
|
* `thread/fork` response. Values are expected source Provider thread ids. */
|
|
150
141
|
private readonly managedForkThreadStarts;
|
|
151
|
-
constructor({ config, commandRunner, sessionStore, allowedRoots, appServerClient,
|
|
142
|
+
constructor({ config, commandRunner, sessionStore, allowedRoots, appServerClient, forwarderClientFactory, preloadClientFactory, injectionClientFactory, sessionId, runtimeHomeSessionId, }: {
|
|
152
143
|
config: AppConfig;
|
|
153
144
|
commandRunner?: CodexCommandRunner;
|
|
154
145
|
sessionStore?: CodexSessionStore;
|
|
155
146
|
allowedRoots?: string[];
|
|
156
147
|
appServerClient?: CodexAppServerClient | null;
|
|
157
|
-
/** Monotonic-ish clock for idle reaping (tests inject a controllable one). */
|
|
158
|
-
now?: () => number;
|
|
159
|
-
/** Idle TTL after which a per-budget composite backend is reaped (ms). */
|
|
160
|
-
backendIdleTtlMs?: number;
|
|
161
148
|
/** Override the live-session forwarder connection factory (tests inject a fake). */
|
|
162
149
|
forwarderClientFactory?: (appServerUrl: string) => CodexAppServerClient;
|
|
163
150
|
/** Override the short-lived known-thread preload connection factory. */
|
|
@@ -175,14 +162,6 @@ export declare class LocalAgentHost implements CodexCapabilities {
|
|
|
175
162
|
runtimeHomeSessionId?: string;
|
|
176
163
|
});
|
|
177
164
|
private getBackend;
|
|
178
|
-
/**
|
|
179
|
-
* Stop + drop per-budget composite backends that have been idle longer than
|
|
180
|
-
* the TTL and have no in-flight run. Base per-runtime backends (key === the
|
|
181
|
-
* bare runtime id) are never reaped — they live for the process lifetime, as
|
|
182
|
-
* before. There are no per-conversation close hooks, so this lazy sweep (run
|
|
183
|
-
* on every `getBackend`) is how dedicated app-servers get reclaimed.
|
|
184
|
-
*/
|
|
185
|
-
private reapIdleBackends;
|
|
186
165
|
private createBackend;
|
|
187
166
|
/** This session's private native home, shared by its app-server + TUI. */
|
|
188
167
|
private runtimeHome;
|
|
@@ -208,11 +187,16 @@ export declare class LocalAgentHost implements CodexCapabilities {
|
|
|
208
187
|
* SAME thread the structured data channel drives.
|
|
209
188
|
*/
|
|
210
189
|
codexTerminalSpec(localThreadId: string): Promise<{
|
|
190
|
+
lifecycle: "required" | "auxiliary";
|
|
211
191
|
command: string;
|
|
212
192
|
args: string[];
|
|
213
193
|
cwd: string;
|
|
214
194
|
env?: Record<string, string>;
|
|
215
195
|
skipTraexStartupPrompts?: boolean;
|
|
196
|
+
scrollback?: number;
|
|
197
|
+
tmuxAllowPassthrough?: boolean;
|
|
198
|
+
tmuxStartOnAttach?: boolean;
|
|
199
|
+
keepAliveAfterExit?: boolean;
|
|
216
200
|
} | null>;
|
|
217
201
|
/**
|
|
218
202
|
* Bring up (idempotently) a session's persistent codex forwarder and emit its
|
|
@@ -302,9 +286,11 @@ export declare class LocalAgentHost implements CodexCapabilities {
|
|
|
302
286
|
* forwarder and per-session app-server as one disposable runtime envelope;
|
|
303
287
|
* the next message recreates that envelope and cold-resumes the native id. */
|
|
304
288
|
teardownLiveCodexSession(localThreadId: string, error?: Error): boolean;
|
|
289
|
+
teardownAuxiliaryTerminalRuntime(localThreadId: string): Promise<void>;
|
|
305
290
|
/** Complete the second shutdown phase after the runner has killed all native
|
|
306
291
|
* terminals and hook subprocesses. Must run before the runner process exits. */
|
|
307
|
-
finalizeStoppedLiveSessions(): void
|
|
292
|
+
finalizeStoppedLiveSessions(): Promise<void>;
|
|
293
|
+
private trackCleanup;
|
|
308
294
|
/** The interactive `claude` TUI spec for a claude-native live session: the real
|
|
309
295
|
* `claude` binary with rynx's hooks (`--settings`), resuming the bound session.
|
|
310
296
|
* Trust is pre-seeded so the daemon-spawned TUI never blocks on a first-run
|