@memtensor/memos-local-openclaw-plugin 0.3.19 → 1.0.0

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 (106) hide show
  1. package/README.md +232 -22
  2. package/dist/capture/index.d.ts +1 -1
  3. package/dist/capture/index.d.ts.map +1 -1
  4. package/dist/capture/index.js +33 -8
  5. package/dist/capture/index.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/ingest/providers/anthropic.d.ts.map +1 -1
  11. package/dist/ingest/providers/anthropic.js +22 -8
  12. package/dist/ingest/providers/anthropic.js.map +1 -1
  13. package/dist/ingest/providers/bedrock.d.ts.map +1 -1
  14. package/dist/ingest/providers/bedrock.js +22 -8
  15. package/dist/ingest/providers/bedrock.js.map +1 -1
  16. package/dist/ingest/providers/gemini.d.ts.map +1 -1
  17. package/dist/ingest/providers/gemini.js +22 -8
  18. package/dist/ingest/providers/gemini.js.map +1 -1
  19. package/dist/ingest/providers/index.d.ts +13 -18
  20. package/dist/ingest/providers/index.d.ts.map +1 -1
  21. package/dist/ingest/providers/index.js +213 -139
  22. package/dist/ingest/providers/index.js.map +1 -1
  23. package/dist/ingest/providers/openai.d.ts +1 -1
  24. package/dist/ingest/providers/openai.d.ts.map +1 -1
  25. package/dist/ingest/providers/openai.js +37 -17
  26. package/dist/ingest/providers/openai.js.map +1 -1
  27. package/dist/ingest/task-processor.d.ts +28 -3
  28. package/dist/ingest/task-processor.d.ts.map +1 -1
  29. package/dist/ingest/task-processor.js +166 -67
  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 +97 -75
  33. package/dist/ingest/worker.js.map +1 -1
  34. package/dist/shared/llm-call.d.ts +26 -0
  35. package/dist/shared/llm-call.d.ts.map +1 -0
  36. package/dist/shared/llm-call.js +163 -0
  37. package/dist/shared/llm-call.js.map +1 -0
  38. package/dist/skill/evaluator.d.ts +0 -3
  39. package/dist/skill/evaluator.d.ts.map +1 -1
  40. package/dist/skill/evaluator.js +34 -59
  41. package/dist/skill/evaluator.js.map +1 -1
  42. package/dist/skill/evolver.d.ts +22 -1
  43. package/dist/skill/evolver.d.ts.map +1 -1
  44. package/dist/skill/evolver.js +191 -32
  45. package/dist/skill/evolver.js.map +1 -1
  46. package/dist/skill/generator.d.ts +0 -3
  47. package/dist/skill/generator.d.ts.map +1 -1
  48. package/dist/skill/generator.js +15 -50
  49. package/dist/skill/generator.js.map +1 -1
  50. package/dist/skill/upgrader.d.ts +0 -2
  51. package/dist/skill/upgrader.d.ts.map +1 -1
  52. package/dist/skill/upgrader.js +4 -39
  53. package/dist/skill/upgrader.js.map +1 -1
  54. package/dist/skill/validator.d.ts +0 -2
  55. package/dist/skill/validator.d.ts.map +1 -1
  56. package/dist/skill/validator.js +14 -44
  57. package/dist/skill/validator.js.map +1 -1
  58. package/dist/storage/sqlite.d.ts +13 -2
  59. package/dist/storage/sqlite.d.ts.map +1 -1
  60. package/dist/storage/sqlite.js +72 -6
  61. package/dist/storage/sqlite.js.map +1 -1
  62. package/dist/tools/memory-get.d.ts.map +1 -1
  63. package/dist/tools/memory-get.js +5 -1
  64. package/dist/tools/memory-get.js.map +1 -1
  65. package/dist/tools/memory-search.d.ts.map +1 -1
  66. package/dist/tools/memory-search.js +5 -0
  67. package/dist/tools/memory-search.js.map +1 -1
  68. package/dist/tools/memory-timeline.d.ts.map +1 -1
  69. package/dist/tools/memory-timeline.js +11 -2
  70. package/dist/tools/memory-timeline.js.map +1 -1
  71. package/dist/types.d.ts +2 -1
  72. package/dist/types.d.ts.map +1 -1
  73. package/dist/types.js +1 -1
  74. package/dist/types.js.map +1 -1
  75. package/dist/viewer/html.d.ts +1 -1
  76. package/dist/viewer/html.d.ts.map +1 -1
  77. package/dist/viewer/html.js +233 -9
  78. package/dist/viewer/html.js.map +1 -1
  79. package/dist/viewer/server.d.ts +5 -0
  80. package/dist/viewer/server.d.ts.map +1 -1
  81. package/dist/viewer/server.js +383 -177
  82. package/dist/viewer/server.js.map +1 -1
  83. package/index.ts +26 -4
  84. package/package.json +2 -1
  85. package/src/capture/index.ts +39 -10
  86. package/src/index.ts +3 -2
  87. package/src/ingest/providers/anthropic.ts +22 -8
  88. package/src/ingest/providers/bedrock.ts +22 -8
  89. package/src/ingest/providers/gemini.ts +22 -8
  90. package/src/ingest/providers/index.ts +192 -142
  91. package/src/ingest/providers/openai.ts +37 -17
  92. package/src/ingest/task-processor.ts +183 -65
  93. package/src/ingest/worker.ts +98 -77
  94. package/src/shared/llm-call.ts +144 -0
  95. package/src/skill/evaluator.ts +35 -64
  96. package/src/skill/evolver.ts +201 -33
  97. package/src/skill/generator.ts +16 -59
  98. package/src/skill/upgrader.ts +5 -43
  99. package/src/skill/validator.ts +15 -47
  100. package/src/storage/sqlite.ts +88 -6
  101. package/src/tools/memory-get.ts +6 -1
  102. package/src/tools/memory-search.ts +6 -0
  103. package/src/tools/memory-timeline.ts +13 -1
  104. package/src/types.ts +2 -1
  105. package/src/viewer/html.ts +233 -9
  106. package/src/viewer/server.ts +368 -187
@@ -414,7 +414,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
414
414
 
415
415
  /* ─── Analytics / 统计 ─── */
416
416
  .nav-tabs{display:flex;align-items:center;gap:2px;background:rgba(255,255,255,.06);border-radius:10px;padding:3px}
417
- .nav-tabs .tab{padding:6px 20px;border-radius:8px;font-size:13px;font-weight:600;color:var(--text-sec);background:transparent;border:1px solid transparent;cursor:pointer;transition:all .2s;white-space:nowrap}
417
+ .nav-tabs .tab{padding:6px 20px;border-radius:8px;font-size:13px;font-weight:600;color:var(--text-sec);background:transparent;border:1px solid rgba(0,0,0,0);cursor:pointer;transition:color .2s,background .2s,box-shadow .2s;white-space:nowrap}
418
418
  .nav-tabs .tab:hover{color:var(--text)}
419
419
  .nav-tabs .tab.active{color:var(--text);background:rgba(255,255,255,.1);border-color:var(--border);box-shadow:0 1px 4px rgba(0,0,0,.15)}
420
420
  [data-theme="light"] .nav-tabs{background:rgba(0,0,0,.05)}
@@ -816,6 +816,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
816
816
  <div class="task-detail-summary" id="taskDetailSummary"></div>
817
817
  <div class="task-detail-chunks-title" data-i18n="tasks.chunks">Related Memories</div>
818
818
  <div class="task-detail-chunks" id="taskDetailChunks"></div>
819
+ <div id="taskDetailActions" style="display:flex;gap:8px;margin-top:16px;padding-top:12px;border-top:1px solid var(--border)"></div>
819
820
  </div>
820
821
  </div>
821
822
  </div>
@@ -864,6 +865,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
864
865
  <div class="task-detail-chunks" id="skillVersionsList" style="gap:10px"></div>
865
866
  <div class="task-detail-chunks-title" style="margin-top:16px" data-i18n="skills.related">Related Tasks</div>
866
867
  <div class="task-detail-chunks" id="skillRelatedTasks" style="gap:8px"></div>
868
+ <div id="skillDetailActions" style="display:flex;gap:8px;margin-top:16px;padding-top:12px;border-top:1px solid var(--border)"></div>
867
869
  </div>
868
870
  </div>
869
871
  <div class="analytics-view" id="analyticsView">
@@ -1119,8 +1121,20 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1119
1121
  <div id="migrateActions" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
1120
1122
  <button class="btn btn-ghost" onclick="migrateScan()" id="migrateScanBtn" data-i18n="migrate.scan">Scan Data Sources</button>
1121
1123
  <button class="btn btn-primary" onclick="migrateStart()" id="migrateStartBtn" style="display:none" data-i18n="migrate.start">Start Import</button>
1124
+ <span id="migrateConcurrencyRow" style="display:none;align-items:center;gap:6px">
1125
+ <span style="font-size:11px;color:var(--text-muted)" data-i18n="migrate.concurrency.label">Concurrent agents</span>
1126
+ <select id="migrateConcurrency" class="filter-select" style="min-width:auto;padding:3px 10px;font-size:11px">
1127
+ <option value="1" selected>1</option>
1128
+ <option value="2">2</option>
1129
+ <option value="4">4</option>
1130
+ <option value="8">8</option>
1131
+ </select>
1132
+ </span>
1122
1133
  <span id="migrateStatus" style="font-size:11px;color:var(--text-muted)"></span>
1123
1134
  </div>
1135
+ <div id="migrateConcurrencyWarn" style="display:none;margin-top:8px;padding:8px 12px;background:rgba(245,158,11,.06);border:1px solid rgba(245,158,11,.2);border-radius:8px;font-size:11px;color:#f59e0b;line-height:1.5">
1136
+ <span data-i18n="migrate.concurrency.warn">\u26A0 Increasing concurrency raises LLM API call frequency, which may trigger rate limits and cause failures.</span>
1137
+ </div>
1124
1138
 
1125
1139
  <!-- Post-process section: shown after import completes -->
1126
1140
  <div id="postprocessSection" style="display:none;margin-top:16px">
@@ -1143,11 +1157,23 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1143
1157
  </div>
1144
1158
  </label>
1145
1159
  </div>
1146
- <div style="display:flex;gap:10px;align-items:center">
1160
+ <div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
1147
1161
  <button class="btn btn-primary" id="ppStartBtn" onclick="ppStart()" data-i18n="pp.start">Start Processing</button>
1148
1162
  <button class="btn btn-sm" id="ppStopBtn" onclick="ppStop()" style="display:none;background:rgba(239,68,68,.12);color:#ef4444;border:1px solid rgba(239,68,68,.3);font-size:12px;padding:5px 16px;font-weight:600" data-i18n="migrate.stop">\u25A0 Stop</button>
1163
+ <span style="display:inline-flex;align-items:center;gap:6px">
1164
+ <span style="font-size:11px;color:var(--text-muted)" data-i18n="pp.concurrency.label">Concurrent agents</span>
1165
+ <select id="ppConcurrency" class="filter-select" style="min-width:auto;padding:3px 10px;font-size:11px">
1166
+ <option value="1" selected>1</option>
1167
+ <option value="2">2</option>
1168
+ <option value="4">4</option>
1169
+ <option value="8">8</option>
1170
+ </select>
1171
+ </span>
1149
1172
  <span id="ppStatus" style="font-size:11px;color:var(--text-muted)"></span>
1150
1173
  </div>
1174
+ <div id="ppConcurrencyWarn" style="display:none;margin-top:8px;padding:8px 12px;background:rgba(245,158,11,.06);border:1px solid rgba(245,158,11,.2);border-radius:8px;font-size:11px;color:#f59e0b;line-height:1.5">
1175
+ <span data-i18n="pp.concurrency.warn">\u26A0 Increasing concurrency raises LLM API call frequency, which may trigger rate limits and cause failures.</span>
1176
+ </div>
1151
1177
  <div id="ppProgress" style="display:none;margin-top:12px">
1152
1178
  <div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">
1153
1179
  <div style="font-size:12px;font-weight:600;color:var(--text)" id="ppPhaseLabel"></div>
@@ -1452,6 +1478,8 @@ const I18N={
1452
1478
  'migrate.config.warn.desc':'Please configure both Embedding Model and Summarizer Model above before importing. These are required for processing memories.',
1453
1479
  'migrate.sqlite.label':'Memory Index (SQLite)',
1454
1480
  'migrate.sessions.label':'Conversation History',
1481
+ 'migrate.concurrency.label':'Concurrent agents',
1482
+ 'migrate.concurrency.warn':'\u26A0 Increasing concurrency raises LLM API call frequency, which may trigger rate limits and cause failures.',
1455
1483
  'migrate.scan':'Scan Data Sources',
1456
1484
  'migrate.start':'Start Import',
1457
1485
  'migrate.scanning':'Scanning...',
@@ -1475,6 +1503,8 @@ const I18N={
1475
1503
  'pp.tasks.hint':'Group imported messages into tasks and generate a structured summary (title, goal, steps, result) for each one. Makes it easier to search and recall past work.',
1476
1504
  'pp.skills.label':'Trigger skill evolution',
1477
1505
  'pp.skills.hint':'Analyze completed tasks and automatically create or upgrade reusable skills (SKILL.md). Requires task summaries to be enabled. May take longer due to LLM evaluation.',
1506
+ 'pp.concurrency.label':'Concurrent agents',
1507
+ 'pp.concurrency.warn':'\u26A0 Increasing concurrency raises LLM API call frequency, which may trigger rate limits and cause failures.',
1478
1508
  'pp.start':'Start Processing',
1479
1509
  'pp.resume':'Resume Processing',
1480
1510
  'pp.running':'Processing',
@@ -1523,7 +1553,25 @@ const I18N={
1523
1553
  'tasks.role.assistant':'Assistant',
1524
1554
  'tasks.error':'Error',
1525
1555
  'tasks.error.detail':'Failed to load task details',
1526
- 'tasks.untitled.related':'Untitled'
1556
+ 'tasks.untitled.related':'Untitled',
1557
+ 'task.edit':'Edit',
1558
+ 'task.delete':'Delete',
1559
+ 'task.save':'Save',
1560
+ 'task.cancel':'Cancel',
1561
+ 'task.delete.confirm':'Are you sure you want to delete this task? This cannot be undone.',
1562
+ 'task.delete.error':'Failed to delete task: ',
1563
+ 'task.save.error':'Failed to save task: ',
1564
+ 'task.retrySkill':'Retry Skill Generation',
1565
+ 'task.retrySkill.short':'Retry Skill',
1566
+ 'task.retrySkill.confirm':'Re-trigger skill generation for this task?',
1567
+ 'task.retrySkill.error':'Failed to retry skill generation: ',
1568
+ 'skill.edit':'Edit',
1569
+ 'skill.delete':'Delete',
1570
+ 'skill.save':'Save',
1571
+ 'skill.cancel':'Cancel',
1572
+ 'skill.delete.confirm':'Are you sure you want to delete this skill? This will also remove all associated files and cannot be undone.',
1573
+ 'skill.delete.error':'Failed to delete skill: ',
1574
+ 'skill.save.error':'Failed to save skill: '
1527
1575
  },
1528
1576
  zh:{
1529
1577
  'title':'OpenClaw 记忆',
@@ -1723,6 +1771,8 @@ const I18N={
1723
1771
  'migrate.config.warn.desc':'请先在上方配置好 Embedding 模型和 Summarizer 模型,这两项是处理记忆所必需的。',
1724
1772
  'migrate.sqlite.label':'记忆索引 (SQLite)',
1725
1773
  'migrate.sessions.label':'对话历史',
1774
+ 'migrate.concurrency.label':'并行 Agent 数',
1775
+ 'migrate.concurrency.warn':'\u26A0 提高并行数会增加 LLM API 调用频率,可能触发限流而导致失败。',
1726
1776
  'migrate.scan':'扫描数据源',
1727
1777
  'migrate.start':'开始导入',
1728
1778
  'migrate.scanning':'扫描中...',
@@ -1746,6 +1796,8 @@ const I18N={
1746
1796
  'pp.tasks.hint':'将导入的消息按任务分组,为每个任务生成结构化摘要(标题、目标、步骤、结果),方便日后搜索和回忆。',
1747
1797
  'pp.skills.label':'触发技能进化',
1748
1798
  'pp.skills.hint':'分析已完成的任务,自动创建或升级可复用的技能(SKILL.md)。需要先启用任务摘要。由于需要 LLM 评估,耗时较长。',
1799
+ 'pp.concurrency.label':'并行 Agent 数',
1800
+ 'pp.concurrency.warn':'\u26A0 提高并行数会增加 LLM API 调用频率,可能触发限流而导致失败。',
1749
1801
  'pp.start':'开始处理',
1750
1802
  'pp.resume':'继续处理',
1751
1803
  'pp.running':'正在处理',
@@ -1794,7 +1846,25 @@ const I18N={
1794
1846
  'tasks.role.assistant':'助手',
1795
1847
  'tasks.error':'出错了',
1796
1848
  'tasks.error.detail':'加载任务详情失败',
1797
- 'tasks.untitled.related':'未命名'
1849
+ 'tasks.untitled.related':'未命名',
1850
+ 'task.edit':'编辑',
1851
+ 'task.delete':'删除',
1852
+ 'task.save':'保存',
1853
+ 'task.cancel':'取消',
1854
+ 'task.delete.confirm':'确定要删除此任务吗?此操作不可撤销。',
1855
+ 'task.delete.error':'删除任务失败:',
1856
+ 'task.save.error':'保存任务失败:',
1857
+ 'task.retrySkill':'重新生成技能',
1858
+ 'task.retrySkill.short':'重试技能',
1859
+ 'task.retrySkill.confirm':'确定要为此任务重新触发技能生成吗?',
1860
+ 'task.retrySkill.error':'重新生成技能失败:',
1861
+ 'skill.edit':'编辑',
1862
+ 'skill.delete':'删除',
1863
+ 'skill.save':'保存',
1864
+ 'skill.cancel':'取消',
1865
+ 'skill.delete.confirm':'确定要删除此技能吗?关联的文件也会被删除,此操作不可撤销。',
1866
+ 'skill.delete.error':'删除技能失败:',
1867
+ 'skill.save.error':'保存技能失败:'
1798
1868
  }
1799
1869
  };
1800
1870
  const LANG_KEY='memos-viewer-lang';
@@ -2213,6 +2283,12 @@ async function loadTasks(){
2213
2283
  '<span class="tag"><span class="icon">\\u{1F4DD}</span> '+task.chunkCount+' '+t('tasks.chunks.label')+'</span>'+
2214
2284
  '<span class="tag"><span class="icon">\\u{1F4C2}</span> '+(task.sessionKey||'').slice(0,12)+'</span>'+
2215
2285
  '</div>'+
2286
+ '<div class="card-actions" onclick="event.stopPropagation()">'+
2287
+ '<button class="btn btn-sm btn-ghost" onclick="openTaskDetail(\\''+task.id+'\\')">'+t('card.expand')+'</button>'+
2288
+ '<button class="btn btn-sm btn-ghost" onclick="editTaskInline(\\''+task.id+'\\')">'+t('card.edit')+'</button>'+
2289
+ (task.status==='completed'&&(!task.skillStatus||task.skillStatus==='not_generated'||task.skillStatus==='skipped')?'<button class="btn btn-sm btn-ghost" onclick="retrySkillGen(\\''+task.id+'\\')">'+t('task.retrySkill.short')+'</button>':'')+
2290
+ '<button class="btn btn-sm btn-ghost" style="color:var(--accent)" onclick="deleteTask(\\''+task.id+'\\')">'+t('task.delete')+'</button>'+
2291
+ '</div>'+
2216
2292
  '</div>';
2217
2293
  }).join('');
2218
2294
 
@@ -2237,7 +2313,10 @@ function renderTasksPagination(total){
2237
2313
  el.innerHTML=html;
2238
2314
  }
2239
2315
 
2316
+ var _currentTaskId=null;
2317
+ var _currentTaskData=null;
2240
2318
  async function openTaskDetail(taskId){
2319
+ _currentTaskId=taskId;
2241
2320
  const overlay=document.getElementById('taskDetailOverlay');
2242
2321
  overlay.classList.add('show');
2243
2322
  document.getElementById('taskDetailTitle').textContent=t('tasks.loading');
@@ -2246,6 +2325,7 @@ async function openTaskDetail(taskId){
2246
2325
  document.getElementById('taskSkillSection').className='task-skill-section';
2247
2326
  document.getElementById('taskDetailSummary').textContent='';
2248
2327
  document.getElementById('taskDetailChunks').innerHTML='<div class="spinner"></div>';
2328
+ document.getElementById('taskDetailActions').innerHTML='';
2249
2329
 
2250
2330
  try{
2251
2331
  const r=await fetch('/api/task/'+taskId);
@@ -2263,9 +2343,13 @@ async function openTaskDetail(taskId){
2263
2343
  meta.push('<div style="width:100%;margin-top:4px"><span class="meta-item" style="width:100%">'+t('tasks.taskid')+'<span class="task-id-full">'+esc(task.id)+'</span></span></div>');
2264
2344
  document.getElementById('taskDetailMeta').innerHTML=meta.join('');
2265
2345
 
2346
+ _currentTaskData=task;
2347
+
2266
2348
  // ── Skill status section ──
2267
2349
  renderTaskSkillSection(task);
2268
2350
 
2351
+ document.getElementById('taskDetailActions').innerHTML='';
2352
+
2269
2353
  var summaryEl=document.getElementById('taskDetailSummary');
2270
2354
  if(task.status==='skipped'){
2271
2355
  summaryEl.innerHTML='<div style="color:var(--text-muted);font-style:italic;display:flex;align-items:flex-start;gap:8px"><span style="font-size:18px">\\u26A0\\uFE0F</span><span>'+esc(task.summary||t('tasks.skipped.default'))+'</span></div>';
@@ -2319,19 +2403,31 @@ function renderTaskSkillSection(task){
2319
2403
  }else if(ss==='not_generated'){
2320
2404
  section.className='task-skill-section status-not_generated';
2321
2405
  section.innerHTML='<div class="skill-status-header">\\u274C \u672A\u751F\u6210\u6280\u80FD</div>'+
2322
- '<div class="skill-status-reason">\u539F\u56E0\uFF1A'+esc(task.skillReason||'\u7ECF LLM \u8BC4\u4F30\uFF0C\u8BE5\u4EFB\u52A1\u4E0D\u9002\u5408\u63D0\u70BC\u4E3A\u53EF\u590D\u7528\u6280\u80FD\u3002')+'</div>';
2406
+ '<div class="skill-status-reason">\u539F\u56E0\uFF1A'+esc(task.skillReason||'\u7ECF LLM \u8BC4\u4F30\uFF0C\u8BE5\u4EFB\u52A1\u4E0D\u9002\u5408\u63D0\u70BC\u4E3A\u53EF\u590D\u7528\u6280\u80FD\u3002')+'</div>'+
2407
+ (task.status==='completed'?'<button class="btn btn-primary" onclick="retrySkillGen(\\''+esc(task.id)+'\\')" style="margin-top:8px;font-size:12px">'+t('task.retrySkill')+'</button>':'');
2323
2408
  }else if(ss==='skipped'){
2324
2409
  section.className='task-skill-section status-skipped';
2325
2410
  section.innerHTML='<div class="skill-status-header">\\u23ED \u8DF3\u8FC7\u6280\u80FD\u8BC4\u4F30</div>'+
2326
- '<div class="skill-status-reason">\u539F\u56E0\uFF1A'+esc(task.skillReason||'')+'</div>';
2411
+ '<div class="skill-status-reason">\u539F\u56E0\uFF1A'+esc(task.skillReason||'')+'</div>'+
2412
+ (task.status==='completed'?'<button class="btn btn-primary" onclick="retrySkillGen(\\''+esc(task.id)+'\\')" style="margin-top:8px;font-size:12px">'+t('task.retrySkill')+'</button>':'');
2413
+ }else if(ss==='queued'){
2414
+ section.className='task-skill-section status-generating';
2415
+ section.innerHTML='<div class="skill-status-header">\\u{1F4CB} \u6392\u961F\u4E2D</div>'+
2416
+ '<div class="skill-status-reason">'+esc(task.skillReason||'\u7B49\u5F85\u6280\u80FD\u8BC4\u4F30\uFF0C\u524D\u65B9\u4EFB\u52A1\u5904\u7406\u5B8C\u6210\u540E\u81EA\u52A8\u5F00\u59CB\u3002')+'</div>';
2327
2417
  }else if(task.status==='active'){
2328
2418
  section.className='task-skill-section status-skipped';
2329
2419
  section.innerHTML='<div class="skill-status-header">\\u23F8 \u4EFB\u52A1\u8FDB\u884C\u4E2D</div>'+
2330
2420
  '<div class="skill-status-reason">\u6280\u80FD\u8BC4\u4F30\u5728\u4EFB\u52A1\u5B8C\u6210\u540E\u81EA\u52A8\u8FD0\u884C\u3002</div>';
2421
+ }else if(task.status==='completed'){
2422
+ section.className='task-skill-section status-generating';
2423
+ section.innerHTML='<div class="skill-status-header">\\u23F3 \u7B49\u5F85\u8BC4\u4F30</div>'+
2424
+ '<div class="skill-status-reason">\u4EFB\u52A1\u5DF2\u5B8C\u6210\uFF0C\u6280\u80FD\u8BC4\u4F30\u5373\u5C06\u5F00\u59CB\u3002</div>'+
2425
+ '<button class="btn btn-primary" onclick="retrySkillGen(\\''+esc(task.id)+'\\')" style="margin-top:8px;font-size:12px">'+t('task.retrySkill')+'</button>';
2331
2426
  }else{
2332
2427
  section.className='task-skill-section status-skipped';
2333
2428
  section.innerHTML='<div class="skill-status-header">\\u2014 \u65E0\u6280\u80FD\u4FE1\u606F</div>'+
2334
- '<div class="skill-status-reason">\u8BE5\u4EFB\u52A1\u5728\u6280\u80FD\u8FDB\u5316\u7CFB\u7EDF\u542F\u7528\u4E4B\u524D\u5B8C\u6210\uFF0C\u65E0\u6280\u80FD\u8BC4\u4F30\u8BB0\u5F55\u3002</div>';
2429
+ '<div class="skill-status-reason">\u8BE5\u4EFB\u52A1\u672A\u8FDB\u884C\u6280\u80FD\u8BC4\u4F30\u3002</div>'+
2430
+ (task.status==='completed'?'<button class="btn btn-primary" onclick="retrySkillGen(\\''+esc(task.id)+'\\')" style="margin-top:8px;font-size:12px">'+t('task.retrySkill')+'</button>':'');
2335
2431
  }
2336
2432
  }
2337
2433
 
@@ -2340,6 +2436,55 @@ function closeTaskDetail(event){
2340
2436
  document.getElementById('taskDetailOverlay').classList.remove('show');
2341
2437
  }
2342
2438
 
2439
+ async function retrySkillGen(taskId){
2440
+ if(!confirm(t('task.retrySkill.confirm'))) return;
2441
+ try{
2442
+ const r=await fetch('/api/task/'+taskId+'/retry-skill',{method:'POST'});
2443
+ const d=await r.json();
2444
+ if(!r.ok) throw new Error(d.error||'unknown');
2445
+ openTaskDetail(taskId);
2446
+ }catch(e){ alert(t('task.retrySkill.error')+e.message); }
2447
+ }
2448
+
2449
+ async function deleteTask(taskId){
2450
+ if(!confirm(t('task.delete.confirm'))) return;
2451
+ try{
2452
+ const r=await fetch('/api/task/'+taskId,{method:'DELETE'});
2453
+ const d=await r.json();
2454
+ if(!r.ok) throw new Error(d.error||'unknown');
2455
+ closeTaskDetail();
2456
+ document.getElementById('taskDetailOverlay').classList.remove('show');
2457
+ loadTasks();
2458
+ }catch(e){ alert(t('task.delete.error')+e.message); }
2459
+ }
2460
+
2461
+ async function editTaskInline(){
2462
+ if(!_currentTaskData) return;
2463
+ var task=_currentTaskData;
2464
+ var titleEl=document.getElementById('taskDetailTitle');
2465
+ var summaryEl=document.getElementById('taskDetailSummary');
2466
+ var actionsEl=document.getElementById('taskDetailActions');
2467
+
2468
+ titleEl.innerHTML='<input id="editTaskTitle" class="filter-input" style="width:100%;font-size:16px;font-weight:600" value="'+esc(task.title||'')+'"/>';
2469
+ summaryEl.innerHTML='<textarea id="editTaskSummary" class="filter-input" style="width:100%;min-height:80px;font-size:13px;resize:vertical">'+esc(task.summary||'')+'</textarea>';
2470
+ actionsEl.innerHTML=
2471
+ '<button class="btn btn-primary" onclick="saveTaskEdit()" style="font-size:12px">'+t('task.save')+'</button>'+
2472
+ '<button class="btn btn-ghost" onclick="openTaskDetail(\\''+esc(task.id)+'\\')" style="font-size:12px">'+t('task.cancel')+'</button>';
2473
+ }
2474
+
2475
+ async function saveTaskEdit(){
2476
+ if(!_currentTaskId) return;
2477
+ var title=document.getElementById('editTaskTitle').value.trim();
2478
+ var summary=document.getElementById('editTaskSummary').value.trim();
2479
+ try{
2480
+ const r=await fetch('/api/task/'+_currentTaskId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({title:title,summary:summary})});
2481
+ const d=await r.json();
2482
+ if(!r.ok) throw new Error(d.error||'unknown');
2483
+ openTaskDetail(_currentTaskId);
2484
+ loadTasks();
2485
+ }catch(e){ alert(t('task.save.error')+e.message); }
2486
+ }
2487
+
2343
2488
  /* ─── Skills View Logic ─── */
2344
2489
  let skillsStatusFilter='';
2345
2490
 
@@ -2397,6 +2542,12 @@ async function loadSkills(){
2397
2542
  '<span class="tag"><span class="icon">\\u{1F4E6}</span> '+skill.sourceType+'</span>'+
2398
2543
  (tags.length>0?'<div class="skill-card-tags">'+tags.map(t=>'<span class="skill-tag">'+esc(t)+'</span>').join('')+'</div>':'')+
2399
2544
  '</div>'+
2545
+ '<div class="card-actions" onclick="event.stopPropagation()">'+
2546
+ '<button class="btn btn-sm btn-ghost" onclick="openSkillDetail(\\''+skill.id+'\\')">'+t('card.expand')+'</button>'+
2547
+ '<button class="btn btn-sm btn-ghost" onclick="editSkillInline(\\''+skill.id+'\\')">'+t('card.edit')+'</button>'+
2548
+ (skill.visibility==='public'?'<button class="btn btn-sm btn-ghost" onclick="toggleSkillPublic(\\''+skill.id+'\\',false)">\\u{1F512} '+t('skills.setPrivate')+'</button>':'<button class="btn btn-sm btn-ghost" onclick="toggleSkillPublic(\\''+skill.id+'\\',true)">\\u{1F310} '+t('skills.setPublic')+'</button>')+
2549
+ '<button class="btn btn-sm btn-ghost" style="color:var(--accent)" onclick="deleteSkill(\\''+skill.id+'\\')">'+t('skill.delete')+'</button>'+
2550
+ '</div>'+
2400
2551
  '</div>';
2401
2552
  }).join('');
2402
2553
  }catch(e){
@@ -2421,6 +2572,7 @@ async function openSkillDetail(skillId){
2421
2572
  document.getElementById('skillDetailContent').innerHTML='<div class="spinner"></div>';
2422
2573
  document.getElementById('skillVersionsList').innerHTML='<div class="spinner"></div>';
2423
2574
  document.getElementById('skillRelatedTasks').innerHTML='';
2575
+ document.getElementById('skillDetailActions').innerHTML='';
2424
2576
 
2425
2577
  try{
2426
2578
  const r=await fetch('/api/skill/'+skillId);
@@ -2516,6 +2668,9 @@ async function openSkillDetail(skillId){
2516
2668
  ).join('');
2517
2669
  }
2518
2670
 
2671
+ window._currentSkillData=skill;
2672
+ document.getElementById('skillDetailActions').innerHTML='';
2673
+
2519
2674
  }catch(e){
2520
2675
  document.getElementById('skillDetailTitle').textContent=t('skills.error');
2521
2676
  document.getElementById('skillDetailContent').innerHTML='<div style="color:var(--rose);padding:16px">'+t('skills.error.detail')+esc(String(e))+'</div>';
@@ -2544,6 +2699,18 @@ async function toggleSkillVisibility(){
2544
2699
  }
2545
2700
  }
2546
2701
 
2702
+ async function toggleSkillPublic(id,setPublic){
2703
+ const newVis=setPublic?'public':'private';
2704
+ try{
2705
+ const r=await fetch('/api/skill/'+id+'/visibility',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({visibility:newVis})});
2706
+ if(!r.ok) throw new Error('Failed: '+r.status);
2707
+ toast(setPublic?t('toast.setPublic'):t('toast.setPrivate'),'success');
2708
+ loadSkills();
2709
+ }catch(e){
2710
+ toast('Error: '+e.message,'error');
2711
+ }
2712
+ }
2713
+
2547
2714
  /* ─── Settings / Config ─── */
2548
2715
  async function loadConfig(){
2549
2716
  try{
@@ -2665,6 +2832,41 @@ function closeSkillDetail(event){
2665
2832
  document.getElementById('skillDetailOverlay').classList.remove('show');
2666
2833
  }
2667
2834
 
2835
+ async function deleteSkill(skillId){
2836
+ if(!confirm(t('skill.delete.confirm'))) return;
2837
+ try{
2838
+ const r=await fetch('/api/skill/'+skillId,{method:'DELETE'});
2839
+ const d=await r.json();
2840
+ if(!r.ok) throw new Error(d.error||'unknown');
2841
+ closeSkillDetail();
2842
+ document.getElementById('skillDetailOverlay').classList.remove('show');
2843
+ loadSkills();
2844
+ }catch(e){ alert(t('skill.delete.error')+e.message); }
2845
+ }
2846
+
2847
+ function editSkillInline(){
2848
+ var skill=window._currentSkillData;
2849
+ if(!skill) return;
2850
+ var descEl=document.getElementById('skillDetailDesc');
2851
+ var actionsEl=document.getElementById('skillDetailActions');
2852
+ descEl.innerHTML='<textarea id="editSkillDesc" class="filter-input" style="width:100%;min-height:60px;font-size:13px;resize:vertical">'+esc(skill.description||'')+'</textarea>';
2853
+ actionsEl.innerHTML=
2854
+ '<button class="btn btn-primary" onclick="saveSkillEdit()" style="font-size:12px">'+t('skill.save')+'</button>'+
2855
+ '<button class="btn btn-ghost" onclick="openSkillDetail(\\''+esc(skill.id)+'\\')" style="font-size:12px">'+t('skill.cancel')+'</button>';
2856
+ }
2857
+
2858
+ async function saveSkillEdit(){
2859
+ if(!currentSkillId) return;
2860
+ var desc=document.getElementById('editSkillDesc').value.trim();
2861
+ try{
2862
+ const r=await fetch('/api/skill/'+currentSkillId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({description:desc})});
2863
+ const d=await r.json();
2864
+ if(!r.ok) throw new Error(d.error||'unknown');
2865
+ openSkillDetail(currentSkillId);
2866
+ loadSkills();
2867
+ }catch(e){ alert(t('skill.save.error')+e.message); }
2868
+ }
2869
+
2668
2870
  function formatDuration(ms){
2669
2871
  const s=Math.floor(ms/1000);
2670
2872
  if(s<60) return s+'s';
@@ -3368,6 +3570,19 @@ async function clearAll(){
3368
3570
  let migrateScanData=null;
3369
3571
  let migrateStats={stored:0,skipped:0,merged:0,errors:0};
3370
3572
 
3573
+ (function(){
3574
+ const sel=document.getElementById('migrateConcurrency');
3575
+ if(sel) sel.addEventListener('change',function(){
3576
+ const w=document.getElementById('migrateConcurrencyWarn');
3577
+ if(w) w.style.display=parseInt(this.value,10)>1?'block':'none';
3578
+ });
3579
+ const ppSel=document.getElementById('ppConcurrency');
3580
+ if(ppSel) ppSel.addEventListener('change',function(){
3581
+ const w=document.getElementById('ppConcurrencyWarn');
3582
+ if(w) w.style.display=parseInt(this.value,10)>1?'block':'none';
3583
+ });
3584
+ })();
3585
+
3371
3586
  async function migrateScan(){
3372
3587
  const btn=document.getElementById('migrateScanBtn');
3373
3588
  btn.disabled=true;
@@ -3403,6 +3618,7 @@ async function migrateScan(){
3403
3618
 
3404
3619
  if(d.totalItems>0 && d.configReady){
3405
3620
  document.getElementById('migrateStartBtn').style.display='inline-flex';
3621
+ document.getElementById('migrateConcurrencyRow').style.display='inline-flex';
3406
3622
  }
3407
3623
 
3408
3624
  if(d.totalItems===0){
@@ -3424,10 +3640,15 @@ function migrateStart(){
3424
3640
  if(!migrateScanData||!migrateScanData.configReady)return;
3425
3641
  if(!confirm(t('migrate.start')+'?'))return;
3426
3642
 
3643
+ const concSel=document.getElementById('migrateConcurrency');
3644
+ const concurrency=concSel?parseInt(concSel.value,10)||1:1;
3645
+
3427
3646
  window._migrateRunning=true;
3428
3647
  _migrateStatusChecked=false;
3429
3648
  document.getElementById('migrateStartBtn').style.display='none';
3430
3649
  document.getElementById('migrateScanBtn').disabled=true;
3650
+ document.getElementById('migrateConcurrencyRow').style.display='none';
3651
+ document.getElementById('migrateConcurrencyWarn').style.display='none';
3431
3652
  document.getElementById('migrateProgress').style.display='block';
3432
3653
  document.getElementById('migrateLiveLog').innerHTML='';
3433
3654
  migrateStats={stored:0,skipped:0,merged:0,errors:0};
@@ -3437,7 +3658,7 @@ function migrateStart(){
3437
3658
  document.getElementById('migrateBar').style.width='0%';
3438
3659
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#6366f1,#8b5cf6)';
3439
3660
  document.getElementById('migrateCounter').textContent='';
3440
- const body=JSON.stringify({sources:['sqlite','sessions']});
3661
+ const body=JSON.stringify({sources:['sqlite','sessions'],concurrency});
3441
3662
  connectMigrateSSE('/api/migrate/start','POST',body);
3442
3663
  }
3443
3664
 
@@ -3642,6 +3863,9 @@ function ppStart(){
3642
3863
  var enableSkills=document.getElementById('ppEnableSkills').checked;
3643
3864
  if(!enableTasks&&!enableSkills){toast(t('pp.select.warn'),'error');return;}
3644
3865
 
3866
+ var ppConcSel=document.getElementById('ppConcurrency');
3867
+ var ppConcurrency=ppConcSel?parseInt(ppConcSel.value,10)||1:1;
3868
+
3645
3869
  window._ppRunning=true;
3646
3870
  _ppSSEConnected=false;
3647
3871
  ppStats={tasks:0,skills:0,errors:0,skipped:0};
@@ -3658,7 +3882,7 @@ function ppStart(){
3658
3882
  document.getElementById('ppLiveLog').innerHTML='';
3659
3883
  updatePPStats();
3660
3884
 
3661
- var body=JSON.stringify({enableTasks:enableTasks,enableSkills:enableSkills});
3885
+ var body=JSON.stringify({enableTasks:enableTasks,enableSkills:enableSkills,concurrency:ppConcurrency});
3662
3886
  fetch('/api/migrate/postprocess',{method:'POST',headers:{'Content-Type':'application/json'},body:body})
3663
3887
  .then(function(r){
3664
3888
  if(!r.ok){