chatroom-cli 1.0.79 → 1.0.82

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 +112 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10263,7 +10263,12 @@ class ProcessDriver {
10263
10263
  return {
10264
10264
  success: true,
10265
10265
  message: "Agent spawned successfully",
10266
- handle
10266
+ handle,
10267
+ onExit: (callback) => {
10268
+ childProcess.on("exit", (code2, signal) => {
10269
+ callback(code2, signal);
10270
+ });
10271
+ }
10267
10272
  };
10268
10273
  } catch (error) {
10269
10274
  return {
@@ -10276,7 +10281,26 @@ class ProcessDriver {
10276
10281
  if (handle.type !== "process" || !handle.pid) {
10277
10282
  throw new Error(`Cannot stop: handle has no PID (type=${handle.type})`);
10278
10283
  }
10279
- process.kill(handle.pid, "SIGTERM");
10284
+ const pid = handle.pid;
10285
+ try {
10286
+ process.kill(pid, "SIGTERM");
10287
+ } catch {
10288
+ return;
10289
+ }
10290
+ const KILL_TIMEOUT_MS = 5000;
10291
+ const POLL_INTERVAL_MS = 200;
10292
+ const deadline = Date.now() + KILL_TIMEOUT_MS;
10293
+ while (Date.now() < deadline) {
10294
+ try {
10295
+ process.kill(pid, 0);
10296
+ } catch {
10297
+ return;
10298
+ }
10299
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
10300
+ }
10301
+ try {
10302
+ process.kill(pid, "SIGKILL");
10303
+ } catch {}
10280
10304
  }
10281
10305
  async isAlive(handle) {
10282
10306
  if (handle.type !== "process" || !handle.pid) {
@@ -10677,6 +10701,9 @@ var init_register_agent = __esm(() => {
10677
10701
  init_client2();
10678
10702
  init_machine();
10679
10703
  });
10704
+
10705
+ // ../../services/backend/config/reliability.ts
10706
+ var HEARTBEAT_INTERVAL_MS = 30000, HEARTBEAT_TTL_MS = 90000, DAEMON_HEARTBEAT_INTERVAL_MS = 30000;
10680
10707
  // ../../services/backend/prompts/base/cli/task-started/command.ts
10681
10708
  function taskStartedCommand(params) {
10682
10709
  const prefix = params.cliEnvPrefix || "";
@@ -10904,6 +10931,7 @@ async function waitForTask(chatroomId, options) {
10904
10931
  sessionId,
10905
10932
  chatroomId,
10906
10933
  role,
10934
+ readyUntil: Date.now() + HEARTBEAT_TTL_MS,
10907
10935
  connectionId
10908
10936
  });
10909
10937
  const connectionTime = new Date().toISOString().replace("T", " ").substring(0, 19);
@@ -10911,12 +10939,11 @@ async function waitForTask(chatroomId, options) {
10911
10939
  console.log(`[${connectionTime}] ⏳ Connecting to chatroom as "${role}"...`);
10912
10940
  }
10913
10941
  try {
10914
- const convexUrl2 = getConvexUrl();
10915
10942
  const initPromptResult = await client2.query(api.messages.getInitPrompt, {
10916
10943
  sessionId,
10917
10944
  chatroomId,
10918
10945
  role,
10919
- convexUrl: convexUrl2
10946
+ convexUrl
10920
10947
  });
10921
10948
  if (initPromptResult?.prompt) {
10922
10949
  const connectedTime = new Date().toISOString().replace("T", " ").substring(0, 19);
@@ -10941,6 +10968,46 @@ async function waitForTask(chatroomId, options) {
10941
10968
  }
10942
10969
  }
10943
10970
  } catch {}
10971
+ const heartbeatTimer = setInterval(() => {
10972
+ client2.mutation(api.participants.heartbeat, {
10973
+ sessionId,
10974
+ chatroomId,
10975
+ role,
10976
+ connectionId
10977
+ }).then((result) => {
10978
+ if (result?.status === "rejoin_required") {
10979
+ if (!silent) {
10980
+ console.warn(`⚠️ Participant record missing — re-joining chatroom`);
10981
+ }
10982
+ return client2.mutation(api.participants.join, {
10983
+ sessionId,
10984
+ chatroomId,
10985
+ role,
10986
+ readyUntil: Date.now() + HEARTBEAT_TTL_MS,
10987
+ connectionId
10988
+ });
10989
+ }
10990
+ }).catch((err) => {
10991
+ if (!silent) {
10992
+ console.warn(`⚠️ Heartbeat failed: ${err.message}`);
10993
+ }
10994
+ });
10995
+ }, HEARTBEAT_INTERVAL_MS);
10996
+ heartbeatTimer.unref();
10997
+ let cleanedUp = false;
10998
+ const cleanup = async () => {
10999
+ if (cleanedUp)
11000
+ return;
11001
+ cleanedUp = true;
11002
+ clearInterval(heartbeatTimer);
11003
+ try {
11004
+ await client2.mutation(api.participants.leave, {
11005
+ sessionId,
11006
+ chatroomId,
11007
+ role
11008
+ });
11009
+ } catch {}
11010
+ };
10944
11011
  let taskProcessed = false;
10945
11012
  let unsubscribe = null;
10946
11013
  const handlePendingTasks = async (pendingTasks) => {
@@ -10954,6 +11021,8 @@ async function waitForTask(chatroomId, options) {
10954
11021
  if (currentConnectionId && currentConnectionId !== connectionId) {
10955
11022
  if (unsubscribe)
10956
11023
  unsubscribe();
11024
+ clearInterval(heartbeatTimer);
11025
+ cleanedUp = true;
10957
11026
  const takeoverTime = new Date().toISOString().replace("T", " ").substring(0, 19);
10958
11027
  console.log(`
10959
11028
  ${"─".repeat(50)}`);
@@ -11004,6 +11073,8 @@ ${"─".repeat(50)}`);
11004
11073
  }
11005
11074
  if (unsubscribe)
11006
11075
  unsubscribe();
11076
+ clearInterval(heartbeatTimer);
11077
+ cleanedUp = true;
11007
11078
  const activeUntil = Date.now() + DEFAULT_ACTIVE_TIMEOUT_MS;
11008
11079
  await client2.mutation(api.participants.updateStatus, {
11009
11080
  sessionId,
@@ -11040,18 +11111,20 @@ ${"─".repeat(50)}`);
11040
11111
  const handleSignal = (_signal) => {
11041
11112
  if (unsubscribe)
11042
11113
  unsubscribe();
11043
- const signalTime = new Date().toISOString().replace("T", " ").substring(0, 19);
11044
- console.log(`
11114
+ cleanup().finally(() => {
11115
+ const signalTime = new Date().toISOString().replace("T", " ").substring(0, 19);
11116
+ console.log(`
11045
11117
  ${"─".repeat(50)}`);
11046
- console.log(`⚠️ RECONNECTION REQUIRED
11118
+ console.log(`⚠️ RECONNECTION REQUIRED
11047
11119
  `);
11048
- console.log(`[${signalTime}] Why: Process interrupted (unexpected termination)`);
11049
- console.log(`Impact: You are no longer listening for tasks`);
11050
- console.log(`Action: Run this command immediately to resume availability
11120
+ console.log(`[${signalTime}] Why: Process interrupted (unexpected termination)`);
11121
+ console.log(`Impact: You are no longer listening for tasks`);
11122
+ console.log(`Action: Run this command immediately to resume availability
11051
11123
  `);
11052
- console.log(waitForTaskCommand({ chatroomId, role, cliEnvPrefix }));
11053
- console.log(`${"─".repeat(50)}`);
11054
- process.exit(0);
11124
+ console.log(waitForTaskCommand({ chatroomId, role, cliEnvPrefix }));
11125
+ console.log(`${"─".repeat(50)}`);
11126
+ process.exit(0);
11127
+ });
11055
11128
  };
11056
11129
  process.on("SIGINT", () => handleSignal("SIGINT"));
11057
11130
  process.on("SIGTERM", () => handleSignal("SIGTERM"));
@@ -13066,6 +13139,16 @@ async function handleStartAgent(ctx, command) {
13066
13139
  } catch (e) {
13067
13140
  console.log(` ⚠️ Failed to update PID in backend: ${e.message}`);
13068
13141
  }
13142
+ if (startResult.onExit) {
13143
+ const spawnedPid = startResult.handle.pid;
13144
+ startResult.onExit((code2, signal) => {
13145
+ const ts = formatTimestamp();
13146
+ console.log(`[${ts}] ⚠️ Agent process exited unexpectedly ` + `(PID: ${spawnedPid}, role: ${role}, code: ${code2}, signal: ${signal})`);
13147
+ clearAgentPidEverywhere(ctx, chatroomId, role).catch((err) => {
13148
+ console.log(` ⚠️ Failed to clear PID after exit: ${err.message}`);
13149
+ });
13150
+ });
13151
+ }
13069
13152
  }
13070
13153
  return { result: msg, failed: false };
13071
13154
  }
@@ -13310,6 +13393,7 @@ Run any chatroom command first to register this machine,`);
13310
13393
  }
13311
13394
  const ctx = { client: client2, sessionId: typedSessionId, machineId, config: config3 };
13312
13395
  console.log(`[${formatTimestamp()}] \uD83D\uDE80 Daemon started`);
13396
+ console.log(` CLI version: ${getVersion()}`);
13313
13397
  console.log(` Machine ID: ${machineId}`);
13314
13398
  console.log(` Hostname: ${config3?.hostname ?? "Unknown"}`);
13315
13399
  console.log(` Available harnesses: ${config3?.availableHarnesses.join(", ") || "none"}`);
@@ -13326,9 +13410,23 @@ Run any chatroom command first to register this machine,`);
13326
13410
  return ctx;
13327
13411
  }
13328
13412
  async function startCommandLoop(ctx) {
13413
+ let heartbeatCount = 0;
13414
+ const heartbeatTimer = setInterval(() => {
13415
+ ctx.client.mutation(api.machines.daemonHeartbeat, {
13416
+ sessionId: ctx.sessionId,
13417
+ machineId: ctx.machineId
13418
+ }).then(() => {
13419
+ heartbeatCount++;
13420
+ console.log(`[${formatTimestamp()}] \uD83D\uDC93 Daemon heartbeat #${heartbeatCount} OK`);
13421
+ }).catch((err) => {
13422
+ console.warn(`[${formatTimestamp()}] ⚠️ Daemon heartbeat failed: ${err.message}`);
13423
+ });
13424
+ }, DAEMON_HEARTBEAT_INTERVAL_MS);
13425
+ heartbeatTimer.unref();
13329
13426
  const shutdown = async () => {
13330
13427
  console.log(`
13331
13428
  [${formatTimestamp()}] Shutting down...`);
13429
+ clearInterval(heartbeatTimer);
13332
13430
  try {
13333
13431
  await ctx.client.mutation(api.machines.updateDaemonStatus, {
13334
13432
  sessionId: ctx.sessionId,
@@ -13424,6 +13522,7 @@ var init_daemon_start = __esm(() => {
13424
13522
  init_storage();
13425
13523
  init_client2();
13426
13524
  init_machine();
13525
+ init_version();
13427
13526
  MODEL_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
13428
13527
  });
13429
13528
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatroom-cli",
3
- "version": "1.0.79",
3
+ "version": "1.0.82",
4
4
  "description": "CLI for multi-agent chatroom collaboration",
5
5
  "type": "module",
6
6
  "bin": {