mingdao-harness 0.3.1 → 0.4.0
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 +24 -2
- package/docs/DEVELOPER.md +126 -0
- package/docs/PLAN-v0.3.2.md +65 -0
- package/docs/PLAN-v0.4.0.md +38 -0
- package/docs/STRATEGY-NEXT.md +175 -0
- package/package.json +2 -1
- package/presets/local-audit.json +10 -0
- package/src/agent.js +47 -4
- package/src/cli.js +37 -6
- package/src/commands/repl.js +42 -5
- package/src/compact.js +3 -1
- package/src/index.js +30 -4
- package/src/model-caps.js +69 -0
- package/src/presets.js +154 -0
- package/src/prompts.js +5 -2
- package/src/providers/index.js +45 -6
- package/src/providers/openai-compatible.js +8 -3
- package/src/tasks/worker.js +3 -0
- package/src/tools/index.js +113 -1
- package/src/web/app.js +15 -3
- package/src/web/index.html +15 -1
- package/src/web/routes/domains/config.js +26 -2
- package/src/web/routes/domains/misc.js +8 -0
- package/src/web/server.js +30 -4
package/docs/CONFIG.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
| `baseUrl` | OpenAI 兼容 API 地址(可覆盖内置服务商默认值) |
|
|
24
24
|
| `permission` | `ask`(默认)/ `auto` / `readonly`,或规则对象(见下) |
|
|
25
25
|
| `sandbox` | `off` / `readonly` / `safe`(Linux + bubblewrap;其余平台自动降级) |
|
|
26
|
-
| `contextBudget` |
|
|
26
|
+
| `contextBudget` | 期望的上下文预算 tokens;实际预算按模型窗口自动收紧(见「本地模型自适应」) |
|
|
27
27
|
|
|
28
28
|
可选字段:`temperature`、`maxOutputTokens`、`includeUsage`(流式请求 usage 统计,个别网关不支持
|
|
29
29
|
`stream_options` 时设 `false`)、`autoTitle`(自动生成会话标题,默认开)、`notify`(任务桌面通知,默认开)、
|
|
@@ -221,7 +221,8 @@ completion 计费,防止推理吃满上限时空轮白烧)、`compactTrigger
|
|
|
221
221
|
{
|
|
222
222
|
"customModels": {
|
|
223
223
|
"my-gpt4": { "label": "我的 GPT-4 网关", "baseUrl": "https://gateway.example.com/v1" },
|
|
224
|
-
"my-ds": { "label": "自建 DeepSeek 网关", "baseUrl": "https://gw.example.com/v1", "tokenizer": "deepseek" }
|
|
224
|
+
"my-ds": { "label": "自建 DeepSeek 网关", "baseUrl": "https://gw.example.com/v1", "tokenizer": "deepseek" },
|
|
225
|
+
"local-qwen": { "label": "本机 Qwen", "baseUrl": "http://127.0.0.1:8081/v1", "contextWindow": 131072, "maxOutputTokens": 8192 }
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
228
|
```
|
|
@@ -231,6 +232,27 @@ completion 计费,防止推理吃满上限时空轮白烧)、`compactTrigger
|
|
|
231
232
|
自定义端点若跑的是 DeepSeek 系模型(模型名不以 `deepseek` 开头时默认走启发式估算、预算误差
|
|
232
233
|
可达 ±2 倍),加 `"tokenizer": "deepseek"` 即按官方词表精确计数:
|
|
233
234
|
|
|
235
|
+
### 本地模型自适应(v0.3.2)
|
|
236
|
+
|
|
237
|
+
本机/内网部署的推理框架(baseUrl 为 `127.0.0.1`/`localhost`/私网 IP)自动按「资源有限」对待,
|
|
238
|
+
避免长任务把上下文撑到窗口边缘后 prefill 指数恶化、被客户端超时掐断(典型:127k 上下文首 token
|
|
239
|
+
需 200s+,客户端 3 分钟无响应断开 → network error)。机制:
|
|
240
|
+
|
|
241
|
+
- **上下文窗口感知**:`customModels.<name>.contextWindow` 显式声明模型真实窗口;未声明时本地
|
|
242
|
+
模型兜底 **32k**、远程兜底 **128k**。
|
|
243
|
+
- **安全预算**:`contextBudget` 会被自动收紧到 `min(contextBudget, 窗口×75%, 窗口−maxOutput−余量)`,
|
|
244
|
+
prompt 永不逼近窗口边缘(75% 舒适区以上 prefill 时间陡增)。
|
|
245
|
+
- **边缘检测**:模型每轮上报真实 `prompt_tokens`,≥ 窗口 85% 时下一轮强制压缩历史(即使启发式
|
|
246
|
+
计数低估也强制触发)。
|
|
247
|
+
- **工具输出截断**:单条工具结果按 `窗口/16` 封顶(最少 2000 字),小窗口不再整条回灌大段代码。
|
|
248
|
+
- **分层超时**:`timeout.firstTokenMs`(首 token 等待,本地默认 600s / 远程 300s)、
|
|
249
|
+
`timeout.streamIdleMs`(流式空闲,默认 120s)、`timeout.totalMs`(总量,本地 30min / 远程 10min),
|
|
250
|
+
留空则自适应;本地慢 prefill 不再被一刀切超时误杀。
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
{ "timeout": { "firstTokenMs": 600000, "streamIdleMs": 120000, "totalMs": 1800000 } }
|
|
254
|
+
```
|
|
255
|
+
|
|
234
256
|
## 自定义 Provider 模块(非 OpenAI 兼容协议)
|
|
235
257
|
|
|
236
258
|
在 `~/.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,65 @@
|
|
|
1
|
+
# v0.3.2 规划:本地模型自适应(资源受限部署不中断)
|
|
2
|
+
|
|
3
|
+
## 背景(用户 MacBook M5 Pro 实测定位结论)
|
|
4
|
+
|
|
5
|
+
本地部署 `mtplx-qwen38-27b`(131072 窗口 + q8 KV 量化)跑 71 步长任务时 network error。
|
|
6
|
+
其他 agent 定位结论:**不是内存拒绝,是「长上下文 prefill 过慢 → 客户端等待超时主动断连」**。
|
|
7
|
+
|
|
8
|
+
- 服务端无任何 507/内存拒绝,q8 生效,峰值 47.38GB < 48G 预算。
|
|
9
|
+
- 失败请求:prompt=127,912 / 131,072(窗口边缘),`request_cancelled=client_disconnected`,
|
|
10
|
+
188.9s 内 0 token 输出。
|
|
11
|
+
- prefill 指数恶化:ttft 46.4s → 67.8s → 96.0s → 196.5s → 中断(189s 无输出)。
|
|
12
|
+
根因:每轮全量历史 + 上一轮大段代码输出回灌,新增 prefill 从 7.9k 涨到 32k,
|
|
13
|
+
prefill 速度仅 ~165–185 tok/s,ttft 超过客户端等待阈值。
|
|
14
|
+
|
|
15
|
+
调用 DeepSeek 官方 API 无此问题(窗口 1M + prefill 极快),说明瓶颈在「资源受限的本地模型」,
|
|
16
|
+
必须做成**共性能力**:其他客户本地部署更小模型(窗口小/内存少)也会踩,不能只修 MacBook。
|
|
17
|
+
|
|
18
|
+
## 目标
|
|
19
|
+
|
|
20
|
+
让 MingDao 针对不同「参数 / 上下文窗口 / KV cache / 机器资源」的模型**灵活自适应**,
|
|
21
|
+
小模型、低内存自动收紧参数适应,不撑爆、不误杀;本地慢 prefill 不被一刀切超时掐断。
|
|
22
|
+
|
|
23
|
+
## 方案(本次实现)
|
|
24
|
+
|
|
25
|
+
### 1. 模型能力解析(`src/model-caps.js` 新增)
|
|
26
|
+
单一来源解析 `contextWindow / maxOutputTokens / isLocal`:
|
|
27
|
+
- 优先级:`customModels.<name>.contextWindow/maxOutputTokens` > 内置 preset > 兜底。
|
|
28
|
+
- 兜底:本地模型 32k、远程 128k(本地小模型宁可保守不撑爆)。
|
|
29
|
+
- `isLocalBaseUrl`:127.0.0.1 / localhost / 私网 IP 判定本地推理框架。
|
|
30
|
+
|
|
31
|
+
### 2. 安全预算推导(`safeBudget`)
|
|
32
|
+
`budget = min(期望 contextBudget, 窗口×75% 舒适区, 窗口 − maxOutput − 2048 余量)`。
|
|
33
|
+
prompt 永不逼近窗口边缘(75% 以上 prefill 时间陡增),从根上避免 prefill 爆炸。
|
|
34
|
+
对内置模型零影响(pro 200k / flash 128k 均远小于各自窗口 75%)。
|
|
35
|
+
|
|
36
|
+
### 3. 分层超时(providers)
|
|
37
|
+
- 首 token 等待:本地 600s / 远程 300s(覆盖慢 prefill)。
|
|
38
|
+
- 流式空闲:有帧后 120s 无新帧即断(真挂死才断)。
|
|
39
|
+
- 总量:本地 30min / 远程 10min。
|
|
40
|
+
- `config.timeout.{firstTokenMs,streamIdleMs,totalMs}` 可覆盖;`parseStream` 按「帧到达」刷新
|
|
41
|
+
空闲计时(prefill 阶段服务端可能先发 usage-only 帧,不误杀)。
|
|
42
|
+
|
|
43
|
+
### 4. 边缘检测 + 强制压缩
|
|
44
|
+
模型每轮上报真实 `prompt_tokens`,≥ 窗口 85% 时下一轮 `force` 压缩——即便非 DeepSeek 模型
|
|
45
|
+
启发式计数低估(误差 ±2 倍)也强制触发(`compact.js` force 绕过触发线门槛)。
|
|
46
|
+
|
|
47
|
+
### 5. 工具输出截断自适应
|
|
48
|
+
单条工具结果按 `窗口/16` 封顶(最少 2000 字),小窗口不再整条回灌大段代码。
|
|
49
|
+
|
|
50
|
+
### 6. 配置/UI 打通
|
|
51
|
+
- `customModels.<name>.contextWindow/maxOutputTokens`(WebUI 添加自定义模型表单新增两项)。
|
|
52
|
+
- `config.timeout.*`(设置 → 通用面板新增三项,秒为单位,留空自适应)。
|
|
53
|
+
- `/api/config`、`/api/models-config` 契约扩展。
|
|
54
|
+
|
|
55
|
+
## 验收
|
|
56
|
+
|
|
57
|
+
- smoke 新增 model-caps(本地/远程判定、窗口兜底/显式声明、舒适区+输出余量预算)与
|
|
58
|
+
provider 分层超时(首 token vs 流式空闲)两组断言。
|
|
59
|
+
- 全绿:smoke 69 组 / e2e-local / e2e-web / e2e-schedule / api-contracts / bench;strict 0/0。
|
|
60
|
+
- 用户在 3820 用本地模型跑长任务验收,确认无 network error 中断后发布。
|
|
61
|
+
|
|
62
|
+
## 非目标(顺延)
|
|
63
|
+
|
|
64
|
+
- 增量上下文(基线+变化)——自动续跑 + 语义检索已覆盖省钱与长程主场景。
|
|
65
|
+
- 本地窗口自动探测(从服务端 /models 读 max_model_len)——依赖各家推理框架能力,暂用显式声明。
|
|
@@ -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,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.0",
|
|
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
|
@@ -6,6 +6,7 @@ import { trimMessages, clampText, messageTokens, approxTokens } from './context.
|
|
|
6
6
|
import { compactConversation } from './compact.js';
|
|
7
7
|
import { buildToolSchemas, dispatch } from './tools/index.js';
|
|
8
8
|
import { modelPreset } from './models.js';
|
|
9
|
+
import { resolveModelCaps, safeBudget, EDGE_RATIO } from './model-caps.js';
|
|
9
10
|
import { makeTokenCounter } from './tokenizer.js';
|
|
10
11
|
import { createHooks } from './hooks.js';
|
|
11
12
|
import { createIO, style, C } from './ui.js';
|
|
@@ -29,8 +30,19 @@ const SUBAGENT_MAX_STEPS = 24;
|
|
|
29
30
|
*/
|
|
30
31
|
export function createAgent({ provider, permission, io, modelName, workingDir, cfg = {}, undoStore, maxSteps, mcp, onCompact, sessionRef }) {
|
|
31
32
|
const preset = modelPreset(modelName) || {};
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
// v0.3.2 模型自适应:预算按模型上下文窗口推导(留输出余量 + 75% 舒适区),
|
|
34
|
+
// 自定义/本地小模型不再套 128000 默认撑爆窗口;prompt 永不逼近窗口边缘(prefill 不爆炸)。
|
|
35
|
+
const caps = resolveModelCaps(cfg, modelName);
|
|
36
|
+
// safeBudget 恒用:即使用户显式 contextBudget 也套「窗口−输出−余量」上限与 75% 舒适区,
|
|
37
|
+
// 防显式值撑爆窗口(本地模型窗口可能只有 32k/131k,用户却留了默认 128000)。
|
|
38
|
+
const budget = safeBudget(cfg, caps);
|
|
39
|
+
// maxOutput 也按窗口封顶:显式配超大 maxOutputTokens 时,prompt(预算)+output 仍不得越过窗口
|
|
40
|
+
// (预算已按 caps.maxOutputTokens 留余量,但显式值可能更大——此处兜底,防服务端截断/拒绝)
|
|
41
|
+
const maxOutput = Math.min(cfg.maxOutputTokens || caps.maxOutputTokens, Math.max(1024, caps.contextWindow - budget));
|
|
42
|
+
// v0.3.2 工具输出截断自适应:窗口越小截得越狠(单条工具结果按窗口 1/16 封顶,最少 2000 字),
|
|
43
|
+
// 但绝不超过旧默认 20000(大窗口模型如 1M 不因公式放大回灌、不推高成本)。
|
|
44
|
+
// 本地小模型(32k 窗口 → 2k 字)不再把大段代码/日志整条回灌,省 prompt 且不撑爆窗口。
|
|
45
|
+
const toolResultCap = Math.min(20000, Math.max(2000, Math.floor(caps.contextWindow / 16)));
|
|
34
46
|
const temperature = cfg.temperature ?? preset.temperature ?? 0.6;
|
|
35
47
|
const reasoningEffort = cfg.reasoningByModel?.[modelName] ?? cfg.reasoningEffort ?? preset.reasoningEffort?.default ?? undefined;
|
|
36
48
|
const hooks = createHooks(cfg.hooks, workingDir);
|
|
@@ -55,8 +67,18 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
55
67
|
const hasWriteIntent = (/** @type {any} */ text) => WRITE_INTENT_RE.test(String(text || ''));
|
|
56
68
|
// A1(前缀稳定):剥描述集合按「回合冻结快照」——回合内恒定(至多两态:只读档/全量档),
|
|
57
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 || ''); // 白名单拦截提示用
|
|
58
73
|
const toolsFor = (/** @type {boolean} */ readOnlyPhase, /** @type {Set<string>} */ strippedSet) => {
|
|
59
|
-
|
|
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
|
+
}
|
|
60
82
|
if (!readOnlyPhase) return schemas;
|
|
61
83
|
return schemas.filter((/** @type {any} */ t) => {
|
|
62
84
|
const n = t?.function?.name;
|
|
@@ -152,6 +174,9 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
152
174
|
let aborted = false;
|
|
153
175
|
let emptyRounds = 0; // 连续空/截断输出计数(防止无限续写)
|
|
154
176
|
let currentAc = /** @type {any} */ (null);
|
|
177
|
+
// v0.3.2 边缘检测状态:模型上报 prompt_tokens 逼近窗口 → 下一轮强制压缩(见下方 compactConversation force)
|
|
178
|
+
let windowPressure = false;
|
|
179
|
+
let pressureWarned = false;
|
|
155
180
|
// 省钱 B4(护栏降级):action='downgrade' 超限后本回合切换到便宜模型继续执行;
|
|
156
181
|
// activeModel 是本回合实际使用的模型(分账/记录归属它),downgraded 保证只提示一次。
|
|
157
182
|
let activeModel = modelName;
|
|
@@ -201,6 +226,7 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
201
226
|
provider,
|
|
202
227
|
executorModel: subagentModel(cfg, modelName),
|
|
203
228
|
triggerRatio: cfg.compactTrigger, // 可配置触发线(默认 80%)
|
|
229
|
+
force: windowPressure, // v0.3.2:逼近窗口时强制压缩(忽略最小阈值门槛)
|
|
204
230
|
});
|
|
205
231
|
if (compacted) {
|
|
206
232
|
messages.splice(0, messages.length, ...compacted.messages);
|
|
@@ -368,6 +394,16 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
368
394
|
if (Number.isFinite(res.usage.prompt_cache_miss_tokens)) {
|
|
369
395
|
usage.prompt_cache_miss_tokens = (usage.prompt_cache_miss_tokens || 0) + res.usage.prompt_cache_miss_tokens;
|
|
370
396
|
}
|
|
397
|
+
// v0.3.2 边缘检测:模型上报的真实 prompt_tokens(含缓存命中)逼近窗口 85% 即标记——
|
|
398
|
+
// 下一轮强制激进压缩(force),不让 prompt 逼近窗口边缘导致 prefill 指数恶化。
|
|
399
|
+
const realPrompt = Number(res.usage.prompt_tokens);
|
|
400
|
+
if (Number.isFinite(realPrompt) && realPrompt > 0 && realPrompt >= caps.contextWindow * EDGE_RATIO) {
|
|
401
|
+
windowPressure = true;
|
|
402
|
+
if (!pressureWarned) {
|
|
403
|
+
pressureWarned = true;
|
|
404
|
+
io.print(style(`⚠ 上下文已逼近模型窗口(${realPrompt}/${caps.contextWindow},≥${Math.round(EDGE_RATIO * 100)}%),下轮将强制压缩历史防 prefill 恶化`, C.yellow));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
371
407
|
}
|
|
372
408
|
|
|
373
409
|
if (res.toolCalls?.length) {
|
|
@@ -441,6 +477,13 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
441
477
|
}
|
|
442
478
|
|
|
443
479
|
const isMcp = name.startsWith('mcp__');
|
|
480
|
+
// v0.4.0 Agent Preset:白名单强制(模型可能调用白名单外工具——schema 已不发,此处兜底硬拦)
|
|
481
|
+
if (presetToolSet && !presetToolSet.has(isMcp ? name.slice(5) : name)) {
|
|
482
|
+
io.renderToolDenied(name, args, `不在预设工具白名单内(${activePresetName || 'preset'})`);
|
|
483
|
+
if (auditOn) auditEntry({ denied: true, reason: '预设工具白名单拦截' });
|
|
484
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: `工具 ${name} 不在当前预设的工具白名单内,已拒绝执行。` });
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
444
487
|
let allowed = false;
|
|
445
488
|
if (isMcp && mcp?.isReadonly(name)) {
|
|
446
489
|
allowed = true; // MCP 工具的只读标注自动放行
|
|
@@ -529,7 +572,7 @@ export function createAgent({ provider, permission, io, modelName, workingDir, c
|
|
|
529
572
|
}
|
|
530
573
|
const text = typeof result === 'string' ? result : JSON.stringify(result); // 紧凑 JSON(评估 B3):嵌套结果省 10-20% 回填 token,且下轮按 prompt 重复计费
|
|
531
574
|
const prefix = prep.cached ? '(与同回合相同调用结果一致,已复用)\n' : '';
|
|
532
|
-
messages.push({ role: 'tool', tool_call_id: prep.tc.id, content: prefix + clampText(text) });
|
|
575
|
+
messages.push({ role: 'tool', tool_call_id: prep.tc.id, content: prefix + clampText(text, toolResultCap) });
|
|
533
576
|
}
|
|
534
577
|
|
|
535
578
|
let i = 0;
|