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

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 (86) hide show
  1. package/dist/capture/index.js +52 -8
  2. package/dist/capture/index.js.map +1 -1
  3. package/dist/ingest/chunker.d.ts +3 -4
  4. package/dist/ingest/chunker.d.ts.map +1 -1
  5. package/dist/ingest/chunker.js +19 -24
  6. package/dist/ingest/chunker.js.map +1 -1
  7. package/dist/ingest/providers/anthropic.d.ts +3 -1
  8. package/dist/ingest/providers/anthropic.d.ts.map +1 -1
  9. package/dist/ingest/providers/anthropic.js +79 -39
  10. package/dist/ingest/providers/anthropic.js.map +1 -1
  11. package/dist/ingest/providers/bedrock.d.ts +3 -1
  12. package/dist/ingest/providers/bedrock.d.ts.map +1 -1
  13. package/dist/ingest/providers/bedrock.js +79 -39
  14. package/dist/ingest/providers/bedrock.js.map +1 -1
  15. package/dist/ingest/providers/gemini.d.ts +3 -1
  16. package/dist/ingest/providers/gemini.d.ts.map +1 -1
  17. package/dist/ingest/providers/gemini.js +77 -39
  18. package/dist/ingest/providers/gemini.js.map +1 -1
  19. package/dist/ingest/providers/index.d.ts +3 -1
  20. package/dist/ingest/providers/index.d.ts.map +1 -1
  21. package/dist/ingest/providers/index.js +70 -30
  22. package/dist/ingest/providers/index.js.map +1 -1
  23. package/dist/ingest/providers/openai.d.ts +3 -1
  24. package/dist/ingest/providers/openai.d.ts.map +1 -1
  25. package/dist/ingest/providers/openai.js +80 -39
  26. package/dist/ingest/providers/openai.js.map +1 -1
  27. package/dist/ingest/task-processor.d.ts +1 -0
  28. package/dist/ingest/task-processor.d.ts.map +1 -1
  29. package/dist/ingest/task-processor.js +33 -9
  30. package/dist/ingest/task-processor.js.map +1 -1
  31. package/dist/ingest/worker.d.ts.map +1 -1
  32. package/dist/ingest/worker.js +29 -13
  33. package/dist/ingest/worker.js.map +1 -1
  34. package/dist/recall/engine.d.ts.map +1 -1
  35. package/dist/recall/engine.js +19 -14
  36. package/dist/recall/engine.js.map +1 -1
  37. package/dist/skill/bundled-memory-guide.d.ts +1 -5
  38. package/dist/skill/bundled-memory-guide.d.ts.map +1 -1
  39. package/dist/skill/bundled-memory-guide.js +38 -97
  40. package/dist/skill/bundled-memory-guide.js.map +1 -1
  41. package/dist/skill/evaluator.js +1 -1
  42. package/dist/storage/sqlite.d.ts +1 -2
  43. package/dist/storage/sqlite.d.ts.map +1 -1
  44. package/dist/storage/sqlite.js +90 -17
  45. package/dist/storage/sqlite.js.map +1 -1
  46. package/dist/tools/memory-get.d.ts.map +1 -1
  47. package/dist/tools/memory-get.js +1 -3
  48. package/dist/tools/memory-get.js.map +1 -1
  49. package/dist/types.d.ts +2 -2
  50. package/dist/types.d.ts.map +1 -1
  51. package/dist/types.js +1 -1
  52. package/dist/types.js.map +1 -1
  53. package/dist/update-check.d.ts +21 -0
  54. package/dist/update-check.d.ts.map +1 -0
  55. package/dist/update-check.js +111 -0
  56. package/dist/update-check.js.map +1 -0
  57. package/dist/viewer/html.d.ts.map +1 -1
  58. package/dist/viewer/html.js +444 -182
  59. package/dist/viewer/html.js.map +1 -1
  60. package/dist/viewer/server.d.ts +1 -1
  61. package/dist/viewer/server.d.ts.map +1 -1
  62. package/dist/viewer/server.js +142 -78
  63. package/dist/viewer/server.js.map +1 -1
  64. package/index.ts +206 -198
  65. package/openclaw.plugin.json +3 -0
  66. package/package.json +5 -1
  67. package/scripts/postinstall.cjs +69 -2
  68. package/skill/memos-memory-guide/SKILL.md +73 -36
  69. package/src/capture/index.ts +52 -8
  70. package/src/ingest/chunker.ts +22 -30
  71. package/src/ingest/providers/anthropic.ts +89 -41
  72. package/src/ingest/providers/bedrock.ts +90 -41
  73. package/src/ingest/providers/gemini.ts +89 -41
  74. package/src/ingest/providers/index.ts +81 -35
  75. package/src/ingest/providers/openai.ts +90 -41
  76. package/src/ingest/task-processor.ts +29 -8
  77. package/src/ingest/worker.ts +31 -13
  78. package/src/recall/engine.ts +20 -13
  79. package/src/skill/bundled-memory-guide.ts +5 -96
  80. package/src/skill/evaluator.ts +1 -1
  81. package/src/storage/sqlite.ts +93 -21
  82. package/src/tools/memory-get.ts +1 -4
  83. package/src/types.ts +2 -9
  84. package/src/update-check.ts +96 -0
  85. package/src/viewer/html.ts +444 -182
  86. package/src/viewer/server.ts +101 -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}
@@ -648,14 +689,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
648
689
  .tool-agg-table .ms-val.slow{color:var(--accent)}
649
690
  .chart-legend .dot.violet{background:var(--violet)}
650
691
  .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
692
  .metrics-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
660
693
  .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
694
  .range-btn:hover{border-color:var(--pri);color:var(--pri)}
@@ -812,15 +845,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
812
845
  <button class="filter-chip" data-role="assistant" onclick="setRoleFilter(this,'assistant')">Assistant</button>
813
846
  <button class="filter-chip" data-role="system" onclick="setRoleFilter(this,'system')">System</button>
814
847
  <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
848
  <select id="filterSort" class="filter-select" onchange="applyFilters()">
825
849
  <option value="newest" data-i18n="filter.newest">Newest first</option>
826
850
  <option value="oldest" data-i18n="filter.oldest">Oldest first</option>
@@ -954,16 +978,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
954
978
  <div id="toolAggTable" style="margin-top:20px"></div>
955
979
  </div>
956
980
 
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
981
  </div>
968
982
 
969
983
  <!-- ─── Logs View ─── -->
@@ -1203,7 +1217,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1203
1217
  </div>
1204
1218
 
1205
1219
  <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>
1220
+ <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
1221
  <button class="btn btn-primary" onclick="migrateStart()" id="migrateStartBtn" style="display:none" data-i18n="migrate.start">Start Import</button>
1208
1222
  <span id="migrateConcurrencyRow" style="display:none;align-items:center;gap:6px">
1209
1223
  <span style="font-size:11px;color:var(--text-muted)" data-i18n="migrate.concurrency.label">Concurrent agents</span>
@@ -1344,14 +1358,13 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1344
1358
  <!-- ─── Memory Modal ─── -->
1345
1359
  <div class="modal-overlay" id="modalOverlay">
1346
1360
  <div class="modal">
1347
- <h2 id="modalTitle" data-i18n="modal.new">New Memory</h2>
1361
+ <h2 id="modalTitle" data-i18n="modal.edit">Edit Memory</h2>
1348
1362
  <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
1363
  <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
1364
  <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
1365
  <div class="modal-actions">
1353
1366
  <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>
1367
+ <button class="btn btn-primary" id="modalSubmit" onclick="submitModal()" data-i18n="modal.save">Save</button>
1355
1368
  </div>
1356
1369
  </div>
1357
1370
  </div>
@@ -1441,13 +1454,6 @@ const I18N={
1441
1454
  'search.meta.text':' text',
1442
1455
  'search.meta.results':' results',
1443
1456
  '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
1457
  'filter.newest':'Newest first',
1452
1458
  'filter.oldest':'Oldest first',
1453
1459
  'filter.allowners':'All owners',
@@ -1463,6 +1469,8 @@ const I18N={
1463
1469
  'card.delete':'Delete',
1464
1470
  'card.evolved':'Evolved',
1465
1471
  'card.times':'times',
1472
+ 'card.newMessage':'New message',
1473
+ 'card.mergedInfo':'Merged memory',
1466
1474
  'card.updated':'updated',
1467
1475
  'card.evolveHistory':'Evolution History',
1468
1476
  'card.oldSummary':'Old',
@@ -1486,21 +1494,15 @@ const I18N={
1486
1494
  'chart.toolperf':'Tool Response Time',
1487
1495
  'chart.list':'List',
1488
1496
  'chart.search':'Search',
1489
- 'breakdown.role':'By Role',
1490
- 'breakdown.kind':'By Kind',
1491
- 'modal.new':'New Memory',
1492
1497
  'modal.edit':'Edit Memory',
1493
1498
  'modal.role':'Role',
1494
1499
  'modal.content':'Content',
1495
1500
  'modal.content.ph':'Memory content...',
1496
1501
  'modal.summary':'Summary',
1497
1502
  'modal.summary.ph':'Brief summary (optional)',
1498
- 'modal.kind':'Kind',
1499
1503
  'modal.cancel':'Cancel',
1500
- 'modal.create':'Create',
1501
1504
  'modal.save':'Save',
1502
1505
  'modal.err.empty':'Please enter content',
1503
- 'toast.created':'Memory created',
1504
1506
  'toast.updated':'Memory updated',
1505
1507
  'toast.deleted':'Memory deleted',
1506
1508
  'toast.opfail':'Operation failed',
@@ -1527,6 +1529,11 @@ const I18N={
1527
1529
  'logs.output':'OUTPUT',
1528
1530
  'logs.empty':'No logs yet. Logs will appear here when tools are called.',
1529
1531
  'logs.ago':'ago',
1532
+ 'logs.recall.initial':'Initial Retrieval',
1533
+ 'logs.recall.filtered':'LLM Filtered',
1534
+ 'logs.recall.noHits':'No matching memories',
1535
+ 'logs.recall.noneRelevant':'LLM filter: none relevant',
1536
+ 'logs.recall.more':'{n} more...',
1530
1537
  'tab.import':'\u{1F4E5} Import',
1531
1538
  'tab.settings':'\u2699 Settings',
1532
1539
  'settings.modelconfig':'Model Configuration',
@@ -1555,6 +1562,7 @@ const I18N={
1555
1562
  'settings.test.loading':'Testing...',
1556
1563
  'settings.test.ok':'Connected',
1557
1564
  'settings.test.fail':'Failed',
1565
+ 'settings.session.expired':'Session expired, please refresh the page to log in again',
1558
1566
  'settings.save':'Save Settings',
1559
1567
  'settings.reset':'Reset',
1560
1568
  'settings.saved':'Saved',
@@ -1587,12 +1595,18 @@ const I18N={
1587
1595
  'migrate.scan':'Scan Data Sources',
1588
1596
  'migrate.start':'Start Import',
1589
1597
  'migrate.scanning':'Scanning...',
1598
+ 'migrate.scan.required':'Please scan data sources first',
1599
+ 'migrate.scan.done':'Scan complete \u2014 {n} new items found',
1600
+ 'migrate.imported.hint':'{n} items already imported',
1601
+ 'migrate.reconnect.hint':'--- {n} items processed before page reload ---',
1590
1602
  'migrate.stat.stored':'Stored',
1591
1603
  'migrate.stat.skipped':'Skipped',
1592
1604
  'migrate.stat.merged':'Merged',
1593
1605
  'migrate.stat.errors':'Errors',
1594
1606
  'migrate.phase.sqlite':'Importing memory index...',
1595
1607
  'migrate.phase.sessions':'Importing conversation history...',
1608
+ 'migrate.phase.stopped':'Import stopped',
1609
+ 'migrate.phase.done':'Import completed',
1596
1610
  'migrate.chunks':'chunks',
1597
1611
  'migrate.sessions.count':'sessions, {n} messages',
1598
1612
  'migrate.nodata':'No OpenClaw data found to import.',
@@ -1618,7 +1632,8 @@ const I18N={
1618
1632
  'pp.select.warn':'Please select at least one option.',
1619
1633
  'pp.skill.created':'Skill created',
1620
1634
  'pp.stat.tasks':'Tasks',
1621
- 'pp.stat.skills':'Skills',
1635
+ 'pp.stat.skills':'Evolutions',
1636
+ 'pp.stat.skills.total':'Skills',
1622
1637
  'pp.stat.errors':'Errors',
1623
1638
  'pp.stat.skipped':'Skipped',
1624
1639
  'pp.info.skipped':'{n} sessions already processed, skipping.',
@@ -1678,6 +1693,10 @@ const I18N={
1678
1693
  'skill.save.error':'Failed to save skill: ',
1679
1694
  'update.available':'New version available',
1680
1695
  'update.run':'Run',
1696
+ 'update.btn':'Update now',
1697
+ 'update.installing':'Installing update...',
1698
+ 'update.success':'Update installed! Restarting...',
1699
+ 'update.failed':'Update failed',
1681
1700
  'update.dismiss':'Dismiss'
1682
1701
  },
1683
1702
  zh:{
@@ -1756,13 +1775,6 @@ const I18N={
1756
1775
  'search.meta.text':' 文本',
1757
1776
  'search.meta.results':' 条结果',
1758
1777
  'filter.all':'全部',
1759
- 'filter.allkinds':'所有类型',
1760
- 'filter.paragraph':'段落',
1761
- 'filter.code':'代码',
1762
- 'filter.dialog':'对话',
1763
- 'filter.list':'列表',
1764
- 'filter.error':'错误',
1765
- 'filter.command':'命令',
1766
1778
  'filter.newest':'最新优先',
1767
1779
  'filter.oldest':'最早优先',
1768
1780
  'filter.allowners':'所有归属',
@@ -1778,6 +1790,8 @@ const I18N={
1778
1790
  'card.delete':'删除',
1779
1791
  'card.evolved':'已演化',
1780
1792
  'card.times':'次',
1793
+ 'card.newMessage':'新消息',
1794
+ 'card.mergedInfo':'合并记忆',
1781
1795
  'card.updated':'更新于',
1782
1796
  'card.evolveHistory':'演化记录',
1783
1797
  'card.oldSummary':'旧摘要',
@@ -1801,21 +1815,15 @@ const I18N={
1801
1815
  'chart.toolperf':'工具响应耗时',
1802
1816
  'chart.list':'列表',
1803
1817
  'chart.search':'搜索',
1804
- 'breakdown.role':'按角色',
1805
- 'breakdown.kind':'按类型',
1806
- 'modal.new':'新建记忆',
1807
1818
  'modal.edit':'编辑记忆',
1808
1819
  'modal.role':'角色',
1809
1820
  'modal.content':'内容',
1810
1821
  'modal.content.ph':'记忆内容...',
1811
1822
  'modal.summary':'摘要',
1812
1823
  'modal.summary.ph':'简要摘要(可选)',
1813
- 'modal.kind':'类型',
1814
1824
  'modal.cancel':'取消',
1815
- 'modal.create':'创建',
1816
1825
  'modal.save':'保存',
1817
1826
  'modal.err.empty':'请输入内容',
1818
- 'toast.created':'记忆已创建',
1819
1827
  'toast.updated':'记忆已更新',
1820
1828
  'toast.deleted':'记忆已删除',
1821
1829
  'toast.opfail':'操作失败',
@@ -1842,6 +1850,11 @@ const I18N={
1842
1850
  'logs.output':'输出',
1843
1851
  'logs.empty':'暂无日志。当工具被调用时日志会显示在这里。',
1844
1852
  'logs.ago':'前',
1853
+ 'logs.recall.initial':'初始检索',
1854
+ 'logs.recall.filtered':'LLM 过滤后',
1855
+ 'logs.recall.noHits':'未匹配到记忆',
1856
+ 'logs.recall.noneRelevant':'LLM 过滤:无相关记忆',
1857
+ 'logs.recall.more':'还有 {n} 条...',
1845
1858
  'tab.import':'\u{1F4E5} 导入',
1846
1859
  'tab.settings':'\u2699 设置',
1847
1860
  'settings.modelconfig':'模型配置',
@@ -1870,6 +1883,7 @@ const I18N={
1870
1883
  'settings.test.loading':'测试中...',
1871
1884
  'settings.test.ok':'连接成功',
1872
1885
  'settings.test.fail':'连接失败',
1886
+ 'settings.session.expired':'登录已过期,请刷新页面重新登录',
1873
1887
  'settings.save':'保存设置',
1874
1888
  'settings.reset':'重置',
1875
1889
  'settings.saved':'已保存',
@@ -1902,12 +1916,18 @@ const I18N={
1902
1916
  'migrate.scan':'扫描数据源',
1903
1917
  'migrate.start':'开始导入',
1904
1918
  'migrate.scanning':'扫描中...',
1919
+ 'migrate.scan.required':'请先扫描数据源',
1920
+ 'migrate.scan.done':'扫描完成 — 发现 {n} 条新数据可导入',
1921
+ 'migrate.imported.hint':'已导入 {n} 条记忆',
1922
+ 'migrate.reconnect.hint':'--- 页面刷新前已处理 {n} 条 ---',
1905
1923
  'migrate.stat.stored':'已存储',
1906
1924
  'migrate.stat.skipped':'已跳过',
1907
1925
  'migrate.stat.merged':'已合并',
1908
1926
  'migrate.stat.errors':'错误',
1909
1927
  'migrate.phase.sqlite':'正在导入记忆索引...',
1910
1928
  'migrate.phase.sessions':'正在导入对话历史...',
1929
+ 'migrate.phase.stopped':'导入已停止',
1930
+ 'migrate.phase.done':'导入完成',
1911
1931
  'migrate.chunks':'条记忆',
1912
1932
  'migrate.sessions.count':'个会话,{n} 条消息',
1913
1933
  'migrate.nodata':'未找到可导入的 OpenClaw 数据。',
@@ -1933,7 +1953,8 @@ const I18N={
1933
1953
  'pp.select.warn':'请至少选择一个选项。',
1934
1954
  'pp.skill.created':'技能已创建',
1935
1955
  'pp.stat.tasks':'任务',
1936
- 'pp.stat.skills':'技能',
1956
+ 'pp.stat.skills':'进化',
1957
+ 'pp.stat.skills.total':'技能',
1937
1958
  'pp.stat.errors':'错误',
1938
1959
  'pp.stat.skipped':'已跳过',
1939
1960
  'pp.info.skipped':'已有 {n} 个会话处理过,自动跳过。',
@@ -1993,6 +2014,10 @@ const I18N={
1993
2014
  'skill.save.error':'保存技能失败:',
1994
2015
  'update.available':'发现新版本',
1995
2016
  'update.run':'执行命令',
2017
+ 'update.btn':'立即更新',
2018
+ 'update.installing':'正在安装更新...',
2019
+ 'update.success':'更新成功!正在重启...',
2020
+ 'update.failed':'更新失败',
1996
2021
  'update.dismiss':'关闭'
1997
2022
  }
1998
2023
  };
@@ -2142,7 +2167,7 @@ function switchView(view){
2142
2167
  loadModelHealth();
2143
2168
  } else if(view==='import'){
2144
2169
  migrateView.classList.add('show');
2145
- if(!window._migrateRunning) migrateScan();
2170
+ if(!window._migrateRunning) migrateScan(false);
2146
2171
  }
2147
2172
  }
2148
2173
  }
@@ -2210,6 +2235,39 @@ function formatLogTime(ts){
2210
2235
  return y+'-'+m+'-'+day+' '+time;
2211
2236
  }
2212
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
+
2213
2271
  function buildLogSummary(lg){
2214
2272
  let inputObj=null;
2215
2273
  try{inputObj=JSON.parse(lg.input);}catch(_){}
@@ -2217,11 +2275,54 @@ function buildLogSummary(lg){
2217
2275
  const tn=lg.toolName;
2218
2276
  if(tn==='memory_search'&&inputObj){
2219
2277
  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>';
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
+ }
2225
2326
  }else if(tn==='memory_add'&&inputObj){
2226
2327
  const out=lg.output||'';
2227
2328
  const statsMatch=out.match(/^([^\\n]+)/);
@@ -2234,39 +2335,18 @@ function buildLogSummary(lg){
2234
2335
  });
2235
2336
  html+='</div>';
2236
2337
  }
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){
2338
+ var parsed=parseMemoryAddEntries(out);
2339
+ if(parsed.length>0){
2261
2340
  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
- }
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>';
2270
2350
  });
2271
2351
  html+='</div>';
2272
2352
  }
@@ -2274,11 +2354,53 @@ function buildLogSummary(lg){
2274
2354
  const keys=Object.keys(inputObj);
2275
2355
  keys.slice(0,4).forEach(k=>{
2276
2356
  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>';
2357
+ html+='<span class="log-summary-kv"><span class="kv-label">'+escapeHtml(k)+':</span><span class="kv-val">'+escapeHtml(v)+'</span></span>';
2278
2358
  });
2279
2359
  }
2280
2360
  return html;
2281
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>';
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>';
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>';
2402
+ return html;
2403
+ }
2282
2404
  function renderLogs(logs){
2283
2405
  const el=document.getElementById('logsList');
2284
2406
  if(!logs.length){
@@ -2291,7 +2413,29 @@ function renderLogs(logs){
2291
2413
  const toolCls=lg.toolName.replace(/[^a-zA-Z0-9_]/g,'_');
2292
2414
  const dur=lg.durationMs<1000?Math.round(lg.durationMs)+'ms':(lg.durationMs/1000).toFixed(1)+'s';
2293
2415
  let inputDisplay='';
2294
- 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(_){}
2295
2439
  const summary=buildLogSummary(lg);
2296
2440
  return '<div class="log-entry" id="log-'+i+'">'+
2297
2441
  '<div class="log-header" onclick="toggleLog('+i+')">'+
@@ -2305,11 +2449,11 @@ function renderLogs(logs){
2305
2449
  '<div class="log-detail" id="log-detail-'+i+'">'+
2306
2450
  '<div class="log-io-section">'+
2307
2451
  '<div class="log-io-label">\u25B6 '+t('logs.input')+'</div>'+
2308
- '<pre class="log-io-content">'+escapeHtml(inputDisplay)+'</pre>'+
2452
+ (inputHtml?inputHtml:'<pre class="log-io-content">'+escapeHtml(inputDisplay)+'</pre>')+
2309
2453
  '</div>'+
2310
2454
  '<div class="log-io-section">'+
2311
2455
  '<div class="log-io-label">\u25C0 '+t('logs.output')+'</div>'+
2312
- '<pre class="log-io-content">'+escapeHtml(lg.output)+'</pre>'+
2456
+ (outputHtml?outputHtml:'<pre class="log-io-content">'+escapeHtml(lg.output)+'</pre>')+
2313
2457
  '</div>'+
2314
2458
  '</div>'+
2315
2459
  '</div>';
@@ -2352,8 +2496,6 @@ async function loadMetrics(){
2352
2496
  document.getElementById('mSessions').textContent=formatNum(d.totals.sessions);
2353
2497
  document.getElementById('mEmbeddings').textContent=formatNum(d.totals.embeddings);
2354
2498
  renderChartWrites(d.writesPerDay);
2355
- renderBreakdown(d.roleBreakdown,'breakdownRole');
2356
- renderBreakdown(d.kindBreakdown,'breakdownKind');
2357
2499
  loadToolMetrics();
2358
2500
  }
2359
2501
 
@@ -2869,7 +3011,7 @@ function classifyError(msg){
2869
3011
  if(msg.indexOf('ECONNREFUSED')>=0) return 'Connection refused';
2870
3012
  if(msg.indexOf('ENOTFOUND')>=0) return 'DNS resolution failed';
2871
3013
  if(msg.indexOf('403')>=0) return 'Forbidden (403)';
2872
- return msg.length>50?msg.slice(0,47)+'...':msg;
3014
+ return msg;
2873
3015
  }
2874
3016
 
2875
3017
  function shortenModel(s){return s?s.replace('openai_compatible/','').replace('openai/',''):'\u2014';}
@@ -3058,6 +3200,7 @@ async function saveConfig(){
3058
3200
  // 2) Test embedding
3059
3201
  try{
3060
3202
  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||''})});
3203
+ if(er.status===401){done();toast(t('settings.session.expired'),'error');return;}
3061
3204
  var ed=await er.json();
3062
3205
  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
3206
  document.getElementById('testEmbResult').className='test-result ok';document.getElementById('testEmbResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3067,6 +3210,7 @@ async function saveConfig(){
3067
3210
  if(hasSumConfig&&cfg.summarizer){
3068
3211
  try{
3069
3212
  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||''})});
3213
+ if(sr.status===401){done();toast(t('settings.session.expired'),'error');return;}
3070
3214
  var sd=await sr.json();
3071
3215
  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
3216
  document.getElementById('testSumResult').className='test-result ok';document.getElementById('testSumResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3077,6 +3221,7 @@ async function saveConfig(){
3077
3221
  if(hasSkillConfig&&cfg.skillEvolution.summarizer){
3078
3222
  try{
3079
3223
  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||''})});
3224
+ if(kr.status===401){done();toast(t('settings.session.expired'),'error');return;}
3080
3225
  var kd=await kr.json();
3081
3226
  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
3227
  document.getElementById('testSkillResult').className='test-result ok';document.getElementById('testSkillResult').innerHTML='\\u2705 '+t('settings.test.ok');
@@ -3134,6 +3279,7 @@ async function testModel(type){
3134
3279
  try{
3135
3280
  var body={type:type,provider:provider,model:model,endpoint:endpoint,apiKey:apiKey};
3136
3281
  var r=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
3282
+ if(r.status===401){resultEl.className='test-result fail';resultEl.innerHTML='\\u274C '+t('settings.session.expired');btn.disabled=false;return;}
3137
3283
  var d=await r.json();
3138
3284
  if(d.ok){
3139
3285
  resultEl.className='test-result ok';
@@ -3458,17 +3604,6 @@ function renderToolAgg(data){
3458
3604
  '</tbody></table>';
3459
3605
  }
3460
3606
 
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
3607
  /* ─── Data loading ─── */
3473
3608
  async function loadAll(){
3474
3609
  await Promise.all([loadStats(),loadMemories()]);
@@ -3551,8 +3686,6 @@ function getFilterParams(){
3551
3686
  const p=new URLSearchParams();
3552
3687
  if(activeSession) p.set('session',activeSession);
3553
3688
  if(activeRole) p.set('role',activeRole);
3554
- const kind=document.getElementById('filterKind').value;
3555
- if(kind) p.set('kind',kind);
3556
3689
  const df=document.getElementById('dateFrom').value;
3557
3690
  if(df) p.set('dateFrom',df);
3558
3691
  const dt=document.getElementById('dateTo').value;
@@ -3656,18 +3789,19 @@ function renderMemories(items){
3656
3789
  list.innerHTML=items.map(m=>{
3657
3790
  const time=m.created_at?new Date(typeof m.created_at==='number'?m.created_at:m.created_at).toLocaleString('zh-CN'):'';
3658
3791
  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||'');
3792
+ const rawSummary=m.summary||'';
3793
+ const rawContent=m.content||'';
3794
+ const content=esc(rawContent);
3662
3795
  const id=m.id;
3663
3796
  const vscore=m._vscore?'<span class="vscore-badge">'+Math.round(m._vscore*100)+'%</span>':'';
3664
3797
  const sid=m.session_key||'';
3665
3798
  const sidShort=sid.length>18?sid.slice(0,6)+'..'+sid.slice(-6):sid;
3666
3799
  const mc=m.merge_count||0;
3800
+ const cardTitle=esc(rawSummary||rawContent||'');
3667
3801
  const mergeBadge=mc>0?'<span class="merge-badge">\\u{1F504} '+t('card.evolved')+' '+mc+t('card.times')+'</span>':'';
3668
3802
  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
3803
  const ds=m.dedup_status||'active';
3670
- const isInactive=ds==='duplicate'||ds==='merged';
3804
+ const isInactive=ds==='merged';
3671
3805
  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
3806
  const isImported=sid.startsWith('openclaw-import-')||sid.startsWith('openclaw-session-');
3673
3807
  const importBadge=isImported?'<span class="import-badge">\u{1F990} '+t('card.imported')+'</span>':'';
@@ -3675,7 +3809,7 @@ function renderMemories(items){
3675
3809
  const isPublicMem=ownerVal==='public';
3676
3810
  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
3811
  let dedupInfo='';
3678
- if(isInactive){
3812
+ if(ds==='duplicate'||ds==='merged'){
3679
3813
  const reason=m.dedup_reason?'<span style="font-size:11px;color:var(--text-muted)">'+t('card.dedupReason')+esc(m.dedup_reason)+'</span>':'';
3680
3814
  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
3815
  dedupInfo='<div style="margin-top:6px;font-size:11px">'+target+' '+reason+'</div>';
@@ -3698,8 +3832,23 @@ function renderMemories(items){
3698
3832
  }catch(e){}
3699
3833
  }
3700
3834
  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>'+
3835
+ '<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>'+
3836
+ '<div class="card-summary">'+cardTitle+'</div>'+
3837
+ (function(){
3838
+ if(mc<=0) return '';
3839
+ var mergeHtml='<div class="card-merged-info">';
3840
+ mergeHtml+='<div class="card-merged-label">\\u{1F504} '+t('card.mergedInfo')+' ('+mc+t('card.times')+')</div>';
3841
+ var sources=m.merge_sources||[];
3842
+ if(sources.length>0){
3843
+ mergeHtml+='<div style="display:flex;flex-wrap:wrap;gap:6px">';
3844
+ sources.forEach(function(s){
3845
+ mergeHtml+='<span class="dedup-target-link" onclick="scrollToMemory(\\''+s.id+'\\')">\\u{1F517} '+s.id.slice(0,8)+'...</span>';
3846
+ });
3847
+ mergeHtml+='</div>';
3848
+ }
3849
+ mergeHtml+='</div>';
3850
+ return mergeHtml;
3851
+ })()+
3703
3852
  dedupInfo+
3704
3853
  '<div class="card-content" id="content-'+id+'"><pre>'+content+'</pre></div>'+
3705
3854
  historyHtml+
@@ -3778,12 +3927,11 @@ async function showMemoryModal(chunkId){
3778
3927
  const m=data.memory;
3779
3928
  const role=(m.role||'unknown').toUpperCase();
3780
3929
  const roleCls=(m.role||'').toLowerCase();
3781
- const kind=m.kind||'paragraph';
3782
3930
  const ds=m.dedup_status||'active';
3783
3931
  const time=new Date(m.created_at).toLocaleString('zh-CN');
3784
3932
  const updated=m.updated_at?new Date(m.updated_at).toLocaleString('zh-CN'):'';
3785
3933
  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>';
3934
+ html+='<div class="modal-header-row"><span class="role-tag '+roleCls+'">'+role+'</span>';
3787
3935
  if(ds!=='active') html+='<span class="dedup-badge '+(ds==='duplicate'?'duplicate':'merged')+'">'+ds+'</span>';
3788
3936
  html+='</div>';
3789
3937
  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 +3989,6 @@ function renderSummaryHtml(raw){
3841
3989
  }
3842
3990
 
3843
3991
  /* ─── 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
3992
  function openEditModal(id){
3856
3993
  const m=memoryCache[id];
3857
3994
  if(!m){toast(t('toast.notfound'),'error');return}
@@ -3861,7 +3998,6 @@ function openEditModal(id){
3861
3998
  document.getElementById('mRole').value=m.role||'user';
3862
3999
  document.getElementById('mContent').value=m.content||'';
3863
4000
  document.getElementById('mSummary').value=m.summary||'';
3864
- document.getElementById('mKind').value=m.kind||'paragraph';
3865
4001
  document.getElementById('modalOverlay').classList.add('show');
3866
4002
  }
3867
4003
 
@@ -3870,21 +4006,16 @@ function closeModal(){
3870
4006
  }
3871
4007
 
3872
4008
  async function submitModal(){
4009
+ if(!editingId)return;
3873
4010
  const data={
3874
4011
  role:document.getElementById('mRole').value,
3875
4012
  content:document.getElementById('mContent').value,
3876
4013
  summary:document.getElementById('mSummary').value,
3877
- kind:document.getElementById('mKind').value,
3878
4014
  };
3879
4015
  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
- }
4016
+ const r=await fetch('/api/memory/'+editingId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
3886
4017
  const d=await r.json();
3887
- if(d.ok){toast(editingId?t('toast.updated'):t('toast.created'),'success');closeModal();loadAll();}
4018
+ if(d.ok){toast(t('toast.updated'),'success');closeModal();loadAll();}
3888
4019
  else{toast(d.error||t('toast.opfail'),'error')}
3889
4020
  }
3890
4021
 
@@ -3907,12 +4038,15 @@ async function toggleMemoryPublic(id,setPublic){
3907
4038
  }
3908
4039
 
3909
4040
  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')}
4041
+ try{
4042
+ if(!confirm(t('confirm.clearall')))return;
4043
+ if(!confirm(t('confirm.clearall2')))return;
4044
+ const r=await fetch('/api/memories',{method:'DELETE'});
4045
+ if(r.status===401){toast(t('settings.session.expired'),'error');return;}
4046
+ const d=await r.json();
4047
+ if(d.ok){toast(t('toast.cleared'),'success');loadAll();}
4048
+ else{toast(t('toast.clearfail'),'error')}
4049
+ }catch(e){toast('Error: '+e.message,'error')}
3916
4050
  }
3917
4051
 
3918
4052
  /* ─── Migration ─── */
@@ -3932,7 +4066,7 @@ let migrateStats={stored:0,skipped:0,merged:0,errors:0};
3932
4066
  });
3933
4067
  })();
3934
4068
 
3935
- async function migrateScan(){
4069
+ async function migrateScan(showToast){
3936
4070
  const btn=document.getElementById('migrateScanBtn');
3937
4071
  btn.disabled=true;
3938
4072
  btn.textContent=t('migrate.scanning');
@@ -3965,9 +4099,31 @@ async function migrateScan(){
3965
4099
  t('migrate.config.warn.desc')+' ('+parts.join(', ')+')';
3966
4100
  }
3967
4101
 
4102
+ const imported=d.importedChunkCount||0;
4103
+ const remaining=Math.max(0,(d.totalItems||0)-imported);
4104
+
3968
4105
  if(d.totalItems>0 && d.configReady){
3969
4106
  document.getElementById('migrateStartBtn').style.display='inline-flex';
3970
4107
  document.getElementById('migrateConcurrencyRow').style.display='inline-flex';
4108
+ if(d.hasImportedData){
4109
+ document.getElementById('migrateStartBtn').textContent=t('migrate.resume');
4110
+ }else{
4111
+ document.getElementById('migrateStartBtn').textContent=t('migrate.start');
4112
+ }
4113
+ }
4114
+
4115
+ var hintEl=document.getElementById('migrateImportedHint');
4116
+ if(!hintEl){
4117
+ hintEl=document.createElement('div');
4118
+ hintEl.id='migrateImportedHint';
4119
+ hintEl.style.cssText='font-size:12px;color:var(--text-sec);padding:6px 0';
4120
+ document.getElementById('migrateActions').appendChild(hintEl);
4121
+ }
4122
+ if(imported>0){
4123
+ hintEl.textContent=t('migrate.imported.hint').replace('{n}',imported);
4124
+ hintEl.style.display='block';
4125
+ }else{
4126
+ hintEl.style.display='none';
3971
4127
  }
3972
4128
 
3973
4129
  if(d.totalItems===0){
@@ -3977,6 +4133,7 @@ async function migrateScan(){
3977
4133
  if(d.hasImportedData){
3978
4134
  document.getElementById('postprocessSection').style.display='block';
3979
4135
  }
4136
+ if(showToast) toast(t('migrate.scan.done').replace('{n}',remaining),'success');
3980
4137
  }catch(e){
3981
4138
  toast('Scan failed: '+e.message,'error');
3982
4139
  }finally{
@@ -3986,27 +4143,36 @@ async function migrateScan(){
3986
4143
  }
3987
4144
 
3988
4145
  function migrateStart(){
3989
- if(!migrateScanData||!migrateScanData.configReady)return;
3990
- if(!confirm(t('migrate.start')+'?'))return;
4146
+ const isResume=document.getElementById('migrateStartBtn').textContent===t('migrate.resume');
4147
+ if(!isResume){
4148
+ if(!migrateScanData||!migrateScanData.configReady){
4149
+ toast(t('migrate.scan.required'),'error');
4150
+ return;
4151
+ }
4152
+ if(!confirm(t('migrate.start')+'?'))return;
4153
+ }
3991
4154
 
3992
4155
  const concSel=document.getElementById('migrateConcurrency');
3993
4156
  const concurrency=concSel?parseInt(concSel.value,10)||1:1;
3994
4157
 
3995
4158
  window._migrateRunning=true;
3996
- _migrateStatusChecked=false;
4159
+ _migrateStatusChecked=true;
3997
4160
  document.getElementById('migrateStartBtn').style.display='none';
3998
4161
  document.getElementById('migrateScanBtn').disabled=true;
4162
+ var hintEl=document.getElementById('migrateImportedHint');
4163
+ if(hintEl) hintEl.style.display='none';
3999
4164
  document.getElementById('migrateConcurrencyRow').style.display='none';
4000
4165
  document.getElementById('migrateConcurrencyWarn').style.display='none';
4001
4166
  document.getElementById('migrateProgress').style.display='block';
4002
4167
  document.getElementById('migrateLiveLog').innerHTML='';
4003
4168
  migrateStats={stored:0,skipped:0,merged:0,errors:0};
4004
4169
  updateMigrateStats();
4170
+ document.getElementById('migrateBar').style.width='0%';
4171
+ document.getElementById('migrateCounter').textContent='';
4005
4172
 
4006
4173
  document.getElementById('migrateStopBtn').disabled=false;
4007
- document.getElementById('migrateBar').style.width='0%';
4174
+ document.getElementById('migrateStopBtn').style.display='inline-flex';
4008
4175
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#6366f1,#8b5cf6)';
4009
- document.getElementById('migrateCounter').textContent='';
4010
4176
  const body=JSON.stringify({sources:['sqlite','sessions'],concurrency});
4011
4177
  connectMigrateSSE('/api/migrate/start','POST',body);
4012
4178
  }
@@ -4043,7 +4209,7 @@ function readSSEStream(r){
4043
4209
  const NL=String.fromCharCode(10);
4044
4210
  function pump(){
4045
4211
  reader.read().then(({done,value})=>{
4046
- if(done){if(!migrateDoneCalled&&!window._migrateRunning)onMigrateDone(false);return;}
4212
+ if(done){if(!migrateDoneCalled)onMigrateDone(false);return;}
4047
4213
  buf+=decoder.decode(value,{stream:true});
4048
4214
  const lines=buf.split(NL);
4049
4215
  buf=lines.pop()||'';
@@ -4066,7 +4232,7 @@ function readSSEStream(r){
4066
4232
 
4067
4233
  var _migrateStatusChecked=false;
4068
4234
  async function checkMigrateStatus(){
4069
- if(_migrateStatusChecked) return;
4235
+ if(_migrateStatusChecked||window._migrateRunning) return;
4070
4236
  _migrateStatusChecked=true;
4071
4237
  try{
4072
4238
  const r=await fetch('/api/migrate/status');
@@ -4088,7 +4254,18 @@ async function checkMigrateStatus(){
4088
4254
  document.getElementById('migrateCounter').textContent=s.processed+' / '+s.total+' ('+pct+'%)';
4089
4255
  const label=s.phase==='sqlite'?t('migrate.phase.sqlite'):t('migrate.phase.sessions');
4090
4256
  document.getElementById('migratePhaseLabel').textContent=label;
4257
+ document.getElementById('migrateStopBtn').style.display='inline-flex';
4258
+ if(s.processed>0){
4259
+ const log=document.getElementById('migrateLiveLog');
4260
+ const hint=document.createElement('div');
4261
+ hint.style.cssText='text-align:center;padding:8px 12px;color:var(--text-muted);font-size:11px;border-bottom:1px solid var(--border)';
4262
+ hint.textContent=t('migrate.reconnect.hint').replace('{n}',s.processed);
4263
+ log.appendChild(hint);
4264
+ }
4091
4265
  connectMigrateSSE('/api/migrate/stream','GET',null);
4266
+ fetch('/api/migrate/scan').then(function(sr){return sr.json()}).then(function(sd){
4267
+ if(sd&&sd.hasImportedData) document.getElementById('postprocessSection').style.display='block';
4268
+ }).catch(function(){});
4092
4269
  }else if(s.done&&(s.stored>0||s.skipped>0||s.stopped)){
4093
4270
  migrateStats={stored:s.stored,skipped:s.skipped,merged:s.merged,errors:s.errors};
4094
4271
  updateMigrateStats();
@@ -4186,18 +4363,23 @@ function onMigrateDone(wasStopped,skipReload){
4186
4363
  document.getElementById('migrateScanBtn').disabled=false;
4187
4364
  document.getElementById('migrateStopBtn').disabled=true;
4188
4365
  document.getElementById('migrateStopBtn').textContent=t('migrate.stop');
4366
+ document.getElementById('migrateStopBtn').style.display='none';
4189
4367
  if(wasStopped){
4190
4368
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#f59e0b,#fbbf24)';
4191
4369
  document.getElementById('migrateStartBtn').style.display='inline-flex';
4192
4370
  document.getElementById('migrateStartBtn').textContent=t('migrate.resume');
4371
+ document.getElementById('migratePhaseLabel').textContent=t('migrate.phase.stopped');
4193
4372
  }else{
4194
4373
  document.getElementById('migrateBar').style.width='100%';
4195
4374
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4196
4375
  const total=migrateStats.stored+migrateStats.skipped+migrateStats.merged+migrateStats.errors;
4197
4376
  if(total>0) document.getElementById('migrateCounter').textContent=total+' / '+total+' (100%)';
4377
+ document.getElementById('migratePhaseLabel').textContent=t('migrate.phase.done');
4198
4378
  }
4199
4379
  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';
4380
+ if(d&&d.hasImportedData){
4381
+ document.getElementById('postprocessSection').style.display='block';
4382
+ }
4201
4383
  }).catch(()=>{});
4202
4384
  if(!skipReload) loadAll();
4203
4385
  }
@@ -4325,12 +4507,18 @@ function connectPPSSE(){
4325
4507
  }).catch(function(){});
4326
4508
  }else if(s.done){
4327
4509
  document.getElementById('postprocessSection').style.display='block';
4328
- ppStats={tasks:s.tasksCreated||0,skills:s.skillsCreated||0,errors:s.errors||0,skipped:0};
4510
+ ppStats={tasks:s.tasksCreated||0,skills:s.skillsCreated||0,errors:s.errors||0,skipped:s.skippedSessions||0};
4329
4511
  updatePPStats();
4330
4512
  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+'%)';
4513
+ var totalAll=(s.total||0)+(s.skippedSessions||0);
4514
+ if(totalAll>0){
4515
+ document.getElementById('ppBar').style.width='100%';
4516
+ document.getElementById('ppCounter').textContent=totalAll+' / '+totalAll+' (100%)';
4517
+ }else{
4518
+ var pct2=s.total>0?Math.round((s.processed/s.total)*100):0;
4519
+ document.getElementById('ppBar').style.width=pct2+'%';
4520
+ document.getElementById('ppCounter').textContent=s.processed+' / '+s.total+' ('+pct2+'%)';
4521
+ }
4334
4522
  ppDone(!!s.stopped,false,true);
4335
4523
  }
4336
4524
  }).catch(function(){});
@@ -4338,9 +4526,11 @@ function connectPPSSE(){
4338
4526
 
4339
4527
  function handlePPEvent(evtType,data){
4340
4528
  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+'%)';
4529
+ if(data.total>0){
4530
+ var pct=Math.round((data.processed/data.total)*100);
4531
+ document.getElementById('ppBar').style.width=pct+'%';
4532
+ document.getElementById('ppCounter').textContent=data.processed+' / '+data.total+' ('+pct+'%)';
4533
+ }
4344
4534
  }else if(evtType==='info'){
4345
4535
  if(data.alreadyProcessed>0){
4346
4536
  ppStats.skipped=data.alreadyProcessed;
@@ -4349,6 +4539,10 @@ function handlePPEvent(evtType,data){
4349
4539
  }
4350
4540
  if(data.pending===0){
4351
4541
  appendPPLogItem({step:'done',session:t('pp.info.allDone'),index:'',total:''});
4542
+ document.getElementById('ppPhaseLabel').textContent=t('pp.info.allDone');
4543
+ document.getElementById('ppBar').style.width='100%';
4544
+ document.getElementById('ppBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4545
+ document.getElementById('ppCounter').textContent=data.alreadyProcessed+' / '+data.totalSessions;
4352
4546
  }else{
4353
4547
  document.getElementById('ppPhaseLabel').textContent=t('pp.info.pending').replace('{n}',data.pending);
4354
4548
  }
@@ -4360,12 +4554,10 @@ function handlePPEvent(evtType,data){
4360
4554
  document.getElementById('ppPhaseLabel').textContent=t('pp.running')+' — '+actionLabel+' — '+label;
4361
4555
  }
4362
4556
  if(data.step==='done'){
4363
- if(data.action==='skill-only'){
4364
- ppStats.skills++;
4365
- }else{
4557
+ if(data.action!=='skill-only'){
4366
4558
  ppStats.tasks++;
4559
+ updatePPStats();
4367
4560
  }
4368
- updatePPStats();
4369
4561
  }else if(data.step==='error'){
4370
4562
  ppStats.errors++;
4371
4563
  updatePPStats();
@@ -4393,6 +4585,7 @@ function ppDone(wasStopped,wasFailed,skipReload){
4393
4585
  document.getElementById('ppStopBtn').style.display='none';
4394
4586
  document.getElementById('ppStartBtn').style.display='inline-flex';
4395
4587
  document.getElementById('ppStartBtn').textContent=wasStopped?t('pp.resume'):t('pp.start');
4588
+ document.getElementById('ppStartBtn').disabled=false;
4396
4589
  var doneEl=document.getElementById('ppDone');
4397
4590
  doneEl.style.display='block';
4398
4591
  if(wasFailed){
@@ -4400,17 +4593,38 @@ function ppDone(wasStopped,wasFailed,skipReload){
4400
4593
  doneEl.style.color='#ef4444';
4401
4594
  doneEl.textContent=t('pp.failed')||'Processing failed — check error above';
4402
4595
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#ef4444,#dc2626)';
4596
+ document.getElementById('ppPhaseLabel').textContent=t('pp.failed');
4403
4597
  }else if(wasStopped){
4404
4598
  doneEl.style.background='rgba(245,158,11,.06)';
4405
4599
  doneEl.style.color='#f59e0b';
4406
4600
  doneEl.textContent=t('pp.stopped');
4407
4601
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#f59e0b,#fbbf24)';
4602
+ document.getElementById('ppPhaseLabel').textContent=t('pp.stopped');
4408
4603
  }else{
4409
4604
  doneEl.style.background='rgba(34,197,94,.06)';
4410
4605
  doneEl.style.color='#22c55e';
4411
- doneEl.textContent=t('pp.done')+' ('+t('pp.stat.tasks')+': '+ppStats.tasks+', '+t('pp.stat.skills')+': '+ppStats.skills+')';
4412
4606
  document.getElementById('ppBar').style.width='100%';
4413
4607
  document.getElementById('ppBar').style.background='linear-gradient(90deg,#22c55e,#16a34a)';
4608
+ document.getElementById('ppPhaseLabel').textContent=t('pp.done');
4609
+ var ppTotal=ppStats.tasks+ppStats.skipped+ppStats.errors;
4610
+ if(ppTotal>0) document.getElementById('ppCounter').textContent=ppTotal+' / '+ppTotal+' (100%)';
4611
+ fetch('/api/migrate/postprocess/status').then(function(r){return r.json()}).then(function(st){
4612
+ var totalTasks=st.existingTasks||0;
4613
+ var totalSkills=st.existingSkills||0;
4614
+ var lines=[];
4615
+ if(ppStats.tasks>0) lines.push(t('pp.stat.tasks')+' +'+ppStats.tasks);
4616
+ if(ppStats.skills>0) lines.push(t('pp.stat.skills')+' +'+ppStats.skills);
4617
+ if(ppStats.skipped>0) lines.push(t('pp.stat.skipped')+': '+ppStats.skipped);
4618
+ var runText=lines.length>0?' ('+lines.join(', ')+')':'';
4619
+ var totalText=' — '+t('pp.stat.tasks')+' '+totalTasks+', '+t('pp.stat.skills.total')+' '+totalSkills;
4620
+ doneEl.textContent=t('pp.done')+runText+totalText;
4621
+ }).catch(function(){
4622
+ var parts=[];
4623
+ if(ppStats.tasks>0) parts.push(t('pp.stat.tasks')+': '+ppStats.tasks);
4624
+ if(ppStats.skills>0) parts.push(t('pp.stat.skills')+': '+ppStats.skills);
4625
+ if(ppStats.skipped>0) parts.push(t('pp.stat.skipped')+': '+ppStats.skipped);
4626
+ doneEl.textContent=t('pp.done')+(parts.length>0?' ('+parts.join(', ')+')':'');
4627
+ });
4414
4628
  }
4415
4629
  if(!skipReload) loadAll();
4416
4630
  }
@@ -4448,16 +4662,64 @@ function toggleViewerTheme(){const el=document.documentElement;const cur=el.getA
4448
4662
  initViewerTheme();
4449
4663
 
4450
4664
  /* ─── Update check ─── */
4665
+ function waitForGatewayAndReload(maxAttempts,attempt){
4666
+ attempt=attempt||0;
4667
+ if(attempt>=maxAttempts){window.location.reload();return;}
4668
+ setTimeout(function(){
4669
+ fetch('/api/update-check').then(function(r){
4670
+ if(r.ok) window.location.reload();
4671
+ else waitForGatewayAndReload(maxAttempts,attempt+1);
4672
+ }).catch(function(){waitForGatewayAndReload(maxAttempts,attempt+1);});
4673
+ },3000);
4674
+ }
4675
+ function doUpdateInstall(packageSpec,btnEl){
4676
+ btnEl.disabled=true;
4677
+ btnEl.textContent=t('update.installing');
4678
+ fetch('/api/update-install',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({packageSpec:packageSpec})})
4679
+ .then(function(r){return r.json()})
4680
+ .then(function(d){
4681
+ if(d.ok){
4682
+ btnEl.textContent=t('update.success');
4683
+ btnEl.style.background='#22c55e';
4684
+ btnEl.style.color='#fff';
4685
+ waitForGatewayAndReload(20);
4686
+ }else{
4687
+ btnEl.textContent=t('update.failed')+': '+(d.error||'').slice(0,80);
4688
+ btnEl.style.background='#ef4444';
4689
+ btnEl.style.color='#fff';
4690
+ btnEl.disabled=false;
4691
+ setTimeout(function(){btnEl.textContent=t('update.btn');btnEl.style.background='';btnEl.style.color='';},5000);
4692
+ }
4693
+ })
4694
+ .catch(function(e){
4695
+ btnEl.textContent=t('update.failed');
4696
+ btnEl.disabled=false;
4697
+ setTimeout(function(){btnEl.textContent=t('update.btn');btnEl.style.background='';btnEl.style.color='';},5000);
4698
+ });
4699
+ }
4451
4700
  async function checkForUpdate(){
4452
4701
  try{
4453
4702
  const r=await fetch('/api/update-check');
4454
4703
  if(!r.ok)return;
4455
4704
  const d=await r.json();
4456
4705
  if(!d.updateAvailable)return;
4706
+ const pkgSpec=d.installCommand?d.installCommand.replace(/^openclaw plugins install\s+/,''):(d.packageName+'@'+d.latest);
4457
4707
  const banner=document.createElement('div');
4458
4708
  banner.id='updateBanner';
4459
4709
  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>';
4710
+ var leftSpan=document.createElement('span');
4711
+ leftSpan.innerHTML='🔔 '+t('update.available')+': <b>v'+esc(d.current)+'</b> → <b>v'+esc(d.latest)+'</b>';
4712
+ var btnUpdate=document.createElement('button');
4713
+ btnUpdate.textContent=t('update.btn');
4714
+ btnUpdate.style.cssText='background:#fff;color:#d97706;border:none;padding:5px 16px;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;margin-left:12px';
4715
+ btnUpdate.onclick=function(){doUpdateInstall(pkgSpec,btnUpdate)};
4716
+ var btnClose=document.createElement('button');
4717
+ btnClose.innerHTML='&times;';
4718
+ btnClose.style.cssText='background:none;border:none;color:#fff;font-size:18px;cursor:pointer;padding:0 4px';
4719
+ btnClose.onclick=function(){banner.remove();document.body.style.paddingTop='';};
4720
+ leftSpan.appendChild(btnUpdate);
4721
+ banner.appendChild(leftSpan);
4722
+ banner.appendChild(btnClose);
4461
4723
  document.body.prepend(banner);
4462
4724
  document.body.style.paddingTop='48px';
4463
4725
  }catch(e){}