muuuuse 0.2.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 +43 -55
- package/bin/muuse.js +0 -0
- package/package.json +8 -3
- package/src/agents.js +100 -6
- package/src/cli.js +51 -124
- package/src/runtime.js +457 -496
- package/src/util.js +44 -47
package/src/runtime.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const fs = require("node:fs");
|
|
2
2
|
const path = require("node:path");
|
|
3
|
-
const
|
|
3
|
+
const pty = require("node-pty");
|
|
4
4
|
|
|
5
5
|
const {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
detectAgentTypeFromCommand,
|
|
7
|
+
expandPresetCommand,
|
|
8
8
|
readClaudeAnswers,
|
|
9
9
|
readCodexAnswers,
|
|
10
10
|
readGeminiAnswers,
|
|
@@ -12,14 +12,12 @@ const {
|
|
|
12
12
|
selectCodexSessionFile,
|
|
13
13
|
selectGeminiSessionFile,
|
|
14
14
|
} = require("./agents");
|
|
15
|
-
const { capturePaneText, getPaneChildProcesses, getPaneInfo, paneExists, sendTextAndEnter, setPaneTitle } = require("./tmux");
|
|
16
15
|
const {
|
|
17
16
|
BRAND,
|
|
18
|
-
CONTROLLER_WAIT_MS,
|
|
19
17
|
POLL_MS,
|
|
20
18
|
appendJsonl,
|
|
21
19
|
createId,
|
|
22
|
-
|
|
20
|
+
getDefaultSessionName,
|
|
23
21
|
getFileSize,
|
|
24
22
|
getSeatPaths,
|
|
25
23
|
hashText,
|
|
@@ -32,553 +30,461 @@ const {
|
|
|
32
30
|
writeJson,
|
|
33
31
|
} = require("./util");
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
process.kill(daemon.pid, "SIGTERM");
|
|
41
|
-
} catch (error) {
|
|
42
|
-
// Ignore stale pid races.
|
|
43
|
-
}
|
|
44
|
-
}
|
|
33
|
+
const GENERIC_IDLE_MS = 900;
|
|
34
|
+
|
|
35
|
+
function resolveSessionName(sessionOverride, currentPath = process.cwd()) {
|
|
36
|
+
return sessionOverride || getDefaultSessionName(currentPath);
|
|
45
37
|
}
|
|
46
38
|
|
|
47
|
-
function
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
child.unref();
|
|
39
|
+
function resolveProgramTokens(commandTokens, usePresets = true) {
|
|
40
|
+
const resolved = expandPresetCommand(commandTokens, usePresets);
|
|
41
|
+
if (resolved.length === 0) {
|
|
42
|
+
throw new Error("Seat commands now require a program. Example: `muuuuse 1 codex`.");
|
|
43
|
+
}
|
|
44
|
+
return resolved;
|
|
54
45
|
}
|
|
55
46
|
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
windowName: paneInfo.windowName,
|
|
67
|
-
cwd: paneInfo.currentPath,
|
|
68
|
-
armedAt: new Date().toISOString(),
|
|
69
|
-
instanceId: createId(12),
|
|
70
|
-
};
|
|
47
|
+
function formatCommand(commandTokens) {
|
|
48
|
+
return commandTokens
|
|
49
|
+
.map((token) => {
|
|
50
|
+
if (/^[a-zA-Z0-9._/@:=+-]+$/.test(token)) {
|
|
51
|
+
return token;
|
|
52
|
+
}
|
|
53
|
+
return JSON.stringify(token);
|
|
54
|
+
})
|
|
55
|
+
.join(" ");
|
|
56
|
+
}
|
|
71
57
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
58
|
+
function previewText(text, maxLength = 88) {
|
|
59
|
+
const compact = sanitizeRelayText(text).replace(/\s+/g, " ");
|
|
60
|
+
if (compact.length <= maxLength) {
|
|
61
|
+
return compact;
|
|
62
|
+
}
|
|
63
|
+
return `${compact.slice(0, maxLength - 3)}...`;
|
|
76
64
|
}
|
|
77
65
|
|
|
78
|
-
function
|
|
79
|
-
return
|
|
80
|
-
.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
66
|
+
function parseAnswerEntries(text) {
|
|
67
|
+
return String(text || "")
|
|
68
|
+
.split("\n")
|
|
69
|
+
.map((line) => line.trim())
|
|
70
|
+
.filter((line) => line.length > 0)
|
|
71
|
+
.map((line) => {
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(line);
|
|
74
|
+
} catch (error) {
|
|
84
75
|
return null;
|
|
85
76
|
}
|
|
86
|
-
return meta;
|
|
87
77
|
})
|
|
88
|
-
.filter((entry) => entry
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function findSeatByPane(sessionName, paneId) {
|
|
92
|
-
return listArmedSeats(sessionName).find((seat) => seat.paneId === paneId) || null;
|
|
78
|
+
.filter((entry) => entry && entry.type === "answer" && typeof entry.text === "string");
|
|
93
79
|
}
|
|
94
80
|
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
throw new Error("This pane is not armed. Run `muuuuse 1` or `muuuuse 2` first.");
|
|
81
|
+
function resolveSessionFile(agentType, currentPath, processStartedAtMs, options = {}) {
|
|
82
|
+
if (agentType === "codex") {
|
|
83
|
+
return selectCodexSessionFile(currentPath, processStartedAtMs, options);
|
|
99
84
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
.map((step) => sanitizeRelayText(step))
|
|
103
|
-
.filter((step) => step.length > 0);
|
|
104
|
-
|
|
105
|
-
if (normalizedSteps.length === 0) {
|
|
106
|
-
throw new Error("Script mode needs at least one non-empty step.");
|
|
85
|
+
if (agentType === "claude") {
|
|
86
|
+
return selectClaudeSessionFile(currentPath, processStartedAtMs);
|
|
107
87
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
cursor: 0,
|
|
113
|
-
steps: normalizedSteps,
|
|
114
|
-
updatedAt: new Date().toISOString(),
|
|
115
|
-
});
|
|
116
|
-
setPaneTitle(paneId, `muuuuse ${seat.seatId} script`);
|
|
117
|
-
return {
|
|
118
|
-
seatId: seat.seatId,
|
|
119
|
-
steps: normalizedSteps,
|
|
120
|
-
};
|
|
88
|
+
if (agentType === "gemini") {
|
|
89
|
+
return selectGeminiSessionFile(currentPath, processStartedAtMs);
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
121
92
|
}
|
|
122
93
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
94
|
+
class GenericAnswerTracker {
|
|
95
|
+
constructor() {
|
|
96
|
+
this.active = false;
|
|
97
|
+
this.buffer = "";
|
|
98
|
+
this.lastInputText = "";
|
|
99
|
+
this.lastOutputAt = 0;
|
|
100
|
+
this.lastFingerprint = null;
|
|
127
101
|
}
|
|
128
102
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
function queueSeatCommand(sessionName, seatId, text, meta = {}) {
|
|
136
|
-
const seatPaths = getSeatPaths(sessionName, seatId);
|
|
137
|
-
const payload = sanitizeRelayText(text);
|
|
138
|
-
if (!payload) {
|
|
139
|
-
return null;
|
|
103
|
+
noteTurnStart(inputText = "") {
|
|
104
|
+
this.active = true;
|
|
105
|
+
this.buffer = "";
|
|
106
|
+
this.lastInputText = sanitizeRelayText(inputText);
|
|
107
|
+
this.lastOutputAt = 0;
|
|
140
108
|
}
|
|
141
109
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
createdAt: new Date().toISOString(),
|
|
147
|
-
...meta,
|
|
148
|
-
};
|
|
149
|
-
appendJsonl(seatPaths.commandsPath, command);
|
|
150
|
-
return command;
|
|
151
|
-
}
|
|
110
|
+
append(data) {
|
|
111
|
+
if (!this.active) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
152
114
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
this.sessionName = sessionName;
|
|
156
|
-
this.seedSeat = options.seedSeat === 2 ? 2 : 1;
|
|
157
|
-
this.seedText = sanitizeRelayText(options.seedText || "");
|
|
158
|
-
this.maxRelays = Number.isFinite(options.maxRelays) ? options.maxRelays : Number.POSITIVE_INFINITY;
|
|
159
|
-
this.relayCount = 0;
|
|
160
|
-
this.stopped = false;
|
|
161
|
-
this.offsets = { 1: 0, 2: 0 };
|
|
162
|
-
this.controllerPath = getControllerPath(sessionName);
|
|
163
|
-
this.seats = new Map();
|
|
164
|
-
}
|
|
115
|
+
this.buffer += String(data || "");
|
|
116
|
+
this.lastOutputAt = Date.now();
|
|
165
117
|
|
|
166
|
-
|
|
167
|
-
|
|
118
|
+
if (this.buffer.length > 24000) {
|
|
119
|
+
this.buffer = this.buffer.slice(-24000);
|
|
120
|
+
}
|
|
168
121
|
}
|
|
169
122
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
process.once("SIGINT", stop);
|
|
175
|
-
process.once("SIGTERM", stop);
|
|
176
|
-
}
|
|
123
|
+
consumeReady() {
|
|
124
|
+
if (!this.active || !this.lastOutputAt) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
177
127
|
|
|
178
|
-
|
|
179
|
-
|
|
128
|
+
if (Date.now() - this.lastOutputAt < GENERIC_IDLE_MS) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
180
131
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (this.seats.has(1) && this.seats.has(2)) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
await sleep(CONTROLLER_WAIT_MS);
|
|
132
|
+
const text = extractGenericAnswer(this.buffer, this.lastInputText);
|
|
133
|
+
if (!text) {
|
|
134
|
+
return null;
|
|
188
135
|
}
|
|
189
|
-
}
|
|
190
136
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
this.offsets[seatId] = getFileSize(eventsPath);
|
|
137
|
+
const fingerprint = hashText(`${this.lastInputText}\n${text}`);
|
|
138
|
+
if (fingerprint === this.lastFingerprint) {
|
|
139
|
+
return null;
|
|
195
140
|
}
|
|
196
|
-
}
|
|
197
141
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
sessionName: this.sessionName,
|
|
202
|
-
seedSeat: this.seedSeat,
|
|
203
|
-
relays: this.relayCount,
|
|
204
|
-
startedAt: new Date().toISOString(),
|
|
205
|
-
});
|
|
142
|
+
this.lastFingerprint = fingerprint;
|
|
143
|
+
this.active = false;
|
|
144
|
+
return text;
|
|
206
145
|
}
|
|
146
|
+
}
|
|
207
147
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
148
|
+
function extractGenericAnswer(rawText, lastInputText) {
|
|
149
|
+
let candidate = sanitizeRelayText(rawText, 12000);
|
|
150
|
+
if (!candidate) {
|
|
151
|
+
return null;
|
|
213
152
|
}
|
|
214
153
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (this.stopped) {
|
|
219
|
-
return 0;
|
|
154
|
+
if (lastInputText) {
|
|
155
|
+
if (candidate === lastInputText) {
|
|
156
|
+
return null;
|
|
220
157
|
}
|
|
158
|
+
if (candidate.startsWith(`${lastInputText}\n`)) {
|
|
159
|
+
candidate = candidate.slice(lastInputText.length).trim();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
221
162
|
|
|
222
|
-
|
|
223
|
-
|
|
163
|
+
const markerAnswer = extractMarkedAnswer(candidate);
|
|
164
|
+
if (markerAnswer) {
|
|
165
|
+
return markerAnswer;
|
|
166
|
+
}
|
|
224
167
|
|
|
225
|
-
|
|
226
|
-
|
|
168
|
+
const blocks = candidate
|
|
169
|
+
.split(/\n{2,}/)
|
|
170
|
+
.map((block) => block.trim())
|
|
171
|
+
.filter((block) => block.length > 0);
|
|
227
172
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
source: "controller_seed",
|
|
231
|
-
});
|
|
232
|
-
this.print(`Kickoff -> seat ${this.seedSeat}: ${previewText(this.seedText)}`);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
try {
|
|
236
|
-
while (!this.stopped) {
|
|
237
|
-
await this.forwardNewAnswers();
|
|
238
|
-
if (this.relayCount >= this.maxRelays) {
|
|
239
|
-
this.print(`${BRAND} hit the relay cap (${this.maxRelays}).`);
|
|
240
|
-
return 0;
|
|
241
|
-
}
|
|
242
|
-
await sleep(POLL_MS);
|
|
243
|
-
}
|
|
244
|
-
return 0;
|
|
245
|
-
} finally {
|
|
246
|
-
this.removeState();
|
|
247
|
-
}
|
|
173
|
+
if (blocks.length === 0) {
|
|
174
|
+
return null;
|
|
248
175
|
}
|
|
249
176
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const targetSeatId = seatId === 1 ? 2 : 1;
|
|
253
|
-
const { eventsPath } = getSeatPaths(this.sessionName, seatId);
|
|
254
|
-
const { nextOffset, text } = readAppendedText(eventsPath, this.offsets[seatId]);
|
|
255
|
-
this.offsets[seatId] = nextOffset;
|
|
256
|
-
if (!text.trim()) {
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
177
|
+
return sanitizeRelayText(blocks[blocks.length - 1]);
|
|
178
|
+
}
|
|
259
179
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
try {
|
|
266
|
-
return JSON.parse(line);
|
|
267
|
-
} catch (error) {
|
|
268
|
-
return null;
|
|
269
|
-
}
|
|
270
|
-
})
|
|
271
|
-
.filter((entry) => entry && entry.type === "answer" && typeof entry.text === "string");
|
|
272
|
-
|
|
273
|
-
for (const entry of entries) {
|
|
274
|
-
const queued = queueSeatCommand(this.sessionName, targetSeatId, entry.text, {
|
|
275
|
-
sourceSeat: seatId,
|
|
276
|
-
sourceEventId: entry.id,
|
|
277
|
-
});
|
|
278
|
-
if (!queued) {
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
this.relayCount += 1;
|
|
282
|
-
this.print(`[${seatId} -> ${targetSeatId}] ${previewText(entry.text)}`);
|
|
283
|
-
if (this.relayCount >= this.maxRelays) {
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
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;
|
|
288
185
|
}
|
|
186
|
+
|
|
187
|
+
const answerLines = lines.slice(answerIndex);
|
|
188
|
+
answerLines[0] = answerLines[0].trim().replace(/^\(answer\)\s*/, "");
|
|
189
|
+
return sanitizeRelayText(answerLines.join("\n"));
|
|
289
190
|
}
|
|
290
191
|
|
|
291
|
-
class
|
|
292
|
-
constructor(
|
|
293
|
-
this.
|
|
294
|
-
this.
|
|
295
|
-
this.
|
|
296
|
-
this.
|
|
192
|
+
class SeatProcess {
|
|
193
|
+
constructor(options) {
|
|
194
|
+
this.seatId = options.seatId;
|
|
195
|
+
this.partnerSeatId = options.seatId === 1 ? 2 : 1;
|
|
196
|
+
this.sessionName = options.sessionName;
|
|
197
|
+
this.cwd = options.cwd;
|
|
198
|
+
this.commandTokens = [...options.commandTokens];
|
|
199
|
+
this.agentType = detectAgentTypeFromCommand(this.commandTokens);
|
|
200
|
+
this.maxRelays = options.maxRelays;
|
|
201
|
+
|
|
202
|
+
this.paths = getSeatPaths(this.sessionName, this.seatId);
|
|
203
|
+
this.partnerPaths = getSeatPaths(this.sessionName, this.partnerSeatId);
|
|
204
|
+
this.partnerOffset = getFileSize(this.partnerPaths.eventsPath);
|
|
205
|
+
|
|
206
|
+
this.child = null;
|
|
207
|
+
this.childPid = null;
|
|
208
|
+
this.childExit = null;
|
|
209
|
+
this.startedAtMs = Date.now();
|
|
210
|
+
this.relayCount = 0;
|
|
211
|
+
this.linked = false;
|
|
297
212
|
this.stopped = false;
|
|
298
|
-
this.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
213
|
+
this.stdinCleanup = null;
|
|
214
|
+
this.resizeCleanup = null;
|
|
215
|
+
this.childToken = createId(16);
|
|
216
|
+
this.processStartedAtMs = null;
|
|
217
|
+
|
|
218
|
+
this.sessionState = {
|
|
219
|
+
file: null,
|
|
303
220
|
offset: 0,
|
|
304
221
|
lastMessageId: null,
|
|
305
|
-
processStartedAtMs: null,
|
|
306
|
-
};
|
|
307
|
-
this.paneState = {
|
|
308
|
-
text: "",
|
|
309
|
-
changedAt: 0,
|
|
310
|
-
lastCandidateHash: null,
|
|
311
222
|
};
|
|
223
|
+
|
|
224
|
+
this.genericTracker = new GenericAnswerTracker();
|
|
312
225
|
}
|
|
313
226
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
this.stopped = true;
|
|
317
|
-
};
|
|
318
|
-
process.once("SIGINT", stop);
|
|
319
|
-
process.once("SIGTERM", stop);
|
|
227
|
+
log(message) {
|
|
228
|
+
process.stderr.write(`${message}\n`);
|
|
320
229
|
}
|
|
321
230
|
|
|
322
|
-
|
|
323
|
-
writeJson(this.paths.
|
|
231
|
+
writeMeta(extra = {}) {
|
|
232
|
+
writeJson(this.paths.metaPath, {
|
|
233
|
+
seatId: this.seatId,
|
|
234
|
+
sessionName: this.sessionName,
|
|
235
|
+
cwd: this.cwd,
|
|
324
236
|
pid: process.pid,
|
|
237
|
+
childPid: this.childPid,
|
|
238
|
+
childToken: this.childToken,
|
|
239
|
+
agentType: this.agentType,
|
|
240
|
+
command: this.commandTokens,
|
|
241
|
+
commandLine: formatCommand(this.commandTokens),
|
|
242
|
+
startedAt: new Date(this.startedAtMs).toISOString(),
|
|
243
|
+
...extra,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
writeStatus(extra = {}) {
|
|
248
|
+
writeJson(this.paths.statusPath, {
|
|
325
249
|
seatId: this.seatId,
|
|
326
250
|
sessionName: this.sessionName,
|
|
327
|
-
|
|
251
|
+
cwd: this.cwd,
|
|
252
|
+
pid: process.pid,
|
|
253
|
+
childPid: this.childPid,
|
|
254
|
+
childToken: this.childToken,
|
|
255
|
+
agentType: this.agentType,
|
|
256
|
+
command: this.commandTokens,
|
|
257
|
+
relayCount: this.relayCount,
|
|
258
|
+
updatedAt: new Date().toISOString(),
|
|
259
|
+
...extra,
|
|
328
260
|
});
|
|
329
261
|
}
|
|
330
262
|
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
263
|
+
installSignalHandlers() {
|
|
264
|
+
const stop = () => {
|
|
265
|
+
this.stopped = true;
|
|
266
|
+
};
|
|
267
|
+
process.once("SIGINT", stop);
|
|
268
|
+
process.once("SIGTERM", stop);
|
|
336
269
|
}
|
|
337
270
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
271
|
+
installStdinProxy() {
|
|
272
|
+
const handleData = (chunk) => {
|
|
273
|
+
if (!this.child) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
341
276
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
277
|
+
const text = chunk.toString("utf8");
|
|
278
|
+
this.child.write(text);
|
|
279
|
+
if (this.shouldUseGenericCapture() && /[\r\n]/.test(text)) {
|
|
280
|
+
this.genericTracker.noteTurnStart("");
|
|
346
281
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
285
|
+
process.stdin.setRawMode(true);
|
|
350
286
|
}
|
|
287
|
+
process.stdin.resume();
|
|
288
|
+
process.stdin.on("data", handleData);
|
|
289
|
+
|
|
290
|
+
this.stdinCleanup = () => {
|
|
291
|
+
process.stdin.off("data", handleData);
|
|
292
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
293
|
+
process.stdin.setRawMode(false);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
351
296
|
}
|
|
352
297
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (!meta || !paneExists(meta.paneId)) {
|
|
356
|
-
this.writeStatus({ state: "waiting_for_pane" });
|
|
298
|
+
installResizeHandler() {
|
|
299
|
+
if (!process.stdout.isTTY) {
|
|
357
300
|
return;
|
|
358
301
|
}
|
|
359
302
|
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
303
|
+
const handleResize = () => {
|
|
304
|
+
if (!this.child) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
365
307
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
});
|
|
373
|
-
}
|
|
308
|
+
try {
|
|
309
|
+
this.child.resize(process.stdout.columns || 80, process.stdout.rows || 24);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
// Ignore resize failures while the child is exiting.
|
|
312
|
+
}
|
|
313
|
+
};
|
|
374
314
|
|
|
375
|
-
|
|
376
|
-
this.
|
|
315
|
+
process.stdout.on("resize", handleResize);
|
|
316
|
+
this.resizeCleanup = () => {
|
|
317
|
+
process.stdout.off("resize", handleResize);
|
|
318
|
+
};
|
|
319
|
+
}
|
|
377
320
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
321
|
+
launchChild() {
|
|
322
|
+
resetDir(this.paths.dir);
|
|
323
|
+
|
|
324
|
+
const [file, ...args] = this.commandTokens;
|
|
325
|
+
this.child = pty.spawn(file, args, {
|
|
326
|
+
cols: process.stdout.columns || 80,
|
|
327
|
+
cwd: this.cwd,
|
|
328
|
+
env: {
|
|
329
|
+
...process.env,
|
|
330
|
+
MUUUUSE_CHILD_TOKEN: this.childToken,
|
|
331
|
+
MUUUUSE_SEAT: String(this.seatId),
|
|
332
|
+
MUUUUSE_SESSION: this.sessionName,
|
|
333
|
+
},
|
|
334
|
+
name: process.env.TERM || "xterm-256color",
|
|
335
|
+
rows: process.stdout.rows || 24,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
this.childPid = this.child.pid;
|
|
339
|
+
this.processStartedAtMs = Date.now();
|
|
340
|
+
this.writeMeta();
|
|
341
|
+
this.writeStatus({
|
|
342
|
+
partnerSeatId: this.partnerSeatId,
|
|
343
|
+
state: "running",
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
this.child.onData((data) => {
|
|
347
|
+
process.stdout.write(data);
|
|
348
|
+
if (this.shouldUseGenericCapture()) {
|
|
349
|
+
this.genericTracker.append(data);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
this.child.onExit(({ exitCode, signal }) => {
|
|
354
|
+
this.childExit = {
|
|
355
|
+
exitCode,
|
|
356
|
+
signal: signal || null,
|
|
357
|
+
};
|
|
358
|
+
this.stopped = true;
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
partnerIsLive() {
|
|
363
|
+
const partnerStatus = readJson(this.partnerPaths.statusPath, null);
|
|
364
|
+
return Boolean(partnerStatus?.pid && isPidAlive(partnerStatus.pid));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
maybeMarkLinked() {
|
|
368
|
+
if (this.linked || !this.partnerIsLive()) {
|
|
385
369
|
return;
|
|
386
370
|
}
|
|
387
371
|
|
|
388
|
-
this.
|
|
372
|
+
this.linked = true;
|
|
373
|
+
this.log(`${BRAND} seat ${this.seatId} linked with seat ${this.partnerSeatId} in session ${this.sessionName}.`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
shouldUseGenericCapture() {
|
|
377
|
+
return !this.agentType;
|
|
389
378
|
}
|
|
390
379
|
|
|
391
|
-
|
|
392
|
-
const { nextOffset, text } = readAppendedText(this.
|
|
393
|
-
this.
|
|
380
|
+
pullPartnerEvents() {
|
|
381
|
+
const { nextOffset, text } = readAppendedText(this.partnerPaths.eventsPath, this.partnerOffset);
|
|
382
|
+
this.partnerOffset = nextOffset;
|
|
394
383
|
if (!text.trim()) {
|
|
395
384
|
return;
|
|
396
385
|
}
|
|
397
386
|
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
.
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
})
|
|
409
|
-
.filter((entry) => entry && entry.type === "deliver" && typeof entry.text === "string");
|
|
387
|
+
const entries = parseAnswerEntries(text);
|
|
388
|
+
for (const entry of entries) {
|
|
389
|
+
if (!this.child) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (Number.isFinite(this.maxRelays) && this.relayCount >= this.maxRelays) {
|
|
393
|
+
this.log(`${BRAND} seat ${this.seatId} hit the relay cap (${this.maxRelays}).`);
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
410
396
|
|
|
411
|
-
|
|
412
|
-
if (
|
|
413
|
-
this.handleScriptTurn(meta, script, command);
|
|
397
|
+
const payload = sanitizeRelayText(entry.text);
|
|
398
|
+
if (!payload) {
|
|
414
399
|
continue;
|
|
415
400
|
}
|
|
416
401
|
|
|
417
|
-
|
|
402
|
+
if (this.shouldUseGenericCapture()) {
|
|
403
|
+
this.genericTracker.noteTurnStart(payload);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
this.child.write(payload.replace(/\n/g, "\r"));
|
|
407
|
+
this.child.write("\r");
|
|
408
|
+
this.relayCount += 1;
|
|
409
|
+
this.log(`[${this.partnerSeatId} -> ${this.seatId}] ${previewText(payload)}`);
|
|
418
410
|
}
|
|
419
411
|
}
|
|
420
412
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (steps.length === 0) {
|
|
413
|
+
resolveStructuredLog() {
|
|
414
|
+
if (!this.agentType || this.sessionState.file) {
|
|
424
415
|
return;
|
|
425
416
|
}
|
|
426
417
|
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
};
|
|
436
|
-
writeJson(this.paths.scriptPath, nextScript);
|
|
437
|
-
this.emitAnswer({
|
|
438
|
-
id: createId(12),
|
|
439
|
-
origin: "script",
|
|
440
|
-
text: nextText,
|
|
441
|
-
createdAt: new Date().toISOString(),
|
|
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,
|
|
442
426
|
});
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
collectLiveAnswers(meta, paneInfo) {
|
|
446
|
-
const detectedAgent = detectAgent(getPaneChildProcesses(meta.paneId));
|
|
447
|
-
if (!detectedAgent) {
|
|
448
|
-
this.liveState = {
|
|
449
|
-
type: null,
|
|
450
|
-
pid: null,
|
|
451
|
-
currentPath: paneInfo.currentPath,
|
|
452
|
-
sessionFile: null,
|
|
453
|
-
offset: 0,
|
|
454
|
-
lastMessageId: null,
|
|
455
|
-
processStartedAtMs: null,
|
|
456
|
-
};
|
|
457
|
-
this.writeStatus({
|
|
458
|
-
state: "armed",
|
|
459
|
-
cwd: paneInfo.currentPath,
|
|
460
|
-
agent: null,
|
|
461
|
-
});
|
|
427
|
+
if (!sessionFile) {
|
|
462
428
|
return;
|
|
463
429
|
}
|
|
464
430
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
this.
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
this.
|
|
472
|
-
type: detectedAgent.type,
|
|
473
|
-
pid: detectedAgent.pid,
|
|
474
|
-
currentPath: paneInfo.currentPath,
|
|
475
|
-
sessionFile: null,
|
|
476
|
-
offset: 0,
|
|
477
|
-
lastMessageId: null,
|
|
478
|
-
processStartedAtMs: detectedAgent.processStartedAtMs,
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (!this.liveState.sessionFile) {
|
|
483
|
-
this.liveState.sessionFile = resolveSessionFile(
|
|
484
|
-
detectedAgent.type,
|
|
485
|
-
paneInfo.currentPath,
|
|
486
|
-
detectedAgent.processStartedAtMs,
|
|
487
|
-
meta.paneId
|
|
488
|
-
);
|
|
489
|
-
if (this.liveState.sessionFile) {
|
|
490
|
-
if (detectedAgent.type === "gemini") {
|
|
491
|
-
const baseline = readGeminiAnswers(this.liveState.sessionFile, null);
|
|
492
|
-
this.liveState.lastMessageId = baseline.lastMessageId;
|
|
493
|
-
this.liveState.offset = baseline.fileSize;
|
|
494
|
-
} else {
|
|
495
|
-
this.liveState.offset = getFileSize(this.liveState.sessionFile);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
431
|
+
this.sessionState.file = sessionFile;
|
|
432
|
+
if (this.agentType === "gemini") {
|
|
433
|
+
const baseline = readGeminiAnswers(sessionFile, null);
|
|
434
|
+
this.sessionState.lastMessageId = baseline.lastMessageId;
|
|
435
|
+
this.sessionState.offset = baseline.fileSize;
|
|
436
|
+
} else {
|
|
437
|
+
this.sessionState.offset = getFileSize(sessionFile);
|
|
498
438
|
}
|
|
439
|
+
}
|
|
499
440
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
cwd: paneInfo.currentPath,
|
|
504
|
-
agent: detectedAgent.type,
|
|
505
|
-
log: "waiting_for_session_log",
|
|
506
|
-
});
|
|
441
|
+
collectStructuredAnswers() {
|
|
442
|
+
this.resolveStructuredLog();
|
|
443
|
+
if (!this.sessionState.file || !this.agentType) {
|
|
507
444
|
return;
|
|
508
445
|
}
|
|
509
446
|
|
|
510
447
|
const answers = [];
|
|
511
|
-
if (
|
|
512
|
-
const result = readCodexAnswers(this.
|
|
513
|
-
this.
|
|
448
|
+
if (this.agentType === "codex") {
|
|
449
|
+
const result = readCodexAnswers(this.sessionState.file, this.sessionState.offset);
|
|
450
|
+
this.sessionState.offset = result.nextOffset;
|
|
514
451
|
answers.push(...result.answers);
|
|
515
|
-
} else if (
|
|
516
|
-
const result = readClaudeAnswers(this.
|
|
517
|
-
this.
|
|
452
|
+
} else if (this.agentType === "claude") {
|
|
453
|
+
const result = readClaudeAnswers(this.sessionState.file, this.sessionState.offset);
|
|
454
|
+
this.sessionState.offset = result.nextOffset;
|
|
518
455
|
answers.push(...result.answers);
|
|
519
|
-
} else if (
|
|
520
|
-
const result = readGeminiAnswers(this.
|
|
521
|
-
this.
|
|
522
|
-
this.
|
|
456
|
+
} else if (this.agentType === "gemini") {
|
|
457
|
+
const result = readGeminiAnswers(this.sessionState.file, this.sessionState.lastMessageId);
|
|
458
|
+
this.sessionState.lastMessageId = result.lastMessageId;
|
|
459
|
+
this.sessionState.offset = result.fileSize;
|
|
523
460
|
answers.push(...result.answers);
|
|
524
461
|
}
|
|
525
462
|
|
|
526
463
|
for (const answer of answers) {
|
|
527
464
|
this.emitAnswer({
|
|
528
|
-
|
|
529
|
-
|
|
465
|
+
createdAt: answer.timestamp,
|
|
466
|
+
id: answer.id,
|
|
467
|
+
origin: this.agentType,
|
|
530
468
|
text: answer.text,
|
|
531
|
-
createdAt: answer.timestamp || new Date().toISOString(),
|
|
532
469
|
});
|
|
533
470
|
}
|
|
534
|
-
|
|
535
|
-
this.collectPaneFallback(meta, detectedAgent);
|
|
536
|
-
|
|
537
|
-
this.writeStatus({
|
|
538
|
-
state: "armed",
|
|
539
|
-
cwd: paneInfo.currentPath,
|
|
540
|
-
agent: detectedAgent.type,
|
|
541
|
-
log: this.liveState.sessionFile,
|
|
542
|
-
lastAnswerAt: answers.length > 0 ? answers[answers.length - 1].timestamp : undefined,
|
|
543
|
-
});
|
|
544
471
|
}
|
|
545
472
|
|
|
546
|
-
|
|
547
|
-
if (
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
const paneText = capturePaneText(meta.paneId, 240);
|
|
552
|
-
if (!paneText.trim()) {
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
if (paneText !== this.paneState.text) {
|
|
557
|
-
this.paneState.text = paneText;
|
|
558
|
-
this.paneState.changedAt = Date.now();
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
if (Date.now() - this.paneState.changedAt < 2200) {
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
const candidate = extractCodexPaneAnswer(paneText);
|
|
567
|
-
if (!candidate) {
|
|
473
|
+
collectGenericAnswers() {
|
|
474
|
+
if (!this.shouldUseGenericCapture()) {
|
|
568
475
|
return;
|
|
569
476
|
}
|
|
570
477
|
|
|
571
|
-
const
|
|
572
|
-
if (
|
|
478
|
+
const text = this.genericTracker.consumeReady();
|
|
479
|
+
if (!text) {
|
|
573
480
|
return;
|
|
574
481
|
}
|
|
575
482
|
|
|
576
|
-
this.paneState.lastCandidateHash = candidateHash;
|
|
577
483
|
this.emitAnswer({
|
|
578
|
-
id: createId(12),
|
|
579
|
-
origin: "codex_pane",
|
|
580
|
-
text: candidate,
|
|
581
484
|
createdAt: new Date().toISOString(),
|
|
485
|
+
id: createId(12),
|
|
486
|
+
origin: "generic",
|
|
487
|
+
text,
|
|
582
488
|
});
|
|
583
489
|
}
|
|
584
490
|
|
|
@@ -596,88 +502,143 @@ class SeatDaemon {
|
|
|
596
502
|
text,
|
|
597
503
|
createdAt: entry.createdAt || new Date().toISOString(),
|
|
598
504
|
});
|
|
505
|
+
|
|
506
|
+
this.log(`[${this.seatId}] ${previewText(text)}`);
|
|
599
507
|
}
|
|
600
508
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
509
|
+
async tick() {
|
|
510
|
+
this.maybeMarkLinked();
|
|
511
|
+
this.pullPartnerEvents();
|
|
512
|
+
this.collectStructuredAnswers();
|
|
513
|
+
this.collectGenericAnswers();
|
|
514
|
+
|
|
515
|
+
this.writeStatus({
|
|
516
|
+
partnerSeatId: this.partnerSeatId,
|
|
517
|
+
partnerLive: this.partnerIsLive(),
|
|
518
|
+
state: this.childExit ? "exited" : "running",
|
|
519
|
+
structuredLog: this.sessionState.file,
|
|
608
520
|
});
|
|
609
521
|
}
|
|
610
|
-
}
|
|
611
522
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
return selectClaudeSessionFile(currentPath, processStartedAtMs);
|
|
618
|
-
}
|
|
619
|
-
if (agentType === "gemini") {
|
|
620
|
-
return selectGeminiSessionFile(currentPath, processStartedAtMs);
|
|
621
|
-
}
|
|
622
|
-
return null;
|
|
623
|
-
}
|
|
523
|
+
async run() {
|
|
524
|
+
this.installSignalHandlers();
|
|
525
|
+
this.launchChild();
|
|
526
|
+
this.installStdinProxy();
|
|
527
|
+
this.installResizeHandler();
|
|
624
528
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
529
|
+
this.log(`${BRAND} seat ${this.seatId} started in session ${this.sessionName}.`);
|
|
530
|
+
this.log(`Command: ${formatCommand(this.commandTokens)}`);
|
|
531
|
+
this.log(`Stop both seats from another terminal with: muuuuse 3 stop`);
|
|
532
|
+
|
|
533
|
+
try {
|
|
534
|
+
while (!this.stopped) {
|
|
535
|
+
await this.tick();
|
|
536
|
+
await sleep(POLL_MS);
|
|
537
|
+
}
|
|
538
|
+
} finally {
|
|
539
|
+
this.cleanup();
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return this.childExit?.exitCode ?? 0;
|
|
629
543
|
}
|
|
630
|
-
return `${compact.slice(0, maxLength - 3)}...`;
|
|
631
|
-
}
|
|
632
544
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
545
|
+
cleanup() {
|
|
546
|
+
if (this.stdinCleanup) {
|
|
547
|
+
this.stdinCleanup();
|
|
548
|
+
this.stdinCleanup = null;
|
|
549
|
+
}
|
|
550
|
+
if (this.resizeCleanup) {
|
|
551
|
+
this.resizeCleanup();
|
|
552
|
+
this.resizeCleanup = null;
|
|
553
|
+
}
|
|
636
554
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
555
|
+
if (this.child && !this.childExit) {
|
|
556
|
+
try {
|
|
557
|
+
this.child.kill("SIGTERM");
|
|
558
|
+
} catch (error) {
|
|
559
|
+
// Ignore races during shutdown.
|
|
560
|
+
}
|
|
641
561
|
}
|
|
562
|
+
|
|
563
|
+
this.writeMeta({
|
|
564
|
+
childPid: this.childPid,
|
|
565
|
+
exitedAt: new Date().toISOString(),
|
|
566
|
+
});
|
|
567
|
+
this.writeStatus({
|
|
568
|
+
childPid: this.childPid,
|
|
569
|
+
exitCode: this.childExit?.exitCode ?? null,
|
|
570
|
+
exitedAt: new Date().toISOString(),
|
|
571
|
+
partnerSeatId: this.partnerSeatId,
|
|
572
|
+
state: "exited",
|
|
573
|
+
});
|
|
642
574
|
}
|
|
575
|
+
}
|
|
643
576
|
|
|
644
|
-
|
|
645
|
-
|
|
577
|
+
function stopSession(sessionName) {
|
|
578
|
+
const results = [];
|
|
579
|
+
|
|
580
|
+
for (const seatId of [1, 2]) {
|
|
581
|
+
const paths = getSeatPaths(sessionName, seatId);
|
|
582
|
+
const status = readJson(paths.statusPath, null);
|
|
583
|
+
const meta = readJson(paths.metaPath, null);
|
|
584
|
+
const wrapperPid = status?.pid || meta?.pid || null;
|
|
585
|
+
const childPid = status?.childPid || meta?.childPid || null;
|
|
586
|
+
|
|
587
|
+
let wrapperStopped = false;
|
|
588
|
+
let childStopped = false;
|
|
589
|
+
|
|
590
|
+
if (wrapperPid && isPidAlive(wrapperPid)) {
|
|
591
|
+
try {
|
|
592
|
+
process.kill(wrapperPid, "SIGTERM");
|
|
593
|
+
wrapperStopped = true;
|
|
594
|
+
} catch (error) {
|
|
595
|
+
wrapperStopped = false;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
646
598
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
599
|
+
if (childPid && isPidAlive(childPid)) {
|
|
600
|
+
try {
|
|
601
|
+
process.kill(childPid, "SIGTERM");
|
|
602
|
+
childStopped = true;
|
|
603
|
+
} catch (error) {
|
|
604
|
+
childStopped = false;
|
|
605
|
+
}
|
|
651
606
|
}
|
|
652
|
-
}
|
|
653
607
|
|
|
654
|
-
|
|
655
|
-
|
|
608
|
+
results.push({
|
|
609
|
+
seatId,
|
|
610
|
+
childPid,
|
|
611
|
+
childStopped,
|
|
612
|
+
wrapperPid,
|
|
613
|
+
wrapperStopped,
|
|
614
|
+
});
|
|
656
615
|
}
|
|
657
616
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
return "";
|
|
664
|
-
}
|
|
617
|
+
return {
|
|
618
|
+
sessionName,
|
|
619
|
+
seats: results,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
665
622
|
|
|
666
|
-
|
|
667
|
-
return
|
|
623
|
+
function readSessionStatus(sessionName) {
|
|
624
|
+
return {
|
|
625
|
+
sessionName,
|
|
626
|
+
seats: [1, 2].map((seatId) => {
|
|
627
|
+
const paths = getSeatPaths(sessionName, seatId);
|
|
628
|
+
const status = readJson(paths.statusPath, null);
|
|
629
|
+
return {
|
|
630
|
+
seatId,
|
|
631
|
+
status,
|
|
632
|
+
};
|
|
633
|
+
}),
|
|
634
|
+
};
|
|
668
635
|
}
|
|
669
636
|
|
|
670
637
|
module.exports = {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
enableLiveMode,
|
|
678
|
-
extractCodexPaneAnswer,
|
|
679
|
-
findSeatByPane,
|
|
680
|
-
listArmedSeats,
|
|
681
|
-
previewText,
|
|
682
|
-
queueSeatCommand,
|
|
638
|
+
SeatProcess,
|
|
639
|
+
formatCommand,
|
|
640
|
+
readSessionStatus,
|
|
641
|
+
resolveProgramTokens,
|
|
642
|
+
resolveSessionName,
|
|
643
|
+
stopSession,
|
|
683
644
|
};
|