ppxans-harness 2.4.0 → 3.2.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/LICENSE +201 -201
- package/README.md +218 -265
- package/bin/ppx-channels.js +2 -2
- package/bin/ppx-serve.js +5 -5
- package/bin/ppx-setup.js +124 -0
- package/bin/ppx-web.js +140 -0
- package/bin/ppx.js +2 -2
- package/config/identity.md +6 -6
- package/config/ishiki.md +16 -16
- package/config/ppx.json +15 -151
- package/config/ppx.json.example +143 -0
- package/package.json +17 -10
- package/skills/.usage.json +6 -0
- package/skills/agent-professional-training/SKILL.md +94 -0
- package/skills/brainstorm/SKILL.md +24 -0
- package/skills/cupid-lover-comms/SKILL.md +37 -0
- package/skills/debug/SKILL.md +26 -0
- package/skills/plan/SKILL.md +25 -0
- package/skills/ponytail/SKILL.md +25 -0
- package/skills/ppx-memory/SKILL.md +91 -0
- package/skills/ppx-memory/scripts/cli.js +192 -0
- package/skills/ppx-memory/scripts/experience.js +133 -0
- package/skills/ppx-memory/scripts/fact-store.js +842 -0
- package/skills/ppx-memory/scripts/l0.js +52 -0
- package/skills/ppx-memory/scripts/l2.js +146 -0
- package/skills/ppx-memory/scripts/l3.js +112 -0
- package/skills/ppx-memory/scripts/memory-ticker.js +238 -0
- package/skills/ppx-memory/scripts/pii.js +42 -0
- package/skills/ppx-memory/scripts/schema.js +80 -0
- package/skills/ppx-memory/scripts/session.js +398 -0
- package/skills/ppx-memory/scripts/similarity.js +43 -0
- package/skills/ppx-memory/scripts/store.js +116 -0
- package/skills/ppx-memory/scripts/wal.js +38 -0
- package/skills/ppx-selfheal/SKILL.md +24 -0
- package/skills/ppx-selfheal/scripts/cli.js +80 -0
- package/skills/ppx-selfheal/scripts/healer.js +184 -0
- package/skills/ppx-selfheal/scripts/logger.js +17 -0
- package/skills/ppx-selfheal/scripts/store.js +116 -0
- package/skills/prompt-depth-kit/SKILL.md +28 -0
- package/skills/session-naming/SKILL.md +36 -0
- package/skills/verify/SKILL.md +25 -0
- package/src/agent/index.js +1340 -717
- package/src/agent/prompts.js +47 -3
- package/src/aml-server.js +197 -151
- package/src/ans/eviction.js +123 -143
- package/src/ans/guard.js +159 -120
- package/src/ans/lifecycle.js +96 -93
- package/src/ans/proactive.js +112 -129
- package/src/ans/reward.js +95 -111
- package/src/ans/values.js +15 -15
- package/src/audit/audit-chain.js +43 -7
- package/src/audit/verifier.js +157 -120
- package/src/bus/circuit-breaker.js +9 -1
- package/src/bus/runtime-bus.js +107 -93
- package/src/channels/base.js +57 -34
- package/src/channels/feishu.js +118 -126
- package/src/channels/http.js +1090 -592
- package/src/channels/index.js +111 -110
- package/src/channels/log.js +29 -29
- package/src/channels/wechat-crypto.js +73 -74
- package/src/channels/wechat.js +191 -197
- package/src/channels/workspace.js +94 -0
- package/src/channels-cli.js +126 -124
- package/src/cli.js +129 -120
- package/src/commands/index.js +142 -0
- package/src/config/channels.js +137 -170
- package/src/config/index.js +277 -224
- package/src/config/placeholder.js +31 -0
- package/src/config/providers.js +148 -188
- package/src/config/settings.js +156 -182
- package/src/core/policy.js +69 -18
- package/src/core/trace.js +8 -6
- package/src/edit/editblock.js +266 -0
- package/src/edit/snapshot.js +67 -0
- package/src/evidence/index.js +153 -0
- package/src/evolve/playbook.js +9 -10
- package/src/hooks/index.js +113 -0
- package/src/llm/client.js +188 -446
- package/src/llm/dsml.js +74 -74
- package/src/llm/embedder.js +41 -35
- package/src/llm/fence.js +52 -105
- package/src/llm/index.js +4 -4
- package/src/llm/local-embedder.js +94 -0
- package/src/llm/presets.js +113 -0
- package/src/llm/pricing.js +93 -0
- package/src/llm/retry.js +73 -73
- package/src/llm/router.js +92 -97
- package/src/mcp/admin.js +326 -0
- package/src/mcp/client.js +487 -375
- package/src/mcp/http.js +203 -0
- package/src/mcp/index.js +116 -116
- package/src/mcp/server.js +392 -0
- package/src/mcp/tasks.js +133 -0
- package/src/memory/asset-hub.js +6 -11
- package/src/memory/canvas.js +2 -5
- package/src/memory/compaction.js +28 -28
- package/src/memory/experience.js +133 -122
- package/src/memory/fact-store.js +914 -698
- package/src/memory/failure-episode.js +20 -11
- package/src/memory/fork.js +17 -8
- package/src/memory/index.js +8 -6
- package/src/memory/l0.js +53 -52
- package/src/memory/l2.js +145 -130
- package/src/memory/l3.js +111 -111
- package/src/memory/legion-board.js +71 -0
- package/src/memory/memory-ticker.js +239 -240
- package/src/memory/session.js +398 -397
- package/src/memory/sqlite-store.js +581 -0
- package/src/mode/blackboard.js +49 -49
- package/src/mode/graph.js +42 -41
- package/src/mode/index.js +64 -64
- package/src/mode/legion.js +54 -51
- package/src/mode/plan-exec.js +50 -50
- package/src/mode/router.js +28 -40
- package/src/orchestrator/agent-worker.js +69 -69
- package/src/orchestrator/dag.js +90 -83
- package/src/orchestrator/experts.js +76 -0
- package/src/orchestrator/index.js +1 -1
- package/src/orchestrator/legion.js +179 -187
- package/src/orchestrator/supervisor.js +6 -8
- package/src/permissions/index.js +378 -0
- package/src/persona/index.js +28 -29
- package/src/plugin/builtin.js +313 -212
- package/src/plugin/context.js +80 -79
- package/src/plugin/index.js +62 -62
- package/src/plugin/v3.js +73 -0
- package/src/protocol/index.js +148 -0
- package/src/repomap/index.js +309 -0
- package/src/review/index.js +393 -0
- package/src/seam/registry.js +3 -0
- package/src/seam/shell.js +55 -55
- package/src/security/injection.js +79 -0
- package/src/selfheal/evolve.js +67 -67
- package/src/selfheal/healer.js +184 -167
- package/src/selfheal/run.js +9 -9
- package/src/server.js +63 -60
- package/src/services/diagnose.js +180 -0
- package/src/services/learning-service.js +9 -0
- package/src/services/memory-health.js +34 -6
- package/src/services/memory-service.js +42 -9
- package/src/services/triage.js +138 -0
- package/src/session/parts.js +76 -0
- package/src/session/projection.js +73 -0
- package/src/session/rollout.js +54 -0
- package/src/session/turn.js +137 -0
- package/src/skills/lint.js +72 -0
- package/src/skills/loader.js +231 -150
- package/src/skills/search.js +58 -0
- package/src/skills/verify.js +95 -100
- package/src/tools/advanced.js +388 -352
- package/src/tools/builtin.js +384 -297
- package/src/tools/catalog.js +283 -159
- package/src/tools/command-guard.js +112 -112
- package/src/tools/custom.js +47 -47
- package/src/tools/delegate.js +383 -297
- package/src/tools/document.js +254 -253
- package/src/tools/git.js +151 -0
- package/src/tools/governance.js +47 -20
- package/src/tools/index.js +16 -11
- package/src/tools/methods.js +178 -178
- package/src/tools/ocr.js +59 -59
- package/src/tools/sandbox-worker.js +40 -0
- package/src/tools/sandbox.js +92 -0
- package/src/tools/seam.js +162 -125
- package/src/tools/selfmod.js +196 -176
- package/src/tools/v3.js +225 -0
- package/src/tools/vad.js +176 -0
- package/src/tools/voice.js +238 -0
- package/src/utils/async.js +14 -0
- package/src/utils/config-file.js +53 -0
- package/src/utils/crashguard.js +88 -0
- package/src/utils/http.js +53 -0
- package/src/utils/id.js +8 -0
- package/src/utils/json-state.js +33 -0
- package/src/utils/logger.js +17 -17
- package/src/utils/ndjson.js +25 -0
- package/src/utils/pii.js +42 -42
- package/src/utils/rate-limit.js +50 -0
- package/src/utils/schema.js +80 -0
- package/src/utils/similarity.js +43 -0
- package/src/utils/store.js +170 -108
- package/src/utils/text.js +15 -15
- package/src/utils/trace.js +153 -153
- package/src/utils/wal.js +39 -0
- package/src/utils/winutf8.js +16 -15
- package/src/wiki/index.js +170 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
// src/mcp/server.js - MCP (Model Context Protocol) 服务器核心 (零依赖)
|
|
2
|
+
// 把皮皮虾的 43+ 内置工具 + 记忆/轨迹/统计/会话资源 + 方法型技能 暴露为标准 MCP 服务。
|
|
3
|
+
// 双 era 支持:
|
|
4
|
+
// - 现代 2026-07-28: 无握手, 每请求 _meta 携带 protocolVersion/clientInfo/clientCapabilities,
|
|
5
|
+
// server/discover 探测, 结果带 resultType, 响应 _meta 带 serverInfo 身份戳。
|
|
6
|
+
// - legacy 2025-06-18 及更早: initialize 握手 + capabilities 协商 (兼容存量 MCP 客户端)。
|
|
7
|
+
// 传输无关: 仅做 JSON-RPC 分发, stdio/HTTP 传输由上层 (src/mcp/http.js 等) 接入。
|
|
8
|
+
import { TOOL_ERROR_PREFIX } from "../tools/catalog.js";
|
|
9
|
+
import { warn, debug } from "../utils/logger.js";
|
|
10
|
+
import { sanitizeMcpName } from "./index.js";
|
|
11
|
+
|
|
12
|
+
// 版本号统一读 package.json, 避免与发布版本漂移 (外部体检: 硬编码 2.5.0 导致 serverInfo 落后真实版本)
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
let PKG_VERSION = "0.0.0";
|
|
17
|
+
try {
|
|
18
|
+
PKG_VERSION = JSON.parse(readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json"), "utf8")).version || "0.0.0";
|
|
19
|
+
} catch (e) { debug(`[mcp/server] 已忽略异常: ${e && e.message ? e.message : e}`); } // 非标准安装位置时降级, 不影响启动
|
|
20
|
+
|
|
21
|
+
// 协议版本常量 (与官方 schema/2026-07-28 对齐)
|
|
22
|
+
export const MODERN_PROTOCOL_VERSION = "2026-07-28"; // 现代 era (每请求 _meta)
|
|
23
|
+
export const LEGACY_PROTOCOL_VERSION = "2025-06-18"; // legacy era (initialize 握手) 最高支持版本
|
|
24
|
+
export const SUPPORTED_VERSIONS = [MODERN_PROTOCOL_VERSION, LEGACY_PROTOCOL_VERSION, "2025-03-26", "2024-11-05"];
|
|
25
|
+
export const SERVER_INFO_META_KEY = "io.modelcontextprotocol/serverInfo";
|
|
26
|
+
|
|
27
|
+
// ---- MCP 标准错误码 (JSON-RPC 保留段 + MCP 定义段) ----
|
|
28
|
+
export const MCP_ERROR = {
|
|
29
|
+
PARSE_ERROR: -32700,
|
|
30
|
+
INVALID_REQUEST: -32600,
|
|
31
|
+
METHOD_NOT_FOUND: -32601,
|
|
32
|
+
INVALID_PARAMS: -32602,
|
|
33
|
+
INTERNAL_ERROR: -32603,
|
|
34
|
+
HEADER_MISMATCH: -32020, // HTTP 头与 body _meta 不一致
|
|
35
|
+
MISSING_REQUIRED_CLIENT_CAPABILITY: -32021,
|
|
36
|
+
UNSUPPORTED_PROTOCOL_VERSION: -32022, // 版本不支持
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// MCP 请求级错误: 携带 code/message/data, 由分发层转成 JSON-RPC error 响应
|
|
40
|
+
export class McpError extends Error {
|
|
41
|
+
constructor(code, message, data = null) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.code = code;
|
|
44
|
+
this.data = data;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isModernRequest(msg) {
|
|
49
|
+
return !!(msg && msg.params && msg.params._meta && msg.params._meta["io.modelcontextprotocol/protocolVersion"]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isLegacyInitialize(msg) {
|
|
53
|
+
return !!(msg && msg.method === "initialize" && !isModernRequest(msg));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 结果统一加 serverInfo 身份戳 (2026-07-28: 身份在 result._meta 而非 body)
|
|
57
|
+
function stampServerInfo(result, serverInfo) {
|
|
58
|
+
if (!result || typeof result !== "object") return result;
|
|
59
|
+
return { ...result, _meta: { ...(result._meta || {}), [SERVER_INFO_META_KEY]: serverInfo } };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---- 工具名清洗: MCP 规范只允许 [A-Za-z0-9_.-] ----
|
|
63
|
+
function safeToolName(name) {
|
|
64
|
+
const n = sanitizeMcpName(name); // 只留 \w.-, 截断 64
|
|
65
|
+
return n || "unnamed";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class McpServer {
|
|
69
|
+
/**
|
|
70
|
+
* @param {object} agent - PPXAgent 实例 (提供 tools/facts/scenes/traces/sessionStore/stats 等)
|
|
71
|
+
* @param {object} [opts]
|
|
72
|
+
* @param {string} [opts.name] serverInfo.name (默认 agent 名)
|
|
73
|
+
* @param {string} [opts.version] serverInfo.version
|
|
74
|
+
* @param {string[]} [opts.supportedVersions] 支持的协议版本列表
|
|
75
|
+
* @param {object[]} [opts.extraTools] 额外虚拟工具: [{name,title,description,inputSchema,execute}]
|
|
76
|
+
* @param {object[]} [opts.prompts] 额外提示模板: [{name,description,arguments:[{name,required,description}]}]
|
|
77
|
+
*/
|
|
78
|
+
constructor(agent, opts = {}) {
|
|
79
|
+
this.agent = agent;
|
|
80
|
+
this.serverInfo = {
|
|
81
|
+
name: opts.name || agent?.config?.agent?.name || "ppxans-harness",
|
|
82
|
+
version: opts.version || PKG_VERSION,
|
|
83
|
+
};
|
|
84
|
+
this.supportedVersions = opts.supportedVersions || SUPPORTED_VERSIONS;
|
|
85
|
+
// 对话虚拟工具 (MCP 客户端驱动 agent 对话的入口, 不进 catalog 避免污染 LLM 工具列表)
|
|
86
|
+
// 两个对话工具入参完全一致 (message + sessionId), 共用一个 schema 工厂, 避免改一处漏一处;
|
|
87
|
+
// 每次调用返回新对象, 不共享引用 (防止下游按需改写 schema 时互相污染)。
|
|
88
|
+
const chatInputSchema = () => ({
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
message: { type: "string", description: "用户消息" },
|
|
92
|
+
sessionId: { type: "string", description: "会话标识 (默认 default)" },
|
|
93
|
+
},
|
|
94
|
+
required: ["message"],
|
|
95
|
+
});
|
|
96
|
+
const chatTools = [
|
|
97
|
+
{
|
|
98
|
+
name: "ppx.chat.send",
|
|
99
|
+
title: "对话 (非流式)",
|
|
100
|
+
description: "发送消息给皮皮虾 agent, 返回完整回复。内部会执行完整工具调用循环 (记忆/搜索/命令等)。sessionId 用于区分会话上下文, 默认 default。",
|
|
101
|
+
inputSchema: chatInputSchema(),
|
|
102
|
+
execute: async (args, ctx, agent) => {
|
|
103
|
+
const reply = await agent.chat(String(args.message || ""), { sessionKey: args.sessionId || "default" });
|
|
104
|
+
return { content: [{ type: "text", text: String(reply) }] };
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "ppx.chat.stream",
|
|
109
|
+
title: "对话 (流式)",
|
|
110
|
+
description: "发送消息给皮皮虾 agent, 流式返回回复 (SSE 进度通知)。内部会执行完整工具调用循环。sessionId 用于区分会话上下文, 默认 default。",
|
|
111
|
+
inputSchema: chatInputSchema(),
|
|
112
|
+
execute: async (args, ctx, agent) => {
|
|
113
|
+
let full = "";
|
|
114
|
+
const reply = await agent.chatStream(String(args.message || ""), {
|
|
115
|
+
sessionKey: args.sessionId || "default",
|
|
116
|
+
onDelta: (d) => { full += d; ctx?.stream?.onDelta?.(d); },
|
|
117
|
+
// v2.6.0: 结构化工具/推理事件透传 (web 前端渲染工具卡片 + 轮次进度)
|
|
118
|
+
onTool: (ev) => { ctx?.stream?.onTool?.(ev); },
|
|
119
|
+
onStep: (ev) => { ctx?.stream?.onStep?.(ev); },
|
|
120
|
+
});
|
|
121
|
+
const text = full || String(reply);
|
|
122
|
+
return { content: [{ type: "text", text }] };
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
this.extraTools = opts.extraTools ? [...chatTools, ...opts.extraTools] : chatTools;
|
|
127
|
+
this.extraPrompts = opts.prompts || [];
|
|
128
|
+
// 工具名 -> 实现映射 (虚拟工具), 冲突时优先内置工具 (工具名唯一性在 server 内)
|
|
129
|
+
this._virtualTools = new Map(this.extraTools.map((t) => [safeToolName(t.name), t]));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---- 对外分发入口: 接收 JSON-RPC 消息, 返回 { result } 或抛 McpError ----
|
|
133
|
+
async handle(msg, ctx = {}) {
|
|
134
|
+
if (!msg || typeof msg !== "object") throw new McpError(MCP_ERROR.INVALID_REQUEST, "无效请求: 非 JSON-RPC 对象");
|
|
135
|
+
if (msg.jsonrpc !== "2.0") throw new McpError(MCP_ERROR.INVALID_REQUEST, "jsonrpc 字段必须为 2.0");
|
|
136
|
+
const method = msg.method;
|
|
137
|
+
if (typeof method !== "string" || !method) throw new McpError(MCP_ERROR.INVALID_REQUEST, "缺少 method");
|
|
138
|
+
|
|
139
|
+
// 版本协商 (现代 era): 请求版本不在支持列表 → UnsupportedProtocolVersionError
|
|
140
|
+
const reqMeta = (msg.params && msg.params._meta) || {};
|
|
141
|
+
const reqVersion = reqMeta["io.modelcontextprotocol/protocolVersion"];
|
|
142
|
+
if (reqVersion && !this.supportedVersions.includes(reqVersion)) {
|
|
143
|
+
throw new McpError(MCP_ERROR.UNSUPPORTED_PROTOCOL_VERSION, "Unsupported protocol version", {
|
|
144
|
+
supported: this.supportedVersions,
|
|
145
|
+
requested: reqVersion,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 分发
|
|
150
|
+
switch (method) {
|
|
151
|
+
// ---- 现代 era 核心 ----
|
|
152
|
+
case "server/discover": {
|
|
153
|
+
if (!isModernRequest(msg)) throw new McpError(MCP_ERROR.INVALID_REQUEST, "server/discover 需现代 _meta");
|
|
154
|
+
return { result: stampServerInfo({
|
|
155
|
+
resultType: "complete",
|
|
156
|
+
supportedVersions: this.supportedVersions,
|
|
157
|
+
capabilities: await this._capabilities(),
|
|
158
|
+
instructions: this._instructions(),
|
|
159
|
+
ttlMs: 3600000,
|
|
160
|
+
cacheScope: "public",
|
|
161
|
+
}, this.serverInfo) };
|
|
162
|
+
}
|
|
163
|
+
case "ping":
|
|
164
|
+
return { result: stampServerInfo({ resultType: "complete" }, this.serverInfo) };
|
|
165
|
+
|
|
166
|
+
// ---- tools ----
|
|
167
|
+
case "tools/list": {
|
|
168
|
+
const tools = await this._listTools();
|
|
169
|
+
// 分页: 单页返回全部 (cursor 非空时返回空, 表示无更多页)
|
|
170
|
+
const cursor = msg.params && msg.params.cursor;
|
|
171
|
+
const result = {
|
|
172
|
+
resultType: "complete",
|
|
173
|
+
tools,
|
|
174
|
+
ttlMs: 300000, // 工具列表 5 分钟缓存 (2026-07-28 缓存语义)
|
|
175
|
+
cacheScope: "public",
|
|
176
|
+
};
|
|
177
|
+
if (cursor) result.nextCursor = null;
|
|
178
|
+
return { result: stampServerInfo(result, this.serverInfo) };
|
|
179
|
+
}
|
|
180
|
+
case "tools/call": {
|
|
181
|
+
const name = msg.params && msg.params.name;
|
|
182
|
+
if (!name) throw new McpError(MCP_ERROR.INVALID_PARAMS, "tools/call 缺少 name");
|
|
183
|
+
const args = (msg.params && msg.params.arguments) || {};
|
|
184
|
+
const raw = await this._callTool(name, args, ctx);
|
|
185
|
+
// raw 可能是 { content, structuredContent, isError, stream? } 或字符串 (内置工具返回)
|
|
186
|
+
const result = { resultType: "complete" };
|
|
187
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw) && ("content" in raw || "stream" in raw)) {
|
|
188
|
+
Object.assign(result, raw);
|
|
189
|
+
delete result.stream; // stream 由传输层消费
|
|
190
|
+
if (raw.stream) result.streamHandle = raw.stream;
|
|
191
|
+
} else {
|
|
192
|
+
const text = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
193
|
+
// isError 判定: 皮皮虾 [工具错误] 前缀 或 工具内部返回的 {"error":...} JSON
|
|
194
|
+
let isError = typeof raw === "string" && raw.startsWith(TOOL_ERROR_PREFIX);
|
|
195
|
+
if (!isError && typeof raw === "string") {
|
|
196
|
+
const trimmed = raw.trim();
|
|
197
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
198
|
+
try { isError = !!(JSON.parse(trimmed).error); } catch { /* 非 JSON 不管 */ }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
result.content = [{ type: "text", text }];
|
|
202
|
+
result.isError = isError;
|
|
203
|
+
}
|
|
204
|
+
return { result: stampServerInfo(result, this.serverInfo), stream: raw && raw.stream ? raw.stream : null };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ---- resources ----
|
|
208
|
+
case "resources/list": {
|
|
209
|
+
const result = { resultType: "complete", resources: await this._listResources() };
|
|
210
|
+
return { result: stampServerInfo(result, this.serverInfo) };
|
|
211
|
+
}
|
|
212
|
+
case "resources/read": {
|
|
213
|
+
const uri = msg.params && msg.params.uri;
|
|
214
|
+
if (!uri) throw new McpError(MCP_ERROR.INVALID_PARAMS, "resources/read 缺少 uri");
|
|
215
|
+
const contents = await this._readResource(uri);
|
|
216
|
+
if (!contents) throw new McpError(MCP_ERROR.INVALID_PARAMS, `资源不存在: ${uri}`);
|
|
217
|
+
return { result: stampServerInfo({ resultType: "complete", contents }, this.serverInfo) };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---- prompts ----
|
|
221
|
+
case "prompts/list": {
|
|
222
|
+
const result = { resultType: "complete", prompts: this._listPrompts() };
|
|
223
|
+
return { result: stampServerInfo(result, this.serverInfo) };
|
|
224
|
+
}
|
|
225
|
+
case "prompts/get": {
|
|
226
|
+
const name = msg.params && msg.params.name;
|
|
227
|
+
if (!name) throw new McpError(MCP_ERROR.INVALID_PARAMS, "prompts/get 缺少 name");
|
|
228
|
+
const messages = this._getPrompt(name, (msg.params && msg.params.arguments) || {});
|
|
229
|
+
if (!messages) throw new McpError(MCP_ERROR.INVALID_PARAMS, `提示模板不存在: ${name}`);
|
|
230
|
+
return { result: stampServerInfo({ resultType: "complete", messages }, this.serverInfo) };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ---- legacy era (initialize 握手) ----
|
|
234
|
+
case "initialize": {
|
|
235
|
+
if (isLegacyInitialize(msg)) {
|
|
236
|
+
const clientVersion = (msg.params && msg.params.protocolVersion) || "2024-11-05";
|
|
237
|
+
const negotiated = this.supportedVersions.includes(clientVersion) ? clientVersion : LEGACY_PROTOCOL_VERSION;
|
|
238
|
+
return { result: {
|
|
239
|
+
protocolVersion: negotiated,
|
|
240
|
+
capabilities: await this._capabilities(),
|
|
241
|
+
serverInfo: this.serverInfo,
|
|
242
|
+
instructions: this._instructions(),
|
|
243
|
+
} };
|
|
244
|
+
}
|
|
245
|
+
throw new McpError(MCP_ERROR.INVALID_REQUEST, "initialize 需 legacy 格式 (无现代 _meta)");
|
|
246
|
+
}
|
|
247
|
+
case "notifications/initialized":
|
|
248
|
+
case "notifications/cancelled":
|
|
249
|
+
return { result: null }; // 通知无响应, 传输层处理为 202/忽略
|
|
250
|
+
|
|
251
|
+
default:
|
|
252
|
+
throw new McpError(MCP_ERROR.METHOD_NOT_FOUND, `Method not found: ${method}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ---- 能力声明 ----
|
|
257
|
+
async _capabilities() {
|
|
258
|
+
return {
|
|
259
|
+
tools: { listChanged: false },
|
|
260
|
+
resources: { subscribe: false, listChanged: false },
|
|
261
|
+
prompts: {},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
_instructions() {
|
|
266
|
+
return "PPXANS-Harness (皮皮虾) 智能体内核。可用 tools 执行文件/命令/搜索/记忆/文档/治理等操作; " +
|
|
267
|
+
"resources 暴露记忆 (memory://)、轨迹 (traces://)、统计 (stats://)、会话 (sessions://) 数据; " +
|
|
268
|
+
"prompts 提供方法型技能模板 (humanize/plan/debug 等)。" +
|
|
269
|
+
"对话入口: 使用工具 ppx.chat.send 发送消息给 agent (含完整工具调用循环)。";
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ---- tools 实现 ----
|
|
273
|
+
async _listTools() {
|
|
274
|
+
const out = [];
|
|
275
|
+
const seen = new Set();
|
|
276
|
+
// 1. 内置 + MCP 注册工具 (catalog 全量)
|
|
277
|
+
const detailed = this.agent && this.agent.tools && typeof this.agent.tools.listDetailed === "function"
|
|
278
|
+
? this.agent.tools.listDetailed() : [];
|
|
279
|
+
for (const t of detailed) {
|
|
280
|
+
const name = safeToolName(t.name);
|
|
281
|
+
if (!t.enabled || seen.has(name)) continue;
|
|
282
|
+
seen.add(name);
|
|
283
|
+
const tool = {
|
|
284
|
+
name,
|
|
285
|
+
title: t.title || t.name,
|
|
286
|
+
description: t.description || `MCP 工具: ${name}`,
|
|
287
|
+
inputSchema: t.parameters || { type: "object", properties: {} },
|
|
288
|
+
};
|
|
289
|
+
if (t.timeoutMs) tool.annotations = { timeoutMs: t.timeoutMs, idempotent: !!t.idempotent, readOnlyHint: false };
|
|
290
|
+
out.push(tool);
|
|
291
|
+
}
|
|
292
|
+
// 2. 虚拟工具 (对话等)
|
|
293
|
+
for (const [name, t] of this._virtualTools) {
|
|
294
|
+
if (seen.has(name)) continue;
|
|
295
|
+
seen.add(name);
|
|
296
|
+
out.push({
|
|
297
|
+
name,
|
|
298
|
+
title: t.title || name,
|
|
299
|
+
description: t.description || "",
|
|
300
|
+
inputSchema: t.inputSchema || { type: "object", properties: {} },
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return out;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async _callTool(name, args, ctx) {
|
|
307
|
+
// 虚拟工具优先 (对话/流式等非 catalog 能力)
|
|
308
|
+
if (this._virtualTools.has(name)) {
|
|
309
|
+
return this._virtualTools.get(name).execute(args, ctx, this.agent);
|
|
310
|
+
}
|
|
311
|
+
// catalog 工具: 走统一策略链 (命令守卫/审计/超时)
|
|
312
|
+
if (this.agent && this.agent.tools && typeof this.agent.tools.call === "function") {
|
|
313
|
+
// 未知工具需在进入 catalog.call 前拦截 (catalog.call 返回错误串而非抛错)
|
|
314
|
+
if (typeof this.agent.tools.has === "function" && !this.agent.tools.has(name)) {
|
|
315
|
+
throw new McpError(MCP_ERROR.INVALID_PARAMS, `未知工具: ${name}`);
|
|
316
|
+
}
|
|
317
|
+
return this.agent.tools.call(name, args || {}, {});
|
|
318
|
+
}
|
|
319
|
+
throw new McpError(MCP_ERROR.INVALID_PARAMS, `未知工具: ${name}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ---- resources 实现 ----
|
|
323
|
+
async _listResources() {
|
|
324
|
+
const out = [];
|
|
325
|
+
const add = (uri, name, mimeType = "application/json", description = "") => out.push({ uri, name, mimeType, description });
|
|
326
|
+
add("memory://facts", "L1 原子记忆", "application/json", "长期记忆事实库 (含分数/类型)");
|
|
327
|
+
add("memory://scenes", "L2 场景", "application/json", "记忆聚类场景");
|
|
328
|
+
add("traces://recent", "最近工具轨迹", "application/json", "最近 N 条工具调用轨迹");
|
|
329
|
+
add("stats://overview", "运行统计", "application/json", "记忆/轨迹/工具/会话聚合统计");
|
|
330
|
+
add("sessions://list", "会话列表", "application/json", "全部会话 key");
|
|
331
|
+
// 动态: 会话历史按 key (sessions://<key>/history)
|
|
332
|
+
if (this.agent && this.agent.sessionStore && typeof this.agent.sessionStore.list === "function") {
|
|
333
|
+
for (const s of this.agent.sessionStore.list()) {
|
|
334
|
+
const key = typeof s === "string" ? s : (s && (s.key || s.id)) || "";
|
|
335
|
+
if (key) add(`sessions://${encodeURIComponent(key)}/history`, `会话历史: ${key}`, "application/json");
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async _readResource(uri) {
|
|
342
|
+
const json = (obj) => [{ uri, mimeType: "application/json", text: JSON.stringify(obj, null, 2) }];
|
|
343
|
+
const a = this.agent;
|
|
344
|
+
try {
|
|
345
|
+
if (uri === "memory://facts") return json(a.facts ? a.facts.list() : []);
|
|
346
|
+
if (uri === "memory://scenes") return json(a.scenes && typeof a.scenes.listWithDesc === "function" ? a.scenes.listWithDesc() : []);
|
|
347
|
+
if (uri === "traces://recent") return json(a.traces && typeof a.traces.read === "function" ? a.traces.read(undefined, 50) : []);
|
|
348
|
+
if (uri === "stats://overview") return json(typeof a.stats === "function" ? a.stats() : (a.traces && a.traces.stats ? a.traces.stats() : {}));
|
|
349
|
+
if (uri === "sessions://list") return json(a.sessionStore && typeof a.sessionStore.list === "function" ? a.sessionStore.list() : []);
|
|
350
|
+
const m = uri.match(/^sessions:\/\/([^/]+)\/history$/);
|
|
351
|
+
if (m && a.sessionStore && typeof a.sessionStore.deriveMessages === "function") {
|
|
352
|
+
const key = decodeURIComponent(m[1]);
|
|
353
|
+
return json(a.sessionStore.deriveMessages(key));
|
|
354
|
+
}
|
|
355
|
+
} catch (e) {
|
|
356
|
+
warn(`[mcp] 资源读取失败 ${uri}: ${e.message}`);
|
|
357
|
+
return json({ error: e.message });
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ---- prompts 实现 ----
|
|
363
|
+
_listPrompts() {
|
|
364
|
+
const builtins = [
|
|
365
|
+
{ name: "humanize", description: "去 AI 味: 检查并改写长文, 消除虚高意义/虚假深度/广告腔等 AI 模式残留", arguments: [{ name: "text", required: true, description: "待检查文本" }] },
|
|
366
|
+
{ name: "plan", description: "精确计划: 把模糊目标拆成可执行步骤 (P1 目标/P2 输入/P3 输出/P4 约束/P5 模式)", arguments: [{ name: "goal", required: true, description: "目标" }] },
|
|
367
|
+
{ name: "debug", description: "五步调试: 复现 → 二分 → 根因 → 修复 → 验证", arguments: [{ name: "problem", required: true, description: "问题描述" }] },
|
|
368
|
+
{ name: "verify", description: "验证优先: 任何声称完成前产出可复现证据", arguments: [{ name: "claim", required: true, description: "待验证的声称" }] },
|
|
369
|
+
{ name: "write_article", description: "分阶段写作: 大纲 → 初稿 → 打磨", arguments: [{ name: "topic", required: true, description: "主题" }] },
|
|
370
|
+
];
|
|
371
|
+
const extra = this.extraPrompts.map((p) => ({
|
|
372
|
+
name: safeToolName(p.name),
|
|
373
|
+
description: p.description || "",
|
|
374
|
+
arguments: (p.arguments || []).map((a) => ({ name: a.name, required: !!a.required, description: a.description || "" })),
|
|
375
|
+
}));
|
|
376
|
+
return [...builtins, ...extra];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
_getPrompt(name, args) {
|
|
380
|
+
const builtin = {
|
|
381
|
+
humanize: () => [{ role: "user", content: { type: "text", text: `请对以下文本执行去 AI 味检查并改写 (消除虚高意义/虚假深度/广告腔/模糊归因/AI 词汇/规则三连/破折号狂魔/空洞结尾):\n\n${args.text || ""}` } }],
|
|
382
|
+
plan: () => [{ role: "user", content: { type: "text", text: `请为以下目标制定精确计划 (P1 目标 / P2 输入 / P3 输出 / P4 约束 / P5 模式):\n\n${args.goal || ""}` } }],
|
|
383
|
+
debug: () => [{ role: "user", content: { type: "text", text: `请用五步调试法 (复现 → 二分 → 根因 → 修复 → 验证) 处理:\n\n${args.problem || ""}` } }],
|
|
384
|
+
verify: () => [{ role: "user", content: { type: "text", text: `请验证以下声称, 给出可复现证据或明确"未验证+原因":\n\n${args.claim || ""}` } }],
|
|
385
|
+
write_article: () => [{ role: "user", content: { type: "text", text: `请分阶段写作 (大纲 → 初稿 → 打磨), 主题:\n\n${args.topic || ""}` } }],
|
|
386
|
+
}[name];
|
|
387
|
+
if (builtin) return builtin();
|
|
388
|
+
const extra = this.extraPrompts.find((p) => safeToolName(p.name) === name);
|
|
389
|
+
if (extra && typeof extra.build === "function") return extra.build(args);
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
}
|
package/src/mcp/tasks.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// src/mcp/tasks.js - 任务面板存储 (零依赖)
|
|
2
|
+
// 轻量任务工作区: 任务 = { id, title, description, status, steps[], result, createdAt, updatedAt }
|
|
3
|
+
// 持久化到 <root>/data/tasks.json (原子写), 支持 create/list/get/update/step/delete/complete。
|
|
4
|
+
// 供 MCP 工具 ppx.task.* 与 web 前端任务面板使用。
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { readJson, writeJson } from "../utils/store.js";
|
|
7
|
+
import { shortId } from "../utils/id.js";
|
|
8
|
+
|
|
9
|
+
const VALID_TASK_STATUS = new Set(["todo", "running", "done", "failed"]);
|
|
10
|
+
const VALID_STEP_STATUS = new Set(["pending", "running", "done", "failed"]);
|
|
11
|
+
|
|
12
|
+
export class TaskBoard {
|
|
13
|
+
constructor(root) {
|
|
14
|
+
this.root = root;
|
|
15
|
+
this.file = path.join(root, "data", "tasks.json");
|
|
16
|
+
this.tasks = new Map();
|
|
17
|
+
this._load();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_load() {
|
|
21
|
+
const raw = readJson(this.file, null); // 损坏则从空开始, 下次写入覆盖
|
|
22
|
+
if (raw && Array.isArray(raw.tasks)) {
|
|
23
|
+
for (const t of raw.tasks) {
|
|
24
|
+
if (t && t.id) this.tasks.set(t.id, this._normalize(t));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
_save() {
|
|
30
|
+
try {
|
|
31
|
+
writeJson(this.file, { tasks: [...this.tasks.values()] });
|
|
32
|
+
} catch (e) { /* 持久化失败不阻断内存操作 */ }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_normalize(t) {
|
|
36
|
+
return {
|
|
37
|
+
id: String(t.id),
|
|
38
|
+
title: String(t.title || "未命名任务"),
|
|
39
|
+
description: String(t.description || ""),
|
|
40
|
+
status: VALID_TASK_STATUS.has(t.status) ? t.status : "todo",
|
|
41
|
+
steps: Array.isArray(t.steps) ? t.steps.map((s, i) => ({
|
|
42
|
+
title: String((s && s.title) || (typeof s === "string" ? s : `步骤 ${i + 1}`)),
|
|
43
|
+
status: VALID_STEP_STATUS.has(s && s.status) ? s.status : "pending",
|
|
44
|
+
detail: String((s && s.detail) || ""),
|
|
45
|
+
})) : [],
|
|
46
|
+
result: String(t.result || ""),
|
|
47
|
+
createdAt: t.createdAt || Date.now(),
|
|
48
|
+
updatedAt: t.updatedAt || Date.now(),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_touch(t) { t.updatedAt = Date.now(); }
|
|
53
|
+
|
|
54
|
+
create({ title, description = "", steps = [] } = {}) {
|
|
55
|
+
if (!title) throw new Error("任务需 title");
|
|
56
|
+
const id = shortId("t_", 5);
|
|
57
|
+
const t = this._normalize({
|
|
58
|
+
id, title, description,
|
|
59
|
+
steps: steps.map((s) => ({ title: String(s) })).filter((s) => s.title),
|
|
60
|
+
});
|
|
61
|
+
this.tasks.set(id, t);
|
|
62
|
+
this._save();
|
|
63
|
+
return t;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get(id) { return this.tasks.get(String(id)) || null; }
|
|
67
|
+
|
|
68
|
+
list() {
|
|
69
|
+
const arr = [...this.tasks.values()].sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
|
|
70
|
+
return {
|
|
71
|
+
tasks: arr,
|
|
72
|
+
counts: {
|
|
73
|
+
todo: arr.filter((t) => t.status === "todo").length,
|
|
74
|
+
running: arr.filter((t) => t.status === "running").length,
|
|
75
|
+
done: arr.filter((t) => t.status === "done").length,
|
|
76
|
+
failed: arr.filter((t) => t.status === "failed").length,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
update({ id, status, title, description } = {}) {
|
|
82
|
+
const t = this.get(id);
|
|
83
|
+
if (!t) throw new Error("任务不存在");
|
|
84
|
+
if (status != null) {
|
|
85
|
+
if (!VALID_TASK_STATUS.has(status)) throw new Error(`非法任务状态: ${status}`);
|
|
86
|
+
t.status = status;
|
|
87
|
+
}
|
|
88
|
+
if (title != null) t.title = String(title);
|
|
89
|
+
if (description != null) t.description = String(description);
|
|
90
|
+
this._touch(t);
|
|
91
|
+
this._save();
|
|
92
|
+
return t;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
step({ id, index, status, detail } = {}) {
|
|
96
|
+
const t = this.get(id);
|
|
97
|
+
if (!t) throw new Error("任务不存在");
|
|
98
|
+
const i = Number(index);
|
|
99
|
+
if (!Number.isInteger(i) || i < 0 || i >= t.steps.length) throw new Error("步骤 index 越界");
|
|
100
|
+
if (status != null) {
|
|
101
|
+
if (!VALID_STEP_STATUS.has(status)) throw new Error(`非法步骤状态: ${status}`);
|
|
102
|
+
t.steps[i].status = status;
|
|
103
|
+
}
|
|
104
|
+
if (detail != null) t.steps[i].detail = String(detail);
|
|
105
|
+
// 任一步骤 running → 任务 running; 全部 done → 任务 done (自动派生)
|
|
106
|
+
if (t.steps.some((s) => s.status === "running")) t.status = "running";
|
|
107
|
+
else if (t.steps.length && t.steps.every((s) => s.status === "done")) t.status = "done";
|
|
108
|
+
this._touch(t);
|
|
109
|
+
this._save();
|
|
110
|
+
return t;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
complete(id, result = "") {
|
|
114
|
+
const t = this.get(id);
|
|
115
|
+
if (!t) throw new Error("任务不存在");
|
|
116
|
+
for (const s of t.steps) if (s.status !== "failed") s.status = "done";
|
|
117
|
+
t.status = "done";
|
|
118
|
+
if (result) t.result = String(result).slice(0, 20000);
|
|
119
|
+
this._touch(t);
|
|
120
|
+
this._save();
|
|
121
|
+
return t;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
delete({ id } = {}) {
|
|
125
|
+
const ok = this.tasks.delete(String(id || ""));
|
|
126
|
+
if (ok) this._save();
|
|
127
|
+
return { ok };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function createTaskBoard(root) {
|
|
132
|
+
return new TaskBoard(root);
|
|
133
|
+
}
|
package/src/memory/asset-hub.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
// - 检索隔离: 私有资产仅 owner 可检索; 团队资产共享 (简化: 全局可见)
|
|
6
6
|
// 皮皮虾自研实现, 构建在现有 facts (带 scope) 之上: 资产 = 一组带 scope 的 facts + 元数据登记。
|
|
7
7
|
// 纯代码可测, 无 LLM。
|
|
8
|
-
import fs from "node:fs";
|
|
9
8
|
import path from "node:path";
|
|
10
|
-
import { ensureDir,
|
|
9
|
+
import { ensureDir, readJson, writeJson } from "../utils/store.js";
|
|
10
|
+
import { shortId } from "../utils/id.js";
|
|
11
11
|
|
|
12
12
|
export const VISIBILITY = { PRIVATE: "private", TEAM: "team" };
|
|
13
13
|
|
|
@@ -26,22 +26,17 @@ export class AssetHub {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
_load() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const d = JSON.parse(fs.readFileSync(this.file, "utf8"));
|
|
32
|
-
if (Array.isArray(d)) return d;
|
|
33
|
-
}
|
|
34
|
-
} catch {}
|
|
35
|
-
return [];
|
|
29
|
+
const d = readJson(this.file, null);
|
|
30
|
+
return Array.isArray(d) ? d : [];
|
|
36
31
|
}
|
|
37
32
|
|
|
38
|
-
_save() {
|
|
33
|
+
_save() { writeJson(this.file, this._assets); }
|
|
39
34
|
|
|
40
35
|
// 登记一个资产 (关联到 facts scope)
|
|
41
36
|
// { name, kind: 'document'|'skill'|'experience', scope, owner, visibility, source, description }
|
|
42
37
|
register(asset) {
|
|
43
38
|
if (!asset || !asset.name) throw new Error("资产需 name");
|
|
44
|
-
const id = "as"
|
|
39
|
+
const id = shortId("as", 5);
|
|
45
40
|
const a = {
|
|
46
41
|
id,
|
|
47
42
|
name: asset.name,
|
package/src/memory/canvas.js
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
// 细节按 node_id 从事件日志取 —— 上层存结构, 下层存证据, 完整 drill-down 路径。
|
|
5
5
|
// 皮皮虾自研实现: 从 trace.js 的事件流 (turn/step 边界事件) 归纳画布, 非 LLM、纯代码、可测。
|
|
6
6
|
// 触发条件: steps >= minSteps (默认 8) 或事件数超阈值时才开启, 防过度设计。
|
|
7
|
-
import fs from "node:fs";
|
|
8
7
|
import path from "node:path";
|
|
9
|
-
import { ensureDir, writeText, logicalDay } from "../utils/store.js";
|
|
8
|
+
import { ensureDir, writeText, readJson, logicalDay } from "../utils/store.js";
|
|
10
9
|
|
|
11
10
|
export const CANVAS_DEFAULTS = { minSteps: 8, maxNodes: 30, maxEdges: 40 };
|
|
12
11
|
|
|
@@ -101,9 +100,7 @@ export class CanvasStore {
|
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
read(day = logicalDay()) {
|
|
104
|
-
|
|
105
|
-
if (!fs.existsSync(f)) return null;
|
|
106
|
-
try { return JSON.parse(fs.readFileSync(f, "utf8")); } catch { return null; }
|
|
103
|
+
return readJson(this._file(day), null);
|
|
107
104
|
}
|
|
108
105
|
|
|
109
106
|
// 从 trace 事件自动生成并保存 (若步骤数达标); 返回 null 表示未触发
|
package/src/memory/compaction.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
// src/memory/compaction.js - 会话压缩层 (吸收 OpenClaw compaction 精华)
|
|
2
|
-
// 设计要点:
|
|
3
|
-
// - 超阈值时用 LLM 把旧对话压缩成「结构化摘要」(固定格式), 替换旧历史, 防上下文失控
|
|
4
|
-
// - 摘要可迭代: 摘要作为 system 消息进入下一轮, 下次压缩时摘要+新对话一起再压
|
|
5
|
-
// - 固定格式: 目标/进展/关键决策/待办/关键上下文, 保证可预测、可续写
|
|
6
|
-
|
|
7
|
-
export const COMPACTION_SYSTEM_PROMPT = `你是对话压缩器。把下面的对话历史压缩成一段结构化摘要,严格按以下格式输出(无内容的小节填「无」):
|
|
8
|
-
- 目标:用户想达成什么
|
|
9
|
-
- 进展:已完成的关键步骤
|
|
10
|
-
- 关键决策:已确定的重要决定/约定
|
|
11
|
-
- 待办:尚未完成的事项
|
|
12
|
-
- 关键上下文:后续对话必须知道的细节(数字/路径/偏好/约束)
|
|
13
|
-
要求:简洁,≤300字,保留所有关键事实,不要客套,直接输出摘要。`;
|
|
14
|
-
|
|
15
|
-
// 组装压缩请求消息 (system + 待压缩对话)
|
|
16
|
-
export function buildCompactionMessages(transcript) {
|
|
17
|
-
return [
|
|
18
|
-
{ role: "system", content: COMPACTION_SYSTEM_PROMPT },
|
|
19
|
-
{ role: "user", content: String(transcript).slice(0, 8000) },
|
|
20
|
-
];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// 把投影消息列表转成压缩用文本 (role → 中文标签)
|
|
24
|
-
export function transcriptToText(messages) {
|
|
25
|
-
return messages
|
|
26
|
-
.map((m) => (m.role === "user" ? "用户" : m.role === "assistant" ? "助手" : m.role) + ": " + String(m.content ?? ""))
|
|
27
|
-
.join("\n");
|
|
28
|
-
}
|
|
1
|
+
// src/memory/compaction.js - 会话压缩层 (吸收 OpenClaw compaction 精华)
|
|
2
|
+
// 设计要点:
|
|
3
|
+
// - 超阈值时用 LLM 把旧对话压缩成「结构化摘要」(固定格式), 替换旧历史, 防上下文失控
|
|
4
|
+
// - 摘要可迭代: 摘要作为 system 消息进入下一轮, 下次压缩时摘要+新对话一起再压
|
|
5
|
+
// - 固定格式: 目标/进展/关键决策/待办/关键上下文, 保证可预测、可续写
|
|
6
|
+
|
|
7
|
+
export const COMPACTION_SYSTEM_PROMPT = `你是对话压缩器。把下面的对话历史压缩成一段结构化摘要,严格按以下格式输出(无内容的小节填「无」):
|
|
8
|
+
- 目标:用户想达成什么
|
|
9
|
+
- 进展:已完成的关键步骤
|
|
10
|
+
- 关键决策:已确定的重要决定/约定
|
|
11
|
+
- 待办:尚未完成的事项
|
|
12
|
+
- 关键上下文:后续对话必须知道的细节(数字/路径/偏好/约束)
|
|
13
|
+
要求:简洁,≤300字,保留所有关键事实,不要客套,直接输出摘要。`;
|
|
14
|
+
|
|
15
|
+
// 组装压缩请求消息 (system + 待压缩对话)
|
|
16
|
+
export function buildCompactionMessages(transcript) {
|
|
17
|
+
return [
|
|
18
|
+
{ role: "system", content: COMPACTION_SYSTEM_PROMPT },
|
|
19
|
+
{ role: "user", content: String(transcript).slice(0, 8000) },
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 把投影消息列表转成压缩用文本 (role → 中文标签)
|
|
24
|
+
export function transcriptToText(messages) {
|
|
25
|
+
return messages
|
|
26
|
+
.map((m) => (m.role === "user" ? "用户" : m.role === "assistant" ? "助手" : m.role) + ": " + String(m.content ?? ""))
|
|
27
|
+
.join("\n");
|
|
28
|
+
}
|