muuuuse 5.5.4 → 6.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 +2 -3
- package/src/cli.js +12 -81
- package/src/runtime.js +43 -339
- package/src/util.js +12 -33
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muuuuse",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "🔌Muuuuse arms regular terminals in isolated pairs and can continue relay output into any other armed seat.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -40,8 +40,7 @@
|
|
|
40
40
|
],
|
|
41
41
|
"scripts": {
|
|
42
42
|
"test": "node test/cli.test.js",
|
|
43
|
-
"pack:local": "npm pack"
|
|
44
|
-
"prepublishOnly": "npm test"
|
|
43
|
+
"pack:local": "npm pack"
|
|
45
44
|
},
|
|
46
45
|
"dependencies": {
|
|
47
46
|
"node-pty": "^1.1.0"
|
package/src/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { BRAND,
|
|
1
|
+
const { BRAND, normalizeSeatId, usage } = require("./util");
|
|
2
2
|
const { ArmedSeat, getStatusReport, stopAllSessions } = require("./runtime");
|
|
3
3
|
|
|
4
4
|
async function main(argv = process.argv.slice(2)) {
|
|
@@ -56,12 +56,10 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
56
56
|
|
|
57
57
|
const seatId = normalizeSeatId(command);
|
|
58
58
|
if (seatId) {
|
|
59
|
-
const {
|
|
59
|
+
const { continueTargets } = parseSeatOptions(command, argv.slice(1));
|
|
60
60
|
const seat = new ArmedSeat({
|
|
61
61
|
cwd: process.cwd(),
|
|
62
62
|
continueTargets,
|
|
63
|
-
continueSeatId,
|
|
64
|
-
flowMode,
|
|
65
63
|
seatId,
|
|
66
64
|
});
|
|
67
65
|
const code = await seat.run();
|
|
@@ -75,15 +73,11 @@ function renderSeatStatus(seat) {
|
|
|
75
73
|
const bits = [
|
|
76
74
|
`seat ${seat.seatId}: ${seat.state}`,
|
|
77
75
|
`agent ${seat.agent || "idle"}`,
|
|
78
|
-
`flow ${seat.flowMode || "off"}`,
|
|
79
76
|
`relays ${seat.relayCount}`,
|
|
80
77
|
`wrapper ${seat.wrapperPid || "-"}`,
|
|
81
78
|
`child ${seat.childPid || "-"}`,
|
|
82
79
|
];
|
|
83
80
|
|
|
84
|
-
if (seat.partnerLive) {
|
|
85
|
-
bits.push("peer live");
|
|
86
|
-
}
|
|
87
81
|
const renderedLinks = renderLinkTargets(seat);
|
|
88
82
|
if (renderedLinks) {
|
|
89
83
|
bits.push(`link ${renderedLinks}`);
|
|
@@ -106,17 +100,7 @@ function renderSeatStatus(seat) {
|
|
|
106
100
|
}
|
|
107
101
|
|
|
108
102
|
function renderLinkTargets(seat) {
|
|
109
|
-
const targets = [];
|
|
110
|
-
if (seat.partnerSeatId) {
|
|
111
|
-
targets.push({
|
|
112
|
-
targetSeatId: seat.partnerSeatId,
|
|
113
|
-
flowMode: seat.flowMode || "off",
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
for (const target of Array.isArray(seat.continueTargets) ? seat.continueTargets : []) {
|
|
117
|
-
targets.push(target);
|
|
118
|
-
}
|
|
119
|
-
|
|
103
|
+
const targets = Array.isArray(seat.continueTargets) ? seat.continueTargets : [];
|
|
120
104
|
return targets
|
|
121
105
|
.map((target) => `${target.targetSeatId}:${target.flowMode}`)
|
|
122
106
|
.join(", ");
|
|
@@ -124,41 +108,16 @@ function renderLinkTargets(seat) {
|
|
|
124
108
|
|
|
125
109
|
function parseSeatOptions(command, args) {
|
|
126
110
|
const seatId = normalizeSeatId(command);
|
|
127
|
-
let flowMode = "off";
|
|
128
|
-
let continueSeatId = null;
|
|
129
111
|
let continueTargets = [];
|
|
130
112
|
let index = 0;
|
|
131
113
|
|
|
132
114
|
while (index < args.length) {
|
|
133
115
|
const token = String(args[index] || "").trim().toLowerCase();
|
|
134
116
|
|
|
135
|
-
if (token === "flow") {
|
|
136
|
-
const flowToken = String(args[index + 1] || "").trim().toLowerCase();
|
|
137
|
-
if (flowToken === "on" || flowToken === "off") {
|
|
138
|
-
flowMode = flowToken;
|
|
139
|
-
index += 2;
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (token === "continue") {
|
|
146
|
-
const parsedTargets = parseContinueTargets(args.slice(index + 1), flowMode);
|
|
147
|
-
if (parsedTargets.targets.length > 0) {
|
|
148
|
-
continueTargets = mergeTargets(continueTargets, parsedTargets.targets);
|
|
149
|
-
continueSeatId = continueTargets[0].targetSeatId;
|
|
150
|
-
index += 1 + parsedTargets.consumed;
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
117
|
if (token === "link") {
|
|
157
|
-
const parsedLinks = parseLinkTargets(args.slice(index + 1), seatId
|
|
118
|
+
const parsedLinks = parseLinkTargets(args.slice(index + 1), seatId);
|
|
158
119
|
if (parsedLinks.consumed > 0) {
|
|
159
|
-
flowMode = parsedLinks.flowMode;
|
|
160
120
|
continueTargets = mergeTargets(continueTargets, parsedLinks.continueTargets);
|
|
161
|
-
continueSeatId = continueTargets[0]?.targetSeatId || null;
|
|
162
121
|
index += 1 + parsedLinks.consumed;
|
|
163
122
|
continue;
|
|
164
123
|
}
|
|
@@ -169,11 +128,11 @@ function parseSeatOptions(command, args) {
|
|
|
169
128
|
}
|
|
170
129
|
|
|
171
130
|
if (index === args.length) {
|
|
172
|
-
return {
|
|
131
|
+
return { continueTargets };
|
|
173
132
|
}
|
|
174
133
|
|
|
175
134
|
throw new Error(
|
|
176
|
-
`\`muuuuse ${command}\` accepts no extra arguments
|
|
135
|
+
`\`muuuuse ${command}\` accepts no extra arguments or \`link <seat> flow on [<seat> flow off ...]\`. Run it directly in the terminal you want to arm.`
|
|
177
136
|
);
|
|
178
137
|
}
|
|
179
138
|
|
|
@@ -189,32 +148,8 @@ function mergeTargets(existingTargets, nextTargets) {
|
|
|
189
148
|
return merged;
|
|
190
149
|
}
|
|
191
150
|
|
|
192
|
-
function
|
|
193
|
-
const targets = [];
|
|
194
|
-
let consumed = 0;
|
|
195
|
-
|
|
196
|
-
while (consumed < args.length) {
|
|
197
|
-
const targetSeatId = normalizeSeatId(args[consumed]);
|
|
198
|
-
if (!targetSeatId) {
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const nextFlowMode = parseFlowModeToken(args[consumed + 1], args[consumed + 2]);
|
|
203
|
-
const target = {
|
|
204
|
-
targetSeatId,
|
|
205
|
-
flowMode: nextFlowMode || defaultFlowMode,
|
|
206
|
-
};
|
|
207
|
-
upsertTarget(targets, target);
|
|
208
|
-
consumed += nextFlowMode ? 3 : 1;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return { consumed, targets };
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function parseLinkTargets(args, seatId, defaultFlowMode) {
|
|
215
|
-
const partnerSeatId = seatId ? getPartnerSeatId(seatId) : null;
|
|
151
|
+
function parseLinkTargets(args, seatId) {
|
|
216
152
|
const continueTargets = [];
|
|
217
|
-
let flowMode = defaultFlowMode;
|
|
218
153
|
let consumed = 0;
|
|
219
154
|
|
|
220
155
|
while (consumed < args.length) {
|
|
@@ -228,19 +163,15 @@ function parseLinkTargets(args, seatId, defaultFlowMode) {
|
|
|
228
163
|
break;
|
|
229
164
|
}
|
|
230
165
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
targetSeatId,
|
|
236
|
-
flowMode: targetFlowMode,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
166
|
+
upsertTarget(continueTargets, {
|
|
167
|
+
targetSeatId,
|
|
168
|
+
flowMode: targetFlowMode,
|
|
169
|
+
});
|
|
239
170
|
|
|
240
171
|
consumed += 3;
|
|
241
172
|
}
|
|
242
173
|
|
|
243
|
-
return { consumed, continueTargets
|
|
174
|
+
return { consumed, continueTargets };
|
|
244
175
|
}
|
|
245
176
|
|
|
246
177
|
function parseFlowModeToken(flowToken, modeToken) {
|
package/src/runtime.js
CHANGED
|
@@ -20,12 +20,10 @@ const {
|
|
|
20
20
|
ensureDir,
|
|
21
21
|
getDefaultSessionName,
|
|
22
22
|
getFileSize,
|
|
23
|
-
getPartnerSeatId,
|
|
24
23
|
getSeatPaths,
|
|
25
24
|
getSessionPaths,
|
|
26
25
|
getStateRoot,
|
|
27
26
|
hashText,
|
|
28
|
-
isAnchorSeat,
|
|
29
27
|
isPidAlive,
|
|
30
28
|
listSeatIds,
|
|
31
29
|
loadOrCreateSeatIdentity,
|
|
@@ -142,42 +140,19 @@ function sleepSync(ms) {
|
|
|
142
140
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
143
141
|
}
|
|
144
142
|
|
|
145
|
-
function
|
|
146
|
-
const normalizedSeatId = normalizeSeatId(seatId);
|
|
147
|
-
const anchorSeatId = getPartnerSeatId(normalizedSeatId);
|
|
148
|
-
if (!normalizedSeatId || !anchorSeatId || isAnchorSeat(normalizedSeatId)) {
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
|
|
143
|
+
function findExistingSessionName(currentPath = process.cwd()) {
|
|
152
144
|
const candidates = listSessionNames()
|
|
153
145
|
.map((sessionName) => {
|
|
154
146
|
const sessionPaths = getSessionPaths(sessionName);
|
|
155
147
|
const controller = readJson(sessionPaths.controllerPath, null);
|
|
156
|
-
const
|
|
157
|
-
const seatPaths = getSeatPaths(sessionName, normalizedSeatId);
|
|
158
|
-
const anchorMeta = readJson(anchorPaths.metaPath, null);
|
|
159
|
-
const anchorStatus = readJson(anchorPaths.statusPath, null);
|
|
160
|
-
const seatMeta = readJson(seatPaths.metaPath, null);
|
|
161
|
-
const seatStatus = readJson(seatPaths.statusPath, null);
|
|
162
|
-
const stopRequest = readJson(sessionPaths.stopPath, null);
|
|
163
|
-
|
|
164
|
-
const cwd = controller?.cwd || anchorStatus?.cwd || anchorMeta?.cwd || seatStatus?.cwd || seatMeta?.cwd || null;
|
|
148
|
+
const cwd = controller?.cwd || null;
|
|
165
149
|
if (!matchesWorkingPath(cwd, currentPath)) {
|
|
166
150
|
return null;
|
|
167
151
|
}
|
|
168
152
|
|
|
169
|
-
const
|
|
170
|
-
const anchorChildPid = anchorStatus?.childPid || anchorMeta?.childPid || null;
|
|
171
|
-
const seatWrapperPid = seatStatus?.pid || seatMeta?.pid || null;
|
|
172
|
-
const seatChildPid = seatStatus?.childPid || seatMeta?.childPid || null;
|
|
173
|
-
const anchorLive = isPidAlive(anchorWrapperPid) || isPidAlive(anchorChildPid);
|
|
174
|
-
const seatLive = isPidAlive(seatWrapperPid) || isPidAlive(seatChildPid);
|
|
153
|
+
const stopRequest = readJson(sessionPaths.stopPath, null);
|
|
175
154
|
const stopRequestedAtMs = Date.parse(stopRequest?.requestedAt || "");
|
|
176
|
-
const createdAtMs = Date.parse(controller?.createdAt ||
|
|
177
|
-
|
|
178
|
-
if (!anchorLive || seatLive) {
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
155
|
+
const createdAtMs = Date.parse(controller?.createdAt || "");
|
|
181
156
|
|
|
182
157
|
if (Number.isFinite(stopRequestedAtMs) && Number.isFinite(createdAtMs) && stopRequestedAtMs > createdAtMs) {
|
|
183
158
|
return null;
|
|
@@ -194,10 +169,10 @@ function findJoinableSessionName(currentPath = process.cwd(), seatId = 2) {
|
|
|
194
169
|
return candidates[0]?.sessionName || null;
|
|
195
170
|
}
|
|
196
171
|
|
|
197
|
-
function
|
|
172
|
+
function waitForExistingSessionName(currentPath = process.cwd(), timeoutMs = SEAT_JOIN_WAIT_MS) {
|
|
198
173
|
const deadline = Date.now() + timeoutMs;
|
|
199
174
|
while (Date.now() <= deadline) {
|
|
200
|
-
const sessionName =
|
|
175
|
+
const sessionName = findExistingSessionName(currentPath);
|
|
201
176
|
if (sessionName) {
|
|
202
177
|
return sessionName;
|
|
203
178
|
}
|
|
@@ -208,11 +183,12 @@ function waitForJoinableSessionName(currentPath = process.cwd(), seatId = 2, tim
|
|
|
208
183
|
}
|
|
209
184
|
|
|
210
185
|
function resolveSessionName(currentPath = process.cwd(), seatId = 1) {
|
|
211
|
-
|
|
212
|
-
|
|
186
|
+
const existing = findExistingSessionName(currentPath);
|
|
187
|
+
if (existing) {
|
|
188
|
+
return existing;
|
|
213
189
|
}
|
|
214
190
|
|
|
215
|
-
return
|
|
191
|
+
return createSessionName(currentPath);
|
|
216
192
|
}
|
|
217
193
|
|
|
218
194
|
function parseAnswerEntries(text) {
|
|
@@ -414,26 +390,6 @@ function resolveSessionFile(agentType, agentPid, currentPath, captureSinceMs, pr
|
|
|
414
390
|
return null;
|
|
415
391
|
}
|
|
416
392
|
|
|
417
|
-
function buildClaimMessage(sessionName, challenge, seat1PublicKey, seat2PublicKey) {
|
|
418
|
-
return JSON.stringify({
|
|
419
|
-
type: "muuuuse_pair_claim",
|
|
420
|
-
sessionName,
|
|
421
|
-
challenge,
|
|
422
|
-
seat1PublicKey,
|
|
423
|
-
seat2PublicKey,
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function buildAckMessage(sessionName, challenge, seat1PublicKey, seat2PublicKey) {
|
|
428
|
-
return JSON.stringify({
|
|
429
|
-
type: "muuuuse_pair_ack",
|
|
430
|
-
sessionName,
|
|
431
|
-
challenge,
|
|
432
|
-
seat1PublicKey,
|
|
433
|
-
seat2PublicKey,
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
|
|
437
393
|
function buildAnswerSignaturePayload(sessionName, challenge, entry) {
|
|
438
394
|
return JSON.stringify({
|
|
439
395
|
type: "muuuuse_answer",
|
|
@@ -584,26 +540,35 @@ async function sendTextAndEnter(child, text, shouldAbort = () => false) {
|
|
|
584
540
|
class ArmedSeat {
|
|
585
541
|
constructor(options) {
|
|
586
542
|
this.seatId = options.seatId;
|
|
587
|
-
this.partnerSeatId = getPartnerSeatId(options.seatId);
|
|
588
|
-
this.anchorSeatId = isAnchorSeat(options.seatId) ? options.seatId : this.partnerSeatId;
|
|
589
|
-
this.flowMode = normalizeFlowMode(options.flowMode);
|
|
590
|
-
this.continueSeatId = normalizeContinueSeatId(options.continueSeatId);
|
|
591
543
|
this.continueTargets = Array.isArray(options.continueTargets) ? options.continueTargets : [];
|
|
592
544
|
this.cwd = normalizeWorkingPath(options.cwd);
|
|
593
|
-
|
|
594
|
-
|
|
545
|
+
|
|
546
|
+
// Auto-link partner seat for backwards compatibility (seat 1→2, seat 2→1)
|
|
547
|
+
// This preserves v5 behavior where odd seats (anchor) initiate relay to even (partner)
|
|
548
|
+
if (this.continueTargets.length === 0) {
|
|
549
|
+
if (this.seatId === 1) {
|
|
550
|
+
// Seat 1 (odd/anchor) relays to seat 2
|
|
551
|
+
this.continueTargets.push({ targetSeatId: 2, flowMode: "on" });
|
|
552
|
+
} else if (this.seatId === 3) {
|
|
553
|
+
// Seat 3 relays to seat 4
|
|
554
|
+
this.continueTargets.push({ targetSeatId: 4, flowMode: "on" });
|
|
555
|
+
}
|
|
556
|
+
// Even seats don't auto-link (they receive from odd partners)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (this.continueTargets.some((t) => t.targetSeatId === this.seatId)) {
|
|
560
|
+
throw new Error(`\`muuuuse ${this.seatId}\` cannot relay to itself.`);
|
|
595
561
|
}
|
|
596
|
-
this.sessionName = resolveSessionName(this.cwd
|
|
562
|
+
this.sessionName = resolveSessionName(this.cwd);
|
|
597
563
|
if (!this.sessionName) {
|
|
598
564
|
throw new Error(
|
|
599
|
-
`
|
|
565
|
+
`Failed to create or find session in ${this.cwd}.`
|
|
600
566
|
);
|
|
601
567
|
}
|
|
602
568
|
this.sessionPaths = getSessionPaths(this.sessionName);
|
|
603
569
|
this.paths = getSeatPaths(this.sessionName, this.seatId);
|
|
604
|
-
this.partnerPaths = getSeatPaths(this.sessionName, this.partnerSeatId);
|
|
605
570
|
this.continueOffset = getFileSize(this.paths.continuePath);
|
|
606
|
-
this.
|
|
571
|
+
this.relayTargets = {};
|
|
607
572
|
|
|
608
573
|
this.child = null;
|
|
609
574
|
this.childPid = null;
|
|
@@ -623,9 +588,8 @@ class ArmedSeat {
|
|
|
623
588
|
this.recentEmittedAnswers = [];
|
|
624
589
|
this.trustState = {
|
|
625
590
|
challenge: null,
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
pairedAt: null,
|
|
591
|
+
phase: "initialized",
|
|
592
|
+
createdAt: null,
|
|
629
593
|
};
|
|
630
594
|
this.liveState = {
|
|
631
595
|
type: null,
|
|
@@ -647,11 +611,6 @@ class ArmedSeat {
|
|
|
647
611
|
cwd: this.cwd,
|
|
648
612
|
createdAt: current.createdAt || this.startedAt,
|
|
649
613
|
updatedAt: new Date().toISOString(),
|
|
650
|
-
anchorSeatId: this.anchorSeatId,
|
|
651
|
-
partnerSeatId: this.partnerSeatId,
|
|
652
|
-
anchorSeatPid: this.seatId === this.anchorSeatId ? process.pid : current.anchorSeatPid || null,
|
|
653
|
-
partnerSeatPid: this.seatId === this.partnerSeatId ? process.pid : current.partnerSeatPid || null,
|
|
654
|
-
pid: this.seatId === this.anchorSeatId ? process.pid : current.pid || null,
|
|
655
614
|
...extra,
|
|
656
615
|
});
|
|
657
616
|
}
|
|
@@ -663,10 +622,7 @@ class ArmedSeat {
|
|
|
663
622
|
writeMeta(extra = {}) {
|
|
664
623
|
writeJson(this.paths.metaPath, {
|
|
665
624
|
seatId: this.seatId,
|
|
666
|
-
partnerSeatId: this.partnerSeatId,
|
|
667
625
|
sessionName: this.sessionName,
|
|
668
|
-
flowMode: this.flowMode,
|
|
669
|
-
continueSeatId: this.continueSeatId,
|
|
670
626
|
continueTargets: this.continueTargets,
|
|
671
627
|
cwd: this.cwd,
|
|
672
628
|
pid: process.pid,
|
|
@@ -680,10 +636,7 @@ class ArmedSeat {
|
|
|
680
636
|
writeStatus(extra = {}) {
|
|
681
637
|
writeJson(this.paths.statusPath, {
|
|
682
638
|
seatId: this.seatId,
|
|
683
|
-
partnerSeatId: this.partnerSeatId,
|
|
684
639
|
sessionName: this.sessionName,
|
|
685
|
-
flowMode: this.flowMode,
|
|
686
|
-
continueSeatId: this.continueSeatId,
|
|
687
640
|
continueTargets: this.continueTargets,
|
|
688
641
|
cwd: this.cwd,
|
|
689
642
|
pid: process.pid,
|
|
@@ -696,168 +649,19 @@ class ArmedSeat {
|
|
|
696
649
|
|
|
697
650
|
initializeTrustMaterial() {
|
|
698
651
|
this.identity = loadOrCreateSeatIdentity(this.paths);
|
|
699
|
-
|
|
700
|
-
if (!isAnchorSeat(this.seatId)) {
|
|
701
|
-
return;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
652
|
writeJson(this.paths.challengePath, {
|
|
705
653
|
sessionName: this.sessionName,
|
|
706
|
-
challenge: createId(48),
|
|
707
654
|
publicKey: this.identity.publicKey,
|
|
708
655
|
createdAt: new Date().toISOString(),
|
|
709
656
|
});
|
|
710
|
-
this.trustState.
|
|
711
|
-
this.trustState.
|
|
712
|
-
this.trustState.phase = "waiting_for_peer_signature";
|
|
713
|
-
this.trustState.pairedAt = null;
|
|
714
|
-
fs.rmSync(this.paths.ackPath, { force: true });
|
|
715
|
-
fs.rmSync(this.partnerPaths.claimPath, { force: true });
|
|
657
|
+
this.trustState.phase = "initialized";
|
|
658
|
+
this.trustState.createdAt = new Date().toISOString();
|
|
716
659
|
}
|
|
717
660
|
|
|
718
661
|
syncTrustState() {
|
|
719
662
|
if (!this.identity) {
|
|
720
663
|
this.initializeTrustMaterial();
|
|
721
664
|
}
|
|
722
|
-
|
|
723
|
-
if (isAnchorSeat(this.seatId)) {
|
|
724
|
-
this.syncSeatOneTrust();
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
this.syncSeatTwoTrust();
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
syncSeatOneTrust() {
|
|
732
|
-
const challengeRecord = readSeatChallenge(this.paths, this.sessionName);
|
|
733
|
-
if (!challengeRecord || challengeRecord.publicKey !== this.identity.publicKey) {
|
|
734
|
-
this.trustState = {
|
|
735
|
-
challenge: null,
|
|
736
|
-
peerPublicKey: null,
|
|
737
|
-
phase: "waiting_for_peer_signature",
|
|
738
|
-
pairedAt: null,
|
|
739
|
-
};
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
this.trustState.challenge = challengeRecord.challenge;
|
|
744
|
-
const claim = readJson(this.partnerPaths.claimPath, null);
|
|
745
|
-
if (
|
|
746
|
-
!claim ||
|
|
747
|
-
claim.sessionName !== this.sessionName ||
|
|
748
|
-
claim.challenge !== challengeRecord.challenge ||
|
|
749
|
-
typeof claim.publicKey !== "string" ||
|
|
750
|
-
typeof claim.signature !== "string" ||
|
|
751
|
-
!verifyText(
|
|
752
|
-
buildClaimMessage(
|
|
753
|
-
this.sessionName,
|
|
754
|
-
challengeRecord.challenge,
|
|
755
|
-
this.identity.publicKey,
|
|
756
|
-
claim.publicKey.trim()
|
|
757
|
-
),
|
|
758
|
-
claim.signature,
|
|
759
|
-
claim.publicKey
|
|
760
|
-
)
|
|
761
|
-
) {
|
|
762
|
-
this.trustState.peerPublicKey = null;
|
|
763
|
-
this.trustState.phase = "waiting_for_peer_signature";
|
|
764
|
-
this.trustState.pairedAt = null;
|
|
765
|
-
fs.rmSync(this.paths.ackPath, { force: true });
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
const peerPublicKey = claim.publicKey.trim();
|
|
770
|
-
const ackMessage = buildAckMessage(this.sessionName, challengeRecord.challenge, this.identity.publicKey, peerPublicKey);
|
|
771
|
-
const currentAck = readJson(this.paths.ackPath, null);
|
|
772
|
-
const ackIsValid = Boolean(
|
|
773
|
-
currentAck &&
|
|
774
|
-
currentAck.sessionName === this.sessionName &&
|
|
775
|
-
currentAck.challenge === challengeRecord.challenge &&
|
|
776
|
-
currentAck.publicKey === this.identity.publicKey &&
|
|
777
|
-
currentAck.peerPublicKey === peerPublicKey &&
|
|
778
|
-
typeof currentAck.signature === "string" &&
|
|
779
|
-
verifyText(ackMessage, currentAck.signature, this.identity.publicKey)
|
|
780
|
-
);
|
|
781
|
-
if (!ackIsValid) {
|
|
782
|
-
writeJson(this.paths.ackPath, {
|
|
783
|
-
sessionName: this.sessionName,
|
|
784
|
-
challenge: challengeRecord.challenge,
|
|
785
|
-
publicKey: this.identity.publicKey,
|
|
786
|
-
peerPublicKey,
|
|
787
|
-
signature: signText(ackMessage, this.identity.privateKey),
|
|
788
|
-
signedAt: new Date().toISOString(),
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
const ackRecord = ackIsValid ? currentAck : readJson(this.paths.ackPath, null);
|
|
793
|
-
this.trustState.peerPublicKey = peerPublicKey;
|
|
794
|
-
this.trustState.phase = "paired";
|
|
795
|
-
this.trustState.pairedAt = ackRecord?.signedAt || new Date().toISOString();
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
syncSeatTwoTrust() {
|
|
799
|
-
const challengeRecord = readSeatChallenge(this.partnerPaths, this.sessionName);
|
|
800
|
-
if (!challengeRecord) {
|
|
801
|
-
this.trustState = {
|
|
802
|
-
challenge: null,
|
|
803
|
-
peerPublicKey: null,
|
|
804
|
-
phase: "waiting_for_anchor_key",
|
|
805
|
-
pairedAt: null,
|
|
806
|
-
};
|
|
807
|
-
return;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
const challenge = challengeRecord.challenge;
|
|
811
|
-
const peerPublicKey = challengeRecord.publicKey;
|
|
812
|
-
const claimPayload = {
|
|
813
|
-
sessionName: this.sessionName,
|
|
814
|
-
challenge,
|
|
815
|
-
publicKey: this.identity.publicKey,
|
|
816
|
-
};
|
|
817
|
-
const claimSignature = signText(
|
|
818
|
-
buildClaimMessage(this.sessionName, challenge, peerPublicKey, this.identity.publicKey),
|
|
819
|
-
this.identity.privateKey
|
|
820
|
-
);
|
|
821
|
-
const currentClaim = readJson(this.paths.claimPath, null);
|
|
822
|
-
if (
|
|
823
|
-
!currentClaim ||
|
|
824
|
-
currentClaim.sessionName !== claimPayload.sessionName ||
|
|
825
|
-
currentClaim.challenge !== claimPayload.challenge ||
|
|
826
|
-
currentClaim.publicKey !== claimPayload.publicKey ||
|
|
827
|
-
currentClaim.signature !== claimSignature
|
|
828
|
-
) {
|
|
829
|
-
writeJson(this.paths.claimPath, {
|
|
830
|
-
...claimPayload,
|
|
831
|
-
signature: claimSignature,
|
|
832
|
-
signedAt: new Date().toISOString(),
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
const ack = readJson(this.partnerPaths.ackPath, null);
|
|
837
|
-
const paired = Boolean(
|
|
838
|
-
ack &&
|
|
839
|
-
ack.sessionName === this.sessionName &&
|
|
840
|
-
ack.challenge === challenge &&
|
|
841
|
-
ack.peerPublicKey === this.identity.publicKey &&
|
|
842
|
-
ack.publicKey === peerPublicKey &&
|
|
843
|
-
typeof ack.signature === "string" &&
|
|
844
|
-
verifyText(
|
|
845
|
-
buildAckMessage(this.sessionName, challenge, peerPublicKey, this.identity.publicKey),
|
|
846
|
-
ack.signature,
|
|
847
|
-
peerPublicKey
|
|
848
|
-
)
|
|
849
|
-
);
|
|
850
|
-
|
|
851
|
-
this.trustState.challenge = challenge;
|
|
852
|
-
this.trustState.peerPublicKey = peerPublicKey;
|
|
853
|
-
this.trustState.phase = paired ? "paired" : "waiting_for_pair_ack";
|
|
854
|
-
this.trustState.pairedAt = paired ? (ack.signedAt || new Date().toISOString()) : null;
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
isPaired() {
|
|
858
|
-
return this.trustState.phase === "paired" &&
|
|
859
|
-
typeof this.trustState.challenge === "string" &&
|
|
860
|
-
typeof this.trustState.peerPublicKey === "string";
|
|
861
665
|
}
|
|
862
666
|
|
|
863
667
|
launchShell() {
|
|
@@ -1014,11 +818,6 @@ class ArmedSeat {
|
|
|
1014
818
|
}
|
|
1015
819
|
}
|
|
1016
820
|
|
|
1017
|
-
partnerIsLive() {
|
|
1018
|
-
const partner = readJson(this.partnerPaths.statusPath, null);
|
|
1019
|
-
return Boolean(partner?.pid && isPidAlive(partner.pid));
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
821
|
stopRequested() {
|
|
1023
822
|
const request = readJson(this.sessionPaths.stopPath, null);
|
|
1024
823
|
if (!request?.requestedAt) {
|
|
@@ -1068,73 +867,6 @@ class ArmedSeat {
|
|
|
1068
867
|
};
|
|
1069
868
|
}
|
|
1070
869
|
|
|
1071
|
-
async pullPartnerEvents() {
|
|
1072
|
-
const { nextOffset, text } = readAppendedText(this.partnerPaths.eventsPath, this.partnerOffset);
|
|
1073
|
-
this.partnerOffset = nextOffset;
|
|
1074
|
-
if (!text.trim() || !this.child || this.stopped || !this.isPaired()) {
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
const entries = parseAnswerEntries(text);
|
|
1079
|
-
for (const entry of entries) {
|
|
1080
|
-
if (this.stopped || this.stopRequested()) {
|
|
1081
|
-
this.requestStop("stop_requested");
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
if (!shouldAcceptInboundEntry(this.flowMode, entry)) {
|
|
1086
|
-
continue;
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
const payload = sanitizeRelayText(entry.text);
|
|
1090
|
-
const signaturePayload = buildAnswerSignaturePayload(this.sessionName, this.trustState.challenge, {
|
|
1091
|
-
chainId: entry.chainId || entry.id,
|
|
1092
|
-
hop: Number.isInteger(entry.hop) ? entry.hop : 0,
|
|
1093
|
-
id: entry.id,
|
|
1094
|
-
seatId: entry.seatId,
|
|
1095
|
-
origin: entry.origin || "unknown",
|
|
1096
|
-
phase: getRelayPhase(entry),
|
|
1097
|
-
createdAt: entry.createdAt,
|
|
1098
|
-
text: payload,
|
|
1099
|
-
});
|
|
1100
|
-
if (
|
|
1101
|
-
!payload ||
|
|
1102
|
-
entry.challenge !== this.trustState.challenge ||
|
|
1103
|
-
entry.publicKey !== this.trustState.peerPublicKey ||
|
|
1104
|
-
typeof entry.signature !== "string" ||
|
|
1105
|
-
!verifyText(signaturePayload, entry.signature, this.trustState.peerPublicKey)
|
|
1106
|
-
) {
|
|
1107
|
-
continue;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
const delivered = await sendTextAndEnter(
|
|
1111
|
-
this.child,
|
|
1112
|
-
payload,
|
|
1113
|
-
() => this.stopped || this.stopRequested() || !this.child || Boolean(this.childExit)
|
|
1114
|
-
);
|
|
1115
|
-
if (!delivered) {
|
|
1116
|
-
this.requestStop("relay_aborted");
|
|
1117
|
-
return;
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
if (this.stopped || this.stopRequested()) {
|
|
1121
|
-
this.requestStop("stop_requested");
|
|
1122
|
-
return;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
const deliveredAtMs = Date.now();
|
|
1126
|
-
this.pendingInboundContext = {
|
|
1127
|
-
chainId: entry.chainId || entry.id,
|
|
1128
|
-
deliveredAtMs,
|
|
1129
|
-
expiresAtMs: deliveredAtMs + PENDING_RELAY_CONTEXT_TTL_MS,
|
|
1130
|
-
hop: Number.isInteger(entry.hop) ? entry.hop : 0,
|
|
1131
|
-
};
|
|
1132
|
-
this.relayCount += 1;
|
|
1133
|
-
this.rememberInboundRelay(payload);
|
|
1134
|
-
this.log(`[${this.partnerSeatId} -> ${this.seatId}] ${previewText(payload)}`);
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
870
|
async pullContinuationEvents() {
|
|
1139
871
|
const { nextOffset, text } = readAppendedText(this.paths.continuePath, this.continueOffset);
|
|
1140
872
|
this.continueOffset = nextOffset;
|
|
@@ -1149,10 +881,6 @@ class ArmedSeat {
|
|
|
1149
881
|
return;
|
|
1150
882
|
}
|
|
1151
883
|
|
|
1152
|
-
if (!shouldAcceptInboundEntry(this.flowMode, entry)) {
|
|
1153
|
-
continue;
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
884
|
const payload = sanitizeRelayText(entry.text);
|
|
1157
885
|
if (!payload) {
|
|
1158
886
|
continue;
|
|
@@ -1366,7 +1094,7 @@ class ArmedSeat {
|
|
|
1366
1094
|
this.liveState.sessionFile,
|
|
1367
1095
|
this.liveState.offset,
|
|
1368
1096
|
this.liveState.captureSinceMs,
|
|
1369
|
-
{ flowMode:
|
|
1097
|
+
{ flowMode: true }
|
|
1370
1098
|
);
|
|
1371
1099
|
this.liveState.offset = result.nextOffset;
|
|
1372
1100
|
answers.push(...result.answers);
|
|
@@ -1375,7 +1103,7 @@ class ArmedSeat {
|
|
|
1375
1103
|
this.liveState.sessionFile,
|
|
1376
1104
|
this.liveState.offset,
|
|
1377
1105
|
this.liveState.captureSinceMs,
|
|
1378
|
-
{ flowMode:
|
|
1106
|
+
{ flowMode: true }
|
|
1379
1107
|
);
|
|
1380
1108
|
this.liveState.offset = result.nextOffset;
|
|
1381
1109
|
answers.push(...result.answers);
|
|
@@ -1384,7 +1112,7 @@ class ArmedSeat {
|
|
|
1384
1112
|
this.liveState.sessionFile,
|
|
1385
1113
|
this.liveState.lastMessageId,
|
|
1386
1114
|
this.liveState.captureSinceMs,
|
|
1387
|
-
{ flowMode:
|
|
1115
|
+
{ flowMode: true }
|
|
1388
1116
|
);
|
|
1389
1117
|
this.liveState.lastMessageId = result.lastMessageId;
|
|
1390
1118
|
this.liveState.offset = result.fileSize;
|
|
@@ -1461,21 +1189,13 @@ class ArmedSeat {
|
|
|
1461
1189
|
}
|
|
1462
1190
|
|
|
1463
1191
|
forwardContinuation(signedEntry) {
|
|
1464
|
-
// Route to legacy single continueSeatId if set
|
|
1465
|
-
if (this.continueSeatId) {
|
|
1466
|
-
const target = this.findContinuationTarget();
|
|
1467
|
-
if (!target) {
|
|
1468
|
-
this.log(`[${this.seatId}] continue ${this.continueSeatId} unavailable`);
|
|
1469
|
-
return;
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
const continuationEntry = buildContinuationEntry(this.sessionName, target.seatId, signedEntry);
|
|
1473
|
-
appendJsonl(target.paths.continuePath, continuationEntry);
|
|
1474
|
-
this.log(`[${this.seatId} => ${target.seatId}] ${previewText(continuationEntry.text)}`);
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
1192
|
// Route to all continueTargets with per-target flow modes
|
|
1478
1193
|
for (const targetEntry of this.continueTargets) {
|
|
1194
|
+
// Skip entries that don't match the target's flowMode
|
|
1195
|
+
if (!shouldAcceptInboundEntry(targetEntry.flowMode, signedEntry)) {
|
|
1196
|
+
continue;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1479
1199
|
const target = this.findContinuationTarget(targetEntry.targetSeatId);
|
|
1480
1200
|
if (!target) {
|
|
1481
1201
|
this.log(`[${this.seatId}] target ${targetEntry.targetSeatId} unavailable`);
|
|
@@ -1492,7 +1212,6 @@ class ArmedSeat {
|
|
|
1492
1212
|
if (this.stopRequested()) {
|
|
1493
1213
|
this.writeStatus({
|
|
1494
1214
|
state: "stopping",
|
|
1495
|
-
partnerLive: this.partnerIsLive(),
|
|
1496
1215
|
trust: this.trustState.phase,
|
|
1497
1216
|
});
|
|
1498
1217
|
this.requestStop("stop_requested");
|
|
@@ -1500,7 +1219,6 @@ class ArmedSeat {
|
|
|
1500
1219
|
}
|
|
1501
1220
|
|
|
1502
1221
|
this.syncTrustState();
|
|
1503
|
-
await this.pullPartnerEvents();
|
|
1504
1222
|
await this.pullContinuationEvents();
|
|
1505
1223
|
if (this.stopped || this.stopRequested()) {
|
|
1506
1224
|
this.requestStop("stop_requested");
|
|
@@ -1515,11 +1233,9 @@ class ArmedSeat {
|
|
|
1515
1233
|
this.writeStatus({
|
|
1516
1234
|
state: live.state,
|
|
1517
1235
|
agent: live.agent,
|
|
1518
|
-
flowMode: this.flowMode,
|
|
1519
1236
|
cwd: live.cwd,
|
|
1520
1237
|
log: live.log,
|
|
1521
1238
|
lastAnswerAt: live.lastAnswerAt,
|
|
1522
|
-
partnerLive: this.partnerIsLive(),
|
|
1523
1239
|
trust: this.trustState.phase,
|
|
1524
1240
|
challengeReady: Boolean(this.trustState.challenge),
|
|
1525
1241
|
});
|
|
@@ -1533,18 +1249,9 @@ class ArmedSeat {
|
|
|
1533
1249
|
|
|
1534
1250
|
this.log(`${BRAND} seat ${this.seatId} armed for ${this.sessionName}.`);
|
|
1535
1251
|
this.log("Use this shell normally. Codex, Claude, and Gemini relay automatically from their local session logs.");
|
|
1536
|
-
this.log(`Seat ${this.seatId} relay mode is flow ${this.flowMode}.`);
|
|
1537
|
-
if (this.continueSeatId) {
|
|
1538
|
-
this.log(`Seat ${this.seatId} continues to seat ${this.continueSeatId}.`);
|
|
1539
|
-
}
|
|
1540
1252
|
if (this.continueTargets.length > 0) {
|
|
1541
|
-
const targets = this.continueTargets.map((t) => `${t.targetSeatId} (${t.flowMode})`).join(", ");
|
|
1542
|
-
this.log(`Seat ${this.seatId}
|
|
1543
|
-
}
|
|
1544
|
-
if (isAnchorSeat(this.seatId)) {
|
|
1545
|
-
this.log(`Seat ${this.seatId} generated the session key and is waiting for seat ${this.partnerSeatId} to sign it.`);
|
|
1546
|
-
} else {
|
|
1547
|
-
this.log(`Seat ${this.seatId} will sign the session key from seat ${this.partnerSeatId}, then relay goes live.`);
|
|
1253
|
+
const targets = this.continueTargets.map((t) => `${t.targetSeatId} (flow ${t.flowMode})`).join(", ");
|
|
1254
|
+
this.log(`Seat ${this.seatId} relays to ${targets}.`);
|
|
1548
1255
|
}
|
|
1549
1256
|
this.log("Run `muuuuse status` or `muuuuse stop` from any terminal.");
|
|
1550
1257
|
|
|
@@ -1641,8 +1348,6 @@ function buildSeatReport(sessionName, seatId) {
|
|
|
1641
1348
|
return {
|
|
1642
1349
|
seatId,
|
|
1643
1350
|
state: wrapperLive ? status?.state || "running" : "orphaned_child",
|
|
1644
|
-
flowMode: status?.flowMode || meta?.flowMode || "off",
|
|
1645
|
-
continueSeatId: status?.continueSeatId || meta?.continueSeatId || null,
|
|
1646
1351
|
continueTargets: status?.continueTargets || meta?.continueTargets || [],
|
|
1647
1352
|
wrapperPid,
|
|
1648
1353
|
childPid,
|
|
@@ -1657,7 +1362,6 @@ function buildSeatReport(sessionName, seatId) {
|
|
|
1657
1362
|
trust: status?.trust || null,
|
|
1658
1363
|
updatedAt: status?.updatedAt || null,
|
|
1659
1364
|
lastAnswerAt: status?.lastAnswerAt || null,
|
|
1660
|
-
partnerLive: Boolean(status?.partnerLive),
|
|
1661
1365
|
};
|
|
1662
1366
|
}
|
|
1663
1367
|
|
package/src/util.js
CHANGED
|
@@ -9,7 +9,7 @@ const fs = require("node:fs");
|
|
|
9
9
|
const os = require("node:os");
|
|
10
10
|
const path = require("node:path");
|
|
11
11
|
|
|
12
|
-
const BRAND = "🔌Muuuuse";
|
|
12
|
+
const BRAND = "🔌Muuuuse v6.0.0";
|
|
13
13
|
const POLL_MS = 220;
|
|
14
14
|
const MAX_RELAY_CHARS = 4000;
|
|
15
15
|
const SESSION_MATCH_WINDOW_MS = 5 * 60 * 1000;
|
|
@@ -175,17 +175,6 @@ function normalizeSeatId(value) {
|
|
|
175
175
|
return seatId;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
function isAnchorSeat(seatId) {
|
|
179
|
-
return normalizeSeatId(seatId) % 2 === 1;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function getPartnerSeatId(seatId) {
|
|
183
|
-
const normalized = normalizeSeatId(seatId);
|
|
184
|
-
if (!normalized) {
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
return isAnchorSeat(normalized) ? normalized + 1 : normalized - 1;
|
|
188
|
-
}
|
|
189
178
|
|
|
190
179
|
function listSeatIds(sessionName) {
|
|
191
180
|
const sessionDir = getSessionDir(sessionName);
|
|
@@ -282,36 +271,28 @@ function listSessionNames() {
|
|
|
282
271
|
|
|
283
272
|
function usage() {
|
|
284
273
|
return [
|
|
285
|
-
`${BRAND}
|
|
274
|
+
`${BRAND} relay protocol for long-horizon zero-drift agentic code loops. agents relay output between terminals, converging to lucid conclusions.`,
|
|
286
275
|
"",
|
|
287
276
|
"Usage:",
|
|
288
277
|
" muuuuse 1",
|
|
289
|
-
" muuuuse 1 flow on",
|
|
290
|
-
" muuuuse 1 flow off",
|
|
291
|
-
" muuuuse 1 flow on continue 3",
|
|
278
|
+
" muuuuse 1 link 2 flow on",
|
|
279
|
+
" muuuuse 1 link 2 flow on 3 flow off",
|
|
292
280
|
" muuuuse 2",
|
|
293
|
-
" muuuuse 2 flow on",
|
|
294
|
-
" muuuuse 2 flow off",
|
|
295
|
-
" muuuuse 2 flow on continue 3",
|
|
281
|
+
" muuuuse 2 link 3 flow on",
|
|
296
282
|
" muuuuse 3",
|
|
297
|
-
" muuuuse 4",
|
|
298
|
-
" muuuuse 4 flow on continue 1",
|
|
299
283
|
" muuuuse stop",
|
|
300
284
|
" muuuuse status",
|
|
301
285
|
"",
|
|
302
286
|
"Flow:",
|
|
303
|
-
" 1. Run `muuuuse
|
|
304
|
-
" 2.
|
|
305
|
-
" 3.
|
|
306
|
-
" 4.
|
|
307
|
-
" 5.
|
|
308
|
-
" 6. Optional: add `continue <seat>` to forward that seat's relayed output into another armed seat.",
|
|
309
|
-
" 7. Use those armed shells normally.",
|
|
310
|
-
" 8. `flow off` sends final answers only. `flow on` keeps assistant commentary bouncing.",
|
|
311
|
-
" 9. Run `muuuuse status` or `muuuuse stop` from any shell.",
|
|
287
|
+
" 1. Run `muuuuse <seat>` in each terminal to arm it (any seat number, any count).",
|
|
288
|
+
" 2. Optionally add `link <target> flow on/off [<target> flow on/off ...]` to relay output to other seats.",
|
|
289
|
+
" 3. Use those armed shells normally. Codex, Claude, and Gemini relay automatically from their local session logs.",
|
|
290
|
+
" 4. `flow off` sends final answers only. `flow on` sends both commentary and final answers.",
|
|
291
|
+
" 5. Run `muuuuse status` or `muuuuse stop` from any terminal.",
|
|
312
292
|
"",
|
|
313
293
|
"Notes:",
|
|
314
|
-
" -
|
|
294
|
+
" - Any seat can relay to any other seat independently.",
|
|
295
|
+
" - `muuuuse stop` and `muuuuse status` work from any terminal.",
|
|
315
296
|
" - State lives under `~/.muuuuse`.",
|
|
316
297
|
].join("\n");
|
|
317
298
|
}
|
|
@@ -325,9 +306,7 @@ module.exports = {
|
|
|
325
306
|
ensureDir,
|
|
326
307
|
getDefaultSessionName,
|
|
327
308
|
getFileSize,
|
|
328
|
-
getPartnerSeatId,
|
|
329
309
|
loadOrCreateSeatIdentity,
|
|
330
|
-
isAnchorSeat,
|
|
331
310
|
getSeatPaths,
|
|
332
311
|
getSessionPaths,
|
|
333
312
|
getStateRoot,
|