@rynx-ai/runtime 0.1.11-beta.24 → 0.1.11-beta.26
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-bridge.d.ts +2 -0
- package/dist/claude/native-bridge.js +23 -0
- package/dist/claude/native-hook-main.js +62 -0
- package/dist/claude/native-integration.d.ts +8 -3
- package/dist/claude/native-integration.js +38 -8
- package/dist/claude/transcript.js +27 -17
- package/dist/codex-app-server/client.js +11 -1
- package/dist/codex-app-server/forwarder.d.ts +25 -1
- package/dist/codex-app-server/forwarder.js +216 -55
- package/dist/codex-app-server/mapping.d.ts +0 -6
- package/dist/codex-app-server/mapping.js +70 -5
- package/dist/codex-app-server/protocol.d.ts +35 -2
- package/dist/host.d.ts +15 -7
- package/dist/host.js +517 -50
- package/dist/input-resources.d.ts +4 -0
- package/dist/input-resources.js +21 -5
- package/dist/runner/child.d.ts +13 -13
- package/dist/runner/child.js +183 -13
- package/dist/runner/manager.d.ts +12 -4
- package/dist/runner/manager.js +204 -20
- package/dist/runner/protocol.d.ts +47 -1
- package/dist/runner/protocol.js +5 -0
- package/dist/terminal/registry.js +1 -1
- package/dist/terminal/tmux.d.ts +5 -5
- package/dist/terminal/tmux.js +6 -6
- package/package.json +2 -2
|
@@ -3,6 +3,10 @@ import type { UserInput } from "./codex-app-server/protocol.js";
|
|
|
3
3
|
/** Convert the provider-native user echo back to resource references. Unknown
|
|
4
4
|
* local paths and remote URLs are deliberately omitted rather than exposed. */
|
|
5
5
|
export declare function codexUserContent(input: readonly UserInput[]): UserContentPart[];
|
|
6
|
+
/** Match the forwarder's public user echo shape exactly. Codex merges adjacent
|
|
7
|
+
* text inputs (including the internal file marker) into one string before the
|
|
8
|
+
* Host sees the completed userMessage item. */
|
|
9
|
+
export declare function codexUserEchoContent(input: readonly UserInput[]): string | UserContentPart[] | undefined;
|
|
6
10
|
/** Claude's native TUI has no structured image RPC. The target daemon supplies
|
|
7
11
|
* only managed local paths and the marker makes the transcript echo reversible. */
|
|
8
12
|
export declare function claudeInputText(input: RuntimeUserInput, attachmentToken?: string): string;
|
package/dist/input-resources.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { basename, extname } from "node:path";
|
|
3
3
|
const CLAUDE_ATTACHMENT_TOKEN = "RYNX_ATTACHMENT_SET";
|
|
4
|
-
const CLAUDE_ATTACHMENT_TOKEN_RE = /^\[RYNX_ATTACHMENT_SET (att_[0-9a-f-]{36}): inspect each RYNX_IMAGE_RESOURCE path with the Read tool before answering\.\]$/m;
|
|
4
|
+
const CLAUDE_ATTACHMENT_TOKEN_RE = /^\[RYNX_ATTACHMENT_SET (att_[0-9a-f-]{36}): inspect each RYNX_IMAGE_RESOURCE or RYNX_FILE_RESOURCE path with the Read tool before answering\.\]$/m;
|
|
5
5
|
/** Convert the provider-native user echo back to resource references. Unknown
|
|
6
6
|
* local paths and remote URLs are deliberately omitted rather than exposed. */
|
|
7
7
|
export function codexUserContent(input) {
|
|
@@ -20,21 +20,37 @@ export function codexUserContent(input) {
|
|
|
20
20
|
}
|
|
21
21
|
return parts;
|
|
22
22
|
}
|
|
23
|
+
/** Match the forwarder's public user echo shape exactly. Codex merges adjacent
|
|
24
|
+
* text inputs (including the internal file marker) into one string before the
|
|
25
|
+
* Host sees the completed userMessage item. */
|
|
26
|
+
export function codexUserEchoContent(input) {
|
|
27
|
+
const parts = codexUserContent(input);
|
|
28
|
+
if (parts.length === 0)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (parts.every((part) => part.type === "input_text")) {
|
|
31
|
+
const text = parts.map((part) => part.text).join("").trim();
|
|
32
|
+
return text || undefined;
|
|
33
|
+
}
|
|
34
|
+
return parts;
|
|
35
|
+
}
|
|
23
36
|
/** Claude's native TUI has no structured image RPC. The target daemon supplies
|
|
24
37
|
* only managed local paths and the marker makes the transcript echo reversible. */
|
|
25
38
|
export function claudeInputText(input, attachmentToken = `att_${randomUUID()}`) {
|
|
26
|
-
const
|
|
39
|
+
const attachments = input.content.filter((part) => part.type !== "text");
|
|
27
40
|
const text = input.content
|
|
28
41
|
.filter((part) => part.type === "text")
|
|
29
42
|
.map((part) => part.text)
|
|
30
43
|
.join("");
|
|
31
|
-
if (
|
|
44
|
+
if (attachments.length === 0)
|
|
32
45
|
return text;
|
|
33
46
|
if (!/^att_[0-9a-f-]{36}$/.test(attachmentToken)) {
|
|
34
47
|
throw new Error("Claude attachment token is invalid");
|
|
35
48
|
}
|
|
36
|
-
const preamble = `[${CLAUDE_ATTACHMENT_TOKEN} ${attachmentToken}: inspect each RYNX_IMAGE_RESOURCE path with the Read tool before answering.]`;
|
|
37
|
-
const markers =
|
|
49
|
+
const preamble = `[${CLAUDE_ATTACHMENT_TOKEN} ${attachmentToken}: inspect each RYNX_IMAGE_RESOURCE or RYNX_FILE_RESOURCE path with the Read tool before answering.]`;
|
|
50
|
+
const markers = attachments.map((part) => `[[RYNX_${part.type === "local_image" ? "IMAGE" : "FILE"}_RESOURCE ${JSON.stringify({
|
|
51
|
+
path: part.path,
|
|
52
|
+
...(part.resource.filename ? { filename: part.resource.filename } : {}),
|
|
53
|
+
})}]]`);
|
|
38
54
|
return [preamble, ...markers, text]
|
|
39
55
|
.filter((part, index) => index <= markers.length || part.length > 0)
|
|
40
56
|
.join("\n");
|
package/dist/runner/child.d.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
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 { type AgentCapabilities, type LiveSessionFailure, type ResolvedExecutionSnapshot, type RuntimeUserInput, type SessionInteractionResolution, type SessionWorkspaceSnapshot } from "@rynx-ai/core";
|
|
1
|
+
import { type AgentCapabilities, type LiveSessionFailure, type ResolvedExecutionSnapshot, type RuntimeTurnOptions, type RuntimeUserInput, type SessionCollaborationMode, type SessionInteractionResolution, type SessionWorkspaceSnapshot } from "@rynx-ai/core";
|
|
11
2
|
import type { SessionEvent } from "@rynx-ai/core";
|
|
12
3
|
import type { TerminalInjector } from "../claude/native-integration.js";
|
|
13
4
|
import type { ResolveInteractionResult } from "../interactions.js";
|
|
@@ -54,15 +45,17 @@ interface LiveCodexProvider {
|
|
|
54
45
|
* observer/forwarder, and evict the owned app-server while preserving the
|
|
55
46
|
* durable native-session binding for a later cold resume. */
|
|
56
47
|
teardownLiveCodexSession?(localThreadId: string, error?: Error): boolean;
|
|
57
|
-
injectMessage?(localThreadId: string, input: RuntimeUserInput | string): Promise<InjectResult>;
|
|
48
|
+
injectMessage?(localThreadId: string, input: RuntimeUserInput | string, options?: RuntimeTurnOptions): Promise<InjectResult>;
|
|
49
|
+
updateCollaborationMode?(localThreadId: string, mode: SessionCollaborationMode): Promise<void>;
|
|
58
50
|
interruptLive?(localThreadId: string): Promise<boolean>;
|
|
59
51
|
stopLiveCodexSession?(localThreadId: string, opts?: {
|
|
60
52
|
deferClaudeInteractionCleanup?: boolean;
|
|
61
53
|
}): void;
|
|
62
54
|
finalizeStoppedLiveSessions?(): void;
|
|
63
55
|
resolveInteraction?(localThreadId: string, interactionId: string, resolution: SessionInteractionResolution): Promise<ResolveInteractionResult>;
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
56
|
+
/** Hand the session's tmux pane injector to the host (which doesn't own
|
|
57
|
+
* tmux). Claude uses it for messages; Codex uses it only for TUI-local
|
|
58
|
+
* controls that the app-server cannot resolve, such as the final Plan picker. */
|
|
66
59
|
attachTerminalInjector?(localThreadId: string, injector: TerminalInjector): void;
|
|
67
60
|
/** Internal Provider operation used only by MachineSessionService through
|
|
68
61
|
* RunnerManager. It is deliberately absent from public AgentCapabilities. */
|
|
@@ -93,6 +86,8 @@ export declare class RunnerSession {
|
|
|
93
86
|
private readonly cancelledTerminalOpens;
|
|
94
87
|
/** Session ids with a live codex forwarder started here (stopped on shutdown). */
|
|
95
88
|
private readonly liveIds;
|
|
89
|
+
private mirrorQueue;
|
|
90
|
+
private readonly mirrorImageAcks;
|
|
96
91
|
/** Provider name retained for asynchronous Terminal-exit diagnostics. */
|
|
97
92
|
private readonly liveRuntimes;
|
|
98
93
|
private shuttingDown;
|
|
@@ -106,6 +101,10 @@ export declare class RunnerSession {
|
|
|
106
101
|
* new session AND tells the daemon to alias the runner (terminal transfer) so
|
|
107
102
|
* the new session stays injectable. */
|
|
108
103
|
private mirrorChannel;
|
|
104
|
+
private enqueueMirror;
|
|
105
|
+
private sendMirroredEvent;
|
|
106
|
+
private sendMirrorImageFrame;
|
|
107
|
+
private rejectMirrorImageAcks;
|
|
109
108
|
/**
|
|
110
109
|
* Eagerly bring up a session's codex-native live view. Fresh sessions connect
|
|
111
110
|
* the discovery listener before launching the detached TUI; known-thread
|
|
@@ -120,6 +119,7 @@ export declare class RunnerSession {
|
|
|
120
119
|
* already-rejected readiness promise. */
|
|
121
120
|
private failCodexThreadStartup;
|
|
122
121
|
private inject;
|
|
122
|
+
private updateCollaborationMode;
|
|
123
123
|
private interruptLive;
|
|
124
124
|
/** Launch (idempotently) the session's codex TUI pane from the executor's
|
|
125
125
|
* `codexTerminalSpec`. Shares the `${id}-main` terminal id with `term.open`,
|
package/dist/runner/child.js
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
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 { randomUUID } from "node:crypto";
|
|
10
11
|
import { AgentRuntimeError, } from "@rynx-ai/core";
|
|
11
12
|
import { TerminalRegistry } from "../terminal/registry.js";
|
|
12
|
-
import { toWireError, } from "./protocol.js";
|
|
13
|
+
import { RUNNER_IMAGE_CHUNK_CHARS, RUNNER_IMAGE_MAX_RESULT_CHARS, toWireError, } from "./protocol.js";
|
|
13
14
|
import { isCodexLineageProvider } from "./startup-policy.js";
|
|
14
15
|
/** Maximum time for a fresh Codex-lineage TUI to publish its native thread. */
|
|
15
16
|
const CODEX_THREAD_START_TIMEOUT_MS = 30_000;
|
|
@@ -48,6 +49,7 @@ const TRAEX_STARTUP_POLL_MS = 100;
|
|
|
48
49
|
const TRAEX_AUTHORIZATION_POLL_MS = 500;
|
|
49
50
|
const TRAEX_AUTHORIZATION_WAIT_MS = 15 * 60_000;
|
|
50
51
|
const TRAEX_PROMPT_RETRY_MS = 500;
|
|
52
|
+
const MIRROR_IMAGE_ACK_TIMEOUT_MS = 30_000;
|
|
51
53
|
function normalizeTraexPane(pane) {
|
|
52
54
|
return pane.toLowerCase().replace(/\s+/g, " ").trim();
|
|
53
55
|
}
|
|
@@ -63,6 +65,48 @@ function isTerminalProtocolResponse(input) {
|
|
|
63
65
|
// Neither is evidence that the user has taken over startup prompt handling.
|
|
64
66
|
return /^(?:\x1b\[(?:(?:\?|>)[0-9;]*c|[0-9;]*(?:n|R|t)|[IO])|\x1b\][0-9]+;[^\x07\x1b]*(?:\x07|\x1b\\))+$/.test(input);
|
|
65
67
|
}
|
|
68
|
+
function detachGeneratedImageResult(event) {
|
|
69
|
+
if (event.type !== "response.output_item.done" ||
|
|
70
|
+
event.item.type !== "function_call_output") {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const data = event.item.data;
|
|
74
|
+
const pending = data.__rynxGeneratedImage;
|
|
75
|
+
if (!pending?.result)
|
|
76
|
+
return undefined;
|
|
77
|
+
const { result, ...withoutResult } = pending;
|
|
78
|
+
return {
|
|
79
|
+
result,
|
|
80
|
+
event: {
|
|
81
|
+
...event,
|
|
82
|
+
item: {
|
|
83
|
+
...event.item,
|
|
84
|
+
data: {
|
|
85
|
+
...data,
|
|
86
|
+
__rynxGeneratedImage: withoutResult,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function generatedImagePreviewUnavailable(event) {
|
|
93
|
+
if (event.type !== "response.output_item.done" ||
|
|
94
|
+
event.item.type !== "function_call_output") {
|
|
95
|
+
return event;
|
|
96
|
+
}
|
|
97
|
+
const data = event.item.data;
|
|
98
|
+
const { __rynxGeneratedImage: _omitted, ...publicData } = data;
|
|
99
|
+
return {
|
|
100
|
+
...event,
|
|
101
|
+
item: {
|
|
102
|
+
...event.item,
|
|
103
|
+
data: {
|
|
104
|
+
...publicData,
|
|
105
|
+
output: `${event.item.data.output}\nImage preview unavailable.`.trim(),
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
66
110
|
export class RunnerSession {
|
|
67
111
|
transport;
|
|
68
112
|
executor;
|
|
@@ -78,6 +122,8 @@ export class RunnerSession {
|
|
|
78
122
|
cancelledTerminalOpens = new Set();
|
|
79
123
|
/** Session ids with a live codex forwarder started here (stopped on shutdown). */
|
|
80
124
|
liveIds = new Set();
|
|
125
|
+
mirrorQueue = Promise.resolve();
|
|
126
|
+
mirrorImageAcks = new Map();
|
|
81
127
|
/** Provider name retained for asynchronous Terminal-exit diagnostics. */
|
|
82
128
|
liveRuntimes = new Map();
|
|
83
129
|
shuttingDown = false;
|
|
@@ -90,10 +136,36 @@ export class RunnerSession {
|
|
|
90
136
|
transport.close();
|
|
91
137
|
});
|
|
92
138
|
this.transport.onMessage((msg) => this.handle(msg));
|
|
139
|
+
this.transport.onClose((error) => {
|
|
140
|
+
this.rejectMirrorImageAcks(error ?? new Error("runner transport closed"));
|
|
141
|
+
});
|
|
93
142
|
this.transport.send({ t: "ready" });
|
|
94
143
|
}
|
|
95
144
|
handle(msg) {
|
|
96
145
|
switch (msg.t) {
|
|
146
|
+
case "mirror.image.ack": {
|
|
147
|
+
const pending = this.mirrorImageAcks.get(msg.transferId);
|
|
148
|
+
if (!pending)
|
|
149
|
+
return;
|
|
150
|
+
if (pending.seq !== msg.seq) {
|
|
151
|
+
pending.reject(new Error("generated image acknowledgement sequence changed"));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (pending.timer)
|
|
155
|
+
clearTimeout(pending.timer);
|
|
156
|
+
this.mirrorImageAcks.delete(msg.transferId);
|
|
157
|
+
pending.resolve();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
case "mirror.image.hold": {
|
|
161
|
+
const pending = this.mirrorImageAcks.get(msg.transferId);
|
|
162
|
+
if (!pending || pending.seq !== msg.seq)
|
|
163
|
+
return;
|
|
164
|
+
if (pending.timer)
|
|
165
|
+
clearTimeout(pending.timer);
|
|
166
|
+
pending.timer = undefined;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
97
169
|
case "cap":
|
|
98
170
|
void this.runCap(msg.capId, msg.name, msg.args);
|
|
99
171
|
return;
|
|
@@ -149,6 +221,9 @@ export class RunnerSession {
|
|
|
149
221
|
case "inject":
|
|
150
222
|
void this.inject(msg);
|
|
151
223
|
return;
|
|
224
|
+
case "collaboration.update":
|
|
225
|
+
void this.updateCollaborationMode(msg);
|
|
226
|
+
return;
|
|
152
227
|
case "live.interrupt":
|
|
153
228
|
void this.interruptLive(msg);
|
|
154
229
|
return;
|
|
@@ -185,21 +260,92 @@ export class RunnerSession {
|
|
|
185
260
|
* the new session stays injectable. */
|
|
186
261
|
mirrorChannel(localThreadId) {
|
|
187
262
|
const target = { id: localThreadId };
|
|
188
|
-
const emit = (event) =>
|
|
263
|
+
const emit = (event) => {
|
|
264
|
+
const sessionId = target.id;
|
|
265
|
+
this.enqueueMirror(() => this.sendMirroredEvent(sessionId, event));
|
|
266
|
+
};
|
|
189
267
|
const retarget = (newId, meta) => {
|
|
190
268
|
target.id = newId;
|
|
191
|
-
this.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
269
|
+
this.enqueueMirror(() => {
|
|
270
|
+
this.transport.send({
|
|
271
|
+
t: "rotate",
|
|
272
|
+
from: localThreadId,
|
|
273
|
+
to: newId,
|
|
274
|
+
kind: meta.kind,
|
|
275
|
+
workspace: meta.workspace,
|
|
276
|
+
execution: meta.execution,
|
|
277
|
+
...(meta.parentSessionId ? { parentSessionId: meta.parentSessionId } : {}),
|
|
278
|
+
});
|
|
199
279
|
});
|
|
200
280
|
};
|
|
201
281
|
return { emit, retarget };
|
|
202
282
|
}
|
|
283
|
+
enqueueMirror(operation) {
|
|
284
|
+
this.mirrorQueue = this.mirrorQueue.then(operation).catch((error) => {
|
|
285
|
+
if (!this.shuttingDown) {
|
|
286
|
+
this.rejectMirrorImageAcks(error instanceof Error ? error : new Error(String(error)));
|
|
287
|
+
this.shutdown();
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
async sendMirroredEvent(sessionId, event) {
|
|
292
|
+
const detached = detachGeneratedImageResult(event);
|
|
293
|
+
if (!detached) {
|
|
294
|
+
this.transport.send({ t: "mirror", sessionId, event });
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (detached.result.length > RUNNER_IMAGE_MAX_RESULT_CHARS) {
|
|
298
|
+
this.transport.send({
|
|
299
|
+
t: "mirror",
|
|
300
|
+
sessionId,
|
|
301
|
+
event: generatedImagePreviewUnavailable(detached.event),
|
|
302
|
+
});
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const transferId = `img_${randomUUID()}`;
|
|
306
|
+
let seq = 0;
|
|
307
|
+
await this.sendMirrorImageFrame(transferId, seq, {
|
|
308
|
+
t: "mirror.image.begin",
|
|
309
|
+
transferId,
|
|
310
|
+
sessionId,
|
|
311
|
+
event: detached.event,
|
|
312
|
+
totalChars: detached.result.length,
|
|
313
|
+
});
|
|
314
|
+
for (let offset = 0; offset < detached.result.length; offset += RUNNER_IMAGE_CHUNK_CHARS) {
|
|
315
|
+
seq += 1;
|
|
316
|
+
await this.sendMirrorImageFrame(transferId, seq, {
|
|
317
|
+
t: "mirror.image.chunk",
|
|
318
|
+
transferId,
|
|
319
|
+
seq,
|
|
320
|
+
data: detached.result.slice(offset, offset + RUNNER_IMAGE_CHUNK_CHARS),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
seq += 1;
|
|
324
|
+
await this.sendMirrorImageFrame(transferId, seq, {
|
|
325
|
+
t: "mirror.image.commit",
|
|
326
|
+
transferId,
|
|
327
|
+
seq,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
sendMirrorImageFrame(transferId, seq, frame) {
|
|
331
|
+
return new Promise((resolve, reject) => {
|
|
332
|
+
const timer = setTimeout(() => {
|
|
333
|
+
this.mirrorImageAcks.delete(transferId);
|
|
334
|
+
reject(new Error("generated image transfer acknowledgement timed out"));
|
|
335
|
+
}, MIRROR_IMAGE_ACK_TIMEOUT_MS);
|
|
336
|
+
timer.unref?.();
|
|
337
|
+
this.mirrorImageAcks.set(transferId, { seq, resolve, reject, timer });
|
|
338
|
+
this.transport.send(frame);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
rejectMirrorImageAcks(error) {
|
|
342
|
+
for (const pending of this.mirrorImageAcks.values()) {
|
|
343
|
+
if (pending.timer)
|
|
344
|
+
clearTimeout(pending.timer);
|
|
345
|
+
pending.reject(error);
|
|
346
|
+
}
|
|
347
|
+
this.mirrorImageAcks.clear();
|
|
348
|
+
}
|
|
203
349
|
/**
|
|
204
350
|
* Eagerly bring up a session's codex-native live view. Fresh sessions connect
|
|
205
351
|
* the discovery listener before launching the detached TUI; known-thread
|
|
@@ -274,7 +420,7 @@ export class RunnerSession {
|
|
|
274
420
|
: nativePhaseError(runtime, "native_terminal_launch_failed", "Terminal failed to launch", error);
|
|
275
421
|
}
|
|
276
422
|
}
|
|
277
|
-
//
|
|
423
|
+
// reference implementation's known-thread path launches `codex --remote resume` first,
|
|
278
424
|
// then creates `_codex_forward_known_thread` as background work. Keep the
|
|
279
425
|
// ordering explicit here: observer connection failure cannot delay or
|
|
280
426
|
// reject cold-resume admission.
|
|
@@ -377,7 +523,7 @@ export class RunnerSession {
|
|
|
377
523
|
const provider = this.liveProvider;
|
|
378
524
|
try {
|
|
379
525
|
const input = msg.input ?? msg.text;
|
|
380
|
-
const result = (await provider.injectMessage?.(msg.localThreadId, input))
|
|
526
|
+
const result = (await provider.injectMessage?.(msg.localThreadId, input, msg.options))
|
|
381
527
|
?? { outcome: "notLive" };
|
|
382
528
|
// App-server injection is independent of the Terminal TUI startup, so it
|
|
383
529
|
// must not cancel prompt handling for the pane that is still starting.
|
|
@@ -413,6 +559,29 @@ export class RunnerSession {
|
|
|
413
559
|
});
|
|
414
560
|
}
|
|
415
561
|
}
|
|
562
|
+
async updateCollaborationMode(msg) {
|
|
563
|
+
try {
|
|
564
|
+
if (!this.liveProvider.updateCollaborationMode) {
|
|
565
|
+
throw new AgentRuntimeError("Native collaboration mode is unavailable", 409, "collaboration_mode_unavailable");
|
|
566
|
+
}
|
|
567
|
+
await this.liveProvider.updateCollaborationMode(msg.localThreadId, msg.mode);
|
|
568
|
+
this.transport.send({
|
|
569
|
+
t: "collaboration.updated",
|
|
570
|
+
reqId: msg.reqId,
|
|
571
|
+
localThreadId: msg.localThreadId,
|
|
572
|
+
ok: true,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
this.transport.send({
|
|
577
|
+
t: "collaboration.updated",
|
|
578
|
+
reqId: msg.reqId,
|
|
579
|
+
localThreadId: msg.localThreadId,
|
|
580
|
+
ok: false,
|
|
581
|
+
error: toWireError(error),
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
416
585
|
async interruptLive(msg) {
|
|
417
586
|
const provider = this.liveProvider;
|
|
418
587
|
let ok = false;
|
|
@@ -472,7 +641,7 @@ export class RunnerSession {
|
|
|
472
641
|
? terminal.isAliveAsync().then((alive) => alive ? "alive" : "dead")
|
|
473
642
|
: Promise.resolve(terminal.isAlive() ? "alive" : "dead");
|
|
474
643
|
void probe.then((liveness) => {
|
|
475
|
-
// A failed pane probe is inconclusive. Only
|
|
644
|
+
// A failed pane probe is inconclusive. Only reference implementation's lifecycle
|
|
476
645
|
// evidence — capture target gone or explicit pane_dead — may fail it.
|
|
477
646
|
if (liveness !== "dead" ||
|
|
478
647
|
this.shuttingDown ||
|
|
@@ -600,6 +769,7 @@ export class RunnerSession {
|
|
|
600
769
|
if (this.shuttingDown)
|
|
601
770
|
return;
|
|
602
771
|
this.shuttingDown = true;
|
|
772
|
+
this.rejectMirrorImageAcks(new Error("runner is shutting down"));
|
|
603
773
|
this.stopLive();
|
|
604
774
|
this.terminals.closeAll();
|
|
605
775
|
this.liveProvider.finalizeStoppedLiveSessions?.();
|
package/dist/runner/manager.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
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 LiveSessionFailure, type InjectResult, 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 RuntimeTurnOptions, type RuntimeUserInput, type SessionCollaborationMode, 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 TerminalOpenErrorCode, type TerminalRole } from "./protocol.js";
|
|
@@ -73,7 +73,7 @@ export interface RunnerManagerOptions {
|
|
|
73
73
|
* created for the runner child, with direct-child fallback. */
|
|
74
74
|
signalChild?: (child: ChildProcess, signal: NodeJS.Signals, processGroup: boolean) => void;
|
|
75
75
|
/** Injected for tests. Best-effort close of the deterministic private tmux
|
|
76
|
-
* server owned by a Session runner;
|
|
76
|
+
* server owned by a Session runner; reference implementation does not verify/retry close. */
|
|
77
77
|
terminateTerminalServer?: (terminalName: string) => boolean | Promise<boolean>;
|
|
78
78
|
/** Injected tmux `#{window_activity}` reader. Returns epoch seconds. */
|
|
79
79
|
terminalWindowActivityAt?: (terminalName: string) => number | null | Promise<number | null>;
|
|
@@ -179,6 +179,9 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
179
179
|
private stopPromise;
|
|
180
180
|
/** Sink for mirrored {@link SessionEvent}s from every session's forwarder. */
|
|
181
181
|
private mirrorListener;
|
|
182
|
+
/** Synchronous persistence hook for native collaboration-mode reflection.
|
|
183
|
+
* It runs before the event reaches the Session bus. */
|
|
184
|
+
private collaborationModeListener;
|
|
182
185
|
/** Sink for session rotations (claude `/clear`·`/fork`) — server records meta. */
|
|
183
186
|
private rotateListener;
|
|
184
187
|
/** Session keys with a live codex forwarder — never reaped while present. */
|
|
@@ -226,7 +229,8 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
226
229
|
* session's persistent codex forwarder (web- AND TUI-initiated turns). The
|
|
227
230
|
* server wires this to `persistSessionEvent` (bus + canonical log).
|
|
228
231
|
*/
|
|
229
|
-
onMirror(listener: (sessionId: string, event: SessionEvent) => void): void;
|
|
232
|
+
onMirror(listener: (sessionId: string, event: SessionEvent) => void | Promise<void>): void;
|
|
233
|
+
onCollaborationMode(listener: (sessionId: string, mode: SessionCollaborationMode) => void): void;
|
|
230
234
|
/** Register the sink for session rotations (claude `/clear`·`/fork`): the server
|
|
231
235
|
* records the new session's meta (carry-over agent/model/title). */
|
|
232
236
|
onRotate(listener: (rotation: RotateInfo) => void | Promise<void>): void;
|
|
@@ -273,7 +277,7 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
273
277
|
* all output. Resolves true when the app-server accepted the turn, false when
|
|
274
278
|
* the session has no live forwarder (caller falls back to the run path).
|
|
275
279
|
*/
|
|
276
|
-
injectMessage(localThreadId: string, input: RuntimeUserInput | string): Promise<InjectResult>;
|
|
280
|
+
injectMessage(localThreadId: string, input: RuntimeUserInput | string, options?: RuntimeTurnOptions): Promise<InjectResult>;
|
|
277
281
|
private injectMessageAdmitted;
|
|
278
282
|
/**
|
|
279
283
|
* Interrupt a session's active live turn — the web Stop button (codex
|
|
@@ -283,6 +287,8 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
283
287
|
interruptLiveSession(localThreadId: string): Promise<boolean>;
|
|
284
288
|
/** Resolve a native question/approval without ever spawning a new runner. */
|
|
285
289
|
resolveInteraction(localThreadId: string, interactionId: string, resolution: SessionInteractionResolution): Promise<ResolveInteractionResult>;
|
|
290
|
+
/** Update a loaded Codex-lineage native thread without spawning a runner. */
|
|
291
|
+
updateCollaborationMode(localThreadId: string, mode: SessionCollaborationMode): Promise<void>;
|
|
286
292
|
listModels(runtime?: AgentRuntimeId): Promise<ModelListResponse | null>;
|
|
287
293
|
getGoal(localThreadId: string): Promise<CapabilityResult<ThreadGoal | null>>;
|
|
288
294
|
setGoal(localThreadId: string, objective: string): Promise<CapabilityResult>;
|
|
@@ -325,6 +331,8 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
325
331
|
private bufferForkTargetMessage;
|
|
326
332
|
private reservedForkTarget;
|
|
327
333
|
private deliverForkBufferedMessage;
|
|
334
|
+
private deliverMirrorMessage;
|
|
335
|
+
private failMirrorImageTransfer;
|
|
328
336
|
private deliverRotateMessage;
|
|
329
337
|
private spawnHandle;
|
|
330
338
|
private onChildMessage;
|