@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.
- package/dist/claude/executor.d.ts +19 -5
- package/dist/claude/executor.js +56 -12
- package/dist/claude/models.d.ts +0 -5
- package/dist/claude/models.js +1 -7
- package/dist/claude/native-bridge.d.ts +103 -1
- package/dist/claude/native-bridge.js +460 -30
- package/dist/claude/native-hook-main.js +81 -1
- package/dist/claude/native-hooks.js +7 -0
- package/dist/claude/native-integration.d.ts +178 -26
- package/dist/claude/native-integration.js +1571 -170
- package/dist/claude/session-status.d.ts +39 -0
- package/dist/claude/session-status.js +163 -0
- package/dist/claude/transcript-clone.d.ts +18 -0
- package/dist/claude/transcript-clone.js +497 -0
- package/dist/claude/transcript.d.ts +27 -4
- package/dist/claude/transcript.js +183 -46
- package/dist/codex-app-server/client.d.ts +10 -6
- package/dist/codex-app-server/client.js +67 -15
- package/dist/codex-app-server/forwarder.d.ts +96 -3
- package/dist/codex-app-server/forwarder.js +542 -57
- package/dist/codex-app-server/mapping.d.ts +3 -6
- package/dist/codex-app-server/mapping.js +228 -36
- package/dist/codex-app-server/mcp-startup.d.ts +13 -0
- package/dist/codex-app-server/mcp-startup.js +63 -0
- 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/protocol.d.ts +68 -7
- package/dist/codex-app-server/transport.d.ts +2 -2
- package/dist/codex-app-server/transport.js +4 -0
- package/dist/codex-app-server/ws-channel.d.ts +8 -1
- package/dist/codex-app-server/ws-channel.js +113 -28
- package/dist/codex-child-env.js +3 -32
- package/dist/codex-home.d.ts +35 -3
- package/dist/codex-home.js +323 -18
- package/dist/codex-session-store.d.ts +23 -0
- package/dist/codex-session-store.js +21 -0
- package/dist/host.d.ts +106 -48
- package/dist/host.js +2024 -640
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/input-resources.d.ts +4 -0
- package/dist/input-resources.js +21 -5
- package/dist/models-catalog.js +12 -3
- package/dist/runner/child.d.ts +105 -28
- package/dist/runner/child.js +1625 -100
- package/dist/runner/manager.d.ts +114 -46
- package/dist/runner/manager.js +1523 -446
- package/dist/runner/protocol.d.ts +212 -24
- package/dist/runner/protocol.js +5 -0
- package/dist/runner/startup-policy.d.ts +7 -0
- package/dist/runner/startup-policy.js +10 -0
- package/dist/runner/transport.d.ts +18 -2
- package/dist/runner/transport.js +82 -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 +44 -23
- package/dist/terminal/spool.d.ts +47 -0
- package/dist/terminal/spool.js +231 -0
- package/dist/terminal/tmux.d.ts +126 -74
- package/dist/terminal/tmux.js +807 -211
- 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
|
-
/**
|
|
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
|
|
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?:
|
|
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
|
|
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
|
|
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
|
|
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;
|