@memtensor/memos-local-openclaw-plugin 1.0.2-beta.5 → 1.0.2-beta.7

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 (90) hide show
  1. package/dist/capture/index.js +52 -8
  2. package/dist/capture/index.js.map +1 -1
  3. package/dist/embedding/index.d.ts.map +1 -1
  4. package/dist/embedding/index.js +4 -3
  5. package/dist/embedding/index.js.map +1 -1
  6. package/dist/ingest/chunker.d.ts +3 -4
  7. package/dist/ingest/chunker.d.ts.map +1 -1
  8. package/dist/ingest/chunker.js +19 -24
  9. package/dist/ingest/chunker.js.map +1 -1
  10. package/dist/ingest/providers/anthropic.d.ts +3 -1
  11. package/dist/ingest/providers/anthropic.d.ts.map +1 -1
  12. package/dist/ingest/providers/anthropic.js +79 -39
  13. package/dist/ingest/providers/anthropic.js.map +1 -1
  14. package/dist/ingest/providers/bedrock.d.ts +3 -1
  15. package/dist/ingest/providers/bedrock.d.ts.map +1 -1
  16. package/dist/ingest/providers/bedrock.js +79 -39
  17. package/dist/ingest/providers/bedrock.js.map +1 -1
  18. package/dist/ingest/providers/gemini.d.ts +3 -1
  19. package/dist/ingest/providers/gemini.d.ts.map +1 -1
  20. package/dist/ingest/providers/gemini.js +77 -39
  21. package/dist/ingest/providers/gemini.js.map +1 -1
  22. package/dist/ingest/providers/index.d.ts +3 -1
  23. package/dist/ingest/providers/index.d.ts.map +1 -1
  24. package/dist/ingest/providers/index.js +107 -30
  25. package/dist/ingest/providers/index.js.map +1 -1
  26. package/dist/ingest/providers/openai.d.ts +3 -1
  27. package/dist/ingest/providers/openai.d.ts.map +1 -1
  28. package/dist/ingest/providers/openai.js +80 -39
  29. package/dist/ingest/providers/openai.js.map +1 -1
  30. package/dist/ingest/task-processor.d.ts +1 -0
  31. package/dist/ingest/task-processor.d.ts.map +1 -1
  32. package/dist/ingest/task-processor.js +33 -9
  33. package/dist/ingest/task-processor.js.map +1 -1
  34. package/dist/ingest/worker.d.ts.map +1 -1
  35. package/dist/ingest/worker.js +29 -13
  36. package/dist/ingest/worker.js.map +1 -1
  37. package/dist/recall/engine.d.ts.map +1 -1
  38. package/dist/recall/engine.js +19 -14
  39. package/dist/recall/engine.js.map +1 -1
  40. package/dist/skill/bundled-memory-guide.d.ts +1 -5
  41. package/dist/skill/bundled-memory-guide.d.ts.map +1 -1
  42. package/dist/skill/bundled-memory-guide.js +38 -97
  43. package/dist/skill/bundled-memory-guide.js.map +1 -1
  44. package/dist/skill/evaluator.js +1 -1
  45. package/dist/storage/sqlite.d.ts +1 -2
  46. package/dist/storage/sqlite.d.ts.map +1 -1
  47. package/dist/storage/sqlite.js +90 -17
  48. package/dist/storage/sqlite.js.map +1 -1
  49. package/dist/tools/memory-get.d.ts.map +1 -1
  50. package/dist/tools/memory-get.js +1 -3
  51. package/dist/tools/memory-get.js.map +1 -1
  52. package/dist/types.d.ts +3 -3
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js +1 -1
  55. package/dist/types.js.map +1 -1
  56. package/dist/update-check.d.ts +21 -0
  57. package/dist/update-check.d.ts.map +1 -0
  58. package/dist/update-check.js +110 -0
  59. package/dist/update-check.js.map +1 -0
  60. package/dist/viewer/html.d.ts.map +1 -1
  61. package/dist/viewer/html.js +487 -189
  62. package/dist/viewer/html.js.map +1 -1
  63. package/dist/viewer/server.d.ts +1 -1
  64. package/dist/viewer/server.d.ts.map +1 -1
  65. package/dist/viewer/server.js +240 -78
  66. package/dist/viewer/server.js.map +1 -1
  67. package/index.ts +205 -197
  68. package/openclaw.plugin.json +3 -0
  69. package/package.json +8 -3
  70. package/scripts/postinstall.cjs +69 -2
  71. package/skill/memos-memory-guide/SKILL.md +73 -36
  72. package/src/capture/index.ts +52 -8
  73. package/src/embedding/index.ts +4 -2
  74. package/src/ingest/chunker.ts +22 -30
  75. package/src/ingest/providers/anthropic.ts +89 -41
  76. package/src/ingest/providers/bedrock.ts +90 -41
  77. package/src/ingest/providers/gemini.ts +89 -41
  78. package/src/ingest/providers/index.ts +118 -35
  79. package/src/ingest/providers/openai.ts +90 -41
  80. package/src/ingest/task-processor.ts +29 -8
  81. package/src/ingest/worker.ts +31 -13
  82. package/src/recall/engine.ts +20 -13
  83. package/src/skill/bundled-memory-guide.ts +5 -96
  84. package/src/skill/evaluator.ts +1 -1
  85. package/src/storage/sqlite.ts +93 -21
  86. package/src/tools/memory-get.ts +1 -4
  87. package/src/types.ts +9 -10
  88. package/src/update-check.ts +95 -0
  89. package/src/viewer/html.ts +487 -189
  90. package/src/viewer/server.ts +187 -66
@@ -41,7 +41,7 @@ return `<!DOCTYPE html>
41
41
  [data-theme="light"] .auth-screen{background:linear-gradient(135deg,#f0f4ff 0%,#f8f9fb 50%,#eef2ff 100%)}
42
42
  [data-theme="light"] .auth-card{box-shadow:0 25px 50px -12px rgba(0,0,0,.08)}
43
43
  [data-theme="light"] .topbar{background:rgba(255,255,255,.92);border-bottom-color:var(--border);backdrop-filter:blur(8px)}
44
- [data-theme="light"] .session-item .count,[data-theme="light"] .kind-tag,[data-theme="light"] .session-tag{background:rgba(0,0,0,.05)}
44
+ [data-theme="light"] .session-item .count,[data-theme="light"] .session-tag{background:rgba(0,0,0,.05)}
45
45
  [data-theme="light"] .card-content pre{background:#f3f4f6;border-color:var(--border)}
46
46
  [data-theme="light"] .vscore-badge{background:rgba(79,70,229,.06);color:#4f46e5}
47
47
  [data-theme="light"] ::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15)}
@@ -66,10 +66,6 @@ return `<!DOCTYPE html>
66
66
  [data-theme="light"] .tool-agg-table td{background:transparent}
67
67
  [data-theme="light"] .tool-agg-table tr:hover td{background:rgba(79,70,229,.03)}
68
68
  [data-theme="light"] .tool-agg-table th{color:#9ca3af}
69
- [data-theme="light"] .breakdown-item{background:#f9fafb;border-color:var(--border)}
70
- [data-theme="light"] .breakdown-item:hover{background:#f3f4f6;border-color:#cbd5e1}
71
- [data-theme="light"] .breakdown-bar-wrap{background:#e5e7eb}
72
- [data-theme="light"] .breakdown-bar{background:linear-gradient(90deg,#4f46e5,#6366f1);box-shadow:none}
73
69
  [data-theme="light"] .range-btn{background:transparent;border-color:var(--border);color:var(--text-sec)}
74
70
  [data-theme="light"] .range-btn.active{background:rgba(79,70,229,.06);color:#4f46e5;border-color:rgba(79,70,229,.2)}
75
71
  [data-theme="light"] .range-btn:hover{border-color:#4f46e5;color:#4f46e5}
@@ -164,10 +160,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
164
160
  .role-tag.user{background:var(--pri-glow);color:var(--pri);border:1px solid rgba(99,102,241,.12)}
165
161
  .role-tag.assistant{background:var(--accent-glow);color:var(--accent);border:1px solid rgba(230,57,70,.2)}
166
162
  .role-tag.system{background:var(--amber-bg);color:var(--amber);border:1px solid rgba(245,158,11,.2)}
167
- .kind-tag{padding:4px 10px;border-radius:8px;font-size:11px;color:var(--text-sec);background:rgba(0,0,0,.2);font-weight:500}
168
163
  .card-time{font-size:12px;color:var(--text-sec);display:flex;align-items:center;gap:8px}
169
164
  .session-tag{font-size:11px;font-family:ui-monospace,monospace;color:var(--text-muted);background:rgba(0,0,0,.2);padding:3px 8px;border-radius:6px;cursor:default}
170
- .card-summary{font-size:15px;font-weight:600;color:var(--text);margin-bottom:10px;line-height:1.5;letter-spacing:-.01em}
165
+ .card-summary{font-size:15px;font-weight:600;color:var(--text);margin-bottom:10px;line-height:1.5;letter-spacing:-.01em;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
171
166
  .card-content{font-size:13px;color:var(--text-sec);line-height:1.65;max-height:0;overflow:hidden;transition:max-height .3s ease}
172
167
  .card-content.show{max-height:600px;overflow-y:auto}
173
168
  .card-content pre{white-space:pre-wrap;word-break:break-all;background:rgba(0,0,0,.25);padding:14px;border-radius:10px;font-size:12px;font-family:ui-monospace,monospace;margin-top:10px;border:1px solid var(--border);color:var(--text-sec)}
@@ -212,6 +207,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
212
207
  .modal-meta-row{display:flex;flex-wrap:wrap;gap:12px;font-size:11px;color:var(--text-sec);padding:8px 0;border-top:1px dashed var(--border)}
213
208
  [data-theme="light"] .merge-history{background:rgba(0,0,0,.04)}
214
209
  [data-theme="light"] .merge-history-item{border-bottom-color:rgba(0,0,0,.06)}
210
+ .card-merged-info{margin-top:8px;padding:8px 12px;background:rgba(16,185,129,.06);border:1px dashed rgba(16,185,129,.2);border-radius:8px;font-size:12px;line-height:1.6;color:var(--text-sec)}
211
+ .card-merged-label{font-size:10px;font-weight:600;color:#10b981;margin-bottom:4px;display:flex;align-items:center;gap:4px}
212
+ [data-theme="light"] .card-merged-info{background:rgba(16,185,129,.04);border-color:rgba(16,185,129,.15)}
215
213
 
216
214
  /* ─── Buttons ─── */
217
215
  .btn{padding:7px 14px;border-radius:8px;border:1px solid var(--border);background:var(--bg-card);color:var(--text);font-size:13px;font-weight:500;transition:all .18s ease;display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
@@ -479,7 +477,41 @@ input,textarea,select{font-family:inherit;font-size:inherit}
479
477
  .log-stat-chip.merged{background:rgba(168,85,247,.12);color:#c084fc}
480
478
  .log-stat-chip.errors{background:rgba(248,113,113,.12);color:#f87171}
481
479
  .log-msg-list{margin-top:8px;display:flex;flex-direction:column;gap:4px}
482
- .log-msg-item{display:flex;gap:8px;align-items:flex-start;font-size:11.5px;line-height:1.5;padding:4px 10px;border-radius:6px;background:rgba(255,255,255,.02)}
480
+ .log-msg-item{display:flex;gap:8px;align-items:flex-start;font-size:11.5px;line-height:1.5;padding:4px 10px;border-radius:6px;background:rgba(255,255,255,.02);overflow:hidden}
481
+ .log-msg-item.expanded{flex-wrap:wrap}
482
+ .recall-layers{margin-top:8px;display:flex;flex-direction:column;gap:10px}
483
+ .recall-layer-title{font-size:11px;font-weight:600;color:var(--text-sec);margin-bottom:4px;display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none}
484
+ .recall-layer-title .recall-expand-icon{transition:transform .15s;font-size:9px}
485
+ .recall-layer.expanded .recall-layer-title .recall-expand-icon{transform:rotate(90deg)}
486
+ .recall-count{font-size:10px;font-weight:700;padding:1px 6px;border-radius:10px;background:rgba(99,102,241,.1);color:var(--pri)}
487
+ .recall-items{display:none;flex-direction:column;gap:3px}
488
+ .recall-layer.expanded .recall-items{display:flex}
489
+ .recall-item{font-size:11px;line-height:1.4;padding:4px 8px;border-radius:5px;background:rgba(255,255,255,.02);cursor:pointer}
490
+ .recall-item:hover{background:rgba(99,102,241,.06)}
491
+ [data-theme="light"] .recall-item{background:rgba(0,0,0,.02)}
492
+ [data-theme="light"] .recall-item:hover{background:rgba(99,102,241,.06)}
493
+ .recall-item-head{display:flex;gap:6px;align-items:center}
494
+ .recall-idx{flex-shrink:0;font-size:10px;font-weight:600;color:var(--text-muted);min-width:14px;text-align:right}
495
+ .recall-score{flex-shrink:0;font-family:'SF Mono',Consolas,monospace;font-size:10px;font-weight:600;padding:1px 5px;border-radius:4px}
496
+ .recall-score.high{background:rgba(34,197,94,.12);color:#22c55e}
497
+ .recall-score.mid{background:rgba(251,191,36,.12);color:#f59e0b}
498
+ .recall-score.low{background:rgba(248,113,113,.1);color:var(--text-muted)}
499
+ .recall-summary-short{flex:1;color:var(--text-sec);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
500
+ .recall-expand-icon{flex-shrink:0;font-size:10px;color:var(--text-muted);transition:transform .15s}
501
+ .recall-item.expanded .recall-expand-icon{transform:rotate(90deg)}
502
+ .recall-summary-full{display:none;margin-top:4px;padding:6px 8px 4px 28px;font-size:11px;line-height:1.5;color:var(--text);word-break:break-word;border-top:1px dashed var(--border)}
503
+ .recall-item.expanded .recall-summary-full{display:block}
504
+ .recall-layer.filtered .recall-layer-title{color:var(--pri)}
505
+ .recall-layer.filtered.empty .recall-layer-title{color:var(--text-muted)}
506
+ .recall-more{font-size:10px;color:var(--text-muted);padding:2px 8px}
507
+ .recall-detail{padding:4px 0}
508
+ .recall-detail-section{margin-bottom:10px}
509
+ .recall-detail-title{font-size:11px;font-weight:600;color:var(--text-sec);margin-bottom:6px;padding-bottom:4px;border-bottom:1px dashed var(--border);cursor:pointer;user-select:none;display:flex;align-items:center;gap:6px}
510
+ .recall-detail-title .recall-expand-icon{transition:transform .15s;font-size:9px}
511
+ .recall-detail-section.expanded .recall-detail-title .recall-expand-icon{transform:rotate(90deg)}
512
+ .recall-detail-section .recall-detail-items{display:none;flex-direction:column;gap:3px}
513
+ .recall-detail-section.expanded .recall-detail-items{display:flex}
514
+ .recall-detail-section.filtered .recall-detail-title{color:var(--pri)}
483
515
  [data-theme="light"] .log-msg-item{background:rgba(0,0,0,.02)}
484
516
  .log-msg-role{flex-shrink:0;font-size:10px;font-weight:600;padding:1px 6px;border-radius:4px;text-transform:uppercase;letter-spacing:.3px}
485
517
  .log-msg-role.user{background:rgba(59,130,246,.12);color:#60a5fa}
@@ -492,6 +524,15 @@ input,textarea,select{font-family:inherit;font-size:inherit}
492
524
  .log-msg-action.merged{color:#c084fc}
493
525
  .log-msg-action.error{color:#f87171}
494
526
  .log-msg-text{color:var(--text);opacity:.85;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
527
+ .log-msg-text-short{color:var(--text);opacity:.85;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
528
+ .log-msg-text-full{display:none;color:var(--text);opacity:.85;flex:1;min-width:0;word-break:break-word;white-space:pre-wrap}
529
+ .log-msg-item.expanded .log-msg-text-short{display:none}
530
+ .log-msg-item.expanded .log-msg-text-full{display:block}
531
+ .log-msg-item.expanded .recall-expand-icon{transform:rotate(90deg)}
532
+ .log-add-detail{display:flex;flex-direction:column;gap:8px}
533
+ .log-add-msg{display:flex;gap:8px;align-items:flex-start;font-size:12px;line-height:1.6}
534
+ .log-add-msg-role{flex-shrink:0;font-size:10px;font-weight:600;text-transform:uppercase;padding:2px 8px;border-radius:4px;background:rgba(99,102,241,.1);color:var(--pri)}
535
+ .log-add-msg-content{flex:1;min-width:0;word-break:break-word;white-space:pre-wrap;color:var(--text)}
495
536
  .log-detail{display:none;border-top:1px solid var(--border);padding:0}
496
537
  .log-detail.open{display:block}
497
538
  .log-expand-btn{font-size:10px;color:var(--text-sec);opacity:.5;margin-left:auto;transition:transform .2s,opacity .15s;display:inline-block}
@@ -545,7 +586,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
545
586
  [data-theme="light"] .settings-actions .btn-primary:hover{background:rgba(79,70,229,.1);border-color:#4f46e5}
546
587
  .settings-saved{display:inline-flex;align-items:center;gap:6px;color:var(--green);font-size:12px;font-weight:600;opacity:0;transition:opacity .3s}
547
588
  .settings-saved.show{opacity:1}
548
- .model-health-bar{margin-bottom:20px;border-radius:var(--radius-lg);overflow:hidden}
589
+ .model-health-bar{margin-bottom:20px;border-radius:var(--radius-lg);overflow:visible}
549
590
  .mh-table{width:100%;border-collapse:separate;border-spacing:0;font-size:12px}
550
591
  .mh-table th{text-align:left;padding:6px 12px;font-size:10px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;background:var(--bg);border-bottom:1px solid var(--border)}
551
592
  .mh-table td{padding:8px 12px;border-bottom:1px solid var(--border);vertical-align:middle}
@@ -563,7 +604,8 @@ input,textarea,select{font-family:inherit;font-size:inherit}
563
604
  .mh-badge.error{background:rgba(239,68,68,.1);color:#dc2626}
564
605
  .mh-badge.unknown{background:rgba(148,163,184,.1);color:#64748b}
565
606
  .mh-model-name{color:var(--text-muted);font-size:11px;font-family:var(--font-mono,'SFMono-Regular',Consolas,monospace)}
566
- .mh-err-text{font-size:11px;color:var(--rose);max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:help}
607
+ .mh-err-text{font-size:11px;color:var(--rose);max-width:320px;display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:help}
608
+ #mhTooltip{display:none;position:fixed;min-width:280px;max-width:480px;max-height:300px;overflow-y:auto;padding:8px 10px;background:var(--bg-card,#1e1e2e);color:var(--text,#e2e8f0);border:1px solid var(--border,#333);border-radius:6px;font-size:11px;line-height:1.5;white-space:pre-wrap;word-break:break-all;box-shadow:0 4px 12px rgba(0,0,0,.25);z-index:10000;pointer-events:none}
567
609
  .mh-time{font-size:10px;color:var(--text-muted);white-space:nowrap}
568
610
  .mh-empty{padding:16px;font-size:12px;color:var(--text-muted);text-align:center}
569
611
  @keyframes healthPulse{0%,100%{opacity:1}50%{opacity:.4}}
@@ -645,14 +687,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
645
687
  .tool-agg-table .ms-val.slow{color:var(--accent)}
646
688
  .chart-legend .dot.violet{background:var(--violet)}
647
689
  .chart-legend .dot.green{background:var(--green)}
648
- .breakdown-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:20px}
649
- .breakdown-item{display:flex;flex-direction:column;gap:5px;padding:10px 12px;background:rgba(255,255,255,.02);border-radius:8px;border:1px solid var(--border);transition:all .15s}
650
- .breakdown-item:hover{border-color:var(--border-glow);background:rgba(255,255,255,.04)}
651
- .breakdown-item .bd-top{display:flex;align-items:center;justify-content:space-between}
652
- .breakdown-item .label{font-size:12px;color:var(--text-sec);font-weight:500;text-transform:capitalize}
653
- .breakdown-item .value{font-size:13px;font-weight:600;color:var(--text)}
654
- .breakdown-bar-wrap{height:3px;background:rgba(255,255,255,.06);border-radius:2px;overflow:hidden}
655
- .breakdown-bar{height:100%;border-radius:2px;background:var(--pri);transition:width .5s ease}
656
690
  .metrics-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
657
691
  .range-btn{padding:5px 12px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--text-sec);font-size:12px;font-weight:500;cursor:pointer;transition:all .15s}
658
692
  .range-btn:hover{border-color:var(--pri);color:var(--pri)}
@@ -809,15 +843,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
809
843
  <button class="filter-chip" data-role="assistant" onclick="setRoleFilter(this,'assistant')">Assistant</button>
810
844
  <button class="filter-chip" data-role="system" onclick="setRoleFilter(this,'system')">System</button>
811
845
  <span class="filter-sep"></span>
812
- <select id="filterKind" class="filter-select" onchange="applyFilters()">
813
- <option value="" data-i18n="filter.allkinds">All kinds</option>
814
- <option value="paragraph" data-i18n="filter.paragraph">Paragraph</option>
815
- <option value="code_block" data-i18n="filter.code">Code</option>
816
- <option value="dialog" data-i18n="filter.dialog">Dialog</option>
817
- <option value="list" data-i18n="filter.list">List</option>
818
- <option value="error_stack" data-i18n="filter.error">Error</option>
819
- <option value="command" data-i18n="filter.command">Command</option>
820
- </select>
821
846
  <select id="filterSort" class="filter-select" onchange="applyFilters()">
822
847
  <option value="newest" data-i18n="filter.newest">Newest first</option>
823
848
  <option value="oldest" data-i18n="filter.oldest">Oldest first</option>
@@ -951,16 +976,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
951
976
  <div id="toolAggTable" style="margin-top:20px"></div>
952
977
  </div>
953
978
 
954
- <div class="breakdown-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
955
- <div class="analytics-section">
956
- <h3><span class="icon">\u{1F464}</span> <span data-i18n="breakdown.role">By Role</span></h3>
957
- <div id="breakdownRole"></div>
958
- </div>
959
- <div class="analytics-section">
960
- <h3><span class="icon">\u{1F4DD}</span> <span data-i18n="breakdown.kind">By Kind</span></h3>
961
- <div id="breakdownKind"></div>
962
- </div>
963
- </div>
964
979
  </div>
965
980
 
966
981
  <!-- ─── Logs View ─── -->
@@ -1200,7 +1215,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1200
1215
  </div>
1201
1216
 
1202
1217
  <div id="migrateActions" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
1203
- <button class="btn btn-ghost" onclick="migrateScan()" id="migrateScanBtn" data-i18n="migrate.scan">Scan Data Sources</button>
1218
+ <button class="btn" onclick="migrateScan(true)" id="migrateScanBtn" style="background:var(--bg);border:1px solid var(--border);color:var(--text);font-weight:600;padding:7px 18px;cursor:pointer" data-i18n="migrate.scan">Scan Data Sources</button>
1204
1219
  <button class="btn btn-primary" onclick="migrateStart()" id="migrateStartBtn" style="display:none" data-i18n="migrate.start">Start Import</button>
1205
1220
  <span id="migrateConcurrencyRow" style="display:none;align-items:center;gap:6px">
1206
1221
  <span style="font-size:11px;color:var(--text-muted)" data-i18n="migrate.concurrency.label">Concurrent agents</span>
@@ -1341,14 +1356,13 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1341
1356
  <!-- ─── Memory Modal ─── -->
1342
1357
  <div class="modal-overlay" id="modalOverlay">
1343
1358
  <div class="modal">
1344
- <h2 id="modalTitle" data-i18n="modal.new">New Memory</h2>
1359
+ <h2 id="modalTitle" data-i18n="modal.edit">Edit Memory</h2>
1345
1360
  <div class="form-group"><label data-i18n="modal.role">Role</label><select id="mRole"><option value="user">User</option><option value="assistant">Assistant</option><option value="system">System</option></select></div>
1346
1361
  <div class="form-group"><label data-i18n="modal.content">Content</label><textarea id="mContent" rows="4" data-i18n-ph="modal.content.ph" placeholder="Memory content..."></textarea></div>
1347
1362
  <div class="form-group"><label data-i18n="modal.summary">Summary</label><input type="text" id="mSummary" data-i18n-ph="modal.summary.ph" placeholder="Brief summary (optional)"></div>
1348
- <div class="form-group"><label data-i18n="modal.kind">Kind</label><select id="mKind"><option value="paragraph" data-i18n="filter.paragraph">Paragraph</option><option value="code" data-i18n="filter.code">Code</option><option value="dialog" data-i18n="filter.dialog">Dialog</option></select></div>
1349
1363
  <div class="modal-actions">
1350
1364
  <button class="btn btn-ghost" onclick="closeModal()" data-i18n="modal.cancel">Cancel</button>
1351
- <button class="btn btn-primary" id="modalSubmit" onclick="submitModal()" data-i18n="modal.create">Create</button>
1365
+ <button class="btn btn-primary" id="modalSubmit" onclick="submitModal()" data-i18n="modal.save">Save</button>
1352
1366
  </div>
1353
1367
  </div>
1354
1368
  </div>
@@ -1438,13 +1452,6 @@ const I18N={
1438
1452
  'search.meta.text':' text',
1439
1453
  'search.meta.results':' results',
1440
1454
  'filter.all':'All',
1441
- 'filter.allkinds':'All kinds',
1442
- 'filter.paragraph':'Paragraph',
1443
- 'filter.code':'Code',
1444
- 'filter.dialog':'Dialog',
1445
- 'filter.list':'List',
1446
- 'filter.error':'Error',
1447
- 'filter.command':'Command',
1448
1455
  'filter.newest':'Newest first',
1449
1456
  'filter.oldest':'Oldest first',
1450
1457
  'filter.allowners':'All owners',
@@ -1460,6 +1467,8 @@ const I18N={
1460
1467
  'card.delete':'Delete',
1461
1468
  'card.evolved':'Evolved',
1462
1469
  'card.times':'times',
1470
+ 'card.newMessage':'New message',
1471
+ 'card.mergedInfo':'Merged memory',
1463
1472
  'card.updated':'updated',
1464
1473
  'card.evolveHistory':'Evolution History',
1465
1474
  'card.oldSummary':'Old',
@@ -1483,21 +1492,15 @@ const I18N={
1483
1492
  'chart.toolperf':'Tool Response Time',
1484
1493
  'chart.list':'List',
1485
1494
  'chart.search':'Search',
1486
- 'breakdown.role':'By Role',
1487
- 'breakdown.kind':'By Kind',
1488
- 'modal.new':'New Memory',
1489
1495
  'modal.edit':'Edit Memory',
1490
1496
  'modal.role':'Role',
1491
1497
  'modal.content':'Content',
1492
1498
  'modal.content.ph':'Memory content...',
1493
1499
  'modal.summary':'Summary',
1494
1500
  'modal.summary.ph':'Brief summary (optional)',
1495
- 'modal.kind':'Kind',
1496
1501
  'modal.cancel':'Cancel',
1497
- 'modal.create':'Create',
1498
1502
  'modal.save':'Save',
1499
1503
  'modal.err.empty':'Please enter content',
1500
- 'toast.created':'Memory created',
1501
1504
  'toast.updated':'Memory updated',
1502
1505
  'toast.deleted':'Memory deleted',
1503
1506
  'toast.opfail':'Operation failed',
@@ -1524,6 +1527,11 @@ const I18N={
1524
1527
  'logs.output':'OUTPUT',
1525
1528
  'logs.empty':'No logs yet. Logs will appear here when tools are called.',
1526
1529
  'logs.ago':'ago',
1530
+ 'logs.recall.initial':'Initial Retrieval',
1531
+ 'logs.recall.filtered':'LLM Filtered',
1532
+ 'logs.recall.noHits':'No matching memories',
1533
+ 'logs.recall.noneRelevant':'LLM filter: none relevant',
1534
+ 'logs.recall.more':'{n} more...',
1527
1535
  'tab.import':'\u{1F4E5} Import',
1528
1536
  'tab.settings':'\u2699 Settings',
1529
1537
  'settings.modelconfig':'Model Configuration',
@@ -1552,6 +1560,7 @@ const I18N={
1552
1560
  'settings.test.loading':'Testing...',
1553
1561
  'settings.test.ok':'Connected',
1554
1562
  'settings.test.fail':'Failed',
1563
+ 'settings.session.expired':'Session expired, please refresh the page to log in again',
1555
1564
  'settings.save':'Save Settings',
1556
1565
  'settings.reset':'Reset',
1557
1566
  'settings.saved':'Saved',
@@ -1584,12 +1593,18 @@ const I18N={
1584
1593
  'migrate.scan':'Scan Data Sources',
1585
1594
  'migrate.start':'Start Import',
1586
1595
  'migrate.scanning':'Scanning...',
1596
+ 'migrate.scan.required':'Please scan data sources first',
1597
+ 'migrate.scan.done':'Scan complete \u2014 {n} new items found',
1598
+ 'migrate.imported.hint':'{n} items already imported',
1599
+ 'migrate.reconnect.hint':'--- {n} items processed before page reload ---',
1587
1600
  'migrate.stat.stored':'Stored',
1588
1601
  'migrate.stat.skipped':'Skipped',
1589
1602
  'migrate.stat.merged':'Merged',
1590
1603
  'migrate.stat.errors':'Errors',
1591
1604
  'migrate.phase.sqlite':'Importing memory index...',
1592
1605
  'migrate.phase.sessions':'Importing conversation history...',
1606
+ 'migrate.phase.stopped':'Import stopped',
1607
+ 'migrate.phase.done':'Import completed',
1593
1608
  'migrate.chunks':'chunks',
1594
1609
  'migrate.sessions.count':'sessions, {n} messages',
1595
1610
  'migrate.nodata':'No OpenClaw data found to import.',
@@ -1615,7 +1630,8 @@ const I18N={
1615
1630
  'pp.select.warn':'Please select at least one option.',
1616
1631
  'pp.skill.created':'Skill created',
1617
1632
  'pp.stat.tasks':'Tasks',
1618
- 'pp.stat.skills':'Skills',
1633
+ 'pp.stat.skills':'Evolutions',
1634
+ 'pp.stat.skills.total':'Skills',
1619
1635
  'pp.stat.errors':'Errors',
1620
1636
  'pp.stat.skipped':'Skipped',
1621
1637
  'pp.info.skipped':'{n} sessions already processed, skipping.',
@@ -1675,6 +1691,11 @@ const I18N={
1675
1691
  'skill.save.error':'Failed to save skill: ',
1676
1692
  'update.available':'New version available',
1677
1693
  'update.run':'Run',
1694
+ 'update.btn':'Update',
1695
+ 'update.installing':'Installing...',
1696
+ 'update.success':'Updated!',
1697
+ 'update.failed':'Update failed',
1698
+ 'update.restarting':'Restarting service...',
1678
1699
  'update.dismiss':'Dismiss'
1679
1700
  },
1680
1701
  zh:{
@@ -1753,13 +1774,6 @@ const I18N={
1753
1774
  'search.meta.text':' 文本',
1754
1775
  'search.meta.results':' 条结果',
1755
1776
  'filter.all':'全部',
1756
- 'filter.allkinds':'所有类型',
1757
- 'filter.paragraph':'段落',
1758
- 'filter.code':'代码',
1759
- 'filter.dialog':'对话',
1760
- 'filter.list':'列表',
1761
- 'filter.error':'错误',
1762
- 'filter.command':'命令',
1763
1777
  'filter.newest':'最新优先',
1764
1778
  'filter.oldest':'最早优先',
1765
1779
  'filter.allowners':'所有归属',
@@ -1775,6 +1789,8 @@ const I18N={
1775
1789
  'card.delete':'删除',
1776
1790
  'card.evolved':'已演化',
1777
1791
  'card.times':'次',
1792
+ 'card.newMessage':'新消息',
1793
+ 'card.mergedInfo':'合并记忆',
1778
1794
  'card.updated':'更新于',
1779
1795
  'card.evolveHistory':'演化记录',
1780
1796
  'card.oldSummary':'旧摘要',
@@ -1798,21 +1814,15 @@ const I18N={
1798
1814
  'chart.toolperf':'工具响应耗时',
1799
1815
  'chart.list':'列表',
1800
1816
  'chart.search':'搜索',
1801
- 'breakdown.role':'按角色',
1802
- 'breakdown.kind':'按类型',
1803
- 'modal.new':'新建记忆',
1804
1817
  'modal.edit':'编辑记忆',
1805
1818
  'modal.role':'角色',
1806
1819
  'modal.content':'内容',
1807
1820
  'modal.content.ph':'记忆内容...',
1808
1821
  'modal.summary':'摘要',
1809
1822
  'modal.summary.ph':'简要摘要(可选)',
1810
- 'modal.kind':'类型',
1811
1823
  'modal.cancel':'取消',
1812
- 'modal.create':'创建',
1813
1824
  'modal.save':'保存',
1814
1825
  'modal.err.empty':'请输入内容',
1815
- 'toast.created':'记忆已创建',
1816
1826
  'toast.updated':'记忆已更新',
1817
1827
  'toast.deleted':'记忆已删除',
1818
1828
  'toast.opfail':'操作失败',
@@ -1839,6 +1849,11 @@ const I18N={
1839
1849
  'logs.output':'输出',
1840
1850
  'logs.empty':'暂无日志。当工具被调用时日志会显示在这里。',
1841
1851
  'logs.ago':'前',
1852
+ 'logs.recall.initial':'初始检索',
1853
+ 'logs.recall.filtered':'LLM 过滤后',
1854
+ 'logs.recall.noHits':'未匹配到记忆',
1855
+ 'logs.recall.noneRelevant':'LLM 过滤:无相关记忆',
1856
+ 'logs.recall.more':'还有 {n} 条...',
1842
1857
  'tab.import':'\u{1F4E5} 导入',
1843
1858
  'tab.settings':'\u2699 设置',
1844
1859
  'settings.modelconfig':'模型配置',
@@ -1867,6 +1882,7 @@ const I18N={
1867
1882
  'settings.test.loading':'测试中...',
1868
1883
  'settings.test.ok':'连接成功',
1869
1884
  'settings.test.fail':'连接失败',
1885
+ 'settings.session.expired':'登录已过期,请刷新页面重新登录',
1870
1886
  'settings.save':'保存设置',
1871
1887
  'settings.reset':'重置',
1872
1888
  'settings.saved':'已保存',
@@ -1899,12 +1915,18 @@ const I18N={
1899
1915
  'migrate.scan':'扫描数据源',
1900
1916
  'migrate.start':'开始导入',
1901
1917
  'migrate.scanning':'扫描中...',
1918
+ 'migrate.scan.required':'请先扫描数据源',
1919
+ 'migrate.scan.done':'扫描完成 — 发现 {n} 条新数据可导入',
1920
+ 'migrate.imported.hint':'已导入 {n} 条记忆',
1921
+ 'migrate.reconnect.hint':'--- 页面刷新前已处理 {n} 条 ---',
1902
1922
  'migrate.stat.stored':'已存储',
1903
1923
  'migrate.stat.skipped':'已跳过',
1904
1924
  'migrate.stat.merged':'已合并',
1905
1925
  'migrate.stat.errors':'错误',
1906
1926
  'migrate.phase.sqlite':'正在导入记忆索引...',
1907
1927
  'migrate.phase.sessions':'正在导入对话历史...',
1928
+ 'migrate.phase.stopped':'导入已停止',
1929
+ 'migrate.phase.done':'导入完成',
1908
1930
  'migrate.chunks':'条记忆',
1909
1931
  'migrate.sessions.count':'个会话,{n} 条消息',
1910
1932
  'migrate.nodata':'未找到可导入的 OpenClaw 数据。',
@@ -1930,7 +1952,8 @@ const I18N={
1930
1952
  'pp.select.warn':'请至少选择一个选项。',
1931
1953
  'pp.skill.created':'技能已创建',
1932
1954
  'pp.stat.tasks':'任务',
1933
- 'pp.stat.skills':'技能',
1955
+ 'pp.stat.skills':'进化',
1956
+ 'pp.stat.skills.total':'技能',
1934
1957
  'pp.stat.errors':'错误',
1935
1958
  'pp.stat.skipped':'已跳过',
1936
1959
  'pp.info.skipped':'已有 {n} 个会话处理过,自动跳过。',
@@ -1990,6 +2013,11 @@ const I18N={
1990
2013
  'skill.save.error':'保存技能失败:',
1991
2014
  'update.available':'发现新版本',
1992
2015
  'update.run':'执行命令',
2016
+ 'update.btn':'更新',
2017
+ 'update.installing':'安装中...',
2018
+ 'update.success':'更新完成',
2019
+ 'update.failed':'更新失败',
2020
+ 'update.restarting':'正在重启服务...',
1993
2021
  'update.dismiss':'关闭'
1994
2022
  }
1995
2023
  };
@@ -2139,7 +2167,7 @@ function switchView(view){
2139
2167
  loadModelHealth();
2140
2168
  } else if(view==='import'){
2141
2169
  migrateView.classList.add('show');
2142
- if(!window._migrateRunning) migrateScan();
2170
+ if(!window._migrateRunning) migrateScan(false);
2143
2171
  }
2144
2172
  }
2145
2173
  }
@@ -2207,6 +2235,39 @@ function formatLogTime(ts){
2207
2235
  return y+'-'+m+'-'+day+' '+time;
2208
2236
  }
2209
2237
 
2238
+ function parseMemoryAddEntries(out){
2239
+ var lines=out.split('\\n');
2240
+ var results=[];
2241
+ for(var i=0;i<lines.length;i++){
2242
+ var line=lines[i].trim();
2243
+ if(!line) continue;
2244
+ if(line.startsWith('{')){
2245
+ try{
2246
+ var obj=JSON.parse(line);
2247
+ if(obj.role&&obj.action){results.push({role:obj.role,action:obj.action,summary:obj.summary||'',content:obj.content||'',reason:obj.reason||''});continue;}
2248
+ }catch(e){}
2249
+ }
2250
+ var rm=line.match(/^\\[(\\w+)\\]\\s*([^\u2192]+)\u2192/);
2251
+ if(rm){
2252
+ var role=rm[1],actionRaw=rm[2].trim();
2253
+ var action='stored';
2254
+ if(actionRaw.indexOf('exact-dup')>=0||actionRaw.indexOf('\u23ED')>=0) action='exact-dup';
2255
+ else if(actionRaw.indexOf('dedup')>=0||actionRaw.indexOf('\uD83D\uDD01')>=0) action='dedup';
2256
+ else if(actionRaw.indexOf('merged')>=0||actionRaw.indexOf('\uD83D\uDD00')>=0) action='merged';
2257
+ else if(actionRaw.indexOf('error')>=0||actionRaw.indexOf('\u274C')>=0) action='error';
2258
+ var afterArrow=line.replace(/^\\[\\w+\\]\\s*[^\u2192]+\u2192\\s*/,'');
2259
+ var contentLines=[afterArrow];
2260
+ while(i+1<lines.length&&!lines[i+1].trim().startsWith('[')&&!lines[i+1].trim().startsWith('{')){
2261
+ i++;
2262
+ if(lines[i].trim()) contentLines.push(lines[i]);
2263
+ else contentLines.push('');
2264
+ }
2265
+ results.push({role:role,action:action,summary:'',content:contentLines.join('\\n'),reason:''});
2266
+ }
2267
+ }
2268
+ return results;
2269
+ }
2270
+
2210
2271
  function buildLogSummary(lg){
2211
2272
  let inputObj=null;
2212
2273
  try{inputObj=JSON.parse(lg.input);}catch(_){}
@@ -2214,11 +2275,54 @@ function buildLogSummary(lg){
2214
2275
  const tn=lg.toolName;
2215
2276
  if(tn==='memory_search'&&inputObj){
2216
2277
  const q=inputObj.query||'';
2217
- if(q) html+='<div class="log-summary-query">'+escapeHtml(q.length>200?q.slice(0,200)+'...':q)+'</div>';
2218
- const outLines=(lg.output||'').split('\\n');
2219
- const memCount=outLines.filter(l=>l.match(/^\\d+\\.\\s*\\[/)).length;
2220
- if(memCount>0) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u{1F4CE} '+memCount+' memories retrieved</div>';
2221
- else if(lg.output&&lg.output.includes('no hits')) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u2205 No matching memories</div>';
2278
+ if(q) html+='<div class="log-summary-query">'+escapeHtml(q)+'</div>';
2279
+ var recallData=null;
2280
+ try{recallData=JSON.parse(lg.output);}catch(_){}
2281
+ if(recallData&&recallData.candidates){
2282
+ var cands=recallData.candidates||[];
2283
+ var filtered=recallData.filtered||[];
2284
+ if(cands.length===0){
2285
+ html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u2205 '+t('logs.recall.noHits')+'</div>';
2286
+ }else{
2287
+ html+='<div class="recall-layers">';
2288
+ html+='<div class="recall-layer" onclick="this.classList.toggle(\\\'expanded\\\')">';
2289
+ html+='<div class="recall-layer-title"><span class="recall-expand-icon">\u25B6</span>\u{1F50D} '+t('logs.recall.initial')+' <span class="recall-count">'+cands.length+'</span></div>';
2290
+ html+='<div class="recall-items">';
2291
+ cands.forEach(function(c,i){
2292
+ var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
2293
+ var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
2294
+ var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
2295
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2296
+ 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>';
2297
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2298
+ html+='</div>';
2299
+ });
2300
+ html+='</div></div>';
2301
+ if(filtered.length>0){
2302
+ html+='<div class="recall-layer filtered" onclick="this.classList.toggle(\\\'expanded\\\')">';
2303
+ html+='<div class="recall-layer-title"><span class="recall-expand-icon">\u25B6</span>\u2705 '+t('logs.recall.filtered')+' <span class="recall-count">'+filtered.length+'</span></div>';
2304
+ html+='<div class="recall-items">';
2305
+ filtered.forEach(function(f){
2306
+ var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
2307
+ var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
2308
+ var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
2309
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2310
+ 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>';
2311
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2312
+ html+='</div>';
2313
+ });
2314
+ html+='</div></div>';
2315
+ }else{
2316
+ html+='<div style="font-size:10px;color:var(--text-muted);margin-top:2px">\u26A0 '+t('logs.recall.noneRelevant')+'</div>';
2317
+ }
2318
+ html+='</div>';
2319
+ }
2320
+ }else{
2321
+ var outLines=(lg.output||'').split('\\n');
2322
+ var memCount=outLines.filter(function(l){return l.match(/^\\d+\\.\\s*\\[/)}).length;
2323
+ if(memCount>0) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u{1F4CE} '+memCount+' memories retrieved</div>';
2324
+ else if(lg.output&&lg.output.includes('no hits')) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u2205 No matching memories</div>';
2325
+ }
2222
2326
  }else if(tn==='memory_add'&&inputObj){
2223
2327
  const out=lg.output||'';
2224
2328
  const statsMatch=out.match(/^([^\\n]+)/);
@@ -2231,39 +2335,18 @@ function buildLogSummary(lg){
2231
2335
  });
2232
2336
  html+='</div>';
2233
2337
  }
2234
- const outLines=out.split('\\n').filter(l=>l.startsWith('['));
2235
- if(outLines.length>0){
2236
- html+='<div class="log-msg-list">';
2237
- outLines.forEach(function(l){
2238
- var rm=l.match(/^\\[(\\w+)\\]\\s*([^\u2192]+)\u2192\\s*(.*)/);
2239
- if(rm){
2240
- var role=rm[1],actionRaw=rm[2].trim(),text=rm[3].trim();
2241
- var actionCls='stored';
2242
- if(actionRaw.indexOf('exact-dup')>=0||actionRaw.indexOf('\u23ED')>=0) actionCls='exact-dup';
2243
- else if(actionRaw.indexOf('dedup')>=0||actionRaw.indexOf('\uD83D\uDD01')>=0) actionCls='dedup';
2244
- else if(actionRaw.indexOf('merged')>=0||actionRaw.indexOf('\uD83D\uDD00')>=0) actionCls='merged';
2245
- else if(actionRaw.indexOf('error')>=0||actionRaw.indexOf('\u274C')>=0) actionCls='error';
2246
- var actionLabel={'stored':'\u2713 stored','exact-dup':'\u23ED skip','dedup':'\uD83D\uDD01 dedup','merged':'\uD83D\uDD00 merged','error':'\u2717 error'}[actionCls]||actionCls;
2247
- html+='<div class="log-msg-item">'+
2248
- '<span class="log-msg-role '+role+'">'+role+'</span>'+
2249
- '<span class="log-msg-action '+actionCls+'">'+actionLabel+'</span>'+
2250
- '<span class="log-msg-text">'+escapeHtml(text.length>150?text.slice(0,150)+'...':text)+'</span>'+
2251
- '</div>';
2252
- }else{
2253
- html+='<div class="log-msg-item"><span class="log-msg-text">'+escapeHtml(l.length>200?l.slice(0,200)+'...':l)+'</span></div>';
2254
- }
2255
- });
2256
- html+='</div>';
2257
- }else if(inputObj.details&&Array.isArray(inputObj.details)&&inputObj.details.length>0){
2338
+ var parsed=parseMemoryAddEntries(out);
2339
+ if(parsed.length>0){
2258
2340
  html+='<div class="log-msg-list">';
2259
- inputObj.details.forEach(function(d){
2260
- var s=typeof d==='string'?d:String(d);
2261
- var dm=s.match(/^\\[(\\w+)\\]\\s*(.*)/);
2262
- if(dm){
2263
- html+='<div class="log-msg-item"><span class="log-msg-role '+dm[1]+'">'+dm[1]+'</span><span class="log-msg-text">'+escapeHtml(dm[2].length>150?dm[2].slice(0,150)+'...':dm[2])+'</span></div>';
2264
- }else{
2265
- html+='<div class="log-msg-item"><span class="log-msg-text">'+escapeHtml(s.length>150?s.slice(0,150)+'...':s)+'</span></div>';
2266
- }
2341
+ parsed.forEach(function(e){
2342
+ var actionCls=e.action==='exact-dup'?'exact-dup':e.action==='dedup'?'dedup':e.action==='merged'?'merged':e.action==='error'?'error':'stored';
2343
+ var actionLabel={'stored':'\u2713 stored','exact-dup':'\u23ED skip','dedup':'\uD83D\uDD01 dedup','merged':'\uD83D\uDD00 merged','error':'\u2717 error'}[actionCls]||actionCls;
2344
+ var displayText=e.content.split('\\n')[0].trim();
2345
+ html+='<div class="log-msg-item">'+
2346
+ '<span class="log-msg-role '+e.role+'">'+e.role+'</span>'+
2347
+ '<span class="log-msg-action '+actionCls+'">'+actionLabel+'</span>'+
2348
+ '<span class="log-msg-text">'+escapeHtml(displayText)+'</span>'+
2349
+ '</div>';
2267
2350
  });
2268
2351
  html+='</div>';
2269
2352
  }
@@ -2271,9 +2354,51 @@ function buildLogSummary(lg){
2271
2354
  const keys=Object.keys(inputObj);
2272
2355
  keys.slice(0,4).forEach(k=>{
2273
2356
  const v=String(inputObj[k]);
2274
- html+='<span class="log-summary-kv"><span class="kv-label">'+escapeHtml(k)+':</span><span class="kv-val">'+escapeHtml(v.length>60?v.slice(0,60)+'...':v)+'</span></span>';
2357
+ html+='<span class="log-summary-kv"><span class="kv-label">'+escapeHtml(k)+':</span><span class="kv-val">'+escapeHtml(v)+'</span></span>';
2358
+ });
2359
+ }
2360
+ return html;
2361
+ }
2362
+ function buildRecallDetailHtml(rd){
2363
+ var html='<div class="recall-detail">';
2364
+ var cands=rd.candidates||[];
2365
+ var filtered=rd.filtered||[];
2366
+ if(cands.length>0){
2367
+ html+='<div class="recall-detail-section" onclick="this.classList.toggle(\\\'expanded\\\')">';
2368
+ html+='<div class="recall-detail-title"><span class="recall-expand-icon">\u25B6</span>\u{1F50D} '+t('logs.recall.initial')+' ('+cands.length+')</div>';
2369
+ html+='<div class="recall-detail-items">';
2370
+ cands.forEach(function(c,i){
2371
+ var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
2372
+ var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
2373
+ var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
2374
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2375
+ 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>';
2376
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2377
+ html+='</div>';
2378
+ });
2379
+ html+='</div></div>';
2380
+ }
2381
+ if(filtered.length>0){
2382
+ html+='<div class="recall-detail-section filtered" onclick="this.classList.toggle(\\\'expanded\\\')">';
2383
+ html+='<div class="recall-detail-title"><span class="recall-expand-icon">\u25B6</span>\u2705 '+t('logs.recall.filtered')+' ('+filtered.length+')</div>';
2384
+ html+='<div class="recall-detail-items">';
2385
+ filtered.forEach(function(f,i){
2386
+ var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
2387
+ var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
2388
+ var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
2389
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2390
+ 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>';
2391
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2392
+ html+='</div>';
2275
2393
  });
2394
+ html+='</div></div>';
2395
+ }else if(cands.length>0){
2396
+ html+='<div style="font-size:10px;color:var(--text-muted);margin-top:4px">\u26A0 '+t('logs.recall.noneRelevant')+'</div>';
2276
2397
  }
2398
+ if(rd.status==='error'&&rd.error){
2399
+ html+='<div style="margin-top:8px;color:var(--accent);font-size:12px">\u274C '+escapeHtml(rd.error)+'</div>';
2400
+ }
2401
+ html+='</div>';
2277
2402
  return html;
2278
2403
  }
2279
2404
  function renderLogs(logs){
@@ -2288,7 +2413,29 @@ function renderLogs(logs){
2288
2413
  const toolCls=lg.toolName.replace(/[^a-zA-Z0-9_]/g,'_');
2289
2414
  const dur=lg.durationMs<1000?Math.round(lg.durationMs)+'ms':(lg.durationMs/1000).toFixed(1)+'s';
2290
2415
  let inputDisplay='';
2291
- try{const parsed=JSON.parse(lg.input);inputDisplay=JSON.stringify(parsed,null,2);}catch(_){inputDisplay=lg.input;}
2416
+ let inputHtml='';
2417
+ let outputHtml='';
2418
+ try{
2419
+ const parsed=JSON.parse(lg.input);
2420
+ if(lg.toolName==='memory_add'){
2421
+ var addEntries=parseMemoryAddEntries(lg.output||'');
2422
+ if(addEntries.length>0){
2423
+ inputHtml='<div class="log-add-detail">';
2424
+ addEntries.forEach(function(e){
2425
+ inputHtml+='<div class="log-add-msg"><div class="log-add-msg-role">'+escapeHtml(e.role)+'</div><div class="log-add-msg-content">'+escapeHtml(e.content).replace(/\\n/g,'<br>')+'</div></div>';
2426
+ });
2427
+ inputHtml+='</div>';
2428
+ }
2429
+ }else if(parsed.type==='auto_recall'||parsed.type==='tool_call'){
2430
+ inputDisplay=JSON.stringify({query:parsed.query},null,2);
2431
+ }else{
2432
+ inputDisplay=JSON.stringify(parsed,null,2);
2433
+ }
2434
+ }catch(_){inputDisplay=lg.input;}
2435
+ try{
2436
+ var rd2=null;try{rd2=JSON.parse(lg.output);}catch(_e){}
2437
+ if(rd2&&rd2.candidates){outputHtml=buildRecallDetailHtml(rd2);}
2438
+ }catch(_){}
2292
2439
  const summary=buildLogSummary(lg);
2293
2440
  return '<div class="log-entry" id="log-'+i+'">'+
2294
2441
  '<div class="log-header" onclick="toggleLog('+i+')">'+
@@ -2302,11 +2449,11 @@ function renderLogs(logs){
2302
2449
  '<div class="log-detail" id="log-detail-'+i+'">'+
2303
2450
  '<div class="log-io-section">'+
2304
2451
  '<div class="log-io-label">\u25B6 '+t('logs.input')+'</div>'+
2305
- '<pre class="log-io-content">'+escapeHtml(inputDisplay)+'</pre>'+
2452
+ (inputHtml?inputHtml:'<pre class="log-io-content">'+escapeHtml(inputDisplay)+'</pre>')+
2306
2453
  '</div>'+
2307
2454
  '<div class="log-io-section">'+
2308
2455
  '<div class="log-io-label">\u25C0 '+t('logs.output')+'</div>'+
2309
- '<pre class="log-io-content">'+escapeHtml(lg.output)+'</pre>'+
2456
+ (outputHtml?outputHtml:'<pre class="log-io-content">'+escapeHtml(lg.output)+'</pre>')+
2310
2457
  '</div>'+
2311
2458
  '</div>'+
2312
2459
  '</div>';
@@ -2349,8 +2496,6 @@ async function loadMetrics(){
2349
2496
  document.getElementById('mSessions').textContent=formatNum(d.totals.sessions);
2350
2497
  document.getElementById('mEmbeddings').textContent=formatNum(d.totals.embeddings);
2351
2498
  renderChartWrites(d.writesPerDay);
2352
- renderBreakdown(d.roleBreakdown,'breakdownRole');
2353
- renderBreakdown(d.kindBreakdown,'breakdownKind');
2354
2499
  loadToolMetrics();
2355
2500
  }
2356
2501
 
@@ -2866,7 +3011,14 @@ function classifyError(msg){
2866
3011
  if(msg.indexOf('ECONNREFUSED')>=0) return 'Connection refused';
2867
3012
  if(msg.indexOf('ENOTFOUND')>=0) return 'DNS resolution failed';
2868
3013
  if(msg.indexOf('403')>=0) return 'Forbidden (403)';
2869
- return msg.length>50?msg.slice(0,47)+'...':msg;
3014
+ if(msg.indexOf('503')>=0||msg.indexOf('upstream connect error')>=0||msg.indexOf('Service Unavailable')>=0) return 'Service unavailable (503)';
3015
+ if(msg.indexOf('502')>=0||msg.indexOf('Bad Gateway')>=0) return 'Bad gateway (502)';
3016
+ if(msg.indexOf('500')>=0||msg.indexOf('Internal Server Error')>=0) return 'Server error (500)';
3017
+ if(msg.indexOf('404')>=0||msg.indexOf('Not Found')>=0) return 'Not found (404)';
3018
+ if(msg.indexOf('fetch failed')>=0||msg.indexOf('ETIMEDOUT')>=0) return 'Network error';
3019
+ if(msg.indexOf('Unknown')>=0&&msg.indexOf('provider')>=0) return 'Unknown provider';
3020
+ var m=msg.match(/\((\d{3})\)/); if(m) return 'HTTP error ('+m[1]+')';
3021
+ return msg.length>80?msg.substring(0,77)+'...':msg;
2870
3022
  }
2871
3023
 
2872
3024
  function shortenModel(s){return s?s.replace('openai_compatible/','').replace('openai/',''):'\u2014';}
@@ -2912,7 +3064,7 @@ async function loadModelHealth(){
2912
3064
  issue+=shortErr;
2913
3065
  if(m.consecutiveErrors>1) issue+=' ('+m.consecutiveErrors+'x)';
2914
3066
  }
2915
- if(issue) h+='<td><span class="mh-err-text" title="'+escapeHtml(m.lastErrorMessage||'')+'">'+escapeHtml(issue)+'</span></td>';
3067
+ if(issue) h+='<td><span class="mh-err-text" data-err="'+escapeHtml(m.lastErrorMessage||'')+'">'+escapeHtml(issue)+'</span></td>';
2916
3068
  else h+='<td><span style="color:var(--text-muted);font-size:11px">\u2014</span></td>';
2917
3069
 
2918
3070
  h+='<td style="text-align:right"><span class="mh-time">'+(ago||'\u2014')+'</span></td>';
@@ -2920,11 +3072,29 @@ async function loadModelHealth(){
2920
3072
  }
2921
3073
  h+='</tbody></table>';
2922
3074
  bar.innerHTML=h;
3075
+ initMhTooltips();
2923
3076
  }catch(e){
2924
3077
  bar.innerHTML='<div class="mh-empty">Failed to load model health</div>';
2925
3078
  }
2926
3079
  }
2927
3080
 
3081
+ function initMhTooltips(){
3082
+ var tip=document.getElementById('mhTooltip');
3083
+ if(!tip){tip=document.createElement('div');tip.id='mhTooltip';document.body.appendChild(tip);}
3084
+ document.querySelectorAll('.mh-err-text[data-err]').forEach(function(el){
3085
+ el.addEventListener('mouseenter',function(e){
3086
+ var msg=el.getAttribute('data-err');
3087
+ if(!msg)return;
3088
+ tip.textContent=msg;
3089
+ tip.style.display='block';
3090
+ var rect=el.getBoundingClientRect();
3091
+ tip.style.left=Math.max(0,Math.min(rect.left,window.innerWidth-490))+'px';
3092
+ tip.style.top=(rect.bottom+6)+'px';
3093
+ });
3094
+ el.addEventListener('mouseleave',function(){tip.style.display='none';});
3095
+ });
3096
+ }
3097
+
2928
3098
  function timeAgo(ts){
2929
3099
  var diff=Date.now()-ts;
2930
3100
  if(diff<60000) return 'just now';
@@ -3055,6 +3225,7 @@ async function saveConfig(){
3055
3225
  // 2) Test embedding
3056
3226
  try{
3057
3227
  var er=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'embedding',provider:cfg.embedding.provider,model:cfg.embedding.model||'',endpoint:cfg.embedding.endpoint||'',apiKey:cfg.embedding.apiKey||''})});
3228
+ if(er.status===401){done();toast(t('settings.session.expired'),'error');return;}
3058
3229
  var ed=await er.json();
3059
3230
  if(!ed.ok){done();toast(t('settings.save.emb.fail')+': '+ed.error,'error');document.getElementById('testEmbResult').className='test-result fail';document.getElementById('testEmbResult').innerHTML='\\u274C '+ed.error;return;}
3060
3231
  document.getElementById('testEmbResult').className='test-result ok';document.getElementById('testEmbResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3064,6 +3235,7 @@ async function saveConfig(){
3064
3235
  if(hasSumConfig&&cfg.summarizer){
3065
3236
  try{
3066
3237
  var sr=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'summarizer',provider:cfg.summarizer.provider,model:cfg.summarizer.model||'',endpoint:cfg.summarizer.endpoint||'',apiKey:cfg.summarizer.apiKey||''})});
3238
+ if(sr.status===401){done();toast(t('settings.session.expired'),'error');return;}
3067
3239
  var sd=await sr.json();
3068
3240
  if(!sd.ok){done();toast(t('settings.save.sum.fail')+': '+sd.error,'error');document.getElementById('testSumResult').className='test-result fail';document.getElementById('testSumResult').innerHTML='\\u274C '+sd.error;return;}
3069
3241
  document.getElementById('testSumResult').className='test-result ok';document.getElementById('testSumResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3074,6 +3246,7 @@ async function saveConfig(){
3074
3246
  if(hasSkillConfig&&cfg.skillEvolution.summarizer){
3075
3247
  try{
3076
3248
  var kr=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'summarizer',provider:cfg.skillEvolution.summarizer.provider,model:cfg.skillEvolution.summarizer.model||'',endpoint:cfg.skillEvolution.summarizer.endpoint||'',apiKey:cfg.skillEvolution.summarizer.apiKey||''})});
3249
+ if(kr.status===401){done();toast(t('settings.session.expired'),'error');return;}
3077
3250
  var kd=await kr.json();
3078
3251
  if(!kd.ok){done();toast(t('settings.save.skill.fail')+': '+kd.error,'error');document.getElementById('testSkillResult').className='test-result fail';document.getElementById('testSkillResult').innerHTML='\\u274C '+kd.error;return;}
3079
3252
  document.getElementById('testSkillResult').className='test-result ok';document.getElementById('testSkillResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3131,6 +3304,7 @@ async function testModel(type){
3131
3304
  try{
3132
3305
  var body={type:type,provider:provider,model:model,endpoint:endpoint,apiKey:apiKey};
3133
3306
  var r=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
3307
+ if(r.status===401){resultEl.className='test-result fail';resultEl.innerHTML='\\u274C '+t('settings.session.expired');btn.disabled=false;return;}
3134
3308
  var d=await r.json();
3135
3309
  if(d.ok){
3136
3310
  resultEl.className='test-result ok';
@@ -3455,17 +3629,6 @@ function renderToolAgg(data){
3455
3629
  '</tbody></table>';
3456
3630
  }
3457
3631
 
3458
- function renderBreakdown(obj,containerId){
3459
- const el=document.getElementById(containerId);
3460
- if(!el)return;
3461
- const entries=Object.entries(obj||{}).sort((a,b)=>b[1]-a[1]);
3462
- const total=entries.reduce((s,[,v])=>s+v,0)||1;
3463
- el.innerHTML=entries.map(([label,value])=>{
3464
- const pct=Math.round((value/total)*100);
3465
- return '<div class="breakdown-item"><div class="bd-top"><span class="label">'+esc(label)+'</span><span class="value">'+value+' <span style="font-size:11px;font-weight:500;color:var(--text-muted)">('+pct+'%)</span></span></div><div class="breakdown-bar-wrap"><div class="breakdown-bar" style="width:'+pct+'%"></div></div></div>';
3466
- }).join('');
3467
- }
3468
-
3469
3632
  /* ─── Data loading ─── */
3470
3633
  async function loadAll(){
3471
3634
  await Promise.all([loadStats(),loadMemories()]);
@@ -3548,8 +3711,6 @@ function getFilterParams(){
3548
3711
  const p=new URLSearchParams();
3549
3712
  if(activeSession) p.set('session',activeSession);
3550
3713
  if(activeRole) p.set('role',activeRole);
3551
- const kind=document.getElementById('filterKind').value;
3552
- if(kind) p.set('kind',kind);
3553
3714
  const df=document.getElementById('dateFrom').value;
3554
3715
  if(df) p.set('dateFrom',df);
3555
3716
  const dt=document.getElementById('dateTo').value;
@@ -3653,18 +3814,19 @@ function renderMemories(items){
3653
3814
  list.innerHTML=items.map(m=>{
3654
3815
  const time=m.created_at?new Date(typeof m.created_at==='number'?m.created_at:m.created_at).toLocaleString('zh-CN'):'';
3655
3816
  const role=m.role||'user';
3656
- const kind=m.kind||'paragraph';
3657
- const summary=esc(m.summary||m.content?.slice(0,120)||'');
3658
- const content=esc(m.content||'');
3817
+ const rawSummary=m.summary||'';
3818
+ const rawContent=m.content||'';
3819
+ const content=esc(rawContent);
3659
3820
  const id=m.id;
3660
3821
  const vscore=m._vscore?'<span class="vscore-badge">'+Math.round(m._vscore*100)+'%</span>':'';
3661
3822
  const sid=m.session_key||'';
3662
3823
  const sidShort=sid.length>18?sid.slice(0,6)+'..'+sid.slice(-6):sid;
3663
3824
  const mc=m.merge_count||0;
3825
+ const cardTitle=esc(rawSummary||rawContent||'');
3664
3826
  const mergeBadge=mc>0?'<span class="merge-badge">\\u{1F504} '+t('card.evolved')+' '+mc+t('card.times')+'</span>':'';
3665
3827
  const updatedAt=(m.updated_at&&m.updated_at>m.created_at)?'<span class="card-updated">'+t('card.updated')+' '+new Date(m.updated_at).toLocaleString('zh-CN')+'</span>':'';
3666
3828
  const ds=m.dedup_status||'active';
3667
- const isInactive=ds==='duplicate'||ds==='merged';
3829
+ const isInactive=ds==='merged';
3668
3830
  const dedupBadge=ds==='duplicate'?'<span class="dedup-badge duplicate">'+t('card.dedupDuplicate')+'</span>':ds==='merged'?'<span class="dedup-badge merged">'+t('card.dedupMerged')+'</span>':'';
3669
3831
  const isImported=sid.startsWith('openclaw-import-')||sid.startsWith('openclaw-session-');
3670
3832
  const importBadge=isImported?'<span class="import-badge">\u{1F990} '+t('card.imported')+'</span>':'';
@@ -3672,7 +3834,7 @@ function renderMemories(items){
3672
3834
  const isPublicMem=ownerVal==='public';
3673
3835
  const ownerBadge=isPublicMem?'<span class="owner-badge public">\\u{1F310} '+t('filter.public')+'</span>':'<span class="owner-badge agent">\\u{1F512} '+t('filter.private')+'</span>';
3674
3836
  let dedupInfo='';
3675
- if(isInactive){
3837
+ if(ds==='duplicate'||ds==='merged'){
3676
3838
  const reason=m.dedup_reason?'<span style="font-size:11px;color:var(--text-muted)">'+t('card.dedupReason')+esc(m.dedup_reason)+'</span>':'';
3677
3839
  const target=m.dedup_target?'<span class="dedup-target-link" onclick="scrollToMemory(\\''+m.dedup_target+'\\')">'+t('card.dedupTarget')+m.dedup_target.slice(0,8)+'...</span>':'';
3678
3840
  dedupInfo='<div style="margin-top:6px;font-size:11px">'+target+' '+reason+'</div>';
@@ -3695,8 +3857,23 @@ function renderMemories(items){
3695
3857
  }catch(e){}
3696
3858
  }
3697
3859
  return '<div class="memory-card'+(isInactive?' dedup-inactive':'')+'">'+
3698
- '<div class="card-header"><div class="meta"><span class="role-tag '+role+'">'+role+'</span><span class="kind-tag">'+kind+'</span>'+ownerBadge+importBadge+dedupBadge+mergeBadge+'</div><span class="card-time"><span class="session-tag" title="'+esc(sid)+'">'+esc(sidShort)+'</span> '+time+updatedAt+'</span></div>'+
3699
- '<div class="card-summary">'+summary+'</div>'+
3860
+ '<div class="card-header"><div class="meta"><span class="role-tag '+role+'">'+role+'</span>'+ownerBadge+importBadge+dedupBadge+mergeBadge+'</div><span class="card-time"><span class="session-tag" title="'+esc(sid)+'">'+esc(sidShort)+'</span> '+time+updatedAt+'</span></div>'+
3861
+ '<div class="card-summary">'+cardTitle+'</div>'+
3862
+ (function(){
3863
+ if(mc<=0) return '';
3864
+ var mergeHtml='<div class="card-merged-info">';
3865
+ mergeHtml+='<div class="card-merged-label">\\u{1F504} '+t('card.mergedInfo')+' ('+mc+t('card.times')+')</div>';
3866
+ var sources=m.merge_sources||[];
3867
+ if(sources.length>0){
3868
+ mergeHtml+='<div style="display:flex;flex-wrap:wrap;gap:6px">';
3869
+ sources.forEach(function(s){
3870
+ mergeHtml+='<span class="dedup-target-link" onclick="scrollToMemory(\\''+s.id+'\\')">\\u{1F517} '+s.id.slice(0,8)+'...</span>';
3871
+ });
3872
+ mergeHtml+='</div>';
3873
+ }
3874
+ mergeHtml+='</div>';
3875
+ return mergeHtml;
3876
+ })()+
3700
3877
  dedupInfo+
3701
3878
  '<div class="card-content" id="content-'+id+'"><pre>'+content+'</pre></div>'+
3702
3879
  historyHtml+
@@ -3775,12 +3952,11 @@ async function showMemoryModal(chunkId){
3775
3952
  const m=data.memory;
3776
3953
  const role=(m.role||'unknown').toUpperCase();
3777
3954
  const roleCls=(m.role||'').toLowerCase();
3778
- const kind=m.kind||'paragraph';
3779
3955
  const ds=m.dedup_status||'active';
3780
3956
  const time=new Date(m.created_at).toLocaleString('zh-CN');
3781
3957
  const updated=m.updated_at?new Date(m.updated_at).toLocaleString('zh-CN'):'';
3782
3958
  let html='<div class="modal-memory-card">';
3783
- html+='<div class="modal-header-row"><span class="role-tag '+roleCls+'">'+role+'</span><span class="kind-tag">'+kind+'</span>';
3959
+ html+='<div class="modal-header-row"><span class="role-tag '+roleCls+'">'+role+'</span>';
3784
3960
  if(ds!=='active') html+='<span class="dedup-badge '+(ds==='duplicate'?'duplicate':'merged')+'">'+ds+'</span>';
3785
3961
  html+='</div>';
3786
3962
  html+='<div class="modal-field"><div class="modal-field-label">ID</div><div class="modal-field-val" style="font-family:monospace;font-size:11px">'+esc(m.id)+'</div></div>';
@@ -3838,17 +4014,6 @@ function renderSummaryHtml(raw){
3838
4014
  }
3839
4015
 
3840
4016
  /* ─── CRUD ─── */
3841
- function openCreateModal(){
3842
- editingId=null;
3843
- document.getElementById('modalTitle').textContent=t('modal.new');
3844
- document.getElementById('modalSubmit').textContent=t('modal.create');
3845
- document.getElementById('mRole').value='user';
3846
- document.getElementById('mContent').value='';
3847
- document.getElementById('mSummary').value='';
3848
- document.getElementById('mKind').value='paragraph';
3849
- document.getElementById('modalOverlay').classList.add('show');
3850
- }
3851
-
3852
4017
  function openEditModal(id){
3853
4018
  const m=memoryCache[id];
3854
4019
  if(!m){toast(t('toast.notfound'),'error');return}
@@ -3858,7 +4023,6 @@ function openEditModal(id){
3858
4023
  document.getElementById('mRole').value=m.role||'user';
3859
4024
  document.getElementById('mContent').value=m.content||'';
3860
4025
  document.getElementById('mSummary').value=m.summary||'';
3861
- document.getElementById('mKind').value=m.kind||'paragraph';
3862
4026
  document.getElementById('modalOverlay').classList.add('show');
3863
4027
  }
3864
4028
 
@@ -3867,21 +4031,16 @@ function closeModal(){
3867
4031
  }
3868
4032
 
3869
4033
  async function submitModal(){
4034
+ if(!editingId)return;
3870
4035
  const data={
3871
4036
  role:document.getElementById('mRole').value,
3872
4037
  content:document.getElementById('mContent').value,
3873
4038
  summary:document.getElementById('mSummary').value,
3874
- kind:document.getElementById('mKind').value,
3875
4039
  };
3876
4040
  if(!data.content.trim()){toast(t('modal.err.empty'),'error');return}
3877
- let r;
3878
- if(editingId){
3879
- r=await fetch('/api/memory/'+editingId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3880
- } else {
3881
- r=await fetch('/api/memory',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3882
- }
4041
+ const r=await fetch('/api/memory/'+editingId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3883
4042
  const d=await r.json();
3884
- if(d.ok){toast(editingId?t('toast.updated'):t('toast.created'),'success');closeModal();loadAll();}
4043
+ if(d.ok){toast(t('toast.updated'),'success');closeModal();loadAll();}
3885
4044
  else{toast(d.error||t('toast.opfail'),'error')}
3886
4045
  }
3887
4046
 
@@ -3904,12 +4063,15 @@ async function toggleMemoryPublic(id,setPublic){
3904
4063
  }
3905
4064
 
3906
4065
  async function clearAll(){
3907
- if(!confirm(t('confirm.clearall')))return;
3908
- if(!confirm(t('confirm.clearall2')))return;
3909
- const r=await fetch('/api/memories',{method:'DELETE'});
3910
- const d=await r.json();
3911
- if(d.ok){toast(t('toast.cleared'),'success');loadAll();}
3912
- else{toast(t('toast.clearfail'),'error')}
4066
+ try{
4067
+ if(!confirm(t('confirm.clearall')))return;
4068
+ if(!confirm(t('confirm.clearall2')))return;
4069
+ const r=await fetch('/api/memories',{method:'DELETE'});
4070
+ if(r.status===401){toast(t('settings.session.expired'),'error');return;}
4071
+ const d=await r.json();
4072
+ if(d.ok){toast(t('toast.cleared'),'success');loadAll();}
4073
+ else{toast(t('toast.clearfail'),'error')}
4074
+ }catch(e){toast('Error: '+e.message,'error')}
3913
4075
  }
3914
4076
 
3915
4077
  /* ─── Migration ─── */
@@ -3929,7 +4091,7 @@ let migrateStats={stored:0,skipped:0,merged:0,errors:0};
3929
4091
  });
3930
4092
  })();
3931
4093
 
3932
- async function migrateScan(){
4094
+ async function migrateScan(showToast){
3933
4095
  const btn=document.getElementById('migrateScanBtn');
3934
4096
  btn.disabled=true;
3935
4097
  btn.textContent=t('migrate.scanning');
@@ -3962,9 +4124,31 @@ async function migrateScan(){
3962
4124
  t('migrate.config.warn.desc')+' ('+parts.join(', ')+')';
3963
4125
  }
3964
4126
 
4127
+ const imported=d.importedChunkCount||0;
4128
+ const remaining=Math.max(0,(d.totalItems||0)-imported);
4129
+
3965
4130
  if(d.totalItems>0 && d.configReady){
3966
4131
  document.getElementById('migrateStartBtn').style.display='inline-flex';
3967
4132
  document.getElementById('migrateConcurrencyRow').style.display='inline-flex';
4133
+ if(d.hasImportedData){
4134
+ document.getElementById('migrateStartBtn').textContent=t('migrate.resume');
4135
+ }else{
4136
+ document.getElementById('migrateStartBtn').textContent=t('migrate.start');
4137
+ }
4138
+ }
4139
+
4140
+ var hintEl=document.getElementById('migrateImportedHint');
4141
+ if(!hintEl){
4142
+ hintEl=document.createElement('div');
4143
+ hintEl.id='migrateImportedHint';
4144
+ hintEl.style.cssText='font-size:12px;color:var(--text-sec);padding:6px 0';
4145
+ document.getElementById('migrateActions').appendChild(hintEl);
4146
+ }
4147
+ if(imported>0){
4148
+ hintEl.textContent=t('migrate.imported.hint').replace('{n}',imported);
4149
+ hintEl.style.display='block';
4150
+ }else{
4151
+ hintEl.style.display='none';
3968
4152
  }
3969
4153
 
3970
4154
  if(d.totalItems===0){
@@ -3974,6 +4158,7 @@ async function migrateScan(){
3974
4158
  if(d.hasImportedData){
3975
4159
  document.getElementById('postprocessSection').style.display='block';
3976
4160
  }
4161
+ if(showToast) toast(t('migrate.scan.done').replace('{n}',remaining),'success');
3977
4162
  }catch(e){
3978
4163
  toast('Scan failed: '+e.message,'error');
3979
4164
  }finally{
@@ -3983,27 +4168,36 @@ async function migrateScan(){
3983
4168
  }
3984
4169
 
3985
4170
  function migrateStart(){
3986
- if(!migrateScanData||!migrateScanData.configReady)return;
3987
- if(!confirm(t('migrate.start')+'?'))return;
4171
+ const isResume=document.getElementById('migrateStartBtn').textContent===t('migrate.resume');
4172
+ if(!isResume){
4173
+ if(!migrateScanData||!migrateScanData.configReady){
4174
+ toast(t('migrate.scan.required'),'error');
4175
+ return;
4176
+ }
4177
+ if(!confirm(t('migrate.start')+'?'))return;
4178
+ }
3988
4179
 
3989
4180
  const concSel=document.getElementById('migrateConcurrency');
3990
4181
  const concurrency=concSel?parseInt(concSel.value,10)||1:1;
3991
4182
 
3992
4183
  window._migrateRunning=true;
3993
- _migrateStatusChecked=false;
4184
+ _migrateStatusChecked=true;
3994
4185
  document.getElementById('migrateStartBtn').style.display='none';
3995
4186
  document.getElementById('migrateScanBtn').disabled=true;
4187
+ var hintEl=document.getElementById('migrateImportedHint');
4188
+ if(hintEl) hintEl.style.display='none';
3996
4189
  document.getElementById('migrateConcurrencyRow').style.display='none';
3997
4190
  document.getElementById('migrateConcurrencyWarn').style.display='none';
3998
4191
  document.getElementById('migrateProgress').style.display='block';
3999
4192
  document.getElementById('migrateLiveLog').innerHTML='';
4000
4193
  migrateStats={stored:0,skipped:0,merged:0,errors:0};
4001
4194
  updateMigrateStats();
4195
+ document.getElementById('migrateBar').style.width='0%';
4196
+ document.getElementById('migrateCounter').textContent='';
4002
4197
 
4003
4198
  document.getElementById('migrateStopBtn').disabled=false;
4004
- document.getElementById('migrateBar').style.width='0%';
4199
+ document.getElementById('migrateStopBtn').style.display='inline-flex';
4005
4200
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#6366f1,#8b5cf6)';
4006
- document.getElementById('migrateCounter').textContent='';
4007
4201
  const body=JSON.stringify({sources:['sqlite','sessions'],concurrency});
4008
4202
  connectMigrateSSE('/api/migrate/start','POST',body);
4009
4203
  }
@@ -4040,7 +4234,7 @@ function readSSEStream(r){
4040
4234
  const NL=String.fromCharCode(10);
4041
4235
  function pump(){
4042
4236
  reader.read().then(({done,value})=>{
4043
- if(done){if(!migrateDoneCalled&&!window._migrateRunning)onMigrateDone(false);return;}
4237
+ if(done){if(!migrateDoneCalled)onMigrateDone(false);return;}
4044
4238
  buf+=decoder.decode(value,{stream:true});
4045
4239
  const lines=buf.split(NL);
4046
4240
  buf=lines.pop()||'';
@@ -4063,7 +4257,7 @@ function readSSEStream(r){
4063
4257
 
4064
4258
  var _migrateStatusChecked=false;
4065
4259
  async function checkMigrateStatus(){
4066
- if(_migrateStatusChecked) return;
4260
+ if(_migrateStatusChecked||window._migrateRunning) return;
4067
4261
  _migrateStatusChecked=true;
4068
4262
  try{
4069
4263
  const r=await fetch('/api/migrate/status');
@@ -4085,7 +4279,18 @@ async function checkMigrateStatus(){
4085
4279
  document.getElementById('migrateCounter').textContent=s.processed+' / '+s.total+' ('+pct+'%)';
4086
4280
  const label=s.phase==='sqlite'?t('migrate.phase.sqlite'):t('migrate.phase.sessions');
4087
4281
  document.getElementById('migratePhaseLabel').textContent=label;
4282
+ document.getElementById('migrateStopBtn').style.display='inline-flex';
4283
+ if(s.processed>0){
4284
+ const log=document.getElementById('migrateLiveLog');
4285
+ const hint=document.createElement('div');
4286
+ hint.style.cssText='text-align:center;padding:8px 12px;color:var(--text-muted);font-size:11px;border-bottom:1px solid var(--border)';
4287
+ hint.textContent=t('migrate.reconnect.hint').replace('{n}',s.processed);
4288
+ log.appendChild(hint);
4289
+ }
4088
4290
  connectMigrateSSE('/api/migrate/stream','GET',null);
4291
+ fetch('/api/migrate/scan').then(function(sr){return sr.json()}).then(function(sd){
4292
+ if(sd&&sd.hasImportedData) document.getElementById('postprocessSection').style.display='block';
4293
+ }).catch(function(){});
4089
4294
  }else if(s.done&&(s.stored>0||s.skipped>0||s.stopped)){
4090
4295
  migrateStats={stored:s.stored,skipped:s.skipped,merged:s.merged,errors:s.errors};
4091
4296
  updateMigrateStats();
@@ -4183,18 +4388,23 @@ function onMigrateDone(wasStopped,skipReload){
4183
4388
  document.getElementById('migrateScanBtn').disabled=false;
4184
4389
  document.getElementById('migrateStopBtn').disabled=true;
4185
4390
  document.getElementById('migrateStopBtn').textContent=t('migrate.stop');
4391
+ document.getElementById('migrateStopBtn').style.display='none';
4186
4392
  if(wasStopped){
4187
4393
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#f59e0b,#fbbf24)';
4188
4394
  document.getElementById('migrateStartBtn').style.display='inline-flex';
4189
4395
  document.getElementById('migrateStartBtn').textContent=t('migrate.resume');
4396
+ document.getElementById('migratePhaseLabel').textContent=t('migrate.phase.stopped');
4190
4397
  }else{
4191
4398
  document.getElementById('migrateBar').style.width='100%';
4192
4399
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4193
4400
  const total=migrateStats.stored+migrateStats.skipped+migrateStats.merged+migrateStats.errors;
4194
4401
  if(total>0) document.getElementById('migrateCounter').textContent=total+' / '+total+' (100%)';
4402
+ document.getElementById('migratePhaseLabel').textContent=t('migrate.phase.done');
4195
4403
  }
4196
4404
  fetch('/api/migrate/scan').then(r=>{if(!r.ok)throw new Error();return r.json()}).then(d=>{
4197
- if(d&&d.hasImportedData) document.getElementById('postprocessSection').style.display='block';
4405
+ if(d&&d.hasImportedData){
4406
+ document.getElementById('postprocessSection').style.display='block';
4407
+ }
4198
4408
  }).catch(()=>{});
4199
4409
  if(!skipReload) loadAll();
4200
4410
  }
@@ -4322,12 +4532,18 @@ function connectPPSSE(){
4322
4532
  }).catch(function(){});
4323
4533
  }else if(s.done){
4324
4534
  document.getElementById('postprocessSection').style.display='block';
4325
- ppStats={tasks:s.tasksCreated||0,skills:s.skillsCreated||0,errors:s.errors||0,skipped:0};
4535
+ ppStats={tasks:s.tasksCreated||0,skills:s.skillsCreated||0,errors:s.errors||0,skipped:s.skippedSessions||0};
4326
4536
  updatePPStats();
4327
4537
  document.getElementById('ppProgress').style.display='block';
4328
- var pct2=s.total>0?Math.round((s.processed/s.total)*100):0;
4329
- document.getElementById('ppBar').style.width=pct2+'%';
4330
- document.getElementById('ppCounter').textContent=s.processed+' / '+s.total+' ('+pct2+'%)';
4538
+ var totalAll=(s.total||0)+(s.skippedSessions||0);
4539
+ if(totalAll>0){
4540
+ document.getElementById('ppBar').style.width='100%';
4541
+ document.getElementById('ppCounter').textContent=totalAll+' / '+totalAll+' (100%)';
4542
+ }else{
4543
+ var pct2=s.total>0?Math.round((s.processed/s.total)*100):0;
4544
+ document.getElementById('ppBar').style.width=pct2+'%';
4545
+ document.getElementById('ppCounter').textContent=s.processed+' / '+s.total+' ('+pct2+'%)';
4546
+ }
4331
4547
  ppDone(!!s.stopped,false,true);
4332
4548
  }
4333
4549
  }).catch(function(){});
@@ -4335,9 +4551,11 @@ function connectPPSSE(){
4335
4551
 
4336
4552
  function handlePPEvent(evtType,data){
4337
4553
  if(evtType==='progress'){
4338
- var pct=data.total>0?Math.round((data.processed/data.total)*100):0;
4339
- document.getElementById('ppBar').style.width=pct+'%';
4340
- document.getElementById('ppCounter').textContent=data.processed+' / '+data.total+' ('+pct+'%)';
4554
+ if(data.total>0){
4555
+ var pct=Math.round((data.processed/data.total)*100);
4556
+ document.getElementById('ppBar').style.width=pct+'%';
4557
+ document.getElementById('ppCounter').textContent=data.processed+' / '+data.total+' ('+pct+'%)';
4558
+ }
4341
4559
  }else if(evtType==='info'){
4342
4560
  if(data.alreadyProcessed>0){
4343
4561
  ppStats.skipped=data.alreadyProcessed;
@@ -4346,6 +4564,10 @@ function handlePPEvent(evtType,data){
4346
4564
  }
4347
4565
  if(data.pending===0){
4348
4566
  appendPPLogItem({step:'done',session:t('pp.info.allDone'),index:'',total:''});
4567
+ document.getElementById('ppPhaseLabel').textContent=t('pp.info.allDone');
4568
+ document.getElementById('ppBar').style.width='100%';
4569
+ document.getElementById('ppBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4570
+ document.getElementById('ppCounter').textContent=data.alreadyProcessed+' / '+data.totalSessions;
4349
4571
  }else{
4350
4572
  document.getElementById('ppPhaseLabel').textContent=t('pp.info.pending').replace('{n}',data.pending);
4351
4573
  }
@@ -4357,12 +4579,10 @@ function handlePPEvent(evtType,data){
4357
4579
  document.getElementById('ppPhaseLabel').textContent=t('pp.running')+' — '+actionLabel+' — '+label;
4358
4580
  }
4359
4581
  if(data.step==='done'){
4360
- if(data.action==='skill-only'){
4361
- ppStats.skills++;
4362
- }else{
4582
+ if(data.action!=='skill-only'){
4363
4583
  ppStats.tasks++;
4584
+ updatePPStats();
4364
4585
  }
4365
- updatePPStats();
4366
4586
  }else if(data.step==='error'){
4367
4587
  ppStats.errors++;
4368
4588
  updatePPStats();
@@ -4390,6 +4610,7 @@ function ppDone(wasStopped,wasFailed,skipReload){
4390
4610
  document.getElementById('ppStopBtn').style.display='none';
4391
4611
  document.getElementById('ppStartBtn').style.display='inline-flex';
4392
4612
  document.getElementById('ppStartBtn').textContent=wasStopped?t('pp.resume'):t('pp.start');
4613
+ document.getElementById('ppStartBtn').disabled=false;
4393
4614
  var doneEl=document.getElementById('ppDone');
4394
4615
  doneEl.style.display='block';
4395
4616
  if(wasFailed){
@@ -4397,17 +4618,38 @@ function ppDone(wasStopped,wasFailed,skipReload){
4397
4618
  doneEl.style.color='#ef4444';
4398
4619
  doneEl.textContent=t('pp.failed')||'Processing failed — check error above';
4399
4620
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#ef4444,#dc2626)';
4621
+ document.getElementById('ppPhaseLabel').textContent=t('pp.failed');
4400
4622
  }else if(wasStopped){
4401
4623
  doneEl.style.background='rgba(245,158,11,.06)';
4402
4624
  doneEl.style.color='#f59e0b';
4403
4625
  doneEl.textContent=t('pp.stopped');
4404
4626
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#f59e0b,#fbbf24)';
4627
+ document.getElementById('ppPhaseLabel').textContent=t('pp.stopped');
4405
4628
  }else{
4406
4629
  doneEl.style.background='rgba(34,197,94,.06)';
4407
4630
  doneEl.style.color='#22c55e';
4408
- doneEl.textContent=t('pp.done')+' ('+t('pp.stat.tasks')+': '+ppStats.tasks+', '+t('pp.stat.skills')+': '+ppStats.skills+')';
4409
4631
  document.getElementById('ppBar').style.width='100%';
4410
4632
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4633
+ document.getElementById('ppPhaseLabel').textContent=t('pp.done');
4634
+ var ppTotal=ppStats.tasks+ppStats.skipped+ppStats.errors;
4635
+ if(ppTotal>0) document.getElementById('ppCounter').textContent=ppTotal+' / '+ppTotal+' (100%)';
4636
+ fetch('/api/migrate/postprocess/status').then(function(r){return r.json()}).then(function(st){
4637
+ var totalTasks=st.existingTasks||0;
4638
+ var totalSkills=st.existingSkills||0;
4639
+ var lines=[];
4640
+ if(ppStats.tasks>0) lines.push(t('pp.stat.tasks')+' +'+ppStats.tasks);
4641
+ if(ppStats.skills>0) lines.push(t('pp.stat.skills')+' +'+ppStats.skills);
4642
+ if(ppStats.skipped>0) lines.push(t('pp.stat.skipped')+': '+ppStats.skipped);
4643
+ var runText=lines.length>0?' ('+lines.join(', ')+')':'';
4644
+ var totalText=' — '+t('pp.stat.tasks')+' '+totalTasks+', '+t('pp.stat.skills.total')+' '+totalSkills;
4645
+ doneEl.textContent=t('pp.done')+runText+totalText;
4646
+ }).catch(function(){
4647
+ var parts=[];
4648
+ if(ppStats.tasks>0) parts.push(t('pp.stat.tasks')+': '+ppStats.tasks);
4649
+ if(ppStats.skills>0) parts.push(t('pp.stat.skills')+': '+ppStats.skills);
4650
+ if(ppStats.skipped>0) parts.push(t('pp.stat.skipped')+': '+ppStats.skipped);
4651
+ doneEl.textContent=t('pp.done')+(parts.length>0?' ('+parts.join(', ')+')':'');
4652
+ });
4411
4653
  }
4412
4654
  if(!skipReload) loadAll();
4413
4655
  }
@@ -4445,18 +4687,74 @@ function toggleViewerTheme(){const el=document.documentElement;const cur=el.getA
4445
4687
  initViewerTheme();
4446
4688
 
4447
4689
  /* ─── Update check ─── */
4690
+ function waitForGatewayAndReload(maxAttempts,attempt){
4691
+ attempt=attempt||0;
4692
+ if(attempt>=maxAttempts){window.location.reload();return;}
4693
+ setTimeout(function(){
4694
+ fetch('/api/auth/status').then(function(){
4695
+ window.location.reload();
4696
+ }).catch(function(){waitForGatewayAndReload(maxAttempts,attempt+1);});
4697
+ },3000);
4698
+ }
4699
+ function doUpdateInstall(packageSpec,btnEl,statusEl){
4700
+ btnEl.disabled=true;
4701
+ btnEl.textContent=t('update.installing');
4702
+ btnEl.style.cssText='background:rgba(99,102,241,.15);color:var(--pri);border:1px solid rgba(99,102,241,.3);border-radius:6px;padding:4px 14px;font-size:12px;font-weight:600;cursor:wait;white-space:nowrap';
4703
+ fetch('/api/update-install',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({packageSpec:packageSpec})})
4704
+ .then(function(r){return r.json()})
4705
+ .then(function(d){
4706
+ if(d.ok){
4707
+ btnEl.textContent=t('update.success');
4708
+ btnEl.style.cssText='background:rgba(34,197,94,.15);color:#22c55e;border:1px solid rgba(34,197,94,.3);border-radius:6px;padding:4px 14px;font-size:12px;font-weight:600;cursor:default;white-space:nowrap';
4709
+ if(statusEl)statusEl.textContent=t('update.restarting');
4710
+ waitForGatewayAndReload(40);
4711
+ }else{
4712
+ btnEl.textContent=t('update.btn');
4713
+ btnEl.style.cssText='background:none;border:1px solid currentColor;border-radius:6px;padding:4px 14px;font-size:12px;font-weight:600;color:inherit;cursor:pointer;white-space:nowrap;opacity:.85';
4714
+ btnEl.disabled=false;
4715
+ if(statusEl)statusEl.textContent=t('update.failed')+': '+(d.error||'').slice(0,60);
4716
+ setTimeout(function(){if(statusEl)statusEl.textContent='';},8000);
4717
+ }
4718
+ })
4719
+ .catch(function(){
4720
+ btnEl.textContent=t('update.btn');
4721
+ btnEl.style.cssText='background:none;border:1px solid currentColor;border-radius:6px;padding:4px 14px;font-size:12px;font-weight:600;color:inherit;cursor:pointer;white-space:nowrap;opacity:.85';
4722
+ btnEl.disabled=false;
4723
+ });
4724
+ }
4448
4725
  async function checkForUpdate(){
4449
4726
  try{
4450
4727
  const r=await fetch('/api/update-check');
4451
4728
  if(!r.ok)return;
4452
4729
  const d=await r.json();
4453
4730
  if(!d.updateAvailable)return;
4454
- const banner=document.createElement('div');
4731
+ const pkgSpec=d.installCommand?d.installCommand.replace(/^(?:npx\s+)?openclaw\s+plugins\s+install\s+/,''):(d.packageName+'@'+d.latest);
4732
+ var banner=document.createElement('div');
4455
4733
  banner.id='updateBanner';
4456
- banner.style.cssText='position:fixed;top:0;left:0;right:0;z-index:9999;background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;padding:10px 20px;display:flex;align-items:center;justify-content:space-between;font-size:14px;box-shadow:0 2px 8px rgba(0,0,0,.25)';
4457
- banner.innerHTML='<span>🔔 '+t('update.available')+': <b>v'+esc(d.current)+'</b> → <b>v'+esc(d.latest)+'</b> — '+t('update.run')+': <code style="background:rgba(0,0,0,.2);padding:2px 8px;border-radius:4px;margin:0 4px">openclaw plugins install '+esc(d.packageName)+'</code></span><button onclick="this.parentElement.remove();document.body.style.paddingTop=\\'\\';" style="background:none;border:none;color:#fff;font-size:18px;cursor:pointer;padding:0 4px">&times;</button>';
4458
- document.body.prepend(banner);
4459
- document.body.style.paddingTop='48px';
4734
+ banner.style.cssText='display:flex;align-items:center;gap:10px;padding:12px 20px;font-size:13px;font-weight:500;border-radius:10px;margin:0 32px;animation:slideIn .3s ease;background:rgba(245,158,11,.1);color:#d97706;border:1px solid rgba(245,158,11,.25)';
4735
+ var textNode=document.createElement('div');
4736
+ textNode.style.cssText='display:flex;align-items:center;gap:8px;flex-shrink:0';
4737
+ textNode.innerHTML='\u{1F4E6} '+t('update.available')+' <b style="margin:0 2px">v'+esc(d.current)+'</b> \u2192 <b style="margin:0 2px">v'+esc(d.latest)+'</b>';
4738
+ var btnUpdate=document.createElement('button');
4739
+ btnUpdate.className='emb-banner-btn';
4740
+ btnUpdate.textContent=t('update.btn');
4741
+ var statusDiv=document.createElement('div');
4742
+ statusDiv.style.cssText='font-size:11px;opacity:.8;flex-shrink:0';
4743
+ btnUpdate.onclick=function(){doUpdateInstall(pkgSpec,btnUpdate,statusDiv)};
4744
+ textNode.appendChild(btnUpdate);
4745
+ var spacer=document.createElement('div');
4746
+ spacer.style.cssText='flex:1';
4747
+ var btnClose=document.createElement('button');
4748
+ btnClose.className='emb-banner-close';
4749
+ btnClose.innerHTML='&times;';
4750
+ btnClose.onclick=function(){banner.remove()};
4751
+ banner.appendChild(textNode);
4752
+ banner.appendChild(statusDiv);
4753
+ banner.appendChild(spacer);
4754
+ banner.appendChild(btnClose);
4755
+ var embBanner=document.querySelector('.emb-banner');
4756
+ if(embBanner&&embBanner.parentNode){embBanner.parentNode.insertBefore(banner,embBanner);}
4757
+ else{var ct=document.querySelector('.content-area')||document.querySelector('main')||document.body;if(ct.firstChild)ct.insertBefore(banner,ct.firstChild);else ct.appendChild(banner);}
4460
4758
  }catch(e){}
4461
4759
  }
4462
4760