muuuuse 3.1.1 → 3.3.2
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 +201 -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;
|
|
260
258
|
}
|
|
261
259
|
|
|
262
|
-
|
|
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
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
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,100 @@ class ArmedSeat {
|
|
|
1092
872
|
return Number.isFinite(requestedAtMs) && requestedAtMs > this.startedAtMs;
|
|
1093
873
|
}
|
|
1094
874
|
|
|
1095
|
-
|
|
1096
|
-
|
|
875
|
+
sourceLinksToTarget(sourceSeatId, targetSeatId = this.seatId) {
|
|
876
|
+
const desiredSeatId = normalizeSeatId(sourceSeatId);
|
|
877
|
+
const desiredTargetSeatId = normalizeSeatId(targetSeatId);
|
|
878
|
+
if (!desiredSeatId || !desiredTargetSeatId) {
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const sourcePaths = getSeatPaths(this.sessionName, desiredSeatId);
|
|
883
|
+
const sourceStatus = readJson(sourcePaths.statusPath, null);
|
|
884
|
+
const sourceMeta = readJson(sourcePaths.metaPath, null);
|
|
885
|
+
const sourceContinueSeatId = sourceStatus?.continueSeatId || sourceMeta?.continueSeatId || null;
|
|
886
|
+
const sourceContinueTargets = normalizeContinueTargets(
|
|
887
|
+
sourceStatus?.continueTargets || sourceMeta?.continueTargets
|
|
888
|
+
);
|
|
889
|
+
|
|
890
|
+
const configuredTargets = [...sourceContinueTargets];
|
|
891
|
+
if (
|
|
892
|
+
sourceContinueSeatId &&
|
|
893
|
+
!configuredTargets.some((target) => target.seatId === normalizeSeatId(sourceContinueSeatId))
|
|
894
|
+
) {
|
|
895
|
+
configuredTargets.push({
|
|
896
|
+
seatId: normalizeSeatId(sourceContinueSeatId),
|
|
897
|
+
flowMode: normalizeFlowMode(sourceStatus?.flowMode || sourceMeta?.flowMode),
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
return configuredTargets.some((target) => target.seatId === desiredTargetSeatId);
|
|
1097
902
|
}
|
|
1098
903
|
|
|
1099
|
-
|
|
1100
|
-
const
|
|
1101
|
-
if (!
|
|
904
|
+
readSourcePublicKey(sourceSeatId) {
|
|
905
|
+
const desiredSeatId = normalizeSeatId(sourceSeatId);
|
|
906
|
+
if (!desiredSeatId) {
|
|
1102
907
|
return null;
|
|
1103
908
|
}
|
|
1104
909
|
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
910
|
+
const sourcePaths = getSeatPaths(this.sessionName, desiredSeatId);
|
|
911
|
+
const sourceMeta = readJson(sourcePaths.metaPath, null);
|
|
912
|
+
if (typeof sourceMeta?.publicKey === "string" && sourceMeta.publicKey.trim()) {
|
|
913
|
+
return sourceMeta.publicKey.trim();
|
|
914
|
+
}
|
|
1110
915
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
916
|
+
try {
|
|
917
|
+
const key = fs.readFileSync(sourcePaths.publicKeyPath, "utf8").trim();
|
|
918
|
+
return key || null;
|
|
919
|
+
} catch {
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
1115
923
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
};
|
|
1122
|
-
})
|
|
1123
|
-
.filter((entry) => entry !== null)
|
|
1124
|
-
.sort((left, right) => right.updatedAtMs - left.updatedAtMs);
|
|
924
|
+
findLinkedTarget(targetSeatId) {
|
|
925
|
+
const desiredSeatId = normalizeContinueSeatId(targetSeatId);
|
|
926
|
+
if (!desiredSeatId) {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
1125
929
|
|
|
1126
|
-
|
|
930
|
+
const seat = buildSeatReport(this.sessionName, desiredSeatId);
|
|
931
|
+
if (!seat || !matchesWorkingPath(seat.cwd, this.cwd)) {
|
|
1127
932
|
return null;
|
|
1128
933
|
}
|
|
1129
934
|
|
|
1130
|
-
const target = candidates[0];
|
|
1131
935
|
return {
|
|
1132
|
-
seatId:
|
|
1133
|
-
|
|
1134
|
-
paths: getSeatPaths(target.sessionName, target.seat.seatId),
|
|
936
|
+
seatId: seat.seatId,
|
|
937
|
+
paths: getSeatPaths(this.sessionName, seat.seatId),
|
|
1135
938
|
};
|
|
1136
939
|
}
|
|
1137
940
|
|
|
1138
|
-
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
941
|
+
verifyInboundEntry(entry) {
|
|
942
|
+
const sourceSeatId = normalizeSeatId(entry?.sourceSeatId || entry?.seatId);
|
|
943
|
+
const targetSeatId = normalizeSeatId(entry?.targetSeatId);
|
|
944
|
+
const payload = sanitizeRelayText(entry?.text);
|
|
945
|
+
if (!sourceSeatId || targetSeatId !== this.seatId || !payload || !this.sourceLinksToTarget(sourceSeatId, targetSeatId)) {
|
|
946
|
+
return false;
|
|
1143
947
|
}
|
|
1144
948
|
|
|
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
|
-
}
|
|
949
|
+
const publicKey = this.readSourcePublicKey(sourceSeatId);
|
|
950
|
+
if (!publicKey || entry.publicKey !== publicKey || typeof entry.signature !== "string") {
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
1156
953
|
|
|
1157
|
-
|
|
1158
|
-
|
|
954
|
+
return verifyText(
|
|
955
|
+
buildAnswerSignaturePayload(this.sessionName, null, {
|
|
956
|
+
id: entry.id,
|
|
957
|
+
sourceSeatId,
|
|
958
|
+
targetSeatId,
|
|
1159
959
|
chainId: entry.chainId || entry.id,
|
|
1160
960
|
hop: Number.isInteger(entry.hop) ? entry.hop : 0,
|
|
1161
|
-
id: entry.id,
|
|
1162
|
-
seatId: entry.seatId,
|
|
1163
961
|
origin: entry.origin || "unknown",
|
|
1164
962
|
phase: getRelayPhase(entry),
|
|
1165
|
-
flowMode: inboundFlowMode,
|
|
1166
963
|
createdAt: entry.createdAt,
|
|
1167
964
|
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
|
-
}
|
|
965
|
+
}),
|
|
966
|
+
entry.signature,
|
|
967
|
+
publicKey
|
|
968
|
+
);
|
|
1205
969
|
}
|
|
1206
970
|
|
|
1207
971
|
async pullContinuationEvents() {
|
|
@@ -1218,8 +982,7 @@ class ArmedSeat {
|
|
|
1218
982
|
return;
|
|
1219
983
|
}
|
|
1220
984
|
|
|
1221
|
-
|
|
1222
|
-
if (!shouldAcceptInboundEntry(continueFlowMode, entry)) {
|
|
985
|
+
if (!this.verifyInboundEntry(entry)) {
|
|
1223
986
|
continue;
|
|
1224
987
|
}
|
|
1225
988
|
|
|
@@ -1431,13 +1194,12 @@ class ArmedSeat {
|
|
|
1431
1194
|
}
|
|
1432
1195
|
|
|
1433
1196
|
const answers = [];
|
|
1434
|
-
const captureCommentary = this.shouldCaptureCommentary();
|
|
1435
1197
|
if (detectedAgent.type === "codex") {
|
|
1436
1198
|
const result = readCodexAnswers(
|
|
1437
1199
|
this.liveState.sessionFile,
|
|
1438
1200
|
this.liveState.offset,
|
|
1439
1201
|
this.liveState.captureSinceMs,
|
|
1440
|
-
{ flowMode:
|
|
1202
|
+
{ flowMode: this.shouldCaptureCommentary() }
|
|
1441
1203
|
);
|
|
1442
1204
|
this.liveState.offset = result.nextOffset;
|
|
1443
1205
|
answers.push(...result.answers);
|
|
@@ -1446,7 +1208,7 @@ class ArmedSeat {
|
|
|
1446
1208
|
this.liveState.sessionFile,
|
|
1447
1209
|
this.liveState.offset,
|
|
1448
1210
|
this.liveState.captureSinceMs,
|
|
1449
|
-
{ flowMode:
|
|
1211
|
+
{ flowMode: this.shouldCaptureCommentary() }
|
|
1450
1212
|
);
|
|
1451
1213
|
this.liveState.offset = result.nextOffset;
|
|
1452
1214
|
answers.push(...result.answers);
|
|
@@ -1455,7 +1217,7 @@ class ArmedSeat {
|
|
|
1455
1217
|
this.liveState.sessionFile,
|
|
1456
1218
|
this.liveState.lastMessageId,
|
|
1457
1219
|
this.liveState.captureSinceMs,
|
|
1458
|
-
{ flowMode:
|
|
1220
|
+
{ flowMode: this.shouldCaptureCommentary() }
|
|
1459
1221
|
);
|
|
1460
1222
|
this.liveState.lastMessageId = result.lastMessageId;
|
|
1461
1223
|
this.liveState.offset = result.fileSize;
|
|
@@ -1488,7 +1250,7 @@ class ArmedSeat {
|
|
|
1488
1250
|
}
|
|
1489
1251
|
|
|
1490
1252
|
const payload = sanitizeRelayText(entry.text);
|
|
1491
|
-
if (!payload
|
|
1253
|
+
if (!payload) {
|
|
1492
1254
|
return;
|
|
1493
1255
|
}
|
|
1494
1256
|
|
|
@@ -1507,65 +1269,66 @@ class ArmedSeat {
|
|
|
1507
1269
|
const pendingInboundContext = this.getPendingInboundContext();
|
|
1508
1270
|
|
|
1509
1271
|
const entryId = entry.id || createId(12);
|
|
1510
|
-
const
|
|
1272
|
+
const relayEntry = {
|
|
1511
1273
|
id: entryId,
|
|
1512
1274
|
type: "answer",
|
|
1513
1275
|
seatId: this.seatId,
|
|
1276
|
+
sourceSeatId: this.seatId,
|
|
1514
1277
|
origin: entry.origin || "unknown",
|
|
1515
1278
|
phase: entry.phase || "final_answer",
|
|
1516
|
-
flowMode: this.flowMode,
|
|
1517
1279
|
text: payload,
|
|
1518
1280
|
createdAt: entry.createdAt || new Date().toISOString(),
|
|
1519
1281
|
chainId: pendingInboundContext?.chainId || entry.chainId || entryId,
|
|
1520
1282
|
hop: pendingInboundContext ? pendingInboundContext.hop + 1 : 0,
|
|
1521
|
-
challenge: this.trustState.challenge,
|
|
1522
|
-
publicKey: this.identity.publicKey,
|
|
1523
1283
|
};
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
);
|
|
1528
|
-
appendJsonl(this.paths.eventsPath, signedEntry);
|
|
1529
|
-
this.forwardContinuation(signedEntry);
|
|
1284
|
+
|
|
1285
|
+
appendJsonl(this.paths.eventsPath, relayEntry);
|
|
1286
|
+
this.forwardContinuation(relayEntry);
|
|
1530
1287
|
this.rememberEmittedAnswer(answerKey);
|
|
1531
1288
|
|
|
1532
1289
|
this.log(`[${this.seatId}] ${previewText(payload)}`);
|
|
1533
1290
|
}
|
|
1534
1291
|
|
|
1535
|
-
forwardContinuation(
|
|
1536
|
-
|
|
1292
|
+
forwardContinuation(relayEntry) {
|
|
1293
|
+
const targets = this.getConfiguredTargets();
|
|
1294
|
+
if (targets.length === 0) {
|
|
1537
1295
|
return;
|
|
1538
1296
|
}
|
|
1539
1297
|
|
|
1540
|
-
for (const targetEntry of
|
|
1541
|
-
|
|
1298
|
+
for (const targetEntry of targets) {
|
|
1299
|
+
if (!shouldAcceptInboundEntry(targetEntry.flowMode, relayEntry)) {
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const target = this.findLinkedTarget(targetEntry.seatId);
|
|
1542
1304
|
if (!target) {
|
|
1543
|
-
this.log(`[${this.seatId}]
|
|
1305
|
+
this.log(`[${this.seatId}] link ${targetEntry.seatId} unavailable`);
|
|
1544
1306
|
continue;
|
|
1545
1307
|
}
|
|
1546
1308
|
|
|
1547
|
-
const continuationEntry = buildContinuationEntry(
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1309
|
+
const continuationEntry = buildContinuationEntry(
|
|
1310
|
+
this.sessionName,
|
|
1311
|
+
target.seatId,
|
|
1312
|
+
relayEntry,
|
|
1313
|
+
targetEntry.flowMode
|
|
1314
|
+
);
|
|
1315
|
+
continuationEntry.publicKey = this.identity.publicKey;
|
|
1316
|
+
continuationEntry.signature = signText(
|
|
1317
|
+
buildAnswerSignaturePayload(this.sessionName, null, continuationEntry),
|
|
1318
|
+
this.identity.privateKey
|
|
1319
|
+
);
|
|
1551
1320
|
appendJsonl(target.paths.continuePath, continuationEntry);
|
|
1552
|
-
this.log(`[${this.seatId} => ${target.seatId}
|
|
1321
|
+
this.log(`[${this.seatId} => ${target.seatId}] ${previewText(continuationEntry.text)}`);
|
|
1553
1322
|
}
|
|
1554
1323
|
}
|
|
1555
1324
|
|
|
1556
1325
|
async tick() {
|
|
1557
1326
|
if (this.stopRequested()) {
|
|
1558
|
-
this.writeStatus({
|
|
1559
|
-
state: "stopping",
|
|
1560
|
-
partnerLive: this.partnerIsLive(),
|
|
1561
|
-
trust: this.trustState.phase,
|
|
1562
|
-
});
|
|
1327
|
+
this.writeStatus({ state: "stopping" });
|
|
1563
1328
|
this.requestStop("stop_requested");
|
|
1564
1329
|
return;
|
|
1565
1330
|
}
|
|
1566
1331
|
|
|
1567
|
-
this.syncTrustState();
|
|
1568
|
-
await this.pullPartnerEvents();
|
|
1569
1332
|
await this.pullContinuationEvents();
|
|
1570
1333
|
if (this.stopped || this.stopRequested()) {
|
|
1571
1334
|
this.requestStop("stop_requested");
|
|
@@ -1584,9 +1347,6 @@ class ArmedSeat {
|
|
|
1584
1347
|
cwd: live.cwd,
|
|
1585
1348
|
log: live.log,
|
|
1586
1349
|
lastAnswerAt: live.lastAnswerAt,
|
|
1587
|
-
partnerLive: this.partnerIsLive(),
|
|
1588
|
-
trust: this.trustState.phase,
|
|
1589
|
-
challengeReady: Boolean(this.trustState.challenge),
|
|
1590
1350
|
});
|
|
1591
1351
|
}
|
|
1592
1352
|
|
|
@@ -1598,17 +1358,17 @@ class ArmedSeat {
|
|
|
1598
1358
|
|
|
1599
1359
|
this.log(`${BRAND} seat ${this.seatId} armed for ${this.sessionName}.`);
|
|
1600
1360
|
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.
|
|
1361
|
+
this.log(`Seat ${this.seatId} default relay mode is flow ${this.flowMode}.`);
|
|
1362
|
+
if (this.continueSeatId) {
|
|
1363
|
+
this.log(`Seat ${this.seatId} continues to seat ${this.continueSeatId}.`);
|
|
1364
|
+
}
|
|
1365
|
+
const configuredTargets = this.getConfiguredTargets();
|
|
1366
|
+
if (configuredTargets.length > 0) {
|
|
1603
1367
|
this.log(
|
|
1604
|
-
`Seat ${this.seatId}
|
|
1368
|
+
`Seat ${this.seatId} links signed relay targets: ${configuredTargets.map((target) => `${target.seatId}:${target.flowMode}`).join(", ")}.`
|
|
1605
1369
|
);
|
|
1606
1370
|
}
|
|
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
|
-
}
|
|
1371
|
+
this.log("Signed relays are accepted when the sender linked to this seat.");
|
|
1612
1372
|
this.log("Run `muuuuse status` or `muuuuse stop` from any terminal.");
|
|
1613
1373
|
|
|
1614
1374
|
try {
|
|
@@ -1703,18 +1463,10 @@ function buildSeatReport(sessionName, seatId) {
|
|
|
1703
1463
|
|
|
1704
1464
|
return {
|
|
1705
1465
|
seatId,
|
|
1706
|
-
partnerSeatId: status?.partnerSeatId || meta?.partnerSeatId || getPartnerSeatId(seatId),
|
|
1707
1466
|
state: wrapperLive ? status?.state || "running" : "orphaned_child",
|
|
1708
1467
|
flowMode: status?.flowMode || meta?.flowMode || "off",
|
|
1709
1468
|
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
|
-
),
|
|
1469
|
+
continueTargets: normalizeContinueTargets(status?.continueTargets || meta?.continueTargets),
|
|
1718
1470
|
wrapperPid,
|
|
1719
1471
|
childPid,
|
|
1720
1472
|
wrapperLive,
|
|
@@ -1725,10 +1477,8 @@ function buildSeatReport(sessionName, seatId) {
|
|
|
1725
1477
|
relayCount: status?.relayCount || 0,
|
|
1726
1478
|
log: status?.log || null,
|
|
1727
1479
|
startedAt: meta?.startedAt || null,
|
|
1728
|
-
trust: status?.trust || null,
|
|
1729
1480
|
updatedAt: status?.updatedAt || null,
|
|
1730
1481
|
lastAnswerAt: status?.lastAnswerAt || null,
|
|
1731
|
-
partnerLive: Boolean(status?.partnerLive),
|
|
1732
1482
|
};
|
|
1733
1483
|
}
|
|
1734
1484
|
|