@melaya/runner 1.1.48 → 1.1.49

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.
Files changed (2) hide show
  1. package/dist/connection.js +23 -4
  2. package/package.json +1 -1
@@ -77,6 +77,11 @@ let warmSpareSeed = null;
77
77
  // SYNCHRONOUSLY here closes that window.
78
78
  const bootingAssistants = new Set();
79
79
  const ASSISTANT_IDLE_MS = 15 * 60 * 1000;
80
+ // OOM backstop for the assistant stdout line-framer: if a single unterminated
81
+ // line (no newline) grows past this, flush + reset rather than let `+=` build an
82
+ // unbounded string. Generous — legitimate frames (tool results, transcripts) are
83
+ // far smaller; a line this large would exceed the socket buffer anyway.
84
+ const ASSISTANT_STDOUT_MAX_CARRY = 64 * 1024 * 1024;
80
85
  export async function connect(opts) {
81
86
  const spinner = ora("Connecting to Melaya...").start();
82
87
  // Connect to the /api/v1/runner NAMESPACE on the existing Socket.IO server.
@@ -1324,11 +1329,25 @@ export async function connect(opts) {
1324
1329
  console.log(chalk.hex("#7c6ff0")(` ◆ Assistant ${a.forward ? "session" : "spare"} ${a.sid.slice(0, 12)}… (${a.providerLabel})`));
1325
1330
  proc.stdout?.on("data", (data) => {
1326
1331
  session.lastActivity = Date.now();
1332
+ // O(n) line framing: accumulate, split ONCE, carry the trailing partial.
1333
+ // The old `buf += …; while(indexOf/slice)` loop re-sliced the WHOLE buffer
1334
+ // per line — O(n²) on a big burst (a large tool result / browser tree in
1335
+ // the transcript). That blocked the Node event loop long enough that
1336
+ // Socket.IO couldn't service the connection (send the engine.io pong), so
1337
+ // the server's pingTimeout dropped the socket MID-TURN — the "heartbeat
1338
+ // stops while working" failure. Keeping this O(n) means the heartbeat and
1339
+ // pong keep flowing no matter how much the host streams.
1327
1340
  session.stdoutBuf += data.toString();
1328
- let idx;
1329
- while ((idx = session.stdoutBuf.indexOf("\n")) >= 0) {
1330
- const line = session.stdoutBuf.slice(0, idx);
1331
- session.stdoutBuf = session.stdoutBuf.slice(idx + 1);
1341
+ const parts = session.stdoutBuf.split("\n");
1342
+ session.stdoutBuf = parts.pop() ?? "";
1343
+ // OOM backstop: an unterminated line that keeps growing across chunks would
1344
+ // make the `+=` above quadratic again. Flush it as its own line and reset.
1345
+ if (session.stdoutBuf.length > ASSISTANT_STDOUT_MAX_CARRY) {
1346
+ parts.push(session.stdoutBuf);
1347
+ session.stdoutBuf = "";
1348
+ console.log(chalk.yellow(` [assistant-stdout] flushed oversized line (${(ASSISTANT_STDOUT_MAX_CARRY / 1048576) | 0}MB+) to protect the event loop`));
1349
+ }
1350
+ for (const line of parts) {
1332
1351
  const t = line.trim();
1333
1352
  // A protocol frame can arrive GLUED to a preceding raw print that had no
1334
1353
  // trailing newline (agentscope prints the agent transcript to the same
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melaya/runner",
3
- "version": "1.1.48",
3
+ "version": "1.1.49",
4
4
  "description": "Run Melaya AI pipelines locally with your own LM Studio or Ollama models",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,