chatroom-cli 1.3.0 → 1.4.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.
Files changed (2) hide show
  1. package/dist/index.js +180 -141
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10280,10 +10280,6 @@ function ensureMachineRegistered() {
10280
10280
  harnessVersions: config.harnessVersions
10281
10281
  };
10282
10282
  }
10283
- function getMachineId() {
10284
- const config = loadMachineConfig();
10285
- return config?.machineId ?? null;
10286
- }
10287
10283
  var CHATROOM_DIR2, MACHINE_FILE = "machine.json";
10288
10284
  var init_storage2 = __esm(() => {
10289
10285
  init_detection();
@@ -10756,6 +10752,9 @@ var init_pi_agent_service = __esm(() => {
10756
10752
  const pid = childProcess.pid;
10757
10753
  const context = options.context;
10758
10754
  const entry = this.registerProcess(pid, context);
10755
+ const roleTag = context.role ?? "unknown";
10756
+ const chatroomSuffix = context.chatroomId ? `@${context.chatroomId.slice(-6)}` : "";
10757
+ const logPrefix = `[pi:${roleTag}${chatroomSuffix}`;
10759
10758
  const outputCallbacks = [];
10760
10759
  if (childProcess.stdout) {
10761
10760
  const reader = new PiRpcReader(childProcess.stdout);
@@ -10767,7 +10766,7 @@ var init_pi_agent_service = __esm(() => {
10767
10766
  for (const line of textBuffer.split(`
10768
10767
  `)) {
10769
10768
  if (line)
10770
- process.stdout.write(`[pi text] ${line}
10769
+ process.stdout.write(`${logPrefix} text] ${line}
10771
10770
  `);
10772
10771
  }
10773
10772
  textBuffer = "";
@@ -10778,7 +10777,7 @@ var init_pi_agent_service = __esm(() => {
10778
10777
  for (const line of thinkingBuffer.split(`
10779
10778
  `)) {
10780
10779
  if (line)
10781
- process.stdout.write(`[pi thinking] ${line}
10780
+ process.stdout.write(`${logPrefix} thinking] ${line}
10782
10781
  `);
10783
10782
  }
10784
10783
  thinkingBuffer = "";
@@ -10811,15 +10810,38 @@ var init_pi_agent_service = __esm(() => {
10811
10810
  reader.onAgentEnd(() => {
10812
10811
  flushText();
10813
10812
  flushThinking();
10814
- process.stdout.write(`[pi agent_end]
10813
+ process.stdout.write(`${logPrefix} agent_end]
10815
10814
  `);
10816
10815
  });
10817
10816
  reader.onToolCall((name) => {
10818
10817
  flushText();
10819
10818
  flushThinking();
10820
- process.stdout.write(`[pi tool: ${name}]
10819
+ process.stdout.write(`${logPrefix} tool: ${name}]
10821
10820
  `);
10822
10821
  });
10822
+ if (childProcess.stderr) {
10823
+ childProcess.stderr.pipe(process.stderr, { end: false });
10824
+ childProcess.stderr.on("data", () => {
10825
+ entry.lastOutputAt = Date.now();
10826
+ for (const cb of outputCallbacks)
10827
+ cb();
10828
+ });
10829
+ }
10830
+ return {
10831
+ pid,
10832
+ onExit: (cb) => {
10833
+ childProcess.on("exit", (code2, signal) => {
10834
+ this.deleteProcess(pid);
10835
+ cb({ code: code2, signal, context });
10836
+ });
10837
+ },
10838
+ onOutput: (cb) => {
10839
+ outputCallbacks.push(cb);
10840
+ },
10841
+ onAgentEnd: (cb) => {
10842
+ reader.onAgentEnd(cb);
10843
+ }
10844
+ };
10823
10845
  }
10824
10846
  if (childProcess.stderr) {
10825
10847
  childProcess.stderr.pipe(process.stderr, { end: false });
@@ -11298,6 +11320,15 @@ async function registerAgent(chatroomId, options, deps) {
11298
11320
  harnessVersions: machineInfo.harnessVersions,
11299
11321
  availableModels
11300
11322
  });
11323
+ try {
11324
+ await d.backend.mutation(api.machines.recordAgentRegistered, {
11325
+ sessionId,
11326
+ chatroomId,
11327
+ role,
11328
+ agentType: "remote",
11329
+ machineId: machineInfo.machineId
11330
+ });
11331
+ } catch {}
11301
11332
  console.log(`✅ Registered as remote agent for role "${role}"`);
11302
11333
  console.log(` Machine: ${machineInfo.hostname} (${machineInfo.machineId})`);
11303
11334
  console.log(` Working directory: ${process.cwd()}`);
@@ -11371,39 +11402,11 @@ var init_task_started = __esm(() => {
11371
11402
  function getNextTaskGuidance() {
11372
11403
  return `\uD83D\uDD17 STAYING CONNECTED TO YOUR TEAM
11373
11404
 
11374
- Your primary directive: Stay available to respond to user and team requests.
11375
-
11376
- ⚠️ CRITICAL: Run get-next-task in the FOREGROUND
11377
-
11378
- Two requirements:
11379
-
11380
- 1. **Shell foreground** - Never use \`&\`, \`nohup\`, or backgrounding
11381
- • Must run directly in your active terminal
11382
- • Backgrounded processes cannot receive tasks
11405
+ Your primary directive: Stay available to receive tasks from your team.
11383
11406
 
11384
- 2. **Blocking execution** - Wait for command completion
11385
- • Do not respond to user or perform other work while running
11386
- • Only proceed after the command exits (signal or task received)
11407
+ Run \`get-next-task\` after completing work and handing off. This is how your team sends you the next task.
11387
11408
 
11388
- ⚠️ WHEN THE PROCESS IS TERMINATED OR TIMED OUT
11389
-
11390
- \`\`\`mermaid
11391
- flowchart TD
11392
- A([Start]) --> B[Command terminated unexpectedly]
11393
- B --> C{Urgent pending work?}
11394
- C -->|yes| D[Finish urgent work]
11395
- D --> E[Reconnect with get-next-task]
11396
- C -->|no| E
11397
- E --> F([Stop])
11398
- \`\`\`
11399
-
11400
- \uD83D\uDCCB BACKLOG TASKS
11401
- chatroom backlog list --chatroom-id=<chatroomId> --role=<role> --status=backlog
11402
- chatroom backlog --help
11403
-
11404
- \uD83D\uDCCB CONTEXT RECOVERY (after compaction/summarization)
11405
- If your context was compacted, run: chatroom get-system-prompt --chatroom-id=<id> --role=<role>
11406
- to reload your full system and role prompt.`;
11409
+ If interrupted or restarted: finish any in-progress work, then run \`get-next-task\` to reconnect.`;
11407
11410
  }
11408
11411
  var init_reminder = () => {};
11409
11412
  // ../../services/backend/prompts/config/index.ts
@@ -13075,10 +13078,13 @@ async function readContext(chatroomId, options, deps) {
13075
13078
  role: options.role
13076
13079
  });
13077
13080
  if (context.messages.length === 0 && !context.currentContext) {
13081
+ console.log(`<context role="${options.role}">`);
13078
13082
  console.log(`
13079
13083
  \uD83D\uDCED No context available`);
13084
+ console.log("</context>");
13080
13085
  return;
13081
13086
  }
13087
+ console.log(`<context role="${options.role}">`);
13082
13088
  console.log(`
13083
13089
  \uD83D\uDCDA CONTEXT FOR ${options.role.toUpperCase()}`);
13084
13090
  console.log("═".repeat(60));
@@ -13114,16 +13120,9 @@ async function readContext(chatroomId, options, deps) {
13114
13120
  \uD83D\uDCAC Chat History:`);
13115
13121
  console.log("─".repeat(60));
13116
13122
  for (const message of context.messages) {
13117
- const timestamp = new Date(message._creationTime).toLocaleString();
13118
- const classificationBadge = message.classification ? ` [${message.classification.toUpperCase()}]` : "";
13119
- console.log(`
13120
- \uD83D\uDD39 Message ID: ${message._id}`);
13121
- console.log(` Time: ${timestamp}`);
13122
- console.log(` From: ${message.senderRole}`);
13123
- if (message.targetRole) {
13124
- console.log(` To: ${message.targetRole}`);
13125
- }
13126
- console.log(` Type: ${message.type}${classificationBadge}`);
13123
+ const toAttr = message.targetRole ? ` to="${message.targetRole}"` : "";
13124
+ const classAttr = message.classification ? ` classification="${message.classification}"` : "";
13125
+ console.log(`<message id="${message._id}" from="${message.senderRole}"${toAttr} type="${message.type}"${classAttr}>`);
13127
13126
  if (message.featureTitle) {
13128
13127
  console.log(` Feature: ${sanitizeForTerminal(message.featureTitle)}`);
13129
13128
  }
@@ -13135,9 +13134,12 @@ async function readContext(chatroomId, options, deps) {
13135
13134
  }
13136
13135
  if (message.taskContent) {
13137
13136
  const safeTaskContent = sanitizeForTerminal(message.taskContent);
13138
- console.log(` Content: ${safeTaskContent.split(`
13139
- `).map((l, i2) => i2 === 0 ? l : ` ${l}`).join(`
13140
- `)}`);
13137
+ console.log(` Content:`);
13138
+ console.log(` <task-content>`);
13139
+ console.log(safeTaskContent.split(`
13140
+ `).map((l) => ` ${l}`).join(`
13141
+ `));
13142
+ console.log(` </task-content>`);
13141
13143
  }
13142
13144
  }
13143
13145
  if (message.attachedTasks && message.attachedTasks.length > 0) {
@@ -13147,22 +13149,26 @@ async function readContext(chatroomId, options, deps) {
13147
13149
  console.log(` Type: Task`);
13148
13150
  const contentLines = sanitizeForTerminal(task.content).split(`
13149
13151
  `);
13150
- console.log(` Content: ${contentLines[0]}`);
13151
- if (contentLines.length > 1) {
13152
- for (let i2 = 1;i2 < contentLines.length; i2++) {
13153
- console.log(` ${contentLines[i2]}`);
13154
- }
13152
+ console.log(` Content:`);
13153
+ console.log(` <task-content>`);
13154
+ for (const line of contentLines) {
13155
+ console.log(` ${line}`);
13155
13156
  }
13157
+ console.log(` </task-content>`);
13156
13158
  }
13157
13159
  }
13158
13160
  console.log(` Content:`);
13161
+ console.log(` <message-content>`);
13159
13162
  const safeMessageContent = sanitizeForTerminal(message.content);
13160
13163
  console.log(safeMessageContent.split(`
13161
13164
  `).map((l) => ` ${l}`).join(`
13162
13165
  `));
13166
+ console.log(` </message-content>`);
13167
+ console.log(`</message>`);
13163
13168
  }
13164
13169
  console.log(`
13165
13170
  ` + "═".repeat(60));
13171
+ console.log("</context>");
13166
13172
  } catch (err) {
13167
13173
  console.error(`❌ Failed to read context: ${sanitizeUnknownForTerminal(err.message)}`);
13168
13174
  process.exit(1);
@@ -13912,6 +13918,13 @@ async function executeStartAgent(ctx, args) {
13912
13918
  intentional: wasIntentional
13913
13919
  });
13914
13920
  });
13921
+ if (spawnResult.onAgentEnd) {
13922
+ spawnResult.onAgentEnd(() => {
13923
+ try {
13924
+ ctx.deps.processes.kill(-pid, "SIGTERM");
13925
+ } catch {}
13926
+ });
13927
+ }
13915
13928
  let lastReportedTokenAt = 0;
13916
13929
  spawnResult.onOutput(() => {
13917
13930
  const now = Date.now();
@@ -14164,6 +14177,9 @@ async function refreshModels(ctx) {
14164
14177
  }
14165
14178
  if (!ctx.config)
14166
14179
  return;
14180
+ const freshConfig = ensureMachineRegistered();
14181
+ ctx.config.availableHarnesses = freshConfig.availableHarnesses;
14182
+ ctx.config.harnessVersions = freshConfig.harnessVersions;
14167
14183
  const totalCount = Object.values(models).flat().length;
14168
14184
  try {
14169
14185
  await ctx.deps.backend.mutation(api.machines.register, {
@@ -14180,6 +14196,41 @@ async function refreshModels(ctx) {
14180
14196
  console.warn(`[${formatTimestamp()}] ⚠️ Model refresh failed: ${error.message}`);
14181
14197
  }
14182
14198
  }
14199
+ function evictStaleDedupEntries(processedCommandIds, processedPingIds) {
14200
+ const evictBefore = Date.now() - AGENT_REQUEST_DEADLINE_MS;
14201
+ for (const [id, ts] of processedCommandIds) {
14202
+ if (ts < evictBefore)
14203
+ processedCommandIds.delete(id);
14204
+ }
14205
+ for (const [id, ts] of processedPingIds) {
14206
+ if (ts < evictBefore)
14207
+ processedPingIds.delete(id);
14208
+ }
14209
+ }
14210
+ async function dispatchCommandEvent(ctx, event, processedCommandIds, processedPingIds) {
14211
+ const eventId = event._id.toString();
14212
+ if (event.type === "agent.requestStart") {
14213
+ if (processedCommandIds.has(eventId))
14214
+ return;
14215
+ processedCommandIds.set(eventId, Date.now());
14216
+ await onRequestStartAgent(ctx, event);
14217
+ } else if (event.type === "agent.requestStop") {
14218
+ if (processedCommandIds.has(eventId))
14219
+ return;
14220
+ processedCommandIds.set(eventId, Date.now());
14221
+ await onRequestStopAgent(ctx, event);
14222
+ } else if (event.type === "daemon.ping") {
14223
+ if (processedPingIds.has(eventId))
14224
+ return;
14225
+ processedPingIds.set(eventId, Date.now());
14226
+ handlePing();
14227
+ await ctx.deps.backend.mutation(api.machines.ackPing, {
14228
+ sessionId: ctx.sessionId,
14229
+ machineId: ctx.machineId,
14230
+ pingEventId: event._id
14231
+ });
14232
+ }
14233
+ }
14183
14234
  async function startCommandLoop(ctx) {
14184
14235
  let heartbeatCount = 0;
14185
14236
  const heartbeatTimer = setInterval(() => {
@@ -14218,40 +14269,11 @@ Listening for commands...`);
14218
14269
  }, async (result) => {
14219
14270
  if (!result.events || result.events.length === 0)
14220
14271
  return;
14221
- const evictBefore = Date.now() - AGENT_REQUEST_DEADLINE_MS;
14222
- for (const [id, ts] of processedCommandIds) {
14223
- if (ts < evictBefore)
14224
- processedCommandIds.delete(id);
14225
- }
14226
- for (const [id, ts] of processedPingIds) {
14227
- if (ts < evictBefore)
14228
- processedPingIds.delete(id);
14229
- }
14272
+ evictStaleDedupEntries(processedCommandIds, processedPingIds);
14230
14273
  for (const event of result.events) {
14231
- const eventId = event._id.toString();
14232
14274
  try {
14233
14275
  console.log(`[${formatTimestamp()}] \uD83D\uDCE1 Stream command event: ${event.type}`);
14234
- if (event.type === "agent.requestStart") {
14235
- if (processedCommandIds.has(eventId))
14236
- continue;
14237
- processedCommandIds.set(eventId, Date.now());
14238
- await onRequestStartAgent(ctx, event);
14239
- } else if (event.type === "agent.requestStop") {
14240
- if (processedCommandIds.has(eventId))
14241
- continue;
14242
- processedCommandIds.set(eventId, Date.now());
14243
- await onRequestStopAgent(ctx, event);
14244
- } else if (event.type === "daemon.ping") {
14245
- if (processedPingIds.has(eventId))
14246
- continue;
14247
- processedPingIds.set(eventId, Date.now());
14248
- handlePing();
14249
- await ctx.deps.backend.mutation(api.machines.ackPing, {
14250
- sessionId: ctx.sessionId,
14251
- machineId: ctx.machineId,
14252
- pingEventId: event._id
14253
- });
14254
- }
14276
+ await dispatchCommandEvent(ctx, event, processedCommandIds, processedPingIds);
14255
14277
  } catch (err) {
14256
14278
  console.error(`[${formatTimestamp()}] ❌ Stream command event failed: ${err.message}`);
14257
14279
  }
@@ -14269,6 +14291,7 @@ var MODEL_REFRESH_INTERVAL_MS;
14269
14291
  var init_command_loop = __esm(() => {
14270
14292
  init_api3();
14271
14293
  init_client2();
14294
+ init_machine();
14272
14295
  init_on_daemon_shutdown();
14273
14296
  init_on_request_start_agent();
14274
14297
  init_on_request_stop_agent();
@@ -14447,11 +14470,7 @@ function createDefaultDeps16() {
14447
14470
  }
14448
14471
  };
14449
14472
  }
14450
- async function initDaemon() {
14451
- if (!acquireLock()) {
14452
- process.exit(1);
14453
- }
14454
- const convexUrl = getConvexUrl();
14473
+ function validateAuthentication(convexUrl) {
14455
14474
  const sessionId = getSessionId();
14456
14475
  if (!sessionId) {
14457
14476
  const otherUrls = getOtherSessionUrls();
@@ -14468,18 +14487,10 @@ Run: chatroom auth login`);
14468
14487
  releaseLock();
14469
14488
  process.exit(1);
14470
14489
  }
14471
- const machineId = getMachineId();
14472
- if (!machineId) {
14473
- console.error(`❌ Machine not registered`);
14474
- console.error(`
14475
- Run any chatroom command first to register this machine,`);
14476
- console.error(`for example: chatroom auth status`);
14477
- releaseLock();
14478
- process.exit(1);
14479
- }
14480
- const client2 = await getConvexClient();
14481
- const typedSessionId = sessionId;
14482
- const validation = await client2.query(api.cliAuth.validateSession, { sessionId: typedSessionId });
14490
+ return sessionId;
14491
+ }
14492
+ async function validateSession(client2, sessionId, convexUrl) {
14493
+ const validation = await client2.query(api.cliAuth.validateSession, { sessionId });
14483
14494
  if (!validation.valid) {
14484
14495
  console.error(`❌ Session invalid: ${validation.reason}`);
14485
14496
  console.error(`
@@ -14487,32 +14498,34 @@ Run: chatroom auth login`);
14487
14498
  releaseLock();
14488
14499
  process.exit(1);
14489
14500
  }
14501
+ }
14502
+ function setupMachine() {
14503
+ ensureMachineRegistered();
14490
14504
  const config3 = loadMachineConfig();
14491
- const openCodeService = new OpenCodeAgentService;
14492
- const piService = new PiAgentService;
14493
- const agentServices = new Map([
14494
- ["opencode", openCodeService],
14495
- ["pi", piService]
14496
- ]);
14505
+ return config3;
14506
+ }
14507
+ async function registerCapabilities(client2, sessionId, config3, agentServices) {
14508
+ const { machineId } = config3;
14497
14509
  const availableModels = await discoverModels(agentServices);
14498
- if (config3) {
14499
- try {
14500
- await client2.mutation(api.machines.register, {
14501
- sessionId: typedSessionId,
14502
- machineId,
14503
- hostname: config3.hostname,
14504
- os: config3.os,
14505
- availableHarnesses: config3.availableHarnesses,
14506
- harnessVersions: config3.harnessVersions,
14507
- availableModels
14508
- });
14509
- } catch (error) {
14510
- console.warn(`⚠️ Machine registration update failed: ${error.message}`);
14511
- }
14510
+ try {
14511
+ await client2.mutation(api.machines.register, {
14512
+ sessionId,
14513
+ machineId,
14514
+ hostname: config3.hostname,
14515
+ os: config3.os,
14516
+ availableHarnesses: config3.availableHarnesses,
14517
+ harnessVersions: config3.harnessVersions,
14518
+ availableModels
14519
+ });
14520
+ } catch (error) {
14521
+ console.warn(`⚠️ Machine registration update failed: ${error.message}`);
14512
14522
  }
14523
+ return availableModels;
14524
+ }
14525
+ async function connectDaemon(client2, sessionId, machineId, convexUrl) {
14513
14526
  try {
14514
14527
  await client2.mutation(api.machines.updateDaemonStatus, {
14515
- sessionId: typedSessionId,
14528
+ sessionId,
14516
14529
  machineId,
14517
14530
  connected: true
14518
14531
  });
@@ -14525,6 +14538,45 @@ Run: chatroom auth login`);
14525
14538
  releaseLock();
14526
14539
  process.exit(1);
14527
14540
  }
14541
+ }
14542
+ function logStartup(ctx, availableModels) {
14543
+ console.log(`[${formatTimestamp()}] \uD83D\uDE80 Daemon started`);
14544
+ console.log(` CLI version: ${getVersion()}`);
14545
+ console.log(` Machine ID: ${ctx.machineId}`);
14546
+ console.log(` Hostname: ${ctx.config?.hostname ?? "unknown"}`);
14547
+ console.log(` Available harnesses: ${ctx.config?.availableHarnesses.join(", ") || "none"}`);
14548
+ console.log(` Available models: ${Object.keys(availableModels).length > 0 ? `${Object.values(availableModels).flat().length} models across ${Object.keys(availableModels).join(", ")}` : "none discovered"}`);
14549
+ console.log(` PID: ${process.pid}`);
14550
+ }
14551
+ async function recoverState(ctx) {
14552
+ console.log(`
14553
+ [${formatTimestamp()}] \uD83D\uDD04 Recovering agent state...`);
14554
+ try {
14555
+ await recoverAgentState(ctx);
14556
+ } catch (e) {
14557
+ console.log(` ⚠️ Recovery failed: ${e.message}`);
14558
+ console.log(` Continuing with fresh state`);
14559
+ }
14560
+ }
14561
+ async function initDaemon() {
14562
+ if (!acquireLock()) {
14563
+ process.exit(1);
14564
+ }
14565
+ const convexUrl = getConvexUrl();
14566
+ const sessionId = validateAuthentication(convexUrl);
14567
+ const client2 = await getConvexClient();
14568
+ const typedSessionId = sessionId;
14569
+ await validateSession(client2, typedSessionId, convexUrl);
14570
+ const config3 = setupMachine();
14571
+ const { machineId } = config3;
14572
+ const openCodeService = new OpenCodeAgentService;
14573
+ const piService = new PiAgentService;
14574
+ const agentServices = new Map([
14575
+ ["opencode", openCodeService],
14576
+ ["pi", piService]
14577
+ ]);
14578
+ const availableModels = await registerCapabilities(client2, typedSessionId, config3, agentServices);
14579
+ await connectDaemon(client2, typedSessionId, machineId, convexUrl);
14528
14580
  const deps = createDefaultDeps16();
14529
14581
  deps.backend.mutation = (endpoint, args) => client2.mutation(endpoint, args);
14530
14582
  deps.backend.query = (endpoint, args) => client2.query(endpoint, args);
@@ -14539,21 +14591,8 @@ Run: chatroom auth login`);
14539
14591
  agentServices
14540
14592
  };
14541
14593
  registerEventListeners(ctx);
14542
- console.log(`[${formatTimestamp()}] \uD83D\uDE80 Daemon started`);
14543
- console.log(` CLI version: ${getVersion()}`);
14544
- console.log(` Machine ID: ${machineId}`);
14545
- console.log(` Hostname: ${config3?.hostname ?? "Unknown"}`);
14546
- console.log(` Available harnesses: ${config3?.availableHarnesses.join(", ") || "none"}`);
14547
- console.log(` Available models: ${Object.keys(availableModels).length > 0 ? `${Object.values(availableModels).flat().length} models across ${Object.keys(availableModels).join(", ")}` : "none discovered"}`);
14548
- console.log(` PID: ${process.pid}`);
14549
- console.log(`
14550
- [${formatTimestamp()}] \uD83D\uDD04 Recovering agent state...`);
14551
- try {
14552
- await recoverAgentState(ctx);
14553
- } catch (e) {
14554
- console.log(` ⚠️ Recovery failed: ${e.message}`);
14555
- console.log(` Continuing with fresh state`);
14556
- }
14594
+ logStartup(ctx, availableModels);
14595
+ await recoverState(ctx);
14557
14596
  return ctx;
14558
14597
  }
14559
14598
  var init_init2 = __esm(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatroom-cli",
3
- "version": "1.3.0",
3
+ "version": "1.4.2",
4
4
  "description": "CLI for multi-agent chatroom collaboration",
5
5
  "type": "module",
6
6
  "bin": {