mingdao-harness 0.6.20 → 0.6.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mingdao-harness",
3
- "version": "0.6.20",
3
+ "version": "0.6.21",
4
4
  "description": "MingDao Harness —— 开源智能体框架(Agent Harness)。零依赖、开箱即用,针对 DeepSeek-V4 系列优化,开放主流模型接入。",
5
5
  "type": "module",
6
6
  "bin": {
package/src/web/app.js CHANGED
@@ -761,7 +761,7 @@ function renderWorkStatus(){
761
761
  const running=bgTasks.filter((/** @type {any} */ t)=>t.status==='running');
762
762
  const latest=running.length?running[running.length-1]:null;
763
763
  bg.style.display=''; bg.textContent='🛠 '+bgRunning+' 个后台任务运行中'+(latest?' · '+String(latest.message||'').slice(0,22):'')+' — 点击查看详情';
764
- bg.onclick=()=>{ const tp=$('#tasksPanel'); tp.style.display = tp.style.display==='none'?'flex':'none'; if(tp.style.display==='flex'){ $('#trajPanel').style.display='none'; $('#subPanel').style.display='none'; updateTasksPanel(); } syncPanelLayout(); };
764
+ bg.onclick=()=>{ activateRailTab('tasks'); }; // §3.82:后台任务 chip 打开右栏「任务」标签
765
765
  } else { bg.style.display='none'; bg.onclick=null; }
766
766
  }
767
767
  // P1-4:右栏「本轮运行」与状态条**同源**——renderRunPanel() 内部只走 runPanelView()→runStripView(),
@@ -1166,6 +1166,44 @@ let homeDir = '';
1166
1166
  let workingDirCache = '';
1167
1167
  let compactAvailable = false; // Web 端当前没有 /api/compact;为 true 时才显示压缩入口
1168
1168
  let runPanelOpen = false;
1169
+ // —— 右栏四标签(§3.82 第二步):轨迹 / 子Agent / 任务 / 仪表盘 ——
1170
+ // 标签切换**只控制 display**(railTabDisplay 纯函数),四个面板节点常驻 DOM、id 不变;
1171
+ // 不重写 updateTasksPanel / renderDashboard / openTraj / renderSubPanel 的数据逻辑。
1172
+ const RAIL_TABS = ['trace', 'sub', 'tasks', 'dash'];
1173
+ const RAIL_TAB_PANELS = { trace: '#tabTrace', sub: '#tabSub', tasks: '#tabTasks', dash: '#tabDash' };
1174
+ const RAIL_TAB_BUTTONS = { trace: '#rtTrace', sub: '#rtSub', tasks: '#rtTasks', dash: '#rtDash' };
1175
+ let activeRailTab = 'trace';
1176
+ function railTabDisplay(tab) {
1177
+ const active = RAIL_TABS.includes(tab) ? tab : 'trace';
1178
+ /** @type {Record<string, 'flex'|'none'>} */
1179
+ const out = {};
1180
+ for (const key of RAIL_TABS) out[key] = key === active ? 'flex' : 'none';
1181
+ return out;
1182
+ }
1183
+ function setRailTab(tab) {
1184
+ activeRailTab = RAIL_TABS.includes(tab) ? tab : 'trace';
1185
+ const disp = railTabDisplay(activeRailTab);
1186
+ for (const key of RAIL_TABS) {
1187
+ const panel = $(RAIL_TAB_PANELS[key]);
1188
+ if (panel) panel.style.display = disp[key];
1189
+ const btn = $(RAIL_TAB_BUTTONS[key]);
1190
+ if (btn) {
1191
+ btn.classList.toggle('on', key === activeRailTab);
1192
+ btn.setAttribute('aria-selected', key === activeRailTab ? 'true' : 'false');
1193
+ }
1194
+ }
1195
+ // 搬进标签页的两个旧面板:display 继续跟着标签走(updateTasksPanel / refreshCostBadge 靠它判可见)。
1196
+ const tp = $('#tasksPanel'); if (tp) tp.style.display = disp.tasks;
1197
+ const dp = $('#dashPanel'); if (dp) dp.style.display = disp.dash;
1198
+ if (activeRailTab === 'tasks' && typeof updateTasksPanel === 'function') updateTasksPanel();
1199
+ if (activeRailTab === 'dash' && typeof refreshCostBadge === 'function') refreshCostBadge();
1200
+ return activeRailTab;
1201
+ }
1202
+ /** 点标签/既有入口的统一出口:先开右栏,再切标签。 */
1203
+ function activateRailTab(tab) {
1204
+ setRunPanel(true);
1205
+ return setRailTab(tab);
1206
+ }
1169
1207
  let responsiveInitialized = false;
1170
1208
  // —— 轨迹收纳状态(§3.73):两个折叠区 + 展开过的轮次/工具/子代理(重渲染后保持展开) ——
1171
1209
  const traceFoldState = { trace: true, subs: true };
@@ -1214,8 +1252,7 @@ initTheme();
1214
1252
  // —— 右栏「本轮运行」:<1180 收起为抽屉;宽屏默认展开;不做任何新轮询 ——
1215
1253
  function syncRunPanel() {
1216
1254
  const el = $('#runPanel'); if (!el) return;
1217
- // 右边缘只留一个抽屉:子 Agent / 任务 / 省钱仪表盘打开时先让运行面板让位
1218
- // (syncPanelLayout 统一算,避免"两个面板各写一份互斥逻辑")。
1255
+ // §3.82:四个面板都在右栏标签页内,右边缘只剩右栏一个抽屉(显隐由 syncPanelLayout 统一算)。
1219
1256
  syncPanelLayout();
1220
1257
  const chip = $('#runChip'); if (chip) chip.setAttribute('aria-expanded', runPanelOpen ? 'true' : 'false');
1221
1258
  const col = $('#rpCollapse'); if (col) col.textContent = '收起';
@@ -1242,6 +1279,12 @@ function initResponsive() {
1242
1279
  initResponsive();
1243
1280
  // §3.73:绑定两个折叠区 + 末尾区块总开关(渲染入口在下面的轨迹收纳块)。
1244
1281
  initTraceUI();
1282
+ // §3.82:四个标签按钮(只切 display + 开右栏;默认轨迹)。
1283
+ for (const key of RAIL_TABS) {
1284
+ const btn = $(RAIL_TAB_BUTTONS[key]);
1285
+ if (btn) btn.onclick = () => activateRailTab(key);
1286
+ }
1287
+ setRailTab('trace');
1245
1288
 
1246
1289
  // —— 工具时间线 DOM:一张卡 + 可复用行(开始/完成不换节点) ——
1247
1290
  function toolStepNode(step) {
@@ -1678,6 +1721,7 @@ function focusTraceRound(round) {
1678
1721
  const body = $('#tdBody'); if (body) body.hidden = false;
1679
1722
  const toggle = $('#tdToggle'); if (toggle) toggle.setAttribute('aria-expanded', 'true');
1680
1723
  } else {
1724
+ setRailTab('trace');
1681
1725
  setRunPanel(true);
1682
1726
  }
1683
1727
  traceFoldState.trace = true;
@@ -1743,6 +1787,7 @@ function renderSessionSettings() {
1743
1787
  ? ((presetData.find((p) => p.name === psel.value) || {}).label || psel.value)
1744
1788
  : '无预设';
1745
1789
  runState.presetName = presetLabel;
1790
+ set('#ssPresetSrc', '当前:' + presetLabel + ' · 来源:你的选择 · 仅下一轮(工具白名单 / 参数)');
1746
1791
  set('#ssEffective', sessionEffectiveLine({ model: md.name, modelLabel: md.label, permission: mode, sourceLabel: permissionSourceLabel(source), preset: presetLabel }));
1747
1792
  set('#chipModel', md.label || md.name || '模型');
1748
1793
  set('#chipPerm', permissionModeLabel(mode));
@@ -2070,38 +2115,29 @@ function openTraj(msg){
2070
2115
  // 点击标题定位到聊天中的对应工具卡
2071
2116
  list.appendChild(div);
2072
2117
  }
2073
- $('#trajPanel').style.display='flex';
2074
- $('#tasksPanel').style.display='none'; // 仅任务面板(右侧)与轨迹互斥;子代理面板可与轨迹同显
2118
+ // §3.82:轨迹入口不再单独开旧浮层——切到右栏「轨迹」标签(旧面板与其 #tjList 仍在 DOM 里)。
2075
2119
  $('#trajRailBtn').classList.add('on');
2076
- syncPanelLayout();
2120
+ activateRailTab('trace');
2077
2121
  }
2078
- // 面板布局同步(2026-09-02):轨迹/子代理/任务面板打开时以 body class 推开内容区,
2079
- // 聊天文本与附件行不再被固定面板遮挡;面板宽度随屏宽自动收窄(CSS 媒体查询)。
2122
+ // 面板布局同步:§3.82 起轨迹/子代理/任务/仪表盘都在右栏标签页内,
2123
+ // 右栏是否显示只由 runPanelOpen 决定;旧的四条 body 推开 class 不再使用(CSS 规则保留不动)。
2080
2124
  function syncPanelLayout(){
2081
- const open=(id)=>document.getElementById(id)?.style.display==='flex';
2082
- document.body.classList.toggle('traj-open', open('trajPanel'));
2083
- document.body.classList.toggle('sub-open', open('subPanel'));
2084
- document.body.classList.toggle('tasks-open', open('tasksPanel'));
2085
- document.body.classList.toggle('dash-open', open('dashPanel'));
2086
- // P1-4:右边缘只留一个抽屉——既有右侧面板(子代理/任务/省钱仪表盘)打开时运行面板让位;
2087
- // 它们关闭后 syncPanelLayout 再被调用,运行面板按 runPanelOpen 恢复。
2088
- const otherRight=open('subPanel')||open('tasksPanel')||open('dashPanel');
2125
+ document.body.classList.remove('traj-open','sub-open','tasks-open','dash-open');
2089
2126
  const narrow=layoutClassesFor(window.innerWidth||0).narrowRun;
2090
- const showRun=Boolean(runPanelOpen)&&!otherRight;
2127
+ const showRun=Boolean(runPanelOpen);
2091
2128
  const rp=$('#runPanel'); if(rp) rp.hidden=!showRun;
2092
2129
  document.body.classList.toggle('run-open', showRun&&!narrow);
2093
2130
  document.body.classList.toggle('run-drawer', showRun&&narrow);
2094
2131
  // §3.73:窄屏右栏收起时,轨迹/子Agent 在对话流末尾显示;右栏(抽屉)一开就让位。
2095
2132
  syncTraceDock();
2096
2133
  }
2097
- $('#tjClose').onclick=()=>{ $('#trajPanel').style.display='none'; $('#trajRailBtn').classList.remove('on'); syncPanelLayout(); };
2134
+ $('#tjClose').onclick=()=>{ setRunPanel(false); $('#trajRailBtn').classList.remove('on'); };
2098
2135
  $('#trajRailBtn').onclick=()=>{
2099
- const p=$('#trajPanel');
2100
- const willShow=p.style.display==='none';
2101
- if(willShow){ const m=lastMsgWithTraj(); if(!m){ uiAlert('本轮还没有可展示的轨迹(先发起一个任务)'); return; } openTraj(m); }
2102
- else p.style.display='none';
2103
- $('#trajRailBtn').classList.toggle('on', willShow);
2104
- syncPanelLayout();
2136
+ // §3.82:轨迹入口 = 打开右栏并切到「轨迹」标签(没有轨迹时也切过去,显示空态)。
2137
+ const m=lastMsgWithTraj();
2138
+ if(m && m._traj && m._traj.length){ openTraj(m); return; }
2139
+ $('#trajRailBtn').classList.add('on');
2140
+ activateRailTab('trace');
2105
2141
  };
2106
2142
  function lastMsgWithTraj(){
2107
2143
  const msgs=[...document.querySelectorAll('#chat .msg-ai')].reverse();
@@ -2128,15 +2164,13 @@ function renderSubPanel(){
2128
2164
  if(!count) list.innerHTML='<div class="empty" style="color:var(--faint);font-size:12px;padding:10px">本会话还没有子 Agent(task 工具会派生子 Agent)</div>';
2129
2165
  }
2130
2166
  const toggleSubPanel=()=>{
2131
- const p=$('#subPanel');
2132
- const willShow=p.style.display==='none';
2133
- p.style.display=willShow?'flex':'none';
2134
- $('#subRailBtn').classList.toggle('on', willShow);
2135
- if(willShow){ $('#tasksPanel').style.display='none'; renderSubPanel(); } // 轨迹(左侧)可与子代理(右侧)同显
2136
- syncPanelLayout();
2167
+ // §3.82:子Agent 入口 = 打开右栏并切到「子Agent」标签(不再单独开旧浮层)。
2168
+ renderSubPanel();
2169
+ activateRailTab('sub');
2170
+ $('#subRailBtn').classList.add('on');
2137
2171
  };
2138
2172
  $('#subRailBtn').onclick=toggleSubPanel;
2139
- $('#sbClose').onclick=()=>{ $('#subPanel').style.display='none'; $('#subRailBtn').classList.remove('on'); syncPanelLayout(); };
2173
+ $('#sbClose').onclick=()=>{ setRunPanel(false); $('#subRailBtn').classList.remove('on'); };
2140
2174
  // —— 任务面板(多会话并行) ——
2141
2175
  function setBtn(){
2142
2176
  // §138:停止点击后立刻有可见反馈(「停止中…」+ 禁用防连点),回合真正收尾后由 finally 复位。
@@ -2203,8 +2237,12 @@ async function refreshCostBadge(){
2203
2237
  if($('#dashPanel').style.display==='flex') renderDashboard(j);
2204
2238
  }
2205
2239
  refreshCostBadge(); setInterval(refreshCostBadge, 15000);
2206
- $('#costBadge').onclick=()=>{ const p=$('#dashPanel'); const show=p.style.display==='none'; p.style.display=show?'flex':'none'; if(show){ $('#subPanel').style.display='none'; $('#trajPanel').style.display='none'; $('#tasksPanel').style.display='none'; $('#subRailBtn').classList.remove('on'); $('#trajRailBtn').classList.remove('on'); refreshCostBadge(); } syncPanelLayout(); };
2207
- $('#dashClose').onclick=()=>{ $('#dashPanel').style.display='none'; syncPanelLayout(); };
2240
+ // §3.82:顶栏费用徽标 = 打开右栏「仪表盘」标签(原有 📊 内容仍在 #dashPanel 里;关闭用 ✕/收起)。
2241
+ $('#costBadge').onclick=()=>{
2242
+ $('#trajRailBtn').classList.remove('on'); $('#subRailBtn').classList.remove('on');
2243
+ activateRailTab('dash');
2244
+ };
2245
+ $('#dashClose').onclick=()=>{ setRunPanel(false); };
2208
2246
  // 底部状态栏:轮次/步数/LLM 与工具时长/首 token 平均/吞吐/缓存命中/输入输出 tokens
2209
2247
  async function refreshStatusBar(){
2210
2248
  const r=await fetch('/api/cache-stats',{cache:'no-store'}).catch(()=>null); if(!r) return;
@@ -2216,9 +2254,20 @@ async function refreshStatusBar(){
2216
2254
  $('#statusBar').textContent = s.turns+' 轮 · '+s.steps+' 步 | LLM '+fmtDur(s.llmMs)+' · 工具调用 '+fmtDur(s.toolMs)+' | 首 token 平均 '+ft+' · '+tps+' | 缓存命中 '+rate+' | 输入 '+fmtTok(s.prompt)+' tok · 输出 '+fmtTok(s.completion)+' tok';
2217
2255
  }
2218
2256
  refreshStatusBar(); setInterval(refreshStatusBar, 15000);
2219
- $('#tasksBtn').onclick=()=>{ const p=$('#tasksPanel'); p.style.display = p.style.display==='none'?'flex':'none'; if(p.style.display==='flex'){ $('#subPanel').style.display='none'; $('#trajPanel').style.display='none'; $('#trajRailBtn').classList.remove('on'); $('#subRailBtn').classList.remove('on'); updateTasksPanel(); } syncPanelLayout(); };
2220
- $('#tpClose').onclick=()=>{ $('#tasksPanel').style.display='none'; syncPanelLayout(); };
2257
+ // §3.82:顶栏「任务」= 打开右栏「任务」标签(右栏收起时也会展开;关闭用 ✕/收起)。
2258
+ $('#tasksBtn').onclick=()=>{
2259
+ $('#trajRailBtn').classList.remove('on'); $('#subRailBtn').classList.remove('on');
2260
+ activateRailTab('tasks');
2261
+ };
2262
+ $('#tpClose').onclick=()=>{ setRunPanel(false); };
2221
2263
 
2264
+ // v0.6.21(§3.81):顶栏「我在哪」——工作空间 › 当前会话。数据只取既有 currentWorkspaceName /
2265
+ // sessionsCache / currentSession,不新增后端字段。会话列表或工作空间刷新后同步更新。
2266
+ function renderCrumb(){
2267
+ const w=$('#crumbWs'); if(w) w.textContent=currentWorkspaceName||'工作空间';
2268
+ const cur=sessionsCache.find((s)=>s&&s.file===currentSession);
2269
+ const c=$('#crumbSession'); if(c) c.textContent=cur?(cur.label||cur.file):(currentSession?String(currentSession).replace(/\.jsonl$/,''):'新对话');
2270
+ }
2222
2271
  async function refreshSessions(q){
2223
2272
  const u=q?'/api/sessions?q='+encodeURIComponent(q):'/api/sessions';
2224
2273
  const r=await fetch(u).catch(()=>null); if(!r)return;
@@ -2227,6 +2276,7 @@ async function refreshSessions(q){
2227
2276
  const sel=$('#sessions'); sel.innerHTML='<option value="">历史会话</option>';
2228
2277
  for(const s of sessionsCache){ const o=document.createElement('option'); o.value=s.file; o.textContent=s.label; sel.appendChild(o); }
2229
2278
  if(currentSession&&!q) sel.value=currentSession;
2279
+ renderCrumb();
2230
2280
  }
2231
2281
  let searchTimer=null;
2232
2282
  $('#sessionSearch').addEventListener('input',e=>{ clearTimeout(searchTimer); searchTimer=setTimeout(()=>refreshSessions(e.target.value.trim()),300); });
@@ -2381,6 +2431,7 @@ async function refreshWsSel(){
2381
2431
  sel.appendChild(add);
2382
2432
  const mgr=document.createElement('option'); mgr.value='__manage__'; mgr.textContent='管理(重命名/删除/改目录)…';
2383
2433
  sel.appendChild(mgr);
2434
+ renderCrumb();
2384
2435
  }
2385
2436
  /** 命令面板与顶栏下拉共用的工作空间切换(携带当前会话,语义与旧路径一致)。 */
2386
2437
  async function applyWorkspaceSwitch(name){
@@ -2799,13 +2850,13 @@ function finalizeCurrentSession(){
2799
2850
  if(!currentSession) return;
2800
2851
  fetch('/api/session-finalize',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({file:currentSession})}).catch(()=>{});
2801
2852
  }
2802
- async function loadSession(file){ if(currentSession && currentSession!==file) finalizeCurrentSession(); currentSession=file; chatEl.innerHTML=''; resetTraceState(); const r=await fetch('/api/session?file='+encodeURIComponent(file)); const j=await r.json(); for(const m of j.messages||[]){ if(typeof m.content==='string'&&m.content.startsWith('(系统提示)')) continue; if(m.role==='user') addUser(m.content); else { const el=newAiMsg(); aiContent(el).innerHTML=renderMarkdown(m.content); } } if(j.workspace){ const sel=$('#wsSel'); const has=[...sel.options].some(o=>o.value===j.workspace); if(has){ sel.value=j.workspace; } else { refreshWsSel(); } renderBanner({text:'↩ 已回到该会话的工作空间:'+j.workspace}); } if(j.taskState&&(j.taskState.status==='cap'||j.taskState.status==='interrupted')){ renderBanner({text:'⚠ 该会话有未完成任务(步数上限中断)——直接发送消息即可从断点续跑,已完成文件不会重复做。', warn:true}); } scrollBottom(); renderEmptyState(); }
2853
+ async function loadSession(file){ if(currentSession && currentSession!==file) finalizeCurrentSession(); currentSession=file; chatEl.innerHTML=''; resetTraceState(); const r=await fetch('/api/session?file='+encodeURIComponent(file)); const j=await r.json(); for(const m of j.messages||[]){ if(typeof m.content==='string'&&m.content.startsWith('(系统提示)')) continue; if(m.role==='user') addUser(m.content); else { const el=newAiMsg(); aiContent(el).innerHTML=renderMarkdown(m.content); } } if(j.workspace){ const sel=$('#wsSel'); const has=[...sel.options].some(o=>o.value===j.workspace); if(has){ sel.value=j.workspace; } else { refreshWsSel(); } renderBanner({text:'↩ 已回到该会话的工作空间:'+j.workspace}); } if(j.taskState&&(j.taskState.status==='cap'||j.taskState.status==='interrupted')){ renderBanner({text:'⚠ 该会话有未完成任务(步数上限中断)——直接发送消息即可从断点续跑,已完成文件不会重复做。', warn:true}); } scrollBottom(); renderEmptyState(); renderCrumb(); }
2803
2854
 
2804
2855
  // §138:主按钮在生成态=停止,必须走**同一个**停止出口(stopTurnFromButton → stopTurn → POST /api/abort)
2805
2856
  sendBtn.onclick=()=>{ if(generating){ stopTurnFromButton(); } else { send(); } };
2806
2857
  input.addEventListener('keydown',e=>{ if(e.key==='Enter'&&!e.shiftKey){ e.preventDefault(); send(); } });
2807
2858
  input.addEventListener('input',()=>{ input.style.height='44px'; input.style.height=Math.min(input.scrollHeight,200)+'px'; });
2808
- $('#newChat').onclick=()=>{ finalizeCurrentSession(); currentSession=null; chatEl.innerHTML=''; resetTraceState(); renderEmptyState(); refreshSessions(); };
2859
+ $('#newChat').onclick=()=>{ finalizeCurrentSession(); currentSession=null; chatEl.innerHTML=''; resetTraceState(); renderEmptyState(); renderCrumb(); refreshSessions(); };
2809
2860
  $('#sessMgrBtn').onclick=openSessModal;
2810
2861
  $('#sessClose').onclick=()=>{ $('#sessModal').style.display='none'; };
2811
2862
  async function openSessModal(){
@@ -2821,6 +2872,6 @@ async function openSessModal(){
2821
2872
  div.appendChild(rn); div.appendChild(dl); list.appendChild(div);
2822
2873
  }
2823
2874
  }
2824
- $('#sessions').onchange=e=>{ if(e.target.value){ loadSession(e.target.value); } else { finalizeCurrentSession(); currentSession=null; chatEl.innerHTML=''; resetTraceState(); renderEmptyState(); } };
2875
+ $('#sessions').onchange=e=>{ if(e.target.value){ loadSession(e.target.value); } else { finalizeCurrentSession(); currentSession=null; chatEl.innerHTML=''; resetTraceState(); renderEmptyState(); renderCrumb(); } };
2825
2876
  if('serviceWorker' in navigator){ navigator.serviceWorker.register('/sw.js').catch(()=>{}); }
2826
2877
  init();
@@ -65,14 +65,35 @@ header{min-height:52px;display:flex;align-items:center;gap:10px;padding:8px 12px
65
65
  header .logo{font-weight:700;font-size:16px;color:var(--accent);letter-spacing:.5px;flex:none}
66
66
  header .logo span{color:var(--text)}
67
67
  header .badge{font-size:12px;color:var(--dim);background:var(--bg3);border:1px solid var(--border);padding:2px 10px;border-radius:20px;flex:none;overflow:hidden;text-overflow:ellipsis;max-width:180px}
68
+ /* §3.83:crumb 是左半区唯一会随会话名变长的项;限到 120px,避免会话名一长就把右半区的
69
+ 「历史会话」再次挤出 1000px 视口(完整标题仍在 title 里,内容不丢)。 */
70
+ #crumb{max-width:120px}
68
71
  header button{white-space:nowrap;flex:none}
69
72
  header select{flex:none}
73
+ /* §3.83 顶栏 1000px 不裁切:右半区控件(费用 / 搜索会话 / 历史会话 / 任务 / 会话管理 / 新对话)
74
+ 收进一个可横向滚动的容器(min-width:0 才允许收缩);宽屏由容器内的 .spacer 把控件推到最右,
75
+ 窄屏从「费用 + 搜索会话 + 历史会话」开始露出,其余控件在同一容器里横向滚动触达——
76
+ 不再整条顶栏溢出到屏幕外。 */
77
+ header .hdr-right{display:flex;align-items:center;gap:8px;flex:1 1 auto;min-width:0;overflow-x:auto;overflow-y:hidden;scrollbar-width:thin;scrollbar-color:var(--border) transparent}
78
+ header .hdr-right::-webkit-scrollbar{height:4px}
79
+ header .hdr-right::-webkit-scrollbar-thumb{background:var(--border);border:0;border-radius:2px}
80
+ header .hdr-right::-webkit-scrollbar-track{background:transparent}
70
81
  #sessionSearch{flex:1 1 auto;min-width:70px;max-width:220px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:6px 12px;font-size:13px}
71
82
  #modelSel{max-width:170px;min-width:0}
72
83
  #presetSel{max-width:140px;min-width:0}
73
84
  #permSel{max-width:150px;min-width:0}
74
85
  #sessions{max-width:150px;min-width:0}
75
- #wsSel{max-width:130px;min-width:64px}
86
+ #wsSel{max-width:110px;min-width:64px}
87
+ /* §3.83 1000px:诊断徽标(≤900 本就隐藏)让位给「费用 + 搜索 + 历史会话」;顶栏 gap 10→6,
88
+ 右半区容器因此拿到约 322px(费用 122 + 搜索 70 + 历史会话 98),三者完整可见。 */
89
+ @media (max-width:1100px){
90
+ header{gap:6px;padding:8px}
91
+ header .hdr-right{gap:8px}
92
+ #envBadge{display:none!important}
93
+ #wsSel{max-width:120px}
94
+ #sessionSearch{min-width:70px;max-width:120px}
95
+ #sessions{max-width:110px}
96
+ }
76
97
  @media (max-width:860px){main{padding:14px 0 8px}#chat{padding:0 10px}#envBadge{display:none!important}#sessionSearch{max-width:110px}#modelSel{max-width:110px}#permSel{max-width:120px}#sessions{max-width:110px}#wsSel{max-width:96px}header{gap:6px;padding:8px 8px}header .logo span{display:none}}
77
98
  #sessionSearch:focus{outline:none;border-color:var(--accent2)}
78
99
  #tasksPanel{position:fixed;right:0;top:52px;bottom:0;width:280px;background:var(--bg2);border-left:1px solid var(--border);z-index:40;display:flex;flex-direction:column}
@@ -450,17 +471,19 @@ body.run-open #scrollBottomBtn{right:326px}
450
471
  body.run-drawer #runPanel{display:flex;top:52px;width:300px;max-width:92vw;z-index:60;box-shadow:-12px 0 32px rgba(0,0,0,.4)}
451
472
  }
452
473
 
453
- /* —— 会话设置弹层(收编模型 / 预设 / 权限三件套)—— */
454
- #sessSheet{position:fixed;right:18px;bottom:110px;width:min(410px,calc(100vw - 24px));max-height:min(72vh,560px);background:var(--panel);border:1px solid var(--border);border-radius:14px;box-shadow:0 16px 48px rgba(0,0,0,.5);z-index:70;display:flex;flex-direction:column;overflow:hidden}
474
+ /* —— 会话设置弹层(收编模型 / 预设 / 权限三件套)——
475
+ §3.83:间距按设计稿 .popover 对齐(head/body 11px 13px、field label 5px、
476
+ effective 13px/11px + 8px 10px、foot 9px 13px);圆角保持设计稿 14px(sheet)/8px(effective)。 */
477
+ #sessSheet{position:fixed;right:18px;bottom:110px;width:min(400px,calc(100vw - 24px));max-height:min(72vh,560px);background:var(--panel);border:1px solid var(--border);border-radius:14px;box-shadow:0 16px 48px rgba(0,0,0,.5);z-index:70;display:flex;flex-direction:column;overflow:hidden}
455
478
  body.run-open #sessSheet{right:318px}
456
479
  @media (max-width:1180px){body.run-open #sessSheet{right:18px}}
457
- .ss-head{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid var(--border);font-size:13px;font-weight:600;color:var(--text);flex:none}
480
+ .ss-head{display:flex;align-items:center;gap:8px;padding:11px 13px;border-bottom:1px solid var(--border);font-size:13px;font-weight:600;color:var(--text);flex:none}
458
481
  .ss-head .ss-sub{font-size:11px;color:var(--faint);font-weight:400}
459
482
  .ss-head button{margin-left:auto;padding:1px 8px;font-size:11px}
460
- .ss-body{padding:10px 12px;overflow-y:auto;min-height:0}
483
+ .ss-body{padding:11px 13px;overflow-y:auto;min-height:0}
461
484
  .ss-field{margin-bottom:12px}
462
485
  .ss-field:last-child{margin-bottom:0}
463
- .ss-field>label{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--dim);margin-bottom:4px}
486
+ .ss-field>label{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--dim);margin-bottom:5px}
464
487
  .ss-field .ss-scope{margin-left:auto;font-size:10px;color:var(--faint);border:1px solid var(--border);border-radius:999px;padding:0 6px;line-height:16px}
465
488
  .ss-field select{width:100%;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:6px 8px;font-size:13px}
466
489
  .ss-src{font-size:11px;color:var(--faint);margin-top:4px;line-height:1.55;word-break:break-word}
@@ -469,9 +492,9 @@ body.run-open #sessSheet{right:318px}
469
492
  .ss-seg{display:flex;gap:4px;background:var(--bg3);border:1px solid var(--border);border-radius:9px;padding:3px}
470
493
  .ss-seg button{flex:1;height:26px;border:0;border-radius:6px;background:transparent;color:var(--dim);font-size:12px}
471
494
  .ss-seg button.on{background:var(--panel);color:var(--text);font-weight:600;box-shadow:0 1px 3px rgba(0,0,0,.18)}
472
- .ss-effective{margin:0 12px 8px;padding:7px 9px;border:1px dashed var(--border);border-radius:8px;font-size:11.5px;color:var(--dim);line-height:1.65}
495
+ .ss-effective{margin:0 13px 11px;padding:8px 10px;border:1px dashed var(--border);border-radius:8px;font-size:11.5px;color:var(--dim);line-height:1.65}
473
496
  .ss-effective b{color:var(--text)}
474
- .ss-foot{display:flex;align-items:center;gap:8px;padding:8px 12px;border-top:1px solid var(--border)}
497
+ .ss-foot{display:flex;align-items:center;gap:8px;padding:9px 13px;border-top:1px solid var(--border)}
475
498
  .ss-foot .grow{flex:1;font-size:11px;color:var(--faint)}
476
499
  #sessSettingsBtn{max-width:230px;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
477
500
  #sessSettingsBtn b{overflow:hidden;text-overflow:ellipsis}
@@ -562,63 +585,82 @@ body.run-open #sessSheet{right:318px}
562
585
  .dr .p{color:var(--accent2);font-variant-numeric:tabular-nums}
563
586
  .dr .c{color:var(--faint);font-variant-numeric:tabular-nums}
564
587
  .badge-btn:hover{border-color:var(--accent2);color:var(--accent2)}
588
+ /* —— §3.82 右栏四标签(轨迹 / 子Agent / 任务 / 仪表盘)——
589
+ 只新增标签栏/标签页容器样式;#runPanel 宽度、body.run-open 推开量、顶栏与网格均不动。
590
+ 四个面板本体搬进标签页后不再 fixed(覆盖既有 #tasksPanel/#dashPanel 的定位,宽度跟随右栏)。 */
591
+ .rp-top{flex:none;padding:10px 12px 0}
592
+ .rp-top .rp-sec{margin-bottom:8px}
593
+ /* §3.83:四个标签统一成设计稿 `.seg` 分段控件观感(外框 bg3 + padding 3 / 圆角 9,
594
+ 内按钮 26px 高 / 圆角 6;选中态 panel 底 + text 字 + 600 字重)。只改样式,切换逻辑不动。 */
595
+ .rp-tabs{display:flex;gap:4px;padding:3px;margin:0 12px 8px;flex:none;background:var(--bg3);border:1px solid var(--border);border-radius:9px}
596
+ .rp-tabs .seg{flex:1;min-width:0;height:26px;padding:0 4px;border:0;border-radius:6px;background:transparent;color:var(--dim);font-size:12px;white-space:nowrap;cursor:pointer}
597
+ .rp-tabs .seg:hover{color:var(--text)}
598
+ .rp-tabs .seg.on{background:var(--panel);color:var(--text);font-weight:600;box-shadow:0 1px 3px rgba(0,0,0,.18)}
599
+ .rp-tabbody{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}
600
+ .rp-tab{flex:1;min-height:0;display:flex;flex-direction:column;overflow-y:auto;padding:10px 12px}
601
+ .rp-tab.rp-tab-flush{padding:0;overflow:hidden}
602
+ #runPanel #tasksPanel,#runPanel #dashPanel{position:static;right:auto;top:auto;bottom:auto;width:auto;max-width:none;height:auto;border-left:0;z-index:auto;box-shadow:none;background:transparent;flex:1;min-height:0;overflow:hidden}
603
+
604
+ /* —— §3.83 对比度:承载信息的次要文本从 --faint 提到 --dim ——
605
+ 只改下列选择器的 color,**不动** :root 里的 --faint/--dim 取值(避免全局漂移);
606
+ WCAG 对比度(暗色 #81858C→#ADB2B8):bg 4.92→8.54、bg2 4.64→8.06、
607
+ panel 3.76→6.53、bg3 3.26→5.67。覆盖:消息元信息/用量、提示条、任务与子Agent状态、
608
+ 轨迹轮次与计数、工具耗时与输出计数、来源/作用域胶囊、rp-foot 说明、空态说明、底部累计状态。 */
609
+ .msg-meta,
610
+ .usage,
611
+ .banner:not(.banner-warn),
612
+ #hint,
613
+ #statusBar,
614
+ .sb-item .sb-meta,
615
+ .tp-item .tp-meta,
616
+ .tl-row .tl-dur,
617
+ .tl-out-meta,
618
+ .tl-more,
619
+ .tr-round-no,
620
+ .tr-sub-meta,
621
+ .tr-sub-stats,
622
+ .tr-sub-wait,
623
+ .tr-sub-state,
624
+ .rp-fold-meta,
625
+ .td-fold-meta,
626
+ .td-meta,
627
+ .rp-kv .rp-src,
628
+ .rp-foot .grow,
629
+ .ss-src,
630
+ .ss-field .ss-scope,
631
+ .dash-head .dash-sub,
632
+ .dr .c,
633
+ .tr-empty,
634
+ #emptyState .es-lead,
635
+ .empty-step .es-main span,
636
+ .empty-step .es-state{color:var(--dim)}
565
637
  </style>
566
638
  </head>
567
639
  <body>
568
640
  <header>
569
641
  <div class="logo">MingDao <span>Harness</span></div>
642
+ <!-- v0.6.21 小步重排(§3.81):顶栏只保留「我在哪」的当前位置;模型/预设/权限只在 #sessSheet 里改。 -->
643
+ <span id="crumb" class="badge" title="当前工作空间 › 当前会话"><span id="crumbWs">工作空间</span><span aria-hidden="true"> › </span><b id="crumbSession">新对话</b></span>
570
644
  <span id="cfgMsg" class="badge" style="display:none"></span>
571
645
  <button id="paletteBtn" title="搜索或运行命令(⌘K / Ctrl+K)" aria-haspopup="dialog">⌘K <span class="palette-label">搜索或运行</span></button>
572
646
  <button id="cfgBtn" title="设置">⚙</button>
573
647
  <button id="runChip" title="本轮运行面板(窄屏以抽屉展开)" aria-expanded="false">运行</button>
574
648
  <button id="themeBtn" title="切换明暗主题(跟随系统时由系统偏好决定)" aria-label="切换明暗主题">🌙</button>
575
649
  <select id="wsSel" title="工作空间(切换 / 新建,目录自动创建)"></select>
576
- <span class="badge" id="envBadge"></span><span class="badge badge-btn" id="costBadge" title="省钱仪表盘(点击展开)" style="cursor:pointer">📊 —</span>
577
- <div class="spacer"></div>
578
- <input id="sessionSearch" placeholder="搜索会话…" title="关键词检索历史会话">
579
- <select id="sessions" title="历史会话"><option value="">历史会话</option></select>
580
- <button id="tasksBtn" title="任务面板">任务</button><button id="sessMgrBtn" title="管理会话(重命名/删除)">✎</button>
581
- <button id="newChat">新对话</button>
650
+ <span class="badge" id="envBadge"></span>
651
+ <div class="hdr-right">
652
+ <div class="spacer"></div>
653
+ <span class="badge badge-btn" id="costBadge" title="省钱仪表盘(点击展开)" style="cursor:pointer">📊 —</span>
654
+ <input id="sessionSearch" placeholder="搜索会话…" title="关键词检索历史会话">
655
+ <select id="sessions" title="历史会话"><option value="">历史会话</option></select>
656
+ <button id="tasksBtn" title="任务面板">任务</button><button id="sessMgrBtn" title="管理会话(重命名/删除)">✎</button>
657
+ <button id="newChat">新对话</button>
658
+ </div>
582
659
  </header>
583
660
  <div id="trajRail">
584
661
  <button class="rail-btn" id="trajRailBtn" title="轨迹(点击查看详情)">🧭</button>
585
662
  <button class="rail-btn" id="subRailBtn" title="子 Agent">🤖<span class="rail-badge" id="subBadge" style="display:none"></span></button>
586
663
  </div>
587
- <aside id="subPanel" style="display:none">
588
- <div class="sb-head">🤖 子 Agent<span id="sbCount" style="font-size:11.5px;color:var(--faint);font-weight:400"></span><button id="sbClose">关闭</button></div>
589
- <div id="sbList"></div>
590
- </aside>
591
- <aside id="trajPanel" style="display:none">
592
- <div class="tj-head">🧭 任务轨迹<span id="tjSummary" style="font-size:11.5px;color:var(--faint);font-weight:400"></span><button id="tjClose">关闭</button></div>
593
- <div id="tjList"></div>
594
- </aside>
595
- <aside id="tasksPanel" style="display:none">
596
- <div class="tp-head">任务面板 <span id="tpCount"></span><button id="tpClose" style="float:right">✕</button></div>
597
- <div id="tpList"></div>
598
- </aside>
599
- <aside id="dashPanel" style="display:none">
600
- <div class="dash-head">📊 省钱仪表盘 <span class="dash-sub">费用 · 命中 · 节省</span><button id="dashClose">✕</button></div>
601
- <div class="dash-body">
602
- <div class="dash-kpi">
603
- <div class="kpi"><span class="kpi-l">今日费用</span><span class="kpi-v" id="kpiToday">—</span></div>
604
- <div class="kpi"><span class="kpi-l">缓存命中</span><span class="kpi-v" id="kpiRate">—</span></div>
605
- <div class="kpi"><span class="kpi-l">已节省</span><span class="kpi-v" style="color:var(--accent)" id="kpiSaved">—</span></div>
606
- <div class="kpi"><span class="kpi-l">轮次/步数</span><span class="kpi-v" id="kpiTurns">—</span></div>
607
- </div>
608
- <div class="dash-gauge-wrap">
609
- <svg id="rateGauge" viewBox="0 0 120 120" width="120" height="120"></svg>
610
- <div class="gauge-label"><b id="rateGaugeNum">—</b><span>命中率</span></div>
611
- </div>
612
- <div class="dash-sec">近 14 天费用趋势(¥)</div>
613
- <div id="trendChart" class="dash-chart"></div>
614
- <div class="dash-sec">模型费用 Top</div>
615
- <div id="dashModels" class="dash-bars"></div>
616
- <div class="dash-sec">工具耗时 Top</div>
617
- <div id="dashTools" class="dash-bars"></div>
618
- <div class="dash-sec">最近缓存命中</div>
619
- <div id="dashRecent" class="dash-recent"></div>
620
- </div>
621
- </aside>
622
664
  <main>
623
665
  <!-- P1-6 空态三步引导(v0.6.19 §3.70):无会话 / 无消息时显示;示例任务只填入输入框,不自动发送。 -->
624
666
  <div id="emptyState" hidden>
@@ -690,7 +732,8 @@ body.run-open #sessSheet{right:318px}
690
732
  ≥1180 默认展开并推开正文;<1180 收起为顶栏「运行」chip,点开是覆盖式抽屉。无新增轮询。 -->
691
733
  <aside id="runPanel" aria-label="本轮运行" hidden>
692
734
  <div class="rp-head"><span class="rp-live" aria-hidden="true"></span><span>本轮运行</span><span class="rp-sub">指标口径:本轮(非累计)</span><button id="rpCollapse" type="button" title="收起右栏">收起</button></div>
693
- <div class="rp-body">
735
+ <!-- §3.82:状态条(阶段/首帧/吞吐/工具数/上下文/费用)留在顶部,不随标签切换。 -->
736
+ <div class="rp-top">
694
737
  <section class="rp-sec">
695
738
  <div class="rp-sec-title">会话配置<span class="line"></span></div>
696
739
  <dl class="rp-kv">
@@ -712,15 +755,71 @@ body.run-open #sessSheet{right:318px}
712
755
  <div class="rp-metric wide"><span class="l">本轮费用(估算)</span><span class="v" data-rk="cost" id="rpCost">—</span></div>
713
756
  </div>
714
757
  </section>
715
- <!-- §3.73:轨迹 / 子Agent 两块可折叠区。对话流每轮只留一行摘要,细节都收在这里;
716
- 统计与摘要同源(renderTracePanels → 同一 traceRoundForMsg 视图)。 -->
717
- <section class="rp-sec" id="rpTraceSec">
718
- <button class="rp-sec-title rp-fold-head" type="button" data-fold="trace" aria-controls="rpTraceFoldBody" aria-expanded="true">轨迹<span class="rp-fold-meta" id="rpTraceMeta">—</span><span class="line"></span><span class="rp-fold-tri" aria-hidden="true"></span></button>
719
- <div id="rpTraceFoldBody"><div id="rpTraceList" class="tr-list"></div></div>
758
+ </div>
759
+ <!-- §3.82 第二步:四个标签。只切 display(railTabDisplay),节点不销毁、id 不变。 -->
760
+ <div class="rp-tabs" role="tablist" aria-label="右栏标签">
761
+ <button id="rtTrace" type="button" role="tab" data-tab="trace" aria-selected="true" aria-controls="tabTrace" class="seg on">轨迹</button>
762
+ <button id="rtSub" type="button" role="tab" data-tab="sub" aria-selected="false" aria-controls="tabSub" class="seg">子Agent</button>
763
+ <button id="rtTasks" type="button" role="tab" data-tab="tasks" aria-selected="false" aria-controls="tabTasks" class="seg">任务</button>
764
+ <button id="rtDash" type="button" role="tab" data-tab="dash" aria-selected="false" aria-controls="tabDash" class="seg">仪表盘</button>
765
+ </div>
766
+ <div class="rp-tabbody">
767
+ <section id="tabTrace" class="rp-tab" role="tabpanel" aria-labelledby="rtTrace" data-tab="trace">
768
+ <!-- §3.73:轨迹折叠区(renderTracePanels 写入 #rpTraceList,不在这里重写)。 -->
769
+ <section class="rp-sec" id="rpTraceSec">
770
+ <button class="rp-sec-title rp-fold-head" type="button" data-fold="trace" aria-controls="rpTraceFoldBody" aria-expanded="true">轨迹<span class="rp-fold-meta" id="rpTraceMeta">—</span><span class="line"></span><span class="rp-fold-tri" aria-hidden="true"></span></button>
771
+ <div id="rpTraceFoldBody"><div id="rpTraceList" class="tr-list"></div></div>
772
+ </section>
773
+ <!-- 旧轨迹面板随标签搬入(id/子节点原样保留,openTraj 仍写 #tjList);默认收起,避免与上面的轮次列表重复。 -->
774
+ <aside id="trajPanel" style="display:none">
775
+ <div class="tj-head">🧭 任务轨迹<span id="tjSummary" style="font-size:11.5px;color:var(--faint);font-weight:400"></span><button id="tjClose">关闭</button></div>
776
+ <div id="tjList"></div>
777
+ </aside>
778
+ </section>
779
+ <section id="tabSub" class="rp-tab" role="tabpanel" aria-labelledby="rtSub" data-tab="sub" style="display:none">
780
+ <!-- §3.73:子Agent 折叠区(renderTracePanels 写入 #rpSubList)。 -->
781
+ <section class="rp-sec" id="rpSubSec">
782
+ <button class="rp-sec-title rp-fold-head" type="button" data-fold="subs" aria-controls="rpSubFoldBody" aria-expanded="true">子Agent<span class="rp-fold-meta" id="rpSubMeta">—</span><span class="line"></span><span class="rp-fold-tri" aria-hidden="true"></span></button>
783
+ <div id="rpSubFoldBody"><div id="rpSubList" class="tr-list"></div></div>
784
+ </section>
785
+ <!-- 旧子Agent 面板随标签搬入(id/子节点原样保留);默认收起。 -->
786
+ <aside id="subPanel" style="display:none">
787
+ <div class="sb-head">🤖 子 Agent<span id="sbCount" style="font-size:11.5px;color:var(--faint);font-weight:400"></span><button id="sbClose">关闭</button></div>
788
+ <div id="sbList"></div>
789
+ </aside>
790
+ </section>
791
+ <section id="tabTasks" class="rp-tab rp-tab-flush" role="tabpanel" aria-labelledby="rtTasks" data-tab="tasks" style="display:none">
792
+ <!-- 任务面板:原 <aside id="tasksPanel"> 整体搬入(#tpList/#tpCount 不动),宽度改为跟随右栏。 -->
793
+ <aside id="tasksPanel" style="display:none">
794
+ <div class="tp-head">任务面板 <span id="tpCount"></span><button id="tpClose" style="float:right">✕</button></div>
795
+ <div id="tpList"></div>
796
+ </aside>
720
797
  </section>
721
- <section class="rp-sec" id="rpSubSec">
722
- <button class="rp-sec-title rp-fold-head" type="button" data-fold="subs" aria-controls="rpSubFoldBody" aria-expanded="true">子Agent<span class="rp-fold-meta" id="rpSubMeta">—</span><span class="line"></span><span class="rp-fold-tri" aria-hidden="true"></span></button>
723
- <div id="rpSubFoldBody"><div id="rpSubList" class="tr-list"></div></div>
798
+ <section id="tabDash" class="rp-tab rp-tab-flush" role="tabpanel" aria-labelledby="rtDash" data-tab="dash" style="display:none">
799
+ <!-- 省钱仪表盘:原 <aside id="dashPanel"> 整体搬入(KPI/趋势/Top 全部保留),宽度改为跟随右栏。 -->
800
+ <aside id="dashPanel" style="display:none">
801
+ <div class="dash-head">📊 省钱仪表盘 <span class="dash-sub">费用 · 命中 · 节省</span><button id="dashClose">✕</button></div>
802
+ <div class="dash-body">
803
+ <div class="dash-kpi">
804
+ <div class="kpi"><span class="kpi-l">今日费用</span><span class="kpi-v" id="kpiToday">—</span></div>
805
+ <div class="kpi"><span class="kpi-l">缓存命中</span><span class="kpi-v" id="kpiRate">—</span></div>
806
+ <div class="kpi"><span class="kpi-l">已节省</span><span class="kpi-v" style="color:var(--accent)" id="kpiSaved">—</span></div>
807
+ <div class="kpi"><span class="kpi-l">轮次/步数</span><span class="kpi-v" id="kpiTurns">—</span></div>
808
+ </div>
809
+ <div class="dash-gauge-wrap">
810
+ <svg id="rateGauge" viewBox="0 0 120 120" width="120" height="120"></svg>
811
+ <div class="gauge-label"><b id="rateGaugeNum">—</b><span>命中率</span></div>
812
+ </div>
813
+ <div class="dash-sec">近 14 天费用趋势(¥)</div>
814
+ <div id="trendChart" class="dash-chart"></div>
815
+ <div class="dash-sec">模型费用 Top</div>
816
+ <div id="dashModels" class="dash-bars"></div>
817
+ <div class="dash-sec">工具耗时 Top</div>
818
+ <div id="dashTools" class="dash-bars"></div>
819
+ <div class="dash-sec">最近缓存命中</div>
820
+ <div id="dashRecent" class="dash-recent"></div>
821
+ </div>
822
+ </aside>
724
823
  </section>
725
824
  </div>
726
825
  <div class="rp-foot"><span class="grow" id="rpFootNote">停止与压缩:与状态条同一条停止出口</span><button id="rpCompact" type="button" hidden title="后端有 /compact 时才显示">压缩上下文</button><button id="rpStop" type="button" disabled>■ 停止</button></div>
@@ -739,7 +838,7 @@ body.run-open #sessSheet{right:318px}
739
838
  <div class="ss-field">
740
839
  <label for="presetSel">预设<span class="ss-scope">仅下一轮</span></label>
741
840
  <select id="presetSel" title="智能体预设:一键切换工具白名单/权限/参数组合(选「无预设」恢复默认)"><option value="">无预设</option></select>
742
- <div class="ss-src">来源:你的选择 · 只影响下一轮(工具白名单 / 参数);不改变权限来源。</div>
841
+ <div class="ss-src" id="ssPresetSrc">当前:无预设 · 来源:你的选择 · 仅下一轮(工具白名单 / 参数);不改变权限来源。</div>
743
842
  </div>
744
843
  <div class="ss-field">
745
844
  <label>权限<span class="ss-scope">全局 + 本轮</span></label>