@rynx-ai/runtime 0.1.11-beta.21 → 0.1.11-beta.23
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-integration.d.ts +7 -0
- package/dist/claude/native-integration.js +25 -2
- package/dist/codex-app-server/client.d.ts +4 -3
- package/dist/codex-app-server/client.js +35 -12
- package/dist/codex-app-server/forwarder.d.ts +2 -7
- package/dist/codex-app-server/forwarder.js +12 -50
- package/dist/codex-app-server/mapping.d.ts +1 -0
- package/dist/codex-app-server/mapping.js +8 -1
- package/dist/codex-app-server/protocol.d.ts +1 -2
- package/dist/host.d.ts +36 -25
- package/dist/host.js +384 -441
- package/dist/index.d.ts +1 -1
- package/dist/runner/child.d.ts +18 -7
- package/dist/runner/child.js +169 -45
- package/dist/runner/manager.d.ts +14 -15
- package/dist/runner/manager.js +90 -98
- package/dist/runner/protocol.d.ts +16 -15
- package/dist/runner/startup-policy.d.ts +3 -0
- package/dist/runner/startup-policy.js +5 -0
- package/dist/terminal/registry.js +3 -2
- package/dist/terminal/tmux.d.ts +29 -5
- package/dist/terminal/tmux.js +98 -52
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type { CodexCapabilities, CapabilityResult, CodexRuntimeStatus, } from ".
|
|
|
9
9
|
export type { ClaudeForkIntent, CodexSessionStore, CodexSessionRecord, } from "./codex-session-store.js";
|
|
10
10
|
export { RunnerManager, TerminalOpenError } from "./runner/manager.js";
|
|
11
11
|
export type { RunnerManagerOptions, RunnerSessionContext, RunnerSessionContextProvider, OpenTerminalOptions, ParentTerminal, } from "./runner/manager.js";
|
|
12
|
-
export type { InjectOutcome, TerminalOpenErrorCode, TerminalRole } from "./runner/protocol.js";
|
|
12
|
+
export type { InjectOutcome, InjectResult, TerminalOpenErrorCode, TerminalRole } from "./runner/protocol.js";
|
|
13
13
|
export type { ResolveInteractionResult, RuntimeInteractionEvent, RuntimeInteractionListener, } from "./interactions.js";
|
|
14
14
|
export { probeRuntimeStatus } from "./runtime-status.js";
|
|
15
15
|
export { listRuntimeModels } from "./models-catalog.js";
|
package/dist/runner/child.d.ts
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* per-thread capabilities, answered against the same backend so the parent never
|
|
8
8
|
* needs an app-server of its own.
|
|
9
9
|
*/
|
|
10
|
-
import { type AgentCapabilities, type ResolvedExecutionSnapshot, type RuntimeUserInput, type SessionInteractionResolution, type SessionWorkspaceSnapshot } from "@rynx-ai/core";
|
|
10
|
+
import { type AgentCapabilities, type LiveSessionFailure, type ResolvedExecutionSnapshot, type RuntimeUserInput, type SessionInteractionResolution, type SessionWorkspaceSnapshot } from "@rynx-ai/core";
|
|
11
11
|
import type { SessionEvent } from "@rynx-ai/core";
|
|
12
12
|
import type { TerminalInjector } from "../claude/native-integration.js";
|
|
13
13
|
import type { ResolveInteractionResult } from "../interactions.js";
|
|
14
|
-
import { type
|
|
14
|
+
import { type InjectResult } from "./protocol.js";
|
|
15
15
|
import type { ChildTransport } from "./transport.js";
|
|
16
16
|
/** Re-target the mirror to a freshly minted rynx session (claude `/clear`·`/fork`)
|
|
17
17
|
* + record the terminal transfer with the daemon. The child owns the transport,
|
|
@@ -38,14 +38,23 @@ interface LiveCodexProvider {
|
|
|
38
38
|
execution: ResolvedExecutionSnapshot;
|
|
39
39
|
retargetMirror?: RetargetMirror;
|
|
40
40
|
}): Promise<boolean>;
|
|
41
|
+
/** Known-thread Codex/Traex resume only: start the independent transcript
|
|
42
|
+
* observer after the replacement TUI has launched. Fresh discovery connects
|
|
43
|
+
* its listener before launch and this method is an idempotent no-op. */
|
|
44
|
+
startLiveCodexObserver?(localThreadId: string): void;
|
|
41
45
|
/** `null` delegates the deadline to RunnerManager. */
|
|
42
46
|
waitLiveReady?(localThreadId: string, timeoutMs?: number | null): Promise<boolean>;
|
|
43
47
|
/** `null` delegates the deadline to RunnerManager. */
|
|
44
48
|
waitTerminalReady?(localThreadId: string, timeoutMs?: number | null): Promise<boolean>;
|
|
45
49
|
liveSessionError?(localThreadId: string): string | undefined;
|
|
50
|
+
liveSessionFailure?(localThreadId: string): LiveSessionFailure | undefined;
|
|
46
51
|
failLiveStartup?(localThreadId: string, error: Error): boolean;
|
|
47
52
|
failLiveSession?(localThreadId: string, error: Error): boolean;
|
|
48
|
-
|
|
53
|
+
/** Codex/Traex auxiliary Terminal lifecycle: settle the current Turn, stop
|
|
54
|
+
* observer/forwarder, and evict the owned app-server while preserving the
|
|
55
|
+
* durable native-session binding for a later cold resume. */
|
|
56
|
+
teardownLiveCodexSession?(localThreadId: string, error?: Error): boolean;
|
|
57
|
+
injectMessage?(localThreadId: string, input: RuntimeUserInput | string): Promise<InjectResult>;
|
|
49
58
|
interruptLive?(localThreadId: string): Promise<boolean>;
|
|
50
59
|
stopLiveCodexSession?(localThreadId: string, opts?: {
|
|
51
60
|
deferClaudeInteractionCleanup?: boolean;
|
|
@@ -84,6 +93,8 @@ export declare class RunnerSession {
|
|
|
84
93
|
private readonly cancelledTerminalOpens;
|
|
85
94
|
/** Session ids with a live codex forwarder started here (stopped on shutdown). */
|
|
86
95
|
private readonly liveIds;
|
|
96
|
+
/** Provider name retained for asynchronous Terminal-exit diagnostics. */
|
|
97
|
+
private readonly liveRuntimes;
|
|
87
98
|
private shuttingDown;
|
|
88
99
|
constructor({ transport, executor, onShutdown }: RunnerSessionDeps);
|
|
89
100
|
private handle;
|
|
@@ -96,10 +107,10 @@ export declare class RunnerSession {
|
|
|
96
107
|
* the new session stays injectable. */
|
|
97
108
|
private mirrorChannel;
|
|
98
109
|
/**
|
|
99
|
-
* Eagerly bring up a session's codex-native live view
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
110
|
+
* Eagerly bring up a session's codex-native live view. Fresh sessions connect
|
|
111
|
+
* the discovery listener before launching the detached TUI; known-thread
|
|
112
|
+
* resumes preload first, launch the replacement TUI, then start their
|
|
113
|
+
* persistent observer in the background.
|
|
103
114
|
*/
|
|
104
115
|
private ensureLive;
|
|
105
116
|
private monitorCodexThreadStartup;
|
package/dist/runner/child.js
CHANGED
|
@@ -1,8 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Child-side runner session. One per runner process (i.e. per session); owns the
|
|
3
|
+
* single execution backend via the injected executor (a {@link LocalAgentHost}
|
|
4
|
+
* in production, which in a per-session process holds exactly one backend). It
|
|
5
|
+
* translates inbound {@link ToChild} control messages into the live co-drive
|
|
6
|
+
* surface (bring up the session's forwarder + TUI, inject / interrupt turns) and
|
|
7
|
+
* per-thread capabilities, answered against the same backend so the parent never
|
|
8
|
+
* needs an app-server of its own.
|
|
9
|
+
*/
|
|
10
|
+
import { AgentRuntimeError, } from "@rynx-ai/core";
|
|
1
11
|
import { TerminalRegistry } from "../terminal/registry.js";
|
|
2
|
-
import { toWireError } from "./protocol.js";
|
|
3
|
-
import {
|
|
12
|
+
import { toWireError, } from "./protocol.js";
|
|
13
|
+
import { isCodexLineageProvider } from "./startup-policy.js";
|
|
4
14
|
/** Maximum time for a fresh Codex-lineage TUI to publish its native thread. */
|
|
5
15
|
const CODEX_THREAD_START_TIMEOUT_MS = 30_000;
|
|
16
|
+
function providerDisplayName(runtime) {
|
|
17
|
+
return runtime === "traex" ? "Traex" : runtime === "claude" ? "Claude" : "Codex";
|
|
18
|
+
}
|
|
19
|
+
function providerFailure(provider, localThreadId, fallback) {
|
|
20
|
+
return provider.liveSessionFailure?.(localThreadId) ?? {
|
|
21
|
+
message: provider.liveSessionError?.(localThreadId) ?? fallback.message,
|
|
22
|
+
code: fallback.code,
|
|
23
|
+
statusCode: 503,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function nativePhaseError(runtime, code, phase, cause) {
|
|
27
|
+
const detail = cause === undefined
|
|
28
|
+
? ""
|
|
29
|
+
: `: ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
30
|
+
return new AgentRuntimeError(`${providerDisplayName(runtime)} ${phase}${detail}`, 503, code);
|
|
31
|
+
}
|
|
32
|
+
function terminalFailureDetail(pane) {
|
|
33
|
+
// A captured pane is a screen snapshot, not stderr. Do not present ordinary
|
|
34
|
+
// TUI chrome ("Working", shortcuts, model/status bars) as the process's exit
|
|
35
|
+
// cause. Retain only lines that plausibly carry an actual startup/runtime
|
|
36
|
+
// failure; the structured error still reports the proven pane exit itself.
|
|
37
|
+
return pane
|
|
38
|
+
.split("\n")
|
|
39
|
+
.map((line) => line.trim())
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.filter((line) => /\b(?:error|fatal|failed|failure|panic|refused|denied|unauthorized|invalid)\b/i.test(line))
|
|
42
|
+
.slice(-6)
|
|
43
|
+
.join(" ")
|
|
44
|
+
.slice(-1_000);
|
|
45
|
+
}
|
|
6
46
|
const TRAEX_STARTUP_WATCH_MS = 20_000;
|
|
7
47
|
const TRAEX_STARTUP_POLL_MS = 100;
|
|
8
48
|
const TRAEX_AUTHORIZATION_POLL_MS = 500;
|
|
@@ -38,6 +78,8 @@ export class RunnerSession {
|
|
|
38
78
|
cancelledTerminalOpens = new Set();
|
|
39
79
|
/** Session ids with a live codex forwarder started here (stopped on shutdown). */
|
|
40
80
|
liveIds = new Set();
|
|
81
|
+
/** Provider name retained for asynchronous Terminal-exit diagnostics. */
|
|
82
|
+
liveRuntimes = new Map();
|
|
41
83
|
shuttingDown = false;
|
|
42
84
|
constructor({ transport, executor, onShutdown }) {
|
|
43
85
|
this.transport = transport;
|
|
@@ -159,10 +201,10 @@ export class RunnerSession {
|
|
|
159
201
|
return { emit, retarget };
|
|
160
202
|
}
|
|
161
203
|
/**
|
|
162
|
-
* Eagerly bring up a session's codex-native live view
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
204
|
+
* Eagerly bring up a session's codex-native live view. Fresh sessions connect
|
|
205
|
+
* the discovery listener before launching the detached TUI; known-thread
|
|
206
|
+
* resumes preload first, launch the replacement TUI, then start their
|
|
207
|
+
* persistent observer in the background.
|
|
166
208
|
*/
|
|
167
209
|
async ensureLive(msg) {
|
|
168
210
|
const provider = this.liveProvider;
|
|
@@ -174,34 +216,38 @@ export class RunnerSession {
|
|
|
174
216
|
retargetMirror: retarget,
|
|
175
217
|
});
|
|
176
218
|
if (!started) {
|
|
219
|
+
const runtime = msg.execution?.provider;
|
|
177
220
|
this.transport.send({
|
|
178
221
|
t: "live.ready",
|
|
179
222
|
reqId: msg.reqId,
|
|
180
223
|
localThreadId: msg.localThreadId,
|
|
181
224
|
ok: false,
|
|
182
|
-
error: provider
|
|
183
|
-
|
|
225
|
+
error: providerFailure(provider, msg.localThreadId, {
|
|
226
|
+
runtime,
|
|
227
|
+
code: "native_start_failed",
|
|
228
|
+
message: `${providerDisplayName(runtime)} native session did not start`,
|
|
229
|
+
}),
|
|
184
230
|
});
|
|
185
231
|
return;
|
|
186
232
|
}
|
|
187
233
|
this.liveIds.add(msg.localThreadId);
|
|
188
|
-
// Codex-lineage startup is phase-bounded: app-server readiness is
|
|
189
|
-
// handled by its channel, then the already-connected
|
|
190
|
-
// observer gets a full window for the TUI's thread/started event.
|
|
191
|
-
// Claude retains the parent-owned SessionStart deadline.
|
|
192
234
|
const runtime = msg.execution?.provider;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// discovery owns the separate 30s phase below.
|
|
196
|
-
const terminalReadyTimeoutMs = isManagedNativeProvider(runtime) ? null : undefined;
|
|
235
|
+
if (runtime)
|
|
236
|
+
this.liveRuntimes.set(msg.localThreadId, runtime);
|
|
197
237
|
// Launch the TUI attached to the already-bound thread. Re-launch when the
|
|
198
238
|
// pane is absent OR its process has died (`isAlive` probes `#{pane_dead}`) — so a
|
|
199
239
|
// reconnect after the TUI exited restarts it instead of skipping (a
|
|
200
240
|
// launched-once guard would leave a dead "Pane is dead" husk forever).
|
|
201
241
|
if (!this.terminals.get(`${msg.localThreadId}-main`)?.isAlive()) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
242
|
+
// Codex/Traex `ensureLiveCodexSession` already performs the backend
|
|
243
|
+
// preload for an existing thread. Match the native lifecycle: launch
|
|
244
|
+
// the TUI immediately after that preload and let the observer subscribe
|
|
245
|
+
// in the background. Fresh sessions likewise launch before discovery.
|
|
246
|
+
const terminalReady = isCodexLineageProvider(runtime)
|
|
247
|
+
? true
|
|
248
|
+
: provider.waitTerminalReady
|
|
249
|
+
? await provider.waitTerminalReady(msg.localThreadId)
|
|
250
|
+
: true;
|
|
205
251
|
if (!terminalReady) {
|
|
206
252
|
const providerName = runtime === "traex" ? "Traex" : runtime === "codex" ? "Codex" : "Provider";
|
|
207
253
|
this.transport.send({
|
|
@@ -209,14 +255,31 @@ export class RunnerSession {
|
|
|
209
255
|
reqId: msg.reqId,
|
|
210
256
|
localThreadId: msg.localThreadId,
|
|
211
257
|
ok: false,
|
|
212
|
-
error:
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
:
|
|
258
|
+
error: providerFailure(provider, msg.localThreadId, {
|
|
259
|
+
runtime,
|
|
260
|
+
code: "native_resume_failed",
|
|
261
|
+
message: runtime === "codex" || runtime === "traex"
|
|
262
|
+
? `${providerName} app-server was ready, but its existing session could not be resumed for the Terminal`
|
|
263
|
+
: "Provider thread was not ready for Terminal resume",
|
|
264
|
+
}),
|
|
216
265
|
});
|
|
217
266
|
return;
|
|
218
267
|
}
|
|
219
|
-
|
|
268
|
+
try {
|
|
269
|
+
await this.launchCodexPane(msg.localThreadId, msg.cols, msg.rows);
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
throw error instanceof AgentRuntimeError
|
|
273
|
+
? error
|
|
274
|
+
: nativePhaseError(runtime, "native_terminal_launch_failed", "Terminal failed to launch", error);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Omnigent's known-thread path launches `codex --remote resume` first,
|
|
278
|
+
// then creates `_codex_forward_known_thread` as background work. Keep the
|
|
279
|
+
// ordering explicit here: observer connection failure cannot delay or
|
|
280
|
+
// reject cold-resume admission.
|
|
281
|
+
if (isCodexLineageProvider(runtime)) {
|
|
282
|
+
provider.startLiveCodexObserver?.(msg.localThreadId);
|
|
220
283
|
}
|
|
221
284
|
let ready = true;
|
|
222
285
|
if (runtime === "codex" || runtime === "traex") {
|
|
@@ -226,12 +289,15 @@ export class RunnerSession {
|
|
|
226
289
|
this.monitorCodexThreadStartup(msg.localThreadId, runtime, provider);
|
|
227
290
|
}
|
|
228
291
|
else if (msg.waitForReady !== false && provider.waitLiveReady) {
|
|
229
|
-
ready = await provider.waitLiveReady(msg.localThreadId
|
|
292
|
+
ready = await provider.waitLiveReady(msg.localThreadId);
|
|
230
293
|
}
|
|
231
294
|
const readinessError = ready
|
|
232
295
|
? undefined
|
|
233
|
-
: provider
|
|
234
|
-
|
|
296
|
+
: providerFailure(provider, msg.localThreadId, {
|
|
297
|
+
runtime,
|
|
298
|
+
code: "native_readiness_failed",
|
|
299
|
+
message: `${providerDisplayName(runtime)} native session was not ready`,
|
|
300
|
+
});
|
|
235
301
|
this.transport.send({
|
|
236
302
|
t: "live.ready",
|
|
237
303
|
reqId: msg.reqId,
|
|
@@ -246,7 +312,7 @@ export class RunnerSession {
|
|
|
246
312
|
reqId: msg.reqId,
|
|
247
313
|
localThreadId: msg.localThreadId,
|
|
248
314
|
ok: false,
|
|
249
|
-
error:
|
|
315
|
+
error: toWireError(error),
|
|
250
316
|
});
|
|
251
317
|
}
|
|
252
318
|
}
|
|
@@ -269,11 +335,15 @@ export class RunnerSession {
|
|
|
269
335
|
: "";
|
|
270
336
|
const detail = provider.liveSessionError?.(localThreadId)
|
|
271
337
|
?? (paneFailure
|
|
272
|
-
?
|
|
338
|
+
? `${providerDisplayName(runtime)} Terminal exited before native session discovery: ${paneFailure}`
|
|
273
339
|
: `${runtime === "traex" ? "Traex" : "Codex"} TUI did not publish thread/started within ${CODEX_THREAD_START_TIMEOUT_MS / 1_000}s after app-server and observer readiness`);
|
|
274
|
-
this.failCodexThreadStartup(localThreadId, provider, new
|
|
340
|
+
this.failCodexThreadStartup(localThreadId, provider, new AgentRuntimeError(detail, 503, paneFailure
|
|
341
|
+
? "native_terminal_exited_before_session"
|
|
342
|
+
: "native_thread_discovery_timeout"));
|
|
275
343
|
}).catch((error) => {
|
|
276
|
-
this.failCodexThreadStartup(localThreadId, provider, error instanceof
|
|
344
|
+
this.failCodexThreadStartup(localThreadId, provider, error instanceof AgentRuntimeError
|
|
345
|
+
? error
|
|
346
|
+
: nativePhaseError(runtime, "native_thread_discovery_failed", "native session discovery failed", error));
|
|
277
347
|
});
|
|
278
348
|
}
|
|
279
349
|
/** Discovery failure is terminal for this partial native launch. Publish the
|
|
@@ -282,7 +352,7 @@ export class RunnerSession {
|
|
|
282
352
|
* already-rejected readiness promise. */
|
|
283
353
|
failCodexThreadStartup(localThreadId, provider, error) {
|
|
284
354
|
if (!provider.failLiveStartup?.(localThreadId, error))
|
|
285
|
-
return;
|
|
355
|
+
return false;
|
|
286
356
|
const watcher = this.terminalWatchers.get(localThreadId);
|
|
287
357
|
if (watcher)
|
|
288
358
|
clearInterval(watcher);
|
|
@@ -294,24 +364,42 @@ export class RunnerSession {
|
|
|
294
364
|
catch (closeError) {
|
|
295
365
|
console.warn(`[runner] session=${localThreadId} failed to close native Terminal after startup failure: ${closeError instanceof Error ? closeError.message : String(closeError)}`);
|
|
296
366
|
}
|
|
297
|
-
provider.
|
|
367
|
+
if (provider.teardownLiveCodexSession) {
|
|
368
|
+
provider.teardownLiveCodexSession(localThreadId);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
provider.stopLiveCodexSession?.(localThreadId);
|
|
372
|
+
}
|
|
298
373
|
this.liveIds.delete(localThreadId);
|
|
374
|
+
return true;
|
|
299
375
|
}
|
|
300
376
|
async inject(msg) {
|
|
301
377
|
const provider = this.liveProvider;
|
|
302
378
|
try {
|
|
303
379
|
const input = msg.input ?? msg.text;
|
|
304
|
-
const
|
|
380
|
+
const result = (await provider.injectMessage?.(msg.localThreadId, input))
|
|
381
|
+
?? { outcome: "notLive" };
|
|
305
382
|
// App-server injection is independent of the Terminal TUI startup, so it
|
|
306
383
|
// must not cancel prompt handling for the pane that is still starting.
|
|
307
|
-
const error = outcome === "injected" || outcome === "steered"
|
|
384
|
+
const error = result.outcome === "injected" || result.outcome === "steered"
|
|
308
385
|
? undefined
|
|
309
|
-
: provider
|
|
386
|
+
: providerFailure(provider, msg.localThreadId, {
|
|
387
|
+
code: result.outcome === "notReady"
|
|
388
|
+
? "native_thread_not_ready"
|
|
389
|
+
: result.outcome === "notLive"
|
|
390
|
+
? "native_session_not_live"
|
|
391
|
+
: "native_message_injection_failed",
|
|
392
|
+
message: result.outcome === "notReady"
|
|
393
|
+
? "Native session was not ready for message injection"
|
|
394
|
+
: result.outcome === "notLive"
|
|
395
|
+
? "Native session was not live for message injection"
|
|
396
|
+
: "Native message injection failed",
|
|
397
|
+
});
|
|
310
398
|
this.transport.send({
|
|
311
399
|
t: "injected",
|
|
312
400
|
reqId: msg.reqId,
|
|
313
401
|
localThreadId: msg.localThreadId,
|
|
314
|
-
|
|
402
|
+
result,
|
|
315
403
|
...(error ? { error } : {}),
|
|
316
404
|
});
|
|
317
405
|
}
|
|
@@ -320,8 +408,8 @@ export class RunnerSession {
|
|
|
320
408
|
t: "injected",
|
|
321
409
|
reqId: msg.reqId,
|
|
322
410
|
localThreadId: msg.localThreadId,
|
|
323
|
-
outcome: "failed",
|
|
324
|
-
error:
|
|
411
|
+
result: { outcome: "failed" },
|
|
412
|
+
error: toWireError(error),
|
|
325
413
|
});
|
|
326
414
|
}
|
|
327
415
|
}
|
|
@@ -333,7 +421,7 @@ export class RunnerSession {
|
|
|
333
421
|
ok = (await provider.interruptLive?.(msg.localThreadId)) ?? false;
|
|
334
422
|
}
|
|
335
423
|
catch (e) {
|
|
336
|
-
error =
|
|
424
|
+
error = toWireError(e);
|
|
337
425
|
}
|
|
338
426
|
this.transport.send({
|
|
339
427
|
t: "interrupted",
|
|
@@ -376,15 +464,50 @@ export class RunnerSession {
|
|
|
376
464
|
if (checking || this.shuttingDown || this.terminals.get(terminalId) !== terminal)
|
|
377
465
|
return;
|
|
378
466
|
checking = true;
|
|
379
|
-
const probe = terminal.
|
|
380
|
-
|
|
381
|
-
|
|
467
|
+
const probe = terminal.lifecycleLivenessAsync
|
|
468
|
+
? terminal.lifecycleLivenessAsync()
|
|
469
|
+
: terminal.livenessAsync
|
|
470
|
+
? terminal.livenessAsync()
|
|
471
|
+
: terminal.isAliveAsync
|
|
472
|
+
? terminal.isAliveAsync().then((alive) => alive ? "alive" : "dead")
|
|
473
|
+
: Promise.resolve(terminal.isAlive() ? "alive" : "dead");
|
|
474
|
+
void probe.then((liveness) => {
|
|
475
|
+
// A failed pane probe is inconclusive. Only Omnigent's lifecycle
|
|
476
|
+
// evidence — capture target gone or explicit pane_dead — may fail it.
|
|
477
|
+
if (liveness !== "dead" ||
|
|
478
|
+
this.shuttingDown ||
|
|
479
|
+
this.terminals.get(terminalId) !== terminal)
|
|
382
480
|
return;
|
|
383
481
|
clearInterval(timer);
|
|
384
482
|
this.terminalWatchers.delete(localThreadId);
|
|
385
483
|
this.cancelTraexStartupWatcher(localThreadId);
|
|
386
|
-
this.
|
|
387
|
-
|
|
484
|
+
const runtime = this.liveRuntimes.get(localThreadId);
|
|
485
|
+
const paneFailure = terminalFailureDetail(terminal.capturePane?.() ?? "");
|
|
486
|
+
const startupFailed = this.failCodexThreadStartup(localThreadId, this.liveProvider, nativePhaseError(runtime, "native_terminal_exited_before_session", "Terminal exited before native session discovery completed", paneFailure || undefined));
|
|
487
|
+
if (isCodexLineageProvider(runtime)) {
|
|
488
|
+
if (!startupFailed) {
|
|
489
|
+
try {
|
|
490
|
+
this.terminals.close(terminalId);
|
|
491
|
+
}
|
|
492
|
+
catch (closeError) {
|
|
493
|
+
console.warn(`[runner] session=${localThreadId} failed to close exited native Terminal: ${closeError instanceof Error ? closeError.message : String(closeError)}`);
|
|
494
|
+
}
|
|
495
|
+
const exitError = nativePhaseError(runtime, "native_terminal_exited", "Terminal exited unexpectedly", paneFailure || undefined);
|
|
496
|
+
if (this.liveProvider.teardownLiveCodexSession) {
|
|
497
|
+
this.liveProvider.teardownLiveCodexSession(localThreadId, exitError);
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
this.liveProvider.failLiveSession?.(localThreadId, exitError);
|
|
501
|
+
this.liveProvider.stopLiveCodexSession?.(localThreadId);
|
|
502
|
+
}
|
|
503
|
+
this.liveIds.delete(localThreadId);
|
|
504
|
+
console.warn(`[runner] session=${localThreadId} ${providerDisplayName(runtime)} auxiliary Terminal exited; native runtime torn down for cold resume`);
|
|
505
|
+
}
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
this.liveProvider.failLiveSession?.(localThreadId, nativePhaseError(runtime, "native_terminal_exited", "Terminal exited unexpectedly", paneFailure || undefined));
|
|
509
|
+
}).catch((error) => {
|
|
510
|
+
console.warn(`[runner] session=${localThreadId} native Terminal liveness probe failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
388
511
|
}).finally(() => {
|
|
389
512
|
checking = false;
|
|
390
513
|
});
|
|
@@ -469,6 +592,7 @@ export class RunnerSession {
|
|
|
469
592
|
});
|
|
470
593
|
}
|
|
471
594
|
this.liveIds.clear();
|
|
595
|
+
this.liveRuntimes.clear();
|
|
472
596
|
}
|
|
473
597
|
/** Stop event forwarding, kill native terminals/hooks, then synchronously
|
|
474
598
|
* scrub provider handoff files before the child process is allowed to exit. */
|
package/dist/runner/manager.d.ts
CHANGED
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
* channel.
|
|
17
17
|
*/
|
|
18
18
|
import { spawn as nodeSpawn, type ChildProcess } from "node:child_process";
|
|
19
|
-
import { type AdmissionReservation, type AgentCapabilities, type AgentRuntimeId, type AppConfig, type CapabilityResult, type ModelListResponse, type ResolvedExecutionSnapshot, type RuntimeUserInput, type SessionInteractionResolution, type SessionEvent, type SessionWorkspaceSnapshot, type ThreadGoal } from "@rynx-ai/core";
|
|
19
|
+
import { type AdmissionReservation, type AgentCapabilities, type AgentRuntimeId, type AppConfig, type CapabilityResult, type ModelListResponse, type LiveSessionFailure, type InjectResult, type ResolvedExecutionSnapshot, type RuntimeUserInput, type SessionInteractionResolution, type SessionEvent, type SessionWorkspaceSnapshot, type ThreadGoal } from "@rynx-ai/core";
|
|
20
20
|
import { type CodexSessionStore } from "../host.js";
|
|
21
21
|
import type { ResolveInteractionResult } from "../interactions.js";
|
|
22
|
-
import { type
|
|
22
|
+
import { type TerminalOpenErrorCode, type TerminalRole } from "./protocol.js";
|
|
23
23
|
/** Runtime-local context attached to one Session runner process. The provider is
|
|
24
24
|
* intentionally generic: package-specific resources stay in the composition
|
|
25
25
|
* root that implements this port. */
|
|
@@ -53,12 +53,13 @@ export interface RunnerManagerOptions {
|
|
|
53
53
|
shutdownGraceMs?: number;
|
|
54
54
|
/** Final bounded wait for child exit after SIGKILL. */
|
|
55
55
|
shutdownKillGraceMs?: number;
|
|
56
|
-
/** Max wait for a setup-pane launch acknowledgement.
|
|
56
|
+
/** Max wait for a legacy setup-pane launch acknowledgement. Codex/Traex use
|
|
57
|
+
* their internal phase deadlines and are not wrapped by this timer. */
|
|
57
58
|
liveStartTimeoutMs?: number;
|
|
58
59
|
/** Max wait for a Provider-native thread to become ready. */
|
|
59
60
|
liveReadyTimeoutMs?: number;
|
|
60
|
-
/** Claude SessionStart deadline. Codex/Traex
|
|
61
|
-
*
|
|
61
|
+
/** Claude SessionStart deadline. Codex/Traex have no parent-owned startup
|
|
62
|
+
* deadline; their app-server/discovery/injection phases report directly. */
|
|
62
63
|
nativeLiveStartTimeoutMs?: number;
|
|
63
64
|
/** Max wait for a native interrupt acknowledgement before reporting it
|
|
64
65
|
* unproven. Callers may then use the explicit force-stop path. */
|
|
@@ -66,15 +67,13 @@ export interface RunnerManagerOptions {
|
|
|
66
67
|
/** Max wait for an accepted owner TUI submission to become observable as a
|
|
67
68
|
* mirrored response or a published native rotation. */
|
|
68
69
|
terminalInputHandoffTimeoutMs?: number;
|
|
69
|
-
/** Initial exponential backoff for retrying unproven terminal cleanup. */
|
|
70
|
-
terminalInputCleanupRetryMs?: number;
|
|
71
70
|
/** Injected for tests. */
|
|
72
71
|
spawn?: typeof nodeSpawn;
|
|
73
72
|
/** Injected for tests. Defaults to signaling the whole POSIX process group
|
|
74
73
|
* created for the runner child, with direct-child fallback. */
|
|
75
74
|
signalChild?: (child: ChildProcess, signal: NodeJS.Signals, processGroup: boolean) => void;
|
|
76
|
-
/** Injected for tests.
|
|
77
|
-
* server owned by a Session runner. */
|
|
75
|
+
/** Injected for tests. Best-effort close of the deterministic private tmux
|
|
76
|
+
* server owned by a Session runner; Omnigent does not verify/retry close. */
|
|
78
77
|
terminateTerminalServer?: (terminalName: string) => boolean | Promise<boolean>;
|
|
79
78
|
/** Injected tmux `#{window_activity}` reader. Returns epoch seconds. */
|
|
80
79
|
terminalWindowActivityAt?: (terminalName: string) => number | null | Promise<number | null>;
|
|
@@ -175,7 +174,6 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
175
174
|
private readonly nativeLiveStartTimeoutMs;
|
|
176
175
|
private readonly liveInterruptTimeoutMs;
|
|
177
176
|
private readonly terminalInputHandoffTimeoutMs;
|
|
178
|
-
private readonly terminalInputCleanupRetryMs;
|
|
179
177
|
private reapPromise;
|
|
180
178
|
private stopping;
|
|
181
179
|
private stopPromise;
|
|
@@ -242,14 +240,14 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
242
240
|
private finishTerminalInputHandoff;
|
|
243
241
|
private finishAllTerminalInputHandoffs;
|
|
244
242
|
private preserveTerminalInputHandoffsAsActivity;
|
|
245
|
-
private scheduleTerminalInputCleanupRetry;
|
|
246
243
|
private expireTerminalInputHandoffs;
|
|
247
244
|
/**
|
|
248
245
|
* Eagerly bring up a session's codex-native live view (persistent forwarder +
|
|
249
246
|
* detached `codex --remote` TUI) in its runner child, spawning the runner if
|
|
250
|
-
* needed. Idempotent. For Codex-lineage
|
|
251
|
-
*
|
|
252
|
-
*
|
|
247
|
+
* needed. Idempotent. For Codex-lineage sessions, resolves after backend
|
|
248
|
+
* preload and pane launch; known-thread observer attach and fresh-thread
|
|
249
|
+
* discovery continue in the background. Other providers retain their
|
|
250
|
+
* readiness gate.
|
|
253
251
|
*/
|
|
254
252
|
ensureLiveSession(localThreadId: string, opts: {
|
|
255
253
|
workspace: SessionWorkspaceSnapshot;
|
|
@@ -268,13 +266,14 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
268
266
|
}): Promise<boolean>;
|
|
269
267
|
private requestLiveSession;
|
|
270
268
|
lastLiveSessionError(localThreadId: string): string | undefined;
|
|
269
|
+
lastLiveSessionFailure(localThreadId: string): LiveSessionFailure | undefined;
|
|
271
270
|
/**
|
|
272
271
|
* Inject a user turn into a session's live codex thread — reference implementation's
|
|
273
272
|
* single-writer web send (`turn/start` / `turn/steer`); the forwarder mirrors
|
|
274
273
|
* all output. Resolves true when the app-server accepted the turn, false when
|
|
275
274
|
* the session has no live forwarder (caller falls back to the run path).
|
|
276
275
|
*/
|
|
277
|
-
injectMessage(localThreadId: string, input: RuntimeUserInput | string): Promise<
|
|
276
|
+
injectMessage(localThreadId: string, input: RuntimeUserInput | string): Promise<InjectResult>;
|
|
278
277
|
private injectMessageAdmitted;
|
|
279
278
|
/**
|
|
280
279
|
* Interrupt a session's active live turn — the web Stop button (codex
|