chatroom-cli 1.10.0 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +164 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14908,7 +14908,8 @@ async function initDaemon() {
14908
14908
  events,
14909
14909
  agentServices,
14910
14910
  activeWorkingDirs: new Set,
14911
- lastPushedGitState: new Map
14911
+ lastPushedGitState: new Map,
14912
+ agentEndedTurn: new Map
14912
14913
  };
14913
14914
  registerEventListeners(ctx);
14914
14915
  logStartup(ctx, availableModels);
@@ -15032,6 +15033,8 @@ async function executeStartAgent(ctx, args) {
15032
15033
  const { pid } = spawnResult;
15033
15034
  const msg = `Agent spawned (PID: ${pid})`;
15034
15035
  console.log(` ✅ ${msg}`);
15036
+ const agentEndKey = `${chatroomId}:${role.toLowerCase()}`;
15037
+ ctx.agentEndedTurn.delete(agentEndKey);
15035
15038
  ctx.deps.spawning.recordSpawn(chatroomId);
15036
15039
  try {
15037
15040
  await ctx.deps.backend.mutation(api.machines.updateSpawnedAgent, {
@@ -15072,6 +15075,7 @@ async function executeStartAgent(ctx, args) {
15072
15075
  });
15073
15076
  if (spawnResult.onAgentEnd) {
15074
15077
  spawnResult.onAgentEnd(() => {
15078
+ ctx.agentEndedTurn.set(agentEndKey, true);
15075
15079
  try {
15076
15080
  ctx.deps.processes.kill(-pid, "SIGTERM");
15077
15081
  } catch {}
@@ -15729,6 +15733,162 @@ var init_git_heartbeat = __esm(() => {
15729
15733
  init_git_polling();
15730
15734
  });
15731
15735
 
15736
+ // src/infrastructure/services/remote-agents/harness-restart-policy.ts
15737
+ class OpenCodeRestartPolicy {
15738
+ id = "opencode";
15739
+ shouldStartAgent(params) {
15740
+ const { task, agentConfig } = params;
15741
+ if (agentConfig.desiredState !== "running") {
15742
+ return false;
15743
+ }
15744
+ if (agentConfig.circuitState === "open") {
15745
+ return false;
15746
+ }
15747
+ if (task.status === "in_progress") {
15748
+ return agentConfig.spawnedAgentPid == null;
15749
+ }
15750
+ if (task.status === "pending" || task.status === "acknowledged") {
15751
+ return agentConfig.spawnedAgentPid == null;
15752
+ }
15753
+ return false;
15754
+ }
15755
+ }
15756
+
15757
+ class PiRestartPolicy {
15758
+ id = "pi";
15759
+ shouldStartAgent(params, context) {
15760
+ const { task, agentConfig } = params;
15761
+ if (agentConfig.desiredState !== "running") {
15762
+ return false;
15763
+ }
15764
+ if (agentConfig.circuitState === "open") {
15765
+ return false;
15766
+ }
15767
+ if (task.status === "in_progress") {
15768
+ return agentConfig.spawnedAgentPid == null;
15769
+ }
15770
+ if (task.status === "pending" || task.status === "acknowledged") {
15771
+ if (agentConfig.spawnedAgentPid == null) {
15772
+ return true;
15773
+ }
15774
+ }
15775
+ if (task.status !== "pending" && task.status !== "acknowledged") {
15776
+ return false;
15777
+ }
15778
+ if (!context?.agentEndedTurn) {
15779
+ return false;
15780
+ }
15781
+ const key = `${task.chatroomId}:${agentConfig.role}`;
15782
+ const hasEndedTurn = context.agentEndedTurn.get(key);
15783
+ return hasEndedTurn === true;
15784
+ }
15785
+ }
15786
+ function getRestartPolicyForHarness(harness) {
15787
+ switch (harness) {
15788
+ case "opencode":
15789
+ return new OpenCodeRestartPolicy;
15790
+ case "pi":
15791
+ return new PiRestartPolicy;
15792
+ default:
15793
+ return {
15794
+ id: harness,
15795
+ shouldStartAgent: () => false
15796
+ };
15797
+ }
15798
+ }
15799
+
15800
+ // src/commands/machine/daemon-start/task-monitor.ts
15801
+ function canAttemptRestart(chatroomId, role, now) {
15802
+ const key = `${chatroomId}:${role.toLowerCase()}`;
15803
+ const lastAttempt = lastRestartAttempt.get(key) ?? 0;
15804
+ return now - lastAttempt >= RESTART_COOLDOWN_MS;
15805
+ }
15806
+ function recordRestartAttempt(chatroomId, role, now) {
15807
+ const key = `${chatroomId}:${role.toLowerCase()}`;
15808
+ lastRestartAttempt.set(key, now);
15809
+ }
15810
+ function startTaskMonitor(ctx) {
15811
+ let unsubscribe = null;
15812
+ let isRunning = true;
15813
+ const startMonitoring = async () => {
15814
+ try {
15815
+ const wsClient2 = await getConvexWsClient();
15816
+ const agentEndContext = {
15817
+ agentEndedTurn: ctx.agentEndedTurn
15818
+ };
15819
+ unsubscribe = wsClient2.onUpdate(api.machines.getAssignedTasks, {
15820
+ sessionId: ctx.sessionId,
15821
+ machineId: ctx.machineId
15822
+ }, async (result) => {
15823
+ if (!result?.tasks || result.tasks.length === 0)
15824
+ return;
15825
+ const now = Date.now();
15826
+ for (const taskInfo of result.tasks) {
15827
+ const { task, agentConfig } = {
15828
+ task: {
15829
+ taskId: taskInfo.taskId,
15830
+ chatroomId: taskInfo.chatroomId,
15831
+ status: taskInfo.status,
15832
+ assignedTo: taskInfo.assignedTo,
15833
+ updatedAt: taskInfo.updatedAt,
15834
+ createdAt: taskInfo.createdAt
15835
+ },
15836
+ agentConfig: taskInfo.agentConfig
15837
+ };
15838
+ const policy = getRestartPolicyForHarness(agentConfig.agentHarness);
15839
+ const shouldStart = policy.shouldStartAgent({ task, agentConfig }, agentEndContext);
15840
+ if (!shouldStart)
15841
+ continue;
15842
+ if (!canAttemptRestart(task.chatroomId, agentConfig.role, now)) {
15843
+ continue;
15844
+ }
15845
+ if (!agentConfig.workingDir) {
15846
+ console.warn(`[${formatTimestamp()}] ⚠️ Missing workingDir for ${task.chatroomId}/${agentConfig.role}` + ` — skipping`);
15847
+ continue;
15848
+ }
15849
+ console.log(`[${formatTimestamp()}] \uD83D\uDCE1 Task monitor: starting agent for ` + `${task.chatroomId}/${agentConfig.role} (harness: ${agentConfig.agentHarness})`);
15850
+ recordRestartAttempt(task.chatroomId, agentConfig.role, now);
15851
+ try {
15852
+ await executeStartAgent(ctx, {
15853
+ chatroomId: task.chatroomId,
15854
+ role: agentConfig.role,
15855
+ agentHarness: agentConfig.agentHarness,
15856
+ model: agentConfig.model,
15857
+ workingDir: agentConfig.workingDir,
15858
+ reason: "daemon.task_monitor"
15859
+ });
15860
+ } catch (err) {
15861
+ console.error(`[${formatTimestamp()}] ❌ Task monitor failed to start agent ` + `for ${task.chatroomId}/${agentConfig.role}: ${err.message}`);
15862
+ }
15863
+ }
15864
+ });
15865
+ console.log(`[${formatTimestamp()}] \uD83D\uDD0D Task monitor started`);
15866
+ } catch (err) {
15867
+ if (isRunning) {
15868
+ console.error(`[${formatTimestamp()}] ❌ Task monitor error: ${err.message}`);
15869
+ setTimeout(startMonitoring, 5000);
15870
+ }
15871
+ }
15872
+ };
15873
+ startMonitoring();
15874
+ return {
15875
+ stop: () => {
15876
+ isRunning = false;
15877
+ if (unsubscribe) {
15878
+ unsubscribe();
15879
+ unsubscribe = null;
15880
+ }
15881
+ }
15882
+ };
15883
+ }
15884
+ var RESTART_COOLDOWN_MS = 60000, lastRestartAttempt;
15885
+ var init_task_monitor = __esm(() => {
15886
+ init_api3();
15887
+ init_client2();
15888
+ init_start_agent();
15889
+ lastRestartAttempt = new Map;
15890
+ });
15891
+
15732
15892
  // src/commands/machine/daemon-start/command-loop.ts
15733
15893
  async function refreshModels(ctx) {
15734
15894
  if (!ctx.config)
@@ -15816,12 +15976,14 @@ async function startCommandLoop(ctx) {
15816
15976
  }, DAEMON_HEARTBEAT_INTERVAL_MS);
15817
15977
  heartbeatTimer.unref();
15818
15978
  const gitPollingHandle = startGitPollingLoop(ctx);
15979
+ const taskMonitorHandle = startTaskMonitor(ctx);
15819
15980
  pushGitState(ctx).catch(() => {});
15820
15981
  const shutdown = async () => {
15821
15982
  console.log(`
15822
15983
  [${formatTimestamp()}] Shutting down...`);
15823
15984
  clearInterval(heartbeatTimer);
15824
15985
  gitPollingHandle.stop();
15986
+ taskMonitorHandle.stop();
15825
15987
  await onDaemonShutdown(ctx);
15826
15988
  releaseLock();
15827
15989
  process.exit(0);
@@ -15873,6 +16035,7 @@ var init_command_loop = __esm(() => {
15873
16035
  init_pid();
15874
16036
  init_git_polling();
15875
16037
  init_git_heartbeat();
16038
+ init_task_monitor();
15876
16039
  MODEL_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
15877
16040
  });
15878
16041
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatroom-cli",
3
- "version": "1.10.0",
3
+ "version": "1.11.1",
4
4
  "description": "CLI for multi-agent chatroom collaboration",
5
5
  "type": "module",
6
6
  "bin": {