@rynx-ai/runtime 0.1.0 → 0.1.9
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 +3 -5
- package/dist/claude/executor.js +3 -5
- package/dist/claude/native-bridge.d.ts +74 -17
- package/dist/claude/native-bridge.js +225 -30
- package/dist/claude/native-hook-main.js +291 -39
- package/dist/claude/native-hooks.d.ts +3 -2
- package/dist/claude/native-hooks.js +15 -6
- package/dist/claude/native-integration.d.ts +78 -5
- package/dist/claude/native-integration.js +417 -26
- package/dist/claude/settings.d.ts +8 -0
- package/dist/claude/settings.js +50 -0
- package/dist/claude/transcript.d.ts +2 -2
- package/dist/claude/transcript.js +3 -3
- package/dist/codex/rollout-synth.js +1 -1
- package/dist/codex-app-server/client.d.ts +26 -40
- package/dist/codex-app-server/client.js +1128 -99
- package/dist/codex-app-server/forwarder.d.ts +7 -7
- package/dist/codex-app-server/forwarder.js +11 -5
- package/dist/codex-app-server/mapping.d.ts +1 -1
- package/dist/codex-app-server/mapping.js +27 -2
- package/dist/codex-app-server/protocol.d.ts +238 -4
- package/dist/codex-app-server/transport.d.ts +20 -5
- package/dist/codex-app-server/transport.js +93 -40
- package/dist/codex-app-server/ws-channel.d.ts +3 -3
- package/dist/codex-app-server/ws-channel.js +23 -7
- package/dist/codex-child-env.js +33 -0
- package/dist/codex-home.d.ts +6 -6
- package/dist/codex-home.js +8 -9
- package/dist/codex-session-store.d.ts +2 -1
- package/dist/host.d.ts +34 -33
- package/dist/host.js +531 -91
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/interactions.d.ts +61 -0
- package/dist/interactions.js +236 -0
- package/dist/models-catalog.d.ts +1 -1
- package/dist/models-catalog.js +1 -1
- package/dist/runner/child.d.ts +9 -1
- package/dist/runner/child.js +93 -15
- package/dist/runner/manager.d.ts +59 -10
- package/dist/runner/manager.js +385 -41
- package/dist/runner/protocol.d.ts +18 -7
- package/dist/runner-main.js +9 -6
- package/dist/runtime-status.js +1 -1
- package/dist/terminal/claude-tui.d.ts +8 -3
- package/dist/terminal/claude-tui.js +6 -2
- package/dist/terminal/codex-tui.d.ts +3 -3
- package/dist/terminal/codex-tui.js +1 -1
- package/dist/terminal/registry.d.ts +1 -1
- package/dist/terminal/registry.js +1 -1
- package/dist/terminal/tmux.d.ts +6 -6
- package/dist/terminal/tmux.js +10 -10
- package/package.json +3 -3
|
@@ -67,6 +67,9 @@ export class CodexTransportError extends Error {
|
|
|
67
67
|
this.data = data;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
/** A server request was resolved by another app-server client. In that case the
|
|
71
|
+
* local handler must settle without sending a second JSON-RPC response. */
|
|
72
|
+
export const NO_SERVER_RESPONSE = Symbol("NO_SERVER_RESPONSE");
|
|
70
73
|
/**
|
|
71
74
|
* Wraps the Codex `app-server` subprocess and exposes a typed JSON-RPC API
|
|
72
75
|
* over its stdio. Frames are line-delimited NDJSON (no Content-Length
|
|
@@ -88,6 +91,7 @@ export class CodexAppServerTransport extends EventEmitter {
|
|
|
88
91
|
logger;
|
|
89
92
|
onServerRequest;
|
|
90
93
|
onNotification;
|
|
94
|
+
onServerRequestResponseDelivery;
|
|
91
95
|
child = null;
|
|
92
96
|
rl = null;
|
|
93
97
|
stderrRl = null;
|
|
@@ -95,7 +99,7 @@ export class CodexAppServerTransport extends EventEmitter {
|
|
|
95
99
|
pending = new Map();
|
|
96
100
|
startPromise = null;
|
|
97
101
|
exited = false;
|
|
98
|
-
constructor({ spawner, channel, logger = defaultLogger, onServerRequest, onNotification, }) {
|
|
102
|
+
constructor({ spawner, channel, logger = defaultLogger, onServerRequest, onNotification, onServerRequestResponseDelivery, }) {
|
|
99
103
|
super();
|
|
100
104
|
if (!spawner && !channel) {
|
|
101
105
|
throw new Error("CodexAppServerTransport requires a spawner or a channel");
|
|
@@ -105,6 +109,7 @@ export class CodexAppServerTransport extends EventEmitter {
|
|
|
105
109
|
this.logger = logger;
|
|
106
110
|
this.onServerRequest = onServerRequest;
|
|
107
111
|
this.onNotification = onNotification;
|
|
112
|
+
this.onServerRequestResponseDelivery = onServerRequestResponseDelivery;
|
|
108
113
|
}
|
|
109
114
|
emit(event, ...args) {
|
|
110
115
|
return super.emit(event, ...args);
|
|
@@ -145,39 +150,46 @@ export class CodexAppServerTransport extends EventEmitter {
|
|
|
145
150
|
async sendRequest(method, params) {
|
|
146
151
|
await this.ensureStarted();
|
|
147
152
|
const id = this.nextId++;
|
|
148
|
-
|
|
153
|
+
const envelope = {
|
|
154
|
+
jsonrpc: "2.0",
|
|
155
|
+
id,
|
|
156
|
+
method,
|
|
157
|
+
...(params === undefined ? {} : { params }),
|
|
158
|
+
};
|
|
159
|
+
let rejectResponse;
|
|
160
|
+
const response = new Promise((resolve, reject) => {
|
|
161
|
+
rejectResponse = reject;
|
|
149
162
|
this.pending.set(id, {
|
|
150
163
|
resolve: resolve,
|
|
151
164
|
reject,
|
|
152
165
|
method,
|
|
153
166
|
});
|
|
154
|
-
const envelope = {
|
|
155
|
-
jsonrpc: "2.0",
|
|
156
|
-
id,
|
|
157
|
-
method,
|
|
158
|
-
...(params === undefined ? {} : { params }),
|
|
159
|
-
};
|
|
160
|
-
try {
|
|
161
|
-
this.writeLine(envelope);
|
|
162
|
-
}
|
|
163
|
-
catch (error) {
|
|
164
|
-
this.pending.delete(id);
|
|
165
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
166
|
-
}
|
|
167
167
|
});
|
|
168
|
+
try {
|
|
169
|
+
await this.writeLine(envelope);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
this.pending.delete(id);
|
|
173
|
+
rejectResponse(error instanceof Error ? error : new Error(String(error)));
|
|
174
|
+
}
|
|
175
|
+
return await response;
|
|
168
176
|
}
|
|
169
|
-
sendNotification(method, params) {
|
|
177
|
+
async sendNotification(method, params) {
|
|
170
178
|
const envelope = {
|
|
171
179
|
jsonrpc: "2.0",
|
|
172
180
|
method,
|
|
173
181
|
...(params === undefined ? {} : { params }),
|
|
174
182
|
};
|
|
175
|
-
this.writeLine(envelope);
|
|
183
|
+
await this.writeLine(envelope);
|
|
176
184
|
}
|
|
177
185
|
async stop(signal = "SIGTERM") {
|
|
178
186
|
if (this.channel) {
|
|
179
|
-
this.exited = true;
|
|
180
187
|
await this.channel.stop(signal);
|
|
188
|
+
// Channel implementations may report close asynchronously (or not at all).
|
|
189
|
+
// Run the same exit path synchronously so pending RPCs reject and clients
|
|
190
|
+
// clear pending native interactions before stop() resolves.
|
|
191
|
+
if (!this.exited)
|
|
192
|
+
this.handleExit(null, signal, null);
|
|
181
193
|
return;
|
|
182
194
|
}
|
|
183
195
|
const child = this.child;
|
|
@@ -268,25 +280,29 @@ export class CodexAppServerTransport extends EventEmitter {
|
|
|
268
280
|
});
|
|
269
281
|
this.emit("exit", code, signal);
|
|
270
282
|
}
|
|
271
|
-
writeLine(envelope) {
|
|
283
|
+
async writeLine(envelope) {
|
|
272
284
|
const line = `${JSON.stringify(envelope)}\n`;
|
|
273
285
|
if (this.channel) {
|
|
274
286
|
if (this.exited) {
|
|
275
287
|
throw new CodexTransportError("Codex app-server is not running");
|
|
276
288
|
}
|
|
277
|
-
this.channel.send(line);
|
|
289
|
+
await this.channel.send(line);
|
|
278
290
|
return;
|
|
279
291
|
}
|
|
280
292
|
const child = this.child;
|
|
281
293
|
if (!child || this.exited) {
|
|
282
294
|
throw new CodexTransportError("Codex app-server is not running");
|
|
283
295
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
296
|
+
await new Promise((resolve, reject) => {
|
|
297
|
+
const ok = child.stdin.write(line, (error) => {
|
|
298
|
+
if (error)
|
|
299
|
+
reject(error);
|
|
300
|
+
else
|
|
301
|
+
resolve();
|
|
302
|
+
});
|
|
303
|
+
if (!ok)
|
|
304
|
+
this.logger.log({ event: "transport.stdin_backpressure" });
|
|
305
|
+
});
|
|
290
306
|
}
|
|
291
307
|
handleLine(line) {
|
|
292
308
|
if (!line.trim()) {
|
|
@@ -352,34 +368,71 @@ export class CodexAppServerTransport extends EventEmitter {
|
|
|
352
368
|
}
|
|
353
369
|
async handleServerRequest(envelope) {
|
|
354
370
|
if (!this.onServerRequest) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
371
|
+
try {
|
|
372
|
+
await this.respondToServerRequest(envelope.id, undefined, {
|
|
373
|
+
code: -32601,
|
|
374
|
+
message: `No handler registered for ${envelope.method}`,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
this.logger.log({
|
|
379
|
+
event: "transport.server_request_error_write_failed",
|
|
380
|
+
error: error instanceof Error ? error.message : String(error),
|
|
381
|
+
});
|
|
382
|
+
}
|
|
359
383
|
return;
|
|
360
384
|
}
|
|
385
|
+
let result;
|
|
361
386
|
try {
|
|
362
|
-
|
|
363
|
-
this.respondToServerRequest(envelope.id, result, null);
|
|
387
|
+
result = await this.onServerRequest(envelope.method, envelope.params, envelope.id);
|
|
364
388
|
}
|
|
365
389
|
catch (error) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
390
|
+
try {
|
|
391
|
+
await this.respondToServerRequest(envelope.id, undefined, {
|
|
392
|
+
code: -32000,
|
|
393
|
+
message: error instanceof Error ? error.message : String(error),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
catch (writeError) {
|
|
397
|
+
this.logger.log({
|
|
398
|
+
event: "transport.server_request_error_write_failed",
|
|
399
|
+
error: writeError instanceof Error ? writeError.message : String(writeError),
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (result === NO_SERVER_RESPONSE)
|
|
405
|
+
return;
|
|
406
|
+
try {
|
|
407
|
+
await this.respondToServerRequest(envelope.id, result, null);
|
|
408
|
+
this.notifyServerRequestResponseDelivery(envelope.id, { delivered: true });
|
|
409
|
+
}
|
|
410
|
+
catch (writeError) {
|
|
411
|
+
const error = writeError instanceof Error ? writeError : new Error(String(writeError));
|
|
412
|
+
this.logger.log({
|
|
413
|
+
event: "transport.server_request_write_failed",
|
|
414
|
+
error: error.message,
|
|
415
|
+
});
|
|
416
|
+
this.notifyServerRequestResponseDelivery(envelope.id, {
|
|
417
|
+
delivered: false,
|
|
418
|
+
error,
|
|
369
419
|
});
|
|
370
420
|
}
|
|
371
421
|
}
|
|
372
|
-
respondToServerRequest(id, result, error) {
|
|
422
|
+
async respondToServerRequest(id, result, error) {
|
|
373
423
|
const envelope = error
|
|
374
424
|
? { jsonrpc: "2.0", id, error }
|
|
375
425
|
: { jsonrpc: "2.0", id, result };
|
|
426
|
+
await this.writeLine(envelope);
|
|
427
|
+
}
|
|
428
|
+
notifyServerRequestResponseDelivery(requestId, result) {
|
|
376
429
|
try {
|
|
377
|
-
this.
|
|
430
|
+
this.onServerRequestResponseDelivery?.(requestId, result);
|
|
378
431
|
}
|
|
379
|
-
catch (
|
|
432
|
+
catch (error) {
|
|
380
433
|
this.logger.log({
|
|
381
|
-
event: "transport.
|
|
382
|
-
error:
|
|
434
|
+
event: "transport.server_request_delivery_handler_failed",
|
|
435
|
+
error: error instanceof Error ? error.message : String(error),
|
|
383
436
|
});
|
|
384
437
|
}
|
|
385
438
|
}
|
|
@@ -28,7 +28,7 @@ export declare class WsRpcChannel implements RpcChannel {
|
|
|
28
28
|
}) => void): void;
|
|
29
29
|
isOpen(): boolean;
|
|
30
30
|
start(): Promise<void>;
|
|
31
|
-
send(line: string): void
|
|
31
|
+
send(line: string): Promise<void>;
|
|
32
32
|
stop(signal?: NodeJS.Signals): Promise<void>;
|
|
33
33
|
private emitClose;
|
|
34
34
|
private connectWithRetry;
|
|
@@ -37,7 +37,7 @@ export declare class WsRpcChannel implements RpcChannel {
|
|
|
37
37
|
/**
|
|
38
38
|
* Connect-only {@link RpcChannel}: attaches an ADDITIONAL client to an app-server
|
|
39
39
|
* someone else already started (a {@link WsRpcChannel}'s `url`) — no spawn. This
|
|
40
|
-
* is how rynx runs
|
|
40
|
+
* is how rynx runs reference implementation's multi-connection codex-native model: the backend
|
|
41
41
|
* client owns the app-server + drives injection, while a SEPARATE forwarder
|
|
42
42
|
* connection `thread/resume`s the same thread to subscribe to its item/turn
|
|
43
43
|
* notifications (verified: codex delivers a thread's items to every connection
|
|
@@ -65,7 +65,7 @@ export declare class ExternalWsChannel implements RpcChannel {
|
|
|
65
65
|
}) => void): void;
|
|
66
66
|
isOpen(): boolean;
|
|
67
67
|
start(): Promise<void>;
|
|
68
|
-
send(line: string): void
|
|
68
|
+
send(line: string): Promise<void>;
|
|
69
69
|
stop(): Promise<void>;
|
|
70
70
|
private emitClose;
|
|
71
71
|
private connect;
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* WebSocket {@link RpcChannel} for a codex `app-server --listen ws://IP:PORT`.
|
|
3
3
|
*
|
|
4
4
|
* Why a WebSocket and not the default stdio: stdio admits exactly one client
|
|
5
|
-
* (the process that spawned it), so it cannot be co-driven.
|
|
5
|
+
* (the process that spawned it), so it cannot be co-driven. reference implementation's working
|
|
6
6
|
* codex-native uses a multi-client transport (ws / uds) so a separate `codex
|
|
7
7
|
* --remote` TUI can attach to the SAME app-server and resume the SAME thread.
|
|
8
8
|
* This channel owns that app-server child on a loopback ws port and connects a
|
|
9
9
|
* client to it; the TUI attaches to {@link WsRpcChannel.url}.
|
|
10
10
|
*
|
|
11
|
-
* Framing matches codex (verified from
|
|
11
|
+
* Framing matches codex (verified from reference implementation): one JSON-RPC object per
|
|
12
12
|
* WebSocket text frame — no newline delimiting. So `send` writes one frame per
|
|
13
13
|
* message and every inbound frame is one complete JSON object.
|
|
14
14
|
*/
|
|
@@ -69,12 +69,20 @@ export class WsRpcChannel {
|
|
|
69
69
|
this.ws.on("close", (code) => this.emitClose(code ?? null, null, null));
|
|
70
70
|
this.ws.on("error", (error) => this.emitClose(null, null, error));
|
|
71
71
|
}
|
|
72
|
-
send(line) {
|
|
72
|
+
async send(line) {
|
|
73
73
|
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
74
74
|
throw new Error("codex ws channel is not open");
|
|
75
75
|
}
|
|
76
76
|
// Codex expects one JSON object per frame; drop the NDJSON newline.
|
|
77
|
-
|
|
77
|
+
const payload = line.endsWith("\n") ? line.slice(0, -1) : line;
|
|
78
|
+
await new Promise((resolve, reject) => {
|
|
79
|
+
this.ws.send(payload, (error) => {
|
|
80
|
+
if (error)
|
|
81
|
+
reject(error);
|
|
82
|
+
else
|
|
83
|
+
resolve();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
78
86
|
}
|
|
79
87
|
async stop(signal = "SIGTERM") {
|
|
80
88
|
try {
|
|
@@ -140,7 +148,7 @@ export class WsRpcChannel {
|
|
|
140
148
|
/**
|
|
141
149
|
* Connect-only {@link RpcChannel}: attaches an ADDITIONAL client to an app-server
|
|
142
150
|
* someone else already started (a {@link WsRpcChannel}'s `url`) — no spawn. This
|
|
143
|
-
* is how rynx runs
|
|
151
|
+
* is how rynx runs reference implementation's multi-connection codex-native model: the backend
|
|
144
152
|
* client owns the app-server + drives injection, while a SEPARATE forwarder
|
|
145
153
|
* connection `thread/resume`s the same thread to subscribe to its item/turn
|
|
146
154
|
* notifications (verified: codex delivers a thread's items to every connection
|
|
@@ -189,11 +197,19 @@ export class ExternalWsChannel {
|
|
|
189
197
|
this.ws.on("close", (code) => this.emitClose(code ?? null, null, null));
|
|
190
198
|
this.ws.on("error", (error) => this.emitClose(null, null, error));
|
|
191
199
|
}
|
|
192
|
-
send(line) {
|
|
200
|
+
async send(line) {
|
|
193
201
|
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
194
202
|
throw new Error("external codex ws channel is not open");
|
|
195
203
|
}
|
|
196
|
-
|
|
204
|
+
const payload = line.endsWith("\n") ? line.slice(0, -1) : line;
|
|
205
|
+
await new Promise((resolve, reject) => {
|
|
206
|
+
this.ws.send(payload, (error) => {
|
|
207
|
+
if (error)
|
|
208
|
+
reject(error);
|
|
209
|
+
else
|
|
210
|
+
resolve();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
197
213
|
}
|
|
198
214
|
async stop() {
|
|
199
215
|
try {
|
package/dist/codex-child-env.js
CHANGED
|
@@ -6,6 +6,14 @@ const CODEX_CHILD_ENV_ALLOWLIST = [
|
|
|
6
6
|
"TMPDIR",
|
|
7
7
|
"CODEX_HOME",
|
|
8
8
|
"TRAE_HOME",
|
|
9
|
+
// Keep Runtime-local CLIs on the same resident daemon when its state root is
|
|
10
|
+
// configured outside the default ~/.rynx location.
|
|
11
|
+
"RYNX_HOME",
|
|
12
|
+
// RunnerManager supplies this rotation-safe handle for the exact managed
|
|
13
|
+
// Session. Codex command executions need it to call their own Runtime
|
|
14
|
+
// Browser; the raw Browser capability and internal runner key stay outside
|
|
15
|
+
// the allowlist.
|
|
16
|
+
"RYNX_BROWSER_CONTEXT_FILE",
|
|
9
17
|
"HTTP_PROXY",
|
|
10
18
|
"HTTPS_PROXY",
|
|
11
19
|
"ALL_PROXY",
|
|
@@ -15,6 +23,24 @@ const CODEX_CHILD_ENV_ALLOWLIST = [
|
|
|
15
23
|
"all_proxy",
|
|
16
24
|
"no_proxy",
|
|
17
25
|
];
|
|
26
|
+
const PROXY_ENV_KEYS = [
|
|
27
|
+
"HTTP_PROXY",
|
|
28
|
+
"HTTPS_PROXY",
|
|
29
|
+
"ALL_PROXY",
|
|
30
|
+
"http_proxy",
|
|
31
|
+
"https_proxy",
|
|
32
|
+
"all_proxy",
|
|
33
|
+
];
|
|
34
|
+
const LOOPBACK_NO_PROXY_HOSTS = ["localhost", "127.0.0.1", "::1"];
|
|
35
|
+
function withLoopbackNoProxy(value) {
|
|
36
|
+
const entries = value?.split(",").map((entry) => entry.trim()).filter(Boolean) ?? [];
|
|
37
|
+
const seen = new Set(entries.map((entry) => entry.toLowerCase()));
|
|
38
|
+
for (const host of LOOPBACK_NO_PROXY_HOSTS) {
|
|
39
|
+
if (!seen.has(host))
|
|
40
|
+
entries.push(host);
|
|
41
|
+
}
|
|
42
|
+
return entries.join(",");
|
|
43
|
+
}
|
|
18
44
|
export function createCodexChildEnv(env) {
|
|
19
45
|
const childEnv = {};
|
|
20
46
|
for (const key of CODEX_CHILD_ENV_ALLOWLIST) {
|
|
@@ -23,5 +49,12 @@ export function createCodexChildEnv(env) {
|
|
|
23
49
|
childEnv[key] = value;
|
|
24
50
|
}
|
|
25
51
|
}
|
|
52
|
+
if (childEnv.NO_PROXY || childEnv.no_proxy || PROXY_ENV_KEYS.some((key) => childEnv[key])) {
|
|
53
|
+
const upper = childEnv.NO_PROXY ?? childEnv.no_proxy;
|
|
54
|
+
const lower = childEnv.no_proxy ?? childEnv.NO_PROXY;
|
|
55
|
+
// CIDR matching in NO_PROXY varies; Codex's WebSocket client needs exact loopback hosts.
|
|
56
|
+
childEnv.NO_PROXY = withLoopbackNoProxy(upper);
|
|
57
|
+
childEnv.no_proxy = withLoopbackNoProxy(lower);
|
|
58
|
+
}
|
|
26
59
|
return childEnv;
|
|
27
60
|
}
|
package/dist/codex-home.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The deterministic private CODEX_HOME path for a rynx session. PER-SESSION
|
|
3
|
-
* (uid-scoped + `sha256(sessionId)[:32]`), mirroring
|
|
3
|
+
* (uid-scoped + `sha256(sessionId)[:32]`), mirroring reference implementation's per-session
|
|
4
4
|
* `bridge_dir/codex-home` and rynx's own `claudeBridgeDir`. Pure — computes the
|
|
5
5
|
* path without touching disk, so the daemon (rollout-synth) and the runner child
|
|
6
6
|
* (app-server) both locate the SAME session's home from `sessionId` alone.
|
|
@@ -14,17 +14,17 @@ export declare function legacyCodexHomePath(): string;
|
|
|
14
14
|
* `auth.json`) and settings (copied `config.toml`) but NOT the real home's pending
|
|
15
15
|
* update / first-run (NUX) state — so a co-driven `codex` app-server + `--remote`
|
|
16
16
|
* TUI never block on an "Update now / Press enter to continue" prompt (which would
|
|
17
|
-
* wedge terminal injection). Ports
|
|
17
|
+
* wedge terminal injection). Ports reference implementation's `_CODEX_HOME_SYMLINK_FILES` /
|
|
18
18
|
* `_CODEX_HOME_COPY_FILES`.
|
|
19
19
|
*
|
|
20
|
-
* PER-SESSION: one private home per rynx session (matching
|
|
20
|
+
* PER-SESSION: one private home per rynx session (matching reference implementation), so concurrent
|
|
21
21
|
* agents never share a `skills/` dir. Idempotent: the symlink/copy are refreshed each
|
|
22
22
|
* call so a re-login/config change propagates. Returns the private home dir.
|
|
23
23
|
*/
|
|
24
24
|
export declare function prepareCodexHome(sessionId: string, realHome?: string): string;
|
|
25
25
|
/**
|
|
26
26
|
* Link a resolved skill set into `<codexHome>/skills/<name>/` so the native Codex
|
|
27
|
-
* discovers them at `$CODEX_HOME/skills/` — the SAME filesystem mechanism
|
|
27
|
+
* discovers them at `$CODEX_HOME/skills/` — the SAME filesystem mechanism reference implementation
|
|
28
28
|
* uses (`populate_codex_skills_from_bundle` → `_populate_codex_skills`), NOT a
|
|
29
29
|
* `<skills_instructions>` block injected into the prompt. Codex's app-server
|
|
30
30
|
* watches this dir and re-scans on change, so a live `codex --remote` TUI — whose
|
|
@@ -32,9 +32,9 @@ export declare function prepareCodexHome(sessionId: string, realHome?: string):
|
|
|
32
32
|
* — still sees the agent's skills.
|
|
33
33
|
*
|
|
34
34
|
* Each skill is a symlink to its source dir; a filesystem without symlink support
|
|
35
|
-
* falls back to a recursive copy (matches
|
|
35
|
+
* falls back to a recursive copy (matches reference implementation's fallback).
|
|
36
36
|
*
|
|
37
|
-
*
|
|
37
|
+
* reference implementation boots a fresh per-session CODEX_HOME, so it only ever links into an
|
|
38
38
|
* empty dir. rynx shares ONE private home per runtime (see {@link prepareCodexHome}),
|
|
39
39
|
* so this CONVERGES the dir to `skills`: it links the missing ones and removes
|
|
40
40
|
* entries no longer selected, honouring the agent spec's gating. (Concurrent
|
package/dist/codex-home.js
CHANGED
|
@@ -17,7 +17,7 @@ function rynxUidRoot() {
|
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* The deterministic private CODEX_HOME path for a rynx session. PER-SESSION
|
|
20
|
-
* (uid-scoped + `sha256(sessionId)[:32]`), mirroring
|
|
20
|
+
* (uid-scoped + `sha256(sessionId)[:32]`), mirroring reference implementation's per-session
|
|
21
21
|
* `bridge_dir/codex-home` and rynx's own `claudeBridgeDir`. Pure — computes the
|
|
22
22
|
* path without touching disk, so the daemon (rollout-synth) and the runner child
|
|
23
23
|
* (app-server) both locate the SAME session's home from `sessionId` alone.
|
|
@@ -36,10 +36,10 @@ export function legacyCodexHomePath() {
|
|
|
36
36
|
* `auth.json`) and settings (copied `config.toml`) but NOT the real home's pending
|
|
37
37
|
* update / first-run (NUX) state — so a co-driven `codex` app-server + `--remote`
|
|
38
38
|
* TUI never block on an "Update now / Press enter to continue" prompt (which would
|
|
39
|
-
* wedge terminal injection). Ports
|
|
39
|
+
* wedge terminal injection). Ports reference implementation's `_CODEX_HOME_SYMLINK_FILES` /
|
|
40
40
|
* `_CODEX_HOME_COPY_FILES`.
|
|
41
41
|
*
|
|
42
|
-
* PER-SESSION: one private home per rynx session (matching
|
|
42
|
+
* PER-SESSION: one private home per rynx session (matching reference implementation), so concurrent
|
|
43
43
|
* agents never share a `skills/` dir. Idempotent: the symlink/copy are refreshed each
|
|
44
44
|
* call so a re-login/config change propagates. Returns the private home dir.
|
|
45
45
|
*/
|
|
@@ -79,7 +79,7 @@ export function prepareCodexHome(sessionId, realHome = realCodexHome()) {
|
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
81
|
* Link a resolved skill set into `<codexHome>/skills/<name>/` so the native Codex
|
|
82
|
-
* discovers them at `$CODEX_HOME/skills/` — the SAME filesystem mechanism
|
|
82
|
+
* discovers them at `$CODEX_HOME/skills/` — the SAME filesystem mechanism reference implementation
|
|
83
83
|
* uses (`populate_codex_skills_from_bundle` → `_populate_codex_skills`), NOT a
|
|
84
84
|
* `<skills_instructions>` block injected into the prompt. Codex's app-server
|
|
85
85
|
* watches this dir and re-scans on change, so a live `codex --remote` TUI — whose
|
|
@@ -87,9 +87,9 @@ export function prepareCodexHome(sessionId, realHome = realCodexHome()) {
|
|
|
87
87
|
* — still sees the agent's skills.
|
|
88
88
|
*
|
|
89
89
|
* Each skill is a symlink to its source dir; a filesystem without symlink support
|
|
90
|
-
* falls back to a recursive copy (matches
|
|
90
|
+
* falls back to a recursive copy (matches reference implementation's fallback).
|
|
91
91
|
*
|
|
92
|
-
*
|
|
92
|
+
* reference implementation boots a fresh per-session CODEX_HOME, so it only ever links into an
|
|
93
93
|
* empty dir. rynx shares ONE private home per runtime (see {@link prepareCodexHome}),
|
|
94
94
|
* so this CONVERGES the dir to `skills`: it links the missing ones and removes
|
|
95
95
|
* entries no longer selected, honouring the agent spec's gating. (Concurrent
|
|
@@ -99,7 +99,7 @@ export function prepareCodexHome(sessionId, realHome = realCodexHome()) {
|
|
|
99
99
|
export function populateCodexSkills(codexHome, skills) {
|
|
100
100
|
const skillsDir = join(codexHome, "skills");
|
|
101
101
|
const want = new Map(skills.map((s) => [s.name, s.dir]));
|
|
102
|
-
// Converge: drop entries no longer selected (shared-home adaptation;
|
|
102
|
+
// Converge: drop entries no longer selected (shared-home adaptation; reference implementation's
|
|
103
103
|
// per-session home never needs this). `.system` holds codex's own embedded system
|
|
104
104
|
// skills (the app-server installs them into `$CODEX_HOME/skills/.system`) — never
|
|
105
105
|
// rynx-managed, so leave it untouched; only converge the entries we linked.
|
|
@@ -116,8 +116,7 @@ export function populateCodexSkills(codexHome, skills) {
|
|
|
116
116
|
mkdirSync(skillsDir, { recursive: true });
|
|
117
117
|
for (const [name, src] of want) {
|
|
118
118
|
const link = join(skillsDir, name);
|
|
119
|
-
|
|
120
|
-
continue; // already linked (a skill's dir for a name is stable)
|
|
119
|
+
rmSync(link, { recursive: true, force: true });
|
|
121
120
|
try {
|
|
122
121
|
symlinkSync(src, link);
|
|
123
122
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { type AgentRuntimeId } from "@rynx-ai/core";
|
|
1
|
+
import { type AgentRuntimeId, type ReasoningEffort } from "@rynx-ai/core";
|
|
2
2
|
import type { AppConfig } from "@rynx-ai/core";
|
|
3
3
|
export interface CodexSessionRecord {
|
|
4
4
|
localThreadId: string;
|
|
5
5
|
codexSessionId: string;
|
|
6
6
|
cwd: string;
|
|
7
7
|
model: string;
|
|
8
|
+
reasoningEffort?: ReasoningEffort;
|
|
8
9
|
/** Runtime this thread is bound to. Legacy records backfill to `codex`. */
|
|
9
10
|
runtime: AgentRuntimeId;
|
|
10
11
|
/** Declarative agent spec name bound to this thread, if any. */
|