@memtensor/memos-local-openclaw-plugin 1.0.4-beta.10 → 1.0.4-beta.11

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 (67) hide show
  1. package/dist/client/connector.d.ts +1 -0
  2. package/dist/client/connector.d.ts.map +1 -1
  3. package/dist/client/connector.js +37 -8
  4. package/dist/client/connector.js.map +1 -1
  5. package/dist/hub/server.d.ts +1 -0
  6. package/dist/hub/server.d.ts.map +1 -1
  7. package/dist/hub/server.js +122 -28
  8. package/dist/hub/server.js.map +1 -1
  9. package/dist/hub/user-manager.d.ts +9 -0
  10. package/dist/hub/user-manager.d.ts.map +1 -1
  11. package/dist/hub/user-manager.js +26 -2
  12. package/dist/hub/user-manager.js.map +1 -1
  13. package/dist/recall/engine.d.ts.map +1 -1
  14. package/dist/recall/engine.js +2 -0
  15. package/dist/recall/engine.js.map +1 -1
  16. package/dist/sharing/types.d.ts +1 -1
  17. package/dist/sharing/types.d.ts.map +1 -1
  18. package/dist/skill/evolver.d.ts +2 -0
  19. package/dist/skill/evolver.d.ts.map +1 -1
  20. package/dist/skill/evolver.js +56 -5
  21. package/dist/skill/evolver.js.map +1 -1
  22. package/dist/skill/generator.d.ts +2 -0
  23. package/dist/skill/generator.d.ts.map +1 -1
  24. package/dist/skill/generator.js +45 -3
  25. package/dist/skill/generator.js.map +1 -1
  26. package/dist/skill/installer.d.ts +26 -0
  27. package/dist/skill/installer.d.ts.map +1 -1
  28. package/dist/skill/installer.js +80 -4
  29. package/dist/skill/installer.js.map +1 -1
  30. package/dist/skill/upgrader.d.ts +2 -0
  31. package/dist/skill/upgrader.d.ts.map +1 -1
  32. package/dist/skill/upgrader.js +139 -1
  33. package/dist/skill/upgrader.js.map +1 -1
  34. package/dist/skill/validator.d.ts +3 -0
  35. package/dist/skill/validator.d.ts.map +1 -1
  36. package/dist/skill/validator.js +75 -0
  37. package/dist/skill/validator.js.map +1 -1
  38. package/dist/storage/sqlite.d.ts +15 -0
  39. package/dist/storage/sqlite.d.ts.map +1 -1
  40. package/dist/storage/sqlite.js +91 -9
  41. package/dist/storage/sqlite.js.map +1 -1
  42. package/dist/types.d.ts +10 -0
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/types.js +4 -0
  45. package/dist/types.js.map +1 -1
  46. package/dist/viewer/html.d.ts.map +1 -1
  47. package/dist/viewer/html.js +44 -23
  48. package/dist/viewer/html.js.map +1 -1
  49. package/dist/viewer/server.d.ts.map +1 -1
  50. package/dist/viewer/server.js +35 -15
  51. package/dist/viewer/server.js.map +1 -1
  52. package/index.ts +316 -13
  53. package/package.json +1 -1
  54. package/src/client/connector.ts +41 -8
  55. package/src/hub/server.ts +123 -27
  56. package/src/hub/user-manager.ts +42 -6
  57. package/src/recall/engine.ts +2 -0
  58. package/src/sharing/types.ts +1 -1
  59. package/src/skill/evolver.ts +58 -6
  60. package/src/skill/generator.ts +44 -5
  61. package/src/skill/installer.ts +107 -4
  62. package/src/skill/upgrader.ts +139 -1
  63. package/src/skill/validator.ts +79 -0
  64. package/src/storage/sqlite.ts +105 -9
  65. package/src/types.ts +11 -0
  66. package/src/viewer/html.ts +44 -23
  67. package/src/viewer/server.ts +35 -15
@@ -804,6 +804,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
804
804
  .recall-score.high{background:rgba(34,197,94,.12);color:#22c55e}
805
805
  .recall-score.mid{background:rgba(251,191,36,.12);color:#f59e0b}
806
806
  .recall-score.low{background:rgba(248,113,113,.1);color:var(--text-muted)}
807
+ .recall-origin{flex-shrink:0;font-size:9px;font-weight:600;padding:1px 5px;border-radius:4px}
808
+ .recall-origin.local-shared{background:rgba(59,130,246,.12);color:#3b82f6}
809
+ .recall-origin.hub-memory{background:rgba(139,92,246,.12);color:#8b5cf6}
810
+ .recall-origin.hub-remote{background:rgba(139,92,246,.12);color:#8b5cf6}
807
811
  .recall-summary-short{flex:1;color:var(--text-sec);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
808
812
  .recall-expand-icon{flex-shrink:0;font-size:10px;color:var(--text-muted);transition:transform .15s}
809
813
  .recall-item.expanded .recall-expand-icon{transform:rotate(90deg)}
@@ -1216,7 +1220,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1216
1220
  </div>
1217
1221
  <button class="btn btn-ghost btn-sm" onclick="doLogout()" data-i18n="logout">Logout</button>
1218
1222
  </div>
1219
- </div>
1223
+ </div>
1220
1224
  </div>
1221
1225
 
1222
1226
  <div class="main-content">
@@ -2229,6 +2233,9 @@ const I18N={
2229
2233
  'logs.recall.noHits':'No matching memories',
2230
2234
  'logs.recall.noneRelevant':'LLM filter: none relevant',
2231
2235
  'logs.recall.more':'{n} more...',
2236
+ 'recall.origin.localShared':'Local Shared',
2237
+ 'recall.origin.hubMemory':'Team Cache',
2238
+ 'recall.origin.hubRemote':'Team',
2232
2239
  'tab.import':'\u{1F4E5} Import',
2233
2240
  'tab.settings':'\u2699 Settings',
2234
2241
  'settings.modelconfig':'Model Configuration',
@@ -2940,6 +2947,9 @@ const I18N={
2940
2947
  'logs.recall.noHits':'未匹配到记忆',
2941
2948
  'logs.recall.noneRelevant':'LLM 过滤:无相关记忆',
2942
2949
  'logs.recall.more':'还有 {n} 条...',
2950
+ 'recall.origin.localShared':'本机共享',
2951
+ 'recall.origin.hubMemory':'团队缓存',
2952
+ 'recall.origin.hubRemote':'团队',
2943
2953
  'tab.import':'\u{1F4E5} 导入',
2944
2954
  'tab.settings':'\u2699 设置',
2945
2955
  'settings.modelconfig':'模型配置',
@@ -4087,7 +4097,7 @@ function adminPaginateHtml(total,page,refilterFn){
4087
4097
  if(end<pages) html+=(end<pages-1?'<span class="pg-info">...</span>':'')+'<button class="pg-btn" onclick="'+refilterFn+'Page('+(pages-1)+')">'+pages+'</button>';
4088
4098
  html+='<button class="pg-btn'+(page>=pages-1?' disabled':'')+'" onclick="'+refilterFn+'Page('+(page+1)+')">\\u2192</button>';
4089
4099
  html+='<span class="pg-info">'+total+' '+t('pagination.total')+'</span>';
4090
- html+='</div>';
4100
+ html+='</div>';
4091
4101
  return html;
4092
4102
  }
4093
4103
 
@@ -4149,12 +4159,12 @@ async function loadAdminData(){
4149
4159
  var fetches;
4150
4160
  if(isAdmin){
4151
4161
  fetches=await Promise.all([
4152
- fetch('/api/sharing/users').then(function(r){return r.json();}),
4153
- fetch('/api/admin/shared-tasks').then(function(r){return r.json();}),
4154
- fetch('/api/admin/shared-skills').then(function(r){return r.json();}),
4155
- fetch('/api/sharing/pending-users').then(function(r){return r.json();}),
4156
- fetch('/api/admin/shared-memories').then(function(r){return r.json();})
4157
- ]);
4162
+ fetch('/api/sharing/users').then(function(r){return r.json();}),
4163
+ fetch('/api/admin/shared-tasks').then(function(r){return r.json();}),
4164
+ fetch('/api/admin/shared-skills').then(function(r){return r.json();}),
4165
+ fetch('/api/sharing/pending-users').then(function(r){return r.json();}),
4166
+ fetch('/api/admin/shared-memories').then(function(r){return r.json();})
4167
+ ]);
4158
4168
  }else{
4159
4169
  fetches=await Promise.all([
4160
4170
  Promise.resolve({users:[]}),
@@ -5389,6 +5399,13 @@ function parseMemoryAddEntries(out){
5389
5399
  return results;
5390
5400
  }
5391
5401
 
5402
+ function recallOriginBadge(origin){
5403
+ if(origin==='local-shared') return '<span class="recall-origin local-shared">'+t('recall.origin.localShared')+'</span>';
5404
+ if(origin==='hub-memory') return '<span class="recall-origin hub-memory">'+t('recall.origin.hubMemory')+'</span>';
5405
+ if(origin==='hub-remote') return '<span class="recall-origin hub-remote">'+t('recall.origin.hubRemote')+'</span>';
5406
+ return '';
5407
+ }
5408
+
5392
5409
  function buildLogSummary(lg){
5393
5410
  let inputObj=null;
5394
5411
  try{inputObj=JSON.parse(lg.input);}catch(_){}
@@ -5413,8 +5430,9 @@ function buildLogSummary(lg){
5413
5430
  var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
5414
5431
  var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
5415
5432
  var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
5433
+ var oBadge=recallOriginBadge(c.origin);
5416
5434
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5417
- html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5435
+ html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5418
5436
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5419
5437
  html+='</div>';
5420
5438
  });
@@ -5427,8 +5445,9 @@ function buildLogSummary(lg){
5427
5445
  var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
5428
5446
  var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
5429
5447
  var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
5448
+ var oBadge=recallOriginBadge(f.origin);
5430
5449
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5431
- html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+f.score.toFixed(2)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5450
+ html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+f.score.toFixed(2)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5432
5451
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5433
5452
  html+='</div>';
5434
5453
  });
@@ -5492,8 +5511,9 @@ function buildRecallDetailHtml(rd){
5492
5511
  var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
5493
5512
  var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
5494
5513
  var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
5514
+ var oBadge=recallOriginBadge(c.origin);
5495
5515
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5496
- html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+c.score.toFixed(3)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5516
+ html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+c.score.toFixed(3)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5497
5517
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5498
5518
  html+='</div>';
5499
5519
  });
@@ -5507,8 +5527,9 @@ function buildRecallDetailHtml(rd){
5507
5527
  var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
5508
5528
  var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
5509
5529
  var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
5530
+ var oBadge=recallOriginBadge(f.origin);
5510
5531
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5511
- html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+f.score.toFixed(3)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5532
+ html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+f.score.toFixed(3)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5512
5533
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5513
5534
  html+='</div>';
5514
5535
  });
@@ -6714,8 +6735,8 @@ async function saveHubConfig(){
6714
6735
  if(!td.ok){
6715
6736
  var errMsg=td.error==='cannot_join_self'?t('sharing.cannotJoinSelf'):(td.error||t('settings.hub.test.fail'));
6716
6737
  done();toast(errMsg,'error');return;
6717
- }
6718
- }catch(e){
6738
+ }
6739
+ }catch(e){
6719
6740
  done();toast(t('settings.hub.test.fail')+': '+String(e),'error');return;
6720
6741
  }
6721
6742
  }
@@ -7536,8 +7557,8 @@ function getFilterParams(){
7536
7557
  if(scope==='local'){
7537
7558
  p.set('owner',_currentAgentOwner);
7538
7559
  }else if(scope==='allLocal'){
7539
- const owner=document.getElementById('filterOwner').value;
7540
- if(owner) p.set('owner',owner);
7560
+ const owner=document.getElementById('filterOwner').value;
7561
+ if(owner) p.set('owner',owner);
7541
7562
  }
7542
7563
  return p;
7543
7564
  }
@@ -7567,11 +7588,11 @@ async function loadMemories(page,silent){
7567
7588
  renderPagination();
7568
7589
  }catch(e){
7569
7590
  if(!silent){
7570
- list.innerHTML='';
7571
- totalPages=1;totalCount=0;
7591
+ list.innerHTML='';
7592
+ totalPages=1;totalCount=0;
7572
7593
  _lastMemoriesFingerprint='';
7573
- renderMemories([]);
7574
- renderPagination();
7594
+ renderMemories([]);
7595
+ renderPagination();
7575
7596
  }
7576
7597
  }
7577
7598
  }
@@ -7598,9 +7619,9 @@ async function loadHubMemories(silent){
7598
7619
  }catch(e){
7599
7620
  if(!silent){
7600
7621
  _lastMemoriesFingerprint='';
7601
- document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
7602
- renderMemories([]);
7603
- document.getElementById('pagination').innerHTML='';
7622
+ document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
7623
+ renderMemories([]);
7624
+ document.getElementById('pagination').innerHTML='';
7604
7625
  }
7605
7626
  }
7606
7627
  }
@@ -1780,7 +1780,8 @@ export class ViewerServer {
1780
1780
  localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
1781
1781
  try {
1782
1782
  const u = new URL(hubUrl);
1783
- if (localIPs.includes(u.hostname)) {
1783
+ const targetPort = u.port || (u.protocol === "https:" ? "443" : "80");
1784
+ if (localIPs.includes(u.hostname) && targetPort === String(this.port)) {
1784
1785
  return this.jsonResponse(res, { ok: false, error: "cannot_join_self" });
1785
1786
  }
1786
1787
  } catch {}
@@ -1788,10 +1789,13 @@ export class ViewerServer {
1788
1789
  const nickname = sharing.client?.nickname;
1789
1790
  const username = nickname || os.userInfo().username || "user";
1790
1791
  const hostname = os.hostname() || "unknown";
1792
+ const persisted = this.store.getClientHubConnection();
1793
+ const existingIdentityKey = persisted?.identityKey || "";
1791
1794
  const result = await hubRequestJson(hubUrl, "", "/api/v1/hub/join", {
1792
1795
  method: "POST",
1793
- body: JSON.stringify({ teamToken, username, deviceName: hostname, reapply: true }),
1796
+ body: JSON.stringify({ teamToken, username, deviceName: hostname, reapply: true, identityKey: existingIdentityKey }),
1794
1797
  }) as any;
1798
+ const returnedIdentityKey = String(result.identityKey || existingIdentityKey || "");
1795
1799
  this.store.setClientHubConnection({
1796
1800
  hubUrl,
1797
1801
  userId: String(result.userId || ""),
@@ -1799,6 +1803,8 @@ export class ViewerServer {
1799
1803
  userToken: result.userToken || "",
1800
1804
  role: "member",
1801
1805
  connectedAt: Date.now(),
1806
+ identityKey: returnedIdentityKey,
1807
+ lastKnownStatus: result.status || "",
1802
1808
  });
1803
1809
  this.jsonResponse(res, { ok: true, status: result.status || "pending" });
1804
1810
  } catch (err) {
@@ -2592,7 +2598,8 @@ export class ViewerServer {
2592
2598
  localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
2593
2599
  try {
2594
2600
  const u = new URL(addr.startsWith("http") ? addr : `http://${addr}`);
2595
- if (localIPs.includes(u.hostname)) {
2601
+ const targetPort = u.port || (u.protocol === "https:" ? "443" : "80");
2602
+ if (localIPs.includes(u.hostname) && targetPort === String(this.port)) {
2596
2603
  res.writeHead(400, { "Content-Type": "application/json" });
2597
2604
  res.end(JSON.stringify({ error: "cannot_join_self" }));
2598
2605
  return;
@@ -2618,17 +2625,22 @@ export class ViewerServer {
2618
2625
  const isClient = newEnabled && newRole === "client";
2619
2626
  if (wasClient && !isClient) {
2620
2627
  this.notifyHubLeave();
2621
- this.store.clearClientHubConnection();
2622
- this.log.info("Cleared client hub connection (sharing disabled or role changed)");
2628
+ const oldConn = this.store.getClientHubConnection();
2629
+ if (oldConn) {
2630
+ this.store.setClientHubConnection({ ...oldConn, userToken: "", lastKnownStatus: "left" });
2631
+ }
2632
+ this.log.info("Client hub connection token cleared (sharing disabled or role changed), identity preserved");
2623
2633
  }
2624
2634
 
2625
- // Detect switching to a different Hub while still in client mode
2626
2635
  if (wasClient && isClient) {
2627
2636
  const newClientAddr = String((merged.client as Record<string, unknown>)?.hubAddress || "");
2628
2637
  if (newClientAddr && oldClientHubAddress && normalizeHubUrl(newClientAddr) !== normalizeHubUrl(oldClientHubAddress)) {
2629
2638
  this.notifyHubLeave();
2630
- this.store.clearClientHubConnection();
2631
- this.log.info("Cleared client hub connection (switched to different Hub)");
2639
+ const oldConn = this.store.getClientHubConnection();
2640
+ if (oldConn) {
2641
+ this.store.setClientHubConnection({ ...oldConn, hubUrl: normalizeHubUrl(newClientAddr), userToken: "", lastKnownStatus: "hub_changed" });
2642
+ }
2643
+ this.log.info("Client hub connection token cleared (switched to different Hub), identity preserved");
2632
2644
  }
2633
2645
  }
2634
2646
 
@@ -2645,9 +2657,11 @@ export class ViewerServer {
2645
2657
  this.log.info("Plugin config updated via Viewer");
2646
2658
  this.stopHubHeartbeat();
2647
2659
 
2648
- // When switching to client mode, immediately send join request
2660
+ // When switching to client mode or re-enabling sharing as client, send join request
2649
2661
  const finalSharing = config.sharing as Record<string, unknown> | undefined;
2650
- if (finalSharing?.role === "client" && oldSharingRole !== "client") {
2662
+ const nowClient = Boolean(finalSharing?.enabled) && finalSharing?.role === "client";
2663
+ const previouslyClient = oldSharingEnabled && oldSharingRole === "client";
2664
+ if (nowClient && !previouslyClient) {
2651
2665
  this.autoJoinOnSave(finalSharing).catch(e => this.log.warn(`Auto-join on save failed: ${e}`));
2652
2666
  }
2653
2667
 
@@ -2670,10 +2684,13 @@ export class ViewerServer {
2670
2684
  const nickname = String(clientCfg?.nickname || "");
2671
2685
  const username = nickname || os.userInfo().username || "user";
2672
2686
  const hostname = os.hostname() || "unknown";
2687
+ const persisted = this.store.getClientHubConnection();
2688
+ const existingIdentityKey = persisted?.identityKey || "";
2673
2689
  const result = await hubRequestJson(hubUrl, "", "/api/v1/hub/join", {
2674
2690
  method: "POST",
2675
- body: JSON.stringify({ teamToken, username, deviceName: hostname }),
2691
+ body: JSON.stringify({ teamToken, username, deviceName: hostname, identityKey: existingIdentityKey }),
2676
2692
  }) as any;
2693
+ const returnedIdentityKey = String(result.identityKey || existingIdentityKey || "");
2677
2694
  this.store.setClientHubConnection({
2678
2695
  hubUrl,
2679
2696
  userId: String(result.userId || ""),
@@ -2681,6 +2698,8 @@ export class ViewerServer {
2681
2698
  userToken: result.userToken || "",
2682
2699
  role: "member",
2683
2700
  connectedAt: Date.now(),
2701
+ identityKey: returnedIdentityKey,
2702
+ lastKnownStatus: result.status || "",
2684
2703
  });
2685
2704
  this.log.info(`Auto-join on save: status=${result.status}, userId=${result.userId}`);
2686
2705
  if (result.userToken) {
@@ -2768,10 +2787,10 @@ export class ViewerServer {
2768
2787
  this.log.warn(`Failed to update hub-auth.json: ${e}`);
2769
2788
  }
2770
2789
  } else {
2771
- const persisted = this.store.getClientHubConnection();
2772
- if (persisted) {
2790
+ const persistedConn = this.store.getClientHubConnection();
2791
+ if (persistedConn) {
2773
2792
  this.store.setClientHubConnection({
2774
- ...persisted,
2793
+ ...persistedConn,
2775
2794
  username: result.username,
2776
2795
  userToken: result.userToken,
2777
2796
  });
@@ -2798,7 +2817,8 @@ export class ViewerServer {
2798
2817
  const localIPs = this.getLocalIPs();
2799
2818
  localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
2800
2819
  const parsed = new URL(hubUrl.startsWith("http") ? hubUrl : `http://${hubUrl}`);
2801
- if (localIPs.includes(parsed.hostname)) {
2820
+ const targetPort = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
2821
+ if (localIPs.includes(parsed.hostname) && targetPort === String(this.port)) {
2802
2822
  this.jsonResponse(res, { ok: false, error: "cannot_join_self" });
2803
2823
  return;
2804
2824
  }