mingdao-harness 0.3.2 → 0.4.1
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 +27 -2
- package/docs/DEVELOPER.md +126 -0
- package/docs/PLAN-v0.4.0.md +38 -0
- package/docs/PLAN-v0.4.1.md +65 -0
- package/docs/STRATEGY-NEXT.md +175 -0
- package/package.json +2 -1
- package/presets/local-audit.json +10 -0
- package/src/agent.js +60 -12
- package/src/batch.js +4 -3
- package/src/cli.js +45 -6
- package/src/commands/diagnose.js +15 -0
- package/src/commands/repl.js +50 -5
- package/src/context.js +3 -3
- package/src/cost-guard.js +3 -1
- package/src/index.js +30 -4
- package/src/mcp.js +7 -2
- package/src/memory.js +5 -1
- package/src/permissions.js +4 -2
- package/src/presets.js +175 -0
- package/src/prompts.js +5 -2
- package/src/routing.js +5 -1
- package/src/tasks/worker.js +3 -0
- package/src/tools/bash.js +10 -2
- package/src/tools/fetch.js +34 -2
- package/src/tools/fs-tools.js +66 -10
- package/src/tools/git.js +10 -2
- package/src/tools/index.js +145 -1
- package/src/web/app.js +10 -1
- package/src/web/index.html +4 -1
- package/src/web/routes/domains/misc.js +8 -0
- package/src/web/server.js +36 -4
package/docs/CONFIG.md
CHANGED
|
@@ -111,8 +111,21 @@ WebUI 中每个会话记住自己的工作目录:新会话记录创建时的
|
|
|
111
111
|
}
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
格式与 Claude Code 相同;工具以 `mcp__<服务器>__<工具>` 并入 Agent
|
|
115
|
-
|
|
114
|
+
格式与 Claude Code 相同;工具以 `mcp__<服务器>__<工具>` 并入 Agent 循环。
|
|
115
|
+
|
|
116
|
+
- **只读自动放行需显式授信(v0.4.1 P0)**:带 `readOnlyHint` 的工具默认**不再**自动放行——
|
|
117
|
+
服务器可谎称只读绕过权限确认。只有加了 `"trusted": true` 的服务器,其 `readOnlyHint` 才被
|
|
118
|
+
信任(只读档自动放行、ask 档不询问);未授信服务器的全部工具(含标注只读的)都走权限确认。
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"mcpServers": {
|
|
123
|
+
"filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/你的/目录"], "trusted": true }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
会话内 `/mcp` 查看状态;`mingdao mcp preset list/add` 一键接入常用服务器。
|
|
116
129
|
|
|
117
130
|
## WebUI 服务器
|
|
118
131
|
|
|
@@ -253,6 +266,18 @@ completion 计费,防止推理吃满上限时空轮白烧)、`compactTrigger
|
|
|
253
266
|
{ "timeout": { "firstTokenMs": 600000, "streamIdleMs": 120000, "totalMs": 1800000 } }
|
|
254
267
|
```
|
|
255
268
|
|
|
269
|
+
### 文件访问边界(v0.4.1 P0 路径穿越防护)
|
|
270
|
+
|
|
271
|
+
read/write/edit/ls/glob/grep/undo 默认限定在**工作目录**内——auto 权限模式下,模型(或被提示注入
|
|
272
|
+
诱导)也无法读 `~/.ssh`、`~/.mingdao/credentials.json`(API Key 明文)等越界文件;`realpath` 逐级
|
|
273
|
+
校验防软链接逃逸。需要访问工作目录外时,显式加白名单:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{ "fsAllowDirs": ["/home/you/projects/shared", "/tmp/build-output"] }
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
白名单目录同样受 `realpath` 校验(目录内的软链接指向白名单外仍拒绝)。
|
|
280
|
+
|
|
256
281
|
## 自定义 Provider 模块(非 OpenAI 兼容协议)
|
|
257
282
|
|
|
258
283
|
在 `~/.mingdao/providers/<name>.mjs` 导出:
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# MingDao Harness 开发者指南(v0.4.0 契约化)
|
|
2
|
+
|
|
3
|
+
> 战略依据:[STRATEGY-NEXT.md](STRATEGY-NEXT.md)(垂直产品 × 开放内核)。
|
|
4
|
+
> 本指南面向**用 MingDao 做二次开发 / 定制自己智能体**的开发者。
|
|
5
|
+
> 稳定契约:`@stable` 导出在 minor 版本内保持向后兼容;`@experimental` 可能调整。
|
|
6
|
+
|
|
7
|
+
## 零、三种使用方式
|
|
8
|
+
|
|
9
|
+
| 方式 | 适合 | 命令 |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| 产品终端 | 开箱即用的 DeepSeek 省钱 Coding Agent | `npm i -g mingdao-harness && mingdao` |
|
|
12
|
+
| **Agent Preset** | 不改代码,声明式定制智能体(本指南重点) | `mingdao --preset <名>` |
|
|
13
|
+
| **库嵌入** | 把 Agent 嵌进自己的 Node 程序 | `npm i mingdao-harness` + `import { createAgent } from 'mingdao-harness'` |
|
|
14
|
+
|
|
15
|
+
零依赖承诺:安装无 node_modules 树;公共 API 只用 Node ≥18.17 内置能力。
|
|
16
|
+
|
|
17
|
+
## 一、Agent Preset:声明式定制智能体
|
|
18
|
+
|
|
19
|
+
### 1.1 什么是预设
|
|
20
|
+
|
|
21
|
+
一个 JSON 文件 = { 系统提示定制段, 工具白名单, 权限模式, 模型建议, 参数 }。
|
|
22
|
+
放在三个位置(同名后者遮蔽前者):
|
|
23
|
+
|
|
24
|
+
1. `<项目>/.mingdao/presets/<名>.json` — 项目级(随项目走)
|
|
25
|
+
2. `~/.mingdao/presets/<名>.json` — 用户级(本机全局)
|
|
26
|
+
3. `presets/`(随 npm 包分发)— 内置参考(已内置 `local-audit` 示例)
|
|
27
|
+
|
|
28
|
+
### 1.2 格式
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"name": "code-reviewer",
|
|
33
|
+
"label": "代码审查员",
|
|
34
|
+
"description": "只读审查并输出分级报告",
|
|
35
|
+
"systemPrompt": "你是代码审查员。只读审查,按严重度分级输出,每条带文件:行号证据。",
|
|
36
|
+
"tools": ["read", "ls", "glob", "grep", "skill", "git", "fetch", "todo"],
|
|
37
|
+
"permission": "auto",
|
|
38
|
+
"model": "deepseek-v4-flash",
|
|
39
|
+
"temperature": 0.3,
|
|
40
|
+
"maxOutputTokens": 4096,
|
|
41
|
+
"maxRounds": 4,
|
|
42
|
+
"contextBudget": 96000
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
字段全部可选(缺省保持当前配置)。`tools` 白名单外的工具对模型不可见、调用会被硬拦。
|
|
47
|
+
未知字段会**校验报错**(防拼写错误静默失效)。
|
|
48
|
+
`model` 是**建议**:CLI 在未显式 `-m` 时采纳;WebUI 以用户当前选择的模型为准(预设不覆盖)。
|
|
49
|
+
|
|
50
|
+
### 1.3 使用
|
|
51
|
+
|
|
52
|
+
- CLI:`mingdao --preset code-reviewer "审查 src/ 目录"`;交互模式 `mingdao --preset code-reviewer`。
|
|
53
|
+
- REPL:`/preset` 列出全部;`/preset code-reviewer` 会话内切换(工具白名单/权限/参数即时生效)。
|
|
54
|
+
- WebUI:输入框旁「预设…」下拉选择(随本次发送生效,服务端按会话应用)。
|
|
55
|
+
- 程序化:`import { loadPreset, presetConfigOverrides, presetSystemBlock } from 'mingdao-harness'`。
|
|
56
|
+
|
|
57
|
+
## 二、第三方工具:registerTool / config.tools
|
|
58
|
+
|
|
59
|
+
### 2.1 程序化注册(嵌入自己程序时)
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import { registerTool, createAgent } from 'mingdao-harness';
|
|
63
|
+
|
|
64
|
+
registerTool({
|
|
65
|
+
name: 'weather',
|
|
66
|
+
description: '查询城市天气',
|
|
67
|
+
parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
|
|
68
|
+
run: async (args, ctx) => ({ ok: true, output: `${args.city}:晴 24°C` }),
|
|
69
|
+
});
|
|
70
|
+
// 之后 createAgent 的模型就能调用 weather;执行走统一权限/审计/省钱链路。
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
约束:名字 `[A-Za-z0-9][A-Za-z0-9_-]{0,63}`、不得与内置 13 工具同名、不得重复注册;
|
|
74
|
+
`run` 抛异常会转成结构化错误回填(不中断会话)。
|
|
75
|
+
|
|
76
|
+
### 2.2 声明式挂载(config.json,不改代码)
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{ "tools": [ { "name": "date-now", "description": "当前时间", "command": "date" } ] }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`command` 经 `/bin/bash -lc` 执行;**参数以 `MINGDAO_TOOL_ARGS`(JSON)环境变量传入**——
|
|
83
|
+
不做字符串拼接(防注入),由命令自行解析;执行受权限引擎门控(与 bash 同权重)。
|
|
84
|
+
改 config.tools 需重启生效(与 MCP 预设一致)。
|
|
85
|
+
|
|
86
|
+
## 三、库嵌入:最小示例
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
import { createProvider, createAgent, createPermission, createIO } from 'mingdao-harness';
|
|
90
|
+
|
|
91
|
+
const provider = await createProvider(cfg, 'deepseek-v4-flash'); // cfg: 同 config.json 结构
|
|
92
|
+
const io = createIO(); // 或自实现 print/ask 接口
|
|
93
|
+
const agent = createAgent({
|
|
94
|
+
provider,
|
|
95
|
+
permission: createPermission('ask', io),
|
|
96
|
+
io,
|
|
97
|
+
modelName: 'deepseek-v4-flash',
|
|
98
|
+
workingDir: process.cwd(),
|
|
99
|
+
cfg,
|
|
100
|
+
});
|
|
101
|
+
const res = await agent.runTurn([
|
|
102
|
+
{ role: 'system', content: '你是代码助手。' },
|
|
103
|
+
{ role: 'user', content: '帮我看看 package.json 的依赖' },
|
|
104
|
+
]);
|
|
105
|
+
console.log(res.text, res.usage, res.perf);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 四、公共 API 速查(@stable 面)
|
|
109
|
+
|
|
110
|
+
| 分组 | 导出 |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| Agent 内核 | `createAgent` · `createPermission` · `createIO` |
|
|
113
|
+
| Provider/模型 | `createProvider` · `resolveProviderConfig` · `modelPreset` · `resolveModelCaps` · `safeBudget` · `isLocalBaseUrl` |
|
|
114
|
+
| 工具 | `registerTool` · `listRegisteredTools` · `mountConfigTools` · `buildToolSchemas` · `dispatch` |
|
|
115
|
+
| Agent Preset | `listPresets` · `loadPreset` · `validatePreset` · `presetConfigOverrides` · `presetSystemBlock` |
|
|
116
|
+
| 上下文 | `trimMessages` · `approxTokens` · `clampText` · `compactConversation` |
|
|
117
|
+
| 配置/凭证 | `loadConfig` · `saveConfig` · `mingdaoHome` · `setStoredKey` · `maskKey` |
|
|
118
|
+
| 计价/计量 | `estimateCost` · `isPeakHour` · `countTokens` · `makeTokenCounter` |
|
|
119
|
+
|
|
120
|
+
@experimental(接口可能调整):update/audit/skill-lib/skills/mcp/session 组。
|
|
121
|
+
|
|
122
|
+
## 五、约定
|
|
123
|
+
|
|
124
|
+
- 预设/工具的扩展点沿用既有安全链路(权限引擎、审计、脱敏、沙箱),**不提供绕过入口**。
|
|
125
|
+
- 公共 API 变更必须过测试门禁(smoke 含「公共 API 导出面」断言)+ 发布前自检。
|
|
126
|
+
- 自定义 Provider 模块(非 OpenAI 兼容协议)见 [PROVIDERS.md](PROVIDERS.md)。
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# v0.4.0 规划:阶段 A「契约化」——把 MingDao 变成可二次开发的开放内核
|
|
2
|
+
|
|
3
|
+
> 依据:docs/STRATEGY-NEXT.md(已确认:垂直产品 × 开放内核;本地/私有化第一公民与 DeepSeek 省钱并列双主攻;v0.4.0 先做契约化)。
|
|
4
|
+
> 原则:不新增重架构、不动零依赖根基;把**已存在**的扩展点从「能用」升级为「有契约、有文档、有示例、有测试」的一等公民。
|
|
5
|
+
> 节奏:小版本分批,每批全绿 + 发布前自检;实现后不发布,等用户在 3820 验收确认。
|
|
6
|
+
|
|
7
|
+
## 一、现状盘点(扩展点已存在,缺契约)
|
|
8
|
+
|
|
9
|
+
- 公共 API 面:`src/index.js` 已导出 `createAgent` / `createProvider` / 工具 / 上下文 / 权限 / 模型 / 会话 / 计价 / tokenizer 等(42 行导出,无 semver 承诺、无开发者文档)。
|
|
10
|
+
- 扩展点:自定义 Provider 模块(`<home>/providers/*.mjs`)、hooks(PreToolUse/PostToolUse)、用户级/项目级技能、MCP 客户端、`customModels`——全部可用,但契约散落、无统一示例。
|
|
11
|
+
|
|
12
|
+
## 二、本版目标
|
|
13
|
+
|
|
14
|
+
1. **公共 API 冻结与文档**:`docs/DEVELOPER.md`(API 参考 + 最小示例集),`src/index.js` 导出面逐项标注稳定性(stable / experimental)。
|
|
15
|
+
2. **Agent Preset(智能体预设)**:声明式文件 = { 系统提示, 工具集白名单, 权限策略, 记忆策略, 模型建议 },放 `<home>/presets/` 或 `<项目>/.mingdao/presets/`;CLI(`mingdao --preset <名>`)与 WebUI 一键选用。
|
|
16
|
+
3. **第三方工具注册**:`registerTool({ name, schema, run })` 程序化注册 + `config.tools` 声明式挂载,工具进审计/权限/省钱 schema 链路。
|
|
17
|
+
4. **契约测试**:预设加载、工具注册、公共 API 面的自动化断言入 smoke。
|
|
18
|
+
|
|
19
|
+
## 三、任务分解(实现顺序)
|
|
20
|
+
|
|
21
|
+
1. `src/presets.js`(新):预设目录发现/加载/校验(JSON,schema 校验 + 错误可读);内置一个示例预设(如「本地模型审计」:system 提示 + 只读工具集 + auto 权限 + 语义记忆)。
|
|
22
|
+
2. `src/index.js`:导出 `loadPresets`/`resolvePreset`/`buildPresetConfig`;导出稳定性注释分 stable/experimental 两组。
|
|
23
|
+
3. `src/tools/index.js`:`registerTool` + `listRegisteredTools`;`config.tools` 声明式工具挂载(name/schema/command 三态:内置函数引用 / bash 命令包装 / 自定义 Provider 函数);注入 dispatch + 权限引擎 + 审计。
|
|
24
|
+
4. CLI:`mingdao --preset <名>` 参数 + REPL `/preset`;WebUI 模型选择器旁加预设下拉(复用 /api/models-config 模式新增 /api/presets)。
|
|
25
|
+
5. `docs/DEVELOPER.md`:API 参考表(stable/experimental 标注)+ 5 个最小示例(自定义工具 / 自定义 Provider / 自定义权限 / 自定义记忆 / embed 进自己的程序)。
|
|
26
|
+
6. 测试:smoke 新增「presets:发现/校验/应用」「第三方工具:注册/调度/审计归属/权限拦截」「公共 API:导出面断言」;api-contracts 新增 /api/presets 契约。
|
|
27
|
+
7. 全绿(smoke/e2e/bench/strict/typecheck)+ 自检 → 3820 验收 → 等确认后发布。
|
|
28
|
+
|
|
29
|
+
## 四、验收标准
|
|
30
|
+
|
|
31
|
+
- `npm i mingdao-harness` 后按 DEVELOPER.md 三步跑通一个自定义智能体(预设 + 自定义工具)。
|
|
32
|
+
- 预设文件语义与 DSH profile 对齐但零依赖、JSON 声明式。
|
|
33
|
+
- 全绿测试门禁 + strict 0/0 + 发布前自检;用户在 3820 验收后再发布。
|
|
34
|
+
|
|
35
|
+
## 五、非目标(顺延后续版本)
|
|
36
|
+
|
|
37
|
+
- 预设市场/registry 分发(阶段 B)、本地模型一键接入预设(阶段 B)、企业私有化包(阶段 C)。
|
|
38
|
+
- 不引入 Cordis/插件内核;不做云平台/账号体系。
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# v0.4.1 规划:安全版本(偿还 v0.4.0 审计安全债)
|
|
2
|
+
|
|
3
|
+
> 依据:`MingDao Harness v0.4.0 完整审计报告.docx`(2026-09-05,源码通读 + 4 项实测复现)。
|
|
4
|
+
> 定调:**暂停新功能,先清零 P0 安全债**——一个能自动执行 shell 的 agent,安全边界优先级高于任何新特性。
|
|
5
|
+
> 纪律:实现后不单独发布,与 v0.4.1 一起发布;等用户在 3820 验收后再发。
|
|
6
|
+
|
|
7
|
+
## 已修复(本批次)
|
|
8
|
+
|
|
9
|
+
### P0 安全(5 项)
|
|
10
|
+
1. **路径穿越防护**(fs-tools.js):read/write/edit/ls/glob/grep/undo 限定工作目录 + `config.fsAllowDirs`
|
|
11
|
+
白名单;`realpath` 逐级校验防软链接逃逸。auto 模式也无法读 ~/.ssh、credentials.json。
|
|
12
|
+
2. **SSRF 302 重定向绕过**(fetch.js):`redirect:'manual'` 手动跟随,每跳重新 isPrivateHost + DNS 复检,
|
|
13
|
+
跳数上限 5。
|
|
14
|
+
3. **权限前缀规则绕过**(permissions.js):黑名单改白名单字符 `[A-Za-z0-9_ ./\\:-]`,单 `&`、重定向
|
|
15
|
+
`< >`、回车 `\r` 及一切 shell 元字符都不匹配前缀规则,回落权限确认。
|
|
16
|
+
4. **预设 permission 提权**(presets.js):新增 `presetPermissionOverride`——预设声明 permission 若
|
|
17
|
+
比当前更宽松(ask→auto 等)则忽略并提示;CLI/REPL/WebUI 三入口统一接入。
|
|
18
|
+
5. **MCP 权限收紧**(mcp.js):`readOnlyHint` 自动放行改为「仅 `mcpServers.<name>.trusted:true` 才信任」,
|
|
19
|
+
未授信服务器谎报只读也走权限确认。
|
|
20
|
+
|
|
21
|
+
### P1 正确性(6 项)
|
|
22
|
+
6. **turnToolCache 跨步去重失效**(agent.js):声明从 while 体内提到 for 轮内——去重跨步生效。
|
|
23
|
+
7. **windowPressure 永不复位**(agent.js):压缩成功后复位,不再每轮 force 压缩持续烧钱。
|
|
24
|
+
8. **mountConfigTools 阻塞事件循环**(tools/index.js):spawnSync → 异步 spawn + Promise。
|
|
25
|
+
9. **batch.js 未跟进 model-caps**:超窗口预检改用 `resolveModelCaps(cfg, model).contextWindow`。
|
|
26
|
+
10. **语义检索无匹配记忆全丢**(memory.js):无共同词时回退最近 N 条(相关性失败 ≠ 记忆不存在)。
|
|
27
|
+
11. **沙箱探测缺陷**(bash.js):`detectSandbox` 从 `--version` 改为最小真实沙箱探测
|
|
28
|
+
(`bwrap --ro-bind / / --tmpfs /tmp true`),容器内 bwrap 不可用则降级 none。
|
|
29
|
+
|
|
30
|
+
### P2 质量(6 项)
|
|
31
|
+
12. 正则笔误 `/^f[c d]/` → `/^f[cd]/`(fetch.js + server.js)。
|
|
32
|
+
13. git.js log/diff 限量实现(log 默认 -n 50、diff 默认 --stat),修注释与实现不符。
|
|
33
|
+
14. cost-guard.js `todayCostWarned` TDZ 前置。
|
|
34
|
+
15. registerTool 增 `readOnly` 选项(进 READONLY_TOOLS,只读档自动放行)。
|
|
35
|
+
16. context.js 幂等守卫死代码移除(回收结果从不回写原 messages,守卫恒 false)。
|
|
36
|
+
17. agent.js reasoning 清洗 O(n²) → 单次遍历 O(n)。
|
|
37
|
+
|
|
38
|
+
### macOS 本地模型「输出截断 / 子代理无反馈」(追加修复,2026-09-05)
|
|
39
|
+
|
|
40
|
+
用户 MacBook 用 mtplx-qwen38-27b(routing.enabled=true)复现,日志 `text=0` 且 `status=done`。两个根因:
|
|
41
|
+
|
|
42
|
+
18. **子代理无反馈**(routing.js):`subagentModel` 在 routing 开启时恒返回 executor
|
|
43
|
+
(deepseek-v4-flash),无 routeTask 的「池外不干预」检查——本地/自定义模型派子代理时,把
|
|
44
|
+
executor 模型名发到本地 baseUrl(8081)→ 服务端不认识 → 400 → 子代理全灭。修复:池外跟随当前模型。
|
|
45
|
+
19. **输出截断/text=0**(agent.js):兜底总结请求此前用 `trimMessages(messages, budget)` 全量历史,
|
|
46
|
+
本地 q8 模型 ≈98k token 的 prefill 逼近/超过 600s 首 token 超时 → 总结请求失败被 `catch {}` 静默吞 →
|
|
47
|
+
返回 text:null。修复:兜底总结改用轻量输入(system + 交付物清单 + 提示,几 k token),慢 prefill
|
|
48
|
+
也能秒出总结;失败不再静默(io.print 提示原因)。
|
|
49
|
+
20. 子代理空输出透出 note 原因(agent.js):不再笼统「(子任务无输出)」,主线程可据此决策。
|
|
50
|
+
21. diagnose 报告新增「当前模型能力」段:本地模型未声明 contextWindow 时显式提示兜底 32k 及其后果
|
|
51
|
+
(输出/预算被压缩),帮用户自诊。
|
|
52
|
+
|
|
53
|
+
## 顺延(明确记录,不强行塞入本批次)
|
|
54
|
+
|
|
55
|
+
- **WebUI 速率限制**:本地优先工具默认回环绑定 + token 认证已挡远程;已有 `MAX_CONCURRENT=8`
|
|
56
|
+
并发上限 + `costGuard` 日费用上限双保险。令牌桶的维度(IP/会话/token)与阈值需产品决策,
|
|
57
|
+
待 v0.4.2 单独设计。
|
|
58
|
+
- **容器环境 CI job**:审计建议加 container 矩阵覆盖 bwrap 不可用路径(沙箱探测修复已从代码层
|
|
59
|
+
缓解,CI 覆盖作为后续工程项)。
|
|
60
|
+
- **省钱机制有效性自检**(/cost 展示各机制实际节省额):依赖更细的分账埋点,顺延。
|
|
61
|
+
|
|
62
|
+
## 验收
|
|
63
|
+
|
|
64
|
+
- smoke 新增路径穿越/SSRF 重定向/权限绕过/预设提权/MCP trusted/语义检索回退等回归断言。
|
|
65
|
+
- 全绿门禁 + strict 0/0 + typecheck;发布前自检;用户 3820 验收后再随 v0.4.1 发布。
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# MingDao Harness 战略复盘与长期路线(2026-09)
|
|
2
|
+
|
|
3
|
+
> 成文:2026-09-05(v0.3.2 发布后)
|
|
4
|
+
> 性质:战略策划文件,非迭代任务。路线图各阶段**经确认后**才逐步落地;
|
|
5
|
+
> 每期迭代前先对照本文件确认方向不漂移,每期保持「可独立发布 + 全绿测试门禁 + 发布前自检」纪律。
|
|
6
|
+
> 数据口径:本地仓库 git 历史 + 官网线上状态(2026-09-05 实查)。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 一、回望:这 18 天我们走了什么路
|
|
11
|
+
|
|
12
|
+
**时间轴**:v0.1.0(2026-08-18)→ v0.3.2(2026-09-05),18 天 83 个版本,平均 4.6 版/天。
|
|
13
|
+
|
|
14
|
+
**沉淀下来的资产(按可验证程度排序):**
|
|
15
|
+
|
|
16
|
+
| 资产 | 事实 | 性质 |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| 零依赖内核 | `src/` 73 文件 ~15K 行纯 Node 内置 API,无运行时 npm 依赖;`npm i -g` 秒装 | 分发/审计成本极低,同赛道稀缺 |
|
|
19
|
+
| DeepSeek 省钱纵深 | 官方词表 BPE tokenizer(黄金值精确命中)· 前缀缓存两态冻结 · 峰谷双窗口计价 · Batch 半价 · 滞回压缩 · bench-savings 省 63% 基线 | 对 DeepSeek 计费细节的闭环掌握,通用框架不会做 |
|
|
20
|
+
| 长程任务能力 | 任务续跑 + 检查点(v0.3.0)· 自动续跑(v0.3.1)· 项目级记忆 · 子代理 | 从「24 步必断」到「长任务连续执行」 |
|
|
21
|
+
| 本地模型自适应 | 上下文窗口感知预算 + 分层超时 + 边缘强制压缩 + 工具输出自适应截断(v0.3.2) | 大厂押注云端模型,这是空档 |
|
|
22
|
+
| 工程可信度 | strict 棘轮 0/0 · 覆盖率 60% · bench 208 断言 · 三平台 CI · 每次发布自检 | 信任是独立开发者最稀缺的护城河 |
|
|
23
|
+
| 中国分发链路 | 官网直连下载(国内极速)+ gitee/gitcode 镜像 + 论坛 + 桌面自动更新 | 大厂不覆盖的「国内可用性」细节 |
|
|
24
|
+
| 双界面 | TUI + WebUI + Electron 桌面(四平台签名包) | 使用面完整 |
|
|
25
|
+
|
|
26
|
+
**结论**:我们走的其实是「**垂直产品路线**」——围绕 DeepSeek 省钱 + Coding 场景做深,每一版都在强化这个垂直切面。这条路是对的,但它有天花板:产品只能覆盖我们自己能想得到的场景,生态的想象空间被我们自己框死。
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 二、抬头看路:2026-09 的格局(外部实查)
|
|
31
|
+
|
|
32
|
+
| 玩家 | 模式 | 事实(来源) |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| **DSH(DeepSeek Harness)** | 官方智能体框架 | 2026-08-13 开源公测,MIT 协议,基于 Cordis「一切皆插件」,对标 Claude Cowork/Codex;插件生态同步开放;`npx @deepseek-ai/dsh web`;四种模式(标准/PTC/极简/创造)。**Model+Harness=Agent** 是官方公式。([IT之家](https://www.ithome.com/0/989/446.htm)) |
|
|
35
|
+
| **WorkBuddy(腾讯)** | 通用 Agent 平台 | 2026-03 上线,2026-09-02 开放平台:首批 100+ 伙伴,打通硬件/行业应用/开发者三层生态(Skill/Expert/Connector,MCP+CLI 双方案);目标「Agent 时代的操作系统」。([IT168](https://cloud.it168.com/a2026/0902/6948/000006948500.shtml)) |
|
|
36
|
+
| **Claude Cowork** | 企业级 Agent+插件 | Anthropic 官方,企业定制插件,团队级工作流。([claude.com](https://claude.com/ko-kr/blog/cowork-plugins-across-enterprise)) |
|
|
37
|
+
| **Claude Code / OpenHands** | 通用 Coding Agent | Claude Code $17/月托管;OpenHands BYOK 开源。([theaiagentindex](https://theaiagentindex.com/compare/openhands-vs-claude-code)) |
|
|
38
|
+
| **元脑 Z3 等** | 本地智能体工作站 | 浪潮等硬件厂商做「中小企业本地智能体」一体机。([InfoQ](https://www.infoq.cn/article/yi0mr2CkzQZgljL00kgA)) |
|
|
39
|
+
|
|
40
|
+
**格局读法**:
|
|
41
|
+
|
|
42
|
+
1. **框架生态位已被官方拿走**。DSH 背靠 DeepSeek 官方 + MIT 开源 + Cordis 插件生态,任何第三方去拼「谁的框架更框架」都是正面撞车。
|
|
43
|
+
2. **平台生态位被巨头拿走**。WorkBuddy 的开放平台是腾讯体量的资源战(硬件联名 + 行业伙伴 + 开发者三层),不可复制。
|
|
44
|
+
3. **本地/私有化开始有玩家**。元脑 Z3 证明「中小企业本地智能体」是真需求,但它卖的是硬件一体机,软件层依然是空档。
|
|
45
|
+
4. **独立开发者仍有机会的缝隙**:模型中立、零依赖、私有化、资源受限设备、中国本地分发——这些都是巨头和官方框架不优先的地方。
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 三、路线抉择:DSH 框架模式 or WorkBuddy 通用 Agent 模式?
|
|
50
|
+
|
|
51
|
+
### 3.1 两条路的本质与代价
|
|
52
|
+
|
|
53
|
+
**纯 DSH 框架模式**(开放插件内核,开发者二次开发):
|
|
54
|
+
- 代价:需要 Cordis 级插件内核(与零依赖冲突)、插件生态冷启动(框架没人用就死)、与官方 DSH 正面撞车。
|
|
55
|
+
- 我们没有 DeepSeek 官方的品牌和流量,无法复制。
|
|
56
|
+
|
|
57
|
+
**纯 WorkBuddy 模式**(通用 Agent + 开放平台):
|
|
58
|
+
- 代价:需要腾讯级的硬件/行业资源、平台治理、商业化能力;通用办公场景与 WorkBuddy/讯飞等正面交锋。
|
|
59
|
+
- 我们既没有资源,也不应该丢下已经做深的 Coding + DeepSeek 垂直面。
|
|
60
|
+
|
|
61
|
+
### 3.2 结论:不做「二选一」,做「垂直产品 × 开放内核」一体两面
|
|
62
|
+
|
|
63
|
+
**MingDao 的路 = 保持垂直产品的深度(DeepSeek 省钱 Coding Agent),同时把内核开放出来(让第三方开发者定制自己的智能体)。**
|
|
64
|
+
|
|
65
|
+
理由(各自独立成立):
|
|
66
|
+
|
|
67
|
+
1. **产品侧**:我们已经用 83 个版本证明了「DeepSeek 重度用户 + 省钱 + 省心」这条垂直产品线成立(官网已上线、桌面分发、真实用户验收)。丢垂直面去追平台 = 自废武功。
|
|
68
|
+
2. **内核侧**:`src/index.js` 已经导出了完整公共 API 面(`createAgent` / `createProvider` / 工具 / 上下文 / 权限 / 会话 / 计价 / tokenizer,42 行导出),扩展点(自定义 Provider 模块、hooks、用户级技能、MCP)也已存在——**我们其实已经是「半个框架」了,缺的是把扩展点从「能用」变成「有契约、有文档、有示例、有分发」的一等公民**。
|
|
69
|
+
3. **差异化恰好落在巨头缝隙**:DSH 要 pnpm install + build + Cordis 概念负担;WorkBuddy 是云端平台。我们给开发者的是「**零依赖、一条 npm 命令、本地/私有化第一公民、模型中立**」的定制基座——这是官方框架和巨头平台都不给的位置。
|
|
70
|
+
|
|
71
|
+
### 3.3 是否开放接口?——开放,但要按我们的方式开放
|
|
72
|
+
|
|
73
|
+
- **开放什么**:智能体预设(Agent Preset:系统提示 + 工具集 + 权限策略 + 记忆策略打包成一个可安装单元)、程序化工具注册(第三方工具进注册表)、hooks、Provider、技能——这些点已存在,v0.4.x 起把它们正式化为**有契约的公共 API**(semver 保证,example 仓库,文档站)。
|
|
74
|
+
- **不开放什么(至少现在)**:不引入 Cordis/重插件内核(保住零依赖)、不做云平台账号体系、不做插件商店抽成商业化。
|
|
75
|
+
- **开放的方式**:`npm i mingdao-harness` 当库用 + `<preset>.json/mjs` 声明式组合,学习成本以「分钟」计,而不是 DSH 的「先懂 Cordis」。
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 四、优势与护城河:诚实盘点
|
|
80
|
+
|
|
81
|
+
**真护城河(别人短期抄不走):**
|
|
82
|
+
|
|
83
|
+
| # | 护城河 | 为什么难抄 | 现状 |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| 1 | **DeepSeek 省钱纵深链** | 官方词表黄金值、峰谷双窗口、缓存 1/30、Batch 半价的组合闭环 + bench 棘轮锁定,需要长期浸在 DeepSeek 计费细节里 | 已建成,持续加深 |
|
|
86
|
+
| 2 | **零依赖内核** | 主流框架全是依赖树;推到「一条命令装完」需要从头设计约束 | 已建成 |
|
|
87
|
+
| 3 | **本地模型自适应** | 大厂产品绑定自家云端模型;「资源受限本地模型不中断」是空档,我们已有 v0.3.2 的完整机制 | 刚建成,是未来主攻方向 |
|
|
88
|
+
| 4 | **工程可信度** | strict 0/0 + 覆盖率 + bench + 发布自检的纪律是组织习惯,不是功能 | 已建成 |
|
|
89
|
+
|
|
90
|
+
**伪护城河(会褪色,要警惕):**
|
|
91
|
+
- 功能广度(工具数/技能数)——DSH 插件生态几个月内就会超过我们。
|
|
92
|
+
- 「先发」本身——DSH 公测才一个月,窗口极短。
|
|
93
|
+
- 省钱幅度数字——会被大厂「抄作业」,只有持续领先才有意义。
|
|
94
|
+
|
|
95
|
+
**护城河的正确用法**:不靠单一护城河,靠「#1+#2+#3+#4 的组合 + 持续领先节奏」——单点都可被追平,组合与速度难被复制。
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 五、错位竞争:怎么避开与头部大厂正面交锋
|
|
100
|
+
|
|
101
|
+
**三条避战原则:**
|
|
102
|
+
|
|
103
|
+
1. **不拼框架拼契约**。DSH 的框架深度我们拼不过,但「零依赖 + 分钟级上手」的定制体验是 Cordis 给不了的。开发者要的不是更多抽象,是更快落地。
|
|
104
|
+
2. **不拼平台拼私有**。WorkBuddy 拼云上生态,我们拼「你的模型、你的机器、你的数据不出门」——政企私有化、科研本地算力、个人 MacBook 本地模型。
|
|
105
|
+
3. **不拼通用拼垂直**。通用办公场景让 WorkBuddy 和讯飞去卷;我们把「DeepSeek 省钱」和「本地模型自适应」两个垂直面做到他们不想做、做了不划算的深度。
|
|
106
|
+
|
|
107
|
+
**对标表(MingDao 与谁不打、与谁打):**
|
|
108
|
+
|
|
109
|
+
| 维度 | 巨头在做什么 | MingDao 的打法 |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| 模型绑定 | 各自绑自家云端模型 | **模型中立**:DeepSeek 深度优化 + 任意 OpenAI 兼容 + 本地模型一等公民 |
|
|
112
|
+
| 部署 | 云平台 / 托管订阅 | **私有化/本机第一**:npm 即装、桌面即装、数据不出门 |
|
|
113
|
+
| 生态 | 插件市场、开放平台 | **轻量契约**:预设/工具/技能契约 + 官方示例库,不追商店规模 |
|
|
114
|
+
| 分发 | 官方渠道 | **中国本地分发**:官网直连、gitee/gitcode、论坛(已建成,继续维护) |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 六、独特的 MingDao:一句话定位
|
|
119
|
+
|
|
120
|
+
> **「模型中立、零依赖、会省钱、私有化第一公民的 Agent 内核与终端」——
|
|
121
|
+
> 给 DeepSeek 重度用户一个最省钱的 Coding Agent,给开发者一个分钟级上手的定制基座,给本地/私有化场景一个不被云端绑架的选择。**
|
|
122
|
+
|
|
123
|
+
对照 STRATEGY-0.3 的旧定位(「DeepSeek 生态里最好用、最能省、最省心的 Coding Agent 终端」):**旧定位是产品定位,新定位是产品+内核双定位**。产品线继续按旧定位做深,新增的内核线把天花板打开。
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 七、路线图(确认后逐步落地,每期可独立发布)
|
|
128
|
+
|
|
129
|
+
### 阶段 A「契约化」(v0.4.x,约 2-3 版)
|
|
130
|
+
把已有扩展点正式化为公共 API,**不新增重架构**:
|
|
131
|
+
1. 公共 API 冻结与文档:`src/index.js` 导出面逐项 semver 化 + JSDoc 契约 + `docs/DEVELOPER.md`(API 参考 + 5 个最小示例:自定义工具、自定义 Provider、自定义权限、自定义记忆、embed 进自己的程序)。
|
|
132
|
+
2. **Agent Preset 格式**:一个声明式文件 = { 系统提示, 工具集白名单, 权限策略, 记忆策略, 模型建议 },放 `<home>/presets/` 或 `<项目>/.mingdao/presets/`,CLI/WebUI 一键选用(对齐 DSH 的 profile 概念,但零依赖、JSON 声明式)。
|
|
133
|
+
3. 第三方工具注册 API:`registerTool({ name, schema, run })` 程序化注册 + `config.tools` 声明式挂载,进审计/权限/省钱链路。
|
|
134
|
+
- 验收:全绿 + 新增 API 契约测试 + example 仓库跑通。
|
|
135
|
+
|
|
136
|
+
### 阶段 B「生态萌芽」(v0.5.x)
|
|
137
|
+
1. 官网开发者区:文档站 + 示例库 + 预设目录(官网分发,与桌面安装包同一基建)。
|
|
138
|
+
2. 预设市场雏形:预设/skill 的 registry 已有 sha256 防篡改基建(技能库已用),复用到预设分发。
|
|
139
|
+
3. 本地模型生态打通:ollama / llama.cpp / vLLM 一键接入预设(复用 v0.3.2 的窗口自适应),把「本地模型」从「用户自己配」变成「选一个预设即可」。
|
|
140
|
+
- 验收:开发者按文档 10 分钟跑通自定义智能体;本地预设端到端可用。
|
|
141
|
+
|
|
142
|
+
### 阶段 C「纵深与护城河加固」(v0.6.x)
|
|
143
|
+
1. 省钱纵深继续领先:Batch 自动化、路由升级、更精细分账(保持 bench 棘轮只升不降)。
|
|
144
|
+
2. 企业私有化包:内网部署包(零外网依赖)+ 国产 GPU/本地推理适配。
|
|
145
|
+
3. 生态反哺产品:社区预设/工具里验证过的能力回并进内置产品。
|
|
146
|
+
|
|
147
|
+
### 明确的「不做」清单
|
|
148
|
+
- 不自研模型、不做云端 Agent 平台、不做账号/订阅体系。
|
|
149
|
+
- 不引入 Cordis/任何重依赖插件内核(零依赖是根基)。
|
|
150
|
+
- 不做硬件生态、不做应用商店抽成。
|
|
151
|
+
- 不与 DSH/WorkBuddy 在「框架完备度/平台规模」上对标竞争。
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 八、节奏与纪律(防止战略漂移)
|
|
156
|
+
|
|
157
|
+
1. **每期对照本文件**:迭代前先回答「这一期强化的是 #1-#4 里哪条护城河?有没有撞进『不做』清单?」
|
|
158
|
+
2. **产品/内核双线比例**:产品线(省钱/省心)与内核线(契约/生态)按 2:1 分配精力,产品线永远优先(产品是内核的活广告)。
|
|
159
|
+
3. **发布纪律不变**:全绿测试门禁 + 发布前自检 + 用户验收后再发布。
|
|
160
|
+
4. **战略复盘节奏**:每 10 个版本重读一次本文件,用事实(下载/issue/生态反馈)校准路线。
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 九、待用户决策的关键点
|
|
165
|
+
|
|
166
|
+
> **✅ 已确认(2026-09-05)**:
|
|
167
|
+
> 1. 方向:**认同「垂直产品 × 开放内核」路线**——不做纯 DSH 框架、不做纯 WorkBuddy 平台。
|
|
168
|
+
> 2. 主攻:**「本地/私有化第一公民」与「DeepSeek 省钱」并列双主攻**。
|
|
169
|
+
> 3. 节奏:**v0.4.0 先做阶段 A「契约化」**(公共 API 冻结 + Agent Preset + 第三方工具注册 + DEVELOPER 文档示例)。
|
|
170
|
+
>
|
|
171
|
+
> 落地计划见 [PLAN-v0.4.0.md](PLAN-v0.4.0.md)。
|
|
172
|
+
|
|
173
|
+
1. **方向确认**:是否认同「垂直产品 × 开放内核」路线(不纯框架、不纯平台)?——✅ 认同
|
|
174
|
+
2. **开放节奏**:阶段 A 的「契约化」是否作为 v0.4.0 的第一优先级?——✅ 是
|
|
175
|
+
3. **本地模型战略地位**:是否把「本地/私有化第一公民」提升为与「DeepSeek 省钱」并列的主攻方向?——✅ 是,双主攻
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mingdao-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "MingDao Harness —— 开源智能体框架(Agent Harness)。零依赖、开箱即用,针对 DeepSeek-V4 系列优化,开放主流模型接入。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"src/",
|
|
16
16
|
"skills/",
|
|
17
|
+
"presets/",
|
|
17
18
|
"assets/",
|
|
18
19
|
"docs/",
|
|
19
20
|
"install.sh",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "local-audit",
|
|
3
|
+
"label": "本地模型审计",
|
|
4
|
+
"description": "面向本地/资源受限模型(如 131k 窗口 q8 量化)的只读审计预设:只读工具集 + auto 权限 + 保守参数,配合 v0.3.2 本地自适应长任务不中断",
|
|
5
|
+
"systemPrompt": "你是一名代码审计员。任务是对给定代码库做只读审计:先 ls/glob 摸清结构,再 read/grep 逐文件审查,可用 git 查看历史与 diff、fetch 抓取相关文档。输出审计报告:发现的缺陷按严重度分级,每条给出文件:行号证据;不做任何修改。若上下文紧张,先审查最关键的部分并明示未覆盖区域。",
|
|
6
|
+
"tools": ["read", "ls", "glob", "grep", "skill", "git", "fetch", "todo"],
|
|
7
|
+
"permission": "auto",
|
|
8
|
+
"maxRounds": 4,
|
|
9
|
+
"maxOutputTokens": 4096
|
|
10
|
+
}
|
package/src/agent.js
CHANGED
|
@@ -67,8 +67,18 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
67
67
|
const hasWriteIntent = (/** @type {any} */ text) => WRITE_INTENT_RE.test(String(text || ''));
|
|
68
68
|
// A1(前缀稳定):剥描述集合按「回合冻结快照」——回合内恒定(至多两态:只读档/全量档),
|
|
69
69
|
// 新使用的工具只在下一回合才进入剥描述集合;回合边界本身就有新 user 消息,schema 变化免费。
|
|
70
|
+
// v0.4.0 Agent Preset:cfg.presetTools 白名单恒生效(在只读档过滤之后收紧——预设只减不增)。
|
|
71
|
+
const presetToolSet = Array.isArray(cfg.presetTools) ? new Set(cfg.presetTools.map(String)) : null;
|
|
72
|
+
const activePresetName = String(cfg.presetName || ''); // 白名单拦截提示用
|
|
70
73
|
const toolsFor = (/** @type {boolean} */ readOnlyPhase, /** @type {Set<string>} */ strippedSet) => {
|
|
71
|
-
|
|
74
|
+
let schemas = buildToolSchemas(strippedSet, mcpSchemas());
|
|
75
|
+
if (presetToolSet) {
|
|
76
|
+
schemas = schemas.filter((/** @type {any} */ t) => {
|
|
77
|
+
const n = t?.function?.name;
|
|
78
|
+
if (!n) return true;
|
|
79
|
+
return presetToolSet.has(n) || (n.startsWith('mcp__') && presetToolSet.has(n.slice(5)));
|
|
80
|
+
});
|
|
81
|
+
}
|
|
72
82
|
if (!readOnlyPhase) return schemas;
|
|
73
83
|
return schemas.filter((/** @type {any} */ t) => {
|
|
74
84
|
const n = t?.function?.name;
|
|
@@ -113,7 +123,12 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
113
123
|
const t0 = Date.now();
|
|
114
124
|
const res = await subAgent.runTurn(messages);
|
|
115
125
|
const ms = Date.now() - t0;
|
|
116
|
-
|
|
126
|
+
// v0.4.1:子代理空输出给主线程可用的失败信号(含 note 原因),而非笼统「无输出」——
|
|
127
|
+
// 主智能体据此决定是否重试/换法,而非把子代理静默当作「已完成但没说话」。
|
|
128
|
+
const text =
|
|
129
|
+
res.text ||
|
|
130
|
+
(res.truncated ? '(子任务达到步骤上限,未完成' : '(子任务无输出') +
|
|
131
|
+
(res.note ? ':' + res.note : '') + ')';
|
|
117
132
|
io.print(style(` ↳ 子任务完成(${ms}ms)`, C.magenta));
|
|
118
133
|
return text;
|
|
119
134
|
}
|
|
@@ -199,12 +214,14 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
199
214
|
}
|
|
200
215
|
};
|
|
201
216
|
try {
|
|
217
|
+
// 同回合只读工具去重(Hermes C4):相同 name+args 的只读调用只执行一次,结果复用回填。
|
|
218
|
+
// v0.4.1 P1 修复:turnToolCache 必须声明在 while 之外、for 轮内——此前在 while 体内每步重建,
|
|
219
|
+
// 去重只在本步的多个工具调用间生效,跨步(如先 grep 定位再 read 确认同一文件)完全失效。
|
|
220
|
+
const turnToolCache = new Map();
|
|
202
221
|
for (round = 0; round < maxRounds; round++) {
|
|
203
222
|
steps = 0;
|
|
204
223
|
while (steps < stepLimit) {
|
|
205
224
|
steps += 1;
|
|
206
|
-
// 同回合只读工具去重(Hermes C4):相同 name+args 的只读调用只执行一次,结果复用回填
|
|
207
|
-
const turnToolCache = new Map();
|
|
208
225
|
// 自动压缩(P3-1):预算不足、静默裁剪即将丢弃早期段落时,先用 executor 模型
|
|
209
226
|
// 把被裁段落压成摘要注入,替代「失忆」;失败/不值得时回退普通裁剪。
|
|
210
227
|
if (cfg.autoCompact !== false) {
|
|
@@ -230,6 +247,12 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
230
247
|
C.dim
|
|
231
248
|
)
|
|
232
249
|
);
|
|
250
|
+
// v0.4.1 P1 修复:压缩成功后复位 windowPressure——此前永不复位,后续每轮都 force 压缩,
|
|
251
|
+
// 叠加 compact.js force 分支跳过 2000 token 最小阈值,增量段极短也发真实模型调用持续烧钱。
|
|
252
|
+
if (windowPressure) {
|
|
253
|
+
windowPressure = false;
|
|
254
|
+
pressureWarned = false;
|
|
255
|
+
}
|
|
233
256
|
try {
|
|
234
257
|
onCompact?.(messages);
|
|
235
258
|
} catch {}
|
|
@@ -241,14 +264,25 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
241
264
|
// reasoning 回填防护(Kimi P0 修正):带 tool_calls 的 assistant 消息必须「完整」回传
|
|
242
265
|
// reasoning_content(DeepSeek thinking_mode 官方要求:后续请求含 tools 时缺/截断即 400,
|
|
243
266
|
// 多轮工具会话必崩);仅纯文本回复的 reasoning 可裁剪(不回传也不影响)。
|
|
267
|
+
// v0.4.1 P2 修复:单次遍历构建新数组(O(n)),此前循环内每次替换都 sanitized.map 全量重建(O(n²))。
|
|
244
268
|
let sanitized = trimmed;
|
|
245
|
-
|
|
269
|
+
let dirty = false;
|
|
270
|
+
for (let i = 0; i < sanitized.length; i++) {
|
|
271
|
+
const m = sanitized[i];
|
|
246
272
|
const rc = m.reasoning_content;
|
|
247
273
|
if (typeof rc !== 'string' || (Array.isArray(m.tool_calls) && m.tool_calls.length)) continue;
|
|
274
|
+
let replacement = null;
|
|
248
275
|
if (rc.length > 4000) {
|
|
249
|
-
|
|
276
|
+
replacement = `[思考过程已省略(原 ${rc.length} 字)]`;
|
|
250
277
|
} else if (rc.length > 1000) {
|
|
251
|
-
|
|
278
|
+
replacement = rc.slice(-500) + ' …[思考过程已截断]';
|
|
279
|
+
}
|
|
280
|
+
if (replacement !== null) {
|
|
281
|
+
if (!dirty) {
|
|
282
|
+
sanitized = sanitized.slice();
|
|
283
|
+
dirty = true;
|
|
284
|
+
}
|
|
285
|
+
sanitized[i] = { ...m, reasoning_content: replacement };
|
|
252
286
|
}
|
|
253
287
|
}
|
|
254
288
|
|
|
@@ -467,6 +501,13 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
467
501
|
}
|
|
468
502
|
|
|
469
503
|
const isMcp = name.startsWith('mcp__');
|
|
504
|
+
// v0.4.0 Agent Preset:白名单强制(模型可能调用白名单外工具——schema 已不发,此处兜底硬拦)
|
|
505
|
+
if (presetToolSet && !presetToolSet.has(isMcp ? name.slice(5) : name)) {
|
|
506
|
+
io.renderToolDenied(name, args, `不在预设工具白名单内(${activePresetName || 'preset'})`);
|
|
507
|
+
if (auditOn) auditEntry({ denied: true, reason: '预设工具白名单拦截' });
|
|
508
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: `工具 ${name} 不在当前预设的工具白名单内,已拒绝执行。` });
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
470
511
|
let allowed = false;
|
|
471
512
|
if (isMcp && mcp?.isReadonly(name)) {
|
|
472
513
|
allowed = true; // MCP 工具的只读标注自动放行
|
|
@@ -700,13 +741,17 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
700
741
|
stripOrphanCalls();
|
|
701
742
|
// v0.2.8 兜底总结(对齐 DSH):跑满步数/末轮无正文且未中断时,补一次 no-tool 小输出请求,
|
|
702
743
|
// 让任务以「总结文字 + 交付物清单」收尾,而非静默结束;失败则回退旧行为(text:null)。
|
|
744
|
+
// v0.4.1 修复:输入轻量化——此前用 trimMessages(messages, budget) 全量历史,本地 q8 量化模型
|
|
745
|
+
// (prefill ~165 tok/s)≈98k token 的 prefill 逼近/超过 600s 首 token 超时 → 总结请求失败被吞 →
|
|
746
|
+
// 表现为「输出截断/子代理无反馈」。改用 system + 交付物清单 + 提示(几 k token),慢 prefill 也能秒出总结。
|
|
703
747
|
if (!aborted && messages.length) {
|
|
704
748
|
try {
|
|
705
|
-
|
|
706
|
-
const lastMsg = messages[messages.length - 1];
|
|
707
|
-
if (lastMsg.role === 'assistant' && !lastMsg.content && !Array.isArray(lastMsg.tool_calls)) messages.pop();
|
|
749
|
+
const sys = messages.find((/** @type {any} */ m) => m.role === 'system');
|
|
708
750
|
const wrapReq = [
|
|
709
|
-
...
|
|
751
|
+
...(sys ? [sys] : []),
|
|
752
|
+
...(deliverables.length
|
|
753
|
+
? [{ role: 'user', content: '已交付文件:\n' + deliverables.map((/** @type {string} */ f) => `- ${f}`).join('\n') }]
|
|
754
|
+
: []),
|
|
710
755
|
{ role: 'user', content: '(系统提示)任务已执行完毕。请用一段话总结刚才完成的工作,列出交付物(文件路径),并说明遗留问题与后续建议。' },
|
|
711
756
|
];
|
|
712
757
|
currentAc = new AbortController();
|
|
@@ -729,7 +774,10 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
729
774
|
if (wrapRes.text) messages.push({ role: 'assistant', content: wrapRes.text });
|
|
730
775
|
// capHit:本轮因步数上限被迫收尾(任务可能未真正完成)→ 供上层落检查点续跑
|
|
731
776
|
return { text: wrapRes.text || null, reasoning: wrapRes.reasoning || '', usage, steps, finish, truncated: false, aborted: false, capHit: true, durationMs: Date.now() - startedAt, perf: perf() };
|
|
732
|
-
} catch {}
|
|
777
|
+
} catch (/** @type {any} */ err) {
|
|
778
|
+
// v0.4.1:不再静默吞异常——总结失败原因透出,便于定位(此前用户只见「输出截断/无反馈」)
|
|
779
|
+
try { io.print(style(`⚠ 兜底总结失败:${String(err?.message || err)}`, C.yellow)); } catch {}
|
|
780
|
+
}
|
|
733
781
|
}
|
|
734
782
|
return { text: null, reasoning: '', usage, steps, finish, truncated: true, aborted: false, capHit: true, durationMs: Date.now() - startedAt, perf: perf() };
|
|
735
783
|
}
|