mingdao-harness 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mingdao-harness",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "MingDao Harness —— 开源智能体框架(Agent Harness)。零依赖、开箱即用,针对 DeepSeek-V4 系列优化,开放主流模型接入。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "local-audit",
3
3
  "label": "本地模型审计",
4
- "description": "面向本地/资源受限模型(如 131k 窗口 q8 量化)的只读审计预设:只读工具集 + auto 权限 + 保守参数,配合 v0.3.2 本地自适应长任务不中断",
4
+ "description": "面向本地/资源受限模型(如 131k 窗口 q8 量化)的只读审计预设:只读工具集 + 只读权限 + 保守参数,配合 v0.3.2 本地自适应长任务不中断",
5
5
  "systemPrompt": "你是一名代码审计员。任务是对给定代码库做只读审计:先 ls/glob 摸清结构,再 read/grep 逐文件审查,可用 git 查看历史与 diff、fetch 抓取相关文档。输出审计报告:发现的缺陷按严重度分级,每条给出文件:行号证据;不做任何修改。若上下文紧张,先审查最关键的部分并明示未覆盖区域。",
6
6
  "tools": ["read", "ls", "glob", "grep", "skill", "git", "fetch", "todo"],
7
- "permission": "auto",
7
+ "permission": "readonly",
8
8
  "maxRounds": 4,
9
9
  "maxOutputTokens": 4096
10
10
  }
package/src/agent.js CHANGED
@@ -232,7 +232,9 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
232
232
  count,
233
233
  provider,
234
234
  executorModel: subagentModel(cfg, modelName),
235
- triggerRatio: cfg.compactTrigger, // 可配置触发线(默认 80%)
235
+ // 可配置触发线:默认远程 80%;本地模型默认 60% 提前压缩——本地推理内存预算有限,
236
+ // 等到 80% 再压时上下文已累积过多、prefill 与内存双高(v0.4.2 本地模型 507 修复)
237
+ triggerRatio: Number(cfg.compactTrigger) > 0 ? cfg.compactTrigger : (caps.isLocal ? 0.6 : undefined),
236
238
  force: windowPressure, // v0.3.2:逼近窗口时强制压缩(忽略最小阈值门槛)
237
239
  });
238
240
  if (compacted) {
@@ -456,6 +458,10 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
456
458
  // 其余模式/工具保持串行,避免多个权限对话框交错。事件顺序(start/render/post/回填)不变。
457
459
  const READONLY_BATCH = new Set(['read', 'ls', 'glob', 'grep']);
458
460
  const canBatch = permission.mode === 'auto';
461
+ // v0.4.2(本地模型 507 memory_refusal 修复):子代理目标为本地模型时,只读子代理不并行——
462
+ // 多路大 prefill 同时冲进本地推理服务会击穿其单进程内存预算(507 memory_refusal),
463
+ // 串行化只读子代理避免并发峰值(只读 read/ls/glob/grep 仍并行,它们不额外触发大 prefill)。
464
+ const subagentIsLocal = resolveModelCaps(cfg, subagentModel(cfg, modelName)).isLocal;
459
465
 
460
466
  // 预检:解析参数 → PreToolUse 钩子 → 权限检查;拒绝/失败只回填不执行(返回 null)
461
467
  // task 工具标记 readOnly 时也可并行(评估 A4:只读子代理 Promise.all)
@@ -608,7 +614,7 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
608
614
  const prep = /** @type {any} */ (await prepTool(tc));
609
615
  const name = tc.function?.name || '';
610
616
  let batchable = canBatch && Boolean(prep) && !prep.isMcp && READONLY_BATCH.has(name);
611
- if (!batchable && canBatch && Boolean(prep) && name === 'task' && prep.args?.readOnly === true) batchable = true;
617
+ if (!batchable && canBatch && Boolean(prep) && name === 'task' && prep.args?.readOnly === true && !subagentIsLocal) batchable = true;
612
618
  batch.push({ prep, batchable });
613
619
  i += 1;
614
620
  if (!batchable) break;
@@ -49,8 +49,10 @@ export async function chat(/** @type {any} */ { baseUrl, apiKey, model, messages
49
49
  const j = JSON.parse(raw);
50
50
  if (j?.error?.message) detail = j.error.message;
51
51
  } catch {}
52
+ // 本地模型内存不足(507 memory_refusal):给可操作降级提示而非裸状态码(v0.4.2 本地模型 507 修复)
53
+ const hint = res.status === 507 ? '(本地模型内存不足:请压缩上下文、减少并发子任务,或重启模型服务释放内存)' : '';
52
54
  /** @type {ApiError} */
53
- const e = new Error(`[${model}] API 错误 ${res.status}: ${detail}`);
55
+ const e = new Error(`[${model}] API 错误 ${res.status}: ${detail}${hint}`);
54
56
  e.status = res.status;
55
57
  e.headers = res.headers; // 重试退避读取 Retry-After 用
56
58
  throw e;
package/src/web/app.js CHANGED
@@ -64,6 +64,7 @@ function initTips() {
64
64
  const perm = $('#permSel'); if (perm) attachTip(perm, perm.getAttribute('title'));
65
65
  const reas = $('#reasoningSel'); if (reas) attachTip(reas, reas.getAttribute('title'));
66
66
  const model = $('#modelSel'); if (model) attachTip(model, () => { const o = model.options[model.selectedIndex]; return (o && o.title) ? o.title : '切换模型'; });
67
+ const preset = $('#presetSel'); if (preset) attachTip(preset, () => { const o = preset.options[preset.selectedIndex]; return (o && o.title) ? o.title : '智能体预设:一键切换工具白名单/权限/参数组合(选「无预设」恢复默认)'; });
67
68
  const at = $('#attachBtn'); if (at) attachTip(at, at.getAttribute('title'));
68
69
  }
69
70
  initTips();
@@ -148,6 +149,7 @@ $('#dirPickOk').onclick = () => { const cb = pickerCb; pickerCb = null; $('#dirM
148
149
  $('#dirPickNone').onclick = () => { const cb = pickerCb; pickerCb = null; $('#dirModal').style.display = 'none'; if (cb) cb(null); };
149
150
  $('#dirPickCancel').onclick = () => { pickerCb = null; $('#dirModal').style.display = 'none'; };
150
151
  const chatEl = $('#chat'), input = $('#input'), sendBtn = $('#sendBtn');
152
+ let presetData = []; // v0.4.0 Agent Preset:预设列表缓存(含 description/tools/permission,供下拉提示与选中反馈)
151
153
  let activeAiMsg=null, bgRunning=0, curSteps=0, curWorkT0=0, curTools=0, curTasks=0; // 本轮进度(活动条/状态条/轨迹共用)
152
154
  let bgTasks=[]; // 后台任务列表快照(chip tooltip 详情用,updateTasksPanel 每 2s 刷新)
153
155
  let curPhase='模型推理中'; // 阶段语义(服务端 progress 事件下发)
@@ -615,6 +617,23 @@ function applyReasoningUI(reasoning){
615
617
  sel.value = ['off','low','high','max'].includes(effort) ? effort : 'high';
616
618
  }
617
619
  $('#reasoningSel').onchange=()=>{ applyConfig({reasoningEffort:$('#reasoningSel').value}); };
620
+ // v0.4.0 Agent Preset:选中预设时给出「这是什么 / 覆盖了什么」的即时反馈(否则用户不知道各选项作用)
621
+ $('#presetSel').onchange=()=>{ presetPicked(); };
622
+ function presetPicked(){
623
+ const val=$('#presetSel')?.value||'';
624
+ const p=presetData.find((x)=>x.name===val);
625
+ if(!p){ renderBanner({ text: '已取消预设,恢复默认配置(不注入预设定制段/白名单/权限覆盖)。' }); return; } // 选回「无预设」= 退出预设模式
626
+ const over=[];
627
+ if(Array.isArray(p.tools)) over.push('工具白名单 '+p.tools.length+' 个');
628
+ if(p.permission) over.push('权限 '+p.permission);
629
+ if(p.model) over.push('模型 '+p.model);
630
+ if(p.maxRounds) over.push('maxRounds '+p.maxRounds);
631
+ if(p.maxOutputTokens) over.push('maxOutput '+p.maxOutputTokens);
632
+ if(p.temperature!==undefined) over.push('温度 '+p.temperature);
633
+ if(p.contextBudget) over.push('预算 '+p.contextBudget);
634
+ const summary=over.length?'(覆盖:'+over.join(' · ')+')':'';
635
+ renderBanner({ text: '🧩 已选预设「'+(p.label||p.name)+'」:'+(p.description||'(无描述)')+summary+'。随本次发送生效,其余设置保持不变。' });
636
+ }
618
637
  async function init(){
619
638
  try{
620
639
  const r=await fetch('/api/state',{cache:'no-store'}); const j=await r.json();
@@ -653,7 +672,8 @@ async function init(){
653
672
  try{
654
673
  // v0.4.0 Agent Preset:加载预设列表进下拉(项目 → 用户 → 内置)
655
674
  const pr=await fetch('/api/presets',{cache:'no-store'}).catch(()=>null); const pj=pr?await pr.json():{presets:[]};
656
- const psel=$('#presetSel'); if(psel&&pj.presets){ for(const p of pj.presets){ const o=document.createElement('option'); o.value=p.name; o.textContent=p.label+'('+(p.source==='project'?'项目':p.source==='user'?'用户':'内置')+')'; o.title=p.description||p.name; psel.appendChild(o); } }
675
+ presetData = Array.isArray(pj.presets) ? pj.presets : [];
676
+ const psel=$('#presetSel'); if(psel&&presetData.length){ for(const p of presetData){ const o=document.createElement('option'); o.value=p.name; o.textContent=p.label+'('+(p.source==='project'?'项目':p.source==='user'?'用户':'内置')+')'; o.title=p.description||p.name; psel.appendChild(o); } }
657
677
  }catch(e){}
658
678
  try{
659
679
  const dr=await fetch('/api/draft?file='+encodeURIComponent(currentSession||''),{cache:'no-store'}); const dj=await dr.json();
@@ -332,7 +332,7 @@ footer{flex:none;border-top:1px solid var(--border);background:var(--bg2);paddin
332
332
  <option value="readonly">只读</option>
333
333
  </select>
334
334
  <select id="modelSel" title="切换模型"></select>
335
- <select id="presetSel" title="智能体预设:一键切换工具白名单/权限/参数组合(v0.4.0 契约化)"><option value="">预设…</option></select>
335
+ <select id="presetSel" title="智能体预设:一键切换工具白名单/权限/参数组合(选「无预设」恢复默认)"><option value="">无预设</option></select>
336
336
  <select id="reasoningSel" title="思考模式(推理等级):关=不推理省 token · 低 · 高(默认)· 最高=最强推理" style="display:none">
337
337
  <option value="off">关</option>
338
338
  <option value="low">低</option>