@memtensor/memos-local-openclaw-plugin 1.0.8-beta.2 → 1.0.8-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -144,10 +144,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
144
144
  .stat-card.rose .stat-value{color:var(--rose)}
145
145
 
146
146
  .sidebar .section-title{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em;margin:24px 0 12px;padding:0 2px}
147
- .sidebar .session-list{display:flex;flex-direction:column;gap:6px;flex:1;min-height:0;overflow-y:auto;padding-right:4px;flex-shrink:1}
148
- .session-item{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:var(--bg-card);border:1px solid var(--border);border-radius:10px;cursor:pointer;transition:all .15s;font-size:13px;color:var(--text)}
149
- .session-item:hover{border-color:var(--pri);background:var(--pri-glow)}
150
- .session-item.active{border-color:var(--pri);background:var(--pri-glow);font-weight:600;color:var(--pri)}
151
147
  .session-item .count{color:var(--text-sec);font-size:11px;font-weight:600;background:rgba(0,0,0,.2);padding:3px 8px;border-radius:8px}
152
148
 
153
149
  .provider-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--green-bg);color:var(--green);border-radius:999px;font-size:11px;font-weight:600;margin-top:10px}
@@ -833,6 +829,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
833
829
  .recall-origin.local-shared{background:rgba(59,130,246,.12);color:#3b82f6}
834
830
  .recall-origin.hub-memory{background:rgba(139,92,246,.12);color:#8b5cf6}
835
831
  .recall-origin.hub-remote{background:rgba(139,92,246,.12);color:#8b5cf6}
832
+ .recall-origin.agent-tag{background:rgba(20,184,166,.12);color:#14b8a6}
836
833
  .recall-summary-short{flex:1;color:var(--text-sec);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
837
834
  .recall-expand-icon{flex-shrink:0;font-size:10px;color:var(--text-muted);transition:transform .15s}
838
835
  .recall-item.expanded .recall-expand-icon{transform:rotate(90deg)}
@@ -1263,7 +1260,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1263
1260
  </div>
1264
1261
  <div id="embeddingStatus"></div>
1265
1262
  <button class="btn btn-sm btn-ghost" style="width:100%;margin:20px 0 12px;justify-content:center;color:var(--text-muted);font-size:11px" onclick="clearAll()" data-i18n="sidebar.clear">\u{1F5D1} Clear All Data</button>
1266
- <div class="session-list" id="sessionList" style="display:none"></div>
1267
1263
  </div>
1268
1264
 
1269
1265
  <div class="view-container">
@@ -1820,6 +1816,11 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1820
1816
  <input type="number" id="cfgViewerPort" placeholder="18799">
1821
1817
  <div class="field-hint" data-i18n="settings.viewerport.hint">Requires restart to take effect</div>
1822
1818
  </div>
1819
+ <div class="settings-field">
1820
+ <label data-i18n="settings.taskAutoFinalize">Task Auto-Finalize (hours)</label>
1821
+ <input type="number" id="cfgTaskAutoFinalizeHours" placeholder="4" min="0" step="1" style="max-width:120px">
1822
+ <div class="field-hint" data-i18n="settings.taskAutoFinalize.hint">Active tasks with no new messages beyond this duration will be automatically summarized and completed when the Tasks page is opened. Set to 0 to disable. Default: 4 hours.</div>
1823
+ </div>
1823
1824
  </div>
1824
1825
  <div class="settings-card-divider"></div>
1825
1826
  <div class="settings-toggle">
@@ -2320,6 +2321,8 @@ const I18N={
2320
2321
  'settings.telemetry.hint':'Only collects tool names, latencies and version info. No memory content or personal data.',
2321
2322
  'settings.viewerport':'Viewer Port',
2322
2323
  'settings.viewerport.hint':'Requires restart to take effect',
2324
+ 'settings.taskAutoFinalize':'Task Auto-Finalize (hours)',
2325
+ 'settings.taskAutoFinalize.hint':'Active tasks with no new messages beyond this duration will be automatically summarized and completed when the Tasks page is opened. Set to 0 to disable. Default: 4 hours.',
2323
2326
  'settings.test':'Test Connection',
2324
2327
  'settings.test.loading':'Testing...',
2325
2328
  'settings.test.ok':'Connected',
@@ -3087,6 +3090,8 @@ const I18N={
3087
3090
  'settings.telemetry.hint':'仅收集工具名称、响应时间和版本号,不涉及任何记忆内容或个人数据。',
3088
3091
  'settings.viewerport':'Viewer 端口',
3089
3092
  'settings.viewerport.hint':'修改后需重启网关生效',
3093
+ 'settings.taskAutoFinalize':'任务自动完结(小时)',
3094
+ 'settings.taskAutoFinalize.hint':'处于进行中的任务如果超过设定时间没有新消息,打开任务页面时会自动生成总结并标记为已完成。设为 0 则关闭此功能。默认:4 小时。',
3090
3095
  'settings.test':'测试连接',
3091
3096
  'settings.test.loading':'测试中...',
3092
3097
  'settings.test.ok':'连接成功',
@@ -3963,6 +3968,11 @@ function _updateScopeSelectorsVisibility(hubAvailable){
3963
3968
  var el=document.getElementById(ids[i]);
3964
3969
  if(el) el.style.display=hubAvailable?'':'none';
3965
3970
  }
3971
+ if(!hubAvailable){
3972
+ if(memorySearchScope==='hub'){memorySearchScope='allLocal';try{localStorage.setItem('memos_memorySearchScope','allLocal');}catch(e){}}
3973
+ if(taskSearchScope==='hub'){taskSearchScope='allLocal';try{localStorage.setItem('memos_taskSearchScope','allLocal');}catch(e){}}
3974
+ if(skillSearchScope==='hub'){skillSearchScope='allLocal';try{localStorage.setItem('memos_skillSearchScope','allLocal');}catch(e){}}
3975
+ }
3966
3976
  }
3967
3977
  async function loadSharingStatus(forcePending){
3968
3978
  try{
@@ -5184,11 +5194,13 @@ function renderSharingMemorySearchResults(data,query){
5184
5194
  '<div class="result-section">'+
5185
5195
  '<div class="result-section-header"><div class="result-section-title">'+t('search.localResults')+'</div><div class="result-section-sub">'+localHits.length+' hit(s)</div></div>'+
5186
5196
  '<div class="search-hit-list">'+(localHits.length?localHits.map(function(hit,idx){
5197
+ var agentName='';if(hit.owner){var ap=(hit.owner||'').split(':');agentName=ap.length>=3?ap[ap.length-1]:(ap.length>=2?ap[1]:hit.owner);}
5187
5198
  return '<div class="search-hit-card">'+
5188
5199
  '<div class="summary">'+(idx+1)+'. '+esc(hit.summary||'(no summary)')+'</div>'+
5189
5200
  '<div class="excerpt">'+esc(hit.excerpt||'')+'</div>'+
5190
5201
  '<div class="search-hit-meta">'+
5191
5202
  '<span class="meta-chip">role: '+esc(hit.role||'unknown')+'</span>'+
5203
+ (agentName?'<span class="meta-chip" style="background:rgba(20,184,166,.12);color:#14b8a6">'+esc(agentName)+'</span>':'')+
5192
5204
  (hit.score!=null?'<span class="meta-chip">score: '+Math.round(hit.score*100)+'%</span>':'')+
5193
5205
  (hit.taskId?'<span class="meta-chip">task: '+esc(hit.taskId)+'</span>':'')+
5194
5206
  '</div>'+
@@ -5198,11 +5210,13 @@ function renderSharingMemorySearchResults(data,query){
5198
5210
  '<div class="result-section">'+
5199
5211
  '<div class="result-section-header"><div class="result-section-title">'+t('search.hubResults')+'</div><div class="result-section-sub">'+hubHits.length+' hit(s)</div></div>'+
5200
5212
  '<div class="search-hit-list">'+(hubHits.length?hubHits.map(function(hit,idx){
5213
+ var hubAgentName='';if(hit.sourceAgent){var hap=(hit.sourceAgent||'').split(':');hubAgentName=hap.length>=3?hap[hap.length-1]:(hap.length>=2?hap[1]:hit.sourceAgent);}
5201
5214
  return '<div class="hub-hit-card">'+
5202
5215
  '<div class="summary">'+(idx+1)+'. '+esc(hit.summary||'(no summary)')+'</div>'+
5203
5216
  '<div class="excerpt">'+esc(hit.excerpt||'')+'</div>'+
5204
5217
  '<div class="hub-hit-meta">'+
5205
5218
  '<span class="meta-chip">owner: '+fmtOwner(hit)+'</span>'+
5219
+ (hubAgentName?'<span class="meta-chip" style="background:rgba(20,184,166,.12);color:#14b8a6">'+esc(hubAgentName)+'</span>':'')+
5206
5220
  (hit.groupName?'<span class="meta-chip">group: '+esc(hit.groupName)+'</span>':'')+
5207
5221
  '<span class="meta-chip">visibility: '+esc(hit.visibility||'hub')+'</span>'+
5208
5222
  '</div>'+
@@ -5710,6 +5724,13 @@ function recallOriginBadge(origin){
5710
5724
  if(origin==='hub-remote') return '<span class="recall-origin hub-remote">'+t('recall.origin.hubRemote')+'</span>';
5711
5725
  return '';
5712
5726
  }
5727
+ function agentBadge(owner){
5728
+ if(!owner) return '';
5729
+ var parts=(owner||'').split(':');
5730
+ var name=parts.length>=3?parts[parts.length-1]:(parts.length>=2?parts[1]:owner);
5731
+ if(!name) return '';
5732
+ return '<span class="recall-origin agent-tag">\u{1F916} '+escapeHtml(name)+'</span>';
5733
+ }
5713
5734
 
5714
5735
  function buildLogSummary(lg){
5715
5736
  let inputObj=null;
@@ -5736,8 +5757,9 @@ function buildLogSummary(lg){
5736
5757
  var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
5737
5758
  var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
5738
5759
  var oBadge=recallOriginBadge(c.origin);
5760
+ var aBadge2=agentBadge(c.owner);
5739
5761
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5740
- html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5762
+ html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span>'+oBadge+aBadge2+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5741
5763
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5742
5764
  html+='</div>';
5743
5765
  });
@@ -5752,8 +5774,9 @@ function buildLogSummary(lg){
5752
5774
  var shortText=escapeHtml(c.summary||c.original_excerpt||'');
5753
5775
  var fullText=escapeHtml(c.original_excerpt||c.summary||'');
5754
5776
  var owner=c.ownerName?' ['+escapeHtml(c.ownerName)+']':'';
5777
+ var haBadge2=agentBadge(c.sourceAgent||'');
5755
5778
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5756
- html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'assistant')+'">'+(c.role||'assistant')+'</span><span class="recall-origin hub-remote">'+t('recall.origin.hubRemote')+'</span>'+owner+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5779
+ html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'assistant')+'">'+(c.role||'assistant')+'</span><span class="recall-origin hub-remote">'+t('recall.origin.hubRemote')+'</span>'+owner+haBadge2+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5757
5780
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5758
5781
  html+='</div>';
5759
5782
  });
@@ -5769,8 +5792,9 @@ function buildLogSummary(lg){
5769
5792
  var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
5770
5793
  var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
5771
5794
  var oBadge=recallOriginBadge(f.origin);
5795
+ var faBadge2=agentBadge(f.owner);
5772
5796
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5773
- html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+f.score.toFixed(2)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5797
+ html+='<div class="recall-item-head"><span class="recall-score '+scoreClass+'">'+f.score.toFixed(2)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span>'+oBadge+faBadge2+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5774
5798
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5775
5799
  html+='</div>';
5776
5800
  });
@@ -5836,8 +5860,9 @@ function buildRecallDetailHtml(rd){
5836
5860
  var shortText=escapeHtml(c.summary||c.content||c.original_excerpt||'');
5837
5861
  var fullText=escapeHtml(c.content||c.original_excerpt||c.summary||'');
5838
5862
  var oBadge=recallOriginBadge(c.origin);
5863
+ var aBadge=agentBadge(c.owner);
5839
5864
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5840
- html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+c.score.toFixed(3)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5865
+ html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+c.score.toFixed(3)+'</span><span class="log-msg-role '+(c.role||'user')+'">'+(c.role||'user')+'</span>'+oBadge+aBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5841
5866
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5842
5867
  html+='</div>';
5843
5868
  });
@@ -5852,8 +5877,9 @@ function buildRecallDetailHtml(rd){
5852
5877
  var shortText=escapeHtml(c.summary||c.original_excerpt||'');
5853
5878
  var fullText=escapeHtml(c.original_excerpt||c.summary||'');
5854
5879
  var owner=c.ownerName?' ['+escapeHtml(c.ownerName)+']':'';
5880
+ var haBadge=agentBadge(c.sourceAgent||'');
5855
5881
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5856
- html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'assistant')+'">'+(c.role||'assistant')+'</span><span class="recall-origin hub-remote">'+t('recall.origin.hubRemote')+'</span>'+owner+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5882
+ html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+c.score.toFixed(2)+'</span><span class="log-msg-role '+(c.role||'assistant')+'">'+(c.role||'assistant')+'</span><span class="recall-origin hub-remote">'+t('recall.origin.hubRemote')+'</span>'+owner+haBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5857
5883
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5858
5884
  html+='</div>';
5859
5885
  });
@@ -5869,8 +5895,9 @@ function buildRecallDetailHtml(rd){
5869
5895
  var shortText=escapeHtml(f.summary||f.content||f.original_excerpt||'');
5870
5896
  var fullText=escapeHtml(f.content||f.original_excerpt||f.summary||'');
5871
5897
  var oBadge=recallOriginBadge(f.origin);
5898
+ var faBadge=agentBadge(f.owner);
5872
5899
  html+='<div class="recall-item" onclick="event.stopPropagation();this.classList.toggle(\\\'expanded\\\')">';
5873
- html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+f.score.toFixed(3)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span>'+oBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5900
+ html+='<div class="recall-item-head"><span class="recall-idx">'+(i+1)+'</span><span class="recall-score '+scoreClass+'">'+f.score.toFixed(3)+'</span><span class="log-msg-role '+(f.role||'user')+'">'+(f.role||'user')+'</span>'+oBadge+faBadge+'<span class="recall-summary-short">'+shortText+'</span><span class="recall-expand-icon">\u25B6</span></div>';
5874
5901
  html+='<div class="recall-summary-full">'+fullText+'</div>';
5875
5902
  html+='</div>';
5876
5903
  });
@@ -6105,7 +6132,7 @@ function renderTaskCards(tasks,container){
6105
6132
  '<span class="tag"><span class="icon">\\u{1F4C5}</span> '+timeStr+'</span>'+
6106
6133
  (durationStr?'<span class="tag"><span class="icon">\\u23F1</span> '+durationStr+'</span>':'')+
6107
6134
  '<span class="tag"><span class="icon">\\u{1F4DD}</span> '+(task.chunkCount||0)+' '+t('tasks.chunks.label')+'</span>'+
6108
- '<span class="tag"><span class="icon">\\u{1F4C2}</span> '+(task.sessionKey||'').slice(0,12)+'</span>'+
6135
+ '<span class="tag"><span class="icon">\\u{1F4C2}</span> '+(task.sessionKey||'')+'</span>'+
6109
6136
  '</div>'+
6110
6137
  '<div class="card-actions" onclick="event.stopPropagation()">'+
6111
6138
  '<button class="btn btn-sm btn-ghost" onclick="openTaskDetail(\\''+task.id+'\\')">'+t('card.expand')+'</button>'+
@@ -7081,6 +7108,7 @@ async function loadConfig(){
7081
7108
  document.getElementById('cfgSkillApiKey').value=skSum.apiKey||'';
7082
7109
 
7083
7110
  document.getElementById('cfgViewerPort').value=cfg.viewerPort||'';
7111
+ document.getElementById('cfgTaskAutoFinalizeHours').value=cfg.taskAutoFinalizeHours!=null?cfg.taskAutoFinalizeHours:'';
7084
7112
 
7085
7113
  const tel=cfg.telemetry||{};
7086
7114
  document.getElementById('cfgTelemetryEnabled').checked=tel.enabled!==false;
@@ -7385,6 +7413,8 @@ async function saveGeneralConfig(){
7385
7413
  const cfg={};
7386
7414
  const vp=document.getElementById('cfgViewerPort').value.trim();
7387
7415
  if(vp) cfg.viewerPort=Number(vp);
7416
+ const tafh=document.getElementById('cfgTaskAutoFinalizeHours').value.trim();
7417
+ cfg.taskAutoFinalizeHours=tafh!==''?Number(tafh):4;
7388
7418
  cfg.telemetry={enabled:document.getElementById('cfgTelemetryEnabled').checked};
7389
7419
 
7390
7420
  await doSaveConfig(cfg, saveBtn, 'generalSaved');
@@ -8207,14 +8237,6 @@ async function loadStats(ownerFilter){
8207
8237
  }
8208
8238
 
8209
8239
  const sidebarSessions=getSessionsForView(_activeView);
8210
- const sl=document.getElementById('sessionList');
8211
- if(sl) sl.style.display=sidebarSessions.length>0?'':'none';
8212
- sl.innerHTML='<div class="session-item'+(activeSession===null?' active':'')+'" onclick="filterSession(null)"><span>'+t('sidebar.allsessions')+'</span>'+countBadgeHtml(sidebarSessions.reduce(function(sum,s){return sum+(s.count||0);},0))+'</div>';
8213
- sidebarSessions.forEach(s=>{
8214
- const isActive=activeSession===s.session_key;
8215
- const name=s.session_key.length>20?s.session_key.slice(0,8)+'...'+s.session_key.slice(-8):s.session_key;
8216
- sl.innerHTML+='<div class="session-item'+(isActive?' active':'')+'" onclick="filterSession(\\''+s.session_key.replace(/'/g,"\\\\'")+'\\')"><span title="'+s.session_key+'">'+name+'</span>'+countBadgeHtml(s.count||0)+'</div>';
8217
- });
8218
8240
 
8219
8241
  [['filterSession','memories'],['taskFilterSession','tasks'],['skillFilterSession','skills']].forEach(function(pair){
8220
8242
  var selId=pair[0];
@@ -8244,8 +8266,7 @@ async function loadStats(ownerFilter){
8244
8266
  var label=o.replace('agent:','');
8245
8267
  sel.innerHTML+='<option value="'+o+'"'+(o===curVal?' selected':'')+'>'+label+'</option>';
8246
8268
  });
8247
- if(agents.length<=1) sel.style.display='none';
8248
- else sel.style.display='';
8269
+ sel.style.display='';
8249
8270
  });
8250
8271
  }
8251
8272
  }
@@ -8458,10 +8479,6 @@ function filterSession(key){
8458
8479
  var fSel=document.getElementById(selId);
8459
8480
  if(fSel) fSel.value=key||'';
8460
8481
  });
8461
- document.querySelectorAll('#sessionList .session-item').forEach(function(el,i){
8462
- if(i===0) el.classList.toggle('active',!key);
8463
- else el.classList.toggle('active',el.querySelector('span')?.title===key);
8464
- });
8465
8482
  if(_activeView==='tasks'){
8466
8483
  loadStats();
8467
8484
  loadTasks();
@@ -8772,7 +8789,7 @@ async function showMemoryModal(chunkId){
8772
8789
  h+='<div class="mm-section"><div class="mm-section-label">'+t('admin.content')+'</div><pre class="mm-content">'+esc(m.content)+'</pre></div>';
8773
8790
  }
8774
8791
  h+='<div class="mm-meta">';
8775
- if(m.session_key) h+='<div class="mm-meta-chip"><strong>'+t('admin.session')+'</strong><span>'+esc(m.session_key.slice(0,12))+'</span></div>';
8792
+ if(m.session_key) h+='<div class="mm-meta-chip"><strong>'+t('admin.session')+'</strong><span>'+esc(m.session_key)+'</span></div>';
8776
8793
  h+='<div class="mm-meta-chip"><strong>'+t('memory.detail.created')+'</strong><span>'+fmtModalDate(m.created_at)+'</span></div>';
8777
8794
  if(m.updated_at) h+='<div class="mm-meta-chip"><strong>'+t('memory.detail.updated')+'</strong><span>'+fmtModalDate(m.updated_at)+'</span></div>';
8778
8795
  if(m.kind) h+='<div class="mm-meta-chip"><strong>'+t('admin.kind')+'</strong><span>'+esc(m.kind)+'</span></div>';
@@ -8917,7 +8934,13 @@ async function clearAll(){
8917
8934
  const r=await fetch('/api/memories',{method:'DELETE'});
8918
8935
  if(r.status===401){toast(t('settings.session.expired'),'error');return;}
8919
8936
  const d=await r.json();
8920
- if(d.ok){toast(t('toast.cleared'),'success');loadAll();}
8937
+ if(d.ok){
8938
+ toast(t('toast.cleared'),'success');
8939
+ selectedMemoryIds.clear();selectedTaskIds.clear();selectedSkillIds.clear();
8940
+ _lastMemoriesFingerprint='';_lastTasksFingerprint='';_lastSkillsFingerprint='';_lastStatsFp='';
8941
+ await loadStats();
8942
+ await Promise.all([loadMemories(),loadTasks(),loadSkills(),loadSharingStatus(false)]);
8943
+ }
8921
8944
  else{toast(t('toast.clearfail'),'error')}
8922
8945
  }catch(e){toast('Error: '+e.message,'error')}
8923
8946
  }
@@ -9631,7 +9654,10 @@ function doUpdateInstall(packageSpec,btnEl,statusEl,targetVersion){
9631
9654
  btnEl.disabled=false;
9632
9655
  });
9633
9656
  }
9657
+ var _updateChecked=false;
9634
9658
  async function checkForUpdate(){
9659
+ if(_updateChecked) return;
9660
+ _updateChecked=true;
9635
9661
  try{
9636
9662
  const r=await fetch('/api/update-check?_t='+Date.now(),{cache:'no-store'});
9637
9663
  if(!r.ok)return;
@@ -144,6 +144,8 @@ export class ViewerServer {
144
144
  private lastKnownNotifCount = 0;
145
145
  private hubHeartbeatTimer?: ReturnType<typeof setInterval>;
146
146
  private static readonly HUB_HEARTBEAT_INTERVAL_MS = 45_000;
147
+ private static readonly STALE_TASK_TIMEOUT_MS = 4 * 60 * 60 * 1000;
148
+ private staleFinalizeRunning = false;
147
149
 
148
150
  constructor(opts: ViewerServerOptions) {
149
151
  this.store = opts.store;
@@ -637,6 +639,61 @@ export class ViewerServer {
637
639
 
638
640
  this.backfillTaskEmbeddings(items);
639
641
  this.jsonResponse(res, { tasks: items, total, limit, offset });
642
+ this.autoFinalizeStaleTasks();
643
+ }
644
+
645
+ private getTaskAutoFinalizeMs(): number {
646
+ const hours = this.ctx?.config?.taskAutoFinalizeHours;
647
+ if (hours !== undefined && hours !== null) return hours * 60 * 60 * 1000;
648
+ try {
649
+ const cfgPath = this.getOpenClawConfigPath();
650
+ if (fs.existsSync(cfgPath)) {
651
+ const raw = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
652
+ const entries = raw?.plugins?.entries ?? {};
653
+ const pluginCfg = entries["memos-local-openclaw-plugin"]?.config
654
+ ?? entries["memos-local"]?.config ?? {};
655
+ if (pluginCfg.taskAutoFinalizeHours !== undefined) return pluginCfg.taskAutoFinalizeHours * 60 * 60 * 1000;
656
+ }
657
+ } catch { /* fall through */ }
658
+ return ViewerServer.STALE_TASK_TIMEOUT_MS;
659
+ }
660
+
661
+ private autoFinalizeStaleTasks(): void {
662
+ if (this.staleFinalizeRunning || !this.ctx) return;
663
+ const thresholdMs = this.getTaskAutoFinalizeMs();
664
+ if (thresholdMs <= 0) return;
665
+ const db = (this.store as any).db;
666
+ const now = Date.now();
667
+ let staleTasks: Array<{ id: string }>;
668
+ try {
669
+ staleTasks = db.prepare(`
670
+ SELECT t.id
671
+ FROM tasks t
672
+ LEFT JOIN chunks c ON c.task_id = t.id
673
+ WHERE t.status = 'active'
674
+ GROUP BY t.id
675
+ HAVING (? - COALESCE(MAX(c.created_at), t.started_at)) > ?
676
+ `).all(now, thresholdMs) as Array<{ id: string }>;
677
+ } catch { return; }
678
+ if (staleTasks.length === 0) return;
679
+
680
+ this.staleFinalizeRunning = true;
681
+ const hours = Math.round(thresholdMs / 3600000);
682
+ this.log.info(`Auto-finalizing ${staleTasks.length} stale active task(s) (idle > ${hours}h)`);
683
+ const tp = new TaskProcessor(this.store, this.ctx);
684
+ (async () => {
685
+ for (const row of staleTasks) {
686
+ const task = this.store.getTask(row.id);
687
+ if (!task || task.status !== "active") continue;
688
+ try {
689
+ await tp.finalizeTask(task);
690
+ this.log.info(`Auto-finalized stale task=${task.id}`);
691
+ } catch (err) {
692
+ this.log.warn(`Failed to auto-finalize task=${task.id}: ${err}`);
693
+ }
694
+ }
695
+ })().catch((err) => this.log.warn(`autoFinalizeStaleTasks error: ${err}`))
696
+ .finally(() => { this.staleFinalizeRunning = false; });
640
697
  }
641
698
 
642
699
  private async serveTaskSearch(res: http.ServerResponse, url: URL): Promise<void> {
@@ -1483,7 +1540,7 @@ export class ViewerServer {
1483
1540
  const refreshedChunk = db.prepare("SELECT * FROM chunks WHERE id = ?").get(chunkId) as any;
1484
1541
  const response = await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/share", {
1485
1542
  method: "POST",
1486
- body: JSON.stringify({ memory: { sourceChunkId: refreshedChunk.id, role: refreshedChunk.role, content: refreshedChunk.content, summary: refreshedChunk.summary, kind: refreshedChunk.kind, groupId: null, visibility: "public" } }),
1543
+ body: JSON.stringify({ memory: { sourceChunkId: refreshedChunk.id, sourceAgent: refreshedChunk.owner || "", role: refreshedChunk.role, content: refreshedChunk.content, summary: refreshedChunk.summary, kind: refreshedChunk.kind, groupId: null, visibility: "public" } }),
1487
1544
  });
1488
1545
  if (!isLocalShared) this.store.markMemorySharedLocally(chunkId);
1489
1546
  const memoryId = String((response as any)?.memoryId ?? "");
@@ -1493,6 +1550,7 @@ export class ViewerServer {
1493
1550
  this.store.upsertHubMemory({
1494
1551
  id: memoryId || existing?.id || crypto.randomUUID(),
1495
1552
  sourceChunkId: chunkId, sourceUserId: hubClient.userId,
1553
+ sourceAgent: refreshedChunk.owner || "",
1496
1554
  role: refreshedChunk.role, content: refreshedChunk.content, summary: refreshedChunk.summary ?? "",
1497
1555
  kind: refreshedChunk.kind, groupId: null, visibility: "public",
1498
1556
  createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
@@ -2302,6 +2360,7 @@ export class ViewerServer {
2302
2360
  ref: { sessionKey: row.session_key, chunkId: row.id, turnId: row.turn_id, seq: row.seq },
2303
2361
  taskId: row.task_id ?? null,
2304
2362
  skillId: row.skill_id ?? null,
2363
+ owner: row.owner || "",
2305
2364
  }));
2306
2365
  return { hits, meta: { total: hits.length, usedMaxResults: maxResults } };
2307
2366
  }
@@ -2411,6 +2470,7 @@ export class ViewerServer {
2411
2470
  body: JSON.stringify({
2412
2471
  memory: {
2413
2472
  sourceChunkId: chunk.id,
2473
+ sourceAgent: chunk.owner || "",
2414
2474
  role: chunk.role,
2415
2475
  content: chunk.content,
2416
2476
  summary: chunk.summary,
@@ -2428,6 +2488,7 @@ export class ViewerServer {
2428
2488
  id: mid || existing?.id || crypto.randomUUID(),
2429
2489
  sourceChunkId: chunk.id,
2430
2490
  sourceUserId: hubClient.userId,
2491
+ sourceAgent: chunk.owner || "",
2431
2492
  role: chunk.role,
2432
2493
  content: chunk.content,
2433
2494
  summary: chunk.summary ?? "",
@@ -2976,6 +3037,7 @@ export class ViewerServer {
2976
3037
  if (newCfg.summarizer) config.summarizer = newCfg.summarizer;
2977
3038
  if (newCfg.skillEvolution) config.skillEvolution = newCfg.skillEvolution;
2978
3039
  if (newCfg.viewerPort) config.viewerPort = newCfg.viewerPort;
3040
+ if (newCfg.taskAutoFinalizeHours !== undefined) config.taskAutoFinalizeHours = newCfg.taskAutoFinalizeHours;
2979
3041
  if (newCfg.telemetry !== undefined) config.telemetry = newCfg.telemetry;
2980
3042
  if (newCfg.sharing !== undefined) {
2981
3043
  const existing = (config.sharing as Record<string, unknown>) || {};