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
package/src/mode/graph.js
CHANGED
|
@@ -1,41 +1,42 @@
|
|
|
1
|
-
// src/mode/graph.js - Graph / Workflow 模式
|
|
2
|
-
// 显式工作流编排: 按节点顺序执行, 每个节点走 react 工具循环, 结果 checkpoint 到会话日志。
|
|
3
|
-
// 适合: 企业级、可审计、可恢复的确定性流程。当前为顺序 DAG, 未来可扩展依赖/并行。
|
|
4
|
-
import { buildMessages } from "./index.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
1
|
+
// src/mode/graph.js - Graph / Workflow 模式
|
|
2
|
+
// 显式工作流编排: 按节点顺序执行, 每个节点走 react 工具循环, 结果 checkpoint 到会话日志。
|
|
3
|
+
// 适合: 企业级、可审计、可恢复的确定性流程。当前为顺序 DAG, 未来可扩展依赖/并行。
|
|
4
|
+
import { buildMessages } from "./index.js";
|
|
5
|
+
import { debug } from "../utils/logger.js";
|
|
6
|
+
|
|
7
|
+
// 归一化 workflow 节点: 接受字符串数组或 { name, task } 对象数组
|
|
8
|
+
export function normalizeNodes(workflow) {
|
|
9
|
+
if (!Array.isArray(workflow)) return [];
|
|
10
|
+
return workflow.map((node) => {
|
|
11
|
+
if (typeof node === "string") return { name: node, task: node };
|
|
12
|
+
return { name: String(node.name || node.task || ""), task: String(node.task || node.name || "") };
|
|
13
|
+
}).filter((n) => n.task);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function graphExecutor(agent, userMsg, { sessionKey = "default", workflow = null } = {}) {
|
|
17
|
+
if (!agent.llm) {
|
|
18
|
+
return (await agent._localIntent(userMsg)) || "[皮皮虾] 未配置模型 provider (配置见 docs/QUICKSTART.md 第 3 节)。";
|
|
19
|
+
}
|
|
20
|
+
// workflow 来源: opts 传入 > config.agent.workflow > 默认单节点
|
|
21
|
+
const raw = workflow || (agent.config.agent && agent.config.agent.workflow) || [userMsg];
|
|
22
|
+
const nodes = normalizeNodes(raw);
|
|
23
|
+
if (!nodes.length) {
|
|
24
|
+
const messages = await buildMessages(agent, userMsg, sessionKey);
|
|
25
|
+
return agent._llmWithFallback(messages);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const base = await buildMessages(agent, userMsg, sessionKey);
|
|
29
|
+
const results = [];
|
|
30
|
+
for (const node of nodes) {
|
|
31
|
+
let r;
|
|
32
|
+
try {
|
|
33
|
+
r = await agent._llmWithFallback([...base, { role: "user", content: `[节点: ${node.name}] ${node.task}` }]);
|
|
34
|
+
} catch (e) {
|
|
35
|
+
r = `(节点 ${node.name} 失败: ${e.message})`;
|
|
36
|
+
}
|
|
37
|
+
// checkpoint: 节点结果落会话日志, 崩溃后可恢复/审计
|
|
38
|
+
try { agent.sessionStore.append(sessionKey, "tool/result", { content: `[workflow:${node.name}] ${r}` }); } catch (e) { debug(`[mode/graph] 已忽略异常: ${e && e.message ? e.message : e}`); }
|
|
39
|
+
results.push(`节点[${node.name}]:\n${r}`);
|
|
40
|
+
}
|
|
41
|
+
return results.join("\n\n");
|
|
42
|
+
}
|
package/src/mode/index.js
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
// src/mode/index.js - Agent 模式注册表 (编排策略可插拔)
|
|
2
|
-
// 把「怎么编排」从 chat() 里抽出来, 每种模式是一个 executor(agent, userMsg, opts) -> reply。
|
|
3
|
-
// 内置: react(工具循环) / single(纯对话) / plan-exec / router / blackboard / graph / legion。
|
|
4
|
-
// 默认 react; 用 config.agent.mode 或 chat({ mode }) 切换。
|
|
5
|
-
|
|
6
|
-
export class ModeRegistry {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.modes = new Map(); // name -> executor
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
register(name, executor) {
|
|
12
|
-
if (!name || typeof executor !== "function") throw new Error("模式注册失败: 需 name + executor 函数");
|
|
13
|
-
this.modes.set(name, executor);
|
|
14
|
-
return this;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
has(name) { return this.modes.has(name); }
|
|
18
|
-
get(name) { return this.modes.get(name); }
|
|
19
|
-
list() { return [...this.modes.keys()]; }
|
|
20
|
-
|
|
21
|
-
// 执行某模式: executor(agent, userMsg, opts) -> reply
|
|
22
|
-
async run(name, agent, userMsg, opts = {}) {
|
|
23
|
-
const exe = this.modes.get(name);
|
|
24
|
-
if (!exe) throw new Error(`[模式] 未知模式: ${name} (可用: ${this.list().join(", ")})`);
|
|
25
|
-
return exe(agent, userMsg, opts);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// 组装 [system + 历史 + 当前] 消息 (含防重复), 供各 executor 复用
|
|
30
|
-
export async function buildMessages(agent, userMsg, sessionKey = "default") {
|
|
31
|
-
const system = agent._context(userMsg);
|
|
32
|
-
const history = await agent._loadHistory(sessionKey);
|
|
33
|
-
const messages = [{ role: "system", content: system }, ...history];
|
|
34
|
-
const hist = agent._getSession(sessionKey);
|
|
35
|
-
const isRepeat = hist[hist.length - 1]?.role === "user" && hist[hist.length - 1].content === String(userMsg);
|
|
36
|
-
if (!isRepeat) messages.push({ role: "user", content: agent._userContent(userMsg) });
|
|
37
|
-
return messages;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ---- 内置模式 ----
|
|
41
|
-
|
|
42
|
-
// ReAct: 推理-行动-观察循环 (带工具, 默认模式)
|
|
43
|
-
async function reactExecutor(agent, userMsg, { sessionKey = "default" } = {}) {
|
|
44
|
-
if (!agent.llm) {
|
|
45
|
-
return (await agent._localIntent(userMsg)) || "[皮皮虾] 未配置模型 provider,仅本地记忆 + 工具模式 (配置见 docs/QUICKSTART.md 第 3 节)。";
|
|
46
|
-
}
|
|
47
|
-
const messages = await buildMessages(agent, userMsg, sessionKey);
|
|
48
|
-
return agent._llmWithFallback(messages);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// 单Agent: 纯 LLM 对话, 不挂工具 (省 token, 适合纯问答/闲聊)
|
|
52
|
-
async function singleExecutor(agent, userMsg, { sessionKey = "default" } = {}) {
|
|
53
|
-
if (!agent.llm) return "[皮皮虾] 未配置模型 provider (配置见 docs/QUICKSTART.md 第 3 节)。";
|
|
54
|
-
const messages = await buildMessages(agent, userMsg, sessionKey);
|
|
55
|
-
const r = await agent.llm.chat(messages);
|
|
56
|
-
return r.content;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// 注册内置模式
|
|
60
|
-
export function registerDefaultModes(registry) {
|
|
61
|
-
registry.register("react", reactExecutor);
|
|
62
|
-
registry.register("single", singleExecutor);
|
|
63
|
-
return registry;
|
|
64
|
-
}
|
|
1
|
+
// src/mode/index.js - Agent 模式注册表 (编排策略可插拔)
|
|
2
|
+
// 把「怎么编排」从 chat() 里抽出来, 每种模式是一个 executor(agent, userMsg, opts) -> reply。
|
|
3
|
+
// 内置: react(工具循环) / single(纯对话) / plan-exec / router / blackboard / graph / legion。
|
|
4
|
+
// 默认 react; 用 config.agent.mode 或 chat({ mode }) 切换。
|
|
5
|
+
|
|
6
|
+
export class ModeRegistry {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.modes = new Map(); // name -> executor
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
register(name, executor) {
|
|
12
|
+
if (!name || typeof executor !== "function") throw new Error("模式注册失败: 需 name + executor 函数");
|
|
13
|
+
this.modes.set(name, executor);
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
has(name) { return this.modes.has(name); }
|
|
18
|
+
get(name) { return this.modes.get(name); }
|
|
19
|
+
list() { return [...this.modes.keys()]; }
|
|
20
|
+
|
|
21
|
+
// 执行某模式: executor(agent, userMsg, opts) -> reply
|
|
22
|
+
async run(name, agent, userMsg, opts = {}) {
|
|
23
|
+
const exe = this.modes.get(name);
|
|
24
|
+
if (!exe) throw new Error(`[模式] 未知模式: ${name} (可用: ${this.list().join(", ")})`);
|
|
25
|
+
return exe(agent, userMsg, opts);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 组装 [system + 历史 + 当前] 消息 (含防重复), 供各 executor 复用
|
|
30
|
+
export async function buildMessages(agent, userMsg, sessionKey = "default") {
|
|
31
|
+
const system = agent._context(userMsg);
|
|
32
|
+
const history = await agent._loadHistory(sessionKey);
|
|
33
|
+
const messages = [{ role: "system", content: system }, ...history];
|
|
34
|
+
const hist = agent._getSession(sessionKey);
|
|
35
|
+
const isRepeat = hist[hist.length - 1]?.role === "user" && hist[hist.length - 1].content === String(userMsg);
|
|
36
|
+
if (!isRepeat) messages.push({ role: "user", content: agent._userContent(userMsg) });
|
|
37
|
+
return messages;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---- 内置模式 ----
|
|
41
|
+
|
|
42
|
+
// ReAct: 推理-行动-观察循环 (带工具, 默认模式)
|
|
43
|
+
async function reactExecutor(agent, userMsg, { sessionKey = "default" } = {}) {
|
|
44
|
+
if (!agent.llm) {
|
|
45
|
+
return (await agent._localIntent(userMsg)) || "[皮皮虾] 未配置模型 provider,仅本地记忆 + 工具模式 (配置见 docs/QUICKSTART.md 第 3 节)。";
|
|
46
|
+
}
|
|
47
|
+
const messages = await buildMessages(agent, userMsg, sessionKey);
|
|
48
|
+
return agent._llmWithFallback(messages);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 单Agent: 纯 LLM 对话, 不挂工具 (省 token, 适合纯问答/闲聊)
|
|
52
|
+
async function singleExecutor(agent, userMsg, { sessionKey = "default" } = {}) {
|
|
53
|
+
if (!agent.llm) return "[皮皮虾] 未配置模型 provider (配置见 docs/QUICKSTART.md 第 3 节)。";
|
|
54
|
+
const messages = await buildMessages(agent, userMsg, sessionKey);
|
|
55
|
+
const r = await agent.llm.chat(messages);
|
|
56
|
+
return r.content;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 注册内置模式
|
|
60
|
+
export function registerDefaultModes(registry) {
|
|
61
|
+
registry.register("react", reactExecutor);
|
|
62
|
+
registry.register("single", singleExecutor);
|
|
63
|
+
return registry;
|
|
64
|
+
}
|
package/src/mode/legion.js
CHANGED
|
@@ -1,51 +1,54 @@
|
|
|
1
|
-
// src/mode/legion.js - 多 Agent 军团模式
|
|
2
|
-
// 把 Legion (多进程军团 + DAG 编排) 接入 mode 系统, chat({ mode: "legion" }) 可用。
|
|
3
|
-
// 编排策略:
|
|
4
|
-
// - 有 workflow (DAG 节点数组) 时走 runDag: 按依赖拓扑分层并行, 上游结果流入下游
|
|
5
|
-
// - 无 workflow 时走 broadcast: 同一问题广播给全部 agent, 取第一个有效回复
|
|
6
|
-
// 军团配置: config.agent.legion = { size, workflow } 或 config.orchestrator
|
|
7
|
-
// 可注入: opts.legion (测试/复用已有军团实例, 免重复 spawn)
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import { Legion } from "../orchestrator/legion.js";
|
|
10
|
-
import { warn } from "../utils/logger.js";
|
|
11
|
-
|
|
12
|
-
export async function legionExecutor(agent, userMsg, { sessionKey = "default", legion = null, workflow = null, size = null } = {}) {
|
|
13
|
-
const cfg = (agent.config && (agent.config.agent?.legion || agent.config.orchestrator)) || {};
|
|
14
|
-
|
|
15
|
-
// 1. 拿或懒建军团 (缓存到 agent, 复用子进程, 不重复 spawn)
|
|
16
|
-
let L = legion || agent._legion;
|
|
17
|
-
if (!L) {
|
|
18
|
-
L = new Legion();
|
|
19
|
-
const n = size || cfg.size || 2;
|
|
20
|
-
for (let i = 0; i < n; i++) {
|
|
21
|
-
// 每个 agent 独立数据目录, 隔离记忆/会话, 互不干扰
|
|
22
|
-
const dataDir = path.join(agent.dataDir, "legion", `agent-${i}`);
|
|
23
|
-
L.spawnAgent(`agent-${i}`, { dataDir });
|
|
24
|
-
}
|
|
25
|
-
agent._legion = L;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// 2. 编排: workflow(DAG) 优先, 否则 broadcast
|
|
29
|
-
const wf = workflow || cfg.workflow;
|
|
30
|
-
if (Array.isArray(wf) && wf.length) {
|
|
31
|
-
// v1.0.8: 校验节点结构 (需 {id, task}), 防字符串数组直接 TypeError
|
|
32
|
-
const bad = wf.find((n) => !n || typeof n !== "object" || !n.id || !n.task);
|
|
33
|
-
if (bad) return `[军团] workflow 节点需 {id, task} 对象, 非法节点: ${JSON.stringify(bad).slice(0, 80)}`;
|
|
34
|
-
try {
|
|
35
|
-
const { results } = await L.runDag({ nodes: wf });
|
|
36
|
-
const lines = Object.entries(results).map(([id, r]) => `【${id}】\n${r}`);
|
|
37
|
-
return lines.join("\n\n");
|
|
38
|
-
} catch (e) {
|
|
39
|
-
return `[军团] DAG 编排失败: ${e.message}`;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
const results = await L.broadcast("chat", userMsg);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
1
|
+
// src/mode/legion.js - 多 Agent 军团模式
|
|
2
|
+
// 把 Legion (多进程军团 + DAG 编排) 接入 mode 系统, chat({ mode: "legion" }) 可用。
|
|
3
|
+
// 编排策略:
|
|
4
|
+
// - 有 workflow (DAG 节点数组) 时走 runDag: 按依赖拓扑分层并行, 上游结果流入下游
|
|
5
|
+
// - 无 workflow 时走 broadcast: 同一问题广播给全部 agent, 取第一个有效回复
|
|
6
|
+
// 军团配置: config.agent.legion = { size, workflow } 或 config.orchestrator
|
|
7
|
+
// 可注入: opts.legion (测试/复用已有军团实例, 免重复 spawn)
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { Legion } from "../orchestrator/legion.js";
|
|
10
|
+
import { warn } from "../utils/logger.js";
|
|
11
|
+
|
|
12
|
+
export async function legionExecutor(agent, userMsg, { sessionKey = "default", legion = null, workflow = null, size = null } = {}) {
|
|
13
|
+
const cfg = (agent.config && (agent.config.agent?.legion || agent.config.orchestrator)) || {};
|
|
14
|
+
|
|
15
|
+
// 1. 拿或懒建军团 (缓存到 agent, 复用子进程, 不重复 spawn)
|
|
16
|
+
let L = legion || agent._legion;
|
|
17
|
+
if (!L) {
|
|
18
|
+
L = new Legion();
|
|
19
|
+
const n = size || cfg.size || 2;
|
|
20
|
+
for (let i = 0; i < n; i++) {
|
|
21
|
+
// 每个 agent 独立数据目录, 隔离记忆/会话, 互不干扰
|
|
22
|
+
const dataDir = path.join(agent.dataDir, "legion", `agent-${i}`);
|
|
23
|
+
L.spawnAgent(`agent-${i}`, { dataDir });
|
|
24
|
+
}
|
|
25
|
+
agent._legion = L;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 2. 编排: workflow(DAG) 优先, 否则 broadcast
|
|
29
|
+
const wf = workflow || cfg.workflow;
|
|
30
|
+
if (Array.isArray(wf) && wf.length) {
|
|
31
|
+
// v1.0.8: 校验节点结构 (需 {id, task}), 防字符串数组直接 TypeError
|
|
32
|
+
const bad = wf.find((n) => !n || typeof n !== "object" || !n.id || !n.task);
|
|
33
|
+
if (bad) return `[军团] workflow 节点需 {id, task} 对象, 非法节点: ${JSON.stringify(bad).slice(0, 80)}`;
|
|
34
|
+
try {
|
|
35
|
+
const { results } = await L.runDag({ nodes: wf });
|
|
36
|
+
const lines = Object.entries(results).map(([id, r]) => `【${id}】\n${r}`);
|
|
37
|
+
return lines.join("\n\n");
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return `[军团] DAG 编排失败: ${e.message}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const results = await L.broadcast("chat", userMsg);
|
|
45
|
+
// 真实 broadcast 返回形状: { agent, id, type:'reply', reply } 或 { agent, type:'error', error }
|
|
46
|
+
// (曾误写成 { status:'fulfilled', value:{ reply } } —— 那是测试桩伪造的形状, 导致此处恒为空,
|
|
47
|
+
// 军团 broadcast 模式在真实环境永远走兜底串)
|
|
48
|
+
const ok = results.filter((r) => r && r.type === "reply" && r.reply);
|
|
49
|
+
if (ok.length) return ok[0].reply;
|
|
50
|
+
} catch (e) {
|
|
51
|
+
warn("[legion] broadcast 失败:", e.message);
|
|
52
|
+
}
|
|
53
|
+
return "[军团] 所有 agent 均未返回有效结果 (请确认已 spawnAgent 或配置 config.agent.legion.size)";
|
|
54
|
+
}
|
package/src/mode/plan-exec.js
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
// src/mode/plan-exec.js - Plan & Execute 模式
|
|
2
|
-
// 先让 LLM 生成完整计划(步骤清单), 再逐步执行(每步走 react 工具循环), 最后汇总。
|
|
3
|
-
// 适合: 可分解的多步任务, 结构化、可预测、减少中间推理浪费。
|
|
4
|
-
import { buildMessages } from "./index.js";
|
|
5
|
-
|
|
6
|
-
// 解析 LLM 返回的步骤清单 (JSON 数组, 容忍 markdown 代码块包裹)
|
|
7
|
-
export function parseSteps(text) {
|
|
8
|
-
const cleaned = String(text || "").replace(/```(?:json|JSON)?\s*/g, "").replace(/```/g, "").trim();
|
|
9
|
-
const m = cleaned.match(/\[[\s\S]*\]/);
|
|
10
|
-
if (!m) return [];
|
|
11
|
-
try {
|
|
12
|
-
const arr = JSON.parse(m[0]);
|
|
13
|
-
return Array.isArray(arr) ? arr.map((x) => String(x).trim()).filter(Boolean) : [];
|
|
14
|
-
} catch { return []; }
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// 规划: LLM 生成步骤清单 (无 LLM 返回空)
|
|
18
|
-
async function planSteps(agent, userMsg) {
|
|
19
|
-
if (!agent.llm) return [];
|
|
20
|
-
const r = await agent.llm.chat([
|
|
21
|
-
{ role: "system", content: "你是任务规划器。把用户的任务分解成 2-6 个可独立执行的具体步骤, 每步一句话(含要做什么)。只输出 JSON 数组, 例如 [\"读取配置文件\", \"修改端口\", \"重启服务\"]。不要解释。" },
|
|
22
|
-
{ role: "user", content: String(userMsg).slice(0, 2000) },
|
|
23
|
-
]);
|
|
24
|
-
return parseSteps(r.content);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function planExecExecutor(agent, userMsg, { sessionKey = "default", maxSteps = 6 } = {}) {
|
|
28
|
-
if (!agent.llm) {
|
|
29
|
-
return (await agent._localIntent(userMsg)) || "[皮皮虾] 未配置模型 provider (配置见 docs/QUICKSTART.md 第 3 节)。";
|
|
30
|
-
}
|
|
31
|
-
// 1. 规划
|
|
32
|
-
const steps = (await planSteps(agent, userMsg)).slice(0, maxSteps);
|
|
33
|
-
if (!steps.length) {
|
|
34
|
-
// 规划失败: 退回 react 模式
|
|
35
|
-
const messages = await buildMessages(agent, userMsg, sessionKey);
|
|
36
|
-
return agent._llmWithFallback(messages);
|
|
37
|
-
}
|
|
38
|
-
// 2. 逐步执行 (每步走工具循环)
|
|
39
|
-
const base = await buildMessages(agent, userMsg, sessionKey);
|
|
40
|
-
const results = [];
|
|
41
|
-
for (let i = 0; i < steps.length; i++) {
|
|
42
|
-
const stepMsg = [...base, { role: "user", content: `[第 ${i + 1}/${steps.length} 步] ${steps[i]}` }];
|
|
43
|
-
let r;
|
|
44
|
-
try { r = await agent._llmWithFallback(stepMsg); }
|
|
45
|
-
catch (e) { r = `(步骤失败: ${e.message})`; }
|
|
46
|
-
results.push(`步骤${i + 1}: ${steps[i]}\n${r}`);
|
|
47
|
-
}
|
|
48
|
-
// 3. 汇总
|
|
49
|
-
return results.join("\n\n");
|
|
50
|
-
}
|
|
1
|
+
// src/mode/plan-exec.js - Plan & Execute 模式
|
|
2
|
+
// 先让 LLM 生成完整计划(步骤清单), 再逐步执行(每步走 react 工具循环), 最后汇总。
|
|
3
|
+
// 适合: 可分解的多步任务, 结构化、可预测、减少中间推理浪费。
|
|
4
|
+
import { buildMessages } from "./index.js";
|
|
5
|
+
|
|
6
|
+
// 解析 LLM 返回的步骤清单 (JSON 数组, 容忍 markdown 代码块包裹)
|
|
7
|
+
export function parseSteps(text) {
|
|
8
|
+
const cleaned = String(text || "").replace(/```(?:json|JSON)?\s*/g, "").replace(/```/g, "").trim();
|
|
9
|
+
const m = cleaned.match(/\[[\s\S]*\]/);
|
|
10
|
+
if (!m) return [];
|
|
11
|
+
try {
|
|
12
|
+
const arr = JSON.parse(m[0]);
|
|
13
|
+
return Array.isArray(arr) ? arr.map((x) => String(x).trim()).filter(Boolean) : [];
|
|
14
|
+
} catch { return []; }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 规划: LLM 生成步骤清单 (无 LLM 返回空)
|
|
18
|
+
async function planSteps(agent, userMsg) {
|
|
19
|
+
if (!agent.llm) return [];
|
|
20
|
+
const r = await agent.llm.chat([
|
|
21
|
+
{ role: "system", content: "你是任务规划器。把用户的任务分解成 2-6 个可独立执行的具体步骤, 每步一句话(含要做什么)。只输出 JSON 数组, 例如 [\"读取配置文件\", \"修改端口\", \"重启服务\"]。不要解释。" },
|
|
22
|
+
{ role: "user", content: String(userMsg).slice(0, 2000) },
|
|
23
|
+
]);
|
|
24
|
+
return parseSteps(r.content);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function planExecExecutor(agent, userMsg, { sessionKey = "default", maxSteps = 6 } = {}) {
|
|
28
|
+
if (!agent.llm) {
|
|
29
|
+
return (await agent._localIntent(userMsg)) || "[皮皮虾] 未配置模型 provider (配置见 docs/QUICKSTART.md 第 3 节)。";
|
|
30
|
+
}
|
|
31
|
+
// 1. 规划
|
|
32
|
+
const steps = (await planSteps(agent, userMsg)).slice(0, maxSteps);
|
|
33
|
+
if (!steps.length) {
|
|
34
|
+
// 规划失败: 退回 react 模式
|
|
35
|
+
const messages = await buildMessages(agent, userMsg, sessionKey);
|
|
36
|
+
return agent._llmWithFallback(messages);
|
|
37
|
+
}
|
|
38
|
+
// 2. 逐步执行 (每步走工具循环)
|
|
39
|
+
const base = await buildMessages(agent, userMsg, sessionKey);
|
|
40
|
+
const results = [];
|
|
41
|
+
for (let i = 0; i < steps.length; i++) {
|
|
42
|
+
const stepMsg = [...base, { role: "user", content: `[第 ${i + 1}/${steps.length} 步] ${steps[i]}` }];
|
|
43
|
+
let r;
|
|
44
|
+
try { r = await agent._llmWithFallback(stepMsg); }
|
|
45
|
+
catch (e) { r = `(步骤失败: ${e.message})`; }
|
|
46
|
+
results.push(`步骤${i + 1}: ${steps[i]}\n${r}`);
|
|
47
|
+
}
|
|
48
|
+
// 3. 汇总
|
|
49
|
+
return results.join("\n\n");
|
|
50
|
+
}
|
package/src/mode/router.js
CHANGED
|
@@ -1,40 +1,28 @@
|
|
|
1
|
-
// src/mode/router.js - Router + Skill 模式
|
|
2
|
-
// 意图路由: 按用户输入匹配已安装技能, 把匹配到的 SKILL.md 注入上下文后执行。
|
|
3
|
-
// 适合: 多领域任务, 快速定位专门能力; 按需加载技能, 省 token。
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { SkillLoader } from "../skills/loader.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return (await agent._localIntent(userMsg)) || "[皮皮虾] 未配置模型 provider (配置见 docs/QUICKSTART.md 第 3 节)。";
|
|
30
|
-
}
|
|
31
|
-
// 1. 技能路由: 匹配用户输入到已安装技能
|
|
32
|
-
const loader = new SkillLoader(path.join(agent.root, "skills"));
|
|
33
|
-
const skill = matchSkill(loader, userMsg);
|
|
34
|
-
// 2. 注入技能内容到 system prompt, 再执行 (场景上下文已由 agent._context 注入)
|
|
35
|
-
const system = agent._context(userMsg)
|
|
36
|
-
+ (skill ? `\n\n[已激活技能: ${skill.name}]\n${loader.read(skill.id)}` : "");
|
|
37
|
-
const history = await agent._loadHistory(sessionKey);
|
|
38
|
-
const messages = [{ role: "system", content: system }, ...history, { role: "user", content: String(userMsg) }];
|
|
39
|
-
return agent._llmWithFallback(messages);
|
|
40
|
-
}
|
|
1
|
+
// src/mode/router.js - Router + Skill 模式
|
|
2
|
+
// 意图路由: 按用户输入匹配已安装技能, 把匹配到的 SKILL.md 注入上下文后执行。
|
|
3
|
+
// 适合: 多领域任务, 快速定位专门能力; 按需加载技能, 省 token。
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { SkillLoader } from "../skills/loader.js";
|
|
6
|
+
import { debug } from "../utils/logger.js";
|
|
7
|
+
|
|
8
|
+
// 技能匹配移至 src/skills/search.js (2026-10-01): name 加权 + 高置信阈值 + 歧义不押注,
|
|
9
|
+
// 并供 skill_search 工具复用同一打分器。此处保持转出口兼容既有 import。
|
|
10
|
+
export { matchSkill } from "../skills/search.js";
|
|
11
|
+
|
|
12
|
+
export async function routerExecutor(agent, userMsg, { sessionKey = "default" } = {}) {
|
|
13
|
+
if (!agent.llm) {
|
|
14
|
+
return (await agent._localIntent(userMsg)) || "[皮皮虾] 未配置模型 provider (配置见 docs/QUICKSTART.md 第 3 节)。";
|
|
15
|
+
}
|
|
16
|
+
// 1. 技能路由: 匹配用户输入到已安装技能
|
|
17
|
+
// 2026-10-01 优化: 复用 agent.skills (原每条消息 new 一个 loader 全量重扫),
|
|
18
|
+
// 命中即 trackUse — 路由路径此前绕过了使用统计, 自进化飞轮 (auto_skill/升级闸门) 因此少计数。
|
|
19
|
+
const loader = agent.skills || new SkillLoader(path.join(agent.root, "skills"));
|
|
20
|
+
const skill = matchSkill(loader, userMsg);
|
|
21
|
+
if (skill && typeof loader.trackUse === "function") { try { loader.trackUse(skill.id); } catch (e) { debug(`[mode/router] 已忽略异常: ${e && e.message ? e.message : e}`); } }
|
|
22
|
+
// 2. 注入技能内容到 system prompt, 再执行 (场景上下文已由 agent._context 注入)
|
|
23
|
+
const system = agent._context(userMsg)
|
|
24
|
+
+ (skill ? `\n\n[已激活技能: ${skill.name}]\n${loader.read(skill.id)}` : "");
|
|
25
|
+
const history = await agent._loadHistory(sessionKey);
|
|
26
|
+
const messages = [{ role: "system", content: system }, ...history, { role: "user", content: String(userMsg) }];
|
|
27
|
+
return agent._llmWithFallback(messages);
|
|
28
|
+
}
|
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
// src/orchestrator/agent-worker.js - 子 agent 进程 (独立 PPXAgent 实例)
|
|
2
|
-
// 通过 stdin 收消息, stdout 回结果. 每个 worker 完全隔离.
|
|
3
|
-
import { PPXAgent } from "../agent/index.js";
|
|
4
|
-
|
|
5
|
-
const root = process.cwd();
|
|
6
|
-
// 可选: 独立数据目录 (多进程军团隔离, 避免互踩同一个 data/)
|
|
7
|
-
// 可选: 全局共享目录 (跨 agent 共享经验库 = ANS 全局记忆)
|
|
8
|
-
const agent = new PPXAgent({
|
|
9
|
-
root,
|
|
10
|
-
dataDir: process.env.PPX_AGENT_DATA_DIR || undefined,
|
|
11
|
-
globalDataDir: process.env.PPX_AGENT_GLOBAL_DATA_DIR || undefined,
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// 只读审查者模式 (SDD, Superpowers): 禁用一切修改/执行类工具, 只能读/查
|
|
15
|
-
// 供 spawn_agent review 循环的审查者角色使用
|
|
16
|
-
if (process.env.PPX_AGENT_READONLY === "1") {
|
|
17
|
-
agent.enableReadonlyMode();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// 从 stdin 读 JSON 行
|
|
21
|
-
process.stdin.setEncoding("utf8");
|
|
22
|
-
let buf = "";
|
|
23
|
-
let currentReqId = null; // 当前正在处理的 chat 请求 id (step 事件路由用)
|
|
24
|
-
// v1.0.8: promise 链串行处理 (防止 data 事件并发时 currentReqId/_perspective 互相覆盖)
|
|
25
|
-
let queue = Promise.resolve();
|
|
26
|
-
// step 事件转发: 把 agent 的推理轮次进度实时回给主进程 (turn/step 可观测性)
|
|
27
|
-
agent.setStepEvent((ev) => {
|
|
28
|
-
if (currentReqId == null) return;
|
|
29
|
-
process.stdout.write(JSON.stringify({ id: currentReqId, type: "step", ...ev }) + "\n");
|
|
30
|
-
});
|
|
31
|
-
process.stdin.on("data", (chunk) => {
|
|
32
|
-
buf += chunk;
|
|
33
|
-
let idx;
|
|
34
|
-
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
35
|
-
const line = buf.slice(0, idx).trim();
|
|
36
|
-
buf = buf.slice(idx + 1);
|
|
37
|
-
if (!line) continue;
|
|
38
|
-
queue = queue.then(() => handleLine(line));
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
async function handleLine(line) {
|
|
43
|
-
try {
|
|
44
|
-
const req = JSON.parse(line);
|
|
45
|
-
if (req.type === "chat") {
|
|
46
|
-
currentReqId = req.id;
|
|
47
|
-
// 差异化上下文 (多 agent 协调): 委派方可注入子 agent 专属视角, 对抗同质失败
|
|
48
|
-
const prevPerspective = agent._perspective;
|
|
49
|
-
agent._perspective = req.perspective ? String(req.perspective).slice(0, 4000) : null;
|
|
50
|
-
let reply;
|
|
51
|
-
try {
|
|
52
|
-
reply = await agent.chat(req.message);
|
|
53
|
-
} finally {
|
|
54
|
-
agent._perspective = prevPerspective;
|
|
55
|
-
}
|
|
56
|
-
currentReqId = null;
|
|
57
|
-
process.stdout.write(JSON.stringify({ id: req.id, type: "reply", reply }) + "\n");
|
|
58
|
-
} else if (req.type === "ping") {
|
|
59
|
-
process.stdout.write(JSON.stringify({ id: req.id, type: "pong", name: agent.config.agent?.name || "ppx" }) + "\n");
|
|
60
|
-
} else if (req.type === "shutdown") {
|
|
61
|
-
agent.shutdown();
|
|
62
|
-
process.stdout.write(JSON.stringify({ id: req.id, type: "bye" }) + "\n");
|
|
63
|
-
process.exit(0);
|
|
64
|
-
}
|
|
65
|
-
} catch (e) {
|
|
66
|
-
// v1.0.8: error 行必须带 req.id, 否则主进程 Legion.pending 匹配不到永不 settle (永久挂起)
|
|
67
|
-
process.stdout.write(JSON.stringify({ id: currentReqId || null, type: "error", error: e.message }) + "\n");
|
|
68
|
-
currentReqId = null;
|
|
69
|
-
}
|
|
1
|
+
// src/orchestrator/agent-worker.js - 子 agent 进程 (独立 PPXAgent 实例)
|
|
2
|
+
// 通过 stdin 收消息, stdout 回结果. 每个 worker 完全隔离.
|
|
3
|
+
import { PPXAgent } from "../agent/index.js";
|
|
4
|
+
|
|
5
|
+
const root = process.cwd();
|
|
6
|
+
// 可选: 独立数据目录 (多进程军团隔离, 避免互踩同一个 data/)
|
|
7
|
+
// 可选: 全局共享目录 (跨 agent 共享经验库 = ANS 全局记忆)
|
|
8
|
+
const agent = new PPXAgent({
|
|
9
|
+
root,
|
|
10
|
+
dataDir: process.env.PPX_AGENT_DATA_DIR || undefined,
|
|
11
|
+
globalDataDir: process.env.PPX_AGENT_GLOBAL_DATA_DIR || undefined,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// 只读审查者模式 (SDD, Superpowers): 禁用一切修改/执行类工具, 只能读/查
|
|
15
|
+
// 供 spawn_agent review 循环的审查者角色使用
|
|
16
|
+
if (process.env.PPX_AGENT_READONLY === "1") {
|
|
17
|
+
agent.enableReadonlyMode();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 从 stdin 读 JSON 行
|
|
21
|
+
process.stdin.setEncoding("utf8");
|
|
22
|
+
let buf = "";
|
|
23
|
+
let currentReqId = null; // 当前正在处理的 chat 请求 id (step 事件路由用)
|
|
24
|
+
// v1.0.8: promise 链串行处理 (防止 data 事件并发时 currentReqId/_perspective 互相覆盖)
|
|
25
|
+
let queue = Promise.resolve();
|
|
26
|
+
// step 事件转发: 把 agent 的推理轮次进度实时回给主进程 (turn/step 可观测性)
|
|
27
|
+
agent.setStepEvent((ev) => {
|
|
28
|
+
if (currentReqId == null) return;
|
|
29
|
+
process.stdout.write(JSON.stringify({ id: currentReqId, type: "step", ...ev }) + "\n");
|
|
30
|
+
});
|
|
31
|
+
process.stdin.on("data", (chunk) => {
|
|
32
|
+
buf += chunk;
|
|
33
|
+
let idx;
|
|
34
|
+
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
35
|
+
const line = buf.slice(0, idx).trim();
|
|
36
|
+
buf = buf.slice(idx + 1);
|
|
37
|
+
if (!line) continue;
|
|
38
|
+
queue = queue.then(() => handleLine(line));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
async function handleLine(line) {
|
|
43
|
+
try {
|
|
44
|
+
const req = JSON.parse(line);
|
|
45
|
+
if (req.type === "chat") {
|
|
46
|
+
currentReqId = req.id;
|
|
47
|
+
// 差异化上下文 (多 agent 协调): 委派方可注入子 agent 专属视角, 对抗同质失败
|
|
48
|
+
const prevPerspective = agent._perspective;
|
|
49
|
+
agent._perspective = req.perspective ? String(req.perspective).slice(0, 4000) : null;
|
|
50
|
+
let reply;
|
|
51
|
+
try {
|
|
52
|
+
reply = await agent.chat(req.message);
|
|
53
|
+
} finally {
|
|
54
|
+
agent._perspective = prevPerspective;
|
|
55
|
+
}
|
|
56
|
+
currentReqId = null;
|
|
57
|
+
process.stdout.write(JSON.stringify({ id: req.id, type: "reply", reply }) + "\n");
|
|
58
|
+
} else if (req.type === "ping") {
|
|
59
|
+
process.stdout.write(JSON.stringify({ id: req.id, type: "pong", name: agent.config.agent?.name || "ppx" }) + "\n");
|
|
60
|
+
} else if (req.type === "shutdown") {
|
|
61
|
+
agent.shutdown();
|
|
62
|
+
process.stdout.write(JSON.stringify({ id: req.id, type: "bye" }) + "\n");
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {
|
|
66
|
+
// v1.0.8: error 行必须带 req.id, 否则主进程 Legion.pending 匹配不到永不 settle (永久挂起)
|
|
67
|
+
process.stdout.write(JSON.stringify({ id: currentReqId || null, type: "error", error: e.message }) + "\n");
|
|
68
|
+
currentReqId = null;
|
|
69
|
+
}
|
|
70
70
|
}
|