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