chatroom-cli 1.2.4 → 1.2.7

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 +256 -361
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10371,6 +10371,20 @@ function listAgentEntries(machineId) {
10371
10371
  }
10372
10372
  return results;
10373
10373
  }
10374
+ function persistEventCursor(machineId, lastSeenEventId) {
10375
+ try {
10376
+ const state = loadOrCreate(machineId);
10377
+ state.lastSeenEventId = lastSeenEventId;
10378
+ state.updatedAt = new Date().toISOString();
10379
+ saveDaemonState(state);
10380
+ } catch (err) {
10381
+ console.warn(`⚠️ Failed to persist event cursor: ${err.message}`);
10382
+ }
10383
+ }
10384
+ function loadEventCursor(machineId) {
10385
+ const state = loadDaemonState(machineId);
10386
+ return state?.lastSeenEventId ?? null;
10387
+ }
10374
10388
  var CHATROOM_DIR3, STATE_DIR, STATE_VERSION = "1";
10375
10389
  var init_daemon_state = __esm(() => {
10376
10390
  CHATROOM_DIR3 = join4(homedir3(), ".chatroom");
@@ -11314,7 +11328,7 @@ var init_register_agent = __esm(() => {
11314
11328
  init_opencode();
11315
11329
  init_pi();
11316
11330
  });
11317
- // ../../services/backend/prompts/base/cli/task-started/command.ts
11331
+ // ../../services/backend/prompts/cli/task-started/command.ts
11318
11332
  function taskStartedCommand(params) {
11319
11333
  const prefix = params.cliEnvPrefix || "";
11320
11334
  const chatroomId = params.chatroomId || "<chatroom-id>";
@@ -11338,22 +11352,22 @@ EOF`;
11338
11352
  return baseCmd;
11339
11353
  }
11340
11354
 
11341
- // ../../services/backend/prompts/base/cli/task-started/classification/new-feature.ts
11355
+ // ../../services/backend/prompts/cli/task-started/classification/new-feature.ts
11342
11356
  var init_new_feature = () => {};
11343
- // ../../services/backend/prompts/base/cli/task-started/classification/index.ts
11357
+ // ../../services/backend/prompts/cli/task-started/classification/index.ts
11344
11358
  var init_classification = __esm(() => {
11345
11359
  init_new_feature();
11346
11360
  });
11347
- // ../../services/backend/prompts/base/cli/task-started/main-prompt.ts
11361
+ // ../../services/backend/prompts/cli/task-started/main-prompt.ts
11348
11362
  var init_main_prompt = () => {};
11349
11363
 
11350
- // ../../services/backend/prompts/base/cli/task-started/index.ts
11364
+ // ../../services/backend/prompts/cli/task-started/index.ts
11351
11365
  var init_task_started = __esm(() => {
11352
11366
  init_classification();
11353
11367
  init_main_prompt();
11354
11368
  });
11355
11369
 
11356
- // ../../services/backend/prompts/base/cli/get-next-task/reminder.ts
11370
+ // ../../services/backend/prompts/cli/get-next-task/reminder.ts
11357
11371
  function getNextTaskGuidance() {
11358
11372
  return `\uD83D\uDD17 STAYING CONNECTED TO YOUR TEAM
11359
11373
 
@@ -11442,7 +11456,7 @@ var init_getting_started_content = __esm(() => {
11442
11456
  init_utils();
11443
11457
  });
11444
11458
 
11445
- // ../../services/backend/prompts/base/cli/index.ts
11459
+ // ../../services/backend/prompts/cli/index.ts
11446
11460
  var init_cli = __esm(() => {
11447
11461
  init_task_started();
11448
11462
  init_reminder();
@@ -11468,7 +11482,7 @@ var init_errorCodes = __esm(() => {
11468
11482
  ];
11469
11483
  });
11470
11484
 
11471
- // ../../services/backend/prompts/base/cli/get-next-task/command.ts
11485
+ // ../../services/backend/prompts/cli/get-next-task/command.ts
11472
11486
  function getNextTaskCommand(params) {
11473
11487
  const prefix = params.cliEnvPrefix || "";
11474
11488
  const chatroomId = params.chatroomId || "<chatroom-id>";
@@ -12169,7 +12183,7 @@ function formatDecodeError(error) {
12169
12183
  return message;
12170
12184
  }
12171
12185
 
12172
- // ../../services/backend/prompts/base/cli/handoff/command.ts
12186
+ // ../../services/backend/prompts/cli/handoff/command.ts
12173
12187
  function handoffCommand(params) {
12174
12188
  const prefix = params.cliEnvPrefix || "";
12175
12189
  const chatroomId = params.chatroomId || "<chatroom-id>";
@@ -13643,6 +13657,7 @@ async function getSystemPrompt(chatroomId, options, deps) {
13643
13657
  const prompt = await d.backend.query(api.prompts.webapp.getAgentPrompt, {
13644
13658
  chatroomId,
13645
13659
  role,
13660
+ teamId: chatroom.teamId,
13646
13661
  teamName: chatroom.teamName,
13647
13662
  teamRoles: chatroom.teamRoles,
13648
13663
  teamEntryPoint: chatroom.teamEntryPoint,
@@ -13663,66 +13678,14 @@ var init_get_system_prompt = __esm(() => {
13663
13678
  });
13664
13679
 
13665
13680
  // ../../services/backend/config/reliability.ts
13666
- var DAEMON_HEARTBEAT_INTERVAL_MS = 30000;
13681
+ var DAEMON_HEARTBEAT_INTERVAL_MS = 30000, AGENT_REQUEST_DEADLINE_MS = 120000;
13667
13682
 
13668
13683
  // src/commands/machine/daemon-start/utils.ts
13669
13684
  function formatTimestamp() {
13670
13685
  return new Date().toISOString().replace("T", " ").substring(0, 19);
13671
13686
  }
13672
- function parseMachineCommand(raw) {
13673
- switch (raw.type) {
13674
- case "ping":
13675
- return { _id: raw._id, type: "ping", payload: {}, createdAt: raw.createdAt };
13676
- case "status":
13677
- return { _id: raw._id, type: "status", payload: {}, createdAt: raw.createdAt };
13678
- case "start-agent": {
13679
- const { chatroomId, role, agentHarness } = raw.payload;
13680
- if (!chatroomId || !role || !agentHarness) {
13681
- console.error(` ⚠️ Invalid start-agent command: missing chatroomId, role, or agentHarness`);
13682
- return null;
13683
- }
13684
- if (!raw.reason) {
13685
- console.error(` ⚠️ Invalid start-agent command: missing required reason field`);
13686
- return null;
13687
- }
13688
- return {
13689
- _id: raw._id,
13690
- type: "start-agent",
13691
- reason: raw.reason,
13692
- payload: {
13693
- chatroomId,
13694
- role,
13695
- agentHarness,
13696
- model: raw.payload.model,
13697
- workingDir: raw.payload.workingDir
13698
- },
13699
- createdAt: raw.createdAt
13700
- };
13701
- }
13702
- case "stop-agent": {
13703
- const { chatroomId, role } = raw.payload;
13704
- if (!chatroomId || !role) {
13705
- console.error(` ⚠️ Invalid stop-agent command: missing chatroomId or role`);
13706
- return null;
13707
- }
13708
- if (!raw.reason) {
13709
- console.error(` ⚠️ Invalid stop-agent command: missing required reason field`);
13710
- return null;
13711
- }
13712
- return {
13713
- _id: raw._id,
13714
- type: "stop-agent",
13715
- reason: raw.reason,
13716
- payload: { chatroomId, role },
13717
- createdAt: raw.createdAt
13718
- };
13719
- }
13720
- default:
13721
- return null;
13722
- }
13723
- }
13724
13687
 
13725
- // src/commands/machine/events/on-agent-shutdown/index.ts
13688
+ // src/events/lifecycle/on-agent-shutdown.ts
13726
13689
  async function onAgentShutdown(ctx, options) {
13727
13690
  const { chatroomId, role, pid, skipKill } = options;
13728
13691
  try {
@@ -13781,42 +13744,13 @@ async function onAgentShutdown(ctx, options) {
13781
13744
  console.log(` ⚠️ Failed to clear local PID for ${role}: ${e.message}`);
13782
13745
  }
13783
13746
  }
13784
- let spawnedAgentCleared = false;
13785
- if (killed || skipKill) {
13786
- try {
13787
- await ctx.deps.backend.mutation(api.machines.updateSpawnedAgent, {
13788
- sessionId: ctx.sessionId,
13789
- machineId: ctx.machineId,
13790
- chatroomId,
13791
- role,
13792
- pid: undefined
13793
- });
13794
- spawnedAgentCleared = true;
13795
- } catch (e) {
13796
- console.log(` ⚠️ Failed to clear spawnedAgent for ${role}: ${e.message}`);
13797
- }
13798
- }
13799
- let participantRemoved = false;
13800
- try {
13801
- await ctx.deps.backend.mutation(api.participants.leave, {
13802
- sessionId: ctx.sessionId,
13803
- chatroomId,
13804
- role
13805
- });
13806
- participantRemoved = true;
13807
- } catch (e) {
13808
- console.log(` ⚠️ Failed to remove participant for ${role}: ${e.message}`);
13809
- }
13810
13747
  return {
13811
13748
  killed: killed || (skipKill ?? false),
13812
- cleaned: spawnedAgentCleared && participantRemoved
13749
+ cleaned: killed || (skipKill ?? false)
13813
13750
  };
13814
13751
  }
13815
- var init_on_agent_shutdown = __esm(() => {
13816
- init_api3();
13817
- });
13818
13752
 
13819
- // src/commands/machine/events/on-daemon-shutdown/index.ts
13753
+ // src/events/lifecycle/on-daemon-shutdown.ts
13820
13754
  async function onDaemonShutdown(ctx) {
13821
13755
  const agents = ctx.deps.machine.listAgentEntries(ctx.machineId);
13822
13756
  if (agents.length > 0) {
@@ -13855,110 +13789,17 @@ async function onDaemonShutdown(ctx) {
13855
13789
  var AGENT_SHUTDOWN_TIMEOUT_MS = 5000;
13856
13790
  var init_on_daemon_shutdown = __esm(() => {
13857
13791
  init_api3();
13858
- init_on_agent_shutdown();
13859
13792
  });
13860
13793
 
13861
- // src/commands/machine/pid.ts
13862
- import { createHash } from "node:crypto";
13863
- import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync4 } from "node:fs";
13864
- import { homedir as homedir4 } from "node:os";
13865
- import { join as join5 } from "node:path";
13866
- function getUrlHash() {
13867
- const url = getConvexUrl();
13868
- return createHash("sha256").update(url).digest("hex").substring(0, 8);
13869
- }
13870
- function getPidFileName() {
13871
- return `daemon-${getUrlHash()}.pid`;
13872
- }
13873
- function ensureChatroomDir() {
13874
- if (!existsSync4(CHATROOM_DIR4)) {
13875
- mkdirSync4(CHATROOM_DIR4, { recursive: true, mode: 448 });
13876
- }
13877
- }
13878
- function getPidFilePath() {
13879
- return join5(CHATROOM_DIR4, getPidFileName());
13880
- }
13881
- function isProcessRunning(pid) {
13882
- try {
13883
- process.kill(pid, 0);
13884
- return true;
13885
- } catch {
13886
- return false;
13887
- }
13888
- }
13889
- function readPid() {
13890
- const pidPath = getPidFilePath();
13891
- if (!existsSync4(pidPath)) {
13892
- return null;
13893
- }
13894
- try {
13895
- const content = readFileSync6(pidPath, "utf-8").trim();
13896
- const pid = parseInt(content, 10);
13897
- if (isNaN(pid) || pid <= 0) {
13898
- return null;
13899
- }
13900
- return pid;
13901
- } catch {
13902
- return null;
13903
- }
13904
- }
13905
- function writePid() {
13906
- ensureChatroomDir();
13907
- const pidPath = getPidFilePath();
13908
- writeFileSync4(pidPath, process.pid.toString(), "utf-8");
13909
- }
13910
- function removePid() {
13911
- const pidPath = getPidFilePath();
13912
- try {
13913
- if (existsSync4(pidPath)) {
13914
- unlinkSync2(pidPath);
13915
- }
13916
- } catch {}
13917
- }
13918
- function isDaemonRunning() {
13919
- const pid = readPid();
13920
- if (pid === null) {
13921
- return { running: false, pid: null };
13922
- }
13923
- if (isProcessRunning(pid)) {
13924
- return { running: true, pid };
13925
- }
13926
- removePid();
13927
- return { running: false, pid: null };
13928
- }
13929
- function acquireLock() {
13930
- const { running, pid } = isDaemonRunning();
13931
- if (running) {
13932
- console.error(`❌ Daemon already running for ${getConvexUrl()} (PID: ${pid})`);
13933
- return false;
13934
- }
13935
- writePid();
13936
- return true;
13937
- }
13938
- function releaseLock() {
13939
- removePid();
13940
- }
13941
- var CHATROOM_DIR4;
13942
- var init_pid = __esm(() => {
13943
- init_client2();
13944
- CHATROOM_DIR4 = join5(homedir4(), ".chatroom");
13945
- });
13946
-
13947
- // src/commands/machine/daemon-start/handlers/ping.ts
13948
- function handlePing() {
13949
- console.log(` ↪ Responding: pong`);
13950
- return { result: "pong", failed: false };
13951
- }
13952
-
13953
13794
  // src/commands/machine/daemon-start/handlers/start-agent.ts
13954
- async function handleStartAgent(ctx, command) {
13955
- const { chatroomId, role, agentHarness, model, workingDir } = command.payload;
13795
+ async function executeStartAgent(ctx, args) {
13796
+ const { chatroomId, role, agentHarness, model, workingDir, reason } = args;
13956
13797
  console.log(` ↪ start-agent command received`);
13957
13798
  console.log(` Chatroom: ${chatroomId}`);
13958
13799
  console.log(` Role: ${role}`);
13959
13800
  console.log(` Harness: ${agentHarness}`);
13960
- if (command.reason) {
13961
- console.log(` Reason: ${command.reason}`);
13801
+ if (reason) {
13802
+ console.log(` Reason: ${reason}`);
13962
13803
  }
13963
13804
  if (model) {
13964
13805
  console.log(` Model: ${model}`);
@@ -14090,19 +13931,26 @@ async function handleStartAgent(ctx, command) {
14090
13931
  var init_start_agent = __esm(() => {
14091
13932
  init_api3();
14092
13933
  init_client2();
14093
- init_on_agent_shutdown();
14094
13934
  });
14095
13935
 
14096
- // src/commands/machine/daemon-start/handlers/status.ts
14097
- function handleStatus(ctx) {
14098
- const result = JSON.stringify({
14099
- hostname: ctx.config?.hostname,
14100
- os: ctx.config?.os,
14101
- availableHarnesses: ctx.config?.availableHarnesses
13936
+ // src/events/daemon/agent/on-request-start-agent.ts
13937
+ async function onRequestStartAgent(ctx, event) {
13938
+ if (Date.now() > event.deadline) {
13939
+ console.log(`[daemon] ⏰ Skipping expired agent.requestStart for role=${event.role} (deadline passed)`);
13940
+ return;
13941
+ }
13942
+ await executeStartAgent(ctx, {
13943
+ chatroomId: event.chatroomId,
13944
+ role: event.role,
13945
+ agentHarness: event.agentHarness,
13946
+ model: event.model,
13947
+ workingDir: event.workingDir,
13948
+ reason: event.reason
14102
13949
  });
14103
- console.log(` ↪ Responding with status`);
14104
- return { result, failed: false };
14105
13950
  }
13951
+ var init_on_request_start_agent = __esm(() => {
13952
+ init_start_agent();
13953
+ });
14106
13954
 
14107
13955
  // src/commands/machine/daemon-start/handlers/shared.ts
14108
13956
  async function clearAgentPidEverywhere(ctx, chatroomId, role) {
@@ -14124,8 +13972,8 @@ var init_shared = __esm(() => {
14124
13972
  });
14125
13973
 
14126
13974
  // src/commands/machine/daemon-start/handlers/stop-agent.ts
14127
- async function handleStopAgent(ctx, command) {
14128
- const { chatroomId, role } = command.payload;
13975
+ async function executeStopAgent(ctx, args) {
13976
+ const { chatroomId, role } = args;
14129
13977
  console.log(` ↪ stop-agent command received`);
14130
13978
  console.log(` Chatroom: ${chatroomId}`);
14131
13979
  console.log(` Role: ${role}`);
@@ -14150,7 +13998,7 @@ async function handleStopAgent(ctx, command) {
14150
13998
  console.log(` Stopping agent with PID: ${pid}`);
14151
13999
  const isAlive = anyService ? anyService.isAlive(pid) : false;
14152
14000
  if (!isAlive) {
14153
- console.log(` ⚠️ PID ${pid} does not appear to belong to the expected agent`);
14001
+ console.log(` ⚠️ PID ${pid} not found process already exited or was never started`);
14154
14002
  await clearAgentPidEverywhere(ctx, chatroomId, role);
14155
14003
  console.log(` Cleared stale PID`);
14156
14004
  try {
@@ -14195,10 +14043,117 @@ async function handleStopAgent(ctx, command) {
14195
14043
  }
14196
14044
  var init_stop_agent = __esm(() => {
14197
14045
  init_api3();
14198
- init_on_agent_shutdown();
14199
14046
  init_shared();
14200
14047
  });
14201
14048
 
14049
+ // src/events/daemon/agent/on-request-stop-agent.ts
14050
+ async function onRequestStopAgent(ctx, event) {
14051
+ if (Date.now() > event.deadline) {
14052
+ console.log(`[daemon] ⏰ Skipping expired agent.requestStop for role=${event.role} (deadline passed)`);
14053
+ return;
14054
+ }
14055
+ await executeStopAgent(ctx, {
14056
+ chatroomId: event.chatroomId,
14057
+ role: event.role,
14058
+ reason: event.reason
14059
+ });
14060
+ }
14061
+ var init_on_request_stop_agent = __esm(() => {
14062
+ init_stop_agent();
14063
+ });
14064
+
14065
+ // src/commands/machine/pid.ts
14066
+ import { createHash } from "node:crypto";
14067
+ import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync4 } from "node:fs";
14068
+ import { homedir as homedir4 } from "node:os";
14069
+ import { join as join5 } from "node:path";
14070
+ function getUrlHash() {
14071
+ const url = getConvexUrl();
14072
+ return createHash("sha256").update(url).digest("hex").substring(0, 8);
14073
+ }
14074
+ function getPidFileName() {
14075
+ return `daemon-${getUrlHash()}.pid`;
14076
+ }
14077
+ function ensureChatroomDir() {
14078
+ if (!existsSync4(CHATROOM_DIR4)) {
14079
+ mkdirSync4(CHATROOM_DIR4, { recursive: true, mode: 448 });
14080
+ }
14081
+ }
14082
+ function getPidFilePath() {
14083
+ return join5(CHATROOM_DIR4, getPidFileName());
14084
+ }
14085
+ function isProcessRunning(pid) {
14086
+ try {
14087
+ process.kill(pid, 0);
14088
+ return true;
14089
+ } catch {
14090
+ return false;
14091
+ }
14092
+ }
14093
+ function readPid() {
14094
+ const pidPath = getPidFilePath();
14095
+ if (!existsSync4(pidPath)) {
14096
+ return null;
14097
+ }
14098
+ try {
14099
+ const content = readFileSync6(pidPath, "utf-8").trim();
14100
+ const pid = parseInt(content, 10);
14101
+ if (isNaN(pid) || pid <= 0) {
14102
+ return null;
14103
+ }
14104
+ return pid;
14105
+ } catch {
14106
+ return null;
14107
+ }
14108
+ }
14109
+ function writePid() {
14110
+ ensureChatroomDir();
14111
+ const pidPath = getPidFilePath();
14112
+ writeFileSync4(pidPath, process.pid.toString(), "utf-8");
14113
+ }
14114
+ function removePid() {
14115
+ const pidPath = getPidFilePath();
14116
+ try {
14117
+ if (existsSync4(pidPath)) {
14118
+ unlinkSync2(pidPath);
14119
+ }
14120
+ } catch {}
14121
+ }
14122
+ function isDaemonRunning() {
14123
+ const pid = readPid();
14124
+ if (pid === null) {
14125
+ return { running: false, pid: null };
14126
+ }
14127
+ if (isProcessRunning(pid)) {
14128
+ return { running: true, pid };
14129
+ }
14130
+ removePid();
14131
+ return { running: false, pid: null };
14132
+ }
14133
+ function acquireLock() {
14134
+ const { running, pid } = isDaemonRunning();
14135
+ if (running) {
14136
+ console.error(`❌ Daemon already running for ${getConvexUrl()} (PID: ${pid})`);
14137
+ return false;
14138
+ }
14139
+ writePid();
14140
+ return true;
14141
+ }
14142
+ function releaseLock() {
14143
+ removePid();
14144
+ }
14145
+ var CHATROOM_DIR4;
14146
+ var init_pid = __esm(() => {
14147
+ init_client2();
14148
+ CHATROOM_DIR4 = join5(homedir4(), ".chatroom");
14149
+ });
14150
+
14151
+ // src/commands/machine/daemon-start/handlers/ping.ts
14152
+ function handlePing() {
14153
+ console.log(` ↪ Responding: pong`);
14154
+ return { result: "pong", failed: false };
14155
+ }
14156
+
14202
14157
  // src/commands/machine/daemon-start/command-loop.ts
14203
14158
  async function refreshModels(ctx) {
14204
14159
  const models = {};
@@ -14227,70 +14182,6 @@ async function refreshModels(ctx) {
14227
14182
  console.warn(`[${formatTimestamp()}] ⚠️ Model refresh failed: ${error.message}`);
14228
14183
  }
14229
14184
  }
14230
- async function processCommand(ctx, command) {
14231
- console.log(`[${formatTimestamp()}] \uD83D\uDCE8 Command received: ${command.type}`);
14232
- try {
14233
- await ctx.deps.backend.mutation(api.machines.ackCommand, {
14234
- sessionId: ctx.sessionId,
14235
- commandId: command._id,
14236
- status: "processing"
14237
- });
14238
- ctx.events.emit("command:processing", {
14239
- commandId: command._id.toString(),
14240
- type: command.type
14241
- });
14242
- let commandResult;
14243
- switch (command.type) {
14244
- case "ping":
14245
- commandResult = handlePing();
14246
- break;
14247
- case "status":
14248
- commandResult = handleStatus(ctx);
14249
- break;
14250
- case "start-agent":
14251
- commandResult = await handleStartAgent(ctx, command);
14252
- break;
14253
- case "stop-agent":
14254
- commandResult = await handleStopAgent(ctx, command);
14255
- break;
14256
- default: {
14257
- const _exhaustive = command;
14258
- commandResult = {
14259
- result: `Unknown command type: ${_exhaustive.type}`,
14260
- failed: true
14261
- };
14262
- }
14263
- }
14264
- const finalStatus = commandResult.failed ? "failed" : "completed";
14265
- await ctx.deps.backend.mutation(api.machines.ackCommand, {
14266
- sessionId: ctx.sessionId,
14267
- commandId: command._id,
14268
- status: finalStatus,
14269
- result: commandResult.result
14270
- });
14271
- ctx.events.emit("command:completed", {
14272
- commandId: command._id.toString(),
14273
- type: command.type,
14274
- failed: commandResult.failed,
14275
- result: commandResult.result
14276
- });
14277
- if (commandResult.failed) {
14278
- console.log(` ❌ Command failed: ${commandResult.result}`);
14279
- } else {
14280
- console.log(` ✅ Command completed`);
14281
- }
14282
- } catch (error) {
14283
- console.error(` ❌ Command failed: ${error.message}`);
14284
- try {
14285
- await ctx.deps.backend.mutation(api.machines.ackCommand, {
14286
- sessionId: ctx.sessionId,
14287
- commandId: command._id,
14288
- status: "failed",
14289
- result: error.message
14290
- });
14291
- } catch {}
14292
- }
14293
- }
14294
14185
  async function startCommandLoop(ctx) {
14295
14186
  let heartbeatCount = 0;
14296
14187
  const heartbeatTimer = setInterval(() => {
@@ -14317,66 +14208,56 @@ async function startCommandLoop(ctx) {
14317
14208
  process.on("SIGTERM", shutdown);
14318
14209
  process.on("SIGHUP", shutdown);
14319
14210
  const wsClient2 = await getConvexWsClient();
14320
- const commandQueue = [];
14321
- const queuedCommandIds = new Set;
14322
- let drainingQueue = false;
14323
- const enqueueCommands = (commands) => {
14324
- for (const command of commands) {
14325
- const commandId = command._id.toString();
14326
- if (queuedCommandIds.has(commandId))
14327
- continue;
14328
- queuedCommandIds.add(commandId);
14329
- commandQueue.push(command);
14330
- }
14331
- };
14332
- const drainQueue = async () => {
14333
- if (drainingQueue)
14334
- return;
14335
- drainingQueue = true;
14336
- try {
14337
- while (commandQueue.length > 0) {
14338
- const command = commandQueue.shift();
14339
- const commandId = command._id.toString();
14340
- queuedCommandIds.delete(commandId);
14341
- try {
14342
- await processCommand(ctx, command);
14343
- } catch (error) {
14344
- console.error(` ❌ Command processing failed: ${error.message}`);
14345
- }
14346
- }
14347
- } finally {
14348
- drainingQueue = false;
14349
- }
14350
- };
14351
14211
  console.log(`
14352
14212
  Listening for commands...`);
14353
14213
  console.log(`Press Ctrl+C to stop
14354
14214
  `);
14355
- wsClient2.onUpdate(api.machines.getPendingCommands, {
14215
+ const processedCommandIds = new Map;
14216
+ const processedPingIds = new Map;
14217
+ wsClient2.onUpdate(api.machines.getCommandEvents, {
14356
14218
  sessionId: ctx.sessionId,
14357
14219
  machineId: ctx.machineId
14358
14220
  }, async (result) => {
14359
- if (!result.commands || result.commands.length === 0)
14221
+ if (!result.events || result.events.length === 0)
14360
14222
  return;
14361
- const parsed = [];
14362
- for (const raw of result.commands) {
14363
- const command = parseMachineCommand(raw);
14364
- if (command !== null) {
14365
- parsed.push(command);
14366
- } else {
14367
- try {
14368
- await ctx.deps.backend.mutation(api.machines.ackCommand, {
14223
+ const evictBefore = Date.now() - AGENT_REQUEST_DEADLINE_MS;
14224
+ for (const [id, ts] of processedCommandIds) {
14225
+ if (ts < evictBefore)
14226
+ processedCommandIds.delete(id);
14227
+ }
14228
+ for (const [id, ts] of processedPingIds) {
14229
+ if (ts < evictBefore)
14230
+ processedPingIds.delete(id);
14231
+ }
14232
+ for (const event of result.events) {
14233
+ const eventId = event._id.toString();
14234
+ try {
14235
+ console.log(`[${formatTimestamp()}] \uD83D\uDCE1 Stream command event: ${event.type}`);
14236
+ if (event.type === "agent.requestStart") {
14237
+ if (processedCommandIds.has(eventId))
14238
+ continue;
14239
+ processedCommandIds.set(eventId, Date.now());
14240
+ await onRequestStartAgent(ctx, event);
14241
+ } else if (event.type === "agent.requestStop") {
14242
+ if (processedCommandIds.has(eventId))
14243
+ continue;
14244
+ processedCommandIds.set(eventId, Date.now());
14245
+ await onRequestStopAgent(ctx, event);
14246
+ } else if (event.type === "daemon.ping") {
14247
+ if (processedPingIds.has(eventId))
14248
+ continue;
14249
+ processedPingIds.set(eventId, Date.now());
14250
+ handlePing();
14251
+ await ctx.deps.backend.mutation(api.machines.ackPing, {
14369
14252
  sessionId: ctx.sessionId,
14370
- commandId: raw._id,
14371
- status: "failed",
14372
- result: `Invalid command: type="${raw.type}" missing required payload fields`
14253
+ machineId: ctx.machineId,
14254
+ pingEventId: event._id
14373
14255
  });
14374
- console.warn(`[${formatTimestamp()}] ⚠️ Acked invalid command ${raw._id} (type=${raw.type}) as failed`);
14375
- } catch {}
14256
+ }
14257
+ } catch (err) {
14258
+ console.error(`[${formatTimestamp()}] ❌ Stream command event failed: ${err.message}`);
14376
14259
  }
14377
14260
  }
14378
- enqueueCommands(parsed);
14379
- await drainQueue();
14380
14261
  });
14381
14262
  const modelRefreshTimer = setInterval(() => {
14382
14263
  refreshModels(ctx).catch((err) => {
@@ -14391,9 +14272,9 @@ var init_command_loop = __esm(() => {
14391
14272
  init_api3();
14392
14273
  init_client2();
14393
14274
  init_on_daemon_shutdown();
14275
+ init_on_request_start_agent();
14276
+ init_on_request_stop_agent();
14394
14277
  init_pid();
14395
- init_start_agent();
14396
- init_stop_agent();
14397
14278
  MODEL_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
14398
14279
  });
14399
14280
 
@@ -14425,7 +14306,7 @@ var init_state_recovery = __esm(() => {
14425
14306
  init_shared();
14426
14307
  });
14427
14308
 
14428
- // src/commands/machine/daemon-start/event-bus.ts
14309
+ // src/events/daemon/event-bus.ts
14429
14310
  class DaemonEventBus {
14430
14311
  listeners = new Map;
14431
14312
  on(event, listener) {
@@ -14454,54 +14335,66 @@ class DaemonEventBus {
14454
14335
  }
14455
14336
  }
14456
14337
 
14457
- // src/commands/machine/daemon-start/event-listeners.ts
14338
+ // src/events/daemon/agent/on-agent-exited.ts
14339
+ function onAgentExited(ctx, payload) {
14340
+ const { chatroomId, role, pid, code: code2, signal, intentional } = payload;
14341
+ const ts = formatTimestamp();
14342
+ if (intentional) {
14343
+ console.log(`[${ts}] ℹ️ Agent process exited after intentional stop ` + `(PID: ${pid}, role: ${role}, code: ${code2}, signal: ${signal})`);
14344
+ } else {
14345
+ console.log(`[${ts}] ⚠️ Agent process exited ` + `(PID: ${pid}, role: ${role}, code: ${code2}, signal: ${signal})`);
14346
+ }
14347
+ ctx.deps.backend.mutation(api.machines.recordAgentExited, {
14348
+ sessionId: ctx.sessionId,
14349
+ machineId: ctx.machineId,
14350
+ chatroomId,
14351
+ role,
14352
+ pid,
14353
+ intentional,
14354
+ exitCode: code2 ?? undefined,
14355
+ signal: signal ?? undefined
14356
+ }).catch((err) => {
14357
+ console.log(` ⚠️ Failed to record agent exit event: ${err.message}`);
14358
+ });
14359
+ ctx.deps.machine.clearAgentPid(ctx.machineId, chatroomId, role);
14360
+ for (const service of ctx.agentServices.values()) {
14361
+ service.untrack(pid);
14362
+ }
14363
+ }
14364
+ var init_on_agent_exited = __esm(() => {
14365
+ init_api3();
14366
+ });
14367
+
14368
+ // src/events/daemon/agent/on-agent-started.ts
14369
+ function onAgentStarted(ctx, payload) {
14370
+ const ts = formatTimestamp();
14371
+ console.log(`[${ts}] \uD83D\uDFE2 Agent started: ${payload.role} (PID: ${payload.pid}, harness: ${payload.harness})`);
14372
+ }
14373
+ var init_on_agent_started = () => {};
14374
+
14375
+ // src/events/daemon/agent/on-agent-stopped.ts
14376
+ function onAgentStopped(ctx, payload) {
14377
+ const ts = formatTimestamp();
14378
+ console.log(`[${ts}] \uD83D\uDD34 Agent stopped: ${payload.role} (PID: ${payload.pid})`);
14379
+ }
14380
+ var init_on_agent_stopped = () => {};
14381
+
14382
+ // src/events/daemon/register-listeners.ts
14458
14383
  function registerEventListeners(ctx) {
14459
14384
  const unsubs = [];
14460
- unsubs.push(ctx.events.on("agent:exited", (payload) => {
14461
- const { chatroomId, role, pid, code: code2, signal, intentional } = payload;
14462
- const ts = formatTimestamp();
14463
- if (intentional) {
14464
- console.log(`[${ts}] ℹ️ Agent process exited after intentional stop ` + `(PID: ${pid}, role: ${role}, code: ${code2}, signal: ${signal})`);
14465
- } else {
14466
- console.log(`[${ts}] ⚠️ Agent process exited ` + `(PID: ${pid}, role: ${role}, code: ${code2}, signal: ${signal})`);
14467
- }
14468
- ctx.deps.backend.mutation(api.machines.updateSpawnedAgent, {
14469
- sessionId: ctx.sessionId,
14470
- machineId: ctx.machineId,
14471
- chatroomId,
14472
- role,
14473
- pid: undefined
14474
- }).catch((err) => {
14475
- console.log(` ⚠️ Failed to clear PID in backend: ${err.message}`);
14476
- });
14477
- ctx.deps.machine.clearAgentPid(ctx.machineId, chatroomId, role);
14478
- for (const service of ctx.agentServices.values()) {
14479
- service.untrack(pid);
14480
- }
14481
- ctx.deps.backend.mutation(api.participants.leave, {
14482
- sessionId: ctx.sessionId,
14483
- chatroomId,
14484
- role
14485
- }).catch((err) => {
14486
- console.log(` ⚠️ Could not remove participant: ${err.message}`);
14487
- });
14488
- }));
14489
- unsubs.push(ctx.events.on("agent:started", (payload) => {
14490
- const ts = formatTimestamp();
14491
- console.log(`[${ts}] \uD83D\uDFE2 Agent started: ${payload.role} (PID: ${payload.pid}, harness: ${payload.harness})`);
14492
- }));
14493
- unsubs.push(ctx.events.on("agent:stopped", (payload) => {
14494
- const ts = formatTimestamp();
14495
- console.log(`[${ts}] \uD83D\uDD34 Agent stopped: ${payload.role} (PID: ${payload.pid})`);
14496
- }));
14385
+ unsubs.push(ctx.events.on("agent:exited", (payload) => onAgentExited(ctx, payload)));
14386
+ unsubs.push(ctx.events.on("agent:started", (payload) => onAgentStarted(ctx, payload)));
14387
+ unsubs.push(ctx.events.on("agent:stopped", (payload) => onAgentStopped(ctx, payload)));
14497
14388
  return () => {
14498
14389
  for (const unsub of unsubs) {
14499
14390
  unsub();
14500
14391
  }
14501
14392
  };
14502
14393
  }
14503
- var init_event_listeners = __esm(() => {
14504
- init_api3();
14394
+ var init_register_listeners = __esm(() => {
14395
+ init_on_agent_exited();
14396
+ init_on_agent_started();
14397
+ init_on_agent_stopped();
14505
14398
  });
14506
14399
 
14507
14400
  // src/commands/machine/daemon-start/init.ts
@@ -14543,7 +14436,9 @@ function createDefaultDeps16() {
14543
14436
  machine: {
14544
14437
  clearAgentPid,
14545
14438
  persistAgentPid,
14546
- listAgentEntries
14439
+ listAgentEntries,
14440
+ persistEventCursor,
14441
+ loadEventCursor
14547
14442
  },
14548
14443
  clock: {
14549
14444
  now: () => Date.now(),
@@ -14672,7 +14567,7 @@ var init_init2 = __esm(() => {
14672
14567
  init_error_formatting();
14673
14568
  init_version();
14674
14569
  init_pid();
14675
- init_event_listeners();
14570
+ init_register_listeners();
14676
14571
  });
14677
14572
 
14678
14573
  // src/commands/machine/daemon-start/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatroom-cli",
3
- "version": "1.2.4",
3
+ "version": "1.2.7",
4
4
  "description": "CLI for multi-agent chatroom collaboration",
5
5
  "type": "module",
6
6
  "bin": {