mingdao-harness 0.6.30 → 0.6.31

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/agent.js +75 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mingdao-harness",
3
- "version": "0.6.30",
3
+ "version": "0.6.31",
4
4
  "description": "MingDao Harness —— 开源智能体框架(Agent Harness)。零依赖、开箱即用,针对 DeepSeek-V4 系列优化,开放主流模型接入。",
5
5
  "type": "module",
6
6
  "bin": {
package/src/agent.js CHANGED
@@ -79,6 +79,56 @@ const SUBAGENT_MAX_STEPS = 24;
79
79
  * · 真干活的回合每隔几秒就有工具结果或正文增量,60 分钟零增量在任何正常回合都不可能。
80
80
  *
81
81
  * 可配置:config.noProgressTimeoutMs(0/负数/NaN/Infinity 一律回落默认——**不能**用它关掉看门狗)。 */
82
+ // v0.6.31(负责人实测):TTFT 提示"每个回合各打一次"在长审计里刷屏。
83
+ //
84
+ // 现象:本地模型跑长审计时,聊天区反复出现「⏱ 首帧 10.9s(第 1 次模型请求…)」十几次,
85
+ // 而且**每次都写"第 1 次"**——因为每个「回合」都是新 turnProgress(用户点「继续」/自动续跑
86
+ // 都算新回合),`llmCalls===1` 这个条件对每个回合都成立。
87
+ //
88
+ // 判定抽成**纯函数**(可单测、可变异),状态放**进程级**(WebUI 每请求新建 agent,只有进程级
89
+ // 才能跨回合节流;键用会话名/工作目录)。
90
+ const TTFT_NOTICE_DEFAULT_QUIET_MS = 10 * 60 * 1000;
91
+ /** @type {Map<string, {ms: number|null, at: number}>} */
92
+ const ttftNoticeState = new Map();
93
+
94
+ /**
95
+ * 本次是否该打印 TTFT 提示。
96
+ * 规则:① 每回合只在第 1 次请求时考虑;② 同一会话 quietMs 内不重复;
97
+ * ③ 首帧比上次打印时**明显变慢**(≥ slowdownRatio 倍)则例外——劣化信号不能被节流吃掉。
98
+ * @param {{llmCalls:number, ttftMs:number, lastMs?:number|null, lastAt?:number, now:number,
99
+ * quietMs?:number, slowdownRatio?:number}} a
100
+ * @returns {{print: boolean, slowdown: boolean}}
101
+ */
102
+ export function shouldPrintTtftNotice({ llmCalls, ttftMs, lastMs = null, lastAt = 0, now, quietMs = TTFT_NOTICE_DEFAULT_QUIET_MS, slowdownRatio = 2 }) {
103
+ if (Number(llmCalls) !== 1) return { print: false, slowdown: false };
104
+ const ms = Number(ttftMs);
105
+ if (!Number.isFinite(ms)) return { print: false, slowdown: false };
106
+ const slowdown = Number(lastMs) > 0 && ms >= Number(lastMs) * Number(slowdownRatio);
107
+ if (slowdown) return { print: true, slowdown: true };
108
+ if (!Number(lastAt)) return { print: true, slowdown: false };
109
+ return { print: now - Number(lastAt) >= Number(quietMs), slowdown: false };
110
+ }
111
+
112
+ /**
113
+ * 读/写某会话的 TTFT 提示节流状态(进程级,上限 200 个键防泄漏)。
114
+ * @param {string} key
115
+ * @returns {{ms: number|null, at: number}}
116
+ */
117
+ export function ttftNoticeStateFor(key) {
118
+ const k = String(key || 'default');
119
+ let v = ttftNoticeState.get(k);
120
+ if (!v) {
121
+ v = { ms: null, at: 0 };
122
+ if (ttftNoticeState.size >= 200) {
123
+ // TS7 下 `MapIterator#value` 是 `string | undefined`:先收窄再删(与原来等价,size>=200 时必有键)。
124
+ const oldest = ttftNoticeState.keys().next().value;
125
+ if (oldest !== undefined) ttftNoticeState.delete(oldest);
126
+ }
127
+ ttftNoticeState.set(k, v);
128
+ }
129
+ return v;
130
+ }
131
+
82
132
  export const DEFAULT_NO_PROGRESS_TIMEOUT_MS = 3600000;
83
133
 
84
134
  /** 父回合在等待 `task` 子代理时额外给的一点点宽限(ms)。
@@ -1634,10 +1684,31 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
1634
1684
  // (状态条/右栏已有首帧值)。长审计里本地模型会有十几次请求,重复打印只会刷屏。
1635
1685
  // 可选的"后续明显劣化警告"本轮未实现:现有 firstTokenAt 是回合级的,
1636
1686
  // 不能用来量后续请求的 TTFT(否则会得到负值),不为一条提示去改测量口径。
1637
- if (turnProgress.llmCalls === 1) {
1638
- try {
1639
- io.print(style(`⏱ 首帧 ${(ttft / 1000).toFixed(1)}s(第 ${turnProgress.llmCalls} 次模型请求;本地模型这一项直接反映 prefill 快慢)`, C.dim));
1640
- } catch {}
1687
+ // v0.6.31:会话级节流(见文件顶部 shouldPrintTtftNotice)——长审计里「继续」会产生
1688
+ // 很多新回合,每回合都打一次会把聊天区刷满;但首帧**明显变慢**时必须照常报(劣化信号)。
1689
+ {
1690
+ const nk = String(cfg?.sessionRef?.name || cfg?.sessionKey || workingDir || 'default');
1691
+ const st = ttftNoticeStateFor(nk);
1692
+ const verdict = shouldPrintTtftNotice({
1693
+ llmCalls: turnProgress.llmCalls,
1694
+ ttftMs: ttft,
1695
+ lastMs: st.ms,
1696
+ lastAt: st.at,
1697
+ now: Date.now(),
1698
+ quietMs: Number(cfg?.ttftNoticeQuietMs) > 0 ? Number(cfg.ttftNoticeQuietMs) : TTFT_NOTICE_DEFAULT_QUIET_MS,
1699
+ });
1700
+ if (verdict.print) {
1701
+ st.ms = ttft;
1702
+ st.at = Date.now();
1703
+ try {
1704
+ io.print(style(
1705
+ verdict.slowdown
1706
+ ? `⏱ 首帧 ${(ttft / 1000).toFixed(1)}s(比上次明显变慢,本地模型这一项直接反映 prefill 快慢)`
1707
+ : `⏱ 首帧 ${(ttft / 1000).toFixed(1)}s(第 ${turnProgress.llmCalls} 次模型请求;本地模型这一项直接反映 prefill 快慢)`,
1708
+ verdict.slowdown ? C.yellow : C.dim
1709
+ ));
1710
+ } catch {}
1711
+ }
1641
1712
  }
1642
1713
  markProgress('模型增量');
1643
1714
  if (d.text) io.writeText(d.text);