muuuuse 1.3.0 → 1.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 +1 -1
- package/package.json +1 -1
- package/src/agents.js +71 -6
- package/src/runtime.js +35 -13
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ muuuuse 2 bash -lc 'while read line; do printf "right: %s\n\n" "$line"; done'
|
|
|
69
69
|
|
|
70
70
|
Type into one seat and the other seat will receive the relayed block.
|
|
71
71
|
|
|
72
|
-
For Codex, Claude, and Gemini, `🔌Muuuuse`
|
|
72
|
+
For Codex, Claude, and Gemini, `🔌Muuuuse` waits for their structured final-answer logs instead of relaying transient screen chatter. For anything else, it first looks for an explicit `(answer)` block and otherwise falls back to the last stable output block after a turn goes idle.
|
|
73
73
|
|
|
74
74
|
## Sessions
|
|
75
75
|
|
package/package.json
CHANGED
package/src/agents.js
CHANGED
|
@@ -76,6 +76,7 @@ const CLAUDE_ROOT = path.join(os.homedir(), ".claude", "projects");
|
|
|
76
76
|
const GEMINI_ROOT = path.join(os.homedir(), ".gemini", "tmp");
|
|
77
77
|
const CODEX_SNAPSHOT_ROOT = path.join(os.homedir(), ".codex", "shell_snapshots");
|
|
78
78
|
const codexSnapshotPaneCache = new Map();
|
|
79
|
+
const codexSnapshotExportsCache = new Map();
|
|
79
80
|
|
|
80
81
|
function walkFiles(rootPath, predicate, results = []) {
|
|
81
82
|
try {
|
|
@@ -157,6 +158,10 @@ function chooseCandidate(candidates, currentPath, processStartedAtMs) {
|
|
|
157
158
|
return null;
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
if (cwdMatches.length === 1) {
|
|
162
|
+
return cwdMatches[0].path;
|
|
163
|
+
}
|
|
164
|
+
|
|
160
165
|
if (processStartedAtMs !== null) {
|
|
161
166
|
const preciseMatches = cwdMatches
|
|
162
167
|
.map((candidate) => ({
|
|
@@ -166,13 +171,12 @@ function chooseCandidate(candidates, currentPath, processStartedAtMs) {
|
|
|
166
171
|
.filter((candidate) => Number.isFinite(candidate.diffMs) && candidate.diffMs <= SESSION_MATCH_WINDOW_MS)
|
|
167
172
|
.sort((left, right) => left.diffMs - right.diffMs || right.mtimeMs - left.mtimeMs);
|
|
168
173
|
|
|
169
|
-
if (preciseMatches.length
|
|
174
|
+
if (preciseMatches.length === 1) {
|
|
170
175
|
return preciseMatches[0].path;
|
|
171
176
|
}
|
|
172
177
|
}
|
|
173
178
|
|
|
174
|
-
|
|
175
|
-
return fallback ? fallback.path : null;
|
|
179
|
+
return null;
|
|
176
180
|
}
|
|
177
181
|
|
|
178
182
|
function extractThreadId(filePath) {
|
|
@@ -202,6 +206,49 @@ function readCodexSnapshotPane(threadId) {
|
|
|
202
206
|
}
|
|
203
207
|
}
|
|
204
208
|
|
|
209
|
+
function readCodexSnapshotExports(threadId) {
|
|
210
|
+
if (!threadId) {
|
|
211
|
+
return {};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (codexSnapshotExportsCache.has(threadId)) {
|
|
215
|
+
return codexSnapshotExportsCache.get(threadId);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const snapshotPath = path.join(CODEX_SNAPSHOT_ROOT, `${threadId}.sh`);
|
|
219
|
+
try {
|
|
220
|
+
const contents = fs.readFileSync(snapshotPath, "utf8");
|
|
221
|
+
const exportsMap = {};
|
|
222
|
+
const pattern = /^declare -x ([A-Z0-9_]+)="((?:[^"\\]|\\.)*)"$/gm;
|
|
223
|
+
|
|
224
|
+
for (const match of contents.matchAll(pattern)) {
|
|
225
|
+
const [, key = "", rawValue = ""] = match;
|
|
226
|
+
exportsMap[key] = rawValue
|
|
227
|
+
.replace(/\\"/g, "\"")
|
|
228
|
+
.replace(/\\\\/g, "\\");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
codexSnapshotExportsCache.set(threadId, exportsMap);
|
|
232
|
+
return exportsMap;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
codexSnapshotExportsCache.set(threadId, {});
|
|
235
|
+
return {};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function snapshotEnvMatches(exportsMap, expectedEnv = null) {
|
|
240
|
+
if (!expectedEnv || typeof expectedEnv !== "object") {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return Object.entries(expectedEnv).every(([key, value]) => {
|
|
245
|
+
if (value === undefined || value === null) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
return exportsMap[key] === String(value);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
205
252
|
function readCodexCandidate(filePath) {
|
|
206
253
|
try {
|
|
207
254
|
const [firstLine] = readFirstLines(filePath, 1);
|
|
@@ -216,6 +263,7 @@ function readCodexCandidate(filePath) {
|
|
|
216
263
|
return {
|
|
217
264
|
path: filePath,
|
|
218
265
|
threadId: extractThreadId(filePath),
|
|
266
|
+
snapshotExports: readCodexSnapshotExports(extractThreadId(filePath)),
|
|
219
267
|
snapshotPaneId: readCodexSnapshotPane(extractThreadId(filePath)),
|
|
220
268
|
cwd: entry.payload.cwd,
|
|
221
269
|
startedAtMs: Date.parse(entry.payload.timestamp),
|
|
@@ -226,12 +274,24 @@ function readCodexCandidate(filePath) {
|
|
|
226
274
|
}
|
|
227
275
|
}
|
|
228
276
|
|
|
229
|
-
function selectCodexSessionFile(currentPath, processStartedAtMs,
|
|
277
|
+
function selectCodexSessionFile(currentPath, processStartedAtMs, options = {}) {
|
|
278
|
+
const paneId = options.paneId || null;
|
|
279
|
+
const snapshotEnv = options.snapshotEnv || null;
|
|
230
280
|
const candidates = walkFiles(CODEX_ROOT, (filePath) => filePath.endsWith(".jsonl"))
|
|
231
281
|
.map((filePath) => readCodexCandidate(filePath))
|
|
232
282
|
.filter((candidate) => candidate !== null);
|
|
233
283
|
|
|
234
284
|
let scopedCandidates = candidates;
|
|
285
|
+
if (snapshotEnv) {
|
|
286
|
+
const exactEnvMatches = scopedCandidates.filter((candidate) => snapshotEnvMatches(candidate.snapshotExports, snapshotEnv));
|
|
287
|
+
if (exactEnvMatches.length === 1) {
|
|
288
|
+
return exactEnvMatches[0].path;
|
|
289
|
+
}
|
|
290
|
+
if (exactEnvMatches.length > 1) {
|
|
291
|
+
scopedCandidates = exactEnvMatches;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
235
295
|
if (paneId) {
|
|
236
296
|
const exactPaneMatches = scopedCandidates.filter((candidate) => candidate.snapshotPaneId === paneId);
|
|
237
297
|
if (exactPaneMatches.length > 0) {
|
|
@@ -415,12 +475,16 @@ function selectGeminiSessionFile(currentPath, processStartedAtMs) {
|
|
|
415
475
|
.filter((candidate) => Number.isFinite(candidate.diffMs) && candidate.diffMs <= SESSION_MATCH_WINDOW_MS)
|
|
416
476
|
.sort((left, right) => left.diffMs - right.diffMs || right.lastUpdatedMs - left.lastUpdatedMs);
|
|
417
477
|
|
|
418
|
-
if (preciseMatches.length
|
|
478
|
+
if (preciseMatches.length === 1) {
|
|
419
479
|
return preciseMatches[0].path;
|
|
420
480
|
}
|
|
421
481
|
}
|
|
422
482
|
|
|
423
|
-
|
|
483
|
+
if (candidates.length === 1) {
|
|
484
|
+
return candidates[0].path;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return null;
|
|
424
488
|
}
|
|
425
489
|
|
|
426
490
|
function readGeminiAnswers(filePath, lastMessageId = null) {
|
|
@@ -460,6 +524,7 @@ function readGeminiAnswers(filePath, lastMessageId = null) {
|
|
|
460
524
|
|
|
461
525
|
module.exports = {
|
|
462
526
|
PRESETS,
|
|
527
|
+
chooseCandidate,
|
|
463
528
|
detectAgent,
|
|
464
529
|
detectAgentTypeFromCommand,
|
|
465
530
|
expandPresetCommand,
|
package/src/runtime.js
CHANGED
|
@@ -31,7 +31,6 @@ const {
|
|
|
31
31
|
} = require("./util");
|
|
32
32
|
|
|
33
33
|
const GENERIC_IDLE_MS = 900;
|
|
34
|
-
const GENERIC_FALLBACK_DELAY_MS = 4000;
|
|
35
34
|
|
|
36
35
|
function resolveSessionName(sessionOverride, currentPath = process.cwd()) {
|
|
37
36
|
return sessionOverride || getDefaultSessionName(currentPath);
|
|
@@ -79,9 +78,9 @@ function parseAnswerEntries(text) {
|
|
|
79
78
|
.filter((entry) => entry && entry.type === "answer" && typeof entry.text === "string");
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
function resolveSessionFile(agentType, currentPath, processStartedAtMs) {
|
|
81
|
+
function resolveSessionFile(agentType, currentPath, processStartedAtMs, options = {}) {
|
|
83
82
|
if (agentType === "codex") {
|
|
84
|
-
return selectCodexSessionFile(currentPath, processStartedAtMs);
|
|
83
|
+
return selectCodexSessionFile(currentPath, processStartedAtMs, options);
|
|
85
84
|
}
|
|
86
85
|
if (agentType === "claude") {
|
|
87
86
|
return selectClaudeSessionFile(currentPath, processStartedAtMs);
|
|
@@ -161,6 +160,11 @@ function extractGenericAnswer(rawText, lastInputText) {
|
|
|
161
160
|
}
|
|
162
161
|
}
|
|
163
162
|
|
|
163
|
+
const markerAnswer = extractMarkedAnswer(candidate);
|
|
164
|
+
if (markerAnswer) {
|
|
165
|
+
return markerAnswer;
|
|
166
|
+
}
|
|
167
|
+
|
|
164
168
|
const blocks = candidate
|
|
165
169
|
.split(/\n{2,}/)
|
|
166
170
|
.map((block) => block.trim())
|
|
@@ -173,6 +177,18 @@ function extractGenericAnswer(rawText, lastInputText) {
|
|
|
173
177
|
return sanitizeRelayText(blocks[blocks.length - 1]);
|
|
174
178
|
}
|
|
175
179
|
|
|
180
|
+
function extractMarkedAnswer(content) {
|
|
181
|
+
const lines = String(content || "").split("\n");
|
|
182
|
+
const answerIndex = lines.findIndex((line) => line.trim().startsWith("(answer)"));
|
|
183
|
+
if (answerIndex === -1) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const answerLines = lines.slice(answerIndex);
|
|
188
|
+
answerLines[0] = answerLines[0].trim().replace(/^\(answer\)\s*/, "");
|
|
189
|
+
return sanitizeRelayText(answerLines.join("\n"));
|
|
190
|
+
}
|
|
191
|
+
|
|
176
192
|
class SeatProcess {
|
|
177
193
|
constructor(options) {
|
|
178
194
|
this.seatId = options.seatId;
|
|
@@ -196,6 +212,8 @@ class SeatProcess {
|
|
|
196
212
|
this.stopped = false;
|
|
197
213
|
this.stdinCleanup = null;
|
|
198
214
|
this.resizeCleanup = null;
|
|
215
|
+
this.childToken = createId(16);
|
|
216
|
+
this.processStartedAtMs = null;
|
|
199
217
|
|
|
200
218
|
this.sessionState = {
|
|
201
219
|
file: null,
|
|
@@ -217,6 +235,7 @@ class SeatProcess {
|
|
|
217
235
|
cwd: this.cwd,
|
|
218
236
|
pid: process.pid,
|
|
219
237
|
childPid: this.childPid,
|
|
238
|
+
childToken: this.childToken,
|
|
220
239
|
agentType: this.agentType,
|
|
221
240
|
command: this.commandTokens,
|
|
222
241
|
commandLine: formatCommand(this.commandTokens),
|
|
@@ -232,6 +251,7 @@ class SeatProcess {
|
|
|
232
251
|
cwd: this.cwd,
|
|
233
252
|
pid: process.pid,
|
|
234
253
|
childPid: this.childPid,
|
|
254
|
+
childToken: this.childToken,
|
|
235
255
|
agentType: this.agentType,
|
|
236
256
|
command: this.commandTokens,
|
|
237
257
|
relayCount: this.relayCount,
|
|
@@ -307,6 +327,7 @@ class SeatProcess {
|
|
|
307
327
|
cwd: this.cwd,
|
|
308
328
|
env: {
|
|
309
329
|
...process.env,
|
|
330
|
+
MUUUUSE_CHILD_TOKEN: this.childToken,
|
|
310
331
|
MUUUUSE_SEAT: String(this.seatId),
|
|
311
332
|
MUUUUSE_SESSION: this.sessionName,
|
|
312
333
|
},
|
|
@@ -315,6 +336,7 @@ class SeatProcess {
|
|
|
315
336
|
});
|
|
316
337
|
|
|
317
338
|
this.childPid = this.child.pid;
|
|
339
|
+
this.processStartedAtMs = Date.now();
|
|
318
340
|
this.writeMeta();
|
|
319
341
|
this.writeStatus({
|
|
320
342
|
partnerSeatId: this.partnerSeatId,
|
|
@@ -352,15 +374,7 @@ class SeatProcess {
|
|
|
352
374
|
}
|
|
353
375
|
|
|
354
376
|
shouldUseGenericCapture() {
|
|
355
|
-
|
|
356
|
-
return true;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (this.sessionState.file) {
|
|
360
|
-
return false;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return Date.now() - this.startedAtMs >= GENERIC_FALLBACK_DELAY_MS;
|
|
377
|
+
return !this.agentType;
|
|
364
378
|
}
|
|
365
379
|
|
|
366
380
|
pullPartnerEvents() {
|
|
@@ -401,7 +415,15 @@ class SeatProcess {
|
|
|
401
415
|
return;
|
|
402
416
|
}
|
|
403
417
|
|
|
404
|
-
const sessionFile = resolveSessionFile(this.agentType, this.cwd, this.
|
|
418
|
+
const sessionFile = resolveSessionFile(this.agentType, this.cwd, this.processStartedAtMs, {
|
|
419
|
+
snapshotEnv: this.agentType === "codex"
|
|
420
|
+
? {
|
|
421
|
+
MUUUUSE_CHILD_TOKEN: this.childToken,
|
|
422
|
+
MUUUUSE_SEAT: String(this.seatId),
|
|
423
|
+
MUUUUSE_SESSION: this.sessionName,
|
|
424
|
+
}
|
|
425
|
+
: null,
|
|
426
|
+
});
|
|
405
427
|
if (!sessionFile) {
|
|
406
428
|
return;
|
|
407
429
|
}
|