muuuuse 4.0.1 → 5.0.0

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,7 +1,7 @@
1
1
  {
2
2
  "name": "muuuuse",
3
- "version": "4.0.1",
4
- "description": "🔌Muuuuse arms regular terminals in isolated pairs and can continue relay output into any other armed seat.",
3
+ "version": "5.0.0",
4
+ "description": "🔌Muuuuse arms terminals with flexible routing to any targets - pure message relay graph.",
5
5
  "type": "commonjs",
6
6
  "bin": {
7
7
  "muuuuse": "bin/muuse.js"
package/src/agents.js CHANGED
@@ -69,6 +69,44 @@ function detectAgent(processes) {
69
69
  return buildDetectedAgent("gemini", process);
70
70
  }
71
71
  }
72
+
73
+ // Fallback: if no agent process found, check for Claude Code running in a separate terminal
74
+ // (Claude Code's session updates even if it's not a child process of this shell)
75
+ if (processes.length > 0) {
76
+ // Use the bash/shell process as reference for timing
77
+ const shellProcess = processes[0];
78
+ const now = Date.now();
79
+ const recentThreshold = now - 2 * 60 * 1000; // Last 2 minutes
80
+
81
+ const CLAUDE_ROOT = path.join(os.homedir(), ".claude", "projects");
82
+ if (fs.existsSync(CLAUDE_ROOT)) {
83
+ try {
84
+ const sessionFiles = walkFiles(CLAUDE_ROOT, (f) => f.endsWith(".jsonl"))
85
+ .map(filePath => {
86
+ try {
87
+ const stat = fs.statSync(filePath);
88
+ return { filePath, mtimeMs: stat.mtimeMs };
89
+ } catch {
90
+ return null;
91
+ }
92
+ })
93
+ .filter(e => e && e.mtimeMs > recentThreshold)
94
+ .sort((a, b) => b.mtimeMs - a.mtimeMs)[0];
95
+
96
+ if (sessionFiles) {
97
+ return {
98
+ type: "claude",
99
+ pid: process.pid || shellProcess.pid,
100
+ args: "claude-code-terminal-session",
101
+ cwd: shellProcess.cwd || null,
102
+ elapsedSeconds: Math.round((Date.now() - shellProcess.startedAtMs) / 1000),
103
+ processStartedAtMs: Date.now() - (shellProcess.elapsedSeconds ?? 0) * 1000,
104
+ };
105
+ }
106
+ } catch {}
107
+ }
108
+ }
109
+
72
110
  return null;
73
111
  }
74
112
 
package/src/cli.js CHANGED
@@ -105,16 +105,7 @@ function renderSeatStatus(seat) {
105
105
  }
106
106
 
107
107
  function renderLinkTargets(seat) {
108
- const targets = [];
109
- if (seat.partnerSeatId) {
110
- targets.push({
111
- targetSeatId: seat.partnerSeatId,
112
- flowMode: seat.flowMode || "off",
113
- });
114
- }
115
- for (const target of Array.isArray(seat.continueTargets) ? seat.continueTargets : []) {
116
- targets.push(target);
117
- }
108
+ const targets = Array.isArray(seat.continueTargets) ? seat.continueTargets : [];
118
109
  if (targets.length === 0) {
119
110
  return "";
120
111
  }
@@ -165,9 +156,7 @@ function parseSeatOptions(command, args) {
165
156
  }
166
157
 
167
158
  function parseLinkTargets(args, seatId, defaultFlowMode) {
168
- const partnerSeatId = seatId ? getPartnerSeatId(seatId) : null;
169
- const continueTargets = [];
170
- let flowMode = defaultFlowMode;
159
+ const targets = [];
171
160
  let consumed = 0;
172
161
 
173
162
  while (consumed < args.length) {
@@ -181,19 +170,15 @@ function parseLinkTargets(args, seatId, defaultFlowMode) {
181
170
  break;
182
171
  }
183
172
 
184
- if (targetSeatId === partnerSeatId) {
185
- flowMode = targetFlowMode;
186
- } else {
187
- upsertTarget(continueTargets, {
188
- targetSeatId,
189
- flowMode: targetFlowMode,
190
- });
191
- }
173
+ upsertTarget(targets, {
174
+ targetSeatId,
175
+ flowMode: targetFlowMode,
176
+ });
192
177
 
193
178
  consumed += 3;
194
179
  }
195
180
 
196
- return { consumed, continueTargets, flowMode };
181
+ return { consumed, continueTargets: targets, flowMode: defaultFlowMode };
197
182
  }
198
183
 
199
184
  function parseFlowModeToken(flowToken, modeToken) {
package/src/runtime.js CHANGED
@@ -222,7 +222,12 @@ function findJoinableSessionName(currentPath = process.cwd(), seatId = 2) {
222
222
  const stopRequestedAtMs = Date.parse(stopRequest?.requestedAt || "");
223
223
  const createdAtMs = Date.parse(controller?.createdAt || anchorMeta?.startedAt || anchorStatus?.updatedAt || "");
224
224
 
225
- if (!anchorLive || seatLive) {
225
+ // Accept session if: controller exists AND (anchor is live OR anchor has written meta/status files).
226
+ // This handles the startup race: anchor might not have a live PID yet, but if it wrote files, it's initialized.
227
+ const controllerExists = controller !== null;
228
+ const anchorInitialized = anchorMeta !== null || anchorStatus !== null;
229
+ const anchorReady = anchorLive || anchorInitialized;
230
+ if (!controllerExists || !anchorReady || seatLive) {
226
231
  return null;
227
232
  }
228
233
 
@@ -1524,12 +1529,12 @@ class ArmedSeat {
1524
1529
  );
1525
1530
 
1526
1531
  appendJsonl(this.paths.eventsPath, signedEntry);
1527
- this.forwardContinuation(signedEntry);
1532
+ this.routeToTargets(signedEntry);
1528
1533
  this.rememberEmittedAnswer(answerKey);
1529
1534
  this.log(`[${this.seatId}] ${previewText(payload)}`);
1530
1535
  }
1531
1536
 
1532
- forwardContinuation(signedEntry) {
1537
+ routeToTargets(signedEntry) {
1533
1538
  if (this.continueTargets.length === 0) {
1534
1539
  return;
1535
1540
  }
@@ -1541,7 +1546,7 @@ class ArmedSeat {
1541
1546
 
1542
1547
  const target = this.findContinuationTarget(targetEntry.targetSeatId);
1543
1548
  if (!target) {
1544
- this.log(`[${this.seatId}] continue ${targetEntry.targetSeatId} unavailable`);
1549
+ this.log(`[${this.seatId}] target ${targetEntry.targetSeatId} unavailable`);
1545
1550
  continue;
1546
1551
  }
1547
1552