muuuuse 3.1.1 → 3.3.1
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/README.md +13 -11
- package/package.json +2 -2
- package/src/cli.js +90 -82
- package/src/runtime.js +181 -451
- package/src/util.js +18 -28
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,
|
|
@@ -40,9 +38,9 @@ const {
|
|
|
40
38
|
writeJson,
|
|
41
39
|
} = require("./util");
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
// A short settle delay keeps interactive CLIs from treating submit as another newline.
|
|
42
|
+
const TYPE_CHUNK_DELAY_MS = 45;
|
|
44
43
|
const TYPE_CHUNK_SIZE = 24;
|
|
45
|
-
const TYPE_SUBMIT_DELAY_MS = 60;
|
|
46
44
|
const MIRROR_SUPPRESSION_WINDOW_MS = 30 * 1000;
|
|
47
45
|
const PENDING_RELAY_CONTEXT_TTL_MS = 2 * 60 * 1000;
|
|
48
46
|
const EMITTED_ANSWER_TTL_MS = 5 * 60 * 1000;
|
|
@@ -92,24 +90,24 @@ function normalizeContinueSeatId(value) {
|
|
|
92
90
|
return seatId || null;
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
function normalizeContinueTargets(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
for (const entry of Array.isArray(targets) ? targets : []) {
|
|
100
|
-
const targetSeatId = normalizeSeatId(entry?.targetSeatId ?? entry?.seatId ?? entry);
|
|
101
|
-
if (!targetSeatId || seen.has(targetSeatId)) {
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
seen.add(targetSeatId);
|
|
106
|
-
normalized.push({
|
|
107
|
-
targetSeatId,
|
|
108
|
-
flowMode: normalizeFlowMode(entry?.flowMode ?? entry?.flow ?? defaultFlowMode),
|
|
109
|
-
});
|
|
93
|
+
function normalizeContinueTargets(value) {
|
|
94
|
+
if (!Array.isArray(value)) {
|
|
95
|
+
return [];
|
|
110
96
|
}
|
|
111
97
|
|
|
112
|
-
return
|
|
98
|
+
return value
|
|
99
|
+
.map((entry) => {
|
|
100
|
+
const seatId = normalizeSeatId(entry?.seatId);
|
|
101
|
+
if (!seatId) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
seatId,
|
|
107
|
+
flowMode: normalizeFlowMode(entry?.flowMode),
|
|
108
|
+
};
|
|
109
|
+
})
|
|
110
|
+
.filter((entry) => entry !== null);
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
function resolveShell() {
|
|
@@ -189,50 +187,37 @@ function sleepSync(ms) {
|
|
|
189
187
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
190
188
|
}
|
|
191
189
|
|
|
192
|
-
function
|
|
193
|
-
const normalizedSeatId = normalizeSeatId(seatId);
|
|
194
|
-
const anchorSeatId = getPartnerSeatId(normalizedSeatId);
|
|
195
|
-
if (!normalizedSeatId || !anchorSeatId || isAnchorSeat(normalizedSeatId)) {
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
|
|
190
|
+
function findExistingSessionName(currentPath = process.cwd()) {
|
|
199
191
|
const candidates = listSessionNames()
|
|
200
192
|
.map((sessionName) => {
|
|
201
193
|
const sessionPaths = getSessionPaths(sessionName);
|
|
202
194
|
const controller = readJson(sessionPaths.controllerPath, null);
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const anchorStatus = readJson(anchorPaths.statusPath, null);
|
|
207
|
-
const seatMeta = readJson(seatPaths.metaPath, null);
|
|
208
|
-
const seatStatus = readJson(seatPaths.statusPath, null);
|
|
209
|
-
const stopRequest = readJson(sessionPaths.stopPath, null);
|
|
195
|
+
const seats = listSeatIds(sessionName)
|
|
196
|
+
.map((seatId) => buildSeatReport(sessionName, seatId))
|
|
197
|
+
.filter((entry) => entry !== null);
|
|
210
198
|
|
|
211
|
-
const cwd = controller?.cwd ||
|
|
199
|
+
const cwd = controller?.cwd || seats[0]?.cwd || null;
|
|
212
200
|
if (!matchesWorkingPath(cwd, currentPath)) {
|
|
213
201
|
return null;
|
|
214
202
|
}
|
|
215
203
|
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
const seatChildPid = seatStatus?.childPid || seatMeta?.childPid || null;
|
|
220
|
-
const anchorLive = isPidAlive(anchorWrapperPid) || isPidAlive(anchorChildPid);
|
|
221
|
-
const seatLive = isPidAlive(seatWrapperPid) || isPidAlive(seatChildPid);
|
|
222
|
-
const stopRequestedAtMs = Date.parse(stopRequest?.requestedAt || "");
|
|
223
|
-
const createdAtMs = Date.parse(controller?.createdAt || anchorMeta?.startedAt || anchorStatus?.updatedAt || "");
|
|
224
|
-
|
|
225
|
-
if (!anchorLive || seatLive) {
|
|
204
|
+
const controllerPid = controller?.pid || null;
|
|
205
|
+
const controllerLive = isPidAlive(controllerPid);
|
|
206
|
+
if (seats.length === 0 && !controllerLive) {
|
|
226
207
|
return null;
|
|
227
208
|
}
|
|
228
209
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
210
|
+
const createdAtMs = Date.parse(
|
|
211
|
+
controller?.createdAt ||
|
|
212
|
+
seats
|
|
213
|
+
.map((seat) => seat.startedAt || seat.updatedAt || "")
|
|
214
|
+
.find((value) => value) ||
|
|
215
|
+
""
|
|
216
|
+
);
|
|
232
217
|
|
|
233
218
|
return {
|
|
234
219
|
sessionName,
|
|
235
|
-
createdAtMs,
|
|
220
|
+
createdAtMs: Number.isFinite(createdAtMs) ? createdAtMs : 0,
|
|
236
221
|
};
|
|
237
222
|
})
|
|
238
223
|
.filter((entry) => entry !== null)
|
|
@@ -241,10 +226,10 @@ function findJoinableSessionName(currentPath = process.cwd(), seatId = 2) {
|
|
|
241
226
|
return candidates[0]?.sessionName || null;
|
|
242
227
|
}
|
|
243
228
|
|
|
244
|
-
function
|
|
229
|
+
function waitForExistingSessionName(currentPath = process.cwd(), timeoutMs = SEAT_JOIN_WAIT_MS) {
|
|
245
230
|
const deadline = Date.now() + timeoutMs;
|
|
246
231
|
while (Date.now() <= deadline) {
|
|
247
|
-
const sessionName =
|
|
232
|
+
const sessionName = findExistingSessionName(currentPath);
|
|
248
233
|
if (sessionName) {
|
|
249
234
|
return sessionName;
|
|
250
235
|
}
|
|
@@ -255,11 +240,31 @@ function waitForJoinableSessionName(currentPath = process.cwd(), seatId = 2, tim
|
|
|
255
240
|
}
|
|
256
241
|
|
|
257
242
|
function resolveSessionName(currentPath = process.cwd(), seatId = 1) {
|
|
258
|
-
|
|
259
|
-
|
|
243
|
+
const existingSessionName = findExistingSessionName(currentPath);
|
|
244
|
+
if (!existingSessionName) {
|
|
245
|
+
const normalizedSeatId = normalizeSeatId(seatId) || 1;
|
|
246
|
+
const joinWaitMs = Math.min(1000, Math.max(0, normalizedSeatId - 1) * 250);
|
|
247
|
+
const waitedSessionName = waitForExistingSessionName(currentPath, joinWaitMs);
|
|
248
|
+
if (!waitedSessionName) {
|
|
249
|
+
return createSessionName(currentPath);
|
|
250
|
+
}
|
|
251
|
+
const conflictingWaitedSeat = buildSeatReport(waitedSessionName, seatId);
|
|
252
|
+
if (conflictingWaitedSeat) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`Seat ${seatId} is already armed in this cwd. Stop it first or choose another seat number.`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return waitedSessionName;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const conflictingSeat = buildSeatReport(existingSessionName, seatId);
|
|
261
|
+
if (conflictingSeat) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`Seat ${seatId} is already armed in this cwd. Stop it first or choose another seat number.`
|
|
264
|
+
);
|
|
260
265
|
}
|
|
261
266
|
|
|
262
|
-
return
|
|
267
|
+
return existingSessionName;
|
|
263
268
|
}
|
|
264
269
|
|
|
265
270
|
function parseAnswerEntries(text) {
|
|
@@ -461,50 +466,30 @@ function resolveSessionFile(agentType, agentPid, currentPath, captureSinceMs, pr
|
|
|
461
466
|
return null;
|
|
462
467
|
}
|
|
463
468
|
|
|
464
|
-
function buildClaimMessage(sessionName, challenge, seat1PublicKey, seat2PublicKey) {
|
|
465
|
-
return JSON.stringify({
|
|
466
|
-
type: "muuuuse_pair_claim",
|
|
467
|
-
sessionName,
|
|
468
|
-
challenge,
|
|
469
|
-
seat1PublicKey,
|
|
470
|
-
seat2PublicKey,
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function buildAckMessage(sessionName, challenge, seat1PublicKey, seat2PublicKey) {
|
|
475
|
-
return JSON.stringify({
|
|
476
|
-
type: "muuuuse_pair_ack",
|
|
477
|
-
sessionName,
|
|
478
|
-
challenge,
|
|
479
|
-
seat1PublicKey,
|
|
480
|
-
seat2PublicKey,
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
|
|
484
469
|
function buildAnswerSignaturePayload(sessionName, challenge, entry) {
|
|
485
470
|
return JSON.stringify({
|
|
486
|
-
type: "
|
|
471
|
+
type: "muuuuse_relay",
|
|
487
472
|
sessionName,
|
|
488
|
-
challenge,
|
|
489
473
|
chainId: entry.chainId,
|
|
490
474
|
hop: entry.hop,
|
|
491
475
|
id: entry.id,
|
|
492
|
-
|
|
476
|
+
sourceSeatId: normalizeSeatId(entry.sourceSeatId || entry.seatId),
|
|
477
|
+
targetSeatId: normalizeSeatId(entry.targetSeatId),
|
|
493
478
|
origin: entry.origin,
|
|
494
|
-
phase: entry
|
|
495
|
-
flowMode: entry.flowMode || "off",
|
|
479
|
+
phase: getRelayPhase(entry),
|
|
496
480
|
createdAt: entry.createdAt,
|
|
497
481
|
text: entry.text,
|
|
498
482
|
});
|
|
499
483
|
}
|
|
500
484
|
|
|
501
|
-
function buildContinuationEntry(sourceSessionName, targetSeatId, entry) {
|
|
485
|
+
function buildContinuationEntry(sourceSessionName, targetSeatId, entry, targetFlowMode = null) {
|
|
502
486
|
return {
|
|
503
487
|
id: createId(12),
|
|
504
488
|
type: "continue",
|
|
505
489
|
sourceSessionName,
|
|
506
490
|
sourceSeatId: entry.seatId,
|
|
507
491
|
targetSeatId,
|
|
492
|
+
targetFlowMode: normalizeFlowMode(targetFlowMode),
|
|
508
493
|
origin: entry.origin || "unknown",
|
|
509
494
|
phase: entry.phase || "final_answer",
|
|
510
495
|
text: entry.text,
|
|
@@ -512,7 +497,6 @@ function buildContinuationEntry(sourceSessionName, targetSeatId, entry) {
|
|
|
512
497
|
chainId: entry.chainId,
|
|
513
498
|
hop: entry.hop,
|
|
514
499
|
sourceAnswerId: entry.id,
|
|
515
|
-
flowMode: entry.flowMode || null,
|
|
516
500
|
publicKey: entry.publicKey || null,
|
|
517
501
|
signature: entry.signature || null,
|
|
518
502
|
};
|
|
@@ -539,24 +523,6 @@ function getSeatDirIfExists(sessionName, seatId) {
|
|
|
539
523
|
return null;
|
|
540
524
|
}
|
|
541
525
|
|
|
542
|
-
function readSeatChallenge(paths, sessionName) {
|
|
543
|
-
const record = readJson(paths.challengePath, null);
|
|
544
|
-
if (
|
|
545
|
-
!record ||
|
|
546
|
-
record.sessionName !== sessionName ||
|
|
547
|
-
typeof record.challenge !== "string" ||
|
|
548
|
-
typeof record.publicKey !== "string"
|
|
549
|
-
) {
|
|
550
|
-
return null;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
return {
|
|
554
|
-
challenge: record.challenge,
|
|
555
|
-
publicKey: record.publicKey.trim(),
|
|
556
|
-
createdAt: record.createdAt || null,
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
|
|
560
526
|
function normalizeRelayPayloadForTyping(text) {
|
|
561
527
|
return String(text || "")
|
|
562
528
|
.replace(/\r/g, "")
|
|
@@ -621,14 +587,6 @@ async function sendTextAndEnter(child, text, shouldAbort = () => false) {
|
|
|
621
587
|
return false;
|
|
622
588
|
}
|
|
623
589
|
|
|
624
|
-
// Some TUIs treat fast, chunked relay typing as paste input and suppress an
|
|
625
|
-
// immediate Enter. A short settle delay keeps submit behavior reliable.
|
|
626
|
-
await sleep(TYPE_SUBMIT_DELAY_MS);
|
|
627
|
-
|
|
628
|
-
if (shouldAbort() || !child) {
|
|
629
|
-
return false;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
590
|
try {
|
|
633
591
|
child.write("\r");
|
|
634
592
|
} catch {
|
|
@@ -641,31 +599,20 @@ async function sendTextAndEnter(child, text, shouldAbort = () => false) {
|
|
|
641
599
|
class ArmedSeat {
|
|
642
600
|
constructor(options) {
|
|
643
601
|
this.seatId = options.seatId;
|
|
644
|
-
this.partnerSeatId = getPartnerSeatId(options.seatId);
|
|
645
|
-
this.anchorSeatId = isAnchorSeat(options.seatId) ? options.seatId : this.partnerSeatId;
|
|
646
602
|
this.flowMode = normalizeFlowMode(options.flowMode);
|
|
647
|
-
this.
|
|
648
|
-
|
|
649
|
-
options.continueSeatId ? [{ targetSeatId: options.continueSeatId, flowMode: options.flowMode }] : []
|
|
650
|
-
),
|
|
651
|
-
this.flowMode
|
|
652
|
-
);
|
|
653
|
-
this.continueSeatId = this.continueTargets[0]?.targetSeatId || normalizeContinueSeatId(options.continueSeatId);
|
|
603
|
+
this.continueSeatId = normalizeContinueSeatId(options.continueSeatId);
|
|
604
|
+
this.continueTargets = normalizeContinueTargets(options.continueTargets);
|
|
654
605
|
this.cwd = normalizeWorkingPath(options.cwd);
|
|
655
|
-
if (this.
|
|
606
|
+
if (this.continueSeatId === this.seatId) {
|
|
656
607
|
throw new Error(`\`muuuuse ${this.seatId}\` cannot continue to itself.`);
|
|
657
608
|
}
|
|
658
|
-
this.
|
|
659
|
-
|
|
660
|
-
throw new Error(
|
|
661
|
-
`No armed \`muuuuse ${this.partnerSeatId}\` seat is waiting in this cwd. Run \`muuuuse ${this.partnerSeatId}\` first.`
|
|
662
|
-
);
|
|
609
|
+
if (this.continueTargets.some((target) => target.seatId === this.seatId)) {
|
|
610
|
+
throw new Error(`\`muuuuse ${this.seatId}\` cannot link to itself.`);
|
|
663
611
|
}
|
|
612
|
+
this.sessionName = resolveSessionName(this.cwd, this.seatId);
|
|
664
613
|
this.sessionPaths = getSessionPaths(this.sessionName);
|
|
665
614
|
this.paths = getSeatPaths(this.sessionName, this.seatId);
|
|
666
|
-
this.partnerPaths = getSeatPaths(this.sessionName, this.partnerSeatId);
|
|
667
615
|
this.continueOffset = getFileSize(this.paths.continuePath);
|
|
668
|
-
this.partnerOffset = getFileSize(this.partnerPaths.eventsPath);
|
|
669
616
|
|
|
670
617
|
this.child = null;
|
|
671
618
|
this.childPid = null;
|
|
@@ -678,17 +625,11 @@ class ArmedSeat {
|
|
|
678
625
|
this.stdinCleanup = null;
|
|
679
626
|
this.resizeCleanup = null;
|
|
680
627
|
this.forceKillTimer = null;
|
|
681
|
-
this.identity =
|
|
628
|
+
this.identity = loadOrCreateSeatIdentity(this.paths);
|
|
682
629
|
this.lastUserInputAtMs = 0;
|
|
683
630
|
this.pendingInboundContext = null;
|
|
684
631
|
this.recentInboundRelays = [];
|
|
685
632
|
this.recentEmittedAnswers = [];
|
|
686
|
-
this.trustState = {
|
|
687
|
-
challenge: null,
|
|
688
|
-
peerPublicKey: null,
|
|
689
|
-
phase: isAnchorSeat(this.seatId) ? "waiting_for_peer_signature" : "waiting_for_anchor_key",
|
|
690
|
-
pairedAt: null,
|
|
691
|
-
};
|
|
692
633
|
this.liveState = {
|
|
693
634
|
type: null,
|
|
694
635
|
pid: null,
|
|
@@ -709,11 +650,7 @@ class ArmedSeat {
|
|
|
709
650
|
cwd: this.cwd,
|
|
710
651
|
createdAt: current.createdAt || this.startedAt,
|
|
711
652
|
updatedAt: new Date().toISOString(),
|
|
712
|
-
|
|
713
|
-
partnerSeatId: this.partnerSeatId,
|
|
714
|
-
anchorSeatPid: this.seatId === this.anchorSeatId ? process.pid : current.anchorSeatPid || null,
|
|
715
|
-
partnerSeatPid: this.seatId === this.partnerSeatId ? process.pid : current.partnerSeatPid || null,
|
|
716
|
-
pid: this.seatId === this.anchorSeatId ? process.pid : current.pid || null,
|
|
653
|
+
pid: process.pid,
|
|
717
654
|
...extra,
|
|
718
655
|
});
|
|
719
656
|
}
|
|
@@ -725,7 +662,6 @@ class ArmedSeat {
|
|
|
725
662
|
writeMeta(extra = {}) {
|
|
726
663
|
writeJson(this.paths.metaPath, {
|
|
727
664
|
seatId: this.seatId,
|
|
728
|
-
partnerSeatId: this.partnerSeatId,
|
|
729
665
|
sessionName: this.sessionName,
|
|
730
666
|
flowMode: this.flowMode,
|
|
731
667
|
continueSeatId: this.continueSeatId,
|
|
@@ -733,6 +669,7 @@ class ArmedSeat {
|
|
|
733
669
|
cwd: this.cwd,
|
|
734
670
|
pid: process.pid,
|
|
735
671
|
childPid: this.childPid,
|
|
672
|
+
publicKey: this.identity?.publicKey || null,
|
|
736
673
|
command: [resolveShell(), ...resolveShellArgs(resolveShell())],
|
|
737
674
|
startedAt: this.startedAt,
|
|
738
675
|
...extra,
|
|
@@ -742,7 +679,6 @@ class ArmedSeat {
|
|
|
742
679
|
writeStatus(extra = {}) {
|
|
743
680
|
writeJson(this.paths.statusPath, {
|
|
744
681
|
seatId: this.seatId,
|
|
745
|
-
partnerSeatId: this.partnerSeatId,
|
|
746
682
|
sessionName: this.sessionName,
|
|
747
683
|
flowMode: this.flowMode,
|
|
748
684
|
continueSeatId: this.continueSeatId,
|
|
@@ -750,183 +686,17 @@ class ArmedSeat {
|
|
|
750
686
|
cwd: this.cwd,
|
|
751
687
|
pid: process.pid,
|
|
752
688
|
childPid: this.childPid,
|
|
689
|
+
publicKey: this.identity?.publicKey || null,
|
|
753
690
|
relayCount: this.relayCount,
|
|
754
691
|
updatedAt: new Date().toISOString(),
|
|
755
692
|
...extra,
|
|
756
693
|
});
|
|
757
694
|
}
|
|
758
695
|
|
|
759
|
-
initializeTrustMaterial() {
|
|
760
|
-
this.identity = loadOrCreateSeatIdentity(this.paths);
|
|
761
|
-
|
|
762
|
-
if (!isAnchorSeat(this.seatId)) {
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
writeJson(this.paths.challengePath, {
|
|
767
|
-
sessionName: this.sessionName,
|
|
768
|
-
challenge: createId(48),
|
|
769
|
-
publicKey: this.identity.publicKey,
|
|
770
|
-
createdAt: new Date().toISOString(),
|
|
771
|
-
});
|
|
772
|
-
this.trustState.challenge = readSeatChallenge(this.paths, this.sessionName)?.challenge || null;
|
|
773
|
-
this.trustState.peerPublicKey = null;
|
|
774
|
-
this.trustState.phase = "waiting_for_peer_signature";
|
|
775
|
-
this.trustState.pairedAt = null;
|
|
776
|
-
fs.rmSync(this.paths.ackPath, { force: true });
|
|
777
|
-
fs.rmSync(this.partnerPaths.claimPath, { force: true });
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
syncTrustState() {
|
|
781
|
-
if (!this.identity) {
|
|
782
|
-
this.initializeTrustMaterial();
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
if (isAnchorSeat(this.seatId)) {
|
|
786
|
-
this.syncSeatOneTrust();
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
this.syncSeatTwoTrust();
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
syncSeatOneTrust() {
|
|
794
|
-
const challengeRecord = readSeatChallenge(this.paths, this.sessionName);
|
|
795
|
-
if (!challengeRecord || challengeRecord.publicKey !== this.identity.publicKey) {
|
|
796
|
-
this.trustState = {
|
|
797
|
-
challenge: null,
|
|
798
|
-
peerPublicKey: null,
|
|
799
|
-
phase: "waiting_for_peer_signature",
|
|
800
|
-
pairedAt: null,
|
|
801
|
-
};
|
|
802
|
-
return;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
this.trustState.challenge = challengeRecord.challenge;
|
|
806
|
-
const claim = readJson(this.partnerPaths.claimPath, null);
|
|
807
|
-
if (
|
|
808
|
-
!claim ||
|
|
809
|
-
claim.sessionName !== this.sessionName ||
|
|
810
|
-
claim.challenge !== challengeRecord.challenge ||
|
|
811
|
-
typeof claim.publicKey !== "string" ||
|
|
812
|
-
typeof claim.signature !== "string" ||
|
|
813
|
-
!verifyText(
|
|
814
|
-
buildClaimMessage(
|
|
815
|
-
this.sessionName,
|
|
816
|
-
challengeRecord.challenge,
|
|
817
|
-
this.identity.publicKey,
|
|
818
|
-
claim.publicKey.trim()
|
|
819
|
-
),
|
|
820
|
-
claim.signature,
|
|
821
|
-
claim.publicKey
|
|
822
|
-
)
|
|
823
|
-
) {
|
|
824
|
-
this.trustState.peerPublicKey = null;
|
|
825
|
-
this.trustState.phase = "waiting_for_peer_signature";
|
|
826
|
-
this.trustState.pairedAt = null;
|
|
827
|
-
fs.rmSync(this.paths.ackPath, { force: true });
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
const peerPublicKey = claim.publicKey.trim();
|
|
832
|
-
const ackMessage = buildAckMessage(this.sessionName, challengeRecord.challenge, this.identity.publicKey, peerPublicKey);
|
|
833
|
-
const currentAck = readJson(this.paths.ackPath, null);
|
|
834
|
-
const ackIsValid = Boolean(
|
|
835
|
-
currentAck &&
|
|
836
|
-
currentAck.sessionName === this.sessionName &&
|
|
837
|
-
currentAck.challenge === challengeRecord.challenge &&
|
|
838
|
-
currentAck.publicKey === this.identity.publicKey &&
|
|
839
|
-
currentAck.peerPublicKey === peerPublicKey &&
|
|
840
|
-
typeof currentAck.signature === "string" &&
|
|
841
|
-
verifyText(ackMessage, currentAck.signature, this.identity.publicKey)
|
|
842
|
-
);
|
|
843
|
-
if (!ackIsValid) {
|
|
844
|
-
writeJson(this.paths.ackPath, {
|
|
845
|
-
sessionName: this.sessionName,
|
|
846
|
-
challenge: challengeRecord.challenge,
|
|
847
|
-
publicKey: this.identity.publicKey,
|
|
848
|
-
peerPublicKey,
|
|
849
|
-
signature: signText(ackMessage, this.identity.privateKey),
|
|
850
|
-
signedAt: new Date().toISOString(),
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
const ackRecord = ackIsValid ? currentAck : readJson(this.paths.ackPath, null);
|
|
855
|
-
this.trustState.peerPublicKey = peerPublicKey;
|
|
856
|
-
this.trustState.phase = "paired";
|
|
857
|
-
this.trustState.pairedAt = ackRecord?.signedAt || new Date().toISOString();
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
syncSeatTwoTrust() {
|
|
861
|
-
const challengeRecord = readSeatChallenge(this.partnerPaths, this.sessionName);
|
|
862
|
-
if (!challengeRecord) {
|
|
863
|
-
this.trustState = {
|
|
864
|
-
challenge: null,
|
|
865
|
-
peerPublicKey: null,
|
|
866
|
-
phase: "waiting_for_anchor_key",
|
|
867
|
-
pairedAt: null,
|
|
868
|
-
};
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
const challenge = challengeRecord.challenge;
|
|
873
|
-
const peerPublicKey = challengeRecord.publicKey;
|
|
874
|
-
const claimPayload = {
|
|
875
|
-
sessionName: this.sessionName,
|
|
876
|
-
challenge,
|
|
877
|
-
publicKey: this.identity.publicKey,
|
|
878
|
-
};
|
|
879
|
-
const claimSignature = signText(
|
|
880
|
-
buildClaimMessage(this.sessionName, challenge, peerPublicKey, this.identity.publicKey),
|
|
881
|
-
this.identity.privateKey
|
|
882
|
-
);
|
|
883
|
-
const currentClaim = readJson(this.paths.claimPath, null);
|
|
884
|
-
if (
|
|
885
|
-
!currentClaim ||
|
|
886
|
-
currentClaim.sessionName !== claimPayload.sessionName ||
|
|
887
|
-
currentClaim.challenge !== claimPayload.challenge ||
|
|
888
|
-
currentClaim.publicKey !== claimPayload.publicKey ||
|
|
889
|
-
currentClaim.signature !== claimSignature
|
|
890
|
-
) {
|
|
891
|
-
writeJson(this.paths.claimPath, {
|
|
892
|
-
...claimPayload,
|
|
893
|
-
signature: claimSignature,
|
|
894
|
-
signedAt: new Date().toISOString(),
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
const ack = readJson(this.partnerPaths.ackPath, null);
|
|
899
|
-
const paired = Boolean(
|
|
900
|
-
ack &&
|
|
901
|
-
ack.sessionName === this.sessionName &&
|
|
902
|
-
ack.challenge === challenge &&
|
|
903
|
-
ack.peerPublicKey === this.identity.publicKey &&
|
|
904
|
-
ack.publicKey === peerPublicKey &&
|
|
905
|
-
typeof ack.signature === "string" &&
|
|
906
|
-
verifyText(
|
|
907
|
-
buildAckMessage(this.sessionName, challenge, peerPublicKey, this.identity.publicKey),
|
|
908
|
-
ack.signature,
|
|
909
|
-
peerPublicKey
|
|
910
|
-
)
|
|
911
|
-
);
|
|
912
|
-
|
|
913
|
-
this.trustState.challenge = challenge;
|
|
914
|
-
this.trustState.peerPublicKey = peerPublicKey;
|
|
915
|
-
this.trustState.phase = paired ? "paired" : "waiting_for_pair_ack";
|
|
916
|
-
this.trustState.pairedAt = paired ? (ack.signedAt || new Date().toISOString()) : null;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
isPaired() {
|
|
920
|
-
return this.trustState.phase === "paired" &&
|
|
921
|
-
typeof this.trustState.challenge === "string" &&
|
|
922
|
-
typeof this.trustState.peerPublicKey === "string";
|
|
923
|
-
}
|
|
924
|
-
|
|
925
696
|
launchShell() {
|
|
926
697
|
ensureDir(this.paths.dir);
|
|
927
698
|
fs.rmSync(this.paths.pipePath, { force: true });
|
|
928
699
|
clearStaleStopRequest(this.sessionPaths.stopPath, this.startedAtMs);
|
|
929
|
-
this.initializeTrustMaterial();
|
|
930
700
|
this.writeController();
|
|
931
701
|
|
|
932
702
|
const shell = resolveShell();
|
|
@@ -943,7 +713,7 @@ class ArmedSeat {
|
|
|
943
713
|
|
|
944
714
|
this.childPid = this.child.pid;
|
|
945
715
|
this.writeMeta();
|
|
946
|
-
this.writeStatus({ state: "running"
|
|
716
|
+
this.writeStatus({ state: "running" });
|
|
947
717
|
|
|
948
718
|
this.child.onData((data) => {
|
|
949
719
|
fs.appendFileSync(this.paths.pipePath, data);
|
|
@@ -1077,9 +847,19 @@ class ArmedSeat {
|
|
|
1077
847
|
}
|
|
1078
848
|
}
|
|
1079
849
|
|
|
1080
|
-
|
|
1081
|
-
const
|
|
1082
|
-
|
|
850
|
+
getConfiguredTargets() {
|
|
851
|
+
const targets = [...this.continueTargets];
|
|
852
|
+
if (this.continueSeatId && !targets.some((target) => target.seatId === this.continueSeatId)) {
|
|
853
|
+
targets.push({
|
|
854
|
+
seatId: this.continueSeatId,
|
|
855
|
+
flowMode: this.flowMode,
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
return targets;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
shouldCaptureCommentary() {
|
|
862
|
+
return this.getConfiguredTargets().some((target) => target.flowMode === "on");
|
|
1083
863
|
}
|
|
1084
864
|
|
|
1085
865
|
stopRequested() {
|
|
@@ -1092,116 +872,80 @@ class ArmedSeat {
|
|
|
1092
872
|
return Number.isFinite(requestedAtMs) && requestedAtMs > this.startedAtMs;
|
|
1093
873
|
}
|
|
1094
874
|
|
|
1095
|
-
|
|
1096
|
-
|
|
875
|
+
hasAuthorizedSource(sourceSeatId) {
|
|
876
|
+
const desiredSeatId = normalizeSeatId(sourceSeatId);
|
|
877
|
+
if (!desiredSeatId) {
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
return this.getConfiguredTargets().some((target) => target.seatId === desiredSeatId);
|
|
1097
882
|
}
|
|
1098
883
|
|
|
1099
|
-
|
|
1100
|
-
const
|
|
1101
|
-
if (!
|
|
884
|
+
readSourcePublicKey(sourceSeatId) {
|
|
885
|
+
const desiredSeatId = normalizeSeatId(sourceSeatId);
|
|
886
|
+
if (!desiredSeatId) {
|
|
1102
887
|
return null;
|
|
1103
888
|
}
|
|
1104
889
|
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
890
|
+
const sourcePaths = getSeatPaths(this.sessionName, desiredSeatId);
|
|
891
|
+
const sourceMeta = readJson(sourcePaths.metaPath, null);
|
|
892
|
+
if (typeof sourceMeta?.publicKey === "string" && sourceMeta.publicKey.trim()) {
|
|
893
|
+
return sourceMeta.publicKey.trim();
|
|
894
|
+
}
|
|
1110
895
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
896
|
+
try {
|
|
897
|
+
const key = fs.readFileSync(sourcePaths.publicKeyPath, "utf8").trim();
|
|
898
|
+
return key || null;
|
|
899
|
+
} catch {
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
1115
903
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
};
|
|
1122
|
-
})
|
|
1123
|
-
.filter((entry) => entry !== null)
|
|
1124
|
-
.sort((left, right) => right.updatedAtMs - left.updatedAtMs);
|
|
904
|
+
findLinkedTarget(targetSeatId) {
|
|
905
|
+
const desiredSeatId = normalizeContinueSeatId(targetSeatId);
|
|
906
|
+
if (!desiredSeatId) {
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
1125
909
|
|
|
1126
|
-
|
|
910
|
+
const seat = buildSeatReport(this.sessionName, desiredSeatId);
|
|
911
|
+
if (!seat || !matchesWorkingPath(seat.cwd, this.cwd)) {
|
|
1127
912
|
return null;
|
|
1128
913
|
}
|
|
1129
914
|
|
|
1130
|
-
const target = candidates[0];
|
|
1131
915
|
return {
|
|
1132
|
-
seatId:
|
|
1133
|
-
|
|
1134
|
-
paths: getSeatPaths(target.sessionName, target.seat.seatId),
|
|
916
|
+
seatId: seat.seatId,
|
|
917
|
+
paths: getSeatPaths(this.sessionName, seat.seatId),
|
|
1135
918
|
};
|
|
1136
919
|
}
|
|
1137
920
|
|
|
1138
|
-
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
921
|
+
verifyInboundEntry(entry) {
|
|
922
|
+
const sourceSeatId = normalizeSeatId(entry?.sourceSeatId || entry?.seatId);
|
|
923
|
+
const targetSeatId = normalizeSeatId(entry?.targetSeatId);
|
|
924
|
+
const payload = sanitizeRelayText(entry?.text);
|
|
925
|
+
if (!sourceSeatId || targetSeatId !== this.seatId || !payload || !this.hasAuthorizedSource(sourceSeatId)) {
|
|
926
|
+
return false;
|
|
1143
927
|
}
|
|
1144
928
|
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
return;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
const inboundFlowMode = normalizeFlowMode(entry.flowMode || this.flowMode);
|
|
1153
|
-
if (!shouldAcceptInboundEntry(inboundFlowMode, entry)) {
|
|
1154
|
-
continue;
|
|
1155
|
-
}
|
|
929
|
+
const publicKey = this.readSourcePublicKey(sourceSeatId);
|
|
930
|
+
if (!publicKey || entry.publicKey !== publicKey || typeof entry.signature !== "string") {
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
1156
933
|
|
|
1157
|
-
|
|
1158
|
-
|
|
934
|
+
return verifyText(
|
|
935
|
+
buildAnswerSignaturePayload(this.sessionName, null, {
|
|
936
|
+
id: entry.id,
|
|
937
|
+
sourceSeatId,
|
|
938
|
+
targetSeatId,
|
|
1159
939
|
chainId: entry.chainId || entry.id,
|
|
1160
940
|
hop: Number.isInteger(entry.hop) ? entry.hop : 0,
|
|
1161
|
-
id: entry.id,
|
|
1162
|
-
seatId: entry.seatId,
|
|
1163
941
|
origin: entry.origin || "unknown",
|
|
1164
942
|
phase: getRelayPhase(entry),
|
|
1165
|
-
flowMode: inboundFlowMode,
|
|
1166
943
|
createdAt: entry.createdAt,
|
|
1167
944
|
text: payload,
|
|
1168
|
-
})
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
entry.publicKey !== this.trustState.peerPublicKey ||
|
|
1173
|
-
typeof entry.signature !== "string" ||
|
|
1174
|
-
!verifyText(signaturePayload, entry.signature, this.trustState.peerPublicKey)
|
|
1175
|
-
) {
|
|
1176
|
-
continue;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
const delivered = await sendTextAndEnter(
|
|
1180
|
-
this.child,
|
|
1181
|
-
payload,
|
|
1182
|
-
() => this.stopped || this.stopRequested() || !this.child || Boolean(this.childExit)
|
|
1183
|
-
);
|
|
1184
|
-
if (!delivered) {
|
|
1185
|
-
this.requestStop("relay_aborted");
|
|
1186
|
-
return;
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
if (this.stopped || this.stopRequested()) {
|
|
1190
|
-
this.requestStop("stop_requested");
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
const deliveredAtMs = Date.now();
|
|
1195
|
-
this.pendingInboundContext = {
|
|
1196
|
-
chainId: entry.chainId || entry.id,
|
|
1197
|
-
deliveredAtMs,
|
|
1198
|
-
expiresAtMs: deliveredAtMs + PENDING_RELAY_CONTEXT_TTL_MS,
|
|
1199
|
-
hop: Number.isInteger(entry.hop) ? entry.hop : 0,
|
|
1200
|
-
};
|
|
1201
|
-
this.relayCount += 1;
|
|
1202
|
-
this.rememberInboundRelay(payload);
|
|
1203
|
-
this.log(`[${this.partnerSeatId} -> ${this.seatId}] ${previewText(payload)}`);
|
|
1204
|
-
}
|
|
945
|
+
}),
|
|
946
|
+
entry.signature,
|
|
947
|
+
publicKey
|
|
948
|
+
);
|
|
1205
949
|
}
|
|
1206
950
|
|
|
1207
951
|
async pullContinuationEvents() {
|
|
@@ -1218,8 +962,7 @@ class ArmedSeat {
|
|
|
1218
962
|
return;
|
|
1219
963
|
}
|
|
1220
964
|
|
|
1221
|
-
|
|
1222
|
-
if (!shouldAcceptInboundEntry(continueFlowMode, entry)) {
|
|
965
|
+
if (!this.verifyInboundEntry(entry)) {
|
|
1223
966
|
continue;
|
|
1224
967
|
}
|
|
1225
968
|
|
|
@@ -1431,13 +1174,12 @@ class ArmedSeat {
|
|
|
1431
1174
|
}
|
|
1432
1175
|
|
|
1433
1176
|
const answers = [];
|
|
1434
|
-
const captureCommentary = this.shouldCaptureCommentary();
|
|
1435
1177
|
if (detectedAgent.type === "codex") {
|
|
1436
1178
|
const result = readCodexAnswers(
|
|
1437
1179
|
this.liveState.sessionFile,
|
|
1438
1180
|
this.liveState.offset,
|
|
1439
1181
|
this.liveState.captureSinceMs,
|
|
1440
|
-
{ flowMode:
|
|
1182
|
+
{ flowMode: this.shouldCaptureCommentary() }
|
|
1441
1183
|
);
|
|
1442
1184
|
this.liveState.offset = result.nextOffset;
|
|
1443
1185
|
answers.push(...result.answers);
|
|
@@ -1446,7 +1188,7 @@ class ArmedSeat {
|
|
|
1446
1188
|
this.liveState.sessionFile,
|
|
1447
1189
|
this.liveState.offset,
|
|
1448
1190
|
this.liveState.captureSinceMs,
|
|
1449
|
-
{ flowMode:
|
|
1191
|
+
{ flowMode: this.shouldCaptureCommentary() }
|
|
1450
1192
|
);
|
|
1451
1193
|
this.liveState.offset = result.nextOffset;
|
|
1452
1194
|
answers.push(...result.answers);
|
|
@@ -1455,7 +1197,7 @@ class ArmedSeat {
|
|
|
1455
1197
|
this.liveState.sessionFile,
|
|
1456
1198
|
this.liveState.lastMessageId,
|
|
1457
1199
|
this.liveState.captureSinceMs,
|
|
1458
|
-
{ flowMode:
|
|
1200
|
+
{ flowMode: this.shouldCaptureCommentary() }
|
|
1459
1201
|
);
|
|
1460
1202
|
this.liveState.lastMessageId = result.lastMessageId;
|
|
1461
1203
|
this.liveState.offset = result.fileSize;
|
|
@@ -1488,7 +1230,7 @@ class ArmedSeat {
|
|
|
1488
1230
|
}
|
|
1489
1231
|
|
|
1490
1232
|
const payload = sanitizeRelayText(entry.text);
|
|
1491
|
-
if (!payload
|
|
1233
|
+
if (!payload) {
|
|
1492
1234
|
return;
|
|
1493
1235
|
}
|
|
1494
1236
|
|
|
@@ -1507,65 +1249,66 @@ class ArmedSeat {
|
|
|
1507
1249
|
const pendingInboundContext = this.getPendingInboundContext();
|
|
1508
1250
|
|
|
1509
1251
|
const entryId = entry.id || createId(12);
|
|
1510
|
-
const
|
|
1252
|
+
const relayEntry = {
|
|
1511
1253
|
id: entryId,
|
|
1512
1254
|
type: "answer",
|
|
1513
1255
|
seatId: this.seatId,
|
|
1256
|
+
sourceSeatId: this.seatId,
|
|
1514
1257
|
origin: entry.origin || "unknown",
|
|
1515
1258
|
phase: entry.phase || "final_answer",
|
|
1516
|
-
flowMode: this.flowMode,
|
|
1517
1259
|
text: payload,
|
|
1518
1260
|
createdAt: entry.createdAt || new Date().toISOString(),
|
|
1519
1261
|
chainId: pendingInboundContext?.chainId || entry.chainId || entryId,
|
|
1520
1262
|
hop: pendingInboundContext ? pendingInboundContext.hop + 1 : 0,
|
|
1521
|
-
challenge: this.trustState.challenge,
|
|
1522
|
-
publicKey: this.identity.publicKey,
|
|
1523
1263
|
};
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
);
|
|
1528
|
-
appendJsonl(this.paths.eventsPath, signedEntry);
|
|
1529
|
-
this.forwardContinuation(signedEntry);
|
|
1264
|
+
|
|
1265
|
+
appendJsonl(this.paths.eventsPath, relayEntry);
|
|
1266
|
+
this.forwardContinuation(relayEntry);
|
|
1530
1267
|
this.rememberEmittedAnswer(answerKey);
|
|
1531
1268
|
|
|
1532
1269
|
this.log(`[${this.seatId}] ${previewText(payload)}`);
|
|
1533
1270
|
}
|
|
1534
1271
|
|
|
1535
|
-
forwardContinuation(
|
|
1536
|
-
|
|
1272
|
+
forwardContinuation(relayEntry) {
|
|
1273
|
+
const targets = this.getConfiguredTargets();
|
|
1274
|
+
if (targets.length === 0) {
|
|
1537
1275
|
return;
|
|
1538
1276
|
}
|
|
1539
1277
|
|
|
1540
|
-
for (const targetEntry of
|
|
1541
|
-
|
|
1278
|
+
for (const targetEntry of targets) {
|
|
1279
|
+
if (!shouldAcceptInboundEntry(targetEntry.flowMode, relayEntry)) {
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
const target = this.findLinkedTarget(targetEntry.seatId);
|
|
1542
1284
|
if (!target) {
|
|
1543
|
-
this.log(`[${this.seatId}]
|
|
1285
|
+
this.log(`[${this.seatId}] link ${targetEntry.seatId} unavailable`);
|
|
1544
1286
|
continue;
|
|
1545
1287
|
}
|
|
1546
1288
|
|
|
1547
|
-
const continuationEntry = buildContinuationEntry(
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1289
|
+
const continuationEntry = buildContinuationEntry(
|
|
1290
|
+
this.sessionName,
|
|
1291
|
+
target.seatId,
|
|
1292
|
+
relayEntry,
|
|
1293
|
+
targetEntry.flowMode
|
|
1294
|
+
);
|
|
1295
|
+
continuationEntry.publicKey = this.identity.publicKey;
|
|
1296
|
+
continuationEntry.signature = signText(
|
|
1297
|
+
buildAnswerSignaturePayload(this.sessionName, null, continuationEntry),
|
|
1298
|
+
this.identity.privateKey
|
|
1299
|
+
);
|
|
1551
1300
|
appendJsonl(target.paths.continuePath, continuationEntry);
|
|
1552
|
-
this.log(`[${this.seatId} => ${target.seatId}
|
|
1301
|
+
this.log(`[${this.seatId} => ${target.seatId}] ${previewText(continuationEntry.text)}`);
|
|
1553
1302
|
}
|
|
1554
1303
|
}
|
|
1555
1304
|
|
|
1556
1305
|
async tick() {
|
|
1557
1306
|
if (this.stopRequested()) {
|
|
1558
|
-
this.writeStatus({
|
|
1559
|
-
state: "stopping",
|
|
1560
|
-
partnerLive: this.partnerIsLive(),
|
|
1561
|
-
trust: this.trustState.phase,
|
|
1562
|
-
});
|
|
1307
|
+
this.writeStatus({ state: "stopping" });
|
|
1563
1308
|
this.requestStop("stop_requested");
|
|
1564
1309
|
return;
|
|
1565
1310
|
}
|
|
1566
1311
|
|
|
1567
|
-
this.syncTrustState();
|
|
1568
|
-
await this.pullPartnerEvents();
|
|
1569
1312
|
await this.pullContinuationEvents();
|
|
1570
1313
|
if (this.stopped || this.stopRequested()) {
|
|
1571
1314
|
this.requestStop("stop_requested");
|
|
@@ -1584,9 +1327,6 @@ class ArmedSeat {
|
|
|
1584
1327
|
cwd: live.cwd,
|
|
1585
1328
|
log: live.log,
|
|
1586
1329
|
lastAnswerAt: live.lastAnswerAt,
|
|
1587
|
-
partnerLive: this.partnerIsLive(),
|
|
1588
|
-
trust: this.trustState.phase,
|
|
1589
|
-
challengeReady: Boolean(this.trustState.challenge),
|
|
1590
1330
|
});
|
|
1591
1331
|
}
|
|
1592
1332
|
|
|
@@ -1598,17 +1338,17 @@ class ArmedSeat {
|
|
|
1598
1338
|
|
|
1599
1339
|
this.log(`${BRAND} seat ${this.seatId} armed for ${this.sessionName}.`);
|
|
1600
1340
|
this.log("Use this shell normally. Codex, Claude, and Gemini relay automatically from their local session logs.");
|
|
1601
|
-
this.log(`Seat ${this.seatId} relay mode is flow ${this.flowMode}.`);
|
|
1602
|
-
if (this.
|
|
1341
|
+
this.log(`Seat ${this.seatId} default relay mode is flow ${this.flowMode}.`);
|
|
1342
|
+
if (this.continueSeatId) {
|
|
1343
|
+
this.log(`Seat ${this.seatId} continues to seat ${this.continueSeatId}.`);
|
|
1344
|
+
}
|
|
1345
|
+
const configuredTargets = this.getConfiguredTargets();
|
|
1346
|
+
if (configuredTargets.length > 0) {
|
|
1603
1347
|
this.log(
|
|
1604
|
-
`Seat ${this.seatId}
|
|
1348
|
+
`Seat ${this.seatId} links signed relay targets: ${configuredTargets.map((target) => `${target.seatId}:${target.flowMode}`).join(", ")}.`
|
|
1605
1349
|
);
|
|
1606
1350
|
}
|
|
1607
|
-
|
|
1608
|
-
this.log(`Seat ${this.seatId} generated the session key and is waiting for seat ${this.partnerSeatId} to sign it.`);
|
|
1609
|
-
} else {
|
|
1610
|
-
this.log(`Seat ${this.seatId} will sign the session key from seat ${this.partnerSeatId}, then relay goes live.`);
|
|
1611
|
-
}
|
|
1351
|
+
this.log("Signed relays are accepted only from seats that this seat links back to.");
|
|
1612
1352
|
this.log("Run `muuuuse status` or `muuuuse stop` from any terminal.");
|
|
1613
1353
|
|
|
1614
1354
|
try {
|
|
@@ -1703,18 +1443,10 @@ function buildSeatReport(sessionName, seatId) {
|
|
|
1703
1443
|
|
|
1704
1444
|
return {
|
|
1705
1445
|
seatId,
|
|
1706
|
-
partnerSeatId: status?.partnerSeatId || meta?.partnerSeatId || getPartnerSeatId(seatId),
|
|
1707
1446
|
state: wrapperLive ? status?.state || "running" : "orphaned_child",
|
|
1708
1447
|
flowMode: status?.flowMode || meta?.flowMode || "off",
|
|
1709
1448
|
continueSeatId: status?.continueSeatId || meta?.continueSeatId || null,
|
|
1710
|
-
continueTargets: normalizeContinueTargets(
|
|
1711
|
-
status?.continueTargets || meta?.continueTargets || (
|
|
1712
|
-
(status?.continueSeatId || meta?.continueSeatId)
|
|
1713
|
-
? [{ targetSeatId: status?.continueSeatId || meta?.continueSeatId, flowMode: status?.flowMode || meta?.flowMode || "off" }]
|
|
1714
|
-
: []
|
|
1715
|
-
),
|
|
1716
|
-
status?.flowMode || meta?.flowMode || "off"
|
|
1717
|
-
),
|
|
1449
|
+
continueTargets: normalizeContinueTargets(status?.continueTargets || meta?.continueTargets),
|
|
1718
1450
|
wrapperPid,
|
|
1719
1451
|
childPid,
|
|
1720
1452
|
wrapperLive,
|
|
@@ -1725,10 +1457,8 @@ function buildSeatReport(sessionName, seatId) {
|
|
|
1725
1457
|
relayCount: status?.relayCount || 0,
|
|
1726
1458
|
log: status?.log || null,
|
|
1727
1459
|
startedAt: meta?.startedAt || null,
|
|
1728
|
-
trust: status?.trust || null,
|
|
1729
1460
|
updatedAt: status?.updatedAt || null,
|
|
1730
1461
|
lastAnswerAt: status?.lastAnswerAt || null,
|
|
1731
|
-
partnerLive: Boolean(status?.partnerLive),
|
|
1732
1462
|
};
|
|
1733
1463
|
}
|
|
1734
1464
|
|