@rynx-ai/runtime 0.1.10 → 0.1.11-beta.10
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/models.d.ts +0 -5
- package/dist/claude/models.js +1 -7
- package/dist/claude/native-bridge.js +3 -8
- package/dist/claude/native-integration.d.ts +12 -1
- package/dist/claude/native-integration.js +16 -2
- package/dist/claude/transcript.d.ts +0 -7
- package/dist/claude/transcript.js +6 -20
- package/dist/codex-app-server/client.d.ts +2 -1
- package/dist/codex-app-server/forwarder.d.ts +4 -1
- package/dist/codex-app-server/forwarder.js +19 -1
- package/dist/codex-app-server/protocol.d.ts +45 -1
- package/dist/codex-home.d.ts +9 -26
- package/dist/codex-home.js +37 -65
- package/dist/codex-session-store.d.ts +22 -10
- package/dist/codex-session-store.js +277 -12
- package/dist/host.d.ts +48 -47
- package/dist/host.js +810 -355
- package/dist/index.d.ts +2 -3
- package/dist/index.js +1 -2
- package/dist/models-catalog.d.ts +3 -1
- package/dist/models-catalog.js +125 -4
- package/dist/provider-workspace.d.ts +56 -0
- package/dist/provider-workspace.js +83 -0
- package/dist/runner/child.d.ts +59 -6
- package/dist/runner/child.js +138 -19
- package/dist/runner/manager.d.ts +104 -19
- package/dist/runner/manager.js +922 -89
- package/dist/runner/protocol.d.ts +7 -18
- package/dist/runner-main.js +12 -4
- package/dist/runtime-state-paths.d.ts +10 -0
- package/dist/runtime-state-paths.js +53 -0
- package/dist/terminal/claude-tui.d.ts +10 -1
- package/dist/terminal/claude-tui.js +9 -1
- package/dist/terminal/codex-tui.d.ts +5 -1
- package/dist/terminal/codex-tui.js +12 -3
- package/dist/terminal/tmux.d.ts +11 -1
- package/dist/terminal/tmux.js +61 -12
- package/package.json +3 -3
- package/dist/codex/rollout-synth.d.ts +0 -42
- package/dist/codex/rollout-synth.js +0 -245
package/dist/runner/child.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { TerminalRegistry } from "../terminal/registry.js";
|
|
2
2
|
import { toWireError } from "./protocol.js";
|
|
3
|
+
const TRAEX_STARTUP_WATCH_MS = 20_000;
|
|
4
|
+
const TRAEX_STARTUP_POLL_MS = 100;
|
|
5
|
+
const TRAEX_PROMPT_RETRY_MS = 500;
|
|
6
|
+
function normalizeTraexPane(pane) {
|
|
7
|
+
return pane.toLowerCase().replace(/\s+/g, " ").trim();
|
|
8
|
+
}
|
|
9
|
+
function isTerminalProtocolResponse(input) {
|
|
10
|
+
// xterm answers terminal queries through the same onData channel as real
|
|
11
|
+
// keystrokes. CSI carries device/focus/position reports; OSC carries color
|
|
12
|
+
// query replies such as `OSC 10;rgb:... ST` and `OSC 11;rgb:... ST`.
|
|
13
|
+
// Neither is evidence that the user has taken over startup prompt handling.
|
|
14
|
+
return /^(?:\x1b\[(?:(?:\?|>)[0-9;]*c|[0-9;]*(?:n|R|t)|[IO])|\x1b\][0-9]+;[^\x07\x1b]*(?:\x07|\x1b\\))+$/.test(input);
|
|
15
|
+
}
|
|
3
16
|
export class RunnerSession {
|
|
4
17
|
transport;
|
|
5
18
|
executor;
|
|
@@ -7,6 +20,8 @@ export class RunnerSession {
|
|
|
7
20
|
/** Live terminals hosted by this session, and per-attach client handles. */
|
|
8
21
|
terminals = new TerminalRegistry();
|
|
9
22
|
attachments = new Map();
|
|
23
|
+
attachmentThreadIds = new Map();
|
|
24
|
+
traexStartupWatchers = new Map();
|
|
10
25
|
/** Opens are async; a close received before attach resolves tombstones the id. */
|
|
11
26
|
pendingTerminalOpens = new Set();
|
|
12
27
|
cancelledTerminalOpens = new Set();
|
|
@@ -46,7 +61,17 @@ export class RunnerSession {
|
|
|
46
61
|
});
|
|
47
62
|
return;
|
|
48
63
|
case "term.input":
|
|
49
|
-
|
|
64
|
+
{
|
|
65
|
+
const localThreadId = this.attachmentThreadIds.get(msg.attachId);
|
|
66
|
+
const input = Buffer.from(msg.dataB64, "base64").toString("utf8");
|
|
67
|
+
// xterm sends device/focus reports through the same onData channel as
|
|
68
|
+
// keystrokes. They must reach the TUI without pretending the user has
|
|
69
|
+
// taken over startup prompt handling.
|
|
70
|
+
if (!isTerminalProtocolResponse(input)) {
|
|
71
|
+
this.cancelTraexStartupWatcher(localThreadId);
|
|
72
|
+
}
|
|
73
|
+
this.attachments.get(msg.attachId)?.write(input);
|
|
74
|
+
}
|
|
50
75
|
return;
|
|
51
76
|
case "term.resize":
|
|
52
77
|
this.attachments.get(msg.attachId)?.resize(msg.cols, msg.rows);
|
|
@@ -57,6 +82,7 @@ export class RunnerSession {
|
|
|
57
82
|
}
|
|
58
83
|
const attachment = this.attachments.get(msg.attachId);
|
|
59
84
|
this.attachments.delete(msg.attachId);
|
|
85
|
+
this.attachmentThreadIds.delete(msg.attachId);
|
|
60
86
|
attachment?.kill();
|
|
61
87
|
return;
|
|
62
88
|
}
|
|
@@ -114,9 +140,8 @@ export class RunnerSession {
|
|
|
114
140
|
from: localThreadId,
|
|
115
141
|
to: newId,
|
|
116
142
|
kind: meta.kind,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
...(meta.cwd ? { cwd: meta.cwd } : {}),
|
|
143
|
+
workspace: meta.workspace,
|
|
144
|
+
execution: meta.execution,
|
|
120
145
|
...(meta.parentSessionId ? { parentSessionId: meta.parentSessionId } : {}),
|
|
121
146
|
});
|
|
122
147
|
};
|
|
@@ -125,21 +150,17 @@ export class RunnerSession {
|
|
|
125
150
|
/**
|
|
126
151
|
* Eagerly bring up a session's codex-native live view: start the persistent
|
|
127
152
|
* forwarder connection (which resume-subscribes to mirror every turn) and
|
|
128
|
-
* launch the detached `codex --remote` TUI
|
|
129
|
-
* forwarder
|
|
130
|
-
*
|
|
131
|
-
* rollout) and its turns mirror to chat (reference implementation's model).
|
|
153
|
+
* launch the detached `codex --remote resume` TUI against the thread the
|
|
154
|
+
* structured runtime created. The forwarder subscribes to that same thread,
|
|
155
|
+
* so the TUI is usable immediately and its turns mirror to chat.
|
|
132
156
|
*/
|
|
133
157
|
async ensureLive(msg) {
|
|
134
158
|
const provider = this.liveProvider;
|
|
135
159
|
try {
|
|
136
160
|
const { emit, retarget } = this.mirrorChannel(msg.localThreadId);
|
|
137
161
|
const started = await provider.ensureLiveCodexSession?.(msg.localThreadId, emit, {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
...(msg.reasoningEffort ? { reasoningEffort: msg.reasoningEffort } : {}),
|
|
141
|
-
...(msg.agentName ? { agentName: msg.agentName } : {}),
|
|
142
|
-
...(msg.agentSpec ? { agentSpec: msg.agentSpec } : {}),
|
|
162
|
+
workspace: msg.workspace,
|
|
163
|
+
execution: msg.execution,
|
|
143
164
|
retargetMirror: retarget,
|
|
144
165
|
});
|
|
145
166
|
if (!started) {
|
|
@@ -153,12 +174,24 @@ export class RunnerSession {
|
|
|
153
174
|
return;
|
|
154
175
|
}
|
|
155
176
|
this.liveIds.add(msg.localThreadId);
|
|
156
|
-
// Launch the TUI
|
|
157
|
-
//
|
|
158
|
-
// is absent OR its process has died (`isAlive` probes `#{pane_dead}`) — so a
|
|
177
|
+
// Launch the TUI attached to the already-bound thread. Re-launch when the
|
|
178
|
+
// pane is absent OR its process has died (`isAlive` probes `#{pane_dead}`) — so a
|
|
159
179
|
// reconnect after the TUI exited restarts it instead of skipping (a
|
|
160
180
|
// launched-once guard would leave a dead "Pane is dead" husk forever).
|
|
161
181
|
if (!this.terminals.get(`${msg.localThreadId}-main`)?.isAlive()) {
|
|
182
|
+
const terminalReady = provider.waitTerminalReady
|
|
183
|
+
? await provider.waitTerminalReady(msg.localThreadId)
|
|
184
|
+
: true;
|
|
185
|
+
if (!terminalReady) {
|
|
186
|
+
this.transport.send({
|
|
187
|
+
t: "live.ready",
|
|
188
|
+
reqId: msg.reqId,
|
|
189
|
+
localThreadId: msg.localThreadId,
|
|
190
|
+
ok: false,
|
|
191
|
+
error: "Provider thread was not ready for Terminal resume",
|
|
192
|
+
});
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
162
195
|
await this.launchCodexPane(msg.localThreadId, msg.cols, msg.rows);
|
|
163
196
|
}
|
|
164
197
|
const ready = msg.waitForReady === false
|
|
@@ -166,12 +199,27 @@ export class RunnerSession {
|
|
|
166
199
|
: provider.waitLiveReady
|
|
167
200
|
? await provider.waitLiveReady(msg.localThreadId)
|
|
168
201
|
: true;
|
|
202
|
+
const terminal = this.terminals.get(`${msg.localThreadId}-main`);
|
|
203
|
+
const paneFailure = !ready && terminal && !terminal.isAlive()
|
|
204
|
+
? terminal
|
|
205
|
+
.capturePane()
|
|
206
|
+
.split("\n")
|
|
207
|
+
.map((line) => line.trim())
|
|
208
|
+
.filter(Boolean)
|
|
209
|
+
.slice(-6)
|
|
210
|
+
.join(" ")
|
|
211
|
+
.slice(-1_000)
|
|
212
|
+
: "";
|
|
213
|
+
const readinessError = provider.liveSessionError?.(msg.localThreadId)
|
|
214
|
+
?? (paneFailure
|
|
215
|
+
? `Provider terminal exited before session discovery: ${paneFailure}`
|
|
216
|
+
: "live session was not ready before timeout");
|
|
169
217
|
this.transport.send({
|
|
170
218
|
t: "live.ready",
|
|
171
219
|
reqId: msg.reqId,
|
|
172
220
|
localThreadId: msg.localThreadId,
|
|
173
221
|
ok: ready,
|
|
174
|
-
...(ready ? {} : { error:
|
|
222
|
+
...(ready ? {} : { error: readinessError }),
|
|
175
223
|
});
|
|
176
224
|
}
|
|
177
225
|
catch (error) {
|
|
@@ -189,6 +237,8 @@ export class RunnerSession {
|
|
|
189
237
|
try {
|
|
190
238
|
const input = msg.input ?? msg.text;
|
|
191
239
|
const outcome = (await provider.injectMessage?.(msg.localThreadId, input)) ?? "notLive";
|
|
240
|
+
// App-server injection is independent of the Terminal TUI startup, so it
|
|
241
|
+
// must not cancel prompt handling for the pane that is still starting.
|
|
192
242
|
this.transport.send({ t: "injected", reqId: msg.reqId, localThreadId: msg.localThreadId, outcome });
|
|
193
243
|
}
|
|
194
244
|
catch (error) {
|
|
@@ -226,7 +276,8 @@ export class RunnerSession {
|
|
|
226
276
|
const spec = await this.liveProvider.codexTerminalSpec?.(localThreadId);
|
|
227
277
|
if (!spec)
|
|
228
278
|
return;
|
|
229
|
-
const
|
|
279
|
+
const terminalId = `${localThreadId}-main`;
|
|
280
|
+
const term = this.terminals.getOrCreate(terminalId, {
|
|
230
281
|
cwd: spec.cwd,
|
|
231
282
|
command: spec.command,
|
|
232
283
|
args: spec.args,
|
|
@@ -234,8 +285,70 @@ export class RunnerSession {
|
|
|
234
285
|
rows: rows ?? 40,
|
|
235
286
|
...(spec.env ? { env: spec.env } : {}),
|
|
236
287
|
});
|
|
288
|
+
if (spec.skipTraexStartupPrompts) {
|
|
289
|
+
const watcher = Symbol(localThreadId);
|
|
290
|
+
this.traexStartupWatchers.set(localThreadId, watcher);
|
|
291
|
+
void this.skipTraexStartupPrompts(localThreadId, term, watcher);
|
|
292
|
+
}
|
|
237
293
|
this.liveProvider.attachTerminalInjector?.(localThreadId, term);
|
|
238
294
|
}
|
|
295
|
+
cancelTraexStartupWatcher(localThreadId) {
|
|
296
|
+
if (localThreadId)
|
|
297
|
+
this.traexStartupWatchers.delete(localThreadId);
|
|
298
|
+
}
|
|
299
|
+
async skipTraexStartupPrompts(localThreadId, terminal, watcher) {
|
|
300
|
+
const prompts = [
|
|
301
|
+
{
|
|
302
|
+
id: "welcome",
|
|
303
|
+
matches: (pane) => pane.includes("welcome to trae cli") && pane.includes("press enter to continue"),
|
|
304
|
+
dismiss: () => terminal.sendEnter(),
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
id: "migration",
|
|
308
|
+
matches: (pane) => pane.includes("legacy trae cli data detected") &&
|
|
309
|
+
pane.includes("select what to import") &&
|
|
310
|
+
(pane.includes("skip for now") || pane.includes("don't ask again")),
|
|
311
|
+
dismiss: () => terminal.interrupt(),
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
id: "hooks",
|
|
315
|
+
matches: (pane) => pane.includes("hooks need review") &&
|
|
316
|
+
pane.includes("trust all and continue") &&
|
|
317
|
+
pane.includes("continue without trusting"),
|
|
318
|
+
dismiss: () => terminal.interrupt(),
|
|
319
|
+
},
|
|
320
|
+
];
|
|
321
|
+
let activePromptId;
|
|
322
|
+
let lastDismissedAt = 0;
|
|
323
|
+
const deadline = Date.now() + TRAEX_STARTUP_WATCH_MS;
|
|
324
|
+
const terminalId = `${localThreadId}-main`;
|
|
325
|
+
while (!this.shuttingDown &&
|
|
326
|
+
Date.now() < deadline &&
|
|
327
|
+
this.traexStartupWatchers.get(localThreadId) === watcher &&
|
|
328
|
+
this.terminals.get(terminalId) === terminal) {
|
|
329
|
+
// Do not treat a composer frame as completion: Traex can render it before
|
|
330
|
+
// the startup modals arrive. The bounded deadline stops this watcher.
|
|
331
|
+
const pane = normalizeTraexPane(terminal.capturePane());
|
|
332
|
+
const prompt = prompts.find((candidate) => candidate.matches(pane));
|
|
333
|
+
const now = Date.now();
|
|
334
|
+
if (!prompt) {
|
|
335
|
+
activePromptId = undefined;
|
|
336
|
+
}
|
|
337
|
+
else if (prompt.id !== activePromptId ||
|
|
338
|
+
now - lastDismissedAt >= TRAEX_PROMPT_RETRY_MS) {
|
|
339
|
+
prompt.dismiss();
|
|
340
|
+
activePromptId = prompt.id;
|
|
341
|
+
lastDismissedAt = now;
|
|
342
|
+
}
|
|
343
|
+
await new Promise((resolve) => {
|
|
344
|
+
const timer = setTimeout(resolve, TRAEX_STARTUP_POLL_MS);
|
|
345
|
+
timer.unref();
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (this.traexStartupWatchers.get(localThreadId) === watcher) {
|
|
349
|
+
this.traexStartupWatchers.delete(localThreadId);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
239
352
|
stopLive() {
|
|
240
353
|
for (const id of this.liveIds) {
|
|
241
354
|
this.liveProvider.stopLiveCodexSession?.(id, {
|
|
@@ -314,6 +427,8 @@ export class RunnerSession {
|
|
|
314
427
|
return;
|
|
315
428
|
}
|
|
316
429
|
this.attachments.set(msg.attachId, attachment);
|
|
430
|
+
if (msg.localThreadId)
|
|
431
|
+
this.attachmentThreadIds.set(msg.attachId, msg.localThreadId);
|
|
317
432
|
attachment.onData((chunk) => this.transport.send({
|
|
318
433
|
t: "term.data",
|
|
319
434
|
attachId: msg.attachId,
|
|
@@ -321,6 +436,7 @@ export class RunnerSession {
|
|
|
321
436
|
}));
|
|
322
437
|
attachment.onExit((info) => {
|
|
323
438
|
this.attachments.delete(msg.attachId);
|
|
439
|
+
this.attachmentThreadIds.delete(msg.attachId);
|
|
324
440
|
this.transport.send({ t: "term.exit", attachId: msg.attachId, exitCode: info.exitCode });
|
|
325
441
|
});
|
|
326
442
|
this.transport.send({ t: "term.opened", attachId: msg.attachId, role });
|
|
@@ -343,7 +459,10 @@ export class RunnerSession {
|
|
|
343
459
|
case "clearGoal":
|
|
344
460
|
return this.executor.clearGoal(args[0]);
|
|
345
461
|
case "forkSession":
|
|
346
|
-
|
|
462
|
+
if (!this.executor.forkSession) {
|
|
463
|
+
throw new Error("Provider fork is unavailable");
|
|
464
|
+
}
|
|
465
|
+
return this.executor.forkSession(args[0], args[1], args[2]);
|
|
347
466
|
}
|
|
348
467
|
}
|
|
349
468
|
}
|
package/dist/runner/manager.d.ts
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* transport drops in here later without touching `ConversationRuntime` or any
|
|
16
16
|
* channel.
|
|
17
17
|
*/
|
|
18
|
-
import { spawn as nodeSpawn } from "node:child_process";
|
|
19
|
-
import { type
|
|
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";
|
|
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";
|
|
@@ -43,6 +43,9 @@ export interface RunnerManagerOptions {
|
|
|
43
43
|
runnerEntry?: string;
|
|
44
44
|
/** Idle TTL (ms) after which an unused runner is reaped. */
|
|
45
45
|
idleTtlMs?: number;
|
|
46
|
+
/** Maximum time without user/control activity that an active response may
|
|
47
|
+
* protect a detached runner from idle reaping. */
|
|
48
|
+
staleActiveTtlMs?: number;
|
|
46
49
|
/** Background reap sweep interval (ms); `0` disables the timer (tests). */
|
|
47
50
|
reapIntervalMs?: number;
|
|
48
51
|
/** Grace period between SIGTERM and SIGKILL during runner shutdown. */
|
|
@@ -53,8 +56,22 @@ export interface RunnerManagerOptions {
|
|
|
53
56
|
liveStartTimeoutMs?: number;
|
|
54
57
|
/** Max wait for a Provider-native thread to become ready. */
|
|
55
58
|
liveReadyTimeoutMs?: number;
|
|
59
|
+
/** Max wait for a native interrupt acknowledgement before reporting it
|
|
60
|
+
* unproven. Callers may then use the explicit force-stop path. */
|
|
61
|
+
liveInterruptTimeoutMs?: number;
|
|
62
|
+
/** Max wait for an accepted owner TUI submission to become observable as a
|
|
63
|
+
* mirrored response or a published native rotation. */
|
|
64
|
+
terminalInputHandoffTimeoutMs?: number;
|
|
65
|
+
/** Initial exponential backoff for retrying unproven terminal cleanup. */
|
|
66
|
+
terminalInputCleanupRetryMs?: number;
|
|
56
67
|
/** Injected for tests. */
|
|
57
68
|
spawn?: typeof nodeSpawn;
|
|
69
|
+
/** Injected for tests. Defaults to signaling the whole POSIX process group
|
|
70
|
+
* created for the runner child, with direct-child fallback. */
|
|
71
|
+
signalChild?: (child: ChildProcess, signal: NodeJS.Signals, processGroup: boolean) => void;
|
|
72
|
+
/** Injected for tests. Kills and verifies the deterministic private tmux
|
|
73
|
+
* server owned by a Session runner. */
|
|
74
|
+
terminateTerminalServer?: (terminalName: string) => boolean | Promise<boolean>;
|
|
58
75
|
now?: () => number;
|
|
59
76
|
/** Extra env merged into every spawned runner child — e.g. the control-plane
|
|
60
77
|
* URL + token so a claude-native PermissionRequest hook can POST back. */
|
|
@@ -62,6 +79,13 @@ export interface RunnerManagerOptions {
|
|
|
62
79
|
/** Optional lifecycle context for Session runners. The shared `__cap__`
|
|
63
80
|
* runner never opens one. */
|
|
64
81
|
sessionContextProvider?: RunnerSessionContextProvider;
|
|
82
|
+
/** Dynamic process-wide admission fence. Existing cancellation and read-only
|
|
83
|
+
* inspection remain available; every path that may spawn or attach execution
|
|
84
|
+
* checks this immediately before admission. */
|
|
85
|
+
admissionOpen?: () => boolean;
|
|
86
|
+
/** Atomically reserves one work-producing admission while it crosses the
|
|
87
|
+
* asynchronous boundary into an observable live runner/turn. */
|
|
88
|
+
admissionReserve?: () => AdmissionReservation | undefined;
|
|
65
89
|
}
|
|
66
90
|
/** A session rotation reported by a runner child (claude `/clear`·`/fork`): the
|
|
67
91
|
* new session `to` is now aliased to `from`'s runner; carries meta to carry over. */
|
|
@@ -69,9 +93,8 @@ export interface RotateInfo {
|
|
|
69
93
|
from: string;
|
|
70
94
|
to: string;
|
|
71
95
|
kind: "clear" | "fork";
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
cwd?: string;
|
|
96
|
+
workspace: SessionWorkspaceSnapshot;
|
|
97
|
+
execution: ResolvedExecutionSnapshot;
|
|
75
98
|
parentSessionId?: string;
|
|
76
99
|
}
|
|
77
100
|
/** Options for {@link RunnerManager.openTerminal}. */
|
|
@@ -116,9 +139,12 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
116
139
|
private readonly sessionStore;
|
|
117
140
|
private readonly runnerEntry;
|
|
118
141
|
private readonly idleTtlMs;
|
|
142
|
+
private readonly staleActiveTtlMs;
|
|
119
143
|
private readonly spawn;
|
|
120
144
|
private readonly childEnv;
|
|
121
145
|
private readonly sessionContextProvider;
|
|
146
|
+
private readonly admissionOpen;
|
|
147
|
+
private readonly admissionReserve;
|
|
122
148
|
private readonly now;
|
|
123
149
|
private readonly defaultRuntime;
|
|
124
150
|
private readonly handles;
|
|
@@ -128,8 +154,13 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
128
154
|
private readonly reapTimer;
|
|
129
155
|
private readonly shutdownGraceMs;
|
|
130
156
|
private readonly shutdownKillGraceMs;
|
|
157
|
+
private readonly signalChild;
|
|
158
|
+
private readonly terminateTerminalServer;
|
|
131
159
|
private readonly liveStartTimeoutMs;
|
|
132
160
|
private readonly liveReadyTimeoutMs;
|
|
161
|
+
private readonly liveInterruptTimeoutMs;
|
|
162
|
+
private readonly terminalInputHandoffTimeoutMs;
|
|
163
|
+
private readonly terminalInputCleanupRetryMs;
|
|
133
164
|
private stopping;
|
|
134
165
|
private stopPromise;
|
|
135
166
|
/** Sink for mirrored {@link SessionEvent}s from every session's forwarder. */
|
|
@@ -140,6 +171,24 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
140
171
|
private readonly liveSessionKeys;
|
|
141
172
|
/** Last live-start error per local session, surfaced by the control API. */
|
|
142
173
|
private readonly liveErrors;
|
|
174
|
+
/** Last immutable launch snapshots seen for a Session. Used only to restore a
|
|
175
|
+
* target request that crossed a fork reservation boundary. */
|
|
176
|
+
private readonly liveOptions;
|
|
177
|
+
/** A fork reserves its target before the first asynchronous store read. This
|
|
178
|
+
* prevents another entry point from starting the target against an
|
|
179
|
+
* uncommitted Provider binding. */
|
|
180
|
+
private readonly forkReservations;
|
|
181
|
+
/** A native fork temporarily makes the source read-only so its canonical
|
|
182
|
+
* snapshot and Provider context are captured at the same boundary. */
|
|
183
|
+
private readonly sourceForkReservations;
|
|
184
|
+
/** Manager-wide fork de-duplication. LocalAgentHost only sees one source
|
|
185
|
+
* runner, so the fence must live here to cover concurrent source runners. */
|
|
186
|
+
private readonly forkOperations;
|
|
187
|
+
private readonly forkBufferedMessages;
|
|
188
|
+
private readonly forkBufferedTerminalInputs;
|
|
189
|
+
/** Owner TUI submissions accepted by the parent but not yet represented by a
|
|
190
|
+
* mirrored response or a durably published native rotation. */
|
|
191
|
+
private readonly terminalInputHandoffs;
|
|
143
192
|
constructor(opts: RunnerManagerOptions);
|
|
144
193
|
/**
|
|
145
194
|
* Open a live terminal on the session's runner child (spawning it if needed).
|
|
@@ -166,33 +215,39 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
166
215
|
onMirror(listener: (sessionId: string, event: SessionEvent) => void): void;
|
|
167
216
|
/** Register the sink for session rotations (claude `/clear`·`/fork`): the server
|
|
168
217
|
* records the new session's meta (carry-over agent/model/title). */
|
|
169
|
-
onRotate(listener: (rotation: RotateInfo) => void): void;
|
|
218
|
+
onRotate(listener: (rotation: RotateInfo) => void | Promise<void>): void;
|
|
219
|
+
/** Accepted TUI submissions that have not crossed into an observable runtime
|
|
220
|
+
* state. The daemon folds this into runningTurns after closing admission. */
|
|
221
|
+
pendingTerminalInputCount(): number;
|
|
222
|
+
private beginTerminalInputHandoffs;
|
|
223
|
+
private settleTerminalInputHandoff;
|
|
224
|
+
private settleTerminalRotationHandoff;
|
|
225
|
+
private currentTerminalSessionId;
|
|
226
|
+
private finishTerminalInputHandoff;
|
|
227
|
+
private finishAllTerminalInputHandoffs;
|
|
228
|
+
private preserveTerminalInputHandoffsAsActivity;
|
|
229
|
+
private scheduleTerminalInputCleanupRetry;
|
|
230
|
+
private expireTerminalInputHandoffs;
|
|
170
231
|
/**
|
|
171
232
|
* Eagerly bring up a session's codex-native live view (persistent forwarder +
|
|
172
233
|
* detached `codex --remote` TUI) in its runner child, spawning the runner if
|
|
173
234
|
* needed. Idempotent. Resolves true once the codex thread is bound; false for a
|
|
174
235
|
* non-codex / non-live session (the caller then uses the normal run path).
|
|
175
236
|
*/
|
|
176
|
-
ensureLiveSession(localThreadId: string, opts
|
|
177
|
-
|
|
237
|
+
ensureLiveSession(localThreadId: string, opts: {
|
|
238
|
+
workspace: SessionWorkspaceSnapshot;
|
|
239
|
+
execution: ResolvedExecutionSnapshot;
|
|
178
240
|
cols?: number;
|
|
179
241
|
rows?: number;
|
|
180
|
-
runtime?: AgentRuntimeId;
|
|
181
|
-
reasoningEffort?: ReasoningEffort;
|
|
182
|
-
agentName?: string;
|
|
183
|
-
agentSpec?: AgentSpec;
|
|
184
242
|
}): Promise<boolean>;
|
|
185
243
|
/** Start the Provider pane without waiting for login/onboarding to create a
|
|
186
244
|
* native thread. This is the setup-terminal gate; callers may attach as soon
|
|
187
245
|
* as it resolves, while normal message delivery still uses ensureLiveSession. */
|
|
188
|
-
startLiveSession(localThreadId: string, opts
|
|
189
|
-
|
|
246
|
+
startLiveSession(localThreadId: string, opts: {
|
|
247
|
+
workspace: SessionWorkspaceSnapshot;
|
|
248
|
+
execution: ResolvedExecutionSnapshot;
|
|
190
249
|
cols?: number;
|
|
191
250
|
rows?: number;
|
|
192
|
-
runtime?: AgentRuntimeId;
|
|
193
|
-
reasoningEffort?: ReasoningEffort;
|
|
194
|
-
agentName?: string;
|
|
195
|
-
agentSpec?: AgentSpec;
|
|
196
251
|
}): Promise<boolean>;
|
|
197
252
|
private requestLiveSession;
|
|
198
253
|
lastLiveSessionError(localThreadId: string): string | undefined;
|
|
@@ -203,6 +258,7 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
203
258
|
* the session has no live forwarder (caller falls back to the run path).
|
|
204
259
|
*/
|
|
205
260
|
injectMessage(localThreadId: string, input: RuntimeUserInput | string): Promise<InjectOutcome>;
|
|
261
|
+
private injectMessageAdmitted;
|
|
206
262
|
/**
|
|
207
263
|
* Interrupt a session's active live turn — the web Stop button (codex
|
|
208
264
|
* `turn/interrupt`, claude Escape). Best-effort: resolves false with NO spawn
|
|
@@ -215,7 +271,16 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
215
271
|
getGoal(localThreadId: string): Promise<CapabilityResult<ThreadGoal | null>>;
|
|
216
272
|
setGoal(localThreadId: string, objective: string): Promise<CapabilityResult>;
|
|
217
273
|
clearGoal(localThreadId: string): Promise<CapabilityResult>;
|
|
218
|
-
forkSession(currentLocalThreadId: string, newLocalThreadId: string
|
|
274
|
+
forkSession(currentLocalThreadId: string, newLocalThreadId: string, options: {
|
|
275
|
+
workspace: SessionWorkspaceSnapshot;
|
|
276
|
+
execution: ResolvedExecutionSnapshot;
|
|
277
|
+
beforeProviderFork: () => Promise<void>;
|
|
278
|
+
}): Promise<{
|
|
279
|
+
ok: true;
|
|
280
|
+
} | {
|
|
281
|
+
ok: false;
|
|
282
|
+
message: string;
|
|
283
|
+
}>;
|
|
219
284
|
/**
|
|
220
285
|
* Backend-free runtime readiness (not part of `AgentCapabilities`; surfaced for
|
|
221
286
|
* the control console). Never spawns a runner.
|
|
@@ -226,18 +291,38 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
226
291
|
}): Promise<import("../host.js").CodexRuntimeStatus>;
|
|
227
292
|
/** Stop the runner bound to one session (if any), rejecting its in-flight work. */
|
|
228
293
|
stopRunner(localThreadId: string): void;
|
|
294
|
+
/** Force-stop and join the runner bound to one Session. Unlike stopRunner,
|
|
295
|
+
* completion proves that the child process has exited. */
|
|
296
|
+
terminateLiveSession(localThreadId: string): Promise<boolean>;
|
|
229
297
|
/** Stop every runner and join all child exits. Idempotent across concurrent calls. */
|
|
230
298
|
stop(): Promise<void>;
|
|
299
|
+
private handleForCleanup;
|
|
231
300
|
/** Forward a capability to a runner child. Session-less caps (listModels/status)
|
|
232
301
|
* use the shared `CAP_KEY` child; per-thread caps pass the session's key so they
|
|
233
302
|
* run on that session's child (which owns its private CODEX_HOME). */
|
|
234
303
|
private forwardCap;
|
|
235
304
|
private getOrSpawn;
|
|
305
|
+
private assertAdmissionOpen;
|
|
306
|
+
private reserveAdmission;
|
|
307
|
+
private performManagedFork;
|
|
308
|
+
private performClaudeManagedFork;
|
|
309
|
+
private bufferForkTargetMessage;
|
|
310
|
+
private reservedForkTarget;
|
|
311
|
+
private deliverForkBufferedMessage;
|
|
312
|
+
private deliverRotateMessage;
|
|
236
313
|
private spawnHandle;
|
|
237
314
|
private onChildMessage;
|
|
238
315
|
/** Mark a handle dead and reject every pending run/cap with the exit reason. */
|
|
239
316
|
private failHandle;
|
|
240
317
|
private terminateHandle;
|
|
241
318
|
private reapIdle;
|
|
319
|
+
/** Close server-side runtime state before fencing a Provider that stayed
|
|
320
|
+
* active past its hard inactivity deadline. The child transport is closed by
|
|
321
|
+
* terminateHandle immediately afterwards, so these are the final events for
|
|
322
|
+
* the abandoned responses. */
|
|
323
|
+
private failStaleActiveResponses;
|
|
324
|
+
/** Track only response lifecycle, not output volume. Output deltas from a
|
|
325
|
+
* runaway TUI/Provider must not refresh the stale-active deadline. */
|
|
326
|
+
private observeHandleRuntimeEvent;
|
|
242
327
|
private openSessionContext;
|
|
243
328
|
}
|