mingdao-harness 0.3.0 → 0.3.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/docs/CONFIG.md +24 -2
- package/docs/PLAN-v0.3.0.md +1 -1
- package/docs/PLAN-v0.3.2.md +65 -0
- package/package.json +1 -1
- package/src/agent.js +68 -12
- package/src/audit.js +3 -4
- package/src/cli.js +3 -3
- package/src/commands/diagnose.js +2 -12
- package/src/commands/repl.js +3 -3
- package/src/compact.js +3 -1
- package/src/memory.js +35 -1
- package/src/model-caps.js +69 -0
- package/src/providers/index.js +45 -6
- package/src/providers/openai-compatible.js +8 -3
- package/src/redact.js +26 -0
- package/src/task-state.js +18 -0
- package/src/tools/fetch.js +56 -0
- package/src/tools/git.js +31 -0
- package/src/tools/index.js +39 -1
- package/src/web/app.js +63 -64
- package/src/web/index.html +72 -22
- package/src/web/routes/domains/config.js +26 -2
- package/src/web/server.js +11 -5
package/src/web/index.html
CHANGED
|
@@ -31,20 +31,23 @@ body{margin:0;background:var(--bg);color:var(--text);font:15px/1.65 -apple-syste
|
|
|
31
31
|
body.traj-open{padding-left:294px}
|
|
32
32
|
body.sub-open{padding-right:320px}
|
|
33
33
|
body.tasks-open{padding-right:280px}
|
|
34
|
+
body.dash-open{padding-right:400px}
|
|
34
35
|
@media (max-width:1200px){
|
|
35
36
|
#trajPanel{width:210px}body.traj-open{padding-left:252px}
|
|
36
37
|
#subPanel{width:270px}body.sub-open{padding-right:270px}
|
|
37
38
|
#tasksPanel{width:230px}body.tasks-open{padding-right:230px}
|
|
39
|
+
#dashPanel{width:360px}body.dash-open{padding-right:360px}
|
|
38
40
|
}
|
|
39
41
|
@media (max-width:860px){
|
|
40
42
|
#trajPanel{width:160px}body.traj-open{padding-left:202px}
|
|
41
43
|
#subPanel{width:190px}body.sub-open{padding-right:190px}
|
|
42
44
|
#tasksPanel{width:190px}body.tasks-open{padding-right:190px}
|
|
45
|
+
#dashPanel{width:min(340px,94vw)}body.dash-open{padding-right:min(340px,94vw)}
|
|
43
46
|
}
|
|
44
47
|
@media (max-width:560px){
|
|
45
|
-
body.traj-open,body.sub-open,body.tasks-open{padding:0}
|
|
48
|
+
body.traj-open,body.sub-open,body.tasks-open,body.dash-open{padding:0}
|
|
46
49
|
#trajPanel{width:calc(100vw - 42px);left:42px}
|
|
47
|
-
#subPanel,#tasksPanel{width:100vw}
|
|
50
|
+
#subPanel,#tasksPanel,#dashPanel{width:100vw}
|
|
48
51
|
}
|
|
49
52
|
header{min-height:52px;display:flex;align-items:center;gap:10px;padding:8px 12px;border-bottom:1px solid var(--border);background:var(--bg2);flex:none;flex-wrap:nowrap;white-space:nowrap;overflow-x:auto}
|
|
50
53
|
header .logo{font-weight:700;font-size:16px;color:var(--accent);letter-spacing:.5px;flex:none}
|
|
@@ -229,6 +232,36 @@ footer{flex:none;border-top:1px solid var(--border);background:var(--bg2);paddin
|
|
|
229
232
|
@keyframes spin{to{transform:rotate(360deg)}}
|
|
230
233
|
/* 自绘悬浮气泡 tooltip(替换原生 title):暗色悬浮层 + 阴影,跟随锚点居中,越界自动回弹 */
|
|
231
234
|
.mdtip{position:fixed;z-index:1000;max-width:320px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:7px 11px;font-size:12px;line-height:1.6;box-shadow:0 8px 24px rgba(0,0,0,.55);pointer-events:none;white-space:pre-line;word-break:break-word;display:none}
|
|
235
|
+
/* —— 省钱仪表盘(v0.3.1 顶部入口,真·仪表盘) —— */
|
|
236
|
+
#dashPanel{position:fixed;right:0;top:52px;bottom:0;width:400px;max-width:94vw;background:var(--bg2);border-left:1px solid var(--border);z-index:45;display:flex;flex-direction:column;box-shadow:-8px 0 32px rgba(0,0,0,.4)}
|
|
237
|
+
.dash-head{padding:12px 14px;font-size:14px;font-weight:700;color:var(--accent);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px}
|
|
238
|
+
.dash-head .dash-sub{font-size:11.5px;color:var(--faint);font-weight:400}
|
|
239
|
+
.dash-head button{margin-left:auto;padding:2px 8px;font-size:12px}
|
|
240
|
+
.dash-body{flex:1;overflow-y:auto;padding:14px}
|
|
241
|
+
.dash-kpi{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:14px}
|
|
242
|
+
.kpi{background:var(--bg3);border:1px solid var(--border);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:2px}
|
|
243
|
+
.kpi-l{font-size:11px;color:var(--faint)}
|
|
244
|
+
.kpi-v{font-size:17px;font-weight:700;font-variant-numeric:tabular-nums}
|
|
245
|
+
.dash-gauge-wrap{position:relative;width:120px;margin:4px auto 14px;text-align:center}
|
|
246
|
+
.gauge-label{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0}
|
|
247
|
+
.gauge-label b{font-size:20px;color:var(--accent2)}
|
|
248
|
+
.gauge-label span{font-size:10.5px;color:var(--faint)}
|
|
249
|
+
.dash-sec{font-size:11.5px;color:var(--faint);margin:12px 0 6px;border-top:1px dashed var(--border);padding-top:10px}
|
|
250
|
+
.dash-sec:first-of-type{border-top:none;padding-top:0}
|
|
251
|
+
.dash-chart{background:var(--bg3);border:1px solid var(--border);border-radius:10px;padding:8px}
|
|
252
|
+
.dash-bars{display:flex;flex-direction:column;gap:6px}
|
|
253
|
+
.dbar{background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:6px 10px}
|
|
254
|
+
.dbar .t{display:flex;justify-content:space-between;font-size:11.5px;margin-bottom:4px}
|
|
255
|
+
.dbar .t .n{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60%}
|
|
256
|
+
.dbar .t .v{color:var(--dim);font-variant-numeric:tabular-nums}
|
|
257
|
+
.dbar .track{height:5px;border-radius:3px;background:var(--bg2);overflow:hidden}
|
|
258
|
+
.dbar .fill{height:100%;border-radius:3px;background:linear-gradient(90deg,var(--accent2),var(--accent))}
|
|
259
|
+
.dash-recent{display:flex;flex-direction:column;gap:5px;font-size:11.5px}
|
|
260
|
+
.dr{display:flex;justify-content:space-between;background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:5px 10px}
|
|
261
|
+
.dr .m{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:50%}
|
|
262
|
+
.dr .p{color:var(--accent2);font-variant-numeric:tabular-nums}
|
|
263
|
+
.dr .c{color:var(--faint);font-variant-numeric:tabular-nums}
|
|
264
|
+
.badge-btn:hover{border-color:var(--accent2);color:var(--accent2)}
|
|
232
265
|
</style>
|
|
233
266
|
</head>
|
|
234
267
|
<body>
|
|
@@ -237,7 +270,7 @@ footer{flex:none;border-top:1px solid var(--border);background:var(--bg2);paddin
|
|
|
237
270
|
<span id="cfgMsg" class="badge" style="display:none"></span>
|
|
238
271
|
<button id="cfgBtn" title="设置">⚙</button>
|
|
239
272
|
<select id="wsSel" title="工作空间(切换 / 新建,目录自动创建)"></select>
|
|
240
|
-
<span class="badge" id="envBadge"></span><span class="badge" id="costBadge" title="
|
|
273
|
+
<span class="badge" id="envBadge"></span><span class="badge badge-btn" id="costBadge" title="省钱仪表盘(点击展开)" style="cursor:pointer">📊 —</span>
|
|
241
274
|
<div class="spacer"></div>
|
|
242
275
|
<input id="sessionSearch" placeholder="搜索会话…" title="关键词检索历史会话">
|
|
243
276
|
<select id="sessions" title="历史会话"><option value="">历史会话</option></select>
|
|
@@ -260,6 +293,29 @@ footer{flex:none;border-top:1px solid var(--border);background:var(--bg2);paddin
|
|
|
260
293
|
<div class="tp-head">任务面板 <span id="tpCount"></span><button id="tpClose" style="float:right">✕</button></div>
|
|
261
294
|
<div id="tpList"></div>
|
|
262
295
|
</aside>
|
|
296
|
+
<aside id="dashPanel" style="display:none">
|
|
297
|
+
<div class="dash-head">📊 省钱仪表盘 <span class="dash-sub">费用 · 命中 · 节省</span><button id="dashClose">✕</button></div>
|
|
298
|
+
<div class="dash-body">
|
|
299
|
+
<div class="dash-kpi">
|
|
300
|
+
<div class="kpi"><span class="kpi-l">今日费用</span><span class="kpi-v" id="kpiToday">—</span></div>
|
|
301
|
+
<div class="kpi"><span class="kpi-l">缓存命中</span><span class="kpi-v" id="kpiRate">—</span></div>
|
|
302
|
+
<div class="kpi"><span class="kpi-l">已节省</span><span class="kpi-v" style="color:var(--accent)" id="kpiSaved">—</span></div>
|
|
303
|
+
<div class="kpi"><span class="kpi-l">轮次/步数</span><span class="kpi-v" id="kpiTurns">—</span></div>
|
|
304
|
+
</div>
|
|
305
|
+
<div class="dash-gauge-wrap">
|
|
306
|
+
<svg id="rateGauge" viewBox="0 0 120 120" width="120" height="120"></svg>
|
|
307
|
+
<div class="gauge-label"><b id="rateGaugeNum">—</b><span>命中率</span></div>
|
|
308
|
+
</div>
|
|
309
|
+
<div class="dash-sec">近 14 天费用趋势(¥)</div>
|
|
310
|
+
<div id="trendChart" class="dash-chart"></div>
|
|
311
|
+
<div class="dash-sec">模型费用 Top</div>
|
|
312
|
+
<div id="dashModels" class="dash-bars"></div>
|
|
313
|
+
<div class="dash-sec">工具耗时 Top</div>
|
|
314
|
+
<div id="dashTools" class="dash-bars"></div>
|
|
315
|
+
<div class="dash-sec">最近缓存命中</div>
|
|
316
|
+
<div id="dashRecent" class="dash-recent"></div>
|
|
317
|
+
</div>
|
|
318
|
+
</aside>
|
|
263
319
|
<main><div id="chat"></div></main>
|
|
264
320
|
<footer>
|
|
265
321
|
<div id="workStatus" style="display:none"></div>
|
|
@@ -304,7 +360,7 @@ footer{flex:none;border-top:1px solid var(--border);background:var(--bg2);paddin
|
|
|
304
360
|
<button class="cfg-nav-btn" data-panel="schedule">⏰ 调度队列</button>
|
|
305
361
|
<button class="cfg-nav-btn" data-panel="workspace">📁 工作空间</button>
|
|
306
362
|
<button class="cfg-nav-btn" data-panel="memory">🧠 长期记忆</button>
|
|
307
|
-
|
|
363
|
+
|
|
308
364
|
<button class="cfg-nav-btn" data-panel="mcp">🔌 MCP 生态</button>
|
|
309
365
|
<button class="cfg-nav-btn" data-panel="skills">🧩 技能库</button>
|
|
310
366
|
</nav>
|
|
@@ -322,6 +378,11 @@ footer{flex:none;border-top:1px solid var(--border);background:var(--bg2);paddin
|
|
|
322
378
|
<input id="cmUrl" placeholder="API 地址 https://…/v1" style="flex:1.4;min-width:170px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:6px 10px;font-size:13px">
|
|
323
379
|
<input id="cmKey" type="password" placeholder="API Key(可选)" style="flex:1;min-width:120px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:6px 10px;font-size:13px">
|
|
324
380
|
</div>
|
|
381
|
+
<div class="cfg-row" style="display:flex;gap:6px;flex-wrap:wrap">
|
|
382
|
+
<input id="cmCtx" type="number" placeholder="上下文窗口 tokens(本地模型建议填,如 131072)" style="flex:1.6;min-width:180px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:6px 10px;font-size:13px">
|
|
383
|
+
<input id="cmMaxOut" type="number" placeholder="最大输出 tokens(可选,如 8192)" style="flex:1;min-width:120px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:6px 10px;font-size:13px">
|
|
384
|
+
</div>
|
|
385
|
+
<div class="cfg-hint">本地部署模型(127.0.0.1/localhost):填「上下文窗口」后预算自动收紧到窗口 75% 以内并留输出余量,长任务不再撑爆窗口。</div>
|
|
325
386
|
<div class="row" style="justify-content:flex-start"><button id="cmAdd">+ 添加自定义模型</button></div>
|
|
326
387
|
<div class="cfg-row" style="display:flex;gap:6px"><input id="baseUrlOverride" placeholder="API 地址覆盖(可选,覆盖当前服务商地址)" style="flex:1;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:6px 10px;font-size:13px"><button id="baseUrlSave">保存</button></div>
|
|
327
388
|
</section>
|
|
@@ -362,6 +423,12 @@ footer{flex:none;border-top:1px solid var(--border);background:var(--bg2);paddin
|
|
|
362
423
|
</div>
|
|
363
424
|
<div class="cfg-row"><label><input type="checkbox" id="routeChk"> 自动模型路由(规划→pro / 执行→flash)</label></div>
|
|
364
425
|
<div class="cfg-row"><label>上下文预算 tokens <input type="number" id="budgetInput" min="1000" step="1000"></label></div>
|
|
426
|
+
<div class="cfg-row" style="display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end">
|
|
427
|
+
<label style="font-size:12px">首 token 等待(秒)<input type="number" id="toFirstToken" min="0" step="10" placeholder="自动"></label>
|
|
428
|
+
<label style="font-size:12px">流式空闲(秒)<input type="number" id="toStreamIdle" min="0" step="10" placeholder="自动"></label>
|
|
429
|
+
<label style="font-size:12px">总量(秒)<input type="number" id="toTotal" min="0" step="10" placeholder="自动"></label>
|
|
430
|
+
</div>
|
|
431
|
+
<div class="cfg-hint">留空 = 自适应:本地模型自动给更长首 token 等待(慢 prefill 不被误杀)。仅在默认值仍不够/过松时手填。</div>
|
|
365
432
|
<div class="cfg-row"><label><input type="checkbox" id="autoStartChk"> 开机自启(登录后自动启动服务器)</label></div>
|
|
366
433
|
<div class="cfg-row"><label><input type="checkbox" id="notifyChk"> 后台任务完成/失败时桌面通知</label></div>
|
|
367
434
|
</section>
|
|
@@ -402,24 +469,7 @@ footer{flex:none;border-top:1px solid var(--border);background:var(--bg2);paddin
|
|
|
402
469
|
<textarea id="memArea" rows="5" placeholder="例如:所有文档用中文;提交信息用约定式提交…" style="width:100%;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:6px 10px;font-size:12.5px;resize:vertical"></textarea>
|
|
403
470
|
<div class="row" style="justify-content:flex-start"><button id="memSave">保存记忆</button><button id="memDedupe">去重合并</button><span id="memMsg" style="font-size:12px;color:var(--faint)"></span></div>
|
|
404
471
|
</section>
|
|
405
|
-
|
|
406
|
-
<hr style="border:none;border-top:1px solid var(--border);margin:14px 0 8px">
|
|
407
|
-
<h4 style="margin:4px 0;color:var(--accent2)">缓存命中率仪表盘(最近 10 次)</h4>
|
|
408
|
-
<div id="cacheSummary" style="font-size:12.5px;color:var(--dim);padding:2px"></div>
|
|
409
|
-
<div id="cacheRecent" style="max-height:130px;overflow-y:auto"></div>
|
|
410
|
-
<h4 style="margin:10px 0 4px;color:var(--accent2)">费用二级分账(模型 / 工具 Top5 · 近 14 天)</h4>
|
|
411
|
-
<div style="display:flex;gap:10px;flex-wrap:wrap">
|
|
412
|
-
<div style="flex:1;min-width:170px">
|
|
413
|
-
<div style="font-size:11.5px;color:var(--faint)">模型 Top5(费用)</div>
|
|
414
|
-
<div id="costTopModels" style="font-size:12px;color:var(--dim)"></div>
|
|
415
|
-
</div>
|
|
416
|
-
<div style="flex:1;min-width:170px">
|
|
417
|
-
<div style="font-size:11.5px;color:var(--faint)">工具 Top5(耗时)</div>
|
|
418
|
-
<div id="costTopTools" style="font-size:12px;color:var(--dim)"></div>
|
|
419
|
-
</div>
|
|
420
|
-
</div>
|
|
421
|
-
<div id="costLine" style="margin-top:8px"></div>
|
|
422
|
-
</section>
|
|
472
|
+
|
|
423
473
|
<section class="cfg-panel" data-panel="mcp">
|
|
424
474
|
<hr style="border:none;border-top:1px solid var(--border);margin:14px 0 8px">
|
|
425
475
|
<h4 style="margin:4px 0;color:var(--accent2)">MCP 生态预设(一键接入,重启生效)</h4>
|
|
@@ -50,6 +50,7 @@ export async function handle({ req, res, method, p, url }, deps, shared) {
|
|
|
50
50
|
routing: cfg.routing?.enabled ? cfg.routing : null,
|
|
51
51
|
reasoning,
|
|
52
52
|
contextBudget: cfg.contextBudget || 128000,
|
|
53
|
+
timeout: cfg.timeout || null, // v0.3.2:分层超时(firstTokenMs/streamIdleMs/totalMs)
|
|
53
54
|
pricingAsOf: PRICE_DATA_AS_OF,
|
|
54
55
|
autostart: autostartStatus(),
|
|
55
56
|
notify: cfg.notify !== false,
|
|
@@ -112,6 +113,17 @@ export async function handle({ req, res, method, p, url }, deps, shared) {
|
|
|
112
113
|
next.contextBudget = n;
|
|
113
114
|
cfg.contextBudget = n;
|
|
114
115
|
}
|
|
116
|
+
// v0.3.2 超时分层:firstTokenMs(首 token 等待)/ streamIdleMs(流式空闲)/ totalMs(总量)
|
|
117
|
+
if (body.timeout !== undefined && body.timeout && typeof body.timeout === 'object') {
|
|
118
|
+
cfg.timeout = cfg.timeout || {};
|
|
119
|
+
for (const k of ['firstTokenMs', 'streamIdleMs', 'totalMs']) {
|
|
120
|
+
const v = Number(body.timeout[k]);
|
|
121
|
+
if (Number.isFinite(v) && v > 0) cfg.timeout[k] = Math.round(v);
|
|
122
|
+
else if (body.timeout[k] === null || body.timeout[k] === '') delete cfg.timeout[k];
|
|
123
|
+
}
|
|
124
|
+
// 超时在 createProvider 时按 cfg 固化进实例,改后清缓存让新值即时生效(否则要重启)
|
|
125
|
+
providerCache.clear();
|
|
126
|
+
}
|
|
115
127
|
if (body.reasoningEffort !== undefined) {
|
|
116
128
|
const re = String(body.reasoningEffort);
|
|
117
129
|
if (!['off', 'low', 'high', 'max'].includes(re)) {
|
|
@@ -165,6 +177,8 @@ export async function handle({ req, res, method, p, url }, deps, shared) {
|
|
|
165
177
|
baseUrl: cm.baseUrl || '',
|
|
166
178
|
envKey: cm.envKey || null,
|
|
167
179
|
vision: Boolean(cm.vision),
|
|
180
|
+
contextWindow: Number(cm.contextWindow) > 0 ? Number(cm.contextWindow) : null,
|
|
181
|
+
maxOutputTokens: Number(cm.maxOutputTokens) > 0 ? Number(cm.maxOutputTokens) : null,
|
|
168
182
|
keyState: stored ? 'stored' : 'none',
|
|
169
183
|
keyMasked: stored ? maskKey(stored) : null,
|
|
170
184
|
};
|
|
@@ -232,14 +246,23 @@ export async function handle({ req, res, method, p, url }, deps, shared) {
|
|
|
232
246
|
return json(res, 400, { error: `自定义模型 ${name} 已存在(可修改)` });
|
|
233
247
|
}
|
|
234
248
|
cfg.customModels = cfg.customModels || {};
|
|
249
|
+
// 修改时保留未提交字段(tokenizer/contextWindow/maxOutputTokens/vision)——
|
|
250
|
+
// 前端「修改」只发 baseUrl+label,整体替换会丢这些声明,导致本地模型窗口回退兜底
|
|
251
|
+
const prev = action === 'updateCustom' ? (cfg.customModels[name] || {}) : {};
|
|
235
252
|
cfg.customModels[name] = {
|
|
236
253
|
label,
|
|
237
254
|
baseUrl,
|
|
238
|
-
envKey: String(body.envKey || '').trim() || undefined,
|
|
239
|
-
vision: body.vision === true || body.vision === 'on' ? true : undefined,
|
|
255
|
+
envKey: String(body.envKey || '').trim() || prev.envKey || undefined,
|
|
256
|
+
vision: body.vision === true || body.vision === 'on' ? true : prev.vision === true ? true : undefined,
|
|
257
|
+
...(prev.tokenizer ? { tokenizer: prev.tokenizer } : {}),
|
|
258
|
+
// v0.3.2 本地模型自适应:显式声明上下文窗口/最大输出,预算据此推导(不撑爆窗口)
|
|
259
|
+
...(Number(body.contextWindow) > 0 ? { contextWindow: Math.round(Number(body.contextWindow)) } : (prev.contextWindow ? { contextWindow: prev.contextWindow } : {})),
|
|
260
|
+
...(Number(body.maxOutputTokens) > 0 ? { maxOutputTokens: Math.round(Number(body.maxOutputTokens)) } : (prev.maxOutputTokens ? { maxOutputTokens: prev.maxOutputTokens } : {})),
|
|
240
261
|
};
|
|
241
262
|
if (String(body.key || '').trim()) setStoredKey(`custom:${name}`, String(body.key).trim());
|
|
242
263
|
saveConfig(cfg);
|
|
264
|
+
// baseUrl 改动影响 isLocal 判定 → 超时档位变化;清缓存让新超时/新端点即时生效
|
|
265
|
+
providerCache.clear();
|
|
243
266
|
return json(res, 200, { ok: true, name });
|
|
244
267
|
}
|
|
245
268
|
if (action === 'removeCustom') {
|
|
@@ -252,6 +275,7 @@ export async function handle({ req, res, method, p, url }, deps, shared) {
|
|
|
252
275
|
cfg.model = state.modelName;
|
|
253
276
|
}
|
|
254
277
|
saveConfig(cfg);
|
|
278
|
+
providerCache.clear();
|
|
255
279
|
return json(res, 200, { ok: true, name, model: state.modelName });
|
|
256
280
|
}
|
|
257
281
|
// 质检(自定义模型连通性):发起一次最小对话验证 baseUrl+Key 可用
|
package/src/web/server.js
CHANGED
|
@@ -29,8 +29,8 @@ import { MAX_CONCURRENT } from './constants.js';
|
|
|
29
29
|
import { createAgent } from '../agent.js';
|
|
30
30
|
import { createPermission } from '../permissions.js';
|
|
31
31
|
import { buildSystemPrompt } from '../prompts.js';
|
|
32
|
-
import { loadProjectMemory, extractAndAppendProjectMemory } from '../memory.js';
|
|
33
|
-
import {
|
|
32
|
+
import { loadProjectMemory, loadProjectMemoryEntries, retrieveRelevant, extractAndAppendProjectMemory } from '../memory.js';
|
|
33
|
+
import { saveTaskStateMerge, clearTaskState, loadTaskState, resumePrompt } from '../task-state.js';
|
|
34
34
|
import { createWebIO } from './web-io.js';
|
|
35
35
|
import { startMcpServers } from '../mcp.js';
|
|
36
36
|
import {
|
|
@@ -397,7 +397,11 @@ export async function runWebServer({ host = '127.0.0.1', port = 3820, authToken
|
|
|
397
397
|
// 每轮结束提取的新条目只写文件、不回灌当前会话。
|
|
398
398
|
let projectMemorySnapshot = sessionMemoryCache.get(sessionName);
|
|
399
399
|
if (projectMemorySnapshot === undefined) {
|
|
400
|
-
|
|
400
|
+
// v0.3.1 语义检索:用本轮任务(新会话即首条消息)取「相关记忆条目」,而非全量截 4K——
|
|
401
|
+
// 换任务只注入相关记忆、省 token;快照保证同一会话前缀稳定。
|
|
402
|
+
const allEntries = loadProjectMemoryEntries(taskDir);
|
|
403
|
+
const query = String(built.persistText || '');
|
|
404
|
+
projectMemorySnapshot = query ? retrieveRelevant(allEntries, query, 6).join('\n') : loadProjectMemory(taskDir);
|
|
401
405
|
sessionMemoryCache.set(sessionName, projectMemorySnapshot);
|
|
402
406
|
if (sessionMemoryCache.size > 200) {
|
|
403
407
|
const oldest = sessionMemoryCache.keys().next().value;
|
|
@@ -527,7 +531,7 @@ export async function runWebServer({ host = '127.0.0.1', port = 3820, authToken
|
|
|
527
531
|
// v0.3.0 P0-2:任务检查点——跑满步数(capHit)或中断(aborted)时落盘供续跑,正常完成清除
|
|
528
532
|
const finalSessionName = path.basename(session.file);
|
|
529
533
|
if (r.capHit || r.aborted) {
|
|
530
|
-
|
|
534
|
+
saveTaskStateMerge(finalSessionName, {
|
|
531
535
|
goal: built.persistText,
|
|
532
536
|
progress: r.text || '',
|
|
533
537
|
artifacts: io.stats().deliverables,
|
|
@@ -547,7 +551,9 @@ export async function runWebServer({ host = '127.0.0.1', port = 3820, authToken
|
|
|
547
551
|
const { messageTokens } = await import('../context.js');
|
|
548
552
|
const counter = makeTokenCounter(runModel);
|
|
549
553
|
const used = messages.reduce((/** @type {any} */ sum, /** @type {any} */ m) => sum + messageTokens(m, counter), 0);
|
|
550
|
-
|
|
554
|
+
// v0.3.2:展示用 total 与 Agent 实际预算同源(safeBudget 按窗口推导),避免「显示 128k 实际 98k」的误导
|
|
555
|
+
const { resolveModelCaps, safeBudget } = await import('../model-caps.js');
|
|
556
|
+
budgetInfo = { used, total: safeBudget(cfg, resolveModelCaps(cfg, runModel)) };
|
|
551
557
|
} catch {}
|
|
552
558
|
send({
|
|
553
559
|
type: 'done',
|