muuuuse 3.3.4 → 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 +185 -3
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 = () => {
|
|
@@ -1537,6 +1641,8 @@ class ArmedSeat {
|
|
|
1537
1641
|
exitedAt: new Date().toISOString(),
|
|
1538
1642
|
state: "exited",
|
|
1539
1643
|
});
|
|
1644
|
+
|
|
1645
|
+
purgeSeatTransientState(this.sessionPaths.dir, this.paths.dir, this.cwd, this.seatId);
|
|
1540
1646
|
}
|
|
1541
1647
|
}
|
|
1542
1648
|
|
|
@@ -1661,6 +1767,10 @@ function stopAllSessions() {
|
|
|
1661
1767
|
}
|
|
1662
1768
|
}
|
|
1663
1769
|
|
|
1770
|
+
for (const session of report.sessions) {
|
|
1771
|
+
finalizeStoppedSession(session);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1664
1774
|
return {
|
|
1665
1775
|
requestedAt,
|
|
1666
1776
|
sessions: report.sessions,
|
|
@@ -1670,6 +1780,7 @@ function stopAllSessions() {
|
|
|
1670
1780
|
module.exports = {
|
|
1671
1781
|
ArmedSeat,
|
|
1672
1782
|
buildChildEnv,
|
|
1783
|
+
consumeTerminalProxyInput,
|
|
1673
1784
|
ensureSeatGeminiCliHome,
|
|
1674
1785
|
chunkRelayPayloadForTyping,
|
|
1675
1786
|
getStatusReport,
|
|
@@ -1694,6 +1805,77 @@ function signalPid(pid, signal) {
|
|
|
1694
1805
|
}
|
|
1695
1806
|
}
|
|
1696
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
|
+
|
|
1697
1879
|
function signalProcessTree(rootPid, signal) {
|
|
1698
1880
|
const descendants = getChildProcesses(rootPid);
|
|
1699
1881
|
let delivered = 0;
|