@memtensor/memos-local-openclaw-plugin 1.0.4-beta.10 → 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.
Files changed (72) hide show
  1. package/dist/client/connector.d.ts +5 -0
  2. package/dist/client/connector.d.ts.map +1 -1
  3. package/dist/client/connector.js +38 -8
  4. package/dist/client/connector.js.map +1 -1
  5. package/dist/hub/server.d.ts +1 -0
  6. package/dist/hub/server.d.ts.map +1 -1
  7. package/dist/hub/server.js +143 -32
  8. package/dist/hub/server.js.map +1 -1
  9. package/dist/hub/user-manager.d.ts +9 -0
  10. package/dist/hub/user-manager.d.ts.map +1 -1
  11. package/dist/hub/user-manager.js +26 -2
  12. package/dist/hub/user-manager.js.map +1 -1
  13. package/dist/ingest/chunker.d.ts +2 -1
  14. package/dist/ingest/chunker.d.ts.map +1 -1
  15. package/dist/ingest/chunker.js +14 -10
  16. package/dist/ingest/chunker.js.map +1 -1
  17. package/dist/recall/engine.d.ts.map +1 -1
  18. package/dist/recall/engine.js +7 -2
  19. package/dist/recall/engine.js.map +1 -1
  20. package/dist/sharing/types.d.ts +1 -1
  21. package/dist/sharing/types.d.ts.map +1 -1
  22. package/dist/skill/evolver.d.ts +2 -0
  23. package/dist/skill/evolver.d.ts.map +1 -1
  24. package/dist/skill/evolver.js +56 -5
  25. package/dist/skill/evolver.js.map +1 -1
  26. package/dist/skill/generator.d.ts +2 -0
  27. package/dist/skill/generator.d.ts.map +1 -1
  28. package/dist/skill/generator.js +45 -3
  29. package/dist/skill/generator.js.map +1 -1
  30. package/dist/skill/installer.d.ts +26 -0
  31. package/dist/skill/installer.d.ts.map +1 -1
  32. package/dist/skill/installer.js +80 -4
  33. package/dist/skill/installer.js.map +1 -1
  34. package/dist/skill/upgrader.d.ts +2 -0
  35. package/dist/skill/upgrader.d.ts.map +1 -1
  36. package/dist/skill/upgrader.js +139 -1
  37. package/dist/skill/upgrader.js.map +1 -1
  38. package/dist/skill/validator.d.ts +3 -0
  39. package/dist/skill/validator.d.ts.map +1 -1
  40. package/dist/skill/validator.js +75 -0
  41. package/dist/skill/validator.js.map +1 -1
  42. package/dist/storage/sqlite.d.ts +28 -0
  43. package/dist/storage/sqlite.d.ts.map +1 -1
  44. package/dist/storage/sqlite.js +155 -16
  45. package/dist/storage/sqlite.js.map +1 -1
  46. package/dist/types.d.ts +10 -0
  47. package/dist/types.d.ts.map +1 -1
  48. package/dist/types.js +4 -0
  49. package/dist/types.js.map +1 -1
  50. package/dist/viewer/html.d.ts.map +1 -1
  51. package/dist/viewer/html.js +64 -24
  52. package/dist/viewer/html.js.map +1 -1
  53. package/dist/viewer/server.d.ts.map +1 -1
  54. package/dist/viewer/server.js +39 -20
  55. package/dist/viewer/server.js.map +1 -1
  56. package/index.ts +338 -33
  57. package/package.json +1 -1
  58. package/src/client/connector.ts +43 -8
  59. package/src/hub/server.ts +142 -31
  60. package/src/hub/user-manager.ts +42 -6
  61. package/src/ingest/chunker.ts +19 -13
  62. package/src/recall/engine.ts +7 -2
  63. package/src/sharing/types.ts +1 -1
  64. package/src/skill/evolver.ts +58 -6
  65. package/src/skill/generator.ts +44 -5
  66. package/src/skill/installer.ts +107 -4
  67. package/src/skill/upgrader.ts +139 -1
  68. package/src/skill/validator.ts +79 -0
  69. package/src/storage/sqlite.ts +174 -16
  70. package/src/types.ts +11 -0
  71. package/src/viewer/html.ts +64 -24
  72. package/src/viewer/server.ts +39 -20
@@ -114,6 +114,8 @@ export class SqliteStore {
114
114
  this.migrateHubTables();
115
115
  this.migrateHubFtsToTrigram();
116
116
  this.migrateLocalSharedTasksOwner();
117
+ this.migrateHubUserIdentityFields();
118
+ this.migrateClientHubConnectionIdentityFields();
117
119
  this.log.debug("Database schema initialized");
118
120
  }
119
121
 
@@ -131,6 +133,49 @@ export class SqliteStore {
131
133
  } catch { /* table may not exist yet */ }
132
134
  }
133
135
 
136
+ private migrateHubUserIdentityFields(): void {
137
+ try {
138
+ const cols = this.db.prepare("PRAGMA table_info(hub_users)").all() as Array<{ name: string }>;
139
+ if (cols.length === 0) return;
140
+ if (!cols.some(c => c.name === "identity_key")) {
141
+ this.db.exec("ALTER TABLE hub_users ADD COLUMN identity_key TEXT NOT NULL DEFAULT ''");
142
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_hub_users_identity_key ON hub_users(identity_key)");
143
+ this.log.info("Migrated: added identity_key to hub_users");
144
+ }
145
+ if (!cols.some(c => c.name === "left_at")) {
146
+ this.db.exec("ALTER TABLE hub_users ADD COLUMN left_at INTEGER");
147
+ this.log.info("Migrated: added left_at to hub_users");
148
+ }
149
+ if (!cols.some(c => c.name === "removed_at")) {
150
+ this.db.exec("ALTER TABLE hub_users ADD COLUMN removed_at INTEGER");
151
+ this.log.info("Migrated: added removed_at to hub_users");
152
+ }
153
+ if (!cols.some(c => c.name === "rejected_at")) {
154
+ this.db.exec("ALTER TABLE hub_users ADD COLUMN rejected_at INTEGER");
155
+ this.log.info("Migrated: added rejected_at to hub_users");
156
+ }
157
+ if (!cols.some(c => c.name === "rejoin_requested_at")) {
158
+ this.db.exec("ALTER TABLE hub_users ADD COLUMN rejoin_requested_at INTEGER");
159
+ this.log.info("Migrated: added rejoin_requested_at to hub_users");
160
+ }
161
+ } catch { /* table may not exist yet */ }
162
+ }
163
+
164
+ private migrateClientHubConnectionIdentityFields(): void {
165
+ try {
166
+ const cols = this.db.prepare("PRAGMA table_info(client_hub_connection)").all() as Array<{ name: string }>;
167
+ if (cols.length === 0) return;
168
+ if (!cols.some(c => c.name === "identity_key")) {
169
+ this.db.exec("ALTER TABLE client_hub_connection ADD COLUMN identity_key TEXT NOT NULL DEFAULT ''");
170
+ this.log.info("Migrated: added identity_key to client_hub_connection");
171
+ }
172
+ if (!cols.some(c => c.name === "last_known_status")) {
173
+ this.db.exec("ALTER TABLE client_hub_connection ADD COLUMN last_known_status TEXT NOT NULL DEFAULT ''");
174
+ this.log.info("Migrated: added last_known_status to client_hub_connection");
175
+ }
176
+ } catch { /* table may not exist yet */ }
177
+ }
178
+
134
179
  private migrateOwnerFields(): void {
135
180
  const chunkCols = this.db.prepare("PRAGMA table_info(chunks)").all() as Array<{ name: string }>;
136
181
  if (!chunkCols.some((c) => c.name === "owner")) {
@@ -762,6 +807,20 @@ export class SqliteStore {
762
807
  CREATE INDEX IF NOT EXISTS idx_hub_users_status ON hub_users(status);
763
808
  CREATE INDEX IF NOT EXISTS idx_hub_users_role ON hub_users(role);
764
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
+
765
824
  CREATE TABLE IF NOT EXISTS hub_tasks (
766
825
  id TEXT PRIMARY KEY,
767
826
  source_task_id TEXT NOT NULL,
@@ -1731,16 +1790,18 @@ export class SqliteStore {
1731
1790
 
1732
1791
  setClientHubConnection(conn: ClientHubConnection): void {
1733
1792
  this.db.prepare(`
1734
- INSERT INTO client_hub_connection (id, hub_url, user_id, username, user_token, role, connected_at)
1735
- VALUES (1, ?, ?, ?, ?, ?, ?)
1793
+ INSERT INTO client_hub_connection (id, hub_url, user_id, username, user_token, role, connected_at, identity_key, last_known_status)
1794
+ VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?)
1736
1795
  ON CONFLICT(id) DO UPDATE SET
1737
1796
  hub_url = excluded.hub_url,
1738
1797
  user_id = excluded.user_id,
1739
1798
  username = excluded.username,
1740
1799
  user_token = excluded.user_token,
1741
1800
  role = excluded.role,
1742
- connected_at = excluded.connected_at
1743
- `).run(conn.hubUrl, conn.userId, conn.username, conn.userToken, conn.role, conn.connectedAt);
1801
+ connected_at = excluded.connected_at,
1802
+ identity_key = excluded.identity_key,
1803
+ last_known_status = excluded.last_known_status
1804
+ `).run(conn.hubUrl, conn.userId, conn.username, conn.userToken, conn.role, conn.connectedAt, conn.identityKey ?? "", conn.lastKnownStatus ?? "");
1744
1805
  }
1745
1806
 
1746
1807
  getClientHubConnection(): ClientHubConnection | null {
@@ -1847,8 +1908,8 @@ export class SqliteStore {
1847
1908
 
1848
1909
  upsertHubUser(user: HubUserRecord): void {
1849
1910
  this.db.prepare(`
1850
- INSERT INTO hub_users (id, username, device_name, role, status, token_hash, created_at, approved_at)
1851
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1911
+ INSERT INTO hub_users (id, username, device_name, role, status, token_hash, created_at, approved_at, identity_key, left_at, removed_at, rejected_at, rejoin_requested_at)
1912
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1852
1913
  ON CONFLICT(id) DO UPDATE SET
1853
1914
  username = excluded.username,
1854
1915
  device_name = excluded.device_name,
@@ -1856,21 +1917,32 @@ export class SqliteStore {
1856
1917
  status = excluded.status,
1857
1918
  token_hash = excluded.token_hash,
1858
1919
  created_at = excluded.created_at,
1859
- approved_at = excluded.approved_at
1860
- `).run(user.id, user.username, user.deviceName ?? "", user.role, user.status, user.tokenHash, user.createdAt, user.approvedAt);
1920
+ approved_at = excluded.approved_at,
1921
+ identity_key = excluded.identity_key,
1922
+ left_at = excluded.left_at,
1923
+ removed_at = excluded.removed_at,
1924
+ rejected_at = excluded.rejected_at,
1925
+ rejoin_requested_at = excluded.rejoin_requested_at
1926
+ `).run(user.id, user.username, user.deviceName ?? "", user.role, user.status, user.tokenHash, user.createdAt, user.approvedAt, user.identityKey ?? "", user.leftAt ?? null, user.removedAt ?? null, user.rejectedAt ?? null, user.rejoinRequestedAt ?? null);
1861
1927
  }
1862
1928
 
1863
1929
  getHubUser(userId: string): HubUserRecord | null {
1864
1930
  const row = this.db.prepare('SELECT * FROM hub_users WHERE id = ?').get(userId) as HubUserRow | undefined;
1865
1931
  if (!row) return null;
1866
- return rowToHubUser(row);
1932
+ const user = rowToHubUser(row);
1933
+ user.groups = this.getGroupsForHubUser(userId);
1934
+ return user;
1867
1935
  }
1868
1936
 
1869
1937
  listHubUsers(status?: UserStatus): HubUserRecord[] {
1870
1938
  const rows = status
1871
1939
  ? this.db.prepare('SELECT * FROM hub_users WHERE status = ? ORDER BY created_at').all(status) as HubUserRow[]
1872
1940
  : this.db.prepare('SELECT * FROM hub_users ORDER BY created_at').all() as HubUserRow[];
1873
- return rows.map(rowToHubUser);
1941
+ return rows.map(r => {
1942
+ const user = rowToHubUser(r);
1943
+ user.groups = this.getGroupsForHubUser(r.id);
1944
+ return user;
1945
+ });
1874
1946
  }
1875
1947
 
1876
1948
  deleteHubUser(userId: string, cleanResources = false): boolean {
@@ -1881,7 +1953,18 @@ export class SqliteStore {
1881
1953
  const result = this.db.prepare('DELETE FROM hub_users WHERE id = ?').run(userId);
1882
1954
  return result.changes > 0;
1883
1955
  }
1884
- const result = this.db.prepare("UPDATE hub_users SET status = 'removed', token_hash = '' WHERE id = ?").run(userId);
1956
+ const result = this.db.prepare("UPDATE hub_users SET status = 'removed', token_hash = '', removed_at = ? WHERE id = ?").run(Date.now(), userId);
1957
+ return result.changes > 0;
1958
+ }
1959
+
1960
+ findHubUserByIdentityKey(identityKey: string): HubUserRecord | null {
1961
+ if (!identityKey) return null;
1962
+ const row = this.db.prepare('SELECT * FROM hub_users WHERE identity_key = ?').get(identityKey) as HubUserRow | undefined;
1963
+ return row ? rowToHubUser(row) : null;
1964
+ }
1965
+
1966
+ markHubUserLeft(userId: string): boolean {
1967
+ const result = this.db.prepare("UPDATE hub_users SET status = 'left', token_hash = '', left_at = ? WHERE id = ?").run(Date.now(), userId);
1885
1968
  return result.changes > 0;
1886
1969
  }
1887
1970
 
@@ -1889,6 +1972,35 @@ export class SqliteStore {
1889
1972
  this.db.prepare('UPDATE hub_users SET last_ip = ?, last_active_at = ? WHERE id = ?').run(ip, timestamp ?? Date.now(), userId);
1890
1973
  }
1891
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
+
1892
2004
  getHubUserContributions(): Record<string, { memoryCount: number; taskCount: number; skillCount: number }> {
1893
2005
  const result: Record<string, { memoryCount: number; taskCount: number; skillCount: number }> = {};
1894
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 }>;
@@ -2001,20 +2113,37 @@ export class SqliteStore {
2001
2113
  return out;
2002
2114
  }
2003
2115
 
2116
+ getVisibleHubSkillEmbeddings(): Array<{ skillId: string; vector: Float32Array }> {
2117
+ const rows = this.db.prepare(`
2118
+ SELECT hse.skill_id, hse.vector, hse.dimensions
2119
+ FROM hub_skill_embeddings hse
2120
+ JOIN hub_skills hs ON hs.id = hse.skill_id
2121
+ `).all() as Array<{ skill_id: string; vector: Buffer; dimensions: number }>;
2122
+ return rows.map(r => ({
2123
+ skillId: r.skill_id,
2124
+ vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
2125
+ }));
2126
+ }
2127
+
2004
2128
  searchHubChunks(query: string, options?: { userId?: string; maxResults?: number }): Array<{ hit: HubSearchRow; rank: number }> {
2005
2129
  const limit = options?.maxResults ?? 10;
2006
2130
  const userId = options?.userId ?? "";
2007
2131
  const rows = this.db.prepare(`
2008
- SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility, '' as group_name, hu.username as owner_name,
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,
2009
2134
  bm25(hub_chunks_fts) as rank
2010
2135
  FROM hub_chunks_fts f
2011
2136
  JOIN hub_chunks hc ON hc.rowid = f.rowid
2012
2137
  JOIN hub_tasks ht ON ht.id = hc.hub_task_id
2013
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
2014
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 = ?))
2015
2144
  ORDER BY rank
2016
2145
  LIMIT ?
2017
- `).all(sanitizeFtsQuery(query), limit) as HubSearchRow[];
2146
+ `).all(sanitizeFtsQuery(query), userId, userId, limit) as HubSearchRow[];
2018
2147
  return rows.map((row, idx) => ({ hit: row, rank: idx + 1 }));
2019
2148
  }
2020
2149
 
@@ -2039,7 +2168,10 @@ export class SqliteStore {
2039
2168
  FROM hub_embeddings he
2040
2169
  JOIN hub_chunks hc ON hc.id = he.chunk_id
2041
2170
  JOIN hub_tasks ht ON ht.id = hc.hub_task_id
2042
- `).all() as Array<{ chunk_id: string; vector: Buffer; dimensions: number }>;
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 }>;
2043
2175
  return rows.map(r => ({
2044
2176
  chunkId: r.chunk_id,
2045
2177
  vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
@@ -2048,14 +2180,19 @@ export class SqliteStore {
2048
2180
 
2049
2181
  getVisibleHubSearchHitByChunkId(chunkId: string, userId: string): HubSearchRow | null {
2050
2182
  const row = this.db.prepare(`
2051
- SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility, '' as group_name, hu.username as owner_name,
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,
2052
2185
  0 as rank
2053
2186
  FROM hub_chunks hc
2054
2187
  JOIN hub_tasks ht ON ht.id = hc.hub_task_id
2055
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
2056
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 = ?))
2057
2194
  LIMIT 1
2058
- `).get(chunkId) as HubSearchRow | undefined;
2195
+ `).get(chunkId, userId, userId) as HubSearchRow | undefined;
2059
2196
  return row ?? null;
2060
2197
  }
2061
2198
 
@@ -2549,6 +2686,8 @@ interface ClientHubConnection {
2549
2686
  userToken: string;
2550
2687
  role: UserRole;
2551
2688
  connectedAt: number;
2689
+ identityKey?: string;
2690
+ lastKnownStatus?: string;
2552
2691
  }
2553
2692
 
2554
2693
  interface ClientHubConnectionRow {
@@ -2558,6 +2697,8 @@ interface ClientHubConnectionRow {
2558
2697
  user_token: string;
2559
2698
  role: string;
2560
2699
  connected_at: number;
2700
+ identity_key?: string;
2701
+ last_known_status?: string;
2561
2702
  }
2562
2703
 
2563
2704
  function rowToClientHubConnection(row: ClientHubConnectionRow): ClientHubConnection {
@@ -2568,6 +2709,8 @@ function rowToClientHubConnection(row: ClientHubConnectionRow): ClientHubConnect
2568
2709
  userToken: row.user_token,
2569
2710
  role: row.role as UserRole,
2570
2711
  connectedAt: row.connected_at,
2712
+ identityKey: row.identity_key || "",
2713
+ lastKnownStatus: row.last_known_status || "",
2571
2714
  };
2572
2715
  }
2573
2716
 
@@ -2577,6 +2720,11 @@ interface HubUserRecord extends UserInfo {
2577
2720
  approvedAt: number | null;
2578
2721
  lastIp: string;
2579
2722
  lastActiveAt: number | null;
2723
+ identityKey?: string;
2724
+ leftAt?: number | null;
2725
+ removedAt?: number | null;
2726
+ rejectedAt?: number | null;
2727
+ rejoinRequestedAt?: number | null;
2580
2728
  }
2581
2729
 
2582
2730
  interface HubUserRow {
@@ -2590,6 +2738,11 @@ interface HubUserRow {
2590
2738
  approved_at: number | null;
2591
2739
  last_ip: string;
2592
2740
  last_active_at: number | null;
2741
+ identity_key?: string;
2742
+ left_at?: number | null;
2743
+ removed_at?: number | null;
2744
+ rejected_at?: number | null;
2745
+ rejoin_requested_at?: number | null;
2593
2746
  }
2594
2747
 
2595
2748
  function rowToHubUser(row: HubUserRow): HubUserRecord {
@@ -2605,6 +2758,11 @@ function rowToHubUser(row: HubUserRow): HubUserRecord {
2605
2758
  approvedAt: row.approved_at,
2606
2759
  lastIp: row.last_ip || "",
2607
2760
  lastActiveAt: row.last_active_at ?? null,
2761
+ identityKey: row.identity_key || "",
2762
+ leftAt: row.left_at ?? null,
2763
+ removedAt: row.removed_at ?? null,
2764
+ rejectedAt: row.rejected_at ?? null,
2765
+ rejoinRequestedAt: row.rejoin_requested_at ?? null,
2608
2766
  };
2609
2767
  }
2610
2768
 
package/src/types.ts CHANGED
@@ -66,6 +66,8 @@ export interface ChunkRef {
66
66
 
67
67
  // ─── Search / Recall ───
68
68
 
69
+ export type SearchHitOrigin = "local" | "local-shared" | "hub-memory" | "hub-remote";
70
+
69
71
  export interface SearchHit {
70
72
  summary: string;
71
73
  original_excerpt: string;
@@ -74,6 +76,7 @@ export interface SearchHit {
74
76
  taskId: string | null;
75
77
  skillId: string | null;
76
78
  owner?: string;
79
+ origin?: SearchHitOrigin;
77
80
  source: {
78
81
  ts: number;
79
82
  role: Role;
@@ -249,6 +252,10 @@ export interface SkillEvolutionConfig {
249
252
  minConfidence?: number;
250
253
  maxSkillLines?: number;
251
254
  autoInstall?: boolean;
255
+ autoRecallSkills?: boolean;
256
+ autoRecallSkillLimit?: number;
257
+ preferUpgradeExisting?: boolean;
258
+ redactSensitiveInSkill?: boolean;
252
259
  /** Optional independent LLM config for skill evaluation/validation. Falls back to main summarizer if not set. */
253
260
  summarizer?: SummarizerConfig;
254
261
  }
@@ -344,6 +351,10 @@ export const DEFAULTS = {
344
351
  skillMinConfidence: 0.7,
345
352
  skillMaxLines: 400,
346
353
  skillAutoInstall: false,
354
+ skillAutoRecall: true,
355
+ skillAutoRecallLimit: 2,
356
+ skillPreferUpgrade: true,
357
+ skillRedactSensitive: true,
347
358
  } as const;
348
359
 
349
360
  // ─── Plugin Hooks (OpenClaw integration) ───
@@ -804,6 +804,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
804
804
  .recall-score.high{background:rgba(34,197,94,.12);color:#22c55e}
805
805
  .recall-score.mid{background:rgba(251,191,36,.12);color:#f59e0b}
806
806
  .recall-score.low{background:rgba(248,113,113,.1);color:var(--text-muted)}
807
+ .recall-origin{flex-shrink:0;font-size:9px;font-weight:600;padding:1px 5px;border-radius:4px}
808
+ .recall-origin.local-shared{background:rgba(59,130,246,.12);color:#3b82f6}
809
+ .recall-origin.hub-memory{background:rgba(139,92,246,.12);color:#8b5cf6}
810
+ .recall-origin.hub-remote{background:rgba(139,92,246,.12);color:#8b5cf6}
807
811
  .recall-summary-short{flex:1;color:var(--text-sec);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
808
812
  .recall-expand-icon{flex-shrink:0;font-size:10px;color:var(--text-muted);transition:transform .15s}
809
813
  .recall-item.expanded .recall-expand-icon{transform:rotate(90deg)}
@@ -1216,7 +1220,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1216
1220
  </div>
1217
1221
  <button class="btn btn-ghost btn-sm" onclick="doLogout()" data-i18n="logout">Logout</button>
1218
1222
  </div>
1219
- </div>
1223
+ </div>
1220
1224
  </div>
1221
1225
 
1222
1226
  <div class="main-content">
@@ -2229,6 +2233,9 @@ const I18N={
2229
2233
  'logs.recall.noHits':'No matching memories',
2230
2234
  'logs.recall.noneRelevant':'LLM filter: none relevant',
2231
2235
  'logs.recall.more':'{n} more...',
2236
+ 'recall.origin.localShared':'Local Shared',
2237
+ 'recall.origin.hubMemory':'Team Cache',
2238
+ 'recall.origin.hubRemote':'Team',
2232
2239
  'tab.import':'\u{1F4E5} Import',
2233
2240
  'tab.settings':'\u2699 Settings',
2234
2241
  'settings.modelconfig':'Model Configuration',
@@ -2940,6 +2947,9 @@ const I18N={
2940
2947
  'logs.recall.noHits':'未匹配到记忆',
2941
2948
  'logs.recall.noneRelevant':'LLM 过滤:无相关记忆',
2942
2949
  'logs.recall.more':'还有 {n} 条...',
2950
+ 'recall.origin.localShared':'本机共享',
2951
+ 'recall.origin.hubMemory':'团队缓存',
2952
+ 'recall.origin.hubRemote':'团队',
2943
2953
  'tab.import':'\u{1F4E5} 导入',
2944
2954
  'tab.settings':'\u2699 设置',
2945
2955
  'settings.modelconfig':'模型配置',
@@ -3686,7 +3696,14 @@ function switchView(view){
3686
3696
  else if(view==='skills') loadSkills();
3687
3697
  else if(view==='analytics') loadMetrics();
3688
3698
  else if(view==='logs') loadLogs();
3689
- else if(view==='settings'){loadConfig();loadModelHealth();}
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();}
3690
3707
  else if(view==='import'){if(!window._migrateRunning) migrateScan(false);}
3691
3708
  else if(view==='admin'){loadAdminData();}
3692
3709
  }
@@ -3726,6 +3743,13 @@ function onTaskScopeChange(){
3726
3743
 
3727
3744
  var _clientPendingPollTimer=null;
3728
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
+ }
3729
3753
  async function loadSharingStatus(forcePending){
3730
3754
  try{
3731
3755
  const r=await fetch('/api/sharing/status');
@@ -3738,15 +3762,19 @@ async function loadSharingStatus(forcePending){
3738
3762
  if(!d||!d.enabled){
3739
3763
  if(_clientPendingPollTimer){clearInterval(_clientPendingPollTimer);_clientPendingPollTimer=null;}
3740
3764
  _lastSharingConnStatus='';
3765
+ _updateScopeSelectorsVisibility(false);
3741
3766
  return;
3742
3767
  }
3743
3768
  var conn=d.connection||{};
3744
3769
  var curStatus=conn.rejected?'rejected':conn.pendingApproval?'pending':conn.connected?'connected':'none';
3770
+ var hubActive=d.role==='hub'||curStatus==='connected';
3771
+ _updateScopeSelectorsVisibility(hubActive);
3745
3772
  if(_lastSharingConnStatus==='pending'&&curStatus==='rejected'){
3746
3773
  toast(t('sharing.rejected.toast'),'error');
3747
3774
  }
3748
3775
  if(_lastSharingConnStatus==='pending'&&curStatus==='connected'){
3749
3776
  toast(t('sharing.approved.toast'),'success');
3777
+ loadMemories();loadTasks();loadSkills();
3750
3778
  }
3751
3779
  _lastSharingConnStatus=curStatus;
3752
3780
  if(curStatus==='pending'&&!_clientPendingPollTimer){
@@ -3760,6 +3788,7 @@ async function loadSharingStatus(forcePending){
3760
3788
  renderSharingSidebar(null);
3761
3789
  renderSharingSettings(null);
3762
3790
  updateTeamGuide(null);
3791
+ _updateScopeSelectorsVisibility(false);
3763
3792
  }
3764
3793
  }
3765
3794
 
@@ -4087,7 +4116,7 @@ function adminPaginateHtml(total,page,refilterFn){
4087
4116
  if(end<pages) html+=(end<pages-1?'<span class="pg-info">...</span>':'')+'<button class="pg-btn" onclick="'+refilterFn+'Page('+(pages-1)+')">'+pages+'</button>';
4088
4117
  html+='<button class="pg-btn'+(page>=pages-1?' disabled':'')+'" onclick="'+refilterFn+'Page('+(page+1)+')">\\u2192</button>';
4089
4118
  html+='<span class="pg-info">'+total+' '+t('pagination.total')+'</span>';
4090
- html+='</div>';
4119
+ html+='</div>';
4091
4120
  return html;
4092
4121
  }
4093
4122
 
@@ -4149,12 +4178,12 @@ async function loadAdminData(){
4149
4178
  var fetches;
4150
4179
  if(isAdmin){
4151
4180
  fetches=await Promise.all([
4152
- fetch('/api/sharing/users').then(function(r){return r.json();}),
4153
- fetch('/api/admin/shared-tasks').then(function(r){return r.json();}),
4154
- fetch('/api/admin/shared-skills').then(function(r){return r.json();}),
4155
- fetch('/api/sharing/pending-users').then(function(r){return r.json();}),
4156
- fetch('/api/admin/shared-memories').then(function(r){return r.json();})
4157
- ]);
4181
+ fetch('/api/sharing/users').then(function(r){return r.json();}),
4182
+ fetch('/api/admin/shared-tasks').then(function(r){return r.json();}),
4183
+ fetch('/api/admin/shared-skills').then(function(r){return r.json();}),
4184
+ fetch('/api/sharing/pending-users').then(function(r){return r.json();}),
4185
+ fetch('/api/admin/shared-memories').then(function(r){return r.json();})
4186
+ ]);
4158
4187
  }else{
4159
4188
  fetches=await Promise.all([
4160
4189
  Promise.resolve({users:[]}),
@@ -5389,6 +5418,13 @@ function parseMemoryAddEntries(out){
5389
5418
  return results;
5390
5419
  }
5391
5420
 
5421
+ function recallOriginBadge(origin){
5422
+ if(origin==='local-shared') return '<span class="recall-origin local-shared">'+t('recall.origin.localShared')+'</span>';
5423
+ if(origin==='hub-memory') return '<span class="recall-origin hub-memory">'+t('recall.origin.hubMemory')+'</span>';
5424
+ if(origin==='hub-remote') return '<span class="recall-origin hub-remote">'+t('recall.origin.hubRemote')+'</span>';
5425
+ return '';
5426
+ }
5427
+
5392
5428
  function buildLogSummary(lg){
5393
5429
  let inputObj=null;
5394
5430
  try{inputObj=JSON.parse(lg.input);}catch(_){}
@@ -5413,8 +5449,9 @@ function buildLogSummary(lg){
5413
5449
  var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
5414
5450
  var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
5415
5451
  var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
5452
+ var oBadge=recallOriginBadge(c.origin);
5416
5453
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5417
- html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5454
+ html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5418
5455
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5419
5456
  html+='</div>';
5420
5457
  });
@@ -5427,8 +5464,9 @@ function buildLogSummary(lg){
5427
5464
  var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
5428
5465
  var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
5429
5466
  var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
5467
+ var oBadge=recallOriginBadge(f.origin);
5430
5468
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5431
- html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+f.score.toFixed(2)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5469
+ html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+f.score.toFixed(2)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5432
5470
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5433
5471
  html+='</div>';
5434
5472
  });
@@ -5492,8 +5530,9 @@ function buildRecallDetailHtml(rd){
5492
5530
  var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
5493
5531
  var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
5494
5532
  var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
5533
+ var oBadge=recallOriginBadge(c.origin);
5495
5534
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5496
- html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+c.score.toFixed(3)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5535
+ html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+c.score.toFixed(3)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5497
5536
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5498
5537
  html+='</div>';
5499
5538
  });
@@ -5507,8 +5546,9 @@ function buildRecallDetailHtml(rd){
5507
5546
  var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
5508
5547
  var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
5509
5548
  var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
5549
+ var oBadge=recallOriginBadge(f.origin);
5510
5550
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5511
- html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+f.score.toFixed(3)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5551
+ html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+f.score.toFixed(3)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5512
5552
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5513
5553
  html+='</div>';
5514
5554
  });
@@ -6714,8 +6754,8 @@ async function saveHubConfig(){
6714
6754
  if(!td.ok){
6715
6755
  var errMsg=td.error==='cannot_join_self'?t('sharing.cannotJoinSelf'):(td.error||t('settings.hub.test.fail'));
6716
6756
  done();toast(errMsg,'error');return;
6717
- }
6718
- }catch(e){
6757
+ }
6758
+ }catch(e){
6719
6759
  done();toast(t('settings.hub.test.fail')+': '+String(e),'error');return;
6720
6760
  }
6721
6761
  }
@@ -7536,8 +7576,8 @@ function getFilterParams(){
7536
7576
  if(scope==='local'){
7537
7577
  p.set('owner',_currentAgentOwner);
7538
7578
  }else if(scope==='allLocal'){
7539
- const owner=document.getElementById('filterOwner').value;
7540
- if(owner) p.set('owner',owner);
7579
+ const owner=document.getElementById('filterOwner').value;
7580
+ if(owner) p.set('owner',owner);
7541
7581
  }
7542
7582
  return p;
7543
7583
  }
@@ -7567,11 +7607,11 @@ async function loadMemories(page,silent){
7567
7607
  renderPagination();
7568
7608
  }catch(e){
7569
7609
  if(!silent){
7570
- list.innerHTML='';
7571
- totalPages=1;totalCount=0;
7610
+ list.innerHTML='';
7611
+ totalPages=1;totalCount=0;
7572
7612
  _lastMemoriesFingerprint='';
7573
- renderMemories([]);
7574
- renderPagination();
7613
+ renderMemories([]);
7614
+ renderPagination();
7575
7615
  }
7576
7616
  }
7577
7617
  }
@@ -7598,9 +7638,9 @@ async function loadHubMemories(silent){
7598
7638
  }catch(e){
7599
7639
  if(!silent){
7600
7640
  _lastMemoriesFingerprint='';
7601
- document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
7602
- renderMemories([]);
7603
- document.getElementById('pagination').innerHTML='';
7641
+ document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
7642
+ renderMemories([]);
7643
+ document.getElementById('pagination').innerHTML='';
7604
7644
  }
7605
7645
  }
7606
7646
  }