mingdao-harness 0.1.54

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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +246 -0
  3. package/assets/tokenizer-data.json.gz +0 -0
  4. package/docs/ARCHITECTURE.md +108 -0
  5. package/docs/CONFIG.md +257 -0
  6. package/docs/DESKTOP-EVALUATION.md +48 -0
  7. package/docs/PROVIDERS.md +98 -0
  8. package/docs/QA-REPORT.md +333 -0
  9. package/install.bat +8 -0
  10. package/install.ps1 +57 -0
  11. package/install.sh +154 -0
  12. package/package.json +61 -0
  13. package/skills/api-design/SKILL.md +21 -0
  14. package/skills/code-review/SKILL.md +31 -0
  15. package/skills/debugging/SKILL.md +21 -0
  16. package/skills/docker/SKILL.md +27 -0
  17. package/skills/docx/SKILL.md +28 -0
  18. package/skills/frontend-design/SKILL.md +31 -0
  19. package/skills/git-commit/SKILL.md +32 -0
  20. package/skills/pdf/SKILL.md +30 -0
  21. package/skills/pptx/SKILL.md +24 -0
  22. package/skills/refactoring/SKILL.md +24 -0
  23. package/skills/release-checklist/SKILL.md +20 -0
  24. package/skills/testing/SKILL.md +30 -0
  25. package/skills/webapp-testing/SKILL.md +27 -0
  26. package/skills/xlsx/SKILL.md +28 -0
  27. package/src/agent.js +472 -0
  28. package/src/audit.js +68 -0
  29. package/src/autostart.js +74 -0
  30. package/src/batch.js +182 -0
  31. package/src/cachestats.js +215 -0
  32. package/src/cli.js +1099 -0
  33. package/src/commands/key.js +75 -0
  34. package/src/commands/schedule.js +176 -0
  35. package/src/commands/skill.js +168 -0
  36. package/src/commands/sync.js +222 -0
  37. package/src/commands/update.js +157 -0
  38. package/src/commands/workspace.js +109 -0
  39. package/src/compact.js +112 -0
  40. package/src/config.js +134 -0
  41. package/src/context.js +86 -0
  42. package/src/cost-guard.js +58 -0
  43. package/src/credentials.js +69 -0
  44. package/src/hooks.js +123 -0
  45. package/src/index.js +42 -0
  46. package/src/mcp-presets.js +78 -0
  47. package/src/mcp.js +284 -0
  48. package/src/memory.js +242 -0
  49. package/src/model-discovery.js +153 -0
  50. package/src/models.js +186 -0
  51. package/src/notify.js +38 -0
  52. package/src/permissions.js +83 -0
  53. package/src/pricing.js +156 -0
  54. package/src/prompts.js +58 -0
  55. package/src/providers/index.js +135 -0
  56. package/src/providers/openai-compatible.js +171 -0
  57. package/src/routing.js +126 -0
  58. package/src/schedule.js +434 -0
  59. package/src/session-index.js +122 -0
  60. package/src/session.js +131 -0
  61. package/src/skill-lib.js +350 -0
  62. package/src/skill-registry.js +165 -0
  63. package/src/skills.js +135 -0
  64. package/src/sync-server.js +564 -0
  65. package/src/sync.js +479 -0
  66. package/src/tasks.js +126 -0
  67. package/src/titles.js +75 -0
  68. package/src/tokenizer.js +287 -0
  69. package/src/tools/bash.js +165 -0
  70. package/src/tools/fs-tools.js +346 -0
  71. package/src/tools/index.js +287 -0
  72. package/src/ui.js +643 -0
  73. package/src/update.js +222 -0
  74. package/src/web/attachments.js +49 -0
  75. package/src/web/index.html +993 -0
  76. package/src/web/server.js +1173 -0
  77. package/src/web/web-io.js +107 -0
  78. package/src/workspace.js +157 -0
@@ -0,0 +1,107 @@
1
+ // WebUI 的 io 适配器:把 Agent 核心使用的 io 接口事件翻译为 SSE 事件(由 server.js 推送)。
2
+ // 权限确认/选择类交互通过 askHandler 转发到浏览器模态框,等待 POST /api/permission 应答。
3
+
4
+ import { estimateCostLabel } from '../pricing.js';
5
+
6
+ export function createWebIO({ send, askHandler, setAbortHandler }) {
7
+ const io = {
8
+ isTTY: false,
9
+ showReasoning: true,
10
+ _pending: null,
11
+ _toolCount: 0,
12
+ _deliverables: [],
13
+ _activeTools: [], // {seq, key}:并行批次下 toolStart↔tool 按 name+args 配对(审计 P2-7)
14
+ // 交付物与步数统计:写/编辑成功的文件路径(去重)
15
+ stats() {
16
+ return { toolCount: io._toolCount, deliverables: [...new Set(io._deliverables)] };
17
+ },
18
+ setShowReasoning(v) {
19
+ io.showReasoning = !!v;
20
+ },
21
+ ensureRl() {
22
+ throw new Error('Web 模式没有终端输入流');
23
+ },
24
+ setHistory() {},
25
+ print(text = '') {
26
+ send({ type: 'banner', text });
27
+ },
28
+ box(title, lines) {
29
+ send({ type: 'banner', title, lines });
30
+ },
31
+ // 每轮生成开始/结束:前端据此显示持续的「正在思考…」动态指示
32
+ beginTurn() {},
33
+ endTurn() {},
34
+ startSpinner() {
35
+ if (!io._turnActive) {
36
+ io._turnActive = true;
37
+ send({ type: 'turnStart' });
38
+ }
39
+ },
40
+ stopSpinner() {
41
+ io._turnActive = false;
42
+ },
43
+ writeText(t) {
44
+ send({ type: 'text', delta: String(t) });
45
+ },
46
+ writeReasoning(t) {
47
+ if (!io.showReasoning) return;
48
+ send({ type: 'reasoning', delta: String(t) });
49
+ },
50
+ printCodeBlock(code, lang) {
51
+ send({ type: 'code', code, lang });
52
+ },
53
+ renderTool(name, args, result, durationMs) {
54
+ io._toolCount += 1;
55
+ if ((name === 'write' || name === 'edit') && args?.path && result && result.ok !== false) {
56
+ io._deliverables.push(String(args.path));
57
+ }
58
+ // 审计 P2-7:按 name+args 找回 toolStart 的 seq(并行批次下各卡片配对正确)
59
+ const key = name + ':' + JSON.stringify(args || {});
60
+ const idx = io._activeTools.findIndex((a) => a.key === key);
61
+ const seq = idx !== -1 ? io._activeTools.splice(idx, 1)[0].seq : (io._toolSeq || 0) + 1;
62
+ send({ type: 'tool', name, args, result, durationMs, seq });
63
+ },
64
+ // 工具开始执行:前端先渲染带旋转状态的卡片,完成后原地更新(seq 配对)
65
+ renderToolStart(name, args) {
66
+ io._toolSeq = (io._toolSeq || 0) + 1;
67
+ io._activeTools.push({ seq: io._toolSeq, key: name + ':' + JSON.stringify(args || {}) });
68
+ send({ type: 'toolStart', name, args, seq: io._toolSeq });
69
+ },
70
+ renderToolDenied(name, args, reason) {
71
+ send({ type: 'toolDenied', name, args, reason: reason || '未授权' });
72
+ },
73
+ renderTodo(todos) {
74
+ send({ type: 'todo', todos });
75
+ },
76
+ printUsageLine({ modelName, usage, durationMs }) {
77
+ send({
78
+ type: 'usage',
79
+ modelName,
80
+ usage,
81
+ durationMs,
82
+ cost: estimateCostLabel(modelName, usage?.prompt_tokens ?? 0, usage?.completion_tokens ?? 0, usage),
83
+ });
84
+ },
85
+ onSigint(fn) {
86
+ if (setAbortHandler) setAbortHandler(fn);
87
+ return () => {
88
+ if (setAbortHandler) setAbortHandler(null);
89
+ };
90
+ },
91
+ ask(question, opts = {}) {
92
+ return askHandler({ question, hidden: Boolean(opts.hidden) });
93
+ },
94
+ async askMultiline(prompt) {
95
+ return askHandler({ question: prompt });
96
+ },
97
+ async confirm(question) {
98
+ const a = await askHandler({ question: question + ' ', confirm: true });
99
+ return /^y(es)?$/i.test(String(a));
100
+ },
101
+ async choose(label, options) {
102
+ return askHandler({ label, options });
103
+ },
104
+ close() {},
105
+ };
106
+ return io;
107
+ }
@@ -0,0 +1,157 @@
1
+ // 工作空间:项目目录注册表(参考 WorkBuddy 的项目组织思路)——
2
+ // 为常做的项目登记名称与目录,一键回到对应项目,配置与记忆随目录(AGENTS.md / .mingdao/skills 等)自然跟随。
3
+ // 注册表:<mingdao-home>/workspaces.json → { "<名称>": { dir, createdAt, lastUsed } }
4
+ // 命令:mingdao workspace add/list/use/path/remove
5
+
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { mingdaoHome, ensureHome } from './config.js';
9
+
10
+ export function workspacesFile() {
11
+ return path.join(mingdaoHome(), 'workspaces.json');
12
+ }
13
+
14
+ export function loadWorkspaces() {
15
+ try {
16
+ const j = JSON.parse(fs.readFileSync(workspacesFile(), 'utf8'));
17
+ return j && typeof j === 'object' ? j : {};
18
+ } catch {
19
+ return {};
20
+ }
21
+ }
22
+
23
+ export function saveWorkspaces(ws) {
24
+ try {
25
+ ensureHome();
26
+ // 原子写:临时文件 + rename,避免崩溃后注册表被冲空
27
+ const target = workspacesFile();
28
+ const tmp = target + '.tmp';
29
+ fs.writeFileSync(tmp, JSON.stringify(ws, null, 2) + '\n');
30
+ fs.renameSync(tmp, target);
31
+ } catch {}
32
+ }
33
+
34
+ export function addWorkspace(name, dir) {
35
+ const key = String(name).trim();
36
+ if (!key) return { error: '名称不能为空' };
37
+ if (/[\\/]/.test(key)) return { error: '名称不能包含路径分隔符' };
38
+ const target = path.resolve(dir || process.cwd());
39
+ if (!fs.existsSync(target)) return { error: `目录不存在:${target}` };
40
+ const ws = loadWorkspaces();
41
+ ws[key] = { dir: target, createdAt: ws[key]?.createdAt || Date.now(), lastUsed: Date.now() };
42
+ saveWorkspaces(ws);
43
+ return { ok: true, name: key, dir: target };
44
+ }
45
+
46
+ export function removeWorkspace(name) {
47
+ const ws = loadWorkspaces();
48
+ if (!ws[name]) return false;
49
+ delete ws[name];
50
+ saveWorkspaces(ws);
51
+ return true;
52
+ }
53
+
54
+ export function renameWorkspace(name, newName) {
55
+ const key = String(newName).trim();
56
+ if (!key) return { error: '新名称不能为空' };
57
+ if (/[\\/]/.test(key)) return { error: '名称不能包含路径分隔符' };
58
+ if (key === name) return { name: key }; // 原样改名:无操作,避免自删条目
59
+ const ws = loadWorkspaces();
60
+ if (!ws[name]) return { error: `工作空间 ${name} 不存在` };
61
+ if (ws[key]) return { error: `名称 ${key} 已存在` };
62
+ ws[key] = { ...ws[name] };
63
+ delete ws[name];
64
+ saveWorkspaces(ws);
65
+ return { ok: true, name: key };
66
+ }
67
+
68
+ // 修改目录:登记同名即可覆盖目录
69
+ export function setWorkspaceDir(name, dir) {
70
+ return addWorkspace(name, dir);
71
+ }
72
+
73
+ export function workspacePath(name) {
74
+ return loadWorkspaces()[name]?.dir || null;
75
+ }
76
+
77
+ export function touchWorkspace(name) {
78
+ const ws = loadWorkspaces();
79
+ if (!ws[name]) return false;
80
+ ws[name].lastUsed = Date.now();
81
+ saveWorkspaces(ws);
82
+ return true;
83
+ }
84
+
85
+ export function listWorkspaces() {
86
+ return Object.entries(loadWorkspaces())
87
+ .map(([name, w]) => ({ name, ...w }))
88
+ .sort((a, b) => (b.lastUsed || 0) - (a.lastUsed || 0));
89
+ }
90
+
91
+ // 当前目录是否已登记(供横幅/提示)
92
+ export function currentWorkspace(cwd) {
93
+ const target = path.resolve(cwd);
94
+ const hit = listWorkspaces().find((w) => path.resolve(w.dir) === target);
95
+ return hit || null;
96
+ }
97
+
98
+ // 按目录反查工作空间名(会话记录的是目录,展示时需要名字)
99
+ export function workspaceForDir(dir) {
100
+ if (!dir) return null;
101
+ const target = path.resolve(dir);
102
+ return listWorkspaces().find((w) => path.resolve(w.dir) === target) || null;
103
+ }
104
+
105
+ // —— 会话级工作空间(P3-4):WebUI 并行任务互不串目录 ——
106
+ // 每个会话记住自己的工作目录;运行中的任务使用会话记录目录,全局切换只影响新会话,
107
+ // 不再有 process.chdir 影响所有运行中任务的全局副作用。
108
+ export function sessionWorkspacesFile() {
109
+ return path.join(mingdaoHome(), 'session-workspaces.json');
110
+ }
111
+
112
+ export function loadSessionWorkspaces() {
113
+ try {
114
+ const j = JSON.parse(fs.readFileSync(sessionWorkspacesFile(), 'utf8'));
115
+ return j && typeof j === 'object' ? j : {};
116
+ } catch {
117
+ return {};
118
+ }
119
+ }
120
+
121
+ export function saveSessionWorkspaces(map) {
122
+ try {
123
+ ensureHome();
124
+ const target = sessionWorkspacesFile();
125
+ const tmp = target + '.tmp';
126
+ fs.writeFileSync(tmp, JSON.stringify(map, null, 2) + '\n', { mode: 0o600 });
127
+ fs.renameSync(tmp, target);
128
+ } catch {}
129
+ }
130
+
131
+ export function getSessionWorkspace(sessionName) {
132
+ return loadSessionWorkspaces()[sessionName]?.dir || null;
133
+ }
134
+
135
+ export function setSessionWorkspace(sessionName, dir, wsName = null) {
136
+ const map = loadSessionWorkspaces();
137
+ map[sessionName] = { dir: path.resolve(dir), name: wsName || workspaceForDir(dir)?.name || null, at: Date.now() };
138
+ saveSessionWorkspaces(map);
139
+ }
140
+
141
+ export function removeSessionWorkspace(sessionName) {
142
+ const map = loadSessionWorkspaces();
143
+ if (!map[sessionName]) return false;
144
+ delete map[sessionName];
145
+ saveSessionWorkspaces(map);
146
+ return true;
147
+ }
148
+
149
+ // 会话改名时迁移映射(记录保留)
150
+ export function moveSessionWorkspace(oldName, newName) {
151
+ const map = loadSessionWorkspaces();
152
+ if (!map[oldName]) return false;
153
+ map[newName] = map[oldName];
154
+ delete map[oldName];
155
+ saveSessionWorkspaces(map);
156
+ return true;
157
+ }