@rynx-ai/runtime 0.1.11-beta.23 → 0.1.11-beta.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude/executor.d.ts +19 -5
- package/dist/claude/executor.js +56 -12
- package/dist/claude/native-hook-main.js +62 -0
- package/dist/claude/native-integration.js +1 -1
- package/dist/codex-app-server/forwarder.d.ts +16 -1
- package/dist/codex-app-server/forwarder.js +61 -17
- package/dist/codex-app-server/mapping.js +16 -1
- package/dist/codex-app-server/protocol.d.ts +25 -1
- package/dist/host.d.ts +15 -7
- package/dist/host.js +535 -44
- package/dist/runner/child.d.ts +7 -4
- package/dist/runner/child.js +29 -3
- package/dist/runner/manager.d.ts +10 -3
- package/dist/runner/manager.js +66 -7
- package/dist/runner/protocol.d.ts +16 -1
- 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
package/dist/runner/child.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
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 LiveSessionFailure, type ResolvedExecutionSnapshot, type RuntimeUserInput, type SessionInteractionResolution, type SessionWorkspaceSnapshot } from "@rynx-ai/core";
|
|
10
|
+
import { type AgentCapabilities, type LiveSessionFailure, type ResolvedExecutionSnapshot, type RuntimeTurnOptions, type RuntimeUserInput, type SessionCollaborationMode, 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";
|
|
@@ -54,15 +54,17 @@ interface LiveCodexProvider {
|
|
|
54
54
|
* observer/forwarder, and evict the owned app-server while preserving the
|
|
55
55
|
* durable native-session binding for a later cold resume. */
|
|
56
56
|
teardownLiveCodexSession?(localThreadId: string, error?: Error): boolean;
|
|
57
|
-
injectMessage?(localThreadId: string, input: RuntimeUserInput | string): Promise<InjectResult>;
|
|
57
|
+
injectMessage?(localThreadId: string, input: RuntimeUserInput | string, options?: RuntimeTurnOptions): Promise<InjectResult>;
|
|
58
|
+
updateCollaborationMode?(localThreadId: string, mode: SessionCollaborationMode): Promise<void>;
|
|
58
59
|
interruptLive?(localThreadId: string): Promise<boolean>;
|
|
59
60
|
stopLiveCodexSession?(localThreadId: string, opts?: {
|
|
60
61
|
deferClaudeInteractionCleanup?: boolean;
|
|
61
62
|
}): void;
|
|
62
63
|
finalizeStoppedLiveSessions?(): void;
|
|
63
64
|
resolveInteraction?(localThreadId: string, interactionId: string, resolution: SessionInteractionResolution): Promise<ResolveInteractionResult>;
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
65
|
+
/** Hand the session's tmux pane injector to the host (which doesn't own
|
|
66
|
+
* tmux). Claude uses it for messages; Codex uses it only for TUI-local
|
|
67
|
+
* controls that the app-server cannot resolve, such as the final Plan picker. */
|
|
66
68
|
attachTerminalInjector?(localThreadId: string, injector: TerminalInjector): void;
|
|
67
69
|
/** Internal Provider operation used only by MachineSessionService through
|
|
68
70
|
* RunnerManager. It is deliberately absent from public AgentCapabilities. */
|
|
@@ -120,6 +122,7 @@ export declare class RunnerSession {
|
|
|
120
122
|
* already-rejected readiness promise. */
|
|
121
123
|
private failCodexThreadStartup;
|
|
122
124
|
private inject;
|
|
125
|
+
private updateCollaborationMode;
|
|
123
126
|
private interruptLive;
|
|
124
127
|
/** Launch (idempotently) the session's codex TUI pane from the executor's
|
|
125
128
|
* `codexTerminalSpec`. Shares the `${id}-main` terminal id with `term.open`,
|
package/dist/runner/child.js
CHANGED
|
@@ -149,6 +149,9 @@ export class RunnerSession {
|
|
|
149
149
|
case "inject":
|
|
150
150
|
void this.inject(msg);
|
|
151
151
|
return;
|
|
152
|
+
case "collaboration.update":
|
|
153
|
+
void this.updateCollaborationMode(msg);
|
|
154
|
+
return;
|
|
152
155
|
case "live.interrupt":
|
|
153
156
|
void this.interruptLive(msg);
|
|
154
157
|
return;
|
|
@@ -274,7 +277,7 @@ export class RunnerSession {
|
|
|
274
277
|
: nativePhaseError(runtime, "native_terminal_launch_failed", "Terminal failed to launch", error);
|
|
275
278
|
}
|
|
276
279
|
}
|
|
277
|
-
//
|
|
280
|
+
// reference implementation's known-thread path launches `codex --remote resume` first,
|
|
278
281
|
// then creates `_codex_forward_known_thread` as background work. Keep the
|
|
279
282
|
// ordering explicit here: observer connection failure cannot delay or
|
|
280
283
|
// reject cold-resume admission.
|
|
@@ -377,7 +380,7 @@ export class RunnerSession {
|
|
|
377
380
|
const provider = this.liveProvider;
|
|
378
381
|
try {
|
|
379
382
|
const input = msg.input ?? msg.text;
|
|
380
|
-
const result = (await provider.injectMessage?.(msg.localThreadId, input))
|
|
383
|
+
const result = (await provider.injectMessage?.(msg.localThreadId, input, msg.options))
|
|
381
384
|
?? { outcome: "notLive" };
|
|
382
385
|
// App-server injection is independent of the Terminal TUI startup, so it
|
|
383
386
|
// must not cancel prompt handling for the pane that is still starting.
|
|
@@ -413,6 +416,29 @@ export class RunnerSession {
|
|
|
413
416
|
});
|
|
414
417
|
}
|
|
415
418
|
}
|
|
419
|
+
async updateCollaborationMode(msg) {
|
|
420
|
+
try {
|
|
421
|
+
if (!this.liveProvider.updateCollaborationMode) {
|
|
422
|
+
throw new AgentRuntimeError("Native collaboration mode is unavailable", 409, "collaboration_mode_unavailable");
|
|
423
|
+
}
|
|
424
|
+
await this.liveProvider.updateCollaborationMode(msg.localThreadId, msg.mode);
|
|
425
|
+
this.transport.send({
|
|
426
|
+
t: "collaboration.updated",
|
|
427
|
+
reqId: msg.reqId,
|
|
428
|
+
localThreadId: msg.localThreadId,
|
|
429
|
+
ok: true,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
this.transport.send({
|
|
434
|
+
t: "collaboration.updated",
|
|
435
|
+
reqId: msg.reqId,
|
|
436
|
+
localThreadId: msg.localThreadId,
|
|
437
|
+
ok: false,
|
|
438
|
+
error: toWireError(error),
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
416
442
|
async interruptLive(msg) {
|
|
417
443
|
const provider = this.liveProvider;
|
|
418
444
|
let ok = false;
|
|
@@ -472,7 +498,7 @@ export class RunnerSession {
|
|
|
472
498
|
? terminal.isAliveAsync().then((alive) => alive ? "alive" : "dead")
|
|
473
499
|
: Promise.resolve(terminal.isAlive() ? "alive" : "dead");
|
|
474
500
|
void probe.then((liveness) => {
|
|
475
|
-
// A failed pane probe is inconclusive. Only
|
|
501
|
+
// A failed pane probe is inconclusive. Only reference implementation's lifecycle
|
|
476
502
|
// evidence — capture target gone or explicit pane_dead — may fail it.
|
|
477
503
|
if (liveness !== "dead" ||
|
|
478
504
|
this.shuttingDown ||
|
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. */
|
|
@@ -227,6 +230,7 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
227
230
|
* server wires this to `persistSessionEvent` (bus + canonical log).
|
|
228
231
|
*/
|
|
229
232
|
onMirror(listener: (sessionId: string, event: SessionEvent) => 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>;
|
|
@@ -340,6 +346,7 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
340
346
|
private isNativePaneBusy;
|
|
341
347
|
private isManagedNativeHandle;
|
|
342
348
|
private failActiveResponses;
|
|
349
|
+
private interruptActiveResponses;
|
|
343
350
|
/** Track the provider-authoritative response lifecycle. Native-pane busy
|
|
344
351
|
* classification consumes this level directly; output volume is separately
|
|
345
352
|
* grounded in tmux's own activity clock. */
|
package/dist/runner/manager.js
CHANGED
|
@@ -182,6 +182,9 @@ export class RunnerManager {
|
|
|
182
182
|
stopPromise;
|
|
183
183
|
/** Sink for mirrored {@link SessionEvent}s from every session's forwarder. */
|
|
184
184
|
mirrorListener = null;
|
|
185
|
+
/** Synchronous persistence hook for native collaboration-mode reflection.
|
|
186
|
+
* It runs before the event reaches the Session bus. */
|
|
187
|
+
collaborationModeListener = null;
|
|
185
188
|
/** Sink for session rotations (claude `/clear`·`/fork`) — server records meta. */
|
|
186
189
|
rotateListener = null;
|
|
187
190
|
/** Session keys with a live codex forwarder — never reaped while present. */
|
|
@@ -349,6 +352,9 @@ export class RunnerManager {
|
|
|
349
352
|
onMirror(listener) {
|
|
350
353
|
this.mirrorListener = listener;
|
|
351
354
|
}
|
|
355
|
+
onCollaborationMode(listener) {
|
|
356
|
+
this.collaborationModeListener = listener;
|
|
357
|
+
}
|
|
352
358
|
/** Register the sink for session rotations (claude `/clear`·`/fork`): the server
|
|
353
359
|
* records the new session's meta (carry-over agent/model/title). */
|
|
354
360
|
onRotate(listener) {
|
|
@@ -623,19 +629,19 @@ export class RunnerManager {
|
|
|
623
629
|
* all output. Resolves true when the app-server accepted the turn, false when
|
|
624
630
|
* the session has no live forwarder (caller falls back to the run path).
|
|
625
631
|
*/
|
|
626
|
-
injectMessage(localThreadId, input) {
|
|
632
|
+
injectMessage(localThreadId, input, options) {
|
|
627
633
|
const admission = this.reserveAdmission();
|
|
628
|
-
return this.injectMessageAdmitted(localThreadId, input)
|
|
634
|
+
return this.injectMessageAdmitted(localThreadId, input, options)
|
|
629
635
|
.finally(() => admission.release());
|
|
630
636
|
}
|
|
631
|
-
injectMessageAdmitted(localThreadId, input) {
|
|
637
|
+
injectMessageAdmitted(localThreadId, input, options) {
|
|
632
638
|
const sourceReservation = this.sourceForkReservations.get(localThreadId);
|
|
633
639
|
if (sourceReservation) {
|
|
634
|
-
return sourceReservation.then(() => this.injectMessageAdmitted(localThreadId, input));
|
|
640
|
+
return sourceReservation.then(() => this.injectMessageAdmitted(localThreadId, input, options));
|
|
635
641
|
}
|
|
636
642
|
const reservation = this.forkReservations.get(localThreadId);
|
|
637
643
|
if (reservation) {
|
|
638
|
-
return reservation.then(() => this.injectMessageAdmitted(localThreadId, input));
|
|
644
|
+
return reservation.then(() => this.injectMessageAdmitted(localThreadId, input, options));
|
|
639
645
|
}
|
|
640
646
|
const handle = this.getOrSpawn(localThreadId);
|
|
641
647
|
handle.lastUsedAt = this.now();
|
|
@@ -670,8 +676,8 @@ export class RunnerManager {
|
|
|
670
676
|
resolve(finish());
|
|
671
677
|
});
|
|
672
678
|
handle.transport.send(typeof input === "string"
|
|
673
|
-
? { t: "inject", reqId, localThreadId, text: input }
|
|
674
|
-
: { t: "inject", reqId, localThreadId, input });
|
|
679
|
+
? { t: "inject", reqId, localThreadId, text: input, ...(options ? { options } : {}) }
|
|
680
|
+
: { t: "inject", reqId, localThreadId, input, ...(options ? { options } : {}) });
|
|
675
681
|
});
|
|
676
682
|
}
|
|
677
683
|
/**
|
|
@@ -723,6 +729,34 @@ export class RunnerManager {
|
|
|
723
729
|
});
|
|
724
730
|
});
|
|
725
731
|
}
|
|
732
|
+
/** Update a loaded Codex-lineage native thread without spawning a runner. */
|
|
733
|
+
updateCollaborationMode(localThreadId, mode) {
|
|
734
|
+
const handle = this.handles.get(localThreadId);
|
|
735
|
+
if (!handle || handle.dead || !this.liveSessionKeys.has(localThreadId)) {
|
|
736
|
+
return Promise.reject(new AgentRuntimeError("Native Session is not live", 409, "native_session_not_live"));
|
|
737
|
+
}
|
|
738
|
+
handle.lastUsedAt = this.now();
|
|
739
|
+
const reqId = randomUUID();
|
|
740
|
+
return new Promise((resolve, reject) => {
|
|
741
|
+
handle.live.set(reqId, (reply) => {
|
|
742
|
+
if (reply.collaborationUpdated) {
|
|
743
|
+
resolve();
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
reject(fromWireError(reply.error ?? {
|
|
747
|
+
message: "Native collaboration mode update failed",
|
|
748
|
+
code: "collaboration_mode_update_failed",
|
|
749
|
+
statusCode: 503,
|
|
750
|
+
}));
|
|
751
|
+
});
|
|
752
|
+
handle.transport.send({
|
|
753
|
+
t: "collaboration.update",
|
|
754
|
+
reqId,
|
|
755
|
+
localThreadId,
|
|
756
|
+
mode,
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
}
|
|
726
760
|
// ── AgentCapabilities ──────────────────────────────────────────────────────
|
|
727
761
|
async listModels(runtime) {
|
|
728
762
|
return listRuntimeModels(this.config, runtime ?? this.defaultRuntime);
|
|
@@ -1248,6 +1282,9 @@ export class RunnerManager {
|
|
|
1248
1282
|
return;
|
|
1249
1283
|
if (this.bufferForkTargetMessage(handle, msg))
|
|
1250
1284
|
return;
|
|
1285
|
+
if (msg.event.type === "session.collaboration_mode") {
|
|
1286
|
+
this.collaborationModeListener?.(msg.sessionId, msg.event.mode);
|
|
1287
|
+
}
|
|
1251
1288
|
this.observeHandleRuntimeEvent(handle, msg.event);
|
|
1252
1289
|
this.mirrorListener?.(msg.sessionId, msg.event);
|
|
1253
1290
|
if (msg.event.type === "response.created") {
|
|
@@ -1275,6 +1312,15 @@ export class RunnerManager {
|
|
|
1275
1312
|
: { ok: msg.ok, error: msg.error });
|
|
1276
1313
|
return;
|
|
1277
1314
|
}
|
|
1315
|
+
case "collaboration.updated": {
|
|
1316
|
+
const resolve = handle.live.get(msg.reqId);
|
|
1317
|
+
handle.live.delete(msg.reqId);
|
|
1318
|
+
resolve?.({
|
|
1319
|
+
collaborationUpdated: msg.ok,
|
|
1320
|
+
...(msg.error ? { error: msg.error } : {}),
|
|
1321
|
+
});
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1278
1324
|
case "interaction.resolved": {
|
|
1279
1325
|
const resolve = handle.live.get(msg.reqId);
|
|
1280
1326
|
handle.live.delete(msg.reqId);
|
|
@@ -1335,6 +1381,7 @@ export class RunnerManager {
|
|
|
1335
1381
|
catch {
|
|
1336
1382
|
// The process signal below remains the authoritative shutdown path.
|
|
1337
1383
|
}
|
|
1384
|
+
this.interruptActiveResponses(handle);
|
|
1338
1385
|
this.failHandle(handle, reason, { failActiveResponses: false });
|
|
1339
1386
|
}
|
|
1340
1387
|
this.signalChild(handle.child, "SIGTERM", handle.processGroup);
|
|
@@ -1477,6 +1524,18 @@ export class RunnerManager {
|
|
|
1477
1524
|
});
|
|
1478
1525
|
}
|
|
1479
1526
|
}
|
|
1527
|
+
interruptActiveResponses(handle) {
|
|
1528
|
+
const responseIds = [...handle.activeResponseIds];
|
|
1529
|
+
handle.activeResponseIds.clear();
|
|
1530
|
+
const sessionId = this.currentTerminalSessionId(handle, handle.key);
|
|
1531
|
+
for (const responseId of responseIds) {
|
|
1532
|
+
this.mirrorListener?.(sessionId, {
|
|
1533
|
+
type: "session.interrupted",
|
|
1534
|
+
sessionId,
|
|
1535
|
+
responseId,
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1480
1539
|
/** Track the provider-authoritative response lifecycle. Native-pane busy
|
|
1481
1540
|
* classification consumes this level directly; output volume is separately
|
|
1482
1541
|
* grounded in tmux's own activity clock. */
|
|
@@ -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 InjectResult, type ResolvedExecutionSnapshot, type RuntimeUserInput, type SessionInteractionResolution, type SessionEvent, type SessionWorkspaceSnapshot } from "@rynx-ai/core";
|
|
9
|
+
import { AgentRuntimeError, type InjectResult, type ResolvedExecutionSnapshot, type RuntimeTurnOptions, type RuntimeUserInput, type SessionCollaborationMode, type SessionInteractionResolution, type SessionEvent, type SessionWorkspaceSnapshot } from "@rynx-ai/core";
|
|
10
10
|
export type { InjectOutcome, InjectResult } from "@rynx-ai/core";
|
|
11
11
|
import type { ResolveInteractionResult } from "../interactions.js";
|
|
12
12
|
/** A runtime error flattened for the wire; rebuilt parent-side as `AgentRuntimeError`. */
|
|
@@ -88,6 +88,7 @@ export type ToChild = {
|
|
|
88
88
|
t: "inject";
|
|
89
89
|
reqId: string;
|
|
90
90
|
localThreadId: string;
|
|
91
|
+
options?: RuntimeTurnOptions;
|
|
91
92
|
} & ({
|
|
92
93
|
input: RuntimeUserInput;
|
|
93
94
|
text?: never;
|
|
@@ -95,6 +96,14 @@ export type ToChild = {
|
|
|
95
96
|
text: string;
|
|
96
97
|
input?: never;
|
|
97
98
|
}))
|
|
99
|
+
/** Apply a Codex-lineage collaboration mode to an already-loaded native
|
|
100
|
+
* thread. This control path never starts a runner or waits for a new Turn. */
|
|
101
|
+
| {
|
|
102
|
+
t: "collaboration.update";
|
|
103
|
+
reqId: string;
|
|
104
|
+
localThreadId: string;
|
|
105
|
+
mode: SessionCollaborationMode;
|
|
106
|
+
}
|
|
98
107
|
/** Interrupt the session's active turn — the web Stop button. codex: app-server
|
|
99
108
|
* `turn/interrupt`; claude: an Escape keystroke to the pane. The child replies
|
|
100
109
|
* `interrupted` (echoing `reqId`). */
|
|
@@ -184,6 +193,12 @@ export type FromChild = {
|
|
|
184
193
|
localThreadId: string;
|
|
185
194
|
ok: boolean;
|
|
186
195
|
error?: WireError;
|
|
196
|
+
} | {
|
|
197
|
+
t: "collaboration.updated";
|
|
198
|
+
reqId: string;
|
|
199
|
+
localThreadId: string;
|
|
200
|
+
ok: boolean;
|
|
201
|
+
error?: WireError;
|
|
187
202
|
} | {
|
|
188
203
|
t: "interaction.resolved";
|
|
189
204
|
reqId: string;
|
|
@@ -59,7 +59,7 @@ export class TerminalRegistry {
|
|
|
59
59
|
const terminal = this.terminals.get(id);
|
|
60
60
|
if (!terminal)
|
|
61
61
|
return;
|
|
62
|
-
//
|
|
62
|
+
// reference implementation removes the resource from its registry before best-effort close.
|
|
63
63
|
this.terminals.delete(id);
|
|
64
64
|
terminal.kill();
|
|
65
65
|
}
|
package/dist/terminal/tmux.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface TerminalAttachment {
|
|
|
14
14
|
/** Result of probing the inner tmux pane. `unknown` is deliberately distinct
|
|
15
15
|
* from `dead`: a timed-out/unexecutable control command is inconclusive;
|
|
16
16
|
* `dead` requires an explicit dead pane. The lifecycle watcher separately
|
|
17
|
-
* mirrors
|
|
17
|
+
* mirrors reference implementation's capture failure path for a vanished server/session. */
|
|
18
18
|
export type TerminalLiveness = "alive" | "dead" | "unknown";
|
|
19
19
|
/** Minimal node-pty surface (kept local so this module has no type dep on it). */
|
|
20
20
|
interface PtyProcess {
|
|
@@ -67,7 +67,7 @@ export declare function tmuxWindowActivityAt(name: string, tmuxBin?: string): Pr
|
|
|
67
67
|
* This remains authoritative if a parent-side attachment bookkeeping edge was
|
|
68
68
|
* missed during a transport failure. */
|
|
69
69
|
export declare function tmuxHasAttachedClient(name: string, tmuxBin?: string): Promise<boolean>;
|
|
70
|
-
/** Best-effort close of one private tmux server. Mirrors
|
|
70
|
+
/** Best-effort close of one private tmux server. Mirrors reference implementation's bounded
|
|
71
71
|
* `TerminalInstance.close`: attempt `kill-server`, then retire the private
|
|
72
72
|
* socket regardless of command outcome. The registry has already forgotten
|
|
73
73
|
* the resource, so cleanup failure is diagnostic rather than a second
|
|
@@ -93,7 +93,7 @@ export declare class TmuxTerminal {
|
|
|
93
93
|
* out once per attached client. */
|
|
94
94
|
private paneLivenessFlight?;
|
|
95
95
|
/** Shared by lifecycle callers so capture + pane-dead remains one ordered
|
|
96
|
-
*
|
|
96
|
+
* reference implementation-style observation per Terminal. */
|
|
97
97
|
private lifecycleLivenessFlight?;
|
|
98
98
|
constructor(opts: TmuxTerminalOptions);
|
|
99
99
|
/** tmux argv prefix targeting this terminal's private server. */
|
|
@@ -138,10 +138,10 @@ export declare class TmuxTerminal {
|
|
|
138
138
|
/** Async pane-liveness probe — MUST NOT block the event loop. The attach
|
|
139
139
|
* pane-death watcher polls this on an interval; a synchronous `execFileSync`
|
|
140
140
|
* there stalls the runner child's event loop (freezing the PTY stream → the
|
|
141
|
-
* terminal appears "stuck"). Exactly like
|
|
141
|
+
* terminal appears "stuck"). Exactly like reference implementation's definitive pane probe,
|
|
142
142
|
* every command error is `unknown`; only `#{pane_dead}=1` is `dead`. */
|
|
143
143
|
livenessAsync(): Promise<TerminalLiveness>;
|
|
144
|
-
/**
|
|
144
|
+
/** reference implementation's always-on terminal lifecycle watcher first captures the pane:
|
|
145
145
|
* a control command that ran and reports the target missing is terminal exit;
|
|
146
146
|
* a probe that cannot spawn is inconclusive. If capture succeeds, the normal
|
|
147
147
|
* definitive `pane_dead` probe distinguishes live from exited. */
|
package/dist/terminal/tmux.js
CHANGED
|
@@ -79,7 +79,7 @@ export function tmuxHasAttachedClient(name, tmuxBin = resolveTmuxBin()) {
|
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
-
/** Best-effort close of one private tmux server. Mirrors
|
|
82
|
+
/** Best-effort close of one private tmux server. Mirrors reference implementation's bounded
|
|
83
83
|
* `TerminalInstance.close`: attempt `kill-server`, then retire the private
|
|
84
84
|
* socket regardless of command outcome. The registry has already forgotten
|
|
85
85
|
* the resource, so cleanup failure is diagnostic rather than a second
|
|
@@ -96,7 +96,7 @@ export function terminateTmuxServer(name, tmuxBin = resolveTmuxBin()) {
|
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
catch {
|
|
99
|
-
//
|
|
99
|
+
// reference implementation treats close as best-effort and still retires the resource.
|
|
100
100
|
}
|
|
101
101
|
try {
|
|
102
102
|
unlinkSync(socketPath);
|
|
@@ -146,7 +146,7 @@ export class TmuxTerminal {
|
|
|
146
146
|
* out once per attached client. */
|
|
147
147
|
paneLivenessFlight;
|
|
148
148
|
/** Shared by lifecycle callers so capture + pane-dead remains one ordered
|
|
149
|
-
*
|
|
149
|
+
* reference implementation-style observation per Terminal. */
|
|
150
150
|
lifecycleLivenessFlight;
|
|
151
151
|
constructor(opts) {
|
|
152
152
|
this.name = opts.name;
|
|
@@ -285,7 +285,7 @@ export class TmuxTerminal {
|
|
|
285
285
|
/** Async pane-liveness probe — MUST NOT block the event loop. The attach
|
|
286
286
|
* pane-death watcher polls this on an interval; a synchronous `execFileSync`
|
|
287
287
|
* there stalls the runner child's event loop (freezing the PTY stream → the
|
|
288
|
-
* terminal appears "stuck"). Exactly like
|
|
288
|
+
* terminal appears "stuck"). Exactly like reference implementation's definitive pane probe,
|
|
289
289
|
* every command error is `unknown`; only `#{pane_dead}=1` is `dead`. */
|
|
290
290
|
livenessAsync() {
|
|
291
291
|
if (!this.started)
|
|
@@ -320,7 +320,7 @@ export class TmuxTerminal {
|
|
|
320
320
|
});
|
|
321
321
|
return flight;
|
|
322
322
|
}
|
|
323
|
-
/**
|
|
323
|
+
/** reference implementation's always-on terminal lifecycle watcher first captures the pane:
|
|
324
324
|
* a control command that ran and reports the target missing is terminal exit;
|
|
325
325
|
* a probe that cannot spawn is inconclusive. If capture succeeds, the normal
|
|
326
326
|
* definitive `pane_dead` probe distinguishes live from exited. */
|
|
@@ -340,7 +340,7 @@ export class TmuxTerminal {
|
|
|
340
340
|
}
|
|
341
341
|
this.lastPaneSnapshot = stdout.toString();
|
|
342
342
|
void this.livenessAsync().then((liveness) => {
|
|
343
|
-
// A successful capture proves the target exists.
|
|
343
|
+
// A successful capture proves the target exists. reference implementation treats a
|
|
344
344
|
// subsequent pane-dead probe failure as non-dead for this tick.
|
|
345
345
|
resolve(liveness === "dead" ? "dead" : "alive");
|
|
346
346
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rynx-ai/runtime",
|
|
3
|
-
"version": "0.1.11-beta.
|
|
3
|
+
"version": "0.1.11-beta.25",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/rynx-ai/rynx.git",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"node-pty": "1.2.0-beta.15",
|
|
28
28
|
"smol-toml": "1.7.1",
|
|
29
29
|
"ws": "^8.21.0",
|
|
30
|
-
"@rynx-ai/core": "0.1.11-beta.
|
|
30
|
+
"@rynx-ai/core": "0.1.11-beta.25"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/ws": "^8.18.1"
|