@memtensor/memos-local-openclaw-plugin 1.0.4-beta.11 → 1.0.4-beta.12
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/dist/client/connector.d.ts +4 -0
- package/dist/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +1 -0
- package/dist/client/connector.js.map +1 -1
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +21 -4
- package/dist/hub/server.js.map +1 -1
- package/dist/ingest/chunker.d.ts +2 -1
- package/dist/ingest/chunker.d.ts.map +1 -1
- package/dist/ingest/chunker.js +14 -10
- package/dist/ingest/chunker.js.map +1 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +5 -2
- package/dist/recall/engine.js.map +1 -1
- package/dist/storage/sqlite.d.ts +13 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +64 -7
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +20 -1
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +4 -5
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +22 -20
- package/package.json +1 -1
- package/src/client/connector.ts +2 -0
- package/src/hub/server.ts +19 -4
- package/src/ingest/chunker.ts +19 -13
- package/src/recall/engine.ts +5 -2
- package/src/storage/sqlite.ts +69 -7
- package/src/viewer/html.ts +20 -1
- package/src/viewer/server.ts +4 -5
package/index.ts
CHANGED
|
@@ -386,27 +386,29 @@ const memosLocalPlugin = {
|
|
|
386
386
|
}),
|
|
387
387
|
}) as { memoryId?: string; visibility?: "public" | "group" };
|
|
388
388
|
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
389
|
+
const memoryId = response?.memoryId ?? `${chunk.id}-hub`;
|
|
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.
|
|
393
|
+
if (ctx.config.sharing?.role === "hub") {
|
|
394
|
+
const now = Date.now();
|
|
395
|
+
const existing = store.getHubMemoryBySource(hubClient.userId, chunk.id);
|
|
396
|
+
store.upsertHubMemory({
|
|
397
|
+
id: memoryId,
|
|
398
|
+
sourceChunkId: chunk.id,
|
|
399
|
+
sourceUserId: hubClient.userId,
|
|
400
|
+
role: chunk.role,
|
|
401
|
+
content: chunk.content,
|
|
402
|
+
summary: chunk.summary ?? "",
|
|
403
|
+
kind: chunk.kind,
|
|
404
|
+
groupId,
|
|
405
|
+
visibility,
|
|
406
|
+
createdAt: existing?.createdAt ?? now,
|
|
407
|
+
updatedAt: now,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
404
410
|
|
|
405
|
-
return {
|
|
406
|
-
memoryId: response?.memoryId ?? existing?.id ?? `${chunk.id}-hub`,
|
|
407
|
-
visibility,
|
|
408
|
-
groupId,
|
|
409
|
-
};
|
|
411
|
+
return { memoryId, visibility, groupId };
|
|
410
412
|
};
|
|
411
413
|
|
|
412
414
|
const unshareMemoryFromHub = async (
|
package/package.json
CHANGED
package/src/client/connector.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface HubStatusInfo {
|
|
|
21
21
|
username: string;
|
|
22
22
|
role: UserRole;
|
|
23
23
|
status: UserStatus | string;
|
|
24
|
+
groups?: Array<{ id: string; name: string }>;
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -201,6 +202,7 @@ export async function getHubStatus(store: SqliteStore, config: MemosLocalConfig)
|
|
|
201
202
|
username: latestUsername,
|
|
202
203
|
role: latestRole,
|
|
203
204
|
status: String(me.status ?? "active"),
|
|
205
|
+
groups: Array.isArray(me.groups) ? me.groups : [],
|
|
204
206
|
},
|
|
205
207
|
};
|
|
206
208
|
} catch (err: any) {
|
package/src/hub/server.ts
CHANGED
|
@@ -377,18 +377,33 @@ export class HubServer {
|
|
|
377
377
|
if (req.method === "POST" && routePath === "/api/v1/hub/admin/approve-user") {
|
|
378
378
|
if (auth.role !== "admin") return this.json(res, 403, { error: "forbidden" });
|
|
379
379
|
const body = await this.readJson(req);
|
|
380
|
-
const
|
|
381
|
-
const
|
|
380
|
+
const userId = String(body.userId);
|
|
381
|
+
const username = String(body.username || "");
|
|
382
|
+
const token = issueUserToken({ userId, username, role: "member", status: "active" }, this.authSecret);
|
|
383
|
+
const approved = this.userManager.approveUser(userId, token);
|
|
382
384
|
if (!approved) return this.json(res, 404, { error: "not_found" });
|
|
383
|
-
try { this.opts.store.updateHubUserActivity(
|
|
385
|
+
try { this.opts.store.updateHubUserActivity(userId, ""); } catch { /* best-effort */ }
|
|
386
|
+
try {
|
|
387
|
+
this.opts.store.insertHubNotification({
|
|
388
|
+
id: randomUUID(), userId, type: "membership_approved",
|
|
389
|
+
resource: "user", title: `Your request to join team "${this.teamName}" has been approved. Welcome!`,
|
|
390
|
+
});
|
|
391
|
+
} catch { /* best-effort */ }
|
|
384
392
|
return this.json(res, 200, { status: "active", token });
|
|
385
393
|
}
|
|
386
394
|
|
|
387
395
|
if (req.method === "POST" && routePath === "/api/v1/hub/admin/reject-user") {
|
|
388
396
|
if (auth.role !== "admin") return this.json(res, 403, { error: "forbidden" });
|
|
389
397
|
const body = await this.readJson(req);
|
|
390
|
-
const
|
|
398
|
+
const userId = String(body.userId);
|
|
399
|
+
const rejected = this.userManager.rejectUser(userId);
|
|
391
400
|
if (!rejected) return this.json(res, 404, { error: "not_found" });
|
|
401
|
+
try {
|
|
402
|
+
this.opts.store.insertHubNotification({
|
|
403
|
+
id: randomUUID(), userId, type: "membership_rejected",
|
|
404
|
+
resource: "user", title: `Your request to join team "${this.teamName}" has been declined.`,
|
|
405
|
+
});
|
|
406
|
+
} catch { /* best-effort */ }
|
|
392
407
|
return this.json(res, 200, { status: "rejected" });
|
|
393
408
|
}
|
|
394
409
|
|
package/src/ingest/chunker.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
export type ChunkKind = "paragraph" | "code_block" | "error_stack" | "list" | "command";
|
|
2
|
+
|
|
1
3
|
export interface RawChunk {
|
|
2
4
|
content: string;
|
|
3
|
-
kind:
|
|
5
|
+
kind: ChunkKind;
|
|
4
6
|
}
|
|
5
7
|
|
|
6
8
|
const MAX_CHUNK_CHARS = 3000;
|
|
@@ -28,21 +30,25 @@ const COMMAND_LINE_RE = /^(?:\$|>|#)\s+.+$/gm;
|
|
|
28
30
|
*/
|
|
29
31
|
export function chunkText(text: string): RawChunk[] {
|
|
30
32
|
let remaining = text;
|
|
31
|
-
const slots: Array<{ placeholder: string; content: string }> = [];
|
|
33
|
+
const slots: Array<{ placeholder: string; content: string; kind: ChunkKind }> = [];
|
|
32
34
|
let counter = 0;
|
|
33
35
|
|
|
34
|
-
function ph(content: string): string {
|
|
36
|
+
function ph(content: string, kind: ChunkKind = "paragraph"): string {
|
|
35
37
|
const tag = `\x00SLOT_${counter++}\x00`;
|
|
36
|
-
slots.push({ placeholder: tag, content: content.trim() });
|
|
38
|
+
slots.push({ placeholder: tag, content: content.trim(), kind });
|
|
37
39
|
return tag;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
remaining = remaining.replace(FENCED_CODE_RE, (m) => ph(m));
|
|
42
|
+
remaining = remaining.replace(FENCED_CODE_RE, (m) => ph(m, "code_block"));
|
|
41
43
|
remaining = extractBraceBlocks(remaining, ph);
|
|
42
44
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
const structuralKinds: Array<[RegExp, ChunkKind]> = [
|
|
46
|
+
[ERROR_STACK_RE, "error_stack"],
|
|
47
|
+
[LIST_BLOCK_RE, "list"],
|
|
48
|
+
[COMMAND_LINE_RE, "command"],
|
|
49
|
+
];
|
|
50
|
+
for (const [re, kind] of structuralKinds) {
|
|
51
|
+
remaining = remaining.replace(re, (m) => ph(m, kind));
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
const raw: RawChunk[] = [];
|
|
@@ -57,7 +63,7 @@ export function chunkText(text: string): RawChunk[] {
|
|
|
57
63
|
for (const part of parts) {
|
|
58
64
|
const slot = slots.find((s) => s.placeholder === part);
|
|
59
65
|
if (slot) {
|
|
60
|
-
raw.push({ content: slot.content, kind:
|
|
66
|
+
raw.push({ content: slot.content, kind: slot.kind });
|
|
61
67
|
} else if (part.trim().length >= MIN_CHUNK_CHARS) {
|
|
62
68
|
raw.push({ content: part.trim(), kind: "paragraph" });
|
|
63
69
|
}
|
|
@@ -69,7 +75,7 @@ export function chunkText(text: string): RawChunk[] {
|
|
|
69
75
|
|
|
70
76
|
for (const s of slots) {
|
|
71
77
|
if (!raw.some((c) => c.content === s.content)) {
|
|
72
|
-
raw.push({ content: s.content, kind:
|
|
78
|
+
raw.push({ content: s.content, kind: s.kind });
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
81
|
|
|
@@ -85,7 +91,7 @@ export function chunkText(text: string): RawChunk[] {
|
|
|
85
91
|
*/
|
|
86
92
|
function extractBraceBlocks(
|
|
87
93
|
text: string,
|
|
88
|
-
ph: (content: string) => string,
|
|
94
|
+
ph: (content: string, kind?: ChunkKind) => string,
|
|
89
95
|
): string {
|
|
90
96
|
const lines = text.split("\n");
|
|
91
97
|
const result: string[] = [];
|
|
@@ -119,7 +125,7 @@ function extractBraceBlocks(
|
|
|
119
125
|
if (depth <= 0 || (BLOCK_CLOSE_RE.test(line) && depth <= 0)) {
|
|
120
126
|
const block = blockLines.join("\n");
|
|
121
127
|
if (block.trim().length >= MIN_CHUNK_CHARS) {
|
|
122
|
-
result.push(ph(block));
|
|
128
|
+
result.push(ph(block, "code_block"));
|
|
123
129
|
} else {
|
|
124
130
|
result.push(block);
|
|
125
131
|
}
|
|
@@ -135,7 +141,7 @@ function extractBraceBlocks(
|
|
|
135
141
|
if (blockLines.length > 0) {
|
|
136
142
|
const block = blockLines.join("\n");
|
|
137
143
|
if (block.trim().length >= MIN_CHUNK_CHARS) {
|
|
138
|
-
result.push(ph(block));
|
|
144
|
+
result.push(ph(block, "code_block"));
|
|
139
145
|
} else {
|
|
140
146
|
result.push(block);
|
|
141
147
|
}
|
package/src/recall/engine.ts
CHANGED
|
@@ -74,11 +74,14 @@ export class RecallEngine {
|
|
|
74
74
|
score: 1 / (i + 1),
|
|
75
75
|
}));
|
|
76
76
|
|
|
77
|
-
// Step 1c: Hub memories search
|
|
77
|
+
// Step 1c: Hub memories search — only in Hub mode where local DB owns the
|
|
78
|
+
// hub_memories data and embeddings were generated by the same Embedder.
|
|
79
|
+
// Client mode must use remote API (hubSearchMemories) to avoid cross-model
|
|
80
|
+
// embedding mismatch.
|
|
78
81
|
let hubMemFtsRanked: Array<{ id: string; score: number }> = [];
|
|
79
82
|
let hubMemVecRanked: Array<{ id: string; score: number }> = [];
|
|
80
83
|
let hubMemPatternRanked: Array<{ id: string; score: number }> = [];
|
|
81
|
-
if (query && this.ctx.config.sharing?.enabled) {
|
|
84
|
+
if (query && this.ctx.config.sharing?.enabled && this.ctx.config.sharing.role === "hub") {
|
|
82
85
|
try {
|
|
83
86
|
const hubFtsHits = this.store.searchHubMemories(query, { maxResults: candidatePool });
|
|
84
87
|
hubMemFtsRanked = hubFtsHits.map(({ hit }, i) => ({
|
package/src/storage/sqlite.ts
CHANGED
|
@@ -807,6 +807,20 @@ export class SqliteStore {
|
|
|
807
807
|
CREATE INDEX IF NOT EXISTS idx_hub_users_status ON hub_users(status);
|
|
808
808
|
CREATE INDEX IF NOT EXISTS idx_hub_users_role ON hub_users(role);
|
|
809
809
|
|
|
810
|
+
CREATE TABLE IF NOT EXISTS hub_groups (
|
|
811
|
+
id TEXT PRIMARY KEY,
|
|
812
|
+
name TEXT NOT NULL,
|
|
813
|
+
description TEXT NOT NULL DEFAULT '',
|
|
814
|
+
created_at INTEGER NOT NULL
|
|
815
|
+
);
|
|
816
|
+
|
|
817
|
+
CREATE TABLE IF NOT EXISTS hub_group_members (
|
|
818
|
+
group_id TEXT NOT NULL REFERENCES hub_groups(id) ON DELETE CASCADE,
|
|
819
|
+
user_id TEXT NOT NULL REFERENCES hub_users(id) ON DELETE CASCADE,
|
|
820
|
+
joined_at INTEGER NOT NULL,
|
|
821
|
+
PRIMARY KEY (group_id, user_id)
|
|
822
|
+
);
|
|
823
|
+
|
|
810
824
|
CREATE TABLE IF NOT EXISTS hub_tasks (
|
|
811
825
|
id TEXT PRIMARY KEY,
|
|
812
826
|
source_task_id TEXT NOT NULL,
|
|
@@ -1915,14 +1929,20 @@ export class SqliteStore {
|
|
|
1915
1929
|
getHubUser(userId: string): HubUserRecord | null {
|
|
1916
1930
|
const row = this.db.prepare('SELECT * FROM hub_users WHERE id = ?').get(userId) as HubUserRow | undefined;
|
|
1917
1931
|
if (!row) return null;
|
|
1918
|
-
|
|
1932
|
+
const user = rowToHubUser(row);
|
|
1933
|
+
user.groups = this.getGroupsForHubUser(userId);
|
|
1934
|
+
return user;
|
|
1919
1935
|
}
|
|
1920
1936
|
|
|
1921
1937
|
listHubUsers(status?: UserStatus): HubUserRecord[] {
|
|
1922
1938
|
const rows = status
|
|
1923
1939
|
? this.db.prepare('SELECT * FROM hub_users WHERE status = ? ORDER BY created_at').all(status) as HubUserRow[]
|
|
1924
1940
|
: this.db.prepare('SELECT * FROM hub_users ORDER BY created_at').all() as HubUserRow[];
|
|
1925
|
-
return rows.map(
|
|
1941
|
+
return rows.map(r => {
|
|
1942
|
+
const user = rowToHubUser(r);
|
|
1943
|
+
user.groups = this.getGroupsForHubUser(r.id);
|
|
1944
|
+
return user;
|
|
1945
|
+
});
|
|
1926
1946
|
}
|
|
1927
1947
|
|
|
1928
1948
|
deleteHubUser(userId: string, cleanResources = false): boolean {
|
|
@@ -1952,6 +1972,35 @@ export class SqliteStore {
|
|
|
1952
1972
|
this.db.prepare('UPDATE hub_users SET last_ip = ?, last_active_at = ? WHERE id = ?').run(ip, timestamp ?? Date.now(), userId);
|
|
1953
1973
|
}
|
|
1954
1974
|
|
|
1975
|
+
// ─── Hub Groups ───
|
|
1976
|
+
|
|
1977
|
+
upsertHubGroup(group: { id: string; name: string; description?: string; createdAt: number }): void {
|
|
1978
|
+
this.db.prepare(`
|
|
1979
|
+
INSERT INTO hub_groups (id, name, description, created_at)
|
|
1980
|
+
VALUES (?, ?, ?, ?)
|
|
1981
|
+
ON CONFLICT(id) DO UPDATE SET name = excluded.name, description = excluded.description
|
|
1982
|
+
`).run(group.id, group.name, group.description ?? "", group.createdAt);
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
addHubGroupMember(groupId: string, userId: string, joinedAt: number): void {
|
|
1986
|
+
this.db.prepare(`
|
|
1987
|
+
INSERT OR IGNORE INTO hub_group_members (group_id, user_id, joined_at)
|
|
1988
|
+
VALUES (?, ?, ?)
|
|
1989
|
+
`).run(groupId, userId, joinedAt);
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
removeHubGroupMember(groupId: string, userId: string): void {
|
|
1993
|
+
this.db.prepare('DELETE FROM hub_group_members WHERE group_id = ? AND user_id = ?').run(groupId, userId);
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
getGroupsForHubUser(userId: string): Array<{ id: string; name: string; description: string }> {
|
|
1997
|
+
return this.db.prepare(`
|
|
1998
|
+
SELECT g.id, g.name, g.description FROM hub_groups g
|
|
1999
|
+
JOIN hub_group_members m ON m.group_id = g.id
|
|
2000
|
+
WHERE m.user_id = ?
|
|
2001
|
+
`).all(userId) as Array<{ id: string; name: string; description: string }>;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
1955
2004
|
getHubUserContributions(): Record<string, { memoryCount: number; taskCount: number; skillCount: number }> {
|
|
1956
2005
|
const result: Record<string, { memoryCount: number; taskCount: number; skillCount: number }> = {};
|
|
1957
2006
|
const memRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_memories GROUP BY source_user_id').all() as Array<{ source_user_id: string; cnt: number }>;
|
|
@@ -2080,16 +2129,21 @@ export class SqliteStore {
|
|
|
2080
2129
|
const limit = options?.maxResults ?? 10;
|
|
2081
2130
|
const userId = options?.userId ?? "";
|
|
2082
2131
|
const rows = this.db.prepare(`
|
|
2083
|
-
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
2132
|
+
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
2133
|
+
COALESCE(hg.name, '') as group_name, hu.username as owner_name,
|
|
2084
2134
|
bm25(hub_chunks_fts) as rank
|
|
2085
2135
|
FROM hub_chunks_fts f
|
|
2086
2136
|
JOIN hub_chunks hc ON hc.rowid = f.rowid
|
|
2087
2137
|
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
2088
2138
|
LEFT JOIN hub_users hu ON hu.id = ht.source_user_id
|
|
2139
|
+
LEFT JOIN hub_groups hg ON hg.id = ht.group_id
|
|
2089
2140
|
WHERE hub_chunks_fts MATCH ?
|
|
2141
|
+
AND (ht.visibility = 'public'
|
|
2142
|
+
OR ht.source_user_id = ?
|
|
2143
|
+
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = ht.group_id AND gm.user_id = ?))
|
|
2090
2144
|
ORDER BY rank
|
|
2091
2145
|
LIMIT ?
|
|
2092
|
-
`).all(sanitizeFtsQuery(query), limit) as HubSearchRow[];
|
|
2146
|
+
`).all(sanitizeFtsQuery(query), userId, userId, limit) as HubSearchRow[];
|
|
2093
2147
|
return rows.map((row, idx) => ({ hit: row, rank: idx + 1 }));
|
|
2094
2148
|
}
|
|
2095
2149
|
|
|
@@ -2114,7 +2168,10 @@ export class SqliteStore {
|
|
|
2114
2168
|
FROM hub_embeddings he
|
|
2115
2169
|
JOIN hub_chunks hc ON hc.id = he.chunk_id
|
|
2116
2170
|
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
2117
|
-
|
|
2171
|
+
WHERE ht.visibility = 'public'
|
|
2172
|
+
OR ht.source_user_id = ?
|
|
2173
|
+
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = ht.group_id AND gm.user_id = ?)
|
|
2174
|
+
`).all(userId, userId) as Array<{ chunk_id: string; vector: Buffer; dimensions: number }>;
|
|
2118
2175
|
return rows.map(r => ({
|
|
2119
2176
|
chunkId: r.chunk_id,
|
|
2120
2177
|
vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
|
|
@@ -2123,14 +2180,19 @@ export class SqliteStore {
|
|
|
2123
2180
|
|
|
2124
2181
|
getVisibleHubSearchHitByChunkId(chunkId: string, userId: string): HubSearchRow | null {
|
|
2125
2182
|
const row = this.db.prepare(`
|
|
2126
|
-
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
2183
|
+
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
2184
|
+
COALESCE(hg.name, '') as group_name, hu.username as owner_name,
|
|
2127
2185
|
0 as rank
|
|
2128
2186
|
FROM hub_chunks hc
|
|
2129
2187
|
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
2130
2188
|
LEFT JOIN hub_users hu ON hu.id = ht.source_user_id
|
|
2189
|
+
LEFT JOIN hub_groups hg ON hg.id = ht.group_id
|
|
2131
2190
|
WHERE hc.id = ?
|
|
2191
|
+
AND (ht.visibility = 'public'
|
|
2192
|
+
OR ht.source_user_id = ?
|
|
2193
|
+
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = ht.group_id AND gm.user_id = ?))
|
|
2132
2194
|
LIMIT 1
|
|
2133
|
-
`).get(chunkId) as HubSearchRow | undefined;
|
|
2195
|
+
`).get(chunkId, userId, userId) as HubSearchRow | undefined;
|
|
2134
2196
|
return row ?? null;
|
|
2135
2197
|
}
|
|
2136
2198
|
|
package/src/viewer/html.ts
CHANGED
|
@@ -3696,7 +3696,14 @@ function switchView(view){
|
|
|
3696
3696
|
else if(view==='skills') loadSkills();
|
|
3697
3697
|
else if(view==='analytics') loadMetrics();
|
|
3698
3698
|
else if(view==='logs') loadLogs();
|
|
3699
|
-
else if(view==='settings'){loadConfig()
|
|
3699
|
+
else if(view==='settings'){loadConfig().then(function(){
|
|
3700
|
+
var notDismissed=localStorage.getItem('memos-team-guide-dismissed')!=='1';
|
|
3701
|
+
var sharingOn=document.getElementById('cfgSharingEnabled');
|
|
3702
|
+
var sharingNotEnabled=!sharingOn||!sharingOn.checked;
|
|
3703
|
+
if(notDismissed&&sharingNotEnabled){
|
|
3704
|
+
switchSettingsTab('hub',document.querySelector('.settings-tab-btn[data-tab="hub"]'));
|
|
3705
|
+
}
|
|
3706
|
+
});loadModelHealth();}
|
|
3700
3707
|
else if(view==='import'){if(!window._migrateRunning) migrateScan(false);}
|
|
3701
3708
|
else if(view==='admin'){loadAdminData();}
|
|
3702
3709
|
}
|
|
@@ -3736,6 +3743,13 @@ function onTaskScopeChange(){
|
|
|
3736
3743
|
|
|
3737
3744
|
var _clientPendingPollTimer=null;
|
|
3738
3745
|
var _lastSharingConnStatus='';
|
|
3746
|
+
function _updateScopeSelectorsVisibility(hubAvailable){
|
|
3747
|
+
var ids=['memorySearchScope','taskSearchScope','skillSearchScope'];
|
|
3748
|
+
for(var i=0;i<ids.length;i++){
|
|
3749
|
+
var el=document.getElementById(ids[i]);
|
|
3750
|
+
if(el) el.style.display=hubAvailable?'':'none';
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3739
3753
|
async function loadSharingStatus(forcePending){
|
|
3740
3754
|
try{
|
|
3741
3755
|
const r=await fetch('/api/sharing/status');
|
|
@@ -3748,15 +3762,19 @@ async function loadSharingStatus(forcePending){
|
|
|
3748
3762
|
if(!d||!d.enabled){
|
|
3749
3763
|
if(_clientPendingPollTimer){clearInterval(_clientPendingPollTimer);_clientPendingPollTimer=null;}
|
|
3750
3764
|
_lastSharingConnStatus='';
|
|
3765
|
+
_updateScopeSelectorsVisibility(false);
|
|
3751
3766
|
return;
|
|
3752
3767
|
}
|
|
3753
3768
|
var conn=d.connection||{};
|
|
3754
3769
|
var curStatus=conn.rejected?'rejected':conn.pendingApproval?'pending':conn.connected?'connected':'none';
|
|
3770
|
+
var hubActive=d.role==='hub'||curStatus==='connected';
|
|
3771
|
+
_updateScopeSelectorsVisibility(hubActive);
|
|
3755
3772
|
if(_lastSharingConnStatus==='pending'&&curStatus==='rejected'){
|
|
3756
3773
|
toast(t('sharing.rejected.toast'),'error');
|
|
3757
3774
|
}
|
|
3758
3775
|
if(_lastSharingConnStatus==='pending'&&curStatus==='connected'){
|
|
3759
3776
|
toast(t('sharing.approved.toast'),'success');
|
|
3777
|
+
loadMemories();loadTasks();loadSkills();
|
|
3760
3778
|
}
|
|
3761
3779
|
_lastSharingConnStatus=curStatus;
|
|
3762
3780
|
if(curStatus==='pending'&&!_clientPendingPollTimer){
|
|
@@ -3770,6 +3788,7 @@ async function loadSharingStatus(forcePending){
|
|
|
3770
3788
|
renderSharingSidebar(null);
|
|
3771
3789
|
renderSharingSettings(null);
|
|
3772
3790
|
updateTeamGuide(null);
|
|
3791
|
+
_updateScopeSelectorsVisibility(false);
|
|
3773
3792
|
}
|
|
3774
3793
|
}
|
|
3775
3794
|
|
package/src/viewer/server.ts
CHANGED
|
@@ -1232,7 +1232,7 @@ export class ViewerServer {
|
|
|
1232
1232
|
body: JSON.stringify({ memory: { sourceChunkId: refreshedChunk.id, role: refreshedChunk.role, content: refreshedChunk.content, summary: refreshedChunk.summary, kind: refreshedChunk.kind, groupId: null, visibility: "public" } }),
|
|
1233
1233
|
});
|
|
1234
1234
|
if (!isLocalShared) this.store.markMemorySharedLocally(chunkId);
|
|
1235
|
-
if (hubClient.userId) {
|
|
1235
|
+
if (hubClient.userId && this.ctx?.config?.sharing?.role === "hub") {
|
|
1236
1236
|
const existing = this.store.getHubMemoryBySource(hubClient.userId, chunkId);
|
|
1237
1237
|
this.store.upsertHubMemory({
|
|
1238
1238
|
id: (response as any)?.memoryId ?? existing?.id ?? crypto.randomUUID(),
|
|
@@ -2080,14 +2080,13 @@ export class ViewerServer {
|
|
|
2080
2080
|
},
|
|
2081
2081
|
}),
|
|
2082
2082
|
});
|
|
2083
|
-
|
|
2084
|
-
if (hubUserId) {
|
|
2083
|
+
if (hubClient.userId && this.ctx?.config?.sharing?.role === "hub") {
|
|
2085
2084
|
const now = Date.now();
|
|
2086
|
-
const existing = this.store.getHubMemoryBySource(
|
|
2085
|
+
const existing = this.store.getHubMemoryBySource(hubClient.userId, chunk.id);
|
|
2087
2086
|
this.store.upsertHubMemory({
|
|
2088
2087
|
id: (response as any)?.memoryId ?? existing?.id ?? crypto.randomUUID(),
|
|
2089
2088
|
sourceChunkId: chunk.id,
|
|
2090
|
-
sourceUserId:
|
|
2089
|
+
sourceUserId: hubClient.userId,
|
|
2091
2090
|
role: chunk.role,
|
|
2092
2091
|
content: chunk.content,
|
|
2093
2092
|
summary: chunk.summary ?? "",
|