@sma1lboy/rove 0.9.72 → 0.9.74

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.
@@ -186,6 +186,7 @@ async function nodePtyDriver(spawn) {
186
186
  }
187
187
 
188
188
  // ../kobe-daemon/src/daemon/pty-server.ts
189
+ import { readFileSync as readFileSync4 } from "node:fs";
189
190
  import { mkdir as mkdir2, unlink as unlink2, writeFile } from "node:fs/promises";
190
191
  import { createServer } from "node:net";
191
192
  import { dirname as dirname3 } from "node:path";
@@ -375,15 +376,33 @@ function frameToLine(frame) {
375
376
  // ../kobe-daemon/src/daemon/pty-exit-store.ts
376
377
  import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
377
378
  import { dirname as dirname2 } from "node:path";
379
+
380
+ // ../kobe-daemon/src/daemon/terminal-rows.ts
381
+ var CURSOR_DOWN_RE = /\x1b\[(\d*)[BE]/g;
382
+ var CURSOR_POSITION_RE = /\x1b\[[\d;]*[Hf]/g;
383
+ var MAX_ROWS_PER_ESCAPE = 200;
384
+ var ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?|\x1b[@-_]/g;
385
+ function breakRowsOnCursorMotion(raw) {
386
+ return raw.replace(CURSOR_DOWN_RE, (_m, count) => `
387
+ `.repeat(Math.min(MAX_ROWS_PER_ESCAPE, Math.max(1, Number.parseInt(count, 10) || 1)))).replace(CURSOR_POSITION_RE, `
388
+ `);
389
+ }
390
+ function terminalRows(raw, maxLineChars) {
391
+ const plain = breakRowsOnCursorMotion(raw).replace(ANSI_RE, "").replace(/\r\n/g, `
392
+ `);
393
+ return plain.split(`
394
+ `).map((line) => {
395
+ const overwritten = line.split("\r").pop() ?? "";
396
+ return maxLineChars === undefined ? overwritten : overwritten.slice(0, maxLineChars);
397
+ });
398
+ }
399
+
400
+ // ../kobe-daemon/src/daemon/pty-exit-store.ts
378
401
  var MAX_RECORDS = 50;
379
402
  var TAIL_LINES = 40;
380
403
  var TAIL_LINE_CHARS = 500;
381
- var ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?|\x1b[@-_]/g;
382
404
  function plainTail(raw) {
383
- const plain = raw.replace(ANSI_RE, "").replace(/\r\n/g, `
384
- `);
385
- const lines = plain.split(`
386
- `).map((line) => (line.split("\r").pop() ?? "").slice(0, TAIL_LINE_CHARS));
405
+ const lines = terminalRows(raw, TAIL_LINE_CHARS);
387
406
  while (lines.length > 0 && (lines[lines.length - 1] ?? "").trim() === "")
388
407
  lines.pop();
389
408
  return lines.slice(-TAIL_LINES);
@@ -1285,6 +1304,14 @@ class PtyHost {
1285
1304
 
1286
1305
  // ../kobe-daemon/src/daemon/pty-server.ts
1287
1306
  var DEFAULT_IDLE_EXIT_MS = 60000;
1307
+ var DEFAULT_ORPHAN_CHECK_MS = 30000;
1308
+ function resolveMaxLifetimeMs() {
1309
+ const raw = process.env.KOBE_PTY_MAX_LIFETIME_MS;
1310
+ if (raw === undefined)
1311
+ return null;
1312
+ const n = Number(raw);
1313
+ return Number.isFinite(n) && n > 0 ? n : null;
1314
+ }
1288
1315
  function resolveIdleExitMs() {
1289
1316
  const raw = process.env.KOBE_PTY_IDLE_EXIT_MS;
1290
1317
  if (raw === undefined)
@@ -1297,6 +1324,9 @@ async function startPtyHostServer(options = {}) {
1297
1324
  const pidPath = options.pidPath ?? defaultPtyHostPidPath();
1298
1325
  const freezeDir = options.freezeDir ?? defaultPtyFreezeDir();
1299
1326
  const idleExitMs = options.idleExitMs || resolveIdleExitMs();
1327
+ const orphanCheckMs = options.orphanCheckMs || DEFAULT_ORPHAN_CHECK_MS;
1328
+ const maxLifetimeMs = options.maxLifetimeMs ?? resolveMaxLifetimeMs();
1329
+ const bootedAtMs = Date.now();
1300
1330
  const log = options.log ?? (() => {});
1301
1331
  const clients = new Set;
1302
1332
  let stopping = false;
@@ -1318,6 +1348,31 @@ async function startPtyHostServer(options = {}) {
1318
1348
  stop();
1319
1349
  }, idleExitMs);
1320
1350
  };
1351
+ const orphaned = () => {
1352
+ if (maxLifetimeMs !== null && Date.now() - bootedAtMs >= maxLifetimeMs) {
1353
+ return `exceeded its ${maxLifetimeMs}ms lifetime ceiling`;
1354
+ }
1355
+ let owner;
1356
+ try {
1357
+ owner = readFileSync4(pidPath, "utf8").trim();
1358
+ } catch {
1359
+ return `pidfile ${pidPath} is gone`;
1360
+ }
1361
+ const pid = Number.parseInt(owner, 10);
1362
+ if (pid === process.pid)
1363
+ return null;
1364
+ return Number.isInteger(pid) ? `pidfile ${pidPath} now names pid ${pid}` : `pidfile ${pidPath} is unreadable`;
1365
+ };
1366
+ const orphanTimer = setInterval(() => {
1367
+ if (stopping)
1368
+ return;
1369
+ const reason = orphaned();
1370
+ if (!reason)
1371
+ return;
1372
+ log("orphan", `${reason} — exiting rather than outliving my owner`);
1373
+ stop();
1374
+ }, orphanCheckMs);
1375
+ orphanTimer.unref?.();
1321
1376
  const ptys = new PtyHost({
1322
1377
  onSessionStart: cancelIdle,
1323
1378
  onSessionEnd: () => {
@@ -1371,6 +1426,7 @@ async function startPtyHostServer(options = {}) {
1371
1426
  return;
1372
1427
  stopping = true;
1373
1428
  cancelIdle();
1429
+ clearInterval(orphanTimer);
1374
1430
  await ptys.shutdown();
1375
1431
  if (wipeFreezeOnStop)
1376
1432
  clearFrozenSessions(freezeDir);