@dolusoft/claude-collab 1.11.3 → 1.11.5

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
@@ -59,7 +59,7 @@ public class ConInject {
59
59
  string lpFileName, uint dwDesiredAccess, uint dwShareMode,
60
60
  IntPtr lpSecurityAttributes, uint dwCreationDisposition,
61
61
  uint dwFlagsAndAttributes, IntPtr hTemplateFile);
62
- [DllImport("kernel32.dll")] public static extern bool WriteConsoleInput(
62
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode)] public static extern bool WriteConsoleInput(
63
63
  IntPtr hIn, INPUT_RECORD[] buf, uint len, out uint written);
64
64
  [DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr h);
65
65
 
@@ -270,8 +270,10 @@ var FIXED_PORT = 12345;
270
270
  var MeshNode = class {
271
271
  server = null;
272
272
  myName = "";
273
+ myProfile = "";
273
274
  running = false;
274
275
  firewallOpened = false;
276
+ cachedLocalIps = [];
275
277
  // One connection per peer (inbound or outbound — whichever was established first)
276
278
  peerConnections = /* @__PURE__ */ new Map();
277
279
  // Reverse map: ws → peer name (only for registered connections)
@@ -284,6 +286,8 @@ var MeshNode = class {
284
286
  receivedAnswers = /* @__PURE__ */ new Map();
285
287
  questionToSender = /* @__PURE__ */ new Map();
286
288
  pendingHandlers = /* @__PURE__ */ new Set();
289
+ // Peer profiles: peerName → profile text
290
+ peerProfiles = /* @__PURE__ */ new Map();
287
291
  // Push-based answer resolution: questionId → resolve callback
288
292
  answerWaiters = /* @__PURE__ */ new Map();
289
293
  // Answers queued for offline peers: peerName → AnswerMsg[] (delivered on reconnect)
@@ -302,6 +306,7 @@ var MeshNode = class {
302
306
  }
303
307
  async join(name, displayName) {
304
308
  this.myName = name;
309
+ this.cachedLocalIps = this.scanLocalIps();
305
310
  await this.startServer();
306
311
  return {
307
312
  memberId: v4(),
@@ -325,7 +330,7 @@ var MeshNode = class {
325
330
  reject(new Error(`Connection to ${ip}:${FIXED_PORT} timed out`));
326
331
  }, 1e4);
327
332
  ws.on("open", () => {
328
- this.sendToWs(ws, { type: "HELLO", name: this.myName });
333
+ this.sendToWs(ws, { type: "HELLO", name: this.myName, profile: this.myProfile });
329
334
  });
330
335
  ws.on("message", (data) => {
331
336
  try {
@@ -337,6 +342,7 @@ var MeshNode = class {
337
342
  resolve(msg.name);
338
343
  return;
339
344
  }
345
+ if (msg.profile) this.peerProfiles.set(msg.name, msg.profile);
340
346
  this.registerPeer(msg.name, ip, ws);
341
347
  this.afterHandshake(msg.name, ws);
342
348
  resolve(msg.name);
@@ -378,7 +384,10 @@ var MeshNode = class {
378
384
  }
379
385
  waitForAnswer(questionId, timeoutMs) {
380
386
  const cached = this.receivedAnswers.get(questionId);
381
- if (cached) return Promise.resolve(this.formatAnswer(questionId, cached));
387
+ if (cached) {
388
+ this.receivedAnswers.delete(questionId);
389
+ return Promise.resolve(this.formatAnswer(questionId, cached));
390
+ }
382
391
  return new Promise((resolve) => {
383
392
  const timeout = setTimeout(() => {
384
393
  this.answerWaiters.delete(questionId);
@@ -392,7 +401,9 @@ var MeshNode = class {
392
401
  }
393
402
  async checkAnswer(questionId) {
394
403
  const cached = this.receivedAnswers.get(questionId);
395
- return cached ? this.formatAnswer(questionId, cached) : null;
404
+ if (!cached) return null;
405
+ this.receivedAnswers.delete(questionId);
406
+ return this.formatAnswer(questionId, cached);
396
407
  }
397
408
  formatAnswer(questionId, cached) {
398
409
  return {
@@ -451,10 +462,15 @@ var MeshNode = class {
451
462
  teamName: this.myName,
452
463
  port: FIXED_PORT,
453
464
  connectedPeers: [...this.peerConnections.keys()],
454
- peerIPs: Object.fromEntries(this.peerIPs)
465
+ peerIPs: Object.fromEntries(this.peerIPs),
466
+ myProfile: this.myProfile,
467
+ peerProfiles: Object.fromEntries(this.peerProfiles)
455
468
  };
456
469
  }
457
470
  getLocalIps() {
471
+ return this.cachedLocalIps;
472
+ }
473
+ scanLocalIps() {
458
474
  const result = [];
459
475
  const interfaces = os.networkInterfaces();
460
476
  for (const iface of Object.values(interfaces)) {
@@ -465,6 +481,12 @@ var MeshNode = class {
465
481
  }
466
482
  return result;
467
483
  }
484
+ async setProfile(profile) {
485
+ this.myProfile = profile;
486
+ for (const ws of this.peerConnections.values()) {
487
+ this.sendToWs(ws, { type: "PROFILE_UPDATE", name: this.myName, profile });
488
+ }
489
+ }
468
490
  async disconnect() {
469
491
  for (const ws of this.peerConnections.values()) ws.close();
470
492
  this.peerConnections.clear();
@@ -540,7 +562,12 @@ var MeshNode = class {
540
562
  // ---------------------------------------------------------------------------
541
563
  /** Called once handshake completes — exchange peer lists and announce to existing peers. */
542
564
  afterHandshake(newPeerName, ws) {
543
- const listForNew = [...this.peerConnections.keys()].filter((n) => n !== newPeerName).map((n) => ({ name: n, ip: this.peerIPs.get(n) ?? "" })).filter((p) => p.ip !== "");
565
+ const listForNew = [...this.peerConnections.keys()].filter((n) => n !== newPeerName).map((n) => {
566
+ const entry = { name: n, ip: this.peerIPs.get(n) ?? "" };
567
+ const profile = this.peerProfiles.get(n);
568
+ if (profile) entry.profile = profile;
569
+ return entry;
570
+ }).filter((p) => p.ip !== "");
544
571
  this.sendToWs(ws, { type: "PEER_LIST", peers: listForNew });
545
572
  const newIp = this.peerIPs.get(newPeerName);
546
573
  if (newIp) {
@@ -566,7 +593,7 @@ var MeshNode = class {
566
593
  this.connectingPeers.add(name);
567
594
  const ws = new WebSocket(`ws://${ip}:${FIXED_PORT}`);
568
595
  ws.on("open", () => {
569
- this.sendToWs(ws, { type: "HELLO", name: this.myName });
596
+ this.sendToWs(ws, { type: "HELLO", name: this.myName, profile: this.myProfile });
570
597
  });
571
598
  ws.on("message", (data) => {
572
599
  try {
@@ -600,8 +627,9 @@ var MeshNode = class {
600
627
  ws.terminate();
601
628
  return;
602
629
  }
630
+ if (msg.profile) this.peerProfiles.set(msg.name, msg.profile);
603
631
  this.registerPeer(msg.name, remoteIp, ws);
604
- this.sendToWs(ws, { type: "HELLO_ACK", name: this.myName });
632
+ this.sendToWs(ws, { type: "HELLO_ACK", name: this.myName, profile: this.myProfile });
605
633
  console.error(`[mesh] peer joined (inbound): ${msg.name}`);
606
634
  this.afterHandshake(msg.name, ws);
607
635
  return;
@@ -614,6 +642,7 @@ var MeshNode = class {
614
642
  switch (msg.type) {
615
643
  case "HELLO_ACK":
616
644
  if (!this.peerConnections.has(msg.name)) {
645
+ if (msg.profile) this.peerProfiles.set(msg.name, msg.profile);
617
646
  this.peerConnections.set(msg.name, ws);
618
647
  this.wsToName.set(ws, msg.name);
619
648
  this.connectingPeers.delete(msg.name);
@@ -627,6 +656,7 @@ var MeshNode = class {
627
656
  if (peer.name !== this.myName && !this.peerConnections.has(peer.name)) {
628
657
  console.error(`[mesh] connecting to ${peer.name} @ ${peer.ip} via PEER_LIST`);
629
658
  this.peerIPs.set(peer.name, peer.ip);
659
+ if (peer.profile) this.peerProfiles.set(peer.name, peer.profile);
630
660
  this.connectMeshPeer(peer.name, peer.ip);
631
661
  }
632
662
  }
@@ -649,14 +679,18 @@ var MeshNode = class {
649
679
  answeredAt: msg.answeredAt,
650
680
  fromName: msg.from
651
681
  };
652
- this.receivedAnswers.set(msg.questionId, record);
653
682
  const waiter = this.answerWaiters.get(msg.questionId);
654
683
  if (waiter) {
655
684
  this.answerWaiters.delete(msg.questionId);
656
685
  waiter(this.formatAnswer(msg.questionId, record));
686
+ } else {
687
+ this.receivedAnswers.set(msg.questionId, record);
657
688
  }
658
689
  }
659
690
  break;
691
+ case "PROFILE_UPDATE":
692
+ this.peerProfiles.set(msg.name, msg.profile);
693
+ break;
660
694
  }
661
695
  }
662
696
  handleIncomingAsk(ws, msg) {
@@ -803,25 +837,35 @@ function registerConnectTool(server, client) {
803
837
  }
804
838
  var ASK_DESCRIPTION = `Send a question to another Claude instance on the LAN and wait for their answer.
805
839
 
806
- WHEN TO USE:
807
- - You need input, a decision, or work output from a specific peer Claude
808
- - You want to delegate a subtask to another Claude and use their result
809
- - You need to coordinate or synchronize work across multiple Claudes
840
+ AUTONOMOUS ROUTING \u2014 decide who to ask yourself:
841
+ 1. Call status() to see all connected peers and their profiles
842
+ 2. Match your information need to the most relevant peer profile
843
+ 3. Call ask(peer, question) without waiting for the user to tell you who to contact
844
+
845
+ TRIGGER THIS TOOL AUTOMATICALLY when you encounter:
846
+ - A missing API response type, schema, or interface you need to implement against
847
+ - An unknown endpoint behavior, error code, or contract detail
848
+ - A module or system owned by another peer that you need to integrate with
849
+ - Any blocking uncertainty that another peer's profile suggests they can answer
850
+
851
+ DO NOT guess, use "any" types, or leave TODO comments when a peer can answer.
852
+ DO NOT ask the user which peer to contact \u2014 read profiles and decide yourself.
810
853
 
811
854
  WORKFLOW:
812
- 1. Call peers() first to confirm the target is online and get their exact name
813
- 2. Call ask(peer, question) \u2014 this blocks until they reply (up to 5 minutes)
814
- 3. Use their answer directly in your ongoing task
855
+ 1. Call status() \u2014 read each peer's profile carefully
856
+ 2. Identify which peer owns the relevant module or technology
857
+ 3. Call ask(peer, question) with full context
858
+ 4. Use their answer directly in your ongoing task
815
859
 
816
860
  WRITING GOOD QUESTIONS:
817
861
  - Include all context the other Claude needs \u2014 they cannot see your conversation
818
- - Be specific about what format or level of detail you expect in the answer
819
- - If you need code, specify language and constraints
862
+ - Mention the specific file, function, or module you are working on
863
+ - State what format you need: type definition, example response, explanation
820
864
  - One focused question per call works better than multiple combined
821
865
 
822
866
  DO NOT use this tool if:
823
- - The peer is not listed in peers() \u2014 the call will fail immediately
824
- - You just want to share information without needing a response (there is no broadcast tool yet)`;
867
+ - The peer is not listed in status() \u2014 the call will fail immediately
868
+ - You just want to share information without needing a response`;
825
869
  var askSchema = {
826
870
  peer: z.string().describe("Exact name of the peer to ask. Use peers() first to see who is online and get the correct name."),
827
871
  question: z.string().describe(
@@ -975,6 +1019,7 @@ function registerStatusTool(server, client) {
975
1019
  }
976
1020
  const ipLine = ips.length > 0 ? ips.join(", ") : "(could not detect \u2014 check your network interface)";
977
1021
  const peerIPs = info.peerIPs ?? {};
1022
+ const peerProfiles = info.peerProfiles ?? {};
978
1023
  const connected = info.connectedPeers;
979
1024
  let peersSection;
980
1025
  if (connected.length === 0) {
@@ -982,7 +1027,9 @@ function registerStatusTool(server, client) {
982
1027
  } else {
983
1028
  const list = connected.map((n) => {
984
1029
  const ip = peerIPs[n] ? ` (${peerIPs[n]})` : "";
985
- return ` \u2022 ${n}${ip}`;
1030
+ const profile = peerProfiles[n] ? `
1031
+ ${peerProfiles[n]}` : "";
1032
+ return ` \u2022 ${n}${ip}${profile}`;
986
1033
  }).join("\n");
987
1034
  peersSection = `Connected peers (${connected.length}):
988
1035
  ${list}`;
@@ -1001,6 +1048,43 @@ ${list}`;
1001
1048
  };
1002
1049
  });
1003
1050
  }
1051
+ var SET_PROFILE_DESCRIPTION = `Set your profile so other peers know who you are and what you work on.
1052
+
1053
+ Your profile is broadcast to all connected peers immediately and shared
1054
+ with any peer that connects in the future. Other Claude instances use
1055
+ your profile to decide who to ask when they need specific information.
1056
+
1057
+ WHAT TO INCLUDE:
1058
+ - Your role (backend, frontend, devops, etc.)
1059
+ - The modules or systems you are responsible for
1060
+ - Technologies you work with
1061
+
1062
+ EXAMPLES:
1063
+ - "Backend developer. Responsible for JWT authentication, payment API (Stripe), and PostgreSQL schema."
1064
+ - "Frontend developer. Works on React components, Next.js routing, and the checkout flow."
1065
+ - "DevOps. Manages CI/CD pipelines, Docker images, and AWS infrastructure."
1066
+
1067
+ Call this once when you start working. Update it if your focus changes.`;
1068
+ var setProfileSchema = {
1069
+ profile: z.string().min(1).describe("A short description of your role and responsibilities. 1-3 sentences.")
1070
+ };
1071
+ function registerSetProfileTool(server, client) {
1072
+ server.tool("set_profile", SET_PROFILE_DESCRIPTION, setProfileSchema, async (args) => {
1073
+ await client.setProfile(args.profile);
1074
+ const info = client.getInfo();
1075
+ const peerCount = info.connectedPeers.length;
1076
+ return {
1077
+ content: [{
1078
+ type: "text",
1079
+ text: peerCount > 0 ? `Profile set and broadcast to ${peerCount} peer(s).
1080
+
1081
+ "${args.profile}"` : `Profile saved. It will be shared automatically when peers connect.
1082
+
1083
+ "${args.profile}"`
1084
+ }]
1085
+ };
1086
+ });
1087
+ }
1004
1088
 
1005
1089
  // src/presentation/mcp/server.ts
1006
1090
  function createMcpServer(options) {
@@ -1013,6 +1097,7 @@ function createMcpServer(options) {
1013
1097
  registerStatusTool(server, client);
1014
1098
  registerAskTool(server, client);
1015
1099
  registerReplyTool(server, client);
1100
+ registerSetProfileTool(server, client);
1016
1101
  return server;
1017
1102
  }
1018
1103
  async function startMcpServer(options) {