@memtensor/memos-local-openclaw-plugin 1.0.4-beta.8 → 1.0.4-beta.9

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.
@@ -667,6 +667,12 @@ export class ViewerServer {
667
667
  owners = ownerRows.map((o: any) => o.owner);
668
668
  } catch { /* column may not exist yet */ }
669
669
 
670
+ let currentAgentOwner = "agent:main";
671
+ try {
672
+ const latest = db.prepare("SELECT owner FROM chunks WHERE owner IS NOT NULL AND owner LIKE 'agent:%' ORDER BY created_at DESC LIMIT 1").get() as any;
673
+ if (latest?.owner) currentAgentOwner = latest.owner;
674
+ } catch { /* best-effort */ }
675
+
670
676
  this.jsonResponse(res, {
671
677
  totalMemories: total.count, totalSessions: sessions.count, totalEmbeddings: embCount,
672
678
  totalSkills: skillCount,
@@ -675,6 +681,7 @@ export class ViewerServer {
675
681
  timeRange: { earliest: timeRange.earliest, latest: timeRange.latest },
676
682
  sessions: sessionList,
677
683
  owners,
684
+ currentAgentOwner,
678
685
  });
679
686
  } catch (e) {
680
687
  this.log.warn(`stats error: ${e}`);
@@ -1742,13 +1749,21 @@ export class ViewerServer {
1742
1749
  }
1743
1750
  try {
1744
1751
  const hubUrl = normalizeHubUrl(hubAddress);
1752
+ const localIPs = this.getLocalIPs();
1753
+ localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
1754
+ try {
1755
+ const u = new URL(hubUrl);
1756
+ if (localIPs.includes(u.hostname)) {
1757
+ return this.jsonResponse(res, { ok: false, error: "cannot_join_self" });
1758
+ }
1759
+ } catch {}
1745
1760
  const os = await import("os");
1746
1761
  const nickname = sharing.client?.nickname;
1747
1762
  const username = nickname || os.userInfo().username || "user";
1748
1763
  const hostname = os.hostname() || "unknown";
1749
1764
  const result = await hubRequestJson(hubUrl, "", "/api/v1/hub/join", {
1750
1765
  method: "POST",
1751
- body: JSON.stringify({ teamToken, username, deviceName: hostname }),
1766
+ body: JSON.stringify({ teamToken, username, deviceName: hostname, reapply: true }),
1752
1767
  }) as any;
1753
1768
  this.store.setClientHubConnection({
1754
1769
  hubUrl,
@@ -2526,7 +2541,10 @@ export class ViewerServer {
2526
2541
  if (!entry.config) entry.config = {};
2527
2542
  const config = entry.config as Record<string, unknown>;
2528
2543
 
2529
- const oldSharingRole = (config.sharing as Record<string, unknown>)?.role as string | undefined;
2544
+ const oldSharing = config.sharing as Record<string, unknown> | undefined;
2545
+ const oldSharingRole = oldSharing?.role as string | undefined;
2546
+ const oldSharingEnabled = Boolean(oldSharing?.enabled);
2547
+ const oldClientHubAddress = String((oldSharing?.client as Record<string, unknown>)?.hubAddress || "");
2530
2548
 
2531
2549
  if (newCfg.embedding) config.embedding = newCfg.embedding;
2532
2550
  if (newCfg.summarizer) config.summarizer = newCfg.summarizer;
@@ -2556,10 +2574,35 @@ export class ViewerServer {
2556
2574
  }
2557
2575
  }
2558
2576
 
2559
- // When switching away from client mode, notify Hub that we're leaving
2560
2577
  const newRole = merged.role as string | undefined;
2561
- if (oldSharingRole === "client" && newRole !== "client") {
2578
+ const newEnabled = Boolean(merged.enabled);
2579
+
2580
+ // Detect disabling sharing or switching away from hub mode
2581
+ const wasHub = oldSharingEnabled && oldSharingRole === "hub";
2582
+ const isHub = newEnabled && newRole === "hub";
2583
+ if (wasHub && !isHub) {
2584
+ await this.notifyHubShutdown();
2585
+ this.stopHubHeartbeat();
2586
+ this.log.info("Hub shutting down: notified connected clients");
2587
+ }
2588
+
2589
+ // Detect disabling sharing or switching away from client mode
2590
+ const wasClient = oldSharingEnabled && oldSharingRole === "client";
2591
+ const isClient = newEnabled && newRole === "client";
2592
+ if (wasClient && !isClient) {
2562
2593
  this.notifyHubLeave();
2594
+ this.store.clearClientHubConnection();
2595
+ this.log.info("Cleared client hub connection (sharing disabled or role changed)");
2596
+ }
2597
+
2598
+ // Detect switching to a different Hub while still in client mode
2599
+ if (wasClient && isClient) {
2600
+ const newClientAddr = String((merged.client as Record<string, unknown>)?.hubAddress || "");
2601
+ if (newClientAddr && oldClientHubAddress && normalizeHubUrl(newClientAddr) !== normalizeHubUrl(oldClientHubAddress)) {
2602
+ this.notifyHubLeave();
2603
+ this.store.clearClientHubConnection();
2604
+ this.log.info("Cleared client hub connection (switched to different Hub)");
2605
+ }
2563
2606
  }
2564
2607
 
2565
2608
  if (merged.role === "hub") {
@@ -2574,6 +2617,13 @@ export class ViewerServer {
2574
2617
  fs.writeFileSync(cfgPath, JSON.stringify(raw, null, 2), "utf-8");
2575
2618
  this.log.info("Plugin config updated via Viewer");
2576
2619
  this.stopHubHeartbeat();
2620
+
2621
+ // When switching to client mode, immediately send join request
2622
+ const finalSharing = config.sharing as Record<string, unknown> | undefined;
2623
+ if (finalSharing?.role === "client" && oldSharingRole !== "client") {
2624
+ this.autoJoinOnSave(finalSharing).catch(e => this.log.warn(`Auto-join on save failed: ${e}`));
2625
+ }
2626
+
2577
2627
  this.jsonResponse(res, { ok: true });
2578
2628
  } catch (e) {
2579
2629
  this.log.warn(`handleSaveConfig error: ${e}`);
@@ -2583,6 +2633,34 @@ export class ViewerServer {
2583
2633
  });
2584
2634
  }
2585
2635
 
2636
+ private async autoJoinOnSave(sharing: Record<string, unknown>): Promise<void> {
2637
+ const clientCfg = sharing.client as Record<string, unknown> | undefined;
2638
+ const hubAddress = String(clientCfg?.hubAddress || "");
2639
+ const teamToken = String(clientCfg?.teamToken || "");
2640
+ if (!hubAddress || !teamToken) return;
2641
+ const hubUrl = normalizeHubUrl(hubAddress);
2642
+ const os = await import("os");
2643
+ const nickname = String(clientCfg?.nickname || "");
2644
+ const username = nickname || os.userInfo().username || "user";
2645
+ const hostname = os.hostname() || "unknown";
2646
+ const result = await hubRequestJson(hubUrl, "", "/api/v1/hub/join", {
2647
+ method: "POST",
2648
+ body: JSON.stringify({ teamToken, username, deviceName: hostname }),
2649
+ }) as any;
2650
+ this.store.setClientHubConnection({
2651
+ hubUrl,
2652
+ userId: String(result.userId || ""),
2653
+ username,
2654
+ userToken: result.userToken || "",
2655
+ role: "member",
2656
+ connectedAt: Date.now(),
2657
+ });
2658
+ this.log.info(`Auto-join on save: status=${result.status}, userId=${result.userId}`);
2659
+ if (result.userToken) {
2660
+ this.startHubHeartbeat();
2661
+ }
2662
+ }
2663
+
2586
2664
  private async notifyHubLeave(): Promise<void> {
2587
2665
  try {
2588
2666
  const hub = this.resolveHubConnection();
@@ -2601,6 +2679,41 @@ export class ViewerServer {
2601
2679
  }
2602
2680
  }
2603
2681
 
2682
+ private async notifyHubShutdown(): Promise<void> {
2683
+ try {
2684
+ const sharing = this.ctx?.config.sharing;
2685
+ if (!sharing || sharing.role !== "hub") return;
2686
+ const hubPort = sharing.hub?.port ?? 18800;
2687
+ const authPath = path.join(this.dataDir, "hub-auth.json");
2688
+ let adminToken: string | undefined;
2689
+ try {
2690
+ const authData = JSON.parse(fs.readFileSync(authPath, "utf8"));
2691
+ adminToken = authData?.bootstrapAdminToken;
2692
+ } catch { return; }
2693
+ if (!adminToken) return;
2694
+
2695
+ const users = this.store.listHubUsers("active");
2696
+ const { v4: uuidv4 } = require("uuid");
2697
+ for (const u of users) {
2698
+ try {
2699
+ this.store.insertHubNotification({
2700
+ id: uuidv4(),
2701
+ userId: u.id,
2702
+ type: "hub_shutdown",
2703
+ resource: "hub",
2704
+ title: "Hub is shutting down",
2705
+ message: "The Hub server is shutting down. You may be disconnected.",
2706
+ });
2707
+ } catch (e) {
2708
+ this.log.warn(`Failed to insert shutdown notification for user ${u.id}: ${e}`);
2709
+ }
2710
+ }
2711
+ this.log.info(`Hub shutdown: notified ${users.length} approved user(s)`);
2712
+ } catch (e) {
2713
+ this.log.warn(`notifyHubShutdown error: ${e}`);
2714
+ }
2715
+ }
2716
+
2604
2717
  private handleUpdateUsername(req: http.IncomingMessage, res: http.ServerResponse): void {
2605
2718
  this.readBody(req, async (body) => {
2606
2719
  if (!this.ctx) return this.jsonResponse(res, { error: "sharing_unavailable" });