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 CHANGED
@@ -1,9 +1,9 @@
1
1
  # 🦐 PPXANS-Harness
2
2
 
3
3
  > **PPXANS-Harness = 皮皮虾神经系 (ANS) + Harness 一体化智能体内核**,纯 Node.js 编写,**零运行时依赖**。
4
- > 43 内置工具 · L0–L4 五层记忆 · SHA-256 审计哈希链 · MCP 客户端 · 多模型路由 · 自愈 7/7 · 597 测试。
4
+ > 45+ 内置工具 · L0–L4 五层记忆 · SHA-256 审计哈希链 · MCP 服务端+客户端 · 多模型路由 · 自愈 7/7 · **716 测试全绿**。
5
5
  >
6
- > 由 `ppx-agent v1.6.0`(合并基座)+ `ppx-v2 v0.4.0`(能力吸收)合并而成。合并范围与取舍见 [MERGE-REPORT.md](MERGE-REPORT.md)、第三方来源见 [references/THIRD-PARTY-SOURCES.md](references/THIRD-PARTY-SOURCES.md)。
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
- | 🔧 **43个内置工具** | 文件/命令/搜索/HTTP/定时/记忆检索/读图/文档加载/文档入库/OCR/code_act/refine/refine_skill + **10个治理运维工具**(memory_forget/restore/export/import/clear_layer/list_deleted、audit_verify、persona_build/read、selfheal_run) |
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
- | ✅ **OpenClaw / DeepSeek Harness 底座** | LLM 引擎通过 `openclaw agent` CLI 驱动 OpenClaw,或通过 `dsh` 后端驱动内嵌 DeepSeek Harness(`.deps/deepseek-harness`);围栏协议代理工具,保留多 provider 回退 |
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**:默认用 `src/llm/router.js` 在本地/HTTP、内嵌 dsh 引擎、云端 OpenAI 兼容 API 之间自动回退(OpenAI/DeepSeek/火山/通义/本地),多 provider 自动回退 + 瞬态错误重试。
62
+ 皮皮虾是**独立自包含的 agent,只跑自己的底座**:`src/llm/client.js` 用纯 Node `fetch` 直连任意 OpenAI 兼容 API(OpenAI/DeepSeek/火山/通义/智谱/本地 lmstudio/ollama/vLLM),零第三方引擎依赖。
60
63
 
61
- - 默认:本地/HTTP 直连优先(router 按 local → dsh 引擎 → cloud 顺序回退,配 API key 即可跑)
62
- - **DeepSeek Harness 底座(可选,需手动安装)**:`.deps/deepseek-harness` 为**可选底座,不随仓库分发**(见 .gitignore),需先 `npm run dsh:install` + `npm run dsh:build` 才可用;`dsh` 已加入 `providers` 首位(default_id=dsh)。未安装时健康检查自动判不可用并回退 http/cloud,不受影响。
63
- - 可选引擎:`openclaw` / `dsh` 后端代码保留(`backend: "openclaw"` / `"deepseek"`),需自行在 config 加 provider 或用环境变量 `PPX_OPENCLAW_MJS` / `PPX_DSH_ROOT` 指定引擎位置
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/ 工具系统 (43个 + MCP 动态注册; governance.js 为治理工具)
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/ 测试 (597 项 591 过 0 失败 6 跳过)
248
+ ├── test/ 测试 (720 项 716 过 0 失败 4 跳过)
252
249
  └── docs/ 文档
253
250
  ```
254
251
 
package/config/ppx.json CHANGED
@@ -1,151 +1,133 @@
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": "dsh",
24
- "backend": "deepseek",
25
- "dsh_root": ".deps/deepseek-harness",
26
- "timeout_ms": 180000
27
- },
28
- {
29
- "id": "openai",
30
- "base_url": "https://api.openai.com/v1",
31
- "api_key_env": "OPENAI_API_KEY",
32
- "model": "gpt-4o-mini",
33
- "timeout_ms": 180000,
34
- "context_window": 128000
35
- },
36
- {
37
- "id": "deepseek",
38
- "base_url": "https://api.deepseek.com/v1",
39
- "api_key_env": "DEEPSEEK_API_KEY",
40
- "model": "deepseek-chat",
41
- "timeout_ms": 180000,
42
- "context_window": 65536
43
- },
44
- {
45
- "id": "dashscope",
46
- "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
47
- "api_key_env": "DASHSCOPE_API_KEY",
48
- "model": "qwen-turbo",
49
- "timeout_ms": 180000,
50
- "context_window": 131072
51
- },
52
- {
53
- "id": "qwen-vl",
54
- "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
55
- "api_key_env": "DASHSCOPE_API_KEY",
56
- "model": "qwen-vl-max",
57
- "vision": true,
58
- "timeout_ms": 180000,
59
- "context_window": 32768
60
- },
61
- {
62
- "id": "lmstudio",
63
- "base_url": "http://127.0.0.1:1234/v1",
64
- "api_key": "lm-studio",
65
- "model": "gemma-4-e2b-uncensored-hauhaucs-aggressive-q8_k_p",
66
- "vision": false,
67
- "timeout_ms": 180000
68
- },
69
- {
70
- "id": "zhipu",
71
- "base_url": "https://open.bigmodel.cn/api/paas/v4",
72
- "api_key_env": "ZHIPU_API_KEY",
73
- "model": "glm-5v-turbo",
74
- "vision": true,
75
- "timeout_ms": 180000,
76
- "context_window": 65536
77
- }
78
- ],
79
- "_optional_engines": {
80
- "_note": "可选引擎。火山引擎 volcengine 需先填真实 endpoint(model 不能留 REPLACE_WITH_YOUR_ENDPOINT 占位), 把它加回 providers 数组并设 VOLCENGINE_API_KEY 即启用;本地引擎(openclaw/dsh)默认不启用, 要用时把对象移入 providers 并设对应路径环境变量。",
81
- "openclaw": {
82
- "id": "openclaw",
83
- "backend": "openclaw",
84
- "mjs": "",
85
- "session_key": "ppx:main",
86
- "timeout_ms": 180000
87
- }
88
- },
89
- "memory": {
90
- "enabled": true,
91
- "token_budget": 2500,
92
- "decay_per_day": 0.02,
93
- "hit_bonus": 5,
94
- "base_importance": 10,
95
- "compile_threshold": 4.5,
96
- "forget_speed": 1,
97
- "memory_ttl_days": 90
98
- },
99
- "audit": {
100
- "enabled": true,
101
- "_note": "工具调用审计哈希链 (吸收自 ppx-v2)。append-only + SHA-256 链式防篡改, 落 data/logs/audit.ndjson。设 false 可关闭 (性能敏感场景)。校验: ppx 对话里调 audit_verify 工具, 或 npm run audit:verify。"
102
- },
103
- "embedding": {
104
- "base_url": "http://127.0.0.1:1234/v1",
105
- "api_key": "lm-studio",
106
- "model": "text-embedding-nomic-embed-text-v1.5"
107
- },
108
- "experience": {
109
- "enabled": true
110
- },
111
- "selfheal": {
112
- "enabled": true,
113
- "check_interval_ms": 60000
114
- },
115
- "tools": {
116
- "enabled": true
117
- },
118
- "channels": {
119
- "http": {
120
- "enabled": true,
121
- "port": 8899,
122
- "auth_token": ""
123
- },
124
- "log": {
125
- "enabled": true,
126
- "target": "console"
127
- },
128
- "feishu": {
129
- "enabled": false,
130
- "appId": "",
131
- "appSecret": "",
132
- "verifyToken": ""
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.0",
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",
@@ -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
- // 统一工具执行入口 (供 http 原生 tool_calls + openclaw/dsh 围栏代理共用) [P0#1]
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 isSpecial = (p) => p.backend === "openclaw" || p.backend === "dsh" || p.id === "dsh";
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): 有本地模型即满足运行, 不告警(本地测试正用本地模型)
@@ -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 时生效 (openclaw/dsh 走文本围栏不传图)。
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);
@@ -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
- // v1.0.8: webhook 路由分发 (feishu/wechat 等): 匹配路径交给对应通道, 不走主逻辑
178
- const reqPath0 = (req.url || "/").split("?")[0];
179
- const wh = this.webhookRoutes.get(reqPath0);
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 = !origin ? "*" : (this.corsOrigins.includes(origin) ? origin : null);
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 && !this._authed(req, res)) { res.writeHead(401, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "unauthorized" })); return; }
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
- ? `${client.backend === "openclaw" ? "openclaw 后端: Node 版本满足" : client.backend === "deepseek" ? "dsh 源码就绪" : fromCache ? "复用 agent 客户端, 探测通过" : "API 端点可达"}`
510
- : "探测失败, 请检查 key/base_url/engine 路径/Node 版本";
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) {