chatroom-cli 1.55.0 → 1.55.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/dist/index.js CHANGED
@@ -26374,12 +26374,7 @@ var init_pi_agent_service = __esm(() => {
26374
26374
  });
26375
26375
  }
26376
26376
  spawnPiRpcProcess(args2) {
26377
- const rpcArgs = [
26378
- "--mode",
26379
- "rpc",
26380
- "--session-dir",
26381
- getPiSessionDir(args2.workingDir)
26382
- ];
26377
+ const rpcArgs = ["--mode", "rpc", "--session-dir", getPiSessionDir(args2.workingDir)];
26383
26378
  if (args2.sessionId) {
26384
26379
  rpcArgs.push("--session", args2.sessionId);
26385
26380
  }
@@ -26441,6 +26436,9 @@ var init_pi_agent_service = __esm(() => {
26441
26436
  wireRpcProcess(args2) {
26442
26437
  const { childProcess, context: context4, workingDir, model, harnessSessionId } = args2;
26443
26438
  const pid = childProcess.pid;
26439
+ if (pid == null) {
26440
+ throw new Error("Pi RPC process has no PID");
26441
+ }
26444
26442
  this.childProcesses.set(pid, childProcess);
26445
26443
  this.trackedSessions.set(pid, { harnessSessionId, workingDir, model });
26446
26444
  const entry = this.registerProcess(pid, context4);
@@ -26448,7 +26446,12 @@ var init_pi_agent_service = __esm(() => {
26448
26446
  const chatroomSuffix = context4.chatroomId ? `@${context4.chatroomId.slice(-6)}` : "";
26449
26447
  const logPrefix = `[pi:${roleTag}${chatroomSuffix}`;
26450
26448
  const outputCallbacks = [];
26449
+ const logLineCallbacks = [];
26451
26450
  const agentEndCallbacks = [];
26451
+ const emitLogLine = (line) => {
26452
+ for (const cb of logLineCallbacks)
26453
+ cb(line);
26454
+ };
26452
26455
  const onExit4 = (cb) => {
26453
26456
  childProcess.on("exit", (code2, signal) => {
26454
26457
  this.childProcesses.delete(pid);
@@ -26463,6 +26466,9 @@ var init_pi_agent_service = __esm(() => {
26463
26466
  const onAgentEnd = (cb) => {
26464
26467
  agentEndCallbacks.push(cb);
26465
26468
  };
26469
+ const onLogLine = (cb) => {
26470
+ logLineCallbacks.push(cb);
26471
+ };
26466
26472
  const baseResult = {
26467
26473
  pid,
26468
26474
  harnessSessionId,
@@ -26471,43 +26477,54 @@ var init_pi_agent_service = __esm(() => {
26471
26477
  ...model ? { model } : {}
26472
26478
  },
26473
26479
  onExit: onExit4,
26474
- onOutput
26480
+ onOutput,
26481
+ onLogLine
26482
+ };
26483
+ const writeFormattedLogLine = (formatted) => {
26484
+ process.stdout.write(`${formatted}
26485
+ `);
26486
+ emitLogLine(formatted);
26487
+ };
26488
+ const onStderrData = (chunk2) => {
26489
+ entry.lastOutputAt = Date.now();
26490
+ for (const cb of outputCallbacks)
26491
+ cb();
26492
+ emitLogLine(chunk2.toString());
26493
+ };
26494
+ const attachStderr = (stream) => {
26495
+ if (!stream)
26496
+ return;
26497
+ stream.pipe(process.stderr, { end: false });
26498
+ stream.on("data", onStderrData);
26475
26499
  };
26476
26500
  if (!childProcess.stdout) {
26477
- if (childProcess.stderr) {
26478
- childProcess.stderr.pipe(process.stderr, { end: false });
26479
- childProcess.stderr.on("data", () => {
26480
- entry.lastOutputAt = Date.now();
26481
- for (const cb of outputCallbacks)
26482
- cb();
26483
- });
26484
- }
26501
+ attachStderr(childProcess.stderr);
26485
26502
  return baseResult;
26486
26503
  }
26487
26504
  const reader = args2.reader ?? new PiRpcReader(childProcess.stdout);
26488
26505
  let textBuffer = "";
26489
26506
  let thinkingBuffer = "";
26490
- const flushText = () => {
26491
- if (!textBuffer)
26507
+ const flushBufferedLines = (buffer, kind, clear) => {
26508
+ if (!buffer)
26492
26509
  return;
26493
- for (const line of textBuffer.split(`
26510
+ for (const line of buffer.split(`
26494
26511
  `)) {
26495
- if (line)
26496
- process.stdout.write(`${logPrefix} text] ${line}
26497
- `);
26512
+ if (line) {
26513
+ writeFormattedLogLine(`${logPrefix} ${kind}] ${line}`);
26514
+ }
26498
26515
  }
26499
- textBuffer = "";
26516
+ clear();
26500
26517
  };
26501
- const flushThinking = () => {
26502
- if (!thinkingBuffer)
26503
- return;
26504
- for (const line of thinkingBuffer.split(`
26505
- `)) {
26506
- if (line)
26507
- process.stdout.write(`${logPrefix} thinking] ${line}
26508
- `);
26509
- }
26518
+ const flushText = () => flushBufferedLines(textBuffer, "text", () => {
26519
+ textBuffer = "";
26520
+ });
26521
+ const flushThinking = () => flushBufferedLines(thinkingBuffer, "thinking", () => {
26510
26522
  thinkingBuffer = "";
26523
+ });
26524
+ const notifyOutput = () => {
26525
+ entry.lastOutputAt = Date.now();
26526
+ for (const cb of outputCallbacks)
26527
+ cb();
26511
26528
  };
26512
26529
  reader.onTextDelta((delta) => {
26513
26530
  flushThinking();
@@ -26515,9 +26532,7 @@ var init_pi_agent_service = __esm(() => {
26515
26532
  if (textBuffer.includes(`
26516
26533
  `))
26517
26534
  flushText();
26518
- entry.lastOutputAt = Date.now();
26519
- for (const cb of outputCallbacks)
26520
- cb();
26535
+ notifyOutput();
26521
26536
  });
26522
26537
  reader.onThinkingDelta((delta) => {
26523
26538
  flushText();
@@ -26525,14 +26540,10 @@ var init_pi_agent_service = __esm(() => {
26525
26540
  if (thinkingBuffer.includes(`
26526
26541
  `))
26527
26542
  flushThinking();
26528
- entry.lastOutputAt = Date.now();
26529
- for (const cb of outputCallbacks)
26530
- cb();
26543
+ notifyOutput();
26531
26544
  });
26532
26545
  reader.onAnyEvent(() => {
26533
- entry.lastOutputAt = Date.now();
26534
- for (const cb of outputCallbacks)
26535
- cb();
26546
+ notifyOutput();
26536
26547
  });
26537
26548
  reader.onAgentEnd(() => {
26538
26549
  flushText();
@@ -26546,22 +26557,13 @@ var init_pi_agent_service = __esm(() => {
26546
26557
  flushText();
26547
26558
  flushThinking();
26548
26559
  const argsStr = toolArgs != null ? ` args: ${JSON.stringify(toolArgs)}` : "";
26549
- process.stdout.write(`${logPrefix} tool: ${name}${argsStr}]
26550
- `);
26560
+ writeFormattedLogLine(`${logPrefix} tool: ${name}${argsStr}]`);
26551
26561
  });
26552
26562
  reader.onToolResult((name, result) => {
26553
26563
  const resultStr = typeof result === "string" ? result : JSON.stringify(result);
26554
- process.stdout.write(`${logPrefix} tool_result: ${name} result: ${resultStr}]
26555
- `);
26564
+ writeFormattedLogLine(`${logPrefix} tool_result: ${name} result: ${resultStr}]`);
26556
26565
  });
26557
- if (childProcess.stderr) {
26558
- childProcess.stderr.pipe(process.stderr, { end: false });
26559
- childProcess.stderr.on("data", () => {
26560
- entry.lastOutputAt = Date.now();
26561
- for (const cb of outputCallbacks)
26562
- cb();
26563
- });
26564
- }
26566
+ attachStderr(childProcess.stderr);
26565
26567
  return {
26566
26568
  ...baseResult,
26567
26569
  onAgentEnd
@@ -27629,12 +27631,14 @@ function closeCursorAgentOnFailure(agent, session, exitCode, force = false) {
27629
27631
  // src/infrastructure/services/remote-agents/cursor-sdk/cursor-sdk-stream-adapter.ts
27630
27632
  class CursorSdkStreamAdapter {
27631
27633
  logPrefix;
27634
+ emitLogLine;
27632
27635
  agentEndCallbacks = [];
27633
27636
  outputCallbacks = [];
27634
27637
  agentEndEmitted = false;
27635
27638
  textBuffer = "";
27636
- constructor(logPrefix) {
27639
+ constructor(logPrefix, emitLogLine) {
27637
27640
  this.logPrefix = logPrefix;
27641
+ this.emitLogLine = emitLogLine;
27638
27642
  }
27639
27643
  onAgentEnd(cb) {
27640
27644
  this.agentEndCallbacks.push(cb);
@@ -27650,21 +27654,17 @@ class CursorSdkStreamAdapter {
27650
27654
  break;
27651
27655
  case "tool_call":
27652
27656
  this.flushText();
27653
- process.stdout.write(`${this.logPrefix} tool: ${message.call_id} ${message.name} ${JSON.stringify({ status: message.status, args: message.args })}]
27654
- `);
27657
+ this.writeLine(`${this.logPrefix} tool: ${message.call_id} ${message.name} ${JSON.stringify({ status: message.status, args: message.args })}]`);
27655
27658
  break;
27656
27659
  case "status":
27657
- process.stdout.write(`${this.logPrefix} status: ${message.status}]
27658
- `);
27660
+ this.writeLine(`${this.logPrefix} status: ${message.status}]`);
27659
27661
  break;
27660
27662
  case "thinking":
27661
- process.stdout.write(`${this.logPrefix} thinking] ${message.text}
27662
- `);
27663
+ this.writeLine(`${this.logPrefix} thinking] ${message.text}`);
27663
27664
  break;
27664
27665
  case "system":
27665
27666
  if (message.subtype === "init") {
27666
- process.stdout.write(`${this.logPrefix} system: init]
27667
- `);
27667
+ this.writeLine(`${this.logPrefix} system: init]`);
27668
27668
  }
27669
27669
  break;
27670
27670
  default:
@@ -27695,8 +27695,7 @@ class CursorSdkStreamAdapter {
27695
27695
  for (const line of this.textBuffer.split(`
27696
27696
  `)) {
27697
27697
  if (line)
27698
- process.stdout.write(`${this.logPrefix} text] ${line}
27699
- `);
27698
+ this.writeLine(`${this.logPrefix} text] ${line}`);
27700
27699
  }
27701
27700
  this.textBuffer = "";
27702
27701
  }
@@ -27705,11 +27704,15 @@ class CursorSdkStreamAdapter {
27705
27704
  return;
27706
27705
  this.agentEndEmitted = true;
27707
27706
  this.flushText();
27708
- process.stdout.write(`${this.logPrefix} agent_end]
27709
- `);
27707
+ this.writeLine(`${this.logPrefix} agent_end]`);
27710
27708
  for (const cb of this.agentEndCallbacks)
27711
27709
  cb();
27712
27710
  }
27711
+ writeLine(formatted) {
27712
+ process.stdout.write(`${formatted}
27713
+ `);
27714
+ this.emitLogLine?.(formatted);
27715
+ }
27713
27716
  notifyOutput() {
27714
27717
  for (const cb of this.outputCallbacks)
27715
27718
  cb();
@@ -27782,10 +27785,11 @@ function buildLogPrefix(context4) {
27782
27785
  function resolveModelId(model) {
27783
27786
  return model ? resolveCursorSdkModel(model) : DEFAULT_MODEL;
27784
27787
  }
27785
- function writeSpawnError(logPrefix, err) {
27786
- const reason = formatCursorSdkLoadError(err);
27787
- process.stderr.write(`${logPrefix} spawn-error] ${reason}
27788
+ function writeSpawnError(logPrefix, err, emitLogLine) {
27789
+ const line = `${logPrefix} spawn-error] ${formatCursorSdkLoadError(err)}`;
27790
+ process.stderr.write(`${line}
27788
27791
  `);
27792
+ emitLogLine?.(line);
27789
27793
  console.error(`[${new Date().toISOString()}] ${logPrefix} spawn-error]`, err);
27790
27794
  }
27791
27795
  var NO_SUBAGENT_DIRECTIVE2 = "NEVER spawn subagents. Follow the chatroom instructions strictly.", _sdkCache, _sdkLoadError, CURSOR_SDK_COMMAND = "cursor-sdk", DEFAULT_MODEL = "composer-2.5", AGENT_CREATE_TIMEOUT_MS = 60000, SEND_TIMEOUT_MS = 60000, RUN_WAIT_TIMEOUT_MS = 3600000, MODELS_LIST_TIMEOUT_MS = 1e4, RUN_CANCEL_TIMEOUT_MS = 5000, cachedSdkPackageVersion, CursorSdkAgentService;
@@ -27973,6 +27977,11 @@ ${options.prompt}`;
27973
27977
  const exitCallbacks = [];
27974
27978
  const outputCallbacks = [];
27975
27979
  const agentEndCallbacks = [];
27980
+ const logLineCallbacks = [];
27981
+ const emitLogLine = (line) => {
27982
+ for (const cb of logLineCallbacks)
27983
+ cb(line);
27984
+ };
27976
27985
  const finishExit = (code2, signal) => {
27977
27986
  this.sessions.delete(pid);
27978
27987
  this.deleteProcess(pid);
@@ -27991,7 +28000,8 @@ ${options.prompt}`;
27991
28000
  forceFirstTurn,
27992
28001
  finishExit,
27993
28002
  outputCallbacks,
27994
- agentEndCallbacks
28003
+ agentEndCallbacks,
28004
+ emitLogLine
27995
28005
  });
27996
28006
  return {
27997
28007
  pid,
@@ -28008,6 +28018,9 @@ ${options.prompt}`;
28008
28018
  },
28009
28019
  onAgentEnd: (cb) => {
28010
28020
  agentEndCallbacks.push(cb);
28021
+ },
28022
+ onLogLine: (cb) => {
28023
+ logLineCallbacks.push(cb);
28011
28024
  }
28012
28025
  };
28013
28026
  }
@@ -28021,7 +28034,8 @@ ${options.prompt}`;
28021
28034
  finishExit,
28022
28035
  entry,
28023
28036
  outputCallbacks,
28024
- agentEndCallbacks
28037
+ agentEndCallbacks,
28038
+ emitLogLine
28025
28039
  } = args2;
28026
28040
  let exited = false;
28027
28041
  (async () => {
@@ -28038,7 +28052,7 @@ ${options.prompt}`;
28038
28052
  }), SEND_TIMEOUT_MS, "agent.send");
28039
28053
  session.run = run3;
28040
28054
  isFirstTurn = false;
28041
- const adapter = new CursorSdkStreamAdapter(logPrefix);
28055
+ const adapter = new CursorSdkStreamAdapter(logPrefix, emitLogLine);
28042
28056
  adapter.onOutput(() => {
28043
28057
  entry.lastOutputAt = Date.now();
28044
28058
  for (const cb of outputCallbacks)
@@ -28056,7 +28070,7 @@ ${options.prompt}`;
28056
28070
  }
28057
28071
  } catch (streamErr) {
28058
28072
  exitCode = 1;
28059
- writeSpawnError(logPrefix, streamErr);
28073
+ writeSpawnError(logPrefix, streamErr, emitLogLine);
28060
28074
  break;
28061
28075
  }
28062
28076
  if (session.aborted) {
@@ -28069,14 +28083,16 @@ ${options.prompt}`;
28069
28083
  result = await withTimeout(run3.wait(), RUN_WAIT_TIMEOUT_MS, "run.wait");
28070
28084
  } catch (waitErr) {
28071
28085
  exitCode = 1;
28072
- writeSpawnError(logPrefix, waitErr);
28086
+ writeSpawnError(logPrefix, waitErr, emitLogLine);
28073
28087
  break;
28074
28088
  }
28075
28089
  adapter.flushPendingOutput();
28076
28090
  if (result.status === "error") {
28077
28091
  exitCode = 2;
28078
- process.stderr.write(`${logPrefix} run-error] run ${result.id} failed
28092
+ const runErrorLine = `${logPrefix} run-error] run ${result.id} failed`;
28093
+ process.stderr.write(`${runErrorLine}
28079
28094
  `);
28095
+ emitLogLine(runErrorLine);
28080
28096
  break;
28081
28097
  }
28082
28098
  const resumePromise = waitForResumeOrAbort(session);
@@ -28092,13 +28108,13 @@ ${options.prompt}`;
28092
28108
  nextPrompt = resumePrompt;
28093
28109
  } catch (turnErr) {
28094
28110
  exitCode = 1;
28095
- writeSpawnError(logPrefix, turnErr);
28111
+ writeSpawnError(logPrefix, turnErr, emitLogLine);
28096
28112
  break;
28097
28113
  }
28098
28114
  }
28099
28115
  } catch (err) {
28100
28116
  exitCode = 1;
28101
- writeSpawnError(logPrefix, err);
28117
+ writeSpawnError(logPrefix, err, emitLogLine);
28102
28118
  } finally {
28103
28119
  if (exited)
28104
28120
  return;
@@ -28110,7 +28126,7 @@ ${options.prompt}`;
28110
28126
  finishExit(exitCode, exitSignal);
28111
28127
  }
28112
28128
  })().catch((err) => {
28113
- writeSpawnError(logPrefix, err);
28129
+ writeSpawnError(logPrefix, err, emitLogLine);
28114
28130
  if (exited)
28115
28131
  return;
28116
28132
  exited = true;
@@ -30181,6 +30197,12 @@ function formatLogLine(options, kind, payload) {
30181
30197
  const ts = options.now ? options.now() : new Date().toISOString();
30182
30198
  return `[${ts}] role:${options.role} ${kind}]${payload ? ` ${payload}` : ""}`;
30183
30199
  }
30200
+ function writeLogLine(target, options, kind, payload) {
30201
+ const line = formatLogLine(options, kind, payload);
30202
+ target.write(`${line}
30203
+ `);
30204
+ options.onLogLine?.(line);
30205
+ }
30184
30206
  function isUsageLimitError(error) {
30185
30207
  if (!error || typeof error !== "object")
30186
30208
  return false;
@@ -30236,8 +30258,7 @@ function startSessionEventForwarder(client4, options) {
30236
30258
  continue;
30237
30259
  if (!sessionStarted) {
30238
30260
  sessionStarted = true;
30239
- target.write(formatLogLine(options, "session] Started", `role: ${options.role}`) + `
30240
- `);
30261
+ writeLogLine(target, options, "session] Started", `role: ${options.role}`);
30241
30262
  }
30242
30263
  switch (event.type) {
30243
30264
  case "message.part.updated": {
@@ -30246,14 +30267,12 @@ function startSessionEventForwarder(client4, options) {
30246
30267
  if (part?.type === "text") {
30247
30268
  const chunk2 = props?.delta !== undefined && props.delta !== "" ? props.delta : part.text;
30248
30269
  if (chunk2) {
30249
- target.write(formatLogLine(options, "text", chunk2) + `
30250
- `);
30270
+ writeLogLine(target, options, "text", chunk2);
30251
30271
  }
30252
30272
  } else if (part?.type === "reasoning") {
30253
30273
  const chunk2 = props?.delta !== undefined && props.delta !== "" ? props.delta : part.text;
30254
30274
  if (chunk2) {
30255
- target.write(formatLogLine(options, "thinking", chunk2) + `
30256
- `);
30275
+ writeLogLine(target, options, "thinking", chunk2);
30257
30276
  }
30258
30277
  } else if (part?.type === "tool" && part.tool) {
30259
30278
  let appendInput = function(base, input, tool) {
@@ -30280,8 +30299,7 @@ function startSessionEventForwarder(client4, options) {
30280
30299
  const seenKey = `${callID}:${state}`;
30281
30300
  if (!seenToolStates.has(seenKey)) {
30282
30301
  seenToolStates.set(seenKey, payload);
30283
- target.write(formatLogLine(options, "tool: " + part.tool, payload) + `
30284
- `);
30302
+ writeLogLine(target, options, "tool: " + part.tool, payload);
30285
30303
  }
30286
30304
  if (state === "completed" || state === "error") {
30287
30305
  seenToolStates.delete(seenKey);
@@ -30293,20 +30311,17 @@ function startSessionEventForwarder(client4, options) {
30293
30311
  const props = event.properties;
30294
30312
  const kind = props?.action ?? props?.kind;
30295
30313
  const filePayload = kind ? `${props?.file} (${kind})` : props?.file;
30296
- target.write(formatLogLine(options, "file", filePayload) + `
30297
- `);
30314
+ writeLogLine(target, options, "file", filePayload);
30298
30315
  break;
30299
30316
  }
30300
30317
  case "session.idle": {
30301
- target.write(formatLogLine(options, "agent_end") + `
30302
- `);
30318
+ writeLogLine(target, options, "agent_end");
30303
30319
  for (const cb of agentEndCallbacks)
30304
30320
  cb();
30305
30321
  break;
30306
30322
  }
30307
30323
  case "session.compacted": {
30308
- target.write(formatLogLine(options, "compacted") + `
30309
- `);
30324
+ writeLogLine(target, options, "compacted");
30310
30325
  break;
30311
30326
  }
30312
30327
  case "session.status": {
@@ -30314,8 +30329,7 @@ function startSessionEventForwarder(client4, options) {
30314
30329
  const currentStatus = props?.status?.type;
30315
30330
  if (currentStatus !== lastStatus) {
30316
30331
  lastStatus = currentStatus;
30317
- target.write(formatLogLine(options, "status", currentStatus) + `
30318
- `);
30332
+ writeLogLine(target, options, "status", currentStatus);
30319
30333
  }
30320
30334
  break;
30321
30335
  }
@@ -30329,11 +30343,9 @@ function startSessionEventForwarder(client4, options) {
30329
30343
  } else if (props?.command) {
30330
30344
  payload += ` [command: ${props.command}]`;
30331
30345
  }
30332
- errorTarget.write(formatLogLine(options, "error", payload) + `
30333
- `);
30346
+ writeLogLine(errorTarget, options, "error", payload);
30334
30347
  if (isUsageLimitError(err)) {
30335
- target.write(formatLogLine(options, "agent_end", "reason: usage_limit_reached") + `
30336
- `);
30348
+ writeLogLine(target, options, "agent_end", "reason: usage_limit_reached");
30337
30349
  for (const cb of agentEndCallbacks)
30338
30350
  cb();
30339
30351
  }
@@ -30345,8 +30357,7 @@ function startSessionEventForwarder(client4, options) {
30345
30357
  }
30346
30358
  } catch (err) {
30347
30359
  const message = err instanceof Error ? err.message : String(err);
30348
- errorTarget.write(formatLogLine(options, "error", message) + `
30349
- `);
30360
+ writeLogLine(errorTarget, options, "error", message);
30350
30361
  } finally {
30351
30362
  doneResolve();
30352
30363
  }
@@ -30502,8 +30513,12 @@ var init_opencode_sdk_agent_service = __esm(() => {
30502
30513
  baseUrl,
30503
30514
  agentName,
30504
30515
  model,
30505
- workingDir
30516
+ logLineCallbacks
30506
30517
  } = args2;
30518
+ const emitLogLine = (line) => {
30519
+ for (const lineCb of logLineCallbacks)
30520
+ lineCb(line);
30521
+ };
30507
30522
  const meta = {
30508
30523
  sessionId,
30509
30524
  machineId: context4.machineId,
@@ -30530,10 +30545,11 @@ var init_opencode_sdk_agent_service = __esm(() => {
30530
30545
  });
30531
30546
  }
30532
30547
  if (childProcess.stderr) {
30533
- childProcess.stderr.on("data", () => {
30548
+ childProcess.stderr.on("data", (chunk2) => {
30534
30549
  entry.lastOutputAt = Date.now();
30535
30550
  for (const cb of outputCallbacks)
30536
30551
  cb();
30552
+ emitLogLine(chunk2.toString());
30537
30553
  });
30538
30554
  }
30539
30555
  return {
@@ -30560,6 +30576,19 @@ var init_opencode_sdk_agent_service = __esm(() => {
30560
30576
  },
30561
30577
  onAgentEnd: (cb) => {
30562
30578
  forwarder?.onAgentEnd(cb);
30579
+ },
30580
+ onLogLine: (cb) => {
30581
+ logLineCallbacks.push(cb);
30582
+ }
30583
+ };
30584
+ }
30585
+ createLogLineEmitter() {
30586
+ const logLineCallbacks = [];
30587
+ return {
30588
+ logLineCallbacks,
30589
+ emitLogLine: (line) => {
30590
+ for (const cb of logLineCallbacks)
30591
+ cb(line);
30563
30592
  }
30564
30593
  };
30565
30594
  }
@@ -30571,6 +30600,9 @@ var init_opencode_sdk_agent_service = __esm(() => {
30571
30600
  const workingDir = session.workingDir;
30572
30601
  const childProcess = this.spawnServeProcess(workingDir);
30573
30602
  const pid = childProcess.pid;
30603
+ if (pid == null) {
30604
+ throw new Error("Failed to spawn opencode serve process");
30605
+ }
30574
30606
  const baseUrl = await waitForListeningUrl(childProcess, {
30575
30607
  timeoutMs: SERVE_STARTUP_TIMEOUT_MS
30576
30608
  }).catch((err) => {
@@ -30579,6 +30611,7 @@ var init_opencode_sdk_agent_service = __esm(() => {
30579
30611
  });
30580
30612
  const client4 = createOpencodeClient({ baseUrl });
30581
30613
  let forwarder;
30614
+ const { logLineCallbacks, emitLogLine } = this.createLogLineEmitter();
30582
30615
  try {
30583
30616
  const sessionInfo = await withTimeout2(client4.session.get({ path: { id: sessionId } }), SESSION_GET_TIMEOUT_MS, "session.get");
30584
30617
  if (!sessionInfo.data?.id) {
@@ -30586,7 +30619,8 @@ var init_opencode_sdk_agent_service = __esm(() => {
30586
30619
  }
30587
30620
  forwarder = startSessionEventForwarder(client4, {
30588
30621
  sessionId,
30589
- role: context4.role
30622
+ role: context4.role,
30623
+ onLogLine: emitLogLine
30590
30624
  });
30591
30625
  const agentsResponse = await withTimeout2(client4.app.agents(), AGENTS_LIST_TIMEOUT_MS, "app.agents");
30592
30626
  const availableAgents = agentsResponse.data ?? [];
@@ -30624,13 +30658,17 @@ var init_opencode_sdk_agent_service = __esm(() => {
30624
30658
  baseUrl,
30625
30659
  agentName,
30626
30660
  model: modelForSession,
30627
- workingDir
30661
+ workingDir,
30662
+ logLineCallbacks
30628
30663
  });
30629
30664
  }
30630
30665
  async spawn(options) {
30631
30666
  const { prompt, systemPrompt, model, context: context4 } = options;
30632
30667
  const childProcess = this.spawnServeProcess(options.workingDir);
30633
30668
  const pid = childProcess.pid;
30669
+ if (pid == null) {
30670
+ throw new Error("Failed to spawn opencode serve process");
30671
+ }
30634
30672
  const baseUrl = await waitForListeningUrl(childProcess, {
30635
30673
  timeoutMs: SERVE_STARTUP_TIMEOUT_MS
30636
30674
  }).catch((err) => {
@@ -30643,6 +30681,7 @@ var init_opencode_sdk_agent_service = __esm(() => {
30643
30681
  let sessionId;
30644
30682
  let forwarder;
30645
30683
  let agentName;
30684
+ const { logLineCallbacks, emitLogLine } = this.createLogLineEmitter();
30646
30685
  try {
30647
30686
  const sessionCreateResult = await withTimeout2(client4.session.create({ body: {} }), SESSION_CREATE_TIMEOUT_MS, "session.create");
30648
30687
  if (!sessionCreateResult.data?.id) {
@@ -30651,7 +30690,8 @@ var init_opencode_sdk_agent_service = __esm(() => {
30651
30690
  sessionId = sessionCreateResult.data.id;
30652
30691
  forwarder = startSessionEventForwarder(client4, {
30653
30692
  sessionId,
30654
- role: context4.role
30693
+ role: context4.role,
30694
+ onLogLine: emitLogLine
30655
30695
  });
30656
30696
  const agentsResponse = await withTimeout2(client4.app.agents(), AGENTS_LIST_TIMEOUT_MS, "app.agents");
30657
30697
  const availableAgents = agentsResponse.data ?? [];
@@ -30683,6 +30723,9 @@ var init_opencode_sdk_agent_service = __esm(() => {
30683
30723
  this.sessionStore.remove(sessionId);
30684
30724
  throw err;
30685
30725
  }
30726
+ if (!sessionId || !agentName) {
30727
+ throw new Error("OpenCode session was not initialized");
30728
+ }
30686
30729
  return this.registerRunningSession({
30687
30730
  childProcess,
30688
30731
  pid,
@@ -30692,7 +30735,8 @@ var init_opencode_sdk_agent_service = __esm(() => {
30692
30735
  baseUrl,
30693
30736
  agentName,
30694
30737
  model,
30695
- workingDir: options.workingDir
30738
+ workingDir: options.workingDir,
30739
+ logLineCallbacks
30696
30740
  });
30697
30741
  }
30698
30742
  getHarnessReconnectContext(pid) {
@@ -31402,11 +31446,15 @@ This ensures you never forget to pass control back — just copy the command fro
31402
31446
 
31403
31447
  ### Command Reference
31404
31448
 
31449
+ **Production (default):** omit \`CHATROOM_CONVEX_URL\` — the CLI connects to chatroom cloud automatically. Do not look up the Convex URL from \`.env\`.
31450
+
31405
31451
  | Action | Command |
31406
31452
  |--------|---------|
31407
- | Fetch next task | \`CHATROOM_CONVEX_URL=<endpoint> chatroom get-next-task --chatroom-id=<id> --role=<role>\` |
31408
- | Pass control | \`CHATROOM_CONVEX_URL=<endpoint> chatroom handoff --chatroom-id=<id> --role=<role> --next-role=<role>\` |
31409
- | Refresh system prompt (after compaction) | \`CHATROOM_CONVEX_URL=<endpoint> chatroom get-system-prompt --chatroom-id=<id> --role=<role>\` |
31453
+ | Fetch next task | \`chatroom get-next-task --chatroom-id=<id> --role=<role>\` |
31454
+ | Pass control | \`chatroom handoff --chatroom-id=<id> --role=<role> --next-role=<role>\` |
31455
+ | Refresh system prompt (after compaction) | \`chatroom get-system-prompt --chatroom-id=<id> --role=<role>\` |
31456
+
31457
+ **Non-production only** (local dev, preview): prefix commands with \`CHATROOM_CONVEX_URL=<convex-url> \` (e.g. \`CHATROOM_CONVEX_URL=http://127.0.0.1:3210 chatroom get-next-task ...\`).
31410
31458
 
31411
31459
  ### Context Recovery
31412
31460
 
@@ -80735,6 +80783,7 @@ var init_command_runner = __esm(() => {
80735
80783
  // src/events/lifecycle/on-daemon-shutdown.ts
80736
80784
  async function onDaemonShutdown(ctx) {
80737
80785
  await shutdownAllCommands(ctx);
80786
+ await ctx.deps.agentProcessManager.whenTurnEndsIdle();
80738
80787
  const activeAgents = ctx.deps.agentProcessManager.listActive();
80739
80788
  if (activeAgents.length > 0) {
80740
80789
  console.log(`[${formatTimestamp()}] Stopping ${activeAgents.length} agent(s)...`);
@@ -85370,66 +85419,6 @@ var init_crash_loop_tracker = __esm(() => {
85370
85419
  ];
85371
85420
  });
85372
85421
 
85373
- // src/infrastructure/deps/process.ts
85374
- function isProcessAlive(kill, pid) {
85375
- try {
85376
- kill(pid, 0);
85377
- return true;
85378
- } catch {
85379
- return false;
85380
- }
85381
- }
85382
-
85383
- // src/domain/agent-lifecycle/entities/stop-reason.ts
85384
- function resolveStopReason(code2, signal) {
85385
- if (signal !== null)
85386
- return "agent_process.signal";
85387
- if (code2 === 0)
85388
- return "agent_process.exited_clean";
85389
- return "agent_process.crashed";
85390
- }
85391
-
85392
- // src/domain/agent-lifecycle/policies/preserve-session.ts
85393
- function shouldRetainHarnessSessionForReconnect(reason) {
85394
- switch (reason) {
85395
- case "user.stop":
85396
- case "agent_process.exited_clean":
85397
- case "agent_process.signal":
85398
- case "agent_process.crashed":
85399
- return true;
85400
- default:
85401
- return false;
85402
- }
85403
- }
85404
- function shouldPreserveHarnessTeardown(reason, supportsSessionResume, hasHarnessSessionId) {
85405
- return hasHarnessSessionId && supportsSessionResume && shouldRetainHarnessSessionForReconnect(reason);
85406
- }
85407
-
85408
- // src/domain/agent-lifecycle/policies/decide-resume-path.ts
85409
- function decideResumePathOnRestart(input) {
85410
- if (!input.supportsSessionResume) {
85411
- return "cold";
85412
- }
85413
- if (input.wantResume && input.hasStoredSnapshot) {
85414
- return "daemon_memory";
85415
- }
85416
- return "cold";
85417
- }
85418
- function shouldAutoRestartAfterProcessExit(stopReason) {
85419
- switch (stopReason) {
85420
- case "user.stop":
85421
- case "platform.team_switch":
85422
- case "daemon.shutdown":
85423
- case "daemon.respawn":
85424
- return false;
85425
- default:
85426
- return true;
85427
- }
85428
- }
85429
-
85430
- // src/domain/agent-lifecycle/index.ts
85431
- var init_agent_lifecycle = () => {};
85432
-
85433
85422
  // ../../services/backend/src/domain/entities/harness/claude.config.ts
85434
85423
  var claudeCapabilities;
85435
85424
  var init_claude_config = __esm(() => {
@@ -85584,6 +85573,273 @@ var init_types = __esm(() => {
85584
85573
  };
85585
85574
  });
85586
85575
 
85576
+ // src/infrastructure/services/agent-process-manager/turn-completed-backend.ts
85577
+ function createTurnCompletedBackend(deps) {
85578
+ return {
85579
+ emitResumeStormAborted: (args2) => deps.backend.mutation(api.agentResumeStorm.emitResumeStormAborted, {
85580
+ sessionId: deps.sessionId,
85581
+ machineId: deps.machineId,
85582
+ ...args2
85583
+ }),
85584
+ emitSessionResumed: (args2) => deps.backend.mutation(api.machines.emitSessionResumed, {
85585
+ sessionId: deps.sessionId,
85586
+ machineId: deps.machineId,
85587
+ ...args2
85588
+ }),
85589
+ emitSessionResumeFailed: (args2) => deps.backend.mutation(api.machines.emitSessionResumeFailed, {
85590
+ sessionId: deps.sessionId,
85591
+ machineId: deps.machineId,
85592
+ ...args2
85593
+ })
85594
+ };
85595
+ }
85596
+ var init_turn_completed_backend = __esm(() => {
85597
+ init_api3();
85598
+ });
85599
+
85600
+ // src/infrastructure/services/agent-process-manager/turn-end-queue.ts
85601
+ class TurnEndQueue {
85602
+ tail = Promise.resolve();
85603
+ enqueue(work) {
85604
+ this.tail = this.tail.then(work).catch((err) => {
85605
+ console.log(`[TurnEndQueue] turn-end handler failed: ${err.message}`);
85606
+ });
85607
+ }
85608
+ async whenIdle() {
85609
+ await this.tail;
85610
+ }
85611
+ }
85612
+
85613
+ // src/domain/agent-lifecycle/entities/stop-reason.ts
85614
+ function resolveStopReason(code2, signal) {
85615
+ if (signal !== null)
85616
+ return "agent_process.signal";
85617
+ if (code2 === 0)
85618
+ return "agent_process.exited_clean";
85619
+ return "agent_process.crashed";
85620
+ }
85621
+
85622
+ // src/domain/agent-lifecycle/policies/preserve-session.ts
85623
+ function shouldRetainHarnessSessionForReconnect(reason) {
85624
+ switch (reason) {
85625
+ case "user.stop":
85626
+ case "agent_process.exited_clean":
85627
+ case "agent_process.signal":
85628
+ case "agent_process.crashed":
85629
+ return true;
85630
+ default:
85631
+ return false;
85632
+ }
85633
+ }
85634
+ function shouldPreserveHarnessTeardown(reason, supportsSessionResume, hasHarnessSessionId) {
85635
+ return hasHarnessSessionId && supportsSessionResume && shouldRetainHarnessSessionForReconnect(reason);
85636
+ }
85637
+
85638
+ // src/domain/agent-lifecycle/policies/decide-resume-path.ts
85639
+ function decideResumePathOnRestart(input) {
85640
+ if (!input.supportsSessionResume) {
85641
+ return "cold";
85642
+ }
85643
+ if (input.wantResume && input.hasStoredSnapshot) {
85644
+ return "daemon_memory";
85645
+ }
85646
+ return "cold";
85647
+ }
85648
+ function shouldAutoRestartAfterProcessExit(stopReason) {
85649
+ return !NO_AUTO_RESTART_STOP_REASONS.has(stopReason);
85650
+ }
85651
+ var NO_AUTO_RESTART_STOP_REASONS;
85652
+ var init_decide_resume_path = __esm(() => {
85653
+ NO_AUTO_RESTART_STOP_REASONS = new Set([
85654
+ "user.stop",
85655
+ "platform.team_switch",
85656
+ "platform.resume_storm",
85657
+ "daemon.shutdown",
85658
+ "daemon.respawn"
85659
+ ]);
85660
+ });
85661
+
85662
+ // src/domain/agent-lifecycle/index.ts
85663
+ var init_agent_lifecycle = __esm(() => {
85664
+ init_decide_resume_path();
85665
+ });
85666
+
85667
+ // src/domain/agent-lifecycle/policies/append-recent-log-line.ts
85668
+ function appendRecentLogLine(slot, line) {
85669
+ if (!slot.recentLogLines) {
85670
+ slot.recentLogLines = [];
85671
+ }
85672
+ slot.recentLogLines.push(line);
85673
+ if (slot.recentLogLines.length > RECENT_LOG_LINE_CAP) {
85674
+ slot.recentLogLines.shift();
85675
+ }
85676
+ }
85677
+ var RECENT_LOG_LINE_CAP = 100;
85678
+
85679
+ // src/domain/agent-lifecycle/policies/classify-resume-storm-reason.ts
85680
+ function classifyResumeStormReason(logLines) {
85681
+ const blob = logLines.join(`
85682
+ `);
85683
+ if (!blob.trim()) {
85684
+ return "unknown";
85685
+ }
85686
+ for (const rule of CLASSIFICATION_RULES) {
85687
+ if (rule.patterns.some((pattern) => pattern.test(blob))) {
85688
+ return rule.reason;
85689
+ }
85690
+ }
85691
+ return "unknown";
85692
+ }
85693
+ var CLASSIFICATION_RULES;
85694
+ var init_classify_resume_storm_reason = __esm(() => {
85695
+ CLASSIFICATION_RULES = [
85696
+ {
85697
+ reason: "rate_limit",
85698
+ patterns: [
85699
+ /\b429\b/,
85700
+ /rate.?limit/i,
85701
+ /too many requests/i,
85702
+ /quota exceeded/i,
85703
+ /usage.?limit/i,
85704
+ /tokens per minute/i
85705
+ ]
85706
+ },
85707
+ {
85708
+ reason: "auth_error",
85709
+ patterns: [
85710
+ /\b401\b/,
85711
+ /\b403\b/,
85712
+ /unauthorized/i,
85713
+ /unauthenticated/i,
85714
+ /authentication failed/i,
85715
+ /invalid.{0,24}api.{0,12}key/i,
85716
+ /api key.{0,20}(invalid|missing|expired)/i
85717
+ ]
85718
+ },
85719
+ {
85720
+ reason: "config_error",
85721
+ patterns: [
85722
+ /model not found/i,
85723
+ /invalid model/i,
85724
+ /missing model/i,
85725
+ /config(uration)? error/i,
85726
+ /ENOENT.{0,40}(config|\.env)/i,
85727
+ /no such file.{0,40}(config|credentials)/i
85728
+ ]
85729
+ }
85730
+ ];
85731
+ });
85732
+
85733
+ // src/domain/agent-lifecycle/policies/abort-resume-storm.ts
85734
+ async function tryAbortResumeStorm(deps, input, slot) {
85735
+ const stormCheck = deps.resumeStormTracker.record(input.chatroomId, input.role, deps.now());
85736
+ if (!stormCheck.isStorm) {
85737
+ return false;
85738
+ }
85739
+ deps.resumeStormTracker.reset(input.chatroomId, input.role);
85740
+ if (slot) {
85741
+ slot.resumeInFlight = false;
85742
+ }
85743
+ try {
85744
+ await deps.backend.emitResumeStormAborted({
85745
+ chatroomId: input.chatroomId,
85746
+ role: input.role,
85747
+ reason: classifyResumeStormReason(slot?.recentLogLines ?? []),
85748
+ endCount: stormCheck.endCount,
85749
+ windowMs: stormCheck.windowMs,
85750
+ ...slot?.harnessSessionId ? { harnessSessionId: slot.harnessSessionId } : {}
85751
+ });
85752
+ } catch {}
85753
+ if (slot?.state === "running" && slot.pid === input.pid) {
85754
+ await deps.stopAgent({
85755
+ chatroomId: input.chatroomId,
85756
+ role: input.role,
85757
+ reason: "platform.resume_storm"
85758
+ });
85759
+ }
85760
+ return true;
85761
+ }
85762
+ var init_abort_resume_storm = __esm(() => {
85763
+ init_classify_resume_storm_reason();
85764
+ });
85765
+
85766
+ // src/domain/agent-lifecycle/use-cases/handle-turn-completed.ts
85767
+ async function handleTurnCompleted(deps, input, slot) {
85768
+ if (slot?.resumeInFlight) {
85769
+ return { outcome: "skipped_duplicate" };
85770
+ }
85771
+ if (await tryAbortResumeStorm(deps, input, slot)) {
85772
+ return { outcome: "storm_aborted" };
85773
+ }
85774
+ if (input.supportsSessionResume) {
85775
+ if (slot) {
85776
+ slot.resumeInFlight = true;
85777
+ }
85778
+ try {
85779
+ await deps.resumeTurn(input.pid, deps.composeResumePrompt({ chatroomId: input.chatroomId, role: input.role }));
85780
+ try {
85781
+ await deps.backend.emitSessionResumed({
85782
+ chatroomId: input.chatroomId,
85783
+ role: input.role,
85784
+ ...slot?.harnessSessionId ? { harnessSessionId: slot.harnessSessionId } : {}
85785
+ });
85786
+ } catch {}
85787
+ return { outcome: "resumed" };
85788
+ } catch (err) {
85789
+ try {
85790
+ await deps.backend.emitSessionResumeFailed({
85791
+ chatroomId: input.chatroomId,
85792
+ role: input.role,
85793
+ reason: err.message,
85794
+ ...slot?.harnessSessionId ? { harnessSessionId: slot.harnessSessionId } : {}
85795
+ });
85796
+ } catch {}
85797
+ } finally {
85798
+ if (slot) {
85799
+ slot.resumeInFlight = false;
85800
+ }
85801
+ }
85802
+ }
85803
+ deps.killProcess(input.pid);
85804
+ return { outcome: "killed" };
85805
+ }
85806
+ var init_handle_turn_completed = __esm(() => {
85807
+ init_abort_resume_storm();
85808
+ });
85809
+
85810
+ // src/infrastructure/deps/process.ts
85811
+ function isProcessAlive(kill, pid) {
85812
+ try {
85813
+ kill(pid, 0);
85814
+ return true;
85815
+ } catch {
85816
+ return false;
85817
+ }
85818
+ }
85819
+
85820
+ // src/infrastructure/machine/rapid-resume-tracker.ts
85821
+ class RapidResumeTracker {
85822
+ history = new Map;
85823
+ record(chatroomId, role, now = Date.now()) {
85824
+ const key = `${chatroomId}:${role.toLowerCase()}`;
85825
+ const windowStart = now - RAPID_RESUME_WINDOW_MS;
85826
+ const recent = (this.history.get(key) ?? []).filter((ts) => ts >= windowStart);
85827
+ recent.push(now);
85828
+ this.history.set(key, recent);
85829
+ return {
85830
+ isStorm: recent.length >= RAPID_RESUME_THRESHOLD,
85831
+ recentEnds: recent,
85832
+ endCount: recent.length,
85833
+ windowMs: RAPID_RESUME_WINDOW_MS,
85834
+ threshold: RAPID_RESUME_THRESHOLD
85835
+ };
85836
+ }
85837
+ reset(chatroomId, role) {
85838
+ this.history.delete(`${chatroomId}:${role.toLowerCase()}`);
85839
+ }
85840
+ }
85841
+ var RAPID_RESUME_WINDOW_MS = 30000, RAPID_RESUME_THRESHOLD = 5;
85842
+
85587
85843
  // src/infrastructure/services/remote-agents/spawn-prompt.ts
85588
85844
  function createSpawnPrompt(raw) {
85589
85845
  const trimmed = raw?.trim();
@@ -85602,8 +85858,15 @@ class AgentProcessManager {
85602
85858
  lastHarnessSessions = new Map;
85603
85859
  exitRetryQueue = [];
85604
85860
  exitRetryTimer = null;
85861
+ turnEndQueue = new TurnEndQueue;
85605
85862
  constructor(deps) {
85606
- this.deps = deps;
85863
+ this.deps = {
85864
+ ...deps,
85865
+ resumeStormTracker: deps.resumeStormTracker ?? new RapidResumeTracker
85866
+ };
85867
+ }
85868
+ async whenTurnEndsIdle() {
85869
+ await this.turnEndQueue.whenIdle();
85607
85870
  }
85608
85871
  async ensureRunning(opts) {
85609
85872
  const key = agentKey2(opts.chatroomId, opts.role);
@@ -85672,67 +85935,46 @@ class AgentProcessManager {
85672
85935
  await operation;
85673
85936
  return { success: true };
85674
85937
  }
85675
- async handleAgentEnd(opts) {
85676
- const key = agentKey2(opts.chatroomId, opts.role);
85677
- const slot = this.slots.get(key);
85678
- if (slot?.resumeInFlight) {
85679
- console.log(`[AgentProcessManager] lifecycle.turn.completed: skipping duplicate resume for ${opts.role} (resume already in flight)`);
85680
- return;
85681
- }
85938
+ async runHandleAgentEnd(opts) {
85939
+ const slot = this.slots.get(agentKey2(opts.chatroomId, opts.role));
85940
+ const service = this.deps.agentServices.get(opts.harness);
85682
85941
  const capabilities = getHarnessCapabilities(opts.harness);
85683
- console.log(`[AgentProcessManager] lifecycle.turn.completed: role=${opts.role} pid=${opts.pid} harness=${opts.harness} supportsResume=${capabilities.supportsSessionResume}`);
85684
- if (capabilities.supportsSessionResume) {
85685
- const service = this.deps.agentServices.get(opts.harness);
85686
- if (service?.resumeTurn) {
85687
- if (slot) {
85688
- slot.resumeInFlight = true;
85942
+ const supportsSessionResume = capabilities.supportsSessionResume && typeof service?.resumeTurn === "function";
85943
+ console.log(`[AgentProcessManager] lifecycle.turn.completed: role=${opts.role} pid=${opts.pid} harness=${opts.harness} supportsResume=${supportsSessionResume}`);
85944
+ const result = await handleTurnCompleted({
85945
+ resumeStormTracker: this.deps.resumeStormTracker,
85946
+ backend: createTurnCompletedBackend(this.deps),
85947
+ now: () => this.deps.clock.now(),
85948
+ composeResumePrompt: ({ chatroomId, role }) => composeResumeMessage({
85949
+ chatroomId,
85950
+ role,
85951
+ convexUrl: this.deps.convexUrl
85952
+ }),
85953
+ resumeTurn: async (pid, prompt) => {
85954
+ if (!service?.resumeTurn) {
85955
+ throw new Error("Harness does not support resumeTurn");
85689
85956
  }
85957
+ await service.resumeTurn(pid, prompt);
85958
+ },
85959
+ killProcess: (pid) => {
85690
85960
  try {
85691
- const resumePrompt = composeResumeMessage({
85692
- chatroomId: opts.chatroomId,
85693
- role: opts.role,
85694
- convexUrl: this.deps.convexUrl
85695
- });
85696
- await service.resumeTurn(opts.pid, resumePrompt);
85697
- try {
85698
- await this.deps.backend.mutation(api.machines.emitSessionResumed, {
85699
- sessionId: this.deps.sessionId,
85700
- machineId: this.deps.machineId,
85701
- chatroomId: opts.chatroomId,
85702
- role: opts.role,
85703
- ...slot?.harnessSessionId ? { harnessSessionId: slot.harnessSessionId } : {}
85704
- });
85705
- console.log(`[AgentProcessManager] Emitted agent.sessionResumed for ${opts.role}`);
85706
- } catch (err) {
85707
- console.log(` ⚠️ Failed to emit sessionResumed event: ${err.message}`);
85708
- }
85709
- return;
85710
- } catch (err) {
85711
- const reason = err.message;
85712
- try {
85713
- await this.deps.backend.mutation(api.machines.emitSessionResumeFailed, {
85714
- sessionId: this.deps.sessionId,
85715
- machineId: this.deps.machineId,
85716
- chatroomId: opts.chatroomId,
85717
- role: opts.role,
85718
- reason,
85719
- ...slot?.harnessSessionId ? { harnessSessionId: slot.harnessSessionId } : {}
85720
- });
85721
- console.log(`[AgentProcessManager] ✅ Emitted agent.sessionResumeFailed for ${opts.role}`);
85722
- } catch (emitErr) {
85723
- console.log(` ⚠️ Failed to emit sessionResumeFailed event: ${emitErr.message}`);
85724
- }
85725
- console.log(`[AgentProcessManager] ⚠️ resumeTurn failed for ${opts.role} (pid ${opts.pid}): ${reason} — falling back to kill`);
85726
- } finally {
85727
- if (slot) {
85728
- slot.resumeInFlight = false;
85729
- }
85730
- }
85731
- }
85961
+ this.deps.processes.kill(-pid, "SIGTERM");
85962
+ } catch {}
85963
+ },
85964
+ stopAgent: (args2) => this.stop(args2)
85965
+ }, {
85966
+ chatroomId: opts.chatroomId,
85967
+ role: opts.role,
85968
+ pid: opts.pid,
85969
+ supportsSessionResume
85970
+ }, slot);
85971
+ if (result.outcome === "skipped_duplicate") {
85972
+ console.log(`[AgentProcessManager] lifecycle.turn.completed: skipping duplicate resume for ${opts.role} (resume already in flight)`);
85973
+ } else if (result.outcome === "storm_aborted") {
85974
+ console.log(`[AgentProcessManager] ✅ Handled rapid resume storm for ${opts.role}`);
85975
+ } else if (result.outcome === "resumed") {
85976
+ console.log(`[AgentProcessManager] Emitted agent.sessionResumed for ${opts.role}`);
85732
85977
  }
85733
- try {
85734
- this.deps.processes.kill(-opts.pid, "SIGTERM");
85735
- } catch {}
85736
85978
  }
85737
85979
  async handleExit(opts) {
85738
85980
  const key = agentKey2(opts.chatroomId, opts.role);
@@ -85853,18 +86095,11 @@ class AgentProcessManager {
85853
86095
  signal: undefined,
85854
86096
  agentHarness: entry.harness
85855
86097
  };
85856
- this.deps.backend.mutation(api.machines.recordAgentExited, exitArgs).catch((err) => {
85857
- console.log(` ⚠️ Failed to record agent exit on recovery: ${err.message}`);
85858
- this.queueExitRetry({ role, args: exitArgs });
85859
- });
85860
- try {
85861
- await this.deps.persistence.clearAgentPid(this.deps.machineId, chatroomId, role);
85862
- } catch {}
86098
+ this.recordAgentExitedOrQueueRetry(role, exitArgs, "Failed to record agent exit on recovery");
86099
+ await this.clearAgentPidQuietly(chatroomId, role);
85863
86100
  killed++;
85864
86101
  } else {
85865
- try {
85866
- await this.deps.persistence.clearAgentPid(this.deps.machineId, chatroomId, role);
85867
- } catch {}
86102
+ await this.clearAgentPidQuietly(chatroomId, role);
85868
86103
  cleaned++;
85869
86104
  }
85870
86105
  }
@@ -85936,13 +86171,8 @@ class AgentProcessManager {
85936
86171
  signal: undefined,
85937
86172
  agentHarness: harness
85938
86173
  };
85939
- this.deps.backend.mutation(api.machines.recordAgentExited, exitArgs).catch((err) => {
85940
- console.log(` ⚠️ Failed to record agent exit before respawn: ${err.message}`);
85941
- this.queueExitRetry({ role, args: exitArgs });
85942
- });
85943
- try {
85944
- await this.deps.persistence.clearAgentPid(this.deps.machineId, chatroomId, role);
85945
- } catch {}
86174
+ this.recordAgentExitedOrQueueRetry(role, exitArgs, "Failed to record agent exit before respawn");
86175
+ await this.clearAgentPidQuietly(chatroomId, role);
85946
86176
  }
85947
86177
  async executeEnsureRunning(key, slot, opts) {
85948
86178
  try {
@@ -85962,6 +86192,17 @@ class AgentProcessManager {
85962
86192
  }
85963
86193
  }
85964
86194
  }
86195
+ recordAgentExitedOrQueueRetry(role, exitArgs, failureLog) {
86196
+ this.deps.backend.mutation(api.machines.recordAgentExited, exitArgs).catch((err) => {
86197
+ console.log(` ⚠️ ${failureLog}: ${err.message}`);
86198
+ this.queueExitRetry({ role, args: exitArgs });
86199
+ });
86200
+ }
86201
+ async clearAgentPidQuietly(chatroomId, role) {
86202
+ try {
86203
+ await this.deps.persistence.clearAgentPid(this.deps.machineId, chatroomId, role);
86204
+ } catch {}
86205
+ }
85965
86206
  queueExitRetry(item) {
85966
86207
  this.exitRetryQueue.push(item);
85967
86208
  if (this.exitRetryTimer === null) {
@@ -86205,6 +86446,11 @@ class AgentProcessManager {
86205
86446
  slot.workingDir = opts.workingDir;
86206
86447
  slot.startedAt = this.deps.clock.now();
86207
86448
  slot.pendingOperation = undefined;
86449
+ slot.recentLogLines = [];
86450
+ this.deps.resumeStormTracker.reset(opts.chatroomId, opts.role);
86451
+ if (spawnResult.onLogLine) {
86452
+ spawnResult.onLogLine((line) => appendRecentLogLine(slot, line));
86453
+ }
86208
86454
  this.deps.backend.mutation(api.machines.updateSpawnedAgent, {
86209
86455
  sessionId: this.deps.sessionId,
86210
86456
  machineId: this.deps.machineId,
@@ -86231,12 +86477,12 @@ class AgentProcessManager {
86231
86477
  });
86232
86478
  if (spawnResult.onAgentEnd) {
86233
86479
  spawnResult.onAgentEnd(() => {
86234
- this.handleAgentEnd({
86480
+ this.turnEndQueue.enqueue(() => this.runHandleAgentEnd({
86235
86481
  chatroomId: opts.chatroomId,
86236
86482
  role: opts.role,
86237
86483
  pid,
86238
86484
  harness: opts.agentHarness
86239
- });
86485
+ }));
86240
86486
  });
86241
86487
  }
86242
86488
  let lastReportedTokenAt = 0;
@@ -86348,11 +86594,13 @@ class AgentProcessManager {
86348
86594
  }
86349
86595
  var AGENT_EXIT_RETRY_INTERVAL_MS = 1e4;
86350
86596
  var init_agent_process_manager = __esm(() => {
86351
- init_orphan_tracker();
86597
+ init_generator();
86598
+ init_types();
86599
+ init_turn_completed_backend();
86352
86600
  init_api3();
86601
+ init_orphan_tracker();
86353
86602
  init_agent_lifecycle();
86354
- init_types();
86355
- init_generator();
86603
+ init_handle_turn_completed();
86356
86604
  });
86357
86605
 
86358
86606
  // src/infrastructure/services/harness-spawning/rate-limiter.ts
@@ -88578,4 +88826,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
88578
88826
  });
88579
88827
  program2.parse();
88580
88828
 
88581
- //# debugId=A45EE098A894A8F164756E2164756E21
88829
+ //# debugId=A9CDF253A2BBC68F64756E2164756E21