ppxans-harness 2.4.0 → 2.6.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/README.md +29 -32
- package/config/ppx.json +133 -151
- package/package.json +2 -5
- package/src/agent/index.js +3 -3
- package/src/agent/prompts.js +1 -1
- package/src/channels/http.js +76 -17
- package/src/config/providers.js +5 -11
- package/src/llm/client.js +187 -446
- package/src/llm/fence.js +8 -61
- package/src/llm/index.js +1 -1
- package/src/llm/router.js +5 -13
- package/src/mcp/admin.js +292 -0
- package/src/mcp/client.js +107 -5
- package/src/mcp/http.js +217 -0
- package/src/mcp/server.js +387 -0
- package/src/mcp/tasks.js +138 -0
- package/src/plugin/builtin.js +4 -1
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# 🦐 PPXANS-Harness
|
|
2
2
|
|
|
3
3
|
> **PPXANS-Harness = 皮皮虾神经系 (ANS) + Harness 一体化智能体内核**,纯 Node.js 编写,**零运行时依赖**。
|
|
4
|
-
>
|
|
4
|
+
> 45+ 内置工具 · L0–L4 五层记忆 · SHA-256 审计哈希链 · MCP 服务端+客户端 · 多模型路由 · 自愈 7/7 · **716 测试全绿**。
|
|
5
5
|
>
|
|
6
|
-
> 由 `ppx-agent v1.6.0`(合并基座)+ `ppx-v2 v0.4.0
|
|
6
|
+
> 由 `ppx-agent v1.6.0`(合并基座)+ `ppx-v2 v0.4.0`(能力吸收)合并而成,v2.5.0 起移除全部外部引擎底座,v2.6.0 起 web 壳与外部接入全面切 MCP。合并范围与取舍见 [MERGE-REPORT.md](MERGE-REPORT.md)、第三方来源见 [references/THIRD-PARTY-SOURCES.md](references/THIRD-PARTY-SOURCES.md)。
|
|
7
7
|
|
|
8
8
|
> 🛡️ **自愈基准**:故意注入破坏 -> 自愈引擎修复 -> 输出修复率。
|
|
9
9
|
> 跑 `node scripts/selfheal-bench.js` -> **7/7 100%**(发布前门禁,`PPX_MIN_SELFHEAL` 可设阈值)。
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
> 🔗 **审计哈希链**:工具调用落 append-only + SHA-256 链式账本,篡改可定位到具体行。
|
|
12
12
|
> 跑 `npm run audit:verify` 校验,`npm run audit:verify -- --fix` 隔离损坏段并重建。
|
|
13
13
|
|
|
14
|
-
**一个会自我修复、自我学习、可审计的超级 Agent。** 零运行时依赖,纯 Node 原生,支持各大模型 API +
|
|
14
|
+
**一个会自我修复、自我学习、可审计的超级 Agent。** 零运行时依赖,纯 Node 原生,支持各大模型 API + 本地模型。**标准 MCP 服务器**(`POST /mcp`)让 Claude Desktop / Cursor / 任何 MCP 客户端开箱即用。
|
|
15
15
|
|
|
16
16
|
> 架构参考 openhanako/HanaAgent 与 TencentDB-Agent-Memory,扒其记忆分层、自愈内核、工具系统的精华,用干净自包含实现重搭。
|
|
17
17
|
>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
| 🧠 **五层记忆 L0–L4** | L0原始对话 → L1原子记忆(高斯衰减) → L2场景 → L3核心画像 → **L4程序性记忆**(技能/流程, 衰减仅为 L1 的 1/4) |
|
|
25
25
|
| 🗂️ **记忆治理** | **软删可回滚**(forget/restore) + 版本链(update 留痕) + TTL 自动归档 + 按层清理 + 导出/导入迁移; 遗忘不再不可逆 |
|
|
26
26
|
| 🔐 **审计哈希链** | 工具调用 append-only 账本 + SHA-256 链式防篡改, 篡改/删行可定位到具体行, 支持隔离损坏段重建 |
|
|
27
|
-
| 🔧 **
|
|
27
|
+
| 🔧 **45+ 内置工具** | 文件/命令/搜索/HTTP/定时/记忆检索/读图/文档加载/文档入库/OCR/code_act/refine/refine_skill + **10个治理运维工具**(memory_forget/restore/export/import/clear_layer/list_deleted、audit_verify、persona_build/read、selfheal_run) |
|
|
28
28
|
| 🩺 **自我修复** | 启动体检、损坏JSON自动修复、崩溃恢复、残留清理 |
|
|
29
29
|
| 📚 **自我学习** | 经验库 + 自动提炼用户画像/agent人格 + refine 失败轨迹闭环 + refineSkill 成功轨迹沉淀技能 |
|
|
30
30
|
| 🤖 **多 Agent 军团** | 多进程并行 + DAG 编排 + legion 模式 (broadcast/dispatch/runDag) + spawn_agent 自主协作 (并行/差异化视角/仲裁聚合/SDD 审查循环) |
|
|
@@ -38,10 +38,13 @@
|
|
|
38
38
|
| ✅ 错误自愈 | 工具错误统一语义, 自动重试修正 |
|
|
39
39
|
| ✅ 方法型Skill | humanize去AI味 / write_article分阶段写作 / clarify需求澄清 / brainstorm审批门禁 / plan精确计划 / verify验证优先 / debug五步调试 |
|
|
40
40
|
| 🔌 **MCP 客户端** | 零依赖 MCP 客户端 (stdio + HTTP Streamable), 接入 9600+ MCP 工具服务器 |
|
|
41
|
+
| 🛰️ **MCP 服务器** | 标准 MCP 服务端点 `POST /mcp` (Streamable HTTP, 双 era: 现代 2026-07-28 + legacy initialize 握手), 暴露全部 45+ 工具 / 记忆·轨迹·统计·会话资源 / 方法型技能 prompts / 对话工具 ppx.chat.* (SSE 流式 + 结构化 tool/step 事件) / x-mcp-header 客户端支持 |
|
|
42
|
+
| 🎛️ **MCP 管理工具** | 会话/提供方/设置/任务面板 全部经标准 MCP 工具暴露 (ppx.sessions.* / ppx.providers.* / ppx.settings.* / ppx.task.*), web 产品壳已全面切换, REST /api/* 退役 |
|
|
43
|
+
| ✅ **任务面板** | MCP 任务工具 + web UI 模块: 任务队列 (进行中/待处理/完成/失败) + 步骤状态推进 + 结果回填, 持久化 data/tasks.json, 6 套技能模板 |
|
|
41
44
|
| ✅ 可观测 | 工具调用轨迹JSONL + 失败率/慢工具统计 + **结构化事件流**(traceId 经 AsyncLocalStorage 贯穿) |
|
|
42
45
|
| ✅ LLM真摘要 | 长对话自动LLM语义摘要, 非堆叠 |
|
|
43
46
|
| ✅ 场景系统 | 灵魂文件式场景(手动设定/历史提炼), 命中自动切换行为 |
|
|
44
|
-
| ✅ Next.js产品壳 | web/ 前端代理8899内核,
|
|
47
|
+
| ✅ Next.js产品壳 | web/ 前端代理8899内核, 会话/场景/记忆/轨迹/统计/任务全切 MCP (lib/mcp.ts 浏览器 JSON-RPC 客户端), 流式打字机 + 工具卡片, REST /api/* 退役 (legacy_rest=false 时彻底 410) |
|
|
45
48
|
| ✅ **多轮对话历史** | 会话内上下文连续, 信息量感知裁剪控 token |
|
|
46
49
|
| ✅ **ANS 状态化** | 生命周期落盘(重启不归零) + 主动提醒去重/完成跟踪 + 过期待办跳过 |
|
|
47
50
|
| ✅ **流式输出** | SSE 逐字流式, Web UI 实时渲染 (P1) |
|
|
@@ -51,36 +54,18 @@
|
|
|
51
54
|
| ✅ **测试隔离** | 所有测试用临时目录, 不污染生产数据 (P0) |
|
|
52
55
|
| 🔐 **HTTP 认证** | Bearer Token, 未配置自动生成随机token (P0) |
|
|
53
56
|
| ✅ **Markdown 渲染** | Web UI marked.js 渲染代码块/列表 (P1) |
|
|
54
|
-
| ✅ **
|
|
57
|
+
| ✅ **自研底座 (唯一)** | 纯 Node fetch 直连 OpenAI 兼容 API (OpenAI/DeepSeek/火山/通义/智谱/本地 lmstudio/ollama/vLLM),零运行时依赖,SSE 流式 + 原生 tool_calls + 文本工具调用修复,无任何外部引擎底座 |
|
|
55
58
|
| ✅ **多模型 API 优先** | OpenAI/DeepSeek/火山/通义 + 本地模型兜底 |
|
|
56
59
|
|
|
57
|
-
##
|
|
60
|
+
## 独立底座(自研,无外部引擎)
|
|
58
61
|
|
|
59
|
-
皮皮虾是**独立自包含的 agent
|
|
62
|
+
皮皮虾是**独立自包含的 agent,只跑自己的底座**:`src/llm/client.js` 用纯 Node `fetch` 直连任意 OpenAI 兼容 API(OpenAI/DeepSeek/火山/通义/智谱/本地 lmstudio/ollama/vLLM),零第三方引擎依赖。
|
|
60
63
|
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
+
- **v2.5.0 起外部引擎底座全部移除**:`openclaw` / `dsh`(DeepSeek Harness)后端代码、`_optional_engines` 配置、`.deps/` 内嵌目录、`dsh` npm 脚本、`openclaw-smoke.js` 全部删除。
|
|
65
|
+
- 默认:本地/HTTP 直连优先(router 按 local → 云端真 key 顺序回退,配 API key 即可跑)
|
|
66
|
+
- 多 provider 自动回退 + 瞬态错误重试(429/5xx/timeout)+ SSE 流式 + 原生 tool_calls + 文本工具调用修复(自研围栏 ⟪tool⟫ / DSML 解析)
|
|
64
67
|
- 保留:皮皮虾四层记忆 / 自愈 / 方法Skill / 工具 / web 壳 全部保留
|
|
65
68
|
|
|
66
|
-
### DeepSeek Harness 可选底座(dsh,需手动安装)
|
|
67
|
-
|
|
68
|
-
> `.deps/` 在 .gitignore 中,clone 后目录为空。若要用 dsh 底座,需先按下面步骤安装:
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
# 首次安装/构建内嵌 dsh(需要网络安装依赖)
|
|
72
|
-
npm run dsh:install
|
|
73
|
-
npm run dsh:build
|
|
74
|
-
|
|
75
|
-
# 直接运行 dsh CLI(等同 deepseek-harness 仓库的 pnpm dsh)
|
|
76
|
-
npm run dsh -- web
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
- 源码位置(安装后):`.deps/deepseek-harness/`(完整保留 deepseek-harness 的 packages/apps/docs/scripts/vendor 等)
|
|
80
|
-
- **构建形态优先(v1.5.1+)**:`dshRoot` 存在 `lib/bin.js`(已安装的 dsh npm 包,如 `npm i -g @deepseek-ai/dsh`)时直接零构建运行;否则回退源码形态(`apps/cli/src/bin.ts` + `node_modules/tsx`)
|
|
81
|
-
- 定位优先级:`PPX_DSH_ROOT` 环境变量 > provider 的 `dsh_root` 配置 > 内嵌 `.deps/deepseek-harness` 默认目录
|
|
82
|
-
- 未安装/构建 dsh 时,健康检查判不可用并自动回退 http/cloud,不受影响
|
|
83
|
-
|
|
84
69
|
## 🚀 快速开始
|
|
85
70
|
|
|
86
71
|
### ⚠️ 首次使用:本地模型优先,云端可选(极简配置)
|
|
@@ -137,7 +122,19 @@ npm run chat
|
|
|
137
122
|
# 4. 启动 HTTP 服务
|
|
138
123
|
node src/server.js # http://127.0.0.1:8899
|
|
139
124
|
|
|
140
|
-
# 5.
|
|
125
|
+
# 5. MCP 标准端点 (Streamable HTTP)
|
|
126
|
+
# POST http://127.0.0.1:8899/mcp (同 Bearer token 鉴权)
|
|
127
|
+
# 任何 MCP 客户端 (Claude Desktop / Cursor / MCP Inspector 等) 可直接接入:
|
|
128
|
+
# - server/discover + tools/list + tools/call (45+ 内置工具全量暴露)
|
|
129
|
+
# - resources: memory://facts, traces://recent, stats://overview, sessions://list
|
|
130
|
+
# - prompts: humanize / plan / debug / verify / write_article (方法型技能)
|
|
131
|
+
# - ppx.chat.send / ppx.chat.stream (对话工具, 驱动完整工具循环)
|
|
132
|
+
# - ppx.sessions.* / ppx.providers.* / ppx.settings.* / ppx.task.* (管理工具, web 壳在用)
|
|
133
|
+
# 配置: config/ppx.json → channels.http.mcp { enabled, path, legacy_rest }
|
|
134
|
+
# enabled 默认开; path 默认 /mcp; legacy_rest 默认 true (保留旧 REST 兼容), 设 false 彻底退役 /api/* 与 /message*
|
|
135
|
+
# (设 false 后这些 REST 端点返回 410 并引导 /mcp)
|
|
136
|
+
|
|
137
|
+
# 6. 跑测试
|
|
141
138
|
npm run test
|
|
142
139
|
```
|
|
143
140
|
|
|
@@ -241,14 +238,14 @@ PPXANS-Harness/
|
|
|
241
238
|
│ ├── audit/ verifier 语义验证闸门 + audit-chain 防篡改哈希链
|
|
242
239
|
│ ├── ans/ ANS 神经系 (values/lifecycle/proactive/reward/eviction/guard)
|
|
243
240
|
│ ├── selfheal/ 自愈引擎
|
|
244
|
-
│ ├── tools/ 工具系统 (
|
|
241
|
+
│ ├── tools/ 工具系统 (45+ 个 + MCP 动态注册; governance.js 为治理工具)
|
|
245
242
|
│ ├── channels/ 通道 (http/feishu/wechat)
|
|
246
243
|
│ ├── orchestrator/ 军团编排器 (多进程)
|
|
247
244
|
│ ├── llm/ LLM 客户端
|
|
248
245
|
│ └── utils/ 基础设施
|
|
249
246
|
├── data/ 运行时数据 (不进 git)
|
|
250
247
|
├── references/ 第三方项目来源登记 (不含源码)
|
|
251
|
-
├── test/ 测试 (
|
|
248
|
+
├── test/ 测试 (720 项 716 过 0 失败 4 跳过)
|
|
252
249
|
└── docs/ 文档
|
|
253
250
|
```
|
|
254
251
|
|
package/config/ppx.json
CHANGED
|
@@ -1,151 +1,133 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"wechat": {
|
|
135
|
-
"enabled": false,
|
|
136
|
-
"path": "/wechat/webhook",
|
|
137
|
-
"token": "",
|
|
138
|
-
"encodingAESKey": "",
|
|
139
|
-
"corpId": "",
|
|
140
|
-
"corpSecret": "",
|
|
141
|
-
"agentId": ""
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
"security": {
|
|
145
|
-
"allow_all": false,
|
|
146
|
-
"command_timeout_ms": 30000,
|
|
147
|
-
"deny": [
|
|
148
|
-
|
|
149
|
-
]
|
|
150
|
-
}
|
|
151
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"agent": {
|
|
3
|
+
"name": "皮皮虾",
|
|
4
|
+
"yuan": "ppx",
|
|
5
|
+
"max_tool_rounds": 8,
|
|
6
|
+
"tool_result_budget": 4000,
|
|
7
|
+
"max_tool_error_retry": 2,
|
|
8
|
+
"proactive": {
|
|
9
|
+
"enabled": true,
|
|
10
|
+
"interval_ms": 3600000
|
|
11
|
+
},
|
|
12
|
+
"evolve": {
|
|
13
|
+
"enabled": true,
|
|
14
|
+
"every_calls": 20,
|
|
15
|
+
"min_interval_ms": 30000
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"user": {
|
|
19
|
+
"name": "兄弟"
|
|
20
|
+
},
|
|
21
|
+
"providers": [
|
|
22
|
+
{
|
|
23
|
+
"id": "openai",
|
|
24
|
+
"base_url": "https://api.openai.com/v1",
|
|
25
|
+
"api_key_env": "OPENAI_API_KEY",
|
|
26
|
+
"model": "gpt-4o-mini",
|
|
27
|
+
"timeout_ms": 180000,
|
|
28
|
+
"context_window": 128000
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "deepseek",
|
|
32
|
+
"base_url": "https://api.deepseek.com/v1",
|
|
33
|
+
"api_key_env": "DEEPSEEK_API_KEY",
|
|
34
|
+
"model": "deepseek-chat",
|
|
35
|
+
"timeout_ms": 180000,
|
|
36
|
+
"context_window": 65536
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "dashscope",
|
|
40
|
+
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
41
|
+
"api_key_env": "DASHSCOPE_API_KEY",
|
|
42
|
+
"model": "qwen-turbo",
|
|
43
|
+
"timeout_ms": 180000,
|
|
44
|
+
"context_window": 131072
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "qwen-vl",
|
|
48
|
+
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
49
|
+
"api_key_env": "DASHSCOPE_API_KEY",
|
|
50
|
+
"model": "qwen-vl-max",
|
|
51
|
+
"vision": true,
|
|
52
|
+
"timeout_ms": 180000,
|
|
53
|
+
"context_window": 32768
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": "lmstudio",
|
|
57
|
+
"base_url": "http://127.0.0.1:1234/v1",
|
|
58
|
+
"api_key": "lm-studio",
|
|
59
|
+
"model": "gemma-4-e2b-uncensored-hauhaucs-aggressive-q8_k_p",
|
|
60
|
+
"vision": false,
|
|
61
|
+
"timeout_ms": 180000
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "zhipu",
|
|
65
|
+
"base_url": "https://open.bigmodel.cn/api/paas/v4",
|
|
66
|
+
"api_key_env": "ZHIPU_API_KEY",
|
|
67
|
+
"model": "glm-5v-turbo",
|
|
68
|
+
"vision": true,
|
|
69
|
+
"timeout_ms": 180000,
|
|
70
|
+
"context_window": 65536
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"memory": {
|
|
74
|
+
"enabled": true,
|
|
75
|
+
"token_budget": 2500,
|
|
76
|
+
"decay_per_day": 0.02,
|
|
77
|
+
"hit_bonus": 5,
|
|
78
|
+
"base_importance": 10,
|
|
79
|
+
"compile_threshold": 4.5,
|
|
80
|
+
"forget_speed": 1,
|
|
81
|
+
"memory_ttl_days": 90
|
|
82
|
+
},
|
|
83
|
+
"audit": {
|
|
84
|
+
"enabled": true,
|
|
85
|
+
"_note": "工具调用审计哈希链 (吸收自 ppx-v2)。append-only + SHA-256 链式防篡改, 落 data/logs/audit.ndjson。设 false 可关闭 (性能敏感场景)。校验: ppx 对话里调 audit_verify 工具, 或 npm run audit:verify。"
|
|
86
|
+
},
|
|
87
|
+
"embedding": {
|
|
88
|
+
"base_url": "http://127.0.0.1:1234/v1",
|
|
89
|
+
"api_key": "lm-studio",
|
|
90
|
+
"model": "text-embedding-nomic-embed-text-v1.5"
|
|
91
|
+
},
|
|
92
|
+
"experience": {
|
|
93
|
+
"enabled": true
|
|
94
|
+
},
|
|
95
|
+
"selfheal": {
|
|
96
|
+
"enabled": true,
|
|
97
|
+
"check_interval_ms": 60000
|
|
98
|
+
},
|
|
99
|
+
"tools": {
|
|
100
|
+
"enabled": true
|
|
101
|
+
},
|
|
102
|
+
"channels": {
|
|
103
|
+
"http": {
|
|
104
|
+
"enabled": true,
|
|
105
|
+
"port": 8899,
|
|
106
|
+
"auth_token": ""
|
|
107
|
+
},
|
|
108
|
+
"log": {
|
|
109
|
+
"enabled": true,
|
|
110
|
+
"target": "console"
|
|
111
|
+
},
|
|
112
|
+
"feishu": {
|
|
113
|
+
"enabled": false,
|
|
114
|
+
"appId": "",
|
|
115
|
+
"appSecret": "",
|
|
116
|
+
"verifyToken": ""
|
|
117
|
+
},
|
|
118
|
+
"wechat": {
|
|
119
|
+
"enabled": false,
|
|
120
|
+
"path": "/wechat/webhook",
|
|
121
|
+
"token": "",
|
|
122
|
+
"encodingAESKey": "",
|
|
123
|
+
"corpId": "",
|
|
124
|
+
"corpSecret": "",
|
|
125
|
+
"agentId": ""
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"security": {
|
|
129
|
+
"allow_all": false,
|
|
130
|
+
"command_timeout_ms": 30000,
|
|
131
|
+
"deny": []
|
|
132
|
+
}
|
|
133
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ppxans-harness",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "PPXANS-Harness - 皮皮虾神经系 (ANS) + Harness 一体化智能体内核. 零运行时依赖纯Node. 自愈 + 自学习 + 五层记忆 + SHA-256 审计哈希链 + 多 Agent 军团 + 治理内核(deny-wins/熔断/seam) + 进化内核(Playbook/故障记忆) + 符号画布 + supervisor. Agent Nervous System harness in pure Node, zero runtime dependencies.",
|
|
3
|
+
"version": "2.6.0",
|
|
4
|
+
"description": "PPXANS-Harness - 皮皮虾神经系 (ANS) + Harness 一体化智能体内核. 零运行时依赖纯Node. 自愈 + 自学习 + 五层记忆 + SHA-256 审计哈希链 + 多 Agent 军团 + 治理内核(deny-wins/熔断/seam) + 进化内核(Playbook/故障记忆) + 符号画布 + supervisor. 自研底座: 仅 OpenAI 兼容 HTTP 直连, 无外部引擎依赖. Agent Nervous System harness in pure Node, zero runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "chen6896qqwee <chen6896qqwee@users.noreply.github.com>",
|
|
@@ -31,9 +31,6 @@
|
|
|
31
31
|
"web:build": "npm run build --prefix web",
|
|
32
32
|
"release": "node scripts/release.js",
|
|
33
33
|
"eval": "node scripts/eval.js",
|
|
34
|
-
"dsh": "cd .deps/deepseek-harness && pnpm dsh",
|
|
35
|
-
"dsh:install": "cd .deps/deepseek-harness && pnpm install",
|
|
36
|
-
"dsh:build": "cd .deps/deepseek-harness && pnpm run build",
|
|
37
34
|
"selfheal": "node scripts/selfheal-bench.js",
|
|
38
35
|
"selfheal:run": "node src/selfheal/run.js",
|
|
39
36
|
"audit:verify": "node scripts/audit-verify.js",
|
package/src/agent/index.js
CHANGED
|
@@ -375,6 +375,7 @@ export class PPXAgent {
|
|
|
375
375
|
}
|
|
376
376
|
// 工具类任务: 优先原生 tool_calls 后端 (http)。
|
|
377
377
|
// openclaw 是完整 agent 运行时, 会拒绝围栏协议(视为伪协议); 实测 http 原生 tool_calls 全链路通过。
|
|
378
|
+
// v2.5.0: 外部引擎底座已移除, 仅 http 后端 (原生 tool_calls + 文本工具修复)。
|
|
378
379
|
if (this.toolsEnabled) {
|
|
379
380
|
const native = clients.filter((c) => c.supportsNativeToolCalls);
|
|
380
381
|
const fence = clients.filter((c) => !c.supportsNativeToolCalls);
|
|
@@ -402,7 +403,7 @@ export class PPXAgent {
|
|
|
402
403
|
throw lastErr || new Error("所有 provider 均失败");
|
|
403
404
|
}
|
|
404
405
|
|
|
405
|
-
// 统一工具执行入口 (
|
|
406
|
+
// 统一工具执行入口 (http 原生 tool_calls + 文本工具调用修复) [P0#1]
|
|
406
407
|
// v1.0.7: 移除未使用的 llmInstance 死参数, 所有工具执行统一走此入口 (trace/事件只此一份)
|
|
407
408
|
async _runTool(name, args) {
|
|
408
409
|
const t0 = Date.now();
|
|
@@ -636,8 +637,7 @@ export class PPXAgent {
|
|
|
636
637
|
_warnMissingCloudApi() {
|
|
637
638
|
const provs = (this.config && this.config.providers) || [];
|
|
638
639
|
const isLocal = (p) => /127\.0\.0\.1|localhost|lm-studio|ollama/i.test(p.base_url || "");
|
|
639
|
-
const
|
|
640
|
-
const cloud = provs.filter((p) => !isLocal(p) && !isSpecial(p));
|
|
640
|
+
const cloud = provs.filter((p) => !isLocal(p));
|
|
641
641
|
const hasCloudKey = cloud.some((p) => p.api_key || (p.api_key_env && process.env[p.api_key_env]));
|
|
642
642
|
const hasLocal = provs.some(isLocal);
|
|
643
643
|
// 默认本地优先(agent.model_preference=local): 有本地模型即满足运行, 不告警(本地测试正用本地模型)
|
package/src/agent/prompts.js
CHANGED
|
@@ -8,7 +8,7 @@ import { context as rewardContext } from "../ans/reward.js";
|
|
|
8
8
|
import { imageFileToDataUrl } from "../tools/builtin.js";
|
|
9
9
|
|
|
10
10
|
// 多模态: 提取 user 消息中的图片路径并同步读图, 注入 OpenAI 视觉格式的 content 数组。
|
|
11
|
-
// 仅当当前 LLM 是 http 后端且 provider 标记 vision=true 时生效 (
|
|
11
|
+
// 仅当当前 LLM 是 http 后端且 provider 标记 vision=true 时生效 (自研底座唯一后端)。
|
|
12
12
|
// 返回 string (无图/不支持) 或 [{type:text}, {type:image_url}...]
|
|
13
13
|
export function visionUserContent(llm, root, userMsg) {
|
|
14
14
|
const text = String(userMsg);
|
package/src/channels/http.js
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
import { getSettings, updateSettings } from "../config/settings.js";
|
|
12
12
|
import { suggestProactive } from "../ans/proactive.js";
|
|
13
13
|
import { ensureDir, atomicWrite, readText } from "../utils/store.js";
|
|
14
|
+
import { createMcpEndpoint } from "../mcp/http.js";
|
|
15
|
+
import { createAdminTools } from "../mcp/admin.js";
|
|
14
16
|
|
|
15
17
|
const MAX_BODY = 1024 * 1024; // 请求体上限 1MB
|
|
16
18
|
const RATE_PER_MIN = 60; // 每 IP 每分钟最大请求数 (令牌桶)
|
|
@@ -67,6 +69,36 @@ export class HttpChannel extends Channel {
|
|
|
67
69
|
// 配置后仅放行白名单 origin, 其余跨域请求 403 (token 泄露时降低任意跨站读取风险)
|
|
68
70
|
this.corsOrigins = this._corsFromConfig();
|
|
69
71
|
this._inFlight = 0; // 当前处理中的对话请求数
|
|
72
|
+
// v2.6.0: MCP 标准端点 (Streamable HTTP) — 默认开启, 路径 /mcp
|
|
73
|
+
const mcpCfg = (this.agent?.config?.channels?.http?.mcp) || {};
|
|
74
|
+
this.mcpEnabled = mcpCfg.enabled !== false;
|
|
75
|
+
this.mcpPath = mcpCfg.path || "/mcp";
|
|
76
|
+
// v2.6.0: 产品壳已全部走 MCP (前端不再调用 REST)。服务端默认保留 REST 兼容 (旧脚本/测试不受影响);
|
|
77
|
+
// 想彻底退役可设 channels.http.mcp.legacy_rest=false → /api/* 与 /message* 返回 410 并引导 /mcp
|
|
78
|
+
this.mcpLegacyRest = mcpCfg.legacy_rest !== false;
|
|
79
|
+
if (this.mcpEnabled) {
|
|
80
|
+
// v2.6.0: admin 虚拟工具 (会话/提供方/设置/任务面板) 注入 MCP 端点, 供 web 前端替代 REST /api/*
|
|
81
|
+
const admin = createAdminTools(agent);
|
|
82
|
+
this.mcpAdmin = admin;
|
|
83
|
+
const { server: mcpServer, handler: mcpHandler } = createMcpEndpoint(agent, {
|
|
84
|
+
name: agent.config?.agent?.name || "ppxans-harness",
|
|
85
|
+
version: "2.5.0",
|
|
86
|
+
supportedVersions: mcpCfg.supported_versions,
|
|
87
|
+
extraTools: admin.tools,
|
|
88
|
+
authenticated: (req, res) => {
|
|
89
|
+
if (this._authed(req, res)) return true;
|
|
90
|
+
// _authed 只做判断不写响应, 这里补 401 (否则客户端永远挂等)
|
|
91
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
92
|
+
res.end(JSON.stringify({ error: "unauthorized" }));
|
|
93
|
+
return false;
|
|
94
|
+
},
|
|
95
|
+
rateLimit: (req, res) => this._rateLimit(req, res),
|
|
96
|
+
// Origin 校验已由顶部统一 CORS 处理 (含白名单/默认 localhost), MCP handler 内不再二次拦截
|
|
97
|
+
skipOriginCheck: true,
|
|
98
|
+
});
|
|
99
|
+
this.mcpServer = mcpServer;
|
|
100
|
+
this.mcpHandler = mcpHandler;
|
|
101
|
+
}
|
|
70
102
|
}
|
|
71
103
|
|
|
72
104
|
// 简单并发护栏: 用计数信号量限制同时处理的对话请求, 超出立即 429
|
|
@@ -174,20 +206,12 @@ export class HttpChannel extends Channel {
|
|
|
174
206
|
async connect() {
|
|
175
207
|
this._ensureToken(); // 启动时确保有 token
|
|
176
208
|
this.server = http.createServer(async (req, res) => {
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
if (wh) {
|
|
181
|
-
try { await wh(req, res); } catch (e) {
|
|
182
|
-
try { if (!res.writableEnded) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: e.message })); } } catch {}
|
|
183
|
-
}
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
// CORS (v1.0.7): 默认 * (兼容); 配置 cors_origin 白名单时校验浏览器来源 (无 Origin 的非浏览器请求不受 CORS 约束)
|
|
187
|
-
const origin = req.headers.origin;
|
|
209
|
+
// ---- 统一 CORS 响应头 (所有路由含 /mcp 共用) ----
|
|
210
|
+
// v1.0.7 语义: 默认 * (兼容); 配置 cors_origin 白名单时校验浏览器来源
|
|
211
|
+
const reqOrigin = req.headers.origin;
|
|
188
212
|
let allowOrigin = "*";
|
|
189
213
|
if (this.corsOrigins.length) {
|
|
190
|
-
allowOrigin = !
|
|
214
|
+
allowOrigin = !reqOrigin ? "*" : (this.corsOrigins.includes(reqOrigin) ? reqOrigin : null);
|
|
191
215
|
}
|
|
192
216
|
if (!allowOrigin) {
|
|
193
217
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
@@ -197,15 +221,41 @@ export class HttpChannel extends Channel {
|
|
|
197
221
|
res.setHeader("Access-Control-Allow-Origin", allowOrigin);
|
|
198
222
|
if (allowOrigin !== "*") res.setHeader("Vary", "Origin");
|
|
199
223
|
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
|
200
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
224
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, MCP-Protocol-Version, Mcp-Method, Mcp-Name");
|
|
201
225
|
if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; }
|
|
202
226
|
|
|
227
|
+
// v2.6.0: MCP 标准端点优先 (Streamable HTTP, 单端点 POST)
|
|
228
|
+
const reqPath0 = (req.url || "/").split("?")[0];
|
|
229
|
+
if (this.mcpEnabled && this.mcpHandler && req.method === "POST" && reqPath0 === this.mcpPath) {
|
|
230
|
+
try { await this.mcpHandler(req, res); } catch (e) {
|
|
231
|
+
try { if (!res.writableEnded) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: e.message })); } } catch {}
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// v1.0.8: webhook 路由分发 (feishu/wechat 等): 匹配路径交给对应通道, 不走主逻辑
|
|
236
|
+
const wh = this.webhookRoutes.get(reqPath0);
|
|
237
|
+
if (wh) {
|
|
238
|
+
try { await wh(req, res); } catch (e) {
|
|
239
|
+
try { if (!res.writableEnded) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: e.message })); } } catch {}
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// (后续路由的 CORS 已在顶部统一处理, 这里移除旧的重复设置)
|
|
244
|
+
|
|
203
245
|
// 静态页面与 /health 不设限, 其余 API 限流
|
|
204
246
|
const reqPath = (req.url || "/").split("?")[0];
|
|
205
247
|
if (!(req.method === "GET" && (reqPath === "/" || reqPath === "/index.html" || reqPath === "/health"))) {
|
|
206
248
|
if (!this._rateLimit(req, res)) return;
|
|
207
249
|
}
|
|
208
250
|
|
|
251
|
+
// v2.6.0 REST 退役开关: /message* /sessions* /reset 与 /api/* 同受 mcp.legacy_rest 控制
|
|
252
|
+
// (默认关闭: 全部走标准 MCP 端点 /mcp; 旧脚本可配置 legacy_rest: true 恢复)
|
|
253
|
+
if (!this.mcpLegacyRest && ["/message", "/message/stream", "/chat", "/sessions", "/reset"].some((p) => reqPath === p || reqPath.startsWith(p + "/"))) {
|
|
254
|
+
res.writeHead(410, { "Content-Type": "application/json" });
|
|
255
|
+
res.end(JSON.stringify({ error: "REST 端点已退役, 请使用标准 MCP 端点 POST /mcp (channels.http.mcp.legacy_rest=true 可恢复)" }));
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
209
259
|
if (req.method === "GET" && req.url === "/health") {
|
|
210
260
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
211
261
|
res.end(JSON.stringify({ status: "ok", agent: this.agent.config.agent?.name || "ppx" }));
|
|
@@ -354,9 +404,18 @@ export class HttpChannel extends Channel {
|
|
|
354
404
|
}
|
|
355
405
|
return;
|
|
356
406
|
}
|
|
357
|
-
// API 端点认证
|
|
407
|
+
// API 端点认证 + v2.6.0 REST 退役开关
|
|
408
|
+
// channels.http.mcp.legacy_rest = true 时保留旧 /api/* REST 端点 (兼容旧脚本/客户端);
|
|
409
|
+
// 默认 false: /api/* 返回 410 Gone, 引导使用标准 MCP 端点 /mcp
|
|
358
410
|
const protectedApi = reqPath.startsWith("/api/");
|
|
359
|
-
if (protectedApi
|
|
411
|
+
if (protectedApi) {
|
|
412
|
+
if (!this.mcpLegacyRest) {
|
|
413
|
+
res.writeHead(410, { "Content-Type": "application/json" });
|
|
414
|
+
res.end(JSON.stringify({ error: "REST /api/* 已退役, 请使用标准 MCP 端点 POST /mcp (channels.http.mcp.legacy_rest=true 可恢复)" }));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (!this._authed(req, res)) { res.writeHead(401, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "unauthorized" })); return; }
|
|
418
|
+
}
|
|
360
419
|
// 轨迹 API
|
|
361
420
|
if (req.method === "GET" && reqPath === "/api/traces") {
|
|
362
421
|
const limit = Number((req.url.split("limit=")[1] || "").split("&")[0] || 50);
|
|
@@ -506,8 +565,8 @@ export class HttpChannel extends Channel {
|
|
|
506
565
|
}
|
|
507
566
|
const healthy = await client.health();
|
|
508
567
|
const detail = healthy
|
|
509
|
-
?
|
|
510
|
-
: "探测失败, 请检查 key/base_url
|
|
568
|
+
? (fromCache ? "复用 agent 客户端, 探测通过" : "API 端点可达")
|
|
569
|
+
: "探测失败, 请检查 key/base_url";
|
|
511
570
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
512
571
|
res.end(JSON.stringify({ ok: true, healthy, detail, source: fromCache ? "agent-cache" : "disk-config" }));
|
|
513
572
|
} catch (e) {
|