@rynx-ai/runtime 0.1.9 → 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/native-hook-main.js +45 -8
- package/dist/claude/native-integration.d.ts +45 -11
- package/dist/claude/native-integration.js +211 -59
- package/dist/claude/transcript.js +11 -0
- package/dist/codex/rollout-synth.d.ts +8 -3
- package/dist/codex/rollout-synth.js +64 -31
- package/dist/codex-app-server/client.d.ts +2 -1
- package/dist/codex-app-server/client.js +6 -0
- package/dist/codex-app-server/forwarder.d.ts +29 -3
- package/dist/codex-app-server/forwarder.js +135 -23
- package/dist/codex-app-server/mapping.js +37 -3
- package/dist/codex-app-server/protocol.d.ts +32 -1
- package/dist/codex-home.d.ts +10 -0
- package/dist/codex-home.js +38 -6
- package/dist/host.d.ts +5 -6
- package/dist/host.js +101 -36
- package/dist/input-resources.d.ts +13 -0
- package/dist/input-resources.js +67 -0
- package/dist/models-catalog.d.ts +5 -13
- package/dist/models-catalog.js +59 -8
- package/dist/runner/child.js +7 -4
- package/dist/runner/manager.d.ts +21 -2
- package/dist/runner/manager.js +38 -2
- package/dist/runner/protocol.d.ts +13 -5
- package/package.json +7 -2
package/dist/models-catalog.js
CHANGED
|
@@ -3,25 +3,76 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The parent control plane no longer holds an app-server, so `/models` can't be
|
|
5
5
|
* a live `model/list` RPC anymore. Following reference implementation's static-catalog model, we
|
|
6
|
-
* serve a config-derived list
|
|
7
|
-
*
|
|
8
|
-
* minimal — a fuller curated catalogue can be added here later without touching
|
|
9
|
-
* any caller. claude already has its own static list ({@link listClaudeModels}).
|
|
6
|
+
* serve a config-derived Codex list and Traex's native `models --json` catalog.
|
|
7
|
+
* claude already has its own static list ({@link listClaudeModels}).
|
|
10
8
|
*/
|
|
11
|
-
import {
|
|
9
|
+
import { execFile } from "node:child_process";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
|
+
import { resolveRuntimeBinary, resolveRuntimeModel, } from "@rynx-ai/core";
|
|
12
12
|
import { listClaudeModels } from "./claude/models.js";
|
|
13
|
+
const execFileAsync = promisify(execFile);
|
|
14
|
+
const TRAEX_MODELS_TIMEOUT_MS = 8_000;
|
|
15
|
+
const TRAEX_MODELS_MAX_BYTES = 2 * 1024 * 1024;
|
|
13
16
|
/**
|
|
14
17
|
* The model list for a runtime, without an execution backend.
|
|
15
|
-
*
|
|
16
|
-
* no `TRAEX_MODEL`), matching the prior "unsupported" semantics of `listModels`.
|
|
18
|
+
* Falls back to the configured model if live Traex discovery is unavailable.
|
|
17
19
|
*/
|
|
18
|
-
export function listRuntimeModels(config, runtime) {
|
|
20
|
+
export async function listRuntimeModels(config, runtime, deps = {}) {
|
|
19
21
|
if (runtime === "claude") {
|
|
20
22
|
return listClaudeModels();
|
|
21
23
|
}
|
|
24
|
+
if (runtime === "traex") {
|
|
25
|
+
try {
|
|
26
|
+
const value = await (deps.readTraexModels ?? readTraexModels)();
|
|
27
|
+
const models = normalizeTraexModels(value, resolveRuntimeModel(config, runtime).trim());
|
|
28
|
+
if (models.length > 0)
|
|
29
|
+
return { data: models };
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Keep the configured fallback usable when Traex is missing/logged out.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
22
35
|
const model = resolveRuntimeModel(config, runtime).trim();
|
|
23
36
|
if (!model) {
|
|
24
37
|
return null;
|
|
25
38
|
}
|
|
26
39
|
return { data: [{ id: model, model, isDefault: true }] };
|
|
27
40
|
}
|
|
41
|
+
async function readTraexModels() {
|
|
42
|
+
const { stdout } = await execFileAsync(resolveRuntimeBinary("traex"), ["models", "--json"], {
|
|
43
|
+
encoding: "utf8",
|
|
44
|
+
timeout: TRAEX_MODELS_TIMEOUT_MS,
|
|
45
|
+
maxBuffer: TRAEX_MODELS_MAX_BYTES,
|
|
46
|
+
});
|
|
47
|
+
return JSON.parse(stdout);
|
|
48
|
+
}
|
|
49
|
+
function normalizeTraexModels(value, configuredDefault) {
|
|
50
|
+
if (!Array.isArray(value))
|
|
51
|
+
return [];
|
|
52
|
+
const models = [];
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
for (const item of value) {
|
|
55
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
56
|
+
continue;
|
|
57
|
+
const record = item;
|
|
58
|
+
const id = typeof record.name === "string" ? record.name.trim() : "";
|
|
59
|
+
if (!id || seen.has(id))
|
|
60
|
+
continue;
|
|
61
|
+
seen.add(id);
|
|
62
|
+
models.push({
|
|
63
|
+
id,
|
|
64
|
+
model: id,
|
|
65
|
+
...(typeof record.description === "string" ? { description: record.description } : {}),
|
|
66
|
+
isDefault: configuredDefault ? id === configuredDefault : models.length === 0,
|
|
67
|
+
...(typeof record.context_window === "number" ? { contextWindow: record.context_window } : {}),
|
|
68
|
+
...(Array.isArray(record.supported_mime_types)
|
|
69
|
+
? { supportedMimeTypes: record.supported_mime_types }
|
|
70
|
+
: {}),
|
|
71
|
+
...(record._meta && typeof record._meta === "object" ? { _meta: record._meta } : {}),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (configuredDefault && !seen.has(configuredDefault)) {
|
|
75
|
+
models.unshift({ id: configuredDefault, model: configuredDefault, isDefault: true });
|
|
76
|
+
}
|
|
77
|
+
return models;
|
|
78
|
+
}
|
package/dist/runner/child.js
CHANGED
|
@@ -161,9 +161,11 @@ export class RunnerSession {
|
|
|
161
161
|
if (!this.terminals.get(`${msg.localThreadId}-main`)?.isAlive()) {
|
|
162
162
|
await this.launchCodexPane(msg.localThreadId, msg.cols, msg.rows);
|
|
163
163
|
}
|
|
164
|
-
const ready =
|
|
165
|
-
?
|
|
166
|
-
:
|
|
164
|
+
const ready = msg.waitForReady === false
|
|
165
|
+
? true
|
|
166
|
+
: provider.waitLiveReady
|
|
167
|
+
? await provider.waitLiveReady(msg.localThreadId)
|
|
168
|
+
: true;
|
|
167
169
|
this.transport.send({
|
|
168
170
|
t: "live.ready",
|
|
169
171
|
reqId: msg.reqId,
|
|
@@ -185,7 +187,8 @@ export class RunnerSession {
|
|
|
185
187
|
async inject(msg) {
|
|
186
188
|
const provider = this.liveProvider;
|
|
187
189
|
try {
|
|
188
|
-
const
|
|
190
|
+
const input = msg.input ?? msg.text;
|
|
191
|
+
const outcome = (await provider.injectMessage?.(msg.localThreadId, input)) ?? "notLive";
|
|
189
192
|
this.transport.send({ t: "injected", reqId: msg.reqId, localThreadId: msg.localThreadId, outcome });
|
|
190
193
|
}
|
|
191
194
|
catch (error) {
|
package/dist/runner/manager.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* channel.
|
|
17
17
|
*/
|
|
18
18
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
19
|
-
import { type AgentCapabilities, type AgentRuntimeId, type AgentSpec, type AppConfig, type CapabilityResult, type ModelListResponse, type ReasoningEffort, type SessionInteractionResolution, type SessionEvent, type ThreadGoal } from "@rynx-ai/core";
|
|
19
|
+
import { type AgentCapabilities, type AgentRuntimeId, type AgentSpec, type AppConfig, type CapabilityResult, type ModelListResponse, type ReasoningEffort, type RuntimeUserInput, type SessionInteractionResolution, type SessionEvent, type ThreadGoal } from "@rynx-ai/core";
|
|
20
20
|
import { type CodexSessionStore } from "../host.js";
|
|
21
21
|
import type { ResolveInteractionResult } from "../interactions.js";
|
|
22
22
|
import { type InjectOutcome, type TerminalOpenErrorCode, type TerminalRole } from "./protocol.js";
|
|
@@ -49,6 +49,10 @@ export interface RunnerManagerOptions {
|
|
|
49
49
|
shutdownGraceMs?: number;
|
|
50
50
|
/** Final bounded wait for child exit after SIGKILL. */
|
|
51
51
|
shutdownKillGraceMs?: number;
|
|
52
|
+
/** Max wait for a setup-pane launch acknowledgement. */
|
|
53
|
+
liveStartTimeoutMs?: number;
|
|
54
|
+
/** Max wait for a Provider-native thread to become ready. */
|
|
55
|
+
liveReadyTimeoutMs?: number;
|
|
52
56
|
/** Injected for tests. */
|
|
53
57
|
spawn?: typeof nodeSpawn;
|
|
54
58
|
now?: () => number;
|
|
@@ -124,6 +128,8 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
124
128
|
private readonly reapTimer;
|
|
125
129
|
private readonly shutdownGraceMs;
|
|
126
130
|
private readonly shutdownKillGraceMs;
|
|
131
|
+
private readonly liveStartTimeoutMs;
|
|
132
|
+
private readonly liveReadyTimeoutMs;
|
|
127
133
|
private stopping;
|
|
128
134
|
private stopPromise;
|
|
129
135
|
/** Sink for mirrored {@link SessionEvent}s from every session's forwarder. */
|
|
@@ -176,6 +182,19 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
176
182
|
agentName?: string;
|
|
177
183
|
agentSpec?: AgentSpec;
|
|
178
184
|
}): Promise<boolean>;
|
|
185
|
+
/** Start the Provider pane without waiting for login/onboarding to create a
|
|
186
|
+
* native thread. This is the setup-terminal gate; callers may attach as soon
|
|
187
|
+
* as it resolves, while normal message delivery still uses ensureLiveSession. */
|
|
188
|
+
startLiveSession(localThreadId: string, opts?: {
|
|
189
|
+
cwd?: string;
|
|
190
|
+
cols?: number;
|
|
191
|
+
rows?: number;
|
|
192
|
+
runtime?: AgentRuntimeId;
|
|
193
|
+
reasoningEffort?: ReasoningEffort;
|
|
194
|
+
agentName?: string;
|
|
195
|
+
agentSpec?: AgentSpec;
|
|
196
|
+
}): Promise<boolean>;
|
|
197
|
+
private requestLiveSession;
|
|
179
198
|
lastLiveSessionError(localThreadId: string): string | undefined;
|
|
180
199
|
/**
|
|
181
200
|
* Inject a user turn into a session's live codex thread — reference implementation's
|
|
@@ -183,7 +202,7 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
183
202
|
* all output. Resolves true when the app-server accepted the turn, false when
|
|
184
203
|
* the session has no live forwarder (caller falls back to the run path).
|
|
185
204
|
*/
|
|
186
|
-
injectMessage(localThreadId: string,
|
|
205
|
+
injectMessage(localThreadId: string, input: RuntimeUserInput | string): Promise<InjectOutcome>;
|
|
187
206
|
/**
|
|
188
207
|
* Interrupt a session's active live turn — the web Stop button (codex
|
|
189
208
|
* `turn/interrupt`, claude Escape). Best-effort: resolves false with NO spawn
|
package/dist/runner/manager.js
CHANGED
|
@@ -32,6 +32,10 @@ const STDERR_TAIL_LINES = 40;
|
|
|
32
32
|
const DEFAULT_SHUTDOWN_GRACE_MS = 5_000;
|
|
33
33
|
/** Time allowed for exit after SIGKILL before shutdown reports failure. */
|
|
34
34
|
const DEFAULT_SHUTDOWN_KILL_GRACE_MS = 5_000;
|
|
35
|
+
/** Setup-pane launch should acknowledge quickly; never pin its HTTP request. */
|
|
36
|
+
const DEFAULT_LIVE_START_TIMEOUT_MS = 10_000;
|
|
37
|
+
/** Thread readiness may legitimately wait through Provider startup. */
|
|
38
|
+
const DEFAULT_LIVE_READY_TIMEOUT_MS = 25_000;
|
|
35
39
|
/** Keep per-Session spawn context small enough to remain an environment handoff,
|
|
36
40
|
* not an unbounded transport. */
|
|
37
41
|
const MAX_SESSION_CONTEXT_ENV_ENTRIES = 32;
|
|
@@ -144,6 +148,8 @@ export class RunnerManager {
|
|
|
144
148
|
reapTimer;
|
|
145
149
|
shutdownGraceMs;
|
|
146
150
|
shutdownKillGraceMs;
|
|
151
|
+
liveStartTimeoutMs;
|
|
152
|
+
liveReadyTimeoutMs;
|
|
147
153
|
stopping = false;
|
|
148
154
|
stopPromise;
|
|
149
155
|
/** Sink for mirrored {@link SessionEvent}s from every session's forwarder. */
|
|
@@ -166,6 +172,8 @@ export class RunnerManager {
|
|
|
166
172
|
this.defaultRuntime = opts.config.AGENT_RUNTIME ?? "codex";
|
|
167
173
|
this.shutdownGraceMs = Math.max(0, opts.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS);
|
|
168
174
|
this.shutdownKillGraceMs = Math.max(0, opts.shutdownKillGraceMs ?? DEFAULT_SHUTDOWN_KILL_GRACE_MS);
|
|
175
|
+
this.liveStartTimeoutMs = Math.max(1, opts.liveStartTimeoutMs ?? DEFAULT_LIVE_START_TIMEOUT_MS);
|
|
176
|
+
this.liveReadyTimeoutMs = Math.max(1, opts.liveReadyTimeoutMs ?? DEFAULT_LIVE_READY_TIMEOUT_MS);
|
|
169
177
|
const reapIntervalMs = opts.reapIntervalMs ?? 60_000;
|
|
170
178
|
if (reapIntervalMs > 0) {
|
|
171
179
|
this.reapTimer = setInterval(() => this.reapIdle(), reapIntervalMs);
|
|
@@ -244,12 +252,37 @@ export class RunnerManager {
|
|
|
244
252
|
* non-codex / non-live session (the caller then uses the normal run path).
|
|
245
253
|
*/
|
|
246
254
|
ensureLiveSession(localThreadId, opts) {
|
|
255
|
+
return this.requestLiveSession(localThreadId, opts, true);
|
|
256
|
+
}
|
|
257
|
+
/** Start the Provider pane without waiting for login/onboarding to create a
|
|
258
|
+
* native thread. This is the setup-terminal gate; callers may attach as soon
|
|
259
|
+
* as it resolves, while normal message delivery still uses ensureLiveSession. */
|
|
260
|
+
startLiveSession(localThreadId, opts) {
|
|
261
|
+
return this.requestLiveSession(localThreadId, opts, false);
|
|
262
|
+
}
|
|
263
|
+
requestLiveSession(localThreadId, opts, waitForReady) {
|
|
247
264
|
const handle = this.getOrSpawn(localThreadId);
|
|
248
265
|
handle.lastUsedAt = this.now();
|
|
249
266
|
this.liveSessionKeys.add(localThreadId);
|
|
250
267
|
const reqId = randomUUID();
|
|
251
268
|
return new Promise((resolve) => {
|
|
269
|
+
const timeoutMs = waitForReady ? this.liveReadyTimeoutMs : this.liveStartTimeoutMs;
|
|
270
|
+
const timeout = setTimeout(() => {
|
|
271
|
+
if (!handle.live.delete(reqId))
|
|
272
|
+
return;
|
|
273
|
+
const reason = `runner did not acknowledge ${waitForReady ? "live readiness" : "terminal start"} within ${timeoutMs}ms`;
|
|
274
|
+
this.liveErrors.set(localThreadId, reason);
|
|
275
|
+
this.liveSessionKeys.delete(localThreadId);
|
|
276
|
+
resolve(false);
|
|
277
|
+
// A child that cannot answer a bounded control round-trip is unsafe to
|
|
278
|
+
// reuse. Reap it so the next click gets a fresh runner.
|
|
279
|
+
void this.terminateHandle(handle, reason).catch((error) => {
|
|
280
|
+
logTerminationFailure(handle, error);
|
|
281
|
+
});
|
|
282
|
+
}, timeoutMs);
|
|
283
|
+
timeout.unref?.();
|
|
252
284
|
handle.live.set(reqId, (res) => {
|
|
285
|
+
clearTimeout(timeout);
|
|
253
286
|
const ok = res.ok ?? false;
|
|
254
287
|
if (ok) {
|
|
255
288
|
this.liveErrors.delete(localThreadId);
|
|
@@ -270,6 +303,7 @@ export class RunnerManager {
|
|
|
270
303
|
...(opts?.reasoningEffort ? { reasoningEffort: opts.reasoningEffort } : {}),
|
|
271
304
|
...(opts?.agentName ? { agentName: opts.agentName } : {}),
|
|
272
305
|
...(opts?.agentSpec ? { agentSpec: opts.agentSpec } : {}),
|
|
306
|
+
waitForReady,
|
|
273
307
|
});
|
|
274
308
|
});
|
|
275
309
|
}
|
|
@@ -282,13 +316,15 @@ export class RunnerManager {
|
|
|
282
316
|
* all output. Resolves true when the app-server accepted the turn, false when
|
|
283
317
|
* the session has no live forwarder (caller falls back to the run path).
|
|
284
318
|
*/
|
|
285
|
-
injectMessage(localThreadId,
|
|
319
|
+
injectMessage(localThreadId, input) {
|
|
286
320
|
const handle = this.getOrSpawn(localThreadId);
|
|
287
321
|
handle.lastUsedAt = this.now();
|
|
288
322
|
const reqId = randomUUID();
|
|
289
323
|
return new Promise((resolve) => {
|
|
290
324
|
handle.live.set(reqId, (res) => resolve(res.outcome ?? "failed"));
|
|
291
|
-
handle.transport.send(
|
|
325
|
+
handle.transport.send(typeof input === "string"
|
|
326
|
+
? { t: "inject", reqId, localThreadId, text: input }
|
|
327
|
+
: { t: "inject", reqId, localThreadId, input });
|
|
292
328
|
});
|
|
293
329
|
}
|
|
294
330
|
/**
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* (`live.ensure` / `inject` / `live.interrupt`) plus per-thread capabilities and
|
|
7
7
|
* terminal attachment; the reply channels mirror each request's `reqId`.
|
|
8
8
|
*/
|
|
9
|
-
import { AgentRuntimeError, type AgentRuntimeId, type AgentSpec, type ReasoningEffort, type SessionInteractionResolution, type SessionEvent } from "@rynx-ai/core";
|
|
9
|
+
import { AgentRuntimeError, type AgentRuntimeId, type AgentSpec, type ReasoningEffort, type RuntimeUserInput, type SessionInteractionResolution, type SessionEvent } from "@rynx-ai/core";
|
|
10
10
|
import type { ResolveInteractionResult } from "../interactions.js";
|
|
11
11
|
/** A runtime error flattened for the wire; rebuilt parent-side as `AgentRuntimeError`. */
|
|
12
12
|
export interface WireError {
|
|
@@ -92,16 +92,24 @@ export type ToChild = {
|
|
|
92
92
|
/** An inline agent spec (console session created from an inline config
|
|
93
93
|
* rather than a preset id) — same purpose as `agentName`. */
|
|
94
94
|
agentSpec?: AgentSpec;
|
|
95
|
+
/** Setup terminals only need the Provider pane to exist. Message delivery
|
|
96
|
+
* keeps the default and waits until the native thread is actually bound. */
|
|
97
|
+
waitForReady?: boolean;
|
|
95
98
|
}
|
|
96
99
|
/** Inject a user turn into the session's live codex thread (`turn/start` or
|
|
97
100
|
* `turn/steer`). The forwarder mirrors the output — this is the single-writer
|
|
98
101
|
* web send path. The child replies `injected` (echoing `reqId`). */
|
|
99
|
-
| {
|
|
102
|
+
| ({
|
|
100
103
|
t: "inject";
|
|
101
104
|
reqId: string;
|
|
102
105
|
localThreadId: string;
|
|
106
|
+
} & ({
|
|
107
|
+
input: RuntimeUserInput;
|
|
108
|
+
text?: never;
|
|
109
|
+
} | {
|
|
103
110
|
text: string;
|
|
104
|
-
|
|
111
|
+
input?: never;
|
|
112
|
+
}))
|
|
105
113
|
/** Interrupt the session's active turn — the web Stop button. codex: app-server
|
|
106
114
|
* `turn/interrupt`; claude: an Escape keystroke to the pane. The child replies
|
|
107
115
|
* `interrupted` (echoing `reqId`). */
|
|
@@ -163,8 +171,8 @@ export type FromChild = {
|
|
|
163
171
|
cwd?: string;
|
|
164
172
|
parentSessionId?: string;
|
|
165
173
|
}
|
|
166
|
-
/** Result of a `live.ensure`: `ok` once the
|
|
167
|
-
* for
|
|
174
|
+
/** Result of a `live.ensure`: `ok` once the requested gate is reached (pane
|
|
175
|
+
* started for setup, otherwise native thread bound). Echoes `reqId`. */
|
|
168
176
|
| {
|
|
169
177
|
t: "live.ready";
|
|
170
178
|
reqId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rynx-ai/runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10-beta.2",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/rynx-ai/rynx.git",
|
|
7
|
+
"directory": "packages/runtime"
|
|
8
|
+
},
|
|
4
9
|
"description": "Runtime host that drives local coding-agent CLIs (codex / traex / claude) behind one AgentExecutor + AgentCapabilities contract.",
|
|
5
10
|
"type": "module",
|
|
6
11
|
"publishConfig": {
|
|
@@ -21,7 +26,7 @@
|
|
|
21
26
|
"dependencies": {
|
|
22
27
|
"node-pty": "^1.0.0",
|
|
23
28
|
"ws": "^8.21.0",
|
|
24
|
-
"@rynx-ai/core": "0.1.
|
|
29
|
+
"@rynx-ai/core": "0.1.10-beta.2"
|
|
25
30
|
},
|
|
26
31
|
"devDependencies": {
|
|
27
32
|
"@types/ws": "^8.18.1"
|