muuuuse 3.3.2 → 3.3.3

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.2",
3
+ "version": "3.3.3",
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
@@ -5,6 +5,7 @@ const path = require("node:path");
5
5
 
6
6
  const {
7
7
  getFileSize,
8
+ getSeatGeminiCliHome,
8
9
  hashText,
9
10
  readAppendedText,
10
11
  sanitizeRelayText,
@@ -18,6 +19,23 @@ const GEMINI_ROOT = path.join(os.homedir(), ".gemini", "tmp");
18
19
  const SESSION_START_EARLY_TOLERANCE_MS = 2 * 1000;
19
20
  const STRICT_SINGLE_CANDIDATE_EARLY_TOLERANCE_MS = 250;
20
21
 
22
+ function uniquePaths(paths) {
23
+ return [...new Set(paths.map((entry) => path.resolve(entry)))];
24
+ }
25
+
26
+ function getGeminiRoots(currentPath, options = {}) {
27
+ const roots = [GEMINI_ROOT];
28
+ const seatId = Number.parseInt(String(options.seatId || "").trim(), 10);
29
+ if (Number.isInteger(seatId) && seatId > 0) {
30
+ roots.unshift(path.join(
31
+ getSeatGeminiCliHome(os.homedir(), currentPath, seatId),
32
+ ".gemini",
33
+ "tmp"
34
+ ));
35
+ }
36
+ return uniquePaths(roots);
37
+ }
38
+
21
39
  function walkFiles(rootPath, predicate, results = []) {
22
40
  try {
23
41
  const entries = fs.readdirSync(rootPath, { withFileTypes: true });
@@ -642,14 +660,17 @@ function readGeminiCandidate(filePath) {
642
660
 
643
661
  function selectGeminiSessionFile(currentPath, processStartedAtMs, options = {}) {
644
662
  const projectHash = createHash("sha256").update(currentPath).digest("hex");
645
- const liveCandidates = readOpenSessionCandidates(options.pids ?? options.pid, GEMINI_ROOT, readGeminiCandidate)
663
+ const geminiRoots = getGeminiRoots(currentPath, options);
664
+ const liveCandidates = geminiRoots
665
+ .flatMap((rootPath) => readOpenSessionCandidates(options.pids ?? options.pid, rootPath, readGeminiCandidate))
646
666
  .filter((candidate) => candidate.projectHash === projectHash);
647
667
  const livePath = selectLiveSessionCandidatePath(liveCandidates, projectHash, options.captureSinceMs);
648
668
  if (livePath) {
649
669
  return livePath;
650
670
  }
651
671
 
652
- const candidates = walkFiles(GEMINI_ROOT, (filePath) => filePath.endsWith(".json"))
672
+ const candidates = geminiRoots
673
+ .flatMap((rootPath) => walkFiles(rootPath, (filePath) => filePath.endsWith(".json")))
653
674
  .map((filePath) => readGeminiCandidate(filePath))
654
675
  .filter((candidate) => candidate !== null && candidate.projectHash === projectHash);
655
676
 
package/src/runtime.js CHANGED
@@ -20,6 +20,7 @@ const {
20
20
  ensureDir,
21
21
  getDefaultSessionName,
22
22
  getFileSize,
23
+ getSeatGeminiCliHome,
23
24
  getSeatPaths,
24
25
  getSessionPaths,
25
26
  getStateRoot,
@@ -41,6 +42,14 @@ const {
41
42
  // A short settle delay keeps interactive CLIs from treating submit as another newline.
42
43
  const TYPE_CHUNK_DELAY_MS = 45;
43
44
  const TYPE_CHUNK_SIZE = 24;
45
+ const BRACKETED_PASTE_START = "\u001b[200~";
46
+ const BRACKETED_PASTE_END = "\u001b[201~";
47
+ const GEMINI_SHARED_ENTRY_NAMES = new Set([
48
+ "gemini-credentials.json",
49
+ "google_accounts.json",
50
+ "installation_id",
51
+ "mcp-oauth-tokens-v2.json",
52
+ ]);
44
53
  const MIRROR_SUPPRESSION_WINDOW_MS = 30 * 1000;
45
54
  const PENDING_RELAY_CONTEXT_TTL_MS = 2 * 60 * 1000;
46
55
  const EMITTED_ANSWER_TTL_MS = 5 * 60 * 1000;
@@ -144,6 +153,78 @@ function sanitizeChildPath(pathValue, homeDir) {
144
153
  return entries.join(path.delimiter);
145
154
  }
146
155
 
156
+ function readGeminiApiKeyFromHome(homeDir) {
157
+ const filePath = path.join(homeDir, "gemini.txt");
158
+ try {
159
+ const value = fs.readFileSync(filePath, "utf8").trim();
160
+ return value || null;
161
+ } catch {
162
+ return null;
163
+ }
164
+ }
165
+
166
+ function syncGeminiCliHomeEntry(sourcePath, targetPath) {
167
+ const shouldLink = GEMINI_SHARED_ENTRY_NAMES.has(path.basename(sourcePath));
168
+ try {
169
+ if (fs.lstatSync(targetPath).isSymbolicLink() && fs.realpathSync(targetPath) === sourcePath) {
170
+ return;
171
+ }
172
+ } catch {
173
+ // Recreate the target entry below when missing or mismatched.
174
+ }
175
+
176
+ fs.rmSync(targetPath, { recursive: true, force: true });
177
+
178
+ const sourceStats = fs.lstatSync(sourcePath);
179
+ if (shouldLink) {
180
+ try {
181
+ const linkType = process.platform === "win32"
182
+ ? (sourceStats.isDirectory() ? "junction" : "file")
183
+ : undefined;
184
+ fs.symlinkSync(sourcePath, targetPath, linkType);
185
+ return;
186
+ } catch {
187
+ // Fall through to copying when symlinks are unavailable.
188
+ }
189
+ }
190
+
191
+ if (sourceStats.isDirectory()) {
192
+ fs.cpSync(sourcePath, targetPath, { recursive: true });
193
+ return;
194
+ }
195
+
196
+ fs.copyFileSync(sourcePath, targetPath);
197
+ }
198
+
199
+ function ensureSeatGeminiCliHome(homeDir, cwd, seatId, baseEnv = process.env) {
200
+ const sourceHomeRoot = String(baseEnv.GEMINI_CLI_HOME || homeDir).trim() || homeDir;
201
+ const sourceGeminiDir = path.join(sourceHomeRoot, ".gemini");
202
+ const targetHomeRoot = getSeatGeminiCliHome(homeDir, cwd, seatId);
203
+ const targetGeminiDir = ensureDir(path.join(targetHomeRoot, ".gemini"));
204
+
205
+ let sourceEntries = [];
206
+ try {
207
+ sourceEntries = fs.readdirSync(sourceGeminiDir, { withFileTypes: true });
208
+ } catch {
209
+ ensureDir(path.join(targetGeminiDir, "tmp"));
210
+ return targetHomeRoot;
211
+ }
212
+
213
+ for (const entry of sourceEntries) {
214
+ if (entry.name === "tmp") {
215
+ continue;
216
+ }
217
+
218
+ syncGeminiCliHomeEntry(
219
+ path.join(sourceGeminiDir, entry.name),
220
+ path.join(targetGeminiDir, entry.name)
221
+ );
222
+ }
223
+
224
+ ensureDir(path.join(targetGeminiDir, "tmp"));
225
+ return targetHomeRoot;
226
+ }
227
+
147
228
  function buildChildEnv(seatId, sessionName, cwd, baseEnv = process.env) {
148
229
  const env = { ...baseEnv };
149
230
  for (const key of CHILD_ENV_DROP_KEYS) {
@@ -156,6 +237,13 @@ function buildChildEnv(seatId, sessionName, cwd, baseEnv = process.env) {
156
237
  env.TERM = resolveChildTerm(baseEnv);
157
238
  env.MUUUUSE_SEAT = String(seatId);
158
239
  env.MUUUUSE_SESSION = sessionName;
240
+ if (!String(env.GEMINI_API_KEY || "").trim()) {
241
+ const homeGeminiApiKey = readGeminiApiKeyFromHome(homeDir);
242
+ if (homeGeminiApiKey) {
243
+ env.GEMINI_API_KEY = homeGeminiApiKey;
244
+ }
245
+ }
246
+ env.GEMINI_CLI_HOME = getSeatGeminiCliHome(homeDir, cwd, seatId);
159
247
  return env;
160
248
  }
161
249
 
@@ -566,24 +654,39 @@ function isMeaningfulTerminalInput(input) {
566
654
  }
567
655
 
568
656
  async function sendTextAndEnter(child, text, shouldAbort = () => false) {
657
+ const options = typeof shouldAbort === "function" ? { shouldAbort } : (shouldAbort || {});
658
+ const shouldStop = typeof options.shouldAbort === "function" ? options.shouldAbort : () => false;
659
+ const agentType = String(options.agentType || "").trim().toLowerCase() || null;
569
660
  const payload = normalizeRelayPayloadForTyping(text);
570
661
 
571
662
  if (payload.length > 0) {
572
- for (const chunk of chunkRelayPayloadForTyping(payload)) {
573
- if (shouldAbort() || !child) {
663
+ if (agentType === "codex") {
664
+ if (shouldStop() || !child) {
574
665
  return false;
575
666
  }
576
667
 
577
668
  try {
578
- child.write(chunk);
669
+ child.write(`${BRACKETED_PASTE_START}${payload}${BRACKETED_PASTE_END}`);
579
670
  } catch {
580
671
  return false;
581
672
  }
582
- await sleep(TYPE_CHUNK_DELAY_MS);
673
+ } else {
674
+ for (const chunk of chunkRelayPayloadForTyping(payload)) {
675
+ if (shouldStop() || !child) {
676
+ return false;
677
+ }
678
+
679
+ try {
680
+ child.write(chunk);
681
+ } catch {
682
+ return false;
683
+ }
684
+ await sleep(TYPE_CHUNK_DELAY_MS);
685
+ }
583
686
  }
584
687
  }
585
688
 
586
- if (shouldAbort() || !child) {
689
+ if (shouldStop() || !child) {
587
690
  return false;
588
691
  }
589
692
 
@@ -702,6 +805,12 @@ class ArmedSeat {
702
805
  const shell = resolveShell();
703
806
  const shellArgs = resolveShellArgs(shell);
704
807
  const childEnv = buildChildEnv(this.seatId, this.sessionName, this.cwd);
808
+ ensureSeatGeminiCliHome(
809
+ String(childEnv.HOME || "").trim() || process.env.HOME || "/root",
810
+ this.cwd,
811
+ this.seatId,
812
+ process.env
813
+ );
705
814
  this.child = pty.spawn(shell, shellArgs, {
706
815
  cols: process.stdout.columns || 120,
707
816
  rows: process.stdout.rows || 36,
@@ -975,6 +1084,10 @@ class ArmedSeat {
975
1084
  return;
976
1085
  }
977
1086
 
1087
+ const detectedRelayAgent = this.liveState.type
1088
+ ? { type: this.liveState.type }
1089
+ : detectAgent(getChildProcesses(this.childPid));
1090
+
978
1091
  const entries = parseContinueEntries(text, this.seatId);
979
1092
  for (const entry of entries) {
980
1093
  if (this.stopped || this.stopRequested()) {
@@ -994,7 +1107,10 @@ class ArmedSeat {
994
1107
  const delivered = await sendTextAndEnter(
995
1108
  this.child,
996
1109
  payload,
997
- () => this.stopped || this.stopRequested() || !this.child || Boolean(this.childExit)
1110
+ {
1111
+ agentType: detectedRelayAgent?.type || null,
1112
+ shouldAbort: () => this.stopped || this.stopRequested() || !this.child || Boolean(this.childExit),
1113
+ }
998
1114
  );
999
1115
  if (!delivered) {
1000
1116
  this.requestStop("relay_aborted");
@@ -1551,12 +1667,14 @@ function stopAllSessions() {
1551
1667
  module.exports = {
1552
1668
  ArmedSeat,
1553
1669
  buildChildEnv,
1670
+ ensureSeatGeminiCliHome,
1554
1671
  chunkRelayPayloadForTyping,
1555
1672
  getStatusReport,
1556
1673
  isBareEscapeInput,
1557
1674
  isMeaningfulTerminalInput,
1558
1675
  normalizeRelayPayloadForTyping,
1559
1676
  resolveSessionName,
1677
+ sendTextAndEnter,
1560
1678
  stopAllSessions,
1561
1679
  };
1562
1680
 
package/src/util.js CHANGED
@@ -154,6 +154,16 @@ function getDefaultSessionName(currentPath = process.cwd()) {
154
154
  return `${label}-${hashText(resolvedPath).slice(0, 8)}`;
155
155
  }
156
156
 
157
+ function getSeatGeminiCliHome(homeDir = os.homedir(), currentPath = process.cwd(), seatId) {
158
+ return path.join(
159
+ homeDir,
160
+ ".muuuuse",
161
+ "gemini-cli-homes",
162
+ getDefaultSessionName(currentPath),
163
+ `seat-${seatId}`
164
+ );
165
+ }
166
+
157
167
  function getSessionDir(sessionName) {
158
168
  return ensureDir(path.join(getStateRoot(), "sessions", slugifySegment(sessionName)));
159
169
  }
@@ -315,6 +325,7 @@ module.exports = {
315
325
  ensureDir,
316
326
  getDefaultSessionName,
317
327
  getFileSize,
328
+ getSeatGeminiCliHome,
318
329
  loadOrCreateSeatIdentity,
319
330
  getSeatPaths,
320
331
  getSessionPaths,