@memtensor/memos-local-openclaw-plugin 1.0.4-beta.7 → 1.0.4-beta.9

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 (63) hide show
  1. package/README.md +1 -1
  2. package/dist/capture/index.d.ts.map +1 -1
  3. package/dist/capture/index.js +6 -0
  4. package/dist/capture/index.js.map +1 -1
  5. package/dist/client/connector.d.ts.map +1 -1
  6. package/dist/client/connector.js +61 -7
  7. package/dist/client/connector.js.map +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +0 -2
  10. package/dist/config.js.map +1 -1
  11. package/dist/hub/server.d.ts +7 -0
  12. package/dist/hub/server.d.ts.map +1 -1
  13. package/dist/hub/server.js +171 -8
  14. package/dist/hub/server.js.map +1 -1
  15. package/dist/ingest/providers/index.d.ts.map +1 -1
  16. package/dist/ingest/providers/index.js +37 -6
  17. package/dist/ingest/providers/index.js.map +1 -1
  18. package/dist/recall/engine.d.ts.map +1 -1
  19. package/dist/recall/engine.js +78 -1
  20. package/dist/recall/engine.js.map +1 -1
  21. package/dist/shared/llm-call.d.ts +1 -0
  22. package/dist/shared/llm-call.d.ts.map +1 -1
  23. package/dist/shared/llm-call.js +82 -8
  24. package/dist/shared/llm-call.js.map +1 -1
  25. package/dist/skill/evolver.d.ts +2 -0
  26. package/dist/skill/evolver.d.ts.map +1 -1
  27. package/dist/skill/evolver.js +3 -0
  28. package/dist/skill/evolver.js.map +1 -1
  29. package/dist/storage/sqlite.d.ts +5 -1
  30. package/dist/storage/sqlite.d.ts.map +1 -1
  31. package/dist/storage/sqlite.js +13 -4
  32. package/dist/storage/sqlite.js.map +1 -1
  33. package/dist/telemetry.d.ts +12 -5
  34. package/dist/telemetry.d.ts.map +1 -1
  35. package/dist/telemetry.js +135 -38
  36. package/dist/telemetry.js.map +1 -1
  37. package/dist/types.d.ts +1 -2
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/types.js.map +1 -1
  40. package/dist/viewer/html.d.ts.map +1 -1
  41. package/dist/viewer/html.js +735 -285
  42. package/dist/viewer/html.js.map +1 -1
  43. package/dist/viewer/server.d.ts +16 -0
  44. package/dist/viewer/server.d.ts.map +1 -1
  45. package/dist/viewer/server.js +349 -21
  46. package/dist/viewer/server.js.map +1 -1
  47. package/index.ts +26 -2
  48. package/openclaw.plugin.json +1 -1
  49. package/package.json +1 -2
  50. package/scripts/postinstall.cjs +1 -1
  51. package/src/capture/index.ts +8 -0
  52. package/src/client/connector.ts +62 -7
  53. package/src/config.ts +0 -2
  54. package/src/hub/server.ts +168 -8
  55. package/src/ingest/providers/index.ts +41 -7
  56. package/src/recall/engine.ts +73 -1
  57. package/src/shared/llm-call.ts +97 -9
  58. package/src/skill/evolver.ts +5 -0
  59. package/src/storage/sqlite.ts +19 -6
  60. package/src/telemetry.ts +152 -39
  61. package/src/types.ts +1 -2
  62. package/src/viewer/html.ts +735 -285
  63. package/src/viewer/server.ts +322 -21
@@ -12,6 +12,7 @@ return `<!DOCTYPE html>
12
12
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
13
13
  <style>
14
14
  *{margin:0;padding:0;box-sizing:border-box}
15
+ html{overflow-y:scroll}
15
16
  :root{
16
17
  --bg:#0b0d11;--bg-card:#12141a;--bg-card-hover:#1a1d25;
17
18
  --border:rgba(255,255,255,.08);--border-glow:rgba(255,255,255,.14);
@@ -106,7 +107,8 @@ input,textarea,select{font-family:inherit;font-size:inherit}
106
107
 
107
108
  /* ─── App Layout (dark dashboard, same as www) ─── */
108
109
  .app{display:none;flex-direction:column;min-height:100vh}
109
- .topbar{background:rgba(11,13,17,.88);border-bottom:1px solid var(--border);padding:0 max(32px,calc((100% - 1400px)/2 + 32px));height:56px;display:flex;align-items:center;position:sticky;top:0;z-index:100;backdrop-filter:blur(12px)}
110
+ .topbar{background:rgba(11,13,17,.88);border-bottom:1px solid var(--border);height:56px;display:flex;align-items:center;position:sticky;top:0;z-index:100;backdrop-filter:blur(12px)}
111
+ .topbar-inner{display:flex;align-items:center;width:100%;max-width:1400px;margin:0 auto;padding:0 32px}
110
112
  .topbar .brand{display:flex;align-items:center;gap:8px;font-weight:700;font-size:15px;color:var(--text);letter-spacing:-.02em;flex-shrink:0}
111
113
  .topbar .brand .icon{width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-size:18px;background:none;border-radius:0}
112
114
  .topbar .brand .brand-title{font-size:13px;font-weight:500;opacity:.7}
@@ -192,50 +194,90 @@ input,textarea,select{font-family:inherit;font-size:inherit}
192
194
  .pending-user-name{font-size:14px;font-weight:700;color:var(--text)}
193
195
  .pending-user-meta{font-size:12px;color:var(--text-sec);margin-top:4px}
194
196
  .pending-user-actions{display:flex;gap:8px;margin-top:10px}
195
- /* ─── Admin Panel ─── */
196
- .admin-header{position:relative;padding:28px 28px 20px;background:linear-gradient(135deg,rgba(99,102,241,.08) 0%,rgba(139,92,246,.06) 50%,rgba(6,182,212,.05) 100%);border:1px solid rgba(99,102,241,.12);border-radius:16px;margin-bottom:20px;overflow:hidden}
197
- .admin-header::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--violet),var(--pri),var(--cyan));border-radius:16px 16px 0 0}
198
- .admin-header-top{display:flex;justify-content:space-between;align-items:center}
199
- .admin-header h2{font-size:20px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:10px;margin:0}
200
- .admin-header h2 .ah-icon{width:36px;height:36px;border-radius:10px;background:linear-gradient(135deg,var(--pri),var(--violet));display:flex;align-items:center;justify-content:center;font-size:18px;box-shadow:0 4px 12px rgba(99,102,241,.25)}
201
- .admin-header-sub{font-size:12px;color:var(--text-muted);margin-top:6px}
202
- .admin-stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:10px;margin-top:18px}
203
- .admin-stat-box{position:relative;text-align:center;padding:16px 8px 14px;background:var(--bg-card);border:1px solid var(--border);border-radius:12px;overflow:hidden;transition:all .2s}
204
- .admin-stat-box:hover{border-color:rgba(99,102,241,.25);transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.06)}
205
- .admin-stat-box::before{content:'';position:absolute;top:0;left:50%;transform:translateX(-50%);width:40px;height:2px;border-radius:0 0 4px 4px}
206
- .admin-stat-box:nth-child(1)::before{background:var(--green)}
207
- .admin-stat-box:nth-child(2)::before{background:var(--amber)}
208
- .admin-stat-box:nth-child(3)::before{background:var(--violet)}
209
- .admin-stat-box:nth-child(4)::before{background:var(--cyan)}
210
- .admin-stat-box:nth-child(5)::before{background:var(--pri)}
211
- .admin-stat-box:nth-child(6)::before{background:var(--rose)}
212
- .admin-stat-box .as-icon{font-size:16px;margin-bottom:4px;display:block}
213
- .admin-stat-box .val{font-size:22px;font-weight:800;color:var(--text);font-variant-numeric:tabular-nums}
214
- .admin-stat-box .lbl{font-size:10px;color:var(--text-muted);margin-top:3px;letter-spacing:.03em}
215
- .admin-tabs{display:flex;gap:4px;padding:4px;background:var(--bg);border:1px solid var(--border);border-radius:14px;overflow-x:auto;-webkit-overflow-scrolling:touch;margin-bottom:20px}
216
- .admin-tab{position:relative;display:flex;align-items:center;gap:6px;padding:9px 16px;border:none;background:transparent;color:var(--text-muted);font-size:13px;font-weight:500;cursor:pointer;border-radius:10px;transition:all .2s;white-space:nowrap;font-family:inherit}
217
- .admin-tab:hover{background:rgba(99,102,241,.06);color:var(--text)}
218
- .admin-tab.active{background:var(--bg-card);color:var(--text);font-weight:600;box-shadow:0 2px 8px rgba(0,0,0,.06),0 0 0 1px rgba(99,102,241,.1)}
197
+ /* ─── Admin Panel (Cyber) ─── */
198
+ @keyframes adminGlow{0%,100%{opacity:.6}50%{opacity:1}}
199
+ @keyframes adminPulse{0%{box-shadow:0 0 0 0 rgba(99,102,241,.4)}70%{box-shadow:0 0 0 8px rgba(99,102,241,0)}100%{box-shadow:0 0 0 0 rgba(99,102,241,0)}}
200
+ @keyframes adminScanline{0%{background-position:0 0}100%{background-position:0 100%}}
201
+ @keyframes adminSlideIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
202
+ @keyframes adminCountUp{from{opacity:0;transform:translateY(8px) scale(.8)}to{opacity:1;transform:translateY(0) scale(1)}}
203
+ @keyframes pendingPulse{0%,100%{border-color:rgba(251,191,36,.3)}50%{border-color:rgba(251,191,36,.7)}}
204
+ @keyframes dotBreathe{0%,100%{transform:scale(1);opacity:.8}50%{transform:scale(1.3);opacity:1}}
205
+ .admin-header{position:relative;padding:32px 32px 24px;background:linear-gradient(135deg,rgba(99,102,241,.06) 0%,rgba(6,182,212,.04) 50%,rgba(139,92,246,.06) 100%);border:1px solid rgba(99,102,241,.15);border-radius:20px;margin-bottom:24px;overflow:hidden}
206
+ .admin-header::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,var(--pri),var(--cyan),var(--violet),transparent);animation:adminGlow 3s ease-in-out infinite}
207
+ .admin-header::after{content:'';position:absolute;inset:0;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(99,102,241,.015) 2px,rgba(99,102,241,.015) 4px);background-size:100% 8px;animation:adminScanline 8s linear infinite;pointer-events:none}
208
+ .admin-header-top{display:flex;justify-content:space-between;align-items:center;position:relative;z-index:1}
209
+ .admin-header h2{font-size:18px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:12px;margin:0;letter-spacing:-.01em}
210
+ .admin-header h2 .ah-icon{width:40px;height:40px;border-radius:12px;background:linear-gradient(135deg,var(--pri),var(--violet));display:flex;align-items:center;justify-content:center;font-size:20px;box-shadow:0 4px 20px rgba(99,102,241,.3),inset 0 1px 0 rgba(255,255,255,.15);animation:adminPulse 2.5s infinite}
211
+ .admin-header-sub{font-size:11px;color:var(--text-muted);margin-top:6px;position:relative;z-index:1;letter-spacing:.02em}
212
+ .admin-stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-top:20px;position:relative;z-index:1}
213
+ .admin-stat-box{position:relative;text-align:center;padding:18px 10px 16px;background:rgba(var(--bg-card-rgb,30,30,40),.6);backdrop-filter:blur(12px);border:1px solid rgba(99,102,241,.12);border-radius:14px;overflow:hidden;transition:all .25s cubic-bezier(.4,0,.2,1)}
214
+ .admin-stat-box:hover{border-color:rgba(99,102,241,.35);transform:translateY(-2px);box-shadow:0 8px 24px rgba(99,102,241,.12)}
215
+ .admin-stat-box::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;opacity:.8}
216
+ .admin-stat-box:nth-child(1)::before{background:linear-gradient(90deg,transparent,#22c55e,transparent)}
217
+ .admin-stat-box:nth-child(2)::before{background:linear-gradient(90deg,transparent,#fbbf24,transparent)}
218
+ .admin-stat-box:nth-child(3)::before{background:linear-gradient(90deg,transparent,#8b5cf6,transparent)}
219
+ .admin-stat-box:nth-child(4)::before{background:linear-gradient(90deg,transparent,#06b6d4,transparent)}
220
+ .admin-stat-box:nth-child(5)::before{background:linear-gradient(90deg,transparent,#6366f1,transparent)}
221
+ .admin-stat-box:nth-child(6)::before{background:linear-gradient(90deg,transparent,#f43f5e,transparent)}
222
+ .admin-stat-box::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at 50% 0%,rgba(99,102,241,.06),transparent 70%);pointer-events:none}
223
+ .admin-stat-box .as-icon{font-size:18px;margin-bottom:6px;display:block;filter:drop-shadow(0 2px 4px rgba(0,0,0,.15))}
224
+ .admin-stat-box .val{font-size:26px;font-weight:800;color:var(--text);font-variant-numeric:tabular-nums;animation:adminCountUp .4s ease-out;text-shadow:0 0 20px rgba(99,102,241,.15)}
225
+ .admin-stat-box .lbl{font-size:10px;color:var(--text-muted);margin-top:4px;letter-spacing:.05em;text-transform:uppercase}
226
+ .admin-tabs{display:flex;gap:3px;padding:3px;background:rgba(99,102,241,.03);border:1px solid rgba(99,102,241,.1);border-radius:14px;overflow-x:auto;-webkit-overflow-scrolling:touch;margin-bottom:24px;backdrop-filter:blur(8px)}
227
+ .admin-tab{position:relative;display:flex;align-items:center;gap:6px;padding:10px 18px;border:none;background:transparent;color:var(--text-muted);font-size:13px;font-weight:500;cursor:pointer;border-radius:11px;transition:all .25s cubic-bezier(.4,0,.2,1);white-space:nowrap;font-family:inherit}
228
+ .admin-tab:hover{background:rgba(99,102,241,.08);color:var(--text)}
229
+ .admin-tab.active{background:linear-gradient(135deg,rgba(99,102,241,.12),rgba(139,92,246,.08));color:var(--text);font-weight:600;box-shadow:0 2px 12px rgba(99,102,241,.1),inset 0 1px 0 rgba(255,255,255,.05);border:1px solid rgba(99,102,241,.15)}
219
230
  .admin-tab .at-icon{font-size:14px;line-height:1}
220
- .admin-tab .at-count{font-size:10px;font-weight:700;padding:1px 6px;border-radius:8px;background:rgba(99,102,241,.1);color:var(--pri);min-width:18px;text-align:center}
221
- .admin-tab.active .at-count{background:rgba(99,102,241,.15)}
231
+ .admin-tab .at-count{font-size:10px;font-weight:700;padding:2px 7px;border-radius:8px;background:rgba(99,102,241,.12);color:var(--pri);min-width:18px;text-align:center;transition:all .2s}
232
+ .admin-tab.active .at-count{background:rgba(99,102,241,.2);box-shadow:0 0 8px rgba(99,102,241,.15)}
222
233
  .admin-panel{display:none}
223
- .admin-panel.active{display:block}
224
- .admin-card{border:1px solid var(--border);border-radius:12px;padding:16px 18px;background:var(--bg-card);margin-bottom:10px;transition:all .2s;position:relative;overflow:hidden}
234
+ .admin-panel.active{display:block;animation:adminSlideIn .3s ease-out}
235
+ .admin-card{border:1px solid var(--border);border-radius:14px;padding:18px 20px;background:var(--bg-card);margin-bottom:12px;transition:all .25s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden}
225
236
  .admin-card-clickable{cursor:pointer}
226
- .admin-card::before{content:'';position:absolute;top:0;left:0;bottom:0;width:3px;border-radius:3px 0 0 3px;background:var(--pri);opacity:.5}
227
- .admin-card:hover{border-color:rgba(99,102,241,.25);box-shadow:0 2px 12px rgba(0,0,0,.04);transform:translateY(-1px)}
237
+ .admin-card::before{content:'';position:absolute;top:0;left:0;bottom:0;width:3px;border-radius:3px 0 0 3px;background:var(--pri);opacity:.4;transition:opacity .2s}
238
+ .admin-card:hover{border-color:rgba(99,102,241,.25);box-shadow:0 4px 20px rgba(99,102,241,.06);transform:translateY(-1px)}
239
+ .admin-card:hover::before{opacity:.8}
228
240
  .admin-card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
229
241
  .admin-card-title{font-size:14px;font-weight:700;color:var(--text);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
230
242
  .admin-card-meta{font-size:12px;color:var(--text-muted);line-height:1.5}
231
- .au-contrib{display:flex;gap:16px;padding:10px 0;margin:6px 0;border-top:1px solid var(--border);border-bottom:1px solid var(--border)}
232
- .au-contrib-item{font-size:12px;color:var(--text-sec);display:flex;align-items:center;gap:4px}
233
- .au-contrib-num{font-size:18px;font-weight:700;line-height:1}
234
- .au-info{display:flex;flex-wrap:wrap;gap:6px 14px;padding:8px 0;font-size:12px}
235
- .au-info-item{color:var(--text-muted);white-space:nowrap}
243
+ .au-contrib{display:flex;gap:20px;padding:12px 0;margin:8px 0;border-top:1px solid rgba(99,102,241,.08);border-bottom:1px solid rgba(99,102,241,.08)}
244
+ .au-contrib-item{font-size:12px;color:var(--text-sec);display:flex;align-items:center;gap:5px}
245
+ .au-contrib-num{font-size:20px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}
246
+ .au-info{display:flex;flex-wrap:wrap;gap:6px 16px;padding:8px 0;font-size:11px}
247
+ .au-info-item{color:var(--text-muted);white-space:nowrap;display:inline-flex;align-items:center;gap:3px}
248
+ .au-status-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:5px;vertical-align:middle;flex-shrink:0;transition:all .3s}
249
+ .au-status-dot.online{background:#22c55e;box-shadow:0 0 8px rgba(34,197,94,.6),0 0 16px rgba(34,197,94,.2);animation:dotBreathe 2s ease-in-out infinite}
250
+ .au-status-dot.offline{background:#6b7280;box-shadow:none}
251
+ .au-status-text{font-size:11px;font-weight:600;letter-spacing:.02em}
252
+ .au-status-text.online{color:#22c55e;text-shadow:0 0 8px rgba(34,197,94,.3)}
253
+ .au-status-text.offline{color:#6b7280}
254
+ .au-group-header{font-size:13px;font-weight:700;color:var(--text-sec);margin:20px 0 10px;display:flex;align-items:center;gap:8px;letter-spacing:.02em}
255
+ .au-group-header:first-child{margin-top:0}
256
+ .au-group-header .au-group-dot{display:inline-block;width:8px;height:8px;border-radius:50%}
257
+ .au-group-header .au-group-dot.online{background:#22c55e;box-shadow:0 0 6px rgba(34,197,94,.5)}
258
+ .au-group-header .au-group-dot.offline{background:#6b7280}
259
+ .au-group-header .au-group-count{font-size:12px;font-weight:400;color:var(--text-muted)}
260
+ .au-card.au-offline{opacity:.55}
261
+ .au-card.au-offline:hover{opacity:.8}
262
+ .au-card.au-offline::before{background:#6b7280}
263
+ .au-card.au-online::before{background:#22c55e;box-shadow:0 0 8px rgba(34,197,94,.3)}
264
+ .admin-pending-section{position:relative;margin-bottom:20px;padding:20px;border-radius:16px;border:1px solid rgba(251,191,36,.25);background:linear-gradient(135deg,rgba(251,191,36,.04),rgba(245,158,11,.02));animation:pendingPulse 3s ease-in-out infinite;overflow:hidden}
265
+ .admin-pending-section::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,#fbbf24,#f59e0b,transparent)}
266
+ .admin-pending-section h3{font-size:14px;font-weight:700;color:#fbbf24;margin:0 0 14px;display:flex;align-items:center;gap:8px}
267
+ .admin-pending-section h3 .pending-count{display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:22px;padding:0 6px;border-radius:11px;background:rgba(251,191,36,.2);font-size:12px;font-weight:800;color:#fbbf24}
268
+ .admin-pending-card{border:1px solid rgba(251,191,36,.15);border-radius:12px;padding:16px 18px;background:rgba(251,191,36,.03);margin-bottom:8px;transition:all .2s;position:relative;overflow:hidden}
269
+ .admin-pending-card::before{content:'';position:absolute;top:0;left:0;bottom:0;width:3px;background:linear-gradient(180deg,#fbbf24,#f59e0b);border-radius:3px 0 0 3px}
270
+ .admin-pending-card:hover{border-color:rgba(251,191,36,.35);box-shadow:0 4px 16px rgba(251,191,36,.08)}
271
+ .admin-pending-card .apc-name{font-size:14px;font-weight:700;color:var(--text)}
272
+ .admin-pending-card .apc-meta{font-size:11px;color:var(--text-muted);margin-top:4px;display:flex;align-items:center;gap:6px}
273
+ .admin-pending-card .apc-actions{display:flex;gap:8px;margin-top:12px}
274
+ .admin-pending-card .apc-actions .btn-approve{background:linear-gradient(135deg,#22c55e,#16a34a);color:#fff;border:none;padding:6px 18px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;transition:all .2s;box-shadow:0 2px 8px rgba(34,197,94,.25)}
275
+ .admin-pending-card .apc-actions .btn-approve:hover{transform:translateY(-1px);box-shadow:0 4px 16px rgba(34,197,94,.35)}
276
+ .admin-pending-card .apc-actions .btn-reject{background:transparent;color:var(--rose);border:1px solid rgba(244,63,94,.2);padding:6px 14px;border-radius:8px;font-size:12px;font-weight:500;cursor:pointer;transition:all .2s}
277
+ .admin-pending-card .apc-actions .btn-reject:hover{background:rgba(244,63,94,.06);border-color:rgba(244,63,94,.4)}
236
278
  .admin-card-tags{display:flex;gap:5px;margin:8px 0;align-items:center}
237
279
  .admin-card-tags-left{display:flex;flex-wrap:wrap;gap:5px;align-items:center;flex:1;min-width:0}
238
- .admin-card-tag{display:inline-flex;align-items:center;gap:3px;padding:2px 8px;border-radius:6px;font-size:11px;font-weight:500}
280
+ .admin-card-tag{display:inline-flex;align-items:center;gap:3px;padding:3px 10px;border-radius:6px;font-size:11px;font-weight:600;letter-spacing:.01em}
239
281
  .admin-card-tag.tag-role{background:rgba(99,102,241,.1);color:var(--pri)}
240
282
  .admin-card-tag.tag-kind{background:rgba(245,158,11,.1);color:#f59e0b}
241
283
  .admin-card-tag.tag-owner{background:rgba(34,197,94,.1);color:#22c55e}
@@ -243,16 +285,16 @@ input,textarea,select{font-family:inherit;font-size:inherit}
243
285
  .admin-card-tag.tag-version{background:rgba(139,92,246,.1);color:#8b5cf6}
244
286
  .admin-card-tag.tag-visibility{background:rgba(99,102,241,.08);color:var(--pri)}
245
287
  .admin-card-tag.tag-group{background:rgba(139,92,246,.08);color:#8b5cf6}
246
- .admin-card-preview{font-size:12px;color:var(--text-sec);line-height:1.5;margin:8px 0;padding:8px 10px;background:var(--bg);border-radius:8px;border:1px solid var(--border);max-height:60px;overflow:hidden;white-space:pre-wrap;word-break:break-all}
247
- .admin-card-actions{display:inline-flex;gap:4px;margin-left:auto;align-items:center;flex-shrink:0}
288
+ .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:60px;overflow:hidden;white-space:pre-wrap;word-break:break-all}
289
+ .admin-card-actions{display:inline-flex;gap:6px;margin-left:auto;align-items:center;flex-shrink:0}
248
290
  .admin-card-time{font-size:11px;color:var(--text-muted)}
249
- .admin-card-detail{display:none;margin-top:0;padding:20px 24px 24px;border-top:1px dashed var(--border);background:linear-gradient(180deg,rgba(99,102,241,.015) 0%,var(--bg) 60%);animation:adminDetailIn .25s ease}
291
+ .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}
250
292
  @keyframes adminDetailIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}
251
293
  .admin-card.expanded .admin-card-detail{display:block}
252
- .admin-card.expanded{border-color:rgba(99,102,241,.3);box-shadow:0 4px 24px rgba(99,102,241,.08),0 0 0 1px rgba(99,102,241,.06)}
294
+ .admin-card.expanded{border-color:rgba(99,102,241,.3);box-shadow:0 4px 24px rgba(99,102,241,.1),0 0 0 1px rgba(99,102,241,.08)}
253
295
  .admin-card-detail-meta{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:18px;font-size:12px;color:var(--text-muted)}
254
- .admin-card-detail-meta .meta-item{display:inline-flex;align-items:center;gap:4px;padding:5px 12px;background:var(--bg-card);border:1px solid var(--border);border-radius:20px;font-size:11px;color:var(--text-sec);transition:border-color .15s,background .15s}
255
- .admin-card-detail-meta .meta-item:hover{border-color:rgba(99,102,241,.25);background:rgba(99,102,241,.03)}
296
+ .admin-card-detail-meta .meta-item{display:inline-flex;align-items:center;gap:4px;padding:5px 12px;background:rgba(99,102,241,.03);border:1px solid rgba(99,102,241,.08);border-radius:20px;font-size:11px;color:var(--text-sec);transition:all .2s}
297
+ .admin-card-detail-meta .meta-item:hover{border-color:rgba(99,102,241,.25);background:rgba(99,102,241,.06)}
256
298
  .admin-card-detail-section{margin-top:20px}
257
299
  .admin-card-detail-section .detail-label{font-size:11px;font-weight:700;color:var(--pri);margin-bottom:12px;text-transform:uppercase;letter-spacing:.06em;display:flex;align-items:center;gap:8px}
258
300
  .admin-card-detail-section .detail-label::before{content:'';width:3px;height:14px;border-radius:2px;background:linear-gradient(180deg,var(--pri),rgba(99,102,241,.3))}
@@ -286,18 +328,20 @@ input,textarea,select{font-family:inherit;font-size:inherit}
286
328
  .admin-toolbar{display:flex;align-items:center;gap:10px;margin-bottom:14px;flex-wrap:wrap}
287
329
  .admin-toolbar h3{font-size:14px;font-weight:600;color:var(--text);white-space:nowrap;margin:0;margin-right:auto;line-height:32px}
288
330
  .admin-toolbar select{box-sizing:border-box;height:32px;font-size:12px;border:1px solid var(--border);border-radius:8px;background:var(--bg-card);color:var(--text);vertical-align:middle;margin:0;padding:0 10px}
289
- .admin-badge{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:700;padding:3px 10px;border-radius:999px;letter-spacing:.02em}
290
- .admin-badge.admin{background:rgba(52,199,89,.15);color:#34c759}
291
- .admin-badge.member{background:rgba(142,142,147,.12);color:var(--text-muted)}
292
- .admin-badge.pending{background:rgba(255,159,10,.15);color:#ff9f0a}
331
+ .admin-badge{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:700;padding:3px 10px;border-radius:999px;letter-spacing:.03em;text-transform:uppercase}
332
+ .admin-badge.admin{background:linear-gradient(135deg,rgba(34,197,94,.15),rgba(16,185,129,.1));color:#22c55e;box-shadow:0 0 8px rgba(34,197,94,.1)}
333
+ .admin-badge.member{background:rgba(99,102,241,.08);color:var(--text-muted)}
334
+ .admin-badge.pending{background:linear-gradient(135deg,rgba(251,191,36,.2),rgba(245,158,11,.1));color:#fbbf24;box-shadow:0 0 8px rgba(251,191,36,.1)}
293
335
  .admin-badge.public{background:rgba(99,102,241,.1);color:var(--pri)}
294
336
  .admin-badge.group{background:rgba(139,92,246,.1);color:var(--violet)}
295
- .admin-empty{font-size:13px;color:var(--text-muted);padding:40px 20px;text-align:center;border:1px dashed var(--border);border-radius:12px;background:var(--bg)}
296
- .admin-empty .ae-icon{font-size:28px;display:block;margin-bottom:8px;opacity:.5}
337
+ .admin-empty{font-size:13px;color:var(--text-muted);padding:48px 24px;text-align:center;border:1px dashed rgba(99,102,241,.15);border-radius:16px;background:rgba(99,102,241,.02)}
338
+ .admin-empty .ae-icon{font-size:32px;display:block;margin-bottom:10px;opacity:.4}
297
339
  [data-theme="light"] .admin-badge.admin{background:rgba(5,150,105,.1);color:#059669}
298
340
  [data-theme="light"] .admin-badge.member{background:rgba(0,0,0,.06);color:#6b7280}
299
341
  [data-theme="light"] .admin-badge.pending{background:rgba(245,158,11,.1);color:#d97706}
300
- [data-theme="light"] .admin-header{background:linear-gradient(135deg,rgba(99,102,241,.05) 0%,rgba(139,92,246,.04) 50%,rgba(6,182,212,.03) 100%)}
342
+ [data-theme="light"] .admin-header{background:linear-gradient(135deg,rgba(99,102,241,.04) 0%,rgba(6,182,212,.03) 50%,rgba(139,92,246,.04) 100%)}
343
+ [data-theme="light"] .admin-stat-box{background:rgba(255,255,255,.8)}
344
+ [data-theme="light"] .admin-pending-section{background:linear-gradient(135deg,rgba(251,191,36,.03),rgba(245,158,11,.015))}
301
345
  .confirm-overlay{display:none;position:fixed;inset:0;align-items:center;justify-content:center;background:rgba(0,0,0,.45);backdrop-filter:blur(4px);z-index:9999;padding:20px}
302
346
  .confirm-overlay.show{display:flex}
303
347
  .confirm-panel{width:min(400px,90vw);background:var(--bg-card);border:1px solid var(--border);border-radius:16px;box-shadow:0 20px 60px rgba(0,0,0,.2);overflow:hidden;animation:confirmIn .2s ease}
@@ -539,8 +583,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
539
583
  .pagination .pg-info{font-size:12px;color:var(--text-sec);padding:0 12px}
540
584
 
541
585
  /* ─── Tasks 视图 ─── */
542
- .tasks-view{display:none;flex:1;min-width:0;flex-direction:column;gap:16px}
543
- .tasks-view.show{display:flex}
586
+ .view-container{flex:1;min-width:0}
587
+ .view-container>.vp{display:none;flex-direction:column}
588
+ .view-container>.vp.show{display:flex}
589
+ .tasks-view{flex:1;min-width:0;flex-direction:column;gap:16px}
544
590
  .tasks-header{display:flex;flex-direction:column;gap:14px}
545
591
  .tasks-stats{display:flex;gap:16px}
546
592
  .tasks-stat{display:flex;align-items:center;gap:8px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:12px 18px;flex:1;transition:all .2s}
@@ -618,8 +664,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
618
664
  [data-theme="light"] .tasks-stat{background:#fff}
619
665
 
620
666
  /* ─── Skills ─── */
621
- .skills-view{display:none;flex:1;min-width:0;flex-direction:column;gap:16px}
622
- .skills-view.show{display:flex}
667
+ .skills-view{flex:1;min-width:0;flex-direction:column;gap:16px}
623
668
  .skill-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:18px 20px;cursor:pointer;transition:all .25s;position:relative;overflow:hidden}
624
669
  .skill-card:hover{border-color:var(--border-glow);background:var(--bg-card-hover);transform:translateY(-1px);box-shadow:var(--shadow)}
625
670
  .skill-card::before{content:'';position:absolute;top:0;left:0;bottom:0;width:3px;border-radius:3px 0 0 3px;background:var(--violet)}
@@ -693,9 +738,8 @@ input,textarea,select{font-family:inherit;font-size:inherit}
693
738
  .nav-tabs .tab.active{color:var(--text);background:rgba(255,255,255,.1);border-color:var(--border);box-shadow:0 1px 4px rgba(0,0,0,.15)}
694
739
  [data-theme="light"] .nav-tabs{background:rgba(0,0,0,.05)}
695
740
  [data-theme="light"] .nav-tabs .tab.active{background:#fff;border-color:rgba(0,0,0,.1);box-shadow:0 1px 3px rgba(0,0,0,.08);color:var(--text)}
696
- .analytics-view,.settings-view,.logs-view,.migrate-view,.admin-view{display:none;flex:1;min-width:0;flex-direction:column;gap:20px}
697
- .analytics-view.show,.settings-view.show,.logs-view.show,.migrate-view.show,.admin-view.show{display:flex}
698
- .feed-wrap,.tasks-view,.skills-view,.analytics-view,.settings-view,.logs-view,.migrate-view,.admin-view{max-width:960px}
741
+ .analytics-view,.settings-view,.logs-view,.migrate-view,.admin-view{flex:1;min-width:0;flex-direction:column;gap:20px}
742
+ .feed-wrap,.tasks-view,.skills-view,.analytics-view,.settings-view,.logs-view,.migrate-view,.admin-view{max-width:960px;margin:0 auto}
699
743
 
700
744
  /* ─── Logs ─── */
701
745
  .logs-toolbar{display:flex;align-items:center;justify-content:space-between;padding:8px 0}
@@ -897,7 +941,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
897
941
  .test-result.ok{color:#22c55e}
898
942
  .test-result.fail{color:var(--rose)}
899
943
  .test-result.loading{color:var(--text-muted)}
900
- .settings-actions{display:flex;gap:12px;justify-content:flex-end;align-items:center;margin-top:20px;padding-top:16px;border-top:1px solid var(--border);flex-wrap:nowrap}
944
+ .settings-actions{display:flex;gap:12px;justify-content:flex-end;align-items:center;margin-top:20px;padding-top:16px;flex-wrap:nowrap}
901
945
  .settings-actions .btn{flex:0 0 auto;min-width:0;padding:8px 24px;font-size:13px}
902
946
  .settings-actions .btn-primary{background:rgba(99,102,241,.08);color:var(--pri);border:1px solid rgba(99,102,241,.25);font-weight:600}
903
947
  .settings-actions .btn-primary:hover{background:rgba(99,102,241,.14);border-color:var(--pri)}
@@ -968,8 +1012,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
968
1012
  .migrate-log-item .log-meta .tag.error{background:rgba(239,68,68,.1);color:#ef4444}
969
1013
  .migrate-log-item .log-meta .tag.duplicate{background:rgba(245,158,11,.1);color:#f59e0b}
970
1014
  @keyframes migrateFadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
971
- .feed-wrap{flex:1;min-width:0;display:flex;flex-direction:column}
972
- .feed-wrap.hide{display:none}
1015
+ .feed-wrap{flex:1;min-width:0;flex-direction:column}
973
1016
  .analytics-view{flex-direction:column;gap:20px}
974
1017
  .analytics-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
975
1018
  .analytics-card{position:relative;overflow:hidden;border-radius:var(--radius-lg);padding:18px 16px;transition:all .2s ease;border:1px solid var(--border);background:var(--bg-card)}
@@ -1053,7 +1096,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1053
1096
  [data-theme="light"] .auth-theme-toggle .theme-icon-dark{display:none}
1054
1097
 
1055
1098
  @media(max-width:1100px){.analytics-cards{grid-template-columns:repeat(3,1fr)}}
1056
- @media(max-width:900px){.main-content{flex-direction:column;padding:20px}.sidebar{width:100%}.sidebar .stats-grid{grid-template-columns:repeat(4,1fr)}.analytics-cards{grid-template-columns:repeat(2,1fr)}.topbar{padding:0 16px;gap:8px}.topbar .brand .brand-title{display:none}.topbar .brand .brand-powered{display:none}.topbar-center{justify-content:flex-start}}
1099
+ @media(max-width:900px){.main-content{flex-direction:column;padding:20px}.sidebar{width:100%}.sidebar .stats-grid{grid-template-columns:repeat(4,1fr)}.analytics-cards{grid-template-columns:repeat(2,1fr)}.topbar-inner{padding:0 16px;gap:8px}.topbar .brand .brand-title{display:none}.topbar .brand .brand-powered{display:none}.topbar-center{justify-content:flex-start}}
1057
1100
  </style>
1058
1101
  </head>
1059
1102
  <body>
@@ -1138,6 +1181,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1138
1181
  <!-- ─── Main App ─── -->
1139
1182
  <div class="app" id="app">
1140
1183
  <div class="topbar">
1184
+ <div class="topbar-inner">
1141
1185
  <div class="brand">
1142
1186
  <span class="memos-logo"><svg width="28" height="28" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="topLG" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#ff4d4d"/><stop offset="100%" stop-color="#991b1b"/></linearGradient></defs><path d="M60 10C30 10 15 35 15 55C15 75 30 95 45 100L45 110L55 110L55 100C55 100 60 102 65 100L65 110L75 110L75 100C90 95 105 75 105 55C105 35 90 10 60 10Z" fill="url(#topLG)"/><path d="M20 45C5 40 0 50 5 60C10 70 20 65 25 55C28 48 25 45 20 45Z" fill="url(#topLG)"/><path d="M100 45C115 40 120 50 115 60C110 70 100 65 95 55C92 48 95 45 100 45Z" fill="url(#topLG)"/><path d="M45 15Q35 5 30 8" stroke="#ff4d4d" stroke-width="2" stroke-linecap="round"/><path d="M75 15Q85 5 90 8" stroke="#ff4d4d" stroke-width="2" stroke-linecap="round"/><circle cx="45" cy="35" r="6" fill="#050810"/><circle cx="75" cy="35" r="6" fill="#050810"/><circle cx="46" cy="34" r="2" fill="#00e5cc"/><circle cx="76" cy="34" r="2" fill="#00e5cc"/></svg></span>
1143
1187
  <div class="brand-col"><span data-i18n="title" class="brand-title">MemOS</span><span data-i18n="subtitle" class="brand-powered">Powered by MemOS</span></div>${vBadge}
@@ -1164,10 +1208,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1164
1208
  <div class="notif-panel-body" id="notifPanelBody"><div class="notif-empty" data-i18n="notif.empty">No notifications</div></div>
1165
1209
  </div>
1166
1210
  </div>
1167
- <button class="btn btn-ghost btn-sm" onclick="loadAll()" data-i18n="refresh">\u21BB Refresh</button>
1168
1211
  <button class="btn btn-ghost btn-sm" onclick="doLogout()" data-i18n="logout">Logout</button>
1169
1212
  </div>
1170
1213
  </div>
1214
+ </div>
1171
1215
 
1172
1216
  <div class="main-content">
1173
1217
  <div class="sidebar" id="sidebar">
@@ -1189,18 +1233,19 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1189
1233
  <button class="btn btn-sm btn-ghost" style="width:100%;margin-top:20px;justify-content:center;color:var(--text-muted);font-size:11px" onclick="clearAll()" data-i18n="sidebar.clear">\u{1F5D1} Clear All Data</button>
1190
1234
  </div>
1191
1235
 
1192
- <div class="feed-wrap" id="feedWrap">
1236
+ <div class="view-container">
1237
+ <div class="feed-wrap vp show" id="feedWrap">
1193
1238
  <div class="feed">
1194
1239
  <div class="search-bar">
1195
1240
  <span class="search-icon">\u{1F50D}</span>
1196
1241
  <input type="text" id="searchInput" data-i18n-ph="search.placeholder" placeholder="Search memories (supports semantic search)..." oninput="debounceSearch()">
1197
1242
  <select id="filterOwner" class="filter-select" onchange="onOwnerFilterChange()">
1198
- <option value="" data-i18n="filter.allowners">All owners</option>
1199
- <option value="public" data-i18n="filter.public">Public</option>
1243
+ <option value="" data-i18n="filter.allagents">All agents</option>
1200
1244
  </select>
1201
1245
  <select id="memorySearchScope" class="filter-select" onchange="onMemoryScopeChange()">
1202
- <option value="local" data-i18n="scope.local">Local</option>
1203
- <option value="all" data-i18n="scope.hub">Team</option>
1246
+ <option value="local" data-i18n="scope.thisAgent">This Agent</option>
1247
+ <option value="allLocal" data-i18n="scope.thisDevice">This Device</option>
1248
+ <option value="hub" data-i18n="scope.hub">Team</option>
1204
1249
  </select>
1205
1250
  </div>
1206
1251
  <div class="search-meta" id="searchMeta"></div>
@@ -1229,7 +1274,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1229
1274
  <div class="pagination" id="pagination"></div>
1230
1275
  </div>
1231
1276
  </div>
1232
- <div class="tasks-view" id="tasksView">
1277
+ <div class="tasks-view vp" id="tasksView">
1233
1278
  <div class="tasks-header">
1234
1279
  <div class="tasks-stats">
1235
1280
  <div class="tasks-stat"><span class="tasks-stat-value" id="tasksTotalCount">-</span><span class="tasks-stat-label" data-i18n="tasks.total">Total Tasks</span></div>
@@ -1243,10 +1288,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1243
1288
  <button class="filter-chip" data-task-status="completed" onclick="setTaskStatusFilter(this,'completed')" data-i18n="tasks.status.completed">Completed</button>
1244
1289
  <button class="filter-chip" data-task-status="skipped" onclick="setTaskStatusFilter(this,'skipped')" data-i18n="tasks.status.skipped">Skipped</button>
1245
1290
  <select id="taskSearchScope" class="scope-select" onchange="onTaskScopeChange()">
1246
- <option value="local" data-i18n="scope.local">Local</option>
1247
- <option value="all" data-i18n="scope.hub">Team</option>
1291
+ <option value="local" data-i18n="scope.thisAgent">This Agent</option>
1292
+ <option value="allLocal" data-i18n="scope.thisDevice">This Device</option>
1293
+ <option value="hub" data-i18n="scope.hub">Team</option>
1248
1294
  </select>
1249
- <button class="btn btn-sm btn-ghost" onclick="loadTasks()" style="margin-left:auto" data-i18n="refresh">\u21BB Refresh</button>
1250
1295
  </div>
1251
1296
  </div>
1252
1297
  <div class="tasks-list" id="tasksList"><div class="spinner"></div></div>
@@ -1280,13 +1325,14 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1280
1325
  <div class="content" id="sharedMemoryContent"></div>
1281
1326
  </div>
1282
1327
  </div>
1283
- <div class="skills-view" id="skillsView">
1328
+ <div class="skills-view vp" id="skillsView">
1284
1329
  <div class="search-bar">
1285
1330
  <span class="search-icon">🔍</span>
1286
1331
  <input type="text" id="skillSearchInput" placeholder="Search skills..." data-i18n-ph="skills.search.placeholder" oninput="debounceSkillSearch()">
1287
1332
  <select id="skillSearchScope" class="scope-select" onchange="onSkillScopeChange()">
1288
- <option value="local" data-i18n="scope.local">Local</option>
1289
- <option value="all" data-i18n="scope.hub">Team</option>
1333
+ <option value="local" data-i18n="scope.thisAgent">This Agent</option>
1334
+ <option value="allLocal" data-i18n="scope.thisDevice">This Device</option>
1335
+ <option value="hub" data-i18n="scope.hub">Team</option>
1290
1336
  </select>
1291
1337
  </div>
1292
1338
  <div class="search-meta" id="skillSearchMeta"></div>
@@ -1309,7 +1355,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1309
1355
  <option value="public" data-i18n="filter.public">Public</option>
1310
1356
  <option value="private" data-i18n="filter.private">Private</option>
1311
1357
  </select>
1312
- <button class="btn btn-sm btn-ghost" onclick="loadSkills()" style="margin-left:auto" data-i18n="refresh">\u21BB Refresh</button>
1313
1358
  </div>
1314
1359
  </div>
1315
1360
  <div class="tasks-list" id="skillsList"><div class="spinner"></div></div>
@@ -1342,7 +1387,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1342
1387
  <div id="skillDetailActions" style="display:flex;gap:8px;margin-top:16px;padding-top:12px;border-top:1px solid var(--border)"></div>
1343
1388
  </div>
1344
1389
  </div>
1345
- <div class="analytics-view" id="analyticsView">
1390
+ <div class="analytics-view vp" id="analyticsView">
1346
1391
  <div class="metrics-toolbar" style="margin-bottom:0">
1347
1392
  <span style="font-size:12px;color:var(--text-sec);font-weight:600" data-i18n="range">Range</span>
1348
1393
  <button class="range-btn" data-days="7" onclick="setMetricsDays(7)">7 <span data-i18n="range.days">days</span></button>
@@ -1386,7 +1431,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1386
1431
  </div>
1387
1432
 
1388
1433
  <!-- ─── Logs View ─── -->
1389
- <div class="logs-view" id="logsView">
1434
+ <div class="logs-view vp" id="logsView">
1390
1435
  <div class="logs-toolbar">
1391
1436
  <div class="logs-toolbar-left">
1392
1437
  <select id="logToolFilter" onchange="onLogFilterChange()" style="font-size:12px;padding:4px 8px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);min-width:120px">
@@ -1403,7 +1448,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1403
1448
  </div>
1404
1449
 
1405
1450
  <!-- ─── Settings View ─── -->
1406
- <div class="settings-view" id="settingsView">
1451
+ <div class="settings-view vp" id="settingsView">
1407
1452
  <div class="settings-tabs-bar">
1408
1453
  <button class="settings-tab-btn active" data-tab="models" onclick="switchSettingsTab('models',this)"><span class="stab-dot"></span><span data-i18n="settings.models">AI Models</span></button>
1409
1454
  <button class="settings-tab-btn" data-tab="hub" onclick="switchSettingsTab('hub',this)"><span class="stab-dot"></span><span data-i18n="settings.hub">Team Sharing</span></button>
@@ -1569,7 +1614,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1569
1614
  </div>
1570
1615
  </div>
1571
1616
 
1572
- <div class="settings-card-divider"></div>
1573
1617
  <div class="settings-actions">
1574
1618
  <span class="settings-saved" id="modelsSaved">\u2713 <span data-i18n="settings.saved">Saved</span></span>
1575
1619
  <button class="btn btn-ghost" onclick="loadConfig()" data-i18n="settings.reset">Reset</button>
@@ -1658,6 +1702,11 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1658
1702
  <input type="text" id="cfgHubTeamName" placeholder="My Team">
1659
1703
  <div class="field-hint" data-i18n="settings.hub.teamName.hint">Your team display name</div>
1660
1704
  </div>
1705
+ <div class="settings-field">
1706
+ <label data-i18n="settings.hub.adminName">Admin Name</label>
1707
+ <input type="text" id="cfgHubAdminName" placeholder="" maxlength="32">
1708
+ <div class="field-hint" data-i18n="settings.hub.adminName.hint">Your display name as team admin</div>
1709
+ </div>
1661
1710
  </div>
1662
1711
  <div id="hubShareInfo" style="display:none;margin-top:14px;background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:14px 18px">
1663
1712
  <div style="font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:10px" data-i18n="settings.hub.shareInfo.title">Share this info with your team members:</div>
@@ -1686,6 +1735,11 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1686
1735
  <input type="text" id="cfgClientTeamToken" placeholder="">
1687
1736
  <div class="field-hint" data-i18n="settings.hub.teamTokenClient.hint">Get this from your team admin to join</div>
1688
1737
  </div>
1738
+ <div class="settings-field">
1739
+ <label data-i18n="settings.hub.nickname">Nickname</label>
1740
+ <input type="text" id="cfgClientNickname" placeholder="" maxlength="32">
1741
+ <div class="field-hint" data-i18n="settings.hub.nickname.hint">Your display name in the team. If empty, uses system username.</div>
1742
+ </div>
1689
1743
  <input type="hidden" id="cfgClientUserToken" value="">
1690
1744
  </div>
1691
1745
  <div style="margin-top:12px">
@@ -1740,7 +1794,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1740
1794
  </div>
1741
1795
  <div class="field-hint" style="margin-top:6px" data-i18n="settings.telemetry.hint">Anonymous usage analytics to help improve the plugin. Only sends tool names, latencies, and version info. No memory content, queries, or personal data is ever sent.</div>
1742
1796
 
1743
- <div class="settings-card-divider"></div>
1744
1797
  <div class="settings-actions">
1745
1798
  <span class="settings-saved" id="generalSaved">\u2713 <span data-i18n="settings.saved">Saved</span></span>
1746
1799
  <button class="btn btn-ghost" onclick="loadConfig()" data-i18n="settings.reset">Reset</button>
@@ -1755,13 +1808,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1755
1808
  </div>
1756
1809
 
1757
1810
  <!-- ─── Admin Page ─── -->
1758
- <div class="admin-view" id="adminView">
1811
+ <div class="admin-view vp" id="adminView">
1759
1812
  <div id="adminNotEnabled" style="display:none"></div>
1760
1813
  <div id="adminMainContent">
1761
1814
  <div class="admin-header">
1762
1815
  <div class="admin-header-top">
1763
- <h2><span class="ah-icon">\u{1F6E1}</span> <span data-i18n="admin.title">Team Admin Panel</span></h2>
1764
- <button class="btn btn-sm btn-ghost" onclick="loadAdminData()" style="backdrop-filter:blur(8px)" data-i18n="admin.refresh">\u21BB Refresh</button>
1816
+ <h2><span class="ah-icon">\u{26A1}</span> <span data-i18n="admin.title">Team Admin Panel</span></h2>
1765
1817
  </div>
1766
1818
  <div class="admin-header-sub" data-i18n="admin.subtitle">Manage team members and shared resources</div>
1767
1819
  <div class="admin-stat-row" id="adminStats"></div>
@@ -1780,7 +1832,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1780
1832
  </div>
1781
1833
 
1782
1834
  <!-- ─── Import Page ─── -->
1783
- <div class="migrate-view" id="migrateView">
1835
+ <div class="migrate-view vp" id="migrateView">
1784
1836
  <div class="settings-section" style="border:1px solid rgba(99,102,241,.15)">
1785
1837
  <h3><span class="icon">\u{1F4E5}</span> <span data-i18n="migrate.title">Import OpenClaw Memory</span></h3>
1786
1838
  <p style="font-size:12px;color:var(--text-sec);margin-bottom:12px;line-height:1.6" data-i18n="migrate.desc">Migrate your existing OpenClaw built-in memories and conversation history into this plugin. The import process uses smart deduplication to avoid duplicates.</p>
@@ -1950,6 +2002,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1950
2002
 
1951
2003
  </div>
1952
2004
 
2005
+ </div>
1953
2006
  </div>
1954
2007
  </div>
1955
2008
 
@@ -1973,7 +2026,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1973
2026
  <script>
1974
2027
  let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=40,metricsDays=30;
1975
2028
  let memorySearchScope='local',skillSearchScope='local',taskSearchScope='local';
2029
+ let _lastMemoriesFingerprint='',_lastTasksFingerprint='',_lastSkillsFingerprint='';
1976
2030
  let _embeddingWarningShown=false;
2031
+ let _currentAgentOwner='agent:main';
1977
2032
 
1978
2033
  /* ─── i18n ─── */
1979
2034
  const I18N={
@@ -2024,6 +2079,8 @@ const I18N={
2024
2079
  'skills.load.error':'Failed to load skills',
2025
2080
  'skills.hub.title':'\u{1F310} Team Skills',
2026
2081
  'scope.local':'Local',
2082
+ 'scope.thisAgent':'This Agent',
2083
+ 'scope.thisDevice':'This Device',
2027
2084
  'scope.group':'Group',
2028
2085
  'scope.all':'All',
2029
2086
  'skills.visibility.public':'Shared Locally',
@@ -2052,6 +2109,15 @@ const I18N={
2052
2109
  'notif.removed.memory':'Your shared memory was removed by admin',
2053
2110
  'notif.removed.task':'Your shared task was removed by admin',
2054
2111
  'notif.removed.skill':'Your shared skill was removed by admin',
2112
+ 'notif.shared.memory':'A new memory was shared',
2113
+ 'notif.shared.task':'A new task was shared',
2114
+ 'notif.shared.skill':'A new skill was shared',
2115
+ 'notif.unshared.memory':'A memory was unshared',
2116
+ 'notif.unshared.task':'A task was unshared',
2117
+ 'notif.unshared.skill':'A skill was unshared',
2118
+ 'notif.userJoin':'New user requests to join the team',
2119
+ 'notif.userOnline':'User came online',
2120
+ 'notif.userOffline':'User went offline',
2055
2121
  'notif.clearAll':'Clear all',
2056
2122
  'notif.timeAgo.just':'just now',
2057
2123
  'notif.timeAgo.min':'{n}m ago',
@@ -2075,6 +2141,7 @@ const I18N={
2075
2141
  'filter.newest':'Newest first',
2076
2142
  'filter.oldest':'Oldest first',
2077
2143
  'filter.allowners':'All owners',
2144
+ 'filter.allagents':'All agents',
2078
2145
  'filter.allsessions':'All sessions',
2079
2146
  'filter.public':'Public',
2080
2147
  'filter.private':'Private',
@@ -2323,6 +2390,8 @@ const I18N={
2323
2390
  'settings.hub.port.hint':'Port for team server. Default: 18800',
2324
2391
  'settings.hub.teamName':'Team Name',
2325
2392
  'settings.hub.teamName.hint':'Display name for your team',
2393
+ 'settings.hub.adminName':'Admin Name',
2394
+ 'settings.hub.adminName.hint':'Your display name as team admin',
2326
2395
  'settings.hub.teamToken':'Team Token',
2327
2396
  'settings.hub.teamToken.hint':'Auto-generated secret for clients to join. Click to copy. Share this with your team members.',
2328
2397
  'settings.hub.tokenCopied':'Team Token copied!',
@@ -2342,10 +2411,13 @@ const I18N={
2342
2411
  'settings.hub.hubAddress.hint':'Team server address, e.g. 192.168.1.100:18800',
2343
2412
  'settings.hub.teamTokenClient':'Team Token',
2344
2413
  'settings.hub.teamTokenClient.hint':'Get this from your team admin to join',
2414
+ 'settings.hub.nickname':'Nickname',
2415
+ 'settings.hub.nickname.hint':'Your display name in the team. If empty, uses system username.',
2345
2416
  'settings.hub.userToken':'User Token',
2346
2417
  'settings.hub.userToken.hint':'Usually auto-obtained after joining. Only fill if given by admin.',
2347
2418
  'settings.hub.testConnection':'Test Connection',
2348
2419
  'settings.hub.test.noAddr':'Please enter server address first',
2420
+ 'settings.hub.teamToken.required':'Please enter team token',
2349
2421
  'settings.hub.test.testing':'Testing...',
2350
2422
  'settings.hub.test.ok':'Connected successfully',
2351
2423
  'settings.hub.test.fail':'Connection failed',
@@ -2380,6 +2452,7 @@ const I18N={
2380
2452
  'memory.detail.viewTarget':'View target: ',
2381
2453
  'admin.title':'Team Admin Panel',
2382
2454
  'admin.subtitle':'Manage team members and shared resources',
2455
+ 'admin.subtitle.member':'Browse shared memories, tasks and skills from your team',
2383
2456
  'admin.refresh':'\u21BB Refresh',
2384
2457
  'admin.tab.users':'Users',
2385
2458
  'admin.tab.tasks':'Shared Tasks',
@@ -2395,6 +2468,12 @@ const I18N={
2395
2468
  'admin.pendingApproval':'Pending Approval',
2396
2469
  'admin.activeUsers':'Active Users',
2397
2470
  'admin.noActiveUsers':'No active users.',
2471
+ 'admin.onlineUsers':'Online',
2472
+ 'admin.offlineUsers':'Offline',
2473
+ 'admin.online':'Online',
2474
+ 'admin.offline':'Offline',
2475
+ 'admin.offlineFor':'Offline for {time}',
2476
+ 'admin.onlineNow':'Online now',
2398
2477
  'admin.approve':'Approve',
2399
2478
  'admin.reject':'Reject',
2400
2479
  'admin.device':'Device: ',
@@ -2411,6 +2490,7 @@ const I18N={
2411
2490
  'admin.demoteMember':'Demote to Member',
2412
2491
  'admin.editName':'Edit Name',
2413
2492
  'admin.lastAdminHint':'Last admin — cannot remove or demote',
2493
+ 'admin.ownerHint':'Hub owner — cannot be demoted or removed',
2414
2494
  'admin.editNamePrompt':'Enter new username:',
2415
2495
  'confirm.promoteAdmin':'Promote this user to admin? They will be able to manage all team members and resources.',
2416
2496
  'confirm.demoteMember':'Demote this admin to member?',
@@ -2471,6 +2551,7 @@ const I18N={
2471
2551
  'admin.groupsFailed':'Failed to load groups: ',
2472
2552
  'toast.userApproved':'User approved',
2473
2553
  'sharing.approved.toast':'Your join request has been approved!',
2554
+ 'sharing.rejected.toast':'Your join request was rejected by the admin.',
2474
2555
  'toast.userRejected':'User rejected',
2475
2556
  'toast.approveFail':'Approve failed',
2476
2557
  'toast.rejectFail':'Reject failed',
@@ -2631,6 +2712,8 @@ const I18N={
2631
2712
  'sharing.disable.confirm.hub':'You are about to shut down the team server.\\n\\nWhat will happen:\\n\\u2022 All connected team members will be disconnected\\n\\u2022 They will no longer be able to sync memories, tasks, or skills\\n\\u2022 Shared data is preserved and will be available when you re-enable\\n\\nAre you sure?',
2632
2713
  'sharing.disable.confirm.client':'You are about to disconnect from the team.\\n\\nWhat will happen:\\n\\u2022 You will no longer receive shared memories, tasks, or skills from the team\\n\\u2022 Your local data is preserved and will not be affected\\n\\u2022 You can reconnect later by re-enabling sharing\\n\\nAre you sure?',
2633
2714
  'sharing.disable.restartAlert':'Sharing has been disabled. Please restart the OpenClaw gateway for the change to take effect.\\n\\nRun: openclaw gateway stop && openclaw gateway start',
2715
+ 'sharing.switch.hubToClient':'You are about to switch from Server to Client mode.\\n\\nWhat will happen:\\n\\u2022 The Hub server will shut down after restart\\n\\u2022 All connected team members will be disconnected\\n\\u2022 Shared data on the Hub is preserved for future use\\n\\u2022 You will join the specified remote team as a client\\n\\nAre you sure?',
2716
+ 'sharing.switch.clientToHub':'You are about to switch from Client to Server mode.\\n\\nWhat will happen:\\n\\u2022 You will disconnect from the current team\\n\\u2022 A new Hub server will start after restart\\n\\u2022 Your local data is not affected\\n\\nAre you sure?',
2634
2717
  'admin.notEnabled.title':'Team sharing is not enabled',
2635
2718
  'admin.notEnabled.desc':'The Admin Panel is used to manage team members, shared memories, tasks, and skills. To use this feature, you need to enable team sharing first.',
2636
2719
  'admin.notEnabled.setupHub':'Set Up as Team Server',
@@ -2705,6 +2788,8 @@ const I18N={
2705
2788
  'skills.load.error':'加载技能失败',
2706
2789
  'skills.hub.title':'\u{1F310} 团队共享技能',
2707
2790
  'scope.local':'本地',
2791
+ 'scope.thisAgent':'当前智能体',
2792
+ 'scope.thisDevice':'本机全部',
2708
2793
  'scope.group':'团队',
2709
2794
  'scope.all':'全部',
2710
2795
  'skills.visibility.public':'本机共享',
@@ -2733,6 +2818,15 @@ const I18N={
2733
2818
  'notif.removed.memory':'你共享的记忆被管理员移除',
2734
2819
  'notif.removed.task':'你共享的任务被管理员移除',
2735
2820
  'notif.removed.skill':'你共享的技能被管理员移除',
2821
+ 'notif.shared.memory':'有新的记忆被共享',
2822
+ 'notif.shared.task':'有新的任务被共享',
2823
+ 'notif.shared.skill':'有新的技能被共享',
2824
+ 'notif.unshared.memory':'有记忆取消了共享',
2825
+ 'notif.unshared.task':'有任务取消了共享',
2826
+ 'notif.unshared.skill':'有技能取消了共享',
2827
+ 'notif.userJoin':'有新用户申请加入团队',
2828
+ 'notif.userOnline':'用户上线了',
2829
+ 'notif.userOffline':'用户下线了',
2736
2830
  'notif.clearAll':'清除全部',
2737
2831
  'notif.timeAgo.just':'刚刚',
2738
2832
  'notif.timeAgo.min':'{n}分钟前',
@@ -2756,6 +2850,7 @@ const I18N={
2756
2850
  'filter.newest':'最新优先',
2757
2851
  'filter.oldest':'最早优先',
2758
2852
  'filter.allowners':'所有归属',
2853
+ 'filter.allagents':'全部智能体',
2759
2854
  'filter.allsessions':'全部会话',
2760
2855
  'filter.public':'公开',
2761
2856
  'filter.private':'私有',
@@ -3004,6 +3099,8 @@ const I18N={
3004
3099
  'settings.hub.port.hint':'团队服务端口,默认 18800',
3005
3100
  'settings.hub.teamName':'团队名称',
3006
3101
  'settings.hub.teamName.hint':'你的团队显示名称',
3102
+ 'settings.hub.adminName':'管理员名称',
3103
+ 'settings.hub.adminName.hint':'你在团队中的显示名称',
3007
3104
  'settings.hub.teamToken':'团队令牌',
3008
3105
  'settings.hub.teamToken.hint':'自动生成的密钥,点击可复制。请将此令牌分享给团队成员。',
3009
3106
  'settings.hub.tokenCopied':'团队令牌已复制!',
@@ -3023,10 +3120,13 @@ const I18N={
3023
3120
  'settings.hub.hubAddress.hint':'团队服务器地址,如 192.168.1.100:18800',
3024
3121
  'settings.hub.teamTokenClient':'团队令牌',
3025
3122
  'settings.hub.teamTokenClient.hint':'向团队管理员获取此令牌以加入团队',
3123
+ 'settings.hub.nickname':'昵称',
3124
+ 'settings.hub.nickname.hint':'你在团队中的显示名称。留空则使用系统用户名。',
3026
3125
  'settings.hub.userToken':'用户令牌',
3027
3126
  'settings.hub.userToken.hint':'通常在加入团队后自动获取,无需手动填写。',
3028
3127
  'settings.hub.testConnection':'测试连接',
3029
3128
  'settings.hub.test.noAddr':'请先输入服务器地址',
3129
+ 'settings.hub.teamToken.required':'请输入团队令牌',
3030
3130
  'settings.hub.test.testing':'测试中...',
3031
3131
  'settings.hub.test.ok':'连接成功',
3032
3132
  'settings.hub.test.fail':'连接失败',
@@ -3061,6 +3161,7 @@ const I18N={
3061
3161
  'memory.detail.viewTarget':'查看目标: ',
3062
3162
  'admin.title':'团队管理面板',
3063
3163
  'admin.subtitle':'管理团队成员和共享资源',
3164
+ 'admin.subtitle.member':'浏览团队共享的记忆、任务和技能',
3064
3165
  'admin.refresh':'\u21BB 刷新',
3065
3166
  'admin.tab.users':'用户',
3066
3167
  'admin.tab.tasks':'共享任务',
@@ -3076,6 +3177,12 @@ const I18N={
3076
3177
  'admin.pendingApproval':'待审批',
3077
3178
  'admin.activeUsers':'活跃用户',
3078
3179
  'admin.noActiveUsers':'暂无活跃用户。',
3180
+ 'admin.onlineUsers':'在线',
3181
+ 'admin.offlineUsers':'离线',
3182
+ 'admin.online':'在线',
3183
+ 'admin.offline':'离线',
3184
+ 'admin.offlineFor':'离线 {time}',
3185
+ 'admin.onlineNow':'当前在线',
3079
3186
  'admin.approve':'批准',
3080
3187
  'admin.reject':'拒绝',
3081
3188
  'admin.device':'设备:',
@@ -3092,6 +3199,7 @@ const I18N={
3092
3199
  'admin.demoteMember':'降为成员',
3093
3200
  'admin.editName':'编辑名称',
3094
3201
  'admin.lastAdminHint':'唯一管理员 — 无法删除或降级',
3202
+ 'admin.ownerHint':'Hub 创建者 — 不可降级或移除',
3095
3203
  'admin.editNamePrompt':'请输入新用户名:',
3096
3204
  'confirm.promoteAdmin':'确定要将此用户提升为管理员吗?管理员可以管理所有团队成员和资源。',
3097
3205
  'confirm.demoteMember':'确定要将此管理员降为普通成员吗?',
@@ -3152,6 +3260,7 @@ const I18N={
3152
3260
  'admin.groupsFailed':'加载分组失败:',
3153
3261
  'toast.userApproved':'用户已批准',
3154
3262
  'sharing.approved.toast':'您的加入申请已通过审核!',
3263
+ 'sharing.rejected.toast':'您的加入申请已被管理员拒绝。',
3155
3264
  'toast.userRejected':'用户已拒绝',
3156
3265
  'toast.approveFail':'批准失败',
3157
3266
  'toast.rejectFail':'拒绝失败',
@@ -3312,6 +3421,8 @@ const I18N={
3312
3421
  'sharing.disable.confirm.hub':'你即将关闭团队服务。\\n\\n关闭后将会:\\n\\u2022 所有已连接的团队成员将断开连接\\n\\u2022 他们将无法继续同步记忆、任务和技能\\n\\u2022 已共享的数据会保留,重新开启后仍可使用\\n\\n确定要关闭吗?',
3313
3422
  'sharing.disable.confirm.client':'你即将断开与团队的连接。\\n\\n断开后将会:\\n\\u2022 你将无法再接收团队共享的记忆、任务和技能\\n\\u2022 你的本地数据不受影响,会完整保留\\n\\u2022 之后可以随时重新开启共享来恢复连接\\n\\n确定要断开吗?',
3314
3423
  'sharing.disable.restartAlert':'共享已关闭。请重启 OpenClaw 网关使更改生效。\\n\\n执行命令:openclaw gateway stop && openclaw gateway start',
3424
+ 'sharing.switch.hubToClient':'你即将从服务端模式切换为客户端模式。\\n\\n切换后将会:\\n\\u2022 Hub 服务将在重启后关闭\\n\\u2022 所有已连接的团队成员将断开连接\\n\\u2022 Hub 上的共享数据会保留,以后可恢复使用\\n\\u2022 你将作为客户端加入指定的远程团队\\n\\n确定要切换吗?',
3425
+ 'sharing.switch.clientToHub':'你即将从客户端模式切换为服务端模式。\\n\\n切换后将会:\\n\\u2022 你将断开与当前团队的连接\\n\\u2022 重启后将启动新的 Hub 服务\\n\\u2022 你的本地数据不受影响\\n\\n确定要切换吗?',
3315
3426
  'admin.notEnabled.title':'团队共享尚未开启',
3316
3427
  'admin.notEnabled.desc':'管理面板用于管理团队成员、共享的记忆、任务和技能。使用此功能前,需要先开启团队共享。',
3317
3428
  'admin.notEnabled.setupHub':'配置为团队服务端',
@@ -3549,59 +3660,49 @@ function switchSettingsTab(tab,btn){
3549
3660
  });
3550
3661
  }
3551
3662
 
3663
+ var _activeView='memories';
3552
3664
  function switchView(view){
3665
+ _activeView=view;
3553
3666
  document.querySelectorAll('.nav-tabs .tab').forEach(t=>t.classList.toggle('active',t.dataset.view===view));
3554
- const feedWrap=document.getElementById('feedWrap');
3555
- const analyticsView=document.getElementById('analyticsView');
3556
- const tasksView=document.getElementById('tasksView');
3557
- const skillsView=document.getElementById('skillsView');
3558
- const logsView=document.getElementById('logsView');
3559
- const settingsView=document.getElementById('settingsView');
3560
- const migrateView=document.getElementById('migrateView');
3561
- const adminView=document.getElementById('adminView');
3562
- const sidebar=document.getElementById('sidebar');
3563
- feedWrap.classList.add('hide');
3564
- analyticsView.classList.remove('show');
3565
- tasksView.classList.remove('show');
3566
- skillsView.classList.remove('show');
3567
- logsView.classList.remove('show');
3568
- settingsView.classList.remove('show');
3569
- migrateView.classList.remove('show');
3570
- if(adminView) adminView.classList.remove('show');
3667
+ var viewMap={memories:'feedWrap',tasks:'tasksView',skills:'skillsView',analytics:'analyticsView',logs:'logsView',settings:'settingsView',import:'migrateView',admin:'adminView'};
3668
+ for(var k in viewMap){
3669
+ var el=document.getElementById(viewMap[k]);
3670
+ if(el) el.classList.toggle('show',k===view);
3671
+ }
3571
3672
  var sessionSection=document.getElementById('sidebarSessionSection');
3572
- if(view==='memories'){
3573
- feedWrap.classList.remove('hide');
3574
- if(sessionSection){sessionSection.style.visibility='';sessionSection.style.pointerEvents='';}
3575
- } else if(view==='tasks'||view==='skills'){
3576
- if(sessionSection){sessionSection.style.visibility='hidden';sessionSection.style.pointerEvents='none';}
3577
- if(view==='tasks'){tasksView.classList.add('show');loadTasks();}
3578
- else{skillsView.classList.add('show');loadSkills();}
3579
- } else {
3580
- if(sessionSection){sessionSection.style.visibility='hidden';sessionSection.style.pointerEvents='none';}
3581
- if(view==='analytics'){
3582
- analyticsView.classList.add('show');
3583
- loadMetrics();
3584
- } else if(view==='logs'){
3585
- logsView.classList.add('show');
3586
- loadLogs();
3587
- } else if(view==='settings'){
3588
- settingsView.classList.add('show');
3589
- loadConfig();
3590
- loadModelHealth();
3591
- } else if(view==='import'){
3592
- migrateView.classList.add('show');
3593
- if(!window._migrateRunning) migrateScan(false);
3594
- } else if(view==='admin'){
3595
- if(adminView){adminView.classList.add('show');loadAdminData();}
3596
- }
3673
+ if(sessionSection){
3674
+ if(view==='memories'){sessionSection.style.visibility='';sessionSection.style.pointerEvents='';}
3675
+ else{sessionSection.style.visibility='hidden';sessionSection.style.pointerEvents='none';}
3597
3676
  }
3677
+ if(view==='tasks') loadTasks();
3678
+ else if(view==='skills') loadSkills();
3679
+ else if(view==='analytics') loadMetrics();
3680
+ else if(view==='logs') loadLogs();
3681
+ else if(view==='settings'){loadConfig();loadModelHealth();}
3682
+ else if(view==='import'){if(!window._migrateRunning) migrateScan(false);}
3683
+ else if(view==='admin'){loadAdminData();}
3598
3684
  }
3599
3685
 
3600
3686
  function onMemoryScopeChange(){
3601
3687
  memorySearchScope=document.getElementById('memorySearchScope')?.value||'local';
3688
+ currentPage=1;
3689
+ activeSession=null;activeRole='';
3690
+ _lastMemoriesFingerprint='';
3691
+ var isHub=memorySearchScope==='hub';
3692
+ var isLocal=memorySearchScope==='local';
3693
+ var ownerSel=document.getElementById('filterOwner');
3694
+ var filterBar=document.getElementById('filterBar');
3695
+ var dateFilter=document.querySelector('.date-filter');
3696
+ if(ownerSel) ownerSel.style.display=(isHub||isLocal)?'none':'';
3697
+ if(filterBar) filterBar.style.display=isHub?'none':'';
3698
+ if(dateFilter) dateFilter.style.display=isHub?'none':'';
3602
3699
  if(document.getElementById('searchInput').value.trim()) doSearch(document.getElementById('searchInput').value);
3603
- else if(memorySearchScope!=='local') { document.getElementById('sharingSearchMeta').textContent=''; loadHubMemories(); }
3604
- else { document.getElementById('sharingSearchMeta').textContent=''; loadMemories(); }
3700
+ else if(isHub) { document.getElementById('sharingSearchMeta').textContent=''; loadHubMemories(); }
3701
+ else {
3702
+ document.getElementById('sharingSearchMeta').textContent='';
3703
+ var ownerArg=isLocal?_currentAgentOwner:undefined;
3704
+ loadStats(ownerArg); loadMemories();
3705
+ }
3605
3706
  }
3606
3707
 
3607
3708
  function onSkillScopeChange(){
@@ -3616,6 +3717,7 @@ function onTaskScopeChange(){
3616
3717
  }
3617
3718
 
3618
3719
  var _clientPendingPollTimer=null;
3720
+ var _lastSharingConnStatus='';
3619
3721
  async function loadSharingStatus(forcePending){
3620
3722
  try{
3621
3723
  const r=await fetch('/api/sharing/status');
@@ -3625,13 +3727,26 @@ async function loadSharingStatus(forcePending){
3625
3727
  renderSharingSettings(d);
3626
3728
  updateTeamGuide(d);
3627
3729
  if(forcePending && d && d.admin && d.admin.canManageUsers) loadSharingPendingUsers();
3628
- var conn=d&&d.connection||{};
3629
- if(conn.pendingApproval&&!_clientPendingPollTimer){
3730
+ if(!d||!d.enabled){
3731
+ if(_clientPendingPollTimer){clearInterval(_clientPendingPollTimer);_clientPendingPollTimer=null;}
3732
+ _lastSharingConnStatus='';
3733
+ return;
3734
+ }
3735
+ var conn=d.connection||{};
3736
+ var curStatus=conn.rejected?'rejected':conn.pendingApproval?'pending':conn.connected?'connected':'none';
3737
+ if(_lastSharingConnStatus==='pending'&&curStatus==='rejected'){
3738
+ toast(t('sharing.rejected.toast'),'error');
3739
+ }
3740
+ if(_lastSharingConnStatus==='pending'&&curStatus==='connected'){
3741
+ toast(t('sharing.approved.toast'),'success');
3742
+ }
3743
+ _lastSharingConnStatus=curStatus;
3744
+ if(curStatus==='pending'&&!_clientPendingPollTimer){
3630
3745
  _clientPendingPollTimer=setInterval(function(){loadSharingStatus(false);},10000);
3631
- }else if(!conn.pendingApproval&&_clientPendingPollTimer){
3746
+ }
3747
+ if(curStatus!=='pending'&&_clientPendingPollTimer){
3632
3748
  clearInterval(_clientPendingPollTimer);
3633
3749
  _clientPendingPollTimer=null;
3634
- if(conn.connected) toast(t('sharing.approved.toast'),'success');
3635
3750
  }
3636
3751
  }catch(e){
3637
3752
  renderSharingSidebar(null);
@@ -3640,12 +3755,17 @@ async function loadSharingStatus(forcePending){
3640
3755
  }
3641
3756
  }
3642
3757
 
3758
+ var _lastSidebarFingerprint='';
3643
3759
  function renderSharingSidebar(data){
3644
3760
  var section=document.getElementById('sidebarSharingSection');
3645
3761
  var statusEl=document.getElementById('sharingSidebarStatus');
3646
3762
  var hintEl=document.getElementById('sharingSidebarHint');
3647
3763
  var badgeEl=document.getElementById('sharingSidebarConnBadge');
3648
3764
  if(!statusEl||!hintEl) return;
3765
+ var conn=data&&data.connection||{};
3766
+ var fp=JSON.stringify({e:!!data&&!!data.enabled,r:data&&data.role,pa:!!conn.pendingApproval,rj:!!conn.rejected,c:!!conn.connected,u:conn.user&&conn.user.username,tn:conn.teamName,cc:!!data&&!!data.clientConfigured,hu:data&&data.hubUrl});
3767
+ if(fp===_lastSidebarFingerprint) return;
3768
+ _lastSidebarFingerprint=fp;
3649
3769
  if(!data||!data.enabled){
3650
3770
  if(section) section.style.display='none';
3651
3771
  window._isHubAdmin=false;
@@ -3697,12 +3817,17 @@ function renderSharingSidebar(data){
3697
3817
  }
3698
3818
  }
3699
3819
 
3820
+ var _lastSettingsFingerprint='';
3700
3821
  function renderSharingSettings(data){
3701
3822
  var statusEl=document.getElementById('sharingStatusPanel');
3702
3823
  var teamEl=document.getElementById('sharingTeamPanel');
3703
3824
  var adminEl=document.getElementById('sharingAdminPanel');
3704
3825
  var panelsWrap=document.getElementById('sharingPanelsWrap');
3705
3826
  if(!statusEl||!teamEl||!adminEl) return;
3827
+ var conn2=data&&data.connection||{};
3828
+ var fp2=JSON.stringify({e:!!data&&!!data.enabled,r:data&&data.role,pa:!!conn2.pendingApproval,rj:!!conn2.rejected,c:!!conn2.connected,u:conn2.user&&conn2.user.username,ur:conn2.user&&conn2.user.role,tn:conn2.teamName,cc:!!data&&!!data.clientConfigured,cm:data&&data.admin&&data.admin.canManageUsers});
3829
+ if(fp2===_lastSettingsFingerprint) return;
3830
+ _lastSettingsFingerprint=fp2;
3706
3831
  if(!data||!data.enabled){
3707
3832
  statusEl.innerHTML='';teamEl.innerHTML='';adminEl.innerHTML='';
3708
3833
  if(panelsWrap) panelsWrap.style.display='none';
@@ -3719,8 +3844,12 @@ function renderSharingSettings(data){
3719
3844
  var hubAdminBtn=document.getElementById('hubAdminEntryBtn');
3720
3845
 
3721
3846
  if(actualRole==='hub'){
3722
- statusEl.innerHTML='';teamEl.innerHTML='';adminEl.innerHTML='';
3847
+ teamEl.innerHTML='';adminEl.innerHTML='';statusEl.innerHTML='';statusEl.style.display='none';
3723
3848
  if(hubAdminBtn) hubAdminBtn.style.display=isAdmin?'':'none';
3849
+ var hubUser=conn.user||{};
3850
+ var hubName=hubUser.username||'admin';
3851
+ var adminNameInput=document.getElementById('cfgHubAdminName');
3852
+ if(adminNameInput) adminNameInput.value=hubName;
3724
3853
  return;
3725
3854
  }
3726
3855
 
@@ -3912,6 +4041,9 @@ function guideGoToHub(role){
3912
4041
 
3913
4042
  /* ─── Hub Admin Panel ─── */
3914
4043
  var adminDataCache={users:[],groups:[],tasks:[],skills:[],memories:[]};
4044
+ var _lastAdminFingerprint='';
4045
+ var hubTasksCache=[];
4046
+ var hubSkillsCache=[];
3915
4047
  var ADMIN_PAGE_SIZE=20;
3916
4048
  var adminPage={users:0,tasks:0,skills:0,memories:0};
3917
4049
 
@@ -3933,12 +4065,9 @@ function adminPaginateHtml(total,page,refilterFn){
3933
4065
  }
3934
4066
 
3935
4067
  var _adminPollTimer=null;
3936
- function startAdminPoll(){
3937
- if(_adminPollTimer) return;
3938
- _adminPollTimer=setInterval(function(){pollAdminPending();},30000);
3939
- pollAdminPending();
3940
- }
4068
+ function startAdminPoll(){ startLivePoller(); }
3941
4069
  async function pollAdminPending(){
4070
+ if(!window._isHubAdmin) return;
3942
4071
  try{
3943
4072
  var r=await fetch('/api/sharing/pending-users');
3944
4073
  var d=await r.json();
@@ -3988,24 +4117,44 @@ async function loadAdminData(){
3988
4117
  }
3989
4118
  if(notEnabledEl) notEnabledEl.style.display='none';
3990
4119
  if(mainEl) mainEl.style.display='';
3991
- if(!window._isHubAdmin){
3992
- var statsEl=document.getElementById('adminStats');
3993
- if(statsEl) statsEl.innerHTML='<div class="admin-empty">'+t('admin.noPermission')+'</div>';
3994
- return;
3995
- }
4120
+ var isAdmin=!!window._isHubAdmin;
3996
4121
  try{
3997
- var [usersR,tasksR,skillsR,pendingR,memoriesR]=await Promise.all([
3998
- fetch('/api/sharing/users').then(function(r){return r.json();}),
3999
- fetch('/api/admin/shared-tasks').then(function(r){return r.json();}),
4000
- fetch('/api/admin/shared-skills').then(function(r){return r.json();}),
4001
- fetch('/api/sharing/pending-users').then(function(r){return r.json();}),
4002
- fetch('/api/admin/shared-memories').then(function(r){return r.json();})
4003
- ]);
4004
- adminDataCache.users=Array.isArray(usersR.users)?usersR.users:[];
4005
- adminDataCache.tasks=Array.isArray(tasksR.tasks)?tasksR.tasks:[];
4006
- adminDataCache.skills=Array.isArray(skillsR.skills)?skillsR.skills:[];
4007
- adminDataCache.memories=Array.isArray(memoriesR.memories)?memoriesR.memories:[];
4008
- var pending=Array.isArray(pendingR.users)?pendingR.users:[];
4122
+ var fetches;
4123
+ if(isAdmin){
4124
+ fetches=await Promise.all([
4125
+ fetch('/api/sharing/users').then(function(r){return r.json();}),
4126
+ fetch('/api/admin/shared-tasks').then(function(r){return r.json();}),
4127
+ fetch('/api/admin/shared-skills').then(function(r){return r.json();}),
4128
+ fetch('/api/sharing/pending-users').then(function(r){return r.json();}),
4129
+ fetch('/api/admin/shared-memories').then(function(r){return r.json();})
4130
+ ]);
4131
+ }else{
4132
+ fetches=await Promise.all([
4133
+ Promise.resolve({users:[]}),
4134
+ fetch('/api/sharing/tasks/list?limit=500').then(function(r){return r.json();}),
4135
+ fetch('/api/sharing/skills/list?limit=500').then(function(r){return r.json();}),
4136
+ Promise.resolve({users:[]}),
4137
+ fetch('/api/sharing/memories/list?limit=500').then(function(r){return r.json();})
4138
+ ]);
4139
+ }
4140
+ var usersR=fetches[0],tasksR=fetches[1],skillsR=fetches[2],pendingR=fetches[3],memoriesR=fetches[4];
4141
+ var _newUsers=Array.isArray(usersR.users)?usersR.users:[];
4142
+ var _newTasks=Array.isArray(tasksR.tasks)?tasksR.tasks:[];
4143
+ var _newSkills=Array.isArray(skillsR.skills)?skillsR.skills:[];
4144
+ var _newMemories=Array.isArray(memoriesR.memories)?memoriesR.memories:[];
4145
+ var pending=isAdmin?(Array.isArray(pendingR.users)?pendingR.users:[]):[];
4146
+ var _fp=_newUsers.length+':'+_newTasks.length+':'+_newSkills.length+':'+_newMemories.length+':'+pending.length
4147
+ +':'+_newUsers.map(function(u){return u.id+'|'+(u.isOnline?1:0)+'|'+(u.role||'')}).join(',')
4148
+ +':'+_newMemories.map(function(m){return m.id}).join(',')
4149
+ +':'+_newTasks.map(function(t){return t.id+'|'+(t.status||'')}).join(',')
4150
+ +':'+_newSkills.map(function(s){return s.id+'|'+(s.status||'')}).join(',')
4151
+ +':'+pending.map(function(p){return p.id}).join(',');
4152
+ if(_fp===_lastAdminFingerprint) return;
4153
+ _lastAdminFingerprint=_fp;
4154
+ adminDataCache.users=_newUsers;
4155
+ adminDataCache.tasks=_newTasks;
4156
+ adminDataCache.skills=_newSkills;
4157
+ adminDataCache.memories=_newMemories;
4009
4158
  adminDataCache._pending=pending;
4010
4159
  var badge=document.getElementById('adminPendingBadge');
4011
4160
  if(badge){if(pending.length>0){badge.textContent=pending.length;badge.style.display='inline';}else{badge.style.display='none';}}
@@ -4014,18 +4163,48 @@ async function loadAdminData(){
4014
4163
  renderAdminTasks(adminDataCache.tasks);
4015
4164
  renderAdminSkills(adminDataCache.skills);
4016
4165
  renderAdminMemories(adminDataCache.memories);
4166
+ updateAdminTabsVisibility();
4017
4167
  }catch(e){
4018
4168
  var statsEl=document.getElementById('adminStats');
4019
4169
  if(statsEl) statsEl.innerHTML='<div class="admin-empty">'+t('admin.loadFailed')+esc(String(e))+'</div>';
4020
4170
  }
4021
4171
  }
4172
+ function updateAdminTabsVisibility(){
4173
+ var bar=document.getElementById('adminTabsBar');
4174
+ if(!bar) return;
4175
+ var tabs=bar.querySelectorAll('.admin-tab');
4176
+ var isAdmin=!!window._isHubAdmin;
4177
+ tabs.forEach(function(tab){
4178
+ var onclick=tab.getAttribute('onclick')||'';
4179
+ if(onclick.indexOf("'users'")!==-1){
4180
+ tab.style.display=isAdmin?'':'none';
4181
+ }
4182
+ });
4183
+ if(!isAdmin){
4184
+ var usersPanel=document.getElementById('adminUsersPanel');
4185
+ if(usersPanel&&usersPanel.classList.contains('active')){
4186
+ usersPanel.classList.remove('active');
4187
+ var memPanel=document.getElementById('adminMemoriesPanel');
4188
+ if(memPanel) memPanel.classList.add('active');
4189
+ tabs.forEach(function(tab){
4190
+ tab.classList.remove('active');
4191
+ var onclick=tab.getAttribute('onclick')||'';
4192
+ if(onclick.indexOf("'memories'")!==-1) tab.classList.add('active');
4193
+ });
4194
+ }
4195
+ }
4196
+ var subEl=document.querySelector('.admin-header-sub');
4197
+ if(subEl) subEl.textContent=isAdmin?t('admin.subtitle'):t('admin.subtitle.member');
4198
+ }
4022
4199
 
4023
4200
  function renderAdminStats(pendingCount){
4024
4201
  var el=document.getElementById('adminStats');
4025
4202
  if(!el) return;
4203
+ var isAdmin=!!window._isHubAdmin;
4204
+ var onlineCount=adminDataCache.users.filter(function(u){return !!u.isOnline;}).length;
4026
4205
  el.innerHTML=
4027
- '<div class="admin-stat-box"><span class="as-icon">\u{1F465}</span><div class="val">'+adminDataCache.users.length+'</div><div class="lbl">'+t('admin.stat.activeUsers')+'</div></div>'+
4028
- '<div class="admin-stat-box"><span class="as-icon">\u{23F3}</span><div class="val">'+pendingCount+'</div><div class="lbl">'+t('admin.stat.pending')+'</div></div>'+
4206
+ (isAdmin?'<div class="admin-stat-box"><span class="as-icon">\u{1F465}</span><div class="val">'+onlineCount+' / '+adminDataCache.users.length+'</div><div class="lbl">'+t('admin.stat.activeUsers')+'</div></div>'+
4207
+ '<div class="admin-stat-box"><span class="as-icon">\u{23F3}</span><div class="val">'+pendingCount+'</div><div class="lbl">'+t('admin.stat.pending')+'</div></div>':'')+
4029
4208
  '<div class="admin-stat-box"><span class="as-icon">\u{1F4AD}</span><div class="val">'+(adminDataCache.memories||[]).length+'</div><div class="lbl">'+t('admin.stat.sharedMemories')+'</div></div>'+
4030
4209
  '<div class="admin-stat-box"><span class="as-icon">\u{1F4CB}</span><div class="val">'+adminDataCache.tasks.length+'</div><div class="lbl">'+t('admin.stat.sharedTasks')+'</div></div>'+
4031
4210
  '<div class="admin-stat-box"><span class="as-icon">\u{1F9E0}</span><div class="val">'+adminDataCache.skills.length+'</div><div class="lbl">'+t('admin.stat.sharedSkills')+'</div></div>';
@@ -4035,82 +4214,122 @@ function renderAdminStats(pendingCount){
4035
4214
  tc=document.getElementById('adminTabCountSkills');if(tc)tc.textContent=adminDataCache.skills.length;
4036
4215
  }
4037
4216
 
4217
+ function auRelativeTime(ts){
4218
+ if(!ts) return t('admin.neverActive');
4219
+ var diff=Date.now()-ts;
4220
+ if(diff<60000) return t('notif.timeAgo.just');
4221
+ if(diff<3600000) return t('notif.timeAgo.min').replace('{n}',Math.floor(diff/60000));
4222
+ if(diff<86400000) return t('notif.timeAgo.hour').replace('{n}',Math.floor(diff/3600000));
4223
+ return t('notif.timeAgo.day').replace('{n}',Math.floor(diff/86400000));
4224
+ }
4225
+
4226
+ function renderAdminUserCard(u,adminCount){
4227
+ var uid=escAttr(u.id);
4228
+ var uname=escAttr(u.username||'');
4229
+ var online=!!u.isOnline;
4230
+ var statusCls=online?'online':'offline';
4231
+
4232
+ var statusIndicator='<span class="au-status-dot '+statusCls+'"></span>';
4233
+ var statusLabel=online
4234
+ ?'<span class="au-status-text online">'+t('admin.onlineNow')+'</span>'
4235
+ :'<span class="au-status-text offline">'+auRelativeTime(u.lastActiveAt)+'</span>';
4236
+
4237
+ var titleDisplay='<span class="admin-card-title" id="au_name_'+uid+'">'+statusIndicator+esc(u.username||u.id)+
4238
+ ' <button onclick="adminStartEditName(this,&quot;'+uid+'&quot;,&quot;'+uname+'&quot;)" style="background:none;border:none;cursor:pointer;color:var(--text-muted);padding:2px;font-size:13px;vertical-align:middle;opacity:.5;transition:opacity .15s" onmouseenter="this.style.opacity=1" onmouseleave="this.style.opacity=.5" title="'+t('admin.editName')+'">\u270E</button></span>';
4239
+
4240
+ var editRow='<div id="au_edit_'+uid+'" style="display:none;align-items:center;gap:6px">'+
4241
+ '<input id="au_input_'+uid+'" type="text" value="'+uname+'" style="flex:1;padding:5px 10px;border:1px solid var(--pri);border-radius:8px;font-size:13px;font-weight:600;background:var(--bg);color:var(--text);outline:none;min-width:0" onkeydown="if(event.key===&quot;Enter&quot;)adminSaveEditName(&quot;'+uid+'&quot;);if(event.key===&quot;Escape&quot;)adminCancelEditName(&quot;'+uid+'&quot;)">'+
4242
+ '<button onclick="adminSaveEditName(&quot;'+uid+'&quot;)" class="btn btn-sm btn-primary" style="padding:4px 12px;font-size:11px;white-space:nowrap">\u2713</button>'+
4243
+ '<button onclick="adminCancelEditName(&quot;'+uid+'&quot;)" class="btn btn-sm btn-ghost" style="padding:4px 8px;font-size:11px;color:var(--text-muted)">\u2717</button></div>';
4244
+
4245
+ var mc=u.memoryCount||0, tc=u.taskCount||0, sc=u.skillCount||0;
4246
+ var contribHtml='<div class="au-contrib">'+
4247
+ '<span class="au-contrib-item"><span class="au-contrib-num" style="color:var(--pri)">'+mc+'</span> '+t('admin.contrib.memories')+'</span>'+
4248
+ '<span class="au-contrib-item"><span class="au-contrib-num" style="color:var(--green)">'+tc+'</span> '+t('admin.contrib.tasks')+'</span>'+
4249
+ '<span class="au-contrib-item"><span class="au-contrib-num" style="color:var(--amber)">'+sc+'</span> '+t('admin.contrib.skills')+'</span>'+
4250
+ '</div>';
4251
+
4252
+ var infoRows=[];
4253
+ if(u.deviceName) infoRows.push('<span class="au-info-item">\u{1F4BB} '+t('admin.device')+esc(u.deviceName)+'</span>');
4254
+ if(u.lastIp) infoRows.push('<span class="au-info-item">\u{1F310} '+t('admin.ip')+esc(u.lastIp)+'</span>');
4255
+ if(u.createdAt) infoRows.push('<span class="au-info-item">\u{1F4C5} '+t('admin.joined')+formatDateTimeSeconds(u.createdAt)+'</span>');
4256
+ if(u.approvedAt) infoRows.push('<span class="au-info-item">\u2705 '+t('admin.approved')+formatDateTimeSeconds(u.approvedAt)+'</span>');
4257
+ var lastAct=u.lastActiveAt||u.approvedAt||u.createdAt;
4258
+ infoRows.push('<span class="au-info-item">\u{1F552} '+t('admin.lastActive')+(lastAct?formatDateTimeSeconds(lastAct):t('admin.neverActive'))+'</span>');
4259
+ var infoHtml='<div class="au-info">'+infoRows.join('')+'</div>';
4260
+
4261
+ var actions='';
4262
+ if(u.isOwner){
4263
+ actions+='<span style="font-size:11px;color:var(--text-muted);padding:4px 0">'+t('admin.ownerHint')+'</span>';
4264
+ }else if(u.role!=='admin'){
4265
+ actions+='<button class="btn btn-sm btn-ghost" onclick="adminToggleRole(&quot;'+uid+'&quot;,&quot;admin&quot;)" style="color:var(--accent)">'+t('admin.promoteAdmin')+'</button>';
4266
+ actions+='<button class="btn btn-sm btn-ghost" onclick="adminRemoveUser(&quot;'+uid+'&quot;,&quot;'+uname+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>';
4267
+ }else if(adminCount>1){
4268
+ actions+='<button class="btn btn-sm btn-ghost" onclick="adminToggleRole(&quot;'+uid+'&quot;,&quot;member&quot;)">'+t('admin.demoteMember')+'</button>';
4269
+ actions+='<button class="btn btn-sm btn-ghost" onclick="adminRemoveUser(&quot;'+uid+'&quot;,&quot;'+uname+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>';
4270
+ }else{
4271
+ actions+='<span style="font-size:11px;color:var(--text-muted);padding:4px 0">'+t('admin.lastAdminHint')+'</span>';
4272
+ }
4273
+ var ownerBadge=u.isOwner?' <span style="font-size:9px;background:linear-gradient(135deg,#fbbf24,#f59e0b);color:#000;padding:2px 8px;border-radius:6px;font-weight:800;vertical-align:middle;margin-left:4px;letter-spacing:.04em;text-transform:uppercase;box-shadow:0 2px 8px rgba(251,191,36,.3)">Owner</span>':'';
4274
+
4275
+ return '<div class="admin-card au-card au-'+statusCls+'"><div class="admin-card-header"><div style="flex:1;min-width:0;display:flex;align-items:center;gap:8px;flex-wrap:wrap">'+
4276
+ '<div style="flex:1;min-width:0">'+titleDisplay+editRow+'</div>'+statusLabel+
4277
+ '</div>'+
4278
+ '<div style="display:flex;align-items:center;gap:6px"><span class="admin-badge '+(u.role==='admin'?'admin':'member')+'">'+esc(u.role||'member')+'</span>'+ownerBadge+'</div></div>'+
4279
+ contribHtml+infoHtml+
4280
+ (actions?'<div class="admin-card-actions" style="border-top:1px solid rgba(99,102,241,.08);padding-top:12px;margin-top:6px">'+actions+'</div>':'')+
4281
+ '</div>';
4282
+ }
4283
+
4038
4284
  function renderAdminUsers(users,pending){
4039
4285
  var el=document.getElementById('adminUsersPanel');
4040
4286
  if(!el) return;
4287
+ var isAdmin=!!window._isHubAdmin;
4288
+ if(!isAdmin){
4289
+ el.innerHTML='<div class="admin-empty" style="padding:32px;text-align:center;color:var(--text-sec)">'+t('admin.noPermission')+'</div>';
4290
+ return;
4291
+ }
4041
4292
  var html='';
4042
4293
  if(pending&&pending.length>0){
4043
- html+='<div style="margin-bottom:16px"><h3 style="font-size:14px;font-weight:600;color:var(--amber);margin-bottom:10px">'+t('admin.pendingApproval')+' ('+pending.length+')</h3>';
4294
+ html+='<div class="admin-pending-section"><h3>'+t('admin.pendingApproval')+' <span class="pending-count">'+pending.length+'</span></h3>';
4044
4295
  for(var p=0;p<pending.length;p++){
4045
4296
  var pu=pending[p];
4046
- html+='<div class="admin-card"><div class="admin-card-header"><div class="admin-card-title">'+esc(pu.username||pu.id||'Unknown')+'</div><span class="admin-badge pending">pending</span></div>'+
4047
- '<div class="admin-card-meta">'+t('admin.device')+esc(pu.deviceName||'unknown')+'</div>'+
4048
- '<div class="admin-card-actions">'+
4049
- '<button class="btn btn-sm btn-primary" onclick="adminApproveUser(&quot;'+escAttr(pu.id)+'&quot;,&quot;'+escAttr(pu.username||'')+'&quot;)">'+t('admin.approve')+'</button>'+
4050
- '<button class="btn btn-sm btn-ghost" onclick="adminRejectUser(&quot;'+escAttr(pu.id)+'&quot;)" style="color:var(--rose)">'+t('admin.reject')+'</button>'+
4297
+ html+='<div class="admin-pending-card">'+
4298
+ '<div class="apc-name">'+esc(pu.username||pu.id||'Unknown')+'</div>'+
4299
+ '<div class="apc-meta"><span>\u{1F4BB} '+esc(pu.deviceName||'unknown')+'</span>'+(pu.createdAt?'<span>\u{1F552} '+formatDateTimeSeconds(pu.createdAt)+'</span>':'')+'</div>'+
4300
+ '<div class="apc-actions">'+
4301
+ '<button class="btn-approve" onclick="adminApproveUser(&quot;'+escAttr(pu.id)+'&quot;,&quot;'+escAttr(pu.username||'')+'&quot;)">'+t('admin.approve')+'</button>'+
4302
+ '<button class="btn-reject" onclick="adminRejectUser(&quot;'+escAttr(pu.id)+'&quot;)">'+t('admin.reject')+'</button>'+
4051
4303
  '</div></div>';
4052
4304
  }
4053
4305
  html+='</div>';
4054
4306
  }
4055
- html+='<h3 style="font-size:14px;font-weight:600;color:var(--text);margin-bottom:10px">'+t('admin.activeUsers')+' ('+users.length+')</h3>';
4056
- if(users.length===0){
4307
+
4308
+ var onlineUsers=users.filter(function(u){return !!u.isOnline;});
4309
+ var offlineUsers=users.filter(function(u){return !u.isOnline;});
4310
+ offlineUsers.sort(function(a,b){return (b.lastActiveAt||0)-(a.lastActiveAt||0);});
4311
+ var sorted=onlineUsers.concat(offlineUsers);
4312
+ var adminCount=users.filter(function(x){return x.role==='admin';}).length;
4313
+
4314
+ if(sorted.length===0){
4057
4315
  html+='<div class="admin-empty"><span class="ae-icon">\u{1F465}</span>'+t('admin.noActiveUsers')+'</div>';
4058
4316
  }else{
4059
- var totalUsers=users.length;
4060
- var usersPages=Math.ceil(totalUsers/ADMIN_PAGE_SIZE);
4061
- if(adminPage.users>=usersPages) adminPage.users=Math.max(0,usersPages-1);
4062
- var usersStart=adminPage.users*ADMIN_PAGE_SIZE;
4063
- var usersEnd=Math.min(usersStart+ADMIN_PAGE_SIZE,totalUsers);
4064
- var adminCount=users.filter(function(x){return x.role==='admin';}).length;
4065
- for(var i=usersStart;i<usersEnd;i++){
4066
- var u=users[i];
4067
- var uid=escAttr(u.id);
4068
- var uname=escAttr(u.username||'');
4069
-
4070
- var titleDisplay='<span class="admin-card-title" id="au_name_'+uid+'">'+esc(u.username||u.id)+
4071
- ' <button onclick="adminStartEditName(this,&quot;'+uid+'&quot;,&quot;'+uname+'&quot;)" style="background:none;border:none;cursor:pointer;color:var(--text-muted);padding:2px;font-size:13px;vertical-align:middle;opacity:.5;transition:opacity .15s" onmouseenter="this.style.opacity=1" onmouseleave="this.style.opacity=.5" title="'+t('admin.editName')+'">\u270E</button></span>';
4072
-
4073
- var editRow='<div id="au_edit_'+uid+'" style="display:none;align-items:center;gap:6px">'+
4074
- '<input id="au_input_'+uid+'" type="text" value="'+uname+'" style="flex:1;padding:5px 10px;border:1px solid var(--pri);border-radius:8px;font-size:13px;font-weight:600;background:var(--bg);color:var(--text);outline:none;min-width:0" onkeydown="if(event.key===&quot;Enter&quot;)adminSaveEditName(&quot;'+uid+'&quot;);if(event.key===&quot;Escape&quot;)adminCancelEditName(&quot;'+uid+'&quot;)">'+
4075
- '<button onclick="adminSaveEditName(&quot;'+uid+'&quot;)" class="btn btn-sm btn-primary" style="padding:4px 12px;font-size:11px;white-space:nowrap">\u2713</button>'+
4076
- '<button onclick="adminCancelEditName(&quot;'+uid+'&quot;)" class="btn btn-sm btn-ghost" style="padding:4px 8px;font-size:11px;color:var(--text-muted)">\u2717</button></div>';
4077
-
4078
- var mc=u.memoryCount||0, tc=u.taskCount||0, sc=u.skillCount||0;
4079
- var contribHtml='<div class="au-contrib">'+
4080
- '<span class="au-contrib-item"><span class="au-contrib-num" style="color:var(--pri)">'+mc+'</span> '+t('admin.contrib.memories')+'</span>'+
4081
- '<span class="au-contrib-item"><span class="au-contrib-num" style="color:var(--green)">'+tc+'</span> '+t('admin.contrib.tasks')+'</span>'+
4082
- '<span class="au-contrib-item"><span class="au-contrib-num" style="color:var(--amber)">'+sc+'</span> '+t('admin.contrib.skills')+'</span>'+
4083
- '</div>';
4084
-
4085
- var infoRows=[];
4086
- if(u.deviceName) infoRows.push('<span class="au-info-item">\u{1F4BB} '+t('admin.device')+esc(u.deviceName)+'</span>');
4087
- if(u.lastIp) infoRows.push('<span class="au-info-item">\u{1F310} '+t('admin.ip')+esc(u.lastIp)+'</span>');
4088
- if(u.createdAt) infoRows.push('<span class="au-info-item">\u{1F4C5} '+t('admin.joined')+formatDateTimeSeconds(u.createdAt)+'</span>');
4089
- if(u.approvedAt) infoRows.push('<span class="au-info-item">\u2705 '+t('admin.approved')+formatDateTimeSeconds(u.approvedAt)+'</span>');
4090
- infoRows.push('<span class="au-info-item">\u{1F552} '+t('admin.lastActive')+(u.lastActiveAt?formatDateTimeSeconds(u.lastActiveAt):t('admin.neverActive'))+'</span>');
4091
- var infoHtml='<div class="au-info">'+infoRows.join('')+'</div>';
4092
-
4093
- var actions='';
4094
- if(u.role!=='admin'){
4095
- actions+='<button class="btn btn-sm btn-ghost" onclick="adminToggleRole(&quot;'+uid+'&quot;,&quot;admin&quot;)" style="color:var(--accent)">'+t('admin.promoteAdmin')+'</button>';
4096
- actions+='<button class="btn btn-sm btn-ghost" onclick="adminRemoveUser(&quot;'+uid+'&quot;,&quot;'+uname+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>';
4097
- }else if(adminCount>1){
4098
- actions+='<button class="btn btn-sm btn-ghost" onclick="adminToggleRole(&quot;'+uid+'&quot;,&quot;member&quot;)">'+t('admin.demoteMember')+'</button>';
4099
- actions+='<button class="btn btn-sm btn-ghost" onclick="adminRemoveUser(&quot;'+uid+'&quot;,&quot;'+uname+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>';
4100
- }else{
4101
- actions+='<span style="font-size:11px;color:var(--text-muted);padding:4px 0">'+t('admin.lastAdminHint')+'</span>';
4102
- }
4103
- html+='<div class="admin-card au-card"><div class="admin-card-header"><div style="flex:1;min-width:0">'+titleDisplay+editRow+'</div>'+
4104
- '<span class="admin-badge '+(u.role==='admin'?'admin':'member')+'">'+esc(u.role||'member')+'</span></div>'+
4105
- contribHtml+infoHtml+
4106
- (actions?'<div class="admin-card-actions" style="border-top:1px dashed var(--border);padding-top:10px;margin-top:4px">'+actions+'</div>':'')+
4107
- '</div>';
4317
+ html+='<div class="au-group-header"><span class="au-group-dot online"></span>'+t('admin.onlineUsers')+' <span class="au-group-count">('+onlineUsers.length+')</span></div>';
4318
+ if(onlineUsers.length===0){
4319
+ html+='<div style="font-size:12px;color:var(--text-muted);padding:8px 0 12px">\u2014</div>';
4320
+ }else{
4321
+ for(var i=0;i<onlineUsers.length;i++) html+=renderAdminUserCard(onlineUsers[i],adminCount);
4322
+ }
4323
+ html+='<div class="au-group-header"><span class="au-group-dot offline"></span>'+t('admin.offlineUsers')+' <span class="au-group-count">('+offlineUsers.length+')</span></div>';
4324
+ if(offlineUsers.length===0){
4325
+ html+='<div style="font-size:12px;color:var(--text-muted);padding:8px 0 12px">\u2014</div>';
4326
+ }else{
4327
+ for(var j=0;j<offlineUsers.length;j++) html+=renderAdminUserCard(offlineUsers[j],adminCount);
4108
4328
  }
4109
- html+=adminPaginateHtml(totalUsers,adminPage.users,'adminUsers');
4110
4329
  }
4111
4330
  el.innerHTML=html;
4112
4331
  }
4113
- function adminUsersPage(p){var maxP=Math.max(0,Math.ceil(adminDataCache.users.length/ADMIN_PAGE_SIZE)-1);if(p<0){adminPage.users=Math.max(0,adminPage.users-1);}else{adminPage.users=Math.min(p,maxP);}renderAdminUsers(adminDataCache.users,adminDataCache._pending||[]);}
4332
+ function adminUsersPage(p){renderAdminUsers(adminDataCache.users,adminDataCache._pending||[]);}
4114
4333
 
4115
4334
  async function adminApproveUser(userId,username){
4116
4335
  try{
@@ -4133,7 +4352,9 @@ async function adminToggleRole(userId,newRole){
4133
4352
  try{
4134
4353
  var r=await fetch('/api/sharing/change-role',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,role:newRole})});
4135
4354
  var d=await r.json();
4136
- if(d.ok){toast(t('toast.roleChanged'),'success');loadAdminData();}else{toast(d.error||t('toast.roleChangeFail'),'error');}
4355
+ if(d.ok){toast(t('toast.roleChanged'),'success');loadAdminData();}
4356
+ else if(d.error==='cannot_demote_owner'){toast(t('admin.ownerHint'),'error');}
4357
+ else{toast(d.error||t('toast.roleChangeFail'),'error');}
4137
4358
  }catch(e){toast(t('toast.roleChangeFail')+': '+e.message,'error');}
4138
4359
  }
4139
4360
  function adminStartEditName(btn,userId,currentName){
@@ -4172,7 +4393,9 @@ async function adminRemoveUser(userId,username){
4172
4393
  try{
4173
4394
  var r=await fetch('/api/sharing/remove-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,cleanResources:clean})});
4174
4395
  var d=await r.json();
4175
- if(d.ok){toast(t('toast.userRemoved'),'success');loadAdminData();}else{toast(d.error||t('toast.removeFail'),'error');}
4396
+ if(d.ok){toast(t('toast.userRemoved'),'success');loadAdminData();}
4397
+ else if(d.error==='cannot_remove_owner'){toast(t('admin.ownerHint'),'error');}
4398
+ else{toast(d.error||t('toast.removeFail'),'error');}
4176
4399
  }catch(e){toast(t('toast.removeFail')+': '+e.message,'error');}
4177
4400
  }
4178
4401
 
@@ -4244,7 +4467,7 @@ function renderAdminTasks(tasks){
4244
4467
  (tk.chunkCount!=null?'<span class="admin-card-tag tag-kind">\u{1F4DD} '+tk.chunkCount+' '+t('admin.chunks')+'</span>':'')+
4245
4468
  '</div>'+
4246
4469
  '<span class="admin-card-actions" onclick="event.stopPropagation()">'+
4247
- '<button class="btn btn-sm btn-ghost" onclick="adminDeleteTask(&quot;'+escAttr(tk.id)+'&quot;,&quot;'+escAttr(tk.title||tk.id)+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>'+
4470
+ (window._isHubAdmin?'<button class="btn btn-sm btn-ghost" onclick="adminDeleteTask(&quot;'+escAttr(tk.id)+'&quot;,&quot;'+escAttr(tk.title||tk.id)+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>':'')+
4248
4471
  '<button class="btn btn-sm btn-ghost admin-card-expand-btn" onclick="toggleAdminTaskCard(&quot;'+cardId+'&quot;,'+i+')">'+t('admin.expand')+'</button>'+
4249
4472
  '<span class="admin-card-time">'+(timeRange||formatDateTimeSeconds(tk.updatedAt||tk.createdAt))+'</span>'+
4250
4473
  '</span>'+
@@ -4307,7 +4530,7 @@ function renderAdminSkills(skills){
4307
4530
  (qs!=null?'<span class="admin-card-tag tag-kind">\u2605 '+Number(qs).toFixed(1)+'</span>':'')+
4308
4531
  '</div>'+
4309
4532
  '<span class="admin-card-actions" onclick="event.stopPropagation()">'+
4310
- '<button class="btn btn-sm btn-ghost" onclick="adminDeleteSkill(&quot;'+escAttr(s.id)+'&quot;,&quot;'+escAttr(s.name||s.id)+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>'+
4533
+ (window._isHubAdmin?'<button class="btn btn-sm btn-ghost" onclick="adminDeleteSkill(&quot;'+escAttr(s.id)+'&quot;,&quot;'+escAttr(s.name||s.id)+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>':'')+
4311
4534
  '<button class="btn btn-sm btn-ghost admin-card-expand-btn" onclick="toggleAdminSkillCard(&quot;'+cardId+'&quot;,'+i+')">'+t('admin.expand')+'</button>'+
4312
4535
  (s.sourceSkillId?'<button class="btn btn-sm btn-ghost" onclick="window.open(&quot;/api/skill/'+encodeURIComponent(s.sourceSkillId)+'/download&quot;,&quot;_blank&quot;)" style="color:var(--pri)">\u2B07</button>':'')+
4313
4536
  '<span class="admin-card-time">'+formatDateTimeSeconds(s.updatedAt||s.createdAt)+'</span>'+
@@ -4371,7 +4594,7 @@ function renderAdminMemories(memories){
4371
4594
  (m.kind?'<span class="admin-card-tag tag-kind">'+esc(m.kind)+'</span>':'')+
4372
4595
  '</div>'+
4373
4596
  '<span class="admin-card-actions">'+
4374
- '<button class="btn btn-sm btn-ghost" onclick="event.stopPropagation();adminDeleteMemory(&quot;'+escAttr(m.id)+'&quot;,&quot;'+escAttr(m.summary||m.id)+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>'+
4597
+ (window._isHubAdmin?'<button class="btn btn-sm btn-ghost" onclick="event.stopPropagation();adminDeleteMemory(&quot;'+escAttr(m.id)+'&quot;,&quot;'+escAttr(m.summary||m.id)+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>':'')+
4375
4598
  '<button class="btn btn-sm btn-ghost admin-card-expand-btn" onclick="event.stopPropagation();toggleAdminMemoryCard(&quot;'+cardId+'&quot;,'+i+')">'+t('admin.expand')+'</button>'+
4376
4599
  '<span class="admin-card-time">'+formatDateTimeSeconds(m.updatedAt||m.createdAt)+'</span>'+
4377
4600
  '</span>'+
@@ -4449,12 +4672,19 @@ async function toggleAdminTaskCard(cardId,idx){
4449
4672
  var tk=(adminTasksCache||[])[idx];
4450
4673
  if(!tk){detail.innerHTML='<div style="color:var(--text-muted);font-size:14px;padding:12px">'+t('admin.noContent')+'</div>';return;}
4451
4674
  var localTaskId=tk.sourceTaskId||tk.id;
4675
+ var hubTaskId=tk.id;
4452
4676
  detail.innerHTML='<div class="spinner"></div>';
4453
4677
  var task=null;
4454
4678
  try{
4455
4679
  var r=await fetch('/api/task/'+encodeURIComponent(localTaskId));
4456
4680
  if(r.ok) task=await r.json();
4457
4681
  }catch(e){}
4682
+ if(!task){
4683
+ try{
4684
+ var r2=await fetch('/api/admin/shared-tasks/'+encodeURIComponent(hubTaskId)+'/detail');
4685
+ if(r2.ok) task=await r2.json();
4686
+ }catch(e2){}
4687
+ }
4458
4688
  if(!task){detail.innerHTML='<div style="color:var(--text-muted);font-size:14px;padding:12px">'+t('admin.noContent')+'</div>';return;}
4459
4689
  var metaHtml='<div class="admin-card-detail-meta admin-task-meta">'+
4460
4690
  (tk.status?'<span class="meta-item"><span class="task-status-badge '+tk.status+'">'+esc(tk.status)+'</span></span>':'')+
@@ -4525,11 +4755,18 @@ async function toggleAdminSkillCard(cardId,idx){
4525
4755
  if(!sk){detail.innerHTML='<div style="color:var(--text-muted);font-size:13px">'+t('admin.noContent')+'</div>';return;}
4526
4756
  detail.innerHTML='<div class="spinner"></div>';
4527
4757
  var localSkillId=sk.sourceSkillId||sk.id;
4758
+ var hubSkillId=sk.id;
4528
4759
  var localData=null;
4529
4760
  try{
4530
4761
  var lr=await fetch('/api/skill/'+encodeURIComponent(localSkillId));
4531
4762
  if(lr.ok) localData=await lr.json();
4532
4763
  }catch(e){}
4764
+ if(!localData){
4765
+ try{
4766
+ var hr=await fetch('/api/admin/shared-skills/'+encodeURIComponent(hubSkillId)+'/detail');
4767
+ if(hr.ok) localData=await hr.json();
4768
+ }catch(e2){}
4769
+ }
4533
4770
  var localSkill=localData&&localData.skill?localData.skill:sk;
4534
4771
  var files=localData&&localData.files?localData.files:[];
4535
4772
  var versions=localData&&localData.versions?localData.versions:[];
@@ -5356,22 +5593,33 @@ function setTaskStatusFilter(btn,status){
5356
5593
  loadTasks();
5357
5594
  }
5358
5595
 
5359
- async function loadTasks(){
5596
+ async function loadTasks(silent){
5360
5597
  const scope=document.getElementById('taskSearchScope')?document.getElementById('taskSearchScope').value:taskSearchScope;
5361
5598
  taskSearchScope=scope||'local';
5362
- if(taskSearchScope!=='local'){ return loadHubTasks(); }
5599
+ if(taskSearchScope==='hub'){ return loadHubTasks(); }
5363
5600
  const list=document.getElementById('tasksList');
5364
- list.innerHTML='<div class="spinner"></div>';
5601
+ if(!silent) list.innerHTML='<div class="spinner"></div>';
5365
5602
  try{
5366
5603
  const params=new URLSearchParams({limit:String(TASKS_PER_PAGE),offset:String(tasksPage*TASKS_PER_PAGE)});
5367
5604
  if(tasksStatusFilter) params.set('status',tasksStatusFilter);
5605
+ if(taskSearchScope==='local') params.set('owner','agent:main');
5606
+ var baseP=new URLSearchParams();
5607
+ if(taskSearchScope==='local') baseP.set('owner','agent:main');
5368
5608
  const [data,allD,activeD,compD,skipD]=await Promise.all([
5369
5609
  fetch('/api/tasks?'+params).then(r=>r.json()),
5370
- fetch('/api/tasks?limit=1&offset=0').then(r=>r.json()),
5371
- fetch('/api/tasks?status=active&limit=1&offset=0').then(r=>r.json()),
5372
- fetch('/api/tasks?status=completed&limit=1&offset=0').then(r=>r.json()),
5373
- fetch('/api/tasks?status=skipped&limit=1&offset=0').then(r=>r.json())
5610
+ fetch('/api/tasks?limit=1&offset=0&'+baseP).then(r=>r.json()),
5611
+ fetch('/api/tasks?status=active&limit=1&offset=0&'+baseP).then(r=>r.json()),
5612
+ fetch('/api/tasks?status=completed&limit=1&offset=0&'+baseP).then(r=>r.json()),
5613
+ fetch('/api/tasks?status=skipped&limit=1&offset=0&'+baseP).then(r=>r.json())
5374
5614
  ]);
5615
+ if(silent){
5616
+ var fp=JSON.stringify((data.tasks||[]).map(function(tk){return tk.id+'|'+tk.status+'|'+(tk.updatedAt||tk.startedAt)}));
5617
+ fp+=':'+allD.total+':'+activeD.total+':'+compD.total+':'+skipD.total;
5618
+ if(fp===_lastTasksFingerprint) return;
5619
+ _lastTasksFingerprint=fp;
5620
+ }else{
5621
+ _lastTasksFingerprint='';
5622
+ }
5375
5623
  document.getElementById('tasksTotalCount').textContent=formatNum(allD.total);
5376
5624
  document.getElementById('tasksActiveCount').textContent=formatNum(activeD.total);
5377
5625
  document.getElementById('tasksCompletedCount').textContent=formatNum(compD.total);
@@ -5657,17 +5905,17 @@ function updateSkillCardBadge(skillId,newScope){
5657
5905
  }
5658
5906
  }
5659
5907
 
5660
- async function loadSkills(){
5908
+ async function loadSkills(silent){
5661
5909
  const list=document.getElementById('skillsList');
5662
5910
  const hubList=document.getElementById('hubSkillsList');
5663
- list.innerHTML='<div class="spinner"></div>';
5911
+ if(!silent) list.innerHTML='<div class="spinner"></div>';
5664
5912
  var hubSection=document.getElementById('hubSkillsSection');
5665
5913
  if(hubList){
5666
- if(skillSearchScope==='local'){
5914
+ if(skillSearchScope==='local'||skillSearchScope==='allLocal'){
5667
5915
  if(hubSection) hubSection.style.display='none';
5668
5916
  }else{
5669
5917
  if(hubSection) hubSection.style.display='block';
5670
- hubList.innerHTML='<div class="spinner"></div>';
5918
+ if(!silent) hubList.innerHTML='<div class="spinner"></div>';
5671
5919
  }
5672
5920
  }
5673
5921
 
@@ -5691,6 +5939,13 @@ async function loadSkills(){
5691
5939
  return haystack.includes(q);
5692
5940
  });
5693
5941
  }
5942
+ if(silent){
5943
+ var fp=JSON.stringify(localSkills.map(function(s){return s.id+'|'+s.status+'|'+s.version+'|'+(s.visibility||'')}));
5944
+ if(fp===_lastSkillsFingerprint) return;
5945
+ _lastSkillsFingerprint=fp;
5946
+ }else{
5947
+ _lastSkillsFingerprint='';
5948
+ }
5694
5949
 
5695
5950
  const renderLocalCards=function(skills){
5696
5951
  if(!skills||skills.length===0){
@@ -5736,7 +5991,7 @@ async function loadSkills(){
5736
5991
 
5737
5992
  list.innerHTML=renderLocalCards(localSkills);
5738
5993
 
5739
- if(skillSearchScope==='local'){
5994
+ if(skillSearchScope==='local'||skillSearchScope==='allLocal'){
5740
5995
  if(hubSection) hubSection.style.display='none';
5741
5996
  document.getElementById('skillSearchMeta').textContent=query?(t('skills.search.local')+' '+localSkills.length):'';
5742
5997
  document.getElementById('skillsTotalCount').textContent=formatNum(localSkills.length);
@@ -5749,7 +6004,8 @@ async function loadSkills(){
5749
6004
 
5750
6005
  if(!query){
5751
6006
  if(hubSection) hubSection.style.display='block';
5752
- if(hubList){ loadHubSkills(hubList); }
6007
+ var localIds=new Set(localSkills.map(function(s){return s.id;}));
6008
+ if(hubList){ loadHubSkills(hubList, localIds); }
5753
6009
  document.getElementById('skillSearchMeta').textContent=t('skills.search.local')+' '+localSkills.length;
5754
6010
  document.getElementById('skillsTotalCount').textContent=formatNum(localSkills.length);
5755
6011
  document.getElementById('skillsActiveCount').textContent=formatNum(localSkills.filter(s=>s.status==='active').length);
@@ -5807,7 +6063,47 @@ async function loadSkills(){
5807
6063
  }
5808
6064
  }
5809
6065
 
5810
- async function loadHubSkills(hubList){
6066
+ async function loadHubTasks(){
6067
+ var list=document.getElementById('tasksList');
6068
+ if(!list) return;
6069
+ list.innerHTML='<div class="spinner"></div>';
6070
+ try{
6071
+ var r=await fetch('/api/sharing/tasks/list?limit=40');
6072
+ var d=await r.json();
6073
+ var tasks=Array.isArray(d.tasks)?d.tasks:[];
6074
+ hubTasksCache=tasks;
6075
+ document.getElementById('tasksTotalCount').textContent=formatNum(tasks.length);
6076
+ document.getElementById('tasksActiveCount').textContent='-';
6077
+ document.getElementById('tasksCompletedCount').textContent='-';
6078
+ document.getElementById('tasksSkippedCount').textContent='-';
6079
+ if(!tasks.length){
6080
+ list.innerHTML='<div style="text-align:center;padding:48px;color:var(--text-muted);font-size:14px">'+t('tasks.empty')+'</div>';
6081
+ document.getElementById('tasksPagination').innerHTML='';
6082
+ return;
6083
+ }
6084
+ list.innerHTML=tasks.map(function(task,idx){
6085
+ var timeStr=task.updatedAt?formatTime(task.updatedAt):(task.createdAt?formatTime(task.createdAt):'');
6086
+ return '<div class="task-card" onclick="openHubTaskDetailFromCache(\\x27hub\\x27,'+idx+')" style="cursor:pointer">'+
6087
+ '<div class="task-card-top">'+
6088
+ '<div class="task-card-title">'+esc(task.title||'(no title)')+'</div>'+
6089
+ '<div class="task-card-badges"><span class="scope-badge team">\\u{1F310} '+t('scope.hub')+'</span></div>'+
6090
+ '</div>'+
6091
+ (task.summary?'<div class="task-card-summary">'+esc(task.summary)+'</div>':'')+
6092
+ '<div class="task-card-bottom">'+
6093
+ (timeStr?'<span class="tag"><span class="icon">\\u{1F4C5}</span> '+timeStr+'</span>':'')+
6094
+ '<span class="tag"><span class="icon">\\u{1F464}</span> '+esc(task.ownerName||'unknown')+'</span>'+
6095
+ (task.chunkCount!=null?'<span class="tag"><span class="icon">\\u{1F4DD}</span> '+task.chunkCount+' '+t('tasks.chunks.label')+'</span>':'')+
6096
+ '</div>'+
6097
+ '</div>';
6098
+ }).join('');
6099
+ document.getElementById('tasksPagination').innerHTML='';
6100
+ }catch(e){
6101
+ list.innerHTML='<div style="text-align:center;padding:48px;color:var(--text-muted);font-size:14px">'+t('tasks.empty')+'</div>';
6102
+ document.getElementById('tasksPagination').innerHTML='';
6103
+ }
6104
+ }
6105
+
6106
+ async function loadHubSkills(hubList, localIds){
5811
6107
  if(!hubList) hubList=document.getElementById('hubSkillsList');
5812
6108
  if(!hubList) return;
5813
6109
  var hubSection=document.getElementById('hubSkillsSection');
@@ -5815,7 +6111,8 @@ async function loadHubSkills(hubList){
5815
6111
  try{
5816
6112
  const r=await fetch('/api/sharing/skills/list?limit=40');
5817
6113
  const d=await r.json();
5818
- const skills=Array.isArray(d.skills)?d.skills:[];
6114
+ var allSkills=Array.isArray(d.skills)?d.skills:[];
6115
+ const skills=localIds?allSkills.filter(function(s){return !localIds.has(s.sourceSkillId);}):allSkills;
5819
6116
  hubSkillsCache=skills;
5820
6117
  if(!skills.length){
5821
6118
  if(hubSection) hubSection.style.display='none';
@@ -6167,6 +6464,7 @@ async function loadConfig(){
6167
6464
  document.getElementById('cfgHubTeamToken').value=hub.teamToken||'';
6168
6465
  document.getElementById('cfgClientHubAddress').value=client.hubAddress||'';
6169
6466
  document.getElementById('cfgClientTeamToken').value=client.teamToken||'';
6467
+ document.getElementById('cfgClientNickname').value=client.nickname||'';
6170
6468
  document.getElementById('cfgClientUserToken').value=client.userToken||'';
6171
6469
  onSharingToggle();
6172
6470
  updateHubShareInfo();
@@ -6335,6 +6633,7 @@ async function saveHubConfig(){
6335
6633
  var hubPort=document.getElementById('cfgHubPort').value.trim();
6336
6634
  var hubTeamName=document.getElementById('cfgHubTeamName').value.trim();
6337
6635
  var hubTeamToken=document.getElementById('cfgHubTeamToken').value.trim();
6636
+ var hubAdminName=document.getElementById('cfgHubAdminName').value.trim();
6338
6637
  cfg.sharing.hub={port:hubPort?Number(hubPort):18800};
6339
6638
  if(hubTeamName) cfg.sharing.hub.teamName=hubTeamName;
6340
6639
  if(hubTeamToken) cfg.sharing.hub.teamToken=hubTeamToken;
@@ -6344,8 +6643,12 @@ async function saveHubConfig(){
6344
6643
  var clientAddr=document.getElementById('cfgClientHubAddress').value.trim();
6345
6644
  var clientTeamToken=document.getElementById('cfgClientTeamToken').value.trim();
6346
6645
  var clientUserToken=document.getElementById('cfgClientUserToken').value.trim();
6646
+ var clientNickname=document.getElementById('cfgClientNickname').value.trim();
6647
+ if(!clientAddr){done();toast(t('settings.hub.test.noAddr'),'error');return;}
6648
+ if(!clientTeamToken){done();toast(t('settings.hub.teamToken.required'),'error');return;}
6347
6649
  cfg.sharing.client={};
6348
6650
  if(clientAddr) cfg.sharing.client.hubAddress=clientAddr;
6651
+ if(clientNickname) cfg.sharing.client.nickname=clientNickname;
6349
6652
  if(clientTeamToken) cfg.sharing.client.teamToken=clientTeamToken;
6350
6653
  if(clientUserToken) cfg.sharing.client.userToken=clientUserToken;
6351
6654
  cfg.sharing.hub={port:18800,teamName:'',teamToken:''};
@@ -6358,6 +6661,18 @@ async function saveHubConfig(){
6358
6661
  done();toast(t('sharing.cannotJoinSelf'),'error');return;
6359
6662
  }
6360
6663
  }catch(e){}
6664
+ try{
6665
+ var testUrl=clientAddr.indexOf('://')>-1?clientAddr:'http://'+clientAddr;
6666
+ testUrl=testUrl.replace(/\\/+$/,'');
6667
+ var tr=await fetch('/api/sharing/test-hub',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({hubUrl:testUrl})});
6668
+ var td=await tr.json();
6669
+ if(!td.ok){
6670
+ var errMsg=td.error==='cannot_join_self'?t('sharing.cannotJoinSelf'):(td.error||t('settings.hub.test.fail'));
6671
+ done();toast(errMsg,'error');return;
6672
+ }
6673
+ }catch(e){
6674
+ done();toast(t('settings.hub.test.fail')+': '+String(e),'error');return;
6675
+ }
6361
6676
  }
6362
6677
  }
6363
6678
 
@@ -6367,11 +6682,25 @@ async function saveHubConfig(){
6367
6682
  var confirmMsg=prevRole==='hub'?t('sharing.disable.confirm.hub'):t('sharing.disable.confirm.client');
6368
6683
  if(!(await confirmModal(confirmMsg,{danger:true}))){done();return;}
6369
6684
  }
6685
+ if(prevSharingEnabled&&sharingEnabled&&prevRole&&prevRole!==_sharingRole){
6686
+ var switchMsg=prevRole==='hub'?t('sharing.switch.hubToClient'):t('sharing.switch.clientToHub');
6687
+ if(!(await confirmModal(switchMsg,{danger:true}))){done();return;}
6688
+ }
6370
6689
 
6371
6690
  var ok=await doSaveConfig(cfg, saveBtn, 'hubSaved');
6372
6691
  if(ok){
6692
+ if(sharingEnabled&&_sharingRole==='hub'){
6693
+ var adminNameEl=document.getElementById('cfgHubAdminName');
6694
+ if(adminNameEl&&adminNameEl.value.trim()){
6695
+ try{await fetch('/api/sharing/update-username',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:adminNameEl.value.trim()})});}catch(e){}
6696
+ }
6697
+ }
6698
+ _lastSidebarFingerprint='';
6699
+ _lastSettingsFingerprint='';
6373
6700
  loadSharingStatus(false);
6374
- var needsRestart=sharingEnabled||(prevSharingEnabled&&!sharingEnabled);
6701
+ var enabledChanged=(!!prevSharingEnabled)!==(!!sharingEnabled);
6702
+ var roleChanged=prevRole!==_sharingRole;
6703
+ var needsRestart=enabledChanged||roleChanged;
6375
6704
  if(sharingEnabled) updateHubShareInfo();
6376
6705
  if(needsRestart){
6377
6706
  setTimeout(function(){showRestartOverlay(t('settings.restart.waiting'));},300);
@@ -6791,23 +7120,89 @@ function renderToolAgg(data){
6791
7120
  '</tbody></table>';
6792
7121
  }
6793
7122
 
6794
- /* ─── Sharing status polling ─── */
6795
- var _sharingPollTimer=null;
6796
- function startSharingPoll(){
6797
- stopSharingPoll();
6798
- _sharingPollTimer=setInterval(function(){
6799
- if(sharingStatusCache&&sharingStatusCache.enabled) loadSharingStatus(false);
6800
- },30000);
7123
+ /* ─── Unified live-data poller ─── */
7124
+ var _livePoller=null;
7125
+ var _LIVE_POLL_MS=15000;
7126
+ var _livePollBusy=false;
7127
+
7128
+ async function _livePollTick(){
7129
+ if(_livePollBusy||document.hidden) return;
7130
+ _livePollBusy=true;
7131
+ try{
7132
+ if(sharingStatusCache&&sharingStatusCache.enabled&&_lastSharingConnStatus!=='rejected') loadSharingStatus(false);
7133
+ if(!_notifSSEConnected) pollNotifCount();
7134
+ pollAdminPending();
7135
+ if(_activeView==='admin') loadAdminData();
7136
+ else if(_activeView==='memories'){
7137
+ var _searchVal=(document.getElementById('searchInput')||{}).value||'';
7138
+ if(!_searchVal.trim()){
7139
+ if(memorySearchScope==='hub') loadHubMemories(true);
7140
+ else{loadStats();loadMemories(null,true);}
7141
+ }
7142
+ }
7143
+ else if(_activeView==='tasks') loadTasks(true);
7144
+ else if(_activeView==='skills') loadSkills(true);
7145
+ else if(_activeView==='analytics') loadMetrics();
7146
+ }catch(e){}
7147
+ _livePollBusy=false;
6801
7148
  }
6802
- function stopSharingPoll(){
6803
- if(_sharingPollTimer){clearInterval(_sharingPollTimer);_sharingPollTimer=null;}
7149
+
7150
+ function startLivePoller(){
7151
+ stopLivePoller();
7152
+ _livePoller=setInterval(_livePollTick,_LIVE_POLL_MS);
7153
+ }
7154
+ function stopLivePoller(){
7155
+ if(_livePoller){clearInterval(_livePoller);_livePoller=null;}
6804
7156
  }
6805
7157
 
6806
- /* ─── Notifications ─── */
7158
+ document.addEventListener('visibilitychange',function(){
7159
+ if(document.hidden){
7160
+ stopLivePoller();
7161
+ }else{
7162
+ _livePollTick();
7163
+ startLivePoller();
7164
+ if(!_notifSSE||!_notifSSEConnected) connectNotifSSE();
7165
+ }
7166
+ });
7167
+
7168
+ /* ─── Notifications (SSE push + fallback poll) ─── */
6807
7169
  var _notifCache=[];
6808
7170
  var _notifUnread=0;
6809
7171
  var _notifPollTimer=null;
6810
7172
  var _notifPanelOpen=false;
7173
+ var _notifSSE=null;
7174
+ var _notifSSEConnected=false;
7175
+ var _notifSSERetryMs=1000;
7176
+
7177
+ function connectNotifSSE(){
7178
+ if(_notifSSE) return;
7179
+ try{
7180
+ _notifSSE=new EventSource('/api/notifications/stream');
7181
+ _notifSSE.onmessage=function(ev){
7182
+ try{
7183
+ var d=JSON.parse(ev.data);
7184
+ if(d.type==='connected'){_notifSSEConnected=true;_notifSSERetryMs=1000;return;}
7185
+ if(d.type==='update'){
7186
+ var prev=_notifUnread;
7187
+ _notifUnread=d.unreadCount||0;
7188
+ renderNotifBadge();
7189
+ if(_notifUnread>prev&&_notifPanelOpen) loadNotifications();
7190
+ }
7191
+ if(d.type==='cleared'){
7192
+ _notifUnread=0;_notifCache=[];
7193
+ renderNotifBadge();renderNotifPanel();
7194
+ }
7195
+ }catch(e){}
7196
+ };
7197
+ _notifSSE.onerror=function(){
7198
+ _notifSSEConnected=false;
7199
+ if(_notifSSE){_notifSSE.close();_notifSSE=null;}
7200
+ setTimeout(connectNotifSSE,Math.min(_notifSSERetryMs,30000));
7201
+ _notifSSERetryMs=Math.min(_notifSSERetryMs*2,30000);
7202
+ };
7203
+ }catch(e){}
7204
+ }
7205
+ connectNotifSSE();
6811
7206
 
6812
7207
  function toggleNotifPanel(e){
6813
7208
  if(e)e.stopPropagation();
@@ -6838,7 +7233,10 @@ function notifTimeAgo(ts){
6838
7233
  return t('notif.timeAgo.day').replace('{n}',Math.floor(diff/86400000));
6839
7234
  }
6840
7235
 
6841
- function notifIcon(resource){
7236
+ function notifIcon(resource,type){
7237
+ if(type==='user_online') return '\\u{1F7E2}';
7238
+ if(type==='user_offline') return '\\u{1F534}';
7239
+ if(type==='user_join_request') return '\\u{1F464}';
6842
7240
  if(resource==='memory') return '\\u{1F4DD}';
6843
7241
  if(resource==='task') return '\\u{1F4CB}';
6844
7242
  if(resource==='skill') return '\\u{1F9E0}';
@@ -6849,6 +7247,21 @@ function notifTypeText(n){
6849
7247
  if(n.type==='resource_removed'){
6850
7248
  return t('notif.removed.'+n.resource)||t('notif.removed.memory');
6851
7249
  }
7250
+ if(n.type==='resource_shared'){
7251
+ return t('notif.shared.'+n.resource)||t('notif.shared.memory');
7252
+ }
7253
+ if(n.type==='resource_unshared'){
7254
+ return t('notif.unshared.'+n.resource)||t('notif.unshared.memory');
7255
+ }
7256
+ if(n.type==='user_join_request'){
7257
+ return t('notif.userJoin');
7258
+ }
7259
+ if(n.type==='user_online'){
7260
+ return t('notif.userOnline');
7261
+ }
7262
+ if(n.type==='user_offline'){
7263
+ return t('notif.userOffline');
7264
+ }
6852
7265
  return n.message||n.type;
6853
7266
  }
6854
7267
 
@@ -6893,7 +7306,7 @@ function renderNotifPanel(){
6893
7306
  body.innerHTML=_notifCache.map(function(n){
6894
7307
  var cls='notif-item'+(n.read?'':' unread');
6895
7308
  return '<div class="'+cls+'" onclick="markNotifRead(&quot;'+esc(n.id)+'&quot;)">'+
6896
- '<div class="notif-item-icon">'+notifIcon(n.resource)+'</div>'+
7309
+ '<div class="notif-item-icon">'+notifIcon(n.resource,n.type)+'</div>'+
6897
7310
  '<div class="notif-item-body">'+
6898
7311
  '<div class="notif-item-title">'+esc(notifTypeText(n))+'</div>'+
6899
7312
  '<div class="notif-item-name">'+esc(n.title)+'</div>'+
@@ -6934,24 +7347,20 @@ async function clearAllNotifs(){
6934
7347
  }catch(e){}
6935
7348
  }
6936
7349
 
6937
- function startNotifPoll(){
6938
- if(_notifPollTimer) return;
6939
- pollNotifCount();
6940
- _notifPollTimer=setInterval(pollNotifCount,15000);
6941
- }
6942
-
6943
- function stopNotifPoll(){
6944
- if(_notifPollTimer){clearInterval(_notifPollTimer);_notifPollTimer=null;}
6945
- }
7350
+ function startNotifPoll(){ startLivePoller(); }
7351
+ function stopNotifPoll(){ }
6946
7352
 
6947
7353
  /* ─── Data loading ─── */
6948
7354
  async function loadAll(){
6949
- await Promise.all([loadStats(),loadMemories(),loadSharingStatus(false)]);
7355
+ await loadStats();
7356
+ var initOwner=memorySearchScope==='local'?_currentAgentOwner:undefined;
7357
+ if(initOwner) await loadStats(initOwner);
7358
+ await Promise.all([loadMemories(),loadSharingStatus(false)]);
6950
7359
  checkMigrateStatus();
6951
7360
  connectPPSSE();
6952
7361
  checkForUpdate();
6953
- startSharingPoll();
6954
- startNotifPoll();
7362
+ pollNotifCount();
7363
+ startLivePoller();
6955
7364
  }
6956
7365
 
6957
7366
  async function loadStats(ownerFilter){
@@ -6963,6 +7372,7 @@ async function loadStats(ownerFilter){
6963
7372
  d=await r.json();
6964
7373
  }catch(e){ d={}; }
6965
7374
  if(!d||typeof d!=='object') d={};
7375
+ if(d.currentAgentOwner) _currentAgentOwner=d.currentAgentOwner;
6966
7376
  const tm=d.totalMemories||0;
6967
7377
  const dedupB=d.dedupBreakdown||{};
6968
7378
  const activeCount=dedupB.active||tm;
@@ -7029,11 +7439,14 @@ async function loadStats(ownerFilter){
7029
7439
  const ownerSel=document.getElementById('filterOwner');
7030
7440
  if(ownerSel && d.owners && d.owners.length>0){
7031
7441
  const curVal=ownerSel.value;
7032
- ownerSel.innerHTML='<option value="">'+t('filter.allowners')+'</option>'+'<option value="public">'+t('filter.public')+'</option>';
7033
- d.owners.filter(o=>o && o!=='public').forEach(o=>{
7034
- ownerSel.innerHTML+='<option value="'+o+'">'+o+'</option>';
7442
+ var agents=d.owners.filter(function(o){return o && o.indexOf('agent:')===0;});
7443
+ ownerSel.innerHTML='<option value="">'+t('filter.allagents')+'</option>';
7444
+ agents.forEach(function(o){
7445
+ var label=o.replace('agent:','');
7446
+ ownerSel.innerHTML+='<option value="'+o+'"'+(o===curVal?' selected':'')+'>'+label+'</option>';
7035
7447
  });
7036
- ownerSel.value=curVal;
7448
+ if(agents.length<=1) ownerSel.style.display='none';
7449
+ else ownerSel.style.display='';
7037
7450
  }
7038
7451
  }
7039
7452
 
@@ -7073,59 +7486,92 @@ function getFilterParams(){
7073
7486
  if(dt) p.set('dateTo',dt);
7074
7487
  const sort=document.getElementById('filterSort').value;
7075
7488
  if(sort==='oldest') p.set('sort','oldest');
7076
- const owner=document.getElementById('filterOwner').value;
7077
- if(owner) p.set('owner',owner);
7489
+ const scope=memorySearchScope||'local';
7490
+ if(scope==='local'){
7491
+ p.set('owner',_currentAgentOwner);
7492
+ }else{
7493
+ const owner=document.getElementById('filterOwner').value;
7494
+ if(owner) p.set('owner',owner);
7495
+ }
7078
7496
  return p;
7079
7497
  }
7080
7498
 
7081
- async function loadMemories(page){
7499
+ async function loadMemories(page,silent){
7082
7500
  if(page) currentPage=page;
7083
7501
  const list=document.getElementById('memoryList');
7084
- list.innerHTML='<div class="spinner"></div>';
7502
+ if(!silent) list.innerHTML='<div class="spinner"></div>';
7085
7503
  try{
7086
7504
  const p=getFilterParams();
7087
7505
  p.set('limit',PAGE_SIZE);
7088
7506
  p.set('page',currentPage);
7089
7507
  const r=await fetch('/api/memories?'+p.toString());
7090
7508
  const d=await r.json();
7509
+ var items=d.memories||[];
7510
+ if(silent){
7511
+ var fp=JSON.stringify(items.map(function(m){return m.id+'|'+m.updated_at}));
7512
+ if(fp===_lastMemoriesFingerprint) return;
7513
+ _lastMemoriesFingerprint=fp;
7514
+ }else{
7515
+ _lastMemoriesFingerprint=JSON.stringify(items.map(function(m){return m.id+'|'+m.updated_at}));
7516
+ }
7091
7517
  totalPages=d.totalPages||1;
7092
7518
  totalCount=d.total||0;
7093
7519
  document.getElementById('searchMeta').textContent=totalCount+t('search.meta.total');
7094
- renderMemories(d.memories||[]);
7520
+ renderMemories(items);
7095
7521
  renderPagination();
7096
7522
  }catch(e){
7097
- list.innerHTML='';
7098
- totalPages=1;totalCount=0;
7099
- renderMemories([]);
7100
- renderPagination();
7523
+ if(!silent){
7524
+ list.innerHTML='';
7525
+ totalPages=1;totalCount=0;
7526
+ _lastMemoriesFingerprint='';
7527
+ renderMemories([]);
7528
+ renderPagination();
7529
+ }
7101
7530
  }
7102
7531
  }
7103
7532
 
7104
- async function loadHubMemories(){
7533
+ async function loadHubMemories(silent){
7105
7534
  const list=document.getElementById('memoryList');
7106
- list.innerHTML='<div class="spinner"></div>';
7535
+ if(!silent) list.innerHTML='<div class="spinner"></div>';
7107
7536
  try{
7108
7537
  const r=await fetch('/api/sharing/memories/list?limit='+PAGE_SIZE);
7109
7538
  const d=await r.json();
7110
7539
  const items=d.memories||[];
7540
+ if(silent){
7541
+ var fp=JSON.stringify(items.map(function(m){return m.id+'|'+(m.updated_at||m.created_at)}));
7542
+ if(fp===_lastMemoriesFingerprint) return;
7543
+ _lastMemoriesFingerprint=fp;
7544
+ }else{
7545
+ _lastMemoriesFingerprint=JSON.stringify(items.map(function(m){return m.id+'|'+(m.updated_at||m.created_at)}));
7546
+ }
7547
+ totalPages=1;totalCount=items.length;currentPage=1;
7111
7548
  document.getElementById('searchMeta').textContent=items.length+t('search.meta.total');
7112
7549
  document.getElementById('sharingSearchMeta').textContent='';
7113
7550
  renderMemories(items);
7114
7551
  document.getElementById('pagination').innerHTML='';
7115
7552
  }catch(e){
7116
- document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
7117
- renderMemories([]);
7118
- document.getElementById('pagination').innerHTML='';
7553
+ if(!silent){
7554
+ _lastMemoriesFingerprint='';
7555
+ document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
7556
+ renderMemories([]);
7557
+ document.getElementById('pagination').innerHTML='';
7558
+ }
7119
7559
  }
7120
7560
  }
7121
7561
 
7122
7562
  async function doSearch(query){
7123
7563
  query=(query||'').trim();
7124
- if(!query){loadMemories();return;}
7564
+ if(!query){
7565
+ currentPage=1;
7566
+ if(memorySearchScope==='hub') loadHubMemories();
7567
+ else loadMemories();
7568
+ return;
7569
+ }
7570
+ currentPage=1;
7125
7571
  var scope=document.getElementById('memorySearchScope')?.value||memorySearchScope||'local';
7126
7572
  var list=document.getElementById('memoryList');
7127
7573
  list.innerHTML='<div class="spinner"></div>';
7128
- if(scope!=='local'){
7574
+ if(scope==='hub'){
7129
7575
  try{
7130
7576
  var r=await fetch('/api/sharing/search/memories',{
7131
7577
  method:'POST',
@@ -7133,6 +7579,7 @@ async function doSearch(query){
7133
7579
  body:JSON.stringify({query:query,scope:scope,maxResults:20,role:activeRole||undefined})
7134
7580
  });
7135
7581
  var data=await r.json();
7582
+ totalPages=1;totalCount=(data.results||[]).length;
7136
7583
  renderSharingMemorySearchResults(data,query);
7137
7584
  }catch(e){
7138
7585
  document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
@@ -7147,6 +7594,7 @@ async function doSearch(query){
7147
7594
  var r=await fetch('/api/search?'+p.toString());
7148
7595
  var d=await r.json();
7149
7596
  var total=d.total||0;
7597
+ totalPages=1;totalCount=total;
7150
7598
  var meta=[];
7151
7599
  if(d.vectorCount>0) meta.push(d.vectorCount+t('search.meta.semantic'));
7152
7600
  if(d.ftsCount>0) meta.push(d.ftsCount+t('search.meta.text'));
@@ -7235,7 +7683,8 @@ function renderMemories(items){
7235
7683
  const isPublicMem=ownerVal==='public';
7236
7684
  const localManaged=!!m.localSharingManaged;
7237
7685
  const memShared=m.sharingVisibility||null;
7238
- const memScope=memShared?'team':isPublicMem?'local':'private';
7686
+ const isHubScope=memorySearchScope==='hub';
7687
+ const memScope=isHubScope?'team':memShared?'team':isPublicMem?'local':'private';
7239
7688
  const memScopeBadge=renderScopeBadge(memScope);
7240
7689
  let dedupInfo='';
7241
7690
  if(ds==='duplicate'||ds==='merged'){
@@ -7340,7 +7789,8 @@ function renderPagination(){
7340
7789
  function goPage(p){
7341
7790
  if(p<1||p>totalPages||p===currentPage) return;
7342
7791
  currentPage=p;
7343
- loadMemories();
7792
+ if(memorySearchScope==='hub') loadHubMemories();
7793
+ else loadMemories();
7344
7794
  document.getElementById('memoryList').scrollIntoView({behavior:'smooth',block:'start'});
7345
7795
  }
7346
7796
 
@@ -8223,7 +8673,7 @@ async function checkForUpdate(){
8223
8673
  const pkgSpec=d.installCommand?d.installCommand.replace(/^(?:npx\s+)?openclaw\s+plugins\s+install\s+/,''):(d.packageName+'@'+d.latest);
8224
8674
  var banner=document.createElement('div');
8225
8675
  banner.id='updateBanner';
8226
- banner.style.cssText='display:flex;align-items:center;gap:12px;padding:10px max(32px,calc((100% - 1400px)/2 + 32px));font-size:13px;font-weight:500;box-sizing:border-box;animation:slideIn .3s ease;background:linear-gradient(135deg,rgba(99,102,241,.08),rgba(139,92,246,.06));color:var(--pri);border-bottom:1px solid rgba(99,102,241,.18);backdrop-filter:blur(8px)';
8676
+ banner.style.cssText='display:flex;align-items:center;gap:12px;padding:10px 32px;max-width:1400px;margin:0 auto;width:100%;font-size:13px;font-weight:500;box-sizing:border-box;animation:slideIn .3s ease;background:linear-gradient(135deg,rgba(99,102,241,.08),rgba(139,92,246,.06));color:var(--pri);border-bottom:1px solid rgba(99,102,241,.18);backdrop-filter:blur(8px)';
8227
8677
  var textNode=document.createElement('div');
8228
8678
  textNode.style.cssText='display:flex;align-items:center;gap:8px;flex-shrink:0;font-size:13px';
8229
8679
  textNode.innerHTML='<span style="font-size:15px">\u2728</span> '+t('update.available')+' <span style="padding:2px 8px;border-radius:6px;background:rgba(99,102,241,.1);font-size:12px;font-weight:600">v'+esc(d.current)+'</span> <span style="opacity:.5">\u2192</span> <span style="padding:2px 8px;border-radius:6px;background:rgba(52,211,153,.12);color:var(--green);font-size:12px;font-weight:600">v'+esc(d.latest)+'</span>';
@@ -8256,7 +8706,7 @@ async function checkForUpdate(){
8256
8706
 
8257
8707
  /* ─── Init ─── */
8258
8708
  document.getElementById('modalOverlay').addEventListener('click',e=>{if(e.target.id==='modalOverlay')closeModal()});
8259
- document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape'){e.target.value='';loadMemories()}});
8709
+ document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape'){e.target.value='';currentPage=1;if(memorySearchScope==='hub')loadHubMemories();else loadMemories();}});
8260
8710
  applyI18n();
8261
8711
  checkAuth();
8262
8712
  </script>