chatroom-cli 1.52.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, {
@@ -27825,30 +28015,57 @@ ${options.prompt}` : options.prompt;
27825
28015
  process.stderr.write(`${logPrefix} spawn-error] ${reason}
27826
28016
  `);
27827
28017
  } finally {
27828
- if (!session.agentClosed) {
28018
+ if (!session.agentClosed && !session.preserveForResume) {
27829
28019
  try {
27830
28020
  agent.close();
27831
28021
  session.agentClosed = true;
27832
28022
  } catch {}
27833
28023
  }
27834
28024
  try {
27835
- keeper.kill();
28025
+ session.keeper.kill();
27836
28026
  } catch {}
27837
28027
  finishExit(exitCode, exitSignal);
27838
28028
  }
27839
28029
  })();
27840
- 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({
27841
28059
  pid,
27842
- onExit: (cb) => {
27843
- exitCallbacks.push(cb);
27844
- },
27845
- onOutput: (cb) => {
27846
- outputCallbacks.push(cb);
27847
- },
27848
- onAgentEnd: (cb) => {
27849
- agentEndCallbacks.push(cb);
27850
- }
27851
- };
28060
+ keeper,
28061
+ agent,
28062
+ context: context4,
28063
+ agentName,
28064
+ model: options.model,
28065
+ workingDir: options.workingDir,
28066
+ initialPrompt: fullPrompt,
28067
+ forceFirstTurn: true
28068
+ });
27852
28069
  }
27853
28070
  };
27854
28071
  });
@@ -30039,12 +30256,12 @@ function startSessionEventForwarder(client4, options) {
30039
30256
  // src/infrastructure/services/remote-agents/opencode-sdk/session-metadata-store.ts
30040
30257
  import { existsSync, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
30041
30258
  import { homedir as homedir2 } from "node:os";
30042
- import { dirname as dirname2, join as join7 } from "node:path";
30259
+ import { dirname as dirname2, join as join8 } from "node:path";
30043
30260
 
30044
30261
  class FileSessionMetadataStore {
30045
30262
  filePath;
30046
30263
  constructor(filePath) {
30047
- this.filePath = filePath ?? join7(homedir2(), ".chatroom", "opencode-sdk-sessions.json");
30264
+ this.filePath = filePath ?? join8(homedir2(), ".chatroom", "opencode-sdk-sessions.json");
30048
30265
  }
30049
30266
  load() {
30050
30267
  try {
@@ -30098,7 +30315,7 @@ async function withTimeout2(p, ms, label) {
30098
30315
  clearTimeout(timer);
30099
30316
  }
30100
30317
  }
30101
- 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;
30102
30319
  var init_opencode_sdk_agent_service = __esm(() => {
30103
30320
  init_dist();
30104
30321
  init_base_cli_agent_service();
@@ -30127,28 +30344,30 @@ var init_opencode_sdk_agent_service = __esm(() => {
30127
30344
  return output.split(`
30128
30345
  `).map((line) => line.trim()).filter((line) => line.length > 0);
30129
30346
  }
30130
- async stop(pid) {
30347
+ async stop(pid, options) {
30131
30348
  const forwarder = this.forwarders.get(pid);
30132
30349
  if (forwarder) {
30133
30350
  forwarder.stop();
30134
30351
  this.forwarders.delete(pid);
30135
30352
  }
30353
+ const preserveForResume = options?.preserveForResume === true;
30136
30354
  const meta = this.sessionStore.findByPid(pid);
30137
30355
  if (meta) {
30138
- try {
30139
- const client4 = createOpencodeClient({ baseUrl: meta.baseUrl });
30140
- await withTimeout2(client4.session.abort({ path: { id: meta.sessionId } }), SESSION_ABORT_TIMEOUT_MS, "session.abort");
30141
- } catch (err) {
30142
- 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
+ }
30143
30363
  }
30144
30364
  this.sessionStore.remove(meta.sessionId);
30145
30365
  }
30146
30366
  await super.stop(pid);
30147
30367
  }
30148
- async spawn(options) {
30149
- const { prompt, systemPrompt, model, context: context4 } = options;
30368
+ spawnServeProcess(workingDir) {
30150
30369
  const childProcess = this.deps.spawn(OPENCODE_COMMAND2, ["serve", "--print-logs"], {
30151
- cwd: options.workingDir,
30370
+ cwd: workingDir,
30152
30371
  stdio: ["pipe", "pipe", "pipe"],
30153
30372
  shell: false,
30154
30373
  detached: true,
@@ -30161,6 +30380,146 @@ var init_opencode_sdk_agent_service = __esm(() => {
30161
30380
  if (!childProcess.pid) {
30162
30381
  throw new Error("Failed to spawn opencode serve process");
30163
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);
30164
30523
  const pid = childProcess.pid;
30165
30524
  const baseUrl = await waitForListeningUrl(childProcess, {
30166
30525
  timeoutMs: SERVE_STARTUP_TIMEOUT_MS
@@ -30214,59 +30573,26 @@ var init_opencode_sdk_agent_service = __esm(() => {
30214
30573
  this.sessionStore.remove(sessionId);
30215
30574
  throw err;
30216
30575
  }
30217
- const meta = {
30576
+ return this.registerRunningSession({
30577
+ childProcess,
30578
+ pid,
30218
30579
  sessionId,
30219
- machineId: context4.machineId,
30220
- chatroomId: context4.chatroomId,
30221
- role: context4.role,
30580
+ context: context4,
30581
+ forwarder,
30582
+ baseUrl,
30222
30583
  agentName,
30223
- ...model ? { model } : {},
30224
- pid,
30225
- createdAt: new Date().toISOString(),
30226
- baseUrl
30227
- };
30228
- this.sessionStore.upsert(meta);
30229
- const entry = this.registerProcess(pid, context4);
30230
- if (forwarder)
30231
- this.forwarders.set(pid, forwarder);
30232
- const outputCallbacks = [];
30233
- forwardFiltered(childProcess.stdout, process.stdout, isInfoLine);
30234
- forwardFiltered(childProcess.stderr, process.stderr, isInfoLine);
30235
- if (childProcess.stdout) {
30236
- childProcess.stdout.on("data", () => {
30237
- entry.lastOutputAt = Date.now();
30238
- for (const cb of outputCallbacks)
30239
- cb();
30240
- });
30241
- }
30242
- if (childProcess.stderr) {
30243
- childProcess.stderr.on("data", () => {
30244
- entry.lastOutputAt = Date.now();
30245
- for (const cb of outputCallbacks)
30246
- cb();
30247
- });
30584
+ model,
30585
+ workingDir: options.workingDir
30586
+ });
30587
+ }
30588
+ getHarnessReconnectContext(pid) {
30589
+ const meta = this.sessionStore.findByPid(pid);
30590
+ if (!meta) {
30591
+ return;
30248
30592
  }
30249
30593
  return {
30250
- pid,
30251
- harnessSessionId: sessionId,
30252
- onExit: (cb) => {
30253
- childProcess.on("exit", (code2, signal) => {
30254
- const fwd = this.forwarders.get(pid);
30255
- if (fwd) {
30256
- fwd.stop();
30257
- this.forwarders.delete(pid);
30258
- }
30259
- this.sessionStore.remove(sessionId);
30260
- this.deleteProcess(pid);
30261
- cb({ code: code2, signal, context: context4 });
30262
- });
30263
- },
30264
- onOutput: (cb) => {
30265
- outputCallbacks.push(cb);
30266
- },
30267
- onAgentEnd: (cb) => {
30268
- forwarder?.onAgentEnd(cb);
30269
- }
30594
+ agentName: meta.agentName,
30595
+ ...meta.model ? { model: meta.model } : {}
30270
30596
  };
30271
30597
  }
30272
30598
  async resumeTurn(pid, prompt) {
@@ -30401,16 +30727,16 @@ var MACHINE_CONFIG_VERSION = "1";
30401
30727
  import { randomUUID as randomUUID2 } from "node:crypto";
30402
30728
  import * as fs2 from "node:fs/promises";
30403
30729
  import { homedir as homedir3, hostname as hostname2 } from "node:os";
30404
- import { join as join8 } from "node:path";
30730
+ import { join as join9 } from "node:path";
30405
30731
  function chatroomConfigDir() {
30406
- return join8(homedir3(), ".chatroom");
30732
+ return join9(homedir3(), ".chatroom");
30407
30733
  }
30408
30734
  async function ensureConfigDir2() {
30409
30735
  const dir = chatroomConfigDir();
30410
30736
  await fs2.mkdir(dir, { recursive: true, mode: 448 });
30411
30737
  }
30412
30738
  function getMachineConfigPath() {
30413
- return join8(chatroomConfigDir(), MACHINE_FILE);
30739
+ return join9(chatroomConfigDir(), MACHINE_FILE);
30414
30740
  }
30415
30741
  async function loadConfigFile() {
30416
30742
  const configPath = getMachineConfigPath();
@@ -30502,7 +30828,7 @@ var init_storage2 = __esm(() => {
30502
30828
  // src/infrastructure/machine/daemon-state.ts
30503
30829
  import * as fs3 from "node:fs/promises";
30504
30830
  import { homedir as homedir4 } from "node:os";
30505
- import { join as join9 } from "node:path";
30831
+ import { join as join10 } from "node:path";
30506
30832
  function agentKey(chatroomId, role) {
30507
30833
  return `${chatroomId}/${role}`;
30508
30834
  }
@@ -30510,7 +30836,7 @@ async function ensureStateDir() {
30510
30836
  await fs3.mkdir(STATE_DIR, { recursive: true, mode: 448 });
30511
30837
  }
30512
30838
  function stateFilePath(machineId) {
30513
- return join9(STATE_DIR, `${machineId}.json`);
30839
+ return join10(STATE_DIR, `${machineId}.json`);
30514
30840
  }
30515
30841
  async function loadDaemonState(machineId) {
30516
30842
  const filePath = stateFilePath(machineId);
@@ -30592,8 +30918,8 @@ async function loadEventCursor(machineId) {
30592
30918
  }
30593
30919
  var CHATROOM_DIR2, STATE_DIR, STATE_VERSION = "1";
30594
30920
  var init_daemon_state = __esm(() => {
30595
- CHATROOM_DIR2 = join9(homedir4(), ".chatroom");
30596
- STATE_DIR = join9(CHATROOM_DIR2, "machines", "state");
30921
+ CHATROOM_DIR2 = join10(homedir4(), ".chatroom");
30922
+ STATE_DIR = join10(CHATROOM_DIR2, "machines", "state");
30597
30923
  });
30598
30924
 
30599
30925
  // src/infrastructure/machine/index.ts
@@ -30966,9 +31292,9 @@ var init_init = __esm(() => {
30966
31292
  });
30967
31293
 
30968
31294
  // src/tools/output.ts
30969
- import { join as join10 } from "node:path";
31295
+ import { join as join11 } from "node:path";
30970
31296
  function resolveChatroomDir(workingDir) {
30971
- return join10(workingDir, CHATROOM_DIR3);
31297
+ return join11(workingDir, CHATROOM_DIR3);
30972
31298
  }
30973
31299
  async function ensureChatroomDir(deps, workingDir) {
30974
31300
  const dir = resolveChatroomDir(workingDir);
@@ -30976,7 +31302,7 @@ async function ensureChatroomDir(deps, workingDir) {
30976
31302
  return dir;
30977
31303
  }
30978
31304
  async function ensureGitignore(deps, workingDir) {
30979
- const gitignorePath = join10(workingDir, ".gitignore");
31305
+ const gitignorePath = join11(workingDir, ".gitignore");
30980
31306
  const entry = CHATROOM_DIR3;
30981
31307
  let content = "";
30982
31308
  try {
@@ -31006,7 +31332,7 @@ function formatOutputTimestamp(date = new Date) {
31006
31332
  function generateOutputPath(workingDir, toolName, extension, date) {
31007
31333
  const timestamp = formatOutputTimestamp(date);
31008
31334
  const filename = `${toolName}-${timestamp}.${extension}`;
31009
- return join10(resolveChatroomDir(workingDir), filename);
31335
+ return join11(resolveChatroomDir(workingDir), filename);
31010
31336
  }
31011
31337
  var CHATROOM_DIR3 = ".chatroom";
31012
31338
  var init_output = () => {};
@@ -79409,20 +79735,20 @@ function formatTimestamp() {
79409
79735
 
79410
79736
  // src/infrastructure/services/workspace/workspace-resolver.ts
79411
79737
  import { readFile as readFile5, readdir, stat } from "node:fs/promises";
79412
- import { join as join12, basename, resolve as resolve3 } from "node:path";
79738
+ import { join as join13, basename, resolve as resolve3 } from "node:path";
79413
79739
  async function resolveGlobPattern(rootDir, pattern) {
79414
79740
  const cleaned = pattern.replace(/\/+$/, "");
79415
79741
  if (cleaned.includes("..")) {
79416
79742
  return [];
79417
79743
  }
79418
79744
  if (cleaned.endsWith("/*")) {
79419
- const parentDir = join12(rootDir, cleaned.slice(0, -2));
79745
+ const parentDir = join13(rootDir, cleaned.slice(0, -2));
79420
79746
  try {
79421
79747
  const entries2 = await readdir(parentDir, { withFileTypes: true });
79422
79748
  const dirs = [];
79423
79749
  for (const entry of entries2) {
79424
79750
  if (entry.isDirectory()) {
79425
- const dirPath = join12(parentDir, entry.name);
79751
+ const dirPath = join13(parentDir, entry.name);
79426
79752
  if (resolve3(dirPath).startsWith(resolve3(rootDir))) {
79427
79753
  dirs.push(dirPath);
79428
79754
  }
@@ -79433,7 +79759,7 @@ async function resolveGlobPattern(rootDir, pattern) {
79433
79759
  return [];
79434
79760
  }
79435
79761
  } else {
79436
- const dir = join12(rootDir, cleaned);
79762
+ const dir = join13(rootDir, cleaned);
79437
79763
  try {
79438
79764
  if (!resolve3(dir).startsWith(resolve3(rootDir)))
79439
79765
  return [];
@@ -79446,7 +79772,7 @@ async function resolveGlobPattern(rootDir, pattern) {
79446
79772
  }
79447
79773
  async function readPackageJson(dir) {
79448
79774
  try {
79449
- const content = await readFile5(join12(dir, "package.json"), "utf-8");
79775
+ const content = await readFile5(join13(dir, "package.json"), "utf-8");
79450
79776
  const pkg = JSON.parse(content);
79451
79777
  return {
79452
79778
  name: pkg.name || basename(dir),
@@ -79458,7 +79784,7 @@ async function readPackageJson(dir) {
79458
79784
  }
79459
79785
  async function readPnpmWorkspacePatterns(rootDir) {
79460
79786
  try {
79461
- const content = await readFile5(join12(rootDir, "pnpm-workspace.yaml"), "utf-8");
79787
+ const content = await readFile5(join13(rootDir, "pnpm-workspace.yaml"), "utf-8");
79462
79788
  const patterns = [];
79463
79789
  let inPackages = false;
79464
79790
  for (const line of content.split(`
@@ -79485,7 +79811,7 @@ async function readPnpmWorkspacePatterns(rootDir) {
79485
79811
  }
79486
79812
  async function readPackageJsonWorkspacePatterns(rootDir) {
79487
79813
  try {
79488
- const content = await readFile5(join12(rootDir, "package.json"), "utf-8");
79814
+ const content = await readFile5(join13(rootDir, "package.json"), "utf-8");
79489
79815
  const pkg = JSON.parse(content);
79490
79816
  if (!pkg.workspaces)
79491
79817
  return [];
@@ -79534,11 +79860,11 @@ var init_workspace_resolver = () => {};
79534
79860
 
79535
79861
  // src/infrastructure/services/workspace/command-discovery.ts
79536
79862
  import { access as access2, readFile as readFile6 } from "node:fs/promises";
79537
- import { join as join13, relative, basename as basename2 } from "node:path";
79863
+ import { join as join14, relative, basename as basename2 } from "node:path";
79538
79864
  async function detectPackageManager(workingDir) {
79539
79865
  for (const { file, manager } of LOCKFILE_MAP) {
79540
79866
  try {
79541
- await access2(join13(workingDir, file));
79867
+ await access2(join14(workingDir, file));
79542
79868
  return manager;
79543
79869
  } catch {}
79544
79870
  }
@@ -79585,7 +79911,7 @@ async function discoverCommands(workingDir) {
79585
79911
  const turboTaskNames = [];
79586
79912
  let rootPackageName = basename2(workingDir);
79587
79913
  try {
79588
- const pkgPath = join13(workingDir, "package.json");
79914
+ const pkgPath = join14(workingDir, "package.json");
79589
79915
  const pkgContent = await readFile6(pkgPath, "utf-8");
79590
79916
  const pkg = JSON.parse(pkgContent);
79591
79917
  if (pkg.name)
@@ -79606,7 +79932,7 @@ async function discoverCommands(workingDir) {
79606
79932
  } catch {}
79607
79933
  const rootSubWorkspace = { type: "npm", path: ".", name: rootPackageName };
79608
79934
  try {
79609
- const turboPath = join13(workingDir, "turbo.json");
79935
+ const turboPath = join14(workingDir, "turbo.json");
79610
79936
  const turboContent = await readFile6(turboPath, "utf-8");
79611
79937
  const turbo = JSON.parse(turboContent);
79612
79938
  if (turbo.tasks && typeof turbo.tasks === "object") {
@@ -79660,19 +79986,43 @@ var init_command_discovery = __esm(() => {
79660
79986
  ];
79661
79987
  });
79662
79988
 
79663
- // src/commands/machine/daemon-start/command-sync-heartbeat.ts
79664
- import { createHash as createHash2 } from "node:crypto";
79665
- async function pushCommands(ctx) {
79666
- 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
+ }
79667
80000
  try {
79668
- workspaces = await ctx.deps.backend.query(api.workspaces.listWorkspacesForMachine, {
80001
+ const workspaces = await ctx.deps.backend.query(api.workspaces.listWorkspacesForMachine, {
79669
80002
  sessionId: ctx.sessionId,
79670
80003
  machineId: ctx.machineId
79671
80004
  });
80005
+ extended._workspacesCache = { fetchedAt: now, workspaces };
80006
+ return workspaces;
79672
80007
  } catch (err) {
79673
- console.warn(`[${formatTimestamp()}] ⚠️ Failed to query workspaces for command sync: ${getErrorMessage(err)}`);
79674
- return;
80008
+ console.warn(`[${formatTimestamp()}] ⚠️ Failed to query workspaces: ${getErrorMessage(err)}`);
80009
+ return [];
79675
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;
79676
80026
  const uniqueWorkingDirs = new Set(workspaces.map((ws) => ws.workingDir));
79677
80027
  if (uniqueWorkingDirs.size === 0)
79678
80028
  return;
@@ -79704,6 +80054,7 @@ var init_command_sync_heartbeat = __esm(() => {
79704
80054
  init_api3();
79705
80055
  init_command_discovery();
79706
80056
  init_convex_error();
80057
+ init_workspace_cache();
79707
80058
  });
79708
80059
 
79709
80060
  // src/events/daemon/agent/on-request-start-agent.ts
@@ -79720,7 +80071,8 @@ async function onRequestStartAgent(ctx, event) {
79720
80071
  agentHarness: event.agentHarness,
79721
80072
  model: event.model,
79722
80073
  workingDir: event.workingDir,
79723
- reason: event.reason
80074
+ reason: event.reason,
80075
+ wantResume: event.wantResume ?? true
79724
80076
  });
79725
80077
  if (!result.success) {
79726
80078
  console.log(`[daemon] Agent start rejected for role=${event.role}: ${result.error ?? "unknown"}`);
@@ -79787,7 +80139,7 @@ var init_on_request_stop_agent = () => {};
79787
80139
  import { createHash as createHash3 } from "node:crypto";
79788
80140
  import { existsSync as existsSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync4 } from "node:fs";
79789
80141
  import { homedir as homedir5 } from "node:os";
79790
- import { join as join14 } from "node:path";
80142
+ import { join as join15 } from "node:path";
79791
80143
  function getUrlHash() {
79792
80144
  const url2 = getConvexUrl();
79793
80145
  return createHash3("sha256").update(url2).digest("hex").substring(0, 8);
@@ -79801,7 +80153,7 @@ function ensureChatroomDir2() {
79801
80153
  }
79802
80154
  }
79803
80155
  function getPidFilePath() {
79804
- return join14(CHATROOM_DIR4, getPidFileName());
80156
+ return join15(CHATROOM_DIR4, getPidFileName());
79805
80157
  }
79806
80158
  function isProcessRunning(pid) {
79807
80159
  try {
@@ -79866,7 +80218,7 @@ function releaseLock() {
79866
80218
  var CHATROOM_DIR4;
79867
80219
  var init_pid = __esm(() => {
79868
80220
  init_client2();
79869
- CHATROOM_DIR4 = join14(homedir5(), ".chatroom");
80221
+ CHATROOM_DIR4 = join15(homedir5(), ".chatroom");
79870
80222
  });
79871
80223
 
79872
80224
  // src/infrastructure/git/types.ts
@@ -80395,16 +80747,9 @@ function makeBranchDependentFields(branch) {
80395
80747
  ];
80396
80748
  }
80397
80749
  async function pushGitState(ctx) {
80398
- let workspaces;
80399
- try {
80400
- workspaces = await ctx.deps.backend.query(api.workspaces.listWorkspacesForMachine, {
80401
- sessionId: ctx.sessionId,
80402
- machineId: ctx.machineId
80403
- });
80404
- } catch (err) {
80405
- console.warn(`[${formatTimestamp()}] ⚠️ Failed to query workspaces for git sync: ${getErrorMessage(err)}`);
80750
+ const workspaces = await getWorkspacesForMachine(ctx);
80751
+ if (workspaces.length === 0)
80406
80752
  return;
80407
- }
80408
80753
  const uniqueWorkingDirs = new Set(workspaces.map((ws) => ws.workingDir));
80409
80754
  if (uniqueWorkingDirs.size === 0)
80410
80755
  return;
@@ -80555,6 +80900,7 @@ var lastFullPushMs, branchField, GIT_STATE_FIELDS;
80555
80900
  var init_git_heartbeat = __esm(() => {
80556
80901
  init_reliability();
80557
80902
  init_api3();
80903
+ init_workspace_cache();
80558
80904
  init_git_reader();
80559
80905
  init_git_state_pipeline();
80560
80906
  init_convex_error();
@@ -80956,16 +81302,9 @@ var init_git_subscription = __esm(() => {
80956
81302
 
80957
81303
  // src/commands/machine/daemon-start/commit-detail-sync.ts
80958
81304
  async function syncCommitDetails(ctx, seenShasMap) {
80959
- let workspaces;
80960
- try {
80961
- workspaces = await ctx.deps.backend.query(api.workspaces.listWorkspacesForMachine, {
80962
- sessionId: ctx.sessionId,
80963
- machineId: ctx.machineId
80964
- });
80965
- } catch (err) {
80966
- console.warn(`[${formatTimestamp()}] ⚠️ Failed to query workspaces for commit-detail sync: ${getErrorMessage(err)}`);
81305
+ const workspaces = await getWorkspacesForMachine(ctx);
81306
+ if (workspaces.length === 0)
80967
81307
  return;
80968
- }
80969
81308
  const uniqueWorkingDirs = new Set(workspaces.map((ws) => ws.workingDir));
80970
81309
  if (uniqueWorkingDirs.size === 0)
80971
81310
  return;
@@ -81070,6 +81409,7 @@ var init_commit_detail_sync = __esm(() => {
81070
81409
  init_api3();
81071
81410
  init_git_reader();
81072
81411
  init_convex_error();
81412
+ init_workspace_cache();
81073
81413
  seenShas = new Map;
81074
81414
  });
81075
81415
 
@@ -82496,17 +82836,17 @@ import {
82496
82836
  writeFileSync as writeFileSync4
82497
82837
  } from "node:fs";
82498
82838
  import { homedir as homedir6 } from "node:os";
82499
- import { join as join16 } from "node:path";
82839
+ import { join as join17 } from "node:path";
82500
82840
  function getUrlHash2() {
82501
82841
  const url2 = getConvexUrl();
82502
82842
  return createHash6("sha256").update(url2).digest("hex").substring(0, 8);
82503
82843
  }
82504
82844
  function getChildPidsFilePath() {
82505
- const dir = join16(homedir6(), ".chatroom");
82506
- return join16(dir, `daemon-children-${getUrlHash2()}.pids`);
82845
+ const dir = join17(homedir6(), ".chatroom");
82846
+ return join17(dir, `daemon-children-${getUrlHash2()}.pids`);
82507
82847
  }
82508
82848
  function ensureChatroomDir3() {
82509
- const dir = join16(homedir6(), ".chatroom");
82849
+ const dir = join17(homedir6(), ".chatroom");
82510
82850
  if (!existsSync3(dir)) {
82511
82851
  mkdirSync5(dir, { recursive: true, mode: 448 });
82512
82852
  }
@@ -82600,7 +82940,7 @@ async function reapOrphanedProcessGroups() {
82600
82940
  var CHATROOM_DIR5;
82601
82941
  var init_orphan_tracker = __esm(() => {
82602
82942
  init_client2();
82603
- CHATROOM_DIR5 = join16(homedir6(), ".chatroom");
82943
+ CHATROOM_DIR5 = join17(homedir6(), ".chatroom");
82604
82944
  });
82605
82945
 
82606
82946
  // src/commands/machine/daemon-start/handlers/process/state.ts
@@ -82738,7 +83078,7 @@ var init_killer = __esm(() => {
82738
83078
  // src/commands/machine/daemon-start/handlers/process/output-store.ts
82739
83079
  import { appendFile as appendFile2, mkdir as mkdir6, readFile as readFile8, rm } from "node:fs/promises";
82740
83080
  import { tmpdir } from "node:os";
82741
- import { join as join17 } from "node:path";
83081
+ import { join as join18 } from "node:path";
82742
83082
 
82743
83083
  class TempFileOutputStore {
82744
83084
  state;
@@ -82801,7 +83141,7 @@ function createOutputStore(runId) {
82801
83141
  if (!RUN_ID_RE.test(runId)) {
82802
83142
  throw new Error(`Invalid runId: ${runId}`);
82803
83143
  }
82804
- const filePath = join17(TEMP_DIR, `${runId}.log`);
83144
+ const filePath = join18(TEMP_DIR, `${runId}.log`);
82805
83145
  return new TempFileOutputStore(filePath);
82806
83146
  }
82807
83147
  async function ensureTempDir() {
@@ -82815,7 +83155,7 @@ async function cleanOrphanTempFiles() {
82815
83155
  var TAIL_WINDOW_BYTES, TEMP_DIR, RUN_ID_RE, MAX_TAIL_LINES_V2 = 50;
82816
83156
  var init_output_store = __esm(() => {
82817
83157
  TAIL_WINDOW_BYTES = 32 * 1024;
82818
- TEMP_DIR = join17(tmpdir(), "chatroom-cli", "runs");
83158
+ TEMP_DIR = join18(tmpdir(), "chatroom-cli", "runs");
82819
83159
  RUN_ID_RE = /^[a-z0-9]+$/i;
82820
83160
  });
82821
83161
 
@@ -82831,9 +83171,28 @@ function consumePendingFullSync(runId) {
82831
83171
  }
82832
83172
  function startLogObserverPoll(ctx) {
82833
83173
  let stopped = false;
82834
- const poll4 = async () => {
83174
+ let consecutiveIdlePolls = 0;
83175
+ let pollInFlight = false;
83176
+ let timeoutHandle = null;
83177
+ const scheduleNext = (delayMs) => {
82835
83178
  if (stopped)
82836
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;
82837
83196
  try {
82838
83197
  const runs = await ctx.deps.backend.query(api.commands.listRunsWithLogObservers, {
82839
83198
  sessionId: ctx.sessionId,
@@ -82847,31 +83206,40 @@ function startLogObserverPoll(ctx) {
82847
83206
  pendingFullSyncRunIds.add(run3._id);
82848
83207
  }
82849
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
+ }
82850
83217
  } catch (err) {
82851
83218
  console.warn(`[${formatTimestamp()}] ⚠️ Log-observer poll failed: ${getErrorMessage(err)}`);
83219
+ scheduleNext(ACTIVE_POLL_INTERVAL_MS);
83220
+ } finally {
83221
+ pollInFlight = false;
82852
83222
  }
82853
83223
  };
82854
83224
  poll4();
82855
- const handle = setInterval(() => {
82856
- poll4();
82857
- }, OUTPUT_FLUSH_INTERVAL_MS);
82858
- handle.unref?.();
82859
83225
  return {
82860
83226
  stop: () => {
82861
83227
  stopped = true;
82862
- clearInterval(handle);
83228
+ if (timeoutHandle)
83229
+ clearTimeout(timeoutHandle);
82863
83230
  observedRunIds.clear();
82864
83231
  pendingFullSyncRunIds.clear();
82865
83232
  }
82866
83233
  };
82867
83234
  }
82868
- var observedRunIds, pendingFullSyncRunIds;
83235
+ var observedRunIds, pendingFullSyncRunIds, ACTIVE_POLL_INTERVAL_MS, IDLE_POLL_INTERVAL_MS = 15000, IDLE_SKIP_AFTER_CONSECUTIVE = 3;
82869
83236
  var init_log_observer_sync = __esm(() => {
82870
83237
  init_api3();
82871
83238
  init_convex_error();
82872
83239
  init_state2();
82873
83240
  observedRunIds = new Set;
82874
83241
  pendingFullSyncRunIds = new Set;
83242
+ ACTIVE_POLL_INTERVAL_MS = OUTPUT_FLUSH_INTERVAL_MS;
82875
83243
  });
82876
83244
 
82877
83245
  // src/commands/machine/daemon-start/handlers/process/spawner.ts
@@ -83565,6 +83933,7 @@ function agentKey2(chatroomId, role) {
83565
83933
  class AgentProcessManager {
83566
83934
  deps;
83567
83935
  slots = new Map;
83936
+ lastHarnessSessions = new Map;
83568
83937
  exitRetryQueue = [];
83569
83938
  exitRetryTimer = null;
83570
83939
  constructor(deps) {
@@ -83638,11 +84007,20 @@ class AgentProcessManager {
83638
84007
  return { success: true };
83639
84008
  }
83640
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
+ }
83641
84016
  const capabilities = getHarnessCapabilities(opts.harness);
83642
- console.log(`[AgentProcessManager] agent_end: role=${opts.role} pid=${opts.pid} harness=${opts.harness} wantResume=${opts.wantResume} supportsResume=${capabilities.supportsSessionResume}`);
83643
- 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) {
83644
84019
  const service = this.deps.agentServices.get(opts.harness);
83645
84020
  if (service?.resumeTurn) {
84021
+ if (slot) {
84022
+ slot.resumeInFlight = true;
84023
+ }
83646
84024
  try {
83647
84025
  const resumePrompt = composeResumeMessage({
83648
84026
  chatroomId: opts.chatroomId,
@@ -83650,9 +84028,39 @@ class AgentProcessManager {
83650
84028
  convexUrl: this.deps.convexUrl
83651
84029
  });
83652
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
+ }
83653
84043
  return;
83654
84044
  } catch (err) {
83655
- 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
+ }
83656
84064
  }
83657
84065
  }
83658
84066
  }
@@ -83789,7 +84197,7 @@ class AgentProcessManager {
83789
84197
  getOrCreateSlot(key) {
83790
84198
  let slot = this.slots.get(key);
83791
84199
  if (!slot) {
83792
- slot = { state: "idle", wantResume: true };
84200
+ slot = { state: "idle" };
83793
84201
  this.slots.set(key, slot);
83794
84202
  }
83795
84203
  return slot;
@@ -83863,7 +84271,15 @@ class AgentProcessManager {
83863
84271
  async executeEnsureRunning(key, slot, opts) {
83864
84272
  try {
83865
84273
  await this.killExistingBeforeSpawn(opts.chatroomId, opts.role);
83866
- 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;
83867
84283
  } finally {
83868
84284
  if (slot.pendingOperation) {
83869
84285
  slot.pendingOperation = undefined;
@@ -83903,10 +84319,87 @@ class AgentProcessManager {
83903
84319
  this.exitRetryTimer = null;
83904
84320
  }
83905
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
+ }
83906
84399
  async doEnsureRunning(key, slot, opts) {
83907
84400
  slot.state = "spawning";
83908
- slot.wantResume = opts.wantResume ?? true;
83909
- 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}`);
83910
84403
  try {
83911
84404
  const spawnCheck = this.deps.spawning.shouldAllowSpawn(opts.chatroomId, opts.reason, {
83912
84405
  bypassConcurrentLimit: opts.reason.startsWith("user.")
@@ -83980,22 +84473,37 @@ class AgentProcessManager {
83980
84473
  return { success: false, error: `Unknown agent harness: ${opts.agentHarness}` };
83981
84474
  }
83982
84475
  let spawnResult;
83983
- try {
83984
- 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,
83985
84482
  workingDir: opts.workingDir,
83986
- prompt: createSpawnPrompt(initPromptResult.initialMessage),
83987
- systemPrompt: initPromptResult.rolePrompt,
83988
84483
  model: opts.model,
83989
- context: {
83990
- machineId: this.deps.machineId,
83991
- chatroomId: opts.chatroomId,
83992
- role: opts.role
83993
- }
83994
- });
83995
- } catch (e) {
83996
- slot.state = "idle";
83997
- slot.pendingOperation = undefined;
83998
- 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
+ }
83999
84507
  }
84000
84508
  const { pid } = spawnResult;
84001
84509
  this.deps.spawning.recordSpawn(opts.chatroomId);
@@ -84003,6 +84511,15 @@ class AgentProcessManager {
84003
84511
  slot.pid = pid;
84004
84512
  slot.harness = opts.agentHarness;
84005
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
+ }
84006
84523
  slot.model = opts.model;
84007
84524
  slot.workingDir = opts.workingDir;
84008
84525
  slot.startedAt = this.deps.clock.now();
@@ -84014,7 +84531,8 @@ class AgentProcessManager {
84014
84531
  role: opts.role,
84015
84532
  pid,
84016
84533
  model: opts.model,
84017
- reason: opts.reason
84534
+ reason: opts.reason,
84535
+ ...spawnResult.harnessSessionId ? { harnessSessionId: spawnResult.harnessSessionId } : {}
84018
84536
  }).catch((err) => {
84019
84537
  console.log(` ⚠️ Failed to update PID in backend: ${err.message}`);
84020
84538
  });
@@ -84036,8 +84554,7 @@ class AgentProcessManager {
84036
84554
  chatroomId: opts.chatroomId,
84037
84555
  role: opts.role,
84038
84556
  pid,
84039
- harness: opts.agentHarness,
84040
- wantResume: slot.wantResume
84557
+ harness: opts.agentHarness
84041
84558
  });
84042
84559
  });
84043
84560
  }
@@ -84060,12 +84577,38 @@ class AgentProcessManager {
84060
84577
  return { success: false, error: `Unexpected error: ${e.message}` };
84061
84578
  }
84062
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
+ }
84063
84589
  async doStop(key, slot, pid, opts) {
84064
84590
  try {
84065
84591
  const harness = slot.harness;
84066
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
+ }
84067
84610
  if (service) {
84068
- await service.stop(pid);
84611
+ await service.stop(pid, { preserveForResume });
84069
84612
  service.untrack(pid);
84070
84613
  } else {
84071
84614
  try {
@@ -84137,7 +84680,7 @@ class SpawnRateLimiter {
84137
84680
  this.config = { ...DEFAULT_CONFIG2, ...config3 };
84138
84681
  }
84139
84682
  tryConsume(chatroomId, reason) {
84140
- if (reason.startsWith("user.")) {
84683
+ if (reason.startsWith("user.") || reason === "platform.auto_restart_on_new_context") {
84141
84684
  return { allowed: true };
84142
84685
  }
84143
84686
  const bucket = this._getOrCreateBucket(chatroomId);
@@ -85418,6 +85961,7 @@ async function startCommandLoop(ctx) {
85418
85961
  }).then(() => {
85419
85962
  heartbeatCount++;
85420
85963
  console.log(`[${formatTimestamp()}] \uD83D\uDC93 Daemon heartbeat #${heartbeatCount} OK`);
85964
+ invalidateWorkspacesForMachineCache(ctx);
85421
85965
  if (!ctx.observedSyncEnabled) {
85422
85966
  pushGitState(ctx).catch((err) => {
85423
85967
  console.warn(`[${formatTimestamp()}] ⚠️ Git state push failed: ${getErrorMessage(err)}`);
@@ -85584,6 +86128,7 @@ var init_command_loop = __esm(() => {
85584
86128
  init_manager();
85585
86129
  init_init2();
85586
86130
  init_log_observer_sync();
86131
+ init_workspace_cache();
85587
86132
  init_observed_sync();
85588
86133
  init_api3();
85589
86134
  init_client2();
@@ -86763,4 +87308,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
86763
87308
  });
86764
87309
  program2.parse();
86765
87310
 
86766
- //# debugId=9CDA1F59EFE33D8964756E2164756E21
87311
+ //# debugId=D59205F0E7B31DAD64756E2164756E21