muuuuse 3.3.3 → 3.3.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muuuuse",
3
- "version": "3.3.3",
3
+ "version": "3.3.5",
4
4
  "description": "🔌Muuuuse arms regular terminals and relays assistant output across signed terminal links.",
5
5
  "type": "commonjs",
6
6
  "bin": {
package/src/agents.js CHANGED
@@ -179,22 +179,73 @@ function readCodexSeatClaim(sessionId) {
179
179
  return null;
180
180
  }
181
181
 
182
- const snapshotPath = path.join(CODEX_SNAPSHOT_ROOT, `${sessionId}.sh`);
182
+ const claims = readCodexSeatClaims(sessionId);
183
+ return claims[0] || null;
184
+ }
185
+
186
+ function readCodexSeatClaims(sessionId) {
187
+ if (!sessionId) {
188
+ return [];
189
+ }
190
+
191
+ const candidatePaths = listCodexSnapshotPaths(sessionId);
192
+ return candidatePaths
193
+ .map((snapshotPath) => {
194
+ let text;
195
+ try {
196
+ text = fs.readFileSync(snapshotPath, "utf8");
197
+ } catch {
198
+ return null;
199
+ }
200
+
201
+ const seatMatch = text.match(/declare -x MUUUUSE_SEAT="([^"]+)"/);
202
+ const sessionMatch = text.match(/declare -x MUUUUSE_SESSION="([^"]+)"/);
203
+ if (!seatMatch || !sessionMatch) {
204
+ return null;
205
+ }
206
+
207
+ let mtimeMs = 0;
208
+ try {
209
+ mtimeMs = fs.statSync(snapshotPath).mtimeMs;
210
+ } catch {
211
+ // Best effort only.
212
+ }
213
+
214
+ return {
215
+ seatId: seatMatch[1],
216
+ sessionName: sessionMatch[1],
217
+ snapshotPath,
218
+ mtimeMs,
219
+ };
220
+ })
221
+ .filter((claim) => claim !== null)
222
+ .sort((left, right) => right.mtimeMs - left.mtimeMs || left.snapshotPath.localeCompare(right.snapshotPath));
223
+ }
224
+
225
+ function listCodexSnapshotPaths(sessionId) {
226
+ const exactName = `${sessionId}.sh`;
227
+ const prefix = `${sessionId}.`;
228
+ const candidateNames = [];
229
+
183
230
  try {
184
- const text = fs.readFileSync(snapshotPath, "utf8");
185
- const seatMatch = text.match(/declare -x MUUUUSE_SEAT="([^"]+)"/);
186
- const sessionMatch = text.match(/declare -x MUUUUSE_SESSION="([^"]+)"/);
187
- if (!seatMatch || !sessionMatch) {
188
- return null;
189
- }
231
+ for (const entry of fs.readdirSync(CODEX_SNAPSHOT_ROOT, { withFileTypes: true })) {
232
+ if (!entry.isFile()) {
233
+ continue;
234
+ }
190
235
 
191
- return {
192
- seatId: seatMatch[1],
193
- sessionName: sessionMatch[1],
194
- };
236
+ if (entry.name === exactName || (entry.name.startsWith(prefix) && entry.name.endsWith(".sh"))) {
237
+ candidateNames.push(entry.name);
238
+ }
239
+ }
195
240
  } catch {
196
- return null;
241
+ // Snapshot directory may not exist yet.
197
242
  }
243
+
244
+ return uniquePaths(candidateNames.map((name) => path.join(CODEX_SNAPSHOT_ROOT, name)));
245
+ }
246
+
247
+ function candidateHasClaim(candidate, predicate) {
248
+ return readCodexSeatClaims(candidate.sessionId).some((claim) => predicate(claim));
198
249
  }
199
250
 
200
251
  function selectClaimedCodexCandidatePath(candidates, options = {}) {
@@ -204,29 +255,28 @@ function selectClaimedCodexCandidatePath(candidates, options = {}) {
204
255
  return null;
205
256
  }
206
257
 
207
- const annotated = candidates.map((candidate) => ({
208
- ...candidate,
209
- claim: readCodexSeatClaim(candidate.sessionId),
210
- }));
211
-
212
- const exactMatches = annotated.filter((candidate) => (
213
- candidate.claim?.seatId === seatId &&
214
- candidate.claim?.sessionName === sessionName
258
+ const exactMatches = candidates.filter((candidate) => (
259
+ candidateHasClaim(candidate, (claim) => (
260
+ claim.seatId === seatId &&
261
+ claim.sessionName === sessionName
262
+ ))
215
263
  ));
216
264
  if (exactMatches.length === 1) {
217
265
  return exactMatches[0].path;
218
266
  }
219
267
 
220
- const otherSeatClaims = annotated.filter((candidate) => (
221
- candidate.claim?.sessionName === sessionName &&
222
- candidate.claim?.seatId !== seatId
268
+ const otherSeatClaims = candidates.filter((candidate) => (
269
+ candidateHasClaim(candidate, (claim) => (
270
+ claim.sessionName === sessionName &&
271
+ claim.seatId !== seatId
272
+ ))
223
273
  ));
224
274
  if (otherSeatClaims.length === 0) {
225
275
  return null;
226
276
  }
227
277
 
228
278
  const foreignPaths = new Set(otherSeatClaims.map((candidate) => candidate.path));
229
- const remaining = annotated.filter((candidate) => !foreignPaths.has(candidate.path));
279
+ const remaining = candidates.filter((candidate) => !foreignPaths.has(candidate.path));
230
280
  if (remaining.length === 1) {
231
281
  return remaining[0].path;
232
282
  }
@@ -332,8 +382,10 @@ function selectExactClaimedCodexCandidate(candidates, options = {}, processStart
332
382
 
333
383
  const exactMatches = rankCodexCandidates(
334
384
  candidates.filter((candidate) => {
335
- const claim = readCodexSeatClaim(candidate.sessionId);
336
- return claim?.seatId === seatId && claim?.sessionName === sessionName;
385
+ return candidateHasClaim(candidate, (claim) => (
386
+ claim.seatId === seatId &&
387
+ claim.sessionName === sessionName
388
+ ));
337
389
  }),
338
390
  processStartedAtMs
339
391
  );
@@ -349,8 +401,11 @@ function filterForeignClaimedCodexCandidates(candidates, options = {}) {
349
401
  }
350
402
 
351
403
  return candidates.filter((candidate) => {
352
- const claim = readCodexSeatClaim(candidate.sessionId);
353
- return !(claim?.sessionName === sessionName && claim?.seatId && claim.seatId !== seatId);
404
+ return !candidateHasClaim(candidate, (claim) => (
405
+ claim.sessionName === sessionName &&
406
+ claim.seatId &&
407
+ claim.seatId !== seatId
408
+ ));
354
409
  });
355
410
  }
356
411
 
@@ -391,8 +446,11 @@ function selectCodexCandidatePath(candidates, currentPath, processStartedAtMs, o
391
446
  seatId &&
392
447
  sessionName &&
393
448
  cwdMatches.some((candidate) => {
394
- const claim = readCodexSeatClaim(candidate.sessionId);
395
- return claim?.sessionName === sessionName && claim?.seatId && claim.seatId !== seatId;
449
+ return candidateHasClaim(candidate, (claim) => (
450
+ claim.sessionName === sessionName &&
451
+ claim.seatId &&
452
+ claim.seatId !== seatId
453
+ ));
396
454
  })
397
455
  );
398
456
  const allowedMatches = filterForeignClaimedCodexCandidates(cwdMatches, options);
package/src/runtime.js CHANGED
@@ -56,8 +56,11 @@ const EMITTED_ANSWER_TTL_MS = 5 * 60 * 1000;
56
56
  const MAX_RECENT_INBOUND_RELAYS = 12;
57
57
  const MAX_RECENT_EMITTED_ANSWERS = 48;
58
58
  const STOP_FORCE_KILL_MS = 1200;
59
+ const STOP_PURGE_WAIT_MS = STOP_FORCE_KILL_MS + 1200;
60
+ const STOP_PURGE_POLL_MS = 60;
59
61
  const SEAT_JOIN_WAIT_MS = 3000;
60
62
  const SEAT_JOIN_POLL_MS = 60;
63
+ const MAX_PENDING_PASSIVE_INPUT_CHARS = 512;
61
64
  const CHILD_ENV_DROP_KEYS = [
62
65
  "CODEX_CI",
63
66
  "CODEX_MANAGED_BY_NPM",
@@ -200,6 +203,7 @@ function ensureSeatGeminiCliHome(homeDir, cwd, seatId, baseEnv = process.env) {
200
203
  const sourceHomeRoot = String(baseEnv.GEMINI_CLI_HOME || homeDir).trim() || homeDir;
201
204
  const sourceGeminiDir = path.join(sourceHomeRoot, ".gemini");
202
205
  const targetHomeRoot = getSeatGeminiCliHome(homeDir, cwd, seatId);
206
+ fs.rmSync(targetHomeRoot, { recursive: true, force: true });
203
207
  const targetGeminiDir = ensureDir(path.join(targetHomeRoot, ".gemini"));
204
208
 
205
209
  let sourceEntries = [];
@@ -653,6 +657,103 @@ function isMeaningfulTerminalInput(input) {
653
657
  return stripPassiveTerminalInput(input).length > 0;
654
658
  }
655
659
 
660
+ function consumeTerminalProxyInput(input, pendingPassiveInput = "") {
661
+ const combined = `${pendingPassiveInput}${String(input || "")}`;
662
+ let forwardText = "";
663
+ let index = 0;
664
+
665
+ while (index < combined.length) {
666
+ const current = combined[index];
667
+ if (current !== "\u001b") {
668
+ forwardText += current;
669
+ index += 1;
670
+ continue;
671
+ }
672
+
673
+ if (index + 1 >= combined.length) {
674
+ forwardText += current;
675
+ index += 1;
676
+ continue;
677
+ }
678
+
679
+ const next = combined[index + 1];
680
+ if (next === "]") {
681
+ const belIndex = combined.indexOf("\u0007", index + 2);
682
+ const stIndex = combined.indexOf("\u001b\\", index + 2);
683
+ const endIndex = (
684
+ belIndex !== -1 && stIndex !== -1 ? Math.min(belIndex, stIndex) :
685
+ belIndex !== -1 ? belIndex :
686
+ stIndex
687
+ );
688
+
689
+ if (endIndex === -1) {
690
+ const pending = combined.slice(index).slice(0, MAX_PENDING_PASSIVE_INPUT_CHARS);
691
+ return {
692
+ forwardText,
693
+ meaningful: isMeaningfulTerminalInput(forwardText),
694
+ pendingPassiveInput: pending,
695
+ };
696
+ }
697
+
698
+ index = endIndex + (endIndex === stIndex ? 2 : 1);
699
+ continue;
700
+ }
701
+
702
+ if (next === "[") {
703
+ if (index + 2 >= combined.length) {
704
+ return {
705
+ forwardText,
706
+ meaningful: isMeaningfulTerminalInput(forwardText),
707
+ pendingPassiveInput: combined.slice(index).slice(0, MAX_PENDING_PASSIVE_INPUT_CHARS),
708
+ };
709
+ }
710
+
711
+ let endIndex = index + 2;
712
+ while (endIndex < combined.length && !/[@-~]/.test(combined[endIndex])) {
713
+ endIndex += 1;
714
+ }
715
+
716
+ if (endIndex >= combined.length) {
717
+ const pending = combined.slice(index);
718
+ if (/^\u001b\[(?:\??[0-9;]*)?$/.test(pending)) {
719
+ return {
720
+ forwardText,
721
+ meaningful: isMeaningfulTerminalInput(forwardText),
722
+ pendingPassiveInput: pending.slice(0, MAX_PENDING_PASSIVE_INPUT_CHARS),
723
+ };
724
+ }
725
+
726
+ forwardText += pending;
727
+ break;
728
+ }
729
+
730
+ const sequence = combined.slice(index, endIndex + 1);
731
+ if (
732
+ sequence === "\u001b[I" ||
733
+ sequence === "\u001b[O" ||
734
+ /^\u001b\[\d+;\d+R$/.test(sequence) ||
735
+ /^\u001b\[\?[0-9;]*c$/.test(sequence)
736
+ ) {
737
+ index = endIndex + 1;
738
+ continue;
739
+ }
740
+
741
+ forwardText += sequence;
742
+ index = endIndex + 1;
743
+ continue;
744
+ }
745
+
746
+ forwardText += current;
747
+ index += 1;
748
+ }
749
+
750
+ return {
751
+ forwardText,
752
+ meaningful: isMeaningfulTerminalInput(forwardText),
753
+ pendingPassiveInput: "",
754
+ };
755
+ }
756
+
656
757
  async function sendTextAndEnter(child, text, shouldAbort = () => false) {
657
758
  const options = typeof shouldAbort === "function" ? { shouldAbort } : (shouldAbort || {});
658
759
  const shouldStop = typeof options.shouldAbort === "function" ? options.shouldAbort : () => false;
@@ -730,6 +831,7 @@ class ArmedSeat {
730
831
  this.forceKillTimer = null;
731
832
  this.identity = loadOrCreateSeatIdentity(this.paths);
732
833
  this.lastUserInputAtMs = 0;
834
+ this.pendingPassiveInput = "";
733
835
  this.pendingInboundContext = null;
734
836
  this.recentInboundRelays = [];
735
837
  this.recentEmittedAnswers = [];
@@ -843,14 +945,16 @@ class ArmedSeat {
843
945
  installStdinProxy() {
844
946
  const handleData = (chunk) => {
845
947
  const chunkText = chunk.toString("utf8");
846
- if (isMeaningfulTerminalInput(chunkText)) {
948
+ const proxyInput = consumeTerminalProxyInput(chunkText, this.pendingPassiveInput);
949
+ this.pendingPassiveInput = proxyInput.pendingPassiveInput;
950
+ if (proxyInput.meaningful) {
847
951
  this.lastUserInputAtMs = Date.now();
848
952
  this.pendingInboundContext = null;
849
953
  }
850
- if (!this.child) {
954
+ if (!this.child || proxyInput.forwardText.length === 0) {
851
955
  return;
852
956
  }
853
- this.child.write(chunkText);
957
+ this.child.write(proxyInput.forwardText);
854
958
  };
855
959
 
856
960
  const handleEnd = () => {
@@ -1295,7 +1399,10 @@ class ArmedSeat {
1295
1399
  this.liveState.lastMessageId = null;
1296
1400
  const sessionStartedAtMs = readSessionFileStartedAtMs(detectedAgent.type, resolvedSessionFile);
1297
1401
  if (Number.isFinite(sessionStartedAtMs)) {
1298
- this.liveState.captureSinceMs = Math.min(this.liveState.captureSinceMs, sessionStartedAtMs);
1402
+ this.liveState.captureSinceMs = Math.max(
1403
+ this.startedAtMs,
1404
+ Math.min(this.liveState.captureSinceMs, sessionStartedAtMs)
1405
+ );
1299
1406
  }
1300
1407
  }
1301
1408
 
@@ -1534,6 +1641,8 @@ class ArmedSeat {
1534
1641
  exitedAt: new Date().toISOString(),
1535
1642
  state: "exited",
1536
1643
  });
1644
+
1645
+ purgeSeatTransientState(this.sessionPaths.dir, this.paths.dir, this.cwd, this.seatId);
1537
1646
  }
1538
1647
  }
1539
1648
 
@@ -1658,6 +1767,10 @@ function stopAllSessions() {
1658
1767
  }
1659
1768
  }
1660
1769
 
1770
+ for (const session of report.sessions) {
1771
+ finalizeStoppedSession(session);
1772
+ }
1773
+
1661
1774
  return {
1662
1775
  requestedAt,
1663
1776
  sessions: report.sessions,
@@ -1667,6 +1780,7 @@ function stopAllSessions() {
1667
1780
  module.exports = {
1668
1781
  ArmedSeat,
1669
1782
  buildChildEnv,
1783
+ consumeTerminalProxyInput,
1670
1784
  ensureSeatGeminiCliHome,
1671
1785
  chunkRelayPayloadForTyping,
1672
1786
  getStatusReport,
@@ -1691,6 +1805,77 @@ function signalPid(pid, signal) {
1691
1805
  }
1692
1806
  }
1693
1807
 
1808
+ function finalizeStoppedSession(session, timeoutMs = STOP_PURGE_WAIT_MS) {
1809
+ if (!session || !Array.isArray(session.seats) || session.seats.length === 0) {
1810
+ return;
1811
+ }
1812
+
1813
+ const deadline = Date.now() + timeoutMs;
1814
+ while (Date.now() <= deadline) {
1815
+ const pendingSeats = session.seats.filter((seat) => isPidAlive(seat.wrapperPid) || isPidAlive(seat.childPid));
1816
+ if (pendingSeats.length === 0) {
1817
+ break;
1818
+ }
1819
+ sleepSync(STOP_PURGE_POLL_MS);
1820
+ }
1821
+
1822
+ for (const seat of session.seats) {
1823
+ if (isPidAlive(seat.childPid)) {
1824
+ signalProcessFamily(seat.childPid, "SIGKILL");
1825
+ }
1826
+ if (isPidAlive(seat.wrapperPid)) {
1827
+ signalPid(seat.wrapperPid, "SIGKILL");
1828
+ }
1829
+ }
1830
+
1831
+ sleepSync(STOP_PURGE_POLL_MS);
1832
+
1833
+ const sessionDir = getSessionPaths(session.sessionName).dir;
1834
+ for (const seat of session.seats) {
1835
+ purgeSeatTransientState(sessionDir, getSeatPaths(session.sessionName, seat.seatId).dir, seat.cwd, seat.seatId);
1836
+ }
1837
+ }
1838
+
1839
+ function purgeSeatTransientState(sessionDir, seatDir, cwd, seatId) {
1840
+ let geminiSessionDir = null;
1841
+ try {
1842
+ fs.rmSync(seatDir, { recursive: true, force: true });
1843
+ } catch {
1844
+ // Best effort only.
1845
+ }
1846
+
1847
+ try {
1848
+ const homeDir = String(process.env.HOME || "").trim() || "/root";
1849
+ const geminiSeatHome = getSeatGeminiCliHome(homeDir, cwd, seatId);
1850
+ geminiSessionDir = path.dirname(geminiSeatHome);
1851
+ fs.rmSync(geminiSeatHome, { recursive: true, force: true });
1852
+ } catch {
1853
+ // Best effort only.
1854
+ }
1855
+
1856
+ if (geminiSessionDir) {
1857
+ try {
1858
+ const remainingGeminiSeatDirs = fs.readdirSync(geminiSessionDir, { withFileTypes: true })
1859
+ .filter((entry) => entry.isDirectory() && /^seat-\d+$/.test(entry.name));
1860
+ if (remainingGeminiSeatDirs.length === 0) {
1861
+ fs.rmSync(geminiSessionDir, { recursive: true, force: true });
1862
+ }
1863
+ } catch {
1864
+ // Best effort only.
1865
+ }
1866
+ }
1867
+
1868
+ try {
1869
+ const remainingSeatDirs = fs.readdirSync(sessionDir, { withFileTypes: true })
1870
+ .filter((entry) => entry.isDirectory() && /^seat-\d+$/.test(entry.name));
1871
+ if (remainingSeatDirs.length === 0) {
1872
+ fs.rmSync(sessionDir, { recursive: true, force: true });
1873
+ }
1874
+ } catch {
1875
+ // Best effort only.
1876
+ }
1877
+ }
1878
+
1694
1879
  function signalProcessTree(rootPid, signal) {
1695
1880
  const descendants = getChildProcesses(rootPid);
1696
1881
  let delivered = 0;