muuuuse 3.3.3 → 3.3.5
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 +88 -30
- package/src/runtime.js +189 -4
package/package.json
CHANGED
package/src/agents.js
CHANGED
|
@@ -179,22 +179,73 @@ function readCodexSeatClaim(sessionId) {
|
|
|
179
179
|
return null;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
const
|
|
182
|
+
const claims = readCodexSeatClaims(sessionId);
|
|
183
|
+
return claims[0] || null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function readCodexSeatClaims(sessionId) {
|
|
187
|
+
if (!sessionId) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const candidatePaths = listCodexSnapshotPaths(sessionId);
|
|
192
|
+
return candidatePaths
|
|
193
|
+
.map((snapshotPath) => {
|
|
194
|
+
let text;
|
|
195
|
+
try {
|
|
196
|
+
text = fs.readFileSync(snapshotPath, "utf8");
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const seatMatch = text.match(/declare -x MUUUUSE_SEAT="([^"]+)"/);
|
|
202
|
+
const sessionMatch = text.match(/declare -x MUUUUSE_SESSION="([^"]+)"/);
|
|
203
|
+
if (!seatMatch || !sessionMatch) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let mtimeMs = 0;
|
|
208
|
+
try {
|
|
209
|
+
mtimeMs = fs.statSync(snapshotPath).mtimeMs;
|
|
210
|
+
} catch {
|
|
211
|
+
// Best effort only.
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
seatId: seatMatch[1],
|
|
216
|
+
sessionName: sessionMatch[1],
|
|
217
|
+
snapshotPath,
|
|
218
|
+
mtimeMs,
|
|
219
|
+
};
|
|
220
|
+
})
|
|
221
|
+
.filter((claim) => claim !== null)
|
|
222
|
+
.sort((left, right) => right.mtimeMs - left.mtimeMs || left.snapshotPath.localeCompare(right.snapshotPath));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function listCodexSnapshotPaths(sessionId) {
|
|
226
|
+
const exactName = `${sessionId}.sh`;
|
|
227
|
+
const prefix = `${sessionId}.`;
|
|
228
|
+
const candidateNames = [];
|
|
229
|
+
|
|
183
230
|
try {
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
231
|
+
for (const entry of fs.readdirSync(CODEX_SNAPSHOT_ROOT, { withFileTypes: true })) {
|
|
232
|
+
if (!entry.isFile()) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
190
235
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
236
|
+
if (entry.name === exactName || (entry.name.startsWith(prefix) && entry.name.endsWith(".sh"))) {
|
|
237
|
+
candidateNames.push(entry.name);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
195
240
|
} catch {
|
|
196
|
-
|
|
241
|
+
// Snapshot directory may not exist yet.
|
|
197
242
|
}
|
|
243
|
+
|
|
244
|
+
return uniquePaths(candidateNames.map((name) => path.join(CODEX_SNAPSHOT_ROOT, name)));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function candidateHasClaim(candidate, predicate) {
|
|
248
|
+
return readCodexSeatClaims(candidate.sessionId).some((claim) => predicate(claim));
|
|
198
249
|
}
|
|
199
250
|
|
|
200
251
|
function selectClaimedCodexCandidatePath(candidates, options = {}) {
|
|
@@ -204,29 +255,28 @@ function selectClaimedCodexCandidatePath(candidates, options = {}) {
|
|
|
204
255
|
return null;
|
|
205
256
|
}
|
|
206
257
|
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const exactMatches = annotated.filter((candidate) => (
|
|
213
|
-
candidate.claim?.seatId === seatId &&
|
|
214
|
-
candidate.claim?.sessionName === sessionName
|
|
258
|
+
const exactMatches = candidates.filter((candidate) => (
|
|
259
|
+
candidateHasClaim(candidate, (claim) => (
|
|
260
|
+
claim.seatId === seatId &&
|
|
261
|
+
claim.sessionName === sessionName
|
|
262
|
+
))
|
|
215
263
|
));
|
|
216
264
|
if (exactMatches.length === 1) {
|
|
217
265
|
return exactMatches[0].path;
|
|
218
266
|
}
|
|
219
267
|
|
|
220
|
-
const otherSeatClaims =
|
|
221
|
-
candidate
|
|
222
|
-
|
|
268
|
+
const otherSeatClaims = candidates.filter((candidate) => (
|
|
269
|
+
candidateHasClaim(candidate, (claim) => (
|
|
270
|
+
claim.sessionName === sessionName &&
|
|
271
|
+
claim.seatId !== seatId
|
|
272
|
+
))
|
|
223
273
|
));
|
|
224
274
|
if (otherSeatClaims.length === 0) {
|
|
225
275
|
return null;
|
|
226
276
|
}
|
|
227
277
|
|
|
228
278
|
const foreignPaths = new Set(otherSeatClaims.map((candidate) => candidate.path));
|
|
229
|
-
const remaining =
|
|
279
|
+
const remaining = candidates.filter((candidate) => !foreignPaths.has(candidate.path));
|
|
230
280
|
if (remaining.length === 1) {
|
|
231
281
|
return remaining[0].path;
|
|
232
282
|
}
|
|
@@ -332,8 +382,10 @@ function selectExactClaimedCodexCandidate(candidates, options = {}, processStart
|
|
|
332
382
|
|
|
333
383
|
const exactMatches = rankCodexCandidates(
|
|
334
384
|
candidates.filter((candidate) => {
|
|
335
|
-
|
|
336
|
-
|
|
385
|
+
return candidateHasClaim(candidate, (claim) => (
|
|
386
|
+
claim.seatId === seatId &&
|
|
387
|
+
claim.sessionName === sessionName
|
|
388
|
+
));
|
|
337
389
|
}),
|
|
338
390
|
processStartedAtMs
|
|
339
391
|
);
|
|
@@ -349,8 +401,11 @@ function filterForeignClaimedCodexCandidates(candidates, options = {}) {
|
|
|
349
401
|
}
|
|
350
402
|
|
|
351
403
|
return candidates.filter((candidate) => {
|
|
352
|
-
|
|
353
|
-
|
|
404
|
+
return !candidateHasClaim(candidate, (claim) => (
|
|
405
|
+
claim.sessionName === sessionName &&
|
|
406
|
+
claim.seatId &&
|
|
407
|
+
claim.seatId !== seatId
|
|
408
|
+
));
|
|
354
409
|
});
|
|
355
410
|
}
|
|
356
411
|
|
|
@@ -391,8 +446,11 @@ function selectCodexCandidatePath(candidates, currentPath, processStartedAtMs, o
|
|
|
391
446
|
seatId &&
|
|
392
447
|
sessionName &&
|
|
393
448
|
cwdMatches.some((candidate) => {
|
|
394
|
-
|
|
395
|
-
|
|
449
|
+
return candidateHasClaim(candidate, (claim) => (
|
|
450
|
+
claim.sessionName === sessionName &&
|
|
451
|
+
claim.seatId &&
|
|
452
|
+
claim.seatId !== seatId
|
|
453
|
+
));
|
|
396
454
|
})
|
|
397
455
|
);
|
|
398
456
|
const allowedMatches = filterForeignClaimedCodexCandidates(cwdMatches, options);
|
package/src/runtime.js
CHANGED
|
@@ -56,8 +56,11 @@ const EMITTED_ANSWER_TTL_MS = 5 * 60 * 1000;
|
|
|
56
56
|
const MAX_RECENT_INBOUND_RELAYS = 12;
|
|
57
57
|
const MAX_RECENT_EMITTED_ANSWERS = 48;
|
|
58
58
|
const STOP_FORCE_KILL_MS = 1200;
|
|
59
|
+
const STOP_PURGE_WAIT_MS = STOP_FORCE_KILL_MS + 1200;
|
|
60
|
+
const STOP_PURGE_POLL_MS = 60;
|
|
59
61
|
const SEAT_JOIN_WAIT_MS = 3000;
|
|
60
62
|
const SEAT_JOIN_POLL_MS = 60;
|
|
63
|
+
const MAX_PENDING_PASSIVE_INPUT_CHARS = 512;
|
|
61
64
|
const CHILD_ENV_DROP_KEYS = [
|
|
62
65
|
"CODEX_CI",
|
|
63
66
|
"CODEX_MANAGED_BY_NPM",
|
|
@@ -200,6 +203,7 @@ function ensureSeatGeminiCliHome(homeDir, cwd, seatId, baseEnv = process.env) {
|
|
|
200
203
|
const sourceHomeRoot = String(baseEnv.GEMINI_CLI_HOME || homeDir).trim() || homeDir;
|
|
201
204
|
const sourceGeminiDir = path.join(sourceHomeRoot, ".gemini");
|
|
202
205
|
const targetHomeRoot = getSeatGeminiCliHome(homeDir, cwd, seatId);
|
|
206
|
+
fs.rmSync(targetHomeRoot, { recursive: true, force: true });
|
|
203
207
|
const targetGeminiDir = ensureDir(path.join(targetHomeRoot, ".gemini"));
|
|
204
208
|
|
|
205
209
|
let sourceEntries = [];
|
|
@@ -653,6 +657,103 @@ function isMeaningfulTerminalInput(input) {
|
|
|
653
657
|
return stripPassiveTerminalInput(input).length > 0;
|
|
654
658
|
}
|
|
655
659
|
|
|
660
|
+
function consumeTerminalProxyInput(input, pendingPassiveInput = "") {
|
|
661
|
+
const combined = `${pendingPassiveInput}${String(input || "")}`;
|
|
662
|
+
let forwardText = "";
|
|
663
|
+
let index = 0;
|
|
664
|
+
|
|
665
|
+
while (index < combined.length) {
|
|
666
|
+
const current = combined[index];
|
|
667
|
+
if (current !== "\u001b") {
|
|
668
|
+
forwardText += current;
|
|
669
|
+
index += 1;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (index + 1 >= combined.length) {
|
|
674
|
+
forwardText += current;
|
|
675
|
+
index += 1;
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const next = combined[index + 1];
|
|
680
|
+
if (next === "]") {
|
|
681
|
+
const belIndex = combined.indexOf("\u0007", index + 2);
|
|
682
|
+
const stIndex = combined.indexOf("\u001b\\", index + 2);
|
|
683
|
+
const endIndex = (
|
|
684
|
+
belIndex !== -1 && stIndex !== -1 ? Math.min(belIndex, stIndex) :
|
|
685
|
+
belIndex !== -1 ? belIndex :
|
|
686
|
+
stIndex
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
if (endIndex === -1) {
|
|
690
|
+
const pending = combined.slice(index).slice(0, MAX_PENDING_PASSIVE_INPUT_CHARS);
|
|
691
|
+
return {
|
|
692
|
+
forwardText,
|
|
693
|
+
meaningful: isMeaningfulTerminalInput(forwardText),
|
|
694
|
+
pendingPassiveInput: pending,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
index = endIndex + (endIndex === stIndex ? 2 : 1);
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (next === "[") {
|
|
703
|
+
if (index + 2 >= combined.length) {
|
|
704
|
+
return {
|
|
705
|
+
forwardText,
|
|
706
|
+
meaningful: isMeaningfulTerminalInput(forwardText),
|
|
707
|
+
pendingPassiveInput: combined.slice(index).slice(0, MAX_PENDING_PASSIVE_INPUT_CHARS),
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
let endIndex = index + 2;
|
|
712
|
+
while (endIndex < combined.length && !/[@-~]/.test(combined[endIndex])) {
|
|
713
|
+
endIndex += 1;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
if (endIndex >= combined.length) {
|
|
717
|
+
const pending = combined.slice(index);
|
|
718
|
+
if (/^\u001b\[(?:\??[0-9;]*)?$/.test(pending)) {
|
|
719
|
+
return {
|
|
720
|
+
forwardText,
|
|
721
|
+
meaningful: isMeaningfulTerminalInput(forwardText),
|
|
722
|
+
pendingPassiveInput: pending.slice(0, MAX_PENDING_PASSIVE_INPUT_CHARS),
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
forwardText += pending;
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const sequence = combined.slice(index, endIndex + 1);
|
|
731
|
+
if (
|
|
732
|
+
sequence === "\u001b[I" ||
|
|
733
|
+
sequence === "\u001b[O" ||
|
|
734
|
+
/^\u001b\[\d+;\d+R$/.test(sequence) ||
|
|
735
|
+
/^\u001b\[\?[0-9;]*c$/.test(sequence)
|
|
736
|
+
) {
|
|
737
|
+
index = endIndex + 1;
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
forwardText += sequence;
|
|
742
|
+
index = endIndex + 1;
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
forwardText += current;
|
|
747
|
+
index += 1;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return {
|
|
751
|
+
forwardText,
|
|
752
|
+
meaningful: isMeaningfulTerminalInput(forwardText),
|
|
753
|
+
pendingPassiveInput: "",
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
|
|
656
757
|
async function sendTextAndEnter(child, text, shouldAbort = () => false) {
|
|
657
758
|
const options = typeof shouldAbort === "function" ? { shouldAbort } : (shouldAbort || {});
|
|
658
759
|
const shouldStop = typeof options.shouldAbort === "function" ? options.shouldAbort : () => false;
|
|
@@ -730,6 +831,7 @@ class ArmedSeat {
|
|
|
730
831
|
this.forceKillTimer = null;
|
|
731
832
|
this.identity = loadOrCreateSeatIdentity(this.paths);
|
|
732
833
|
this.lastUserInputAtMs = 0;
|
|
834
|
+
this.pendingPassiveInput = "";
|
|
733
835
|
this.pendingInboundContext = null;
|
|
734
836
|
this.recentInboundRelays = [];
|
|
735
837
|
this.recentEmittedAnswers = [];
|
|
@@ -843,14 +945,16 @@ class ArmedSeat {
|
|
|
843
945
|
installStdinProxy() {
|
|
844
946
|
const handleData = (chunk) => {
|
|
845
947
|
const chunkText = chunk.toString("utf8");
|
|
846
|
-
|
|
948
|
+
const proxyInput = consumeTerminalProxyInput(chunkText, this.pendingPassiveInput);
|
|
949
|
+
this.pendingPassiveInput = proxyInput.pendingPassiveInput;
|
|
950
|
+
if (proxyInput.meaningful) {
|
|
847
951
|
this.lastUserInputAtMs = Date.now();
|
|
848
952
|
this.pendingInboundContext = null;
|
|
849
953
|
}
|
|
850
|
-
if (!this.child) {
|
|
954
|
+
if (!this.child || proxyInput.forwardText.length === 0) {
|
|
851
955
|
return;
|
|
852
956
|
}
|
|
853
|
-
this.child.write(
|
|
957
|
+
this.child.write(proxyInput.forwardText);
|
|
854
958
|
};
|
|
855
959
|
|
|
856
960
|
const handleEnd = () => {
|
|
@@ -1295,7 +1399,10 @@ class ArmedSeat {
|
|
|
1295
1399
|
this.liveState.lastMessageId = null;
|
|
1296
1400
|
const sessionStartedAtMs = readSessionFileStartedAtMs(detectedAgent.type, resolvedSessionFile);
|
|
1297
1401
|
if (Number.isFinite(sessionStartedAtMs)) {
|
|
1298
|
-
this.liveState.captureSinceMs = Math.
|
|
1402
|
+
this.liveState.captureSinceMs = Math.max(
|
|
1403
|
+
this.startedAtMs,
|
|
1404
|
+
Math.min(this.liveState.captureSinceMs, sessionStartedAtMs)
|
|
1405
|
+
);
|
|
1299
1406
|
}
|
|
1300
1407
|
}
|
|
1301
1408
|
|
|
@@ -1534,6 +1641,8 @@ class ArmedSeat {
|
|
|
1534
1641
|
exitedAt: new Date().toISOString(),
|
|
1535
1642
|
state: "exited",
|
|
1536
1643
|
});
|
|
1644
|
+
|
|
1645
|
+
purgeSeatTransientState(this.sessionPaths.dir, this.paths.dir, this.cwd, this.seatId);
|
|
1537
1646
|
}
|
|
1538
1647
|
}
|
|
1539
1648
|
|
|
@@ -1658,6 +1767,10 @@ function stopAllSessions() {
|
|
|
1658
1767
|
}
|
|
1659
1768
|
}
|
|
1660
1769
|
|
|
1770
|
+
for (const session of report.sessions) {
|
|
1771
|
+
finalizeStoppedSession(session);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1661
1774
|
return {
|
|
1662
1775
|
requestedAt,
|
|
1663
1776
|
sessions: report.sessions,
|
|
@@ -1667,6 +1780,7 @@ function stopAllSessions() {
|
|
|
1667
1780
|
module.exports = {
|
|
1668
1781
|
ArmedSeat,
|
|
1669
1782
|
buildChildEnv,
|
|
1783
|
+
consumeTerminalProxyInput,
|
|
1670
1784
|
ensureSeatGeminiCliHome,
|
|
1671
1785
|
chunkRelayPayloadForTyping,
|
|
1672
1786
|
getStatusReport,
|
|
@@ -1691,6 +1805,77 @@ function signalPid(pid, signal) {
|
|
|
1691
1805
|
}
|
|
1692
1806
|
}
|
|
1693
1807
|
|
|
1808
|
+
function finalizeStoppedSession(session, timeoutMs = STOP_PURGE_WAIT_MS) {
|
|
1809
|
+
if (!session || !Array.isArray(session.seats) || session.seats.length === 0) {
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
const deadline = Date.now() + timeoutMs;
|
|
1814
|
+
while (Date.now() <= deadline) {
|
|
1815
|
+
const pendingSeats = session.seats.filter((seat) => isPidAlive(seat.wrapperPid) || isPidAlive(seat.childPid));
|
|
1816
|
+
if (pendingSeats.length === 0) {
|
|
1817
|
+
break;
|
|
1818
|
+
}
|
|
1819
|
+
sleepSync(STOP_PURGE_POLL_MS);
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
for (const seat of session.seats) {
|
|
1823
|
+
if (isPidAlive(seat.childPid)) {
|
|
1824
|
+
signalProcessFamily(seat.childPid, "SIGKILL");
|
|
1825
|
+
}
|
|
1826
|
+
if (isPidAlive(seat.wrapperPid)) {
|
|
1827
|
+
signalPid(seat.wrapperPid, "SIGKILL");
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
sleepSync(STOP_PURGE_POLL_MS);
|
|
1832
|
+
|
|
1833
|
+
const sessionDir = getSessionPaths(session.sessionName).dir;
|
|
1834
|
+
for (const seat of session.seats) {
|
|
1835
|
+
purgeSeatTransientState(sessionDir, getSeatPaths(session.sessionName, seat.seatId).dir, seat.cwd, seat.seatId);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
function purgeSeatTransientState(sessionDir, seatDir, cwd, seatId) {
|
|
1840
|
+
let geminiSessionDir = null;
|
|
1841
|
+
try {
|
|
1842
|
+
fs.rmSync(seatDir, { recursive: true, force: true });
|
|
1843
|
+
} catch {
|
|
1844
|
+
// Best effort only.
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
try {
|
|
1848
|
+
const homeDir = String(process.env.HOME || "").trim() || "/root";
|
|
1849
|
+
const geminiSeatHome = getSeatGeminiCliHome(homeDir, cwd, seatId);
|
|
1850
|
+
geminiSessionDir = path.dirname(geminiSeatHome);
|
|
1851
|
+
fs.rmSync(geminiSeatHome, { recursive: true, force: true });
|
|
1852
|
+
} catch {
|
|
1853
|
+
// Best effort only.
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
if (geminiSessionDir) {
|
|
1857
|
+
try {
|
|
1858
|
+
const remainingGeminiSeatDirs = fs.readdirSync(geminiSessionDir, { withFileTypes: true })
|
|
1859
|
+
.filter((entry) => entry.isDirectory() && /^seat-\d+$/.test(entry.name));
|
|
1860
|
+
if (remainingGeminiSeatDirs.length === 0) {
|
|
1861
|
+
fs.rmSync(geminiSessionDir, { recursive: true, force: true });
|
|
1862
|
+
}
|
|
1863
|
+
} catch {
|
|
1864
|
+
// Best effort only.
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
try {
|
|
1869
|
+
const remainingSeatDirs = fs.readdirSync(sessionDir, { withFileTypes: true })
|
|
1870
|
+
.filter((entry) => entry.isDirectory() && /^seat-\d+$/.test(entry.name));
|
|
1871
|
+
if (remainingSeatDirs.length === 0) {
|
|
1872
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
1873
|
+
}
|
|
1874
|
+
} catch {
|
|
1875
|
+
// Best effort only.
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1694
1879
|
function signalProcessTree(rootPid, signal) {
|
|
1695
1880
|
const descendants = getChildProcesses(rootPid);
|
|
1696
1881
|
let delivered = 0;
|