@rynx-ai/runtime 0.1.11-beta.21 → 0.1.11-beta.23
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-integration.d.ts +7 -0
- package/dist/claude/native-integration.js +25 -2
- package/dist/codex-app-server/client.d.ts +4 -3
- package/dist/codex-app-server/client.js +35 -12
- package/dist/codex-app-server/forwarder.d.ts +2 -7
- package/dist/codex-app-server/forwarder.js +12 -50
- package/dist/codex-app-server/mapping.d.ts +1 -0
- package/dist/codex-app-server/mapping.js +8 -1
- package/dist/codex-app-server/protocol.d.ts +1 -2
- package/dist/host.d.ts +36 -25
- package/dist/host.js +384 -441
- package/dist/index.d.ts +1 -1
- package/dist/runner/child.d.ts +18 -7
- package/dist/runner/child.js +169 -45
- package/dist/runner/manager.d.ts +14 -15
- package/dist/runner/manager.js +90 -98
- package/dist/runner/protocol.d.ts +16 -15
- package/dist/runner/startup-policy.d.ts +3 -0
- package/dist/runner/startup-policy.js +5 -0
- package/dist/terminal/registry.js +3 -2
- package/dist/terminal/tmux.d.ts +29 -5
- package/dist/terminal/tmux.js +98 -52
- package/package.json +2 -2
package/dist/terminal/tmux.js
CHANGED
|
@@ -79,50 +79,32 @@ export function tmuxHasAttachedClient(name, tmuxBin = resolveTmuxBin()) {
|
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
-
/**
|
|
83
|
-
*
|
|
84
|
-
* command
|
|
82
|
+
/** Best-effort close of one private tmux server. Mirrors Omnigent's bounded
|
|
83
|
+
* `TerminalInstance.close`: attempt `kill-server`, then retire the private
|
|
84
|
+
* socket regardless of command outcome. The registry has already forgotten
|
|
85
|
+
* the resource, so cleanup failure is diagnostic rather than a second
|
|
86
|
+
* lifecycle state. */
|
|
85
87
|
export function terminateTmuxServer(name, tmuxBin = resolveTmuxBin()) {
|
|
86
88
|
const socketPath = tmuxSocketPath(name);
|
|
87
89
|
if (!existsSync(socketPath))
|
|
88
90
|
return true;
|
|
89
91
|
const base = ["-u", "-S", socketPath];
|
|
90
92
|
try {
|
|
91
|
-
execFileSync(tmuxBin, [...base, "kill-server"], {
|
|
93
|
+
execFileSync(tmuxBin, [...base, "kill-server"], {
|
|
94
|
+
stdio: "ignore",
|
|
95
|
+
timeout: 5_000,
|
|
96
|
+
});
|
|
92
97
|
}
|
|
93
98
|
catch {
|
|
94
|
-
//
|
|
99
|
+
// Omnigent treats close as best-effort and still retires the resource.
|
|
95
100
|
}
|
|
96
101
|
try {
|
|
97
|
-
|
|
98
|
-
stdio: "ignore",
|
|
99
|
-
});
|
|
100
|
-
return false;
|
|
102
|
+
unlinkSync(socketPath);
|
|
101
103
|
}
|
|
102
|
-
catch
|
|
103
|
-
//
|
|
104
|
-
// failures (missing/unexecutable tmux) leave liveness unproven and must not
|
|
105
|
-
// unlink a socket that could still belong to a live server.
|
|
106
|
-
if (!error ||
|
|
107
|
-
typeof error !== "object" ||
|
|
108
|
-
!("status" in error) ||
|
|
109
|
-
typeof error.status !== "number") {
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
// tmux custom-socket servers can exit while leaving the filesystem entry
|
|
114
|
-
// behind. Remove that verified-stale socket so process/socket inventories
|
|
115
|
-
// do not accumulate dead terminals forever.
|
|
116
|
-
unlinkSync(socketPath);
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
catch (unlinkError) {
|
|
120
|
-
return Boolean(unlinkError &&
|
|
121
|
-
typeof unlinkError === "object" &&
|
|
122
|
-
"code" in unlinkError &&
|
|
123
|
-
unlinkError.code === "ENOENT");
|
|
124
|
-
}
|
|
104
|
+
catch {
|
|
105
|
+
// Gone already, or best-effort filesystem cleanup failed.
|
|
125
106
|
}
|
|
107
|
+
return true;
|
|
126
108
|
}
|
|
127
109
|
/** Is a usable `tmux` on PATH? Cheap probe for the capability gate. */
|
|
128
110
|
export function isTmuxAvailable(tmuxBin = resolveTmuxBin()) {
|
|
@@ -158,6 +140,14 @@ export class TmuxTerminal {
|
|
|
158
140
|
tmuxBin;
|
|
159
141
|
injectedSpawn;
|
|
160
142
|
started = false;
|
|
143
|
+
lastPaneSnapshot = "";
|
|
144
|
+
/** Shared by every attachment watcher and by the lifecycle watcher's
|
|
145
|
+
* pane-dead stage. One Terminal must never fan the same tmux control probe
|
|
146
|
+
* out once per attached client. */
|
|
147
|
+
paneLivenessFlight;
|
|
148
|
+
/** Shared by lifecycle callers so capture + pane-dead remains one ordered
|
|
149
|
+
* Omnigent-style observation per Terminal. */
|
|
150
|
+
lifecycleLivenessFlight;
|
|
161
151
|
constructor(opts) {
|
|
162
152
|
this.name = opts.name;
|
|
163
153
|
this.cwd = opts.cwd;
|
|
@@ -188,15 +178,8 @@ export class TmuxTerminal {
|
|
|
188
178
|
start() {
|
|
189
179
|
if (this.started)
|
|
190
180
|
return;
|
|
191
|
-
// Clear any stale server on this
|
|
192
|
-
|
|
193
|
-
// reaps our own leftover, never a live sibling.
|
|
194
|
-
try {
|
|
195
|
-
execFileSync(this.tmuxBin, [...this.base(), "kill-server"], { stdio: "ignore" });
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
// No stale server — expected on a fresh start.
|
|
199
|
-
}
|
|
181
|
+
// Clear any stale server on this deterministic private socket before launch.
|
|
182
|
+
terminateTmuxServer(this.name, this.tmuxBin);
|
|
200
183
|
// `new-session -d` starts detached; `-x/-y` seed the pane size so output
|
|
201
184
|
// wraps sanely before the first client attaches and resizes it.
|
|
202
185
|
execFileSync(this.tmuxBin, [
|
|
@@ -302,21 +285,82 @@ export class TmuxTerminal {
|
|
|
302
285
|
/** Async pane-liveness probe — MUST NOT block the event loop. The attach
|
|
303
286
|
* pane-death watcher polls this on an interval; a synchronous `execFileSync`
|
|
304
287
|
* there stalls the runner child's event loop (freezing the PTY stream → the
|
|
305
|
-
* terminal appears "stuck").
|
|
306
|
-
*
|
|
307
|
-
|
|
288
|
+
* terminal appears "stuck"). Exactly like Omnigent's definitive pane probe,
|
|
289
|
+
* every command error is `unknown`; only `#{pane_dead}=1` is `dead`. */
|
|
290
|
+
livenessAsync() {
|
|
308
291
|
if (!this.started)
|
|
309
|
-
return Promise.resolve(
|
|
310
|
-
|
|
292
|
+
return Promise.resolve("dead");
|
|
293
|
+
if (this.paneLivenessFlight)
|
|
294
|
+
return this.paneLivenessFlight;
|
|
295
|
+
const flight = new Promise((resolve) => {
|
|
311
296
|
execFile(this.tmuxBin, [...this.base(), "list-panes", "-t", TMUX_TARGET, "-F", "#{pane_dead}"], { timeout: 2000 }, (err, stdout) => {
|
|
312
297
|
if (err) {
|
|
313
|
-
resolve(
|
|
298
|
+
resolve("unknown");
|
|
314
299
|
return;
|
|
315
300
|
}
|
|
316
301
|
const panes = stdout.toString().split(/\s+/).filter(Boolean);
|
|
317
|
-
|
|
302
|
+
if (panes.includes("1")) {
|
|
303
|
+
resolve("dead");
|
|
304
|
+
}
|
|
305
|
+
else if (panes.length > 0) {
|
|
306
|
+
resolve("alive");
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
resolve("unknown");
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
this.paneLivenessFlight = flight;
|
|
314
|
+
void flight.then(() => {
|
|
315
|
+
if (this.paneLivenessFlight === flight)
|
|
316
|
+
delete this.paneLivenessFlight;
|
|
317
|
+
}, () => {
|
|
318
|
+
if (this.paneLivenessFlight === flight)
|
|
319
|
+
delete this.paneLivenessFlight;
|
|
320
|
+
});
|
|
321
|
+
return flight;
|
|
322
|
+
}
|
|
323
|
+
/** Omnigent's always-on terminal lifecycle watcher first captures the pane:
|
|
324
|
+
* a control command that ran and reports the target missing is terminal exit;
|
|
325
|
+
* a probe that cannot spawn is inconclusive. If capture succeeds, the normal
|
|
326
|
+
* definitive `pane_dead` probe distinguishes live from exited. */
|
|
327
|
+
lifecycleLivenessAsync() {
|
|
328
|
+
if (!this.started)
|
|
329
|
+
return Promise.resolve("dead");
|
|
330
|
+
if (this.lifecycleLivenessFlight)
|
|
331
|
+
return this.lifecycleLivenessFlight;
|
|
332
|
+
const flight = new Promise((resolve) => {
|
|
333
|
+
execFile(this.tmuxBin, [...this.base(), "capture-pane", "-t", TMUX_TARGET, "-p", "-e"], (error, stdout) => {
|
|
334
|
+
if (error) {
|
|
335
|
+
const code = typeof error === "object" && error && "code" in error
|
|
336
|
+
? error.code
|
|
337
|
+
: undefined;
|
|
338
|
+
resolve(typeof code === "number" ? "dead" : "unknown");
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
this.lastPaneSnapshot = stdout.toString();
|
|
342
|
+
void this.livenessAsync().then((liveness) => {
|
|
343
|
+
// A successful capture proves the target exists. Omnigent treats a
|
|
344
|
+
// subsequent pane-dead probe failure as non-dead for this tick.
|
|
345
|
+
resolve(liveness === "dead" ? "dead" : "alive");
|
|
346
|
+
});
|
|
318
347
|
});
|
|
319
348
|
});
|
|
349
|
+
this.lifecycleLivenessFlight = flight;
|
|
350
|
+
void flight.then(() => {
|
|
351
|
+
if (this.lifecycleLivenessFlight === flight)
|
|
352
|
+
delete this.lifecycleLivenessFlight;
|
|
353
|
+
}, () => {
|
|
354
|
+
if (this.lifecycleLivenessFlight === flight)
|
|
355
|
+
delete this.lifecycleLivenessFlight;
|
|
356
|
+
});
|
|
357
|
+
return flight;
|
|
358
|
+
}
|
|
359
|
+
/** Compatibility boolean for callers that cannot represent an inconclusive
|
|
360
|
+
* probe. Unknown must remain live so a transient tmux failure cannot tear down
|
|
361
|
+
* a healthy native Session. */
|
|
362
|
+
async isAliveAsync() {
|
|
363
|
+
return await this.livenessAsync() !== "dead";
|
|
320
364
|
}
|
|
321
365
|
/** PID of the process currently owning the pane. */
|
|
322
366
|
panePid() {
|
|
@@ -344,13 +388,15 @@ export class TmuxTerminal {
|
|
|
344
388
|
*/
|
|
345
389
|
capturePane() {
|
|
346
390
|
try {
|
|
347
|
-
|
|
391
|
+
const snapshot = execFileSync(this.tmuxBin, [...this.base(), "capture-pane", "-t", TMUX_TARGET, "-p"], {
|
|
348
392
|
env: this.env,
|
|
349
393
|
encoding: "utf8",
|
|
350
394
|
});
|
|
395
|
+
this.lastPaneSnapshot = snapshot;
|
|
396
|
+
return snapshot;
|
|
351
397
|
}
|
|
352
398
|
catch {
|
|
353
|
-
return
|
|
399
|
+
return this.lastPaneSnapshot;
|
|
354
400
|
}
|
|
355
401
|
}
|
|
356
402
|
/** Send a submit Enter as a KEY NAME (no `-l`), committing the input line.
|
|
@@ -443,8 +489,8 @@ export class TmuxTerminal {
|
|
|
443
489
|
return;
|
|
444
490
|
// ASYNC probe (never execFileSync here — that would block the event loop
|
|
445
491
|
// and freeze the PTY stream, making the terminal unresponsive).
|
|
446
|
-
void this.
|
|
447
|
-
if (
|
|
492
|
+
void this.livenessAsync().then((liveness) => {
|
|
493
|
+
if (liveness === "dead" && !exited)
|
|
448
494
|
fireExit(0);
|
|
449
495
|
});
|
|
450
496
|
}, 1000);
|
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.23",
|
|
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.23"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/ws": "^8.18.1"
|