@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
@@ -44,7 +44,7 @@ function viewerHTML(pluginVersion) {
44
44
  [data-theme="light"] .auth-screen{background:linear-gradient(135deg,#f0f4ff 0%,#f8f9fb 50%,#eef2ff 100%)}
45
45
  [data-theme="light"] .auth-card{box-shadow:0 25px 50px -12px rgba(0,0,0,.08)}
46
46
  [data-theme="light"] .topbar{background:rgba(255,255,255,.92);border-bottom-color:var(--border);backdrop-filter:blur(8px)}
47
- [data-theme="light"] .session-item .count,[data-theme="light"] .kind-tag,[data-theme="light"] .session-tag{background:rgba(0,0,0,.05)}
47
+ [data-theme="light"] .session-item .count,[data-theme="light"] .session-tag{background:rgba(0,0,0,.05)}
48
48
  [data-theme="light"] .card-content pre{background:#f3f4f6;border-color:var(--border)}
49
49
  [data-theme="light"] .vscore-badge{background:rgba(79,70,229,.06);color:#4f46e5}
50
50
  [data-theme="light"] ::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15)}
@@ -69,10 +69,6 @@ function viewerHTML(pluginVersion) {
69
69
  [data-theme="light"] .tool-agg-table td{background:transparent}
70
70
  [data-theme="light"] .tool-agg-table tr:hover td{background:rgba(79,70,229,.03)}
71
71
  [data-theme="light"] .tool-agg-table th{color:#9ca3af}
72
- [data-theme="light"] .breakdown-item{background:#f9fafb;border-color:var(--border)}
73
- [data-theme="light"] .breakdown-item:hover{background:#f3f4f6;border-color:#cbd5e1}
74
- [data-theme="light"] .breakdown-bar-wrap{background:#e5e7eb}
75
- [data-theme="light"] .breakdown-bar{background:linear-gradient(90deg,#4f46e5,#6366f1);box-shadow:none}
76
72
  [data-theme="light"] .range-btn{background:transparent;border-color:var(--border);color:var(--text-sec)}
77
73
  [data-theme="light"] .range-btn.active{background:rgba(79,70,229,.06);color:#4f46e5;border-color:rgba(79,70,229,.2)}
78
74
  [data-theme="light"] .range-btn:hover{border-color:#4f46e5;color:#4f46e5}
@@ -167,10 +163,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
167
163
  .role-tag.user{background:var(--pri-glow);color:var(--pri);border:1px solid rgba(99,102,241,.12)}
168
164
  .role-tag.assistant{background:var(--accent-glow);color:var(--accent);border:1px solid rgba(230,57,70,.2)}
169
165
  .role-tag.system{background:var(--amber-bg);color:var(--amber);border:1px solid rgba(245,158,11,.2)}
170
- .kind-tag{padding:4px 10px;border-radius:8px;font-size:11px;color:var(--text-sec);background:rgba(0,0,0,.2);font-weight:500}
171
166
  .card-time{font-size:12px;color:var(--text-sec);display:flex;align-items:center;gap:8px}
172
167
  .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}
173
- .card-summary{font-size:15px;font-weight:600;color:var(--text);margin-bottom:10px;line-height:1.5;letter-spacing:-.01em}
168
+ .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}
174
169
  .card-content{font-size:13px;color:var(--text-sec);line-height:1.65;max-height:0;overflow:hidden;transition:max-height .3s ease}
175
170
  .card-content.show{max-height:600px;overflow-y:auto}
176
171
  .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)}
@@ -215,6 +210,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
215
210
  .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)}
216
211
  [data-theme="light"] .merge-history{background:rgba(0,0,0,.04)}
217
212
  [data-theme="light"] .merge-history-item{border-bottom-color:rgba(0,0,0,.06)}
213
+ .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)}
214
+ .card-merged-label{font-size:10px;font-weight:600;color:#10b981;margin-bottom:4px;display:flex;align-items:center;gap:4px}
215
+ [data-theme="light"] .card-merged-info{background:rgba(16,185,129,.04);border-color:rgba(16,185,129,.15)}
218
216
 
219
217
  /* ─── Buttons ─── */
220
218
  .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}
@@ -482,7 +480,41 @@ input,textarea,select{font-family:inherit;font-size:inherit}
482
480
  .log-stat-chip.merged{background:rgba(168,85,247,.12);color:#c084fc}
483
481
  .log-stat-chip.errors{background:rgba(248,113,113,.12);color:#f87171}
484
482
  .log-msg-list{margin-top:8px;display:flex;flex-direction:column;gap:4px}
485
- .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)}
483
+ .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}
484
+ .log-msg-item.expanded{flex-wrap:wrap}
485
+ .recall-layers{margin-top:8px;display:flex;flex-direction:column;gap:10px}
486
+ .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}
487
+ .recall-layer-title .recall-expand-icon{transition:transform .15s;font-size:9px}
488
+ .recall-layer.expanded .recall-layer-title .recall-expand-icon{transform:rotate(90deg)}
489
+ .recall-count{font-size:10px;font-weight:700;padding:1px 6px;border-radius:10px;background:rgba(99,102,241,.1);color:var(--pri)}
490
+ .recall-items{display:none;flex-direction:column;gap:3px}
491
+ .recall-layer.expanded .recall-items{display:flex}
492
+ .recall-item{font-size:11px;line-height:1.4;padding:4px 8px;border-radius:5px;background:rgba(255,255,255,.02);cursor:pointer}
493
+ .recall-item:hover{background:rgba(99,102,241,.06)}
494
+ [data-theme="light"] .recall-item{background:rgba(0,0,0,.02)}
495
+ [data-theme="light"] .recall-item:hover{background:rgba(99,102,241,.06)}
496
+ .recall-item-head{display:flex;gap:6px;align-items:center}
497
+ .recall-idx{flex-shrink:0;font-size:10px;font-weight:600;color:var(--text-muted);min-width:14px;text-align:right}
498
+ .recall-score{flex-shrink:0;font-family:'SF Mono',Consolas,monospace;font-size:10px;font-weight:600;padding:1px 5px;border-radius:4px}
499
+ .recall-score.high{background:rgba(34,197,94,.12);color:#22c55e}
500
+ .recall-score.mid{background:rgba(251,191,36,.12);color:#f59e0b}
501
+ .recall-score.low{background:rgba(248,113,113,.1);color:var(--text-muted)}
502
+ .recall-summary-short{flex:1;color:var(--text-sec);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
503
+ .recall-expand-icon{flex-shrink:0;font-size:10px;color:var(--text-muted);transition:transform .15s}
504
+ .recall-item.expanded .recall-expand-icon{transform:rotate(90deg)}
505
+ .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)}
506
+ .recall-item.expanded .recall-summary-full{display:block}
507
+ .recall-layer.filtered .recall-layer-title{color:var(--pri)}
508
+ .recall-layer.filtered.empty .recall-layer-title{color:var(--text-muted)}
509
+ .recall-more{font-size:10px;color:var(--text-muted);padding:2px 8px}
510
+ .recall-detail{padding:4px 0}
511
+ .recall-detail-section{margin-bottom:10px}
512
+ .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}
513
+ .recall-detail-title .recall-expand-icon{transition:transform .15s;font-size:9px}
514
+ .recall-detail-section.expanded .recall-detail-title .recall-expand-icon{transform:rotate(90deg)}
515
+ .recall-detail-section .recall-detail-items{display:none;flex-direction:column;gap:3px}
516
+ .recall-detail-section.expanded .recall-detail-items{display:flex}
517
+ .recall-detail-section.filtered .recall-detail-title{color:var(--pri)}
486
518
  [data-theme="light"] .log-msg-item{background:rgba(0,0,0,.02)}
487
519
  .log-msg-role{flex-shrink:0;font-size:10px;font-weight:600;padding:1px 6px;border-radius:4px;text-transform:uppercase;letter-spacing:.3px}
488
520
  .log-msg-role.user{background:rgba(59,130,246,.12);color:#60a5fa}
@@ -495,6 +527,15 @@ input,textarea,select{font-family:inherit;font-size:inherit}
495
527
  .log-msg-action.merged{color:#c084fc}
496
528
  .log-msg-action.error{color:#f87171}
497
529
  .log-msg-text{color:var(--text);opacity:.85;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
530
+ .log-msg-text-short{color:var(--text);opacity:.85;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
531
+ .log-msg-text-full{display:none;color:var(--text);opacity:.85;flex:1;min-width:0;word-break:break-word;white-space:pre-wrap}
532
+ .log-msg-item.expanded .log-msg-text-short{display:none}
533
+ .log-msg-item.expanded .log-msg-text-full{display:block}
534
+ .log-msg-item.expanded .recall-expand-icon{transform:rotate(90deg)}
535
+ .log-add-detail{display:flex;flex-direction:column;gap:8px}
536
+ .log-add-msg{display:flex;gap:8px;align-items:flex-start;font-size:12px;line-height:1.6}
537
+ .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)}
538
+ .log-add-msg-content{flex:1;min-width:0;word-break:break-word;white-space:pre-wrap;color:var(--text)}
498
539
  .log-detail{display:none;border-top:1px solid var(--border);padding:0}
499
540
  .log-detail.open{display:block}
500
541
  .log-expand-btn{font-size:10px;color:var(--text-sec);opacity:.5;margin-left:auto;transition:transform .2s,opacity .15s;display:inline-block}
@@ -548,7 +589,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
548
589
  [data-theme="light"] .settings-actions .btn-primary:hover{background:rgba(79,70,229,.1);border-color:#4f46e5}
549
590
  .settings-saved{display:inline-flex;align-items:center;gap:6px;color:var(--green);font-size:12px;font-weight:600;opacity:0;transition:opacity .3s}
550
591
  .settings-saved.show{opacity:1}
551
- .model-health-bar{margin-bottom:20px;border-radius:var(--radius-lg);overflow:hidden}
592
+ .model-health-bar{margin-bottom:20px;border-radius:var(--radius-lg);overflow:visible}
552
593
  .mh-table{width:100%;border-collapse:separate;border-spacing:0;font-size:12px}
553
594
  .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)}
554
595
  .mh-table td{padding:8px 12px;border-bottom:1px solid var(--border);vertical-align:middle}
@@ -566,7 +607,8 @@ input,textarea,select{font-family:inherit;font-size:inherit}
566
607
  .mh-badge.error{background:rgba(239,68,68,.1);color:#dc2626}
567
608
  .mh-badge.unknown{background:rgba(148,163,184,.1);color:#64748b}
568
609
  .mh-model-name{color:var(--text-muted);font-size:11px;font-family:var(--font-mono,'SFMono-Regular',Consolas,monospace)}
569
- .mh-err-text{font-size:11px;color:var(--rose);max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:help}
610
+ .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}
611
+ #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}
570
612
  .mh-time{font-size:10px;color:var(--text-muted);white-space:nowrap}
571
613
  .mh-empty{padding:16px;font-size:12px;color:var(--text-muted);text-align:center}
572
614
  @keyframes healthPulse{0%,100%{opacity:1}50%{opacity:.4}}
@@ -648,14 +690,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
648
690
  .tool-agg-table .ms-val.slow{color:var(--accent)}
649
691
  .chart-legend .dot.violet{background:var(--violet)}
650
692
  .chart-legend .dot.green{background:var(--green)}
651
- .breakdown-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:20px}
652
- .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}
653
- .breakdown-item:hover{border-color:var(--border-glow);background:rgba(255,255,255,.04)}
654
- .breakdown-item .bd-top{display:flex;align-items:center;justify-content:space-between}
655
- .breakdown-item .label{font-size:12px;color:var(--text-sec);font-weight:500;text-transform:capitalize}
656
- .breakdown-item .value{font-size:13px;font-weight:600;color:var(--text)}
657
- .breakdown-bar-wrap{height:3px;background:rgba(255,255,255,.06);border-radius:2px;overflow:hidden}
658
- .breakdown-bar{height:100%;border-radius:2px;background:var(--pri);transition:width .5s ease}
659
693
  .metrics-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
660
694
  .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}
661
695
  .range-btn:hover{border-color:var(--pri);color:var(--pri)}
@@ -812,15 +846,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
812
846
  <button class="filter-chip" data-role="assistant" onclick="setRoleFilter(this,'assistant')">Assistant</button>
813
847
  <button class="filter-chip" data-role="system" onclick="setRoleFilter(this,'system')">System</button>
814
848
  <span class="filter-sep"></span>
815
- <select id="filterKind" class="filter-select" onchange="applyFilters()">
816
- <option value="" data-i18n="filter.allkinds">All kinds</option>
817
- <option value="paragraph" data-i18n="filter.paragraph">Paragraph</option>
818
- <option value="code_block" data-i18n="filter.code">Code</option>
819
- <option value="dialog" data-i18n="filter.dialog">Dialog</option>
820
- <option value="list" data-i18n="filter.list">List</option>
821
- <option value="error_stack" data-i18n="filter.error">Error</option>
822
- <option value="command" data-i18n="filter.command">Command</option>
823
- </select>
824
849
  <select id="filterSort" class="filter-select" onchange="applyFilters()">
825
850
  <option value="newest" data-i18n="filter.newest">Newest first</option>
826
851
  <option value="oldest" data-i18n="filter.oldest">Oldest first</option>
@@ -954,16 +979,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
954
979
  <div id="toolAggTable" style="margin-top:20px"></div>
955
980
  </div>
956
981
 
957
- <div class="breakdown-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
958
- <div class="analytics-section">
959
- <h3><span class="icon">\u{1F464}</span> <span data-i18n="breakdown.role">By Role</span></h3>
960
- <div id="breakdownRole"></div>
961
- </div>
962
- <div class="analytics-section">
963
- <h3><span class="icon">\u{1F4DD}</span> <span data-i18n="breakdown.kind">By Kind</span></h3>
964
- <div id="breakdownKind"></div>
965
- </div>
966
- </div>
967
982
  </div>
968
983
 
969
984
  <!-- ─── Logs View ─── -->
@@ -1203,7 +1218,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1203
1218
  </div>
1204
1219
 
1205
1220
  <div id="migrateActions" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
1206
- <button class="btn btn-ghost" onclick="migrateScan()" id="migrateScanBtn" data-i18n="migrate.scan">Scan Data Sources</button>
1221
+ <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>
1207
1222
  <button class="btn btn-primary" onclick="migrateStart()" id="migrateStartBtn" style="display:none" data-i18n="migrate.start">Start Import</button>
1208
1223
  <span id="migrateConcurrencyRow" style="display:none;align-items:center;gap:6px">
1209
1224
  <span style="font-size:11px;color:var(--text-muted)" data-i18n="migrate.concurrency.label">Concurrent agents</span>
@@ -1344,14 +1359,13 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1344
1359
  <!-- ─── Memory Modal ─── -->
1345
1360
  <div class="modal-overlay" id="modalOverlay">
1346
1361
  <div class="modal">
1347
- <h2 id="modalTitle" data-i18n="modal.new">New Memory</h2>
1362
+ <h2 id="modalTitle" data-i18n="modal.edit">Edit Memory</h2>
1348
1363
  <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>
1349
1364
  <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>
1350
1365
  <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>
1351
- <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>
1352
1366
  <div class="modal-actions">
1353
1367
  <button class="btn btn-ghost" onclick="closeModal()" data-i18n="modal.cancel">Cancel</button>
1354
- <button class="btn btn-primary" id="modalSubmit" onclick="submitModal()" data-i18n="modal.create">Create</button>
1368
+ <button class="btn btn-primary" id="modalSubmit" onclick="submitModal()" data-i18n="modal.save">Save</button>
1355
1369
  </div>
1356
1370
  </div>
1357
1371
  </div>
@@ -1441,13 +1455,6 @@ const I18N={
1441
1455
  'search.meta.text':' text',
1442
1456
  'search.meta.results':' results',
1443
1457
  'filter.all':'All',
1444
- 'filter.allkinds':'All kinds',
1445
- 'filter.paragraph':'Paragraph',
1446
- 'filter.code':'Code',
1447
- 'filter.dialog':'Dialog',
1448
- 'filter.list':'List',
1449
- 'filter.error':'Error',
1450
- 'filter.command':'Command',
1451
1458
  'filter.newest':'Newest first',
1452
1459
  'filter.oldest':'Oldest first',
1453
1460
  'filter.allowners':'All owners',
@@ -1463,6 +1470,8 @@ const I18N={
1463
1470
  'card.delete':'Delete',
1464
1471
  'card.evolved':'Evolved',
1465
1472
  'card.times':'times',
1473
+ 'card.newMessage':'New message',
1474
+ 'card.mergedInfo':'Merged memory',
1466
1475
  'card.updated':'updated',
1467
1476
  'card.evolveHistory':'Evolution History',
1468
1477
  'card.oldSummary':'Old',
@@ -1486,21 +1495,15 @@ const I18N={
1486
1495
  'chart.toolperf':'Tool Response Time',
1487
1496
  'chart.list':'List',
1488
1497
  'chart.search':'Search',
1489
- 'breakdown.role':'By Role',
1490
- 'breakdown.kind':'By Kind',
1491
- 'modal.new':'New Memory',
1492
1498
  'modal.edit':'Edit Memory',
1493
1499
  'modal.role':'Role',
1494
1500
  'modal.content':'Content',
1495
1501
  'modal.content.ph':'Memory content...',
1496
1502
  'modal.summary':'Summary',
1497
1503
  'modal.summary.ph':'Brief summary (optional)',
1498
- 'modal.kind':'Kind',
1499
1504
  'modal.cancel':'Cancel',
1500
- 'modal.create':'Create',
1501
1505
  'modal.save':'Save',
1502
1506
  'modal.err.empty':'Please enter content',
1503
- 'toast.created':'Memory created',
1504
1507
  'toast.updated':'Memory updated',
1505
1508
  'toast.deleted':'Memory deleted',
1506
1509
  'toast.opfail':'Operation failed',
@@ -1527,6 +1530,11 @@ const I18N={
1527
1530
  'logs.output':'OUTPUT',
1528
1531
  'logs.empty':'No logs yet. Logs will appear here when tools are called.',
1529
1532
  'logs.ago':'ago',
1533
+ 'logs.recall.initial':'Initial Retrieval',
1534
+ 'logs.recall.filtered':'LLM Filtered',
1535
+ 'logs.recall.noHits':'No matching memories',
1536
+ 'logs.recall.noneRelevant':'LLM filter: none relevant',
1537
+ 'logs.recall.more':'{n} more...',
1530
1538
  'tab.import':'\u{1F4E5} Import',
1531
1539
  'tab.settings':'\u2699 Settings',
1532
1540
  'settings.modelconfig':'Model Configuration',
@@ -1555,6 +1563,7 @@ const I18N={
1555
1563
  'settings.test.loading':'Testing...',
1556
1564
  'settings.test.ok':'Connected',
1557
1565
  'settings.test.fail':'Failed',
1566
+ 'settings.session.expired':'Session expired, please refresh the page to log in again',
1558
1567
  'settings.save':'Save Settings',
1559
1568
  'settings.reset':'Reset',
1560
1569
  'settings.saved':'Saved',
@@ -1587,12 +1596,18 @@ const I18N={
1587
1596
  'migrate.scan':'Scan Data Sources',
1588
1597
  'migrate.start':'Start Import',
1589
1598
  'migrate.scanning':'Scanning...',
1599
+ 'migrate.scan.required':'Please scan data sources first',
1600
+ 'migrate.scan.done':'Scan complete \u2014 {n} new items found',
1601
+ 'migrate.imported.hint':'{n} items already imported',
1602
+ 'migrate.reconnect.hint':'--- {n} items processed before page reload ---',
1590
1603
  'migrate.stat.stored':'Stored',
1591
1604
  'migrate.stat.skipped':'Skipped',
1592
1605
  'migrate.stat.merged':'Merged',
1593
1606
  'migrate.stat.errors':'Errors',
1594
1607
  'migrate.phase.sqlite':'Importing memory index...',
1595
1608
  'migrate.phase.sessions':'Importing conversation history...',
1609
+ 'migrate.phase.stopped':'Import stopped',
1610
+ 'migrate.phase.done':'Import completed',
1596
1611
  'migrate.chunks':'chunks',
1597
1612
  'migrate.sessions.count':'sessions, {n} messages',
1598
1613
  'migrate.nodata':'No OpenClaw data found to import.',
@@ -1618,7 +1633,8 @@ const I18N={
1618
1633
  'pp.select.warn':'Please select at least one option.',
1619
1634
  'pp.skill.created':'Skill created',
1620
1635
  'pp.stat.tasks':'Tasks',
1621
- 'pp.stat.skills':'Skills',
1636
+ 'pp.stat.skills':'Evolutions',
1637
+ 'pp.stat.skills.total':'Skills',
1622
1638
  'pp.stat.errors':'Errors',
1623
1639
  'pp.stat.skipped':'Skipped',
1624
1640
  'pp.info.skipped':'{n} sessions already processed, skipping.',
@@ -1678,6 +1694,11 @@ const I18N={
1678
1694
  'skill.save.error':'Failed to save skill: ',
1679
1695
  'update.available':'New version available',
1680
1696
  'update.run':'Run',
1697
+ 'update.btn':'Update',
1698
+ 'update.installing':'Installing...',
1699
+ 'update.success':'Updated!',
1700
+ 'update.failed':'Update failed',
1701
+ 'update.restarting':'Restarting service...',
1681
1702
  'update.dismiss':'Dismiss'
1682
1703
  },
1683
1704
  zh:{
@@ -1756,13 +1777,6 @@ const I18N={
1756
1777
  'search.meta.text':' 文本',
1757
1778
  'search.meta.results':' 条结果',
1758
1779
  'filter.all':'全部',
1759
- 'filter.allkinds':'所有类型',
1760
- 'filter.paragraph':'段落',
1761
- 'filter.code':'代码',
1762
- 'filter.dialog':'对话',
1763
- 'filter.list':'列表',
1764
- 'filter.error':'错误',
1765
- 'filter.command':'命令',
1766
1780
  'filter.newest':'最新优先',
1767
1781
  'filter.oldest':'最早优先',
1768
1782
  'filter.allowners':'所有归属',
@@ -1778,6 +1792,8 @@ const I18N={
1778
1792
  'card.delete':'删除',
1779
1793
  'card.evolved':'已演化',
1780
1794
  'card.times':'次',
1795
+ 'card.newMessage':'新消息',
1796
+ 'card.mergedInfo':'合并记忆',
1781
1797
  'card.updated':'更新于',
1782
1798
  'card.evolveHistory':'演化记录',
1783
1799
  'card.oldSummary':'旧摘要',
@@ -1801,21 +1817,15 @@ const I18N={
1801
1817
  'chart.toolperf':'工具响应耗时',
1802
1818
  'chart.list':'列表',
1803
1819
  'chart.search':'搜索',
1804
- 'breakdown.role':'按角色',
1805
- 'breakdown.kind':'按类型',
1806
- 'modal.new':'新建记忆',
1807
1820
  'modal.edit':'编辑记忆',
1808
1821
  'modal.role':'角色',
1809
1822
  'modal.content':'内容',
1810
1823
  'modal.content.ph':'记忆内容...',
1811
1824
  'modal.summary':'摘要',
1812
1825
  'modal.summary.ph':'简要摘要(可选)',
1813
- 'modal.kind':'类型',
1814
1826
  'modal.cancel':'取消',
1815
- 'modal.create':'创建',
1816
1827
  'modal.save':'保存',
1817
1828
  'modal.err.empty':'请输入内容',
1818
- 'toast.created':'记忆已创建',
1819
1829
  'toast.updated':'记忆已更新',
1820
1830
  'toast.deleted':'记忆已删除',
1821
1831
  'toast.opfail':'操作失败',
@@ -1842,6 +1852,11 @@ const I18N={
1842
1852
  'logs.output':'输出',
1843
1853
  'logs.empty':'暂无日志。当工具被调用时日志会显示在这里。',
1844
1854
  'logs.ago':'前',
1855
+ 'logs.recall.initial':'初始检索',
1856
+ 'logs.recall.filtered':'LLM 过滤后',
1857
+ 'logs.recall.noHits':'未匹配到记忆',
1858
+ 'logs.recall.noneRelevant':'LLM 过滤:无相关记忆',
1859
+ 'logs.recall.more':'还有 {n} 条...',
1845
1860
  'tab.import':'\u{1F4E5} 导入',
1846
1861
  'tab.settings':'\u2699 设置',
1847
1862
  'settings.modelconfig':'模型配置',
@@ -1870,6 +1885,7 @@ const I18N={
1870
1885
  'settings.test.loading':'测试中...',
1871
1886
  'settings.test.ok':'连接成功',
1872
1887
  'settings.test.fail':'连接失败',
1888
+ 'settings.session.expired':'登录已过期,请刷新页面重新登录',
1873
1889
  'settings.save':'保存设置',
1874
1890
  'settings.reset':'重置',
1875
1891
  'settings.saved':'已保存',
@@ -1902,12 +1918,18 @@ const I18N={
1902
1918
  'migrate.scan':'扫描数据源',
1903
1919
  'migrate.start':'开始导入',
1904
1920
  'migrate.scanning':'扫描中...',
1921
+ 'migrate.scan.required':'请先扫描数据源',
1922
+ 'migrate.scan.done':'扫描完成 — 发现 {n} 条新数据可导入',
1923
+ 'migrate.imported.hint':'已导入 {n} 条记忆',
1924
+ 'migrate.reconnect.hint':'--- 页面刷新前已处理 {n} 条 ---',
1905
1925
  'migrate.stat.stored':'已存储',
1906
1926
  'migrate.stat.skipped':'已跳过',
1907
1927
  'migrate.stat.merged':'已合并',
1908
1928
  'migrate.stat.errors':'错误',
1909
1929
  'migrate.phase.sqlite':'正在导入记忆索引...',
1910
1930
  'migrate.phase.sessions':'正在导入对话历史...',
1931
+ 'migrate.phase.stopped':'导入已停止',
1932
+ 'migrate.phase.done':'导入完成',
1911
1933
  'migrate.chunks':'条记忆',
1912
1934
  'migrate.sessions.count':'个会话,{n} 条消息',
1913
1935
  'migrate.nodata':'未找到可导入的 OpenClaw 数据。',
@@ -1933,7 +1955,8 @@ const I18N={
1933
1955
  'pp.select.warn':'请至少选择一个选项。',
1934
1956
  'pp.skill.created':'技能已创建',
1935
1957
  'pp.stat.tasks':'任务',
1936
- 'pp.stat.skills':'技能',
1958
+ 'pp.stat.skills':'进化',
1959
+ 'pp.stat.skills.total':'技能',
1937
1960
  'pp.stat.errors':'错误',
1938
1961
  'pp.stat.skipped':'已跳过',
1939
1962
  'pp.info.skipped':'已有 {n} 个会话处理过,自动跳过。',
@@ -1993,6 +2016,11 @@ const I18N={
1993
2016
  'skill.save.error':'保存技能失败:',
1994
2017
  'update.available':'发现新版本',
1995
2018
  'update.run':'执行命令',
2019
+ 'update.btn':'更新',
2020
+ 'update.installing':'安装中...',
2021
+ 'update.success':'更新完成',
2022
+ 'update.failed':'更新失败',
2023
+ 'update.restarting':'正在重启服务...',
1996
2024
  'update.dismiss':'关闭'
1997
2025
  }
1998
2026
  };
@@ -2142,7 +2170,7 @@ function switchView(view){
2142
2170
  loadModelHealth();
2143
2171
  } else if(view==='import'){
2144
2172
  migrateView.classList.add('show');
2145
- if(!window._migrateRunning) migrateScan();
2173
+ if(!window._migrateRunning) migrateScan(false);
2146
2174
  }
2147
2175
  }
2148
2176
  }
@@ -2210,6 +2238,39 @@ function formatLogTime(ts){
2210
2238
  return y+'-'+m+'-'+day+' '+time;
2211
2239
  }
2212
2240
 
2241
+ function parseMemoryAddEntries(out){
2242
+ var lines=out.split('\\n');
2243
+ var results=[];
2244
+ for(var i=0;i<lines.length;i++){
2245
+ var line=lines[i].trim();
2246
+ if(!line) continue;
2247
+ if(line.startsWith('{')){
2248
+ try{
2249
+ var obj=JSON.parse(line);
2250
+ if(obj.role&&obj.action){results.push({role:obj.role,action:obj.action,summary:obj.summary||'',content:obj.content||'',reason:obj.reason||''});continue;}
2251
+ }catch(e){}
2252
+ }
2253
+ var rm=line.match(/^\\[(\\w+)\\]\\s*([^\u2192]+)\u2192/);
2254
+ if(rm){
2255
+ var role=rm[1],actionRaw=rm[2].trim();
2256
+ var action='stored';
2257
+ if(actionRaw.indexOf('exact-dup')>=0||actionRaw.indexOf('\u23ED')>=0) action='exact-dup';
2258
+ else if(actionRaw.indexOf('dedup')>=0||actionRaw.indexOf('\uD83D\uDD01')>=0) action='dedup';
2259
+ else if(actionRaw.indexOf('merged')>=0||actionRaw.indexOf('\uD83D\uDD00')>=0) action='merged';
2260
+ else if(actionRaw.indexOf('error')>=0||actionRaw.indexOf('\u274C')>=0) action='error';
2261
+ var afterArrow=line.replace(/^\\[\\w+\\]\\s*[^\u2192]+\u2192\\s*/,'');
2262
+ var contentLines=[afterArrow];
2263
+ while(i+1<lines.length&&!lines[i+1].trim().startsWith('[')&&!lines[i+1].trim().startsWith('{')){
2264
+ i++;
2265
+ if(lines[i].trim()) contentLines.push(lines[i]);
2266
+ else contentLines.push('');
2267
+ }
2268
+ results.push({role:role,action:action,summary:'',content:contentLines.join('\\n'),reason:''});
2269
+ }
2270
+ }
2271
+ return results;
2272
+ }
2273
+
2213
2274
  function buildLogSummary(lg){
2214
2275
  let inputObj=null;
2215
2276
  try{inputObj=JSON.parse(lg.input);}catch(_){}
@@ -2217,11 +2278,54 @@ function buildLogSummary(lg){
2217
2278
  const tn=lg.toolName;
2218
2279
  if(tn==='memory_search'&&inputObj){
2219
2280
  const q=inputObj.query||'';
2220
- if(q) html+='<div class="log-summary-query">'+escapeHtml(q.length>200?q.slice(0,200)+'...':q)+'</div>';
2221
- const outLines=(lg.output||'').split('\\n');
2222
- const memCount=outLines.filter(l=>l.match(/^\\d+\\.\\s*\\[/)).length;
2223
- if(memCount>0) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u{1F4CE} '+memCount+' memories retrieved</div>';
2224
- 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>';
2281
+ if(q) html+='<div class="log-summary-query">'+escapeHtml(q)+'</div>';
2282
+ var recallData=null;
2283
+ try{recallData=JSON.parse(lg.output);}catch(_){}
2284
+ if(recallData&&recallData.candidates){
2285
+ var cands=recallData.candidates||[];
2286
+ var filtered=recallData.filtered||[];
2287
+ if(cands.length===0){
2288
+ html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u2205 '+t('logs.recall.noHits')+'</div>';
2289
+ }else{
2290
+ html+='<div class="recall-layers">';
2291
+ html+='<div class="recall-layer" onclick="this.classList.toggle(\\\'expanded\\\')">';
2292
+ 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>';
2293
+ html+='<div class="recall-items">';
2294
+ cands.forEach(function(c,i){
2295
+ var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
2296
+ var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
2297
+ var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
2298
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2299
+ 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>';
2300
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2301
+ html+='</div>';
2302
+ });
2303
+ html+='</div></div>';
2304
+ if(filtered.length>0){
2305
+ html+='<div class="recall-layer filtered" onclick="this.classList.toggle(\\\'expanded\\\')">';
2306
+ 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>';
2307
+ html+='<div class="recall-items">';
2308
+ filtered.forEach(function(f){
2309
+ var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
2310
+ var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
2311
+ var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
2312
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2313
+ 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>';
2314
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2315
+ html+='</div>';
2316
+ });
2317
+ html+='</div></div>';
2318
+ }else{
2319
+ html+='<div style="font-size:10px;color:var(--text-muted);margin-top:2px">\u26A0 '+t('logs.recall.noneRelevant')+'</div>';
2320
+ }
2321
+ html+='</div>';
2322
+ }
2323
+ }else{
2324
+ var outLines=(lg.output||'').split('\\n');
2325
+ var memCount=outLines.filter(function(l){return l.match(/^\\d+\\.\\s*\\[/)}).length;
2326
+ if(memCount>0) html+='<div style="margin-top:4px;font-size:11px;color:var(--text-sec)">\u{1F4CE} '+memCount+' memories retrieved</div>';
2327
+ 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>';
2328
+ }
2225
2329
  }else if(tn==='memory_add'&&inputObj){
2226
2330
  const out=lg.output||'';
2227
2331
  const statsMatch=out.match(/^([^\\n]+)/);
@@ -2234,39 +2338,18 @@ function buildLogSummary(lg){
2234
2338
  });
2235
2339
  html+='</div>';
2236
2340
  }
2237
- const outLines=out.split('\\n').filter(l=>l.startsWith('['));
2238
- if(outLines.length>0){
2239
- html+='<div class="log-msg-list">';
2240
- outLines.forEach(function(l){
2241
- var rm=l.match(/^\\[(\\w+)\\]\\s*([^\u2192]+)\u2192\\s*(.*)/);
2242
- if(rm){
2243
- var role=rm[1],actionRaw=rm[2].trim(),text=rm[3].trim();
2244
- var actionCls='stored';
2245
- if(actionRaw.indexOf('exact-dup')>=0||actionRaw.indexOf('\u23ED')>=0) actionCls='exact-dup';
2246
- else if(actionRaw.indexOf('dedup')>=0||actionRaw.indexOf('\uD83D\uDD01')>=0) actionCls='dedup';
2247
- else if(actionRaw.indexOf('merged')>=0||actionRaw.indexOf('\uD83D\uDD00')>=0) actionCls='merged';
2248
- else if(actionRaw.indexOf('error')>=0||actionRaw.indexOf('\u274C')>=0) actionCls='error';
2249
- var actionLabel={'stored':'\u2713 stored','exact-dup':'\u23ED skip','dedup':'\uD83D\uDD01 dedup','merged':'\uD83D\uDD00 merged','error':'\u2717 error'}[actionCls]||actionCls;
2250
- html+='<div class="log-msg-item">'+
2251
- '<span class="log-msg-role '+role+'">'+role+'</span>'+
2252
- '<span class="log-msg-action '+actionCls+'">'+actionLabel+'</span>'+
2253
- '<span class="log-msg-text">'+escapeHtml(text.length>150?text.slice(0,150)+'...':text)+'</span>'+
2254
- '</div>';
2255
- }else{
2256
- html+='<div class="log-msg-item"><span class="log-msg-text">'+escapeHtml(l.length>200?l.slice(0,200)+'...':l)+'</span></div>';
2257
- }
2258
- });
2259
- html+='</div>';
2260
- }else if(inputObj.details&&Array.isArray(inputObj.details)&&inputObj.details.length>0){
2341
+ var parsed=parseMemoryAddEntries(out);
2342
+ if(parsed.length>0){
2261
2343
  html+='<div class="log-msg-list">';
2262
- inputObj.details.forEach(function(d){
2263
- var s=typeof d==='string'?d:String(d);
2264
- var dm=s.match(/^\\[(\\w+)\\]\\s*(.*)/);
2265
- if(dm){
2266
- 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>';
2267
- }else{
2268
- html+='<div class="log-msg-item"><span class="log-msg-text">'+escapeHtml(s.length>150?s.slice(0,150)+'...':s)+'</span></div>';
2269
- }
2344
+ parsed.forEach(function(e){
2345
+ var actionCls=e.action==='exact-dup'?'exact-dup':e.action==='dedup'?'dedup':e.action==='merged'?'merged':e.action==='error'?'error':'stored';
2346
+ var actionLabel={'stored':'\u2713 stored','exact-dup':'\u23ED skip','dedup':'\uD83D\uDD01 dedup','merged':'\uD83D\uDD00 merged','error':'\u2717 error'}[actionCls]||actionCls;
2347
+ var displayText=e.content.split('\\n')[0].trim();
2348
+ html+='<div class="log-msg-item">'+
2349
+ '<span class="log-msg-role '+e.role+'">'+e.role+'</span>'+
2350
+ '<span class="log-msg-action '+actionCls+'">'+actionLabel+'</span>'+
2351
+ '<span class="log-msg-text">'+escapeHtml(displayText)+'</span>'+
2352
+ '</div>';
2270
2353
  });
2271
2354
  html+='</div>';
2272
2355
  }
@@ -2274,9 +2357,51 @@ function buildLogSummary(lg){
2274
2357
  const keys=Object.keys(inputObj);
2275
2358
  keys.slice(0,4).forEach(k=>{
2276
2359
  const v=String(inputObj[k]);
2277
- 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>';
2360
+ html+='<span class="log-summary-kv"><span class="kv-label">'+escapeHtml(k)+':</span><span class="kv-val">'+escapeHtml(v)+'</span></span>';
2361
+ });
2362
+ }
2363
+ return html;
2364
+ }
2365
+ function buildRecallDetailHtml(rd){
2366
+ var html='<div class="recall-detail">';
2367
+ var cands=rd.candidates||[];
2368
+ var filtered=rd.filtered||[];
2369
+ if(cands.length>0){
2370
+ html+='<div class="recall-detail-section" onclick="this.classList.toggle(\\\'expanded\\\')">';
2371
+ html+='<div class="recall-detail-title"><span class="recall-expand-icon">\u25B6</span>\u{1F50D} '+t('logs.recall.initial')+' ('+cands.length+')</div>';
2372
+ html+='<div class="recall-detail-items">';
2373
+ cands.forEach(function(c,i){
2374
+ var scoreClass=c.score>=0.7?'high':c.score>=0.5?'mid':'low';
2375
+ var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
2376
+ var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
2377
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2378
+ 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>';
2379
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2380
+ html+='</div>';
2381
+ });
2382
+ html+='</div></div>';
2383
+ }
2384
+ if(filtered.length>0){
2385
+ html+='<div class="recall-detail-section filtered" onclick="this.classList.toggle(\\\'expanded\\\')">';
2386
+ html+='<div class="recall-detail-title"><span class="recall-expand-icon">\u25B6</span>\u2705 '+t('logs.recall.filtered')+' ('+filtered.length+')</div>';
2387
+ html+='<div class="recall-detail-items">';
2388
+ filtered.forEach(function(f,i){
2389
+ var scoreClass=f.score>=0.7?'high':f.score>=0.5?'mid':'low';
2390
+ var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
2391
+ var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
2392
+ html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
2393
+ 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>';
2394
+ html+='<div class="recall-summary-full">'+fullText+'</div>';
2395
+ html+='</div>';
2278
2396
  });
2397
+ html+='</div></div>';
2398
+ }else if(cands.length>0){
2399
+ html+='<div style="font-size:10px;color:var(--text-muted);margin-top:4px">\u26A0 '+t('logs.recall.noneRelevant')+'</div>';
2279
2400
  }
2401
+ if(rd.status==='error'&&rd.error){
2402
+ html+='<div style="margin-top:8px;color:var(--accent);font-size:12px">\u274C '+escapeHtml(rd.error)+'</div>';
2403
+ }
2404
+ html+='</div>';
2280
2405
  return html;
2281
2406
  }
2282
2407
  function renderLogs(logs){
@@ -2291,7 +2416,29 @@ function renderLogs(logs){
2291
2416
  const toolCls=lg.toolName.replace(/[^a-zA-Z0-9_]/g,'_');
2292
2417
  const dur=lg.durationMs<1000?Math.round(lg.durationMs)+'ms':(lg.durationMs/1000).toFixed(1)+'s';
2293
2418
  let inputDisplay='';
2294
- try{const parsed=JSON.parse(lg.input);inputDisplay=JSON.stringify(parsed,null,2);}catch(_){inputDisplay=lg.input;}
2419
+ let inputHtml='';
2420
+ let outputHtml='';
2421
+ try{
2422
+ const parsed=JSON.parse(lg.input);
2423
+ if(lg.toolName==='memory_add'){
2424
+ var addEntries=parseMemoryAddEntries(lg.output||'');
2425
+ if(addEntries.length>0){
2426
+ inputHtml='<div class="log-add-detail">';
2427
+ addEntries.forEach(function(e){
2428
+ 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>';
2429
+ });
2430
+ inputHtml+='</div>';
2431
+ }
2432
+ }else if(parsed.type==='auto_recall'||parsed.type==='tool_call'){
2433
+ inputDisplay=JSON.stringify({query:parsed.query},null,2);
2434
+ }else{
2435
+ inputDisplay=JSON.stringify(parsed,null,2);
2436
+ }
2437
+ }catch(_){inputDisplay=lg.input;}
2438
+ try{
2439
+ var rd2=null;try{rd2=JSON.parse(lg.output);}catch(_e){}
2440
+ if(rd2&&rd2.candidates){outputHtml=buildRecallDetailHtml(rd2);}
2441
+ }catch(_){}
2295
2442
  const summary=buildLogSummary(lg);
2296
2443
  return '<div class="log-entry" id="log-'+i+'">'+
2297
2444
  '<div class="log-header" onclick="toggleLog('+i+')">'+
@@ -2305,11 +2452,11 @@ function renderLogs(logs){
2305
2452
  '<div class="log-detail" id="log-detail-'+i+'">'+
2306
2453
  '<div class="log-io-section">'+
2307
2454
  '<div class="log-io-label">\u25B6 '+t('logs.input')+'</div>'+
2308
- '<pre class="log-io-content">'+escapeHtml(inputDisplay)+'</pre>'+
2455
+ (inputHtml?inputHtml:'<pre class="log-io-content">'+escapeHtml(inputDisplay)+'</pre>')+
2309
2456
  '</div>'+
2310
2457
  '<div class="log-io-section">'+
2311
2458
  '<div class="log-io-label">\u25C0 '+t('logs.output')+'</div>'+
2312
- '<pre class="log-io-content">'+escapeHtml(lg.output)+'</pre>'+
2459
+ (outputHtml?outputHtml:'<pre class="log-io-content">'+escapeHtml(lg.output)+'</pre>')+
2313
2460
  '</div>'+
2314
2461
  '</div>'+
2315
2462
  '</div>';
@@ -2352,8 +2499,6 @@ async function loadMetrics(){
2352
2499
  document.getElementById('mSessions').textContent=formatNum(d.totals.sessions);
2353
2500
  document.getElementById('mEmbeddings').textContent=formatNum(d.totals.embeddings);
2354
2501
  renderChartWrites(d.writesPerDay);
2355
- renderBreakdown(d.roleBreakdown,'breakdownRole');
2356
- renderBreakdown(d.kindBreakdown,'breakdownKind');
2357
2502
  loadToolMetrics();
2358
2503
  }
2359
2504
 
@@ -2869,7 +3014,14 @@ function classifyError(msg){
2869
3014
  if(msg.indexOf('ECONNREFUSED')>=0) return 'Connection refused';
2870
3015
  if(msg.indexOf('ENOTFOUND')>=0) return 'DNS resolution failed';
2871
3016
  if(msg.indexOf('403')>=0) return 'Forbidden (403)';
2872
- return msg.length>50?msg.slice(0,47)+'...':msg;
3017
+ if(msg.indexOf('503')>=0||msg.indexOf('upstream connect error')>=0||msg.indexOf('Service Unavailable')>=0) return 'Service unavailable (503)';
3018
+ if(msg.indexOf('502')>=0||msg.indexOf('Bad Gateway')>=0) return 'Bad gateway (502)';
3019
+ if(msg.indexOf('500')>=0||msg.indexOf('Internal Server Error')>=0) return 'Server error (500)';
3020
+ if(msg.indexOf('404')>=0||msg.indexOf('Not Found')>=0) return 'Not found (404)';
3021
+ if(msg.indexOf('fetch failed')>=0||msg.indexOf('ETIMEDOUT')>=0) return 'Network error';
3022
+ if(msg.indexOf('Unknown')>=0&&msg.indexOf('provider')>=0) return 'Unknown provider';
3023
+ var m=msg.match(/\((\d{3})\)/); if(m) return 'HTTP error ('+m[1]+')';
3024
+ return msg.length>80?msg.substring(0,77)+'...':msg;
2873
3025
  }
2874
3026
 
2875
3027
  function shortenModel(s){return s?s.replace('openai_compatible/','').replace('openai/',''):'\u2014';}
@@ -2915,7 +3067,7 @@ async function loadModelHealth(){
2915
3067
  issue+=shortErr;
2916
3068
  if(m.consecutiveErrors>1) issue+=' ('+m.consecutiveErrors+'x)';
2917
3069
  }
2918
- if(issue) h+='<td><span class="mh-err-text" title="'+escapeHtml(m.lastErrorMessage||'')+'">'+escapeHtml(issue)+'</span></td>';
3070
+ if(issue) h+='<td><span class="mh-err-text" data-err="'+escapeHtml(m.lastErrorMessage||'')+'">'+escapeHtml(issue)+'</span></td>';
2919
3071
  else h+='<td><span style="color:var(--text-muted);font-size:11px">\u2014</span></td>';
2920
3072
 
2921
3073
  h+='<td style="text-align:right"><span class="mh-time">'+(ago||'\u2014')+'</span></td>';
@@ -2923,11 +3075,29 @@ async function loadModelHealth(){
2923
3075
  }
2924
3076
  h+='</tbody></table>';
2925
3077
  bar.innerHTML=h;
3078
+ initMhTooltips();
2926
3079
  }catch(e){
2927
3080
  bar.innerHTML='<div class="mh-empty">Failed to load model health</div>';
2928
3081
  }
2929
3082
  }
2930
3083
 
3084
+ function initMhTooltips(){
3085
+ var tip=document.getElementById('mhTooltip');
3086
+ if(!tip){tip=document.createElement('div');tip.id='mhTooltip';document.body.appendChild(tip);}
3087
+ document.querySelectorAll('.mh-err-text[data-err]').forEach(function(el){
3088
+ el.addEventListener('mouseenter',function(e){
3089
+ var msg=el.getAttribute('data-err');
3090
+ if(!msg)return;
3091
+ tip.textContent=msg;
3092
+ tip.style.display='block';
3093
+ var rect=el.getBoundingClientRect();
3094
+ tip.style.left=Math.max(0,Math.min(rect.left,window.innerWidth-490))+'px';
3095
+ tip.style.top=(rect.bottom+6)+'px';
3096
+ });
3097
+ el.addEventListener('mouseleave',function(){tip.style.display='none';});
3098
+ });
3099
+ }
3100
+
2931
3101
  function timeAgo(ts){
2932
3102
  var diff=Date.now()-ts;
2933
3103
  if(diff<60000) return 'just now';
@@ -3058,6 +3228,7 @@ async function saveConfig(){
3058
3228
  // 2) Test embedding
3059
3229
  try{
3060
3230
  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||''})});
3231
+ if(er.status===401){done();toast(t('settings.session.expired'),'error');return;}
3061
3232
  var ed=await er.json();
3062
3233
  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;}
3063
3234
  document.getElementById('testEmbResult').className='test-result ok';document.getElementById('testEmbResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3067,6 +3238,7 @@ async function saveConfig(){
3067
3238
  if(hasSumConfig&&cfg.summarizer){
3068
3239
  try{
3069
3240
  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||''})});
3241
+ if(sr.status===401){done();toast(t('settings.session.expired'),'error');return;}
3070
3242
  var sd=await sr.json();
3071
3243
  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;}
3072
3244
  document.getElementById('testSumResult').className='test-result ok';document.getElementById('testSumResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3077,6 +3249,7 @@ async function saveConfig(){
3077
3249
  if(hasSkillConfig&&cfg.skillEvolution.summarizer){
3078
3250
  try{
3079
3251
  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||''})});
3252
+ if(kr.status===401){done();toast(t('settings.session.expired'),'error');return;}
3080
3253
  var kd=await kr.json();
3081
3254
  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;}
3082
3255
  document.getElementById('testSkillResult').className='test-result ok';document.getElementById('testSkillResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3134,6 +3307,7 @@ async function testModel(type){
3134
3307
  try{
3135
3308
  var body={type:type,provider:provider,model:model,endpoint:endpoint,apiKey:apiKey};
3136
3309
  var r=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
3310
+ if(r.status===401){resultEl.className='test-result fail';resultEl.innerHTML='\\u274C '+t('settings.session.expired');btn.disabled=false;return;}
3137
3311
  var d=await r.json();
3138
3312
  if(d.ok){
3139
3313
  resultEl.className='test-result ok';
@@ -3458,17 +3632,6 @@ function renderToolAgg(data){
3458
3632
  '</tbody></table>';
3459
3633
  }
3460
3634
 
3461
- function renderBreakdown(obj,containerId){
3462
- const el=document.getElementById(containerId);
3463
- if(!el)return;
3464
- const entries=Object.entries(obj||{}).sort((a,b)=>b[1]-a[1]);
3465
- const total=entries.reduce((s,[,v])=>s+v,0)||1;
3466
- el.innerHTML=entries.map(([label,value])=>{
3467
- const pct=Math.round((value/total)*100);
3468
- 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>';
3469
- }).join('');
3470
- }
3471
-
3472
3635
  /* ─── Data loading ─── */
3473
3636
  async function loadAll(){
3474
3637
  await Promise.all([loadStats(),loadMemories()]);
@@ -3551,8 +3714,6 @@ function getFilterParams(){
3551
3714
  const p=new URLSearchParams();
3552
3715
  if(activeSession) p.set('session',activeSession);
3553
3716
  if(activeRole) p.set('role',activeRole);
3554
- const kind=document.getElementById('filterKind').value;
3555
- if(kind) p.set('kind',kind);
3556
3717
  const df=document.getElementById('dateFrom').value;
3557
3718
  if(df) p.set('dateFrom',df);
3558
3719
  const dt=document.getElementById('dateTo').value;
@@ -3656,18 +3817,19 @@ function renderMemories(items){
3656
3817
  list.innerHTML=items.map(m=>{
3657
3818
  const time=m.created_at?new Date(typeof m.created_at==='number'?m.created_at:m.created_at).toLocaleString('zh-CN'):'';
3658
3819
  const role=m.role||'user';
3659
- const kind=m.kind||'paragraph';
3660
- const summary=esc(m.summary||m.content?.slice(0,120)||'');
3661
- const content=esc(m.content||'');
3820
+ const rawSummary=m.summary||'';
3821
+ const rawContent=m.content||'';
3822
+ const content=esc(rawContent);
3662
3823
  const id=m.id;
3663
3824
  const vscore=m._vscore?'<span class="vscore-badge">'+Math.round(m._vscore*100)+'%</span>':'';
3664
3825
  const sid=m.session_key||'';
3665
3826
  const sidShort=sid.length>18?sid.slice(0,6)+'..'+sid.slice(-6):sid;
3666
3827
  const mc=m.merge_count||0;
3828
+ const cardTitle=esc(rawSummary||rawContent||'');
3667
3829
  const mergeBadge=mc>0?'<span class="merge-badge">\\u{1F504} '+t('card.evolved')+' '+mc+t('card.times')+'</span>':'';
3668
3830
  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>':'';
3669
3831
  const ds=m.dedup_status||'active';
3670
- const isInactive=ds==='duplicate'||ds==='merged';
3832
+ const isInactive=ds==='merged';
3671
3833
  const dedupBadge=ds==='duplicate'?'<span class="dedup-badge duplicate">'+t('card.dedupDuplicate')+'</span>':ds==='merged'?'<span class="dedup-badge merged">'+t('card.dedupMerged')+'</span>':'';
3672
3834
  const isImported=sid.startsWith('openclaw-import-')||sid.startsWith('openclaw-session-');
3673
3835
  const importBadge=isImported?'<span class="import-badge">\u{1F990} '+t('card.imported')+'</span>':'';
@@ -3675,7 +3837,7 @@ function renderMemories(items){
3675
3837
  const isPublicMem=ownerVal==='public';
3676
3838
  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>';
3677
3839
  let dedupInfo='';
3678
- if(isInactive){
3840
+ if(ds==='duplicate'||ds==='merged'){
3679
3841
  const reason=m.dedup_reason?'<span style="font-size:11px;color:var(--text-muted)">'+t('card.dedupReason')+esc(m.dedup_reason)+'</span>':'';
3680
3842
  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>':'';
3681
3843
  dedupInfo='<div style="margin-top:6px;font-size:11px">'+target+' '+reason+'</div>';
@@ -3698,8 +3860,23 @@ function renderMemories(items){
3698
3860
  }catch(e){}
3699
3861
  }
3700
3862
  return '<div class="memory-card'+(isInactive?' dedup-inactive':'')+'">'+
3701
- '<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>'+
3702
- '<div class="card-summary">'+summary+'</div>'+
3863
+ '<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>'+
3864
+ '<div class="card-summary">'+cardTitle+'</div>'+
3865
+ (function(){
3866
+ if(mc<=0) return '';
3867
+ var mergeHtml='<div class="card-merged-info">';
3868
+ mergeHtml+='<div class="card-merged-label">\\u{1F504} '+t('card.mergedInfo')+' ('+mc+t('card.times')+')</div>';
3869
+ var sources=m.merge_sources||[];
3870
+ if(sources.length>0){
3871
+ mergeHtml+='<div style="display:flex;flex-wrap:wrap;gap:6px">';
3872
+ sources.forEach(function(s){
3873
+ mergeHtml+='<span class="dedup-target-link" onclick="scrollToMemory(\\''+s.id+'\\')">\\u{1F517} '+s.id.slice(0,8)+'...</span>';
3874
+ });
3875
+ mergeHtml+='</div>';
3876
+ }
3877
+ mergeHtml+='</div>';
3878
+ return mergeHtml;
3879
+ })()+
3703
3880
  dedupInfo+
3704
3881
  '<div class="card-content" id="content-'+id+'"><pre>'+content+'</pre></div>'+
3705
3882
  historyHtml+
@@ -3778,12 +3955,11 @@ async function showMemoryModal(chunkId){
3778
3955
  const m=data.memory;
3779
3956
  const role=(m.role||'unknown').toUpperCase();
3780
3957
  const roleCls=(m.role||'').toLowerCase();
3781
- const kind=m.kind||'paragraph';
3782
3958
  const ds=m.dedup_status||'active';
3783
3959
  const time=new Date(m.created_at).toLocaleString('zh-CN');
3784
3960
  const updated=m.updated_at?new Date(m.updated_at).toLocaleString('zh-CN'):'';
3785
3961
  let html='<div class="modal-memory-card">';
3786
- html+='<div class="modal-header-row"><span class="role-tag '+roleCls+'">'+role+'</span><span class="kind-tag">'+kind+'</span>';
3962
+ html+='<div class="modal-header-row"><span class="role-tag '+roleCls+'">'+role+'</span>';
3787
3963
  if(ds!=='active') html+='<span class="dedup-badge '+(ds==='duplicate'?'duplicate':'merged')+'">'+ds+'</span>';
3788
3964
  html+='</div>';
3789
3965
  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>';
@@ -3841,17 +4017,6 @@ function renderSummaryHtml(raw){
3841
4017
  }
3842
4018
 
3843
4019
  /* ─── CRUD ─── */
3844
- function openCreateModal(){
3845
- editingId=null;
3846
- document.getElementById('modalTitle').textContent=t('modal.new');
3847
- document.getElementById('modalSubmit').textContent=t('modal.create');
3848
- document.getElementById('mRole').value='user';
3849
- document.getElementById('mContent').value='';
3850
- document.getElementById('mSummary').value='';
3851
- document.getElementById('mKind').value='paragraph';
3852
- document.getElementById('modalOverlay').classList.add('show');
3853
- }
3854
-
3855
4020
  function openEditModal(id){
3856
4021
  const m=memoryCache[id];
3857
4022
  if(!m){toast(t('toast.notfound'),'error');return}
@@ -3861,7 +4026,6 @@ function openEditModal(id){
3861
4026
  document.getElementById('mRole').value=m.role||'user';
3862
4027
  document.getElementById('mContent').value=m.content||'';
3863
4028
  document.getElementById('mSummary').value=m.summary||'';
3864
- document.getElementById('mKind').value=m.kind||'paragraph';
3865
4029
  document.getElementById('modalOverlay').classList.add('show');
3866
4030
  }
3867
4031
 
@@ -3870,21 +4034,16 @@ function closeModal(){
3870
4034
  }
3871
4035
 
3872
4036
  async function submitModal(){
4037
+ if(!editingId)return;
3873
4038
  const data={
3874
4039
  role:document.getElementById('mRole').value,
3875
4040
  content:document.getElementById('mContent').value,
3876
4041
  summary:document.getElementById('mSummary').value,
3877
- kind:document.getElementById('mKind').value,
3878
4042
  };
3879
4043
  if(!data.content.trim()){toast(t('modal.err.empty'),'error');return}
3880
- let r;
3881
- if(editingId){
3882
- r=await fetch('/api/memory/'+editingId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3883
- } else {
3884
- r=await fetch('/api/memory',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3885
- }
4044
+ const r=await fetch('/api/memory/'+editingId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3886
4045
  const d=await r.json();
3887
- if(d.ok){toast(editingId?t('toast.updated'):t('toast.created'),'success');closeModal();loadAll();}
4046
+ if(d.ok){toast(t('toast.updated'),'success');closeModal();loadAll();}
3888
4047
  else{toast(d.error||t('toast.opfail'),'error')}
3889
4048
  }
3890
4049
 
@@ -3907,12 +4066,15 @@ async function toggleMemoryPublic(id,setPublic){
3907
4066
  }
3908
4067
 
3909
4068
  async function clearAll(){
3910
- if(!confirm(t('confirm.clearall')))return;
3911
- if(!confirm(t('confirm.clearall2')))return;
3912
- const r=await fetch('/api/memories',{method:'DELETE'});
3913
- const d=await r.json();
3914
- if(d.ok){toast(t('toast.cleared'),'success');loadAll();}
3915
- else{toast(t('toast.clearfail'),'error')}
4069
+ try{
4070
+ if(!confirm(t('confirm.clearall')))return;
4071
+ if(!confirm(t('confirm.clearall2')))return;
4072
+ const r=await fetch('/api/memories',{method:'DELETE'});
4073
+ if(r.status===401){toast(t('settings.session.expired'),'error');return;}
4074
+ const d=await r.json();
4075
+ if(d.ok){toast(t('toast.cleared'),'success');loadAll();}
4076
+ else{toast(t('toast.clearfail'),'error')}
4077
+ }catch(e){toast('Error: '+e.message,'error')}
3916
4078
  }
3917
4079
 
3918
4080
  /* ─── Migration ─── */
@@ -3932,7 +4094,7 @@ let migrateStats={stored:0,skipped:0,merged:0,errors:0};
3932
4094
  });
3933
4095
  })();
3934
4096
 
3935
- async function migrateScan(){
4097
+ async function migrateScan(showToast){
3936
4098
  const btn=document.getElementById('migrateScanBtn');
3937
4099
  btn.disabled=true;
3938
4100
  btn.textContent=t('migrate.scanning');
@@ -3965,9 +4127,31 @@ async function migrateScan(){
3965
4127
  t('migrate.config.warn.desc')+' ('+parts.join(', ')+')';
3966
4128
  }
3967
4129
 
4130
+ const imported=d.importedChunkCount||0;
4131
+ const remaining=Math.max(0,(d.totalItems||0)-imported);
4132
+
3968
4133
  if(d.totalItems>0 && d.configReady){
3969
4134
  document.getElementById('migrateStartBtn').style.display='inline-flex';
3970
4135
  document.getElementById('migrateConcurrencyRow').style.display='inline-flex';
4136
+ if(d.hasImportedData){
4137
+ document.getElementById('migrateStartBtn').textContent=t('migrate.resume');
4138
+ }else{
4139
+ document.getElementById('migrateStartBtn').textContent=t('migrate.start');
4140
+ }
4141
+ }
4142
+
4143
+ var hintEl=document.getElementById('migrateImportedHint');
4144
+ if(!hintEl){
4145
+ hintEl=document.createElement('div');
4146
+ hintEl.id='migrateImportedHint';
4147
+ hintEl.style.cssText='font-size:12px;color:var(--text-sec);padding:6px 0';
4148
+ document.getElementById('migrateActions').appendChild(hintEl);
4149
+ }
4150
+ if(imported>0){
4151
+ hintEl.textContent=t('migrate.imported.hint').replace('{n}',imported);
4152
+ hintEl.style.display='block';
4153
+ }else{
4154
+ hintEl.style.display='none';
3971
4155
  }
3972
4156
 
3973
4157
  if(d.totalItems===0){
@@ -3977,6 +4161,7 @@ async function migrateScan(){
3977
4161
  if(d.hasImportedData){
3978
4162
  document.getElementById('postprocessSection').style.display='block';
3979
4163
  }
4164
+ if(showToast) toast(t('migrate.scan.done').replace('{n}',remaining),'success');
3980
4165
  }catch(e){
3981
4166
  toast('Scan failed: '+e.message,'error');
3982
4167
  }finally{
@@ -3986,27 +4171,36 @@ async function migrateScan(){
3986
4171
  }
3987
4172
 
3988
4173
  function migrateStart(){
3989
- if(!migrateScanData||!migrateScanData.configReady)return;
3990
- if(!confirm(t('migrate.start')+'?'))return;
4174
+ const isResume=document.getElementById('migrateStartBtn').textContent===t('migrate.resume');
4175
+ if(!isResume){
4176
+ if(!migrateScanData||!migrateScanData.configReady){
4177
+ toast(t('migrate.scan.required'),'error');
4178
+ return;
4179
+ }
4180
+ if(!confirm(t('migrate.start')+'?'))return;
4181
+ }
3991
4182
 
3992
4183
  const concSel=document.getElementById('migrateConcurrency');
3993
4184
  const concurrency=concSel?parseInt(concSel.value,10)||1:1;
3994
4185
 
3995
4186
  window._migrateRunning=true;
3996
- _migrateStatusChecked=false;
4187
+ _migrateStatusChecked=true;
3997
4188
  document.getElementById('migrateStartBtn').style.display='none';
3998
4189
  document.getElementById('migrateScanBtn').disabled=true;
4190
+ var hintEl=document.getElementById('migrateImportedHint');
4191
+ if(hintEl) hintEl.style.display='none';
3999
4192
  document.getElementById('migrateConcurrencyRow').style.display='none';
4000
4193
  document.getElementById('migrateConcurrencyWarn').style.display='none';
4001
4194
  document.getElementById('migrateProgress').style.display='block';
4002
4195
  document.getElementById('migrateLiveLog').innerHTML='';
4003
4196
  migrateStats={stored:0,skipped:0,merged:0,errors:0};
4004
4197
  updateMigrateStats();
4198
+ document.getElementById('migrateBar').style.width='0%';
4199
+ document.getElementById('migrateCounter').textContent='';
4005
4200
 
4006
4201
  document.getElementById('migrateStopBtn').disabled=false;
4007
- document.getElementById('migrateBar').style.width='0%';
4202
+ document.getElementById('migrateStopBtn').style.display='inline-flex';
4008
4203
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#6366f1,#8b5cf6)';
4009
- document.getElementById('migrateCounter').textContent='';
4010
4204
  const body=JSON.stringify({sources:['sqlite','sessions'],concurrency});
4011
4205
  connectMigrateSSE('/api/migrate/start','POST',body);
4012
4206
  }
@@ -4043,7 +4237,7 @@ function readSSEStream(r){
4043
4237
  const NL=String.fromCharCode(10);
4044
4238
  function pump(){
4045
4239
  reader.read().then(({done,value})=>{
4046
- if(done){if(!migrateDoneCalled&&!window._migrateRunning)onMigrateDone(false);return;}
4240
+ if(done){if(!migrateDoneCalled)onMigrateDone(false);return;}
4047
4241
  buf+=decoder.decode(value,{stream:true});
4048
4242
  const lines=buf.split(NL);
4049
4243
  buf=lines.pop()||'';
@@ -4066,7 +4260,7 @@ function readSSEStream(r){
4066
4260
 
4067
4261
  var _migrateStatusChecked=false;
4068
4262
  async function checkMigrateStatus(){
4069
- if(_migrateStatusChecked) return;
4263
+ if(_migrateStatusChecked||window._migrateRunning) return;
4070
4264
  _migrateStatusChecked=true;
4071
4265
  try{
4072
4266
  const r=await fetch('/api/migrate/status');
@@ -4088,7 +4282,18 @@ async function checkMigrateStatus(){
4088
4282
  document.getElementById('migrateCounter').textContent=s.processed+' / '+s.total+' ('+pct+'%)';
4089
4283
  const label=s.phase==='sqlite'?t('migrate.phase.sqlite'):t('migrate.phase.sessions');
4090
4284
  document.getElementById('migratePhaseLabel').textContent=label;
4285
+ document.getElementById('migrateStopBtn').style.display='inline-flex';
4286
+ if(s.processed>0){
4287
+ const log=document.getElementById('migrateLiveLog');
4288
+ const hint=document.createElement('div');
4289
+ hint.style.cssText='text-align:center;padding:8px 12px;color:var(--text-muted);font-size:11px;border-bottom:1px solid var(--border)';
4290
+ hint.textContent=t('migrate.reconnect.hint').replace('{n}',s.processed);
4291
+ log.appendChild(hint);
4292
+ }
4091
4293
  connectMigrateSSE('/api/migrate/stream','GET',null);
4294
+ fetch('/api/migrate/scan').then(function(sr){return sr.json()}).then(function(sd){
4295
+ if(sd&&sd.hasImportedData) document.getElementById('postprocessSection').style.display='block';
4296
+ }).catch(function(){});
4092
4297
  }else if(s.done&&(s.stored>0||s.skipped>0||s.stopped)){
4093
4298
  migrateStats={stored:s.stored,skipped:s.skipped,merged:s.merged,errors:s.errors};
4094
4299
  updateMigrateStats();
@@ -4186,18 +4391,23 @@ function onMigrateDone(wasStopped,skipReload){
4186
4391
  document.getElementById('migrateScanBtn').disabled=false;
4187
4392
  document.getElementById('migrateStopBtn').disabled=true;
4188
4393
  document.getElementById('migrateStopBtn').textContent=t('migrate.stop');
4394
+ document.getElementById('migrateStopBtn').style.display='none';
4189
4395
  if(wasStopped){
4190
4396
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#f59e0b,#fbbf24)';
4191
4397
  document.getElementById('migrateStartBtn').style.display='inline-flex';
4192
4398
  document.getElementById('migrateStartBtn').textContent=t('migrate.resume');
4399
+ document.getElementById('migratePhaseLabel').textContent=t('migrate.phase.stopped');
4193
4400
  }else{
4194
4401
  document.getElementById('migrateBar').style.width='100%';
4195
4402
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4196
4403
  const total=migrateStats.stored+migrateStats.skipped+migrateStats.merged+migrateStats.errors;
4197
4404
  if(total>0) document.getElementById('migrateCounter').textContent=total+' / '+total+' (100%)';
4405
+ document.getElementById('migratePhaseLabel').textContent=t('migrate.phase.done');
4198
4406
  }
4199
4407
  fetch('/api/migrate/scan').then(r=>{if(!r.ok)throw new Error();return r.json()}).then(d=>{
4200
- if(d&&d.hasImportedData) document.getElementById('postprocessSection').style.display='block';
4408
+ if(d&&d.hasImportedData){
4409
+ document.getElementById('postprocessSection').style.display='block';
4410
+ }
4201
4411
  }).catch(()=>{});
4202
4412
  if(!skipReload) loadAll();
4203
4413
  }
@@ -4325,12 +4535,18 @@ function connectPPSSE(){
4325
4535
  }).catch(function(){});
4326
4536
  }else if(s.done){
4327
4537
  document.getElementById('postprocessSection').style.display='block';
4328
- ppStats={tasks:s.tasksCreated||0,skills:s.skillsCreated||0,errors:s.errors||0,skipped:0};
4538
+ ppStats={tasks:s.tasksCreated||0,skills:s.skillsCreated||0,errors:s.errors||0,skipped:s.skippedSessions||0};
4329
4539
  updatePPStats();
4330
4540
  document.getElementById('ppProgress').style.display='block';
4331
- var pct2=s.total>0?Math.round((s.processed/s.total)*100):0;
4332
- document.getElementById('ppBar').style.width=pct2+'%';
4333
- document.getElementById('ppCounter').textContent=s.processed+' / '+s.total+' ('+pct2+'%)';
4541
+ var totalAll=(s.total||0)+(s.skippedSessions||0);
4542
+ if(totalAll>0){
4543
+ document.getElementById('ppBar').style.width='100%';
4544
+ document.getElementById('ppCounter').textContent=totalAll+' / '+totalAll+' (100%)';
4545
+ }else{
4546
+ var pct2=s.total>0?Math.round((s.processed/s.total)*100):0;
4547
+ document.getElementById('ppBar').style.width=pct2+'%';
4548
+ document.getElementById('ppCounter').textContent=s.processed+' / '+s.total+' ('+pct2+'%)';
4549
+ }
4334
4550
  ppDone(!!s.stopped,false,true);
4335
4551
  }
4336
4552
  }).catch(function(){});
@@ -4338,9 +4554,11 @@ function connectPPSSE(){
4338
4554
 
4339
4555
  function handlePPEvent(evtType,data){
4340
4556
  if(evtType==='progress'){
4341
- var pct=data.total>0?Math.round((data.processed/data.total)*100):0;
4342
- document.getElementById('ppBar').style.width=pct+'%';
4343
- document.getElementById('ppCounter').textContent=data.processed+' / '+data.total+' ('+pct+'%)';
4557
+ if(data.total>0){
4558
+ var pct=Math.round((data.processed/data.total)*100);
4559
+ document.getElementById('ppBar').style.width=pct+'%';
4560
+ document.getElementById('ppCounter').textContent=data.processed+' / '+data.total+' ('+pct+'%)';
4561
+ }
4344
4562
  }else if(evtType==='info'){
4345
4563
  if(data.alreadyProcessed>0){
4346
4564
  ppStats.skipped=data.alreadyProcessed;
@@ -4349,6 +4567,10 @@ function handlePPEvent(evtType,data){
4349
4567
  }
4350
4568
  if(data.pending===0){
4351
4569
  appendPPLogItem({step:'done',session:t('pp.info.allDone'),index:'',total:''});
4570
+ document.getElementById('ppPhaseLabel').textContent=t('pp.info.allDone');
4571
+ document.getElementById('ppBar').style.width='100%';
4572
+ document.getElementById('ppBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4573
+ document.getElementById('ppCounter').textContent=data.alreadyProcessed+' / '+data.totalSessions;
4352
4574
  }else{
4353
4575
  document.getElementById('ppPhaseLabel').textContent=t('pp.info.pending').replace('{n}',data.pending);
4354
4576
  }
@@ -4360,12 +4582,10 @@ function handlePPEvent(evtType,data){
4360
4582
  document.getElementById('ppPhaseLabel').textContent=t('pp.running')+' — '+actionLabel+' — '+label;
4361
4583
  }
4362
4584
  if(data.step==='done'){
4363
- if(data.action==='skill-only'){
4364
- ppStats.skills++;
4365
- }else{
4585
+ if(data.action!=='skill-only'){
4366
4586
  ppStats.tasks++;
4587
+ updatePPStats();
4367
4588
  }
4368
- updatePPStats();
4369
4589
  }else if(data.step==='error'){
4370
4590
  ppStats.errors++;
4371
4591
  updatePPStats();
@@ -4393,6 +4613,7 @@ function ppDone(wasStopped,wasFailed,skipReload){
4393
4613
  document.getElementById('ppStopBtn').style.display='none';
4394
4614
  document.getElementById('ppStartBtn').style.display='inline-flex';
4395
4615
  document.getElementById('ppStartBtn').textContent=wasStopped?t('pp.resume'):t('pp.start');
4616
+ document.getElementById('ppStartBtn').disabled=false;
4396
4617
  var doneEl=document.getElementById('ppDone');
4397
4618
  doneEl.style.display='block';
4398
4619
  if(wasFailed){
@@ -4400,17 +4621,38 @@ function ppDone(wasStopped,wasFailed,skipReload){
4400
4621
  doneEl.style.color='#ef4444';
4401
4622
  doneEl.textContent=t('pp.failed')||'Processing failed — check error above';
4402
4623
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#ef4444,#dc2626)';
4624
+ document.getElementById('ppPhaseLabel').textContent=t('pp.failed');
4403
4625
  }else if(wasStopped){
4404
4626
  doneEl.style.background='rgba(245,158,11,.06)';
4405
4627
  doneEl.style.color='#f59e0b';
4406
4628
  doneEl.textContent=t('pp.stopped');
4407
4629
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#f59e0b,#fbbf24)';
4630
+ document.getElementById('ppPhaseLabel').textContent=t('pp.stopped');
4408
4631
  }else{
4409
4632
  doneEl.style.background='rgba(34,197,94,.06)';
4410
4633
  doneEl.style.color='#22c55e';
4411
- doneEl.textContent=t('pp.done')+' ('+t('pp.stat.tasks')+': '+ppStats.tasks+', '+t('pp.stat.skills')+': '+ppStats.skills+')';
4412
4634
  document.getElementById('ppBar').style.width='100%';
4413
4635
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4636
+ document.getElementById('ppPhaseLabel').textContent=t('pp.done');
4637
+ var ppTotal=ppStats.tasks+ppStats.skipped+ppStats.errors;
4638
+ if(ppTotal>0) document.getElementById('ppCounter').textContent=ppTotal+' / '+ppTotal+' (100%)';
4639
+ fetch('/api/migrate/postprocess/status').then(function(r){return r.json()}).then(function(st){
4640
+ var totalTasks=st.existingTasks||0;
4641
+ var totalSkills=st.existingSkills||0;
4642
+ var lines=[];
4643
+ if(ppStats.tasks>0) lines.push(t('pp.stat.tasks')+' +'+ppStats.tasks);
4644
+ if(ppStats.skills>0) lines.push(t('pp.stat.skills')+' +'+ppStats.skills);
4645
+ if(ppStats.skipped>0) lines.push(t('pp.stat.skipped')+': '+ppStats.skipped);
4646
+ var runText=lines.length>0?' ('+lines.join(', ')+')':'';
4647
+ var totalText=' — '+t('pp.stat.tasks')+' '+totalTasks+', '+t('pp.stat.skills.total')+' '+totalSkills;
4648
+ doneEl.textContent=t('pp.done')+runText+totalText;
4649
+ }).catch(function(){
4650
+ var parts=[];
4651
+ if(ppStats.tasks>0) parts.push(t('pp.stat.tasks')+': '+ppStats.tasks);
4652
+ if(ppStats.skills>0) parts.push(t('pp.stat.skills')+': '+ppStats.skills);
4653
+ if(ppStats.skipped>0) parts.push(t('pp.stat.skipped')+': '+ppStats.skipped);
4654
+ doneEl.textContent=t('pp.done')+(parts.length>0?' ('+parts.join(', ')+')':'');
4655
+ });
4414
4656
  }
4415
4657
  if(!skipReload) loadAll();
4416
4658
  }
@@ -4448,18 +4690,74 @@ function toggleViewerTheme(){const el=document.documentElement;const cur=el.getA
4448
4690
  initViewerTheme();
4449
4691
 
4450
4692
  /* ─── Update check ─── */
4693
+ function waitForGatewayAndReload(maxAttempts,attempt){
4694
+ attempt=attempt||0;
4695
+ if(attempt>=maxAttempts){window.location.reload();return;}
4696
+ setTimeout(function(){
4697
+ fetch('/api/auth/status').then(function(){
4698
+ window.location.reload();
4699
+ }).catch(function(){waitForGatewayAndReload(maxAttempts,attempt+1);});
4700
+ },3000);
4701
+ }
4702
+ function doUpdateInstall(packageSpec,btnEl,statusEl){
4703
+ btnEl.disabled=true;
4704
+ btnEl.textContent=t('update.installing');
4705
+ 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';
4706
+ fetch('/api/update-install',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({packageSpec:packageSpec})})
4707
+ .then(function(r){return r.json()})
4708
+ .then(function(d){
4709
+ if(d.ok){
4710
+ btnEl.textContent=t('update.success');
4711
+ 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';
4712
+ if(statusEl)statusEl.textContent=t('update.restarting');
4713
+ waitForGatewayAndReload(40);
4714
+ }else{
4715
+ btnEl.textContent=t('update.btn');
4716
+ 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';
4717
+ btnEl.disabled=false;
4718
+ if(statusEl)statusEl.textContent=t('update.failed')+': '+(d.error||'').slice(0,60);
4719
+ setTimeout(function(){if(statusEl)statusEl.textContent='';},8000);
4720
+ }
4721
+ })
4722
+ .catch(function(){
4723
+ btnEl.textContent=t('update.btn');
4724
+ 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';
4725
+ btnEl.disabled=false;
4726
+ });
4727
+ }
4451
4728
  async function checkForUpdate(){
4452
4729
  try{
4453
4730
  const r=await fetch('/api/update-check');
4454
4731
  if(!r.ok)return;
4455
4732
  const d=await r.json();
4456
4733
  if(!d.updateAvailable)return;
4457
- const banner=document.createElement('div');
4734
+ const pkgSpec=d.installCommand?d.installCommand.replace(/^(?:npx\s+)?openclaw\s+plugins\s+install\s+/,''):(d.packageName+'@'+d.latest);
4735
+ var banner=document.createElement('div');
4458
4736
  banner.id='updateBanner';
4459
- 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)';
4460
- 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>';
4461
- document.body.prepend(banner);
4462
- document.body.style.paddingTop='48px';
4737
+ 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)';
4738
+ var textNode=document.createElement('div');
4739
+ textNode.style.cssText='display:flex;align-items:center;gap:8px;flex-shrink:0';
4740
+ 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>';
4741
+ var btnUpdate=document.createElement('button');
4742
+ btnUpdate.className='emb-banner-btn';
4743
+ btnUpdate.textContent=t('update.btn');
4744
+ var statusDiv=document.createElement('div');
4745
+ statusDiv.style.cssText='font-size:11px;opacity:.8;flex-shrink:0';
4746
+ btnUpdate.onclick=function(){doUpdateInstall(pkgSpec,btnUpdate,statusDiv)};
4747
+ textNode.appendChild(btnUpdate);
4748
+ var spacer=document.createElement('div');
4749
+ spacer.style.cssText='flex:1';
4750
+ var btnClose=document.createElement('button');
4751
+ btnClose.className='emb-banner-close';
4752
+ btnClose.innerHTML='&times;';
4753
+ btnClose.onclick=function(){banner.remove()};
4754
+ banner.appendChild(textNode);
4755
+ banner.appendChild(statusDiv);
4756
+ banner.appendChild(spacer);
4757
+ banner.appendChild(btnClose);
4758
+ var embBanner=document.querySelector('.emb-banner');
4759
+ if(embBanner&&embBanner.parentNode){embBanner.parentNode.insertBefore(banner,embBanner);}
4760
+ else{var ct=document.querySelector('.content-area')||document.querySelector('main')||document.body;if(ct.firstChild)ct.insertBefore(banner,ct.firstChild);else ct.appendChild(banner);}
4463
4761
  }catch(e){}
4464
4762
  }
4465
4763