@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
@@ -15,6 +15,7 @@ function viewerHTML(pluginVersion) {
15
15
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
16
16
  <style>
17
17
  *{margin:0;padding:0;box-sizing:border-box}
18
+ html{overflow-y:scroll}
18
19
  :root{
19
20
  --bg:#0b0d11;--bg-card:#12141a;--bg-card-hover:#1a1d25;
20
21
  --border:rgba(255,255,255,.08);--border-glow:rgba(255,255,255,.14);
@@ -109,7 +110,8 @@ input,textarea,select{font-family:inherit;font-size:inherit}
109
110
 
110
111
  /* ─── App Layout (dark dashboard, same as www) ─── */
111
112
  .app{display:none;flex-direction:column;min-height:100vh}
112
- .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)}
113
+ .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)}
114
+ .topbar-inner{display:flex;align-items:center;width:100%;max-width:1400px;margin:0 auto;padding:0 32px}
113
115
  .topbar .brand{display:flex;align-items:center;gap:8px;font-weight:700;font-size:15px;color:var(--text);letter-spacing:-.02em;flex-shrink:0}
114
116
  .topbar .brand .icon{width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-size:18px;background:none;border-radius:0}
115
117
  .topbar .brand .brand-title{font-size:13px;font-weight:500;opacity:.7}
@@ -195,50 +197,90 @@ input,textarea,select{font-family:inherit;font-size:inherit}
195
197
  .pending-user-name{font-size:14px;font-weight:700;color:var(--text)}
196
198
  .pending-user-meta{font-size:12px;color:var(--text-sec);margin-top:4px}
197
199
  .pending-user-actions{display:flex;gap:8px;margin-top:10px}
198
- /* ─── Admin Panel ─── */
199
- .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}
200
- .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}
201
- .admin-header-top{display:flex;justify-content:space-between;align-items:center}
202
- .admin-header h2{font-size:20px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:10px;margin:0}
203
- .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)}
204
- .admin-header-sub{font-size:12px;color:var(--text-muted);margin-top:6px}
205
- .admin-stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:10px;margin-top:18px}
206
- .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}
207
- .admin-stat-box:hover{border-color:rgba(99,102,241,.25);transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.06)}
208
- .admin-stat-box::before{content:'';position:absolute;top:0;left:50%;transform:translateX(-50%);width:40px;height:2px;border-radius:0 0 4px 4px}
209
- .admin-stat-box:nth-child(1)::before{background:var(--green)}
210
- .admin-stat-box:nth-child(2)::before{background:var(--amber)}
211
- .admin-stat-box:nth-child(3)::before{background:var(--violet)}
212
- .admin-stat-box:nth-child(4)::before{background:var(--cyan)}
213
- .admin-stat-box:nth-child(5)::before{background:var(--pri)}
214
- .admin-stat-box:nth-child(6)::before{background:var(--rose)}
215
- .admin-stat-box .as-icon{font-size:16px;margin-bottom:4px;display:block}
216
- .admin-stat-box .val{font-size:22px;font-weight:800;color:var(--text);font-variant-numeric:tabular-nums}
217
- .admin-stat-box .lbl{font-size:10px;color:var(--text-muted);margin-top:3px;letter-spacing:.03em}
218
- .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}
219
- .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}
220
- .admin-tab:hover{background:rgba(99,102,241,.06);color:var(--text)}
221
- .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)}
200
+ /* ─── Admin Panel (Cyber) ─── */
201
+ @keyframes adminGlow{0%,100%{opacity:.6}50%{opacity:1}}
202
+ @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)}}
203
+ @keyframes adminScanline{0%{background-position:0 0}100%{background-position:0 100%}}
204
+ @keyframes adminSlideIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
205
+ @keyframes adminCountUp{from{opacity:0;transform:translateY(8px) scale(.8)}to{opacity:1;transform:translateY(0) scale(1)}}
206
+ @keyframes pendingPulse{0%,100%{border-color:rgba(251,191,36,.3)}50%{border-color:rgba(251,191,36,.7)}}
207
+ @keyframes dotBreathe{0%,100%{transform:scale(1);opacity:.8}50%{transform:scale(1.3);opacity:1}}
208
+ .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}
209
+ .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}
210
+ .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}
211
+ .admin-header-top{display:flex;justify-content:space-between;align-items:center;position:relative;z-index:1}
212
+ .admin-header h2{font-size:18px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:12px;margin:0;letter-spacing:-.01em}
213
+ .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}
214
+ .admin-header-sub{font-size:11px;color:var(--text-muted);margin-top:6px;position:relative;z-index:1;letter-spacing:.02em}
215
+ .admin-stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-top:20px;position:relative;z-index:1}
216
+ .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)}
217
+ .admin-stat-box:hover{border-color:rgba(99,102,241,.35);transform:translateY(-2px);box-shadow:0 8px 24px rgba(99,102,241,.12)}
218
+ .admin-stat-box::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;opacity:.8}
219
+ .admin-stat-box:nth-child(1)::before{background:linear-gradient(90deg,transparent,#22c55e,transparent)}
220
+ .admin-stat-box:nth-child(2)::before{background:linear-gradient(90deg,transparent,#fbbf24,transparent)}
221
+ .admin-stat-box:nth-child(3)::before{background:linear-gradient(90deg,transparent,#8b5cf6,transparent)}
222
+ .admin-stat-box:nth-child(4)::before{background:linear-gradient(90deg,transparent,#06b6d4,transparent)}
223
+ .admin-stat-box:nth-child(5)::before{background:linear-gradient(90deg,transparent,#6366f1,transparent)}
224
+ .admin-stat-box:nth-child(6)::before{background:linear-gradient(90deg,transparent,#f43f5e,transparent)}
225
+ .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}
226
+ .admin-stat-box .as-icon{font-size:18px;margin-bottom:6px;display:block;filter:drop-shadow(0 2px 4px rgba(0,0,0,.15))}
227
+ .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)}
228
+ .admin-stat-box .lbl{font-size:10px;color:var(--text-muted);margin-top:4px;letter-spacing:.05em;text-transform:uppercase}
229
+ .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)}
230
+ .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}
231
+ .admin-tab:hover{background:rgba(99,102,241,.08);color:var(--text)}
232
+ .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)}
222
233
  .admin-tab .at-icon{font-size:14px;line-height:1}
223
- .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}
224
- .admin-tab.active .at-count{background:rgba(99,102,241,.15)}
234
+ .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}
235
+ .admin-tab.active .at-count{background:rgba(99,102,241,.2);box-shadow:0 0 8px rgba(99,102,241,.15)}
225
236
  .admin-panel{display:none}
226
- .admin-panel.active{display:block}
227
- .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}
237
+ .admin-panel.active{display:block;animation:adminSlideIn .3s ease-out}
238
+ .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}
228
239
  .admin-card-clickable{cursor:pointer}
229
- .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}
230
- .admin-card:hover{border-color:rgba(99,102,241,.25);box-shadow:0 2px 12px rgba(0,0,0,.04);transform:translateY(-1px)}
240
+ .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}
241
+ .admin-card:hover{border-color:rgba(99,102,241,.25);box-shadow:0 4px 20px rgba(99,102,241,.06);transform:translateY(-1px)}
242
+ .admin-card:hover::before{opacity:.8}
231
243
  .admin-card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
232
244
  .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}
233
245
  .admin-card-meta{font-size:12px;color:var(--text-muted);line-height:1.5}
234
- .au-contrib{display:flex;gap:16px;padding:10px 0;margin:6px 0;border-top:1px solid var(--border);border-bottom:1px solid var(--border)}
235
- .au-contrib-item{font-size:12px;color:var(--text-sec);display:flex;align-items:center;gap:4px}
236
- .au-contrib-num{font-size:18px;font-weight:700;line-height:1}
237
- .au-info{display:flex;flex-wrap:wrap;gap:6px 14px;padding:8px 0;font-size:12px}
238
- .au-info-item{color:var(--text-muted);white-space:nowrap}
246
+ .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)}
247
+ .au-contrib-item{font-size:12px;color:var(--text-sec);display:flex;align-items:center;gap:5px}
248
+ .au-contrib-num{font-size:20px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}
249
+ .au-info{display:flex;flex-wrap:wrap;gap:6px 16px;padding:8px 0;font-size:11px}
250
+ .au-info-item{color:var(--text-muted);white-space:nowrap;display:inline-flex;align-items:center;gap:3px}
251
+ .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}
252
+ .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}
253
+ .au-status-dot.offline{background:#6b7280;box-shadow:none}
254
+ .au-status-text{font-size:11px;font-weight:600;letter-spacing:.02em}
255
+ .au-status-text.online{color:#22c55e;text-shadow:0 0 8px rgba(34,197,94,.3)}
256
+ .au-status-text.offline{color:#6b7280}
257
+ .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}
258
+ .au-group-header:first-child{margin-top:0}
259
+ .au-group-header .au-group-dot{display:inline-block;width:8px;height:8px;border-radius:50%}
260
+ .au-group-header .au-group-dot.online{background:#22c55e;box-shadow:0 0 6px rgba(34,197,94,.5)}
261
+ .au-group-header .au-group-dot.offline{background:#6b7280}
262
+ .au-group-header .au-group-count{font-size:12px;font-weight:400;color:var(--text-muted)}
263
+ .au-card.au-offline{opacity:.55}
264
+ .au-card.au-offline:hover{opacity:.8}
265
+ .au-card.au-offline::before{background:#6b7280}
266
+ .au-card.au-online::before{background:#22c55e;box-shadow:0 0 8px rgba(34,197,94,.3)}
267
+ .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}
268
+ .admin-pending-section::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,#fbbf24,#f59e0b,transparent)}
269
+ .admin-pending-section h3{font-size:14px;font-weight:700;color:#fbbf24;margin:0 0 14px;display:flex;align-items:center;gap:8px}
270
+ .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}
271
+ .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}
272
+ .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}
273
+ .admin-pending-card:hover{border-color:rgba(251,191,36,.35);box-shadow:0 4px 16px rgba(251,191,36,.08)}
274
+ .admin-pending-card .apc-name{font-size:14px;font-weight:700;color:var(--text)}
275
+ .admin-pending-card .apc-meta{font-size:11px;color:var(--text-muted);margin-top:4px;display:flex;align-items:center;gap:6px}
276
+ .admin-pending-card .apc-actions{display:flex;gap:8px;margin-top:12px}
277
+ .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)}
278
+ .admin-pending-card .apc-actions .btn-approve:hover{transform:translateY(-1px);box-shadow:0 4px 16px rgba(34,197,94,.35)}
279
+ .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}
280
+ .admin-pending-card .apc-actions .btn-reject:hover{background:rgba(244,63,94,.06);border-color:rgba(244,63,94,.4)}
239
281
  .admin-card-tags{display:flex;gap:5px;margin:8px 0;align-items:center}
240
282
  .admin-card-tags-left{display:flex;flex-wrap:wrap;gap:5px;align-items:center;flex:1;min-width:0}
241
- .admin-card-tag{display:inline-flex;align-items:center;gap:3px;padding:2px 8px;border-radius:6px;font-size:11px;font-weight:500}
283
+ .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}
242
284
  .admin-card-tag.tag-role{background:rgba(99,102,241,.1);color:var(--pri)}
243
285
  .admin-card-tag.tag-kind{background:rgba(245,158,11,.1);color:#f59e0b}
244
286
  .admin-card-tag.tag-owner{background:rgba(34,197,94,.1);color:#22c55e}
@@ -246,16 +288,16 @@ input,textarea,select{font-family:inherit;font-size:inherit}
246
288
  .admin-card-tag.tag-version{background:rgba(139,92,246,.1);color:#8b5cf6}
247
289
  .admin-card-tag.tag-visibility{background:rgba(99,102,241,.08);color:var(--pri)}
248
290
  .admin-card-tag.tag-group{background:rgba(139,92,246,.08);color:#8b5cf6}
249
- .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}
250
- .admin-card-actions{display:inline-flex;gap:4px;margin-left:auto;align-items:center;flex-shrink:0}
291
+ .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}
292
+ .admin-card-actions{display:inline-flex;gap:6px;margin-left:auto;align-items:center;flex-shrink:0}
251
293
  .admin-card-time{font-size:11px;color:var(--text-muted)}
252
- .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}
294
+ .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}
253
295
  @keyframes adminDetailIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}
254
296
  .admin-card.expanded .admin-card-detail{display:block}
255
- .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)}
297
+ .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)}
256
298
  .admin-card-detail-meta{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:18px;font-size:12px;color:var(--text-muted)}
257
- .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}
258
- .admin-card-detail-meta .meta-item:hover{border-color:rgba(99,102,241,.25);background:rgba(99,102,241,.03)}
299
+ .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}
300
+ .admin-card-detail-meta .meta-item:hover{border-color:rgba(99,102,241,.25);background:rgba(99,102,241,.06)}
259
301
  .admin-card-detail-section{margin-top:20px}
260
302
  .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}
261
303
  .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))}
@@ -289,18 +331,20 @@ input,textarea,select{font-family:inherit;font-size:inherit}
289
331
  .admin-toolbar{display:flex;align-items:center;gap:10px;margin-bottom:14px;flex-wrap:wrap}
290
332
  .admin-toolbar h3{font-size:14px;font-weight:600;color:var(--text);white-space:nowrap;margin:0;margin-right:auto;line-height:32px}
291
333
  .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}
292
- .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}
293
- .admin-badge.admin{background:rgba(52,199,89,.15);color:#34c759}
294
- .admin-badge.member{background:rgba(142,142,147,.12);color:var(--text-muted)}
295
- .admin-badge.pending{background:rgba(255,159,10,.15);color:#ff9f0a}
334
+ .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}
335
+ .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)}
336
+ .admin-badge.member{background:rgba(99,102,241,.08);color:var(--text-muted)}
337
+ .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)}
296
338
  .admin-badge.public{background:rgba(99,102,241,.1);color:var(--pri)}
297
339
  .admin-badge.group{background:rgba(139,92,246,.1);color:var(--violet)}
298
- .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)}
299
- .admin-empty .ae-icon{font-size:28px;display:block;margin-bottom:8px;opacity:.5}
340
+ .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)}
341
+ .admin-empty .ae-icon{font-size:32px;display:block;margin-bottom:10px;opacity:.4}
300
342
  [data-theme="light"] .admin-badge.admin{background:rgba(5,150,105,.1);color:#059669}
301
343
  [data-theme="light"] .admin-badge.member{background:rgba(0,0,0,.06);color:#6b7280}
302
344
  [data-theme="light"] .admin-badge.pending{background:rgba(245,158,11,.1);color:#d97706}
303
- [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%)}
345
+ [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%)}
346
+ [data-theme="light"] .admin-stat-box{background:rgba(255,255,255,.8)}
347
+ [data-theme="light"] .admin-pending-section{background:linear-gradient(135deg,rgba(251,191,36,.03),rgba(245,158,11,.015))}
304
348
  .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}
305
349
  .confirm-overlay.show{display:flex}
306
350
  .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}
@@ -542,8 +586,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
542
586
  .pagination .pg-info{font-size:12px;color:var(--text-sec);padding:0 12px}
543
587
 
544
588
  /* ─── Tasks 视图 ─── */
545
- .tasks-view{display:none;flex:1;min-width:0;flex-direction:column;gap:16px}
546
- .tasks-view.show{display:flex}
589
+ .view-container{flex:1;min-width:0}
590
+ .view-container>.vp{display:none;flex-direction:column}
591
+ .view-container>.vp.show{display:flex}
592
+ .tasks-view{flex:1;min-width:0;flex-direction:column;gap:16px}
547
593
  .tasks-header{display:flex;flex-direction:column;gap:14px}
548
594
  .tasks-stats{display:flex;gap:16px}
549
595
  .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}
@@ -621,8 +667,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
621
667
  [data-theme="light"] .tasks-stat{background:#fff}
622
668
 
623
669
  /* ─── Skills ─── */
624
- .skills-view{display:none;flex:1;min-width:0;flex-direction:column;gap:16px}
625
- .skills-view.show{display:flex}
670
+ .skills-view{flex:1;min-width:0;flex-direction:column;gap:16px}
626
671
  .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}
627
672
  .skill-card:hover{border-color:var(--border-glow);background:var(--bg-card-hover);transform:translateY(-1px);box-shadow:var(--shadow)}
628
673
  .skill-card::before{content:'';position:absolute;top:0;left:0;bottom:0;width:3px;border-radius:3px 0 0 3px;background:var(--violet)}
@@ -696,9 +741,8 @@ input,textarea,select{font-family:inherit;font-size:inherit}
696
741
  .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)}
697
742
  [data-theme="light"] .nav-tabs{background:rgba(0,0,0,.05)}
698
743
  [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)}
699
- .analytics-view,.settings-view,.logs-view,.migrate-view,.admin-view{display:none;flex:1;min-width:0;flex-direction:column;gap:20px}
700
- .analytics-view.show,.settings-view.show,.logs-view.show,.migrate-view.show,.admin-view.show{display:flex}
701
- .feed-wrap,.tasks-view,.skills-view,.analytics-view,.settings-view,.logs-view,.migrate-view,.admin-view{max-width:960px}
744
+ .analytics-view,.settings-view,.logs-view,.migrate-view,.admin-view{flex:1;min-width:0;flex-direction:column;gap:20px}
745
+ .feed-wrap,.tasks-view,.skills-view,.analytics-view,.settings-view,.logs-view,.migrate-view,.admin-view{max-width:960px;margin:0 auto}
702
746
 
703
747
  /* ─── Logs ─── */
704
748
  .logs-toolbar{display:flex;align-items:center;justify-content:space-between;padding:8px 0}
@@ -900,7 +944,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
900
944
  .test-result.ok{color:#22c55e}
901
945
  .test-result.fail{color:var(--rose)}
902
946
  .test-result.loading{color:var(--text-muted)}
903
- .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}
947
+ .settings-actions{display:flex;gap:12px;justify-content:flex-end;align-items:center;margin-top:20px;padding-top:16px;flex-wrap:nowrap}
904
948
  .settings-actions .btn{flex:0 0 auto;min-width:0;padding:8px 24px;font-size:13px}
905
949
  .settings-actions .btn-primary{background:rgba(99,102,241,.08);color:var(--pri);border:1px solid rgba(99,102,241,.25);font-weight:600}
906
950
  .settings-actions .btn-primary:hover{background:rgba(99,102,241,.14);border-color:var(--pri)}
@@ -971,8 +1015,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
971
1015
  .migrate-log-item .log-meta .tag.error{background:rgba(239,68,68,.1);color:#ef4444}
972
1016
  .migrate-log-item .log-meta .tag.duplicate{background:rgba(245,158,11,.1);color:#f59e0b}
973
1017
  @keyframes migrateFadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
974
- .feed-wrap{flex:1;min-width:0;display:flex;flex-direction:column}
975
- .feed-wrap.hide{display:none}
1018
+ .feed-wrap{flex:1;min-width:0;flex-direction:column}
976
1019
  .analytics-view{flex-direction:column;gap:20px}
977
1020
  .analytics-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
978
1021
  .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)}
@@ -1056,7 +1099,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1056
1099
  [data-theme="light"] .auth-theme-toggle .theme-icon-dark{display:none}
1057
1100
 
1058
1101
  @media(max-width:1100px){.analytics-cards{grid-template-columns:repeat(3,1fr)}}
1059
- @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}}
1102
+ @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}}
1060
1103
  </style>
1061
1104
  </head>
1062
1105
  <body>
@@ -1141,6 +1184,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1141
1184
  <!-- ─── Main App ─── -->
1142
1185
  <div class="app" id="app">
1143
1186
  <div class="topbar">
1187
+ <div class="topbar-inner">
1144
1188
  <div class="brand">
1145
1189
  <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>
1146
1190
  <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}
@@ -1167,10 +1211,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1167
1211
  <div class="notif-panel-body" id="notifPanelBody"><div class="notif-empty" data-i18n="notif.empty">No notifications</div></div>
1168
1212
  </div>
1169
1213
  </div>
1170
- <button class="btn btn-ghost btn-sm" onclick="loadAll()" data-i18n="refresh">\u21BB Refresh</button>
1171
1214
  <button class="btn btn-ghost btn-sm" onclick="doLogout()" data-i18n="logout">Logout</button>
1172
1215
  </div>
1173
1216
  </div>
1217
+ </div>
1174
1218
 
1175
1219
  <div class="main-content">
1176
1220
  <div class="sidebar" id="sidebar">
@@ -1192,18 +1236,19 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1192
1236
  <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>
1193
1237
  </div>
1194
1238
 
1195
- <div class="feed-wrap" id="feedWrap">
1239
+ <div class="view-container">
1240
+ <div class="feed-wrap vp show" id="feedWrap">
1196
1241
  <div class="feed">
1197
1242
  <div class="search-bar">
1198
1243
  <span class="search-icon">\u{1F50D}</span>
1199
1244
  <input type="text" id="searchInput" data-i18n-ph="search.placeholder" placeholder="Search memories (supports semantic search)..." oninput="debounceSearch()">
1200
1245
  <select id="filterOwner" class="filter-select" onchange="onOwnerFilterChange()">
1201
- <option value="" data-i18n="filter.allowners">All owners</option>
1202
- <option value="public" data-i18n="filter.public">Public</option>
1246
+ <option value="" data-i18n="filter.allagents">All agents</option>
1203
1247
  </select>
1204
1248
  <select id="memorySearchScope" class="filter-select" onchange="onMemoryScopeChange()">
1205
- <option value="local" data-i18n="scope.local">Local</option>
1206
- <option value="all" data-i18n="scope.hub">Team</option>
1249
+ <option value="local" data-i18n="scope.thisAgent">This Agent</option>
1250
+ <option value="allLocal" data-i18n="scope.thisDevice">This Device</option>
1251
+ <option value="hub" data-i18n="scope.hub">Team</option>
1207
1252
  </select>
1208
1253
  </div>
1209
1254
  <div class="search-meta" id="searchMeta"></div>
@@ -1232,7 +1277,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1232
1277
  <div class="pagination" id="pagination"></div>
1233
1278
  </div>
1234
1279
  </div>
1235
- <div class="tasks-view" id="tasksView">
1280
+ <div class="tasks-view vp" id="tasksView">
1236
1281
  <div class="tasks-header">
1237
1282
  <div class="tasks-stats">
1238
1283
  <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>
@@ -1246,10 +1291,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1246
1291
  <button class="filter-chip" data-task-status="completed" onclick="setTaskStatusFilter(this,'completed')" data-i18n="tasks.status.completed">Completed</button>
1247
1292
  <button class="filter-chip" data-task-status="skipped" onclick="setTaskStatusFilter(this,'skipped')" data-i18n="tasks.status.skipped">Skipped</button>
1248
1293
  <select id="taskSearchScope" class="scope-select" onchange="onTaskScopeChange()">
1249
- <option value="local" data-i18n="scope.local">Local</option>
1250
- <option value="all" data-i18n="scope.hub">Team</option>
1294
+ <option value="local" data-i18n="scope.thisAgent">This Agent</option>
1295
+ <option value="allLocal" data-i18n="scope.thisDevice">This Device</option>
1296
+ <option value="hub" data-i18n="scope.hub">Team</option>
1251
1297
  </select>
1252
- <button class="btn btn-sm btn-ghost" onclick="loadTasks()" style="margin-left:auto" data-i18n="refresh">\u21BB Refresh</button>
1253
1298
  </div>
1254
1299
  </div>
1255
1300
  <div class="tasks-list" id="tasksList"><div class="spinner"></div></div>
@@ -1283,13 +1328,14 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1283
1328
  <div class="content" id="sharedMemoryContent"></div>
1284
1329
  </div>
1285
1330
  </div>
1286
- <div class="skills-view" id="skillsView">
1331
+ <div class="skills-view vp" id="skillsView">
1287
1332
  <div class="search-bar">
1288
1333
  <span class="search-icon">🔍</span>
1289
1334
  <input type="text" id="skillSearchInput" placeholder="Search skills..." data-i18n-ph="skills.search.placeholder" oninput="debounceSkillSearch()">
1290
1335
  <select id="skillSearchScope" class="scope-select" onchange="onSkillScopeChange()">
1291
- <option value="local" data-i18n="scope.local">Local</option>
1292
- <option value="all" data-i18n="scope.hub">Team</option>
1336
+ <option value="local" data-i18n="scope.thisAgent">This Agent</option>
1337
+ <option value="allLocal" data-i18n="scope.thisDevice">This Device</option>
1338
+ <option value="hub" data-i18n="scope.hub">Team</option>
1293
1339
  </select>
1294
1340
  </div>
1295
1341
  <div class="search-meta" id="skillSearchMeta"></div>
@@ -1312,7 +1358,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1312
1358
  <option value="public" data-i18n="filter.public">Public</option>
1313
1359
  <option value="private" data-i18n="filter.private">Private</option>
1314
1360
  </select>
1315
- <button class="btn btn-sm btn-ghost" onclick="loadSkills()" style="margin-left:auto" data-i18n="refresh">\u21BB Refresh</button>
1316
1361
  </div>
1317
1362
  </div>
1318
1363
  <div class="tasks-list" id="skillsList"><div class="spinner"></div></div>
@@ -1345,7 +1390,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1345
1390
  <div id="skillDetailActions" style="display:flex;gap:8px;margin-top:16px;padding-top:12px;border-top:1px solid var(--border)"></div>
1346
1391
  </div>
1347
1392
  </div>
1348
- <div class="analytics-view" id="analyticsView">
1393
+ <div class="analytics-view vp" id="analyticsView">
1349
1394
  <div class="metrics-toolbar" style="margin-bottom:0">
1350
1395
  <span style="font-size:12px;color:var(--text-sec);font-weight:600" data-i18n="range">Range</span>
1351
1396
  <button class="range-btn" data-days="7" onclick="setMetricsDays(7)">7 <span data-i18n="range.days">days</span></button>
@@ -1389,7 +1434,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1389
1434
  </div>
1390
1435
 
1391
1436
  <!-- ─── Logs View ─── -->
1392
- <div class="logs-view" id="logsView">
1437
+ <div class="logs-view vp" id="logsView">
1393
1438
  <div class="logs-toolbar">
1394
1439
  <div class="logs-toolbar-left">
1395
1440
  <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">
@@ -1406,7 +1451,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1406
1451
  </div>
1407
1452
 
1408
1453
  <!-- ─── Settings View ─── -->
1409
- <div class="settings-view" id="settingsView">
1454
+ <div class="settings-view vp" id="settingsView">
1410
1455
  <div class="settings-tabs-bar">
1411
1456
  <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>
1412
1457
  <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>
@@ -1572,7 +1617,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1572
1617
  </div>
1573
1618
  </div>
1574
1619
 
1575
- <div class="settings-card-divider"></div>
1576
1620
  <div class="settings-actions">
1577
1621
  <span class="settings-saved" id="modelsSaved">\u2713 <span data-i18n="settings.saved">Saved</span></span>
1578
1622
  <button class="btn btn-ghost" onclick="loadConfig()" data-i18n="settings.reset">Reset</button>
@@ -1661,6 +1705,11 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1661
1705
  <input type="text" id="cfgHubTeamName" placeholder="My Team">
1662
1706
  <div class="field-hint" data-i18n="settings.hub.teamName.hint">Your team display name</div>
1663
1707
  </div>
1708
+ <div class="settings-field">
1709
+ <label data-i18n="settings.hub.adminName">Admin Name</label>
1710
+ <input type="text" id="cfgHubAdminName" placeholder="" maxlength="32">
1711
+ <div class="field-hint" data-i18n="settings.hub.adminName.hint">Your display name as team admin</div>
1712
+ </div>
1664
1713
  </div>
1665
1714
  <div id="hubShareInfo" style="display:none;margin-top:14px;background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:14px 18px">
1666
1715
  <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>
@@ -1689,6 +1738,11 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1689
1738
  <input type="text" id="cfgClientTeamToken" placeholder="">
1690
1739
  <div class="field-hint" data-i18n="settings.hub.teamTokenClient.hint">Get this from your team admin to join</div>
1691
1740
  </div>
1741
+ <div class="settings-field">
1742
+ <label data-i18n="settings.hub.nickname">Nickname</label>
1743
+ <input type="text" id="cfgClientNickname" placeholder="" maxlength="32">
1744
+ <div class="field-hint" data-i18n="settings.hub.nickname.hint">Your display name in the team. If empty, uses system username.</div>
1745
+ </div>
1692
1746
  <input type="hidden" id="cfgClientUserToken" value="">
1693
1747
  </div>
1694
1748
  <div style="margin-top:12px">
@@ -1743,7 +1797,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1743
1797
  </div>
1744
1798
  <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>
1745
1799
 
1746
- <div class="settings-card-divider"></div>
1747
1800
  <div class="settings-actions">
1748
1801
  <span class="settings-saved" id="generalSaved">\u2713 <span data-i18n="settings.saved">Saved</span></span>
1749
1802
  <button class="btn btn-ghost" onclick="loadConfig()" data-i18n="settings.reset">Reset</button>
@@ -1758,13 +1811,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1758
1811
  </div>
1759
1812
 
1760
1813
  <!-- ─── Admin Page ─── -->
1761
- <div class="admin-view" id="adminView">
1814
+ <div class="admin-view vp" id="adminView">
1762
1815
  <div id="adminNotEnabled" style="display:none"></div>
1763
1816
  <div id="adminMainContent">
1764
1817
  <div class="admin-header">
1765
1818
  <div class="admin-header-top">
1766
- <h2><span class="ah-icon">\u{1F6E1}</span> <span data-i18n="admin.title">Team Admin Panel</span></h2>
1767
- <button class="btn btn-sm btn-ghost" onclick="loadAdminData()" style="backdrop-filter:blur(8px)" data-i18n="admin.refresh">\u21BB Refresh</button>
1819
+ <h2><span class="ah-icon">\u{26A1}</span> <span data-i18n="admin.title">Team Admin Panel</span></h2>
1768
1820
  </div>
1769
1821
  <div class="admin-header-sub" data-i18n="admin.subtitle">Manage team members and shared resources</div>
1770
1822
  <div class="admin-stat-row" id="adminStats"></div>
@@ -1783,7 +1835,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1783
1835
  </div>
1784
1836
 
1785
1837
  <!-- ─── Import Page ─── -->
1786
- <div class="migrate-view" id="migrateView">
1838
+ <div class="migrate-view vp" id="migrateView">
1787
1839
  <div class="settings-section" style="border:1px solid rgba(99,102,241,.15)">
1788
1840
  <h3><span class="icon">\u{1F4E5}</span> <span data-i18n="migrate.title">Import OpenClaw Memory</span></h3>
1789
1841
  <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>
@@ -1953,6 +2005,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1953
2005
 
1954
2006
  </div>
1955
2007
 
2008
+ </div>
1956
2009
  </div>
1957
2010
  </div>
1958
2011
 
@@ -1976,7 +2029,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1976
2029
  <script>
1977
2030
  let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=40,metricsDays=30;
1978
2031
  let memorySearchScope='local',skillSearchScope='local',taskSearchScope='local';
2032
+ let _lastMemoriesFingerprint='',_lastTasksFingerprint='',_lastSkillsFingerprint='';
1979
2033
  let _embeddingWarningShown=false;
2034
+ let _currentAgentOwner='agent:main';
1980
2035
 
1981
2036
  /* ─── i18n ─── */
1982
2037
  const I18N={
@@ -2027,6 +2082,8 @@ const I18N={
2027
2082
  'skills.load.error':'Failed to load skills',
2028
2083
  'skills.hub.title':'\u{1F310} Team Skills',
2029
2084
  'scope.local':'Local',
2085
+ 'scope.thisAgent':'This Agent',
2086
+ 'scope.thisDevice':'This Device',
2030
2087
  'scope.group':'Group',
2031
2088
  'scope.all':'All',
2032
2089
  'skills.visibility.public':'Shared Locally',
@@ -2055,6 +2112,15 @@ const I18N={
2055
2112
  'notif.removed.memory':'Your shared memory was removed by admin',
2056
2113
  'notif.removed.task':'Your shared task was removed by admin',
2057
2114
  'notif.removed.skill':'Your shared skill was removed by admin',
2115
+ 'notif.shared.memory':'A new memory was shared',
2116
+ 'notif.shared.task':'A new task was shared',
2117
+ 'notif.shared.skill':'A new skill was shared',
2118
+ 'notif.unshared.memory':'A memory was unshared',
2119
+ 'notif.unshared.task':'A task was unshared',
2120
+ 'notif.unshared.skill':'A skill was unshared',
2121
+ 'notif.userJoin':'New user requests to join the team',
2122
+ 'notif.userOnline':'User came online',
2123
+ 'notif.userOffline':'User went offline',
2058
2124
  'notif.clearAll':'Clear all',
2059
2125
  'notif.timeAgo.just':'just now',
2060
2126
  'notif.timeAgo.min':'{n}m ago',
@@ -2078,6 +2144,7 @@ const I18N={
2078
2144
  'filter.newest':'Newest first',
2079
2145
  'filter.oldest':'Oldest first',
2080
2146
  'filter.allowners':'All owners',
2147
+ 'filter.allagents':'All agents',
2081
2148
  'filter.allsessions':'All sessions',
2082
2149
  'filter.public':'Public',
2083
2150
  'filter.private':'Private',
@@ -2326,6 +2393,8 @@ const I18N={
2326
2393
  'settings.hub.port.hint':'Port for team server. Default: 18800',
2327
2394
  'settings.hub.teamName':'Team Name',
2328
2395
  'settings.hub.teamName.hint':'Display name for your team',
2396
+ 'settings.hub.adminName':'Admin Name',
2397
+ 'settings.hub.adminName.hint':'Your display name as team admin',
2329
2398
  'settings.hub.teamToken':'Team Token',
2330
2399
  'settings.hub.teamToken.hint':'Auto-generated secret for clients to join. Click to copy. Share this with your team members.',
2331
2400
  'settings.hub.tokenCopied':'Team Token copied!',
@@ -2345,10 +2414,13 @@ const I18N={
2345
2414
  'settings.hub.hubAddress.hint':'Team server address, e.g. 192.168.1.100:18800',
2346
2415
  'settings.hub.teamTokenClient':'Team Token',
2347
2416
  'settings.hub.teamTokenClient.hint':'Get this from your team admin to join',
2417
+ 'settings.hub.nickname':'Nickname',
2418
+ 'settings.hub.nickname.hint':'Your display name in the team. If empty, uses system username.',
2348
2419
  'settings.hub.userToken':'User Token',
2349
2420
  'settings.hub.userToken.hint':'Usually auto-obtained after joining. Only fill if given by admin.',
2350
2421
  'settings.hub.testConnection':'Test Connection',
2351
2422
  'settings.hub.test.noAddr':'Please enter server address first',
2423
+ 'settings.hub.teamToken.required':'Please enter team token',
2352
2424
  'settings.hub.test.testing':'Testing...',
2353
2425
  'settings.hub.test.ok':'Connected successfully',
2354
2426
  'settings.hub.test.fail':'Connection failed',
@@ -2383,6 +2455,7 @@ const I18N={
2383
2455
  'memory.detail.viewTarget':'View target: ',
2384
2456
  'admin.title':'Team Admin Panel',
2385
2457
  'admin.subtitle':'Manage team members and shared resources',
2458
+ 'admin.subtitle.member':'Browse shared memories, tasks and skills from your team',
2386
2459
  'admin.refresh':'\u21BB Refresh',
2387
2460
  'admin.tab.users':'Users',
2388
2461
  'admin.tab.tasks':'Shared Tasks',
@@ -2398,6 +2471,12 @@ const I18N={
2398
2471
  'admin.pendingApproval':'Pending Approval',
2399
2472
  'admin.activeUsers':'Active Users',
2400
2473
  'admin.noActiveUsers':'No active users.',
2474
+ 'admin.onlineUsers':'Online',
2475
+ 'admin.offlineUsers':'Offline',
2476
+ 'admin.online':'Online',
2477
+ 'admin.offline':'Offline',
2478
+ 'admin.offlineFor':'Offline for {time}',
2479
+ 'admin.onlineNow':'Online now',
2401
2480
  'admin.approve':'Approve',
2402
2481
  'admin.reject':'Reject',
2403
2482
  'admin.device':'Device: ',
@@ -2414,6 +2493,7 @@ const I18N={
2414
2493
  'admin.demoteMember':'Demote to Member',
2415
2494
  'admin.editName':'Edit Name',
2416
2495
  'admin.lastAdminHint':'Last admin — cannot remove or demote',
2496
+ 'admin.ownerHint':'Hub owner — cannot be demoted or removed',
2417
2497
  'admin.editNamePrompt':'Enter new username:',
2418
2498
  'confirm.promoteAdmin':'Promote this user to admin? They will be able to manage all team members and resources.',
2419
2499
  'confirm.demoteMember':'Demote this admin to member?',
@@ -2474,6 +2554,7 @@ const I18N={
2474
2554
  'admin.groupsFailed':'Failed to load groups: ',
2475
2555
  'toast.userApproved':'User approved',
2476
2556
  'sharing.approved.toast':'Your join request has been approved!',
2557
+ 'sharing.rejected.toast':'Your join request was rejected by the admin.',
2477
2558
  'toast.userRejected':'User rejected',
2478
2559
  'toast.approveFail':'Approve failed',
2479
2560
  'toast.rejectFail':'Reject failed',
@@ -2634,6 +2715,8 @@ const I18N={
2634
2715
  '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?',
2635
2716
  '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?',
2636
2717
  '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',
2718
+ '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?',
2719
+ '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?',
2637
2720
  'admin.notEnabled.title':'Team sharing is not enabled',
2638
2721
  '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.',
2639
2722
  'admin.notEnabled.setupHub':'Set Up as Team Server',
@@ -2708,6 +2791,8 @@ const I18N={
2708
2791
  'skills.load.error':'加载技能失败',
2709
2792
  'skills.hub.title':'\u{1F310} 团队共享技能',
2710
2793
  'scope.local':'本地',
2794
+ 'scope.thisAgent':'当前智能体',
2795
+ 'scope.thisDevice':'本机全部',
2711
2796
  'scope.group':'团队',
2712
2797
  'scope.all':'全部',
2713
2798
  'skills.visibility.public':'本机共享',
@@ -2736,6 +2821,15 @@ const I18N={
2736
2821
  'notif.removed.memory':'你共享的记忆被管理员移除',
2737
2822
  'notif.removed.task':'你共享的任务被管理员移除',
2738
2823
  'notif.removed.skill':'你共享的技能被管理员移除',
2824
+ 'notif.shared.memory':'有新的记忆被共享',
2825
+ 'notif.shared.task':'有新的任务被共享',
2826
+ 'notif.shared.skill':'有新的技能被共享',
2827
+ 'notif.unshared.memory':'有记忆取消了共享',
2828
+ 'notif.unshared.task':'有任务取消了共享',
2829
+ 'notif.unshared.skill':'有技能取消了共享',
2830
+ 'notif.userJoin':'有新用户申请加入团队',
2831
+ 'notif.userOnline':'用户上线了',
2832
+ 'notif.userOffline':'用户下线了',
2739
2833
  'notif.clearAll':'清除全部',
2740
2834
  'notif.timeAgo.just':'刚刚',
2741
2835
  'notif.timeAgo.min':'{n}分钟前',
@@ -2759,6 +2853,7 @@ const I18N={
2759
2853
  'filter.newest':'最新优先',
2760
2854
  'filter.oldest':'最早优先',
2761
2855
  'filter.allowners':'所有归属',
2856
+ 'filter.allagents':'全部智能体',
2762
2857
  'filter.allsessions':'全部会话',
2763
2858
  'filter.public':'公开',
2764
2859
  'filter.private':'私有',
@@ -3007,6 +3102,8 @@ const I18N={
3007
3102
  'settings.hub.port.hint':'团队服务端口,默认 18800',
3008
3103
  'settings.hub.teamName':'团队名称',
3009
3104
  'settings.hub.teamName.hint':'你的团队显示名称',
3105
+ 'settings.hub.adminName':'管理员名称',
3106
+ 'settings.hub.adminName.hint':'你在团队中的显示名称',
3010
3107
  'settings.hub.teamToken':'团队令牌',
3011
3108
  'settings.hub.teamToken.hint':'自动生成的密钥,点击可复制。请将此令牌分享给团队成员。',
3012
3109
  'settings.hub.tokenCopied':'团队令牌已复制!',
@@ -3026,10 +3123,13 @@ const I18N={
3026
3123
  'settings.hub.hubAddress.hint':'团队服务器地址,如 192.168.1.100:18800',
3027
3124
  'settings.hub.teamTokenClient':'团队令牌',
3028
3125
  'settings.hub.teamTokenClient.hint':'向团队管理员获取此令牌以加入团队',
3126
+ 'settings.hub.nickname':'昵称',
3127
+ 'settings.hub.nickname.hint':'你在团队中的显示名称。留空则使用系统用户名。',
3029
3128
  'settings.hub.userToken':'用户令牌',
3030
3129
  'settings.hub.userToken.hint':'通常在加入团队后自动获取,无需手动填写。',
3031
3130
  'settings.hub.testConnection':'测试连接',
3032
3131
  'settings.hub.test.noAddr':'请先输入服务器地址',
3132
+ 'settings.hub.teamToken.required':'请输入团队令牌',
3033
3133
  'settings.hub.test.testing':'测试中...',
3034
3134
  'settings.hub.test.ok':'连接成功',
3035
3135
  'settings.hub.test.fail':'连接失败',
@@ -3064,6 +3164,7 @@ const I18N={
3064
3164
  'memory.detail.viewTarget':'查看目标: ',
3065
3165
  'admin.title':'团队管理面板',
3066
3166
  'admin.subtitle':'管理团队成员和共享资源',
3167
+ 'admin.subtitle.member':'浏览团队共享的记忆、任务和技能',
3067
3168
  'admin.refresh':'\u21BB 刷新',
3068
3169
  'admin.tab.users':'用户',
3069
3170
  'admin.tab.tasks':'共享任务',
@@ -3079,6 +3180,12 @@ const I18N={
3079
3180
  'admin.pendingApproval':'待审批',
3080
3181
  'admin.activeUsers':'活跃用户',
3081
3182
  'admin.noActiveUsers':'暂无活跃用户。',
3183
+ 'admin.onlineUsers':'在线',
3184
+ 'admin.offlineUsers':'离线',
3185
+ 'admin.online':'在线',
3186
+ 'admin.offline':'离线',
3187
+ 'admin.offlineFor':'离线 {time}',
3188
+ 'admin.onlineNow':'当前在线',
3082
3189
  'admin.approve':'批准',
3083
3190
  'admin.reject':'拒绝',
3084
3191
  'admin.device':'设备:',
@@ -3095,6 +3202,7 @@ const I18N={
3095
3202
  'admin.demoteMember':'降为成员',
3096
3203
  'admin.editName':'编辑名称',
3097
3204
  'admin.lastAdminHint':'唯一管理员 — 无法删除或降级',
3205
+ 'admin.ownerHint':'Hub 创建者 — 不可降级或移除',
3098
3206
  'admin.editNamePrompt':'请输入新用户名:',
3099
3207
  'confirm.promoteAdmin':'确定要将此用户提升为管理员吗?管理员可以管理所有团队成员和资源。',
3100
3208
  'confirm.demoteMember':'确定要将此管理员降为普通成员吗?',
@@ -3155,6 +3263,7 @@ const I18N={
3155
3263
  'admin.groupsFailed':'加载分组失败:',
3156
3264
  'toast.userApproved':'用户已批准',
3157
3265
  'sharing.approved.toast':'您的加入申请已通过审核!',
3266
+ 'sharing.rejected.toast':'您的加入申请已被管理员拒绝。',
3158
3267
  'toast.userRejected':'用户已拒绝',
3159
3268
  'toast.approveFail':'批准失败',
3160
3269
  'toast.rejectFail':'拒绝失败',
@@ -3315,6 +3424,8 @@ const I18N={
3315
3424
  'sharing.disable.confirm.hub':'你即将关闭团队服务。\\n\\n关闭后将会:\\n\\u2022 所有已连接的团队成员将断开连接\\n\\u2022 他们将无法继续同步记忆、任务和技能\\n\\u2022 已共享的数据会保留,重新开启后仍可使用\\n\\n确定要关闭吗?',
3316
3425
  'sharing.disable.confirm.client':'你即将断开与团队的连接。\\n\\n断开后将会:\\n\\u2022 你将无法再接收团队共享的记忆、任务和技能\\n\\u2022 你的本地数据不受影响,会完整保留\\n\\u2022 之后可以随时重新开启共享来恢复连接\\n\\n确定要断开吗?',
3317
3426
  'sharing.disable.restartAlert':'共享已关闭。请重启 OpenClaw 网关使更改生效。\\n\\n执行命令:openclaw gateway stop && openclaw gateway start',
3427
+ 'sharing.switch.hubToClient':'你即将从服务端模式切换为客户端模式。\\n\\n切换后将会:\\n\\u2022 Hub 服务将在重启后关闭\\n\\u2022 所有已连接的团队成员将断开连接\\n\\u2022 Hub 上的共享数据会保留,以后可恢复使用\\n\\u2022 你将作为客户端加入指定的远程团队\\n\\n确定要切换吗?',
3428
+ 'sharing.switch.clientToHub':'你即将从客户端模式切换为服务端模式。\\n\\n切换后将会:\\n\\u2022 你将断开与当前团队的连接\\n\\u2022 重启后将启动新的 Hub 服务\\n\\u2022 你的本地数据不受影响\\n\\n确定要切换吗?',
3318
3429
  'admin.notEnabled.title':'团队共享尚未开启',
3319
3430
  'admin.notEnabled.desc':'管理面板用于管理团队成员、共享的记忆、任务和技能。使用此功能前,需要先开启团队共享。',
3320
3431
  'admin.notEnabled.setupHub':'配置为团队服务端',
@@ -3552,59 +3663,49 @@ function switchSettingsTab(tab,btn){
3552
3663
  });
3553
3664
  }
3554
3665
 
3666
+ var _activeView='memories';
3555
3667
  function switchView(view){
3668
+ _activeView=view;
3556
3669
  document.querySelectorAll('.nav-tabs .tab').forEach(t=>t.classList.toggle('active',t.dataset.view===view));
3557
- const feedWrap=document.getElementById('feedWrap');
3558
- const analyticsView=document.getElementById('analyticsView');
3559
- const tasksView=document.getElementById('tasksView');
3560
- const skillsView=document.getElementById('skillsView');
3561
- const logsView=document.getElementById('logsView');
3562
- const settingsView=document.getElementById('settingsView');
3563
- const migrateView=document.getElementById('migrateView');
3564
- const adminView=document.getElementById('adminView');
3565
- const sidebar=document.getElementById('sidebar');
3566
- feedWrap.classList.add('hide');
3567
- analyticsView.classList.remove('show');
3568
- tasksView.classList.remove('show');
3569
- skillsView.classList.remove('show');
3570
- logsView.classList.remove('show');
3571
- settingsView.classList.remove('show');
3572
- migrateView.classList.remove('show');
3573
- if(adminView) adminView.classList.remove('show');
3670
+ var viewMap={memories:'feedWrap',tasks:'tasksView',skills:'skillsView',analytics:'analyticsView',logs:'logsView',settings:'settingsView',import:'migrateView',admin:'adminView'};
3671
+ for(var k in viewMap){
3672
+ var el=document.getElementById(viewMap[k]);
3673
+ if(el) el.classList.toggle('show',k===view);
3674
+ }
3574
3675
  var sessionSection=document.getElementById('sidebarSessionSection');
3575
- if(view==='memories'){
3576
- feedWrap.classList.remove('hide');
3577
- if(sessionSection){sessionSection.style.visibility='';sessionSection.style.pointerEvents='';}
3578
- } else if(view==='tasks'||view==='skills'){
3579
- if(sessionSection){sessionSection.style.visibility='hidden';sessionSection.style.pointerEvents='none';}
3580
- if(view==='tasks'){tasksView.classList.add('show');loadTasks();}
3581
- else{skillsView.classList.add('show');loadSkills();}
3582
- } else {
3583
- if(sessionSection){sessionSection.style.visibility='hidden';sessionSection.style.pointerEvents='none';}
3584
- if(view==='analytics'){
3585
- analyticsView.classList.add('show');
3586
- loadMetrics();
3587
- } else if(view==='logs'){
3588
- logsView.classList.add('show');
3589
- loadLogs();
3590
- } else if(view==='settings'){
3591
- settingsView.classList.add('show');
3592
- loadConfig();
3593
- loadModelHealth();
3594
- } else if(view==='import'){
3595
- migrateView.classList.add('show');
3596
- if(!window._migrateRunning) migrateScan(false);
3597
- } else if(view==='admin'){
3598
- if(adminView){adminView.classList.add('show');loadAdminData();}
3599
- }
3676
+ if(sessionSection){
3677
+ if(view==='memories'){sessionSection.style.visibility='';sessionSection.style.pointerEvents='';}
3678
+ else{sessionSection.style.visibility='hidden';sessionSection.style.pointerEvents='none';}
3600
3679
  }
3680
+ if(view==='tasks') loadTasks();
3681
+ else if(view==='skills') loadSkills();
3682
+ else if(view==='analytics') loadMetrics();
3683
+ else if(view==='logs') loadLogs();
3684
+ else if(view==='settings'){loadConfig();loadModelHealth();}
3685
+ else if(view==='import'){if(!window._migrateRunning) migrateScan(false);}
3686
+ else if(view==='admin'){loadAdminData();}
3601
3687
  }
3602
3688
 
3603
3689
  function onMemoryScopeChange(){
3604
3690
  memorySearchScope=document.getElementById('memorySearchScope')?.value||'local';
3691
+ currentPage=1;
3692
+ activeSession=null;activeRole='';
3693
+ _lastMemoriesFingerprint='';
3694
+ var isHub=memorySearchScope==='hub';
3695
+ var isLocal=memorySearchScope==='local';
3696
+ var ownerSel=document.getElementById('filterOwner');
3697
+ var filterBar=document.getElementById('filterBar');
3698
+ var dateFilter=document.querySelector('.date-filter');
3699
+ if(ownerSel) ownerSel.style.display=(isHub||isLocal)?'none':'';
3700
+ if(filterBar) filterBar.style.display=isHub?'none':'';
3701
+ if(dateFilter) dateFilter.style.display=isHub?'none':'';
3605
3702
  if(document.getElementById('searchInput').value.trim()) doSearch(document.getElementById('searchInput').value);
3606
- else if(memorySearchScope!=='local') { document.getElementById('sharingSearchMeta').textContent=''; loadHubMemories(); }
3607
- else { document.getElementById('sharingSearchMeta').textContent=''; loadMemories(); }
3703
+ else if(isHub) { document.getElementById('sharingSearchMeta').textContent=''; loadHubMemories(); }
3704
+ else {
3705
+ document.getElementById('sharingSearchMeta').textContent='';
3706
+ var ownerArg=isLocal?_currentAgentOwner:undefined;
3707
+ loadStats(ownerArg); loadMemories();
3708
+ }
3608
3709
  }
3609
3710
 
3610
3711
  function onSkillScopeChange(){
@@ -3619,6 +3720,7 @@ function onTaskScopeChange(){
3619
3720
  }
3620
3721
 
3621
3722
  var _clientPendingPollTimer=null;
3723
+ var _lastSharingConnStatus='';
3622
3724
  async function loadSharingStatus(forcePending){
3623
3725
  try{
3624
3726
  const r=await fetch('/api/sharing/status');
@@ -3628,13 +3730,26 @@ async function loadSharingStatus(forcePending){
3628
3730
  renderSharingSettings(d);
3629
3731
  updateTeamGuide(d);
3630
3732
  if(forcePending && d && d.admin && d.admin.canManageUsers) loadSharingPendingUsers();
3631
- var conn=d&&d.connection||{};
3632
- if(conn.pendingApproval&&!_clientPendingPollTimer){
3733
+ if(!d||!d.enabled){
3734
+ if(_clientPendingPollTimer){clearInterval(_clientPendingPollTimer);_clientPendingPollTimer=null;}
3735
+ _lastSharingConnStatus='';
3736
+ return;
3737
+ }
3738
+ var conn=d.connection||{};
3739
+ var curStatus=conn.rejected?'rejected':conn.pendingApproval?'pending':conn.connected?'connected':'none';
3740
+ if(_lastSharingConnStatus==='pending'&&curStatus==='rejected'){
3741
+ toast(t('sharing.rejected.toast'),'error');
3742
+ }
3743
+ if(_lastSharingConnStatus==='pending'&&curStatus==='connected'){
3744
+ toast(t('sharing.approved.toast'),'success');
3745
+ }
3746
+ _lastSharingConnStatus=curStatus;
3747
+ if(curStatus==='pending'&&!_clientPendingPollTimer){
3633
3748
  _clientPendingPollTimer=setInterval(function(){loadSharingStatus(false);},10000);
3634
- }else if(!conn.pendingApproval&&_clientPendingPollTimer){
3749
+ }
3750
+ if(curStatus!=='pending'&&_clientPendingPollTimer){
3635
3751
  clearInterval(_clientPendingPollTimer);
3636
3752
  _clientPendingPollTimer=null;
3637
- if(conn.connected) toast(t('sharing.approved.toast'),'success');
3638
3753
  }
3639
3754
  }catch(e){
3640
3755
  renderSharingSidebar(null);
@@ -3643,12 +3758,17 @@ async function loadSharingStatus(forcePending){
3643
3758
  }
3644
3759
  }
3645
3760
 
3761
+ var _lastSidebarFingerprint='';
3646
3762
  function renderSharingSidebar(data){
3647
3763
  var section=document.getElementById('sidebarSharingSection');
3648
3764
  var statusEl=document.getElementById('sharingSidebarStatus');
3649
3765
  var hintEl=document.getElementById('sharingSidebarHint');
3650
3766
  var badgeEl=document.getElementById('sharingSidebarConnBadge');
3651
3767
  if(!statusEl||!hintEl) return;
3768
+ var conn=data&&data.connection||{};
3769
+ 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});
3770
+ if(fp===_lastSidebarFingerprint) return;
3771
+ _lastSidebarFingerprint=fp;
3652
3772
  if(!data||!data.enabled){
3653
3773
  if(section) section.style.display='none';
3654
3774
  window._isHubAdmin=false;
@@ -3700,12 +3820,17 @@ function renderSharingSidebar(data){
3700
3820
  }
3701
3821
  }
3702
3822
 
3823
+ var _lastSettingsFingerprint='';
3703
3824
  function renderSharingSettings(data){
3704
3825
  var statusEl=document.getElementById('sharingStatusPanel');
3705
3826
  var teamEl=document.getElementById('sharingTeamPanel');
3706
3827
  var adminEl=document.getElementById('sharingAdminPanel');
3707
3828
  var panelsWrap=document.getElementById('sharingPanelsWrap');
3708
3829
  if(!statusEl||!teamEl||!adminEl) return;
3830
+ var conn2=data&&data.connection||{};
3831
+ 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});
3832
+ if(fp2===_lastSettingsFingerprint) return;
3833
+ _lastSettingsFingerprint=fp2;
3709
3834
  if(!data||!data.enabled){
3710
3835
  statusEl.innerHTML='';teamEl.innerHTML='';adminEl.innerHTML='';
3711
3836
  if(panelsWrap) panelsWrap.style.display='none';
@@ -3722,8 +3847,12 @@ function renderSharingSettings(data){
3722
3847
  var hubAdminBtn=document.getElementById('hubAdminEntryBtn');
3723
3848
 
3724
3849
  if(actualRole==='hub'){
3725
- statusEl.innerHTML='';teamEl.innerHTML='';adminEl.innerHTML='';
3850
+ teamEl.innerHTML='';adminEl.innerHTML='';statusEl.innerHTML='';statusEl.style.display='none';
3726
3851
  if(hubAdminBtn) hubAdminBtn.style.display=isAdmin?'':'none';
3852
+ var hubUser=conn.user||{};
3853
+ var hubName=hubUser.username||'admin';
3854
+ var adminNameInput=document.getElementById('cfgHubAdminName');
3855
+ if(adminNameInput) adminNameInput.value=hubName;
3727
3856
  return;
3728
3857
  }
3729
3858
 
@@ -3915,6 +4044,9 @@ function guideGoToHub(role){
3915
4044
 
3916
4045
  /* ─── Hub Admin Panel ─── */
3917
4046
  var adminDataCache={users:[],groups:[],tasks:[],skills:[],memories:[]};
4047
+ var _lastAdminFingerprint='';
4048
+ var hubTasksCache=[];
4049
+ var hubSkillsCache=[];
3918
4050
  var ADMIN_PAGE_SIZE=20;
3919
4051
  var adminPage={users:0,tasks:0,skills:0,memories:0};
3920
4052
 
@@ -3936,12 +4068,9 @@ function adminPaginateHtml(total,page,refilterFn){
3936
4068
  }
3937
4069
 
3938
4070
  var _adminPollTimer=null;
3939
- function startAdminPoll(){
3940
- if(_adminPollTimer) return;
3941
- _adminPollTimer=setInterval(function(){pollAdminPending();},30000);
3942
- pollAdminPending();
3943
- }
4071
+ function startAdminPoll(){ startLivePoller(); }
3944
4072
  async function pollAdminPending(){
4073
+ if(!window._isHubAdmin) return;
3945
4074
  try{
3946
4075
  var r=await fetch('/api/sharing/pending-users');
3947
4076
  var d=await r.json();
@@ -3991,24 +4120,44 @@ async function loadAdminData(){
3991
4120
  }
3992
4121
  if(notEnabledEl) notEnabledEl.style.display='none';
3993
4122
  if(mainEl) mainEl.style.display='';
3994
- if(!window._isHubAdmin){
3995
- var statsEl=document.getElementById('adminStats');
3996
- if(statsEl) statsEl.innerHTML='<div class="admin-empty">'+t('admin.noPermission')+'</div>';
3997
- return;
3998
- }
4123
+ var isAdmin=!!window._isHubAdmin;
3999
4124
  try{
4000
- var [usersR,tasksR,skillsR,pendingR,memoriesR]=await Promise.all([
4001
- fetch('/api/sharing/users').then(function(r){return r.json();}),
4002
- fetch('/api/admin/shared-tasks').then(function(r){return r.json();}),
4003
- fetch('/api/admin/shared-skills').then(function(r){return r.json();}),
4004
- fetch('/api/sharing/pending-users').then(function(r){return r.json();}),
4005
- fetch('/api/admin/shared-memories').then(function(r){return r.json();})
4006
- ]);
4007
- adminDataCache.users=Array.isArray(usersR.users)?usersR.users:[];
4008
- adminDataCache.tasks=Array.isArray(tasksR.tasks)?tasksR.tasks:[];
4009
- adminDataCache.skills=Array.isArray(skillsR.skills)?skillsR.skills:[];
4010
- adminDataCache.memories=Array.isArray(memoriesR.memories)?memoriesR.memories:[];
4011
- var pending=Array.isArray(pendingR.users)?pendingR.users:[];
4125
+ var fetches;
4126
+ if(isAdmin){
4127
+ fetches=await Promise.all([
4128
+ fetch('/api/sharing/users').then(function(r){return r.json();}),
4129
+ fetch('/api/admin/shared-tasks').then(function(r){return r.json();}),
4130
+ fetch('/api/admin/shared-skills').then(function(r){return r.json();}),
4131
+ fetch('/api/sharing/pending-users').then(function(r){return r.json();}),
4132
+ fetch('/api/admin/shared-memories').then(function(r){return r.json();})
4133
+ ]);
4134
+ }else{
4135
+ fetches=await Promise.all([
4136
+ Promise.resolve({users:[]}),
4137
+ fetch('/api/sharing/tasks/list?limit=500').then(function(r){return r.json();}),
4138
+ fetch('/api/sharing/skills/list?limit=500').then(function(r){return r.json();}),
4139
+ Promise.resolve({users:[]}),
4140
+ fetch('/api/sharing/memories/list?limit=500').then(function(r){return r.json();})
4141
+ ]);
4142
+ }
4143
+ var usersR=fetches[0],tasksR=fetches[1],skillsR=fetches[2],pendingR=fetches[3],memoriesR=fetches[4];
4144
+ var _newUsers=Array.isArray(usersR.users)?usersR.users:[];
4145
+ var _newTasks=Array.isArray(tasksR.tasks)?tasksR.tasks:[];
4146
+ var _newSkills=Array.isArray(skillsR.skills)?skillsR.skills:[];
4147
+ var _newMemories=Array.isArray(memoriesR.memories)?memoriesR.memories:[];
4148
+ var pending=isAdmin?(Array.isArray(pendingR.users)?pendingR.users:[]):[];
4149
+ var _fp=_newUsers.length+':'+_newTasks.length+':'+_newSkills.length+':'+_newMemories.length+':'+pending.length
4150
+ +':'+_newUsers.map(function(u){return u.id+'|'+(u.isOnline?1:0)+'|'+(u.role||'')}).join(',')
4151
+ +':'+_newMemories.map(function(m){return m.id}).join(',')
4152
+ +':'+_newTasks.map(function(t){return t.id+'|'+(t.status||'')}).join(',')
4153
+ +':'+_newSkills.map(function(s){return s.id+'|'+(s.status||'')}).join(',')
4154
+ +':'+pending.map(function(p){return p.id}).join(',');
4155
+ if(_fp===_lastAdminFingerprint) return;
4156
+ _lastAdminFingerprint=_fp;
4157
+ adminDataCache.users=_newUsers;
4158
+ adminDataCache.tasks=_newTasks;
4159
+ adminDataCache.skills=_newSkills;
4160
+ adminDataCache.memories=_newMemories;
4012
4161
  adminDataCache._pending=pending;
4013
4162
  var badge=document.getElementById('adminPendingBadge');
4014
4163
  if(badge){if(pending.length>0){badge.textContent=pending.length;badge.style.display='inline';}else{badge.style.display='none';}}
@@ -4017,18 +4166,48 @@ async function loadAdminData(){
4017
4166
  renderAdminTasks(adminDataCache.tasks);
4018
4167
  renderAdminSkills(adminDataCache.skills);
4019
4168
  renderAdminMemories(adminDataCache.memories);
4169
+ updateAdminTabsVisibility();
4020
4170
  }catch(e){
4021
4171
  var statsEl=document.getElementById('adminStats');
4022
4172
  if(statsEl) statsEl.innerHTML='<div class="admin-empty">'+t('admin.loadFailed')+esc(String(e))+'</div>';
4023
4173
  }
4024
4174
  }
4175
+ function updateAdminTabsVisibility(){
4176
+ var bar=document.getElementById('adminTabsBar');
4177
+ if(!bar) return;
4178
+ var tabs=bar.querySelectorAll('.admin-tab');
4179
+ var isAdmin=!!window._isHubAdmin;
4180
+ tabs.forEach(function(tab){
4181
+ var onclick=tab.getAttribute('onclick')||'';
4182
+ if(onclick.indexOf("'users'")!==-1){
4183
+ tab.style.display=isAdmin?'':'none';
4184
+ }
4185
+ });
4186
+ if(!isAdmin){
4187
+ var usersPanel=document.getElementById('adminUsersPanel');
4188
+ if(usersPanel&&usersPanel.classList.contains('active')){
4189
+ usersPanel.classList.remove('active');
4190
+ var memPanel=document.getElementById('adminMemoriesPanel');
4191
+ if(memPanel) memPanel.classList.add('active');
4192
+ tabs.forEach(function(tab){
4193
+ tab.classList.remove('active');
4194
+ var onclick=tab.getAttribute('onclick')||'';
4195
+ if(onclick.indexOf("'memories'")!==-1) tab.classList.add('active');
4196
+ });
4197
+ }
4198
+ }
4199
+ var subEl=document.querySelector('.admin-header-sub');
4200
+ if(subEl) subEl.textContent=isAdmin?t('admin.subtitle'):t('admin.subtitle.member');
4201
+ }
4025
4202
 
4026
4203
  function renderAdminStats(pendingCount){
4027
4204
  var el=document.getElementById('adminStats');
4028
4205
  if(!el) return;
4206
+ var isAdmin=!!window._isHubAdmin;
4207
+ var onlineCount=adminDataCache.users.filter(function(u){return !!u.isOnline;}).length;
4029
4208
  el.innerHTML=
4030
- '<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>'+
4031
- '<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>'+
4209
+ (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>'+
4210
+ '<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>':'')+
4032
4211
  '<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>'+
4033
4212
  '<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>'+
4034
4213
  '<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>';
@@ -4038,82 +4217,122 @@ function renderAdminStats(pendingCount){
4038
4217
  tc=document.getElementById('adminTabCountSkills');if(tc)tc.textContent=adminDataCache.skills.length;
4039
4218
  }
4040
4219
 
4220
+ function auRelativeTime(ts){
4221
+ if(!ts) return t('admin.neverActive');
4222
+ var diff=Date.now()-ts;
4223
+ if(diff<60000) return t('notif.timeAgo.just');
4224
+ if(diff<3600000) return t('notif.timeAgo.min').replace('{n}',Math.floor(diff/60000));
4225
+ if(diff<86400000) return t('notif.timeAgo.hour').replace('{n}',Math.floor(diff/3600000));
4226
+ return t('notif.timeAgo.day').replace('{n}',Math.floor(diff/86400000));
4227
+ }
4228
+
4229
+ function renderAdminUserCard(u,adminCount){
4230
+ var uid=escAttr(u.id);
4231
+ var uname=escAttr(u.username||'');
4232
+ var online=!!u.isOnline;
4233
+ var statusCls=online?'online':'offline';
4234
+
4235
+ var statusIndicator='<span class="au-status-dot '+statusCls+'"></span>';
4236
+ var statusLabel=online
4237
+ ?'<span class="au-status-text online">'+t('admin.onlineNow')+'</span>'
4238
+ :'<span class="au-status-text offline">'+auRelativeTime(u.lastActiveAt)+'</span>';
4239
+
4240
+ var titleDisplay='<span class="admin-card-title" id="au_name_'+uid+'">'+statusIndicator+esc(u.username||u.id)+
4241
+ ' <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>';
4242
+
4243
+ var editRow='<div id="au_edit_'+uid+'" style="display:none;align-items:center;gap:6px">'+
4244
+ '<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;)">'+
4245
+ '<button onclick="adminSaveEditName(&quot;'+uid+'&quot;)" class="btn btn-sm btn-primary" style="padding:4px 12px;font-size:11px;white-space:nowrap">\u2713</button>'+
4246
+ '<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>';
4247
+
4248
+ var mc=u.memoryCount||0, tc=u.taskCount||0, sc=u.skillCount||0;
4249
+ var contribHtml='<div class="au-contrib">'+
4250
+ '<span class="au-contrib-item"><span class="au-contrib-num" style="color:var(--pri)">'+mc+'</span> '+t('admin.contrib.memories')+'</span>'+
4251
+ '<span class="au-contrib-item"><span class="au-contrib-num" style="color:var(--green)">'+tc+'</span> '+t('admin.contrib.tasks')+'</span>'+
4252
+ '<span class="au-contrib-item"><span class="au-contrib-num" style="color:var(--amber)">'+sc+'</span> '+t('admin.contrib.skills')+'</span>'+
4253
+ '</div>';
4254
+
4255
+ var infoRows=[];
4256
+ if(u.deviceName) infoRows.push('<span class="au-info-item">\u{1F4BB} '+t('admin.device')+esc(u.deviceName)+'</span>');
4257
+ if(u.lastIp) infoRows.push('<span class="au-info-item">\u{1F310} '+t('admin.ip')+esc(u.lastIp)+'</span>');
4258
+ if(u.createdAt) infoRows.push('<span class="au-info-item">\u{1F4C5} '+t('admin.joined')+formatDateTimeSeconds(u.createdAt)+'</span>');
4259
+ if(u.approvedAt) infoRows.push('<span class="au-info-item">\u2705 '+t('admin.approved')+formatDateTimeSeconds(u.approvedAt)+'</span>');
4260
+ var lastAct=u.lastActiveAt||u.approvedAt||u.createdAt;
4261
+ infoRows.push('<span class="au-info-item">\u{1F552} '+t('admin.lastActive')+(lastAct?formatDateTimeSeconds(lastAct):t('admin.neverActive'))+'</span>');
4262
+ var infoHtml='<div class="au-info">'+infoRows.join('')+'</div>';
4263
+
4264
+ var actions='';
4265
+ if(u.isOwner){
4266
+ actions+='<span style="font-size:11px;color:var(--text-muted);padding:4px 0">'+t('admin.ownerHint')+'</span>';
4267
+ }else if(u.role!=='admin'){
4268
+ actions+='<button class="btn btn-sm btn-ghost" onclick="adminToggleRole(&quot;'+uid+'&quot;,&quot;admin&quot;)" style="color:var(--accent)">'+t('admin.promoteAdmin')+'</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 if(adminCount>1){
4271
+ actions+='<button class="btn btn-sm btn-ghost" onclick="adminToggleRole(&quot;'+uid+'&quot;,&quot;member&quot;)">'+t('admin.demoteMember')+'</button>';
4272
+ actions+='<button class="btn btn-sm btn-ghost" onclick="adminRemoveUser(&quot;'+uid+'&quot;,&quot;'+uname+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>';
4273
+ }else{
4274
+ actions+='<span style="font-size:11px;color:var(--text-muted);padding:4px 0">'+t('admin.lastAdminHint')+'</span>';
4275
+ }
4276
+ 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>':'';
4277
+
4278
+ 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">'+
4279
+ '<div style="flex:1;min-width:0">'+titleDisplay+editRow+'</div>'+statusLabel+
4280
+ '</div>'+
4281
+ '<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>'+
4282
+ contribHtml+infoHtml+
4283
+ (actions?'<div class="admin-card-actions" style="border-top:1px solid rgba(99,102,241,.08);padding-top:12px;margin-top:6px">'+actions+'</div>':'')+
4284
+ '</div>';
4285
+ }
4286
+
4041
4287
  function renderAdminUsers(users,pending){
4042
4288
  var el=document.getElementById('adminUsersPanel');
4043
4289
  if(!el) return;
4290
+ var isAdmin=!!window._isHubAdmin;
4291
+ if(!isAdmin){
4292
+ el.innerHTML='<div class="admin-empty" style="padding:32px;text-align:center;color:var(--text-sec)">'+t('admin.noPermission')+'</div>';
4293
+ return;
4294
+ }
4044
4295
  var html='';
4045
4296
  if(pending&&pending.length>0){
4046
- 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>';
4297
+ html+='<div class="admin-pending-section"><h3>'+t('admin.pendingApproval')+' <span class="pending-count">'+pending.length+'</span></h3>';
4047
4298
  for(var p=0;p<pending.length;p++){
4048
4299
  var pu=pending[p];
4049
- 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>'+
4050
- '<div class="admin-card-meta">'+t('admin.device')+esc(pu.deviceName||'unknown')+'</div>'+
4051
- '<div class="admin-card-actions">'+
4052
- '<button class="btn btn-sm btn-primary" onclick="adminApproveUser(&quot;'+escAttr(pu.id)+'&quot;,&quot;'+escAttr(pu.username||'')+'&quot;)">'+t('admin.approve')+'</button>'+
4053
- '<button class="btn btn-sm btn-ghost" onclick="adminRejectUser(&quot;'+escAttr(pu.id)+'&quot;)" style="color:var(--rose)">'+t('admin.reject')+'</button>'+
4300
+ html+='<div class="admin-pending-card">'+
4301
+ '<div class="apc-name">'+esc(pu.username||pu.id||'Unknown')+'</div>'+
4302
+ '<div class="apc-meta"><span>\u{1F4BB} '+esc(pu.deviceName||'unknown')+'</span>'+(pu.createdAt?'<span>\u{1F552} '+formatDateTimeSeconds(pu.createdAt)+'</span>':'')+'</div>'+
4303
+ '<div class="apc-actions">'+
4304
+ '<button class="btn-approve" onclick="adminApproveUser(&quot;'+escAttr(pu.id)+'&quot;,&quot;'+escAttr(pu.username||'')+'&quot;)">'+t('admin.approve')+'</button>'+
4305
+ '<button class="btn-reject" onclick="adminRejectUser(&quot;'+escAttr(pu.id)+'&quot;)">'+t('admin.reject')+'</button>'+
4054
4306
  '</div></div>';
4055
4307
  }
4056
4308
  html+='</div>';
4057
4309
  }
4058
- html+='<h3 style="font-size:14px;font-weight:600;color:var(--text);margin-bottom:10px">'+t('admin.activeUsers')+' ('+users.length+')</h3>';
4059
- if(users.length===0){
4310
+
4311
+ var onlineUsers=users.filter(function(u){return !!u.isOnline;});
4312
+ var offlineUsers=users.filter(function(u){return !u.isOnline;});
4313
+ offlineUsers.sort(function(a,b){return (b.lastActiveAt||0)-(a.lastActiveAt||0);});
4314
+ var sorted=onlineUsers.concat(offlineUsers);
4315
+ var adminCount=users.filter(function(x){return x.role==='admin';}).length;
4316
+
4317
+ if(sorted.length===0){
4060
4318
  html+='<div class="admin-empty"><span class="ae-icon">\u{1F465}</span>'+t('admin.noActiveUsers')+'</div>';
4061
4319
  }else{
4062
- var totalUsers=users.length;
4063
- var usersPages=Math.ceil(totalUsers/ADMIN_PAGE_SIZE);
4064
- if(adminPage.users>=usersPages) adminPage.users=Math.max(0,usersPages-1);
4065
- var usersStart=adminPage.users*ADMIN_PAGE_SIZE;
4066
- var usersEnd=Math.min(usersStart+ADMIN_PAGE_SIZE,totalUsers);
4067
- var adminCount=users.filter(function(x){return x.role==='admin';}).length;
4068
- for(var i=usersStart;i<usersEnd;i++){
4069
- var u=users[i];
4070
- var uid=escAttr(u.id);
4071
- var uname=escAttr(u.username||'');
4072
-
4073
- var titleDisplay='<span class="admin-card-title" id="au_name_'+uid+'">'+esc(u.username||u.id)+
4074
- ' <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>';
4075
-
4076
- var editRow='<div id="au_edit_'+uid+'" style="display:none;align-items:center;gap:6px">'+
4077
- '<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;)">'+
4078
- '<button onclick="adminSaveEditName(&quot;'+uid+'&quot;)" class="btn btn-sm btn-primary" style="padding:4px 12px;font-size:11px;white-space:nowrap">\u2713</button>'+
4079
- '<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>';
4080
-
4081
- var mc=u.memoryCount||0, tc=u.taskCount||0, sc=u.skillCount||0;
4082
- var contribHtml='<div class="au-contrib">'+
4083
- '<span class="au-contrib-item"><span class="au-contrib-num" style="color:var(--pri)">'+mc+'</span> '+t('admin.contrib.memories')+'</span>'+
4084
- '<span class="au-contrib-item"><span class="au-contrib-num" style="color:var(--green)">'+tc+'</span> '+t('admin.contrib.tasks')+'</span>'+
4085
- '<span class="au-contrib-item"><span class="au-contrib-num" style="color:var(--amber)">'+sc+'</span> '+t('admin.contrib.skills')+'</span>'+
4086
- '</div>';
4087
-
4088
- var infoRows=[];
4089
- if(u.deviceName) infoRows.push('<span class="au-info-item">\u{1F4BB} '+t('admin.device')+esc(u.deviceName)+'</span>');
4090
- if(u.lastIp) infoRows.push('<span class="au-info-item">\u{1F310} '+t('admin.ip')+esc(u.lastIp)+'</span>');
4091
- if(u.createdAt) infoRows.push('<span class="au-info-item">\u{1F4C5} '+t('admin.joined')+formatDateTimeSeconds(u.createdAt)+'</span>');
4092
- if(u.approvedAt) infoRows.push('<span class="au-info-item">\u2705 '+t('admin.approved')+formatDateTimeSeconds(u.approvedAt)+'</span>');
4093
- infoRows.push('<span class="au-info-item">\u{1F552} '+t('admin.lastActive')+(u.lastActiveAt?formatDateTimeSeconds(u.lastActiveAt):t('admin.neverActive'))+'</span>');
4094
- var infoHtml='<div class="au-info">'+infoRows.join('')+'</div>';
4095
-
4096
- var actions='';
4097
- if(u.role!=='admin'){
4098
- actions+='<button class="btn btn-sm btn-ghost" onclick="adminToggleRole(&quot;'+uid+'&quot;,&quot;admin&quot;)" style="color:var(--accent)">'+t('admin.promoteAdmin')+'</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 if(adminCount>1){
4101
- actions+='<button class="btn btn-sm btn-ghost" onclick="adminToggleRole(&quot;'+uid+'&quot;,&quot;member&quot;)">'+t('admin.demoteMember')+'</button>';
4102
- actions+='<button class="btn btn-sm btn-ghost" onclick="adminRemoveUser(&quot;'+uid+'&quot;,&quot;'+uname+'&quot;)" style="color:var(--rose)">'+t('admin.remove')+'</button>';
4103
- }else{
4104
- actions+='<span style="font-size:11px;color:var(--text-muted);padding:4px 0">'+t('admin.lastAdminHint')+'</span>';
4105
- }
4106
- html+='<div class="admin-card au-card"><div class="admin-card-header"><div style="flex:1;min-width:0">'+titleDisplay+editRow+'</div>'+
4107
- '<span class="admin-badge '+(u.role==='admin'?'admin':'member')+'">'+esc(u.role||'member')+'</span></div>'+
4108
- contribHtml+infoHtml+
4109
- (actions?'<div class="admin-card-actions" style="border-top:1px dashed var(--border);padding-top:10px;margin-top:4px">'+actions+'</div>':'')+
4110
- '</div>';
4320
+ 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>';
4321
+ if(onlineUsers.length===0){
4322
+ html+='<div style="font-size:12px;color:var(--text-muted);padding:8px 0 12px">\u2014</div>';
4323
+ }else{
4324
+ for(var i=0;i<onlineUsers.length;i++) html+=renderAdminUserCard(onlineUsers[i],adminCount);
4325
+ }
4326
+ 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>';
4327
+ if(offlineUsers.length===0){
4328
+ html+='<div style="font-size:12px;color:var(--text-muted);padding:8px 0 12px">\u2014</div>';
4329
+ }else{
4330
+ for(var j=0;j<offlineUsers.length;j++) html+=renderAdminUserCard(offlineUsers[j],adminCount);
4111
4331
  }
4112
- html+=adminPaginateHtml(totalUsers,adminPage.users,'adminUsers');
4113
4332
  }
4114
4333
  el.innerHTML=html;
4115
4334
  }
4116
- 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||[]);}
4335
+ function adminUsersPage(p){renderAdminUsers(adminDataCache.users,adminDataCache._pending||[]);}
4117
4336
 
4118
4337
  async function adminApproveUser(userId,username){
4119
4338
  try{
@@ -4136,7 +4355,9 @@ async function adminToggleRole(userId,newRole){
4136
4355
  try{
4137
4356
  var r=await fetch('/api/sharing/change-role',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,role:newRole})});
4138
4357
  var d=await r.json();
4139
- if(d.ok){toast(t('toast.roleChanged'),'success');loadAdminData();}else{toast(d.error||t('toast.roleChangeFail'),'error');}
4358
+ if(d.ok){toast(t('toast.roleChanged'),'success');loadAdminData();}
4359
+ else if(d.error==='cannot_demote_owner'){toast(t('admin.ownerHint'),'error');}
4360
+ else{toast(d.error||t('toast.roleChangeFail'),'error');}
4140
4361
  }catch(e){toast(t('toast.roleChangeFail')+': '+e.message,'error');}
4141
4362
  }
4142
4363
  function adminStartEditName(btn,userId,currentName){
@@ -4175,7 +4396,9 @@ async function adminRemoveUser(userId,username){
4175
4396
  try{
4176
4397
  var r=await fetch('/api/sharing/remove-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,cleanResources:clean})});
4177
4398
  var d=await r.json();
4178
- if(d.ok){toast(t('toast.userRemoved'),'success');loadAdminData();}else{toast(d.error||t('toast.removeFail'),'error');}
4399
+ if(d.ok){toast(t('toast.userRemoved'),'success');loadAdminData();}
4400
+ else if(d.error==='cannot_remove_owner'){toast(t('admin.ownerHint'),'error');}
4401
+ else{toast(d.error||t('toast.removeFail'),'error');}
4179
4402
  }catch(e){toast(t('toast.removeFail')+': '+e.message,'error');}
4180
4403
  }
4181
4404
 
@@ -4247,7 +4470,7 @@ function renderAdminTasks(tasks){
4247
4470
  (tk.chunkCount!=null?'<span class="admin-card-tag tag-kind">\u{1F4DD} '+tk.chunkCount+' '+t('admin.chunks')+'</span>':'')+
4248
4471
  '</div>'+
4249
4472
  '<span class="admin-card-actions" onclick="event.stopPropagation()">'+
4250
- '<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>'+
4473
+ (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>':'')+
4251
4474
  '<button class="btn btn-sm btn-ghost admin-card-expand-btn" onclick="toggleAdminTaskCard(&quot;'+cardId+'&quot;,'+i+')">'+t('admin.expand')+'</button>'+
4252
4475
  '<span class="admin-card-time">'+(timeRange||formatDateTimeSeconds(tk.updatedAt||tk.createdAt))+'</span>'+
4253
4476
  '</span>'+
@@ -4310,7 +4533,7 @@ function renderAdminSkills(skills){
4310
4533
  (qs!=null?'<span class="admin-card-tag tag-kind">\u2605 '+Number(qs).toFixed(1)+'</span>':'')+
4311
4534
  '</div>'+
4312
4535
  '<span class="admin-card-actions" onclick="event.stopPropagation()">'+
4313
- '<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>'+
4536
+ (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>':'')+
4314
4537
  '<button class="btn btn-sm btn-ghost admin-card-expand-btn" onclick="toggleAdminSkillCard(&quot;'+cardId+'&quot;,'+i+')">'+t('admin.expand')+'</button>'+
4315
4538
  (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>':'')+
4316
4539
  '<span class="admin-card-time">'+formatDateTimeSeconds(s.updatedAt||s.createdAt)+'</span>'+
@@ -4374,7 +4597,7 @@ function renderAdminMemories(memories){
4374
4597
  (m.kind?'<span class="admin-card-tag tag-kind">'+esc(m.kind)+'</span>':'')+
4375
4598
  '</div>'+
4376
4599
  '<span class="admin-card-actions">'+
4377
- '<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>'+
4600
+ (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>':'')+
4378
4601
  '<button class="btn btn-sm btn-ghost admin-card-expand-btn" onclick="event.stopPropagation();toggleAdminMemoryCard(&quot;'+cardId+'&quot;,'+i+')">'+t('admin.expand')+'</button>'+
4379
4602
  '<span class="admin-card-time">'+formatDateTimeSeconds(m.updatedAt||m.createdAt)+'</span>'+
4380
4603
  '</span>'+
@@ -4452,12 +4675,19 @@ async function toggleAdminTaskCard(cardId,idx){
4452
4675
  var tk=(adminTasksCache||[])[idx];
4453
4676
  if(!tk){detail.innerHTML='<div style="color:var(--text-muted);font-size:14px;padding:12px">'+t('admin.noContent')+'</div>';return;}
4454
4677
  var localTaskId=tk.sourceTaskId||tk.id;
4678
+ var hubTaskId=tk.id;
4455
4679
  detail.innerHTML='<div class="spinner"></div>';
4456
4680
  var task=null;
4457
4681
  try{
4458
4682
  var r=await fetch('/api/task/'+encodeURIComponent(localTaskId));
4459
4683
  if(r.ok) task=await r.json();
4460
4684
  }catch(e){}
4685
+ if(!task){
4686
+ try{
4687
+ var r2=await fetch('/api/admin/shared-tasks/'+encodeURIComponent(hubTaskId)+'/detail');
4688
+ if(r2.ok) task=await r2.json();
4689
+ }catch(e2){}
4690
+ }
4461
4691
  if(!task){detail.innerHTML='<div style="color:var(--text-muted);font-size:14px;padding:12px">'+t('admin.noContent')+'</div>';return;}
4462
4692
  var metaHtml='<div class="admin-card-detail-meta admin-task-meta">'+
4463
4693
  (tk.status?'<span class="meta-item"><span class="task-status-badge '+tk.status+'">'+esc(tk.status)+'</span></span>':'')+
@@ -4528,11 +4758,18 @@ async function toggleAdminSkillCard(cardId,idx){
4528
4758
  if(!sk){detail.innerHTML='<div style="color:var(--text-muted);font-size:13px">'+t('admin.noContent')+'</div>';return;}
4529
4759
  detail.innerHTML='<div class="spinner"></div>';
4530
4760
  var localSkillId=sk.sourceSkillId||sk.id;
4761
+ var hubSkillId=sk.id;
4531
4762
  var localData=null;
4532
4763
  try{
4533
4764
  var lr=await fetch('/api/skill/'+encodeURIComponent(localSkillId));
4534
4765
  if(lr.ok) localData=await lr.json();
4535
4766
  }catch(e){}
4767
+ if(!localData){
4768
+ try{
4769
+ var hr=await fetch('/api/admin/shared-skills/'+encodeURIComponent(hubSkillId)+'/detail');
4770
+ if(hr.ok) localData=await hr.json();
4771
+ }catch(e2){}
4772
+ }
4536
4773
  var localSkill=localData&&localData.skill?localData.skill:sk;
4537
4774
  var files=localData&&localData.files?localData.files:[];
4538
4775
  var versions=localData&&localData.versions?localData.versions:[];
@@ -5359,22 +5596,33 @@ function setTaskStatusFilter(btn,status){
5359
5596
  loadTasks();
5360
5597
  }
5361
5598
 
5362
- async function loadTasks(){
5599
+ async function loadTasks(silent){
5363
5600
  const scope=document.getElementById('taskSearchScope')?document.getElementById('taskSearchScope').value:taskSearchScope;
5364
5601
  taskSearchScope=scope||'local';
5365
- if(taskSearchScope!=='local'){ return loadHubTasks(); }
5602
+ if(taskSearchScope==='hub'){ return loadHubTasks(); }
5366
5603
  const list=document.getElementById('tasksList');
5367
- list.innerHTML='<div class="spinner"></div>';
5604
+ if(!silent) list.innerHTML='<div class="spinner"></div>';
5368
5605
  try{
5369
5606
  const params=new URLSearchParams({limit:String(TASKS_PER_PAGE),offset:String(tasksPage*TASKS_PER_PAGE)});
5370
5607
  if(tasksStatusFilter) params.set('status',tasksStatusFilter);
5608
+ if(taskSearchScope==='local') params.set('owner','agent:main');
5609
+ var baseP=new URLSearchParams();
5610
+ if(taskSearchScope==='local') baseP.set('owner','agent:main');
5371
5611
  const [data,allD,activeD,compD,skipD]=await Promise.all([
5372
5612
  fetch('/api/tasks?'+params).then(r=>r.json()),
5373
- fetch('/api/tasks?limit=1&offset=0').then(r=>r.json()),
5374
- fetch('/api/tasks?status=active&limit=1&offset=0').then(r=>r.json()),
5375
- fetch('/api/tasks?status=completed&limit=1&offset=0').then(r=>r.json()),
5376
- fetch('/api/tasks?status=skipped&limit=1&offset=0').then(r=>r.json())
5613
+ fetch('/api/tasks?limit=1&offset=0&'+baseP).then(r=>r.json()),
5614
+ fetch('/api/tasks?status=active&limit=1&offset=0&'+baseP).then(r=>r.json()),
5615
+ fetch('/api/tasks?status=completed&limit=1&offset=0&'+baseP).then(r=>r.json()),
5616
+ fetch('/api/tasks?status=skipped&limit=1&offset=0&'+baseP).then(r=>r.json())
5377
5617
  ]);
5618
+ if(silent){
5619
+ var fp=JSON.stringify((data.tasks||[]).map(function(tk){return tk.id+'|'+tk.status+'|'+(tk.updatedAt||tk.startedAt)}));
5620
+ fp+=':'+allD.total+':'+activeD.total+':'+compD.total+':'+skipD.total;
5621
+ if(fp===_lastTasksFingerprint) return;
5622
+ _lastTasksFingerprint=fp;
5623
+ }else{
5624
+ _lastTasksFingerprint='';
5625
+ }
5378
5626
  document.getElementById('tasksTotalCount').textContent=formatNum(allD.total);
5379
5627
  document.getElementById('tasksActiveCount').textContent=formatNum(activeD.total);
5380
5628
  document.getElementById('tasksCompletedCount').textContent=formatNum(compD.total);
@@ -5660,17 +5908,17 @@ function updateSkillCardBadge(skillId,newScope){
5660
5908
  }
5661
5909
  }
5662
5910
 
5663
- async function loadSkills(){
5911
+ async function loadSkills(silent){
5664
5912
  const list=document.getElementById('skillsList');
5665
5913
  const hubList=document.getElementById('hubSkillsList');
5666
- list.innerHTML='<div class="spinner"></div>';
5914
+ if(!silent) list.innerHTML='<div class="spinner"></div>';
5667
5915
  var hubSection=document.getElementById('hubSkillsSection');
5668
5916
  if(hubList){
5669
- if(skillSearchScope==='local'){
5917
+ if(skillSearchScope==='local'||skillSearchScope==='allLocal'){
5670
5918
  if(hubSection) hubSection.style.display='none';
5671
5919
  }else{
5672
5920
  if(hubSection) hubSection.style.display='block';
5673
- hubList.innerHTML='<div class="spinner"></div>';
5921
+ if(!silent) hubList.innerHTML='<div class="spinner"></div>';
5674
5922
  }
5675
5923
  }
5676
5924
 
@@ -5694,6 +5942,13 @@ async function loadSkills(){
5694
5942
  return haystack.includes(q);
5695
5943
  });
5696
5944
  }
5945
+ if(silent){
5946
+ var fp=JSON.stringify(localSkills.map(function(s){return s.id+'|'+s.status+'|'+s.version+'|'+(s.visibility||'')}));
5947
+ if(fp===_lastSkillsFingerprint) return;
5948
+ _lastSkillsFingerprint=fp;
5949
+ }else{
5950
+ _lastSkillsFingerprint='';
5951
+ }
5697
5952
 
5698
5953
  const renderLocalCards=function(skills){
5699
5954
  if(!skills||skills.length===0){
@@ -5739,7 +5994,7 @@ async function loadSkills(){
5739
5994
 
5740
5995
  list.innerHTML=renderLocalCards(localSkills);
5741
5996
 
5742
- if(skillSearchScope==='local'){
5997
+ if(skillSearchScope==='local'||skillSearchScope==='allLocal'){
5743
5998
  if(hubSection) hubSection.style.display='none';
5744
5999
  document.getElementById('skillSearchMeta').textContent=query?(t('skills.search.local')+' '+localSkills.length):'';
5745
6000
  document.getElementById('skillsTotalCount').textContent=formatNum(localSkills.length);
@@ -5752,7 +6007,8 @@ async function loadSkills(){
5752
6007
 
5753
6008
  if(!query){
5754
6009
  if(hubSection) hubSection.style.display='block';
5755
- if(hubList){ loadHubSkills(hubList); }
6010
+ var localIds=new Set(localSkills.map(function(s){return s.id;}));
6011
+ if(hubList){ loadHubSkills(hubList, localIds); }
5756
6012
  document.getElementById('skillSearchMeta').textContent=t('skills.search.local')+' '+localSkills.length;
5757
6013
  document.getElementById('skillsTotalCount').textContent=formatNum(localSkills.length);
5758
6014
  document.getElementById('skillsActiveCount').textContent=formatNum(localSkills.filter(s=>s.status==='active').length);
@@ -5810,7 +6066,47 @@ async function loadSkills(){
5810
6066
  }
5811
6067
  }
5812
6068
 
5813
- async function loadHubSkills(hubList){
6069
+ async function loadHubTasks(){
6070
+ var list=document.getElementById('tasksList');
6071
+ if(!list) return;
6072
+ list.innerHTML='<div class="spinner"></div>';
6073
+ try{
6074
+ var r=await fetch('/api/sharing/tasks/list?limit=40');
6075
+ var d=await r.json();
6076
+ var tasks=Array.isArray(d.tasks)?d.tasks:[];
6077
+ hubTasksCache=tasks;
6078
+ document.getElementById('tasksTotalCount').textContent=formatNum(tasks.length);
6079
+ document.getElementById('tasksActiveCount').textContent='-';
6080
+ document.getElementById('tasksCompletedCount').textContent='-';
6081
+ document.getElementById('tasksSkippedCount').textContent='-';
6082
+ if(!tasks.length){
6083
+ list.innerHTML='<div style="text-align:center;padding:48px;color:var(--text-muted);font-size:14px">'+t('tasks.empty')+'</div>';
6084
+ document.getElementById('tasksPagination').innerHTML='';
6085
+ return;
6086
+ }
6087
+ list.innerHTML=tasks.map(function(task,idx){
6088
+ var timeStr=task.updatedAt?formatTime(task.updatedAt):(task.createdAt?formatTime(task.createdAt):'');
6089
+ return '<div class="task-card" onclick="openHubTaskDetailFromCache(\\x27hub\\x27,'+idx+')" style="cursor:pointer">'+
6090
+ '<div class="task-card-top">'+
6091
+ '<div class="task-card-title">'+esc(task.title||'(no title)')+'</div>'+
6092
+ '<div class="task-card-badges"><span class="scope-badge team">\\u{1F310} '+t('scope.hub')+'</span></div>'+
6093
+ '</div>'+
6094
+ (task.summary?'<div class="task-card-summary">'+esc(task.summary)+'</div>':'')+
6095
+ '<div class="task-card-bottom">'+
6096
+ (timeStr?'<span class="tag"><span class="icon">\\u{1F4C5}</span> '+timeStr+'</span>':'')+
6097
+ '<span class="tag"><span class="icon">\\u{1F464}</span> '+esc(task.ownerName||'unknown')+'</span>'+
6098
+ (task.chunkCount!=null?'<span class="tag"><span class="icon">\\u{1F4DD}</span> '+task.chunkCount+' '+t('tasks.chunks.label')+'</span>':'')+
6099
+ '</div>'+
6100
+ '</div>';
6101
+ }).join('');
6102
+ document.getElementById('tasksPagination').innerHTML='';
6103
+ }catch(e){
6104
+ list.innerHTML='<div style="text-align:center;padding:48px;color:var(--text-muted);font-size:14px">'+t('tasks.empty')+'</div>';
6105
+ document.getElementById('tasksPagination').innerHTML='';
6106
+ }
6107
+ }
6108
+
6109
+ async function loadHubSkills(hubList, localIds){
5814
6110
  if(!hubList) hubList=document.getElementById('hubSkillsList');
5815
6111
  if(!hubList) return;
5816
6112
  var hubSection=document.getElementById('hubSkillsSection');
@@ -5818,7 +6114,8 @@ async function loadHubSkills(hubList){
5818
6114
  try{
5819
6115
  const r=await fetch('/api/sharing/skills/list?limit=40');
5820
6116
  const d=await r.json();
5821
- const skills=Array.isArray(d.skills)?d.skills:[];
6117
+ var allSkills=Array.isArray(d.skills)?d.skills:[];
6118
+ const skills=localIds?allSkills.filter(function(s){return !localIds.has(s.sourceSkillId);}):allSkills;
5822
6119
  hubSkillsCache=skills;
5823
6120
  if(!skills.length){
5824
6121
  if(hubSection) hubSection.style.display='none';
@@ -6170,6 +6467,7 @@ async function loadConfig(){
6170
6467
  document.getElementById('cfgHubTeamToken').value=hub.teamToken||'';
6171
6468
  document.getElementById('cfgClientHubAddress').value=client.hubAddress||'';
6172
6469
  document.getElementById('cfgClientTeamToken').value=client.teamToken||'';
6470
+ document.getElementById('cfgClientNickname').value=client.nickname||'';
6173
6471
  document.getElementById('cfgClientUserToken').value=client.userToken||'';
6174
6472
  onSharingToggle();
6175
6473
  updateHubShareInfo();
@@ -6338,6 +6636,7 @@ async function saveHubConfig(){
6338
6636
  var hubPort=document.getElementById('cfgHubPort').value.trim();
6339
6637
  var hubTeamName=document.getElementById('cfgHubTeamName').value.trim();
6340
6638
  var hubTeamToken=document.getElementById('cfgHubTeamToken').value.trim();
6639
+ var hubAdminName=document.getElementById('cfgHubAdminName').value.trim();
6341
6640
  cfg.sharing.hub={port:hubPort?Number(hubPort):18800};
6342
6641
  if(hubTeamName) cfg.sharing.hub.teamName=hubTeamName;
6343
6642
  if(hubTeamToken) cfg.sharing.hub.teamToken=hubTeamToken;
@@ -6347,8 +6646,12 @@ async function saveHubConfig(){
6347
6646
  var clientAddr=document.getElementById('cfgClientHubAddress').value.trim();
6348
6647
  var clientTeamToken=document.getElementById('cfgClientTeamToken').value.trim();
6349
6648
  var clientUserToken=document.getElementById('cfgClientUserToken').value.trim();
6649
+ var clientNickname=document.getElementById('cfgClientNickname').value.trim();
6650
+ if(!clientAddr){done();toast(t('settings.hub.test.noAddr'),'error');return;}
6651
+ if(!clientTeamToken){done();toast(t('settings.hub.teamToken.required'),'error');return;}
6350
6652
  cfg.sharing.client={};
6351
6653
  if(clientAddr) cfg.sharing.client.hubAddress=clientAddr;
6654
+ if(clientNickname) cfg.sharing.client.nickname=clientNickname;
6352
6655
  if(clientTeamToken) cfg.sharing.client.teamToken=clientTeamToken;
6353
6656
  if(clientUserToken) cfg.sharing.client.userToken=clientUserToken;
6354
6657
  cfg.sharing.hub={port:18800,teamName:'',teamToken:''};
@@ -6361,6 +6664,18 @@ async function saveHubConfig(){
6361
6664
  done();toast(t('sharing.cannotJoinSelf'),'error');return;
6362
6665
  }
6363
6666
  }catch(e){}
6667
+ try{
6668
+ var testUrl=clientAddr.indexOf('://')>-1?clientAddr:'http://'+clientAddr;
6669
+ testUrl=testUrl.replace(/\\/+$/,'');
6670
+ var tr=await fetch('/api/sharing/test-hub',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({hubUrl:testUrl})});
6671
+ var td=await tr.json();
6672
+ if(!td.ok){
6673
+ var errMsg=td.error==='cannot_join_self'?t('sharing.cannotJoinSelf'):(td.error||t('settings.hub.test.fail'));
6674
+ done();toast(errMsg,'error');return;
6675
+ }
6676
+ }catch(e){
6677
+ done();toast(t('settings.hub.test.fail')+': '+String(e),'error');return;
6678
+ }
6364
6679
  }
6365
6680
  }
6366
6681
 
@@ -6370,11 +6685,25 @@ async function saveHubConfig(){
6370
6685
  var confirmMsg=prevRole==='hub'?t('sharing.disable.confirm.hub'):t('sharing.disable.confirm.client');
6371
6686
  if(!(await confirmModal(confirmMsg,{danger:true}))){done();return;}
6372
6687
  }
6688
+ if(prevSharingEnabled&&sharingEnabled&&prevRole&&prevRole!==_sharingRole){
6689
+ var switchMsg=prevRole==='hub'?t('sharing.switch.hubToClient'):t('sharing.switch.clientToHub');
6690
+ if(!(await confirmModal(switchMsg,{danger:true}))){done();return;}
6691
+ }
6373
6692
 
6374
6693
  var ok=await doSaveConfig(cfg, saveBtn, 'hubSaved');
6375
6694
  if(ok){
6695
+ if(sharingEnabled&&_sharingRole==='hub'){
6696
+ var adminNameEl=document.getElementById('cfgHubAdminName');
6697
+ if(adminNameEl&&adminNameEl.value.trim()){
6698
+ try{await fetch('/api/sharing/update-username',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:adminNameEl.value.trim()})});}catch(e){}
6699
+ }
6700
+ }
6701
+ _lastSidebarFingerprint='';
6702
+ _lastSettingsFingerprint='';
6376
6703
  loadSharingStatus(false);
6377
- var needsRestart=sharingEnabled||(prevSharingEnabled&&!sharingEnabled);
6704
+ var enabledChanged=(!!prevSharingEnabled)!==(!!sharingEnabled);
6705
+ var roleChanged=prevRole!==_sharingRole;
6706
+ var needsRestart=enabledChanged||roleChanged;
6378
6707
  if(sharingEnabled) updateHubShareInfo();
6379
6708
  if(needsRestart){
6380
6709
  setTimeout(function(){showRestartOverlay(t('settings.restart.waiting'));},300);
@@ -6794,23 +7123,89 @@ function renderToolAgg(data){
6794
7123
  '</tbody></table>';
6795
7124
  }
6796
7125
 
6797
- /* ─── Sharing status polling ─── */
6798
- var _sharingPollTimer=null;
6799
- function startSharingPoll(){
6800
- stopSharingPoll();
6801
- _sharingPollTimer=setInterval(function(){
6802
- if(sharingStatusCache&&sharingStatusCache.enabled) loadSharingStatus(false);
6803
- },30000);
7126
+ /* ─── Unified live-data poller ─── */
7127
+ var _livePoller=null;
7128
+ var _LIVE_POLL_MS=15000;
7129
+ var _livePollBusy=false;
7130
+
7131
+ async function _livePollTick(){
7132
+ if(_livePollBusy||document.hidden) return;
7133
+ _livePollBusy=true;
7134
+ try{
7135
+ if(sharingStatusCache&&sharingStatusCache.enabled&&_lastSharingConnStatus!=='rejected') loadSharingStatus(false);
7136
+ if(!_notifSSEConnected) pollNotifCount();
7137
+ pollAdminPending();
7138
+ if(_activeView==='admin') loadAdminData();
7139
+ else if(_activeView==='memories'){
7140
+ var _searchVal=(document.getElementById('searchInput')||{}).value||'';
7141
+ if(!_searchVal.trim()){
7142
+ if(memorySearchScope==='hub') loadHubMemories(true);
7143
+ else{loadStats();loadMemories(null,true);}
7144
+ }
7145
+ }
7146
+ else if(_activeView==='tasks') loadTasks(true);
7147
+ else if(_activeView==='skills') loadSkills(true);
7148
+ else if(_activeView==='analytics') loadMetrics();
7149
+ }catch(e){}
7150
+ _livePollBusy=false;
6804
7151
  }
6805
- function stopSharingPoll(){
6806
- if(_sharingPollTimer){clearInterval(_sharingPollTimer);_sharingPollTimer=null;}
7152
+
7153
+ function startLivePoller(){
7154
+ stopLivePoller();
7155
+ _livePoller=setInterval(_livePollTick,_LIVE_POLL_MS);
7156
+ }
7157
+ function stopLivePoller(){
7158
+ if(_livePoller){clearInterval(_livePoller);_livePoller=null;}
6807
7159
  }
6808
7160
 
6809
- /* ─── Notifications ─── */
7161
+ document.addEventListener('visibilitychange',function(){
7162
+ if(document.hidden){
7163
+ stopLivePoller();
7164
+ }else{
7165
+ _livePollTick();
7166
+ startLivePoller();
7167
+ if(!_notifSSE||!_notifSSEConnected) connectNotifSSE();
7168
+ }
7169
+ });
7170
+
7171
+ /* ─── Notifications (SSE push + fallback poll) ─── */
6810
7172
  var _notifCache=[];
6811
7173
  var _notifUnread=0;
6812
7174
  var _notifPollTimer=null;
6813
7175
  var _notifPanelOpen=false;
7176
+ var _notifSSE=null;
7177
+ var _notifSSEConnected=false;
7178
+ var _notifSSERetryMs=1000;
7179
+
7180
+ function connectNotifSSE(){
7181
+ if(_notifSSE) return;
7182
+ try{
7183
+ _notifSSE=new EventSource('/api/notifications/stream');
7184
+ _notifSSE.onmessage=function(ev){
7185
+ try{
7186
+ var d=JSON.parse(ev.data);
7187
+ if(d.type==='connected'){_notifSSEConnected=true;_notifSSERetryMs=1000;return;}
7188
+ if(d.type==='update'){
7189
+ var prev=_notifUnread;
7190
+ _notifUnread=d.unreadCount||0;
7191
+ renderNotifBadge();
7192
+ if(_notifUnread>prev&&_notifPanelOpen) loadNotifications();
7193
+ }
7194
+ if(d.type==='cleared'){
7195
+ _notifUnread=0;_notifCache=[];
7196
+ renderNotifBadge();renderNotifPanel();
7197
+ }
7198
+ }catch(e){}
7199
+ };
7200
+ _notifSSE.onerror=function(){
7201
+ _notifSSEConnected=false;
7202
+ if(_notifSSE){_notifSSE.close();_notifSSE=null;}
7203
+ setTimeout(connectNotifSSE,Math.min(_notifSSERetryMs,30000));
7204
+ _notifSSERetryMs=Math.min(_notifSSERetryMs*2,30000);
7205
+ };
7206
+ }catch(e){}
7207
+ }
7208
+ connectNotifSSE();
6814
7209
 
6815
7210
  function toggleNotifPanel(e){
6816
7211
  if(e)e.stopPropagation();
@@ -6841,7 +7236,10 @@ function notifTimeAgo(ts){
6841
7236
  return t('notif.timeAgo.day').replace('{n}',Math.floor(diff/86400000));
6842
7237
  }
6843
7238
 
6844
- function notifIcon(resource){
7239
+ function notifIcon(resource,type){
7240
+ if(type==='user_online') return '\\u{1F7E2}';
7241
+ if(type==='user_offline') return '\\u{1F534}';
7242
+ if(type==='user_join_request') return '\\u{1F464}';
6845
7243
  if(resource==='memory') return '\\u{1F4DD}';
6846
7244
  if(resource==='task') return '\\u{1F4CB}';
6847
7245
  if(resource==='skill') return '\\u{1F9E0}';
@@ -6852,6 +7250,21 @@ function notifTypeText(n){
6852
7250
  if(n.type==='resource_removed'){
6853
7251
  return t('notif.removed.'+n.resource)||t('notif.removed.memory');
6854
7252
  }
7253
+ if(n.type==='resource_shared'){
7254
+ return t('notif.shared.'+n.resource)||t('notif.shared.memory');
7255
+ }
7256
+ if(n.type==='resource_unshared'){
7257
+ return t('notif.unshared.'+n.resource)||t('notif.unshared.memory');
7258
+ }
7259
+ if(n.type==='user_join_request'){
7260
+ return t('notif.userJoin');
7261
+ }
7262
+ if(n.type==='user_online'){
7263
+ return t('notif.userOnline');
7264
+ }
7265
+ if(n.type==='user_offline'){
7266
+ return t('notif.userOffline');
7267
+ }
6855
7268
  return n.message||n.type;
6856
7269
  }
6857
7270
 
@@ -6896,7 +7309,7 @@ function renderNotifPanel(){
6896
7309
  body.innerHTML=_notifCache.map(function(n){
6897
7310
  var cls='notif-item'+(n.read?'':' unread');
6898
7311
  return '<div class="'+cls+'" onclick="markNotifRead(&quot;'+esc(n.id)+'&quot;)">'+
6899
- '<div class="notif-item-icon">'+notifIcon(n.resource)+'</div>'+
7312
+ '<div class="notif-item-icon">'+notifIcon(n.resource,n.type)+'</div>'+
6900
7313
  '<div class="notif-item-body">'+
6901
7314
  '<div class="notif-item-title">'+esc(notifTypeText(n))+'</div>'+
6902
7315
  '<div class="notif-item-name">'+esc(n.title)+'</div>'+
@@ -6937,24 +7350,20 @@ async function clearAllNotifs(){
6937
7350
  }catch(e){}
6938
7351
  }
6939
7352
 
6940
- function startNotifPoll(){
6941
- if(_notifPollTimer) return;
6942
- pollNotifCount();
6943
- _notifPollTimer=setInterval(pollNotifCount,15000);
6944
- }
6945
-
6946
- function stopNotifPoll(){
6947
- if(_notifPollTimer){clearInterval(_notifPollTimer);_notifPollTimer=null;}
6948
- }
7353
+ function startNotifPoll(){ startLivePoller(); }
7354
+ function stopNotifPoll(){ }
6949
7355
 
6950
7356
  /* ─── Data loading ─── */
6951
7357
  async function loadAll(){
6952
- await Promise.all([loadStats(),loadMemories(),loadSharingStatus(false)]);
7358
+ await loadStats();
7359
+ var initOwner=memorySearchScope==='local'?_currentAgentOwner:undefined;
7360
+ if(initOwner) await loadStats(initOwner);
7361
+ await Promise.all([loadMemories(),loadSharingStatus(false)]);
6953
7362
  checkMigrateStatus();
6954
7363
  connectPPSSE();
6955
7364
  checkForUpdate();
6956
- startSharingPoll();
6957
- startNotifPoll();
7365
+ pollNotifCount();
7366
+ startLivePoller();
6958
7367
  }
6959
7368
 
6960
7369
  async function loadStats(ownerFilter){
@@ -6966,6 +7375,7 @@ async function loadStats(ownerFilter){
6966
7375
  d=await r.json();
6967
7376
  }catch(e){ d={}; }
6968
7377
  if(!d||typeof d!=='object') d={};
7378
+ if(d.currentAgentOwner) _currentAgentOwner=d.currentAgentOwner;
6969
7379
  const tm=d.totalMemories||0;
6970
7380
  const dedupB=d.dedupBreakdown||{};
6971
7381
  const activeCount=dedupB.active||tm;
@@ -7032,11 +7442,14 @@ async function loadStats(ownerFilter){
7032
7442
  const ownerSel=document.getElementById('filterOwner');
7033
7443
  if(ownerSel && d.owners && d.owners.length>0){
7034
7444
  const curVal=ownerSel.value;
7035
- ownerSel.innerHTML='<option value="">'+t('filter.allowners')+'</option>'+'<option value="public">'+t('filter.public')+'</option>';
7036
- d.owners.filter(o=>o && o!=='public').forEach(o=>{
7037
- ownerSel.innerHTML+='<option value="'+o+'">'+o+'</option>';
7445
+ var agents=d.owners.filter(function(o){return o && o.indexOf('agent:')===0;});
7446
+ ownerSel.innerHTML='<option value="">'+t('filter.allagents')+'</option>';
7447
+ agents.forEach(function(o){
7448
+ var label=o.replace('agent:','');
7449
+ ownerSel.innerHTML+='<option value="'+o+'"'+(o===curVal?' selected':'')+'>'+label+'</option>';
7038
7450
  });
7039
- ownerSel.value=curVal;
7451
+ if(agents.length<=1) ownerSel.style.display='none';
7452
+ else ownerSel.style.display='';
7040
7453
  }
7041
7454
  }
7042
7455
 
@@ -7076,59 +7489,92 @@ function getFilterParams(){
7076
7489
  if(dt) p.set('dateTo',dt);
7077
7490
  const sort=document.getElementById('filterSort').value;
7078
7491
  if(sort==='oldest') p.set('sort','oldest');
7079
- const owner=document.getElementById('filterOwner').value;
7080
- if(owner) p.set('owner',owner);
7492
+ const scope=memorySearchScope||'local';
7493
+ if(scope==='local'){
7494
+ p.set('owner',_currentAgentOwner);
7495
+ }else{
7496
+ const owner=document.getElementById('filterOwner').value;
7497
+ if(owner) p.set('owner',owner);
7498
+ }
7081
7499
  return p;
7082
7500
  }
7083
7501
 
7084
- async function loadMemories(page){
7502
+ async function loadMemories(page,silent){
7085
7503
  if(page) currentPage=page;
7086
7504
  const list=document.getElementById('memoryList');
7087
- list.innerHTML='<div class="spinner"></div>';
7505
+ if(!silent) list.innerHTML='<div class="spinner"></div>';
7088
7506
  try{
7089
7507
  const p=getFilterParams();
7090
7508
  p.set('limit',PAGE_SIZE);
7091
7509
  p.set('page',currentPage);
7092
7510
  const r=await fetch('/api/memories?'+p.toString());
7093
7511
  const d=await r.json();
7512
+ var items=d.memories||[];
7513
+ if(silent){
7514
+ var fp=JSON.stringify(items.map(function(m){return m.id+'|'+m.updated_at}));
7515
+ if(fp===_lastMemoriesFingerprint) return;
7516
+ _lastMemoriesFingerprint=fp;
7517
+ }else{
7518
+ _lastMemoriesFingerprint=JSON.stringify(items.map(function(m){return m.id+'|'+m.updated_at}));
7519
+ }
7094
7520
  totalPages=d.totalPages||1;
7095
7521
  totalCount=d.total||0;
7096
7522
  document.getElementById('searchMeta').textContent=totalCount+t('search.meta.total');
7097
- renderMemories(d.memories||[]);
7523
+ renderMemories(items);
7098
7524
  renderPagination();
7099
7525
  }catch(e){
7100
- list.innerHTML='';
7101
- totalPages=1;totalCount=0;
7102
- renderMemories([]);
7103
- renderPagination();
7526
+ if(!silent){
7527
+ list.innerHTML='';
7528
+ totalPages=1;totalCount=0;
7529
+ _lastMemoriesFingerprint='';
7530
+ renderMemories([]);
7531
+ renderPagination();
7532
+ }
7104
7533
  }
7105
7534
  }
7106
7535
 
7107
- async function loadHubMemories(){
7536
+ async function loadHubMemories(silent){
7108
7537
  const list=document.getElementById('memoryList');
7109
- list.innerHTML='<div class="spinner"></div>';
7538
+ if(!silent) list.innerHTML='<div class="spinner"></div>';
7110
7539
  try{
7111
7540
  const r=await fetch('/api/sharing/memories/list?limit='+PAGE_SIZE);
7112
7541
  const d=await r.json();
7113
7542
  const items=d.memories||[];
7543
+ if(silent){
7544
+ var fp=JSON.stringify(items.map(function(m){return m.id+'|'+(m.updated_at||m.created_at)}));
7545
+ if(fp===_lastMemoriesFingerprint) return;
7546
+ _lastMemoriesFingerprint=fp;
7547
+ }else{
7548
+ _lastMemoriesFingerprint=JSON.stringify(items.map(function(m){return m.id+'|'+(m.updated_at||m.created_at)}));
7549
+ }
7550
+ totalPages=1;totalCount=items.length;currentPage=1;
7114
7551
  document.getElementById('searchMeta').textContent=items.length+t('search.meta.total');
7115
7552
  document.getElementById('sharingSearchMeta').textContent='';
7116
7553
  renderMemories(items);
7117
7554
  document.getElementById('pagination').innerHTML='';
7118
7555
  }catch(e){
7119
- document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
7120
- renderMemories([]);
7121
- document.getElementById('pagination').innerHTML='';
7556
+ if(!silent){
7557
+ _lastMemoriesFingerprint='';
7558
+ document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
7559
+ renderMemories([]);
7560
+ document.getElementById('pagination').innerHTML='';
7561
+ }
7122
7562
  }
7123
7563
  }
7124
7564
 
7125
7565
  async function doSearch(query){
7126
7566
  query=(query||'').trim();
7127
- if(!query){loadMemories();return;}
7567
+ if(!query){
7568
+ currentPage=1;
7569
+ if(memorySearchScope==='hub') loadHubMemories();
7570
+ else loadMemories();
7571
+ return;
7572
+ }
7573
+ currentPage=1;
7128
7574
  var scope=document.getElementById('memorySearchScope')?.value||memorySearchScope||'local';
7129
7575
  var list=document.getElementById('memoryList');
7130
7576
  list.innerHTML='<div class="spinner"></div>';
7131
- if(scope!=='local'){
7577
+ if(scope==='hub'){
7132
7578
  try{
7133
7579
  var r=await fetch('/api/sharing/search/memories',{
7134
7580
  method:'POST',
@@ -7136,6 +7582,7 @@ async function doSearch(query){
7136
7582
  body:JSON.stringify({query:query,scope:scope,maxResults:20,role:activeRole||undefined})
7137
7583
  });
7138
7584
  var data=await r.json();
7585
+ totalPages=1;totalCount=(data.results||[]).length;
7139
7586
  renderSharingMemorySearchResults(data,query);
7140
7587
  }catch(e){
7141
7588
  document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
@@ -7150,6 +7597,7 @@ async function doSearch(query){
7150
7597
  var r=await fetch('/api/search?'+p.toString());
7151
7598
  var d=await r.json();
7152
7599
  var total=d.total||0;
7600
+ totalPages=1;totalCount=total;
7153
7601
  var meta=[];
7154
7602
  if(d.vectorCount>0) meta.push(d.vectorCount+t('search.meta.semantic'));
7155
7603
  if(d.ftsCount>0) meta.push(d.ftsCount+t('search.meta.text'));
@@ -7238,7 +7686,8 @@ function renderMemories(items){
7238
7686
  const isPublicMem=ownerVal==='public';
7239
7687
  const localManaged=!!m.localSharingManaged;
7240
7688
  const memShared=m.sharingVisibility||null;
7241
- const memScope=memShared?'team':isPublicMem?'local':'private';
7689
+ const isHubScope=memorySearchScope==='hub';
7690
+ const memScope=isHubScope?'team':memShared?'team':isPublicMem?'local':'private';
7242
7691
  const memScopeBadge=renderScopeBadge(memScope);
7243
7692
  let dedupInfo='';
7244
7693
  if(ds==='duplicate'||ds==='merged'){
@@ -7343,7 +7792,8 @@ function renderPagination(){
7343
7792
  function goPage(p){
7344
7793
  if(p<1||p>totalPages||p===currentPage) return;
7345
7794
  currentPage=p;
7346
- loadMemories();
7795
+ if(memorySearchScope==='hub') loadHubMemories();
7796
+ else loadMemories();
7347
7797
  document.getElementById('memoryList').scrollIntoView({behavior:'smooth',block:'start'});
7348
7798
  }
7349
7799
 
@@ -8226,7 +8676,7 @@ async function checkForUpdate(){
8226
8676
  const pkgSpec=d.installCommand?d.installCommand.replace(/^(?:npx\s+)?openclaw\s+plugins\s+install\s+/,''):(d.packageName+'@'+d.latest);
8227
8677
  var banner=document.createElement('div');
8228
8678
  banner.id='updateBanner';
8229
- 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)';
8679
+ 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)';
8230
8680
  var textNode=document.createElement('div');
8231
8681
  textNode.style.cssText='display:flex;align-items:center;gap:8px;flex-shrink:0;font-size:13px';
8232
8682
  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>';
@@ -8259,7 +8709,7 @@ async function checkForUpdate(){
8259
8709
 
8260
8710
  /* ─── Init ─── */
8261
8711
  document.getElementById('modalOverlay').addEventListener('click',e=>{if(e.target.id==='modalOverlay')closeModal()});
8262
- document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape'){e.target.value='';loadMemories()}});
8712
+ document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape'){e.target.value='';currentPage=1;if(memorySearchScope==='hub')loadHubMemories();else loadMemories();}});
8263
8713
  applyI18n();
8264
8714
  checkAuth();
8265
8715
  </script>