@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
package/dist/runner/manager.js
CHANGED
|
@@ -23,7 +23,7 @@ import { AgentRuntimeError, } from "@rynx-ai/core";
|
|
|
23
23
|
import { listRuntimeModels } from "../models-catalog.js";
|
|
24
24
|
import { probeRuntimeStatus } from "../runtime-status.js";
|
|
25
25
|
import { terminateTmuxServer, tmuxHasAttachedClient, tmuxWindowActivityAt, } from "../terminal/tmux.js";
|
|
26
|
-
import { fromWireError, } from "./protocol.js";
|
|
26
|
+
import { RUNNER_IMAGE_CHUNK_CHARS, RUNNER_IMAGE_MAX_RESULT_CHARS, fromWireError, } from "./protocol.js";
|
|
27
27
|
import { isCodexLineageProvider, isManagedNativeProvider } from "./startup-policy.js";
|
|
28
28
|
import { StdioRunnerTransport } from "./transport.js";
|
|
29
29
|
/** Routing key for the shared capability runner (slash-command RPCs). */
|
|
@@ -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);
|
|
@@ -1047,15 +1081,56 @@ export class RunnerManager {
|
|
|
1047
1081
|
if (handle.dead)
|
|
1048
1082
|
return;
|
|
1049
1083
|
if (message.t === "mirror") {
|
|
1050
|
-
this.
|
|
1051
|
-
this.mirrorListener?.(message.sessionId, message.event);
|
|
1052
|
-
if (message.event.type === "response.created") {
|
|
1053
|
-
this.settleTerminalInputHandoff(handle, message.sessionId, "turn");
|
|
1054
|
-
}
|
|
1084
|
+
this.deliverMirrorMessage(handle, message);
|
|
1055
1085
|
return;
|
|
1056
1086
|
}
|
|
1057
1087
|
this.deliverRotateMessage(handle, message);
|
|
1058
1088
|
}
|
|
1089
|
+
deliverMirrorMessage(handle, message) {
|
|
1090
|
+
if (handle.dead)
|
|
1091
|
+
return;
|
|
1092
|
+
if (message.event.type === "session.collaboration_mode") {
|
|
1093
|
+
this.collaborationModeListener?.(message.sessionId, message.event.mode);
|
|
1094
|
+
}
|
|
1095
|
+
this.observeHandleRuntimeEvent(handle, message.event);
|
|
1096
|
+
let delivered;
|
|
1097
|
+
try {
|
|
1098
|
+
delivered = Promise.resolve(this.mirrorListener?.(message.sessionId, message.event));
|
|
1099
|
+
}
|
|
1100
|
+
catch (error) {
|
|
1101
|
+
delivered = Promise.reject(error);
|
|
1102
|
+
}
|
|
1103
|
+
if (message.event.type === "response.created") {
|
|
1104
|
+
// The listener projects response.created into SessionRuntimeIndex
|
|
1105
|
+
// synchronously. Only then may the accepted terminal reservation drain
|
|
1106
|
+
// into a maintenance activity snapshot.
|
|
1107
|
+
this.settleTerminalInputHandoff(handle, message.sessionId, "turn");
|
|
1108
|
+
}
|
|
1109
|
+
if (message.transferId && message.seq !== undefined) {
|
|
1110
|
+
void delivered.then(() => {
|
|
1111
|
+
if (!handle.dead) {
|
|
1112
|
+
handle.transport.send({
|
|
1113
|
+
t: "mirror.image.ack",
|
|
1114
|
+
transferId: message.transferId,
|
|
1115
|
+
seq: message.seq,
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
}, (error) => {
|
|
1119
|
+
this.failMirrorImageTransfer(handle, `generated image publication failed: ${errorMessage(error)}`);
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
else {
|
|
1123
|
+
void delivered.catch((error) => {
|
|
1124
|
+
console.error(`Session mirror publication failed: ${errorMessage(error)}`);
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
failMirrorImageTransfer(handle, reason) {
|
|
1129
|
+
handle.imageTransfers.clear();
|
|
1130
|
+
void this.terminateHandle(handle, reason).catch((error) => {
|
|
1131
|
+
logTerminationFailure(handle, error);
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1059
1134
|
deliverRotateMessage(handle, message) {
|
|
1060
1135
|
if (this.forkReservations.has(message.to) ||
|
|
1061
1136
|
this.sourceForkReservations.has(message.from)) {
|
|
@@ -1190,6 +1265,7 @@ export class RunnerManager {
|
|
|
1190
1265
|
caps: new Map(),
|
|
1191
1266
|
terminals: new Map(),
|
|
1192
1267
|
live: new Map(),
|
|
1268
|
+
imageTransfers: new Map(),
|
|
1193
1269
|
};
|
|
1194
1270
|
this.childHandles.add(handle);
|
|
1195
1271
|
void completion.then(() => this.childHandles.delete(handle));
|
|
@@ -1248,15 +1324,83 @@ export class RunnerManager {
|
|
|
1248
1324
|
return;
|
|
1249
1325
|
if (this.bufferForkTargetMessage(handle, msg))
|
|
1250
1326
|
return;
|
|
1251
|
-
this.
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1327
|
+
this.deliverMirrorMessage(handle, msg);
|
|
1328
|
+
return;
|
|
1329
|
+
case "mirror.image.begin": {
|
|
1330
|
+
if (handle.dead ||
|
|
1331
|
+
handle.imageTransfers.has(msg.transferId) ||
|
|
1332
|
+
!Number.isSafeInteger(msg.totalChars) ||
|
|
1333
|
+
msg.totalChars <= 0 ||
|
|
1334
|
+
msg.totalChars > RUNNER_IMAGE_MAX_RESULT_CHARS ||
|
|
1335
|
+
!isDetachedGeneratedImageEvent(msg.event)) {
|
|
1336
|
+
this.failMirrorImageTransfer(handle, "invalid generated image transfer start");
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
handle.imageTransfers.set(msg.transferId, {
|
|
1340
|
+
sessionId: msg.sessionId,
|
|
1341
|
+
event: msg.event,
|
|
1342
|
+
totalChars: msg.totalChars,
|
|
1343
|
+
receivedChars: 0,
|
|
1344
|
+
nextSeq: 1,
|
|
1345
|
+
chunks: [],
|
|
1346
|
+
});
|
|
1347
|
+
handle.transport.send({ t: "mirror.image.ack", transferId: msg.transferId, seq: 0 });
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
case "mirror.image.chunk": {
|
|
1351
|
+
const transfer = handle.imageTransfers.get(msg.transferId);
|
|
1352
|
+
if (handle.dead ||
|
|
1353
|
+
!transfer ||
|
|
1354
|
+
msg.seq !== transfer.nextSeq ||
|
|
1355
|
+
!msg.data ||
|
|
1356
|
+
msg.data.length > RUNNER_IMAGE_CHUNK_CHARS ||
|
|
1357
|
+
transfer.receivedChars + msg.data.length > transfer.totalChars) {
|
|
1358
|
+
this.failMirrorImageTransfer(handle, "invalid generated image transfer chunk");
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
transfer.chunks.push(msg.data);
|
|
1362
|
+
transfer.receivedChars += msg.data.length;
|
|
1363
|
+
transfer.nextSeq += 1;
|
|
1364
|
+
handle.transport.send({
|
|
1365
|
+
t: "mirror.image.ack",
|
|
1366
|
+
transferId: msg.transferId,
|
|
1367
|
+
seq: msg.seq,
|
|
1368
|
+
});
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
case "mirror.image.commit": {
|
|
1372
|
+
const transfer = handle.imageTransfers.get(msg.transferId);
|
|
1373
|
+
if (handle.dead ||
|
|
1374
|
+
!transfer ||
|
|
1375
|
+
msg.seq !== transfer.nextSeq ||
|
|
1376
|
+
transfer.receivedChars !== transfer.totalChars) {
|
|
1377
|
+
this.failMirrorImageTransfer(handle, "invalid generated image transfer commit");
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
handle.imageTransfers.delete(msg.transferId);
|
|
1381
|
+
const event = attachGeneratedImageResult(transfer.event, transfer.chunks.join(""));
|
|
1382
|
+
if (!event) {
|
|
1383
|
+
this.failMirrorImageTransfer(handle, "generated image transfer target changed");
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
const mirrored = {
|
|
1387
|
+
t: "mirror",
|
|
1388
|
+
sessionId: transfer.sessionId,
|
|
1389
|
+
event,
|
|
1390
|
+
transferId: msg.transferId,
|
|
1391
|
+
seq: msg.seq,
|
|
1392
|
+
};
|
|
1393
|
+
if (this.bufferForkTargetMessage(handle, mirrored)) {
|
|
1394
|
+
handle.transport.send({
|
|
1395
|
+
t: "mirror.image.hold",
|
|
1396
|
+
transferId: msg.transferId,
|
|
1397
|
+
seq: msg.seq,
|
|
1398
|
+
});
|
|
1399
|
+
return;
|
|
1258
1400
|
}
|
|
1401
|
+
this.deliverMirrorMessage(handle, mirrored);
|
|
1259
1402
|
return;
|
|
1403
|
+
}
|
|
1260
1404
|
case "rotate": {
|
|
1261
1405
|
if (handle.dead)
|
|
1262
1406
|
return;
|
|
@@ -1275,6 +1419,15 @@ export class RunnerManager {
|
|
|
1275
1419
|
: { ok: msg.ok, error: msg.error });
|
|
1276
1420
|
return;
|
|
1277
1421
|
}
|
|
1422
|
+
case "collaboration.updated": {
|
|
1423
|
+
const resolve = handle.live.get(msg.reqId);
|
|
1424
|
+
handle.live.delete(msg.reqId);
|
|
1425
|
+
resolve?.({
|
|
1426
|
+
collaborationUpdated: msg.ok,
|
|
1427
|
+
...(msg.error ? { error: msg.error } : {}),
|
|
1428
|
+
});
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1278
1431
|
case "interaction.resolved": {
|
|
1279
1432
|
const resolve = handle.live.get(msg.reqId);
|
|
1280
1433
|
handle.live.delete(msg.reqId);
|
|
@@ -1322,6 +1475,7 @@ export class RunnerManager {
|
|
|
1322
1475
|
handle.caps.clear();
|
|
1323
1476
|
handle.terminals.clear();
|
|
1324
1477
|
handle.live.clear();
|
|
1478
|
+
handle.imageTransfers.clear();
|
|
1325
1479
|
handle.transport.close();
|
|
1326
1480
|
}
|
|
1327
1481
|
terminateHandle(handle, reason) {
|
|
@@ -1684,6 +1838,36 @@ function closeSessionContext(context) {
|
|
|
1684
1838
|
}));
|
|
1685
1839
|
}
|
|
1686
1840
|
}
|
|
1841
|
+
function isDetachedGeneratedImageEvent(event) {
|
|
1842
|
+
if (event.type !== "response.output_item.done" ||
|
|
1843
|
+
event.item.type !== "function_call_output") {
|
|
1844
|
+
return false;
|
|
1845
|
+
}
|
|
1846
|
+
const data = event.item.data;
|
|
1847
|
+
return Boolean(data.__rynxGeneratedImage && data.__rynxGeneratedImage.result === undefined);
|
|
1848
|
+
}
|
|
1849
|
+
function attachGeneratedImageResult(event, result) {
|
|
1850
|
+
if (!isDetachedGeneratedImageEvent(event))
|
|
1851
|
+
return undefined;
|
|
1852
|
+
const outputEvent = event;
|
|
1853
|
+
const item = outputEvent.item;
|
|
1854
|
+
if (item.type !== "function_call_output")
|
|
1855
|
+
return undefined;
|
|
1856
|
+
const data = item.data;
|
|
1857
|
+
return {
|
|
1858
|
+
...outputEvent,
|
|
1859
|
+
item: {
|
|
1860
|
+
...item,
|
|
1861
|
+
data: {
|
|
1862
|
+
...data,
|
|
1863
|
+
__rynxGeneratedImage: {
|
|
1864
|
+
...data.__rynxGeneratedImage,
|
|
1865
|
+
result,
|
|
1866
|
+
},
|
|
1867
|
+
},
|
|
1868
|
+
},
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1687
1871
|
function errorMessage(error) {
|
|
1688
1872
|
return error instanceof Error ? error.message : String(error);
|
|
1689
1873
|
}
|
|
@@ -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`. */
|
|
@@ -26,6 +26,14 @@ export type TerminalOpenErrorCode = "terminal_not_live" | "terminal_open_failed"
|
|
|
26
26
|
/** Parent → child. Terminal PTY bytes ride `term.input` base64-encoded so the
|
|
27
27
|
* NDJSON line framing stays intact (raw bytes contain newlines). */
|
|
28
28
|
export type ToChild = {
|
|
29
|
+
t: "mirror.image.ack";
|
|
30
|
+
transferId: string;
|
|
31
|
+
seq: number;
|
|
32
|
+
} | {
|
|
33
|
+
t: "mirror.image.hold";
|
|
34
|
+
transferId: string;
|
|
35
|
+
seq: number;
|
|
36
|
+
} | {
|
|
29
37
|
t: "cap";
|
|
30
38
|
capId: string;
|
|
31
39
|
name: CapName;
|
|
@@ -88,6 +96,7 @@ export type ToChild = {
|
|
|
88
96
|
t: "inject";
|
|
89
97
|
reqId: string;
|
|
90
98
|
localThreadId: string;
|
|
99
|
+
options?: RuntimeTurnOptions;
|
|
91
100
|
} & ({
|
|
92
101
|
input: RuntimeUserInput;
|
|
93
102
|
text?: never;
|
|
@@ -95,6 +104,14 @@ export type ToChild = {
|
|
|
95
104
|
text: string;
|
|
96
105
|
input?: never;
|
|
97
106
|
}))
|
|
107
|
+
/** Apply a Codex-lineage collaboration mode to an already-loaded native
|
|
108
|
+
* thread. This control path never starts a runner or waits for a new Turn. */
|
|
109
|
+
| {
|
|
110
|
+
t: "collaboration.update";
|
|
111
|
+
reqId: string;
|
|
112
|
+
localThreadId: string;
|
|
113
|
+
mode: SessionCollaborationMode;
|
|
114
|
+
}
|
|
98
115
|
/** Interrupt the session's active turn — the web Stop button. codex: app-server
|
|
99
116
|
* `turn/interrupt`; claude: an Escape keystroke to the pane. The child replies
|
|
100
117
|
* `interrupted` (echoing `reqId`). */
|
|
@@ -142,6 +159,24 @@ export type FromChild = {
|
|
|
142
159
|
t: "mirror";
|
|
143
160
|
sessionId: string;
|
|
144
161
|
event: SessionEvent;
|
|
162
|
+
/** Parent-local completion marker used after a chunked image is rebuilt. */
|
|
163
|
+
transferId?: string;
|
|
164
|
+
seq?: number;
|
|
165
|
+
} | {
|
|
166
|
+
t: "mirror.image.begin";
|
|
167
|
+
transferId: string;
|
|
168
|
+
sessionId: string;
|
|
169
|
+
event: SessionEvent;
|
|
170
|
+
totalChars: number;
|
|
171
|
+
} | {
|
|
172
|
+
t: "mirror.image.chunk";
|
|
173
|
+
transferId: string;
|
|
174
|
+
seq: number;
|
|
175
|
+
data: string;
|
|
176
|
+
} | {
|
|
177
|
+
t: "mirror.image.commit";
|
|
178
|
+
transferId: string;
|
|
179
|
+
seq: number;
|
|
145
180
|
}
|
|
146
181
|
/** The session rotated to a fresh machine-session (claude `/clear`·`/fork`): the
|
|
147
182
|
* child re-pointed its mirror to `to` and asks the daemon to alias the runner
|
|
@@ -184,12 +219,23 @@ export type FromChild = {
|
|
|
184
219
|
localThreadId: string;
|
|
185
220
|
ok: boolean;
|
|
186
221
|
error?: WireError;
|
|
222
|
+
} | {
|
|
223
|
+
t: "collaboration.updated";
|
|
224
|
+
reqId: string;
|
|
225
|
+
localThreadId: string;
|
|
226
|
+
ok: boolean;
|
|
227
|
+
error?: WireError;
|
|
187
228
|
} | {
|
|
188
229
|
t: "interaction.resolved";
|
|
189
230
|
reqId: string;
|
|
190
231
|
localThreadId: string;
|
|
191
232
|
result: ResolveInteractionResult;
|
|
192
233
|
};
|
|
234
|
+
/** Internal runner wire bounds. Generated images are already subject to the
|
|
235
|
+
* Session image byte limit; chunks keep the NDJSON pipe and parser bounded and
|
|
236
|
+
* each chunk is acknowledged before the next is sent. */
|
|
237
|
+
export declare const RUNNER_IMAGE_CHUNK_CHARS: number;
|
|
238
|
+
export declare const RUNNER_IMAGE_MAX_RESULT_CHARS: number;
|
|
193
239
|
/** Encode a message as a single NDJSON line (newline included). */
|
|
194
240
|
export declare function encodeMessage(msg: ToChild | FromChild): string;
|
|
195
241
|
/** Parse one NDJSON line; returns `null` for blank lines or malformed JSON. */
|
package/dist/runner/protocol.js
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
* terminal attachment; the reply channels mirror each request's `reqId`.
|
|
8
8
|
*/
|
|
9
9
|
import { AgentRuntimeError, } from "@rynx-ai/core";
|
|
10
|
+
/** Internal runner wire bounds. Generated images are already subject to the
|
|
11
|
+
* Session image byte limit; chunks keep the NDJSON pipe and parser bounded and
|
|
12
|
+
* each chunk is acknowledged before the next is sent. */
|
|
13
|
+
export const RUNNER_IMAGE_CHUNK_CHARS = 32 * 1024;
|
|
14
|
+
export const RUNNER_IMAGE_MAX_RESULT_CHARS = 8 * 1024 * 1024;
|
|
10
15
|
/** Encode a message as a single NDJSON line (newline included). */
|
|
11
16
|
export function encodeMessage(msg) {
|
|
12
17
|
return `${JSON.stringify(msg)}\n`;
|
|
@@ -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.26",
|
|
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.26"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/ws": "^8.18.1"
|