chatroom-cli 1.49.1 → 1.50.0

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
@@ -30140,6 +30140,7 @@ var init_opencode_sdk_agent_service = __esm(() => {
30140
30140
  } catch (err) {
30141
30141
  console.warn(`[opencode-sdk] session.abort for pid=${pid} sessionId=${meta.sessionId} failed (continuing with SIGTERM):`, err instanceof Error ? err.message : err);
30142
30142
  }
30143
+ this.sessionStore.remove(meta.sessionId);
30143
30144
  }
30144
30145
  await super.stop(pid);
30145
30146
  }
@@ -82749,6 +82750,24 @@ class TempFileOutputStore {
82749
82750
  totalBytes: this.state.totalBytes
82750
82751
  };
82751
82752
  }
82753
+ async getLastNLines(maxLines) {
82754
+ let source;
82755
+ try {
82756
+ source = await readFile8(this.state.filePath, "utf-8");
82757
+ } catch {
82758
+ source = this.state.inMemory;
82759
+ }
82760
+ const lines = source.split(`
82761
+ `);
82762
+ const slice = lines.length > maxLines ? lines.slice(-maxLines) : lines;
82763
+ const content = slice.join(`
82764
+ `);
82765
+ return {
82766
+ content,
82767
+ totalBytes: Buffer.byteLength(content, "utf-8"),
82768
+ lineCount: slice.length
82769
+ };
82770
+ }
82752
82771
  async getFullOutput() {
82753
82772
  try {
82754
82773
  return await readFile8(this.state.filePath, "utf-8");
@@ -82777,39 +82796,96 @@ async function cleanOrphanTempFiles() {
82777
82796
  await rm(TEMP_DIR, { recursive: true, force: true });
82778
82797
  } catch {}
82779
82798
  }
82780
- var TAIL_WINDOW_BYTES, TEMP_DIR, RUN_ID_RE;
82799
+ var TAIL_WINDOW_BYTES, TEMP_DIR, RUN_ID_RE, MAX_TAIL_LINES_V2 = 50;
82781
82800
  var init_output_store = __esm(() => {
82782
82801
  TAIL_WINDOW_BYTES = 32 * 1024;
82783
82802
  TEMP_DIR = join17(tmpdir(), "chatroom-cli", "runs");
82784
82803
  RUN_ID_RE = /^[a-z0-9]+$/i;
82785
82804
  });
82786
82805
 
82806
+ // src/commands/machine/daemon-start/handlers/process/log-observer-sync.ts
82807
+ function isRunLogObserved(runId) {
82808
+ return observedRunIds.has(runId);
82809
+ }
82810
+ function consumePendingFullSync(runId) {
82811
+ if (!pendingFullSyncRunIds.has(runId))
82812
+ return false;
82813
+ pendingFullSyncRunIds.delete(runId);
82814
+ return true;
82815
+ }
82816
+ function startLogObserverPoll(ctx) {
82817
+ let stopped = false;
82818
+ const poll4 = async () => {
82819
+ if (stopped)
82820
+ return;
82821
+ try {
82822
+ const runs = await ctx.deps.backend.query(api.commands.listRunsWithLogObservers, {
82823
+ sessionId: ctx.sessionId,
82824
+ machineId: ctx.machineId
82825
+ });
82826
+ observedRunIds.clear();
82827
+ pendingFullSyncRunIds.clear();
82828
+ for (const run3 of runs) {
82829
+ observedRunIds.add(run3._id);
82830
+ if (run3.pendingFullOutputSync) {
82831
+ pendingFullSyncRunIds.add(run3._id);
82832
+ }
82833
+ }
82834
+ } catch (err) {
82835
+ console.warn(`[${formatTimestamp()}] ⚠️ Log-observer poll failed: ${getErrorMessage(err)}`);
82836
+ }
82837
+ };
82838
+ poll4();
82839
+ const handle = setInterval(() => {
82840
+ poll4();
82841
+ }, OUTPUT_FLUSH_INTERVAL_MS);
82842
+ handle.unref?.();
82843
+ return {
82844
+ stop: () => {
82845
+ stopped = true;
82846
+ clearInterval(handle);
82847
+ observedRunIds.clear();
82848
+ pendingFullSyncRunIds.clear();
82849
+ }
82850
+ };
82851
+ }
82852
+ var observedRunIds, pendingFullSyncRunIds;
82853
+ var init_log_observer_sync = __esm(() => {
82854
+ init_api3();
82855
+ init_convex_error();
82856
+ init_state2();
82857
+ observedRunIds = new Set;
82858
+ pendingFullSyncRunIds = new Set;
82859
+ });
82860
+
82787
82861
  // src/commands/machine/daemon-start/handlers/process/spawner.ts
82788
82862
  import { spawn as spawn4 } from "node:child_process";
82789
- async function flushTail(ctx, tracked) {
82790
- const tail = tracked.store.getTail();
82863
+ async function flushTailV2(ctx, tracked) {
82864
+ if (!isRunLogObserved(tracked.runId))
82865
+ return;
82866
+ const tail = await tracked.store.getLastNLines(MAX_TAIL_LINES_V2);
82791
82867
  if (tail.content.length === 0)
82792
82868
  return;
82793
82869
  const compressed = encodeOutput(tail.content);
82794
82870
  try {
82795
- await ctx.deps.backend.mutation(api.commands.updateRunTail, {
82871
+ await ctx.deps.backend.mutation(api.commands.updateRunTailV2, {
82796
82872
  sessionId: ctx.sessionId,
82797
82873
  machineId: ctx.machineId,
82798
82874
  runId: tracked.runId,
82799
82875
  tailOutput: {
82800
82876
  compression: compressed.compression,
82801
82877
  content: compressed.content,
82802
- byteLength: tail.content.length,
82878
+ byteLength: tail.totalBytes,
82803
82879
  totalBytesWritten: tail.totalBytes,
82804
- updatedAt: Date.now()
82880
+ updatedAt: Date.now(),
82881
+ lineCount: tail.lineCount
82805
82882
  }
82806
82883
  });
82807
82884
  } catch (err) {
82808
82885
  console.warn(`[${formatTimestamp()}] ⚠️ Failed to flush tail for run ${tracked.runId}: ${getErrorMessage(err)}`);
82809
82886
  }
82810
82887
  }
82811
- async function flushFinalChunks(ctx, tracked, runId) {
82812
- await flushTail(ctx, tracked);
82888
+ async function appendFullOutputChunks(ctx, tracked, runId) {
82813
82889
  let fullOutput;
82814
82890
  try {
82815
82891
  fullOutput = await tracked.store.getFullOutput();
@@ -82833,11 +82909,31 @@ async function flushFinalChunks(ctx, tracked, runId) {
82833
82909
  });
82834
82910
  chunkIndex++;
82835
82911
  } catch (err) {
82836
- console.error(`[${formatTimestamp()}] ❌ Failed to flush final chunk ${chunkIndex} for run ${tracked.runId}: ${getErrorMessage(err)}`);
82912
+ console.error(`[${formatTimestamp()}] ❌ Failed to flush chunk ${chunkIndex} for run ${tracked.runId}: ${getErrorMessage(err)}`);
82837
82913
  return;
82838
82914
  }
82839
82915
  }
82840
82916
  }
82917
+ async function flushFinalChunks(ctx, tracked, runId) {
82918
+ await flushTailV2(ctx, tracked);
82919
+ if (consumePendingFullSync(tracked.runId)) {
82920
+ await appendFullOutputChunks(ctx, tracked, runId);
82921
+ }
82922
+ }
82923
+ async function syncFullOutputOnRequest(ctx, tracked, runId) {
82924
+ if (!consumePendingFullSync(tracked.runId))
82925
+ return;
82926
+ await appendFullOutputChunks(ctx, tracked, runId);
82927
+ try {
82928
+ await ctx.deps.backend.mutation(api.commands.clearPendingFullOutputSync, {
82929
+ sessionId: ctx.sessionId,
82930
+ machineId: ctx.machineId,
82931
+ runId
82932
+ });
82933
+ } catch (err) {
82934
+ console.warn(`[${formatTimestamp()}] ⚠️ Failed to clear pending full sync for ${tracked.runId}: ${getErrorMessage(err)}`);
82935
+ }
82936
+ }
82841
82937
  async function spawnCommandProcess(ctx, event, commandKey) {
82842
82938
  const { workingDir, commandName, script, runId } = event;
82843
82939
  const runIdStr = runId.toString();
@@ -82859,7 +82955,8 @@ async function spawnCommandProcess(ctx, event, commandKey) {
82859
82955
  store,
82860
82956
  startedAt: Date.now(),
82861
82957
  flushTimer: setInterval(() => {
82862
- flushTail(ctx, tracked).catch(() => {});
82958
+ flushTailV2(ctx, tracked).catch(() => {});
82959
+ syncFullOutputOnRequest(ctx, tracked, runId).catch(() => {});
82863
82960
  }, OUTPUT_FLUSH_INTERVAL_MS),
82864
82961
  softTimeoutTimer: null,
82865
82962
  terminationIntent: null
@@ -82926,7 +83023,7 @@ async function spawnCommandProcess(ctx, event, commandKey) {
82926
83023
  }
82927
83024
  };
82928
83025
  child.on("exit", (code2, signal) => {
82929
- console.log(`[${formatTimestamp()}] \uD83C\uDFC1 Command exited: ${commandName} (code=${code2}, signal=${signal})`);
83026
+ console.log(`[${formatTimestamp()}] \uD83D\uDCCB Command exited: ${commandName} (code=${code2}, signal=${signal})`);
82930
83027
  finalize(code2, signal).catch(() => {});
82931
83028
  });
82932
83029
  child.on("error", async (err) => {
@@ -82945,6 +83042,7 @@ var init_spawner = __esm(() => {
82945
83042
  init_manager();
82946
83043
  init_killer();
82947
83044
  init_output_store();
83045
+ init_log_observer_sync();
82948
83046
  });
82949
83047
 
82950
83048
  // src/commands/machine/daemon-start/handlers/command-runner.ts
@@ -83327,6 +83425,16 @@ var init_crash_loop_tracker = __esm(() => {
83327
83425
  ];
83328
83426
  });
83329
83427
 
83428
+ // src/infrastructure/deps/process.ts
83429
+ function isProcessAlive(kill, pid) {
83430
+ try {
83431
+ kill(pid, 0);
83432
+ return true;
83433
+ } catch {
83434
+ return false;
83435
+ }
83436
+ }
83437
+
83330
83438
  // src/infrastructure/machine/stop-reason.ts
83331
83439
  function resolveStopReason(code2, signal) {
83332
83440
  if (signal !== null)
@@ -83449,16 +83557,24 @@ class AgentProcessManager {
83449
83557
  async ensureRunning(opts) {
83450
83558
  const key = agentKey2(opts.chatroomId, opts.role);
83451
83559
  const slot = this.getOrCreateSlot(key);
83452
- if (slot.state === "running") {
83453
- return { success: true, pid: slot.pid };
83454
- }
83455
- if (slot.state === "spawning" && slot.pendingOperation) {
83456
- return slot.pendingOperation;
83560
+ if (slot.state === "running" && slot.pid && !isProcessAlive(this.deps.processes.kill, slot.pid)) {
83561
+ slot.state = "idle";
83562
+ slot.pid = undefined;
83563
+ slot.harness = undefined;
83564
+ slot.harnessSessionId = undefined;
83565
+ slot.model = undefined;
83566
+ slot.workingDir = undefined;
83567
+ slot.startedAt = undefined;
83568
+ slot.pendingOperation = undefined;
83457
83569
  }
83458
- if (slot.state === "stopping" && slot.pendingOperation) {
83459
- await slot.pendingOperation;
83570
+ if (slot.pendingOperation) {
83571
+ if (slot.state === "stopping") {
83572
+ await slot.pendingOperation;
83573
+ } else {
83574
+ return slot.pendingOperation;
83575
+ }
83460
83576
  }
83461
- const operation = this.doEnsureRunning(key, slot, opts);
83577
+ const operation = this.executeEnsureRunning(key, slot, opts);
83462
83578
  slot.pendingOperation = operation;
83463
83579
  return operation;
83464
83580
  }
@@ -83621,31 +83737,38 @@ class AgentProcessManager {
83621
83737
  } catch (err) {
83622
83738
  console.warn(`[AgentProcessManager] ⚠️ Failed to load persisted agent entries: ${err.message}`);
83623
83739
  }
83624
- let recovered = 0;
83740
+ let killed = 0;
83625
83741
  let cleaned = 0;
83626
83742
  for (const { chatroomId, role, entry } of entries2) {
83627
- const key = agentKey2(chatroomId, role);
83628
- let alive = false;
83629
- try {
83630
- this.deps.processes.kill(entry.pid, 0);
83631
- alive = true;
83632
- } catch {
83633
- alive = false;
83634
- }
83635
- if (alive) {
83636
- this.slots.set(key, {
83637
- state: "running",
83743
+ if (isProcessAlive(this.deps.processes.kill, entry.pid)) {
83744
+ await this.stopPersistedProcess(entry.pid, entry.harness);
83745
+ const exitArgs = {
83746
+ sessionId: this.deps.sessionId,
83747
+ machineId: this.deps.machineId,
83748
+ chatroomId,
83749
+ role,
83638
83750
  pid: entry.pid,
83639
- harness: entry.harness,
83640
- wantResume: true
83751
+ stopReason: "daemon.shutdown",
83752
+ exitCode: undefined,
83753
+ signal: undefined,
83754
+ agentHarness: entry.harness
83755
+ };
83756
+ this.deps.backend.mutation(api.machines.recordAgentExited, exitArgs).catch((err) => {
83757
+ console.log(` ⚠️ Failed to record agent exit on recovery: ${err.message}`);
83758
+ this.queueExitRetry({ role, args: exitArgs });
83641
83759
  });
83642
- recovered++;
83760
+ try {
83761
+ await this.deps.persistence.clearAgentPid(this.deps.machineId, chatroomId, role);
83762
+ } catch {}
83763
+ killed++;
83643
83764
  } else {
83644
- this.deps.persistence.clearAgentPid(this.deps.machineId, chatroomId, role);
83765
+ try {
83766
+ await this.deps.persistence.clearAgentPid(this.deps.machineId, chatroomId, role);
83767
+ } catch {}
83645
83768
  cleaned++;
83646
83769
  }
83647
83770
  }
83648
- console.log(`[AgentProcessManager] Recovery: ${recovered} alive, ${cleaned} cleaned up`);
83771
+ console.log(`[AgentProcessManager] Recovery: ${killed} killed, ${cleaned} cleaned up`);
83649
83772
  }
83650
83773
  getOrCreateSlot(key) {
83651
83774
  let slot = this.slots.get(key);
@@ -83655,6 +83778,82 @@ class AgentProcessManager {
83655
83778
  }
83656
83779
  return slot;
83657
83780
  }
83781
+ async stopPersistedProcess(pid, harness) {
83782
+ const service = this.deps.agentServices.get(harness);
83783
+ if (service) {
83784
+ try {
83785
+ await service.stop(pid);
83786
+ service.untrack(pid);
83787
+ } catch {}
83788
+ } else {
83789
+ try {
83790
+ this.deps.processes.kill(-pid, "SIGTERM");
83791
+ } catch {}
83792
+ for (const svc of this.deps.agentServices.values()) {
83793
+ svc.untrack(pid);
83794
+ }
83795
+ }
83796
+ untrackChildPid(pid);
83797
+ }
83798
+ async killExistingBeforeSpawn(chatroomId, role) {
83799
+ const key = agentKey2(chatroomId, role);
83800
+ const slot = this.slots.get(key);
83801
+ if (slot?.pid && isProcessAlive(this.deps.processes.kill, slot.pid) && (slot.state === "running" || slot.state === "spawning")) {
83802
+ const pid2 = slot.pid;
83803
+ slot.state = "stopping";
83804
+ await this.doStop(key, slot, pid2, { chatroomId, role, reason: "daemon.respawn" });
83805
+ }
83806
+ let entries2 = [];
83807
+ try {
83808
+ entries2 = await this.deps.persistence.listAgentEntries(this.deps.machineId);
83809
+ } catch {
83810
+ return;
83811
+ }
83812
+ const persisted = entries2.find((e) => e.chatroomId === chatroomId && e.role.toLowerCase() === role.toLowerCase());
83813
+ if (!persisted) {
83814
+ return;
83815
+ }
83816
+ const { pid, harness } = persisted.entry;
83817
+ if (!isProcessAlive(this.deps.processes.kill, pid)) {
83818
+ try {
83819
+ await this.deps.persistence.clearAgentPid(this.deps.machineId, chatroomId, role);
83820
+ } catch {}
83821
+ return;
83822
+ }
83823
+ const currentSlot = this.slots.get(key);
83824
+ if (currentSlot?.pid === pid && currentSlot.state !== "idle") {
83825
+ return;
83826
+ }
83827
+ await this.stopPersistedProcess(pid, harness);
83828
+ const exitArgs = {
83829
+ sessionId: this.deps.sessionId,
83830
+ machineId: this.deps.machineId,
83831
+ chatroomId,
83832
+ role,
83833
+ pid,
83834
+ stopReason: "daemon.respawn",
83835
+ exitCode: undefined,
83836
+ signal: undefined,
83837
+ agentHarness: harness
83838
+ };
83839
+ this.deps.backend.mutation(api.machines.recordAgentExited, exitArgs).catch((err) => {
83840
+ console.log(` ⚠️ Failed to record agent exit before respawn: ${err.message}`);
83841
+ this.queueExitRetry({ role, args: exitArgs });
83842
+ });
83843
+ try {
83844
+ await this.deps.persistence.clearAgentPid(this.deps.machineId, chatroomId, role);
83845
+ } catch {}
83846
+ }
83847
+ async executeEnsureRunning(key, slot, opts) {
83848
+ try {
83849
+ await this.killExistingBeforeSpawn(opts.chatroomId, opts.role);
83850
+ return await this.doEnsureRunning(key, slot, opts);
83851
+ } finally {
83852
+ if (slot.pendingOperation) {
83853
+ slot.pendingOperation = undefined;
83854
+ }
83855
+ }
83856
+ }
83658
83857
  queueExitRetry(item) {
83659
83858
  this.exitRetryQueue.push(item);
83660
83859
  if (this.exitRetryTimer === null) {
@@ -83740,12 +83939,6 @@ class AgentProcessManager {
83740
83939
  slot.pendingOperation = undefined;
83741
83940
  return { success: false, error: `Working directory does not exist: ${opts.workingDir}` };
83742
83941
  }
83743
- if (slot.pid) {
83744
- try {
83745
- this.deps.processes.kill(-slot.pid, "SIGTERM");
83746
- } catch {}
83747
- slot.pid = undefined;
83748
- }
83749
83942
  let initPromptResult;
83750
83943
  try {
83751
83944
  initPromptResult = await this.deps.backend.query(api.messages.getInitPrompt, {
@@ -83865,9 +84058,7 @@ class AgentProcessManager {
83865
84058
  let dead = false;
83866
84059
  for (let i2 = 0;i2 < 20; i2++) {
83867
84060
  await this.deps.clock.delay(500);
83868
- try {
83869
- this.deps.processes.kill(pid, 0);
83870
- } catch {
84061
+ if (!isProcessAlive(this.deps.processes.kill, pid)) {
83871
84062
  dead = true;
83872
84063
  break;
83873
84064
  }
@@ -83878,9 +84069,7 @@ class AgentProcessManager {
83878
84069
  } catch {}
83879
84070
  for (let i2 = 0;i2 < 10; i2++) {
83880
84071
  await this.deps.clock.delay(500);
83881
- try {
83882
- this.deps.processes.kill(pid, 0);
83883
- } catch {
84072
+ if (!isProcessAlive(this.deps.processes.kill, pid)) {
83884
84073
  dead = true;
83885
84074
  break;
83886
84075
  }
@@ -83918,6 +84107,7 @@ class AgentProcessManager {
83918
84107
  }
83919
84108
  var AGENT_EXIT_RETRY_INTERVAL_MS = 1e4;
83920
84109
  var init_agent_process_manager = __esm(() => {
84110
+ init_orphan_tracker();
83921
84111
  init_api3();
83922
84112
  init_types();
83923
84113
  init_generator();
@@ -85184,6 +85374,7 @@ async function startCommandLoop(ctx) {
85184
85374
  let fileContentSubscriptionHandle = null;
85185
85375
  let fileTreeSubscriptionHandle = null;
85186
85376
  let observedSyncSubscriptionHandle = null;
85377
+ let logObserverPollHandle = null;
85187
85378
  let pendingPromptSubscriptionHandle = null;
85188
85379
  let pendingHarnessSessionSubscriptionHandle = null;
85189
85380
  let commandSubscriptionHandle = null;
@@ -85209,6 +85400,8 @@ async function startCommandLoop(ctx) {
85209
85400
  fileTreeSubscriptionHandle.stop();
85210
85401
  if (observedSyncSubscriptionHandle)
85211
85402
  observedSyncSubscriptionHandle.stop();
85403
+ if (logObserverPollHandle)
85404
+ logObserverPollHandle.stop();
85212
85405
  if (pendingPromptSubscriptionHandle)
85213
85406
  pendingPromptSubscriptionHandle.stop();
85214
85407
  if (pendingHarnessSessionSubscriptionHandle)
@@ -85237,6 +85430,7 @@ async function startCommandLoop(ctx) {
85237
85430
  if (ctx.observedSyncEnabled) {
85238
85431
  observedSyncSubscriptionHandle = startObservedSyncSubscription(ctx, wsClient2);
85239
85432
  }
85433
+ logObserverPollHandle = startLogObserverPoll(ctx);
85240
85434
  if (featureFlags.directHarnessWorkers) {
85241
85435
  const sessionRepository = new ConvexSessionRepository({
85242
85436
  backend: ctx.deps.backend,
@@ -85325,6 +85519,7 @@ var init_command_loop = __esm(() => {
85325
85519
  init_command_runner();
85326
85520
  init_manager();
85327
85521
  init_init2();
85522
+ init_log_observer_sync();
85328
85523
  init_observed_sync();
85329
85524
  init_api3();
85330
85525
  init_client2();
@@ -86504,4 +86699,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
86504
86699
  });
86505
86700
  program2.parse();
86506
86701
 
86507
- //# debugId=1017D812F7A83DCE64756E2164756E21
86702
+ //# debugId=3E031CCB2CBFFA9464756E2164756E21