@rynx-ai/runtime 0.1.11-beta.5 → 0.1.11-beta.7
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/runner/child.js +49 -26
- package/dist/runner/manager.d.ts +12 -0
- package/dist/runner/manager.js +84 -6
- package/dist/terminal/tmux.d.ts +3 -1
- package/dist/terminal/tmux.js +30 -14
- package/package.json +3 -3
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;
|
|
@@ -48,8 +61,17 @@ export class RunnerSession {
|
|
|
48
61
|
});
|
|
49
62
|
return;
|
|
50
63
|
case "term.input":
|
|
51
|
-
|
|
52
|
-
|
|
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
|
+
}
|
|
53
75
|
return;
|
|
54
76
|
case "term.resize":
|
|
55
77
|
this.attachments.get(msg.attachId)?.resize(msg.cols, msg.rows);
|
|
@@ -215,8 +237,8 @@ export class RunnerSession {
|
|
|
215
237
|
try {
|
|
216
238
|
const input = msg.input ?? msg.text;
|
|
217
239
|
const outcome = (await provider.injectMessage?.(msg.localThreadId, input)) ?? "notLive";
|
|
218
|
-
|
|
219
|
-
|
|
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.
|
|
220
242
|
this.transport.send({ t: "injected", reqId: msg.reqId, localThreadId: msg.localThreadId, outcome });
|
|
221
243
|
}
|
|
222
244
|
catch (error) {
|
|
@@ -255,7 +277,6 @@ export class RunnerSession {
|
|
|
255
277
|
if (!spec)
|
|
256
278
|
return;
|
|
257
279
|
const terminalId = `${localThreadId}-main`;
|
|
258
|
-
const coldStart = !this.terminals.has(terminalId);
|
|
259
280
|
const term = this.terminals.getOrCreate(terminalId, {
|
|
260
281
|
cwd: spec.cwd,
|
|
261
282
|
command: spec.command,
|
|
@@ -264,7 +285,7 @@ export class RunnerSession {
|
|
|
264
285
|
rows: rows ?? 40,
|
|
265
286
|
...(spec.env ? { env: spec.env } : {}),
|
|
266
287
|
});
|
|
267
|
-
if (
|
|
288
|
+
if (spec.skipTraexStartupPrompts) {
|
|
268
289
|
const watcher = Symbol(localThreadId);
|
|
269
290
|
this.traexStartupWatchers.set(localThreadId, watcher);
|
|
270
291
|
void this.skipTraexStartupPrompts(localThreadId, term, watcher);
|
|
@@ -279,46 +300,48 @@ export class RunnerSession {
|
|
|
279
300
|
const prompts = [
|
|
280
301
|
{
|
|
281
302
|
id: "welcome",
|
|
282
|
-
matches: (pane) => pane.includes("
|
|
303
|
+
matches: (pane) => pane.includes("welcome to trae cli") && pane.includes("press enter to continue"),
|
|
283
304
|
dismiss: () => terminal.sendEnter(),
|
|
284
305
|
},
|
|
285
306
|
{
|
|
286
307
|
id: "migration",
|
|
287
|
-
matches: (pane) => pane.includes("
|
|
288
|
-
pane.includes("
|
|
289
|
-
pane.includes("
|
|
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")),
|
|
290
311
|
dismiss: () => terminal.interrupt(),
|
|
291
312
|
},
|
|
292
313
|
{
|
|
293
314
|
id: "hooks",
|
|
294
|
-
matches: (pane) => pane.includes("
|
|
295
|
-
pane.includes("
|
|
296
|
-
pane.includes("
|
|
297
|
-
pane.includes("Continue without trusting"),
|
|
315
|
+
matches: (pane) => pane.includes("hooks need review") &&
|
|
316
|
+
pane.includes("trust all and continue") &&
|
|
317
|
+
pane.includes("continue without trusting"),
|
|
298
318
|
dismiss: () => terminal.interrupt(),
|
|
299
319
|
},
|
|
300
320
|
];
|
|
301
|
-
|
|
302
|
-
|
|
321
|
+
let activePromptId;
|
|
322
|
+
let lastDismissedAt = 0;
|
|
323
|
+
const deadline = Date.now() + TRAEX_STARTUP_WATCH_MS;
|
|
303
324
|
const terminalId = `${localThreadId}-main`;
|
|
304
325
|
while (!this.shuttingDown &&
|
|
305
326
|
Date.now() < deadline &&
|
|
306
327
|
this.traexStartupWatchers.get(localThreadId) === watcher &&
|
|
307
328
|
this.terminals.get(terminalId) === terminal) {
|
|
308
|
-
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
if (pane.includes("TRAE CLI Next"))
|
|
312
|
-
break;
|
|
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());
|
|
313
332
|
const prompt = prompts.find((candidate) => candidate.matches(pane));
|
|
314
|
-
|
|
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) {
|
|
315
339
|
prompt.dismiss();
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
break;
|
|
340
|
+
activePromptId = prompt.id;
|
|
341
|
+
lastDismissedAt = now;
|
|
319
342
|
}
|
|
320
343
|
await new Promise((resolve) => {
|
|
321
|
-
const timer = setTimeout(resolve,
|
|
344
|
+
const timer = setTimeout(resolve, TRAEX_STARTUP_POLL_MS);
|
|
322
345
|
timer.unref();
|
|
323
346
|
});
|
|
324
347
|
}
|
package/dist/runner/manager.d.ts
CHANGED
|
@@ -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. */
|
|
@@ -136,6 +139,7 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
136
139
|
private readonly sessionStore;
|
|
137
140
|
private readonly runnerEntry;
|
|
138
141
|
private readonly idleTtlMs;
|
|
142
|
+
private readonly staleActiveTtlMs;
|
|
139
143
|
private readonly spawn;
|
|
140
144
|
private readonly childEnv;
|
|
141
145
|
private readonly sessionContextProvider;
|
|
@@ -312,5 +316,13 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
312
316
|
private failHandle;
|
|
313
317
|
private terminateHandle;
|
|
314
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;
|
|
315
327
|
private openSessionContext;
|
|
316
328
|
}
|
package/dist/runner/manager.js
CHANGED
|
@@ -44,6 +44,12 @@ const DEFAULT_TERMINAL_INPUT_HANDOFF_TIMEOUT_MS = 30_000;
|
|
|
44
44
|
/** Retry transient terminal-server cleanup without spinning forever. */
|
|
45
45
|
const DEFAULT_TERMINAL_INPUT_CLEANUP_RETRY_MS = 1_000;
|
|
46
46
|
const MAX_TERMINAL_INPUT_CLEANUP_RETRY_MS = 30_000;
|
|
47
|
+
/** A Provider that never closes its active response must not keep a detached
|
|
48
|
+
* runner + tmux server alive forever. This is intentionally much longer than
|
|
49
|
+
* the normal idle TTL so legitimate long-running turns are not treated as
|
|
50
|
+
* stale. User input/control activity refreshes the deadline; Provider output
|
|
51
|
+
* does not, because a runaway redraw loop is the failure mode this bounds. */
|
|
52
|
+
const DEFAULT_STALE_ACTIVE_TTL_MS = 60 * 60_000;
|
|
47
53
|
/** Keep per-Session spawn context small enough to remain an environment handoff,
|
|
48
54
|
* not an unbounded transport. */
|
|
49
55
|
const MAX_SESSION_CONTEXT_ENV_ENTRIES = 32;
|
|
@@ -144,6 +150,7 @@ export class RunnerManager {
|
|
|
144
150
|
sessionStore;
|
|
145
151
|
runnerEntry;
|
|
146
152
|
idleTtlMs;
|
|
153
|
+
staleActiveTtlMs;
|
|
147
154
|
spawn;
|
|
148
155
|
childEnv;
|
|
149
156
|
sessionContextProvider;
|
|
@@ -198,6 +205,7 @@ export class RunnerManager {
|
|
|
198
205
|
this.sessionStore = opts.sessionStore;
|
|
199
206
|
this.runnerEntry = opts.runnerEntry ?? defaultRunnerEntry();
|
|
200
207
|
this.idleTtlMs = opts.idleTtlMs ?? 300_000;
|
|
208
|
+
this.staleActiveTtlMs = Math.max(this.idleTtlMs, opts.staleActiveTtlMs ?? DEFAULT_STALE_ACTIVE_TTL_MS);
|
|
201
209
|
this.spawn = opts.spawn ?? nodeSpawn;
|
|
202
210
|
this.childEnv = opts.childEnv ?? {};
|
|
203
211
|
this.sessionContextProvider = opts.sessionContextProvider;
|
|
@@ -261,6 +269,8 @@ export class RunnerManager {
|
|
|
261
269
|
pendingEscape: "",
|
|
262
270
|
};
|
|
263
271
|
const terminal = new ManagedTerminal(attachId, (msg) => {
|
|
272
|
+
if (msg.t === "term.input")
|
|
273
|
+
handle.lastUsedAt = this.now();
|
|
264
274
|
const handoffs = msg.t === "term.input" && opts.role === "owner"
|
|
265
275
|
? this.beginTerminalInputHandoffs(handle, this.currentTerminalSessionId(handle, localThreadId), trackedTerminalSubmissions(inputTracker, Buffer.from(msg.dataB64, "base64").toString("utf8")))
|
|
266
276
|
: [];
|
|
@@ -1011,6 +1021,7 @@ export class RunnerManager {
|
|
|
1011
1021
|
if (handle.dead)
|
|
1012
1022
|
return;
|
|
1013
1023
|
if (message.t === "mirror") {
|
|
1024
|
+
this.observeHandleRuntimeEvent(handle, message.event);
|
|
1014
1025
|
this.mirrorListener?.(message.sessionId, message.event);
|
|
1015
1026
|
if (message.event.type === "response.created") {
|
|
1016
1027
|
this.settleTerminalInputHandoff(handle, message.sessionId, "turn");
|
|
@@ -1078,6 +1089,7 @@ export class RunnerManager {
|
|
|
1078
1089
|
this.settleTerminalRotationHandoff(handle, message.from, message.to);
|
|
1079
1090
|
if (handle.dead)
|
|
1080
1091
|
return;
|
|
1092
|
+
handle.activeResponseIds.clear();
|
|
1081
1093
|
this.handles.set(message.to, handle);
|
|
1082
1094
|
this.liveSessionKeys.add(message.to);
|
|
1083
1095
|
this.liveOptions.set(message.to, {
|
|
@@ -1144,6 +1156,7 @@ export class RunnerManager {
|
|
|
1144
1156
|
transport,
|
|
1145
1157
|
stderr: [],
|
|
1146
1158
|
lastUsedAt: this.now(),
|
|
1159
|
+
activeResponseIds: new Set(),
|
|
1147
1160
|
dead: false,
|
|
1148
1161
|
completion,
|
|
1149
1162
|
terminalCleanupRetryFailures: 0,
|
|
@@ -1210,6 +1223,7 @@ export class RunnerManager {
|
|
|
1210
1223
|
return;
|
|
1211
1224
|
if (this.bufferForkTargetMessage(handle, msg))
|
|
1212
1225
|
return;
|
|
1226
|
+
this.observeHandleRuntimeEvent(handle, msg.event);
|
|
1213
1227
|
this.mirrorListener?.(msg.sessionId, msg.event);
|
|
1214
1228
|
if (msg.event.type === "response.created") {
|
|
1215
1229
|
// The listener projects response.created into SessionRuntimeIndex
|
|
@@ -1331,22 +1345,86 @@ export class RunnerManager {
|
|
|
1331
1345
|
reapIdle() {
|
|
1332
1346
|
const now = this.now();
|
|
1333
1347
|
for (const [key, handle] of this.handles) {
|
|
1334
|
-
if (handle.caps.size > 0 || handle.terminals.size > 0) {
|
|
1348
|
+
if (handle.caps.size > 0 || handle.terminals.size > 0 || handle.live.size > 0) {
|
|
1335
1349
|
continue;
|
|
1336
1350
|
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
if (this.liveSessionKeys.has(key)) {
|
|
1351
|
+
const idleForMs = now - handle.lastUsedAt;
|
|
1352
|
+
if (idleForMs < this.idleTtlMs) {
|
|
1340
1353
|
continue;
|
|
1341
1354
|
}
|
|
1342
|
-
|
|
1355
|
+
const hasActiveResponse = handle.activeResponseIds.size > 0;
|
|
1356
|
+
if (hasActiveResponse && idleForMs < this.staleActiveTtlMs) {
|
|
1343
1357
|
continue;
|
|
1344
1358
|
}
|
|
1345
|
-
|
|
1359
|
+
const reason = hasActiveResponse
|
|
1360
|
+
? "stale active runner reaped"
|
|
1361
|
+
: this.liveSessionKeys.has(key)
|
|
1362
|
+
? "idle live runner reaped"
|
|
1363
|
+
: "idle runner reaped";
|
|
1364
|
+
if (hasActiveResponse) {
|
|
1365
|
+
this.failStaleActiveResponses(handle, idleForMs);
|
|
1366
|
+
}
|
|
1367
|
+
void this.terminateHandle(handle, reason).catch((error) => {
|
|
1346
1368
|
logTerminationFailure(handle, error);
|
|
1347
1369
|
});
|
|
1348
1370
|
}
|
|
1349
1371
|
}
|
|
1372
|
+
/** Close server-side runtime state before fencing a Provider that stayed
|
|
1373
|
+
* active past its hard inactivity deadline. The child transport is closed by
|
|
1374
|
+
* terminateHandle immediately afterwards, so these are the final events for
|
|
1375
|
+
* the abandoned responses. */
|
|
1376
|
+
failStaleActiveResponses(handle, idleForMs) {
|
|
1377
|
+
const responseIds = [...handle.activeResponseIds];
|
|
1378
|
+
handle.activeResponseIds.clear();
|
|
1379
|
+
const sessionId = this.currentTerminalSessionId(handle, handle.key);
|
|
1380
|
+
for (const responseId of responseIds) {
|
|
1381
|
+
this.mirrorListener?.(sessionId, {
|
|
1382
|
+
type: "response.failed",
|
|
1383
|
+
responseId,
|
|
1384
|
+
error: {
|
|
1385
|
+
source: "execution",
|
|
1386
|
+
code: "stale_runner_reaped",
|
|
1387
|
+
message: `Runner was reaped after ${Math.floor(idleForMs / 1000)}s without user activity`,
|
|
1388
|
+
},
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
/** Track only response lifecycle, not output volume. Output deltas from a
|
|
1393
|
+
* runaway TUI/Provider must not refresh the stale-active deadline. */
|
|
1394
|
+
observeHandleRuntimeEvent(handle, event) {
|
|
1395
|
+
switch (event.type) {
|
|
1396
|
+
case "response.created":
|
|
1397
|
+
case "response.output_text.delta":
|
|
1398
|
+
case "response.reasoning_summary_text.delta":
|
|
1399
|
+
case "response.function_call_output.delta":
|
|
1400
|
+
case "response.output_item.done":
|
|
1401
|
+
handle.activeResponseIds.add(event.responseId);
|
|
1402
|
+
return;
|
|
1403
|
+
case "session.interaction.requested":
|
|
1404
|
+
handle.activeResponseIds.add(event.responseId);
|
|
1405
|
+
return;
|
|
1406
|
+
case "response.completed":
|
|
1407
|
+
case "response.failed":
|
|
1408
|
+
handle.activeResponseIds.delete(event.responseId);
|
|
1409
|
+
return;
|
|
1410
|
+
case "session.status":
|
|
1411
|
+
if (event.status === "running" && event.responseId) {
|
|
1412
|
+
handle.activeResponseIds.add(event.responseId);
|
|
1413
|
+
}
|
|
1414
|
+
else if (event.status !== "running") {
|
|
1415
|
+
if (event.responseId)
|
|
1416
|
+
handle.activeResponseIds.delete(event.responseId);
|
|
1417
|
+
else
|
|
1418
|
+
handle.activeResponseIds.clear();
|
|
1419
|
+
}
|
|
1420
|
+
return;
|
|
1421
|
+
case "session.rotated":
|
|
1422
|
+
handle.activeResponseIds.clear();
|
|
1423
|
+
return;
|
|
1424
|
+
default:
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1350
1428
|
openSessionContext(sessionId) {
|
|
1351
1429
|
if (!this.sessionContextProvider)
|
|
1352
1430
|
return undefined;
|
package/dist/terminal/tmux.d.ts
CHANGED
|
@@ -146,7 +146,9 @@ export declare class TmuxTerminal {
|
|
|
146
146
|
paste(text: string, bufferName?: string): void;
|
|
147
147
|
/**
|
|
148
148
|
* Attach a client. `role: "read-only"` passes tmux `-r` so the viewer cannot
|
|
149
|
-
* type
|
|
149
|
+
* type and `ignore-size` so even its initial PTY dimensions cannot resize the
|
|
150
|
+
* owner's pane (defense-in-depth on top of the WS bridge dropping input and
|
|
151
|
+
* resize frames).
|
|
150
152
|
*/
|
|
151
153
|
attach(role: "owner" | "read-only", dims?: {
|
|
152
154
|
cols?: number;
|
package/dist/terminal/tmux.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { execFile, execFileSync } from "node:child_process";
|
|
18
18
|
import { createHash } from "node:crypto";
|
|
19
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
19
|
+
import { existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
20
20
|
import { tmpdir } from "node:os";
|
|
21
21
|
import { join } from "node:path";
|
|
22
22
|
const TMUX_TARGET = "main";
|
|
@@ -54,10 +54,28 @@ export function terminateTmuxServer(name, tmuxBin = "tmux") {
|
|
|
54
54
|
return false;
|
|
55
55
|
}
|
|
56
56
|
catch (error) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
// Only a real tmux exit status proves the server/session is absent. Spawn
|
|
58
|
+
// failures (missing/unexecutable tmux) leave liveness unproven and must not
|
|
59
|
+
// unlink a socket that could still belong to a live server.
|
|
60
|
+
if (!error ||
|
|
61
|
+
typeof error !== "object" ||
|
|
62
|
+
!("status" in error) ||
|
|
63
|
+
typeof error.status !== "number") {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
// tmux custom-socket servers can exit while leaving the filesystem entry
|
|
68
|
+
// behind. Remove that verified-stale socket so process/socket inventories
|
|
69
|
+
// do not accumulate dead terminals forever.
|
|
70
|
+
unlinkSync(socketPath);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch (unlinkError) {
|
|
74
|
+
return Boolean(unlinkError &&
|
|
75
|
+
typeof unlinkError === "object" &&
|
|
76
|
+
"code" in unlinkError &&
|
|
77
|
+
unlinkError.code === "ENOENT");
|
|
78
|
+
}
|
|
61
79
|
}
|
|
62
80
|
}
|
|
63
81
|
/** Is a usable `tmux` on PATH? Cheap probe for the capability gate. */
|
|
@@ -319,14 +337,17 @@ export class TmuxTerminal {
|
|
|
319
337
|
}
|
|
320
338
|
/**
|
|
321
339
|
* Attach a client. `role: "read-only"` passes tmux `-r` so the viewer cannot
|
|
322
|
-
* type
|
|
340
|
+
* type and `ignore-size` so even its initial PTY dimensions cannot resize the
|
|
341
|
+
* owner's pane (defense-in-depth on top of the WS bridge dropping input and
|
|
342
|
+
* resize frames).
|
|
323
343
|
*/
|
|
324
344
|
async attach(role, dims) {
|
|
325
345
|
this.start();
|
|
326
346
|
const spawn = this.injectedSpawn ?? (await loadPtySpawn());
|
|
327
|
-
const args = [...this.base(), "attach-session"
|
|
347
|
+
const args = [...this.base(), "attach-session"];
|
|
328
348
|
if (role === "read-only")
|
|
329
|
-
args.push("-r");
|
|
349
|
+
args.push("-r", "-f", "ignore-size");
|
|
350
|
+
args.push("-t", TMUX_TARGET);
|
|
330
351
|
const proc = spawn(this.tmuxBin, args, {
|
|
331
352
|
name: "xterm-256color",
|
|
332
353
|
cols: dims?.cols ?? this.cols,
|
|
@@ -386,12 +407,7 @@ export class TmuxTerminal {
|
|
|
386
407
|
}
|
|
387
408
|
/** Kill the tmux server (ends the session and all attaches). */
|
|
388
409
|
kill() {
|
|
389
|
-
|
|
390
|
-
execFileSync(this.tmuxBin, [...this.base(), "kill-server"], { stdio: "ignore" });
|
|
391
|
-
}
|
|
392
|
-
catch {
|
|
393
|
-
// Already gone — nothing to clean up.
|
|
394
|
-
}
|
|
410
|
+
terminateTmuxServer(this.name, this.tmuxBin);
|
|
395
411
|
this.started = false;
|
|
396
412
|
}
|
|
397
413
|
}
|
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.7",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/rynx-ai/rynx.git",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"node-pty": "
|
|
27
|
+
"node-pty": "1.2.0-beta.15",
|
|
28
28
|
"ws": "^8.21.0",
|
|
29
|
-
"@rynx-ai/core": "0.1.11-beta.
|
|
29
|
+
"@rynx-ai/core": "0.1.11-beta.7"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/ws": "^8.18.1"
|