@melaya/runner 1.1.47 → 1.1.48
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/dist/assistantHost.py +30 -4
- package/dist/connection.js +21 -3
- package/package.json +1 -1
package/dist/assistantHost.py
CHANGED
|
@@ -230,21 +230,47 @@ class _RedactingStdout:
|
|
|
230
230
|
def _scrub(self, s: str) -> str:
|
|
231
231
|
return self._B64.sub(lambda m: f"<redacted base64 {len(m.group(0))} bytes>", s)
|
|
232
232
|
|
|
233
|
+
def _flush_transcript(self):
|
|
234
|
+
# Send any buffered agentscope transcript to STDERR (redacted). Keeping it
|
|
235
|
+
# off stdout means the runner's stdout channel carries ONLY `MELASSIST `
|
|
236
|
+
# protocol frames: a transcript print can never glue onto a frame and be
|
|
237
|
+
# dropped (the missing-delta / garbled-fragment bug), and frame parsing on
|
|
238
|
+
# the runner stays cheap — no multi-KB base64 transcript lines flooding its
|
|
239
|
+
# event loop (which was spiking lag and dropping the socket heartbeat).
|
|
240
|
+
if self._buf:
|
|
241
|
+
try:
|
|
242
|
+
sys.stderr.write(self._scrub(self._buf))
|
|
243
|
+
except Exception:
|
|
244
|
+
pass
|
|
245
|
+
self._buf = ""
|
|
246
|
+
|
|
233
247
|
def write(self, s):
|
|
234
248
|
try:
|
|
249
|
+
# A protocol frame is emitted in ONE write() call, whole and
|
|
250
|
+
# newline-terminated (see _emit). Route it to the REAL stdout byte for
|
|
251
|
+
# byte (no scrub — frames are short structured JSON), after flushing any
|
|
252
|
+
# pending partial transcript so nothing can glue onto it.
|
|
253
|
+
if s.startswith(_EVENT_PREFIX):
|
|
254
|
+
self._flush_transcript()
|
|
255
|
+
self._real.write(s)
|
|
256
|
+
self._real.flush()
|
|
257
|
+
return len(s)
|
|
258
|
+
# Everything else = agentscope's verbatim message transcript → stderr,
|
|
259
|
+
# line-buffered + base64-redacted, so it never pollutes the protocol.
|
|
235
260
|
self._buf += s
|
|
236
261
|
while "\n" in self._buf:
|
|
237
262
|
line, self._buf = self._buf.split("\n", 1)
|
|
238
|
-
|
|
263
|
+
try:
|
|
264
|
+
sys.stderr.write(self._scrub(line) + "\n")
|
|
265
|
+
except Exception:
|
|
266
|
+
pass
|
|
239
267
|
return len(s)
|
|
240
268
|
except Exception:
|
|
241
269
|
return self._real.write(s)
|
|
242
270
|
|
|
243
271
|
def flush(self):
|
|
244
272
|
try:
|
|
245
|
-
|
|
246
|
-
self._real.write(self._scrub(self._buf))
|
|
247
|
-
self._buf = ""
|
|
273
|
+
self._flush_transcript()
|
|
248
274
|
self._real.flush()
|
|
249
275
|
except Exception:
|
|
250
276
|
pass
|
package/dist/connection.js
CHANGED
|
@@ -55,6 +55,11 @@ try {
|
|
|
55
55
|
}
|
|
56
56
|
catch { /* dev tree layout differs — non-fatal */ }
|
|
57
57
|
const activeAssistants = new Map();
|
|
58
|
+
// Line prefix the assistant host uses to frame protocol events on stdout
|
|
59
|
+
// (mirror of assistantHost.py `_EVENT_PREFIX`). Everything else on stdout is
|
|
60
|
+
// raw agentscope transcript (now routed to stderr host-side, but we still parse
|
|
61
|
+
// defensively in case a frame arrives glued to a raw print).
|
|
62
|
+
const _EVENT_PREFIX = "MELASSIST ";
|
|
58
63
|
// Warm pool (size 1): a fully-booted host held in reserve, ready to be ADOPTED
|
|
59
64
|
// by the next new session whose config+generation match — skipping the ~7s cold
|
|
60
65
|
// boot (import + build_agent). Pure optimization: any mismatch or unhealthy
|
|
@@ -1325,9 +1330,17 @@ export async function connect(opts) {
|
|
|
1325
1330
|
const line = session.stdoutBuf.slice(0, idx);
|
|
1326
1331
|
session.stdoutBuf = session.stdoutBuf.slice(idx + 1);
|
|
1327
1332
|
const t = line.trim();
|
|
1328
|
-
|
|
1333
|
+
// A protocol frame can arrive GLUED to a preceding raw print that had no
|
|
1334
|
+
// trailing newline (agentscope prints the agent transcript to the same
|
|
1335
|
+
// stdout), e.g. `Assistant: GotMELASSIST {"kind":"delta",…}`. Find the
|
|
1336
|
+
// prefix ANYWHERE in the line, not just at the start, or that delta is
|
|
1337
|
+
// dropped as raw output (the missing-deltas / garbled-fragment bug).
|
|
1338
|
+
const mi = t.indexOf(_EVENT_PREFIX);
|
|
1339
|
+
if (mi >= 0) {
|
|
1340
|
+
if (mi > 0 && opts.verbose)
|
|
1341
|
+
console.log(chalk.gray(` [assistant-stdout] ${t.slice(0, mi)}`));
|
|
1329
1342
|
try {
|
|
1330
|
-
const parsed = JSON.parse(t.slice(
|
|
1343
|
+
const parsed = JSON.parse(t.slice(mi + _EVENT_PREFIX.length));
|
|
1331
1344
|
if (typeof parsed?.memoryWatermark === "number")
|
|
1332
1345
|
session.memoryWatermark = parsed.memoryWatermark;
|
|
1333
1346
|
if (parsed?.kind === "ready") {
|
|
@@ -1343,7 +1356,12 @@ export async function connect(opts) {
|
|
|
1343
1356
|
if (session.forward)
|
|
1344
1357
|
socket.emit("runner:assistant_event", { sessionId: session.sid, ...parsed });
|
|
1345
1358
|
}
|
|
1346
|
-
catch {
|
|
1359
|
+
catch {
|
|
1360
|
+
// The literal "MELASSIST " appeared inside raw text (not a real frame)
|
|
1361
|
+
// — the JSON tail won't parse; treat the whole line as raw output.
|
|
1362
|
+
if (t && opts.verbose)
|
|
1363
|
+
console.log(chalk.gray(` [assistant-stdout] ${t}`));
|
|
1364
|
+
}
|
|
1347
1365
|
}
|
|
1348
1366
|
else if (t && opts.verbose) {
|
|
1349
1367
|
console.log(chalk.gray(` [assistant-stdout] ${t}`));
|