mingdao-harness 0.2.4 → 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 (61) hide show
  1. package/docs/ROADMAP-NEXT.md +5 -5
  2. package/package.json +1 -1
  3. package/src/agent.js +19 -19
  4. package/src/audit.js +2 -2
  5. package/src/batch.js +7 -7
  6. package/src/cachestats.js +9 -9
  7. package/src/cli.js +29 -679
  8. package/src/commands/desktop.js +1 -1
  9. package/src/commands/key.js +1 -1
  10. package/src/commands/repl.js +711 -0
  11. package/src/commands/schedule.js +7 -7
  12. package/src/commands/skill.js +18 -16
  13. package/src/commands/sync.js +5 -5
  14. package/src/commands/update.js +7 -6
  15. package/src/commands/workspace.js +2 -2
  16. package/src/compact.js +6 -6
  17. package/src/config.js +7 -4
  18. package/src/context.js +11 -11
  19. package/src/credentials.js +6 -0
  20. package/src/hooks.js +11 -11
  21. package/src/mcp-presets.js +5 -5
  22. package/src/mcp.js +19 -19
  23. package/src/memory.js +16 -16
  24. package/src/model-discovery.js +16 -15
  25. package/src/models.js +4 -4
  26. package/src/notify.js +2 -2
  27. package/src/permissions.js +7 -3
  28. package/src/pricing.js +35 -1
  29. package/src/prompts.js +1 -1
  30. package/src/providers/index.js +7 -7
  31. package/src/providers/openai-compatible.js +8 -8
  32. package/src/routing.js +5 -5
  33. package/src/schedule.js +28 -28
  34. package/src/session-index.js +5 -1
  35. package/src/session.js +11 -2
  36. package/src/skill-lib.js +62 -3
  37. package/src/skill-registry.js +10 -9
  38. package/src/skills.js +10 -10
  39. package/src/sync-server.js +86 -3
  40. package/src/sync.js +53 -20
  41. package/src/tasks/worker.js +126 -0
  42. package/src/tasks.js +13 -13
  43. package/src/titles.js +5 -5
  44. package/src/tokenizer.js +22 -4
  45. package/src/tools/bash.js +12 -12
  46. package/src/tools/fs-tools.js +66 -9
  47. package/src/tools/index.js +7 -7
  48. package/src/ui.js +83 -6
  49. package/src/update.js +8 -8
  50. package/src/web/attachments.js +1 -1
  51. package/src/web/routes/api.js +38 -786
  52. package/src/web/routes/domains/config.js +280 -0
  53. package/src/web/routes/domains/misc.js +128 -0
  54. package/src/web/routes/domains/schedule.js +127 -0
  55. package/src/web/routes/domains/sessions.js +105 -0
  56. package/src/web/routes/domains/skills.js +79 -0
  57. package/src/web/routes/domains/sync.js +101 -0
  58. package/src/web/routes/domains/workspace.js +113 -0
  59. package/src/web/server.js +29 -14
  60. package/src/web/web-io.js +54 -12
  61. package/src/workspace.js +14 -14
@@ -14,7 +14,7 @@ import {
14
14
  import { listTasks, killTask, formatTaskRow } from '../tasks.js';
15
15
  import { ensureHome } from '../config.js';
16
16
 
17
- function printTasks(home) {
17
+ function printTasks(/** @type {any} */ home) {
18
18
  const tasks = listTasks(home);
19
19
  if (!tasks.length) {
20
20
  console.log('暂无任务。启动:mingdao run "<任务>"');
@@ -26,7 +26,7 @@ function printTasks(home) {
26
26
  console.log(running ? `\n${running} 个运行中 · mingdao tasks watch 实时刷新 · kill <id> 停止` : '\n无运行中任务');
27
27
  }
28
28
 
29
- async function watchTasks(home) {
29
+ async function watchTasks(/** @type {any} */ home) {
30
30
  if (!process.stdout.isTTY) {
31
31
  printTasks(home);
32
32
  return;
@@ -45,7 +45,7 @@ async function watchTasks(home) {
45
45
  }
46
46
  }
47
47
 
48
- export async function handleTasks(cmd, args) {
48
+ export async function handleTasks(/** @type {any} */ cmd, /** @type {any} */ args) {
49
49
  const home0 = ensureHome();
50
50
  reconcileSchedules(home0);
51
51
  const sub = args[0];
@@ -67,7 +67,7 @@ export async function handleTasks(cmd, args) {
67
67
  return true;
68
68
  }
69
69
 
70
- export async function handleSchedule(cmd, args) {
70
+ export async function handleSchedule(/** @type {any} */ cmd, /** @type {any} */ args) {
71
71
  const home0 = ensureHome();
72
72
  reconcileSchedules(home0);
73
73
  const sub = args[0];
@@ -86,7 +86,7 @@ export async function handleSchedule(cmd, args) {
86
86
  let at = null;
87
87
  let every = null;
88
88
  let anchor = null;
89
- let after = [];
89
+ let /** @type {any} */ after = [];
90
90
  let permission = null;
91
91
  let model = null;
92
92
  let offpeak = false;
@@ -106,7 +106,7 @@ export async function handleSchedule(cmd, args) {
106
106
  process.exitCode = 1;
107
107
  return true;
108
108
  }
109
- const r = addSchedule(home0, question, { at, every, after, permission, model, cwd: process.cwd(), anchor, offpeak });
109
+ const r = addSchedule(home0, question, { at, every, /** @type {any} */ after, permission, model, cwd: process.cwd(), anchor, offpeak });
110
110
  if (r.error) {
111
111
  console.log('[错误] ' + r.error);
112
112
  process.exitCode = 1;
@@ -162,7 +162,7 @@ export async function handleSchedule(cmd, args) {
162
162
  process.exitCode = 1;
163
163
  return true;
164
164
  }
165
- const r = chainSchedules(home0, rest);
165
+ const r = /** @type {any} */ (chainSchedules(home0, rest));
166
166
  if (r.error) {
167
167
  console.log('[错误] ' + r.error);
168
168
  process.exitCode = 1;
@@ -18,14 +18,14 @@ function askAutoStart() {
18
18
  });
19
19
  }
20
20
 
21
- export async function handleSkill(cmd, args) {
21
+ export async function handleSkill(/** @type {any} */ cmd, /** @type {any} */ args) {
22
22
  const sub = args[0] || 'list';
23
23
  const arg = args[1];
24
24
  if (sub === 'search') {
25
25
  const local = searchLibrary(arg || '');
26
26
  const remote = await searchRegistry(arg || '');
27
27
  const localNames = new Set(local.map((s) => s.name));
28
- const remoteOnly = remote.skills ? remote.skills.filter((s) => !localNames.has(s.name)) : [];
28
+ const remoteOnly = remote.skills ? remote.skills.filter((/** @type {any} */ s) => !localNames.has(s.name)) : [];
29
29
  console.log(
30
30
  `技能库匹配:内置 ${local.length}${remote.error ? '' : ` + 线上 ${remoteOnly.length}`} · 安装:mingdao skill install <名称>`
31
31
  );
@@ -41,15 +41,17 @@ export async function handleSkill(cmd, args) {
41
41
  if (sub === 'install') {
42
42
  const r = await installSkill(arg);
43
43
  if (r.error) {
44
- console.log('[错误] ' + r.error);
44
+ console.log('[错误] ' + (/** @type {any} */ (r)).error);
45
45
  process.exitCode = 1;
46
46
  return true;
47
47
  }
48
- if (r.names) {
49
- console.log(`✓ 已从 git 仓库安装 ${r.names.length} 个技能:${r.names.join(', ')}`);
48
+ if ((/** @type {any} */ (r)).names) {
49
+ const rn = /** @type {any} */ (r);
50
+ console.log(`✓ 已从 git 仓库安装 ${rn.names.length} 个技能:${rn.names.join(', ')}`);
50
51
  } else {
51
- const srcLabel = r.host ? '(线上 registry)' : '';
52
- console.log(`✓ 已安装技能 ${r.name}${srcLabel} → ~/.mingdao/skills/${r.name}/(可编辑/删除,下次会话生效)`);
52
+ const rn = /** @type {any} */ (r);
53
+ const srcLabel = rn.host ? '(线上 registry)' : '';
54
+ console.log(`✓ 已安装技能 ${rn.name}${srcLabel} → ~/.mingdao/skills/${rn.name}/(可编辑/删除,下次会话生效)`);
53
55
  }
54
56
  return true;
55
57
  }
@@ -61,7 +63,7 @@ export async function handleSkill(cmd, args) {
61
63
  }
62
64
  const r = uninstallSkill(arg);
63
65
  if (r.error) {
64
- console.log('[错误] ' + r.error);
66
+ console.log('[错误] ' + (/** @type {any} */ (r)).error);
65
67
  process.exitCode = 1;
66
68
  return true;
67
69
  }
@@ -76,11 +78,11 @@ export async function handleSkill(cmd, args) {
76
78
  }
77
79
  const r = await reinstallSkill(arg);
78
80
  if (r.error) {
79
- console.log('[错误] ' + r.error);
81
+ console.log('[错误] ' + (/** @type {any} */ (r)).error);
80
82
  process.exitCode = 1;
81
83
  return true;
82
84
  }
83
- console.log(`✓ 已更新技能 ${r.name}`);
85
+ console.log(`✓ 已更新技能 ${(/** @type {any} */ (r)).name}`);
84
86
  return true;
85
87
  }
86
88
  if (sub === 'trust') {
@@ -91,7 +93,7 @@ export async function handleSkill(cmd, args) {
91
93
  }
92
94
  const r = trustSkill(arg);
93
95
  if (r.error) {
94
- console.log('[错误] ' + r.error);
96
+ console.log('[错误] ' + (/** @type {any} */ (r)).error);
95
97
  process.exitCode = 1;
96
98
  return true;
97
99
  }
@@ -116,7 +118,7 @@ export async function handleSkill(cmd, args) {
116
118
  // WebUI:mingdao web [端口] [--auth-token <令牌>] [--autostart|--no-autostart](评估 P3-1:参数结构不合法的按提问处理)
117
119
  // --autostart/--no-autostart:写 config.web.autoStart——开启后每次运行 mingdao 会自动后台拉起 WebUI;
118
120
  // 首次交互运行且未设置时,询问一次「下次是否自动启动」。
119
- export async function handleWeb(cmd, args) {
121
+ export async function handleWeb(/** @type {any} */ cmd, /** @type {any} */ args) {
120
122
  let portIndex = -1;
121
123
  let tokenSeen = false;
122
124
  let autoChoice; // undefined=未指定;true/false=显式指定
@@ -165,7 +167,7 @@ export async function handleWeb(cmd, args) {
165
167
  const host = cfg0?.web?.host || '127.0.0.1';
166
168
  // 访问令牌优先级:--auth-token 参数 > 环境变量 MINGDAO_WEB_TOKEN > config.json 的 web.token
167
169
  let authToken = process.env.MINGDAO_WEB_TOKEN || cfg0?.web?.token || undefined;
168
- const atIdx = args.findIndex((a) => typeof a === 'string' && a.startsWith('--auth-token'));
170
+ const atIdx = args.findIndex((/** @type {any} */ a) => typeof a === 'string' && a.startsWith('--auth-token'));
169
171
  if (atIdx !== -1) {
170
172
  const raw = args[atIdx];
171
173
  authToken = raw.includes('=') ? raw.slice(raw.indexOf('=') + 1) : args[atIdx + 1];
@@ -179,14 +181,14 @@ export async function handleWeb(cmd, args) {
179
181
  await runWebServer({ host, port, authToken });
180
182
  } catch (err) {
181
183
  // 质检 C2:listen 现在会 reject(如端口占用),CLI 给出明确提示而非挂起
182
- console.error(`[MingDao] WebUI 启动失败:${err?.message || err}`);
184
+ console.error(`[MingDao] WebUI 启动失败:${(/** @type {any} */ (err))?.message || err}`);
183
185
  process.exitCode = 1;
184
186
  }
185
187
  return true;
186
188
  }
187
189
 
188
190
  // 会话检索:mingdao sessions search <关键词>
189
- export async function handleSessions(cmd, args) {
191
+ export async function handleSessions(/** @type {any} */ cmd, /** @type {any} */ args) {
190
192
  if (args[0] !== 'search') return false;
191
193
  const kw = args.slice(1).join(' ').trim();
192
194
  if (!kw) {
@@ -200,7 +202,7 @@ export async function handleSessions(cmd, args) {
200
202
  else {
201
203
  console.log(`找到 ${hits.length} 个会话:`);
202
204
  for (const h of hits) {
203
- console.log(` ${h.name}(${relativeTime(h.mtime)})\n ${h.snippet}`);
205
+ console.log(` ${h.name}(${relativeTime(h.mtime)})\n ${(/** @type {any} */ (h)).snippet}`);
204
206
  }
205
207
  console.log(`\n恢复:mingdao --resume(选择器中可见全部会话)`);
206
208
  }
@@ -16,13 +16,13 @@ import {
16
16
  resolveSyncConflict,
17
17
  } from '../sync.js';
18
18
 
19
- async function askHidden(question) {
19
+ async function askHidden(/** @type {any} */ question) {
20
20
  return new Promise((resolve) => {
21
21
  // _writeToOutput 为 readline 内部接口:静音回显(密码输入),类型护栏下显式 any
22
22
  const rl = /** @type {any} */ (readline.createInterface({ input: process.stdin, output: process.stdout }));
23
23
  const orig = rl._writeToOutput;
24
24
  rl._writeToOutput = () => {};
25
- rl.question(question, (a) => {
25
+ rl.question(question, (/** @type {any} */ a) => {
26
26
  if (typeof orig === 'function') rl._writeToOutput = orig;
27
27
  rl.close();
28
28
  resolve(a.trim());
@@ -30,7 +30,7 @@ async function askHidden(question) {
30
30
  });
31
31
  }
32
32
 
33
- export async function handleSync(cmd, args) {
33
+ export async function handleSync(/** @type {any} */ cmd, /** @type {any} */ args) {
34
34
  const sub = args[0] || 'status';
35
35
  if (sub === 'login') {
36
36
  const username = args[1];
@@ -191,7 +191,7 @@ export async function handleSync(cmd, args) {
191
191
  return true;
192
192
  }
193
193
  if (sub === 'push') {
194
- const r = await syncPush(args[1]);
194
+ const r = /** @type {any} */ (await syncPush(args[1]));
195
195
  if (r.error) {
196
196
  console.log('[错误] ' + r.error);
197
197
  process.exitCode = 1;
@@ -201,7 +201,7 @@ export async function handleSync(cmd, args) {
201
201
  return true;
202
202
  }
203
203
  if (sub === 'pull') {
204
- const r = await syncPull(args[1]);
204
+ const r = /** @type {any} */ (await syncPull(args[1]));
205
205
  if (r.error) {
206
206
  console.log('[错误] ' + r.error);
207
207
  process.exitCode = 1;
@@ -4,7 +4,7 @@ import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { loadConfig } from '../config.js';
6
6
 
7
- export async function handleUpdateFamily(cmd, args) {
7
+ export async function handleUpdateFamily(/** @type {any} */ cmd, /** @type {any} */ args) {
8
8
  // 价格表外置刷新(Hermes C1):mingdao update --pricing —— 从 cfg.pricing.source 拉取官方价格 JSON
9
9
  if (cmd === 'update' && args.includes('--pricing')) {
10
10
  const cfg = loadConfig();
@@ -17,7 +17,7 @@ export async function handleUpdateFamily(cmd, args) {
17
17
  }
18
18
  // 自更新与回滚(git 安装形态)。
19
19
  // 评估 P3-1 子命令劫持防护:参数不合法时回退为普通提问(如 "mingdao update the docs" 是提问而非更新命令)
20
- if (cmd === 'update' && args.every((a) => a === '--check')) {
20
+ if (cmd === 'update' && args.every((/** @type {any} */ a) => a === '--check')) {
21
21
  const { updateCheck, mingdaoUpdate } = await import('../update.js');
22
22
  const checkOnly = args.includes('--check');
23
23
  const r = await (checkOnly ? updateCheck() : mingdaoUpdate());
@@ -64,7 +64,7 @@ export async function handleUpdateFamily(cmd, args) {
64
64
  const raw = srcFile === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(srcFile, 'utf8');
65
65
  questions = raw.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).slice(0, 1000);
66
66
  } catch (err) {
67
- console.log('[错误] 读取问题文件失败:' + (err?.message || err));
67
+ console.log('[错误] 读取问题文件失败:' + ((/** @type {any} */ (err))?.message || err));
68
68
  process.exitCode = 1;
69
69
  return true;
70
70
  }
@@ -84,7 +84,7 @@ export async function handleUpdateFamily(cmd, args) {
84
84
  maxTokens,
85
85
  maxCost,
86
86
  signal: ac.signal,
87
- onStatus: (st) => console.log(' ' + st),
87
+ onStatus: (/** @type {any} */ st) => console.log(' ' + st),
88
88
  });
89
89
  process.removeAllListeners('SIGINT');
90
90
  if (r.error) {
@@ -92,7 +92,8 @@ export async function handleUpdateFamily(cmd, args) {
92
92
  process.exitCode = 1;
93
93
  return true;
94
94
  }
95
- console.log(`✓ 完成 ${r.results.length} 条 · ↑${r.usage.prompt_tokens} ↓${r.usage.completion_tokens} tokens · 费用 ≈¥${r.cost.toFixed(5)}(已按半价)`);
95
+ const rr = /** @type {any} */ (r);
96
+ console.log(`✓ 完成 ${rr.results.length} 条 · ↑${rr.usage.prompt_tokens} ↓${rr.usage.completion_tokens} tokens · 费用 ≈¥${rr.cost.toFixed(5)}(已按半价)`);
96
97
  if (r.deduped) console.log(` (去重合并 ${r.deduped} 条重复问题,结果已回填全部位置)`);
97
98
  console.log(` 结果文件:${r.outputFile}`);
98
99
  console.log(` 任务 ID:${r.batchId}(结果已计入 /cost 分账)`);
@@ -119,7 +120,7 @@ export async function handleUpdateFamily(cmd, args) {
119
120
  console.log('月度报告导出:mingdao cost report [YYYY-MM|all]');
120
121
  return true;
121
122
  }
122
- const bar = (cost, max) => '█'.repeat(Math.max(1, Math.round((cost / max) * 20)));
123
+ const bar = (/** @type {any} */ cost, /** @type {any} */ max) => '█'.repeat(Math.max(1, Math.round((cost / max) * 20)));
123
124
  const md = [];
124
125
  for (const r of rows) {
125
126
  const maxDay = Math.max(...r.days.map((d) => d.cost), 1e-9);
@@ -3,7 +3,7 @@ import { addWorkspace, listWorkspaces, removeWorkspace, workspacePath, touchWork
3
3
  import { presetList, buildPreset } from '../mcp-presets.js';
4
4
  import { ensureHome, loadConfig, saveConfig } from '../config.js';
5
5
 
6
- export async function handleWorkspace(cmd, args) {
6
+ export async function handleWorkspace(/** @type {any} */ cmd, /** @type {any} */ args) {
7
7
  const sub = args[0] || 'list';
8
8
  const name = args[1];
9
9
  if (sub === 'add') {
@@ -73,7 +73,7 @@ export async function handleWorkspace(cmd, args) {
73
73
  }
74
74
 
75
75
  // MCP 预设:mingdao mcp preset list|add <名称> [参数]
76
- export async function handleMcp(cmd, args) {
76
+ export async function handleMcp(/** @type {any} */ cmd, /** @type {any} */ args) {
77
77
  if (args[0] !== 'preset') return false;
78
78
  const home0 = ensureHome();
79
79
  if (args[1] === 'add') {
package/src/compact.js CHANGED
@@ -23,7 +23,7 @@ const SUMMARY_SYSTEM =
23
23
  '保留用户目标与关键要求、已完成的步骤与结论、修改/创建的文件、未完成事项、重要约定与决策;' +
24
24
  '省略已完成的中间过程与细节。只输出 JSON:{"summary": "摘要内容"}。';
25
25
 
26
- export async function summarizeConversation(provider, model, convoText) {
26
+ export async function summarizeConversation(/** @type {any} */ provider, /** @type {any} */ model, /** @type {any} */ convoText) {
27
27
  const base = {
28
28
  model,
29
29
  messages: [
@@ -54,7 +54,7 @@ export async function summarizeConversation(provider, model, convoText) {
54
54
  return { text: text.slice(0, SUMMARY_MAX_CHARS), usage };
55
55
  }
56
56
 
57
- export async function compactConversation({ messages, budget, count, provider, executorModel, triggerRatio, force = false }) {
57
+ export async function compactConversation(/** @type {any} */ { messages, budget, count, provider, executorModel, triggerRatio, force = false }) {
58
58
  if (!messages.length) return null;
59
59
  // 各消息 token 与总量;低于触发线(默认预算 80%)无需压缩
60
60
  const sizes = [];
@@ -85,14 +85,14 @@ export async function compactConversation({ messages, budget, count, provider, e
85
85
  // 被裁段落 → 文本(工具结果截断;tool_calls 带名称标注)
86
86
  const convoText = messages
87
87
  .slice(1, boundary)
88
- .map((m) => {
88
+ .map((/** @type {any} */ m) => {
89
89
  if (m.role === 'tool') {
90
90
  return `工具结果(${m.tool_call_id ?? ''}): ${clampText(String(m.content ?? ''), TOOL_OUTPUT_CAP)}`;
91
91
  }
92
92
  const head = m.role === 'user' ? '用户' : m.role === 'assistant' ? 'MingDao' : String(m.role);
93
93
  const calls =
94
94
  Array.isArray(m.tool_calls) && m.tool_calls.length
95
- ? ` [调用: ${m.tool_calls.map((tc) => `${tc.function?.name ?? ''}(${String(tc.function?.arguments ?? '').slice(0, 200)})`).join('; ')}]`
95
+ ? ` [调用: ${m.tool_calls.map((/** @type {any} */ tc) => `${tc.function?.name ?? ''}(${String(tc.function?.arguments ?? '').slice(0, 200)})`).join('; ')}]`
96
96
  : '';
97
97
  return `${head}: ${String(m.content ?? '')}${calls}`;
98
98
  })
@@ -102,7 +102,7 @@ export async function compactConversation({ messages, budget, count, provider, e
102
102
  // —— 增量压缩(Kimi P2-D):已压缩过的会话只压「旧摘要之后的新增段」,与旧摘要合并(摘要的摘要),
103
103
  // 不再反复把早期内容送进摘要输入——第 N 次压缩输入从 O(历史) 降为 O(增量)。 ——
104
104
  const sumIdx = messages.findIndex(
105
- (m) => m.role === 'user' && typeof m.content === 'string' && m.content.includes('<conversation_summary>')
105
+ (/** @type {any} */ m) => m.role === 'user' && typeof m.content === 'string' && m.content.includes('<conversation_summary>')
106
106
  );
107
107
  let incremental = false;
108
108
  let summary = null;
@@ -113,7 +113,7 @@ export async function compactConversation({ messages, budget, count, provider, e
113
113
  const oldSummary = (m && m[1]) || '';
114
114
  const newText = messages
115
115
  .slice(sumIdx + 1, boundary)
116
- .map((msg) => {
116
+ .map((/** @type {any} */ msg) => {
117
117
  if (msg.role === 'tool') {
118
118
  return `工具结果(${msg.tool_call_id ?? ''}): ${clampText(String(msg.content ?? ''), TOOL_OUTPUT_CAP)}`;
119
119
  }
package/src/config.js CHANGED
@@ -54,6 +54,7 @@ export function ensureMinimalConfig() {
54
54
  return cfg;
55
55
  }
56
56
 
57
+ /** @param {any} cfg */
57
58
  export function saveConfig(cfg) {
58
59
  ensureHome();
59
60
  const p = configPath();
@@ -63,10 +64,12 @@ export function saveConfig(cfg) {
63
64
  } catch {}
64
65
  }
65
66
 
67
+ /** @param {any} cfg @param {any} providerName */
66
68
  export function effectiveApiKey(cfg, providerName) {
67
69
  return resolveApiKey(cfg, providerName, providerPreset(providerName)?.envKey);
68
70
  }
69
71
 
72
+ /** @param {any} io */
70
73
  export async function runWizard(io) {
71
74
  io.box('MingDao Harness 初始化向导', ['① 选服务商 → ② 填 API Key(自动验证)→ ③ 选模型(可跳过)']);
72
75
  io.print('');
@@ -74,9 +77,9 @@ export async function runWizard(io) {
74
77
  const providerKeys = Object.keys(PROVIDERS);
75
78
  const provider = await io.choose(
76
79
  '① 选择模型服务商:',
77
- providerKeys.map((k) => ({ value: k, label: `${k} — ${PROVIDERS[k].label}` }))
80
+ providerKeys.map((k) => ({ value: k, label: `${k} — ${/** @type {Record<string, any>} */ (PROVIDERS)[k].label}` }))
78
81
  );
79
- const pp = PROVIDERS[provider];
82
+ const pp = /** @type {Record<string, any>} */ (PROVIDERS)[provider];
80
83
 
81
84
  let baseUrl = '';
82
85
  if (provider === 'custom') {
@@ -163,13 +166,13 @@ export async function runWizard(io) {
163
166
  const contextBudget = Number(budgetInput) > 0 ? Number(budgetInput) : defaultBudget;
164
167
 
165
168
  // 注意:config.json 不含任何密钥(可安全分享/提交),密钥只存 credentials.json。
166
- const cfg = {
169
+ const cfg = /** @type {any} */ ({
167
170
  provider,
168
171
  baseUrl: baseUrl || pp.baseUrl,
169
172
  permission: perm,
170
173
  sandbox,
171
174
  contextBudget,
172
- };
175
+ });
173
176
  if (model) cfg.model = model;
174
177
  if (routing) cfg.routing = routing;
175
178
  saveConfig(cfg);
package/src/context.js CHANGED
@@ -6,11 +6,11 @@ import { heuristicTokens } from './tokenizer.js';
6
6
  export const TOOL_RESULT_LIMIT = 20000;
7
7
 
8
8
  // 兼容旧接口的启发式估算(英文≈4 字符/token,CJK≈1 字符/token)
9
- export function approxTokens(text) {
9
+ export function approxTokens(/** @type {any} */ text) {
10
10
  return heuristicTokens(text);
11
11
  }
12
12
 
13
- function partTokens(msg, count) {
13
+ function partTokens(/** @type {any} */ msg, /** @type {any} */ count) {
14
14
  let total = count(msg.content || '');
15
15
  for (const tc of msg.tool_calls || []) {
16
16
  total += count(JSON.stringify(tc));
@@ -24,7 +24,7 @@ function partTokens(msg, count) {
24
24
  // 模型切换(新计数器)后自动失效重算。
25
25
  const messageTokenCache = new WeakMap();
26
26
 
27
- export function messageTokens(msg, count = approxTokens) {
27
+ export function messageTokens(/** @type {any} */ msg, count = approxTokens) {
28
28
  const hit = messageTokenCache.get(msg);
29
29
  if (hit && hit.fn === count) return hit.value;
30
30
  const total = partTokens(msg, count) + 4; // 每条消息的协议开销
@@ -35,20 +35,20 @@ export function messageTokens(msg, count = approxTokens) {
35
35
  // 从尾部向前保留消息,直到预算用尽;始终保留首条 system 消息。
36
36
  // 裁剪可能切断 assistant(tool_calls) ↔ tool 的配对,清洗掉孤立消息,
37
37
  // 否则 OpenAI 兼容 API 会因 tool_call_id 找不到对应调用而报 400。
38
- export function cleanToolPairing(kept) {
38
+ export function cleanToolPairing(/** @type {any} */ kept) {
39
39
  const callIds = new Set();
40
40
  for (const m of kept) {
41
41
  if (m.role === 'assistant' && Array.isArray(m.tool_calls)) {
42
42
  for (const tc of m.tool_calls) if (tc?.id) callIds.add(tc.id);
43
43
  }
44
44
  }
45
- const clean = kept.filter((m) => m.role !== 'tool' || callIds.has(m.tool_call_id));
46
- const toolIds = new Set(clean.filter((m) => m.role === 'tool').map((m) => m.tool_call_id));
45
+ const clean = kept.filter((/** @type {any} */ m) => m.role !== 'tool' || callIds.has(m.tool_call_id));
46
+ const toolIds = new Set(clean.filter((/** @type {any} */ m) => m.role === 'tool').map((/** @type {any} */ m) => m.tool_call_id));
47
47
  // 注意:裁剪是每次请求的投影,绝不能改写调用方 messages 里的原对象
48
48
  // (否则会话历史被永久污染:assistant 消息丢失 tool_calls,下一轮 API 报 400)
49
- return clean.map((m) => {
49
+ return clean.map((/** @type {any} */ m) => {
50
50
  if (m.role === 'assistant' && Array.isArray(m.tool_calls) && m.tool_calls.length) {
51
- const keptCalls = m.tool_calls.filter((tc) => toolIds.has(tc.id));
51
+ const keptCalls = m.tool_calls.filter((/** @type {any} */ tc) => toolIds.has(tc.id));
52
52
  if (keptCalls.length !== m.tool_calls.length) {
53
53
  if (keptCalls.length) return { ...m, tool_calls: keptCalls };
54
54
  const copy = { ...m };
@@ -60,12 +60,12 @@ export function cleanToolPairing(kept) {
60
60
  });
61
61
  }
62
62
 
63
- export function trimMessages(messages, budget, count = approxTokens) {
63
+ export function trimMessages(/** @type {any} */ messages, /** @type {any} */ budget, count = approxTokens) {
64
64
  if (!messages.length) return [];
65
65
  const hasSystem = messages[0]?.role === 'system';
66
66
  const system = hasSystem ? [messages[0]] : [];
67
67
  const rest = hasSystem ? messages.slice(1) : messages;
68
- const totalOf = (arr) => arr.reduce((s, m) => s + messageTokens(m, count), 0);
68
+ const totalOf = (/** @type {any} */ arr) => arr.reduce((/** @type {any} */ s, /** @type {any} */ m) => s + messageTokens(m, count), 0);
69
69
  // —— 轻量语义回收(Hermes B1):预算 >80% 时先对最老消息做无模型压缩 ——
70
70
  // 最老 tool 消息 → 单行摘要;老 assistant 长文 → 前 200 字;逐条向新推进直到回到 80% 线。
71
71
  // 相比直接丢弃,被修改的只有前端旧消息——其后未被触碰的近期尾部字节保持不变,
@@ -104,7 +104,7 @@ export function trimMessages(messages, budget, count = approxTokens) {
104
104
  return cleanToolPairing(system.concat(rest));
105
105
  }
106
106
 
107
- export function clampText(text, maxChars = TOOL_RESULT_LIMIT) {
107
+ export function clampText(/** @type {any} */ text, maxChars = TOOL_RESULT_LIMIT) {
108
108
  const s = String(text);
109
109
  if (s.length <= maxChars) return s;
110
110
  return s.slice(0, maxChars) + `\n…[输出过长已截断,原文共 ${s.length} 字符]`;
@@ -24,6 +24,7 @@ export function loadCredentials() {
24
24
  }
25
25
  }
26
26
 
27
+ /** @param {any} creds */
27
28
  export function saveCredentials(creds) {
28
29
  ensureHome();
29
30
  const p = credentialsPath();
@@ -34,10 +35,12 @@ export function saveCredentials(creds) {
34
35
  } catch {}
35
36
  }
36
37
 
38
+ /** @param {any} providerName */
37
39
  export function getStoredKey(providerName) {
38
40
  return loadCredentials()[providerName] || null;
39
41
  }
40
42
 
43
+ /** @param {any} providerName @param {any} key */
41
44
  export function setStoredKey(providerName, key) {
42
45
  const creds = loadCredentials();
43
46
  if (key) creds[providerName] = String(key);
@@ -45,11 +48,13 @@ export function setStoredKey(providerName, key) {
45
48
  saveCredentials(creds);
46
49
  }
47
50
 
51
+ /** @param {any} providerName */
48
52
  export function removeStoredKey(providerName) {
49
53
  setStoredKey(providerName, null);
50
54
  }
51
55
 
52
56
  // 脱敏展示:只显示首 6 位与末 4 位,永不输出完整 Key。
57
+ /** @param {any} key */
53
58
  export function maskKey(key) {
54
59
  if (!key) return null;
55
60
  const s = String(key);
@@ -58,6 +63,7 @@ export function maskKey(key) {
58
63
  }
59
64
 
60
65
  // 统一的 Key 解析链:环境变量 → 本地凭证库 → config.json 显式字段
66
+ /** @param {any} cfg @param {any} providerName @param {any} envKeyHint */
61
67
  export function resolveApiKey(cfg, providerName, envKeyHint) {
62
68
  const envKeys = [envKeyHint, 'MINGDAO_API_KEY'].filter((k, i, a) => k && a.indexOf(k) === i);
63
69
  for (const k of envKeys) {
package/src/hooks.js CHANGED
@@ -10,12 +10,12 @@
10
10
 
11
11
  import { spawn } from 'node:child_process';
12
12
 
13
- function normalize(list) {
13
+ function normalize(/** @type {any} */ list) {
14
14
  if (!Array.isArray(list)) return [];
15
15
  return list.filter((h) => h && typeof h.cmd === 'string' && h.cmd.trim());
16
16
  }
17
17
 
18
- function match(hook, toolName) {
18
+ function match(/** @type {any} */ hook, /** @type {any} */ toolName) {
19
19
  const m = String(hook.matcher || '*').trim();
20
20
  if (!m || m === '*') return true;
21
21
  return m.split(',').some((part) => {
@@ -25,11 +25,11 @@ function match(hook, toolName) {
25
25
  });
26
26
  }
27
27
 
28
- export function createHooks(hooksCfg = {}, workingDir) {
29
- const pre = normalize(hooksCfg?.PreToolUse);
30
- const post = normalize(hooksCfg?.PostToolUse);
28
+ export function createHooks(hooksCfg = {}, /** @type {any} */ workingDir) {
29
+ const pre = normalize((/** @type {any} */ (hooksCfg))?.PreToolUse);
30
+ const post = normalize((/** @type {any} */ (hooksCfg))?.PostToolUse);
31
31
 
32
- function run(hook, payload) {
32
+ function run(/** @type {any} */ hook, /** @type {any} */ payload) {
33
33
  return new Promise((resolve) => {
34
34
  const child = spawn(hook.cmd, {
35
35
  shell: true,
@@ -40,12 +40,12 @@ export function createHooks(hooksCfg = {}, workingDir) {
40
40
  let out = '';
41
41
  let err = '';
42
42
  const MAX_HOOK_OUT = 64 * 1024;
43
- const cap = (acc, d) => {
43
+ const cap = (/** @type {any} */ acc, /** @type {any} */ d) => {
44
44
  const t = acc + d;
45
45
  return t.length > MAX_HOOK_OUT ? t.slice(-MAX_HOOK_OUT) : t;
46
46
  };
47
47
  let settled = false;
48
- const finish = (result) => {
48
+ const finish = (/** @type {any} */ result) => {
49
49
  if (settled) return;
50
50
  settled = true;
51
51
  resolve(result);
@@ -53,7 +53,7 @@ export function createHooks(hooksCfg = {}, workingDir) {
53
53
  const timer = setTimeout(() => {
54
54
  // 审计质量项:超时杀整组(shell:true 的孙进程不成孤儿)
55
55
  try {
56
- process.kill(-child.pid, 'SIGKILL');
56
+ process.kill(-(/** @type {any} */ (child)).pid, 'SIGKILL');
57
57
  } catch {
58
58
  child.kill('SIGKILL');
59
59
  }
@@ -77,7 +77,7 @@ export function createHooks(hooksCfg = {}, workingDir) {
77
77
  }
78
78
 
79
79
  return {
80
- async pre(toolName, args) {
80
+ async pre(/** @type {any} */ toolName, /** @type {any} */ args) {
81
81
  let decision = 'approve';
82
82
  let reason = '';
83
83
  for (const h of pre) {
@@ -106,7 +106,7 @@ export function createHooks(hooksCfg = {}, workingDir) {
106
106
  }
107
107
  return { decision, reason };
108
108
  },
109
- async post(toolName, args, result) {
109
+ async post(/** @type {any} */ toolName, /** @type {any} */ args, /** @type {any} */ result) {
110
110
  for (const h of post) {
111
111
  if (!match(h, toolName)) continue;
112
112
  try {
@@ -57,22 +57,22 @@ export function presetList() {
57
57
  return Object.entries(MCP_PRESETS).map(([name, p]) => ({
58
58
  name,
59
59
  label: p.label,
60
- argLabel: p.argLabel || null,
60
+ argLabel: (/** @type {any} */ (p)).argLabel || null,
61
61
  args: p.args,
62
62
  command: p.command,
63
63
  }));
64
64
  }
65
65
 
66
- export function buildPreset(name, arg, cwd) {
67
- const p = MCP_PRESETS[name];
66
+ export function buildPreset(/** @type {any} */ name, /** @type {any} */ arg, /** @type {any} */ cwd) {
67
+ const p = /** @type {any} */ (MCP_PRESETS)[name];
68
68
  if (!p) return { error: `未知预设 ${name}(mingdao mcp preset list 查看)` };
69
- if (p.argLabel && !arg) {
69
+ if ((/** @type {any} */ (p)).argLabel && !arg) {
70
70
  if (p.args.includes('{dir}')) {
71
71
  arg = cwd; // 目录类参数默认当前工作目录
72
72
  } else {
73
73
  return { error: `该预设需要参数:${p.argLabel}` };
74
74
  }
75
75
  }
76
- const args = p.args.map((a) => (a === '{dir}' ? arg : a));
76
+ const args = p.args.map((/** @type {any} */ a) => (a === '{dir}' ? arg : a));
77
77
  return { config: { command: p.command, args } };
78
78
  }