chatroom-cli 1.52.0 → 1.53.2

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 = () => {};
@@ -79389,9 +79715,11 @@ var init_featureFlags = __esm(() => {
79389
79715
  });
79390
79716
 
79391
79717
  // ../../services/backend/config/reliability.ts
79392
- var DAEMON_HEARTBEAT_INTERVAL_MS = 30000, AGENT_REQUEST_DEADLINE_MS = 120000, OBSERVATION_TTL_MS = 60000, OBSERVED_FULL_PUSH_INTERVAL_MS, OBSERVED_SAFETY_POLL_MS = 30000;
79718
+ var DAEMON_HEARTBEAT_INTERVAL_MS = 30000, AGENT_REQUEST_DEADLINE_MS = 120000, OBSERVATION_TTL_MS = 60000, OBSERVED_FULL_PUSH_INTERVAL_MS, OBSERVED_SAFETY_POLL_MS = 30000, WORKSPACE_RECENCY_WINDOW_MS, WORKSPACE_LIST_RECONCILE_MS;
79393
79719
  var init_reliability = __esm(() => {
79394
79720
  OBSERVED_FULL_PUSH_INTERVAL_MS = 5 * 60000;
79721
+ WORKSPACE_RECENCY_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
79722
+ WORKSPACE_LIST_RECONCILE_MS = 60 * 60 * 1000;
79395
79723
  });
79396
79724
 
79397
79725
  // src/commands/machine/daemon-start/capabilities-snapshot.ts
@@ -79409,20 +79737,20 @@ function formatTimestamp() {
79409
79737
 
79410
79738
  // src/infrastructure/services/workspace/workspace-resolver.ts
79411
79739
  import { readFile as readFile5, readdir, stat } from "node:fs/promises";
79412
- import { join as join12, basename, resolve as resolve3 } from "node:path";
79740
+ import { join as join13, basename, resolve as resolve3 } from "node:path";
79413
79741
  async function resolveGlobPattern(rootDir, pattern) {
79414
79742
  const cleaned = pattern.replace(/\/+$/, "");
79415
79743
  if (cleaned.includes("..")) {
79416
79744
  return [];
79417
79745
  }
79418
79746
  if (cleaned.endsWith("/*")) {
79419
- const parentDir = join12(rootDir, cleaned.slice(0, -2));
79747
+ const parentDir = join13(rootDir, cleaned.slice(0, -2));
79420
79748
  try {
79421
79749
  const entries2 = await readdir(parentDir, { withFileTypes: true });
79422
79750
  const dirs = [];
79423
79751
  for (const entry of entries2) {
79424
79752
  if (entry.isDirectory()) {
79425
- const dirPath = join12(parentDir, entry.name);
79753
+ const dirPath = join13(parentDir, entry.name);
79426
79754
  if (resolve3(dirPath).startsWith(resolve3(rootDir))) {
79427
79755
  dirs.push(dirPath);
79428
79756
  }
@@ -79433,7 +79761,7 @@ async function resolveGlobPattern(rootDir, pattern) {
79433
79761
  return [];
79434
79762
  }
79435
79763
  } else {
79436
- const dir = join12(rootDir, cleaned);
79764
+ const dir = join13(rootDir, cleaned);
79437
79765
  try {
79438
79766
  if (!resolve3(dir).startsWith(resolve3(rootDir)))
79439
79767
  return [];
@@ -79446,7 +79774,7 @@ async function resolveGlobPattern(rootDir, pattern) {
79446
79774
  }
79447
79775
  async function readPackageJson(dir) {
79448
79776
  try {
79449
- const content = await readFile5(join12(dir, "package.json"), "utf-8");
79777
+ const content = await readFile5(join13(dir, "package.json"), "utf-8");
79450
79778
  const pkg = JSON.parse(content);
79451
79779
  return {
79452
79780
  name: pkg.name || basename(dir),
@@ -79458,7 +79786,7 @@ async function readPackageJson(dir) {
79458
79786
  }
79459
79787
  async function readPnpmWorkspacePatterns(rootDir) {
79460
79788
  try {
79461
- const content = await readFile5(join12(rootDir, "pnpm-workspace.yaml"), "utf-8");
79789
+ const content = await readFile5(join13(rootDir, "pnpm-workspace.yaml"), "utf-8");
79462
79790
  const patterns = [];
79463
79791
  let inPackages = false;
79464
79792
  for (const line of content.split(`
@@ -79485,7 +79813,7 @@ async function readPnpmWorkspacePatterns(rootDir) {
79485
79813
  }
79486
79814
  async function readPackageJsonWorkspacePatterns(rootDir) {
79487
79815
  try {
79488
- const content = await readFile5(join12(rootDir, "package.json"), "utf-8");
79816
+ const content = await readFile5(join13(rootDir, "package.json"), "utf-8");
79489
79817
  const pkg = JSON.parse(content);
79490
79818
  if (!pkg.workspaces)
79491
79819
  return [];
@@ -79534,11 +79862,11 @@ var init_workspace_resolver = () => {};
79534
79862
 
79535
79863
  // src/infrastructure/services/workspace/command-discovery.ts
79536
79864
  import { access as access2, readFile as readFile6 } from "node:fs/promises";
79537
- import { join as join13, relative, basename as basename2 } from "node:path";
79865
+ import { join as join14, relative, basename as basename2 } from "node:path";
79538
79866
  async function detectPackageManager(workingDir) {
79539
79867
  for (const { file, manager } of LOCKFILE_MAP) {
79540
79868
  try {
79541
- await access2(join13(workingDir, file));
79869
+ await access2(join14(workingDir, file));
79542
79870
  return manager;
79543
79871
  } catch {}
79544
79872
  }
@@ -79585,7 +79913,7 @@ async function discoverCommands(workingDir) {
79585
79913
  const turboTaskNames = [];
79586
79914
  let rootPackageName = basename2(workingDir);
79587
79915
  try {
79588
- const pkgPath = join13(workingDir, "package.json");
79916
+ const pkgPath = join14(workingDir, "package.json");
79589
79917
  const pkgContent = await readFile6(pkgPath, "utf-8");
79590
79918
  const pkg = JSON.parse(pkgContent);
79591
79919
  if (pkg.name)
@@ -79606,7 +79934,7 @@ async function discoverCommands(workingDir) {
79606
79934
  } catch {}
79607
79935
  const rootSubWorkspace = { type: "npm", path: ".", name: rootPackageName };
79608
79936
  try {
79609
- const turboPath = join13(workingDir, "turbo.json");
79937
+ const turboPath = join14(workingDir, "turbo.json");
79610
79938
  const turboContent = await readFile6(turboPath, "utf-8");
79611
79939
  const turbo = JSON.parse(turboContent);
79612
79940
  if (turbo.tasks && typeof turbo.tasks === "object") {
@@ -79660,19 +79988,39 @@ var init_command_discovery = __esm(() => {
79660
79988
  ];
79661
79989
  });
79662
79990
 
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;
79991
+ // src/commands/machine/daemon-start/workspace-cache.ts
79992
+ async function getWorkspacesForMachine(ctx) {
79993
+ const store = ctx.workspaceListStore;
79994
+ if (store && store.updatedAt > 0) {
79995
+ return store.workspaces;
79996
+ }
79667
79997
  try {
79668
- workspaces = await ctx.deps.backend.query(api.workspaces.listWorkspacesForMachine, {
79998
+ const workspaces = await ctx.deps.backend.query(api.workspaces.listRecentlyObservedWorkspacesForMachine, {
79669
79999
  sessionId: ctx.sessionId,
79670
80000
  machineId: ctx.machineId
79671
80001
  });
80002
+ const mapped = workspaces.map((ws) => ({ workingDir: ws.workingDir }));
80003
+ if (store) {
80004
+ store.workspaces = mapped;
80005
+ store.updatedAt = Date.now();
80006
+ }
80007
+ return mapped;
79672
80008
  } catch (err) {
79673
- console.warn(`[${formatTimestamp()}] ⚠️ Failed to query workspaces for command sync: ${getErrorMessage(err)}`);
79674
- return;
80009
+ console.warn(`[${formatTimestamp()}] ⚠️ Failed to query workspaces: ${getErrorMessage(err)}`);
80010
+ return [];
79675
80011
  }
80012
+ }
80013
+ var init_workspace_cache = __esm(() => {
80014
+ init_api3();
80015
+ init_convex_error();
80016
+ });
80017
+
80018
+ // src/commands/machine/daemon-start/command-sync-heartbeat.ts
80019
+ import { createHash as createHash2 } from "node:crypto";
80020
+ async function pushCommands(ctx) {
80021
+ const workspaces = await getWorkspacesForMachine(ctx);
80022
+ if (workspaces.length === 0)
80023
+ return;
79676
80024
  const uniqueWorkingDirs = new Set(workspaces.map((ws) => ws.workingDir));
79677
80025
  if (uniqueWorkingDirs.size === 0)
79678
80026
  return;
@@ -79704,6 +80052,7 @@ var init_command_sync_heartbeat = __esm(() => {
79704
80052
  init_api3();
79705
80053
  init_command_discovery();
79706
80054
  init_convex_error();
80055
+ init_workspace_cache();
79707
80056
  });
79708
80057
 
79709
80058
  // src/events/daemon/agent/on-request-start-agent.ts
@@ -79720,7 +80069,8 @@ async function onRequestStartAgent(ctx, event) {
79720
80069
  agentHarness: event.agentHarness,
79721
80070
  model: event.model,
79722
80071
  workingDir: event.workingDir,
79723
- reason: event.reason
80072
+ reason: event.reason,
80073
+ wantResume: event.wantResume ?? true
79724
80074
  });
79725
80075
  if (!result.success) {
79726
80076
  console.log(`[daemon] Agent start rejected for role=${event.role}: ${result.error ?? "unknown"}`);
@@ -79787,7 +80137,7 @@ var init_on_request_stop_agent = () => {};
79787
80137
  import { createHash as createHash3 } from "node:crypto";
79788
80138
  import { existsSync as existsSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync4 } from "node:fs";
79789
80139
  import { homedir as homedir5 } from "node:os";
79790
- import { join as join14 } from "node:path";
80140
+ import { join as join15 } from "node:path";
79791
80141
  function getUrlHash() {
79792
80142
  const url2 = getConvexUrl();
79793
80143
  return createHash3("sha256").update(url2).digest("hex").substring(0, 8);
@@ -79801,7 +80151,7 @@ function ensureChatroomDir2() {
79801
80151
  }
79802
80152
  }
79803
80153
  function getPidFilePath() {
79804
- return join14(CHATROOM_DIR4, getPidFileName());
80154
+ return join15(CHATROOM_DIR4, getPidFileName());
79805
80155
  }
79806
80156
  function isProcessRunning(pid) {
79807
80157
  try {
@@ -79866,7 +80216,7 @@ function releaseLock() {
79866
80216
  var CHATROOM_DIR4;
79867
80217
  var init_pid = __esm(() => {
79868
80218
  init_client2();
79869
- CHATROOM_DIR4 = join14(homedir5(), ".chatroom");
80219
+ CHATROOM_DIR4 = join15(homedir5(), ".chatroom");
79870
80220
  });
79871
80221
 
79872
80222
  // src/infrastructure/git/types.ts
@@ -80395,16 +80745,9 @@ function makeBranchDependentFields(branch) {
80395
80745
  ];
80396
80746
  }
80397
80747
  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)}`);
80748
+ const workspaces = await getWorkspacesForMachine(ctx);
80749
+ if (workspaces.length === 0)
80406
80750
  return;
80407
- }
80408
80751
  const uniqueWorkingDirs = new Set(workspaces.map((ws) => ws.workingDir));
80409
80752
  if (uniqueWorkingDirs.size === 0)
80410
80753
  return;
@@ -80555,6 +80898,7 @@ var lastFullPushMs, branchField, GIT_STATE_FIELDS;
80555
80898
  var init_git_heartbeat = __esm(() => {
80556
80899
  init_reliability();
80557
80900
  init_api3();
80901
+ init_workspace_cache();
80558
80902
  init_git_reader();
80559
80903
  init_git_state_pipeline();
80560
80904
  init_convex_error();
@@ -80956,16 +81300,9 @@ var init_git_subscription = __esm(() => {
80956
81300
 
80957
81301
  // src/commands/machine/daemon-start/commit-detail-sync.ts
80958
81302
  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)}`);
81303
+ const workspaces = await getWorkspacesForMachine(ctx);
81304
+ if (workspaces.length === 0)
80967
81305
  return;
80968
- }
80969
81306
  const uniqueWorkingDirs = new Set(workspaces.map((ws) => ws.workingDir));
80970
81307
  if (uniqueWorkingDirs.size === 0)
80971
81308
  return;
@@ -81070,6 +81407,7 @@ var init_commit_detail_sync = __esm(() => {
81070
81407
  init_api3();
81071
81408
  init_git_reader();
81072
81409
  init_convex_error();
81410
+ init_workspace_cache();
81073
81411
  seenShas = new Map;
81074
81412
  });
81075
81413
 
@@ -82496,17 +82834,17 @@ import {
82496
82834
  writeFileSync as writeFileSync4
82497
82835
  } from "node:fs";
82498
82836
  import { homedir as homedir6 } from "node:os";
82499
- import { join as join16 } from "node:path";
82837
+ import { join as join17 } from "node:path";
82500
82838
  function getUrlHash2() {
82501
82839
  const url2 = getConvexUrl();
82502
82840
  return createHash6("sha256").update(url2).digest("hex").substring(0, 8);
82503
82841
  }
82504
82842
  function getChildPidsFilePath() {
82505
- const dir = join16(homedir6(), ".chatroom");
82506
- return join16(dir, `daemon-children-${getUrlHash2()}.pids`);
82843
+ const dir = join17(homedir6(), ".chatroom");
82844
+ return join17(dir, `daemon-children-${getUrlHash2()}.pids`);
82507
82845
  }
82508
82846
  function ensureChatroomDir3() {
82509
- const dir = join16(homedir6(), ".chatroom");
82847
+ const dir = join17(homedir6(), ".chatroom");
82510
82848
  if (!existsSync3(dir)) {
82511
82849
  mkdirSync5(dir, { recursive: true, mode: 448 });
82512
82850
  }
@@ -82600,7 +82938,7 @@ async function reapOrphanedProcessGroups() {
82600
82938
  var CHATROOM_DIR5;
82601
82939
  var init_orphan_tracker = __esm(() => {
82602
82940
  init_client2();
82603
- CHATROOM_DIR5 = join16(homedir6(), ".chatroom");
82941
+ CHATROOM_DIR5 = join17(homedir6(), ".chatroom");
82604
82942
  });
82605
82943
 
82606
82944
  // src/commands/machine/daemon-start/handlers/process/state.ts
@@ -82738,7 +83076,7 @@ var init_killer = __esm(() => {
82738
83076
  // src/commands/machine/daemon-start/handlers/process/output-store.ts
82739
83077
  import { appendFile as appendFile2, mkdir as mkdir6, readFile as readFile8, rm } from "node:fs/promises";
82740
83078
  import { tmpdir } from "node:os";
82741
- import { join as join17 } from "node:path";
83079
+ import { join as join18 } from "node:path";
82742
83080
 
82743
83081
  class TempFileOutputStore {
82744
83082
  state;
@@ -82801,7 +83139,7 @@ function createOutputStore(runId) {
82801
83139
  if (!RUN_ID_RE.test(runId)) {
82802
83140
  throw new Error(`Invalid runId: ${runId}`);
82803
83141
  }
82804
- const filePath = join17(TEMP_DIR, `${runId}.log`);
83142
+ const filePath = join18(TEMP_DIR, `${runId}.log`);
82805
83143
  return new TempFileOutputStore(filePath);
82806
83144
  }
82807
83145
  async function ensureTempDir() {
@@ -82815,11 +83153,49 @@ async function cleanOrphanTempFiles() {
82815
83153
  var TAIL_WINDOW_BYTES, TEMP_DIR, RUN_ID_RE, MAX_TAIL_LINES_V2 = 50;
82816
83154
  var init_output_store = __esm(() => {
82817
83155
  TAIL_WINDOW_BYTES = 32 * 1024;
82818
- TEMP_DIR = join17(tmpdir(), "chatroom-cli", "runs");
83156
+ TEMP_DIR = join18(tmpdir(), "chatroom-cli", "runs");
82819
83157
  RUN_ID_RE = /^[a-z0-9]+$/i;
82820
83158
  });
82821
83159
 
82822
- // src/commands/machine/daemon-start/handlers/process/log-observer-sync.ts
83160
+ // src/commands/machine/daemon-start/handlers/process/log-observer-subscription.ts
83161
+ function setsEqual(a, b) {
83162
+ if (a.size !== b.size)
83163
+ return false;
83164
+ for (const id3 of a) {
83165
+ if (!b.has(id3))
83166
+ return false;
83167
+ }
83168
+ return true;
83169
+ }
83170
+ function formatRunIdShort(runId) {
83171
+ return runId.length > 8 ? `${runId.slice(0, 8)}…` : runId;
83172
+ }
83173
+ function logObserverSetChangeIfNeeded(runs) {
83174
+ const nextObserved = new Set;
83175
+ const nextPending = new Set;
83176
+ for (const run3 of runs) {
83177
+ nextObserved.add(run3._id);
83178
+ if (run3.pendingFullOutputSync) {
83179
+ nextPending.add(run3._id);
83180
+ }
83181
+ }
83182
+ if (setsEqual(observedRunIds, nextObserved) && setsEqual(pendingFullSyncRunIds, nextPending)) {
83183
+ return;
83184
+ }
83185
+ const runSummaries = [...nextObserved].map(formatRunIdShort).join(", ") || "none";
83186
+ console.log(`[${formatTimestamp()}] \uD83D\uDCDC Log observers updated: ${nextObserved.size} run(s) [${runSummaries}] pendingFull=${nextPending.size}`);
83187
+ }
83188
+ function applyObservedRuns(runs) {
83189
+ logObserverSetChangeIfNeeded(runs);
83190
+ observedRunIds.clear();
83191
+ pendingFullSyncRunIds.clear();
83192
+ for (const run3 of runs) {
83193
+ observedRunIds.add(run3._id);
83194
+ if (run3.pendingFullOutputSync) {
83195
+ pendingFullSyncRunIds.add(run3._id);
83196
+ }
83197
+ }
83198
+ }
82823
83199
  function isRunLogObserved(runId) {
82824
83200
  return observedRunIds.has(runId);
82825
83201
  }
@@ -82829,51 +83205,43 @@ function consumePendingFullSync(runId) {
82829
83205
  pendingFullSyncRunIds.delete(runId);
82830
83206
  return true;
82831
83207
  }
82832
- function startLogObserverPoll(ctx) {
83208
+ function startLogObserverSubscription(ctx, wsClient2) {
83209
+ const queryArgs = {
83210
+ sessionId: ctx.sessionId,
83211
+ machineId: ctx.machineId
83212
+ };
82833
83213
  let stopped = false;
82834
- const poll4 = async () => {
83214
+ const unsubscribe = wsClient2.onUpdate(api.daemon.commands.listRunsWithLogObservers, queryArgs, (runs) => {
82835
83215
  if (stopped)
82836
83216
  return;
82837
- try {
82838
- const runs = await ctx.deps.backend.query(api.commands.listRunsWithLogObservers, {
82839
- sessionId: ctx.sessionId,
82840
- machineId: ctx.machineId
82841
- });
82842
- observedRunIds.clear();
82843
- pendingFullSyncRunIds.clear();
82844
- for (const run3 of runs) {
82845
- observedRunIds.add(run3._id);
82846
- if (run3.pendingFullOutputSync) {
82847
- pendingFullSyncRunIds.add(run3._id);
82848
- }
82849
- }
82850
- } catch (err) {
82851
- console.warn(`[${formatTimestamp()}] ⚠️ Log-observer poll failed: ${getErrorMessage(err)}`);
82852
- }
82853
- };
82854
- poll4();
82855
- const handle = setInterval(() => {
82856
- poll4();
82857
- }, OUTPUT_FLUSH_INTERVAL_MS);
82858
- handle.unref?.();
83217
+ applyObservedRuns(runs ?? []);
83218
+ }, (err) => {
83219
+ console.warn(`[${formatTimestamp()}] ⚠️ Log-observer subscription error: ${getErrorMessage(err)}`);
83220
+ });
83221
+ console.log(`[${formatTimestamp()}] \uD83D\uDCDC Log-observer subscription started`);
82859
83222
  return {
82860
83223
  stop: () => {
82861
83224
  stopped = true;
82862
- clearInterval(handle);
83225
+ unsubscribe();
82863
83226
  observedRunIds.clear();
82864
83227
  pendingFullSyncRunIds.clear();
83228
+ console.log(`[${formatTimestamp()}] \uD83D\uDCDC Log-observer subscription stopped`);
82865
83229
  }
82866
83230
  };
82867
83231
  }
82868
83232
  var observedRunIds, pendingFullSyncRunIds;
82869
- var init_log_observer_sync = __esm(() => {
83233
+ var init_log_observer_subscription = __esm(() => {
82870
83234
  init_api3();
82871
83235
  init_convex_error();
82872
- init_state2();
82873
83236
  observedRunIds = new Set;
82874
83237
  pendingFullSyncRunIds = new Set;
82875
83238
  });
82876
83239
 
83240
+ // src/commands/machine/daemon-start/handlers/process/log-observer-sync.ts
83241
+ var init_log_observer_sync = __esm(() => {
83242
+ init_log_observer_subscription();
83243
+ });
83244
+
82877
83245
  // src/commands/machine/daemon-start/handlers/process/spawner.ts
82878
83246
  import { spawn as spawn4 } from "node:child_process";
82879
83247
  async function flushTailV2(ctx, tracked) {
@@ -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);
@@ -84703,6 +85246,62 @@ var init_observed_sync = __esm(() => {
84703
85246
  init_convex_error();
84704
85247
  });
84705
85248
 
85249
+ // src/commands/machine/daemon-start/workspace-list-subscription.ts
85250
+ function toSyncWorkspaces(workspaces) {
85251
+ return workspaces.map((ws) => ({ workingDir: ws.workingDir }));
85252
+ }
85253
+ function applyWorkspaceList(ctx, workspaces) {
85254
+ if (!ctx.workspaceListStore)
85255
+ return;
85256
+ ctx.workspaceListStore.workspaces = toSyncWorkspaces(workspaces);
85257
+ ctx.workspaceListStore.updatedAt = Date.now();
85258
+ }
85259
+ function startWorkspaceListSubscription(ctx, wsClient2) {
85260
+ ctx.workspaceListStore = { workspaces: [], updatedAt: 0 };
85261
+ const queryArgs = {
85262
+ sessionId: ctx.sessionId,
85263
+ machineId: ctx.machineId,
85264
+ recencyWindowMs: WORKSPACE_RECENCY_WINDOW_MS
85265
+ };
85266
+ let stopped = false;
85267
+ let reconcileInFlight = false;
85268
+ const unsubscribe = wsClient2.onUpdate(api.workspaces.listRecentlyObservedWorkspacesForMachine, queryArgs, (workspaces) => {
85269
+ if (stopped)
85270
+ return;
85271
+ applyWorkspaceList(ctx, workspaces ?? []);
85272
+ }, (err) => {
85273
+ console.warn(`[${formatTimestamp()}] ⚠️ Workspace-list subscription error: ${getErrorMessage(err)}`);
85274
+ });
85275
+ const reconcileTimer = setInterval(() => {
85276
+ if (stopped || reconcileInFlight)
85277
+ return;
85278
+ reconcileInFlight = true;
85279
+ ctx.deps.backend.query(api.workspaces.listRecentlyObservedWorkspacesForMachine, queryArgs).then((workspaces) => {
85280
+ if (!stopped)
85281
+ applyWorkspaceList(ctx, workspaces ?? []);
85282
+ }).catch((err) => {
85283
+ console.warn(`[${formatTimestamp()}] ⚠️ Workspace-list reconcile failed: ${getErrorMessage(err)}`);
85284
+ }).finally(() => {
85285
+ reconcileInFlight = false;
85286
+ });
85287
+ }, WORKSPACE_LIST_RECONCILE_MS);
85288
+ console.log(`[${formatTimestamp()}] \uD83D\uDCC2 Workspace-list subscription started`);
85289
+ return {
85290
+ stop: () => {
85291
+ stopped = true;
85292
+ unsubscribe();
85293
+ clearInterval(reconcileTimer);
85294
+ delete ctx.workspaceListStore;
85295
+ console.log(`[${formatTimestamp()}] \uD83D\uDCC2 Workspace-list subscription stopped`);
85296
+ }
85297
+ };
85298
+ }
85299
+ var init_workspace_list_subscription = __esm(() => {
85300
+ init_reliability();
85301
+ init_api3();
85302
+ init_convex_error();
85303
+ });
85304
+
84706
85305
  // src/infrastructure/git/git-writer.ts
84707
85306
  import { exec as exec5 } from "node:child_process";
84708
85307
  import { promisify as promisify5 } from "node:util";
@@ -85437,8 +86036,9 @@ async function startCommandLoop(ctx) {
85437
86036
  let gitSubscriptionHandle = null;
85438
86037
  let fileContentSubscriptionHandle = null;
85439
86038
  let fileTreeSubscriptionHandle = null;
86039
+ let workspaceListSubscriptionHandle = null;
85440
86040
  let observedSyncSubscriptionHandle = null;
85441
- let logObserverPollHandle = null;
86041
+ let logObserverSubscriptionHandle = null;
85442
86042
  let pendingPromptSubscriptionHandle = null;
85443
86043
  let pendingHarnessSessionSubscriptionHandle = null;
85444
86044
  let commandSubscriptionHandle = null;
@@ -85462,10 +86062,12 @@ async function startCommandLoop(ctx) {
85462
86062
  fileContentSubscriptionHandle.stop();
85463
86063
  if (fileTreeSubscriptionHandle)
85464
86064
  fileTreeSubscriptionHandle.stop();
86065
+ if (workspaceListSubscriptionHandle)
86066
+ workspaceListSubscriptionHandle.stop();
85465
86067
  if (observedSyncSubscriptionHandle)
85466
86068
  observedSyncSubscriptionHandle.stop();
85467
- if (logObserverPollHandle)
85468
- logObserverPollHandle.stop();
86069
+ if (logObserverSubscriptionHandle)
86070
+ logObserverSubscriptionHandle.stop();
85469
86071
  if (pendingPromptSubscriptionHandle)
85470
86072
  pendingPromptSubscriptionHandle.stop();
85471
86073
  if (pendingHarnessSessionSubscriptionHandle)
@@ -85491,10 +86093,11 @@ async function startCommandLoop(ctx) {
85491
86093
  gitSubscriptionHandle = startGitRequestSubscription(ctx, wsClient2);
85492
86094
  fileContentSubscriptionHandle = startFileContentSubscription(ctx, wsClient2);
85493
86095
  fileTreeSubscriptionHandle = startFileTreeSubscription(ctx, wsClient2);
86096
+ workspaceListSubscriptionHandle = startWorkspaceListSubscription(ctx, wsClient2);
85494
86097
  if (ctx.observedSyncEnabled) {
85495
86098
  observedSyncSubscriptionHandle = startObservedSyncSubscription(ctx, wsClient2);
85496
86099
  }
85497
- logObserverPollHandle = startLogObserverPoll(ctx);
86100
+ logObserverSubscriptionHandle = startLogObserverSubscription(ctx, wsClient2);
85498
86101
  if (featureFlags.directHarnessWorkers) {
85499
86102
  const sessionRepository = new ConvexSessionRepository({
85500
86103
  backend: ctx.deps.backend,
@@ -85585,6 +86188,7 @@ var init_command_loop = __esm(() => {
85585
86188
  init_init2();
85586
86189
  init_log_observer_sync();
85587
86190
  init_observed_sync();
86191
+ init_workspace_list_subscription();
85588
86192
  init_api3();
85589
86193
  init_client2();
85590
86194
  init_local_actions();
@@ -86759,8 +87363,8 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
86759
87363
  if (!sessionId)
86760
87364
  return;
86761
87365
  const client4 = await getConvexClient2();
86762
- sendLifecycleHeartbeat2(client4, { sessionId, chatroomId, role, action: actionCommand.name() });
87366
+ sendLifecycleHeartbeat2(client4, { sessionId, chatroomId, role });
86763
87367
  });
86764
87368
  program2.parse();
86765
87369
 
86766
- //# debugId=9CDA1F59EFE33D8964756E2164756E21
87370
+ //# debugId=C18F126578765BC664756E2164756E21