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

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 (99) hide show
  1. package/.env.example +7 -0
  2. package/README.md +94 -27
  3. package/dist/capture/index.js +3 -1
  4. package/dist/capture/index.js.map +1 -1
  5. package/dist/client/connector.d.ts +5 -0
  6. package/dist/client/connector.d.ts.map +1 -1
  7. package/dist/client/connector.js +132 -10
  8. package/dist/client/connector.js.map +1 -1
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +2 -1
  11. package/dist/config.js.map +1 -1
  12. package/dist/hub/server.d.ts +2 -0
  13. package/dist/hub/server.d.ts.map +1 -1
  14. package/dist/hub/server.js +251 -38
  15. package/dist/hub/server.js.map +1 -1
  16. package/dist/hub/user-manager.d.ts +9 -0
  17. package/dist/hub/user-manager.d.ts.map +1 -1
  18. package/dist/hub/user-manager.js +26 -2
  19. package/dist/hub/user-manager.js.map +1 -1
  20. package/dist/ingest/chunker.d.ts +2 -1
  21. package/dist/ingest/chunker.d.ts.map +1 -1
  22. package/dist/ingest/chunker.js +14 -10
  23. package/dist/ingest/chunker.js.map +1 -1
  24. package/dist/ingest/providers/index.js +2 -2
  25. package/dist/ingest/providers/index.js.map +1 -1
  26. package/dist/recall/engine.d.ts.map +1 -1
  27. package/dist/recall/engine.js +96 -1
  28. package/dist/recall/engine.js.map +1 -1
  29. package/dist/shared/llm-call.d.ts.map +1 -1
  30. package/dist/shared/llm-call.js +2 -1
  31. package/dist/shared/llm-call.js.map +1 -1
  32. package/dist/sharing/types.d.ts +1 -1
  33. package/dist/sharing/types.d.ts.map +1 -1
  34. package/dist/skill/evolver.d.ts +2 -0
  35. package/dist/skill/evolver.d.ts.map +1 -1
  36. package/dist/skill/evolver.js +56 -5
  37. package/dist/skill/evolver.js.map +1 -1
  38. package/dist/skill/generator.d.ts +2 -0
  39. package/dist/skill/generator.d.ts.map +1 -1
  40. package/dist/skill/generator.js +45 -3
  41. package/dist/skill/generator.js.map +1 -1
  42. package/dist/skill/installer.d.ts +26 -0
  43. package/dist/skill/installer.d.ts.map +1 -1
  44. package/dist/skill/installer.js +80 -4
  45. package/dist/skill/installer.js.map +1 -1
  46. package/dist/skill/upgrader.d.ts +2 -0
  47. package/dist/skill/upgrader.d.ts.map +1 -1
  48. package/dist/skill/upgrader.js +139 -1
  49. package/dist/skill/upgrader.js.map +1 -1
  50. package/dist/skill/validator.d.ts +3 -0
  51. package/dist/skill/validator.d.ts.map +1 -1
  52. package/dist/skill/validator.js +75 -0
  53. package/dist/skill/validator.js.map +1 -1
  54. package/dist/storage/sqlite.d.ts +58 -0
  55. package/dist/storage/sqlite.d.ts.map +1 -1
  56. package/dist/storage/sqlite.js +295 -35
  57. package/dist/storage/sqlite.js.map +1 -1
  58. package/dist/telemetry.d.ts.map +1 -1
  59. package/dist/telemetry.js +27 -8
  60. package/dist/telemetry.js.map +1 -1
  61. package/dist/types.d.ts +10 -0
  62. package/dist/types.d.ts.map +1 -1
  63. package/dist/types.js +4 -0
  64. package/dist/types.js.map +1 -1
  65. package/dist/viewer/html.d.ts.map +1 -1
  66. package/dist/viewer/html.js +796 -289
  67. package/dist/viewer/html.js.map +1 -1
  68. package/dist/viewer/server.d.ts +11 -0
  69. package/dist/viewer/server.d.ts.map +1 -1
  70. package/dist/viewer/server.js +456 -92
  71. package/dist/viewer/server.js.map +1 -1
  72. package/index.ts +411 -52
  73. package/openclaw.plugin.json +1 -1
  74. package/package.json +2 -1
  75. package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
  76. package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
  77. package/prebuilds/linux-x64/better_sqlite3.node +0 -0
  78. package/prebuilds/win32-x64/better_sqlite3.node +0 -0
  79. package/src/capture/index.ts +4 -1
  80. package/src/client/connector.ts +136 -10
  81. package/src/config.ts +2 -1
  82. package/src/hub/server.ts +246 -38
  83. package/src/hub/user-manager.ts +42 -6
  84. package/src/ingest/chunker.ts +19 -13
  85. package/src/ingest/providers/index.ts +2 -2
  86. package/src/recall/engine.ts +89 -1
  87. package/src/shared/llm-call.ts +2 -1
  88. package/src/sharing/types.ts +1 -1
  89. package/src/skill/evolver.ts +58 -6
  90. package/src/skill/generator.ts +44 -5
  91. package/src/skill/installer.ts +107 -4
  92. package/src/skill/upgrader.ts +139 -1
  93. package/src/skill/validator.ts +79 -0
  94. package/src/storage/sqlite.ts +326 -40
  95. package/src/telemetry.ts +27 -9
  96. package/src/types.ts +11 -0
  97. package/src/viewer/html.ts +796 -289
  98. package/src/viewer/server.ts +430 -89
  99. package/telemetry.credentials.json +5 -0
@@ -157,7 +157,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
157
157
  .search-bar input::placeholder{color:var(--text-muted)}
158
158
  .search-bar input:focus{border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-glow)}
159
159
  .search-bar .search-icon{position:absolute;left:14px;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:14px;pointer-events:none}
160
- .search-meta{font-size:12px;color:var(--text-sec);margin-bottom:14px;padding:0 2px}
160
+ .search-meta{font-size:12px;color:var(--text-sec);padding:0 2px}.search-meta:not(:empty){margin-bottom:14px}
161
161
  .scope-select{padding:10px 12px;border:1px solid var(--border);border-radius:10px;background:var(--bg-card);color:var(--text);font-size:13px;min-width:110px;outline:none}
162
162
  .sharing-inline-meta{font-size:12px;color:var(--text-muted);margin:-8px 0 14px 2px}
163
163
  .sharing-sidebar-card{margin:14px 0 18px;border:1px solid var(--border);background:var(--bg-card);border-radius:12px;padding:12px;box-shadow:var(--shadow-sm)}
@@ -197,65 +197,92 @@ input,textarea,select{font-family:inherit;font-size:inherit}
197
197
  .pending-user-name{font-size:14px;font-weight:700;color:var(--text)}
198
198
  .pending-user-meta{font-size:12px;color:var(--text-sec);margin-top:4px}
199
199
  .pending-user-actions{display:flex;gap:8px;margin-top:10px}
200
- /* ─── Admin Panel ─── */
201
- .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}
202
- .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}
203
- .admin-header-top{display:flex;justify-content:space-between;align-items:center}
204
- .admin-header h2{font-size:20px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:10px;margin:0}
205
- .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)}
206
- .admin-header-sub{font-size:12px;color:var(--text-muted);margin-top:6px}
207
- .admin-stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:10px;margin-top:18px}
208
- .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}
209
- .admin-stat-box:hover{border-color:rgba(99,102,241,.25);transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.06)}
210
- .admin-stat-box::before{content:'';position:absolute;top:0;left:50%;transform:translateX(-50%);width:40px;height:2px;border-radius:0 0 4px 4px}
211
- .admin-stat-box:nth-child(1)::before{background:var(--green)}
212
- .admin-stat-box:nth-child(2)::before{background:var(--amber)}
213
- .admin-stat-box:nth-child(3)::before{background:var(--violet)}
214
- .admin-stat-box:nth-child(4)::before{background:var(--cyan)}
215
- .admin-stat-box:nth-child(5)::before{background:var(--pri)}
216
- .admin-stat-box:nth-child(6)::before{background:var(--rose)}
217
- .admin-stat-box .as-icon{font-size:16px;margin-bottom:4px;display:block}
218
- .admin-stat-box .val{font-size:22px;font-weight:800;color:var(--text);font-variant-numeric:tabular-nums}
219
- .admin-stat-box .lbl{font-size:10px;color:var(--text-muted);margin-top:3px;letter-spacing:.03em}
220
- .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}
221
- .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}
222
- .admin-tab:hover{background:rgba(99,102,241,.06);color:var(--text)}
223
- .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)}
224
233
  .admin-tab .at-icon{font-size:14px;line-height:1}
225
- .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}
226
- .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)}
227
236
  .admin-panel{display:none}
228
- .admin-panel.active{display:block}
229
- .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}
230
239
  .admin-card-clickable{cursor:pointer}
231
- .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}
232
- .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}
233
243
  .admin-card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
234
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}
235
245
  .admin-card-meta{font-size:12px;color:var(--text-muted);line-height:1.5}
236
- .au-contrib{display:flex;gap:16px;padding:10px 0;margin:6px 0;border-top:1px solid var(--border);border-bottom:1px solid var(--border)}
237
- .au-contrib-item{font-size:12px;color:var(--text-sec);display:flex;align-items:center;gap:4px}
238
- .au-contrib-num{font-size:18px;font-weight:700;line-height:1}
239
- .au-info{display:flex;flex-wrap:wrap;gap:6px 14px;padding:8px 0;font-size:12px}
240
- .au-info-item{color:var(--text-muted);white-space:nowrap}
241
- .au-status-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:4px;vertical-align:middle;flex-shrink:0}
242
- .au-status-dot.online{background:#22c55e;box-shadow:0 0 6px rgba(34,197,94,.5)}
243
- .au-status-dot.offline{background:#9ca3af}
244
- .au-status-text{font-size:11px;font-weight:500}
245
- .au-status-text.online{color:#22c55e}
246
- .au-status-text.offline{color:#9ca3af}
247
- .au-group-header{font-size:13px;font-weight:600;color:var(--text-sec);margin:18px 0 8px;display:flex;align-items:center;gap:8px}
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:10px;font-weight:600;letter-spacing:.04em;padding:3px 10px;border-radius:6px;white-space:nowrap}
255
+ .au-status-text.online{color:#22c55e;background:rgba(34,197,94,.08);border:1px solid rgba(34,197,94,.18);text-shadow:0 0 8px rgba(34,197,94,.2)}
256
+ .au-status-text.offline{color:#6b7280;background:rgba(107,114,128,.06);border:1px solid rgba(107,114,128,.1)}
257
+ [data-theme="light"] .au-status-text.online{background:rgba(34,197,94,.06);border-color:rgba(34,197,94,.15)}
258
+ [data-theme="light"] .au-status-text.offline{background:rgba(0,0,0,.03);border-color:rgba(0,0,0,.06)}
259
+ .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}
248
260
  .au-group-header:first-child{margin-top:0}
249
261
  .au-group-header .au-group-dot{display:inline-block;width:8px;height:8px;border-radius:50%}
250
- .au-group-header .au-group-dot.online{background:#22c55e}
251
- .au-group-header .au-group-dot.offline{background:#9ca3af}
262
+ .au-group-header .au-group-dot.online{background:#22c55e;box-shadow:0 0 6px rgba(34,197,94,.5)}
263
+ .au-group-header .au-group-dot.offline{background:#6b7280}
252
264
  .au-group-header .au-group-count{font-size:12px;font-weight:400;color:var(--text-muted)}
253
- .au-card.au-offline{opacity:.7}
254
- .au-card.au-offline::before{background:#9ca3af}
255
- .au-card.au-online::before{background:#22c55e}
265
+ .au-card.au-offline{opacity:.55}
266
+ .au-card.au-offline:hover{opacity:.8}
267
+ .au-card.au-offline::before{background:#6b7280}
268
+ .au-card.au-online::before{background:#22c55e;box-shadow:0 0 8px rgba(34,197,94,.3)}
269
+ .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}
270
+ .admin-pending-section::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,#fbbf24,#f59e0b,transparent)}
271
+ .admin-pending-section h3{font-size:14px;font-weight:700;color:#fbbf24;margin:0 0 14px;display:flex;align-items:center;gap:8px}
272
+ .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}
273
+ .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}
274
+ .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}
275
+ .admin-pending-card:hover{border-color:rgba(251,191,36,.35);box-shadow:0 4px 16px rgba(251,191,36,.08)}
276
+ .admin-pending-card .apc-name{font-size:14px;font-weight:700;color:var(--text)}
277
+ .admin-pending-card .apc-meta{font-size:11px;color:var(--text-muted);margin-top:4px;display:flex;align-items:center;gap:6px}
278
+ .admin-pending-card .apc-actions{display:flex;gap:8px;margin-top:12px}
279
+ .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)}
280
+ .admin-pending-card .apc-actions .btn-approve:hover{transform:translateY(-1px);box-shadow:0 4px 16px rgba(34,197,94,.35)}
281
+ .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}
282
+ .admin-pending-card .apc-actions .btn-reject:hover{background:rgba(244,63,94,.06);border-color:rgba(244,63,94,.4)}
256
283
  .admin-card-tags{display:flex;gap:5px;margin:8px 0;align-items:center}
257
284
  .admin-card-tags-left{display:flex;flex-wrap:wrap;gap:5px;align-items:center;flex:1;min-width:0}
258
- .admin-card-tag{display:inline-flex;align-items:center;gap:3px;padding:2px 8px;border-radius:6px;font-size:11px;font-weight:500}
285
+ .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}
259
286
  .admin-card-tag.tag-role{background:rgba(99,102,241,.1);color:var(--pri)}
260
287
  .admin-card-tag.tag-kind{background:rgba(245,158,11,.1);color:#f59e0b}
261
288
  .admin-card-tag.tag-owner{background:rgba(34,197,94,.1);color:#22c55e}
@@ -263,16 +290,16 @@ input,textarea,select{font-family:inherit;font-size:inherit}
263
290
  .admin-card-tag.tag-version{background:rgba(139,92,246,.1);color:#8b5cf6}
264
291
  .admin-card-tag.tag-visibility{background:rgba(99,102,241,.08);color:var(--pri)}
265
292
  .admin-card-tag.tag-group{background:rgba(139,92,246,.08);color:#8b5cf6}
266
- .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}
267
- .admin-card-actions{display:inline-flex;gap:4px;margin-left:auto;align-items:center;flex-shrink:0}
293
+ .admin-card-preview{font-size:12px;color:var(--text-sec);line-height:1.5;margin:8px 0;padding:10px 12px;background:rgba(99,102,241,.02);border-radius:10px;border:1px solid rgba(99,102,241,.08);max-height:120px;overflow:hidden;white-space:pre-wrap;word-break:break-all;position:relative;-webkit-mask-image:linear-gradient(to bottom,#000 88%,transparent 100%);mask-image:linear-gradient(to bottom,#000 88%,transparent 100%)}
294
+ .admin-card-actions{display:inline-flex;gap:6px;margin-left:auto;align-items:center;flex-shrink:0}
268
295
  .admin-card-time{font-size:11px;color:var(--text-muted)}
269
- .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}
296
+ .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}
270
297
  @keyframes adminDetailIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}
271
298
  .admin-card.expanded .admin-card-detail{display:block}
272
- .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)}
299
+ .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)}
273
300
  .admin-card-detail-meta{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:18px;font-size:12px;color:var(--text-muted)}
274
- .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}
275
- .admin-card-detail-meta .meta-item:hover{border-color:rgba(99,102,241,.25);background:rgba(99,102,241,.03)}
301
+ .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}
302
+ .admin-card-detail-meta .meta-item:hover{border-color:rgba(99,102,241,.25);background:rgba(99,102,241,.06)}
276
303
  .admin-card-detail-section{margin-top:20px}
277
304
  .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}
278
305
  .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))}
@@ -298,7 +325,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
298
325
  .adm-msg-side.assistant .adm-msg-role{color:var(--green)}
299
326
  .adm-msg-time{font-size:9px;color:var(--text-muted)}
300
327
  .adm-msg-body{flex:1;min-width:0;padding:12px 16px;font-size:13px;line-height:1.75;color:var(--text);white-space:pre-wrap;word-break:break-word}
301
- .adm-msg-body.collapsed{max-height:120px;overflow:hidden;-webkit-mask-image:linear-gradient(180deg,#000 65%,transparent);mask-image:linear-gradient(180deg,#000 65%,transparent)}
328
+ .adm-msg-body.collapsed{max-height:120px;overflow:hidden;-webkit-mask-image:linear-gradient(180deg,#000 88%,transparent);mask-image:linear-gradient(180deg,#000 88%,transparent)}
302
329
  .adm-msg-toggle{display:none;padding:0 16px 8px;font-size:11px;color:var(--pri);cursor:pointer;transition:color .15s}
303
330
  .adm-msg-toggle:hover{color:var(--pri-dark)}
304
331
  .admin-card-expand-btn{font-size:12px;color:var(--pri);cursor:pointer;background:none;border:none;padding:2px 6px;font-family:inherit}
@@ -306,18 +333,24 @@ input,textarea,select{font-family:inherit;font-size:inherit}
306
333
  .admin-toolbar{display:flex;align-items:center;gap:10px;margin-bottom:14px;flex-wrap:wrap}
307
334
  .admin-toolbar h3{font-size:14px;font-weight:600;color:var(--text);white-space:nowrap;margin:0;margin-right:auto;line-height:32px}
308
335
  .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}
309
- .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}
310
- .admin-badge.admin{background:rgba(52,199,89,.15);color:#34c759}
311
- .admin-badge.member{background:rgba(142,142,147,.12);color:var(--text-muted)}
312
- .admin-badge.pending{background:rgba(255,159,10,.15);color:#ff9f0a}
313
- .admin-badge.public{background:rgba(99,102,241,.1);color:var(--pri)}
314
- .admin-badge.group{background:rgba(139,92,246,.1);color:var(--violet)}
315
- .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)}
316
- .admin-empty .ae-icon{font-size:28px;display:block;margin-bottom:8px;opacity:.5}
317
- [data-theme="light"] .admin-badge.admin{background:rgba(5,150,105,.1);color:#059669}
318
- [data-theme="light"] .admin-badge.member{background:rgba(0,0,0,.06);color:#6b7280}
319
- [data-theme="light"] .admin-badge.pending{background:rgba(245,158,11,.1);color:#d97706}
320
- [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%)}
336
+ .admin-badge{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;padding:3px 10px;border-radius:6px;letter-spacing:.06em;text-transform:uppercase;position:relative;backdrop-filter:blur(4px);transition:all .25s}
337
+ .admin-badge.admin{background:linear-gradient(135deg,rgba(34,197,94,.12),rgba(16,185,129,.06));color:#22c55e;border:1px solid rgba(34,197,94,.2);box-shadow:0 0 12px rgba(34,197,94,.08),inset 0 1px 0 rgba(255,255,255,.05)}
338
+ .admin-badge.admin:hover{box-shadow:0 0 20px rgba(34,197,94,.15),inset 0 1px 0 rgba(255,255,255,.08)}
339
+ .admin-badge.member{background:rgba(99,102,241,.06);color:var(--text-muted);border:1px solid rgba(99,102,241,.12)}
340
+ .admin-badge.pending{background:linear-gradient(135deg,rgba(251,191,36,.12),rgba(245,158,11,.06));color:#fbbf24;border:1px solid rgba(251,191,36,.2);box-shadow:0 0 12px rgba(251,191,36,.08)}
341
+ .admin-badge.public{background:rgba(99,102,241,.08);color:var(--pri);border:1px solid rgba(99,102,241,.15)}
342
+ .admin-badge.group{background:rgba(139,92,246,.08);color:#8b5cf6;border:1px solid rgba(139,92,246,.15)}
343
+ .admin-badge.owner{background:linear-gradient(135deg,rgba(251,191,36,.12),rgba(245,158,11,.08));color:#f59e0b;border:1px solid rgba(251,191,36,.25);box-shadow:0 0 12px rgba(251,191,36,.1),inset 0 1px 0 rgba(255,255,255,.06)}
344
+ .au-badges{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}
345
+ .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)}
346
+ .admin-empty .ae-icon{font-size:32px;display:block;margin-bottom:10px;opacity:.4}
347
+ [data-theme="light"] .admin-badge.admin{background:rgba(5,150,105,.08);color:#059669;border-color:rgba(5,150,105,.18)}
348
+ [data-theme="light"] .admin-badge.member{background:rgba(0,0,0,.04);color:#6b7280;border-color:rgba(0,0,0,.08)}
349
+ [data-theme="light"] .admin-badge.pending{background:rgba(245,158,11,.08);color:#d97706;border-color:rgba(245,158,11,.18)}
350
+ [data-theme="light"] .admin-badge.owner{background:rgba(245,158,11,.08);color:#b45309;border-color:rgba(245,158,11,.2)}
351
+ [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%)}
352
+ [data-theme="light"] .admin-stat-box{background:rgba(255,255,255,.8)}
353
+ [data-theme="light"] .admin-pending-section{background:linear-gradient(135deg,rgba(251,191,36,.03),rgba(245,158,11,.015))}
321
354
  .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}
322
355
  .confirm-overlay.show{display:flex}
323
356
  .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}
@@ -715,7 +748,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
715
748
  [data-theme="light"] .nav-tabs{background:rgba(0,0,0,.05)}
716
749
  [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)}
717
750
  .analytics-view,.settings-view,.logs-view,.migrate-view,.admin-view{flex:1;min-width:0;flex-direction:column;gap:20px}
718
- .feed-wrap,.tasks-view,.skills-view,.analytics-view,.settings-view,.logs-view,.migrate-view,.admin-view{max-width:960px;margin:0 auto}
751
+ .feed-wrap,.tasks-view,.skills-view,.analytics-view,.logs-view,.migrate-view,.admin-view,.settings-view{max-width:960px}
719
752
 
720
753
  /* ─── Logs ─── */
721
754
  .logs-toolbar{display:flex;align-items:center;justify-content:space-between;padding:8px 0}
@@ -774,6 +807,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
774
807
  .recall-score.high{background:rgba(34,197,94,.12);color:#22c55e}
775
808
  .recall-score.mid{background:rgba(251,191,36,.12);color:#f59e0b}
776
809
  .recall-score.low{background:rgba(248,113,113,.1);color:var(--text-muted)}
810
+ .recall-origin{flex-shrink:0;font-size:9px;font-weight:600;padding:1px 5px;border-radius:4px}
811
+ .recall-origin.local-shared{background:rgba(59,130,246,.12);color:#3b82f6}
812
+ .recall-origin.hub-memory{background:rgba(139,92,246,.12);color:#8b5cf6}
813
+ .recall-origin.hub-remote{background:rgba(139,92,246,.12);color:#8b5cf6}
777
814
  .recall-summary-short{flex:1;color:var(--text-sec);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
778
815
  .recall-expand-icon{flex-shrink:0;font-size:10px;color:var(--text-muted);transition:transform .15s}
779
816
  .recall-item.expanded .recall-expand-icon{transform:rotate(90deg)}
@@ -942,8 +979,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
942
979
  .team-guide-steps li::marker{color:var(--pri);font-weight:700;font-size:11px}
943
980
  .team-guide-opt .btn-guide{font-size:11px;padding:5px 14px;border-radius:6px;font-weight:600;border:1px solid rgba(99,102,241,.25);background:rgba(99,102,241,.08);color:var(--pri);cursor:pointer;transition:background .15s,border-color .15s}
944
981
  .team-guide-opt .btn-guide:hover{background:rgba(99,102,241,.14);border-color:var(--pri)}
945
- .team-guide-dismiss{position:absolute;top:10px;right:12px;background:none;border:none;color:var(--text-muted);font-size:15px;cursor:pointer;padding:4px;line-height:1;opacity:.5;transition:opacity .15s}
946
- .team-guide-dismiss:hover{opacity:1}
947
982
  [data-theme="light"] .team-guide{background:linear-gradient(135deg,rgba(6,182,212,.03),rgba(79,70,229,.02));border-color:rgba(6,182,212,.15)}
948
983
  [data-theme="light"] .team-guide-opt{box-shadow:0 1px 3px rgba(0,0,0,.03)}
949
984
  [data-theme="light"] .team-guide-opt:hover{box-shadow:0 4px 16px rgba(0,0,0,.04)}
@@ -1186,7 +1221,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1186
1221
  </div>
1187
1222
  <button class="btn btn-ghost btn-sm" onclick="doLogout()" data-i18n="logout">Logout</button>
1188
1223
  </div>
1189
- </div>
1224
+ </div>
1190
1225
  </div>
1191
1226
 
1192
1227
  <div class="main-content">
@@ -1195,7 +1230,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1195
1230
  <div class="stat-card pri"><div class="stat-value" id="statTotal">-</div><div class="stat-label" data-i18n="stat.memories">Memories</div></div>
1196
1231
  <div class="stat-card green"><div class="stat-value" id="statSessions">-</div><div class="stat-label" data-i18n="stat.sessions">Sessions</div></div>
1197
1232
  <div class="stat-card amber"><div class="stat-value" id="statEmbeddings">-</div><div class="stat-label" data-i18n="stat.embeddings">Embeddings</div></div>
1198
- <div class="stat-card rose"><div class="stat-value" id="statTimeSpan">-</div><div class="stat-label" data-i18n="stat.days">Days</div></div>
1233
+ <div class="stat-card rose"><div class="stat-value" id="statAgents">-</div><div class="stat-label" data-i18n="stat.agents">Agents</div></div>
1199
1234
  </div>
1200
1235
  <div id="sidebarSharingSection" style="display:none">
1201
1236
  <div class="sharing-sidebar-card">
@@ -1218,7 +1253,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1218
1253
  <select id="filterOwner" class="filter-select" onchange="onOwnerFilterChange()">
1219
1254
  <option value="" data-i18n="filter.allagents">All agents</option>
1220
1255
  </select>
1221
- <select id="memorySearchScope" class="filter-select" onchange="onMemoryScopeChange()">
1256
+ <select id="memorySearchScope" class="filter-select" onchange="onMemoryScopeChange()" style="display:none">
1222
1257
  <option value="local" data-i18n="scope.thisAgent">This Agent</option>
1223
1258
  <option value="allLocal" data-i18n="scope.thisDevice">This Device</option>
1224
1259
  <option value="hub" data-i18n="scope.hub">Team</option>
@@ -1230,7 +1265,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1230
1265
  <button class="filter-chip active" data-role="" onclick="setRoleFilter(this,'')" data-i18n="filter.all">All</button>
1231
1266
  <button class="filter-chip" data-role="user" onclick="setRoleFilter(this,'user')">User</button>
1232
1267
  <button class="filter-chip" data-role="assistant" onclick="setRoleFilter(this,'assistant')">Assistant</button>
1233
- <button class="filter-chip" data-role="system" onclick="setRoleFilter(this,'system')">System</button>
1234
1268
  <span class="filter-sep"></span>
1235
1269
  <select id="filterSort" class="filter-select" onchange="applyFilters()">
1236
1270
  <option value="newest" data-i18n="filter.newest">Newest first</option>
@@ -1263,7 +1297,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1263
1297
  <button class="filter-chip" data-task-status="active" onclick="setTaskStatusFilter(this,'active')" data-i18n="tasks.status.active">Active</button>
1264
1298
  <button class="filter-chip" data-task-status="completed" onclick="setTaskStatusFilter(this,'completed')" data-i18n="tasks.status.completed">Completed</button>
1265
1299
  <button class="filter-chip" data-task-status="skipped" onclick="setTaskStatusFilter(this,'skipped')" data-i18n="tasks.status.skipped">Skipped</button>
1266
- <select id="taskSearchScope" class="scope-select" onchange="onTaskScopeChange()">
1300
+ <select id="taskSearchScope" class="scope-select" onchange="onTaskScopeChange()" style="display:none">
1267
1301
  <option value="local" data-i18n="scope.thisAgent">This Agent</option>
1268
1302
  <option value="allLocal" data-i18n="scope.thisDevice">This Device</option>
1269
1303
  <option value="hub" data-i18n="scope.hub">Team</option>
@@ -1305,13 +1339,13 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1305
1339
  <div class="search-bar">
1306
1340
  <span class="search-icon">🔍</span>
1307
1341
  <input type="text" id="skillSearchInput" placeholder="Search skills..." data-i18n-ph="skills.search.placeholder" oninput="debounceSkillSearch()">
1308
- <select id="skillSearchScope" class="scope-select" onchange="onSkillScopeChange()">
1342
+ <select id="skillSearchScope" class="scope-select" onchange="onSkillScopeChange()" style="display:none">
1309
1343
  <option value="local" data-i18n="scope.thisAgent">This Agent</option>
1310
1344
  <option value="allLocal" data-i18n="scope.thisDevice">This Device</option>
1311
1345
  <option value="hub" data-i18n="scope.hub">Team</option>
1312
1346
  </select>
1313
1347
  </div>
1314
- <div class="search-meta" id="skillSearchMeta"></div>
1348
+ <div class="search-meta" id="skillSearchMeta" style="display:none"></div>
1315
1349
  <div class="tasks-header">
1316
1350
  <div class="tasks-stats">
1317
1351
  <div class="tasks-stat"><span class="tasks-stat-value" id="skillsTotalCount">-</span><span class="tasks-stat-label" data-i18n="skills.total">Total Skills</span></div>
@@ -1325,8 +1359,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1325
1359
  <button class="filter-chip" data-skill-status="active" onclick="setSkillStatusFilter(this,'active')" data-i18n="skills.filter.active">Active</button>
1326
1360
  <button class="filter-chip" data-skill-status="draft" onclick="setSkillStatusFilter(this,'draft')" data-i18n="skills.filter.draft">Draft</button>
1327
1361
  <button class="filter-chip" data-skill-status="archived" onclick="setSkillStatusFilter(this,'archived')" data-i18n="skills.filter.archived">Archived</button>
1328
- <span class="filter-sep"></span>
1329
- <select id="skillVisibilityFilter" class="filter-select" onchange="loadSkills()">
1362
+ <select id="skillVisibilityFilter" class="filter-select" onchange="loadSkills()" style="display:none">
1330
1363
  <option value="" data-i18n="filter.allvisibility">All visibility</option>
1331
1364
  <option value="public" data-i18n="filter.public">Public</option>
1332
1365
  <option value="private" data-i18n="filter.private">Private</option>
@@ -1413,7 +1446,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1413
1446
  <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">
1414
1447
  <option value="" data-i18n="logs.allTools">All Tools</option>
1415
1448
  </select>
1416
- <button class="btn btn-sm btn-ghost" onclick="loadLogs()" style="font-size:12px">\u21BB <span data-i18n="logs.refresh">Refresh</span></button>
1449
+
1417
1450
  </div>
1418
1451
  <div class="logs-toolbar-right">
1419
1452
  <input type="checkbox" id="logAutoRefresh" style="display:none">
@@ -1609,9 +1642,8 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1609
1642
  </div>
1610
1643
  </div>
1611
1644
  <div class="settings-card-body">
1612
- <!-- team setup guide (inside Hub card) -->
1645
+ <!-- team setup guide (inside Hub card) — always visible when sharing is not configured -->
1613
1646
  <div class="team-guide" id="teamSetupGuide">
1614
- <button class="team-guide-dismiss" onclick="dismissTeamGuide()" title="Dismiss">&times;</button>
1615
1647
  <div class="team-guide-title">\u{1F680} <span data-i18n="guide.title">Get Started with Team Collaboration</span></div>
1616
1648
  <div class="team-guide-subtitle" data-i18n="guide.subtitle">MemOS supports team memory sharing. Choose one of the following options to enable collaboration, or continue using local-only mode.</div>
1617
1649
  <div class="team-guide-options">
@@ -1625,7 +1657,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1625
1657
  <li><span data-i18n="guide.join.s1">Ask your team admin for the Server Address and Team Token</span></li>
1626
1658
  <li><span data-i18n="guide.join.s2">Enable sharing above, select "Client" mode</span></li>
1627
1659
  <li><span data-i18n="guide.join.s3">Fill in Server Address and Team Token, click "Test Connection"</span></li>
1628
- <li><span data-i18n="guide.join.s4">Save settings and restart the OpenClaw gateway (page refreshes automatically)</span></li>
1660
+ <li><span data-i18n="guide.join.s4">Click "Save & Apply" the service restarts automatically (page refreshes)</span></li>
1629
1661
  </ol>
1630
1662
  <button class="btn-guide" onclick="guideGoToHub('client')" data-i18n="guide.join.btn">\u2192 Configure Client Mode</button>
1631
1663
  </div>
@@ -1637,7 +1669,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1637
1669
  <div class="team-guide-opt-desc" data-i18n="guide.hub.desc">Be the team server. Run it on this device so others can connect and share memories with you.</div>
1638
1670
  <ol class="team-guide-steps">
1639
1671
  <li><span data-i18n="guide.hub.s1">Enable sharing above, select "Server" mode</span></li>
1640
- <li><span data-i18n="guide.hub.s2">Set a team name, save settings, and restart the gateway (page refreshes automatically)</span></li>
1672
+ <li><span data-i18n="guide.hub.s2">Set a team name, click "Save & Apply" the service restarts automatically</span></li>
1641
1673
  <li><span data-i18n="guide.hub.s3">Share the Server Address and Team Token with your team members</span></li>
1642
1674
  <li><span data-i18n="guide.hub.s4">Approve join requests in the Admin Panel</span></li>
1643
1675
  </ol>
@@ -1698,7 +1730,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1698
1730
  <div style="font-weight:700;color:var(--text);margin-bottom:4px" data-i18n="settings.hub.clientSteps.title">Quick Setup (3 steps)</div>
1699
1731
  <div><span style="color:var(--accent)">1.</span> <span data-i18n="settings.hub.clientSteps.s1">Ask your team admin for the Server Address and Team Token</span></div>
1700
1732
  <div><span style="color:var(--accent)">2.</span> <span data-i18n="settings.hub.clientSteps.s2">Fill them in below, click "Test Connection" to verify</span></div>
1701
- <div><span style="color:var(--accent)">3.</span> <span data-i18n="settings.hub.clientSteps.s3">Click "Save Settings", then restart OpenClaw gateway (page refreshes automatically)</span></div>
1733
+ <div><span style="color:var(--accent)">3.</span> <span data-i18n="settings.hub.clientSteps.s3">Click "Save & Apply" the service will restart and page refreshes automatically</span></div>
1702
1734
  </div>
1703
1735
  <div class="settings-grid">
1704
1736
  <div class="settings-field full-width">
@@ -1789,8 +1821,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1789
1821
  <div id="adminMainContent">
1790
1822
  <div class="admin-header">
1791
1823
  <div class="admin-header-top">
1792
- <h2><span class="ah-icon">\u{1F6E1}</span> <span data-i18n="admin.title">Team Admin Panel</span></h2>
1793
- <button class="btn btn-sm btn-ghost" onclick="loadAdminData()" style="backdrop-filter:blur(8px)" data-i18n="admin.refresh">\u21BB Refresh</button>
1824
+ <h2><span class="ah-icon">\u{26A1}</span> <span data-i18n="admin.title">Team Admin Panel</span></h2>
1794
1825
  </div>
1795
1826
  <div class="admin-header-sub" data-i18n="admin.subtitle">Manage team members and shared resources</div>
1796
1827
  <div class="admin-stat-row" id="adminStats"></div>
@@ -2003,7 +2034,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
2003
2034
  <script>
2004
2035
  let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=40,metricsDays=30;
2005
2036
  let memorySearchScope='local',skillSearchScope='local',taskSearchScope='local';
2037
+ let _lastMemoriesFingerprint='',_lastTasksFingerprint='',_lastSkillsFingerprint='';
2006
2038
  let _embeddingWarningShown=false;
2039
+ let _currentAgentOwner='agent:main';
2007
2040
 
2008
2041
  /* ─── i18n ─── */
2009
2042
  const I18N={
@@ -2054,8 +2087,8 @@ const I18N={
2054
2087
  'skills.load.error':'Failed to load skills',
2055
2088
  'skills.hub.title':'\u{1F310} Team Skills',
2056
2089
  'scope.local':'Local',
2057
- 'scope.thisAgent':'This Agent',
2058
- 'scope.thisDevice':'This Device',
2090
+ 'scope.thisAgent':'This Agent Only',
2091
+ 'scope.thisDevice':'All Local Agents',
2059
2092
  'scope.group':'Group',
2060
2093
  'scope.all':'All',
2061
2094
  'skills.visibility.public':'Shared Locally',
@@ -2093,6 +2126,13 @@ const I18N={
2093
2126
  'notif.userJoin':'New user requests to join the team',
2094
2127
  'notif.userOnline':'User came online',
2095
2128
  'notif.userOffline':'User went offline',
2129
+ 'notif.userLeft':'User has left the team',
2130
+ 'notif.membershipApproved':'Your team join request has been approved',
2131
+ 'notif.membershipRejected':'Your team join request has been declined',
2132
+ 'notif.membershipRemoved':'You have been removed from the team by the admin',
2133
+ 'notif.hubShutdown':'The team server has been shut down',
2134
+ 'notif.rolePromoted':'You have been promoted to admin',
2135
+ 'notif.roleDemoted':'You have been changed to member',
2096
2136
  'notif.clearAll':'Clear all',
2097
2137
  'notif.timeAgo.just':'just now',
2098
2138
  'notif.timeAgo.min':'{n}m ago',
@@ -2101,7 +2141,7 @@ const I18N={
2101
2141
  'stat.memories':'Memories',
2102
2142
  'stat.sessions':'Sessions',
2103
2143
  'stat.embeddings':'Embeddings',
2104
- 'stat.days':'Days',
2144
+ 'stat.agents':'Agents',
2105
2145
  'stat.active':'active',
2106
2146
  'stat.deduped':'deduped',
2107
2147
  'sidebar.sessions':'Sessions',
@@ -2200,6 +2240,9 @@ const I18N={
2200
2240
  'logs.recall.noHits':'No matching memories',
2201
2241
  'logs.recall.noneRelevant':'LLM filter: none relevant',
2202
2242
  'logs.recall.more':'{n} more...',
2243
+ 'recall.origin.localShared':'Local Shared',
2244
+ 'recall.origin.hubMemory':'Team Cache',
2245
+ 'recall.origin.hubRemote':'Team',
2203
2246
  'tab.import':'\u{1F4E5} Import',
2204
2247
  'tab.settings':'\u2699 Settings',
2205
2248
  'settings.modelconfig':'Model Configuration',
@@ -2240,12 +2283,12 @@ const I18N={
2240
2283
  'settings.test.ok':'Connected',
2241
2284
  'settings.test.fail':'Failed',
2242
2285
  'settings.session.expired':'Session expired, please refresh the page to log in again',
2243
- 'settings.save':'Save Settings',
2286
+ 'settings.save':'Save & Apply',
2244
2287
  'settings.reset':'Reset',
2245
2288
  'settings.saved':'Saved',
2246
- 'settings.restart.hint':'Some changes require restarting the OpenClaw gateway to take effect.',
2247
- 'settings.restart.autoRefresh':'Page will refresh automatically after the gateway restarts...',
2248
- 'settings.restart.waiting':'Configuration saved. Waiting for gateway to restart...',
2289
+ 'settings.restart.hint':'Changes will take effect after the service restarts automatically.',
2290
+ 'settings.restart.autoRefresh':'Service restarting, page will refresh automatically...',
2291
+ 'settings.restart.waiting':'Configuration saved. Service is restarting...',
2249
2292
  'settings.save.fail':'Failed to save settings',
2250
2293
  'settings.save.emb.required':'Embedding model is required. Please configure an embedding model before saving.',
2251
2294
  'settings.save.emb.fail':'Embedding model test failed, cannot save',
@@ -2372,16 +2415,16 @@ const I18N={
2372
2415
  'settings.hub.tokenCopied':'Team Token copied!',
2373
2416
  'settings.hub.hubSteps.title':'Quick Setup (3 steps)',
2374
2417
  'settings.hub.hubSteps.s1':'Fill in Team Name below (or keep default)',
2375
- 'settings.hub.hubSteps.s2':'Click "Save Settings", then restart OpenClaw gateway',
2418
+ 'settings.hub.hubSteps.s2':'Click "Save & Apply" the service will restart automatically',
2376
2419
  'settings.hub.hubSteps.s3':'Share the Server Address and Team Token below with your team members',
2377
2420
  'settings.hub.clientSteps.title':'Quick Setup (3 steps)',
2378
2421
  'settings.hub.clientSteps.s1':'Ask your team admin for the Server Address and Team Token',
2379
2422
  'settings.hub.clientSteps.s2':'Fill them in below, click "Test Connection" to verify',
2380
- 'settings.hub.clientSteps.s3':'Click "Save Settings", then restart OpenClaw gateway (page refreshes automatically)',
2423
+ 'settings.hub.clientSteps.s3':'Click "Save & Apply" the service will restart and page refreshes automatically',
2381
2424
  'settings.hub.shareInfo.title':'Share this info with your team members:',
2382
2425
  'settings.hub.shareInfo.yourIP':'your-IP',
2383
2426
  'settings.hub.shareInfo.clickCopy':'Click to copy',
2384
- 'settings.hub.restartAlert':'Team sharing config saved! Please restart the OpenClaw gateway for changes to take effect.\\n\\nRun: openclaw gateway stop && openclaw gateway start',
2427
+ 'settings.hub.restartAlert':'Team sharing config saved! The service will restart automatically to apply changes.',
2385
2428
  'settings.hub.hubAddress':'Server Address',
2386
2429
  'settings.hub.hubAddress.hint':'Team server address, e.g. 192.168.1.100:18800',
2387
2430
  'settings.hub.teamTokenClient':'Team Token',
@@ -2392,6 +2435,7 @@ const I18N={
2392
2435
  'settings.hub.userToken.hint':'Usually auto-obtained after joining. Only fill if given by admin.',
2393
2436
  'settings.hub.testConnection':'Test Connection',
2394
2437
  'settings.hub.test.noAddr':'Please enter server address first',
2438
+ 'settings.hub.teamToken.required':'Please enter team token',
2395
2439
  'settings.hub.test.testing':'Testing...',
2396
2440
  'settings.hub.test.ok':'Connected successfully',
2397
2441
  'settings.hub.test.fail':'Connection failed',
@@ -2401,6 +2445,10 @@ const I18N={
2401
2445
  'sidebar.hub':'\u{1F310} Team Sharing',
2402
2446
  'sharing.sidebar.connected':'Connected',
2403
2447
  'sharing.sidebar.disconnected':'Disconnected',
2448
+ 'sharing.sidebar.hubRunning':'Hub Running',
2449
+ 'sharing.sidebar.teamName':'Team',
2450
+ 'sharing.sidebar.members':'Members',
2451
+ 'sharing.sidebar.online':'online',
2404
2452
  'sharing.sidebar.pending':'Pending Approval',
2405
2453
  'sharing.sidebar.rejected':'Rejected',
2406
2454
  'sharing.sidebar.starting':'Starting...',
@@ -2410,11 +2458,21 @@ const I18N={
2410
2458
  'sharing.sidebar.targetHub':'Team Server:',
2411
2459
  'sharing.pendingApproval.hint':'Your join request has been submitted. Please wait for the team admin to approve.',
2412
2460
  'sharing.rejected.hint':'Your join request was rejected by the team admin. Please contact the admin or retry.',
2461
+ 'sharing.removed.hint':'You have been removed from the team by the admin. You can re-apply to join.',
2462
+ 'sharing.joinTeam':'Join Team',
2463
+ 'sharing.joinSent.pending':'Join request sent! Waiting for admin approval.',
2464
+ 'sharing.joinSent.active':'Successfully joined the team!',
2413
2465
  'sharing.retryJoin':'Retry Join',
2414
2466
  'sharing.retryJoin.hint':'Clears local data and re-submits the join request',
2415
2467
  'sharing.retryJoin.confirm':'This will clear your current connection and re-submit a join request. Continue?',
2468
+ 'sharing.leaveTeam':'Leave Team',
2469
+ 'sharing.leaveTeam.confirm':'You are about to leave team "{team}".\\n\\nWhat will happen:\\n\\u2022 You will disconnect from the team server\\n\\u2022 The team admin will be notified that you left\\n\\u2022 You will no longer receive shared memories, tasks, or skills\\n\\u2022 Your local data is preserved and not affected\\n\\u2022 You can rejoin later if the admin approves\\n\\nAre you sure?',
2470
+ 'sharing.leaveTeam.success':'You have left the team. Sharing has been disabled.',
2471
+ 'sharing.leaveTeam.fail':'Failed to leave team',
2472
+ 'sharing.team.default':'the team',
2416
2473
  'sharing.retryJoin.success':'Join request re-submitted. Waiting for admin approval.',
2417
2474
  'sharing.retryJoin.fail':'Failed to retry join',
2475
+ 'sharing.ownerRemoved':'(removed)',
2418
2476
  'sharing.cannotJoinSelf':'Cannot join your own server. Please enter a remote server address.',
2419
2477
  'scope.hub':'Team',
2420
2478
  'memory.detail.title':'Memory Detail',
@@ -2465,6 +2523,7 @@ const I18N={
2465
2523
  'admin.editName':'Edit Name',
2466
2524
  'admin.lastAdminHint':'Last admin — cannot remove or demote',
2467
2525
  'admin.ownerHint':'Hub owner — cannot be demoted or removed',
2526
+ 'admin.selfHint':'This is you',
2468
2527
  'admin.editNamePrompt':'Enter new username:',
2469
2528
  'confirm.promoteAdmin':'Promote this user to admin? They will be able to manage all team members and resources.',
2470
2529
  'confirm.demoteMember':'Demote this admin to member?',
@@ -2525,6 +2584,9 @@ const I18N={
2525
2584
  'admin.groupsFailed':'Failed to load groups: ',
2526
2585
  'toast.userApproved':'User approved',
2527
2586
  'sharing.approved.toast':'Your join request has been approved!',
2587
+ 'sharing.rejected.toast':'Your join request was rejected by the admin.',
2588
+ 'sharing.hubOffline.toast':'Team server is offline. Will reconnect automatically when it comes back.',
2589
+ 'sharing.hubReconnected.toast':'Team server is back online! Connection restored.',
2528
2590
  'toast.userRejected':'User rejected',
2529
2591
  'toast.approveFail':'Approve failed',
2530
2592
  'toast.rejectFail':'Reject failed',
@@ -2617,9 +2679,9 @@ const I18N={
2617
2679
  'share.status.agents':'Local',
2618
2680
  'share.status.hub':'Team',
2619
2681
  'share.scope.title':'Sharing Scope',
2620
- 'share.scope.private':'This Agent Only',
2621
- 'share.scope.local':'All Local Agents',
2622
- 'share.scope.team':'Team',
2682
+ 'share.scope.private':'Private',
2683
+ 'share.scope.local':'Local Shared',
2684
+ 'share.scope.team':'Team Shared',
2623
2685
  'share.scope.current':'Current',
2624
2686
  'share.scope.teamDisabled':'Not connected to team server',
2625
2687
  'share.scope.teamIncludes':'Includes visibility to all local agents',
@@ -2684,7 +2746,10 @@ const I18N={
2684
2746
  'update.dismiss':'Dismiss',
2685
2747
  '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?',
2686
2748
  '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?',
2687
- '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',
2749
+ 'sharing.disable.restartAlert':'Sharing has been disabled. The service will restart automatically to apply the change.',
2750
+ '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 the service restarts\\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?',
2751
+ '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 the service restarts\\n\\u2022 Your local data is not affected\\n\\nAre you sure?',
2752
+ 'sharing.switch.hubAddress':'You are about to leave the current team and join a different one.\\n\\nWhat will happen:\\n\\u2022 You will disconnect from the current team server\\n\\u2022 The current team admin will be notified that you left\\n\\u2022 You will join the new team server as a new member\\n\\u2022 Your local data is not affected\\n\\nAre you sure?',
2688
2753
  'admin.notEnabled.title':'Team sharing is not enabled',
2689
2754
  '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.',
2690
2755
  'admin.notEnabled.setupHub':'Set Up as Team Server',
@@ -2702,12 +2767,12 @@ const I18N={
2702
2767
  'guide.join.s1':'Ask your team admin for the Server Address and Team Token',
2703
2768
  'guide.join.s2':'Go to Settings \u2192 Team Sharing, enable sharing, select "Client" mode',
2704
2769
  'guide.join.s3':'Fill in Server Address and Team Token, click "Test Connection"',
2705
- 'guide.join.s4':'Save settings and restart the OpenClaw gateway (page refreshes automatically)',
2770
+ 'guide.join.s4':'Click "Save & Apply" the service restarts automatically (page refreshes)',
2706
2771
  'guide.join.btn':'\u2192 Configure Client Mode',
2707
2772
  'guide.hub.title':'Start Your Own Team Server',
2708
2773
  'guide.hub.desc':'Be the team server. Run it on this device so others can connect and share memories with you.',
2709
2774
  'guide.hub.s1':'Go to Settings \u2192 Team Sharing, enable sharing, select "Server" mode',
2710
- 'guide.hub.s2':'Set a team name, save settings, and restart the gateway (page refreshes automatically)',
2775
+ 'guide.hub.s2':'Set a team name, click "Save & Apply" the service restarts automatically',
2711
2776
  'guide.hub.s3':'Share the Server Address and Team Token with your team members',
2712
2777
  'guide.hub.s4':'Approve join requests in the Admin Panel',
2713
2778
  'guide.hub.btn':'\u2192 Configure Server Mode'
@@ -2759,8 +2824,8 @@ const I18N={
2759
2824
  'skills.load.error':'加载技能失败',
2760
2825
  'skills.hub.title':'\u{1F310} 团队共享技能',
2761
2826
  'scope.local':'本地',
2762
- 'scope.thisAgent':'当前智能体',
2763
- 'scope.thisDevice':'本机全部',
2827
+ 'scope.thisAgent':'仅本智能体',
2828
+ 'scope.thisDevice':'本机所有智能体',
2764
2829
  'scope.group':'团队',
2765
2830
  'scope.all':'全部',
2766
2831
  'skills.visibility.public':'本机共享',
@@ -2798,6 +2863,13 @@ const I18N={
2798
2863
  'notif.userJoin':'有新用户申请加入团队',
2799
2864
  'notif.userOnline':'用户上线了',
2800
2865
  'notif.userOffline':'用户下线了',
2866
+ 'notif.userLeft':'用户已退出团队',
2867
+ 'notif.membershipApproved':'你的团队加入申请已通过',
2868
+ 'notif.membershipRejected':'你的团队加入申请已被拒绝',
2869
+ 'notif.membershipRemoved':'你已被管理员移出团队',
2870
+ 'notif.hubShutdown':'团队服务已关闭',
2871
+ 'notif.rolePromoted':'你已被提升为管理员',
2872
+ 'notif.roleDemoted':'你已被设为普通成员',
2801
2873
  'notif.clearAll':'清除全部',
2802
2874
  'notif.timeAgo.just':'刚刚',
2803
2875
  'notif.timeAgo.min':'{n}分钟前',
@@ -2806,7 +2878,7 @@ const I18N={
2806
2878
  'stat.memories':'记忆',
2807
2879
  'stat.sessions':'会话',
2808
2880
  'stat.embeddings':'嵌入',
2809
- 'stat.days':'天数',
2881
+ 'stat.agents':'智能体',
2810
2882
  'stat.active':'活跃',
2811
2883
  'stat.deduped':'已去重',
2812
2884
  'sidebar.sessions':'会话列表',
@@ -2905,6 +2977,9 @@ const I18N={
2905
2977
  'logs.recall.noHits':'未匹配到记忆',
2906
2978
  'logs.recall.noneRelevant':'LLM 过滤:无相关记忆',
2907
2979
  'logs.recall.more':'还有 {n} 条...',
2980
+ 'recall.origin.localShared':'本机共享',
2981
+ 'recall.origin.hubMemory':'团队缓存',
2982
+ 'recall.origin.hubRemote':'团队',
2908
2983
  'tab.import':'\u{1F4E5} 导入',
2909
2984
  'tab.settings':'\u2699 设置',
2910
2985
  'settings.modelconfig':'模型配置',
@@ -2945,12 +3020,12 @@ const I18N={
2945
3020
  'settings.test.ok':'连接成功',
2946
3021
  'settings.test.fail':'连接失败',
2947
3022
  'settings.session.expired':'登录已过期,请刷新页面重新登录',
2948
- 'settings.save':'保存设置',
3023
+ 'settings.save':'保存并应用',
2949
3024
  'settings.reset':'重置',
2950
3025
  'settings.saved':'已保存',
2951
- 'settings.restart.hint':'部分设置修改后需要重启 OpenClaw 网关才能生效。',
2952
- 'settings.restart.autoRefresh':'网关重启后页面将自动刷新...',
2953
- 'settings.restart.waiting':'配置已保存,正在等待网关重启...',
3026
+ 'settings.restart.hint':'修改将在服务自动重启后生效。',
3027
+ 'settings.restart.autoRefresh':'服务重启中,页面将自动刷新...',
3028
+ 'settings.restart.waiting':'配置已保存,服务正在重启...',
2954
3029
  'settings.save.fail':'保存设置失败',
2955
3030
  'settings.save.emb.required':'嵌入模型为必填项,请先配置嵌入模型再保存。',
2956
3031
  'settings.save.emb.fail':'嵌入模型测试失败,无法保存',
@@ -3077,16 +3152,16 @@ const I18N={
3077
3152
  'settings.hub.tokenCopied':'团队令牌已复制!',
3078
3153
  'settings.hub.hubSteps.title':'快速配置(3 步)',
3079
3154
  'settings.hub.hubSteps.s1':'填写下方团队名称(或保持默认)',
3080
- 'settings.hub.hubSteps.s2':'点击"保存设置",然后重启 OpenClaw 网关',
3155
+ 'settings.hub.hubSteps.s2':'点击「保存并应用」,服务将自动重启',
3081
3156
  'settings.hub.hubSteps.s3':'将下方的服务器地址和团队令牌分享给团队成员',
3082
3157
  'settings.hub.clientSteps.title':'快速配置(3 步)',
3083
3158
  'settings.hub.clientSteps.s1':'向团队管理员获取服务器地址和团队令牌',
3084
3159
  'settings.hub.clientSteps.s2':'填入下方,点击"测试连接"验证连通性',
3085
- 'settings.hub.clientSteps.s3':'点击「保存设置」,然后重启 OpenClaw 网关(页面会自动刷新)',
3160
+ 'settings.hub.clientSteps.s3':'点击「保存并应用」,服务将自动重启(页面会自动刷新)',
3086
3161
  'settings.hub.shareInfo.title':'请将以下信息分享给团队成员:',
3087
3162
  'settings.hub.shareInfo.yourIP':'你的IP',
3088
3163
  'settings.hub.shareInfo.clickCopy':'点击复制',
3089
- 'settings.hub.restartAlert':'团队共享配置已保存!请重启 OpenClaw 网关使配置生效。\\n\\n执行命令:openclaw gateway stop && openclaw gateway start',
3164
+ 'settings.hub.restartAlert':'团队共享配置已保存!服务将自动重启以应用更改。',
3090
3165
  'settings.hub.hubAddress':'服务器地址',
3091
3166
  'settings.hub.hubAddress.hint':'团队服务器地址,如 192.168.1.100:18800',
3092
3167
  'settings.hub.teamTokenClient':'团队令牌',
@@ -3097,6 +3172,7 @@ const I18N={
3097
3172
  'settings.hub.userToken.hint':'通常在加入团队后自动获取,无需手动填写。',
3098
3173
  'settings.hub.testConnection':'测试连接',
3099
3174
  'settings.hub.test.noAddr':'请先输入服务器地址',
3175
+ 'settings.hub.teamToken.required':'请输入团队令牌',
3100
3176
  'settings.hub.test.testing':'测试中...',
3101
3177
  'settings.hub.test.ok':'连接成功',
3102
3178
  'settings.hub.test.fail':'连接失败',
@@ -3106,6 +3182,10 @@ const I18N={
3106
3182
  'sidebar.hub':'\u{1F310} 团队共享',
3107
3183
  'sharing.sidebar.connected':'已连接',
3108
3184
  'sharing.sidebar.disconnected':'已断开',
3185
+ 'sharing.sidebar.hubRunning':'服务运行中',
3186
+ 'sharing.sidebar.teamName':'团队',
3187
+ 'sharing.sidebar.members':'成员',
3188
+ 'sharing.sidebar.online':'在线',
3109
3189
  'sharing.sidebar.pending':'等待审核',
3110
3190
  'sharing.sidebar.rejected':'已拒绝',
3111
3191
  'sharing.sidebar.starting':'启动中...',
@@ -3115,11 +3195,21 @@ const I18N={
3115
3195
  'sharing.sidebar.targetHub':'团队服务器:',
3116
3196
  'sharing.pendingApproval.hint':'加入申请已提交,请等待团队管理员审核通过。',
3117
3197
  'sharing.rejected.hint':'您的加入申请已被团队管理员拒绝,请联系管理员或重新申请。',
3198
+ 'sharing.removed.hint':'您已被管理员从团队中移除,可以重新申请加入。',
3199
+ 'sharing.joinTeam':'加入团队',
3200
+ 'sharing.joinSent.pending':'加入申请已发送,等待管理员审批。',
3201
+ 'sharing.joinSent.active':'成功加入团队!',
3118
3202
  'sharing.retryJoin':'重新申请',
3119
3203
  'sharing.retryJoin.hint':'清除本地连接数据并重新提交加入申请',
3120
3204
  'sharing.retryJoin.confirm':'这将清除当前连接数据并重新提交加入申请,是否继续?',
3205
+ 'sharing.leaveTeam':'退出团队',
3206
+ 'sharing.leaveTeam.confirm':'你即将退出团队「{team}」。\\n\\n退出后将会:\\n\\u2022 断开与团队服务器的连接\\n\\u2022 团队管理员会收到你退出的通知\\n\\u2022 你将无法再接收团队共享的记忆、任务和技能\\n\\u2022 你的本地数据不受影响,会完整保留\\n\\u2022 之后可以重新申请加入(需管理员审批)\\n\\n确定要退出吗?',
3207
+ 'sharing.leaveTeam.success':'你已退出团队,团队共享已关闭。',
3208
+ 'sharing.leaveTeam.fail':'退出团队失败',
3209
+ 'sharing.team.default':'该团队',
3121
3210
  'sharing.retryJoin.success':'加入申请已重新提交,请等待管理员审核。',
3122
3211
  'sharing.retryJoin.fail':'重新申请失败',
3212
+ 'sharing.ownerRemoved':'(已移除)',
3123
3213
  'sharing.cannotJoinSelf':'不能加入自己的服务端,请输入远程服务器地址。',
3124
3214
  'scope.hub':'团队',
3125
3215
  'memory.detail.title':'记忆详情',
@@ -3170,6 +3260,7 @@ const I18N={
3170
3260
  'admin.editName':'编辑名称',
3171
3261
  'admin.lastAdminHint':'唯一管理员 — 无法删除或降级',
3172
3262
  'admin.ownerHint':'Hub 创建者 — 不可降级或移除',
3263
+ 'admin.selfHint':'这是你自己',
3173
3264
  'admin.editNamePrompt':'请输入新用户名:',
3174
3265
  'confirm.promoteAdmin':'确定要将此用户提升为管理员吗?管理员可以管理所有团队成员和资源。',
3175
3266
  'confirm.demoteMember':'确定要将此管理员降为普通成员吗?',
@@ -3230,6 +3321,9 @@ const I18N={
3230
3321
  'admin.groupsFailed':'加载分组失败:',
3231
3322
  'toast.userApproved':'用户已批准',
3232
3323
  'sharing.approved.toast':'您的加入申请已通过审核!',
3324
+ 'sharing.rejected.toast':'您的加入申请已被管理员拒绝。',
3325
+ 'sharing.hubOffline.toast':'团队服务已离线,恢复后将自动重新连接。',
3326
+ 'sharing.hubReconnected.toast':'团队服务已恢复上线,连接已自动恢复!',
3233
3327
  'toast.userRejected':'用户已拒绝',
3234
3328
  'toast.approveFail':'批准失败',
3235
3329
  'toast.rejectFail':'拒绝失败',
@@ -3322,9 +3416,9 @@ const I18N={
3322
3416
  'share.status.agents':'本机',
3323
3417
  'share.status.hub':'团队',
3324
3418
  'share.scope.title':'共享范围',
3325
- 'share.scope.private':'仅本智能体',
3326
- 'share.scope.local':'本机所有智能体',
3327
- 'share.scope.team':'团队',
3419
+ 'share.scope.private':'私有',
3420
+ 'share.scope.local':'本机共享',
3421
+ 'share.scope.team':'团队共享',
3328
3422
  'share.scope.current':'当前',
3329
3423
  'share.scope.teamDisabled':'未连接团队服务器',
3330
3424
  'share.scope.teamIncludes':'包含本机所有智能体的可见性',
@@ -3389,7 +3483,10 @@ const I18N={
3389
3483
  'update.dismiss':'关闭',
3390
3484
  'sharing.disable.confirm.hub':'你即将关闭团队服务。\\n\\n关闭后将会:\\n\\u2022 所有已连接的团队成员将断开连接\\n\\u2022 他们将无法继续同步记忆、任务和技能\\n\\u2022 已共享的数据会保留,重新开启后仍可使用\\n\\n确定要关闭吗?',
3391
3485
  'sharing.disable.confirm.client':'你即将断开与团队的连接。\\n\\n断开后将会:\\n\\u2022 你将无法再接收团队共享的记忆、任务和技能\\n\\u2022 你的本地数据不受影响,会完整保留\\n\\u2022 之后可以随时重新开启共享来恢复连接\\n\\n确定要断开吗?',
3392
- 'sharing.disable.restartAlert':'共享已关闭。请重启 OpenClaw 网关使更改生效。\\n\\n执行命令:openclaw gateway stop && openclaw gateway start',
3486
+ 'sharing.disable.restartAlert':'共享已关闭,服务将自动重启以应用更改。',
3487
+ 'sharing.switch.hubToClient':'你即将从服务端模式切换为客户端模式。\\n\\n切换后将会:\\n\\u2022 Hub 服务将在服务重启后关闭\\n\\u2022 所有已连接的团队成员将断开连接\\n\\u2022 Hub 上的共享数据会保留,以后可恢复使用\\n\\u2022 你将作为客户端加入指定的远程团队\\n\\n确定要切换吗?',
3488
+ 'sharing.switch.clientToHub':'你即将从客户端模式切换为服务端模式。\\n\\n切换后将会:\\n\\u2022 你将断开与当前团队的连接\\n\\u2022 服务重启后将启动新的 Hub 服务\\n\\u2022 你的本地数据不受影响\\n\\n确定要切换吗?',
3489
+ 'sharing.switch.hubAddress':'你即将离开当前团队并加入新的团队。\\n\\n操作后将会:\\n\\u2022 你将断开与当前团队服务器的连接\\n\\u2022 当前团队管理员会收到你离开的通知\\n\\u2022 你将作为新成员加入新的团队服务器\\n\\u2022 你的本地数据不受影响\\n\\n确定要切换吗?',
3393
3490
  'admin.notEnabled.title':'团队共享尚未开启',
3394
3491
  'admin.notEnabled.desc':'管理面板用于管理团队成员、共享的记忆、任务和技能。使用此功能前,需要先开启团队共享。',
3395
3492
  'admin.notEnabled.setupHub':'配置为团队服务端',
@@ -3407,12 +3504,12 @@ const I18N={
3407
3504
  'guide.join.s1':'向团队管理员索取服务器地址和团队令牌',
3408
3505
  'guide.join.s2':'前往「设置 → 团队共享」,开启共享,选择「客户端」模式',
3409
3506
  'guide.join.s3':'填写服务器地址和团队令牌,点击「测试连接」',
3410
- 'guide.join.s4':'保存设置并重启 OpenClaw 网关(页面会自动刷新)',
3507
+ 'guide.join.s4':'点击「保存并应用」,服务将自动重启(页面会自动刷新)',
3411
3508
  'guide.join.btn':'\u2192 配置客户端模式',
3412
3509
  'guide.hub.title':'自建团队服务',
3413
3510
  'guide.hub.desc':'将本机作为团队服务端,让其他成员连接过来共享记忆。',
3414
3511
  'guide.hub.s1':'前往「设置 → 团队共享」,开启共享,选择「服务端」模式',
3415
- 'guide.hub.s2':'设置团队名称,保存设置后重启网关(页面会自动刷新)',
3512
+ 'guide.hub.s2':'设置团队名称,点击「保存并应用」,服务将自动重启',
3416
3513
  'guide.hub.s3':'将服务器地址和团队令牌分享给团队成员',
3417
3514
  'guide.hub.s4':'在管理面板中审批加入请求',
3418
3515
  'guide.hub.btn':'\u2192 配置服务端模式'
@@ -3518,12 +3615,28 @@ async function doReset(){
3518
3615
  }
3519
3616
 
3520
3617
  var _sharingRole='client';
3618
+ var _loadedClientHubAddress='';
3521
3619
  function _genToken(len){
3522
3620
  var a=new Uint8Array(len||18);crypto.getRandomValues(a);
3523
3621
  return btoa(String.fromCharCode.apply(null,a)).replace(/\\+/g,'-').replace(/\\//g,'_').replace(/=+$/,'');
3524
3622
  }
3525
- function onSharingToggle(){
3526
- var on=document.getElementById('cfgSharingEnabled').checked;
3623
+ async function onSharingToggle(){
3624
+ var chk=document.getElementById('cfgSharingEnabled');
3625
+ var on=chk.checked;
3626
+ if(!on && sharingStatusCache && sharingStatusCache.enabled){
3627
+ var prevRole=sharingStatusCache.role;
3628
+ var confirmMsg=prevRole==='hub'?t('sharing.disable.confirm.hub'):t('sharing.disable.confirm.client');
3629
+ if(!(await confirmModal(confirmMsg,{danger:true}))){
3630
+ chk.checked=true;
3631
+ return;
3632
+ }
3633
+ var cfg={sharing:{enabled:false,role:prevRole}};
3634
+ chk.disabled=true;
3635
+ var result=await doSaveConfig(cfg, null, 'hubSaved');
3636
+ chk.disabled=false;
3637
+ if(!result){chk.checked=true;return;}
3638
+ return;
3639
+ }
3527
3640
  document.getElementById('sharingConfigPanel').style.display=on?'block':'none';
3528
3641
  var pw=document.getElementById('sharingPanelsWrap');
3529
3642
  if(pw) pw.style.display=on?'':'none';
@@ -3541,20 +3654,25 @@ function selectSharingRole(role){
3541
3654
  var tp=document.getElementById('sharingTeamPanel');
3542
3655
  var ap=document.getElementById('sharingAdminPanel');
3543
3656
  if(role==='client'){
3544
- if(sp) sp.style.display='none';
3657
+ if(sp) { sp.style.display='none'; sp.innerHTML=''; }
3545
3658
  if(tp) tp.style.display='none';
3546
3659
  if(ap) ap.style.display='none';
3547
3660
  }else{
3548
- if(sp) sp.style.display='';
3661
+ if(sp) { sp.style.display='none'; sp.innerHTML=''; }
3549
3662
  if(tp) tp.style.display='';
3550
3663
  if(ap) ap.style.display='';
3551
3664
  }
3665
+ _lastSettingsFingerprint='';
3666
+ setTimeout(function(){ loadSharingStatus(true); },200);
3552
3667
  if(role==='hub'){
3553
3668
  var tk=document.getElementById('cfgHubTeamToken');
3554
3669
  if(!tk.value.trim()) tk.value=_genToken(18);
3555
3670
  var tn=document.getElementById('cfgHubTeamName');
3556
3671
  if(!tn.value.trim()) tn.value='My Team';
3557
3672
  }
3673
+ var card=document.getElementById('settingsSharingConfig');
3674
+ var saveBtn=card&&card.querySelector('.settings-actions .btn-primary');
3675
+ if(saveBtn&&typeof _hubSaveBtnLabel==='function') saveBtn.textContent=_hubSaveBtnLabel();
3558
3676
  }
3559
3677
  var _cachedLocalIP='';
3560
3678
  function updateHubShareInfo(){
@@ -3589,13 +3707,6 @@ async function testHubConnection(){
3589
3707
  if(!addr){result.innerHTML='<span style="color:var(--rose)">\u274C '+t('settings.hub.test.noAddr')+'</span>';return;}
3590
3708
  btn.disabled=true;result.innerHTML=t('settings.hub.test.testing');
3591
3709
  try{
3592
- var ipsData=await fetch('/api/local-ips').then(function(r){return r.json();});
3593
- var localAddrs=['127.0.0.1','localhost','0.0.0.0'].concat(ipsData.ips||[]);
3594
- var parsed=new URL(addr.indexOf('://')>-1?addr:'http://'+addr);
3595
- if(localAddrs.indexOf(parsed.hostname)>=0){
3596
- result.innerHTML='<span style="color:var(--rose)">\u274C '+t('sharing.cannotJoinSelf')+'</span>';
3597
- btn.disabled=false;return;
3598
- }
3599
3710
  }catch(e){}
3600
3711
  try{
3601
3712
  var url=addr.match(/^https?:\\/\\//)?addr:'http://'+addr;
@@ -3645,23 +3756,38 @@ function switchView(view){
3645
3756
  else if(view==='skills') loadSkills();
3646
3757
  else if(view==='analytics') loadMetrics();
3647
3758
  else if(view==='logs') loadLogs();
3648
- else if(view==='settings'){loadConfig();loadModelHealth();}
3759
+ else if(view==='settings'){loadConfig().then(function(){
3760
+ var sharingOn=document.getElementById('cfgSharingEnabled');
3761
+ var sharingNotEnabled=!sharingOn||!sharingOn.checked;
3762
+ if(sharingNotEnabled){
3763
+ switchSettingsTab('hub',document.querySelector('.settings-tab-btn[data-tab="hub"]'));
3764
+ }
3765
+ });loadModelHealth();}
3649
3766
  else if(view==='import'){if(!window._migrateRunning) migrateScan(false);}
3650
- else if(view==='admin'){loadAdminData();}
3767
+ else if(view==='admin'){_lastAdminFingerprint='';loadAdminData();}
3651
3768
  }
3652
3769
 
3653
3770
  function onMemoryScopeChange(){
3654
3771
  memorySearchScope=document.getElementById('memorySearchScope')?.value||'local';
3772
+ try{localStorage.setItem('memos_memorySearchScope',memorySearchScope);}catch(e){}
3773
+ currentPage=1;
3774
+ activeSession=null;activeRole='';
3775
+ _lastMemoriesFingerprint='';
3655
3776
  var isHub=memorySearchScope==='hub';
3777
+ var isLocal=memorySearchScope==='local';
3656
3778
  var ownerSel=document.getElementById('filterOwner');
3657
3779
  var filterBar=document.getElementById('filterBar');
3658
3780
  var dateFilter=document.querySelector('.date-filter');
3659
- if(ownerSel) ownerSel.style.display=isHub?'none':'';
3781
+ if(ownerSel){ownerSel.style.display=(isHub||isLocal)?'none':'';if(isHub||isLocal)ownerSel.value='';}
3660
3782
  if(filterBar) filterBar.style.display=isHub?'none':'';
3661
3783
  if(dateFilter) dateFilter.style.display=isHub?'none':'';
3662
3784
  if(document.getElementById('searchInput').value.trim()) doSearch(document.getElementById('searchInput').value);
3663
3785
  else if(isHub) { document.getElementById('sharingSearchMeta').textContent=''; loadHubMemories(); }
3664
- else { document.getElementById('sharingSearchMeta').textContent=''; loadStats(); loadMemories(); }
3786
+ else {
3787
+ document.getElementById('sharingSearchMeta').textContent='';
3788
+ var ownerArg=isLocal?_currentAgentOwner:undefined;
3789
+ loadStats(ownerArg); loadMemories();
3790
+ }
3665
3791
  }
3666
3792
 
3667
3793
  function onSkillScopeChange(){
@@ -3676,6 +3802,14 @@ function onTaskScopeChange(){
3676
3802
  }
3677
3803
 
3678
3804
  var _clientPendingPollTimer=null;
3805
+ var _lastSharingConnStatus='';
3806
+ function _updateScopeSelectorsVisibility(hubAvailable){
3807
+ var ids=['memorySearchScope','taskSearchScope','skillSearchScope'];
3808
+ for(var i=0;i<ids.length;i++){
3809
+ var el=document.getElementById(ids[i]);
3810
+ if(el) el.style.display=hubAvailable?'':'none';
3811
+ }
3812
+ }
3679
3813
  async function loadSharingStatus(forcePending){
3680
3814
  try{
3681
3815
  const r=await fetch('/api/sharing/status');
@@ -3685,27 +3819,64 @@ async function loadSharingStatus(forcePending){
3685
3819
  renderSharingSettings(d);
3686
3820
  updateTeamGuide(d);
3687
3821
  if(forcePending && d && d.admin && d.admin.canManageUsers) loadSharingPendingUsers();
3688
- var conn=d&&d.connection||{};
3689
- if(conn.pendingApproval&&!_clientPendingPollTimer){
3690
- _clientPendingPollTimer=setInterval(function(){loadSharingStatus(false);},10000);
3691
- }else if(!conn.pendingApproval&&_clientPendingPollTimer){
3822
+ if(!d||!d.enabled){
3823
+ if(_clientPendingPollTimer){clearInterval(_clientPendingPollTimer);_clientPendingPollTimer=null;}
3824
+ _lastSharingConnStatus='';
3825
+ _updateScopeSelectorsVisibility(false);
3826
+ return;
3827
+ }
3828
+ var conn=d.connection||{};
3829
+ var curStatus=conn.rejected?'rejected':conn.pendingApproval?'pending':conn.connected?'connected':'none';
3830
+ var hubActive=d.role==='hub'||curStatus==='connected';
3831
+ _updateScopeSelectorsVisibility(hubActive);
3832
+ if(_lastSharingConnStatus==='pending'&&curStatus==='rejected'&&d.role==='client'){
3833
+ toast(t('sharing.rejected.toast'),'error');
3834
+ }
3835
+ if(_lastSharingConnStatus==='pending'&&curStatus==='connected'&&d.role==='client'){
3836
+ toast(t('sharing.approved.toast'),'success');
3837
+ loadMemories();loadTasks();loadSkills();
3838
+ if(_notifSSE){_notifSSE.close();_notifSSE=null;_notifSSEConnected=false;}
3839
+ connectNotifSSE();
3840
+ loadNotifications();
3841
+ }
3842
+ if(_lastSharingConnStatus==='connected'&&curStatus==='none'&&d.role==='client'){
3843
+ toast(t('sharing.hubOffline.toast'),'error');
3844
+ }
3845
+ if(_lastSharingConnStatus==='none'&&curStatus==='connected'&&d.role==='client'){
3846
+ toast(t('sharing.hubReconnected.toast'),'success');
3847
+ loadMemories();loadTasks();loadSkills();
3848
+ if(_notifSSE){_notifSSE.close();_notifSSE=null;_notifSSEConnected=false;}
3849
+ connectNotifSSE();
3850
+ loadNotifications();
3851
+ }
3852
+ _lastSharingConnStatus=curStatus;
3853
+ if(curStatus==='pending'&&!_clientPendingPollTimer){
3854
+ _clientPendingPollTimer=setInterval(function(){loadSharingStatus(false);},5000);
3855
+ }
3856
+ if(curStatus!=='pending'&&_clientPendingPollTimer){
3692
3857
  clearInterval(_clientPendingPollTimer);
3693
3858
  _clientPendingPollTimer=null;
3694
- if(conn.connected) toast(t('sharing.approved.toast'),'success');
3695
3859
  }
3696
3860
  }catch(e){
3697
3861
  renderSharingSidebar(null);
3698
3862
  renderSharingSettings(null);
3699
3863
  updateTeamGuide(null);
3864
+ _updateScopeSelectorsVisibility(false);
3700
3865
  }
3701
3866
  }
3702
3867
 
3868
+ var _lastSidebarFingerprint='';
3703
3869
  function renderSharingSidebar(data){
3704
3870
  var section=document.getElementById('sidebarSharingSection');
3705
3871
  var statusEl=document.getElementById('sharingSidebarStatus');
3706
3872
  var hintEl=document.getElementById('sharingSidebarHint');
3707
3873
  var badgeEl=document.getElementById('sharingSidebarConnBadge');
3708
3874
  if(!statusEl||!hintEl) return;
3875
+ var conn=data&&data.connection||{};
3876
+ var hs=data&&data.hubStats||{};
3877
+ 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,tm:hs.totalMembers,om:hs.onlineMembers,pm:hs.pendingMembers});
3878
+ if(fp===_lastSidebarFingerprint) return;
3879
+ _lastSidebarFingerprint=fp;
3709
3880
  if(!data||!data.enabled){
3710
3881
  if(section) section.style.display='none';
3711
3882
  window._isHubAdmin=false;
@@ -3718,8 +3889,16 @@ function renderSharingSidebar(data){
3718
3889
  badgeEl.innerHTML='<span style="display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:9999px;background:'+color+'15;color:'+color+'"><span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:'+color+(glow?';box-shadow:0 0 4px '+color:'')+'"></span>'+esc(text)+'</span>';
3719
3890
  }
3720
3891
  if(data.role==='hub'){
3721
- setBadge('#34d399',t('sharing.sidebar.connected'),true);
3722
- statusEl.innerHTML='';
3892
+ setBadge('#34d399',t('sharing.sidebar.hubRunning'),true);
3893
+ var hs=data.hubStats||{};
3894
+ var html='<div class="info-grid">';
3895
+ if(conn.teamName) html+='<span class="label">'+t('sharing.sidebar.teamName')+'</span><span class="value" style="font-weight:600">'+esc(conn.teamName)+'</span>';
3896
+ html+='<span class="label">'+t('sharing.sidebar.members')+'</span><span class="value">'+(hs.totalMembers||0)+' <span style="opacity:.5;font-size:10px">/ '+t('sharing.sidebar.online')+' '+(hs.onlineMembers||0)+'</span></span>';
3897
+ if(hs.pendingMembers>0){
3898
+ html+='<span class="label">'+t('sharing.sidebar.pending')+'</span><span class="value" style="color:var(--yellow,#fbbf24);font-weight:600">'+hs.pendingMembers+'</span>';
3899
+ }
3900
+ html+='</div>';
3901
+ statusEl.innerHTML=html;
3723
3902
  hintEl.textContent='';
3724
3903
  }else if(conn.pendingApproval&&conn.user){
3725
3904
  setBadge('#fbbf24',t('sharing.sidebar.pending'),false);
@@ -3735,8 +3914,18 @@ function renderSharingSidebar(data){
3735
3914
  html+='<span class="label">'+t('sharing.sidebar.identity')+'</span><span class="value">'+esc(conn.user.username||'-')+'</span>';
3736
3915
  if(conn.teamName) html+='<span class="label">'+t('sharing.team')+'</span><span class="value">'+esc(conn.teamName)+'</span>';
3737
3916
  html+='</div>';
3917
+ html+='<div style="margin-top:8px"><button class="btn btn-sm btn-primary" onclick="retryHubJoin()" style="font-size:11px">'+t('sharing.retryJoin')+'</button></div>';
3738
3918
  statusEl.innerHTML=html;
3739
3919
  hintEl.textContent=t('sharing.rejected.hint');
3920
+ }else if(conn.removed&&conn.user){
3921
+ setBadge('#ef4444',t('sharing.sidebar.disconnected'),false);
3922
+ var html='<div class="info-grid">';
3923
+ html+='<span class="label">'+t('sharing.sidebar.identity')+'</span><span class="value">'+esc(conn.user.username||'-')+'</span>';
3924
+ if(conn.teamName) html+='<span class="label">'+t('sharing.team')+'</span><span class="value">'+esc(conn.teamName)+'</span>';
3925
+ html+='</div>';
3926
+ html+='<div style="margin-top:8px"><button class="btn btn-sm btn-primary" onclick="retryHubJoin()" style="font-size:11px">'+t('sharing.retryJoin')+'</button></div>';
3927
+ statusEl.innerHTML=html;
3928
+ hintEl.textContent=t('sharing.removed.hint');
3740
3929
  }else if(conn.connected&&conn.user){
3741
3930
  var isAdmin=conn.user.role==='admin';
3742
3931
  setBadge('#34d399',t('sharing.sidebar.connected'),true);
@@ -3757,25 +3946,43 @@ function renderSharingSidebar(data){
3757
3946
  }
3758
3947
  }
3759
3948
 
3949
+ var _lastSettingsFingerprint='';
3760
3950
  function renderSharingSettings(data){
3761
3951
  var statusEl=document.getElementById('sharingStatusPanel');
3762
3952
  var teamEl=document.getElementById('sharingTeamPanel');
3763
3953
  var adminEl=document.getElementById('sharingAdminPanel');
3764
3954
  var panelsWrap=document.getElementById('sharingPanelsWrap');
3765
3955
  if(!statusEl||!teamEl||!adminEl) return;
3956
+ var conn2=data&&data.connection||{};
3957
+ 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});
3958
+ if(fp2===_lastSettingsFingerprint) return;
3959
+ _lastSettingsFingerprint=fp2;
3766
3960
  if(!data||!data.enabled){
3767
3961
  statusEl.innerHTML='';teamEl.innerHTML='';adminEl.innerHTML='';
3768
3962
  if(panelsWrap) panelsWrap.style.display='none';
3963
+ var adminNavTab0=document.querySelector('.tab[data-view="admin"]');
3964
+ if(adminNavTab0) adminNavTab0.style.display='none';
3965
+ if(_activeView==='admin') switchView('memories');
3769
3966
  return;
3770
3967
  }
3771
3968
  if(panelsWrap) panelsWrap.style.display='';
3772
3969
  var conn=data.connection||{};
3773
3970
  var user=conn.user||{};
3774
3971
  var actualRole=data.role||_sharingRole||'client';
3775
- if(data.role) _sharingRole=data.role;
3972
+ var prevIsAdmin=!!window._isHubAdmin;
3776
3973
  var isAdmin=(data.admin&&data.admin.canManageUsers)||(conn.connected&&user.role==='admin')||(actualRole==='hub');
3777
3974
  window._isHubAdmin=isAdmin;
3778
3975
  if(isAdmin) startAdminPoll();
3976
+ var adminNavTab=document.querySelector('.tab[data-view="admin"]');
3977
+ if(adminNavTab){
3978
+ var showTab=(actualRole==='hub')||(conn.connected);
3979
+ adminNavTab.style.display=showTab?'':'none';
3980
+ if(!showTab&&_activeView==='admin') switchView('memories');
3981
+ }
3982
+ if(prevIsAdmin&&!isAdmin&&_activeView==='admin'){
3983
+ _lastAdminFingerprint='';
3984
+ loadAdminData();
3985
+ }
3779
3986
  var hubAdminBtn=document.getElementById('hubAdminEntryBtn');
3780
3987
 
3781
3988
  if(actualRole==='hub'){
@@ -3797,6 +4004,8 @@ function renderSharingSettings(data){
3797
4004
  connBadge='<span class="hic-badge pending"><span class="hic-dot amber"></span>'+t('sharing.sidebar.pending')+'</span>';
3798
4005
  }else if(conn.rejected){
3799
4006
  connBadge='<span class="hic-badge disconnected"><span class="hic-dot red"></span>'+t('sharing.sidebar.rejected')+'</span>';
4007
+ }else if(conn.removed){
4008
+ connBadge='<span class="hic-badge disconnected"><span class="hic-dot red"></span>'+t('sharing.sidebar.disconnected')+'</span>';
3800
4009
  }else if(conn.connected){
3801
4010
  connBadge='<span class="hic-badge connected"><span class="hic-dot green"></span>'+t('sharing.sidebar.connected')+'</span>';
3802
4011
  }else{
@@ -3812,13 +4021,21 @@ function renderSharingSettings(data){
3812
4021
  sh+='</div><div class="hic-empty" style="color:#ef4444">'+t('sharing.rejected.hint')+'</div>'+
3813
4022
  '<div style="margin-top:10px;padding:0 16px 14px"><button class="btn btn-sm btn-primary" onclick="retryHubJoin()">'+t('sharing.retryJoin')+'</button>'+
3814
4023
  '<span style="font-size:11px;color:var(--text-muted);margin-left:8px">'+t('sharing.retryJoin.hint')+'</span></div></div>';
4024
+ }else if(conn.removed){
4025
+ if(user.username) sh+='<span class="hic-label">'+t('sharing.user')+'</span><span class="hic-value">'+esc(user.username)+'</span>';
4026
+ sh+='</div><div class="hic-empty" style="color:#ef4444">'+t('sharing.removed.hint')+'</div>'+
4027
+ '<div style="margin-top:10px;padding:0 16px 14px"><button class="btn btn-sm btn-primary" onclick="retryHubJoin()">'+t('sharing.retryJoin')+'</button>'+
4028
+ '<span style="font-size:11px;color:var(--text-muted);margin-left:8px">'+t('sharing.retryJoin.hint')+'</span></div></div>';
3815
4029
  }else if(conn.connected&&user.username){
3816
4030
  sh+='<span class="hic-label">'+t('sharing.user')+'</span><span class="hic-value" style="display:flex;align-items:center;gap:6px">'+
3817
4031
  '<input type="text" id="hubUsernameInput" value="'+esc(user.username)+'" style="border:1px solid var(--border);border-radius:6px;padding:3px 8px;font-size:12px;background:var(--bg);color:var(--text);width:120px;font-family:inherit" />'+
3818
4032
  '<button class="btn btn-sm" onclick="updateHubUsername()" style="padding:2px 10px;font-size:11px">'+t('sharing.saveUsername')+'</button>'+
3819
4033
  '</span>';
3820
4034
  sh+='<span class="hic-label">'+t('sharing.team')+'</span><span class="hic-value">'+esc(conn.teamName||'-')+'</span>';
3821
- sh+='</div></div>';
4035
+ sh+='</div>'+
4036
+ '<div style="border-top:1px solid var(--border);margin-top:10px;padding:10px 16px 6px;display:flex;align-items:center;justify-content:flex-end">'+
4037
+ '<button class="btn btn-sm" onclick="leaveTeam()" style="color:#ef4444;border-color:rgba(239,68,68,.3);font-size:11px;padding:4px 12px">'+t('sharing.leaveTeam')+'</button>'+
4038
+ '</div></div>';
3822
4039
  }else{
3823
4040
  sh+='</div><div class="hic-empty" style="color:var(--text-muted)">'+t('sharing.disconnected.hint')+'</div>'+
3824
4041
  '<div style="margin-top:10px;padding:0 16px 14px"><button class="btn btn-sm btn-primary" id="btnRetryConn" onclick="retryConnection()">'+t('sharing.retryConnection')+'</button>'+
@@ -3837,6 +4054,7 @@ async function retryConnection(){
3837
4054
  var result=document.getElementById('retryConnResult');
3838
4055
  if(btn){btn.disabled=true;btn.textContent=t('sharing.retryConnection.loading');}
3839
4056
  if(result) result.innerHTML='<span style="color:var(--text-muted)">'+t('sharing.retryConnection.loading')+'</span>';
4057
+ toast(t('sharing.retryConnection.loading'),'info');
3840
4058
  try{
3841
4059
  await loadSharingStatus(false);
3842
4060
  var d=sharingStatusCache;
@@ -3844,9 +4062,11 @@ async function retryConnection(){
3844
4062
  toast(t('sharing.retryConnection.success'),'success');
3845
4063
  if(result) result.innerHTML='<span style="color:#22c55e">\\u2705 '+t('sharing.retryConnection.success')+'</span>';
3846
4064
  }else{
4065
+ toast(t('sharing.retryConnection.fail'),'error');
3847
4066
  if(result) result.innerHTML='<span style="color:#ef4444">'+t('sharing.retryConnection.fail')+'</span>';
3848
4067
  }
3849
4068
  }catch(e){
4069
+ toast(t('sharing.retryConnection.fail'),'error');
3850
4070
  if(result) result.innerHTML='<span style="color:#ef4444">'+t('sharing.retryConnection.fail')+'</span>';
3851
4071
  }
3852
4072
  if(btn){btn.disabled=false;btn.textContent=t('sharing.retryConnection');}
@@ -3859,19 +4079,36 @@ async function retryHubJoin(){
3859
4079
  var d=await r.json();
3860
4080
  if(d.ok){
3861
4081
  toast(t('sharing.retryJoin.success'),'success');
3862
- setTimeout(function(){location.reload();},1500);
4082
+ _lastSidebarFingerprint='';_lastSettingsFingerprint='';_lastSharingConnStatus='';
4083
+ setTimeout(function(){loadSharingStatus(true);},800);
3863
4084
  }else{
3864
4085
  toast(d.error||t('sharing.retryJoin.fail'),'error');
3865
4086
  }
3866
4087
  }catch(e){toast(t('sharing.retryJoin.fail')+': '+e.message,'error');}
3867
4088
  }
3868
4089
 
4090
+ async function leaveTeam(){
4091
+ var teamName=(sharingStatusCache&&sharingStatusCache.connection&&sharingStatusCache.connection.teamName)||'';
4092
+ var msg=t('sharing.leaveTeam.confirm').replace('{team}',teamName||t('sharing.team.default'));
4093
+ if(!(await confirmModal(msg,{danger:true}))) return;
4094
+ try{
4095
+ var r=await fetch('/api/sharing/leave',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'});
4096
+ var d=await r.json();
4097
+ if(d.ok){
4098
+ toast(t('sharing.leaveTeam.success'),'success');
4099
+ showRestartOverlay(t('settings.restart.waiting'));
4100
+ }else{
4101
+ toast(d.error||t('sharing.leaveTeam.fail'),'error');
4102
+ }
4103
+ }catch(e){toast(t('sharing.leaveTeam.fail')+': '+e.message,'error');}
4104
+ }
4105
+
3869
4106
  async function updateHubUsername(){
3870
4107
  var input=document.getElementById('hubUsernameInput');
3871
4108
  if(!input) return;
3872
4109
  var newName=input.value.trim();
3873
4110
  if(!newName||newName.length<2||newName.length>32){
3874
- toast(t('sharing.username.invalid'),'error');
4111
+ alertModal(t('sharing.username.invalid'));
3875
4112
  return;
3876
4113
  }
3877
4114
  try{
@@ -3882,17 +4119,17 @@ async function updateHubUsername(){
3882
4119
  });
3883
4120
  var d=await r.json();
3884
4121
  if(d.error==='username_taken'){
3885
- toast(t('sharing.username.taken'),'error');
4122
+ alertModal(t('sharing.username.taken'),{danger:true});
3886
4123
  return;
3887
4124
  }
3888
4125
  if(d.error){
3889
- toast(d.error,'error');
4126
+ alertModal(d.error,{danger:true});
3890
4127
  return;
3891
4128
  }
3892
4129
  toast(t('sharing.username.updated'),'success');
3893
4130
  loadSharingStatus(false);
3894
4131
  }catch(e){
3895
- toast(t('sharing.username.error'),'error');
4132
+ alertModal(t('sharing.username.error'),{danger:true});
3896
4133
  }
3897
4134
  }
3898
4135
 
@@ -3938,7 +4175,7 @@ async function approveSharingUser(userId,username){
3938
4175
  try{
3939
4176
  const r=await fetch('/api/sharing/approve-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:username})});
3940
4177
  const d=await r.json();
3941
- if(d.ok){toast(t('toast.userApproved'),'success');loadSharingPendingUsers();loadSharingStatus(true);} else {toast(d.error||t('toast.approveFail'),'error');}
4178
+ if(d.ok){toast(t('toast.userApproved'),'success');loadSharingPendingUsers();loadSharingStatus(true);_lastAdminFingerprint='';loadAdminData();} else {toast(d.error||t('toast.approveFail'),'error');}
3942
4179
  }catch(e){toast(t('toast.approveFail')+': '+e.message,'error');}
3943
4180
  }
3944
4181
 
@@ -3946,23 +4183,15 @@ async function rejectSharingUser(userId,username){
3946
4183
  try{
3947
4184
  const r=await fetch('/api/sharing/reject-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:username})});
3948
4185
  const d=await r.json();
3949
- if(d.ok){toast(t('toast.userRejected'),'success');loadSharingPendingUsers();} else {toast(d.error||t('toast.rejectFail'),'error');}
4186
+ if(d.ok){toast(t('toast.userRejected'),'success');loadSharingPendingUsers();_lastAdminFingerprint='';loadAdminData();} else {toast(d.error||t('toast.rejectFail'),'error');}
3950
4187
  }catch(e){toast(t('toast.rejectFail')+': '+e.message,'error');}
3951
4188
  }
3952
4189
 
3953
4190
  /* ─── Team Setup Guide ─── */
3954
- var TEAM_GUIDE_DISMISSED_KEY='memos-team-guide-dismissed';
3955
4191
  function updateTeamGuide(sharingData){
3956
4192
  var el=document.getElementById('teamSetupGuide');
3957
4193
  if(!el) return;
3958
- if(localStorage.getItem(TEAM_GUIDE_DISMISSED_KEY)==='1'){el.style.display='none';return;}
3959
- var isConfigured=sharingData&&sharingData.enabled;
3960
- el.style.display=isConfigured?'none':'block';
3961
- }
3962
- function dismissTeamGuide(){
3963
- localStorage.setItem(TEAM_GUIDE_DISMISSED_KEY,'1');
3964
- var el=document.getElementById('teamSetupGuide');
3965
- if(el) el.style.display='none';
4194
+ el.style.display='block';
3966
4195
  }
3967
4196
  function guideGoToHub(role){
3968
4197
  switchSettingsTab('hub',document.querySelector('.settings-tab-btn[data-tab="hub"]'));
@@ -3976,6 +4205,7 @@ function guideGoToHub(role){
3976
4205
 
3977
4206
  /* ─── Hub Admin Panel ─── */
3978
4207
  var adminDataCache={users:[],groups:[],tasks:[],skills:[],memories:[]};
4208
+ var _lastAdminFingerprint='';
3979
4209
  var hubTasksCache=[];
3980
4210
  var hubSkillsCache=[];
3981
4211
  var ADMIN_PAGE_SIZE=20;
@@ -3994,7 +4224,7 @@ function adminPaginateHtml(total,page,refilterFn){
3994
4224
  if(end<pages) html+=(end<pages-1?'<span class="pg-info">...</span>':'')+'<button class="pg-btn" onclick="'+refilterFn+'Page('+(pages-1)+')">'+pages+'</button>';
3995
4225
  html+='<button class="pg-btn'+(page>=pages-1?' disabled':'')+'" onclick="'+refilterFn+'Page('+(page+1)+')">\\u2192</button>';
3996
4226
  html+='<span class="pg-info">'+total+' '+t('pagination.total')+'</span>';
3997
- html+='</div>';
4227
+ html+='</div>';
3998
4228
  return html;
3999
4229
  }
4000
4230
 
@@ -4056,12 +4286,12 @@ async function loadAdminData(){
4056
4286
  var fetches;
4057
4287
  if(isAdmin){
4058
4288
  fetches=await Promise.all([
4059
- fetch('/api/sharing/users').then(function(r){return r.json();}),
4060
- fetch('/api/admin/shared-tasks').then(function(r){return r.json();}),
4061
- fetch('/api/admin/shared-skills').then(function(r){return r.json();}),
4062
- fetch('/api/sharing/pending-users').then(function(r){return r.json();}),
4063
- fetch('/api/admin/shared-memories').then(function(r){return r.json();})
4064
- ]);
4289
+ fetch('/api/sharing/users').then(function(r){return r.json();}),
4290
+ fetch('/api/admin/shared-tasks').then(function(r){return r.json();}),
4291
+ fetch('/api/admin/shared-skills').then(function(r){return r.json();}),
4292
+ fetch('/api/sharing/pending-users').then(function(r){return r.json();}),
4293
+ fetch('/api/admin/shared-memories').then(function(r){return r.json();})
4294
+ ]);
4065
4295
  }else{
4066
4296
  fetches=await Promise.all([
4067
4297
  Promise.resolve({users:[]}),
@@ -4072,11 +4302,23 @@ async function loadAdminData(){
4072
4302
  ]);
4073
4303
  }
4074
4304
  var usersR=fetches[0],tasksR=fetches[1],skillsR=fetches[2],pendingR=fetches[3],memoriesR=fetches[4];
4075
- adminDataCache.users=Array.isArray(usersR.users)?usersR.users:[];
4076
- adminDataCache.tasks=Array.isArray(tasksR.tasks)?tasksR.tasks:[];
4077
- adminDataCache.skills=Array.isArray(skillsR.skills)?skillsR.skills:[];
4078
- adminDataCache.memories=Array.isArray(memoriesR.memories)?memoriesR.memories:[];
4305
+ var _newUsers=Array.isArray(usersR.users)?usersR.users:[];
4306
+ var _newTasks=Array.isArray(tasksR.tasks)?tasksR.tasks:[];
4307
+ var _newSkills=Array.isArray(skillsR.skills)?skillsR.skills:[];
4308
+ var _newMemories=Array.isArray(memoriesR.memories)?memoriesR.memories:[];
4079
4309
  var pending=isAdmin?(Array.isArray(pendingR.users)?pendingR.users:[]):[];
4310
+ var _fp=_newUsers.length+':'+_newTasks.length+':'+_newSkills.length+':'+_newMemories.length+':'+pending.length
4311
+ +':'+_newUsers.map(function(u){return u.id+'|'+(u.isOnline?1:0)+'|'+(u.role||'')+'|'+(u.status||'')+'|'+(u.username||'')+'|'+(u.memoryCount||0)+'|'+(u.taskCount||0)+'|'+(u.skillCount||0)}).join(',')
4312
+ +':'+_newMemories.map(function(m){return m.id}).join(',')
4313
+ +':'+_newTasks.map(function(t){return t.id+'|'+(t.status||'')}).join(',')
4314
+ +':'+_newSkills.map(function(s){return s.id+'|'+(s.status||'')}).join(',')
4315
+ +':'+pending.map(function(p){return p.id}).join(',');
4316
+ if(_fp===_lastAdminFingerprint) return;
4317
+ _lastAdminFingerprint=_fp;
4318
+ adminDataCache.users=_newUsers;
4319
+ adminDataCache.tasks=_newTasks;
4320
+ adminDataCache.skills=_newSkills;
4321
+ adminDataCache.memories=_newMemories;
4080
4322
  adminDataCache._pending=pending;
4081
4323
  var badge=document.getElementById('adminPendingBadge');
4082
4324
  if(badge){if(pending.length>0){badge.textContent=pending.length;badge.style.display='inline';}else{badge.style.display='none';}}
@@ -4119,11 +4361,15 @@ function updateAdminTabsVisibility(){
4119
4361
  if(subEl) subEl.textContent=isAdmin?t('admin.subtitle'):t('admin.subtitle.member');
4120
4362
  }
4121
4363
 
4364
+ var _lastAdminStatsFp='';
4122
4365
  function renderAdminStats(pendingCount){
4123
4366
  var el=document.getElementById('adminStats');
4124
4367
  if(!el) return;
4125
4368
  var isAdmin=!!window._isHubAdmin;
4126
4369
  var onlineCount=adminDataCache.users.filter(function(u){return !!u.isOnline;}).length;
4370
+ var sfp=onlineCount+':'+adminDataCache.users.length+':'+pendingCount+':'+(adminDataCache.memories||[]).length+':'+adminDataCache.tasks.length+':'+adminDataCache.skills.length+':'+isAdmin;
4371
+ if(sfp===_lastAdminStatsFp) return;
4372
+ _lastAdminStatsFp=sfp;
4127
4373
  el.innerHTML=
4128
4374
  (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>'+
4129
4375
  '<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>':'')+
@@ -4145,9 +4391,10 @@ function auRelativeTime(ts){
4145
4391
  return t('notif.timeAgo.day').replace('{n}',Math.floor(diff/86400000));
4146
4392
  }
4147
4393
 
4148
- function renderAdminUserCard(u,adminCount){
4394
+ function renderAdminUserCard(u,adminCount,myUserId){
4149
4395
  var uid=escAttr(u.id);
4150
4396
  var uname=escAttr(u.username||'');
4397
+ var isSelf=!!(myUserId&&u.id===myUserId);
4151
4398
  var online=!!u.isOnline;
4152
4399
  var statusCls=online?'online':'offline';
4153
4400
 
@@ -4181,7 +4428,9 @@ function renderAdminUserCard(u,adminCount){
4181
4428
  var infoHtml='<div class="au-info">'+infoRows.join('')+'</div>';
4182
4429
 
4183
4430
  var actions='';
4184
- if(u.isOwner){
4431
+ if(isSelf){
4432
+ actions+='<span style="font-size:11px;color:var(--text-muted);padding:4px 0">'+t('admin.selfHint')+'</span>';
4433
+ }else if(u.isOwner){
4185
4434
  actions+='<span style="font-size:11px;color:var(--text-muted);padding:4px 0">'+t('admin.ownerHint')+'</span>';
4186
4435
  }else if(u.role!=='admin'){
4187
4436
  actions+='<button class="btn btn-sm btn-ghost" onclick="adminToggleRole(&quot;'+uid+'&quot;,&quot;admin&quot;)" style="color:var(--accent)">'+t('admin.promoteAdmin')+'</button>';
@@ -4192,14 +4441,15 @@ function renderAdminUserCard(u,adminCount){
4192
4441
  }else{
4193
4442
  actions+='<span style="font-size:11px;color:var(--text-muted);padding:4px 0">'+t('admin.lastAdminHint')+'</span>';
4194
4443
  }
4195
- var ownerBadge=u.isOwner?' <span style="font-size:10px;background:linear-gradient(135deg,var(--amber),#f59e0b);color:#fff;padding:1px 6px;border-radius:4px;font-weight:600;vertical-align:middle;margin-left:4px">Owner</span>':'';
4444
+ var badgesHtml='<div class="au-badges">'+statusLabel+
4445
+ '<span class="admin-badge '+(u.role==='admin'?'admin':'member')+'">'+esc(u.role||'member').toUpperCase()+'</span>'+
4446
+ (u.isOwner?'<span class="admin-badge owner">OWNER</span>':'')+
4447
+ '</div>';
4196
4448
 
4197
- 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">'+
4198
- '<div style="flex:1;min-width:0">'+titleDisplay+editRow+'</div>'+statusLabel+
4199
- '</div>'+
4200
- '<span class="admin-badge '+(u.role==='admin'?'admin':'member')+'">'+esc(u.role||'member')+'</span>'+ownerBadge+'</div>'+
4449
+ return '<div class="admin-card au-card au-'+statusCls+'"><div class="admin-card-header"><div style="flex:1;min-width:0">'+titleDisplay+editRow+'</div>'+
4450
+ badgesHtml+'</div>'+
4201
4451
  contribHtml+infoHtml+
4202
- (actions?'<div class="admin-card-actions" style="border-top:1px dashed var(--border);padding-top:10px;margin-top:4px">'+actions+'</div>':'')+
4452
+ (actions?'<div class="admin-card-actions" style="border-top:1px solid rgba(99,102,241,.08);padding-top:12px;margin-top:6px">'+actions+'</div>':'')+
4203
4453
  '</div>';
4204
4454
  }
4205
4455
 
@@ -4213,14 +4463,15 @@ function renderAdminUsers(users,pending){
4213
4463
  }
4214
4464
  var html='';
4215
4465
  if(pending&&pending.length>0){
4216
- 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>';
4466
+ html+='<div class="admin-pending-section"><h3>'+t('admin.pendingApproval')+' <span class="pending-count">'+pending.length+'</span></h3>';
4217
4467
  for(var p=0;p<pending.length;p++){
4218
4468
  var pu=pending[p];
4219
- 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>'+
4220
- '<div class="admin-card-meta">'+t('admin.device')+esc(pu.deviceName||'unknown')+'</div>'+
4221
- '<div class="admin-card-actions">'+
4222
- '<button class="btn btn-sm btn-primary" onclick="adminApproveUser(&quot;'+escAttr(pu.id)+'&quot;,&quot;'+escAttr(pu.username||'')+'&quot;)">'+t('admin.approve')+'</button>'+
4223
- '<button class="btn btn-sm btn-ghost" onclick="adminRejectUser(&quot;'+escAttr(pu.id)+'&quot;)" style="color:var(--rose)">'+t('admin.reject')+'</button>'+
4469
+ html+='<div class="admin-pending-card">'+
4470
+ '<div class="apc-name">'+esc(pu.username||pu.id||'Unknown')+'</div>'+
4471
+ '<div class="apc-meta"><span>\u{1F4BB} '+esc(pu.deviceName||'unknown')+'</span>'+(pu.createdAt?'<span>\u{1F552} '+formatDateTimeSeconds(pu.createdAt)+'</span>':'')+'</div>'+
4472
+ '<div class="apc-actions">'+
4473
+ '<button class="btn-approve" onclick="adminApproveUser(&quot;'+escAttr(pu.id)+'&quot;,&quot;'+escAttr(pu.username||'')+'&quot;)">'+t('admin.approve')+'</button>'+
4474
+ '<button class="btn-reject" onclick="adminRejectUser(&quot;'+escAttr(pu.id)+'&quot;)">'+t('admin.reject')+'</button>'+
4224
4475
  '</div></div>';
4225
4476
  }
4226
4477
  html+='</div>';
@@ -4231,6 +4482,7 @@ function renderAdminUsers(users,pending){
4231
4482
  offlineUsers.sort(function(a,b){return (b.lastActiveAt||0)-(a.lastActiveAt||0);});
4232
4483
  var sorted=onlineUsers.concat(offlineUsers);
4233
4484
  var adminCount=users.filter(function(x){return x.role==='admin';}).length;
4485
+ var myUserId=sharingStatusCache&&sharingStatusCache.connection&&sharingStatusCache.connection.user?sharingStatusCache.connection.user.id:null;
4234
4486
 
4235
4487
  if(sorted.length===0){
4236
4488
  html+='<div class="admin-empty"><span class="ae-icon">\u{1F465}</span>'+t('admin.noActiveUsers')+'</div>';
@@ -4239,13 +4491,13 @@ function renderAdminUsers(users,pending){
4239
4491
  if(onlineUsers.length===0){
4240
4492
  html+='<div style="font-size:12px;color:var(--text-muted);padding:8px 0 12px">\u2014</div>';
4241
4493
  }else{
4242
- for(var i=0;i<onlineUsers.length;i++) html+=renderAdminUserCard(onlineUsers[i],adminCount);
4494
+ for(var i=0;i<onlineUsers.length;i++) html+=renderAdminUserCard(onlineUsers[i],adminCount,myUserId);
4243
4495
  }
4244
4496
  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>';
4245
4497
  if(offlineUsers.length===0){
4246
4498
  html+='<div style="font-size:12px;color:var(--text-muted);padding:8px 0 12px">\u2014</div>';
4247
4499
  }else{
4248
- for(var j=0;j<offlineUsers.length;j++) html+=renderAdminUserCard(offlineUsers[j],adminCount);
4500
+ for(var j=0;j<offlineUsers.length;j++) html+=renderAdminUserCard(offlineUsers[j],adminCount,myUserId);
4249
4501
  }
4250
4502
  }
4251
4503
  el.innerHTML=html;
@@ -4256,7 +4508,7 @@ async function adminApproveUser(userId,username){
4256
4508
  try{
4257
4509
  var r=await fetch('/api/sharing/approve-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:username})});
4258
4510
  var d=await r.json();
4259
- if(d.ok){toast(t('toast.userApproved'),'success');loadAdminData();}else{toast(d.error||t('toast.approveFail'),'error');}
4511
+ if(d.ok){toast(t('toast.userApproved'),'success');_lastAdminFingerprint='';loadAdminData();}else{toast(d.error||t('toast.approveFail'),'error');}
4260
4512
  }catch(e){toast(t('toast.approveFail')+': '+e.message,'error');}
4261
4513
  }
4262
4514
  async function adminRejectUser(userId){
@@ -4264,7 +4516,7 @@ async function adminRejectUser(userId){
4264
4516
  try{
4265
4517
  var r=await fetch('/api/sharing/reject-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId})});
4266
4518
  var d=await r.json();
4267
- if(d.ok){toast(t('toast.userRejected'),'success');loadAdminData();}else{toast(d.error||t('toast.rejectFail'),'error');}
4519
+ if(d.ok){toast(t('toast.userRejected'),'success');_lastAdminFingerprint='';loadAdminData();}else{toast(d.error||t('toast.rejectFail'),'error');}
4268
4520
  }catch(e){toast(t('toast.rejectFail')+': '+e.message,'error');}
4269
4521
  }
4270
4522
  async function adminToggleRole(userId,newRole){
@@ -4273,7 +4525,14 @@ async function adminToggleRole(userId,newRole){
4273
4525
  try{
4274
4526
  var r=await fetch('/api/sharing/change-role',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,role:newRole})});
4275
4527
  var d=await r.json();
4276
- if(d.ok){toast(t('toast.roleChanged'),'success');loadAdminData();}
4528
+ if(d.ok){
4529
+ toast(t('toast.roleChanged'),'success');
4530
+ _lastAdminFingerprint='';
4531
+ _lastSettingsFingerprint='';
4532
+ _lastSidebarFingerprint='';
4533
+ await loadSharingStatus(false);
4534
+ loadAdminData();
4535
+ }
4277
4536
  else if(d.error==='cannot_demote_owner'){toast(t('admin.ownerHint'),'error');}
4278
4537
  else{toast(d.error||t('toast.roleChangeFail'),'error');}
4279
4538
  }catch(e){toast(t('toast.roleChangeFail')+': '+e.message,'error');}
@@ -4299,13 +4558,24 @@ async function adminSaveEditName(userId){
4299
4558
  var inputEl=document.getElementById('au_input_'+userId);
4300
4559
  if(!inputEl) return;
4301
4560
  var newName=inputEl.value.trim();
4302
- if(!newName||newName.length<2||newName.length>32){toast(t('toast.invalidUsername'),'warn');return;}
4561
+ if(!newName||newName.length<2||newName.length>32){
4562
+ alertModal(t('toast.invalidUsername'),{title:t('admin.editName')});
4563
+ return;
4564
+ }
4303
4565
  inputEl.disabled=true;
4304
4566
  try{
4305
4567
  var r=await fetch('/api/sharing/rename-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:newName})});
4306
4568
  var d=await r.json();
4307
- if(d.ok){toast(t('toast.usernameChanged'),'success');loadAdminData();}else{inputEl.disabled=false;toast(d.error||t('toast.renameFail'),'error');}
4308
- }catch(e){inputEl.disabled=false;toast(t('toast.renameFail')+': '+e.message,'error');}
4569
+ if(d.ok){toast(t('toast.usernameChanged'),'success');adminCancelEditName(userId);loadAdminData();}
4570
+ else{
4571
+ inputEl.disabled=false;
4572
+ if(d.error==='username_taken'){
4573
+ alertModal(t('sharing.username.taken'),{title:t('admin.editName'),danger:true});
4574
+ }else{
4575
+ alertModal(d.error||t('toast.renameFail'),{title:t('admin.editName'),danger:true});
4576
+ }
4577
+ }
4578
+ }catch(e){inputEl.disabled=false;alertModal(t('toast.renameFail'),{title:t('admin.editName'),danger:true});}
4309
4579
  }
4310
4580
 
4311
4581
  async function adminRemoveUser(userId,username){
@@ -4314,7 +4584,7 @@ async function adminRemoveUser(userId,username){
4314
4584
  try{
4315
4585
  var r=await fetch('/api/sharing/remove-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,cleanResources:clean})});
4316
4586
  var d=await r.json();
4317
- if(d.ok){toast(t('toast.userRemoved'),'success');loadAdminData();}
4587
+ if(d.ok){toast(t('toast.userRemoved'),'success');_lastAdminFingerprint='';loadAdminData();}
4318
4588
  else if(d.error==='cannot_remove_owner'){toast(t('admin.ownerHint'),'error');}
4319
4589
  else{toast(d.error||t('toast.removeFail'),'error');}
4320
4590
  }catch(e){toast(t('toast.removeFail')+': '+e.message,'error');}
@@ -4383,7 +4653,7 @@ function renderAdminTasks(tasks){
4383
4653
  '<div class="admin-card-header"><div class="admin-card-title">'+esc(tk.title||tk.id)+'</div></div>'+
4384
4654
  '<div class="admin-card-tags">'+
4385
4655
  '<div class="admin-card-tags-left">'+
4386
- '<span class="admin-card-tag tag-owner">\u{1F464} '+esc(tk.ownerName||tk.sourceUserId||'unknown')+'</span>'+
4656
+ '<span class="admin-card-tag tag-owner">\u{1F464} '+fmtOwner(tk)+'</span>'+
4387
4657
  (tk.status?'<span class="admin-card-tag tag-status">'+esc(tk.status)+'</span>':'')+
4388
4658
  (tk.chunkCount!=null?'<span class="admin-card-tag tag-kind">\u{1F4DD} '+tk.chunkCount+' '+t('admin.chunks')+'</span>':'')+
4389
4659
  '</div>'+
@@ -4445,7 +4715,7 @@ function renderAdminSkills(skills){
4445
4715
  '<div class="admin-card-header"><div class="admin-card-title">'+esc(s.name||s.id)+'</div></div>'+
4446
4716
  '<div class="admin-card-tags">'+
4447
4717
  '<div class="admin-card-tags-left">'+
4448
- '<span class="admin-card-tag tag-owner">\u{1F464} '+esc(s.ownerName||s.sourceUserId||'unknown')+'</span>'+
4718
+ '<span class="admin-card-tag tag-owner">\u{1F464} '+fmtOwner(s)+'</span>'+
4449
4719
  (s.status?'<span class="admin-card-tag tag-status">'+esc(s.status)+'</span>':'')+
4450
4720
  (s.version!=null?'<span class="admin-card-tag tag-version">v'+s.version+'</span>':'')+
4451
4721
  (qs!=null?'<span class="admin-card-tag tag-kind">\u2605 '+Number(qs).toFixed(1)+'</span>':'')+
@@ -4510,7 +4780,7 @@ function renderAdminMemories(memories){
4510
4780
  '<div class="admin-card-header"><div class="admin-card-title">'+esc(m.summary||m.content?.slice(0,80)||m.id)+'</div></div>'+
4511
4781
  '<div class="admin-card-tags">'+
4512
4782
  '<div class="admin-card-tags-left">'+
4513
- '<span class="admin-card-tag tag-owner">\u{1F464} '+esc(m.ownerName||m.sourceUserId||'unknown')+'</span>'+
4783
+ '<span class="admin-card-tag tag-owner">\u{1F464} '+fmtOwner(m)+'</span>'+
4514
4784
  (m.role?'<span class="admin-card-tag tag-role">'+esc(m.role)+'</span>':'')+
4515
4785
  (m.kind?'<span class="admin-card-tag tag-kind">'+esc(m.kind)+'</span>':'')+
4516
4786
  '</div>'+
@@ -4554,7 +4824,7 @@ function toggleAdminMemoryCard(cardId,idx){
4554
4824
  (m.kind?'<span class="meta-item">'+t('admin.kind')+esc(m.kind)+'</span>':'')+
4555
4825
  (m.role?'<span class="meta-item">'+t('admin.role')+esc(m.role)+'</span>':'')+
4556
4826
  (m.visibility?'<span class="meta-item">'+t('admin.visibility')+esc(m.visibility)+'</span>':'')+
4557
- '<span class="meta-item">'+t('admin.owner')+esc(m.ownerName||m.sourceUserId||'unknown')+'</span>'+
4827
+ '<span class="meta-item">'+t('admin.owner')+fmtOwner(m)+'</span>'+
4558
4828
  (m.groupName?'<span class="meta-item">'+t('admin.group')+esc(m.groupName)+'</span>':'')+
4559
4829
  '<span class="meta-item">'+new Date(m.updatedAt||m.createdAt||0).toLocaleString(dateLoc())+'</span>'+
4560
4830
  '</div>';
@@ -4610,7 +4880,7 @@ async function toggleAdminTaskCard(cardId,idx){
4610
4880
  var metaHtml='<div class="admin-card-detail-meta admin-task-meta">'+
4611
4881
  (tk.status?'<span class="meta-item"><span class="task-status-badge '+tk.status+'">'+esc(tk.status)+'</span></span>':'')+
4612
4882
  (tk.visibility?'<span class="meta-item">'+t('admin.visibility')+esc(tk.visibility)+'</span>':'')+
4613
- '<span class="meta-item">'+t('admin.owner')+esc(tk.ownerName||'unknown')+'</span>'+
4883
+ '<span class="meta-item">'+t('admin.owner')+fmtOwner(tk)+'</span>'+
4614
4884
  (tk.groupName?'<span class="meta-item">'+t('admin.group')+esc(tk.groupName)+'</span>':'')+
4615
4885
  (task.chunks&&task.chunks.length?'<span class="meta-item">\u{1F4AC} '+task.chunks.length+' '+t('tasks.chunks.label')+'</span>':'')+
4616
4886
  (task.startedAt?'<span class="meta-item">\u{1F4C5} '+formatDateTimeSeconds(task.startedAt)+'</span>':'')+
@@ -4697,7 +4967,7 @@ async function toggleAdminSkillCard(cardId,idx){
4697
4967
  (localSkill.status?'<span class="meta-item"><span class="skill-badge status-'+localSkill.status+'">'+esc(localSkill.status)+'</span></span>':'')+
4698
4968
  (sk.visibility?'<span class="meta-item">'+t('admin.visibility')+esc(sk.visibility||'hub')+'</span>':'')+
4699
4969
  (qs!=null?'<span class="meta-item"><span class="skill-badge quality '+(qs>=7?'high':qs>=5?'mid':'low')+'">\u2605 '+Number(qs).toFixed(1)+'/10</span></span>':'')+
4700
- '<span class="meta-item">'+t('admin.owner')+esc(sk.ownerName||'unknown')+'</span>'+
4970
+ '<span class="meta-item">'+t('admin.owner')+fmtOwner(sk)+'</span>'+
4701
4971
  (sk.groupName?'<span class="meta-item">'+t('admin.group')+esc(sk.groupName)+'</span>':'')+
4702
4972
  '<span class="meta-item">'+t('admin.updated')+new Date(sk.updatedAt||sk.createdAt||0).toLocaleString(dateLoc())+'</span>'+
4703
4973
  '</div>';
@@ -4766,7 +5036,7 @@ function renderSharingMemorySearchResults(data,query){
4766
5036
  '<div class="summary">'+(idx+1)+'. '+esc(hit.summary||'(no summary)')+'</div>'+
4767
5037
  '<div class="excerpt">'+esc(hit.excerpt||'')+'</div>'+
4768
5038
  '<div class="hub-hit-meta">'+
4769
- '<span class="meta-chip">owner: '+esc(hit.ownerName||'unknown')+'</span>'+
5039
+ '<span class="meta-chip">owner: '+fmtOwner(hit)+'</span>'+
4770
5040
  (hit.groupName?'<span class="meta-chip">group: '+esc(hit.groupName)+'</span>':'')+
4771
5041
  '<span class="meta-chip">visibility: '+esc(hit.visibility||'hub')+'</span>'+
4772
5042
  '</div>'+
@@ -4851,7 +5121,7 @@ function openHubTaskDetailFromCache(cacheKey,idx){
4851
5121
  var meta=[
4852
5122
  '<span class="meta-item">\\u{1F310} '+t('scope.hub')+'</span>',
4853
5123
  task.status?'<span class="meta-item"><span class="task-status-badge '+task.status+'">'+esc(task.status)+'</span></span>':'',
4854
- '<span class="meta-item">'+t('admin.owner')+esc(task.ownerName||'unknown')+'</span>',
5124
+ '<span class="meta-item">'+t('admin.owner')+fmtOwner(task)+'</span>',
4855
5125
  task.groupName?'<span class="meta-item">'+t('admin.group')+esc(task.groupName)+'</span>':'',
4856
5126
  task.visibility?'<span class="meta-item">'+t('admin.visibility')+esc(task.visibility)+'</span>':'',
4857
5127
  task.chunkCount!=null?'<span class="meta-item">\\u{1F4DD} '+esc(String(task.chunkCount))+' '+t('tasks.chunks.label')+'</span>':'',
@@ -4882,14 +5152,14 @@ function openHubSkillDetailFromCache(cacheKey,idx){
4882
5152
  skill.status?'<span class="meta-item"><span class="skill-badge status-'+skill.status+'">'+esc(skill.status)+'</span></span>':'',
4883
5153
  '<span class="meta-item">visibility: '+esc(skill.visibility||'hub')+'</span>',
4884
5154
  qsBadge,
4885
- '<span class="meta-item">'+t('admin.owner')+esc(skill.ownerName||'unknown')+'</span>',
5155
+ '<span class="meta-item">'+t('admin.owner')+fmtOwner(skill)+'</span>',
4886
5156
  skill.groupName?'<span class="meta-item">'+t('admin.group')+esc(skill.groupName)+'</span>':'',
4887
5157
  (skill.updatedAt||skill.createdAt)?'<span class="meta-item">'+t('admin.updated')+new Date(skill.updatedAt||skill.createdAt).toLocaleString(dateLoc())+'</span>':'',
4888
5158
  ].filter(Boolean);
4889
5159
  document.getElementById('skillDetailMeta').innerHTML=meta.join('');
4890
5160
  document.getElementById('skillDetailDesc').textContent=skill.description||'';
4891
5161
  document.getElementById('skillFilesList').innerHTML='';
4892
- document.getElementById('skillDetailContent').innerHTML=skill.content?'<pre>'+esc(skill.content)+'</pre>':'';
5162
+ document.getElementById('skillDetailContent').innerHTML=skill.content?renderSkillMarkdown(skill.content):'';
4893
5163
  document.getElementById('skillVersionsList').innerHTML='';
4894
5164
  document.getElementById('skillRelatedTasks').innerHTML='';
4895
5165
  var visBtn=document.getElementById('skillVisibilityBtn');
@@ -4932,6 +5202,7 @@ function openScopeSelectorModal(resourceType, resourceId, currentScope, onConfir
4932
5202
  var existing=document.getElementById('scopeSelectorOverlay');
4933
5203
  if(existing) existing.remove();
4934
5204
  var teamEnabled=sharingStatusCache&&sharingStatusCache.enabled;
5205
+ var teamConnected=teamEnabled&&sharingStatusCache.connection&&sharingStatusCache.connection.connected;
4935
5206
  var overlay=document.createElement('div');
4936
5207
  overlay.id='scopeSelectorOverlay';
4937
5208
  overlay.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);backdrop-filter:blur(6px);z-index:10000;display:flex;align-items:center;justify-content:center;animation:fadeIn 0.12s ease';
@@ -4945,7 +5216,7 @@ function openScopeSelectorModal(resourceType, resourceId, currentScope, onConfir
4945
5216
  for(var i=0;i<scopes.length;i++){
4946
5217
  var sc=scopes[i];
4947
5218
  var isCurrent=sc===currentScope;
4948
- var isDisabled=sc==='team'&&!teamEnabled;
5219
+ var isDisabled=sc==='team'&&(!teamEnabled||!teamConnected);
4949
5220
  var color=getScopeColor(sc);
4950
5221
  var cursor=isDisabled?'not-allowed':'pointer';
4951
5222
  var opacity=isDisabled?'0.4':'1';
@@ -5014,7 +5285,8 @@ async function confirmScopeSelection(){
5014
5285
  if(st.onConfirm) st.onConfirm(newScope);
5015
5286
  else loadAll();
5016
5287
  }else{
5017
- toast(d.error||t('share.scope.changeFail'),'error');
5288
+ var errMsg=d.error==='inactive_memory'?t('share.scope.inactiveDisabled'):(d.message||d.error||t('share.scope.changeFail'));
5289
+ toast(errMsg,'error');
5018
5290
  }
5019
5291
  }catch(e){toast(t('share.scope.changeFail')+': '+e.message,'error');}
5020
5292
  }
@@ -5265,6 +5537,13 @@ function parseMemoryAddEntries(out){
5265
5537
  return results;
5266
5538
  }
5267
5539
 
5540
+ function recallOriginBadge(origin){
5541
+ if(origin==='local-shared') return '<span class="recall-origin local-shared">'+t('recall.origin.localShared')+'</span>';
5542
+ if(origin==='hub-memory') return '<span class="recall-origin hub-memory">'+t('recall.origin.hubMemory')+'</span>';
5543
+ if(origin==='hub-remote') return '<span class="recall-origin hub-remote">'+t('recall.origin.hubRemote')+'</span>';
5544
+ return '';
5545
+ }
5546
+
5268
5547
  function buildLogSummary(lg){
5269
5548
  let inputObj=null;
5270
5549
  try{inputObj=JSON.parse(lg.input);}catch(_){}
@@ -5289,8 +5568,9 @@ function buildLogSummary(lg){
5289
5568
  var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
5290
5569
  var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
5291
5570
  var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
5571
+ var oBadge=recallOriginBadge(c.origin);
5292
5572
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5293
- html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5573
+ html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5294
5574
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5295
5575
  html+='</div>';
5296
5576
  });
@@ -5303,8 +5583,9 @@ function buildLogSummary(lg){
5303
5583
  var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
5304
5584
  var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
5305
5585
  var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
5586
+ var oBadge=recallOriginBadge(f.origin);
5306
5587
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5307
- html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+f.score.toFixed(2)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5588
+ html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+f.score.toFixed(2)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5308
5589
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5309
5590
  html+='</div>';
5310
5591
  });
@@ -5368,8 +5649,9 @@ function buildRecallDetailHtml(rd){
5368
5649
  var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
5369
5650
  var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
5370
5651
  var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
5652
+ var oBadge=recallOriginBadge(c.origin);
5371
5653
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5372
- html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+c.score.toFixed(3)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5654
+ html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+c.score.toFixed(3)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5373
5655
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5374
5656
  html+='</div>';
5375
5657
  });
@@ -5383,8 +5665,9 @@ function buildRecallDetailHtml(rd){
5383
5665
  var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
5384
5666
  var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
5385
5667
  var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
5668
+ var oBadge=recallOriginBadge(f.origin);
5386
5669
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5387
- html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+f.score.toFixed(3)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span><span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5670
+ html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+f.score.toFixed(3)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5388
5671
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5389
5672
  html+='</div>';
5390
5673
  });
@@ -5514,12 +5797,12 @@ function setTaskStatusFilter(btn,status){
5514
5797
  loadTasks();
5515
5798
  }
5516
5799
 
5517
- async function loadTasks(){
5800
+ async function loadTasks(silent){
5518
5801
  const scope=document.getElementById('taskSearchScope')?document.getElementById('taskSearchScope').value:taskSearchScope;
5519
5802
  taskSearchScope=scope||'local';
5520
5803
  if(taskSearchScope==='hub'){ return loadHubTasks(); }
5521
5804
  const list=document.getElementById('tasksList');
5522
- list.innerHTML='<div class="spinner"></div>';
5805
+ if(!silent) list.innerHTML='<div class="spinner"></div>';
5523
5806
  try{
5524
5807
  const params=new URLSearchParams({limit:String(TASKS_PER_PAGE),offset:String(tasksPage*TASKS_PER_PAGE)});
5525
5808
  if(tasksStatusFilter) params.set('status',tasksStatusFilter);
@@ -5533,6 +5816,14 @@ async function loadTasks(){
5533
5816
  fetch('/api/tasks?status=completed&limit=1&offset=0&'+baseP).then(r=>r.json()),
5534
5817
  fetch('/api/tasks?status=skipped&limit=1&offset=0&'+baseP).then(r=>r.json())
5535
5818
  ]);
5819
+ if(silent){
5820
+ var fp=JSON.stringify((data.tasks||[]).map(function(tk){return tk.id+'|'+tk.status+'|'+(tk.updatedAt||tk.startedAt)}));
5821
+ fp+=':'+allD.total+':'+activeD.total+':'+compD.total+':'+skipD.total;
5822
+ if(fp===_lastTasksFingerprint) return;
5823
+ _lastTasksFingerprint=fp;
5824
+ }else{
5825
+ _lastTasksFingerprint='';
5826
+ }
5536
5827
  document.getElementById('tasksTotalCount').textContent=formatNum(allD.total);
5537
5828
  document.getElementById('tasksActiveCount').textContent=formatNum(activeD.total);
5538
5829
  document.getElementById('tasksCompletedCount').textContent=formatNum(compD.total);
@@ -5818,17 +6109,17 @@ function updateSkillCardBadge(skillId,newScope){
5818
6109
  }
5819
6110
  }
5820
6111
 
5821
- async function loadSkills(){
6112
+ async function loadSkills(silent){
5822
6113
  const list=document.getElementById('skillsList');
5823
6114
  const hubList=document.getElementById('hubSkillsList');
5824
- list.innerHTML='<div class="spinner"></div>';
6115
+ if(!silent) list.innerHTML='<div class="spinner"></div>';
5825
6116
  var hubSection=document.getElementById('hubSkillsSection');
5826
6117
  if(hubList){
5827
6118
  if(skillSearchScope==='local'||skillSearchScope==='allLocal'){
5828
6119
  if(hubSection) hubSection.style.display='none';
5829
6120
  }else{
5830
6121
  if(hubSection) hubSection.style.display='block';
5831
- hubList.innerHTML='<div class="spinner"></div>';
6122
+ if(!silent) hubList.innerHTML='<div class="spinner"></div>';
5832
6123
  }
5833
6124
  }
5834
6125
 
@@ -5852,6 +6143,13 @@ async function loadSkills(){
5852
6143
  return haystack.includes(q);
5853
6144
  });
5854
6145
  }
6146
+ if(silent){
6147
+ var fp=JSON.stringify(localSkills.map(function(s){return s.id+'|'+s.status+'|'+s.version+'|'+(s.visibility||'')}));
6148
+ if(fp===_lastSkillsFingerprint) return;
6149
+ _lastSkillsFingerprint=fp;
6150
+ }else{
6151
+ _lastSkillsFingerprint='';
6152
+ }
5855
6153
 
5856
6154
  const renderLocalCards=function(skills){
5857
6155
  if(!skills||skills.length===0){
@@ -5910,7 +6208,8 @@ async function loadSkills(){
5910
6208
 
5911
6209
  if(!query){
5912
6210
  if(hubSection) hubSection.style.display='block';
5913
- if(hubList){ loadHubSkills(hubList); }
6211
+ var localIds=new Set(localSkills.map(function(s){return s.id;}));
6212
+ if(hubList){ loadHubSkills(hubList, localIds); }
5914
6213
  document.getElementById('skillSearchMeta').textContent=t('skills.search.local')+' '+localSkills.length;
5915
6214
  document.getElementById('skillsTotalCount').textContent=formatNum(localSkills.length);
5916
6215
  document.getElementById('skillsActiveCount').textContent=formatNum(localSkills.filter(s=>s.status==='active').length);
@@ -5944,7 +6243,7 @@ async function loadSkills(){
5944
6243
  '<div class="summary">'+esc(skill.name)+'</div>'+
5945
6244
  '<div class="excerpt">'+esc(skill.description||'')+'</div>'+
5946
6245
  '<div class="hub-skill-meta">'+
5947
- '<span class="meta-chip">owner: '+esc(skill.ownerName||'unknown')+'</span>'+
6246
+ '<span class="meta-chip">owner: '+fmtOwner(skill)+'</span>'+
5948
6247
  (skill.groupName?'<span class="meta-chip">group: '+esc(skill.groupName)+'</span>':'')+
5949
6248
  '<span class="meta-chip">visibility: '+esc(skill.visibility||'hub')+'</span>'+
5950
6249
  (skill.version!=null?'<span class="meta-chip">v'+skill.version+'</span>':'')+
@@ -5991,12 +6290,12 @@ async function loadHubTasks(){
5991
6290
  return '<div class="task-card" onclick="openHubTaskDetailFromCache(\\x27hub\\x27,'+idx+')" style="cursor:pointer">'+
5992
6291
  '<div class="task-card-top">'+
5993
6292
  '<div class="task-card-title">'+esc(task.title||'(no title)')+'</div>'+
5994
- '<div class="task-card-badges"><span class="scope-badge team">\\u{1F310} '+t('scope.hub')+'</span></div>'+
6293
+ '<div class="task-card-badges">'+renderScopeBadge('team')+'</div>'+
5995
6294
  '</div>'+
5996
6295
  (task.summary?'<div class="task-card-summary">'+esc(task.summary)+'</div>':'')+
5997
6296
  '<div class="task-card-bottom">'+
5998
6297
  (timeStr?'<span class="tag"><span class="icon">\\u{1F4C5}</span> '+timeStr+'</span>':'')+
5999
- '<span class="tag"><span class="icon">\\u{1F464}</span> '+esc(task.ownerName||'unknown')+'</span>'+
6298
+ '<span class="tag"><span class="icon">\\u{1F464}</span> '+fmtOwner(task)+'</span>'+
6000
6299
  (task.chunkCount!=null?'<span class="tag"><span class="icon">\\u{1F4DD}</span> '+task.chunkCount+' '+t('tasks.chunks.label')+'</span>':'')+
6001
6300
  '</div>'+
6002
6301
  '</div>';
@@ -6008,7 +6307,7 @@ async function loadHubTasks(){
6008
6307
  }
6009
6308
  }
6010
6309
 
6011
- async function loadHubSkills(hubList){
6310
+ async function loadHubSkills(hubList, localIds){
6012
6311
  if(!hubList) hubList=document.getElementById('hubSkillsList');
6013
6312
  if(!hubList) return;
6014
6313
  var hubSection=document.getElementById('hubSkillsSection');
@@ -6016,7 +6315,8 @@ async function loadHubSkills(hubList){
6016
6315
  try{
6017
6316
  const r=await fetch('/api/sharing/skills/list?limit=40');
6018
6317
  const d=await r.json();
6019
- const skills=Array.isArray(d.skills)?d.skills:[];
6318
+ var allSkills=Array.isArray(d.skills)?d.skills:[];
6319
+ const skills=localIds?allSkills.filter(function(s){return !localIds.has(s.sourceSkillId);}):allSkills;
6020
6320
  hubSkillsCache=skills;
6021
6321
  if(!skills.length){
6022
6322
  if(hubSection) hubSection.style.display='none';
@@ -6028,12 +6328,12 @@ async function loadHubSkills(hubList){
6028
6328
  '<div class="summary">'+esc(skill.name)+'</div>'+
6029
6329
  '<div class="excerpt">'+esc(skill.description||'')+'</div>'+
6030
6330
  '<div class="hub-skill-meta">'+
6031
- '<span class="meta-chip">owner: '+esc(skill.ownerName||'unknown')+'</span>'+
6331
+ '<span class="meta-chip">owner: '+fmtOwner(skill)+'</span>'+
6032
6332
  (skill.groupName?'<span class="meta-chip">group: '+esc(skill.groupName)+'</span>':'')+
6033
6333
  '<span class="meta-chip">visibility: '+esc(skill.visibility||'hub')+'</span>'+
6034
6334
  (skill.version!=null?'<span class="meta-chip">v'+skill.version+'</span>':'')+
6035
6335
  '</div>'+
6036
- '<div class="hub-skill-actions"><button class="btn btn-sm" onclick="event.stopPropagation();pullHubSkill(\\''+escAttr(skill.sourceSkillId)+'\\')">'+t('skill.pullToLocal')+'</button></div>'+
6336
+ '<div class="hub-skill-actions"><button class="btn btn-sm" onclick="event.stopPropagation();pullHubSkill(\\''+escAttr(skill.id)+'\\')">'+t('skill.pullToLocal')+'</button></div>'+
6037
6337
  '</div>';
6038
6338
  }).join('');
6039
6339
  }catch(e){
@@ -6367,6 +6667,7 @@ async function loadConfig(){
6367
6667
  document.getElementById('cfgHubTeamName').value=hub.teamName||'';
6368
6668
  document.getElementById('cfgHubTeamToken').value=hub.teamToken||'';
6369
6669
  document.getElementById('cfgClientHubAddress').value=client.hubAddress||'';
6670
+ _loadedClientHubAddress=client.hubAddress||'';
6370
6671
  document.getElementById('cfgClientTeamToken').value=client.teamToken||'';
6371
6672
  document.getElementById('cfgClientNickname').value=client.nickname||'';
6372
6673
  document.getElementById('cfgClientUserToken').value=client.userToken||'';
@@ -6412,20 +6713,24 @@ function flashSaved(id){
6412
6713
  }
6413
6714
 
6414
6715
  async function doSaveConfig(cfg, btnEl, savedId){
6415
- btnEl.disabled=true;btnEl.textContent=t('settings.test.loading');
6416
- function done(){btnEl.disabled=false;btnEl.textContent=t('settings.save');}
6716
+ if(btnEl){btnEl.disabled=true;btnEl.textContent=t('settings.test.loading');}
6717
+ function done(){if(btnEl){btnEl.disabled=false;btnEl.textContent=t('settings.save');}}
6417
6718
  try{
6418
6719
  const r=await fetch('/api/config',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(cfg)});
6419
- if(r.status===401){done();toast(t('settings.session.expired'),'error');return false;}
6720
+ if(r.status===401){done();toast(t('settings.session.expired'),'error');return null;}
6420
6721
  if(!r.ok) throw new Error(await r.text());
6722
+ var data=await r.json().catch(function(){return {ok:true};});
6421
6723
  flashSaved(savedId);
6422
- toast(t('settings.saved'),'success');
6423
- done();
6424
- return true;
6724
+ if(data&&data.restart){
6725
+ showRestartOverlay(t('settings.restart.waiting'));
6726
+ }else{
6727
+ done();
6728
+ }
6729
+ return data;
6425
6730
  }catch(e){
6426
6731
  toast(t('settings.save.fail')+': '+e.message,'error');
6427
6732
  done();
6428
- return false;
6733
+ return null;
6429
6734
  }
6430
6735
  }
6431
6736
 
@@ -6520,11 +6825,19 @@ async function saveModelsConfig(){
6520
6825
  await doSaveConfig(cfg, saveBtn, 'modelsSaved');
6521
6826
  }
6522
6827
 
6828
+ function _hubSaveBtnLabel(){
6829
+ var on=document.getElementById('cfgSharingEnabled');
6830
+ if(on&&on.checked&&_sharingRole==='client'){
6831
+ var prevClient=sharingStatusCache&&sharingStatusCache.enabled&&sharingStatusCache.role==='client';
6832
+ return prevClient?t('settings.save'):t('sharing.joinTeam');
6833
+ }
6834
+ return t('settings.save');
6835
+ }
6523
6836
  async function saveHubConfig(){
6524
6837
  var card=document.getElementById('settingsSharingConfig');
6525
6838
  var saveBtn=card.querySelector('.settings-actions .btn-primary');
6526
6839
  saveBtn.disabled=true;saveBtn.textContent=t('settings.test.loading');
6527
- function done(){saveBtn.disabled=false;saveBtn.textContent=t('settings.save');}
6840
+ function done(){saveBtn.disabled=false;saveBtn.textContent=_hubSaveBtnLabel();}
6528
6841
 
6529
6842
  const cfg={};
6530
6843
  var sharingEnabled=document.getElementById('cfgSharingEnabled').checked;
@@ -6538,7 +6851,7 @@ async function saveHubConfig(){
6538
6851
  var hubTeamName=document.getElementById('cfgHubTeamName').value.trim();
6539
6852
  var hubTeamToken=document.getElementById('cfgHubTeamToken').value.trim();
6540
6853
  var hubAdminName=document.getElementById('cfgHubAdminName').value.trim();
6541
- cfg.sharing.hub={port:hubPort?Number(hubPort):18800};
6854
+ if(hubPort) cfg.sharing.hub={port:Number(hubPort)}; else cfg.sharing.hub={};
6542
6855
  if(hubTeamName) cfg.sharing.hub.teamName=hubTeamName;
6543
6856
  if(hubTeamToken) cfg.sharing.hub.teamToken=hubTeamToken;
6544
6857
  cfg.sharing.client={hubAddress:'',userToken:'',teamToken:''};
@@ -6548,21 +6861,29 @@ async function saveHubConfig(){
6548
6861
  var clientTeamToken=document.getElementById('cfgClientTeamToken').value.trim();
6549
6862
  var clientUserToken=document.getElementById('cfgClientUserToken').value.trim();
6550
6863
  var clientNickname=document.getElementById('cfgClientNickname').value.trim();
6864
+ if(!clientAddr){done();toast(t('settings.hub.test.noAddr'),'error');return;}
6865
+ if(!clientTeamToken){done();toast(t('settings.hub.teamToken.required'),'error');return;}
6551
6866
  cfg.sharing.client={};
6552
6867
  if(clientAddr) cfg.sharing.client.hubAddress=clientAddr;
6553
6868
  if(clientNickname) cfg.sharing.client.nickname=clientNickname;
6554
6869
  if(clientTeamToken) cfg.sharing.client.teamToken=clientTeamToken;
6555
6870
  if(clientUserToken) cfg.sharing.client.userToken=clientUserToken;
6556
- cfg.sharing.hub={port:18800,teamName:'',teamToken:''};
6871
+ cfg.sharing.hub={teamName:'',teamToken:''};
6557
6872
  if(clientAddr){
6558
6873
  try{
6559
- var ips=await fetch('/api/local-ips').then(function(r){return r.json();});
6560
- var localAddrs=['127.0.0.1','localhost','0.0.0.0'].concat(ips.ips||[]);
6561
- var parsed=new URL(clientAddr.indexOf('://')>-1?clientAddr:'http://'+clientAddr);
6562
- if(localAddrs.indexOf(parsed.hostname)>=0){
6563
- done();toast(t('sharing.cannotJoinSelf'),'error');return;
6564
- }
6565
6874
  }catch(e){}
6875
+ try{
6876
+ var testUrl=clientAddr.indexOf('://')>-1?clientAddr:'http://'+clientAddr;
6877
+ testUrl=testUrl.replace(/\\/+$/,'');
6878
+ var tr=await fetch('/api/sharing/test-hub',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({hubUrl:testUrl})});
6879
+ var td=await tr.json();
6880
+ if(!td.ok){
6881
+ var errMsg=td.error==='cannot_join_self'?t('sharing.cannotJoinSelf'):(td.error||t('settings.hub.test.fail'));
6882
+ done();toast(errMsg,'error');return;
6883
+ }
6884
+ }catch(e){
6885
+ done();toast(t('settings.hub.test.fail')+': '+String(e),'error');return;
6886
+ }
6566
6887
  }
6567
6888
  }
6568
6889
 
@@ -6572,21 +6893,44 @@ async function saveHubConfig(){
6572
6893
  var confirmMsg=prevRole==='hub'?t('sharing.disable.confirm.hub'):t('sharing.disable.confirm.client');
6573
6894
  if(!(await confirmModal(confirmMsg,{danger:true}))){done();return;}
6574
6895
  }
6896
+ if(prevSharingEnabled&&sharingEnabled&&prevRole&&prevRole!==_sharingRole){
6897
+ var switchMsg=prevRole==='hub'?t('sharing.switch.hubToClient'):t('sharing.switch.clientToHub');
6898
+ if(!(await confirmModal(switchMsg,{danger:true}))){done();return;}
6899
+ }
6900
+ if(prevSharingEnabled&&sharingEnabled&&prevRole==='client'&&_sharingRole==='client'){
6901
+ var newAddr=(document.getElementById('cfgClientHubAddress').value||'').trim();
6902
+ if(_loadedClientHubAddress&&newAddr&&newAddr!==_loadedClientHubAddress){
6903
+ if(!(await confirmModal(t('sharing.switch.hubAddress'),{danger:true}))){done();return;}
6904
+ }
6905
+ }
6575
6906
 
6576
- var ok=await doSaveConfig(cfg, saveBtn, 'hubSaved');
6577
- if(ok){
6907
+ var result=await doSaveConfig(cfg, saveBtn, 'hubSaved');
6908
+ if(result){
6578
6909
  if(sharingEnabled&&_sharingRole==='hub'){
6579
6910
  var adminNameEl=document.getElementById('cfgHubAdminName');
6580
6911
  if(adminNameEl&&adminNameEl.value.trim()){
6581
6912
  try{await fetch('/api/sharing/update-username',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:adminNameEl.value.trim()})});}catch(e){}
6582
6913
  }
6583
6914
  }
6584
- loadSharingStatus(false);
6585
- var needsRestart=sharingEnabled||(prevSharingEnabled&&!sharingEnabled);
6586
- if(sharingEnabled) updateHubShareInfo();
6587
- if(needsRestart){
6588
- setTimeout(function(){showRestartOverlay(t('settings.restart.waiting'));},300);
6915
+ if(sharingEnabled&&_sharingRole==='client'&&result.joinStatus){
6916
+ if(result.joinStatus==='pending'){
6917
+ toast(t('sharing.joinSent.pending'),'success');
6918
+ }else if(result.joinStatus==='active'){
6919
+ toast(t('sharing.joinSent.active'),'success');
6920
+ }else{
6921
+ toast(t('settings.saved'),'success');
6922
+ }
6923
+ }else{
6924
+ toast(t('settings.saved'),'success');
6589
6925
  }
6926
+ _lastSidebarFingerprint='';
6927
+ _lastSettingsFingerprint='';
6928
+ _lastSharingConnStatus='';
6929
+ _lastAdminFingerprint='';
6930
+ _lastAdminStatsFp='';
6931
+ if(sharingEnabled) updateHubShareInfo();
6932
+ loadSharingStatus(true);
6933
+ if(_activeView==='admin') loadAdminData();
6590
6934
  }
6591
6935
  }
6592
6936
 
@@ -6676,6 +7020,8 @@ function renderSkillMarkdown(md){
6676
7020
  function closeSkillDetail(event){
6677
7021
  if(event && event.target!==document.getElementById('skillDetailOverlay')) return;
6678
7022
  document.getElementById('skillDetailOverlay').classList.remove('show');
7023
+ currentSkillId='';
7024
+ currentSkillDetail=null;
6679
7025
  }
6680
7026
 
6681
7027
  async function deleteSkill(skillId){
@@ -7010,16 +7356,29 @@ var _livePollBusy=false;
7010
7356
  async function _livePollTick(){
7011
7357
  if(_livePollBusy||document.hidden) return;
7012
7358
  _livePollBusy=true;
7359
+ var _savedScrollY=window.scrollY;
7360
+ var _scrollTargets=['memoryList','tasksList','skillsList','adminUsersPanel','adminMemoriesPanel','adminTasksPanel','adminSkillsPanel'];
7361
+ var _savedScrollMap={};
7362
+ _scrollTargets.forEach(function(id){var el=document.getElementById(id);if(el&&el.scrollTop)_savedScrollMap[id]=el.scrollTop;});
7013
7363
  try{
7014
- if(sharingStatusCache&&sharingStatusCache.enabled) loadSharingStatus(false);
7015
- if(!_notifSSEConnected) pollNotifCount();
7016
- pollAdminPending();
7017
- if(_activeView==='admin') loadAdminData();
7018
- else if(_activeView==='memories'){loadStats();loadMemories();}
7019
- else if(_activeView==='tasks') loadTasks();
7020
- else if(_activeView==='skills') loadSkills();
7021
- else if(_activeView==='analytics') loadMetrics();
7364
+ if(sharingStatusCache&&sharingStatusCache.enabled&&_lastSharingConnStatus!=='rejected') await loadSharingStatus(false);
7365
+ if(!_notifSSEConnected) await pollNotifCount();
7366
+ await pollAdminPending();
7367
+ if(_activeView==='admin') await loadAdminData();
7368
+ else if(_activeView==='memories'){
7369
+ var _searchVal=(document.getElementById('searchInput')||{}).value||'';
7370
+ if(!_searchVal.trim()){
7371
+ if(memorySearchScope==='hub') await loadHubMemories(true);
7372
+ else{var _pollOwner=memorySearchScope==='local'?_currentAgentOwner:undefined;await loadStats(_pollOwner);await loadMemories(null,true);}
7373
+ }
7374
+ }
7375
+ else if(_activeView==='tasks') await loadTasks(true);
7376
+ else if(_activeView==='skills') await loadSkills(true);
7377
+ else if(_activeView==='analytics') await loadMetrics();
7022
7378
  }catch(e){}
7379
+ await new Promise(function(r){requestAnimationFrame(r);});
7380
+ window.scrollTo(0,_savedScrollY);
7381
+ for(var _sid in _savedScrollMap){var _sel=document.getElementById(_sid);if(_sel)_sel.scrollTop=_savedScrollMap[_sid];}
7023
7382
  _livePollBusy=false;
7024
7383
  }
7025
7384
 
@@ -7063,6 +7422,9 @@ function connectNotifSSE(){
7063
7422
  _notifUnread=d.unreadCount||0;
7064
7423
  renderNotifBadge();
7065
7424
  if(_notifUnread>prev&&_notifPanelOpen) loadNotifications();
7425
+ if(_notifUnread>prev&&_activeView==='memories'&&memorySearchScope!=='hub'){
7426
+ syncTeamShareRemovedFromNotifications().then(function(){ loadMemories(currentPage,true); });
7427
+ }
7066
7428
  }
7067
7429
  if(d.type==='cleared'){
7068
7430
  _notifUnread=0;_notifCache=[];
@@ -7112,7 +7474,12 @@ function notifTimeAgo(ts){
7112
7474
  function notifIcon(resource,type){
7113
7475
  if(type==='user_online') return '\\u{1F7E2}';
7114
7476
  if(type==='user_offline') return '\\u{1F534}';
7477
+ if(type==='user_left') return '\\u{1F6AA}';
7115
7478
  if(type==='user_join_request') return '\\u{1F464}';
7479
+ if(type==='membership_removed') return '\\u{26D4}';
7480
+ if(type==='hub_shutdown') return '\\u{1F6D1}';
7481
+ if(type==='role_promoted') return '\\u{2B06}';
7482
+ if(type==='role_demoted') return '\\u{2B07}';
7116
7483
  if(resource==='memory') return '\\u{1F4DD}';
7117
7484
  if(resource==='task') return '\\u{1F4CB}';
7118
7485
  if(resource==='skill') return '\\u{1F9E0}';
@@ -7138,6 +7505,27 @@ function notifTypeText(n){
7138
7505
  if(n.type==='user_offline'){
7139
7506
  return t('notif.userOffline');
7140
7507
  }
7508
+ if(n.type==='user_left'){
7509
+ return t('notif.userLeft');
7510
+ }
7511
+ if(n.type==='membership_approved'){
7512
+ return t('notif.membershipApproved');
7513
+ }
7514
+ if(n.type==='membership_rejected'){
7515
+ return t('notif.membershipRejected');
7516
+ }
7517
+ if(n.type==='membership_removed'){
7518
+ return t('notif.membershipRemoved');
7519
+ }
7520
+ if(n.type==='hub_shutdown'){
7521
+ return t('notif.hubShutdown');
7522
+ }
7523
+ if(n.type==='role_promoted'){
7524
+ return t('notif.rolePromoted');
7525
+ }
7526
+ if(n.type==='role_demoted'){
7527
+ return t('notif.roleDemoted');
7528
+ }
7141
7529
  return n.message||n.type;
7142
7530
  }
7143
7531
 
@@ -7172,6 +7560,21 @@ function renderNotifBadge(){
7172
7560
  }
7173
7561
  }
7174
7562
 
7563
+ var _notifKnownTypes={membership_approved:1,membership_rejected:1,membership_removed:1,hub_shutdown:1,user_left:1,user_online:1,user_offline:1,user_join_request:1,role_promoted:1,role_demoted:1,resource_removed:1,resource_shared:1,resource_unshared:1};
7564
+ function notifDisplayTitle(n){
7565
+ if(_notifKnownTypes[n.type]) return notifTypeText(n);
7566
+ return n.title||notifTypeText(n);
7567
+ }
7568
+ function notifDisplayDetail(n){
7569
+ if(_notifKnownTypes[n.type]){
7570
+ if(n.type==='resource_removed'||n.type==='resource_shared'||n.type==='resource_unshared') return n.title||'';
7571
+ var m=n.title&&n.title.match(/["\u201C]([^"\u201D]+)["\u201D]/);
7572
+ if(m) return m[1];
7573
+ if(n.type==='user_left'||n.type==='user_online'||n.type==='user_offline'||n.type==='user_join_request') return n.title||'';
7574
+ return '';
7575
+ }
7576
+ return n.title||'';
7577
+ }
7175
7578
  function renderNotifPanel(){
7176
7579
  var body=document.getElementById('notifPanelBody');
7177
7580
  if(!body) return;
@@ -7181,11 +7584,12 @@ function renderNotifPanel(){
7181
7584
  }
7182
7585
  body.innerHTML=_notifCache.map(function(n){
7183
7586
  var cls='notif-item'+(n.read?'':' unread');
7587
+ var detail=notifDisplayDetail(n);
7184
7588
  return '<div class="'+cls+'" onclick="markNotifRead(&quot;'+esc(n.id)+'&quot;)">'+
7185
7589
  '<div class="notif-item-icon">'+notifIcon(n.resource,n.type)+'</div>'+
7186
7590
  '<div class="notif-item-body">'+
7187
- '<div class="notif-item-title">'+esc(notifTypeText(n))+'</div>'+
7188
- '<div class="notif-item-name">'+esc(n.title)+'</div>'+
7591
+ '<div class="notif-item-title">'+esc(notifDisplayTitle(n))+'</div>'+
7592
+ (detail?'<div class="notif-item-name">'+esc(detail)+'</div>':'')+
7189
7593
  '<div class="notif-item-time">'+notifTimeAgo(n.createdAt)+'</div>'+
7190
7594
  '</div>'+
7191
7595
  '<div class="notif-item-dot"></div>'+
@@ -7228,7 +7632,10 @@ function stopNotifPoll(){ }
7228
7632
 
7229
7633
  /* ─── Data loading ─── */
7230
7634
  async function loadAll(){
7231
- await Promise.all([loadStats(),loadMemories(),loadSharingStatus(false)]);
7635
+ await loadStats();
7636
+ var initOwner=memorySearchScope==='local'?_currentAgentOwner:undefined;
7637
+ if(initOwner) await loadStats(initOwner);
7638
+ await Promise.all([loadMemories(),loadSharingStatus(false)]);
7232
7639
  checkMigrateStatus();
7233
7640
  connectPPSSE();
7234
7641
  checkForUpdate();
@@ -7236,6 +7643,7 @@ async function loadAll(){
7236
7643
  startLivePoller();
7237
7644
  }
7238
7645
 
7646
+ var _lastStatsFp='';
7239
7647
  async function loadStats(ownerFilter){
7240
7648
  let d;
7241
7649
  try{
@@ -7245,28 +7653,22 @@ async function loadStats(ownerFilter){
7245
7653
  d=await r.json();
7246
7654
  }catch(e){ d={}; }
7247
7655
  if(!d||typeof d!=='object') d={};
7656
+ if(d.currentAgentOwner) _currentAgentOwner=d.currentAgentOwner;
7248
7657
  const tm=d.totalMemories||0;
7249
7658
  const dedupB=d.dedupBreakdown||{};
7250
7659
  const activeCount=dedupB.active||tm;
7251
7660
  const inactiveCount=(dedupB.duplicate||0)+(dedupB.merged||0);
7661
+ var agentCount=(d.owners&&d.owners.length)?d.owners.length:1;
7662
+ var sfp=tm+':'+(d.totalSessions||0)+':'+(d.totalEmbeddings||0)+':'+agentCount+':'+(d.embeddingProvider||'none')+':'+(ownerFilter||'');
7663
+ if(sfp===_lastStatsFp) return;
7664
+ _lastStatsFp=sfp;
7252
7665
  document.getElementById('statTotal').textContent=tm;
7253
7666
  if(inactiveCount>0){
7254
7667
  document.getElementById('statTotal').title=activeCount+' '+t('stat.active')+', '+inactiveCount+' '+t('stat.deduped');
7255
7668
  }
7256
7669
  document.getElementById('statSessions').textContent=d.totalSessions||0;
7257
7670
  document.getElementById('statEmbeddings').textContent=d.totalEmbeddings||0;
7258
- let days=0;
7259
- if(d.timeRange&&d.timeRange.earliest!=null&&d.timeRange.latest!=null){
7260
- let e=Number(d.timeRange.earliest), l=Number(d.timeRange.latest);
7261
- if(Number.isFinite(e)&&Number.isFinite(l)){
7262
- if(e<1e12) e*=1000;
7263
- if(l<1e12) l*=1000;
7264
- days=Math.round((l-e)/86400000);
7265
- days=Math.max(0,Math.min(36500,days));
7266
- if(days===0) days=1;
7267
- }
7268
- }
7269
- document.getElementById('statTimeSpan').textContent=days;
7671
+ document.getElementById('statAgents').textContent=agentCount;
7270
7672
 
7271
7673
  const provEl=document.getElementById('embeddingStatus');
7272
7674
  if(d.embeddingProvider && d.embeddingProvider!=='none'){
@@ -7358,55 +7760,108 @@ function getFilterParams(){
7358
7760
  if(dt) p.set('dateTo',dt);
7359
7761
  const sort=document.getElementById('filterSort').value;
7360
7762
  if(sort==='oldest') p.set('sort','oldest');
7763
+ const scope=memorySearchScope||'local';
7764
+ if(scope==='local'){
7765
+ p.set('owner',_currentAgentOwner);
7766
+ }else if(scope==='allLocal'){
7361
7767
  const owner=document.getElementById('filterOwner').value;
7362
7768
  if(owner) p.set('owner',owner);
7769
+ }
7363
7770
  return p;
7364
7771
  }
7365
7772
 
7366
- async function loadMemories(page){
7773
+ /** Hub admin removed a shared memory — badge-only: clear team_shared_chunks (never touches chunks/embeddings/hub_memories recall data). */
7774
+ async function syncTeamShareRemovedFromNotifications(){
7775
+ try{
7776
+ var r=await fetch('/api/sharing/notifications');
7777
+ var d=await r.json();
7778
+ var list=d.notifications||[];
7779
+ for(var i=0;i<list.length;i++){
7780
+ var n=list[i];
7781
+ if(n.type!=='resource_removed'||n.resource!=='memory'||!n.message) continue;
7782
+ try{
7783
+ var meta=JSON.parse(n.message);
7784
+ if(meta.sourceChunkId){
7785
+ await fetch('/api/sharing/sync-hub-removal',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({sourceChunkId:meta.sourceChunkId,memoryId:meta.memoryId||''})});
7786
+ }
7787
+ }catch(e){}
7788
+ }
7789
+ }catch(e){}
7790
+ }
7791
+
7792
+ async function loadMemories(page,silent){
7367
7793
  if(page) currentPage=page;
7368
7794
  const list=document.getElementById('memoryList');
7369
- list.innerHTML='<div class="spinner"></div>';
7795
+ if(!silent) list.innerHTML='<div class="spinner"></div>';
7370
7796
  try{
7797
+ if(!silent) await syncTeamShareRemovedFromNotifications();
7371
7798
  const p=getFilterParams();
7372
7799
  p.set('limit',PAGE_SIZE);
7373
7800
  p.set('page',currentPage);
7374
7801
  const r=await fetch('/api/memories?'+p.toString());
7375
7802
  const d=await r.json();
7803
+ var items=d.memories||[];
7804
+ if(silent){
7805
+ var fp=JSON.stringify(items.map(function(m){return m.id+'|'+m.updated_at}));
7806
+ if(fp===_lastMemoriesFingerprint) return;
7807
+ _lastMemoriesFingerprint=fp;
7808
+ }else{
7809
+ _lastMemoriesFingerprint=JSON.stringify(items.map(function(m){return m.id+'|'+m.updated_at}));
7810
+ }
7376
7811
  totalPages=d.totalPages||1;
7377
7812
  totalCount=d.total||0;
7378
7813
  document.getElementById('searchMeta').textContent=totalCount+t('search.meta.total');
7379
- renderMemories(d.memories||[]);
7814
+ renderMemories(items);
7380
7815
  renderPagination();
7381
7816
  }catch(e){
7817
+ if(!silent){
7382
7818
  list.innerHTML='';
7383
7819
  totalPages=1;totalCount=0;
7820
+ _lastMemoriesFingerprint='';
7384
7821
  renderMemories([]);
7385
7822
  renderPagination();
7823
+ }
7386
7824
  }
7387
7825
  }
7388
7826
 
7389
- async function loadHubMemories(){
7827
+ async function loadHubMemories(silent){
7390
7828
  const list=document.getElementById('memoryList');
7391
- list.innerHTML='<div class="spinner"></div>';
7829
+ if(!silent) list.innerHTML='<div class="spinner"></div>';
7392
7830
  try{
7393
7831
  const r=await fetch('/api/sharing/memories/list?limit='+PAGE_SIZE);
7394
7832
  const d=await r.json();
7395
7833
  const items=d.memories||[];
7834
+ if(silent){
7835
+ var fp=JSON.stringify(items.map(function(m){return m.id+'|'+(m.updated_at||m.created_at)}));
7836
+ if(fp===_lastMemoriesFingerprint) return;
7837
+ _lastMemoriesFingerprint=fp;
7838
+ }else{
7839
+ _lastMemoriesFingerprint=JSON.stringify(items.map(function(m){return m.id+'|'+(m.updated_at||m.created_at)}));
7840
+ }
7841
+ totalPages=1;totalCount=items.length;currentPage=1;
7396
7842
  document.getElementById('searchMeta').textContent=items.length+t('search.meta.total');
7397
7843
  document.getElementById('sharingSearchMeta').textContent='';
7398
7844
  renderMemories(items);
7399
7845
  document.getElementById('pagination').innerHTML='';
7400
7846
  }catch(e){
7847
+ if(!silent){
7848
+ _lastMemoriesFingerprint='';
7401
7849
  document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
7402
7850
  renderMemories([]);
7403
7851
  document.getElementById('pagination').innerHTML='';
7852
+ }
7404
7853
  }
7405
7854
  }
7406
7855
 
7407
7856
  async function doSearch(query){
7408
7857
  query=(query||'').trim();
7409
- if(!query){loadMemories();return;}
7858
+ if(!query){
7859
+ currentPage=1;
7860
+ if(memorySearchScope==='hub') loadHubMemories();
7861
+ else loadMemories();
7862
+ return;
7863
+ }
7864
+ currentPage=1;
7410
7865
  var scope=document.getElementById('memorySearchScope')?.value||memorySearchScope||'local';
7411
7866
  var list=document.getElementById('memoryList');
7412
7867
  list.innerHTML='<div class="spinner"></div>';
@@ -7418,6 +7873,7 @@ async function doSearch(query){
7418
7873
  body:JSON.stringify({query:query,scope:scope,maxResults:20,role:activeRole||undefined})
7419
7874
  });
7420
7875
  var data=await r.json();
7876
+ totalPages=1;totalCount=(data.results||[]).length;
7421
7877
  renderSharingMemorySearchResults(data,query);
7422
7878
  }catch(e){
7423
7879
  document.getElementById('searchMeta').textContent='0'+t('search.meta.results');
@@ -7432,6 +7888,7 @@ async function doSearch(query){
7432
7888
  var r=await fetch('/api/search?'+p.toString());
7433
7889
  var d=await r.json();
7434
7890
  var total=d.total||0;
7891
+ totalPages=1;totalCount=total;
7435
7892
  var meta=[];
7436
7893
  if(d.vectorCount>0) meta.push(d.vectorCount+t('search.meta.semantic'));
7437
7894
  if(d.ftsCount>0) meta.push(d.ftsCount+t('search.meta.text'));
@@ -7512,7 +7969,7 @@ function renderMemories(items){
7512
7969
  const mergeBadge=mc>0?'<span class="merge-badge">\\u{1F504} '+t('card.evolved')+' '+mc+t('card.times')+'</span>':'';
7513
7970
  const updatedAt=(m.updated_at&&m.updated_at>m.created_at)?'<span class="card-updated">'+t('card.updated')+' '+new Date(m.updated_at).toLocaleString(dateLoc())+'</span>':'';
7514
7971
  const ds=m.dedup_status||'active';
7515
- const isInactive=ds==='merged';
7972
+ const isInactive=ds==='merged'||ds==='duplicate';
7516
7973
  const dedupBadge=ds==='duplicate'?'<span class="dedup-badge duplicate">'+t('card.dedupDuplicate')+'</span>':ds==='merged'?'<span class="dedup-badge merged">'+t('card.dedupMerged')+'</span>':'';
7517
7974
  const isImported=sid.startsWith('openclaw-import-')||sid.startsWith('openclaw-session-');
7518
7975
  const importBadge=isImported?'<span class="import-badge">\u{1F990} '+t('card.imported')+'</span>':'';
@@ -7520,8 +7977,9 @@ function renderMemories(items){
7520
7977
  const isPublicMem=ownerVal==='public';
7521
7978
  const localManaged=!!m.localSharingManaged;
7522
7979
  const memShared=m.sharingVisibility||null;
7980
+ const isHubScope=memorySearchScope==='hub';
7523
7981
  const memScope=memShared?'team':isPublicMem?'local':'private';
7524
- const memScopeBadge=renderScopeBadge(memScope);
7982
+ const memScopeBadge=isHubScope?renderScopeBadge('team'):renderScopeBadge(memScope);
7525
7983
  let dedupInfo='';
7526
7984
  if(ds==='duplicate'||ds==='merged'){
7527
7985
  const reason=m.dedup_reason?'<span style="font-size:11px;color:var(--text-muted)">'+t('card.dedupReason')+esc(m.dedup_reason)+'</span>':'';
@@ -7625,7 +8083,8 @@ function renderPagination(){
7625
8083
  function goPage(p){
7626
8084
  if(p<1||p>totalPages||p===currentPage) return;
7627
8085
  currentPage=p;
7628
- loadMemories();
8086
+ if(memorySearchScope==='hub') loadHubMemories();
8087
+ else loadMemories();
7629
8088
  document.getElementById('memoryList').scrollIntoView({behavior:'smooth',block:'start'});
7630
8089
  }
7631
8090
 
@@ -7705,6 +8164,11 @@ function esc(s){
7705
8164
  if(!s)return'';
7706
8165
  return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
7707
8166
  }
8167
+ function fmtOwner(item){
8168
+ var name=item.ownerName||item.sourceUserId||'unknown';
8169
+ if(item.ownerStatus==='removed') return esc(name)+' <span style="color:#ef4444;font-size:0.9em">'+t('sharing.ownerRemoved')+'</span>';
8170
+ return esc(name);
8171
+ }
7708
8172
 
7709
8173
  function renderSummaryHtml(raw){
7710
8174
  if(!raw)return'';
@@ -8431,18 +8895,25 @@ function confirmModal(message,opts){
8431
8895
  _confirmResolve=resolve;
8432
8896
  var overlay=document.getElementById('confirmOverlay');
8433
8897
  document.getElementById('confirmTitle').textContent=opts.title||t('confirm.title')||'\u786E\u8BA4';
8434
- document.getElementById('confirmBody').textContent=message||'';
8898
+ document.getElementById('confirmBody').innerText=message||'';
8435
8899
  var okBtn=document.getElementById('confirmOkBtn');
8436
8900
  okBtn.textContent=opts.okText||t('confirm.ok')||'\u786E\u5B9A';
8437
8901
  okBtn.className='btn-confirm-ok'+(opts.danger?' danger':'');
8438
- document.getElementById('confirmCancelBtn').textContent=opts.cancelText||t('confirm.cancel')||'\u53D6\u6D88';
8902
+ var cancelBtn=document.getElementById('confirmCancelBtn');
8903
+ cancelBtn.textContent=opts.cancelText||t('confirm.cancel')||'\u53D6\u6D88';
8904
+ cancelBtn.style.display=opts.hideCancel?'none':'';
8439
8905
  overlay.classList.add('show');
8440
8906
  });
8441
8907
  }
8442
8908
  function confirmModalClose(result){
8443
8909
  document.getElementById('confirmOverlay').classList.remove('show');
8910
+ document.getElementById('confirmCancelBtn').style.display='';
8444
8911
  if(_confirmResolve){var r=_confirmResolve;_confirmResolve=null;r(result);}
8445
8912
  }
8913
+ function alertModal(message,opts){
8914
+ opts=opts||{};
8915
+ return confirmModal(message,Object.assign({},opts,{hideCancel:true,okText:opts.okText||t('confirm.ok')||'\u77E5\u9053\u4E86'}));
8916
+ }
8446
8917
 
8447
8918
  /* ─── Theme ─── */
8448
8919
  const VIEWER_THEME_KEY='memos-viewer-theme';
@@ -8465,14 +8936,35 @@ function showRestartOverlay(msg){
8465
8936
  /* ─── Update check ─── */
8466
8937
  function waitForGatewayAndReload(maxAttempts,attempt){
8467
8938
  attempt=attempt||0;
8939
+ var phase=arguments.length>2?arguments[2]:'waitDown';
8940
+ var MAX_WAIT_DOWN=8;
8468
8941
  function forceReload(){window.location.href=window.location.pathname+'?_t='+Date.now();}
8469
8942
  if(attempt>=maxAttempts){forceReload();return;}
8943
+ var delay=phase==='waitDown'?1500:2500;
8470
8944
  setTimeout(function(){
8471
8945
  fetch('/api/auth/status').then(function(r){
8472
- if(r.ok||r.status===401||r.status===403) forceReload();
8473
- else waitForGatewayAndReload(maxAttempts,attempt+1);
8474
- }).catch(function(){waitForGatewayAndReload(maxAttempts,attempt+1);});
8475
- },3000);
8946
+ if(phase==='waitDown'){
8947
+ if(r.ok||r.status===401||r.status===403){
8948
+ if(attempt>=MAX_WAIT_DOWN){
8949
+ forceReload();
8950
+ }else{
8951
+ waitForGatewayAndReload(maxAttempts,attempt+1,'waitDown');
8952
+ }
8953
+ }else{
8954
+ waitForGatewayAndReload(maxAttempts,0,'waitUp');
8955
+ }
8956
+ }else{
8957
+ if(r.ok||r.status===401||r.status===403) forceReload();
8958
+ else waitForGatewayAndReload(maxAttempts,attempt+1,'waitUp');
8959
+ }
8960
+ }).catch(function(){
8961
+ if(phase==='waitDown'){
8962
+ waitForGatewayAndReload(maxAttempts,0,'waitUp');
8963
+ }else{
8964
+ waitForGatewayAndReload(maxAttempts,attempt+1,'waitUp');
8965
+ }
8966
+ });
8967
+ },delay);
8476
8968
  }
8477
8969
  function doUpdateInstall(packageSpec,btnEl,statusEl){
8478
8970
  btnEl.disabled=true;
@@ -8506,9 +8998,12 @@ async function checkForUpdate(){
8506
8998
  const d=await r.json();
8507
8999
  if(!d.updateAvailable)return;
8508
9000
  const pkgSpec=d.installCommand?d.installCommand.replace(/^(?:npx\s+)?openclaw\s+plugins\s+install\s+/,''):(d.packageName+'@'+d.latest);
9001
+ var bannerWrap=document.createElement('div');
9002
+ bannerWrap.id='updateBannerWrap';
9003
+ bannerWrap.style.cssText='background:linear-gradient(135deg,rgba(99,102,241,.08),rgba(139,92,246,.06));border-bottom:1px solid rgba(99,102,241,.18);backdrop-filter:blur(8px);animation:slideIn .3s ease';
8509
9004
  var banner=document.createElement('div');
8510
9005
  banner.id='updateBanner';
8511
- 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)';
9006
+ banner.style.cssText='display:flex;align-items:center;gap:12px;padding:10px 32px;width:100%;max-width:1400px;margin:0 auto;font-size:13px;font-weight:500;box-sizing:border-box;color:var(--pri)';
8512
9007
  var textNode=document.createElement('div');
8513
9008
  textNode.style.cssText='display:flex;align-items:center;gap:8px;flex-shrink:0;font-size:13px';
8514
9009
  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>';
@@ -8528,20 +9023,32 @@ async function checkForUpdate(){
8528
9023
  btnClose.innerHTML='&times;';
8529
9024
  btnClose.onmouseenter=function(){this.style.opacity='1'};
8530
9025
  btnClose.onmouseleave=function(){this.style.opacity='.5'};
8531
- btnClose.onclick=function(){banner.remove()};
9026
+ btnClose.onclick=function(){bannerWrap.remove()};
9027
+ var spacerL=document.createElement('div');
9028
+ spacerL.style.cssText='flex:1';
9029
+ banner.appendChild(spacerL);
8532
9030
  banner.appendChild(textNode);
8533
9031
  banner.appendChild(statusDiv);
8534
9032
  banner.appendChild(spacer);
8535
9033
  banner.appendChild(btnClose);
9034
+ bannerWrap.appendChild(banner);
8536
9035
  var tb=document.querySelector('.topbar');
8537
- if(tb&&tb.parentNode){tb.parentNode.insertBefore(banner,tb);}
8538
- else{document.body.insertBefore(banner,document.body.firstChild);}
9036
+ if(tb&&tb.parentNode){tb.parentNode.insertBefore(bannerWrap,tb);}
9037
+ else{document.body.insertBefore(bannerWrap,document.body.firstChild);}
8539
9038
  }catch(e){}
8540
9039
  }
8541
9040
 
8542
9041
  /* ─── Init ─── */
9042
+ try{
9043
+ var savedScope=localStorage.getItem('memos_memorySearchScope');
9044
+ if(savedScope&&(savedScope==='local'||savedScope==='allLocal'||savedScope==='hub')){
9045
+ memorySearchScope=savedScope;
9046
+ var scopeEl=document.getElementById('memorySearchScope');
9047
+ if(scopeEl) scopeEl.value=savedScope;
9048
+ }
9049
+ }catch(e){}
8543
9050
  document.getElementById('modalOverlay').addEventListener('click',e=>{if(e.target.id==='modalOverlay')closeModal()});
8544
- document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape'){e.target.value='';loadMemories()}});
9051
+ document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape'){e.target.value='';currentPage=1;if(memorySearchScope==='hub')loadHubMemories();else loadMemories();}});
8545
9052
  applyI18n();
8546
9053
  checkAuth();
8547
9054
  </script>