chatroom-cli 1.51.0 → 1.53.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
@@ -26010,7 +26010,7 @@ class BaseCLIAgentService {
26010
26010
  }
26011
26011
  return null;
26012
26012
  }
26013
- async stop(pid) {
26013
+ async stop(pid, _options) {
26014
26014
  try {
26015
26015
  this.deps.kill(-pid, "SIGTERM");
26016
26016
  } catch {
@@ -26172,6 +26172,7 @@ class PiRpcReader {
26172
26172
  toolCallCallbacks = [];
26173
26173
  toolResultCallbacks = [];
26174
26174
  anyEventCallbacks = [];
26175
+ stateResponseCallbacks = [];
26175
26176
  constructor(stream) {
26176
26177
  const rl = createInterface({ input: stream, crlfDelay: Infinity });
26177
26178
  rl.on("line", (line) => this._handleLine(line));
@@ -26194,6 +26195,9 @@ class PiRpcReader {
26194
26195
  onAnyEvent(cb) {
26195
26196
  this.anyEventCallbacks.push(cb);
26196
26197
  }
26198
+ onStateResponse(cb) {
26199
+ this.stateResponseCallbacks.push(cb);
26200
+ }
26197
26201
  _handleLine(line) {
26198
26202
  const trimmed = line.trim();
26199
26203
  if (!trimmed)
@@ -26207,6 +26211,15 @@ class PiRpcReader {
26207
26211
  for (const cb of this.anyEventCallbacks)
26208
26212
  cb();
26209
26213
  const type = event["type"];
26214
+ if (type === "response" && event["command"] === "get_state") {
26215
+ const data = event["data"];
26216
+ const sessionId = data?.["sessionId"];
26217
+ if (event["success"] === true && typeof sessionId === "string") {
26218
+ for (const cb of this.stateResponseCallbacks)
26219
+ cb(sessionId);
26220
+ }
26221
+ return;
26222
+ }
26210
26223
  if (type === "message_update") {
26211
26224
  const assistantMessageEvent = event["assistantMessageEvent"];
26212
26225
  if (assistantMessageEvent?.["type"] === "text_delta") {
@@ -26252,7 +26265,11 @@ class PiRpcReader {
26252
26265
  var init_pi_rpc_reader = () => {};
26253
26266
 
26254
26267
  // src/infrastructure/services/remote-agents/pi/pi-agent-service.ts
26255
- var PI_COMMAND = "pi", PiAgentService;
26268
+ import { join as join6 } from "node:path";
26269
+ function getPiSessionDir(workingDir) {
26270
+ return join6(workingDir, ".chatroom", "pi-sessions");
26271
+ }
26272
+ var PI_COMMAND = "pi", PI_AGENT_NAME = "pi", GET_STATE_TIMEOUT_MS = 5000, SPAWN_READY_DELAY_MS = 500, PiAgentService;
26256
26273
  var init_pi_agent_service = __esm(() => {
26257
26274
  init_base_cli_agent_service();
26258
26275
  init_pi_rpc_reader();
@@ -26261,23 +26278,50 @@ var init_pi_agent_service = __esm(() => {
26261
26278
  displayName = "Pi";
26262
26279
  command = PI_COMMAND;
26263
26280
  childProcesses = new Map;
26281
+ trackedSessions = new Map;
26264
26282
  constructor(deps) {
26265
26283
  super(deps);
26266
26284
  }
26267
26285
  untrack(pid) {
26268
26286
  this.childProcesses.delete(pid);
26287
+ this.trackedSessions.delete(pid);
26269
26288
  super.untrack(pid);
26270
26289
  }
26290
+ getHarnessReconnectContext(pid) {
26291
+ const session = this.trackedSessions.get(pid);
26292
+ if (!session) {
26293
+ return;
26294
+ }
26295
+ return {
26296
+ agentName: PI_AGENT_NAME,
26297
+ ...session.model ? { model: session.model } : {}
26298
+ };
26299
+ }
26300
+ async resumeFromDaemonMemory(options, stored) {
26301
+ const { prompt, systemPrompt, model, context: context4 } = options;
26302
+ const modelForSession = model ?? stored.model;
26303
+ const childProcess = this.spawnPiRpcProcess({
26304
+ workingDir: stored.workingDir,
26305
+ systemPrompt,
26306
+ model: modelForSession,
26307
+ sessionId: stored.harnessSessionId
26308
+ });
26309
+ await this.waitForSpawnReady(childProcess);
26310
+ await this.writePrompt(childProcess, prompt);
26311
+ return this.wireRpcProcess({
26312
+ childProcess,
26313
+ context: context4,
26314
+ workingDir: stored.workingDir,
26315
+ model: modelForSession,
26316
+ harnessSessionId: stored.harnessSessionId
26317
+ });
26318
+ }
26271
26319
  async resumeTurn(pid, prompt) {
26272
26320
  const child = this.childProcesses.get(pid);
26273
- if (!child?.stdin) {
26321
+ if (!child) {
26274
26322
  throw new Error(`No tracked pi process or stdin for pid=${pid}`);
26275
26323
  }
26276
- const message = JSON.stringify({ type: "prompt", message: prompt }) + `
26277
- `;
26278
- await new Promise((resolve, reject) => {
26279
- child.stdin.write(message, (err) => err ? reject(err) : resolve());
26280
- });
26324
+ await this.writePrompt(child, prompt);
26281
26325
  }
26282
26326
  async isInstalled() {
26283
26327
  return this.checkInstalled(PI_COMMAND);
@@ -26307,16 +26351,46 @@ var init_pi_agent_service = __esm(() => {
26307
26351
  return models;
26308
26352
  }
26309
26353
  async spawn(options) {
26310
- const { prompt, systemPrompt, model } = options;
26311
- const args2 = ["--mode", "rpc", "--no-session"];
26312
- if (model) {
26313
- args2.push("--model", model);
26354
+ const { prompt, systemPrompt, model, context: context4, workingDir } = options;
26355
+ const childProcess = this.spawnPiRpcProcess({
26356
+ workingDir,
26357
+ systemPrompt,
26358
+ model
26359
+ });
26360
+ await this.waitForSpawnReady(childProcess);
26361
+ if (!childProcess.stdout) {
26362
+ throw new Error("Pi RPC process has no stdout");
26363
+ }
26364
+ const reader = new PiRpcReader(childProcess.stdout);
26365
+ const harnessSessionId = await this.querySessionId(reader, childProcess.stdin);
26366
+ await this.writePrompt(childProcess, prompt);
26367
+ return this.wireRpcProcess({
26368
+ childProcess,
26369
+ context: context4,
26370
+ workingDir,
26371
+ model,
26372
+ harnessSessionId,
26373
+ reader
26374
+ });
26375
+ }
26376
+ spawnPiRpcProcess(args2) {
26377
+ const rpcArgs = [
26378
+ "--mode",
26379
+ "rpc",
26380
+ "--session-dir",
26381
+ getPiSessionDir(args2.workingDir)
26382
+ ];
26383
+ if (args2.sessionId) {
26384
+ rpcArgs.push("--session", args2.sessionId);
26314
26385
  }
26315
- if (systemPrompt) {
26316
- args2.push("--system-prompt", systemPrompt);
26386
+ if (args2.model) {
26387
+ rpcArgs.push("--model", args2.model);
26317
26388
  }
26318
- const childProcess = this.deps.spawn(PI_COMMAND, args2, {
26319
- cwd: options.workingDir,
26389
+ if (args2.systemPrompt) {
26390
+ rpcArgs.push("--system-prompt", args2.systemPrompt);
26391
+ }
26392
+ const childProcess = this.deps.spawn(PI_COMMAND, rpcArgs, {
26393
+ cwd: args2.workingDir,
26320
26394
  stdio: ["pipe", "pipe", "pipe"],
26321
26395
  shell: false,
26322
26396
  detached: true,
@@ -26326,117 +26400,160 @@ var init_pi_agent_service = __esm(() => {
26326
26400
  GIT_SEQUENCE_EDITOR: "true"
26327
26401
  }
26328
26402
  });
26329
- childProcess.stdin?.write(JSON.stringify({ type: "prompt", message: prompt }) + `
26330
- `);
26331
- await new Promise((resolve) => setTimeout(resolve, 500));
26403
+ return childProcess;
26404
+ }
26405
+ async waitForSpawnReady(childProcess) {
26406
+ await new Promise((resolve) => setTimeout(resolve, SPAWN_READY_DELAY_MS));
26332
26407
  if (childProcess.killed || childProcess.exitCode !== null) {
26333
26408
  throw new Error(`Agent process exited immediately (exit code: ${childProcess.exitCode})`);
26334
26409
  }
26335
26410
  if (!childProcess.pid) {
26336
26411
  throw new Error("Agent process started but has no PID");
26337
26412
  }
26413
+ }
26414
+ writePrompt(child, prompt) {
26415
+ return new Promise((resolve, reject) => {
26416
+ if (!child.stdin) {
26417
+ reject(new Error("Pi RPC process has no stdin"));
26418
+ return;
26419
+ }
26420
+ const message = JSON.stringify({ type: "prompt", message: prompt }) + `
26421
+ `;
26422
+ child.stdin.write(message, (err) => err ? reject(err) : resolve());
26423
+ });
26424
+ }
26425
+ async querySessionId(reader, stdin) {
26426
+ if (!stdin) {
26427
+ throw new Error("Pi RPC process has no stdin");
26428
+ }
26429
+ return new Promise((resolve, reject) => {
26430
+ const timer = setTimeout(() => {
26431
+ reject(new Error(`get_state timed out after ${GET_STATE_TIMEOUT_MS}ms`));
26432
+ }, GET_STATE_TIMEOUT_MS);
26433
+ reader.onStateResponse((sessionId) => {
26434
+ clearTimeout(timer);
26435
+ resolve(sessionId);
26436
+ });
26437
+ stdin.write(JSON.stringify({ type: "get_state" }) + `
26438
+ `);
26439
+ });
26440
+ }
26441
+ wireRpcProcess(args2) {
26442
+ const { childProcess, context: context4, workingDir, model, harnessSessionId } = args2;
26338
26443
  const pid = childProcess.pid;
26339
- const context4 = options.context;
26340
26444
  this.childProcesses.set(pid, childProcess);
26445
+ this.trackedSessions.set(pid, { harnessSessionId, workingDir, model });
26341
26446
  const entry = this.registerProcess(pid, context4);
26342
26447
  const roleTag = context4.role ?? "unknown";
26343
26448
  const chatroomSuffix = context4.chatroomId ? `@${context4.chatroomId.slice(-6)}` : "";
26344
26449
  const logPrefix = `[pi:${roleTag}${chatroomSuffix}`;
26345
26450
  const outputCallbacks = [];
26346
- if (childProcess.stdout) {
26347
- const reader = new PiRpcReader(childProcess.stdout);
26348
- let textBuffer = "";
26349
- let thinkingBuffer = "";
26350
- const flushText = () => {
26351
- if (!textBuffer)
26352
- return;
26353
- for (const line of textBuffer.split(`
26451
+ const agentEndCallbacks = [];
26452
+ const onExit4 = (cb) => {
26453
+ childProcess.on("exit", (code2, signal) => {
26454
+ this.childProcesses.delete(pid);
26455
+ this.trackedSessions.delete(pid);
26456
+ this.deleteProcess(pid);
26457
+ cb({ code: code2, signal, context: context4 });
26458
+ });
26459
+ };
26460
+ const onOutput = (cb) => {
26461
+ outputCallbacks.push(cb);
26462
+ };
26463
+ const onAgentEnd = (cb) => {
26464
+ agentEndCallbacks.push(cb);
26465
+ };
26466
+ const baseResult = {
26467
+ pid,
26468
+ harnessSessionId,
26469
+ harnessReconnect: {
26470
+ agentName: PI_AGENT_NAME,
26471
+ ...model ? { model } : {}
26472
+ },
26473
+ onExit: onExit4,
26474
+ onOutput
26475
+ };
26476
+ 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
+ }
26485
+ return baseResult;
26486
+ }
26487
+ const reader = args2.reader ?? new PiRpcReader(childProcess.stdout);
26488
+ let textBuffer = "";
26489
+ let thinkingBuffer = "";
26490
+ const flushText = () => {
26491
+ if (!textBuffer)
26492
+ return;
26493
+ for (const line of textBuffer.split(`
26354
26494
  `)) {
26355
- if (line)
26356
- process.stdout.write(`${logPrefix} text] ${line}
26495
+ if (line)
26496
+ process.stdout.write(`${logPrefix} text] ${line}
26357
26497
  `);
26358
- }
26359
- textBuffer = "";
26360
- };
26361
- const flushThinking = () => {
26362
- if (!thinkingBuffer)
26363
- return;
26364
- for (const line of thinkingBuffer.split(`
26498
+ }
26499
+ textBuffer = "";
26500
+ };
26501
+ const flushThinking = () => {
26502
+ if (!thinkingBuffer)
26503
+ return;
26504
+ for (const line of thinkingBuffer.split(`
26365
26505
  `)) {
26366
- if (line)
26367
- process.stdout.write(`${logPrefix} thinking] ${line}
26506
+ if (line)
26507
+ process.stdout.write(`${logPrefix} thinking] ${line}
26368
26508
  `);
26369
- }
26370
- thinkingBuffer = "";
26371
- };
26372
- reader.onTextDelta((delta) => {
26373
- flushThinking();
26374
- textBuffer += delta;
26375
- if (textBuffer.includes(`
26509
+ }
26510
+ thinkingBuffer = "";
26511
+ };
26512
+ reader.onTextDelta((delta) => {
26513
+ flushThinking();
26514
+ textBuffer += delta;
26515
+ if (textBuffer.includes(`
26376
26516
  `))
26377
- flushText();
26378
- entry.lastOutputAt = Date.now();
26379
- for (const cb of outputCallbacks)
26380
- cb();
26381
- });
26382
- reader.onThinkingDelta((delta) => {
26383
26517
  flushText();
26384
- thinkingBuffer += delta;
26385
- if (thinkingBuffer.includes(`
26518
+ entry.lastOutputAt = Date.now();
26519
+ for (const cb of outputCallbacks)
26520
+ cb();
26521
+ });
26522
+ reader.onThinkingDelta((delta) => {
26523
+ flushText();
26524
+ thinkingBuffer += delta;
26525
+ if (thinkingBuffer.includes(`
26386
26526
  `))
26387
- flushThinking();
26388
- entry.lastOutputAt = Date.now();
26389
- for (const cb of outputCallbacks)
26390
- cb();
26391
- });
26392
- reader.onAnyEvent(() => {
26393
- entry.lastOutputAt = Date.now();
26394
- for (const cb of outputCallbacks)
26395
- cb();
26396
- });
26397
- reader.onAgentEnd(() => {
26398
- flushText();
26399
26527
  flushThinking();
26400
- process.stdout.write(`${logPrefix} agent_end]
26528
+ entry.lastOutputAt = Date.now();
26529
+ for (const cb of outputCallbacks)
26530
+ cb();
26531
+ });
26532
+ reader.onAnyEvent(() => {
26533
+ entry.lastOutputAt = Date.now();
26534
+ for (const cb of outputCallbacks)
26535
+ cb();
26536
+ });
26537
+ reader.onAgentEnd(() => {
26538
+ flushText();
26539
+ flushThinking();
26540
+ process.stdout.write(`${logPrefix} agent_end]
26401
26541
  `);
26402
- });
26403
- reader.onToolCall((name, args3) => {
26404
- flushText();
26405
- flushThinking();
26406
- const argsStr = args3 != null ? ` args: ${JSON.stringify(args3)}` : "";
26407
- process.stdout.write(`${logPrefix} tool: ${name}${argsStr}]
26542
+ for (const cb of agentEndCallbacks)
26543
+ cb();
26544
+ });
26545
+ reader.onToolCall((name, toolArgs) => {
26546
+ flushText();
26547
+ flushThinking();
26548
+ const argsStr = toolArgs != null ? ` args: ${JSON.stringify(toolArgs)}` : "";
26549
+ process.stdout.write(`${logPrefix} tool: ${name}${argsStr}]
26408
26550
  `);
26409
- });
26410
- reader.onToolResult((name, result) => {
26411
- const resultStr = typeof result === "string" ? result : JSON.stringify(result);
26412
- process.stdout.write(`${logPrefix} tool_result: ${name} result: ${resultStr}]
26551
+ });
26552
+ reader.onToolResult((name, result) => {
26553
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result);
26554
+ process.stdout.write(`${logPrefix} tool_result: ${name} result: ${resultStr}]
26413
26555
  `);
26414
- });
26415
- if (childProcess.stderr) {
26416
- childProcess.stderr.pipe(process.stderr, { end: false });
26417
- childProcess.stderr.on("data", () => {
26418
- entry.lastOutputAt = Date.now();
26419
- for (const cb of outputCallbacks)
26420
- cb();
26421
- });
26422
- }
26423
- return {
26424
- pid,
26425
- onExit: (cb) => {
26426
- childProcess.on("exit", (code2, signal) => {
26427
- this.childProcesses.delete(pid);
26428
- this.deleteProcess(pid);
26429
- cb({ code: code2, signal, context: context4 });
26430
- });
26431
- },
26432
- onOutput: (cb) => {
26433
- outputCallbacks.push(cb);
26434
- },
26435
- onAgentEnd: (cb) => {
26436
- reader.onAgentEnd(cb);
26437
- }
26438
- };
26439
- }
26556
+ });
26440
26557
  if (childProcess.stderr) {
26441
26558
  childProcess.stderr.pipe(process.stderr, { end: false });
26442
26559
  childProcess.stderr.on("data", () => {
@@ -26446,17 +26563,8 @@ var init_pi_agent_service = __esm(() => {
26446
26563
  });
26447
26564
  }
26448
26565
  return {
26449
- pid,
26450
- onExit: (cb) => {
26451
- childProcess.on("exit", (code2, signal) => {
26452
- this.childProcesses.delete(pid);
26453
- this.deleteProcess(pid);
26454
- cb({ code: code2, signal, context: context4 });
26455
- });
26456
- },
26457
- onOutput: (cb) => {
26458
- outputCallbacks.push(cb);
26459
- }
26566
+ ...baseResult,
26567
+ onAgentEnd
26460
26568
  };
26461
26569
  }
26462
26570
  };
@@ -27556,7 +27664,7 @@ class CursorSdkStreamAdapter {
27556
27664
 
27557
27665
  // src/infrastructure/services/remote-agents/cursor-sdk/cursor-sdk-agent-service.ts
27558
27666
  import { readFileSync as readFileSync2 } from "node:fs";
27559
- import { join as join6 } from "node:path";
27667
+ import { join as join7 } from "node:path";
27560
27668
  import { createRequire as createRequire3 } from "node:module";
27561
27669
  import { randomUUID } from "node:crypto";
27562
27670
  async function loadSdk() {
@@ -27577,7 +27685,7 @@ function getSdkPackageVersion() {
27577
27685
  return cachedSdkPackageVersion;
27578
27686
  const require3 = createRequire3(import.meta.url);
27579
27687
  const entry = require3.resolve("@cursor/sdk");
27580
- const packageJsonPath = join6(entry, "..", "..", "..", "package.json");
27688
+ const packageJsonPath = join7(entry, "..", "..", "..", "package.json");
27581
27689
  const pkg = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
27582
27690
  cachedSdkPackageVersion = pkg.version;
27583
27691
  return pkg.version;
@@ -27596,6 +27704,9 @@ async function withTimeout(p, ms, label) {
27596
27704
  clearTimeout(timer);
27597
27705
  }
27598
27706
  }
27707
+ function buildAgentName(context4) {
27708
+ return `${context4.role}@${context4.chatroomId.slice(-6)}`;
27709
+ }
27599
27710
  function waitForResumeOrAbort(session) {
27600
27711
  if (session.aborted)
27601
27712
  return Promise.resolve(null);
@@ -27690,10 +27801,13 @@ var init_cursor_sdk_agent_service = __esm(() => {
27690
27801
  session.abortResolve = undefined;
27691
27802
  resolve(prompt);
27692
27803
  }
27693
- async stop(pid) {
27804
+ async stop(pid, options) {
27694
27805
  const session = this.sessions.get(pid);
27695
27806
  if (session) {
27696
27807
  session.aborted = true;
27808
+ if (options?.preserveForResume) {
27809
+ session.preserveForResume = true;
27810
+ }
27697
27811
  session.abortResolve?.();
27698
27812
  const run3 = session.run;
27699
27813
  if (run3?.supports("cancel")) {
@@ -27703,7 +27817,7 @@ var init_cursor_sdk_agent_service = __esm(() => {
27703
27817
  console.warn(`[cursor-sdk] run.cancel for pid=${pid} failed:`, err instanceof Error ? err.message : err);
27704
27818
  }
27705
27819
  }
27706
- if (!session.agentClosed) {
27820
+ if (!session.preserveForResume && !session.agentClosed) {
27707
27821
  try {
27708
27822
  session.agent.close();
27709
27823
  session.agentClosed = true;
@@ -27713,52 +27827,85 @@ var init_cursor_sdk_agent_service = __esm(() => {
27713
27827
  }
27714
27828
  await super.stop(pid);
27715
27829
  }
27716
- async spawn(options) {
27830
+ getHarnessReconnectContext(pid) {
27831
+ const session = this.sessions.get(pid);
27832
+ if (!session) {
27833
+ return;
27834
+ }
27835
+ return {
27836
+ agentName: session.agentName,
27837
+ ...session.model ? { model: session.model } : {}
27838
+ };
27839
+ }
27840
+ async resumeFromDaemonMemory(options, stored) {
27717
27841
  const apiKey = process.env.CURSOR_API_KEY?.trim();
27718
27842
  if (!apiKey) {
27719
27843
  throw new Error("CURSOR_API_KEY is not set");
27720
27844
  }
27721
- const keeper = this.deps.spawn(process.execPath, ["-e", "setInterval(()=>{},2147483647)"], {
27722
- cwd: options.workingDir,
27723
- stdio: "ignore",
27724
- shell: false,
27725
- detached: true
27726
- });
27727
- if (!keeper.pid) {
27728
- keeper.kill();
27729
- throw new Error("Failed to spawn cursor-sdk keeper process");
27730
- }
27845
+ const keeper = this.spawnKeeper(options.workingDir);
27731
27846
  const pid = keeper.pid;
27732
27847
  const context4 = options.context;
27733
- const entry = this.registerProcess(pid, context4);
27734
- const logPrefix = buildLogPrefix(context4);
27848
+ const agentName = stored.agentName;
27849
+ const modelId = resolveModelId(options.model ?? stored.model);
27735
27850
  const fullPrompt = options.systemPrompt ? `${options.systemPrompt}
27736
27851
 
27737
27852
  ${options.prompt}` : options.prompt;
27738
- const exitCallbacks = [];
27739
- const outputCallbacks = [];
27740
- const agentEndCallbacks = [];
27741
27853
  let agent;
27742
27854
  try {
27743
27855
  const { Agent } = await loadSdk();
27744
- agent = await withTimeout(Agent.create({
27856
+ agent = await withTimeout(Agent.resume(stored.harnessSessionId, {
27745
27857
  apiKey,
27746
- name: `${context4.role}@${context4.chatroomId.slice(-6)}`,
27747
- model: { id: resolveModelId(options.model) },
27748
- local: { cwd: options.workingDir, settingSources: [] }
27749
- }), AGENT_CREATE_TIMEOUT_MS, "Agent.create");
27858
+ model: { id: modelId },
27859
+ local: { cwd: stored.workingDir, settingSources: [] }
27860
+ }), AGENT_CREATE_TIMEOUT_MS, "Agent.resume");
27750
27861
  } catch (err) {
27751
27862
  keeper.kill();
27752
27863
  this.deleteProcess(pid);
27753
27864
  throw err;
27754
27865
  }
27866
+ return this.startRunningSession({
27867
+ pid,
27868
+ keeper,
27869
+ agent,
27870
+ context: context4,
27871
+ agentName,
27872
+ model: options.model ?? stored.model,
27873
+ workingDir: stored.workingDir,
27874
+ initialPrompt: fullPrompt,
27875
+ forceFirstTurn: true
27876
+ });
27877
+ }
27878
+ spawnKeeper(workingDir) {
27879
+ const keeper = this.deps.spawn(process.execPath, ["-e", "setInterval(()=>{},2147483647)"], {
27880
+ cwd: workingDir,
27881
+ stdio: "ignore",
27882
+ shell: false,
27883
+ detached: true
27884
+ });
27885
+ if (!keeper.pid) {
27886
+ keeper.kill();
27887
+ throw new Error("Failed to spawn cursor-sdk keeper process");
27888
+ }
27889
+ return keeper;
27890
+ }
27891
+ startRunningSession(args2) {
27892
+ const { pid, keeper, agent, context: context4, agentName, model, workingDir, initialPrompt, forceFirstTurn } = args2;
27893
+ const entry = this.registerProcess(pid, context4);
27894
+ const logPrefix = buildLogPrefix(context4);
27755
27895
  const session = {
27756
27896
  agent,
27757
27897
  keeper,
27758
27898
  aborted: false,
27759
- agentClosed: false
27899
+ agentClosed: false,
27900
+ preserveForResume: false,
27901
+ agentName,
27902
+ model,
27903
+ workingDir
27760
27904
  };
27761
27905
  this.sessions.set(pid, session);
27906
+ const exitCallbacks = [];
27907
+ const outputCallbacks = [];
27908
+ const agentEndCallbacks = [];
27762
27909
  const finishExit = (code2, signal) => {
27763
27910
  this.sessions.delete(pid);
27764
27911
  this.deleteProcess(pid);
@@ -27766,11 +27913,54 @@ ${options.prompt}` : options.prompt;
27766
27913
  cb({ code: code2, signal, context: context4 });
27767
27914
  }
27768
27915
  };
27916
+ this.runTurnLoop({
27917
+ pid,
27918
+ agent,
27919
+ session,
27920
+ context: context4,
27921
+ entry,
27922
+ logPrefix,
27923
+ initialPrompt,
27924
+ forceFirstTurn,
27925
+ finishExit,
27926
+ outputCallbacks,
27927
+ agentEndCallbacks
27928
+ });
27929
+ return {
27930
+ pid,
27931
+ harnessSessionId: agent.agentId,
27932
+ harnessReconnect: {
27933
+ agentName,
27934
+ ...model ? { model } : {}
27935
+ },
27936
+ onExit: (cb) => {
27937
+ exitCallbacks.push(cb);
27938
+ },
27939
+ onOutput: (cb) => {
27940
+ outputCallbacks.push(cb);
27941
+ },
27942
+ onAgentEnd: (cb) => {
27943
+ agentEndCallbacks.push(cb);
27944
+ }
27945
+ };
27946
+ }
27947
+ runTurnLoop(args2) {
27948
+ const {
27949
+ agent,
27950
+ session,
27951
+ logPrefix,
27952
+ initialPrompt,
27953
+ forceFirstTurn,
27954
+ finishExit,
27955
+ entry,
27956
+ outputCallbacks,
27957
+ agentEndCallbacks
27958
+ } = args2;
27769
27959
  (async () => {
27770
27960
  let exitCode = 0;
27771
27961
  let exitSignal = null;
27772
- let nextPrompt = fullPrompt;
27773
- let isFirstTurn = true;
27962
+ let nextPrompt = initialPrompt;
27963
+ let isFirstTurn = forceFirstTurn;
27774
27964
  try {
27775
27965
  while (!session.aborted) {
27776
27966
  const run3 = await withTimeout(agent.send(nextPrompt, {
@@ -27807,8 +27997,9 @@ ${options.prompt}` : options.prompt;
27807
27997
  `);
27808
27998
  break;
27809
27999
  }
28000
+ const resumePromise = waitForResumeOrAbort(session);
27810
28001
  adapter.finish();
27811
- const resumePrompt = await waitForResumeOrAbort(session);
28002
+ const resumePrompt = await resumePromise;
27812
28003
  if (resumePrompt === null || session.aborted) {
27813
28004
  if (session.aborted) {
27814
28005
  exitCode = 1;
@@ -27824,30 +28015,57 @@ ${options.prompt}` : options.prompt;
27824
28015
  process.stderr.write(`${logPrefix} spawn-error] ${reason}
27825
28016
  `);
27826
28017
  } finally {
27827
- if (!session.agentClosed) {
28018
+ if (!session.agentClosed && !session.preserveForResume) {
27828
28019
  try {
27829
28020
  agent.close();
27830
28021
  session.agentClosed = true;
27831
28022
  } catch {}
27832
28023
  }
27833
28024
  try {
27834
- keeper.kill();
28025
+ session.keeper.kill();
27835
28026
  } catch {}
27836
28027
  finishExit(exitCode, exitSignal);
27837
28028
  }
27838
28029
  })();
27839
- return {
28030
+ }
28031
+ async spawn(options) {
28032
+ const apiKey = process.env.CURSOR_API_KEY?.trim();
28033
+ if (!apiKey) {
28034
+ throw new Error("CURSOR_API_KEY is not set");
28035
+ }
28036
+ const keeper = this.spawnKeeper(options.workingDir);
28037
+ const pid = keeper.pid;
28038
+ const context4 = options.context;
28039
+ const agentName = buildAgentName(context4);
28040
+ const modelId = resolveModelId(options.model);
28041
+ const fullPrompt = options.systemPrompt ? `${options.systemPrompt}
28042
+
28043
+ ${options.prompt}` : options.prompt;
28044
+ let agent;
28045
+ try {
28046
+ const { Agent } = await loadSdk();
28047
+ agent = await withTimeout(Agent.create({
28048
+ apiKey,
28049
+ name: agentName,
28050
+ model: { id: modelId },
28051
+ local: { cwd: options.workingDir, settingSources: [] }
28052
+ }), AGENT_CREATE_TIMEOUT_MS, "Agent.create");
28053
+ } catch (err) {
28054
+ keeper.kill();
28055
+ this.deleteProcess(pid);
28056
+ throw err;
28057
+ }
28058
+ return this.startRunningSession({
27840
28059
  pid,
27841
- onExit: (cb) => {
27842
- exitCallbacks.push(cb);
27843
- },
27844
- onOutput: (cb) => {
27845
- outputCallbacks.push(cb);
27846
- },
27847
- onAgentEnd: (cb) => {
27848
- agentEndCallbacks.push(cb);
27849
- }
27850
- };
28060
+ keeper,
28061
+ agent,
28062
+ context: context4,
28063
+ agentName,
28064
+ model: options.model,
28065
+ workingDir: options.workingDir,
28066
+ initialPrompt: fullPrompt,
28067
+ forceFirstTurn: true
28068
+ });
27851
28069
  }
27852
28070
  };
27853
28071
  });
@@ -30038,12 +30256,12 @@ function startSessionEventForwarder(client4, options) {
30038
30256
  // src/infrastructure/services/remote-agents/opencode-sdk/session-metadata-store.ts
30039
30257
  import { existsSync, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
30040
30258
  import { homedir as homedir2 } from "node:os";
30041
- import { dirname as dirname2, join as join7 } from "node:path";
30259
+ import { dirname as dirname2, join as join8 } from "node:path";
30042
30260
 
30043
30261
  class FileSessionMetadataStore {
30044
30262
  filePath;
30045
30263
  constructor(filePath) {
30046
- this.filePath = filePath ?? join7(homedir2(), ".chatroom", "opencode-sdk-sessions.json");
30264
+ this.filePath = filePath ?? join8(homedir2(), ".chatroom", "opencode-sdk-sessions.json");
30047
30265
  }
30048
30266
  load() {
30049
30267
  try {
@@ -30097,7 +30315,7 @@ async function withTimeout2(p, ms, label) {
30097
30315
  clearTimeout(timer);
30098
30316
  }
30099
30317
  }
30100
- var OPENCODE_COMMAND2 = "opencode", SERVE_STARTUP_TIMEOUT_MS = 1e4, SESSION_CREATE_TIMEOUT_MS = 30000, PROMPT_ASYNC_TIMEOUT_MS = 60000, SESSION_ABORT_TIMEOUT_MS = 5000, AGENTS_LIST_TIMEOUT_MS = 1e4, OpenCodeSdkAgentService;
30318
+ var OPENCODE_COMMAND2 = "opencode", SERVE_STARTUP_TIMEOUT_MS = 1e4, SESSION_CREATE_TIMEOUT_MS = 30000, PROMPT_ASYNC_TIMEOUT_MS = 60000, SESSION_ABORT_TIMEOUT_MS = 5000, SESSION_GET_TIMEOUT_MS = 1e4, AGENTS_LIST_TIMEOUT_MS = 1e4, OpenCodeSdkAgentService;
30101
30319
  var init_opencode_sdk_agent_service = __esm(() => {
30102
30320
  init_dist();
30103
30321
  init_base_cli_agent_service();
@@ -30126,28 +30344,30 @@ var init_opencode_sdk_agent_service = __esm(() => {
30126
30344
  return output.split(`
30127
30345
  `).map((line) => line.trim()).filter((line) => line.length > 0);
30128
30346
  }
30129
- async stop(pid) {
30347
+ async stop(pid, options) {
30130
30348
  const forwarder = this.forwarders.get(pid);
30131
30349
  if (forwarder) {
30132
30350
  forwarder.stop();
30133
30351
  this.forwarders.delete(pid);
30134
30352
  }
30353
+ const preserveForResume = options?.preserveForResume === true;
30135
30354
  const meta = this.sessionStore.findByPid(pid);
30136
30355
  if (meta) {
30137
- try {
30138
- const client4 = createOpencodeClient({ baseUrl: meta.baseUrl });
30139
- await withTimeout2(client4.session.abort({ path: { id: meta.sessionId } }), SESSION_ABORT_TIMEOUT_MS, "session.abort");
30140
- } catch (err) {
30141
- console.warn(`[opencode-sdk] session.abort for pid=${pid} sessionId=${meta.sessionId} failed (continuing with SIGTERM):`, err instanceof Error ? err.message : err);
30356
+ if (!preserveForResume) {
30357
+ try {
30358
+ const client4 = createOpencodeClient({ baseUrl: meta.baseUrl });
30359
+ await withTimeout2(client4.session.abort({ path: { id: meta.sessionId } }), SESSION_ABORT_TIMEOUT_MS, "session.abort");
30360
+ } catch (err) {
30361
+ console.warn(`[opencode-sdk] session.abort for pid=${pid} sessionId=${meta.sessionId} failed (continuing with SIGTERM):`, err instanceof Error ? err.message : err);
30362
+ }
30142
30363
  }
30143
30364
  this.sessionStore.remove(meta.sessionId);
30144
30365
  }
30145
30366
  await super.stop(pid);
30146
30367
  }
30147
- async spawn(options) {
30148
- const { prompt, systemPrompt, model, context: context4 } = options;
30368
+ spawnServeProcess(workingDir) {
30149
30369
  const childProcess = this.deps.spawn(OPENCODE_COMMAND2, ["serve", "--print-logs"], {
30150
- cwd: options.workingDir,
30370
+ cwd: workingDir,
30151
30371
  stdio: ["pipe", "pipe", "pipe"],
30152
30372
  shell: false,
30153
30373
  detached: true,
@@ -30160,6 +30380,146 @@ var init_opencode_sdk_agent_service = __esm(() => {
30160
30380
  if (!childProcess.pid) {
30161
30381
  throw new Error("Failed to spawn opencode serve process");
30162
30382
  }
30383
+ return childProcess;
30384
+ }
30385
+ registerRunningSession(args2) {
30386
+ const {
30387
+ childProcess,
30388
+ pid,
30389
+ sessionId,
30390
+ context: context4,
30391
+ forwarder,
30392
+ baseUrl,
30393
+ agentName,
30394
+ model,
30395
+ workingDir
30396
+ } = args2;
30397
+ const meta = {
30398
+ sessionId,
30399
+ machineId: context4.machineId,
30400
+ chatroomId: context4.chatroomId,
30401
+ role: context4.role,
30402
+ agentName,
30403
+ ...model ? { model } : {},
30404
+ pid,
30405
+ createdAt: new Date().toISOString(),
30406
+ baseUrl
30407
+ };
30408
+ this.sessionStore.upsert(meta);
30409
+ const entry = this.registerProcess(pid, context4);
30410
+ if (forwarder)
30411
+ this.forwarders.set(pid, forwarder);
30412
+ const outputCallbacks = [];
30413
+ forwardFiltered(childProcess.stdout ?? undefined, process.stdout, isInfoLine);
30414
+ forwardFiltered(childProcess.stderr ?? undefined, process.stderr, isInfoLine);
30415
+ if (childProcess.stdout) {
30416
+ childProcess.stdout.on("data", () => {
30417
+ entry.lastOutputAt = Date.now();
30418
+ for (const cb of outputCallbacks)
30419
+ cb();
30420
+ });
30421
+ }
30422
+ if (childProcess.stderr) {
30423
+ childProcess.stderr.on("data", () => {
30424
+ entry.lastOutputAt = Date.now();
30425
+ for (const cb of outputCallbacks)
30426
+ cb();
30427
+ });
30428
+ }
30429
+ return {
30430
+ pid,
30431
+ harnessSessionId: sessionId,
30432
+ harnessReconnect: {
30433
+ agentName,
30434
+ ...model ? { model } : {}
30435
+ },
30436
+ onExit: (cb) => {
30437
+ childProcess.on("exit", (code2, signal) => {
30438
+ const fwd = this.forwarders.get(pid);
30439
+ if (fwd) {
30440
+ fwd.stop();
30441
+ this.forwarders.delete(pid);
30442
+ }
30443
+ this.sessionStore.remove(sessionId);
30444
+ this.deleteProcess(pid);
30445
+ cb({ code: code2, signal, context: context4 });
30446
+ });
30447
+ },
30448
+ onOutput: (cb) => {
30449
+ outputCallbacks.push(cb);
30450
+ },
30451
+ onAgentEnd: (cb) => {
30452
+ forwarder?.onAgentEnd(cb);
30453
+ }
30454
+ };
30455
+ }
30456
+ async resumeFromDaemonMemory(options, session) {
30457
+ const { prompt, systemPrompt, model, context: context4 } = options;
30458
+ const sessionId = session.harnessSessionId;
30459
+ const agentName = session.agentName;
30460
+ const modelForSession = model ?? session.model;
30461
+ const workingDir = session.workingDir;
30462
+ const childProcess = this.spawnServeProcess(workingDir);
30463
+ const pid = childProcess.pid;
30464
+ const baseUrl = await waitForListeningUrl(childProcess, {
30465
+ timeoutMs: SERVE_STARTUP_TIMEOUT_MS
30466
+ }).catch((err) => {
30467
+ childProcess.kill();
30468
+ throw err;
30469
+ });
30470
+ const client4 = createOpencodeClient({ baseUrl });
30471
+ let forwarder;
30472
+ try {
30473
+ const sessionInfo = await withTimeout2(client4.session.get({ path: { id: sessionId } }), SESSION_GET_TIMEOUT_MS, "session.get");
30474
+ if (!sessionInfo.data?.id) {
30475
+ throw new Error(`OpenCode session ${sessionId} not found (sessions may not survive serve restart)`);
30476
+ }
30477
+ forwarder = startSessionEventForwarder(client4, {
30478
+ sessionId,
30479
+ role: context4.role
30480
+ });
30481
+ const agentsResponse = await withTimeout2(client4.app.agents(), AGENTS_LIST_TIMEOUT_MS, "app.agents");
30482
+ const availableAgents = agentsResponse.data ?? [];
30483
+ const agentDef = availableAgents.find((a) => a.name === agentName);
30484
+ const composedSystem = composeSystemPrompt(agentDef?.prompt, systemPrompt);
30485
+ const modelParts = modelForSession ? parseModelId(modelForSession) : undefined;
30486
+ await withTimeout2(client4.session.promptAsync({
30487
+ path: { id: sessionId },
30488
+ body: {
30489
+ agent: agentName,
30490
+ ...composedSystem ? { system: composedSystem } : {},
30491
+ parts: [{ type: "text", text: prompt }],
30492
+ ...modelParts ? { model: modelParts } : {},
30493
+ tools: {
30494
+ task: false,
30495
+ question: false,
30496
+ external_directory: false
30497
+ }
30498
+ }
30499
+ }), PROMPT_ASYNC_TIMEOUT_MS, "session.promptAsync");
30500
+ } catch (err) {
30501
+ const reason = err instanceof Error ? err.message : String(err);
30502
+ process.stderr.write(`[${new Date().toISOString()}] role:${context4.role} resume-error] ${reason}
30503
+ `);
30504
+ forwarder?.stop();
30505
+ childProcess.kill();
30506
+ throw err;
30507
+ }
30508
+ return this.registerRunningSession({
30509
+ childProcess,
30510
+ pid,
30511
+ sessionId,
30512
+ context: context4,
30513
+ forwarder,
30514
+ baseUrl,
30515
+ agentName,
30516
+ model: modelForSession,
30517
+ workingDir
30518
+ });
30519
+ }
30520
+ async spawn(options) {
30521
+ const { prompt, systemPrompt, model, context: context4 } = options;
30522
+ const childProcess = this.spawnServeProcess(options.workingDir);
30163
30523
  const pid = childProcess.pid;
30164
30524
  const baseUrl = await waitForListeningUrl(childProcess, {
30165
30525
  timeoutMs: SERVE_STARTUP_TIMEOUT_MS
@@ -30213,59 +30573,26 @@ var init_opencode_sdk_agent_service = __esm(() => {
30213
30573
  this.sessionStore.remove(sessionId);
30214
30574
  throw err;
30215
30575
  }
30216
- const meta = {
30576
+ return this.registerRunningSession({
30577
+ childProcess,
30578
+ pid,
30217
30579
  sessionId,
30218
- machineId: context4.machineId,
30219
- chatroomId: context4.chatroomId,
30220
- role: context4.role,
30580
+ context: context4,
30581
+ forwarder,
30582
+ baseUrl,
30221
30583
  agentName,
30222
- ...model ? { model } : {},
30223
- pid,
30224
- createdAt: new Date().toISOString(),
30225
- baseUrl
30226
- };
30227
- this.sessionStore.upsert(meta);
30228
- const entry = this.registerProcess(pid, context4);
30229
- if (forwarder)
30230
- this.forwarders.set(pid, forwarder);
30231
- const outputCallbacks = [];
30232
- forwardFiltered(childProcess.stdout, process.stdout, isInfoLine);
30233
- forwardFiltered(childProcess.stderr, process.stderr, isInfoLine);
30234
- if (childProcess.stdout) {
30235
- childProcess.stdout.on("data", () => {
30236
- entry.lastOutputAt = Date.now();
30237
- for (const cb of outputCallbacks)
30238
- cb();
30239
- });
30240
- }
30241
- if (childProcess.stderr) {
30242
- childProcess.stderr.on("data", () => {
30243
- entry.lastOutputAt = Date.now();
30244
- for (const cb of outputCallbacks)
30245
- cb();
30246
- });
30584
+ model,
30585
+ workingDir: options.workingDir
30586
+ });
30587
+ }
30588
+ getHarnessReconnectContext(pid) {
30589
+ const meta = this.sessionStore.findByPid(pid);
30590
+ if (!meta) {
30591
+ return;
30247
30592
  }
30248
30593
  return {
30249
- pid,
30250
- harnessSessionId: sessionId,
30251
- onExit: (cb) => {
30252
- childProcess.on("exit", (code2, signal) => {
30253
- const fwd = this.forwarders.get(pid);
30254
- if (fwd) {
30255
- fwd.stop();
30256
- this.forwarders.delete(pid);
30257
- }
30258
- this.sessionStore.remove(sessionId);
30259
- this.deleteProcess(pid);
30260
- cb({ code: code2, signal, context: context4 });
30261
- });
30262
- },
30263
- onOutput: (cb) => {
30264
- outputCallbacks.push(cb);
30265
- },
30266
- onAgentEnd: (cb) => {
30267
- forwarder?.onAgentEnd(cb);
30268
- }
30594
+ agentName: meta.agentName,
30595
+ ...meta.model ? { model: meta.model } : {}
30269
30596
  };
30270
30597
  }
30271
30598
  async resumeTurn(pid, prompt) {
@@ -30400,16 +30727,16 @@ var MACHINE_CONFIG_VERSION = "1";
30400
30727
  import { randomUUID as randomUUID2 } from "node:crypto";
30401
30728
  import * as fs2 from "node:fs/promises";
30402
30729
  import { homedir as homedir3, hostname as hostname2 } from "node:os";
30403
- import { join as join8 } from "node:path";
30730
+ import { join as join9 } from "node:path";
30404
30731
  function chatroomConfigDir() {
30405
- return join8(homedir3(), ".chatroom");
30732
+ return join9(homedir3(), ".chatroom");
30406
30733
  }
30407
30734
  async function ensureConfigDir2() {
30408
30735
  const dir = chatroomConfigDir();
30409
30736
  await fs2.mkdir(dir, { recursive: true, mode: 448 });
30410
30737
  }
30411
30738
  function getMachineConfigPath() {
30412
- return join8(chatroomConfigDir(), MACHINE_FILE);
30739
+ return join9(chatroomConfigDir(), MACHINE_FILE);
30413
30740
  }
30414
30741
  async function loadConfigFile() {
30415
30742
  const configPath = getMachineConfigPath();
@@ -30501,7 +30828,7 @@ var init_storage2 = __esm(() => {
30501
30828
  // src/infrastructure/machine/daemon-state.ts
30502
30829
  import * as fs3 from "node:fs/promises";
30503
30830
  import { homedir as homedir4 } from "node:os";
30504
- import { join as join9 } from "node:path";
30831
+ import { join as join10 } from "node:path";
30505
30832
  function agentKey(chatroomId, role) {
30506
30833
  return `${chatroomId}/${role}`;
30507
30834
  }
@@ -30509,7 +30836,7 @@ async function ensureStateDir() {
30509
30836
  await fs3.mkdir(STATE_DIR, { recursive: true, mode: 448 });
30510
30837
  }
30511
30838
  function stateFilePath(machineId) {
30512
- return join9(STATE_DIR, `${machineId}.json`);
30839
+ return join10(STATE_DIR, `${machineId}.json`);
30513
30840
  }
30514
30841
  async function loadDaemonState(machineId) {
30515
30842
  const filePath = stateFilePath(machineId);
@@ -30591,8 +30918,8 @@ async function loadEventCursor(machineId) {
30591
30918
  }
30592
30919
  var CHATROOM_DIR2, STATE_DIR, STATE_VERSION = "1";
30593
30920
  var init_daemon_state = __esm(() => {
30594
- CHATROOM_DIR2 = join9(homedir4(), ".chatroom");
30595
- STATE_DIR = join9(CHATROOM_DIR2, "machines", "state");
30921
+ CHATROOM_DIR2 = join10(homedir4(), ".chatroom");
30922
+ STATE_DIR = join10(CHATROOM_DIR2, "machines", "state");
30596
30923
  });
30597
30924
 
30598
30925
  // src/infrastructure/machine/index.ts
@@ -30965,9 +31292,9 @@ var init_init = __esm(() => {
30965
31292
  });
30966
31293
 
30967
31294
  // src/tools/output.ts
30968
- import { join as join10 } from "node:path";
31295
+ import { join as join11 } from "node:path";
30969
31296
  function resolveChatroomDir(workingDir) {
30970
- return join10(workingDir, CHATROOM_DIR3);
31297
+ return join11(workingDir, CHATROOM_DIR3);
30971
31298
  }
30972
31299
  async function ensureChatroomDir(deps, workingDir) {
30973
31300
  const dir = resolveChatroomDir(workingDir);
@@ -30975,7 +31302,7 @@ async function ensureChatroomDir(deps, workingDir) {
30975
31302
  return dir;
30976
31303
  }
30977
31304
  async function ensureGitignore(deps, workingDir) {
30978
- const gitignorePath = join10(workingDir, ".gitignore");
31305
+ const gitignorePath = join11(workingDir, ".gitignore");
30979
31306
  const entry = CHATROOM_DIR3;
30980
31307
  let content = "";
30981
31308
  try {
@@ -31005,7 +31332,7 @@ function formatOutputTimestamp(date = new Date) {
31005
31332
  function generateOutputPath(workingDir, toolName, extension, date) {
31006
31333
  const timestamp = formatOutputTimestamp(date);
31007
31334
  const filename = `${toolName}-${timestamp}.${extension}`;
31008
- return join10(resolveChatroomDir(workingDir), filename);
31335
+ return join11(resolveChatroomDir(workingDir), filename);
31009
31336
  }
31010
31337
  var CHATROOM_DIR3 = ".chatroom";
31011
31338
  var init_output = () => {};
@@ -79408,20 +79735,20 @@ function formatTimestamp() {
79408
79735
 
79409
79736
  // src/infrastructure/services/workspace/workspace-resolver.ts
79410
79737
  import { readFile as readFile5, readdir, stat } from "node:fs/promises";
79411
- import { join as join12, basename, resolve as resolve3 } from "node:path";
79738
+ import { join as join13, basename, resolve as resolve3 } from "node:path";
79412
79739
  async function resolveGlobPattern(rootDir, pattern) {
79413
79740
  const cleaned = pattern.replace(/\/+$/, "");
79414
79741
  if (cleaned.includes("..")) {
79415
79742
  return [];
79416
79743
  }
79417
79744
  if (cleaned.endsWith("/*")) {
79418
- const parentDir = join12(rootDir, cleaned.slice(0, -2));
79745
+ const parentDir = join13(rootDir, cleaned.slice(0, -2));
79419
79746
  try {
79420
79747
  const entries2 = await readdir(parentDir, { withFileTypes: true });
79421
79748
  const dirs = [];
79422
79749
  for (const entry of entries2) {
79423
79750
  if (entry.isDirectory()) {
79424
- const dirPath = join12(parentDir, entry.name);
79751
+ const dirPath = join13(parentDir, entry.name);
79425
79752
  if (resolve3(dirPath).startsWith(resolve3(rootDir))) {
79426
79753
  dirs.push(dirPath);
79427
79754
  }
@@ -79432,7 +79759,7 @@ async function resolveGlobPattern(rootDir, pattern) {
79432
79759
  return [];
79433
79760
  }
79434
79761
  } else {
79435
- const dir = join12(rootDir, cleaned);
79762
+ const dir = join13(rootDir, cleaned);
79436
79763
  try {
79437
79764
  if (!resolve3(dir).startsWith(resolve3(rootDir)))
79438
79765
  return [];
@@ -79445,7 +79772,7 @@ async function resolveGlobPattern(rootDir, pattern) {
79445
79772
  }
79446
79773
  async function readPackageJson(dir) {
79447
79774
  try {
79448
- const content = await readFile5(join12(dir, "package.json"), "utf-8");
79775
+ const content = await readFile5(join13(dir, "package.json"), "utf-8");
79449
79776
  const pkg = JSON.parse(content);
79450
79777
  return {
79451
79778
  name: pkg.name || basename(dir),
@@ -79457,7 +79784,7 @@ async function readPackageJson(dir) {
79457
79784
  }
79458
79785
  async function readPnpmWorkspacePatterns(rootDir) {
79459
79786
  try {
79460
- const content = await readFile5(join12(rootDir, "pnpm-workspace.yaml"), "utf-8");
79787
+ const content = await readFile5(join13(rootDir, "pnpm-workspace.yaml"), "utf-8");
79461
79788
  const patterns = [];
79462
79789
  let inPackages = false;
79463
79790
  for (const line of content.split(`
@@ -79484,7 +79811,7 @@ async function readPnpmWorkspacePatterns(rootDir) {
79484
79811
  }
79485
79812
  async function readPackageJsonWorkspacePatterns(rootDir) {
79486
79813
  try {
79487
- const content = await readFile5(join12(rootDir, "package.json"), "utf-8");
79814
+ const content = await readFile5(join13(rootDir, "package.json"), "utf-8");
79488
79815
  const pkg = JSON.parse(content);
79489
79816
  if (!pkg.workspaces)
79490
79817
  return [];
@@ -79533,11 +79860,11 @@ var init_workspace_resolver = () => {};
79533
79860
 
79534
79861
  // src/infrastructure/services/workspace/command-discovery.ts
79535
79862
  import { access as access2, readFile as readFile6 } from "node:fs/promises";
79536
- import { join as join13, relative, basename as basename2 } from "node:path";
79863
+ import { join as join14, relative, basename as basename2 } from "node:path";
79537
79864
  async function detectPackageManager(workingDir) {
79538
79865
  for (const { file, manager } of LOCKFILE_MAP) {
79539
79866
  try {
79540
- await access2(join13(workingDir, file));
79867
+ await access2(join14(workingDir, file));
79541
79868
  return manager;
79542
79869
  } catch {}
79543
79870
  }
@@ -79584,7 +79911,7 @@ async function discoverCommands(workingDir) {
79584
79911
  const turboTaskNames = [];
79585
79912
  let rootPackageName = basename2(workingDir);
79586
79913
  try {
79587
- const pkgPath = join13(workingDir, "package.json");
79914
+ const pkgPath = join14(workingDir, "package.json");
79588
79915
  const pkgContent = await readFile6(pkgPath, "utf-8");
79589
79916
  const pkg = JSON.parse(pkgContent);
79590
79917
  if (pkg.name)
@@ -79605,7 +79932,7 @@ async function discoverCommands(workingDir) {
79605
79932
  } catch {}
79606
79933
  const rootSubWorkspace = { type: "npm", path: ".", name: rootPackageName };
79607
79934
  try {
79608
- const turboPath = join13(workingDir, "turbo.json");
79935
+ const turboPath = join14(workingDir, "turbo.json");
79609
79936
  const turboContent = await readFile6(turboPath, "utf-8");
79610
79937
  const turbo = JSON.parse(turboContent);
79611
79938
  if (turbo.tasks && typeof turbo.tasks === "object") {
@@ -79659,19 +79986,43 @@ var init_command_discovery = __esm(() => {
79659
79986
  ];
79660
79987
  });
79661
79988
 
79662
- // src/commands/machine/daemon-start/command-sync-heartbeat.ts
79663
- import { createHash as createHash2 } from "node:crypto";
79664
- async function pushCommands(ctx) {
79665
- let workspaces;
79989
+ // src/commands/machine/daemon-start/workspace-cache.ts
79990
+ function invalidateWorkspacesForMachineCache(ctx) {
79991
+ delete ctx._workspacesCache;
79992
+ }
79993
+ async function getWorkspacesForMachine(ctx) {
79994
+ const extended = ctx;
79995
+ const now = Date.now();
79996
+ const cached4 = extended._workspacesCache;
79997
+ if (cached4 && now - cached4.fetchedAt < CACHE_TTL_MS) {
79998
+ return cached4.workspaces;
79999
+ }
79666
80000
  try {
79667
- workspaces = await ctx.deps.backend.query(api.workspaces.listWorkspacesForMachine, {
80001
+ const workspaces = await ctx.deps.backend.query(api.workspaces.listWorkspacesForMachine, {
79668
80002
  sessionId: ctx.sessionId,
79669
80003
  machineId: ctx.machineId
79670
80004
  });
80005
+ extended._workspacesCache = { fetchedAt: now, workspaces };
80006
+ return workspaces;
79671
80007
  } catch (err) {
79672
- console.warn(`[${formatTimestamp()}] ⚠️ Failed to query workspaces for command sync: ${getErrorMessage(err)}`);
79673
- return;
80008
+ console.warn(`[${formatTimestamp()}] ⚠️ Failed to query workspaces: ${getErrorMessage(err)}`);
80009
+ return [];
79674
80010
  }
80011
+ }
80012
+ var CACHE_TTL_MS;
80013
+ var init_workspace_cache = __esm(() => {
80014
+ init_reliability();
80015
+ init_api3();
80016
+ init_convex_error();
80017
+ CACHE_TTL_MS = DAEMON_HEARTBEAT_INTERVAL_MS;
80018
+ });
80019
+
80020
+ // src/commands/machine/daemon-start/command-sync-heartbeat.ts
80021
+ import { createHash as createHash2 } from "node:crypto";
80022
+ async function pushCommands(ctx) {
80023
+ const workspaces = await getWorkspacesForMachine(ctx);
80024
+ if (workspaces.length === 0)
80025
+ return;
79675
80026
  const uniqueWorkingDirs = new Set(workspaces.map((ws) => ws.workingDir));
79676
80027
  if (uniqueWorkingDirs.size === 0)
79677
80028
  return;
@@ -79703,6 +80054,7 @@ var init_command_sync_heartbeat = __esm(() => {
79703
80054
  init_api3();
79704
80055
  init_command_discovery();
79705
80056
  init_convex_error();
80057
+ init_workspace_cache();
79706
80058
  });
79707
80059
 
79708
80060
  // src/events/daemon/agent/on-request-start-agent.ts
@@ -79719,7 +80071,8 @@ async function onRequestStartAgent(ctx, event) {
79719
80071
  agentHarness: event.agentHarness,
79720
80072
  model: event.model,
79721
80073
  workingDir: event.workingDir,
79722
- reason: event.reason
80074
+ reason: event.reason,
80075
+ wantResume: event.wantResume ?? true
79723
80076
  });
79724
80077
  if (!result.success) {
79725
80078
  console.log(`[daemon] Agent start rejected for role=${event.role}: ${result.error ?? "unknown"}`);
@@ -79786,7 +80139,7 @@ var init_on_request_stop_agent = () => {};
79786
80139
  import { createHash as createHash3 } from "node:crypto";
79787
80140
  import { existsSync as existsSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync4 } from "node:fs";
79788
80141
  import { homedir as homedir5 } from "node:os";
79789
- import { join as join14 } from "node:path";
80142
+ import { join as join15 } from "node:path";
79790
80143
  function getUrlHash() {
79791
80144
  const url2 = getConvexUrl();
79792
80145
  return createHash3("sha256").update(url2).digest("hex").substring(0, 8);
@@ -79800,7 +80153,7 @@ function ensureChatroomDir2() {
79800
80153
  }
79801
80154
  }
79802
80155
  function getPidFilePath() {
79803
- return join14(CHATROOM_DIR4, getPidFileName());
80156
+ return join15(CHATROOM_DIR4, getPidFileName());
79804
80157
  }
79805
80158
  function isProcessRunning(pid) {
79806
80159
  try {
@@ -79865,7 +80218,7 @@ function releaseLock() {
79865
80218
  var CHATROOM_DIR4;
79866
80219
  var init_pid = __esm(() => {
79867
80220
  init_client2();
79868
- CHATROOM_DIR4 = join14(homedir5(), ".chatroom");
80221
+ CHATROOM_DIR4 = join15(homedir5(), ".chatroom");
79869
80222
  });
79870
80223
 
79871
80224
  // src/infrastructure/git/types.ts
@@ -80251,6 +80604,13 @@ async function getCommitsAhead(workingDir) {
80251
80604
  const count3 = parseInt(result.stdout.trim(), 10);
80252
80605
  return Number.isNaN(count3) ? 0 : count3;
80253
80606
  }
80607
+ async function getCommitsBehind(workingDir) {
80608
+ const result = await runGit("rev-list --count HEAD..@{upstream}", workingDir);
80609
+ if ("error" in result)
80610
+ return 0;
80611
+ const count3 = parseInt(result.stdout.trim(), 10);
80612
+ return Number.isNaN(count3) ? 0 : count3;
80613
+ }
80254
80614
  async function getCommitStatusChecks(cwd, ref) {
80255
80615
  const repoSlug = await getOriginRepoSlug(cwd);
80256
80616
  if (!repoSlug)
@@ -80387,16 +80747,9 @@ function makeBranchDependentFields(branch) {
80387
80747
  ];
80388
80748
  }
80389
80749
  async function pushGitState(ctx) {
80390
- let workspaces;
80391
- try {
80392
- workspaces = await ctx.deps.backend.query(api.workspaces.listWorkspacesForMachine, {
80393
- sessionId: ctx.sessionId,
80394
- machineId: ctx.machineId
80395
- });
80396
- } catch (err) {
80397
- console.warn(`[${formatTimestamp()}] ⚠️ Failed to query workspaces for git sync: ${getErrorMessage(err)}`);
80750
+ const workspaces = await getWorkspacesForMachine(ctx);
80751
+ if (workspaces.length === 0)
80398
80752
  return;
80399
- }
80400
80753
  const uniqueWorkingDirs = new Set(workspaces.map((ws) => ws.workingDir));
80401
80754
  if (uniqueWorkingDirs.size === 0)
80402
80755
  return;
@@ -80547,6 +80900,7 @@ var lastFullPushMs, branchField, GIT_STATE_FIELDS;
80547
80900
  var init_git_heartbeat = __esm(() => {
80548
80901
  init_reliability();
80549
80902
  init_api3();
80903
+ init_workspace_cache();
80550
80904
  init_git_reader();
80551
80905
  init_git_state_pipeline();
80552
80906
  init_convex_error();
@@ -80600,6 +80954,14 @@ var init_git_heartbeat = __esm(() => {
80600
80954
  toMutationPartial: (raw) => ({ commitsAhead: raw }),
80601
80955
  defaultValue: 0
80602
80956
  },
80957
+ {
80958
+ key: "commitsBehind",
80959
+ includeInSlim: false,
80960
+ collect: (wd) => getCommitsBehind(wd),
80961
+ toHashable: (raw) => raw,
80962
+ toMutationPartial: (raw) => ({ commitsBehind: raw }),
80963
+ defaultValue: 0
80964
+ },
80603
80965
  {
80604
80966
  key: "remotes",
80605
80967
  includeInSlim: false,
@@ -80940,16 +81302,9 @@ var init_git_subscription = __esm(() => {
80940
81302
 
80941
81303
  // src/commands/machine/daemon-start/commit-detail-sync.ts
80942
81304
  async function syncCommitDetails(ctx, seenShasMap) {
80943
- let workspaces;
80944
- try {
80945
- workspaces = await ctx.deps.backend.query(api.workspaces.listWorkspacesForMachine, {
80946
- sessionId: ctx.sessionId,
80947
- machineId: ctx.machineId
80948
- });
80949
- } catch (err) {
80950
- console.warn(`[${formatTimestamp()}] ⚠️ Failed to query workspaces for commit-detail sync: ${getErrorMessage(err)}`);
81305
+ const workspaces = await getWorkspacesForMachine(ctx);
81306
+ if (workspaces.length === 0)
80951
81307
  return;
80952
- }
80953
81308
  const uniqueWorkingDirs = new Set(workspaces.map((ws) => ws.workingDir));
80954
81309
  if (uniqueWorkingDirs.size === 0)
80955
81310
  return;
@@ -81054,6 +81409,7 @@ var init_commit_detail_sync = __esm(() => {
81054
81409
  init_api3();
81055
81410
  init_git_reader();
81056
81411
  init_convex_error();
81412
+ init_workspace_cache();
81057
81413
  seenShas = new Map;
81058
81414
  });
81059
81415
 
@@ -82480,17 +82836,17 @@ import {
82480
82836
  writeFileSync as writeFileSync4
82481
82837
  } from "node:fs";
82482
82838
  import { homedir as homedir6 } from "node:os";
82483
- import { join as join16 } from "node:path";
82839
+ import { join as join17 } from "node:path";
82484
82840
  function getUrlHash2() {
82485
82841
  const url2 = getConvexUrl();
82486
82842
  return createHash6("sha256").update(url2).digest("hex").substring(0, 8);
82487
82843
  }
82488
82844
  function getChildPidsFilePath() {
82489
- const dir = join16(homedir6(), ".chatroom");
82490
- return join16(dir, `daemon-children-${getUrlHash2()}.pids`);
82845
+ const dir = join17(homedir6(), ".chatroom");
82846
+ return join17(dir, `daemon-children-${getUrlHash2()}.pids`);
82491
82847
  }
82492
82848
  function ensureChatroomDir3() {
82493
- const dir = join16(homedir6(), ".chatroom");
82849
+ const dir = join17(homedir6(), ".chatroom");
82494
82850
  if (!existsSync3(dir)) {
82495
82851
  mkdirSync5(dir, { recursive: true, mode: 448 });
82496
82852
  }
@@ -82584,7 +82940,7 @@ async function reapOrphanedProcessGroups() {
82584
82940
  var CHATROOM_DIR5;
82585
82941
  var init_orphan_tracker = __esm(() => {
82586
82942
  init_client2();
82587
- CHATROOM_DIR5 = join16(homedir6(), ".chatroom");
82943
+ CHATROOM_DIR5 = join17(homedir6(), ".chatroom");
82588
82944
  });
82589
82945
 
82590
82946
  // src/commands/machine/daemon-start/handlers/process/state.ts
@@ -82722,7 +83078,7 @@ var init_killer = __esm(() => {
82722
83078
  // src/commands/machine/daemon-start/handlers/process/output-store.ts
82723
83079
  import { appendFile as appendFile2, mkdir as mkdir6, readFile as readFile8, rm } from "node:fs/promises";
82724
83080
  import { tmpdir } from "node:os";
82725
- import { join as join17 } from "node:path";
83081
+ import { join as join18 } from "node:path";
82726
83082
 
82727
83083
  class TempFileOutputStore {
82728
83084
  state;
@@ -82785,7 +83141,7 @@ function createOutputStore(runId) {
82785
83141
  if (!RUN_ID_RE.test(runId)) {
82786
83142
  throw new Error(`Invalid runId: ${runId}`);
82787
83143
  }
82788
- const filePath = join17(TEMP_DIR, `${runId}.log`);
83144
+ const filePath = join18(TEMP_DIR, `${runId}.log`);
82789
83145
  return new TempFileOutputStore(filePath);
82790
83146
  }
82791
83147
  async function ensureTempDir() {
@@ -82799,7 +83155,7 @@ async function cleanOrphanTempFiles() {
82799
83155
  var TAIL_WINDOW_BYTES, TEMP_DIR, RUN_ID_RE, MAX_TAIL_LINES_V2 = 50;
82800
83156
  var init_output_store = __esm(() => {
82801
83157
  TAIL_WINDOW_BYTES = 32 * 1024;
82802
- TEMP_DIR = join17(tmpdir(), "chatroom-cli", "runs");
83158
+ TEMP_DIR = join18(tmpdir(), "chatroom-cli", "runs");
82803
83159
  RUN_ID_RE = /^[a-z0-9]+$/i;
82804
83160
  });
82805
83161
 
@@ -82815,9 +83171,28 @@ function consumePendingFullSync(runId) {
82815
83171
  }
82816
83172
  function startLogObserverPoll(ctx) {
82817
83173
  let stopped = false;
82818
- const poll4 = async () => {
83174
+ let consecutiveIdlePolls = 0;
83175
+ let pollInFlight = false;
83176
+ let timeoutHandle = null;
83177
+ const scheduleNext = (delayMs) => {
82819
83178
  if (stopped)
82820
83179
  return;
83180
+ if (timeoutHandle)
83181
+ clearTimeout(timeoutHandle);
83182
+ timeoutHandle = setTimeout(() => {
83183
+ poll4();
83184
+ }, delayMs);
83185
+ timeoutHandle.unref?.();
83186
+ };
83187
+ const poll4 = async () => {
83188
+ if (stopped || pollInFlight)
83189
+ return;
83190
+ const hasLocalObservers = observedRunIds.size > 0;
83191
+ if (!hasLocalObservers && consecutiveIdlePolls >= IDLE_SKIP_AFTER_CONSECUTIVE) {
83192
+ scheduleNext(IDLE_POLL_INTERVAL_MS);
83193
+ return;
83194
+ }
83195
+ pollInFlight = true;
82821
83196
  try {
82822
83197
  const runs = await ctx.deps.backend.query(api.commands.listRunsWithLogObservers, {
82823
83198
  sessionId: ctx.sessionId,
@@ -82831,31 +83206,40 @@ function startLogObserverPoll(ctx) {
82831
83206
  pendingFullSyncRunIds.add(run3._id);
82832
83207
  }
82833
83208
  }
83209
+ const isActive2 = runs.length > 0 || hasLocalObservers;
83210
+ if (isActive2) {
83211
+ consecutiveIdlePolls = 0;
83212
+ scheduleNext(ACTIVE_POLL_INTERVAL_MS);
83213
+ } else {
83214
+ consecutiveIdlePolls++;
83215
+ scheduleNext(consecutiveIdlePolls >= IDLE_SKIP_AFTER_CONSECUTIVE ? IDLE_POLL_INTERVAL_MS : ACTIVE_POLL_INTERVAL_MS);
83216
+ }
82834
83217
  } catch (err) {
82835
83218
  console.warn(`[${formatTimestamp()}] ⚠️ Log-observer poll failed: ${getErrorMessage(err)}`);
83219
+ scheduleNext(ACTIVE_POLL_INTERVAL_MS);
83220
+ } finally {
83221
+ pollInFlight = false;
82836
83222
  }
82837
83223
  };
82838
83224
  poll4();
82839
- const handle = setInterval(() => {
82840
- poll4();
82841
- }, OUTPUT_FLUSH_INTERVAL_MS);
82842
- handle.unref?.();
82843
83225
  return {
82844
83226
  stop: () => {
82845
83227
  stopped = true;
82846
- clearInterval(handle);
83228
+ if (timeoutHandle)
83229
+ clearTimeout(timeoutHandle);
82847
83230
  observedRunIds.clear();
82848
83231
  pendingFullSyncRunIds.clear();
82849
83232
  }
82850
83233
  };
82851
83234
  }
82852
- var observedRunIds, pendingFullSyncRunIds;
83235
+ var observedRunIds, pendingFullSyncRunIds, ACTIVE_POLL_INTERVAL_MS, IDLE_POLL_INTERVAL_MS = 15000, IDLE_SKIP_AFTER_CONSECUTIVE = 3;
82853
83236
  var init_log_observer_sync = __esm(() => {
82854
83237
  init_api3();
82855
83238
  init_convex_error();
82856
83239
  init_state2();
82857
83240
  observedRunIds = new Set;
82858
83241
  pendingFullSyncRunIds = new Set;
83242
+ ACTIVE_POLL_INTERVAL_MS = OUTPUT_FLUSH_INTERVAL_MS;
82859
83243
  });
82860
83244
 
82861
83245
  // src/commands/machine/daemon-start/handlers/process/spawner.ts
@@ -83549,6 +83933,7 @@ function agentKey2(chatroomId, role) {
83549
83933
  class AgentProcessManager {
83550
83934
  deps;
83551
83935
  slots = new Map;
83936
+ lastHarnessSessions = new Map;
83552
83937
  exitRetryQueue = [];
83553
83938
  exitRetryTimer = null;
83554
83939
  constructor(deps) {
@@ -83622,11 +84007,20 @@ class AgentProcessManager {
83622
84007
  return { success: true };
83623
84008
  }
83624
84009
  async handleAgentEnd(opts) {
84010
+ const key = agentKey2(opts.chatroomId, opts.role);
84011
+ const slot = this.slots.get(key);
84012
+ if (slot?.resumeInFlight) {
84013
+ console.log(`[AgentProcessManager] agent_end: skipping duplicate resume for ${opts.role} (resume already in flight)`);
84014
+ return;
84015
+ }
83625
84016
  const capabilities = getHarnessCapabilities(opts.harness);
83626
- console.log(`[AgentProcessManager] agent_end: role=${opts.role} pid=${opts.pid} harness=${opts.harness} wantResume=${opts.wantResume} supportsResume=${capabilities.supportsSessionResume}`);
83627
- if (capabilities.supportsSessionResume && opts.wantResume) {
84017
+ console.log(`[AgentProcessManager] agent_end: role=${opts.role} pid=${opts.pid} harness=${opts.harness} supportsResume=${capabilities.supportsSessionResume}`);
84018
+ if (capabilities.supportsSessionResume) {
83628
84019
  const service = this.deps.agentServices.get(opts.harness);
83629
84020
  if (service?.resumeTurn) {
84021
+ if (slot) {
84022
+ slot.resumeInFlight = true;
84023
+ }
83630
84024
  try {
83631
84025
  const resumePrompt = composeResumeMessage({
83632
84026
  chatroomId: opts.chatroomId,
@@ -83634,9 +84028,39 @@ class AgentProcessManager {
83634
84028
  convexUrl: this.deps.convexUrl
83635
84029
  });
83636
84030
  await service.resumeTurn(opts.pid, resumePrompt);
84031
+ try {
84032
+ await this.deps.backend.mutation(api.machines.emitSessionResumed, {
84033
+ sessionId: this.deps.sessionId,
84034
+ machineId: this.deps.machineId,
84035
+ chatroomId: opts.chatroomId,
84036
+ role: opts.role,
84037
+ ...slot?.harnessSessionId ? { harnessSessionId: slot.harnessSessionId } : {}
84038
+ });
84039
+ console.log(`[AgentProcessManager] ✅ Emitted agent.sessionResumed for ${opts.role}`);
84040
+ } catch (err) {
84041
+ console.log(` ⚠️ Failed to emit sessionResumed event: ${err.message}`);
84042
+ }
83637
84043
  return;
83638
84044
  } catch (err) {
83639
- console.log(`[AgentProcessManager] ⚠️ resumeTurn failed for ${opts.role} (pid ${opts.pid}): ${err.message} — falling back to kill`);
84045
+ const reason = err.message;
84046
+ try {
84047
+ await this.deps.backend.mutation(api.machines.emitSessionResumeFailed, {
84048
+ sessionId: this.deps.sessionId,
84049
+ machineId: this.deps.machineId,
84050
+ chatroomId: opts.chatroomId,
84051
+ role: opts.role,
84052
+ reason,
84053
+ ...slot?.harnessSessionId ? { harnessSessionId: slot.harnessSessionId } : {}
84054
+ });
84055
+ console.log(`[AgentProcessManager] ✅ Emitted agent.sessionResumeFailed for ${opts.role}`);
84056
+ } catch (emitErr) {
84057
+ console.log(` ⚠️ Failed to emit sessionResumeFailed event: ${emitErr.message}`);
84058
+ }
84059
+ console.log(`[AgentProcessManager] ⚠️ resumeTurn failed for ${opts.role} (pid ${opts.pid}): ${reason} — falling back to kill`);
84060
+ } finally {
84061
+ if (slot) {
84062
+ slot.resumeInFlight = false;
84063
+ }
83640
84064
  }
83641
84065
  }
83642
84066
  }
@@ -83773,7 +84197,7 @@ class AgentProcessManager {
83773
84197
  getOrCreateSlot(key) {
83774
84198
  let slot = this.slots.get(key);
83775
84199
  if (!slot) {
83776
- slot = { state: "idle", wantResume: true };
84200
+ slot = { state: "idle" };
83777
84201
  this.slots.set(key, slot);
83778
84202
  }
83779
84203
  return slot;
@@ -83847,7 +84271,15 @@ class AgentProcessManager {
83847
84271
  async executeEnsureRunning(key, slot, opts) {
83848
84272
  try {
83849
84273
  await this.killExistingBeforeSpawn(opts.chatroomId, opts.role);
83850
- return await this.doEnsureRunning(key, slot, opts);
84274
+ const result = await this.doEnsureRunning(key, slot, opts);
84275
+ if (!result.success && opts.reason === "platform.auto_restart_on_new_context") {
84276
+ console.log(`[AgentProcessManager] Context auto-restart failed (${result.error ?? "unknown"}), ` + `attempting crash recovery (rate-limited)`);
84277
+ return await this.doEnsureRunning(key, slot, {
84278
+ ...opts,
84279
+ reason: "platform.crash_recovery"
84280
+ });
84281
+ }
84282
+ return result;
83851
84283
  } finally {
83852
84284
  if (slot.pendingOperation) {
83853
84285
  slot.pendingOperation = undefined;
@@ -83887,10 +84319,87 @@ class AgentProcessManager {
83887
84319
  this.exitRetryTimer = null;
83888
84320
  }
83889
84321
  }
84322
+ async tryDaemonMemoryResume(opts) {
84323
+ const capabilities = getHarnessCapabilities(opts.agentHarness);
84324
+ if (!capabilities.supportsSessionResume) {
84325
+ return null;
84326
+ }
84327
+ const stored = this.lastHarnessSessions.get(opts.key);
84328
+ if (!stored) {
84329
+ return null;
84330
+ }
84331
+ if (stored.workingDir !== opts.workingDir) {
84332
+ this.clearLastHarnessSession(opts.key);
84333
+ await this.emitSessionResumeFailed(opts.chatroomId, opts.role, "working directory changed", stored.harnessSessionId);
84334
+ return null;
84335
+ }
84336
+ if (stored.harness !== opts.agentHarness || !stored.agentName) {
84337
+ this.clearLastHarnessSession(opts.key);
84338
+ await this.emitSessionResumeFailed(opts.chatroomId, opts.role, stored.harness !== opts.agentHarness ? "harness changed" : "incomplete session in daemon memory", stored.harnessSessionId);
84339
+ return null;
84340
+ }
84341
+ if (!opts.service.resumeFromDaemonMemory) {
84342
+ await this.emitSessionResumeFailed(opts.chatroomId, opts.role, "daemon-memory session resume not yet supported", stored.harnessSessionId);
84343
+ return null;
84344
+ }
84345
+ try {
84346
+ const spawnResult = await opts.service.resumeFromDaemonMemory({
84347
+ workingDir: stored.workingDir,
84348
+ prompt: createSpawnPrompt(opts.initPrompt),
84349
+ systemPrompt: opts.systemPrompt,
84350
+ model: opts.model ?? stored.model,
84351
+ context: {
84352
+ machineId: this.deps.machineId,
84353
+ chatroomId: opts.chatroomId,
84354
+ role: opts.role
84355
+ }
84356
+ }, {
84357
+ harnessSessionId: stored.harnessSessionId,
84358
+ agentName: stored.agentName,
84359
+ workingDir: stored.workingDir,
84360
+ model: stored.model
84361
+ });
84362
+ await this.emitSessionResumed(opts.chatroomId, opts.role, stored.harnessSessionId);
84363
+ return spawnResult;
84364
+ } catch (err) {
84365
+ const reason = err instanceof Error ? err.message : String(err);
84366
+ await this.emitSessionResumeFailed(opts.chatroomId, opts.role, reason, stored.harnessSessionId);
84367
+ return null;
84368
+ }
84369
+ }
84370
+ async emitSessionResumed(chatroomId, role, harnessSessionId) {
84371
+ try {
84372
+ await this.deps.backend.mutation(api.machines.emitSessionResumed, {
84373
+ sessionId: this.deps.sessionId,
84374
+ machineId: this.deps.machineId,
84375
+ chatroomId,
84376
+ role,
84377
+ ...harnessSessionId ? { harnessSessionId } : {}
84378
+ });
84379
+ console.log(`[AgentProcessManager] ✅ Emitted agent.sessionResumed for ${role}`);
84380
+ } catch (err) {
84381
+ console.log(` ⚠️ Failed to emit sessionResumed event: ${err.message}`);
84382
+ }
84383
+ }
84384
+ async emitSessionResumeFailed(chatroomId, role, reason, harnessSessionId) {
84385
+ try {
84386
+ await this.deps.backend.mutation(api.machines.emitSessionResumeFailed, {
84387
+ sessionId: this.deps.sessionId,
84388
+ machineId: this.deps.machineId,
84389
+ chatroomId,
84390
+ role,
84391
+ reason,
84392
+ ...harnessSessionId ? { harnessSessionId } : {}
84393
+ });
84394
+ console.log(`[AgentProcessManager] ✅ Emitted agent.sessionResumeFailed for ${role}`);
84395
+ } catch (err) {
84396
+ console.log(` ⚠️ Failed to emit sessionResumeFailed event: ${err.message}`);
84397
+ }
84398
+ }
83890
84399
  async doEnsureRunning(key, slot, opts) {
83891
84400
  slot.state = "spawning";
83892
- slot.wantResume = opts.wantResume ?? true;
83893
- console.log(`[AgentProcessManager] harness start: role=${opts.role} harness=${opts.agentHarness} wantResume=${slot.wantResume} reason=${opts.reason}`);
84401
+ const wantResume = opts.wantResume ?? true;
84402
+ console.log(`[AgentProcessManager] harness start: role=${opts.role} harness=${opts.agentHarness} wantResume=${wantResume} reason=${opts.reason}`);
83894
84403
  try {
83895
84404
  const spawnCheck = this.deps.spawning.shouldAllowSpawn(opts.chatroomId, opts.reason, {
83896
84405
  bypassConcurrentLimit: opts.reason.startsWith("user.")
@@ -83964,22 +84473,37 @@ class AgentProcessManager {
83964
84473
  return { success: false, error: `Unknown agent harness: ${opts.agentHarness}` };
83965
84474
  }
83966
84475
  let spawnResult;
83967
- try {
83968
- spawnResult = await service.spawn({
84476
+ if (wantResume) {
84477
+ spawnResult = await this.tryDaemonMemoryResume({
84478
+ key,
84479
+ chatroomId: opts.chatroomId,
84480
+ role: opts.role,
84481
+ agentHarness: opts.agentHarness,
83969
84482
  workingDir: opts.workingDir,
83970
- prompt: createSpawnPrompt(initPromptResult.initialMessage),
83971
- systemPrompt: initPromptResult.rolePrompt,
83972
84483
  model: opts.model,
83973
- context: {
83974
- machineId: this.deps.machineId,
83975
- chatroomId: opts.chatroomId,
83976
- role: opts.role
83977
- }
83978
- });
83979
- } catch (e) {
83980
- slot.state = "idle";
83981
- slot.pendingOperation = undefined;
83982
- return { success: false, error: `Failed to spawn agent: ${e.message}` };
84484
+ initPrompt: initPromptResult.initialMessage,
84485
+ systemPrompt: initPromptResult.rolePrompt,
84486
+ service
84487
+ }) ?? undefined;
84488
+ }
84489
+ if (!spawnResult) {
84490
+ try {
84491
+ spawnResult = await service.spawn({
84492
+ workingDir: opts.workingDir,
84493
+ prompt: createSpawnPrompt(initPromptResult.initialMessage),
84494
+ systemPrompt: initPromptResult.rolePrompt,
84495
+ model: opts.model,
84496
+ context: {
84497
+ machineId: this.deps.machineId,
84498
+ chatroomId: opts.chatroomId,
84499
+ role: opts.role
84500
+ }
84501
+ });
84502
+ } catch (e) {
84503
+ slot.state = "idle";
84504
+ slot.pendingOperation = undefined;
84505
+ return { success: false, error: `Failed to spawn agent: ${e.message}` };
84506
+ }
83983
84507
  }
83984
84508
  const { pid } = spawnResult;
83985
84509
  this.deps.spawning.recordSpawn(opts.chatroomId);
@@ -83987,6 +84511,15 @@ class AgentProcessManager {
83987
84511
  slot.pid = pid;
83988
84512
  slot.harness = opts.agentHarness;
83989
84513
  slot.harnessSessionId = spawnResult.harnessSessionId;
84514
+ if (spawnResult.harnessSessionId) {
84515
+ this.recordLastHarnessSession(key, {
84516
+ harnessSessionId: spawnResult.harnessSessionId,
84517
+ harness: opts.agentHarness,
84518
+ agentName: spawnResult.harnessReconnect?.agentName ?? "",
84519
+ workingDir: opts.workingDir,
84520
+ model: opts.model ?? spawnResult.harnessReconnect?.model
84521
+ });
84522
+ }
83990
84523
  slot.model = opts.model;
83991
84524
  slot.workingDir = opts.workingDir;
83992
84525
  slot.startedAt = this.deps.clock.now();
@@ -83998,7 +84531,8 @@ class AgentProcessManager {
83998
84531
  role: opts.role,
83999
84532
  pid,
84000
84533
  model: opts.model,
84001
- reason: opts.reason
84534
+ reason: opts.reason,
84535
+ ...spawnResult.harnessSessionId ? { harnessSessionId: spawnResult.harnessSessionId } : {}
84002
84536
  }).catch((err) => {
84003
84537
  console.log(` ⚠️ Failed to update PID in backend: ${err.message}`);
84004
84538
  });
@@ -84020,8 +84554,7 @@ class AgentProcessManager {
84020
84554
  chatroomId: opts.chatroomId,
84021
84555
  role: opts.role,
84022
84556
  pid,
84023
- harness: opts.agentHarness,
84024
- wantResume: slot.wantResume
84557
+ harness: opts.agentHarness
84025
84558
  });
84026
84559
  });
84027
84560
  }
@@ -84044,12 +84577,38 @@ class AgentProcessManager {
84044
84577
  return { success: false, error: `Unexpected error: ${e.message}` };
84045
84578
  }
84046
84579
  }
84580
+ recordLastHarnessSession(key, ctx) {
84581
+ this.lastHarnessSessions.set(key, ctx);
84582
+ }
84583
+ clearLastHarnessSession(key) {
84584
+ this.lastHarnessSessions.delete(key);
84585
+ }
84586
+ readHarnessReconnectMetadata(service, pid) {
84587
+ return service.getHarnessReconnectContext?.(pid);
84588
+ }
84047
84589
  async doStop(key, slot, pid, opts) {
84048
84590
  try {
84049
84591
  const harness = slot.harness;
84050
84592
  const service = harness ? this.deps.agentServices.get(harness) : undefined;
84593
+ const preserveForResume = opts.reason === "user.stop" && Boolean(slot.harnessSessionId);
84594
+ if (harness && slot.harnessSessionId) {
84595
+ if (preserveForResume) {
84596
+ const harnessMeta = service ? this.readHarnessReconnectMetadata(service, pid) : undefined;
84597
+ this.recordLastHarnessSession(key, {
84598
+ harnessSessionId: slot.harnessSessionId,
84599
+ harness,
84600
+ agentName: harnessMeta?.agentName ?? "",
84601
+ workingDir: slot.workingDir ?? "",
84602
+ model: slot.model ?? harnessMeta?.model
84603
+ });
84604
+ } else {
84605
+ this.clearLastHarnessSession(key);
84606
+ }
84607
+ } else if (!preserveForResume) {
84608
+ this.clearLastHarnessSession(key);
84609
+ }
84051
84610
  if (service) {
84052
- await service.stop(pid);
84611
+ await service.stop(pid, { preserveForResume });
84053
84612
  service.untrack(pid);
84054
84613
  } else {
84055
84614
  try {
@@ -84121,7 +84680,7 @@ class SpawnRateLimiter {
84121
84680
  this.config = { ...DEFAULT_CONFIG2, ...config3 };
84122
84681
  }
84123
84682
  tryConsume(chatroomId, reason) {
84124
- if (reason.startsWith("user.")) {
84683
+ if (reason.startsWith("user.") || reason === "platform.auto_restart_on_new_context") {
84125
84684
  return { allowed: true };
84126
84685
  }
84127
84686
  const bucket = this._getOrCreateBucket(chatroomId);
@@ -84750,6 +85309,37 @@ async function gitPull(workingDir) {
84750
85309
  }
84751
85310
  return { status: "available" };
84752
85311
  }
85312
+ async function gitPush(workingDir) {
85313
+ const result = await runGit2("push", workingDir);
85314
+ if ("error" in result) {
85315
+ const message = result.error.message;
85316
+ if (message.includes("no upstream branch")) {
85317
+ return {
85318
+ status: "error",
85319
+ message: "No upstream branch configured. Set upstream with `git push -u`."
85320
+ };
85321
+ }
85322
+ if (message.includes("Authentication failed") || message.includes("could not read")) {
85323
+ return {
85324
+ status: "error",
85325
+ message: "Authentication failed. Run `gh auth status` to check your GitHub credentials."
85326
+ };
85327
+ }
85328
+ return classifyError2(message);
85329
+ }
85330
+ const stderr = result.stderr.trim();
85331
+ if (stderr && !stderr.includes("Everything up-to-date")) {
85332
+ return { status: "available", message: stderr };
85333
+ }
85334
+ return { status: "available" };
85335
+ }
85336
+ async function gitSync(workingDir) {
85337
+ const pullResult = await gitPull(workingDir);
85338
+ if (pullResult.status === "error") {
85339
+ return pullResult;
85340
+ }
85341
+ return gitPush(workingDir);
85342
+ }
84753
85343
  var execAsync4;
84754
85344
  var init_git_writer = __esm(() => {
84755
85345
  execAsync4 = promisify5(exec5);
@@ -84855,6 +85445,20 @@ async function executeLocalAction(action, workingDir) {
84855
85445
  }
84856
85446
  return { success: true, message: result.message ?? "Pull successful" };
84857
85447
  }
85448
+ case "git-push": {
85449
+ const result = await gitPush(workingDir);
85450
+ if (result.status === "error") {
85451
+ return { success: false, error: result.message };
85452
+ }
85453
+ return { success: true, message: result.message ?? "Push successful" };
85454
+ }
85455
+ case "git-sync": {
85456
+ const result = await gitSync(workingDir);
85457
+ if (result.status === "error") {
85458
+ return { success: false, error: result.message };
85459
+ }
85460
+ return { success: true, message: result.message ?? "Sync successful" };
85461
+ }
84858
85462
  default: {
84859
85463
  const _exhaustive = action;
84860
85464
  return { success: false, error: `Unknown action: ${_exhaustive}` };
@@ -85297,6 +85901,9 @@ async function dispatchCommandEvent(ctx, event, tracker) {
85297
85901
  const result = await executeLocalAction(event.action, event.workingDir);
85298
85902
  if (!result.success) {
85299
85903
  console.warn(`[${formatTimestamp()}] ⚠️ Local action failed: ${result.error}`);
85904
+ } else if (event.action === "git-pull" || event.action === "git-push" || event.action === "git-sync" || event.action === "git-discard-all") {
85905
+ ctx.lastPushedGitState.delete(makeGitStateKey(ctx.machineId, event.workingDir));
85906
+ await pushSingleWorkspaceGitState(ctx, event.workingDir);
85300
85907
  }
85301
85908
  tracker.localActionIds.set(eventId, Date.now());
85302
85909
  } else if (eventType === "command.run") {
@@ -85354,6 +85961,7 @@ async function startCommandLoop(ctx) {
85354
85961
  }).then(() => {
85355
85962
  heartbeatCount++;
85356
85963
  console.log(`[${formatTimestamp()}] \uD83D\uDC93 Daemon heartbeat #${heartbeatCount} OK`);
85964
+ invalidateWorkspacesForMachineCache(ctx);
85357
85965
  if (!ctx.observedSyncEnabled) {
85358
85966
  pushGitState(ctx).catch((err) => {
85359
85967
  console.warn(`[${formatTimestamp()}] ⚠️ Git state push failed: ${getErrorMessage(err)}`);
@@ -85520,6 +86128,7 @@ var init_command_loop = __esm(() => {
85520
86128
  init_manager();
85521
86129
  init_init2();
85522
86130
  init_log_observer_sync();
86131
+ init_workspace_cache();
85523
86132
  init_observed_sync();
85524
86133
  init_api3();
85525
86134
  init_client2();
@@ -86699,4 +87308,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
86699
87308
  });
86700
87309
  program2.parse();
86701
87310
 
86702
- //# debugId=3E031CCB2CBFFA9464756E2164756E21
87311
+ //# debugId=D59205F0E7B31DAD64756E2164756E21