@memtensor/memos-local-openclaw-plugin 1.0.4-beta.0 → 1.0.4-beta.2

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.
@@ -251,6 +251,7 @@ export class ViewerServer {
251
251
  else if (p === "/api/sharing/pending-users" && req.method === "GET") this.serveSharingPendingUsers(res);
252
252
  else if (p === "/api/sharing/approve-user" && req.method === "POST") this.handleSharingApproveUser(req, res);
253
253
  else if (p === "/api/sharing/reject-user" && req.method === "POST") this.handleSharingRejectUser(req, res);
254
+ else if (p === "/api/sharing/retry-join" && req.method === "POST") this.handleRetryJoin(req, res);
254
255
  else if (p === "/api/sharing/search/memories" && req.method === "POST") this.handleSharingMemorySearch(req, res);
255
256
  else if (p === "/api/sharing/memories/list" && req.method === "GET") this.serveSharingMemoryList(res, url);
256
257
  else if (p === "/api/sharing/tasks/list" && req.method === "GET") this.serveSharingTaskList(res, url);
@@ -1185,7 +1186,8 @@ export class ViewerServer {
1185
1186
  return;
1186
1187
  }
1187
1188
 
1188
- if (!hasClientConfig) {
1189
+ const hasPendingConnection = Boolean(persisted?.hubUrl && persisted?.userId && !persisted?.userToken);
1190
+ if (!hasClientConfig && !hasPendingConnection) {
1189
1191
  this.jsonResponse(res, base);
1190
1192
  return;
1191
1193
  }
@@ -1193,12 +1195,23 @@ export class ViewerServer {
1193
1195
  try {
1194
1196
  const status = await getHubStatus(this.store, this.ctx.config);
1195
1197
  const output = { ...base, connection: { ...base.connection, ...status } } as any;
1198
+ if (status.user?.status === "pending") {
1199
+ output.connection.pendingApproval = true;
1200
+ }
1201
+ if (status.user?.status === "rejected") {
1202
+ output.connection.rejected = true;
1203
+ }
1196
1204
  if (status.connected && status.hubUrl) {
1197
1205
  try {
1198
1206
  const info = await fetch(`${status.hubUrl}/api/v1/hub/info`).then((r) => (r.ok ? r.json() : null)).catch(() => null) as any;
1199
1207
  output.connection.teamName = info?.teamName ?? null;
1200
1208
  output.connection.apiVersion = info?.apiVersion ?? null;
1201
1209
  } catch {}
1210
+ } else if (status.hubUrl) {
1211
+ try {
1212
+ const info = await fetch(`${status.hubUrl}/api/v1/hub/info`).then((r) => (r.ok ? r.json() : null)).catch(() => null) as any;
1213
+ output.connection.teamName = info?.teamName ?? null;
1214
+ } catch {}
1202
1215
  }
1203
1216
  output.admin.canManageUsers = status.connected && status.user?.role === "admin";
1204
1217
  output.admin.rejectSupported = output.admin.canManageUsers;
@@ -1256,6 +1269,42 @@ export class ViewerServer {
1256
1269
  });
1257
1270
  }
1258
1271
 
1272
+ private handleRetryJoin(req: http.IncomingMessage, res: http.ServerResponse): void {
1273
+ this.readBody(req, async (_body) => {
1274
+ if (!this.ctx) return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
1275
+ const sharing = this.ctx.config.sharing;
1276
+ if (!sharing?.enabled || sharing.role !== "client") {
1277
+ return this.jsonResponse(res, { ok: false, error: "not_in_client_mode" });
1278
+ }
1279
+ const hubAddress = sharing.client?.hubAddress ?? "";
1280
+ const teamToken = sharing.client?.teamToken ?? "";
1281
+ if (!hubAddress || !teamToken) {
1282
+ return this.jsonResponse(res, { ok: false, error: "missing_hub_address_or_team_token" });
1283
+ }
1284
+ try {
1285
+ const hubUrl = normalizeHubUrl(hubAddress);
1286
+ const os = await import("os");
1287
+ const username = os.userInfo().username || "user";
1288
+ const hostname = os.hostname() || "unknown";
1289
+ const result = await hubRequestJson(hubUrl, "", "/api/v1/hub/join", {
1290
+ method: "POST",
1291
+ body: JSON.stringify({ teamToken, username, deviceName: hostname }),
1292
+ }) as any;
1293
+ this.store.setClientHubConnection({
1294
+ hubUrl,
1295
+ userId: String(result.userId || ""),
1296
+ username,
1297
+ userToken: result.userToken || "",
1298
+ role: "member",
1299
+ connectedAt: Date.now(),
1300
+ });
1301
+ this.jsonResponse(res, { ok: true, status: result.status || "pending" });
1302
+ } catch (err) {
1303
+ this.jsonResponse(res, { ok: false, error: String(err) });
1304
+ }
1305
+ });
1306
+ }
1307
+
1259
1308
  private async serveSharingMemoryList(res: http.ServerResponse, url: URL): Promise<void> {
1260
1309
  if (!this.ctx) return this.jsonResponse(res, { memories: [], error: "sharing_unavailable" });
1261
1310
  try {
@@ -1395,8 +1444,8 @@ export class ViewerServer {
1395
1444
  try {
1396
1445
  const parsed = JSON.parse(body || "{}");
1397
1446
  const taskId = String(parsed.taskId || "");
1398
- const visibility = parsed.visibility === "group" ? "group" : "public";
1399
- const groupId = typeof parsed.groupId === "string" ? parsed.groupId : undefined;
1447
+ const visibility = "public";
1448
+ const groupId: string | undefined = undefined;
1400
1449
  const task = this.store.getTask(taskId);
1401
1450
  if (!task) return this.jsonResponse(res, { ok: false, error: "task_not_found" });
1402
1451
  const chunks = this.store.getChunksByTask(taskId);
@@ -1410,7 +1459,7 @@ export class ViewerServer {
1410
1459
  sourceTaskId: task.id,
1411
1460
  title: task.title,
1412
1461
  summary: task.summary,
1413
- groupId: visibility === "group" ? groupId ?? null : null,
1462
+ groupId: null,
1414
1463
  visibility,
1415
1464
  createdAt: task.startedAt ?? Date.now(),
1416
1465
  updatedAt: task.updatedAt ?? Date.now(),
@@ -1436,7 +1485,7 @@ export class ViewerServer {
1436
1485
  sourceUserId: hubUserId,
1437
1486
  title: task.title,
1438
1487
  summary: task.summary,
1439
- groupId: visibility === "group" ? groupId ?? null : null,
1488
+ groupId: null,
1440
1489
  visibility,
1441
1490
  createdAt: task.startedAt ?? Date.now(),
1442
1491
  updatedAt: task.updatedAt ?? Date.now(),
@@ -1477,8 +1526,8 @@ export class ViewerServer {
1477
1526
  try {
1478
1527
  const parsed = JSON.parse(body || "{}");
1479
1528
  const chunkId = String(parsed.chunkId || "");
1480
- const visibility = parsed.visibility === "group" ? "group" : "public";
1481
- const groupId = typeof parsed.groupId === "string" ? parsed.groupId : undefined;
1529
+ const visibility = "public";
1530
+ const groupId: string | undefined = undefined;
1482
1531
  const db = (this.store as any).db;
1483
1532
  const chunk = db.prepare("SELECT * FROM chunks WHERE id = ?").get(chunkId) as any;
1484
1533
  if (!chunk) return this.jsonResponse(res, { ok: false, error: "memory_not_found" });
@@ -1492,7 +1541,7 @@ export class ViewerServer {
1492
1541
  content: chunk.content,
1493
1542
  summary: chunk.summary,
1494
1543
  kind: chunk.kind,
1495
- groupId: visibility === "group" ? groupId ?? null : null,
1544
+ groupId: null,
1496
1545
  visibility,
1497
1546
  },
1498
1547
  }),
@@ -1509,7 +1558,7 @@ export class ViewerServer {
1509
1558
  content: chunk.content,
1510
1559
  summary: chunk.summary ?? "",
1511
1560
  kind: chunk.kind,
1512
- groupId: visibility === "group" ? groupId ?? null : null,
1561
+ groupId: null,
1513
1562
  visibility,
1514
1563
  createdAt: existing?.createdAt ?? now,
1515
1564
  updatedAt: now,
@@ -1563,8 +1612,8 @@ export class ViewerServer {
1563
1612
  try {
1564
1613
  const parsed = JSON.parse(body || "{}");
1565
1614
  const skillId = String(parsed.skillId || "");
1566
- const visibility = parsed.visibility === "group" ? "group" : "public";
1567
- const groupId = parsed.groupId ? String(parsed.groupId) : null;
1615
+ const visibility = "public";
1616
+ const groupId: string | null = null;
1568
1617
  const skill = this.store.getSkill(skillId);
1569
1618
  if (!skill) return this.jsonResponse(res, { ok: false, error: "skill_not_found" });
1570
1619
  const bundle = buildSkillBundleForHub(this.store, skillId);
@@ -1573,7 +1622,7 @@ export class ViewerServer {
1573
1622
  method: "POST",
1574
1623
  body: JSON.stringify({
1575
1624
  visibility,
1576
- groupId: visibility === "group" ? groupId : null,
1625
+ groupId: null,
1577
1626
  metadata: bundle.metadata,
1578
1627
  bundle: bundle.bundle,
1579
1628
  }),
@@ -1588,7 +1637,7 @@ export class ViewerServer {
1588
1637
  name: skill.name,
1589
1638
  description: skill.description,
1590
1639
  version: skill.version,
1591
- groupId: visibility === "group" ? groupId : null,
1640
+ groupId: null,
1592
1641
  visibility,
1593
1642
  bundle: JSON.stringify(bundle.bundle),
1594
1643
  qualityScore: skill.qualityScore,
@@ -1864,7 +1913,7 @@ export class ViewerServer {
1864
1913
  }
1865
1914
  }
1866
1915
 
1867
- private serveLocalIPs(res: http.ServerResponse): void {
1916
+ private getLocalIPs(): string[] {
1868
1917
  const nets = os.networkInterfaces();
1869
1918
  const ips: string[] = [];
1870
1919
  for (const name of Object.keys(nets)) {
@@ -1874,6 +1923,11 @@ export class ViewerServer {
1874
1923
  }
1875
1924
  }
1876
1925
  }
1926
+ return ips;
1927
+ }
1928
+
1929
+ private serveLocalIPs(res: http.ServerResponse): void {
1930
+ const ips = this.getLocalIPs();
1877
1931
  res.writeHead(200, { "Content-Type": "application/json" });
1878
1932
  res.end(JSON.stringify({ ips }));
1879
1933
  }
@@ -1942,10 +1996,30 @@ export class ViewerServer {
1942
1996
  if (newCfg.sharing !== undefined) {
1943
1997
  const existing = (config.sharing as Record<string, unknown>) || {};
1944
1998
  const merged = { ...existing, ...newCfg.sharing };
1945
- // Deep-merge capabilities so new keys don't wipe existing ones
1946
1999
  if (newCfg.sharing.capabilities && existing.capabilities) {
1947
2000
  merged.capabilities = { ...(existing.capabilities as Record<string, unknown>), ...newCfg.sharing.capabilities };
1948
2001
  }
2002
+ if (merged.role === "client" && merged.client) {
2003
+ const clientCfg = merged.client as Record<string, unknown>;
2004
+ const addr = String(clientCfg.hubAddress || "");
2005
+ if (addr) {
2006
+ const localIPs = this.getLocalIPs();
2007
+ localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
2008
+ try {
2009
+ const u = new URL(addr.startsWith("http") ? addr : `http://${addr}`);
2010
+ if (localIPs.includes(u.hostname)) {
2011
+ res.writeHead(400, { "Content-Type": "application/json" });
2012
+ res.end(JSON.stringify({ error: "cannot_join_self" }));
2013
+ return;
2014
+ }
2015
+ } catch {}
2016
+ }
2017
+ }
2018
+ if (merged.role === "hub") {
2019
+ merged.client = { hubAddress: "", userToken: "", teamToken: "" };
2020
+ } else if (merged.role === "client") {
2021
+ merged.hub = { port: 18800, teamName: "", teamToken: "" };
2022
+ }
1949
2023
  config.sharing = merged;
1950
2024
  }
1951
2025
 
@@ -2014,6 +2088,15 @@ export class ViewerServer {
2014
2088
  try {
2015
2089
  const { hubUrl } = JSON.parse(body);
2016
2090
  if (!hubUrl) { this.jsonResponse(res, { ok: false, error: "hubUrl is required" }); return; }
2091
+ try {
2092
+ const localIPs = this.getLocalIPs();
2093
+ localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
2094
+ const parsed = new URL(hubUrl.startsWith("http") ? hubUrl : `http://${hubUrl}`);
2095
+ if (localIPs.includes(parsed.hostname)) {
2096
+ this.jsonResponse(res, { ok: false, error: "cannot_join_self" });
2097
+ return;
2098
+ }
2099
+ } catch {}
2017
2100
  const url = hubUrl.replace(/\/+$/, "") + "/api/v1/hub/info";
2018
2101
  const ctrl = new AbortController();
2019
2102
  const timeout = setTimeout(() => ctrl.abort(), 8000);