@wendongfly/myhi 1.3.111 → 1.3.113

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.
package/dist/chat.html CHANGED
@@ -3612,8 +3612,14 @@
3612
3612
  `background:${_memScope===val?'var(--purple)':'var(--bg)'};color:${_memScope===val?'#fff':'var(--muted)'}">${label}</button>`;
3613
3613
  const makeBar = () => {
3614
3614
  const bar = document.createElement('div');
3615
- bar.style.cssText = 'display:flex;gap:0.4rem;margin-bottom:0.55rem';
3616
- bar.innerHTML = segBtn('project', '本项目') + segBtn('all', '全部(含全局/其它项目)');
3615
+ bar.style.cssText = 'display:flex;flex-direction:column;gap:0.4rem;margin-bottom:0.55rem';
3616
+ const row1 = document.createElement('div');
3617
+ row1.style.cssText = 'display:flex;gap:0.4rem';
3618
+ row1.innerHTML = segBtn('project', '本项目') + segBtn('all', '全部(含全局/其它项目)');
3619
+ const row2 = document.createElement('div');
3620
+ row2.style.cssText = 'display:flex;gap:0.4rem';
3621
+ row2.innerHTML = '<button onclick="analyzeMemory()" style="flex:1;padding:0.42rem;border-radius:7px;cursor:pointer;font-size:0.76rem;border:1px solid var(--blue2);background:var(--blue2);color:#fff;font-weight:600">🤖 让 AI 过一遍(找过时/重复/废弃)</button>';
3622
+ bar.appendChild(row1); bar.appendChild(row2);
3617
3623
  return bar;
3618
3624
  };
3619
3625
 
@@ -3659,16 +3665,32 @@
3659
3665
  if (kw && !(f.name.toLowerCase().includes(kw) || (f.content || '').toLowerCase().includes(kw))) continue;
3660
3666
  shown++;
3661
3667
  const { desc, body: md } = parseFM(f.content || '');
3668
+ const deletable = f.name.startsWith('memory/') || f.name.startsWith('~/.claude/memory/');
3669
+ const editable = deletable || f.name.endsWith('CLAUDE.md') || f.name.endsWith('settings.json');
3662
3670
  const card = document.createElement('div');
3663
3671
  card.className = 'mem-file';
3664
3672
  const header = document.createElement('div');
3665
3673
  header.className = 'mem-file-header';
3666
3674
  header.innerHTML = `<span class="arrow">▶</span><span style="flex:1;min-width:0"><span class="mem-title">${escHtml(cleanName(f.name))}</span>${desc ? '<span class="mem-desc">' + escHtml(desc) + '</span>' : ''}</span>`;
3675
+ const acts = document.createElement('span');
3676
+ acts.style.cssText = 'display:flex;gap:0.35rem;flex-shrink:0;align-items:center';
3677
+ if (editable) {
3678
+ const eb = document.createElement('span'); eb.textContent = '✏️'; eb.title = '编辑'; eb.style.cssText = 'cursor:pointer;font-size:0.82rem';
3679
+ eb.onclick = (ev) => { ev.stopPropagation(); startEditMemory(card, f); };
3680
+ acts.appendChild(eb);
3681
+ }
3682
+ if (deletable) {
3683
+ const db = document.createElement('span'); db.textContent = '🗑'; db.title = '删除'; db.style.cssText = 'cursor:pointer;font-size:0.82rem';
3684
+ db.onclick = (ev) => { ev.stopPropagation(); delMemory(f.name); };
3685
+ acts.appendChild(db);
3686
+ }
3687
+ header.appendChild(acts);
3667
3688
  const body = document.createElement('div');
3668
3689
  body.className = 'mem-file-body md';
3669
3690
  body.innerHTML = renderMarkdown(md.trim());
3670
3691
  header.onclick = () => { body.classList.toggle('open'); header.querySelector('.arrow').classList.toggle('open'); };
3671
3692
  card.appendChild(header); card.appendChild(body);
3693
+ card._file = f; card._body = body;
3672
3694
  listWrap.appendChild(card);
3673
3695
  }
3674
3696
  if (!shown) listWrap.innerHTML = '<div style="color:var(--muted);text-align:center;padding:1.2rem 0;font-size:0.8rem">没有匹配的记忆</div>';
@@ -3689,6 +3711,67 @@
3689
3711
  document.getElementById('memory-sheet').classList.remove('open');
3690
3712
  };
3691
3713
 
3714
+ // 让 AI 按规则过一遍记忆(只分析给建议,不改文件)
3715
+ window.analyzeMemory = function() {
3716
+ const scopeTxt = _memScope === 'all' ? '所有项目 + 全局' : '本项目';
3717
+ const prompt = [
3718
+ `请帮我过一遍并分析【${scopeTxt}】的记忆文件(在 ~/.claude/ 下:本项目记忆在对应当前工作目录 cwd 编码后的 projects/<编码>/memory/ 里,`,
3719
+ `全局在 ~/.claude/memory/;先 ls 找到目录再逐个读,可用子代理并行读)。`,
3720
+ `【只分析、给建议,先别改动任何文件】——我会在「记忆」面板里自己确认后维护。判断规则:`,
3721
+ `1) 过时:记忆里的文件/路径/版本/参数/节点状态与现状不符(不确定就标"需核实");`,
3722
+ `2) 重复/重叠:多条讲同一件事、可合并;`,
3723
+ `3) 已废弃/已完成:project_* 里已结束或标注"已弃/完成"的,建议降级为 reference 或删除;`,
3724
+ `4) 索引膨胀:MEMORY.md 有无可精简的行。`,
3725
+ `输出一个表格:文件名 | 类别(过时/重复/废弃/保留) | 原因 | 建议(删除/合并到X/更新/保留);最后一句总结:建议删 N 条、合并 M 组、更新 K 条。`,
3726
+ ].join('');
3727
+ closeMemorySheet();
3728
+ if (currentSession?.mode === 'agent') {
3729
+ if (!isController && canTakeControl()) socket.emit('take-control', { sessionId: SESSION_ID });
3730
+ addInputMessage('🤖 让 AI 过一遍记忆(找过时/重复/废弃)');
3731
+ socket.emit('agent:query', { prompt });
3732
+ showThinking();
3733
+ } else {
3734
+ addStatusMessage('该功能仅在 agent(Claude) 会话可用');
3735
+ }
3736
+ };
3737
+
3738
+ // 编辑记忆文件:把该卡片正文换成 textarea(原始 markdown)+ 保存/取消
3739
+ window.startEditMemory = function(card, f) {
3740
+ const wrap = document.createElement('div');
3741
+ wrap.className = 'mem-file-body open';
3742
+ const ta = document.createElement('textarea');
3743
+ ta.value = f.content || '';
3744
+ ta.spellcheck = false;
3745
+ ta.style.cssText = 'width:100%;box-sizing:border-box;min-height:12rem;font-family:\'SF Mono\',Consolas,monospace;font-size:0.75rem;line-height:1.5;background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:0.5rem;resize:vertical';
3746
+ const btns = document.createElement('div');
3747
+ btns.style.cssText = 'display:flex;gap:0.5rem;margin-top:0.4rem';
3748
+ const save = document.createElement('button'); save.textContent = '保存';
3749
+ save.style.cssText = 'flex:2;padding:0.45rem;border:none;border-radius:7px;background:var(--blue2);color:#fff;cursor:pointer;font-weight:600';
3750
+ const cancel = document.createElement('button'); cancel.textContent = '取消';
3751
+ cancel.style.cssText = 'flex:1;padding:0.45rem;border:1px solid var(--border);border-radius:7px;background:var(--bg);color:var(--muted);cursor:pointer';
3752
+ save.onclick = async () => {
3753
+ try {
3754
+ const r = await fetch('/api/memory/save', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ sessionId: SESSION_ID, name: f.name, content: ta.value }) });
3755
+ const d = await r.json();
3756
+ if (d.ok) { addStatusMessage('记忆已保存: ' + f.name); showMemory(); } else { alert('保存失败: ' + (d.error||'')); }
3757
+ } catch (e) { alert('保存出错: ' + e.message); }
3758
+ };
3759
+ cancel.onclick = () => showMemory();
3760
+ btns.appendChild(save); btns.appendChild(cancel);
3761
+ wrap.appendChild(ta); wrap.appendChild(btns);
3762
+ card._body.replaceWith(wrap); card._body = wrap;
3763
+ ta.focus();
3764
+ };
3765
+
3766
+ window.delMemory = async function(name) {
3767
+ if (!confirm('删除记忆文件「' + name.replace(/^.*\//,'') + '」?不可撤销。')) return;
3768
+ try {
3769
+ const r = await fetch('/api/memory/delete', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ sessionId: SESSION_ID, name }) });
3770
+ const d = await r.json();
3771
+ if (d.ok) { addStatusMessage('已删除记忆: ' + name); showMemory(); } else { alert('删除失败: ' + (d.error||'')); }
3772
+ } catch (e) { alert('删除出错: ' + e.message); }
3773
+ };
3774
+
3692
3775
  // ── Agent 模式快捷功能 ──────────────────────────
3693
3776
  let _totalAgentCost = 0;
3694
3777
  // 追踪 result 消息中的费用
package/dist/grid.html CHANGED
@@ -69,7 +69,7 @@
69
69
  <span class="spacer"></span>
70
70
  <button class="bar-btn" onclick="refreshSessions(true)" title="刷新任务列表">↻</button>
71
71
  <button class="bar-btn" onclick="addPane('')" title="加一个空窗格,从下拉选已有任务">+ 窗格</button>
72
- <button class="bar-btn" style="border-color:var(--green);color:var(--green)" onclick="newTask()" title="新建一个任务并放进新窗格">🆕 新建任务</button>
72
+ <button class="bar-btn" style="border-color:var(--green);color:var(--green)" onclick="openNewTask()" title="新建任务(和首页一致:预设/名称/命令/目录)">🆕 新建任务</button>
73
73
  </div>
74
74
  <div id="grid"></div>
75
75
  <div id="empty-hint" style="display:none">点「+ 窗格」或「🆕 新建任务」把多个任务并排看。<br>拉宽窗口自动多几格,或用上方按钮锁定布局。</div>
@@ -92,6 +92,26 @@
92
92
  </div>
93
93
  </div>
94
94
 
95
+ <!-- 新建任务面板(和首页「新建终端会话」一致)-->
96
+ <div id="new-task" style="display:none;position:fixed;inset:0;z-index:100">
97
+ <div style="position:absolute;inset:0;background:rgba(0,0,0,0.5)" onclick="closeNewTask()"></div>
98
+ <div style="position:relative;max-width:460px;width:92%;margin:8vh auto 0;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:1rem;box-shadow:0 16px 48px rgba(0,0,0,0.45)">
99
+ <div style="font-weight:600;margin-bottom:0.7rem">新建任务</div>
100
+ <div id="nt-presets" style="display:flex;gap:0.4rem;margin-bottom:0.7rem;flex-wrap:wrap"></div>
101
+ <label style="font-size:0.72rem;color:var(--muted)">名称 <span style="color:var(--red)">必填</span></label>
102
+ <input id="nt-title" placeholder="请输入任务名称" autocomplete="off" style="width:100%;box-sizing:border-box;margin:0.2rem 0 0.6rem;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:7px;padding:0.45rem 0.6rem;font-size:0.85rem">
103
+ <label style="font-size:0.72rem;color:var(--muted)">启动命令(可选,终端类型用)</label>
104
+ <input id="nt-cmd" placeholder="留空仅打开 shell" autocomplete="off" style="width:100%;box-sizing:border-box;margin:0.2rem 0 0.6rem;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:7px;padding:0.45rem 0.6rem;font-size:0.82rem;font-family:monospace">
105
+ <label style="font-size:0.72rem;color:var(--muted)">工作目录(可选)</label>
106
+ <input id="nt-cwd" placeholder="留空使用默认目录" autocomplete="off" style="width:100%;box-sizing:border-box;margin:0.2rem 0 0.4rem;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:7px;padding:0.45rem 0.6rem;font-size:0.82rem">
107
+ <div id="nt-recent" style="display:flex;flex-wrap:wrap;gap:0.3rem;margin-bottom:0.7rem"></div>
108
+ <div style="display:flex;gap:0.5rem">
109
+ <button onclick="closeNewTask()" style="flex:1;background:var(--bg);border:1px solid var(--border);color:var(--muted);border-radius:8px;padding:0.5rem;cursor:pointer">取消</button>
110
+ <button onclick="submitNewTask()" style="flex:2;background:var(--green);border:none;color:#fff;border-radius:8px;padding:0.5rem;cursor:pointer;font-weight:600">创建</button>
111
+ </div>
112
+ </div>
113
+ </div>
114
+
95
115
  <script>
96
116
  const LS_KEY = 'myhi_grid_v1';
97
117
  const gridEl = document.getElementById('grid');
@@ -275,19 +295,60 @@
275
295
  }));
276
296
  document.getElementById('tp-search').addEventListener('input', renderPicker);
277
297
 
278
- // ── 新建任务(走 socket create-agent,和首页一致)──
298
+ // ── 新建任务(和首页「新建终端会话」一致:预设 + 名称 + 命令 + 目录 + 最近目录)──
279
299
  const socket = io();
280
- window.newTask = function() {
281
- const title = (prompt('新任务名称:') || '').trim();
282
- if (!title) return;
283
- socket.emit('create-agent', { title }, (res) => {
284
- if (res && res.ok && res.session) {
285
- refreshSessions(true).then(() => addPane(res.session.id));
286
- } else {
287
- alert('创建失败:' + ((res && res.error) || '未知错误'));
288
- }
289
- });
300
+ const NT_PRESETS = [
301
+ { icon:'🤖', label:'Claude', title:'claude', type:'agent', cmd:'' },
302
+ { icon:'✨', label:'Gemini', title:'gemini', type:'pty', cmd:'gemini' },
303
+ { icon:'🐚', label:'Shell', title:'shell', type:'pty', cmd:'' },
304
+ ];
305
+ let _ntPreset = 0;
306
+ const RECENT_DIRS_KEY = 'myhi_recent_dirs'; // 和首页共用
307
+ function ntRecentDirs(){ try { return JSON.parse(localStorage.getItem(RECENT_DIRS_KEY)) || {}; } catch { return {}; } }
308
+ function ntRecordDir(dir){ if(!dir) return; const d=ntRecentDirs(); d[dir]=(d[dir]||0)+1; const s=Object.entries(d).sort((a,b)=>b[1]-a[1]).slice(0,5); localStorage.setItem(RECENT_DIRS_KEY, JSON.stringify(Object.fromEntries(s))); }
309
+ function ntRenderPresets(){
310
+ document.getElementById('nt-presets').innerHTML = NT_PRESETS.map((p,i)=>`<button data-i="${i}" style="flex:1;min-width:5rem;display:flex;align-items:center;justify-content:center;gap:0.3rem;border:1px solid ${i===_ntPreset?'var(--blue2)':'var(--border)'};background:${i===_ntPreset?'var(--blue2)':'var(--bg)'};color:${i===_ntPreset?'#fff':'var(--text2)'};border-radius:8px;padding:0.4rem;font-size:0.8rem;cursor:pointer">${p.icon} ${esc(p.label)}</button>`).join('');
311
+ }
312
+ function ntRenderRecent(){
313
+ const dirs = Object.entries(ntRecentDirs()).sort((a,b)=>b[1]-a[1]);
314
+ document.getElementById('nt-recent').innerHTML = dirs.map(([p])=>`<button data-dir="${esc(p)}" style="background:var(--surface2);border:1px solid var(--border);color:var(--muted);border-radius:6px;font-size:0.68rem;padding:0.12rem 0.4rem;cursor:pointer;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(p)}</button>`).join('');
315
+ }
316
+ window.openNewTask = function(){
317
+ _ntPreset = 0;
318
+ document.getElementById('new-task').style.display = '';
319
+ document.getElementById('nt-title').value = '';
320
+ document.getElementById('nt-cmd').value = '';
321
+ document.getElementById('nt-cwd').value = '';
322
+ ntRenderPresets(); ntRenderRecent();
323
+ setTimeout(()=>document.getElementById('nt-title').focus(), 30);
324
+ };
325
+ window.closeNewTask = function(){ document.getElementById('new-task').style.display='none'; };
326
+ window.submitNewTask = function(){
327
+ const title = document.getElementById('nt-title').value.trim();
328
+ if (!title) { document.getElementById('nt-title').focus(); return; }
329
+ const cmd = document.getElementById('nt-cmd').value.trim() || undefined;
330
+ const cwd = document.getElementById('nt-cwd').value.trim() || undefined;
331
+ if (cwd) ntRecordDir(cwd);
332
+ const preset = NT_PRESETS[_ntPreset];
333
+ const done = (res) => {
334
+ if (res && res.ok && res.session) { closeNewTask(); refreshSessions(true).then(()=>addPane(res.session.id)); }
335
+ else alert('创建失败:' + ((res && res.error) || '未知错误'));
336
+ };
337
+ if (preset.type === 'agent') socket.emit('create-agent', { title, cwd }, done);
338
+ else socket.emit('create', { title, initCmd: cmd, cwd }, done);
290
339
  };
340
+ document.getElementById('nt-presets').addEventListener('click', (e) => {
341
+ const b = e.target.closest('[data-i]'); if (!b) return;
342
+ _ntPreset = +b.dataset.i; const p = NT_PRESETS[_ntPreset];
343
+ const t = document.getElementById('nt-title');
344
+ if (!t.value.trim() || NT_PRESETS.some(x => x.title === t.value.trim())) t.value = p.title;
345
+ document.getElementById('nt-cmd').value = p.cmd || '';
346
+ ntRenderPresets();
347
+ });
348
+ document.getElementById('nt-recent').addEventListener('click', (e) => {
349
+ const b = e.target.closest('[data-dir]'); if (!b) return;
350
+ document.getElementById('nt-cwd').value = b.dataset.dir;
351
+ });
291
352
 
292
353
  // ── 初始化 ──
293
354
  (async function init() {