@dolusoft/claude-collab 1.11.2 → 1.11.4

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/cli.js CHANGED
@@ -3,7 +3,8 @@ import { Command } from 'commander';
3
3
  import { WebSocket, WebSocketServer } from 'ws';
4
4
  import { v4 } from 'uuid';
5
5
  import os, { tmpdir } from 'os';
6
- import { execSync, spawn, execFile } from 'child_process';
6
+ import { exec, spawn, execFile } from 'child_process';
7
+ import { promisify } from 'util';
7
8
  import { EventEmitter } from 'events';
8
9
  import { unlinkSync } from 'fs';
9
10
  import { join } from 'path';
@@ -58,7 +59,7 @@ public class ConInject {
58
59
  string lpFileName, uint dwDesiredAccess, uint dwShareMode,
59
60
  IntPtr lpSecurityAttributes, uint dwCreationDisposition,
60
61
  uint dwFlagsAndAttributes, IntPtr hTemplateFile);
61
- [DllImport("kernel32.dll")] public static extern bool WriteConsoleInput(
62
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode)] public static extern bool WriteConsoleInput(
62
63
  IntPtr hIn, INPUT_RECORD[] buf, uint len, out uint written);
63
64
  [DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr h);
64
65
 
@@ -264,12 +265,14 @@ var InjectionQueue = class extends EventEmitter {
264
265
  var injectionQueue = new InjectionQueue();
265
266
 
266
267
  // src/infrastructure/mesh/mesh-node.ts
268
+ var execAsync = promisify(exec);
267
269
  var FIXED_PORT = 12345;
268
270
  var MeshNode = class {
269
271
  server = null;
270
272
  myName = "";
271
273
  running = false;
272
274
  firewallOpened = false;
275
+ cachedLocalIps = [];
273
276
  // One connection per peer (inbound or outbound — whichever was established first)
274
277
  peerConnections = /* @__PURE__ */ new Map();
275
278
  // Reverse map: ws → peer name (only for registered connections)
@@ -280,12 +283,11 @@ var MeshNode = class {
280
283
  connectingPeers = /* @__PURE__ */ new Set();
281
284
  incomingQuestions = /* @__PURE__ */ new Map();
282
285
  receivedAnswers = /* @__PURE__ */ new Map();
283
- sentQuestions = /* @__PURE__ */ new Map();
284
286
  questionToSender = /* @__PURE__ */ new Map();
285
287
  pendingHandlers = /* @__PURE__ */ new Set();
286
288
  // Push-based answer resolution: questionId → resolve callback
287
289
  answerWaiters = /* @__PURE__ */ new Map();
288
- // Answers queued for offline peers: peerName → AnswerMsg (delivered on reconnect)
290
+ // Answers queued for offline peers: peerName → AnswerMsg[] (delivered on reconnect)
289
291
  pendingOutboundAnswers = /* @__PURE__ */ new Map();
290
292
  // ---------------------------------------------------------------------------
291
293
  // ICollabClient implementation
@@ -301,6 +303,7 @@ var MeshNode = class {
301
303
  }
302
304
  async join(name, displayName) {
303
305
  this.myName = name;
306
+ this.cachedLocalIps = this.scanLocalIps();
304
307
  await this.startServer();
305
308
  return {
306
309
  memberId: v4(),
@@ -346,7 +349,6 @@ var MeshNode = class {
346
349
  });
347
350
  ws.on("close", () => {
348
351
  clearTimeout(timeout);
349
- this.connectingPeers.delete(ip);
350
352
  const name = this.wsToName.get(ws);
351
353
  if (name) {
352
354
  this.wsToName.delete(ws);
@@ -358,7 +360,6 @@ var MeshNode = class {
358
360
  });
359
361
  ws.on("error", (err) => {
360
362
  clearTimeout(timeout);
361
- this.connectingPeers.delete(ip);
362
363
  reject(new Error(`Failed to connect to ${ip}:${FIXED_PORT} \u2014 ${err.message}`));
363
364
  });
364
365
  });
@@ -369,7 +370,6 @@ var MeshNode = class {
369
370
  throw new Error(`Peer "${toPeer}" is not connected. Use status() to see who's online.`);
370
371
  }
371
372
  const questionId = v4();
372
- this.sentQuestions.set(questionId, { toPeer, content, askedAt: (/* @__PURE__ */ new Date()).toISOString() });
373
373
  const ackPromise = this.waitForResponse(
374
374
  (m) => m.type === "ASK_ACK" && m.questionId === questionId,
375
375
  5e3
@@ -380,7 +380,10 @@ var MeshNode = class {
380
380
  }
381
381
  waitForAnswer(questionId, timeoutMs) {
382
382
  const cached = this.receivedAnswers.get(questionId);
383
- if (cached) return Promise.resolve(this.formatAnswer(questionId, cached));
383
+ if (cached) {
384
+ this.receivedAnswers.delete(questionId);
385
+ return Promise.resolve(this.formatAnswer(questionId, cached));
386
+ }
384
387
  return new Promise((resolve) => {
385
388
  const timeout = setTimeout(() => {
386
389
  this.answerWaiters.delete(questionId);
@@ -394,7 +397,9 @@ var MeshNode = class {
394
397
  }
395
398
  async checkAnswer(questionId) {
396
399
  const cached = this.receivedAnswers.get(questionId);
397
- return cached ? this.formatAnswer(questionId, cached) : null;
400
+ if (!cached) return null;
401
+ this.receivedAnswers.delete(questionId);
402
+ return this.formatAnswer(questionId, cached);
398
403
  }
399
404
  formatAnswer(questionId, cached) {
400
405
  return {
@@ -412,6 +417,8 @@ var MeshNode = class {
412
417
  question.answerContent = content;
413
418
  question.answerFormat = format;
414
419
  const senderName = this.questionToSender.get(questionId);
420
+ this.incomingQuestions.delete(questionId);
421
+ this.questionToSender.delete(questionId);
415
422
  if (senderName) {
416
423
  const answerMsg = {
417
424
  type: "ANSWER",
@@ -425,7 +432,9 @@ var MeshNode = class {
425
432
  if (ws && ws.readyState === WebSocket.OPEN) {
426
433
  this.sendToWs(ws, answerMsg);
427
434
  } else {
428
- this.pendingOutboundAnswers.set(senderName, answerMsg);
435
+ const queue = this.pendingOutboundAnswers.get(senderName) ?? [];
436
+ queue.push(answerMsg);
437
+ this.pendingOutboundAnswers.set(senderName, queue);
429
438
  console.error(`[mesh] "${senderName}" is offline, answer queued for delivery on reconnect`);
430
439
  }
431
440
  }
@@ -433,7 +442,7 @@ var MeshNode = class {
433
442
  }
434
443
  async getInbox() {
435
444
  const now = Date.now();
436
- const questions = [...this.incomingQuestions.values()].filter((q) => !q.answered).map((q) => ({
445
+ const questions = [...this.incomingQuestions.values()].map((q) => ({
437
446
  questionId: q.questionId,
438
447
  from: { displayName: `${q.fromName} Claude`, teamName: q.fromName },
439
448
  content: q.content,
@@ -453,6 +462,9 @@ var MeshNode = class {
453
462
  };
454
463
  }
455
464
  getLocalIps() {
465
+ return this.cachedLocalIps;
466
+ }
467
+ scanLocalIps() {
456
468
  const result = [];
457
469
  const interfaces = os.networkInterfaces();
458
470
  for (const iface of Object.values(interfaces)) {
@@ -463,31 +475,6 @@ var MeshNode = class {
463
475
  }
464
476
  return result;
465
477
  }
466
- getHistory() {
467
- const entries = [];
468
- for (const [questionId, sent] of this.sentQuestions) {
469
- const answer = this.receivedAnswers.get(questionId);
470
- entries.push({
471
- direction: "sent",
472
- questionId,
473
- peer: sent.toPeer,
474
- question: sent.content,
475
- askedAt: sent.askedAt,
476
- ...answer ? { answer: answer.content, answeredAt: answer.answeredAt } : {}
477
- });
478
- }
479
- for (const [, incoming] of this.incomingQuestions) {
480
- entries.push({
481
- direction: "received",
482
- questionId: incoming.questionId,
483
- peer: incoming.fromName,
484
- question: incoming.content,
485
- askedAt: incoming.createdAt.toISOString(),
486
- ...incoming.answered && incoming.answerContent ? { answer: incoming.answerContent, answeredAt: (/* @__PURE__ */ new Date()).toISOString() } : {}
487
- });
488
- }
489
- return entries.sort((a, b) => a.askedAt.localeCompare(b.askedAt));
490
- }
491
478
  async disconnect() {
492
479
  for (const ws of this.peerConnections.values()) ws.close();
493
480
  this.peerConnections.clear();
@@ -618,7 +605,6 @@ var MeshNode = class {
618
605
  // ---------------------------------------------------------------------------
619
606
  /** Handles messages on inbound connections (server side — we know the remote IP). */
620
607
  handleInboundMessage(ws, remoteIp, msg) {
621
- for (const handler of this.pendingHandlers) handler(msg);
622
608
  if (msg.type === "HELLO") {
623
609
  if (this.peerConnections.has(msg.name)) {
624
610
  ws.terminate();
@@ -673,11 +659,12 @@ var MeshNode = class {
673
659
  answeredAt: msg.answeredAt,
674
660
  fromName: msg.from
675
661
  };
676
- this.receivedAnswers.set(msg.questionId, record);
677
662
  const waiter = this.answerWaiters.get(msg.questionId);
678
663
  if (waiter) {
679
664
  this.answerWaiters.delete(msg.questionId);
680
665
  waiter(this.formatAnswer(msg.questionId, record));
666
+ } else {
667
+ this.receivedAnswers.set(msg.questionId, record);
681
668
  }
682
669
  }
683
670
  break;
@@ -706,10 +693,10 @@ var MeshNode = class {
706
693
  }
707
694
  deliverPendingAnswer(peerName, ws) {
708
695
  const pending = this.pendingOutboundAnswers.get(peerName);
709
- if (pending) {
696
+ if (pending && pending.length > 0) {
710
697
  this.pendingOutboundAnswers.delete(peerName);
711
- this.sendToWs(ws, pending);
712
- console.error(`[mesh] delivered queued answer to "${peerName}" after reconnect`);
698
+ for (const msg of pending) this.sendToWs(ws, msg);
699
+ console.error(`[mesh] delivered ${pending.length} queued answer(s) to "${peerName}" after reconnect`);
713
700
  }
714
701
  }
715
702
  sendToWs(ws, msg) {
@@ -737,12 +724,9 @@ var MeshNode = class {
737
724
  async function killProcessOnPort(port) {
738
725
  try {
739
726
  if (process.platform === "win32") {
740
- const out = execSync(
741
- `netstat -ano | findstr ":${port} "`,
742
- { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
743
- );
727
+ const { stdout } = await execAsync(`netstat -ano | findstr ":${port} "`);
744
728
  const pids = /* @__PURE__ */ new Set();
745
- for (const line of out.split("\n")) {
729
+ for (const line of stdout.split("\n")) {
746
730
  const parts = line.trim().split(/\s+/);
747
731
  if (parts.length >= 5 && parts[3] === "LISTENING" && parts[4]) {
748
732
  pids.add(parts[4]);
@@ -750,13 +734,13 @@ async function killProcessOnPort(port) {
750
734
  }
751
735
  for (const pid of pids) {
752
736
  try {
753
- execSync(`taskkill /PID ${pid} /F`, { stdio: "ignore" });
737
+ await execAsync(`taskkill /PID ${pid} /F`);
754
738
  console.error(`[mesh] killed PID ${pid} on port ${port}`);
755
739
  } catch {
756
740
  }
757
741
  }
758
742
  } else {
759
- execSync(`fuser -k ${port}/tcp`, { stdio: "ignore" });
743
+ await execAsync(`fuser -k ${port}/tcp`);
760
744
  }
761
745
  } catch {
762
746
  }
@@ -972,49 +956,6 @@ function registerReplyTool(server, client) {
972
956
  });
973
957
  }
974
958
 
975
- // src/presentation/mcp/tools/history.tool.ts
976
- var HISTORY_DESCRIPTION = `Show all questions and answers exchanged this session \u2014 both sent and received.
977
-
978
- WHEN TO USE:
979
- - To review what was already discussed before asking a follow-up question
980
- - To check if a previously sent question has been answered yet
981
- - To find a questionId you need to reference
982
- - To catch up on received questions you may have missed
983
-
984
- OUTPUT FORMAT:
985
- - \u2192 peer: question you sent, with their answer below
986
- - \u2190 peer: question they sent you, with your reply below
987
- - (no answer yet) means the question is still waiting for a response`;
988
- function registerHistoryTool(server, client) {
989
- server.tool("history", HISTORY_DESCRIPTION, {}, async () => {
990
- const entries = client.getHistory();
991
- if (entries.length === 0) {
992
- return {
993
- content: [{ type: "text", text: "No questions exchanged yet this session." }]
994
- };
995
- }
996
- const unanswered = entries.filter((e) => !e.answer);
997
- const lines = entries.map((e) => {
998
- const time = new Date(e.askedAt).toLocaleTimeString();
999
- if (e.direction === "sent") {
1000
- const answerLine = e.answer ? ` \u21B3 ${e.peer}: ${e.answer}` : ` \u21B3 (no answer yet)`;
1001
- return `[${time}] \u2192 ${e.peer}: ${e.question}
1002
- ${answerLine}`;
1003
- } else {
1004
- const answerLine = e.answer ? ` \u21B3 you: ${e.answer}` : ` \u21B3 (not replied yet \u2014 use reply() if you haven't answered)`;
1005
- return `[${time}] \u2190 ${e.peer}: ${e.question}
1006
- ${answerLine}`;
1007
- }
1008
- });
1009
- const summary = unanswered.length > 0 ? `
1010
-
1011
- \u26A0 ${unanswered.length} question(s) still waiting for a response.` : "";
1012
- return {
1013
- content: [{ type: "text", text: lines.join("\n\n") + summary }]
1014
- };
1015
- });
1016
- }
1017
-
1018
959
  // src/presentation/mcp/tools/status.tool.ts
1019
960
  var STATUS_DESCRIPTION = `Show your identity and network address on the collaboration network.
1020
961
 
@@ -1083,7 +1024,6 @@ function createMcpServer(options) {
1083
1024
  registerStatusTool(server, client);
1084
1025
  registerAskTool(server, client);
1085
1026
  registerReplyTool(server, client);
1086
- registerHistoryTool(server, client);
1087
1027
  return server;
1088
1028
  }
1089
1029
  async function startMcpServer(options) {