muuuuse 1.5.0 → 2.1.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 +253 -30
- package/src/runtime.js +321 -32
package/package.json
CHANGED
package/src/agents.js
CHANGED
|
@@ -12,9 +12,11 @@ const {
|
|
|
12
12
|
} = require("./util");
|
|
13
13
|
|
|
14
14
|
const CODEX_ROOT = path.join(os.homedir(), ".codex", "sessions");
|
|
15
|
+
const CODEX_SNAPSHOT_ROOT = path.join(os.homedir(), ".codex", "shell_snapshots");
|
|
15
16
|
const CLAUDE_ROOT = path.join(os.homedir(), ".claude", "projects");
|
|
16
17
|
const GEMINI_ROOT = path.join(os.homedir(), ".gemini", "tmp");
|
|
17
18
|
const SESSION_START_EARLY_TOLERANCE_MS = 2 * 1000;
|
|
19
|
+
const STRICT_SINGLE_CANDIDATE_EARLY_TOLERANCE_MS = 250;
|
|
18
20
|
|
|
19
21
|
function walkFiles(rootPath, predicate, results = []) {
|
|
20
22
|
try {
|
|
@@ -51,7 +53,11 @@ function buildDetectedAgent(type, process) {
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
function detectAgent(processes) {
|
|
54
|
-
const ordered = [...processes].sort((left, right) =>
|
|
56
|
+
const ordered = [...processes].sort((left, right) => (
|
|
57
|
+
(left.depth ?? Number.MAX_SAFE_INTEGER) - (right.depth ?? Number.MAX_SAFE_INTEGER) ||
|
|
58
|
+
right.elapsedSeconds - left.elapsedSeconds ||
|
|
59
|
+
left.pid - right.pid
|
|
60
|
+
));
|
|
55
61
|
for (const process of ordered) {
|
|
56
62
|
if (commandMatches(process.args, "codex")) {
|
|
57
63
|
return buildDetectedAgent("codex", process);
|
|
@@ -93,6 +99,33 @@ function readFirstLines(filePath, maxLines = 20) {
|
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
|
|
102
|
+
function sortSessionCandidates(candidates) {
|
|
103
|
+
return candidates
|
|
104
|
+
.slice()
|
|
105
|
+
.sort((left, right) => {
|
|
106
|
+
const leftDiff = Number.isFinite(left.diffMs) ? left.diffMs : Number.MAX_SAFE_INTEGER;
|
|
107
|
+
const rightDiff = Number.isFinite(right.diffMs) ? right.diffMs : Number.MAX_SAFE_INTEGER;
|
|
108
|
+
return (
|
|
109
|
+
leftDiff - rightDiff ||
|
|
110
|
+
right.startedAtMs - left.startedAtMs ||
|
|
111
|
+
right.mtimeMs - left.mtimeMs ||
|
|
112
|
+
left.path.localeCompare(right.path)
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function annotateSessionCandidates(candidates, processStartedAtMs) {
|
|
118
|
+
return candidates.map((candidate) => ({
|
|
119
|
+
...candidate,
|
|
120
|
+
diffMs: Number.isFinite(processStartedAtMs) && Number.isFinite(candidate.startedAtMs)
|
|
121
|
+
? Math.abs(candidate.startedAtMs - processStartedAtMs)
|
|
122
|
+
: Number.POSITIVE_INFINITY,
|
|
123
|
+
relativeStartMs: Number.isFinite(processStartedAtMs) && Number.isFinite(candidate.startedAtMs)
|
|
124
|
+
? candidate.startedAtMs - processStartedAtMs
|
|
125
|
+
: Number.NaN,
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
|
|
96
129
|
function selectSessionCandidatePath(candidates, currentPath, processStartedAtMs) {
|
|
97
130
|
const cwdMatches = candidates.filter((candidate) => candidate.cwd === currentPath);
|
|
98
131
|
if (cwdMatches.length === 0) {
|
|
@@ -107,12 +140,7 @@ function selectSessionCandidatePath(candidates, currentPath, processStartedAtMs)
|
|
|
107
140
|
return null;
|
|
108
141
|
}
|
|
109
142
|
|
|
110
|
-
const preciseMatches = cwdMatches
|
|
111
|
-
.map((candidate) => ({
|
|
112
|
-
...candidate,
|
|
113
|
-
diffMs: Math.abs(candidate.startedAtMs - processStartedAtMs),
|
|
114
|
-
relativeStartMs: candidate.startedAtMs - processStartedAtMs,
|
|
115
|
-
}))
|
|
143
|
+
const preciseMatches = annotateSessionCandidates(cwdMatches, processStartedAtMs)
|
|
116
144
|
.filter((candidate) => (
|
|
117
145
|
Number.isFinite(candidate.diffMs) &&
|
|
118
146
|
Number.isFinite(candidate.relativeStartMs) &&
|
|
@@ -128,29 +156,98 @@ function selectSessionCandidatePath(candidates, currentPath, processStartedAtMs)
|
|
|
128
156
|
return null;
|
|
129
157
|
}
|
|
130
158
|
|
|
131
|
-
function
|
|
132
|
-
if (!
|
|
133
|
-
return
|
|
159
|
+
function readCodexSeatClaim(sessionId) {
|
|
160
|
+
if (!sessionId) {
|
|
161
|
+
return null;
|
|
134
162
|
}
|
|
135
163
|
|
|
136
|
-
const
|
|
164
|
+
const snapshotPath = path.join(CODEX_SNAPSHOT_ROOT, `${sessionId}.sh`);
|
|
137
165
|
try {
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
})
|
|
147
|
-
.filter((entry) => typeof entry === "string")
|
|
148
|
-
.filter((entry) => entry.startsWith(rootPrefix));
|
|
166
|
+
const text = fs.readFileSync(snapshotPath, "utf8");
|
|
167
|
+
const seatMatch = text.match(/declare -x MUUUUSE_SEAT="([^"]+)"/);
|
|
168
|
+
const sessionMatch = text.match(/declare -x MUUUUSE_SESSION="([^"]+)"/);
|
|
169
|
+
if (!seatMatch || !sessionMatch) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
149
172
|
|
|
150
|
-
return
|
|
173
|
+
return {
|
|
174
|
+
seatId: seatMatch[1],
|
|
175
|
+
sessionName: sessionMatch[1],
|
|
176
|
+
};
|
|
151
177
|
} catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function selectClaimedCodexCandidatePath(candidates, options = {}) {
|
|
183
|
+
const seatId = options.seatId == null ? null : String(options.seatId);
|
|
184
|
+
const sessionName = typeof options.sessionName === "string" ? options.sessionName : null;
|
|
185
|
+
if (!seatId || !sessionName || candidates.length <= 1) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const annotated = candidates.map((candidate) => ({
|
|
190
|
+
...candidate,
|
|
191
|
+
claim: readCodexSeatClaim(candidate.sessionId),
|
|
192
|
+
}));
|
|
193
|
+
|
|
194
|
+
const exactMatches = annotated.filter((candidate) => (
|
|
195
|
+
candidate.claim?.seatId === seatId &&
|
|
196
|
+
candidate.claim?.sessionName === sessionName
|
|
197
|
+
));
|
|
198
|
+
if (exactMatches.length === 1) {
|
|
199
|
+
return exactMatches[0].path;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const otherSeatClaims = annotated.filter((candidate) => (
|
|
203
|
+
candidate.claim?.sessionName === sessionName &&
|
|
204
|
+
candidate.claim?.seatId !== seatId
|
|
205
|
+
));
|
|
206
|
+
if (otherSeatClaims.length === 0) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const foreignPaths = new Set(otherSeatClaims.map((candidate) => candidate.path));
|
|
211
|
+
const remaining = annotated.filter((candidate) => !foreignPaths.has(candidate.path));
|
|
212
|
+
if (remaining.length === 1) {
|
|
213
|
+
return remaining[0].path;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function listOpenFilePathsForPids(pids, rootPath) {
|
|
220
|
+
const normalizedPids = [...new Set(
|
|
221
|
+
(Array.isArray(pids) ? pids : [pids])
|
|
222
|
+
.map((pid) => Number.parseInt(pid, 10))
|
|
223
|
+
.filter((pid) => Number.isInteger(pid) && pid > 0)
|
|
224
|
+
)];
|
|
225
|
+
if (normalizedPids.length === 0) {
|
|
152
226
|
return [];
|
|
153
227
|
}
|
|
228
|
+
|
|
229
|
+
const rootPrefix = path.resolve(rootPath);
|
|
230
|
+
const openPaths = new Set();
|
|
231
|
+
|
|
232
|
+
for (const pid of normalizedPids) {
|
|
233
|
+
const fdRoot = `/proc/${pid}/fd`;
|
|
234
|
+
try {
|
|
235
|
+
for (const entry of fs.readdirSync(fdRoot)) {
|
|
236
|
+
try {
|
|
237
|
+
const resolved = fs.realpathSync(path.join(fdRoot, entry));
|
|
238
|
+
if (typeof resolved === "string" && resolved.startsWith(rootPrefix)) {
|
|
239
|
+
openPaths.add(resolved);
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
// Ignore descriptors that disappear while we are inspecting them.
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
// Ignore pids that have already exited.
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return [...openPaths];
|
|
154
251
|
}
|
|
155
252
|
|
|
156
253
|
function selectLiveSessionCandidatePath(candidates, currentPath, captureSinceMs = null) {
|
|
@@ -173,8 +270,8 @@ function selectLiveSessionCandidatePath(candidates, currentPath, captureSinceMs
|
|
|
173
270
|
return ranked[0]?.path || null;
|
|
174
271
|
}
|
|
175
272
|
|
|
176
|
-
function readOpenSessionCandidates(
|
|
177
|
-
return
|
|
273
|
+
function readOpenSessionCandidates(pids, rootPath, reader) {
|
|
274
|
+
return listOpenFilePathsForPids(pids, rootPath)
|
|
178
275
|
.map((filePath) => reader(filePath))
|
|
179
276
|
.filter((candidate) => candidate !== null);
|
|
180
277
|
}
|
|
@@ -195,6 +292,7 @@ function readCodexCandidate(filePath) {
|
|
|
195
292
|
path: filePath,
|
|
196
293
|
cwd: entry.payload.cwd,
|
|
197
294
|
isSubagent: Boolean(entry.payload?.source?.subagent),
|
|
295
|
+
sessionId: entry.payload.id || null,
|
|
198
296
|
startedAtMs: Date.parse(entry.payload.timestamp),
|
|
199
297
|
mtimeMs: fs.statSync(filePath).mtimeMs,
|
|
200
298
|
};
|
|
@@ -203,9 +301,131 @@ function readCodexCandidate(filePath) {
|
|
|
203
301
|
}
|
|
204
302
|
}
|
|
205
303
|
|
|
304
|
+
function rankCodexCandidates(candidates, processStartedAtMs) {
|
|
305
|
+
return sortSessionCandidates(annotateSessionCandidates(candidates, processStartedAtMs));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function selectExactClaimedCodexCandidate(candidates, options = {}, processStartedAtMs = null) {
|
|
309
|
+
const seatId = options.seatId == null ? null : String(options.seatId);
|
|
310
|
+
const sessionName = typeof options.sessionName === "string" ? options.sessionName : null;
|
|
311
|
+
if (!seatId || !sessionName) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const exactMatches = rankCodexCandidates(
|
|
316
|
+
candidates.filter((candidate) => {
|
|
317
|
+
const claim = readCodexSeatClaim(candidate.sessionId);
|
|
318
|
+
return claim?.seatId === seatId && claim?.sessionName === sessionName;
|
|
319
|
+
}),
|
|
320
|
+
processStartedAtMs
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
return exactMatches[0]?.path || null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function filterForeignClaimedCodexCandidates(candidates, options = {}) {
|
|
327
|
+
const seatId = options.seatId == null ? null : String(options.seatId);
|
|
328
|
+
const sessionName = typeof options.sessionName === "string" ? options.sessionName : null;
|
|
329
|
+
if (!seatId || !sessionName) {
|
|
330
|
+
return candidates.slice();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return candidates.filter((candidate) => {
|
|
334
|
+
const claim = readCodexSeatClaim(candidate.sessionId);
|
|
335
|
+
return !(claim?.sessionName === sessionName && claim?.seatId && claim.seatId !== seatId);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function selectStrictSingleCodexCandidatePath(candidates, processStartedAtMs) {
|
|
340
|
+
if (candidates.length !== 1 || !Number.isFinite(processStartedAtMs)) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const [candidate] = annotateSessionCandidates(candidates, processStartedAtMs);
|
|
345
|
+
if (!Number.isFinite(candidate.relativeStartMs)) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (
|
|
350
|
+
candidate.relativeStartMs < -STRICT_SINGLE_CANDIDATE_EARLY_TOLERANCE_MS ||
|
|
351
|
+
candidate.relativeStartMs > SESSION_MATCH_WINDOW_MS
|
|
352
|
+
) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return candidate.path;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function selectCodexCandidatePath(candidates, currentPath, processStartedAtMs, options = {}) {
|
|
360
|
+
const cwdMatches = candidates.filter((candidate) => candidate.cwd === currentPath);
|
|
361
|
+
if (cwdMatches.length === 0) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const seatId = options.seatId == null ? null : String(options.seatId);
|
|
366
|
+
const sessionName = typeof options.sessionName === "string" ? options.sessionName : null;
|
|
367
|
+
const exactClaimPath = selectExactClaimedCodexCandidate(cwdMatches, options, processStartedAtMs);
|
|
368
|
+
if (exactClaimPath) {
|
|
369
|
+
return exactClaimPath;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const foreignClaimsPresent = Boolean(
|
|
373
|
+
seatId &&
|
|
374
|
+
sessionName &&
|
|
375
|
+
cwdMatches.some((candidate) => {
|
|
376
|
+
const claim = readCodexSeatClaim(candidate.sessionId);
|
|
377
|
+
return claim?.sessionName === sessionName && claim?.seatId && claim.seatId !== seatId;
|
|
378
|
+
})
|
|
379
|
+
);
|
|
380
|
+
const allowedMatches = filterForeignClaimedCodexCandidates(cwdMatches, options);
|
|
381
|
+
if (allowedMatches.length === 0) {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (!Number.isFinite(processStartedAtMs)) {
|
|
386
|
+
return allowedMatches.length === 1 ? allowedMatches[0].path : null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const preciseMatches = rankCodexCandidates(
|
|
390
|
+
allowedMatches.filter((candidate) => {
|
|
391
|
+
const annotated = annotateSessionCandidates([candidate], processStartedAtMs)[0];
|
|
392
|
+
return (
|
|
393
|
+
Number.isFinite(annotated.diffMs) &&
|
|
394
|
+
Number.isFinite(annotated.relativeStartMs) &&
|
|
395
|
+
annotated.relativeStartMs >= -SESSION_START_EARLY_TOLERANCE_MS &&
|
|
396
|
+
annotated.relativeStartMs <= SESSION_MATCH_WINDOW_MS
|
|
397
|
+
);
|
|
398
|
+
}),
|
|
399
|
+
processStartedAtMs
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
const preciseClaimPath = selectClaimedCodexCandidatePath(preciseMatches, options);
|
|
403
|
+
if (preciseClaimPath) {
|
|
404
|
+
return preciseClaimPath;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const pairedSeatSelection = seatId && sessionName;
|
|
408
|
+
if (pairedSeatSelection && options.allowUnclaimedSingleCandidate === false && !foreignClaimsPresent) {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (preciseMatches.length === 1) {
|
|
413
|
+
return selectStrictSingleCodexCandidatePath(preciseMatches, processStartedAtMs);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (allowedMatches.length === 1) {
|
|
417
|
+
return selectStrictSingleCodexCandidatePath(allowedMatches, processStartedAtMs);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
|
|
206
423
|
function selectCodexSessionFile(currentPath, processStartedAtMs, options = {}) {
|
|
207
|
-
const liveCandidates = readOpenSessionCandidates(options.pid, CODEX_ROOT, readCodexCandidate);
|
|
208
|
-
const livePath =
|
|
424
|
+
const liveCandidates = readOpenSessionCandidates(options.pids ?? options.pid, CODEX_ROOT, readCodexCandidate);
|
|
425
|
+
const livePath = selectCodexCandidatePath(liveCandidates, currentPath, processStartedAtMs, {
|
|
426
|
+
...options,
|
|
427
|
+
allowUnclaimedSingleCandidate: true,
|
|
428
|
+
});
|
|
209
429
|
if (livePath) {
|
|
210
430
|
return livePath;
|
|
211
431
|
}
|
|
@@ -214,7 +434,10 @@ function selectCodexSessionFile(currentPath, processStartedAtMs, options = {}) {
|
|
|
214
434
|
.map((filePath) => readCodexCandidate(filePath))
|
|
215
435
|
.filter((candidate) => candidate !== null);
|
|
216
436
|
|
|
217
|
-
return
|
|
437
|
+
return selectCodexCandidatePath(candidates, currentPath, processStartedAtMs, {
|
|
438
|
+
...options,
|
|
439
|
+
allowUnclaimedSingleCandidate: false,
|
|
440
|
+
});
|
|
218
441
|
}
|
|
219
442
|
|
|
220
443
|
function extractCodexAssistantText(content) {
|
|
@@ -311,7 +534,7 @@ function readClaudeCandidate(filePath) {
|
|
|
311
534
|
}
|
|
312
535
|
|
|
313
536
|
function selectClaudeSessionFile(currentPath, processStartedAtMs, options = {}) {
|
|
314
|
-
const liveCandidates = readOpenSessionCandidates(options.pid, CLAUDE_ROOT, readClaudeCandidate);
|
|
537
|
+
const liveCandidates = readOpenSessionCandidates(options.pids ?? options.pid, CLAUDE_ROOT, readClaudeCandidate);
|
|
315
538
|
const livePath = selectLiveSessionCandidatePath(liveCandidates, currentPath, options.captureSinceMs);
|
|
316
539
|
if (livePath) {
|
|
317
540
|
return livePath;
|
|
@@ -397,7 +620,7 @@ function readGeminiCandidate(filePath) {
|
|
|
397
620
|
|
|
398
621
|
function selectGeminiSessionFile(currentPath, processStartedAtMs, options = {}) {
|
|
399
622
|
const projectHash = createHash("sha256").update(currentPath).digest("hex");
|
|
400
|
-
const liveCandidates = readOpenSessionCandidates(options.pid, GEMINI_ROOT, readGeminiCandidate)
|
|
623
|
+
const liveCandidates = readOpenSessionCandidates(options.pids ?? options.pid, GEMINI_ROOT, readGeminiCandidate)
|
|
401
624
|
.filter((candidate) => candidate.projectHash === projectHash);
|
|
402
625
|
const livePath = selectLiveSessionCandidatePath(liveCandidates, projectHash, options.captureSinceMs);
|
|
403
626
|
if (livePath) {
|
package/src/runtime.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require("node:fs");
|
|
2
2
|
const { execFileSync } = require("node:child_process");
|
|
3
|
+
const path = require("node:path");
|
|
3
4
|
const pty = require("node-pty");
|
|
4
5
|
|
|
5
6
|
const {
|
|
@@ -36,8 +37,19 @@ const {
|
|
|
36
37
|
|
|
37
38
|
const TYPE_DELAY_MS = 70;
|
|
38
39
|
const MIRROR_SUPPRESSION_WINDOW_MS = 30 * 1000;
|
|
40
|
+
const PENDING_RELAY_CONTEXT_TTL_MS = 2 * 60 * 1000;
|
|
41
|
+
const EMITTED_ANSWER_TTL_MS = 5 * 60 * 1000;
|
|
39
42
|
const MAX_RECENT_INBOUND_RELAYS = 12;
|
|
43
|
+
const MAX_RECENT_EMITTED_ANSWERS = 48;
|
|
44
|
+
const MAX_RELAY_CHAIN_HOP = 1;
|
|
40
45
|
const STOP_FORCE_KILL_MS = 1200;
|
|
46
|
+
const SEAT_JOIN_WAIT_MS = 3000;
|
|
47
|
+
const SEAT_JOIN_POLL_MS = 60;
|
|
48
|
+
const CHILD_ENV_DROP_KEYS = [
|
|
49
|
+
"CODEX_CI",
|
|
50
|
+
"CODEX_MANAGED_BY_NPM",
|
|
51
|
+
"CODEX_THREAD_ID",
|
|
52
|
+
];
|
|
41
53
|
|
|
42
54
|
function resolveShell() {
|
|
43
55
|
const shell = String(process.env.SHELL || "").trim();
|
|
@@ -52,16 +64,139 @@ function resolveShellArgs(shellPath) {
|
|
|
52
64
|
return [];
|
|
53
65
|
}
|
|
54
66
|
|
|
55
|
-
function resolveChildTerm() {
|
|
56
|
-
const inherited = String(
|
|
67
|
+
function resolveChildTerm(sourceEnv = process.env) {
|
|
68
|
+
const inherited = String(sourceEnv.TERM || "").trim();
|
|
57
69
|
if (inherited && inherited.toLowerCase() !== "dumb") {
|
|
58
70
|
return inherited;
|
|
59
71
|
}
|
|
60
72
|
return "xterm-256color";
|
|
61
73
|
}
|
|
62
74
|
|
|
63
|
-
function
|
|
64
|
-
|
|
75
|
+
function sanitizeChildPath(pathValue, homeDir) {
|
|
76
|
+
const arg0Root = path.join(homeDir, ".codex", "tmp", "arg0");
|
|
77
|
+
const entries = String(pathValue || "")
|
|
78
|
+
.split(path.delimiter)
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.filter((entry) => {
|
|
81
|
+
const resolved = path.resolve(entry);
|
|
82
|
+
return resolved !== arg0Root && !resolved.startsWith(`${arg0Root}${path.sep}`);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return entries.join(path.delimiter);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildChildEnv(seatId, sessionName, cwd, baseEnv = process.env) {
|
|
89
|
+
const env = { ...baseEnv };
|
|
90
|
+
for (const key of CHILD_ENV_DROP_KEYS) {
|
|
91
|
+
delete env[key];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const homeDir = String(env.HOME || "").trim() || process.env.HOME || "/root";
|
|
95
|
+
env.PATH = sanitizeChildPath(env.PATH, homeDir);
|
|
96
|
+
env.PWD = cwd;
|
|
97
|
+
env.TERM = resolveChildTerm(baseEnv);
|
|
98
|
+
env.MUUUUSE_SEAT = String(seatId);
|
|
99
|
+
env.MUUUUSE_SESSION = sessionName;
|
|
100
|
+
return env;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function normalizeWorkingPath(currentPath = process.cwd()) {
|
|
104
|
+
try {
|
|
105
|
+
return fs.realpathSync(currentPath);
|
|
106
|
+
} catch {
|
|
107
|
+
return path.resolve(currentPath);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function matchesWorkingPath(leftPath, rightPath) {
|
|
112
|
+
if (!leftPath || !rightPath) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return normalizeWorkingPath(leftPath) === normalizeWorkingPath(rightPath);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createSessionName(currentPath = process.cwd()) {
|
|
120
|
+
return `${getDefaultSessionName(currentPath)}-${createId(6)}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function sleepSync(ms) {
|
|
124
|
+
if (!Number.isFinite(ms) || ms <= 0) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function findJoinableSessionName(currentPath = process.cwd()) {
|
|
132
|
+
const candidates = listSessionNames()
|
|
133
|
+
.map((sessionName) => {
|
|
134
|
+
const sessionPaths = getSessionPaths(sessionName);
|
|
135
|
+
const controller = readJson(sessionPaths.controllerPath, null);
|
|
136
|
+
const seat1Paths = getSeatPaths(sessionName, 1);
|
|
137
|
+
const seat2Paths = getSeatPaths(sessionName, 2);
|
|
138
|
+
const seat1Meta = readJson(seat1Paths.metaPath, null);
|
|
139
|
+
const seat1Status = readJson(seat1Paths.statusPath, null);
|
|
140
|
+
const seat2Meta = readJson(seat2Paths.metaPath, null);
|
|
141
|
+
const seat2Status = readJson(seat2Paths.statusPath, null);
|
|
142
|
+
const stopRequest = readJson(sessionPaths.stopPath, null);
|
|
143
|
+
|
|
144
|
+
const cwd = controller?.cwd || seat1Status?.cwd || seat1Meta?.cwd || seat2Status?.cwd || seat2Meta?.cwd || null;
|
|
145
|
+
if (!matchesWorkingPath(cwd, currentPath)) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const seat1WrapperPid = seat1Status?.pid || seat1Meta?.pid || null;
|
|
150
|
+
const seat1ChildPid = seat1Status?.childPid || seat1Meta?.childPid || null;
|
|
151
|
+
const seat2WrapperPid = seat2Status?.pid || seat2Meta?.pid || null;
|
|
152
|
+
const seat2ChildPid = seat2Status?.childPid || seat2Meta?.childPid || null;
|
|
153
|
+
const seat1Live = isPidAlive(seat1WrapperPid) || isPidAlive(seat1ChildPid);
|
|
154
|
+
const seat2Live = isPidAlive(seat2WrapperPid) || isPidAlive(seat2ChildPid);
|
|
155
|
+
const stopRequestedAtMs = Date.parse(stopRequest?.requestedAt || "");
|
|
156
|
+
const createdAtMs = Date.parse(controller?.createdAt || seat1Meta?.startedAt || seat1Status?.updatedAt || "");
|
|
157
|
+
|
|
158
|
+
if (!seat1Live || seat2Live) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (Number.isFinite(stopRequestedAtMs) && Number.isFinite(createdAtMs) && stopRequestedAtMs > createdAtMs) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
sessionName,
|
|
168
|
+
createdAtMs,
|
|
169
|
+
};
|
|
170
|
+
})
|
|
171
|
+
.filter((entry) => entry !== null)
|
|
172
|
+
.sort((left, right) => right.createdAtMs - left.createdAtMs);
|
|
173
|
+
|
|
174
|
+
return candidates[0]?.sessionName || null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function waitForJoinableSessionName(currentPath = process.cwd(), timeoutMs = SEAT_JOIN_WAIT_MS) {
|
|
178
|
+
const deadline = Date.now() + timeoutMs;
|
|
179
|
+
while (Date.now() <= deadline) {
|
|
180
|
+
const sessionName = findJoinableSessionName(currentPath);
|
|
181
|
+
if (sessionName) {
|
|
182
|
+
return sessionName;
|
|
183
|
+
}
|
|
184
|
+
sleepSync(SEAT_JOIN_POLL_MS);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function resolveSessionName(currentPath = process.cwd(), seatId = 1) {
|
|
191
|
+
if (seatId === 1) {
|
|
192
|
+
return createSessionName(currentPath);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (seatId === 2) {
|
|
196
|
+
return waitForJoinableSessionName(currentPath);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return createSessionName(currentPath);
|
|
65
200
|
}
|
|
66
201
|
|
|
67
202
|
function parseAnswerEntries(text) {
|
|
@@ -121,20 +256,25 @@ function getChildProcesses(rootPid) {
|
|
|
121
256
|
.filter((entry) => entry !== null);
|
|
122
257
|
|
|
123
258
|
const descendants = [];
|
|
124
|
-
const queue = [rootPid];
|
|
125
|
-
const seen = new Set(
|
|
259
|
+
const queue = [{ pid: rootPid, depth: 0 }];
|
|
260
|
+
const seen = new Set([rootPid]);
|
|
126
261
|
|
|
127
262
|
while (queue.length > 0) {
|
|
128
|
-
const
|
|
263
|
+
const current = queue.shift();
|
|
264
|
+
const parentPid = current.pid;
|
|
129
265
|
for (const process of processes) {
|
|
130
266
|
if (process.ppid !== parentPid || seen.has(process.pid)) {
|
|
131
267
|
continue;
|
|
132
268
|
}
|
|
133
269
|
seen.add(process.pid);
|
|
134
|
-
queue.push(
|
|
270
|
+
queue.push({
|
|
271
|
+
pid: process.pid,
|
|
272
|
+
depth: current.depth + 1,
|
|
273
|
+
});
|
|
135
274
|
descendants.push({
|
|
136
275
|
...process,
|
|
137
276
|
cwd: readProcessCwd(process.pid),
|
|
277
|
+
depth: current.depth + 1,
|
|
138
278
|
});
|
|
139
279
|
}
|
|
140
280
|
}
|
|
@@ -145,14 +285,40 @@ function getChildProcesses(rootPid) {
|
|
|
145
285
|
}
|
|
146
286
|
}
|
|
147
287
|
|
|
148
|
-
function
|
|
288
|
+
function getProcessFamilyPids(processes, rootPid) {
|
|
289
|
+
if (!Number.isInteger(rootPid) || rootPid <= 0) {
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const related = new Set([rootPid]);
|
|
294
|
+
const queue = [rootPid];
|
|
295
|
+
|
|
296
|
+
while (queue.length > 0) {
|
|
297
|
+
const parentPid = queue.shift();
|
|
298
|
+
for (const process of processes) {
|
|
299
|
+
if (process.ppid !== parentPid || related.has(process.pid)) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
related.add(process.pid);
|
|
304
|
+
queue.push(process.pid);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return [...related];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function resolveSessionFile(agentType, agentPid, currentPath, captureSinceMs, processStartedAtMs, seatContext = {}) {
|
|
149
312
|
if (!currentPath) {
|
|
150
313
|
return null;
|
|
151
314
|
}
|
|
152
315
|
|
|
153
316
|
const options = {
|
|
154
317
|
pid: agentPid,
|
|
318
|
+
pids: seatContext.agentPids,
|
|
155
319
|
captureSinceMs,
|
|
320
|
+
seatId: seatContext.seatId,
|
|
321
|
+
sessionName: seatContext.sessionName,
|
|
156
322
|
};
|
|
157
323
|
|
|
158
324
|
if (agentType === "codex") {
|
|
@@ -192,6 +358,8 @@ function buildAnswerSignaturePayload(sessionName, challenge, entry) {
|
|
|
192
358
|
type: "muuuuse_answer",
|
|
193
359
|
sessionName,
|
|
194
360
|
challenge,
|
|
361
|
+
chainId: entry.chainId,
|
|
362
|
+
hop: entry.hop,
|
|
195
363
|
id: entry.id,
|
|
196
364
|
seatId: entry.seatId,
|
|
197
365
|
origin: entry.origin,
|
|
@@ -267,8 +435,11 @@ class ArmedSeat {
|
|
|
267
435
|
constructor(options) {
|
|
268
436
|
this.seatId = options.seatId;
|
|
269
437
|
this.partnerSeatId = options.seatId === 1 ? 2 : 1;
|
|
270
|
-
this.cwd = options.cwd;
|
|
271
|
-
this.sessionName = resolveSessionName(this.cwd);
|
|
438
|
+
this.cwd = normalizeWorkingPath(options.cwd);
|
|
439
|
+
this.sessionName = resolveSessionName(this.cwd, this.seatId);
|
|
440
|
+
if (!this.sessionName) {
|
|
441
|
+
throw new Error("No armed `muuuuse 1` seat is waiting in this cwd. Run `muuuuse 1` first.");
|
|
442
|
+
}
|
|
272
443
|
this.sessionPaths = getSessionPaths(this.sessionName);
|
|
273
444
|
this.paths = getSeatPaths(this.sessionName, this.seatId);
|
|
274
445
|
this.partnerPaths = getSeatPaths(this.sessionName, this.partnerSeatId);
|
|
@@ -286,7 +457,10 @@ class ArmedSeat {
|
|
|
286
457
|
this.resizeCleanup = null;
|
|
287
458
|
this.forceKillTimer = null;
|
|
288
459
|
this.identity = null;
|
|
460
|
+
this.lastUserInputAtMs = 0;
|
|
461
|
+
this.pendingInboundContext = null;
|
|
289
462
|
this.recentInboundRelays = [];
|
|
463
|
+
this.recentEmittedAnswers = [];
|
|
290
464
|
this.trustState = {
|
|
291
465
|
challenge: null,
|
|
292
466
|
peerPublicKey: null,
|
|
@@ -306,6 +480,20 @@ class ArmedSeat {
|
|
|
306
480
|
};
|
|
307
481
|
}
|
|
308
482
|
|
|
483
|
+
writeController(extra = {}) {
|
|
484
|
+
const current = readJson(this.sessionPaths.controllerPath, {});
|
|
485
|
+
writeJson(this.sessionPaths.controllerPath, {
|
|
486
|
+
sessionName: this.sessionName,
|
|
487
|
+
cwd: this.cwd,
|
|
488
|
+
createdAt: current.createdAt || this.startedAt,
|
|
489
|
+
updatedAt: new Date().toISOString(),
|
|
490
|
+
seat1Pid: this.seatId === 1 ? process.pid : current.seat1Pid || null,
|
|
491
|
+
seat2Pid: this.seatId === 2 ? process.pid : current.seat2Pid || null,
|
|
492
|
+
pid: this.seatId === 1 ? process.pid : current.pid || null,
|
|
493
|
+
...extra,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
309
497
|
log(message) {
|
|
310
498
|
process.stderr.write(`${message}\n`);
|
|
311
499
|
}
|
|
@@ -509,20 +697,17 @@ class ArmedSeat {
|
|
|
509
697
|
fs.rmSync(this.paths.pipePath, { force: true });
|
|
510
698
|
clearStaleStopRequest(this.sessionPaths.stopPath, this.startedAtMs);
|
|
511
699
|
this.initializeTrustMaterial();
|
|
700
|
+
this.writeController();
|
|
512
701
|
|
|
513
702
|
const shell = resolveShell();
|
|
514
703
|
const shellArgs = resolveShellArgs(shell);
|
|
704
|
+
const childEnv = buildChildEnv(this.seatId, this.sessionName, this.cwd);
|
|
515
705
|
this.child = pty.spawn(shell, shellArgs, {
|
|
516
706
|
cols: process.stdout.columns || 120,
|
|
517
707
|
rows: process.stdout.rows || 36,
|
|
518
708
|
cwd: this.cwd,
|
|
519
|
-
env:
|
|
520
|
-
|
|
521
|
-
TERM: resolveChildTerm(),
|
|
522
|
-
MUUUUSE_SEAT: String(this.seatId),
|
|
523
|
-
MUUUUSE_SESSION: this.sessionName,
|
|
524
|
-
},
|
|
525
|
-
name: resolveChildTerm(),
|
|
709
|
+
env: childEnv,
|
|
710
|
+
name: childEnv.TERM,
|
|
526
711
|
});
|
|
527
712
|
|
|
528
713
|
this.childPid = this.child.pid;
|
|
@@ -547,6 +732,8 @@ class ArmedSeat {
|
|
|
547
732
|
|
|
548
733
|
installStdinProxy() {
|
|
549
734
|
const handleData = (chunk) => {
|
|
735
|
+
this.lastUserInputAtMs = Date.now();
|
|
736
|
+
this.pendingInboundContext = null;
|
|
550
737
|
if (!this.child) {
|
|
551
738
|
return;
|
|
552
739
|
}
|
|
@@ -687,6 +874,8 @@ class ArmedSeat {
|
|
|
687
874
|
|
|
688
875
|
const payload = sanitizeRelayText(entry.text);
|
|
689
876
|
const signaturePayload = buildAnswerSignaturePayload(this.sessionName, this.trustState.challenge, {
|
|
877
|
+
chainId: entry.chainId || entry.id,
|
|
878
|
+
hop: Number.isInteger(entry.hop) ? entry.hop : 0,
|
|
690
879
|
id: entry.id,
|
|
691
880
|
seatId: entry.seatId,
|
|
692
881
|
origin: entry.origin || "unknown",
|
|
@@ -718,6 +907,14 @@ class ArmedSeat {
|
|
|
718
907
|
return;
|
|
719
908
|
}
|
|
720
909
|
|
|
910
|
+
const deliveredAtMs = Date.now();
|
|
911
|
+
this.pendingInboundContext = {
|
|
912
|
+
chainId: entry.chainId || entry.id,
|
|
913
|
+
deliveredAtMs,
|
|
914
|
+
expiresAtMs: deliveredAtMs + PENDING_RELAY_CONTEXT_TTL_MS,
|
|
915
|
+
hop: Number.isInteger(entry.hop) ? entry.hop : 0,
|
|
916
|
+
relayUsed: false,
|
|
917
|
+
};
|
|
721
918
|
this.relayCount += 1;
|
|
722
919
|
this.rememberInboundRelay(payload);
|
|
723
920
|
this.log(`[${this.partnerSeatId} -> ${this.seatId}] ${previewText(payload)}`);
|
|
@@ -749,6 +946,37 @@ class ArmedSeat {
|
|
|
749
946
|
);
|
|
750
947
|
}
|
|
751
948
|
|
|
949
|
+
pruneRecentEmittedAnswers(now = Date.now()) {
|
|
950
|
+
this.recentEmittedAnswers = this.recentEmittedAnswers.filter(
|
|
951
|
+
(entry) => now - entry.timestampMs <= EMITTED_ANSWER_TTL_MS
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
hasRecentEmittedAnswer(answerKey) {
|
|
956
|
+
if (!answerKey) {
|
|
957
|
+
return false;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
this.pruneRecentEmittedAnswers();
|
|
961
|
+
return this.recentEmittedAnswers.some((entry) => entry.key === answerKey);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
rememberEmittedAnswer(answerKey) {
|
|
965
|
+
if (!answerKey) {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
this.pruneRecentEmittedAnswers();
|
|
970
|
+
this.recentEmittedAnswers.push({
|
|
971
|
+
key: answerKey,
|
|
972
|
+
timestampMs: Date.now(),
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
if (this.recentEmittedAnswers.length > MAX_RECENT_EMITTED_ANSWERS) {
|
|
976
|
+
this.recentEmittedAnswers = this.recentEmittedAnswers.slice(-MAX_RECENT_EMITTED_ANSWERS);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
752
980
|
takeMirroredInboundRelay(payload) {
|
|
753
981
|
const normalized = sanitizeRelayText(payload);
|
|
754
982
|
if (!normalized) {
|
|
@@ -766,8 +994,28 @@ class ArmedSeat {
|
|
|
766
994
|
return match;
|
|
767
995
|
}
|
|
768
996
|
|
|
997
|
+
getPendingInboundContext() {
|
|
998
|
+
const context = this.pendingInboundContext;
|
|
999
|
+
if (!context) {
|
|
1000
|
+
return null;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (context.expiresAtMs <= Date.now()) {
|
|
1004
|
+
this.pendingInboundContext = null;
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (this.lastUserInputAtMs > context.deliveredAtMs) {
|
|
1009
|
+
this.pendingInboundContext = null;
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
return context;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
769
1016
|
collectLiveAnswers() {
|
|
770
|
-
const
|
|
1017
|
+
const childProcesses = getChildProcesses(this.childPid);
|
|
1018
|
+
const detectedAgent = detectAgent(childProcesses);
|
|
771
1019
|
if (!detectedAgent) {
|
|
772
1020
|
this.liveState = {
|
|
773
1021
|
type: null,
|
|
@@ -813,19 +1061,24 @@ class ArmedSeat {
|
|
|
813
1061
|
};
|
|
814
1062
|
}
|
|
815
1063
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
this.
|
|
827
|
-
this.liveState.lastMessageId = null;
|
|
1064
|
+
const agentPids = getProcessFamilyPids(childProcesses, detectedAgent.pid);
|
|
1065
|
+
const resolvedSessionFile = resolveSessionFile(
|
|
1066
|
+
detectedAgent.type,
|
|
1067
|
+
detectedAgent.pid,
|
|
1068
|
+
currentPath,
|
|
1069
|
+
this.liveState.captureSinceMs,
|
|
1070
|
+
detectedAgent.processStartedAtMs,
|
|
1071
|
+
{
|
|
1072
|
+
agentPids,
|
|
1073
|
+
seatId: this.seatId,
|
|
1074
|
+
sessionName: this.sessionName,
|
|
828
1075
|
}
|
|
1076
|
+
);
|
|
1077
|
+
|
|
1078
|
+
if (resolvedSessionFile && resolvedSessionFile !== this.liveState.sessionFile) {
|
|
1079
|
+
this.liveState.sessionFile = resolvedSessionFile;
|
|
1080
|
+
this.liveState.offset = 0;
|
|
1081
|
+
this.liveState.lastMessageId = null;
|
|
829
1082
|
}
|
|
830
1083
|
|
|
831
1084
|
if (!this.liveState.sessionFile) {
|
|
@@ -895,19 +1148,39 @@ class ArmedSeat {
|
|
|
895
1148
|
return;
|
|
896
1149
|
}
|
|
897
1150
|
|
|
1151
|
+
const answerKey = buildAnswerKey(entry, payload);
|
|
1152
|
+
if (this.hasRecentEmittedAnswer(answerKey)) {
|
|
1153
|
+
this.log(`[${this.seatId}] suppressed duplicate final answer: ${previewText(payload)}`);
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
898
1157
|
const mirroredInbound = this.takeMirroredInboundRelay(payload);
|
|
899
1158
|
if (mirroredInbound) {
|
|
900
1159
|
this.log(`[${this.seatId}] suppressed mirrored relay: ${previewText(payload)}`);
|
|
901
1160
|
return;
|
|
902
1161
|
}
|
|
903
1162
|
|
|
1163
|
+
const pendingInboundContext = this.getPendingInboundContext();
|
|
1164
|
+
if (pendingInboundContext && pendingInboundContext.hop >= MAX_RELAY_CHAIN_HOP) {
|
|
1165
|
+
this.log(`[${this.seatId}] suppressed relay loop: ${previewText(payload)}`);
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
if (pendingInboundContext?.relayUsed) {
|
|
1170
|
+
this.log(`[${this.seatId}] suppressed extra queued relay output: ${previewText(payload)}`);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const entryId = entry.id || createId(12);
|
|
904
1175
|
const signedEntry = {
|
|
905
|
-
id:
|
|
1176
|
+
id: entryId,
|
|
906
1177
|
type: "answer",
|
|
907
1178
|
seatId: this.seatId,
|
|
908
1179
|
origin: entry.origin || "unknown",
|
|
909
1180
|
text: payload,
|
|
910
1181
|
createdAt: entry.createdAt || new Date().toISOString(),
|
|
1182
|
+
chainId: pendingInboundContext?.chainId || entry.chainId || entryId,
|
|
1183
|
+
hop: pendingInboundContext ? pendingInboundContext.hop + 1 : 0,
|
|
911
1184
|
challenge: this.trustState.challenge,
|
|
912
1185
|
publicKey: this.identity.publicKey,
|
|
913
1186
|
};
|
|
@@ -916,6 +1189,10 @@ class ArmedSeat {
|
|
|
916
1189
|
this.identity.privateKey
|
|
917
1190
|
);
|
|
918
1191
|
appendJsonl(this.paths.eventsPath, signedEntry);
|
|
1192
|
+
this.rememberEmittedAnswer(answerKey);
|
|
1193
|
+
if (pendingInboundContext) {
|
|
1194
|
+
pendingInboundContext.relayUsed = true;
|
|
1195
|
+
}
|
|
919
1196
|
|
|
920
1197
|
this.log(`[${this.seatId}] ${previewText(payload)}`);
|
|
921
1198
|
}
|
|
@@ -1028,6 +1305,17 @@ function previewText(text, maxLength = 88) {
|
|
|
1028
1305
|
return `${compact.slice(0, maxLength - 3)}...`;
|
|
1029
1306
|
}
|
|
1030
1307
|
|
|
1308
|
+
function buildAnswerKey(entry, payload) {
|
|
1309
|
+
const origin = String(entry.origin || "unknown").trim() || "unknown";
|
|
1310
|
+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
1311
|
+
if (id) {
|
|
1312
|
+
return `${origin}:${id}`;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
const createdAt = typeof entry.createdAt === "string" ? entry.createdAt : "";
|
|
1316
|
+
return `${origin}:${createdAt}:${hashText(payload)}`;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1031
1319
|
function buildSeatReport(sessionName, seatId) {
|
|
1032
1320
|
const paths = getSeatPaths(sessionName, seatId);
|
|
1033
1321
|
const daemon = readJson(paths.daemonPath, null);
|
|
@@ -1136,6 +1424,7 @@ function stopAllSessions() {
|
|
|
1136
1424
|
|
|
1137
1425
|
module.exports = {
|
|
1138
1426
|
ArmedSeat,
|
|
1427
|
+
buildChildEnv,
|
|
1139
1428
|
getStatusReport,
|
|
1140
1429
|
resolveSessionName,
|
|
1141
1430
|
stopAllSessions,
|