@memtensor/memos-local-openclaw-plugin 1.0.4-beta.18 → 1.0.4-beta.19

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/index.ts CHANGED
@@ -388,8 +388,7 @@ const memosLocalPlugin = {
388
388
 
389
389
  const memoryId = response?.memoryId ?? `${chunk.id}-hub`;
390
390
 
391
- // Only persist hub_memories locally in Hub mode where this DB owns the data.
392
- // Client mode relies on the remote Hub for storage and search.
391
+ // Hub role: full hub_memories row for local recall/embeddings. Client: metadata only (team_shared_chunks) for UI.
393
392
  if (ctx.config.sharing?.role === "hub") {
394
393
  const now = Date.now();
395
394
  const existing = store.getHubMemoryBySource(hubClient.userId, chunk.id);
@@ -406,6 +405,8 @@ const memosLocalPlugin = {
406
405
  createdAt: existing?.createdAt ?? now,
407
406
  updatedAt: now,
408
407
  });
408
+ } else if (ctx.config.sharing?.enabled && hubClient.userId) {
409
+ store.upsertTeamSharedChunk(chunk.id, { hubMemoryId: memoryId, visibility, groupId });
409
410
  }
410
411
 
411
412
  return { memoryId, visibility, groupId };
@@ -425,6 +426,7 @@ const memosLocalPlugin = {
425
426
  body: JSON.stringify({ sourceChunkId: chunk.id }),
426
427
  });
427
428
  store.deleteHubMemoryBySource(hubClient.userId, chunk.id);
429
+ store.deleteTeamSharedChunk(chunk.id);
428
430
  };
429
431
 
430
432
  // ─── Tool: memory_search ───
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memtensor/memos-local-openclaw-plugin",
3
- "version": "1.0.4-beta.18",
3
+ "version": "1.0.4-beta.19",
4
4
  "description": "MemOS Local memory plugin for OpenClaw — full-write, hybrid-recall, progressive retrieval",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -167,8 +167,11 @@ export function stripInboundMetadata(text: string): string {
167
167
  /** Strip <think…>…</think> blocks emitted by DeepSeek-style reasoning models. */
168
168
  const THINKING_TAG_RE = /<think[\s>][\s\S]*?<\/think>\s*/gi;
169
169
 
170
+ /** Unwrap <final>…</final> tags from MiniMax-style models (keep content, strip tags). */
171
+ const FINAL_TAG_RE = /<\/?final\s*>/gi;
172
+
170
173
  function stripThinkingTags(text: string): string {
171
- return text.replace(THINKING_TAG_RE, "");
174
+ return text.replace(THINKING_TAG_RE, "").replace(FINAL_TAG_RE, "").trim();
172
175
  }
173
176
 
174
177
  function extractEnvelopeTimestamp(text: string): number | null {
package/src/hub/server.ts CHANGED
@@ -414,6 +414,10 @@ export class HubServer {
414
414
  ttlMs,
415
415
  );
416
416
  this.userManager.approveUser(updated.id, newToken);
417
+ if (updated.id === this.authState.bootstrapAdminUserId) {
418
+ this.authState.bootstrapAdminToken = newToken;
419
+ this.saveAuthState();
420
+ }
417
421
  this.opts.log.info(`Hub: user "${auth.userId}" renamed to "${newUsername}"`);
418
422
  return this.json(res, 200, { ok: true, username: newUsername, userToken: newToken });
419
423
  }
@@ -522,6 +526,10 @@ export class HubServer {
522
526
  const updated = this.opts.store.getHubUser(userId)!;
523
527
  const finalUser = { ...updated, username: newUsername };
524
528
  this.opts.store.upsertHubUser(finalUser);
529
+ if (userId === this.authState.bootstrapAdminUserId) {
530
+ this.authState.bootstrapAdminToken = newToken;
531
+ this.saveAuthState();
532
+ }
525
533
  this.opts.log.info(`Hub: admin "${auth.userId}" renamed user "${userId}" to "${newUsername}"`);
526
534
  return this.json(res, 200, { ok: true, username: newUsername });
527
535
  }
@@ -792,6 +792,15 @@ export class SqliteStore {
792
792
  shared_at INTEGER NOT NULL
793
793
  );
794
794
 
795
+ -- Client: team share UI metadata only (no hub_memories row — avoids local FTS/embed recall duplication)
796
+ CREATE TABLE IF NOT EXISTS team_shared_chunks (
797
+ chunk_id TEXT PRIMARY KEY REFERENCES chunks(id) ON DELETE CASCADE,
798
+ hub_memory_id TEXT NOT NULL DEFAULT '',
799
+ visibility TEXT NOT NULL DEFAULT 'public',
800
+ group_id TEXT,
801
+ shared_at INTEGER NOT NULL
802
+ );
803
+
795
804
  CREATE TABLE IF NOT EXISTS hub_users (
796
805
  id TEXT PRIMARY KEY,
797
806
  username TEXT NOT NULL UNIQUE,
@@ -1369,6 +1378,7 @@ export class SqliteStore {
1369
1378
  "skill_versions",
1370
1379
  "skills",
1371
1380
  "local_shared_memories",
1381
+ "team_shared_chunks",
1372
1382
  "local_shared_tasks",
1373
1383
  "embeddings",
1374
1384
  "chunks",
@@ -2355,6 +2365,45 @@ export class SqliteStore {
2355
2365
  return info.changes > 0;
2356
2366
  }
2357
2367
 
2368
+ // ─── Team share metadata (Client role — UI only, not used for local recall / FTS) ───
2369
+
2370
+ upsertTeamSharedChunk(
2371
+ chunkId: string,
2372
+ row: { hubMemoryId?: string; visibility?: string; groupId?: string | null },
2373
+ ): void {
2374
+ const now = Date.now();
2375
+ const vis = row.visibility === "group" ? "group" : "public";
2376
+ const gid = vis === "group" ? (row.groupId ?? null) : null;
2377
+ this.db.prepare(`
2378
+ INSERT INTO team_shared_chunks (chunk_id, hub_memory_id, visibility, group_id, shared_at)
2379
+ VALUES (?, ?, ?, ?, ?)
2380
+ ON CONFLICT(chunk_id) DO UPDATE SET
2381
+ hub_memory_id = excluded.hub_memory_id,
2382
+ visibility = excluded.visibility,
2383
+ group_id = excluded.group_id,
2384
+ shared_at = excluded.shared_at
2385
+ `).run(chunkId, row.hubMemoryId ?? "", vis, gid, now);
2386
+ }
2387
+
2388
+ getTeamSharedChunk(chunkId: string): { chunkId: string; hubMemoryId: string; visibility: string; groupId: string | null; sharedAt: number } | null {
2389
+ const r = this.db.prepare("SELECT chunk_id, hub_memory_id, visibility, group_id, shared_at FROM team_shared_chunks WHERE chunk_id = ?").get(chunkId) as {
2390
+ chunk_id: string; hub_memory_id: string; visibility: string; group_id: string | null; shared_at: number;
2391
+ } | undefined;
2392
+ if (!r) return null;
2393
+ return {
2394
+ chunkId: r.chunk_id,
2395
+ hubMemoryId: r.hub_memory_id,
2396
+ visibility: r.visibility,
2397
+ groupId: r.group_id,
2398
+ sharedAt: r.shared_at,
2399
+ };
2400
+ }
2401
+
2402
+ deleteTeamSharedChunk(chunkId: string): boolean {
2403
+ const info = this.db.prepare("DELETE FROM team_shared_chunks WHERE chunk_id = ?").run(chunkId);
2404
+ return info.changes > 0;
2405
+ }
2406
+
2358
2407
  // ─── Hub Notifications ───
2359
2408
 
2360
2409
  insertHubNotification(n: { id: string; userId: string; type: string; resource: string; title: string; message?: string }): void {
@@ -287,7 +287,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
287
287
  .admin-card-tag.tag-version{background:rgba(139,92,246,.1);color:#8b5cf6}
288
288
  .admin-card-tag.tag-visibility{background:rgba(99,102,241,.08);color:var(--pri)}
289
289
  .admin-card-tag.tag-group{background:rgba(139,92,246,.08);color:#8b5cf6}
290
- .admin-card-preview{font-size:12px;color:var(--text-sec);line-height:1.5;margin:8px 0;padding:10px 12px;background:rgba(99,102,241,.02);border-radius:10px;border:1px solid rgba(99,102,241,.08);max-height:120px;overflow:hidden;white-space:pre-wrap;word-break:break-all;position:relative;-webkit-mask-image:linear-gradient(to bottom,#000 70%,transparent 100%);mask-image:linear-gradient(to bottom,#000 70%,transparent 100%)}
290
+ .admin-card-preview{font-size:12px;color:var(--text-sec);line-height:1.5;margin:8px 0;padding:10px 12px;background:rgba(99,102,241,.02);border-radius:10px;border:1px solid rgba(99,102,241,.08);max-height:120px;overflow:hidden;white-space:pre-wrap;word-break:break-all;position:relative;-webkit-mask-image:linear-gradient(to bottom,#000 88%,transparent 100%);mask-image:linear-gradient(to bottom,#000 88%,transparent 100%)}
291
291
  .admin-card-actions{display:inline-flex;gap:6px;margin-left:auto;align-items:center;flex-shrink:0}
292
292
  .admin-card-time{font-size:11px;color:var(--text-muted)}
293
293
  .admin-card-detail{display:none;margin-top:0;padding:20px 24px 24px;border-top:1px dashed rgba(99,102,241,.12);background:linear-gradient(180deg,rgba(99,102,241,.02) 0%,transparent 60%);animation:adminDetailIn .25s ease}
@@ -322,7 +322,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
322
322
  .adm-msg-side.assistant .adm-msg-role{color:var(--green)}
323
323
  .adm-msg-time{font-size:9px;color:var(--text-muted)}
324
324
  .adm-msg-body{flex:1;min-width:0;padding:12px 16px;font-size:13px;line-height:1.75;color:var(--text);white-space:pre-wrap;word-break:break-word}
325
- .adm-msg-body.collapsed{max-height:120px;overflow:hidden;-webkit-mask-image:linear-gradient(180deg,#000 65%,transparent);mask-image:linear-gradient(180deg,#000 65%,transparent)}
325
+ .adm-msg-body.collapsed{max-height:120px;overflow:hidden;-webkit-mask-image:linear-gradient(180deg,#000 88%,transparent);mask-image:linear-gradient(180deg,#000 88%,transparent)}
326
326
  .adm-msg-toggle{display:none;padding:0 16px 8px;font-size:11px;color:var(--pri);cursor:pointer;transition:color .15s}
327
327
  .adm-msg-toggle:hover{color:var(--pri-dark)}
328
328
  .admin-card-expand-btn{font-size:12px;color:var(--pri);cursor:pointer;background:none;border:none;padding:2px 6px;font-family:inherit}
@@ -492,6 +492,12 @@ export class ViewerServer {
492
492
  const placeholders = chunkIds.map(() => "?").join(",");
493
493
  const sharedRows = db.prepare(`SELECT source_chunk_id, visibility, group_id FROM hub_memories WHERE source_chunk_id IN (${placeholders})`).all(...chunkIds) as Array<{ source_chunk_id: string; visibility: string; group_id: string | null }>;
494
494
  for (const r of sharedRows) sharingMap.set(r.source_chunk_id, r);
495
+ const teamMetaRows = db.prepare(`SELECT chunk_id, visibility, group_id FROM team_shared_chunks WHERE chunk_id IN (${placeholders})`).all(...chunkIds) as Array<{ chunk_id: string; visibility: string; group_id: string | null }>;
496
+ for (const r of teamMetaRows) {
497
+ if (!sharingMap.has(r.chunk_id)) {
498
+ sharingMap.set(r.chunk_id, { visibility: r.visibility, group_id: r.group_id });
499
+ }
500
+ }
495
501
  const localRows = db.prepare(`SELECT chunk_id, original_owner, shared_at FROM local_shared_memories WHERE chunk_id IN (${placeholders})`).all(...chunkIds) as Array<{ chunk_id: string; original_owner: string; shared_at: number }>;
496
502
  for (const r of localRows) localShareMap.set(r.chunk_id, r);
497
503
  } catch {
@@ -1252,15 +1258,19 @@ export class ViewerServer {
1252
1258
  body: JSON.stringify({ memory: { sourceChunkId: refreshedChunk.id, role: refreshedChunk.role, content: refreshedChunk.content, summary: refreshedChunk.summary, kind: refreshedChunk.kind, groupId: null, visibility: "public" } }),
1253
1259
  });
1254
1260
  if (!isLocalShared) this.store.markMemorySharedLocally(chunkId);
1255
- if (hubClient.userId && this.ctx?.config?.sharing?.role === "hub") {
1261
+ const memoryId = String((response as any)?.memoryId ?? "");
1262
+ const isHubRole = this.ctx?.config?.sharing?.role === "hub";
1263
+ if (hubClient.userId && isHubRole) {
1256
1264
  const existing = this.store.getHubMemoryBySource(hubClient.userId, chunkId);
1257
1265
  this.store.upsertHubMemory({
1258
- id: (response as any)?.memoryId ?? existing?.id ?? crypto.randomUUID(),
1266
+ id: memoryId || existing?.id || crypto.randomUUID(),
1259
1267
  sourceChunkId: chunkId, sourceUserId: hubClient.userId,
1260
1268
  role: refreshedChunk.role, content: refreshedChunk.content, summary: refreshedChunk.summary ?? "",
1261
1269
  kind: refreshedChunk.kind, groupId: null, visibility: "public",
1262
1270
  createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
1263
1271
  });
1272
+ } else if (hubClient.userId) {
1273
+ this.store.upsertTeamSharedChunk(chunkId, { hubMemoryId: memoryId, visibility: "public", groupId: null });
1264
1274
  }
1265
1275
  hubSynced = true;
1266
1276
  } else {
@@ -1274,6 +1284,7 @@ export class ViewerServer {
1274
1284
  method: "POST", body: JSON.stringify({ sourceChunkId: chunkId }),
1275
1285
  });
1276
1286
  if (hubClient.userId) this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
1287
+ this.store.deleteTeamSharedChunk(chunkId);
1277
1288
  hubSynced = true;
1278
1289
  } catch (err) { this.log.warn(`Failed to unshare memory from team: ${err}`); }
1279
1290
  }
@@ -1286,6 +1297,7 @@ export class ViewerServer {
1286
1297
  method: "POST", body: JSON.stringify({ sourceChunkId: chunkId }),
1287
1298
  });
1288
1299
  if (hubClient.userId) this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
1300
+ this.store.deleteTeamSharedChunk(chunkId);
1289
1301
  hubSynced = true;
1290
1302
  } catch (err) { this.log.warn(`Failed to unshare memory from team: ${err}`); }
1291
1303
  }
@@ -1495,7 +1507,17 @@ export class ViewerServer {
1495
1507
 
1496
1508
  private getHubMemoryForChunk(chunkId: string): any {
1497
1509
  const db = (this.store as any).db;
1498
- return db.prepare("SELECT * FROM hub_memories WHERE source_chunk_id = ? LIMIT 1").get(chunkId);
1510
+ const hub = db.prepare("SELECT * FROM hub_memories WHERE source_chunk_id = ? LIMIT 1").get(chunkId);
1511
+ if (hub) return hub;
1512
+ const ts = this.store.getTeamSharedChunk(chunkId);
1513
+ if (ts) {
1514
+ return {
1515
+ source_chunk_id: chunkId,
1516
+ visibility: ts.visibility,
1517
+ group_id: ts.groupId,
1518
+ };
1519
+ }
1520
+ return undefined;
1499
1521
  }
1500
1522
 
1501
1523
  private getHubTaskForLocal(taskId: string): any {
@@ -2105,11 +2127,12 @@ export class ViewerServer {
2105
2127
  },
2106
2128
  }),
2107
2129
  });
2130
+ const mid = String((response as any)?.memoryId ?? "");
2108
2131
  if (hubClient.userId && this.ctx?.config?.sharing?.role === "hub") {
2109
2132
  const now = Date.now();
2110
2133
  const existing = this.store.getHubMemoryBySource(hubClient.userId, chunk.id);
2111
2134
  this.store.upsertHubMemory({
2112
- id: (response as any)?.memoryId ?? existing?.id ?? crypto.randomUUID(),
2135
+ id: mid || existing?.id || crypto.randomUUID(),
2113
2136
  sourceChunkId: chunk.id,
2114
2137
  sourceUserId: hubClient.userId,
2115
2138
  role: chunk.role,
@@ -2121,6 +2144,8 @@ export class ViewerServer {
2121
2144
  createdAt: existing?.createdAt ?? now,
2122
2145
  updatedAt: now,
2123
2146
  });
2147
+ } else if (hubClient.userId) {
2148
+ this.store.upsertTeamSharedChunk(chunk.id, { hubMemoryId: mid, visibility, groupId });
2124
2149
  }
2125
2150
  this.jsonResponse(res, { ok: true, chunkId, visibility, response });
2126
2151
  } catch (err) {
@@ -2142,6 +2167,7 @@ export class ViewerServer {
2142
2167
  });
2143
2168
  const hubUserId = hubClient.userId;
2144
2169
  if (hubUserId) this.store.deleteHubMemoryBySource(hubUserId, chunkId);
2170
+ this.store.deleteTeamSharedChunk(chunkId);
2145
2171
  this.jsonResponse(res, { ok: true, chunkId });
2146
2172
  } catch (err) {
2147
2173
  this.jsonResponse(res, { ok: false, error: String(err) });