mingdao-harness 0.2.3 → 0.2.5

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 (65) hide show
  1. package/docs/ROADMAP-NEXT.md +13 -13
  2. package/package.json +1 -1
  3. package/skills/release-checklist/SKILL.md +16 -3
  4. package/src/agent.js +102 -30
  5. package/src/audit.js +2 -2
  6. package/src/batch.js +74 -16
  7. package/src/cachestats.js +57 -11
  8. package/src/cli.js +29 -679
  9. package/src/commands/desktop.js +1 -1
  10. package/src/commands/key.js +1 -1
  11. package/src/commands/repl.js +711 -0
  12. package/src/commands/schedule.js +7 -7
  13. package/src/commands/skill.js +18 -16
  14. package/src/commands/sync.js +5 -5
  15. package/src/commands/update.js +22 -9
  16. package/src/commands/workspace.js +2 -2
  17. package/src/compact.js +6 -6
  18. package/src/config.js +7 -4
  19. package/src/context.js +11 -11
  20. package/src/cost-guard.js +15 -5
  21. package/src/credentials.js +6 -0
  22. package/src/hooks.js +11 -11
  23. package/src/mcp-presets.js +5 -5
  24. package/src/mcp.js +19 -19
  25. package/src/memory.js +16 -16
  26. package/src/model-discovery.js +16 -15
  27. package/src/models.js +4 -4
  28. package/src/notify.js +2 -2
  29. package/src/permissions.js +7 -3
  30. package/src/pricing.js +35 -1
  31. package/src/prompts.js +1 -1
  32. package/src/providers/index.js +7 -7
  33. package/src/providers/openai-compatible.js +8 -8
  34. package/src/routing.js +5 -5
  35. package/src/schedule.js +28 -28
  36. package/src/session-index.js +5 -1
  37. package/src/session.js +11 -2
  38. package/src/skill-lib.js +62 -3
  39. package/src/skill-registry.js +10 -9
  40. package/src/skills.js +10 -10
  41. package/src/sync-server.js +86 -3
  42. package/src/sync.js +53 -20
  43. package/src/tasks/worker.js +126 -0
  44. package/src/tasks.js +13 -13
  45. package/src/titles.js +5 -5
  46. package/src/tokenizer.js +22 -4
  47. package/src/tools/bash.js +12 -12
  48. package/src/tools/fs-tools.js +66 -9
  49. package/src/tools/index.js +77 -36
  50. package/src/ui.js +83 -6
  51. package/src/update.js +8 -8
  52. package/src/web/app.js +43 -0
  53. package/src/web/attachments.js +1 -1
  54. package/src/web/index.html +12 -0
  55. package/src/web/routes/api.js +38 -786
  56. package/src/web/routes/domains/config.js +280 -0
  57. package/src/web/routes/domains/misc.js +128 -0
  58. package/src/web/routes/domains/schedule.js +127 -0
  59. package/src/web/routes/domains/sessions.js +105 -0
  60. package/src/web/routes/domains/skills.js +79 -0
  61. package/src/web/routes/domains/sync.js +101 -0
  62. package/src/web/routes/domains/workspace.js +113 -0
  63. package/src/web/server.js +29 -14
  64. package/src/web/web-io.js +54 -12
  65. package/src/workspace.js +14 -14
package/src/cachestats.js CHANGED
@@ -16,7 +16,7 @@ const MAX_LINES = 20000;
16
16
  const KEEP_LINES = 10000;
17
17
  let cacheStatsCount = 0;
18
18
 
19
- export function recordCacheStats(entry) {
19
+ export function recordCacheStats(/** @type {any} */ entry) {
20
20
  try {
21
21
  ensureHome(); // 审计 B10:与 audit/journal 一致,目录缺失不静默丢统计
22
22
  const line = JSON.stringify({
@@ -33,6 +33,8 @@ export function recordCacheStats(entry) {
33
33
  llmMs: entry.llmMs ?? undefined, // 模型调用总耗时(状态栏 LLM 时长/tok-s)
34
34
  toolMs: entry.toolMs ?? undefined, // 工具执行总耗时(状态栏 工具调用时长)
35
35
  firstTokenMs: entry.firstTokenMs ?? undefined, // 首 token 延迟(状态栏平均首 token)
36
+ reasoning: entry.reasoning ?? undefined, // 省钱 B3:推理 token 估算(费用二级分账维度)
37
+ byTool: Array.isArray(entry.byTool) ? entry.byTool : undefined, // 省钱 B3:逐工具 {tool,calls,ms}
36
38
  });
37
39
  fs.appendFileSync(cacheStatsFile(), line + '\n');
38
40
  cacheStatsCount += 1;
@@ -52,13 +54,13 @@ export function recordCacheStats(entry) {
52
54
  // listCacheStats 读取缓存(审计:costGuard 每步 / WebUI 每 15s / /cost 命令都调用——
53
55
  // 四报告共识 P2-2/§3.3-A:全文件读+逐行 parse 会随 JSONL 增长线性放大 IO。
54
56
  // 按 mtimeMs+size 双键缓存;写入追加会改两者,轮转重写同样改,缓存自动失效)
55
- let _statsCache = null;
57
+ let /** @type {any} */ _statsCache = null;
56
58
 
57
59
  export function listCacheStats(limit = 2000) {
58
60
  try {
59
61
  const file = cacheStatsFile();
60
62
  const st = fs.statSync(file);
61
- if (!_statsCache || _statsCache.mtimeMs !== st.mtimeMs || _statsCache.size !== st.size || _statsCache.limit < limit) {
63
+ if (!/** @type {any} */ _statsCache || _statsCache.mtimeMs !== st.mtimeMs || _statsCache.size !== st.size || _statsCache.limit < limit) {
62
64
  const raw = fs.readFileSync(file, 'utf8');
63
65
  const out = [];
64
66
  for (const l of raw.split('\n')) {
@@ -75,8 +77,8 @@ export function listCacheStats(limit = 2000) {
75
77
  }
76
78
  }
77
79
 
78
- export function summarizeCacheStats(entries) {
79
- const sum = { turns: entries.length, prompt: 0, completion: 0, hit: 0, miss: 0, cost: 0, saved: 0, steps: 0, llmMs: 0, toolMs: 0, firstTokenCount: 0, firstTokenSumMs: 0 };
80
+ export function summarizeCacheStats(/** @type {any} */ entries) {
81
+ const sum = /** @type {any} */ ({ turns: entries.length, prompt: 0, completion: 0, hit: 0, miss: 0, cost: 0, saved: 0, steps: 0, llmMs: 0, toolMs: 0, firstTokenCount: 0, firstTokenSumMs: 0 });
80
82
  for (const e of entries) {
81
83
  sum.prompt += e.prompt || 0;
82
84
  sum.completion += e.completion || 0;
@@ -100,7 +102,7 @@ export function summarizeCacheStats(entries) {
100
102
 
101
103
  // 记录一次用量(agent 侧调用):自动计算命中拆分与节省额
102
104
  // 记录一次用量(agent 侧调用):自动计算命中拆分与节省额;perf 为回合性能指标(状态栏)
103
- export function recordUsage(modelName, usage, perf = null) {
105
+ export function recordUsage(/** @type {any} */ modelName, /** @type {any} */ usage, /** @type {any} */ perf = null) {
104
106
  const split = cacheSplit(usage);
105
107
  const prompt = usage?.prompt_tokens || 0;
106
108
  const completion = usage?.completion_tokens || 0;
@@ -125,11 +127,13 @@ export function recordUsage(modelName, usage, perf = null) {
125
127
  llmMs: perf?.llmMs ?? undefined,
126
128
  toolMs: perf?.toolMs ?? undefined,
127
129
  firstTokenMs: perf?.firstTokenMs ?? undefined,
130
+ reasoning: perf?.reasoningTokens ?? undefined, // 省钱 B3
131
+ byTool: perf?.toolStats ?? undefined, // 省钱 B3
128
132
  });
129
133
  }
130
134
 
131
- export function formatCacheSummary(sum) {
132
- const fmt = (n) => (n >= 1000 ? (n / 1000).toFixed(1) + 'k' : String(n));
135
+ export function formatCacheSummary(/** @type {any} */ sum) {
136
+ const fmt = (/** @type {any} */ n) => (n >= 1000 ? (n / 1000).toFixed(1) + 'k' : String(n));
133
137
  const lines = [
134
138
  `轮次 ${sum.turns} · ↑${fmt(sum.prompt)} ↓${fmt(sum.completion)} tokens`,
135
139
  `缓存命中率 ${sum.rate != null ? (sum.rate * 100).toFixed(0) + '%' : '暂无缓存数据'}`,
@@ -139,9 +143,12 @@ export function formatCacheSummary(sum) {
139
143
  }
140
144
 
141
145
  // 分账统计(评估 /cost 升级):按模型分账、今日费用、batch 半价任务、节省归因
146
+ // 省钱 B3:新增 reasoning 维度、byTool 逐工具累加、byDay 近 14 天按日折线数据
142
147
  export function costBreakdown() {
143
148
  const entries = listCacheStats(100000);
144
149
  const byModel = new Map();
150
+ const byTool = /** @type {Map<string, {calls: number, ms: number}>} */ (new Map());
151
+ const byDay = /** @type {Map<string, number>} */ (new Map()); // 'MM-DD' → cost(近 14 个北京日,含 0 值日)
145
152
  const start = beijingDayStart().getTime();
146
153
  let totalCost = 0;
147
154
  let totalSaved = 0;
@@ -149,21 +156,45 @@ export function costBreakdown() {
149
156
  let batchCost = 0;
150
157
  let hit = 0;
151
158
  let miss = 0;
159
+ let reasoning = 0;
152
160
  for (const e of entries) {
153
161
  totalCost += e.cost || 0;
154
162
  totalSaved += e.saved || 0;
155
163
  hit += e.hit || 0;
156
164
  miss += e.miss || 0;
165
+ reasoning += e.reasoning || 0;
157
166
  if (e.batch) batchCost += e.cost || 0;
158
167
  if (e.at >= start) today += e.cost || 0;
159
- const m = byModel.get(e.model) || { prompt: 0, completion: 0, cost: 0, saved: 0, turns: 0, batchTurns: 0 };
168
+ const m = byModel.get(e.model) || { prompt: 0, completion: 0, cost: 0, saved: 0, turns: 0, batchTurns: 0, reasoning: 0 };
160
169
  m.prompt += e.prompt || 0;
161
170
  m.completion += e.completion || 0;
162
171
  m.cost += e.cost || 0;
163
172
  m.saved += e.saved || 0;
164
173
  m.turns += 1;
174
+ m.reasoning += e.reasoning || 0;
165
175
  if (e.batch) m.batchTurns += 1;
166
176
  byModel.set(e.model, m);
177
+ if (Array.isArray(e.byTool)) {
178
+ for (const t of e.byTool) {
179
+ const tt = byTool.get(t.tool) || { calls: 0, ms: 0 };
180
+ tt.calls += t.calls || 0;
181
+ tt.ms += t.ms || 0;
182
+ byTool.set(t.tool, tt);
183
+ }
184
+ }
185
+ if (e.at) {
186
+ const p = beijingParts(new Date(e.at));
187
+ const key = `${String(p.month).padStart(2, '0')}-${String(p.day).padStart(2, '0')}`;
188
+ byDay.set(key, (byDay.get(key) || 0) + (e.cost || 0));
189
+ }
190
+ }
191
+ // 补全近 14 天(含无记录的 0 值日),保证折线 x 轴连续
192
+ const days = /** @type {Array<{day: string, cost: number}>} */ ([]);
193
+ for (let i = 13; i >= 0; i--) {
194
+ const d = new Date(start - i * 86400000);
195
+ const p = beijingParts(d);
196
+ const key = `${String(p.month).padStart(2, '0')}-${String(p.day).padStart(2, '0')}`;
197
+ days.push({ day: key, cost: byDay.get(key) || 0 });
167
198
  }
168
199
  return {
169
200
  totalCost,
@@ -173,12 +204,16 @@ export function costBreakdown() {
173
204
  hit,
174
205
  miss,
175
206
  rate: hit + miss > 0 ? hit / (hit + miss) : null,
207
+ reasoning,
176
208
  byModel: [...byModel.entries()].map(([model, m]) => ({ model, ...m })).sort((a, b) => b.cost - a.cost),
209
+ byTool: [...byTool.entries()].map(([tool, t]) => ({ tool, ...t })).sort((a, b) => b.ms - a.ms),
210
+ byDay: days,
177
211
  };
178
212
  }
179
213
 
180
214
  // 月度费用报告(/cost 导出):按北京时间月份聚合,month 形如 'YYYY-MM',缺省返回全部月份
181
- export function costMonthlyReport(month) {
215
+ // 省钱 B3:月度聚合增加 reasoning 与 byTool 维度
216
+ export function costMonthlyReport(/** @type {any} */ month) {
182
217
  const entries = listCacheStats(100000);
183
218
  const want = String(month || '').trim();
184
219
  const months = new Map();
@@ -188,7 +223,7 @@ export function costMonthlyReport(month) {
188
223
  if (want && key !== want) continue;
189
224
  let m = months.get(key);
190
225
  if (!m) {
191
- m = { month: key, cost: 0, saved: 0, prompt: 0, completion: 0, hit: 0, miss: 0, batchCost: 0, days: new Map(), models: new Map() };
226
+ m = { month: key, cost: 0, saved: 0, prompt: 0, completion: 0, hit: 0, miss: 0, batchCost: 0, reasoning: 0, days: new Map(), models: new Map(), tools: new Map() };
192
227
  months.set(key, m);
193
228
  }
194
229
  m.cost += e.cost || 0;
@@ -197,6 +232,7 @@ export function costMonthlyReport(month) {
197
232
  m.completion += e.completion || 0;
198
233
  m.hit += e.hit || 0;
199
234
  m.miss += e.miss || 0;
235
+ m.reasoning += e.reasoning || 0;
200
236
  if (e.batch) m.batchCost += e.cost || 0;
201
237
  const day = String(p.day).padStart(2, '0');
202
238
  m.days.set(day, (m.days.get(day) || 0) + (e.cost || 0));
@@ -206,6 +242,14 @@ export function costMonthlyReport(month) {
206
242
  mm.cost += e.cost || 0;
207
243
  mm.turns += 1;
208
244
  m.models.set(e.model, mm);
245
+ if (Array.isArray(e.byTool)) {
246
+ for (const t of e.byTool) {
247
+ const tt = m.tools.get(t.tool) || { calls: 0, ms: 0 };
248
+ tt.calls += t.calls || 0;
249
+ tt.ms += t.ms || 0;
250
+ m.tools.set(t.tool, tt);
251
+ }
252
+ }
209
253
  }
210
254
  return [...months.values()]
211
255
  .sort((a, b) => a.month.localeCompare(b.month))
@@ -219,7 +263,9 @@ export function costMonthlyReport(month) {
219
263
  miss: m.miss,
220
264
  rate: m.hit + m.miss > 0 ? m.hit / (m.hit + m.miss) : null,
221
265
  batchCost: m.batchCost,
266
+ reasoning: m.reasoning,
222
267
  days: [...m.days.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, cost]) => ({ day, cost })),
223
268
  models: [...m.models.entries()].map(([model, x]) => ({ model, ...x })).sort((a, b) => b.cost - a.cost),
269
+ tools: [...m.tools.entries()].map(([tool, t]) => ({ tool, ...t })).sort((a, b) => b.ms - a.ms),
224
270
  }));
225
271
  }