muuuuse 2.2.0 → 2.2.2

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/runtime.js +86 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muuuuse",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "🔌Muuuuse arms two regular terminals and relays assistant output between them.",
5
5
  "type": "commonjs",
6
6
  "bin": {
package/src/runtime.js CHANGED
@@ -35,7 +35,9 @@ const {
35
35
  writeJson,
36
36
  } = require("./util");
37
37
 
38
- const TYPE_DELAY_MS = 70;
38
+ const TYPE_CHUNK_DELAY_MS = 12;
39
+ const TYPE_CHUNK_SIZE = 6;
40
+ const ESCAPE_RELAY_GUARD_MS = 180;
39
41
  const MIRROR_SUPPRESSION_WINDOW_MS = 30 * 1000;
40
42
  const PENDING_RELAY_CONTEXT_TTL_MS = 2 * 60 * 1000;
41
43
  const EMITTED_ANSWER_TTL_MS = 5 * 60 * 1000;
@@ -435,24 +437,77 @@ function readSeatChallenge(paths, sessionName) {
435
437
  };
436
438
  }
437
439
 
438
- async function sendTextAndEnter(child, text, shouldAbort = () => false) {
439
- const payload = String(text || "")
440
+ function normalizeRelayPayloadForTyping(text) {
441
+ return String(text || "")
440
442
  .replace(/\r/g, "")
441
443
  .replace(/\s*\n+\s*/g, " ")
442
444
  .replace(/[ \t]{2,}/g, " ")
443
445
  .trim();
446
+ }
447
+
448
+ function chunkRelayPayloadForTyping(text, chunkSize = TYPE_CHUNK_SIZE) {
449
+ const normalized = String(text || "");
450
+ if (!normalized) {
451
+ return [];
452
+ }
453
+
454
+ const size = Number.isInteger(chunkSize) && chunkSize > 0 ? chunkSize : TYPE_CHUNK_SIZE;
455
+ const chunks = [];
456
+ for (let index = 0; index < normalized.length; index += size) {
457
+ chunks.push(normalized.slice(index, index + size));
458
+ }
459
+ return chunks;
460
+ }
461
+
462
+ function stripPassiveTerminalInput(input) {
463
+ return String(input || "")
464
+ .replace(/\u001b\][\s\S]*?(?:\u0007|\u001b\\)/g, "")
465
+ .replace(/\u001b\[(?:I|O)/g, "")
466
+ .replace(/\u001b\[\d+;\d+R/g, "")
467
+ .replace(/\u001b\[\?[0-9;]*c/g, "")
468
+ .replace(/\u0000/g, "");
469
+ }
470
+
471
+ function isBareEscapeInput(input) {
472
+ return String(input || "") === "\u001b";
473
+ }
474
+
475
+ function isMeaningfulTerminalInput(input) {
476
+ if (isBareEscapeInput(input)) {
477
+ return false;
478
+ }
479
+ return stripPassiveTerminalInput(input).length > 0;
480
+ }
481
+
482
+ function getEscapeRelayDelayMs(lastEscapeAtMs, now = Date.now()) {
483
+ if (!Number.isFinite(lastEscapeAtMs) || lastEscapeAtMs <= 0) {
484
+ return 0;
485
+ }
486
+
487
+ const elapsedMs = now - lastEscapeAtMs;
488
+ if (elapsedMs >= ESCAPE_RELAY_GUARD_MS) {
489
+ return 0;
490
+ }
491
+
492
+ return ESCAPE_RELAY_GUARD_MS - elapsedMs;
493
+ }
494
+
495
+ async function sendTextAndEnter(child, text, shouldAbort = () => false) {
496
+ const payload = normalizeRelayPayloadForTyping(text);
444
497
 
445
498
  if (payload.length > 0) {
446
- if (shouldAbort() || !child) {
447
- return false;
448
- }
499
+ for (const chunk of chunkRelayPayloadForTyping(payload)) {
500
+ if (shouldAbort() || !child) {
501
+ return false;
502
+ }
449
503
 
450
- try {
451
- child.write(payload);
452
- } catch {
453
- return false;
504
+ try {
505
+ child.write(chunk);
506
+ } catch {
507
+ return false;
508
+ }
509
+ await sleep(TYPE_CHUNK_DELAY_MS);
454
510
  }
455
- await sleep(TYPE_DELAY_MS);
456
511
  }
457
512
 
458
513
  if (shouldAbort() || !child) {
@@ -496,6 +551,7 @@ class ArmedSeat {
496
551
  this.forceKillTimer = null;
497
552
  this.identity = null;
498
553
  this.lastUserInputAtMs = 0;
554
+ this.lastEscapeAtMs = 0;
499
555
  this.pendingInboundContext = null;
500
556
  this.recentInboundRelays = [];
501
557
  this.recentEmittedAnswers = [];
@@ -772,12 +828,18 @@ class ArmedSeat {
772
828
 
773
829
  installStdinProxy() {
774
830
  const handleData = (chunk) => {
775
- this.lastUserInputAtMs = Date.now();
776
- this.pendingInboundContext = null;
831
+ const chunkText = chunk.toString("utf8");
832
+ if (isBareEscapeInput(chunkText)) {
833
+ this.lastEscapeAtMs = Date.now();
834
+ }
835
+ if (isMeaningfulTerminalInput(chunkText)) {
836
+ this.lastUserInputAtMs = Date.now();
837
+ this.pendingInboundContext = null;
838
+ }
777
839
  if (!this.child) {
778
840
  return;
779
841
  }
780
- this.child.write(chunk.toString("utf8"));
842
+ this.child.write(chunkText);
781
843
  };
782
844
 
783
845
  const handleEnd = () => {
@@ -932,6 +994,11 @@ class ArmedSeat {
932
994
  continue;
933
995
  }
934
996
 
997
+ const escapeRelayDelayMs = getEscapeRelayDelayMs(this.lastEscapeAtMs);
998
+ if (escapeRelayDelayMs > 0) {
999
+ await sleep(escapeRelayDelayMs);
1000
+ }
1001
+
935
1002
  const delivered = await sendTextAndEnter(
936
1003
  this.child,
937
1004
  payload,
@@ -1475,7 +1542,12 @@ function stopAllSessions() {
1475
1542
  module.exports = {
1476
1543
  ArmedSeat,
1477
1544
  buildChildEnv,
1545
+ chunkRelayPayloadForTyping,
1546
+ getEscapeRelayDelayMs,
1478
1547
  getStatusReport,
1548
+ isBareEscapeInput,
1549
+ isMeaningfulTerminalInput,
1550
+ normalizeRelayPayloadForTyping,
1479
1551
  resolveSessionName,
1480
1552
  stopAllSessions,
1481
1553
  };