Skip to content

Commit 138fa18

Browse files
committed
Merge remote-tracking branch 'origin/native_memos' into native_memos
2 parents 1afd3f5 + 1e5344a commit 138fa18

9 files changed

Lines changed: 191 additions & 31 deletions

File tree

.github/workflows/openclaw-plugin-publish.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
include:
2626
- os: macos-14
2727
platform: darwin-arm64
28-
- os: macos-13
28+
- os: macos-15
2929
platform: darwin-x64
3030
- os: ubuntu-latest
3131
platform: linux-x64
@@ -42,6 +42,11 @@ jobs:
4242
- name: Install dependencies
4343
run: npm install
4444

45+
- name: Rebuild for x64 under Rosetta (darwin-x64 only)
46+
if: matrix.platform == 'darwin-x64'
47+
run: |
48+
arch -x86_64 npm rebuild better-sqlite3
49+
4550
- name: Collect prebuild
4651
shell: bash
4752
run: |

apps/memos-local-openclaw/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ const memosLocalPlugin = {
225225
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf-8"));
226226
pluginVersion = pkg.version ?? pluginVersion;
227227
} catch {}
228-
const telemetry = new Telemetry(ctx.config.telemetry ?? {}, stateDir, pluginVersion, ctx.log);
228+
const telemetry = new Telemetry(ctx.config.telemetry ?? {}, stateDir, pluginVersion, ctx.log, pluginDir);
229229

230230
// Install bundled memory-guide skill so OpenClaw loads it (write from embedded content so it works regardless of deploy layout)
231231
const workspaceSkillsDir = path.join(workspaceDir, "skills");
@@ -388,8 +388,7 @@ const memosLocalPlugin = {
388388

389389
const memoryId = response?.memoryId ?? `${chunk.id}-hub`;
390390

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.
393392
if (ctx.config.sharing?.role === "hub") {
394393
const now = Date.now();
395394
const existing = store.getHubMemoryBySource(hubClient.userId, chunk.id);
@@ -406,6 +405,8 @@ const memosLocalPlugin = {
406405
createdAt: existing?.createdAt ?? now,
407406
updatedAt: now,
408407
});
408+
} else if (ctx.config.sharing?.enabled && hubClient.userId) {
409+
store.upsertTeamSharedChunk(chunk.id, { hubMemoryId: memoryId, visibility, groupId });
409410
}
410411

411412
return { memoryId, visibility, groupId };
@@ -425,6 +426,7 @@ const memosLocalPlugin = {
425426
body: JSON.stringify({ sourceChunkId: chunk.id }),
426427
});
427428
store.deleteHubMemoryBySource(hubClient.userId, chunk.id);
429+
store.deleteTeamSharedChunk(chunk.id);
428430
};
429431

430432
// ─── Tool: memory_search ───

apps/memos-local-openclaw/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@memtensor/memos-local-openclaw-plugin",
3-
"version": "1.0.4-beta.14",
3+
"version": "1.0.5",
44
"description": "MemOS Local memory plugin for OpenClaw — full-write, hybrid-recall, progressive retrieval",
55
"type": "module",
66
"main": "index.ts",

apps/memos-local-openclaw/src/capture/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,11 @@ export function stripInboundMetadata(text: string): string {
167167
/** Strip <think…>…</think> blocks emitted by DeepSeek-style reasoning models. */
168168
const THINKING_TAG_RE = /<think[\s>][\s\S]*?<\/think>\s*/gi;
169169

170+
/** Unwrap <final>…</final> tags from MiniMax-style models (keep content, strip tags). */
171+
const FINAL_TAG_RE = /<\/?final\s*>/gi;
172+
170173
function stripThinkingTags(text: string): string {
171-
return text.replace(THINKING_TAG_RE, "");
174+
return text.replace(THINKING_TAG_RE, "").replace(FINAL_TAG_RE, "").trim();
172175
}
173176

174177
function extractEnvelopeTimestamp(text: string): number | null {

apps/memos-local-openclaw/src/hub/server.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,10 @@ export class HubServer {
414414
ttlMs,
415415
);
416416
this.userManager.approveUser(updated.id, newToken);
417+
if (updated.id === this.authState.bootstrapAdminUserId) {
418+
this.authState.bootstrapAdminToken = newToken;
419+
this.saveAuthState();
420+
}
417421
this.opts.log.info(`Hub: user "${auth.userId}" renamed to "${newUsername}"`);
418422
return this.json(res, 200, { ok: true, username: newUsername, userToken: newToken });
419423
}
@@ -522,6 +526,10 @@ export class HubServer {
522526
const updated = this.opts.store.getHubUser(userId)!;
523527
const finalUser = { ...updated, username: newUsername };
524528
this.opts.store.upsertHubUser(finalUser);
529+
if (userId === this.authState.bootstrapAdminUserId) {
530+
this.authState.bootstrapAdminToken = newToken;
531+
this.saveAuthState();
532+
}
525533
this.opts.log.info(`Hub: admin "${auth.userId}" renamed user "${userId}" to "${newUsername}"`);
526534
return this.json(res, 200, { ok: true, username: newUsername });
527535
}
@@ -942,7 +950,18 @@ export class HubServer {
942950
const deleted = this.opts.store.deleteHubMemoryById(memoryId);
943951
if (!deleted) return this.json(res, 404, { error: "not_found" });
944952
if (memInfo) {
945-
this.opts.store.insertHubNotification({ id: randomUUID(), userId: memInfo.sourceUserId, type: "resource_removed", resource: "memory", title: memInfo.summary || memInfo.id });
953+
const payload = JSON.stringify({
954+
memoryId,
955+
sourceChunkId: memInfo.sourceChunkId,
956+
});
957+
this.opts.store.insertHubNotification({
958+
id: randomUUID(),
959+
userId: memInfo.sourceUserId,
960+
type: "resource_removed",
961+
resource: "memory",
962+
title: memInfo.summary || memInfo.id,
963+
message: payload,
964+
});
946965
}
947966
return this.json(res, 200, { ok: true });
948967
}

apps/memos-local-openclaw/src/storage/sqlite.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,15 @@ export class SqliteStore {
792792
shared_at INTEGER NOT NULL
793793
);
794794
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+
795804
CREATE TABLE IF NOT EXISTS hub_users (
796805
id TEXT PRIMARY KEY,
797806
username TEXT NOT NULL UNIQUE,
@@ -1369,6 +1378,7 @@ export class SqliteStore {
13691378
"skill_versions",
13701379
"skills",
13711380
"local_shared_memories",
1381+
"team_shared_chunks",
13721382
"local_shared_tasks",
13731383
"embeddings",
13741384
"chunks",
@@ -2355,6 +2365,45 @@ export class SqliteStore {
23552365
return info.changes > 0;
23562366
}
23572367

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+
23582407
// ─── Hub Notifications ───
23592408

23602409
insertHubNotification(n: { id: string; userId: string; type: string; resource: string; title: string; message?: string }): void {

apps/memos-local-openclaw/src/telemetry.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,27 @@ export interface TelemetryConfig {
1818
enabled?: boolean;
1919
}
2020

21-
function loadTelemetryCredentials(): { endpoint: string; pid: string; env: string } {
21+
function loadTelemetryCredentials(pluginDir?: string): { endpoint: string; pid: string; env: string } {
2222
if (process.env.MEMOS_ARMS_ENDPOINT) {
2323
return {
2424
endpoint: process.env.MEMOS_ARMS_ENDPOINT,
2525
pid: process.env.MEMOS_ARMS_PID ?? "",
2626
env: process.env.MEMOS_ARMS_ENV ?? "prod",
2727
};
2828
}
29-
try {
30-
const credPath = path.resolve(__dirname, "..", "telemetry.credentials.json");
31-
const raw = fs.readFileSync(credPath, "utf-8");
32-
const creds = JSON.parse(raw);
33-
if (creds.endpoint) return { endpoint: creds.endpoint, pid: creds.pid ?? "", env: creds.env ?? "prod" };
34-
} catch {}
29+
const bases = pluginDir ? [pluginDir, path.join(pluginDir, "src")] : [];
30+
if (typeof __dirname === "string") bases.push(path.resolve(__dirname, ".."), __dirname);
31+
const candidates = bases.map(b => path.join(b, "telemetry.credentials.json"));
32+
for (const credPath of candidates) {
33+
try {
34+
const raw = fs.readFileSync(credPath, "utf-8");
35+
const creds = JSON.parse(raw);
36+
if (creds.endpoint) return { endpoint: creds.endpoint, pid: creds.pid ?? "", env: creds.env ?? "prod" };
37+
} catch {}
38+
}
3539
return { endpoint: "", pid: "", env: "prod" };
3640
}
3741

38-
const _creds = loadTelemetryCredentials();
39-
const ARMS_ENDPOINT = _creds.endpoint;
40-
const ARMS_PID = _creds.pid;
41-
const ARMS_ENV = _creds.env;
42-
4342
const FLUSH_AT = 10;
4443
const FLUSH_INTERVAL_MS = 30_000;
4544
const SEND_TIMEOUT_MS = 30_000;
@@ -67,19 +66,27 @@ export class Telemetry {
6766
private flushTimer: ReturnType<typeof setInterval> | null = null;
6867
private sessionId: string;
6968
private firstSeenDate: string;
69+
private armsEndpoint: string;
70+
private armsPid: string;
71+
private armsEnv: string;
7072

71-
constructor(config: TelemetryConfig, stateDir: string, pluginVersion: string, log: Logger) {
73+
constructor(config: TelemetryConfig, stateDir: string, pluginVersion: string, log: Logger, pluginDir?: string) {
7274
this.log = log;
7375
this.pluginVersion = pluginVersion;
7476
this.enabled = config.enabled !== false;
7577
this.distinctId = this.loadOrCreateAnonymousId(stateDir);
7678
this.firstSeenDate = this.loadOrCreateFirstSeen(stateDir);
7779
this.sessionId = this.loadOrCreateSessionId(stateDir);
7880

79-
if (!this.enabled || !ARMS_ENDPOINT) {
81+
const creds = loadTelemetryCredentials(pluginDir);
82+
this.armsEndpoint = creds.endpoint;
83+
this.armsPid = creds.pid;
84+
this.armsEnv = creds.env;
85+
86+
if (!this.enabled || !this.armsEndpoint) {
8087
this.enabled = false;
8188
this.log.debug(
82-
!ARMS_ENDPOINT
89+
!this.armsEndpoint
8390
? "Telemetry disabled (no credentials configured)"
8491
: "Telemetry disabled (opt-out)",
8592
);
@@ -192,8 +199,8 @@ export class Telemetry {
192199
private buildPayload(events: ArmsEvent[]): Record<string, unknown> {
193200
return {
194201
app: {
195-
id: ARMS_PID,
196-
env: ARMS_ENV,
202+
id: this.armsPid,
203+
env: this.armsEnv,
197204
version: this.pluginVersion,
198205
type: "node",
199206
},
@@ -212,7 +219,7 @@ export class Telemetry {
212219
const payload = this.buildPayload(batch);
213220

214221
try {
215-
const resp = await fetch(ARMS_ENDPOINT, {
222+
const resp = await fetch(this.armsEndpoint, {
216223
method: "POST",
217224
headers: { "Content-Type": "text/plain" },
218225
body: JSON.stringify(payload),

apps/memos-local-openclaw/src/viewer/html.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
287287
.admin-card-tag.tag-version{background:rgba(139,92,246,.1);color:#8b5cf6}
288288
.admin-card-tag.tag-visibility{background:rgba(99,102,241,.08);color:var(--pri)}
289289
.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%)}
291291
.admin-card-actions{display:inline-flex;gap:6px;margin-left:auto;align-items:center;flex-shrink:0}
292292
.admin-card-time{font-size:11px;color:var(--text-muted)}
293293
.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}
322322
.adm-msg-side.assistant .adm-msg-role{color:var(--green)}
323323
.adm-msg-time{font-size:9px;color:var(--text-muted)}
324324
.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)}
326326
.adm-msg-toggle{display:none;padding:0 16px 8px;font-size:11px;color:var(--pri);cursor:pointer;transition:color .15s}
327327
.adm-msg-toggle:hover{color:var(--pri-dark)}
328328
.admin-card-expand-btn{font-size:12px;color:var(--pri);cursor:pointer;background:none;border:none;padding:2px 6px;font-family:inherit}
@@ -7419,6 +7419,9 @@ function connectNotifSSE(){
74197419
_notifUnread=d.unreadCount||0;
74207420
renderNotifBadge();
74217421
if(_notifUnread>prev&&_notifPanelOpen) loadNotifications();
7422+
if(_notifUnread>prev&&_activeView==='memories'&&memorySearchScope!=='hub'){
7423+
syncTeamShareRemovedFromNotifications().then(function(){ loadMemories(currentPage,true); });
7424+
}
74227425
}
74237426
if(d.type==='cleared'){
74247427
_notifUnread=0;_notifCache=[];
@@ -7764,11 +7767,31 @@ function getFilterParams(){
77647767
return p;
77657768
}
77667769

7770+
/** Hub admin removed a shared memory — badge-only: clear team_shared_chunks (never touches chunks/embeddings/hub_memories recall data). */
7771+
async function syncTeamShareRemovedFromNotifications(){
7772+
try{
7773+
var r=await fetch('/api/sharing/notifications');
7774+
var d=await r.json();
7775+
var list=d.notifications||[];
7776+
for(var i=0;i<list.length;i++){
7777+
var n=list[i];
7778+
if(n.type!=='resource_removed'||n.resource!=='memory'||!n.message) continue;
7779+
try{
7780+
var meta=JSON.parse(n.message);
7781+
if(meta.sourceChunkId){
7782+
await fetch('/api/sharing/sync-hub-removal',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({sourceChunkId:meta.sourceChunkId,memoryId:meta.memoryId||''})});
7783+
}
7784+
}catch(e){}
7785+
}
7786+
}catch(e){}
7787+
}
7788+
77677789
async function loadMemories(page,silent){
77687790
if(page) currentPage=page;
77697791
const list=document.getElementById('memoryList');
77707792
if(!silent) list.innerHTML='<div class="spinner"></div>';
77717793
try{
7794+
if(!silent) await syncTeamShareRemovedFromNotifications();
77727795
const p=getFilterParams();
77737796
p.set('limit',PAGE_SIZE);
77747797
p.set('page',currentPage);

0 commit comments

Comments
 (0)