@rynx-ai/runtime 0.1.0 → 0.1.10-beta.2
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 +327 -38
- package/dist/claude/native-hooks.d.ts +3 -2
- package/dist/claude/native-hooks.js +15 -6
- package/dist/claude/native-integration.d.ts +123 -16
- package/dist/claude/native-integration.js +624 -81
- 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 +14 -3
- package/dist/codex/rollout-synth.d.ts +8 -3
- package/dist/codex/rollout-synth.js +65 -32
- package/dist/codex-app-server/client.d.ts +27 -40
- package/dist/codex-app-server/client.js +1134 -99
- package/dist/codex-app-server/forwarder.d.ts +36 -10
- package/dist/codex-app-server/forwarder.js +146 -28
- package/dist/codex-app-server/mapping.d.ts +1 -1
- package/dist/codex-app-server/mapping.js +64 -5
- package/dist/codex-app-server/protocol.d.ts +269 -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 +16 -6
- package/dist/codex-home.js +46 -15
- package/dist/codex-session-store.d.ts +2 -1
- package/dist/host.d.ts +38 -38
- package/dist/host.js +626 -121
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/input-resources.d.ts +13 -0
- package/dist/input-resources.js +67 -0
- package/dist/interactions.d.ts +61 -0
- package/dist/interactions.js +236 -0
- package/dist/models-catalog.d.ts +5 -13
- package/dist/models-catalog.js +60 -9
- package/dist/runner/child.d.ts +9 -1
- package/dist/runner/child.js +100 -19
- package/dist/runner/manager.d.ts +79 -11
- package/dist/runner/manager.js +423 -43
- package/dist/runner/protocol.d.ts +30 -11
- 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 +8 -3
|
@@ -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,11 +1,15 @@
|
|
|
1
|
+
import { type AgentRuntimeId } from "@rynx-ai/core";
|
|
2
|
+
export type CodexLineageRuntime = Exclude<AgentRuntimeId, "claude">;
|
|
1
3
|
/**
|
|
2
4
|
* The deterministic private CODEX_HOME path for a rynx session. PER-SESSION
|
|
3
|
-
* (uid-scoped + `sha256(sessionId)[:32]`), mirroring
|
|
5
|
+
* (uid-scoped + `sha256(sessionId)[:32]`), mirroring reference implementation's per-session
|
|
4
6
|
* `bridge_dir/codex-home` and rynx's own `claudeBridgeDir`. Pure — computes the
|
|
5
7
|
* path without touching disk, so the daemon (rollout-synth) and the runner child
|
|
6
8
|
* (app-server) both locate the SAME session's home from `sessionId` alone.
|
|
7
9
|
*/
|
|
8
10
|
export declare function codexHomePath(sessionId: string): string;
|
|
11
|
+
/** Deterministic private home for a Codex-lineage runtime. */
|
|
12
|
+
export declare function runtimeHomePath(sessionId: string, runtime: CodexLineageRuntime): string;
|
|
9
13
|
/** The OLD uid-scoped shared home (pre per-session). Kept ONLY for back-compat
|
|
10
14
|
* resume fallback — a session's rollout may still live under here. */
|
|
11
15
|
export declare function legacyCodexHomePath(): string;
|
|
@@ -14,17 +18,23 @@ export declare function legacyCodexHomePath(): string;
|
|
|
14
18
|
* `auth.json`) and settings (copied `config.toml`) but NOT the real home's pending
|
|
15
19
|
* update / first-run (NUX) state — so a co-driven `codex` app-server + `--remote`
|
|
16
20
|
* TUI never block on an "Update now / Press enter to continue" prompt (which would
|
|
17
|
-
* wedge terminal injection). Ports
|
|
21
|
+
* wedge terminal injection). Ports reference implementation's `_CODEX_HOME_SYMLINK_FILES` /
|
|
18
22
|
* `_CODEX_HOME_COPY_FILES`.
|
|
19
23
|
*
|
|
20
|
-
* PER-SESSION: one private home per rynx session (matching
|
|
24
|
+
* PER-SESSION: one private home per rynx session (matching reference implementation), so concurrent
|
|
21
25
|
* agents never share a `skills/` dir. Idempotent: the symlink/copy are refreshed each
|
|
22
26
|
* call so a re-login/config change propagates. Returns the private home dir.
|
|
23
27
|
*/
|
|
24
28
|
export declare function prepareCodexHome(sessionId: string, realHome?: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Prepare a private runtime home while inheriting only the login and settings
|
|
31
|
+
* files required by the selected CLI. Mutable update/NUX state remains in the
|
|
32
|
+
* real home and cannot wedge a managed app-server/TUI pair.
|
|
33
|
+
*/
|
|
34
|
+
export declare function prepareRuntimeHome(sessionId: string, runtime: CodexLineageRuntime, realHome?: string): string;
|
|
25
35
|
/**
|
|
26
36
|
* Link a resolved skill set into `<codexHome>/skills/<name>/` so the native Codex
|
|
27
|
-
* discovers them at `$CODEX_HOME/skills/` — the SAME filesystem mechanism
|
|
37
|
+
* discovers them at `$CODEX_HOME/skills/` — the SAME filesystem mechanism reference implementation
|
|
28
38
|
* uses (`populate_codex_skills_from_bundle` → `_populate_codex_skills`), NOT a
|
|
29
39
|
* `<skills_instructions>` block injected into the prompt. Codex's app-server
|
|
30
40
|
* watches this dir and re-scans on change, so a live `codex --remote` TUI — whose
|
|
@@ -32,9 +42,9 @@ export declare function prepareCodexHome(sessionId: string, realHome?: string):
|
|
|
32
42
|
* — still sees the agent's skills.
|
|
33
43
|
*
|
|
34
44
|
* Each skill is a symlink to its source dir; a filesystem without symlink support
|
|
35
|
-
* falls back to a recursive copy (matches
|
|
45
|
+
* falls back to a recursive copy (matches reference implementation's fallback).
|
|
36
46
|
*
|
|
37
|
-
*
|
|
47
|
+
* reference implementation boots a fresh per-session CODEX_HOME, so it only ever links into an
|
|
38
48
|
* empty dir. rynx shares ONE private home per runtime (see {@link prepareCodexHome}),
|
|
39
49
|
* so this CONVERGES the dir to `skills`: it links the missing ones and removes
|
|
40
50
|
* entries no longer selected, honouring the agent spec's gating. (Concurrent
|
package/dist/codex-home.js
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, rmSync, symlinkSync } from "node:fs";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { getRuntimeProfile, resolveRuntimeHome, } from "@rynx-ai/core";
|
|
5
6
|
/** Inherit the user's LIVE login by symlink (stays in sync). */
|
|
6
7
|
const SYMLINK_FILES = ["auth.json"];
|
|
7
8
|
/** Inherit the user's settings by snapshot copy (not the mutable NUX/update state). */
|
|
8
9
|
const COPY_FILES = ["config.toml"];
|
|
10
|
+
const RUNTIME_HOME_FILES = {
|
|
11
|
+
codex: { symlink: SYMLINK_FILES, copy: COPY_FILES },
|
|
12
|
+
traex: {
|
|
13
|
+
// Traex keeps credentials below `cli/`, while its user configuration lives
|
|
14
|
+
// at the TRAE_HOME root. Keep both the current TOML name and the legacy YAML
|
|
15
|
+
// name so existing installations remain usable without an implicit migrate.
|
|
16
|
+
symlink: ["cli/auth.json"],
|
|
17
|
+
copy: ["traecli.toml", "traecli.yaml"],
|
|
18
|
+
},
|
|
19
|
+
};
|
|
9
20
|
/** The user's real CODEX_HOME (env override, else `~/.codex`). */
|
|
10
21
|
function realCodexHome() {
|
|
11
22
|
return process.env.CODEX_HOME?.trim() || join(homedir(), ".codex");
|
|
@@ -17,7 +28,7 @@ function rynxUidRoot() {
|
|
|
17
28
|
}
|
|
18
29
|
/**
|
|
19
30
|
* The deterministic private CODEX_HOME path for a rynx session. PER-SESSION
|
|
20
|
-
* (uid-scoped + `sha256(sessionId)[:32]`), mirroring
|
|
31
|
+
* (uid-scoped + `sha256(sessionId)[:32]`), mirroring reference implementation's per-session
|
|
21
32
|
* `bridge_dir/codex-home` and rynx's own `claudeBridgeDir`. Pure — computes the
|
|
22
33
|
* path without touching disk, so the daemon (rollout-synth) and the runner child
|
|
23
34
|
* (app-server) both locate the SAME session's home from `sessionId` alone.
|
|
@@ -26,6 +37,13 @@ export function codexHomePath(sessionId) {
|
|
|
26
37
|
const digest = createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
27
38
|
return join(rynxUidRoot(), "codex-native", digest, "codex-home");
|
|
28
39
|
}
|
|
40
|
+
/** Deterministic private home for a Codex-lineage runtime. */
|
|
41
|
+
export function runtimeHomePath(sessionId, runtime) {
|
|
42
|
+
if (runtime === "codex")
|
|
43
|
+
return codexHomePath(sessionId);
|
|
44
|
+
const digest = createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
45
|
+
return join(rynxUidRoot(), "traex-native", digest, "trae-home");
|
|
46
|
+
}
|
|
29
47
|
/** The OLD uid-scoped shared home (pre per-session). Kept ONLY for back-compat
|
|
30
48
|
* resume fallback — a session's rollout may still live under here. */
|
|
31
49
|
export function legacyCodexHomePath() {
|
|
@@ -36,21 +54,33 @@ export function legacyCodexHomePath() {
|
|
|
36
54
|
* `auth.json`) and settings (copied `config.toml`) but NOT the real home's pending
|
|
37
55
|
* update / first-run (NUX) state — so a co-driven `codex` app-server + `--remote`
|
|
38
56
|
* TUI never block on an "Update now / Press enter to continue" prompt (which would
|
|
39
|
-
* wedge terminal injection). Ports
|
|
57
|
+
* wedge terminal injection). Ports reference implementation's `_CODEX_HOME_SYMLINK_FILES` /
|
|
40
58
|
* `_CODEX_HOME_COPY_FILES`.
|
|
41
59
|
*
|
|
42
|
-
* PER-SESSION: one private home per rynx session (matching
|
|
60
|
+
* PER-SESSION: one private home per rynx session (matching reference implementation), so concurrent
|
|
43
61
|
* agents never share a `skills/` dir. Idempotent: the symlink/copy are refreshed each
|
|
44
62
|
* call so a re-login/config change propagates. Returns the private home dir.
|
|
45
63
|
*/
|
|
46
64
|
export function prepareCodexHome(sessionId, realHome = realCodexHome()) {
|
|
47
|
-
|
|
65
|
+
return prepareRuntimeHome(sessionId, "codex", realHome);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Prepare a private runtime home while inheriting only the login and settings
|
|
69
|
+
* files required by the selected CLI. Mutable update/NUX state remains in the
|
|
70
|
+
* real home and cannot wedge a managed app-server/TUI pair.
|
|
71
|
+
*/
|
|
72
|
+
export function prepareRuntimeHome(sessionId, runtime, realHome = runtime === "codex"
|
|
73
|
+
? realCodexHome()
|
|
74
|
+
: resolveRuntimeHome(getRuntimeProfile(runtime))) {
|
|
75
|
+
const dir = runtimeHomePath(sessionId, runtime);
|
|
48
76
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
49
|
-
|
|
77
|
+
const files = RUNTIME_HOME_FILES[runtime];
|
|
78
|
+
for (const name of files.symlink) {
|
|
50
79
|
const src = join(realHome, name);
|
|
51
80
|
const dst = join(dir, name);
|
|
81
|
+
mkdirSync(dirname(dst), { recursive: true, mode: 0o700 });
|
|
52
82
|
try {
|
|
53
|
-
rmSync(dst, { force: true });
|
|
83
|
+
rmSync(dst, { recursive: true, force: true });
|
|
54
84
|
}
|
|
55
85
|
catch {
|
|
56
86
|
// absent — fine
|
|
@@ -64,11 +94,13 @@ export function prepareCodexHome(sessionId, realHome = realCodexHome()) {
|
|
|
64
94
|
}
|
|
65
95
|
}
|
|
66
96
|
}
|
|
67
|
-
for (const name of
|
|
97
|
+
for (const name of files.copy) {
|
|
68
98
|
const src = join(realHome, name);
|
|
69
99
|
if (existsSync(src)) {
|
|
70
100
|
try {
|
|
71
|
-
|
|
101
|
+
const dst = join(dir, name);
|
|
102
|
+
mkdirSync(dirname(dst), { recursive: true, mode: 0o700 });
|
|
103
|
+
copyFileSync(src, dst);
|
|
72
104
|
}
|
|
73
105
|
catch {
|
|
74
106
|
// best-effort
|
|
@@ -79,7 +111,7 @@ export function prepareCodexHome(sessionId, realHome = realCodexHome()) {
|
|
|
79
111
|
}
|
|
80
112
|
/**
|
|
81
113
|
* Link a resolved skill set into `<codexHome>/skills/<name>/` so the native Codex
|
|
82
|
-
* discovers them at `$CODEX_HOME/skills/` — the SAME filesystem mechanism
|
|
114
|
+
* discovers them at `$CODEX_HOME/skills/` — the SAME filesystem mechanism reference implementation
|
|
83
115
|
* uses (`populate_codex_skills_from_bundle` → `_populate_codex_skills`), NOT a
|
|
84
116
|
* `<skills_instructions>` block injected into the prompt. Codex's app-server
|
|
85
117
|
* watches this dir and re-scans on change, so a live `codex --remote` TUI — whose
|
|
@@ -87,9 +119,9 @@ export function prepareCodexHome(sessionId, realHome = realCodexHome()) {
|
|
|
87
119
|
* — still sees the agent's skills.
|
|
88
120
|
*
|
|
89
121
|
* Each skill is a symlink to its source dir; a filesystem without symlink support
|
|
90
|
-
* falls back to a recursive copy (matches
|
|
122
|
+
* falls back to a recursive copy (matches reference implementation's fallback).
|
|
91
123
|
*
|
|
92
|
-
*
|
|
124
|
+
* reference implementation boots a fresh per-session CODEX_HOME, so it only ever links into an
|
|
93
125
|
* empty dir. rynx shares ONE private home per runtime (see {@link prepareCodexHome}),
|
|
94
126
|
* so this CONVERGES the dir to `skills`: it links the missing ones and removes
|
|
95
127
|
* entries no longer selected, honouring the agent spec's gating. (Concurrent
|
|
@@ -99,7 +131,7 @@ export function prepareCodexHome(sessionId, realHome = realCodexHome()) {
|
|
|
99
131
|
export function populateCodexSkills(codexHome, skills) {
|
|
100
132
|
const skillsDir = join(codexHome, "skills");
|
|
101
133
|
const want = new Map(skills.map((s) => [s.name, s.dir]));
|
|
102
|
-
// Converge: drop entries no longer selected (shared-home adaptation;
|
|
134
|
+
// Converge: drop entries no longer selected (shared-home adaptation; reference implementation's
|
|
103
135
|
// per-session home never needs this). `.system` holds codex's own embedded system
|
|
104
136
|
// skills (the app-server installs them into `$CODEX_HOME/skills/.system`) — never
|
|
105
137
|
// rynx-managed, so leave it untouched; only converge the entries we linked.
|
|
@@ -116,8 +148,7 @@ export function populateCodexSkills(codexHome, skills) {
|
|
|
116
148
|
mkdirSync(skillsDir, { recursive: true });
|
|
117
149
|
for (const [name, src] of want) {
|
|
118
150
|
const link = join(skillsDir, name);
|
|
119
|
-
|
|
120
|
-
continue; // already linked (a skill's dir for a name is stable)
|
|
151
|
+
rmSync(link, { recursive: true, force: true });
|
|
121
152
|
try {
|
|
122
153
|
symlinkSync(src, link);
|
|
123
154
|
}
|
|
@@ -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. */
|
package/dist/host.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { type AgentSpec, type ResolvedExecutionBudget, type SessionEvent } from "@rynx-ai/core";
|
|
1
|
+
import { type AgentSpec, type ReasoningEffort, type ResolvedExecutionBudget, type RuntimeUserInput, 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";
|
|
5
5
|
import type { InjectOutcome } from "./runner/protocol.js";
|
|
6
|
+
import type { ResolveInteractionResult } from "./interactions.js";
|
|
6
7
|
import { CodexAppServerClient } from "./codex-app-server/client.js";
|
|
7
8
|
import type { ModelListResponse, ThreadGoal } from "./codex-app-server/protocol.js";
|
|
8
9
|
import { type TerminalInjector } from "./claude/native-integration.js";
|
|
@@ -107,7 +108,9 @@ export type RetargetMirror = (newSessionId: string, meta: {
|
|
|
107
108
|
parentSessionId?: string;
|
|
108
109
|
}) => void;
|
|
109
110
|
export interface LiveSessionOpts {
|
|
111
|
+
cwd?: string;
|
|
110
112
|
runtime?: AgentRuntimeId;
|
|
113
|
+
reasoningEffort?: ReasoningEffort;
|
|
111
114
|
/** Preset agent id (or inline {@link LiveSessionOpts.agentSpec}), so the live
|
|
112
115
|
* launch applies the agent's model / skills / instructions — not just the
|
|
113
116
|
* runtime. Absent ⇒ falls back to the config defaults (prior behavior). */
|
|
@@ -137,9 +140,12 @@ export declare class LocalAgentHost implements CodexCapabilities {
|
|
|
137
140
|
private readonly sessionId;
|
|
138
141
|
private sessionSandbox?;
|
|
139
142
|
private sessionApprovalPolicy?;
|
|
140
|
-
private
|
|
143
|
+
private readonly runtimeHomes;
|
|
141
144
|
private readonly liveSessions;
|
|
142
145
|
private readonly liveClaudeSessions;
|
|
146
|
+
/** Claude forwarders stopped before their terminal is killed. Runner shutdown
|
|
147
|
+
* finalizes these synchronously afterwards to scrub raw interaction answers. */
|
|
148
|
+
private readonly pendingClaudeFinalizers;
|
|
143
149
|
private readonly liveEnsuring;
|
|
144
150
|
constructor({ config, commandRunner, sessionStore, allowedRoots, appServerClient, now, backendIdleTtlMs, forwarderClientFactory, sessionId, }: {
|
|
145
151
|
config: AppConfig;
|
|
@@ -169,16 +175,11 @@ export declare class LocalAgentHost implements CodexCapabilities {
|
|
|
169
175
|
*/
|
|
170
176
|
private reapIdleBackends;
|
|
171
177
|
private createBackend;
|
|
172
|
-
/** This session's private
|
|
173
|
-
private
|
|
178
|
+
/** This session's private native home, shared by its app-server + TUI. */
|
|
179
|
+
private runtimeHome;
|
|
174
180
|
private createAppServerClient;
|
|
175
|
-
/**
|
|
176
|
-
|
|
177
|
-
* Routes to the codex app-server client for the thread's runtime. Returns
|
|
178
|
-
* false when no matching pending approval exists (unknown id / already
|
|
179
|
-
* resolved / claude runtime).
|
|
180
|
-
*/
|
|
181
|
-
resolveApproval(localThreadId: string, approvalId: string, decision: "acceptForSession" | "accept" | "decline" | "cancel"): Promise<boolean>;
|
|
181
|
+
/** Resolve a pending native question/approval without opening a new Turn. */
|
|
182
|
+
resolveInteraction(localThreadId: string, interactionId: string, resolution: SessionInteractionResolution): Promise<ResolveInteractionResult>;
|
|
182
183
|
/**
|
|
183
184
|
* The command to run in a session's live terminal so it co-drives the codex
|
|
184
185
|
* app-server thread (Phase D). Returns `null` when live-terminal is off, the
|
|
@@ -195,7 +196,7 @@ export declare class LocalAgentHost implements CodexCapabilities {
|
|
|
195
196
|
} | null>;
|
|
196
197
|
/**
|
|
197
198
|
* Bring up (idempotently) a session's persistent codex forwarder and emit its
|
|
198
|
-
* mirrored {@link SessionEvent}s.
|
|
199
|
+
* mirrored {@link SessionEvent}s. reference implementation's codex-native single-writer model:
|
|
199
200
|
* this forwarder is the SOLE producer of the session's canonical events — every
|
|
200
201
|
* turn on the thread, web-composer-injected AND co-driving-`codex --remote`-TUI
|
|
201
202
|
* initiated. Each turn gets a {@link SessionNormalizer} whose `responseId` is
|
|
@@ -211,11 +212,12 @@ export declare class LocalAgentHost implements CodexCapabilities {
|
|
|
211
212
|
*/
|
|
212
213
|
ensureLiveCodexSession(localThreadId: string, emit: (event: SessionEvent) => void, opts?: LiveSessionOpts): Promise<boolean>;
|
|
213
214
|
private startLiveCodexSession;
|
|
215
|
+
private refreshLiveCodexSession;
|
|
214
216
|
/** Bind a session's codex thread id once known (TUI broadcast or store): persist
|
|
215
217
|
* it, unblock injection, and kick off the resume-subscribe loop (once). */
|
|
216
218
|
private onLiveThreadStarted;
|
|
217
219
|
/**
|
|
218
|
-
* Subscribe the forwarder connection to a thread (
|
|
220
|
+
* Subscribe the forwarder connection to a thread (reference implementation's
|
|
219
221
|
* `_subscribe_until_ready`). A fresh TUI thread has no rollout until its first
|
|
220
222
|
* turn, so `thread/resume` is retried: park until the forwarder observes the
|
|
221
223
|
* thread active, then retry WITHOUT `excludeTurns` so the response backfills the
|
|
@@ -227,7 +229,7 @@ export declare class LocalAgentHost implements CodexCapabilities {
|
|
|
227
229
|
* no live session. Injection and the runner's `live.ready` gate on this. */
|
|
228
230
|
waitLiveReady(localThreadId: string, timeoutMs?: number): Promise<boolean>;
|
|
229
231
|
/**
|
|
230
|
-
* Inject a user turn into a session's live codex thread —
|
|
232
|
+
* Inject a user turn into a session's live codex thread — reference implementation's
|
|
231
233
|
* single-writer web send. `turn/steer` when a turn is open (mid-turn
|
|
232
234
|
* supplement), else `turn/start`. NEVER creates a thread (the TUI owns creation;
|
|
233
235
|
* this targets the id the forwarder captured). Serialized per session so two
|
|
@@ -236,67 +238,66 @@ export declare class LocalAgentHost implements CodexCapabilities {
|
|
|
236
238
|
* Returns an {@link InjectOutcome}: `notLive` when this session has no live
|
|
237
239
|
* forwarder (caller may use the run path); `notReady`/`failed` are hard errors
|
|
238
240
|
* the caller reports WITHOUT re-running (re-running double-writes alongside the
|
|
239
|
-
* forwarder). Park-until-ready (~60s), aligning
|
|
241
|
+
* forwarder). Park-until-ready (~60s), aligning reference implementation's executor waiting for
|
|
240
242
|
* the bridge instead of a short race that falls back to a second output path.
|
|
241
243
|
*/
|
|
242
|
-
injectMessage(localThreadId: string,
|
|
244
|
+
injectMessage(localThreadId: string, input: RuntimeUserInput | string): Promise<InjectOutcome>;
|
|
243
245
|
/**
|
|
244
246
|
* Interrupt the session's active turn — the web Stop button. codex: the
|
|
245
247
|
* app-server `turn/interrupt` on the active `{threadId, turnId}` (exactly what
|
|
246
|
-
* codex's own TUI interrupt key sends —
|
|
247
|
-
* claude: an Escape keystroke into the pane (no app-server —
|
|
248
|
+
* codex's own TUI interrupt key sends — reference implementation `_handle_codex_native_interrupt`).
|
|
249
|
+
* claude: an Escape keystroke into the pane (no app-server — reference implementation
|
|
248
250
|
* `inject_interrupt`). Returns false as a no-op when there is no active turn.
|
|
249
251
|
*/
|
|
250
252
|
interruptLive(localThreadId: string): Promise<boolean>;
|
|
251
253
|
/** Stop + drop a session's live forwarder and its dedicated connection (session
|
|
252
254
|
* close / runner shutdown). The backend inject client is shared — left running. */
|
|
253
|
-
stopLiveCodexSession(localThreadId: string
|
|
255
|
+
stopLiveCodexSession(localThreadId: string, opts?: {
|
|
256
|
+
deferClaudeInteractionCleanup?: boolean;
|
|
257
|
+
}): void;
|
|
258
|
+
/** Complete the second shutdown phase after the runner has killed all native
|
|
259
|
+
* terminals and hook subprocesses. Must run before the runner process exits. */
|
|
260
|
+
finalizeStoppedLiveSessions(): void;
|
|
254
261
|
/** The interactive `claude` TUI spec for a claude-native live session: the real
|
|
255
262
|
* `claude` binary with rynx's hooks (`--settings`), resuming the bound session.
|
|
256
263
|
* Trust is pre-seeded so the daemon-spawned TUI never blocks on a first-run
|
|
257
|
-
* prompt.
|
|
258
|
-
*
|
|
264
|
+
* prompt. Questions and PermissionRequest approvals use the runtime-local
|
|
265
|
+
* generic interaction bridge and remain inside the active Turn. */
|
|
259
266
|
private claudeTerminalSpec;
|
|
260
267
|
/** Bring up a claude-native live forwarder: prepare the bridge dir (the TUI's
|
|
261
268
|
* hooks write into it), then tail bridge + transcript and drive a per-turn
|
|
262
269
|
* {@link SessionNormalizer} → `emit` (the SAME sink shape the codex path uses). */
|
|
263
|
-
/** Resolve an agent spec's launch config — model
|
|
270
|
+
/** Resolve an agent spec's launch config — model, instructions, and the session skill env —
|
|
264
271
|
* for a LIVE native session, reusing the same core resolvers the non-live
|
|
265
272
|
* run path uses ({@link resolveAgentExecution} for the model,
|
|
266
|
-
* {@link resolveAgent} for skills).
|
|
267
|
-
* returned: the co-driven TUI runs on vendor defaults (see
|
|
268
|
-
* startLiveClaudeSession).
|
|
273
|
+
* {@link resolveAgent} for instructions and skills).
|
|
269
274
|
*
|
|
270
275
|
* Skills are spec-rooted: each declared ref is replayed into a
|
|
271
276
|
* SESSION-scoped temp dir (cache-accelerated); the owner's catalog plays no
|
|
272
|
-
* role, and no spec / no `skills` means ZERO skills.
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
277
|
+
* role, and no spec / no `skills` means ZERO skills. Every declared skill
|
|
278
|
+
* must resolve at its declared content hash; missing, failed, or drifted
|
|
279
|
+
* materialization aborts launch before a native session starts.
|
|
280
|
+
* `skillsCleanup` removes the session dir (call on session stop). */
|
|
276
281
|
private resolveLiveAgentConfig;
|
|
277
282
|
private startLiveClaudeSession;
|
|
278
283
|
/** Persist claude's discovered session id (reusing the `codexSessionId` store
|
|
279
284
|
* field, as the claude executor already does) and release the readiness gate. */
|
|
280
285
|
private onClaudeDiscovered;
|
|
281
|
-
/** Inject a web message into a claude-native pane via tmux (
|
|
286
|
+
/** Inject a web message into a claude-native pane via tmux (reference implementation recipe),
|
|
282
287
|
* serialized per session. Parks until the thread is ready AND the tmux injector
|
|
283
288
|
* is (re)attached, then pastes; `injectViaTerminal` RAISES if the prompt never
|
|
284
|
-
* appears (
|
|
289
|
+
* appears (reference implementation RAISE), so a not-ready pane is a hard error — NOT a
|
|
285
290
|
* fall-through-to-run signal. Returns {@link InjectOutcome}. */
|
|
286
291
|
private injectClaude;
|
|
287
292
|
/** Park until the claude session's tmux injector is (re)attached by the
|
|
288
293
|
* runner-child, or the deadline passes. Pane relaunch re-attaches it via
|
|
289
294
|
* {@link attachTerminalInjector}; without this a message during that window
|
|
290
|
-
* would fail and (pre-
|
|
295
|
+
* would fail and (pre-reference implementation) fall through to a second output path. */
|
|
291
296
|
private waitInjector;
|
|
292
297
|
/** Attach a session's tmux pane injector (from the runner-child, which owns the
|
|
293
298
|
* terminal registry — the host does not hold tmux). Ignored for codex sessions
|
|
294
299
|
* (they inject via the app-server). Idempotent. */
|
|
295
300
|
attachTerminalInjector(localThreadId: string, injector: TerminalInjector): void;
|
|
296
|
-
/** The PermissionRequest hook's server target from the runner-child env (the
|
|
297
|
-
* daemon control URL + token), or undefined when unset — then no permission
|
|
298
|
-
* hook is registered and claude surfaces its own TUI prompt in the pane. */
|
|
299
|
-
private claudePermissionConfig;
|
|
300
301
|
/** List the models a runtime exposes (App Server for codex/traex; static for claude). */
|
|
301
302
|
listModels(runtime?: AgentRuntimeId): Promise<ModelListResponse | null>;
|
|
302
303
|
/**
|
|
@@ -315,10 +316,9 @@ export declare function parseCodexLoginStatus(exitCode: number, output: string):
|
|
|
315
316
|
authMode: string | null;
|
|
316
317
|
issues: string[];
|
|
317
318
|
};
|
|
318
|
-
export declare function getCodexModel(config: AppConfig, override?: string): string;
|
|
319
319
|
export { createCodexChildEnv };
|
|
320
320
|
export declare function isUnsupportedMethodError(error: unknown): boolean;
|
|
321
321
|
/** A `thread/resume` failure meaning the thread has no (or an empty) rollout yet
|
|
322
322
|
* — a fresh TUI thread before its first turn. Retryable (park until active).
|
|
323
|
-
* Mirrors
|
|
323
|
+
* Mirrors reference implementation's `_is_thread_not_ready_error`. */
|
|
324
324
|
export declare function isThreadNotReadyError(error: unknown): boolean;
|