muuuuse 2.3.5 → 4.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 +1 -1
- package/src/agents.js +51 -16
- package/src/cli.js +33 -79
- package/src/runtime.js +21 -7
- package/src/util.js +6 -6
package/package.json
CHANGED
package/src/agents.js
CHANGED
|
@@ -555,8 +555,7 @@ function selectClaudeSessionFile(currentPath, processStartedAtMs, options = {})
|
|
|
555
555
|
return selectSessionCandidatePath(candidates, currentPath, processStartedAtMs);
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
-
function extractClaudeAssistantText(content
|
|
559
|
-
const flowMode = options.flowMode === true;
|
|
558
|
+
function extractClaudeAssistantText(content) {
|
|
560
559
|
if (!Array.isArray(content)) {
|
|
561
560
|
return "";
|
|
562
561
|
}
|
|
@@ -569,7 +568,23 @@ function extractClaudeAssistantText(content, options = {}) {
|
|
|
569
568
|
if (item.type === "text" && typeof item.text === "string") {
|
|
570
569
|
return [item.text.trim()];
|
|
571
570
|
}
|
|
572
|
-
|
|
571
|
+
return [];
|
|
572
|
+
})
|
|
573
|
+
.filter((text) => text.length > 0)
|
|
574
|
+
.join("\n");
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function extractClaudeThinkingText(content) {
|
|
578
|
+
if (!Array.isArray(content)) {
|
|
579
|
+
return "";
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return content
|
|
583
|
+
.flatMap((item) => {
|
|
584
|
+
if (!item || typeof item !== "object") {
|
|
585
|
+
return [];
|
|
586
|
+
}
|
|
587
|
+
if (item.type === "thinking" && typeof item.thinking === "string") {
|
|
573
588
|
return [item.thinking.trim()];
|
|
574
589
|
}
|
|
575
590
|
return [];
|
|
@@ -586,28 +601,46 @@ function parseClaudeAssistantLine(line, options = {}) {
|
|
|
586
601
|
return null;
|
|
587
602
|
}
|
|
588
603
|
|
|
589
|
-
|
|
604
|
+
const isFinal = entry.message?.stop_reason === "end_turn";
|
|
605
|
+
if (!flowMode && !isFinal) {
|
|
590
606
|
return null;
|
|
591
607
|
}
|
|
592
608
|
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
609
|
+
const id = entry.uuid || entry.message.id || hashText(line);
|
|
610
|
+
const timestamp = entry.timestamp || new Date().toISOString();
|
|
611
|
+
const results = [];
|
|
612
|
+
|
|
613
|
+
if (flowMode) {
|
|
614
|
+
const thinking = sanitizeRelayText(extractClaudeThinkingText(entry.message.content));
|
|
615
|
+
if (thinking) {
|
|
616
|
+
results.push({
|
|
617
|
+
id: `${id}-thinking`,
|
|
618
|
+
text: thinking,
|
|
619
|
+
phase: "commentary",
|
|
620
|
+
timestamp,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
596
623
|
}
|
|
597
624
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
625
|
+
const text = sanitizeRelayText(extractClaudeAssistantText(entry.message.content));
|
|
626
|
+
if (text) {
|
|
627
|
+
results.push({
|
|
628
|
+
id,
|
|
629
|
+
text,
|
|
630
|
+
phase: isFinal ? "final_answer" : "commentary",
|
|
631
|
+
timestamp,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return results.length > 0 ? results : null;
|
|
604
636
|
} catch {
|
|
605
637
|
return null;
|
|
606
638
|
}
|
|
607
639
|
}
|
|
608
640
|
|
|
609
641
|
function parseClaudeFinalLine(line) {
|
|
610
|
-
|
|
642
|
+
const results = parseClaudeAssistantLine(line, { flowMode: false });
|
|
643
|
+
return Array.isArray(results) ? results[0] || null : results;
|
|
611
644
|
}
|
|
612
645
|
|
|
613
646
|
function readClaudeAnswers(filePath, offset, sinceMs = null, options = {}) {
|
|
@@ -616,8 +649,10 @@ function readClaudeAnswers(filePath, offset, sinceMs = null, options = {}) {
|
|
|
616
649
|
.split("\n")
|
|
617
650
|
.map((line) => line.trim())
|
|
618
651
|
.filter((line) => line.length > 0)
|
|
619
|
-
.
|
|
620
|
-
|
|
652
|
+
.flatMap((line) => {
|
|
653
|
+
const result = parseClaudeAssistantLine(line, options);
|
|
654
|
+
return Array.isArray(result) ? result : result ? [result] : [];
|
|
655
|
+
})
|
|
621
656
|
.filter((entry) => isAnswerNewEnough(entry, sinceMs));
|
|
622
657
|
|
|
623
658
|
return { nextOffset, answers };
|
package/src/cli.js
CHANGED
|
@@ -56,11 +56,10 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
56
56
|
|
|
57
57
|
const seatId = normalizeSeatId(command);
|
|
58
58
|
if (seatId) {
|
|
59
|
-
const { flowMode,
|
|
59
|
+
const { flowMode, continueTargets } = parseSeatOptions(command, argv.slice(1));
|
|
60
60
|
const seat = new ArmedSeat({
|
|
61
61
|
cwd: process.cwd(),
|
|
62
62
|
continueTargets,
|
|
63
|
-
continueSeatId,
|
|
64
63
|
flowMode,
|
|
65
64
|
seatId,
|
|
66
65
|
});
|
|
@@ -116,101 +115,55 @@ function renderLinkTargets(seat) {
|
|
|
116
115
|
for (const target of Array.isArray(seat.continueTargets) ? seat.continueTargets : []) {
|
|
117
116
|
targets.push(target);
|
|
118
117
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
if (targets.length === 0) {
|
|
119
|
+
return "";
|
|
120
|
+
}
|
|
121
|
+
return targets.map((target) => `${target.targetSeatId}:${target.flowMode}`).join(", ");
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
function parseSeatOptions(command, args) {
|
|
126
125
|
const seatId = normalizeSeatId(command);
|
|
127
126
|
let flowMode = "off";
|
|
128
|
-
let continueSeatId = null;
|
|
129
127
|
let continueTargets = [];
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const token = String(args[index] || "").trim().toLowerCase();
|
|
128
|
+
if (args.length === 0) {
|
|
129
|
+
return { flowMode, continueTargets };
|
|
130
|
+
}
|
|
134
131
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
break;
|
|
132
|
+
if (String(args[0] || "").trim().toLowerCase() === "link") {
|
|
133
|
+
const parsedLinks = parseLinkTargets(args.slice(1), seatId, flowMode);
|
|
134
|
+
if (parsedLinks.consumed === args.length - 1 && parsedLinks.consumed > 0) {
|
|
135
|
+
return {
|
|
136
|
+
flowMode: parsedLinks.flowMode,
|
|
137
|
+
continueTargets: parsedLinks.continueTargets,
|
|
138
|
+
};
|
|
143
139
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
break;
|
|
140
|
+
} else {
|
|
141
|
+
let index = 0;
|
|
142
|
+
|
|
143
|
+
const flowToken = String(args[index] || "").trim().toLowerCase();
|
|
144
|
+
const flowModeToken = String(args[index + 1] || "").trim().toLowerCase();
|
|
145
|
+
if (flowToken === "flow" && (flowModeToken === "on" || flowModeToken === "off")) {
|
|
146
|
+
flowMode = flowModeToken;
|
|
147
|
+
index += 2;
|
|
154
148
|
}
|
|
155
149
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
continueSeatId = continueTargets[0]?.targetSeatId || null;
|
|
162
|
-
index += 1 + parsedLinks.consumed;
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
break;
|
|
150
|
+
const continueToken = String(args[index] || "").trim().toLowerCase();
|
|
151
|
+
const targetSeatId = normalizeSeatId(args[index + 1]);
|
|
152
|
+
if (continueToken === "continue" && targetSeatId) {
|
|
153
|
+
continueTargets = [{ targetSeatId, flowMode }];
|
|
154
|
+
index += 2;
|
|
166
155
|
}
|
|
167
156
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (index === args.length) {
|
|
172
|
-
return { flowMode, continueSeatId, continueTargets };
|
|
157
|
+
if (index === args.length) {
|
|
158
|
+
return { flowMode, continueTargets };
|
|
159
|
+
}
|
|
173
160
|
}
|
|
174
161
|
|
|
175
162
|
throw new Error(
|
|
176
|
-
`\`muuuuse ${command}\` accepts no extra arguments
|
|
163
|
+
`\`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
164
|
);
|
|
178
165
|
}
|
|
179
166
|
|
|
180
|
-
function mergeTargets(existingTargets, nextTargets) {
|
|
181
|
-
const merged = [];
|
|
182
|
-
for (const target of Array.isArray(existingTargets) ? existingTargets : []) {
|
|
183
|
-
upsertTarget(merged, target);
|
|
184
|
-
}
|
|
185
|
-
for (const target of Array.isArray(nextTargets) ? nextTargets : []) {
|
|
186
|
-
upsertTarget(merged, target);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return merged;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function parseContinueTargets(args, defaultFlowMode) {
|
|
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
167
|
function parseLinkTargets(args, seatId, defaultFlowMode) {
|
|
215
168
|
const partnerSeatId = seatId ? getPartnerSeatId(seatId) : null;
|
|
216
169
|
const continueTargets = [];
|
|
@@ -263,4 +216,5 @@ function upsertTarget(targets, nextTarget) {
|
|
|
263
216
|
|
|
264
217
|
module.exports = {
|
|
265
218
|
main,
|
|
219
|
+
parseSeatOptions,
|
|
266
220
|
};
|
package/src/runtime.js
CHANGED
|
@@ -42,6 +42,7 @@ const {
|
|
|
42
42
|
|
|
43
43
|
const TYPE_CHUNK_DELAY_MS = 18;
|
|
44
44
|
const TYPE_CHUNK_SIZE = 24;
|
|
45
|
+
const TYPE_SUBMIT_DELAY_MS = 60;
|
|
45
46
|
const MIRROR_SUPPRESSION_WINDOW_MS = 30 * 1000;
|
|
46
47
|
const PENDING_RELAY_CONTEXT_TTL_MS = 2 * 60 * 1000;
|
|
47
48
|
const EMITTED_ANSWER_TTL_MS = 5 * 60 * 1000;
|
|
@@ -620,6 +621,14 @@ async function sendTextAndEnter(child, text, shouldAbort = () => false) {
|
|
|
620
621
|
return false;
|
|
621
622
|
}
|
|
622
623
|
|
|
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
|
+
|
|
623
632
|
try {
|
|
624
633
|
child.write("\r");
|
|
625
634
|
} catch {
|
|
@@ -641,7 +650,6 @@ class ArmedSeat {
|
|
|
641
650
|
),
|
|
642
651
|
this.flowMode
|
|
643
652
|
);
|
|
644
|
-
this.continueSeatId = this.continueTargets[0]?.targetSeatId || normalizeContinueSeatId(options.continueSeatId);
|
|
645
653
|
this.cwd = normalizeWorkingPath(options.cwd);
|
|
646
654
|
if (this.continueTargets.some((target) => target.targetSeatId === this.seatId)) {
|
|
647
655
|
throw new Error(`\`muuuuse ${this.seatId}\` cannot continue to itself.`);
|
|
@@ -719,7 +727,6 @@ class ArmedSeat {
|
|
|
719
727
|
partnerSeatId: this.partnerSeatId,
|
|
720
728
|
sessionName: this.sessionName,
|
|
721
729
|
flowMode: this.flowMode,
|
|
722
|
-
continueSeatId: this.continueSeatId,
|
|
723
730
|
continueTargets: this.continueTargets,
|
|
724
731
|
cwd: this.cwd,
|
|
725
732
|
pid: process.pid,
|
|
@@ -736,7 +743,6 @@ class ArmedSeat {
|
|
|
736
743
|
partnerSeatId: this.partnerSeatId,
|
|
737
744
|
sessionName: this.sessionName,
|
|
738
745
|
flowMode: this.flowMode,
|
|
739
|
-
continueSeatId: this.continueSeatId,
|
|
740
746
|
continueTargets: this.continueTargets,
|
|
741
747
|
cwd: this.cwd,
|
|
742
748
|
pid: process.pid,
|
|
@@ -1516,11 +1522,16 @@ class ArmedSeat {
|
|
|
1516
1522
|
buildAnswerSignaturePayload(this.sessionName, this.trustState.challenge, signedEntry),
|
|
1517
1523
|
this.identity.privateKey
|
|
1518
1524
|
);
|
|
1519
|
-
|
|
1525
|
+
|
|
1526
|
+
// Only write to partner events if local flow mode accepts this phase.
|
|
1527
|
+
// Commentary is gated here; continue targets filter independently.
|
|
1528
|
+
if (shouldAcceptInboundEntry(this.flowMode, signedEntry)) {
|
|
1529
|
+
appendJsonl(this.paths.eventsPath, signedEntry);
|
|
1530
|
+
this.log(`[${this.seatId}] ${previewText(payload)}`);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1520
1533
|
this.forwardContinuation(signedEntry);
|
|
1521
1534
|
this.rememberEmittedAnswer(answerKey);
|
|
1522
|
-
|
|
1523
|
-
this.log(`[${this.seatId}] ${previewText(payload)}`);
|
|
1524
1535
|
}
|
|
1525
1536
|
|
|
1526
1537
|
forwardContinuation(signedEntry) {
|
|
@@ -1529,6 +1540,10 @@ class ArmedSeat {
|
|
|
1529
1540
|
}
|
|
1530
1541
|
|
|
1531
1542
|
for (const targetEntry of this.continueTargets) {
|
|
1543
|
+
if (!shouldAcceptInboundEntry(targetEntry.flowMode, signedEntry)) {
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1532
1547
|
const target = this.findContinuationTarget(targetEntry.targetSeatId);
|
|
1533
1548
|
if (!target) {
|
|
1534
1549
|
this.log(`[${this.seatId}] continue ${targetEntry.targetSeatId} unavailable`);
|
|
@@ -1697,7 +1712,6 @@ function buildSeatReport(sessionName, seatId) {
|
|
|
1697
1712
|
partnerSeatId: status?.partnerSeatId || meta?.partnerSeatId || getPartnerSeatId(seatId),
|
|
1698
1713
|
state: wrapperLive ? status?.state || "running" : "orphaned_child",
|
|
1699
1714
|
flowMode: status?.flowMode || meta?.flowMode || "off",
|
|
1700
|
-
continueSeatId: status?.continueSeatId || meta?.continueSeatId || null,
|
|
1701
1715
|
continueTargets: normalizeContinueTargets(
|
|
1702
1716
|
status?.continueTargets || meta?.continueTargets || (
|
|
1703
1717
|
(status?.continueSeatId || meta?.continueSeatId)
|
package/src/util.js
CHANGED
|
@@ -287,10 +287,10 @@ function usage() {
|
|
|
287
287
|
"Usage:",
|
|
288
288
|
" muuuuse",
|
|
289
289
|
" muuuuse 1",
|
|
290
|
+
" muuuuse 1 link 2 flow off",
|
|
290
291
|
" muuuuse 1 link 2 flow on",
|
|
291
292
|
" muuuuse 1 link 2 flow on 3 flow off",
|
|
292
293
|
" muuuuse 2",
|
|
293
|
-
" muuuuse 2 link 1 flow on",
|
|
294
294
|
" muuuuse 3",
|
|
295
295
|
" muuuuse 4",
|
|
296
296
|
" muuuuse 4 link 3 flow off 1 flow on",
|
|
@@ -298,13 +298,13 @@ function usage() {
|
|
|
298
298
|
" muuuuse status",
|
|
299
299
|
"",
|
|
300
300
|
"Flow:",
|
|
301
|
-
" 1. Run `muuuuse 1` in terminal one.",
|
|
302
|
-
" 2.
|
|
301
|
+
" 1. Run `muuuuse 1` in terminal one, then `muuuuse 2` in terminal two.",
|
|
302
|
+
" 2. Bare seats default to final-only pair relay.",
|
|
303
303
|
" 3. The odd seat generates the session key and the matching even seat signs it automatically.",
|
|
304
304
|
" 4. Additional pairs work the same way: `3/4`, `5/6`, `7/8`...",
|
|
305
|
-
" 5. Use `link <seat> flow on [<seat> flow off ...]` to set each outbound
|
|
306
|
-
" 6.
|
|
307
|
-
" 7.
|
|
305
|
+
" 5. Use `link <seat> flow on [<seat> flow off ...]` to set each outbound route.",
|
|
306
|
+
" 6. Include the odd/even partner in `link` to set normal pair flow.",
|
|
307
|
+
" 7. Any extra linked seats receive routed copies with their own flow mode.",
|
|
308
308
|
" 8. `flow off` sends final answers only. `flow on` keeps assistant commentary bouncing.",
|
|
309
309
|
" 9. Run `muuuuse status` or `muuuuse stop` from any shell.",
|
|
310
310
|
"",
|