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/plugin/context.js
CHANGED
|
@@ -1,79 +1,80 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// -
|
|
5
|
-
// -
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
wrapper
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (this.
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (this.
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.
|
|
78
|
-
|
|
79
|
-
}
|
|
1
|
+
import { debug } from "../utils/logger.js";
|
|
2
|
+
// src/plugin/context.js - 轻量服务注册表 (零依赖插件系统核心)
|
|
3
|
+
// 借鉴 deepseek-harness 的 Cordis "everything is a plugin" 理念, 但零依赖、同步、极简:
|
|
4
|
+
// - 插件 = (ctx) => void 函数, 通过 ctx.provide(key, value) 注册服务, ctx.consume(key) 消费服务
|
|
5
|
+
// - 注册是可逆效果: ctx.onDispose(fn) 挂卸载钩子, ctx.dispose() 逆序执行
|
|
6
|
+
// - 支持父子 context: 子 context 的 consume 会向父查找 (用于 agent 内 scope 隔离)
|
|
7
|
+
// P2⑧ (2026-09-15): 插件两级权限 (吸收 HanaAgent restricted/full-access 思想) ——
|
|
8
|
+
// 敏感服务 (路由/生命周期/页面/工具链) 只允许 full-access 插件注册; restricted 插件
|
|
9
|
+
// 只能注册普通服务。compose 时校验, 违规注册直接拒绝 (代码强制, 不靠约定)。
|
|
10
|
+
export const PLUGIN_ACCESS = { RESTRICTED: "restricted", FULL: "full-access" };
|
|
11
|
+
|
|
12
|
+
// 敏感服务: 仅 full-access 插件可注册 (注册到这些 key 会触发权限校验)
|
|
13
|
+
const SENSITIVE_SERVICES = new Set([
|
|
14
|
+
"routes", // HTTP 路由
|
|
15
|
+
"lifecycle", // 生命周期钩子
|
|
16
|
+
"tools", // 工具链
|
|
17
|
+
"shell", // 命令执行
|
|
18
|
+
"pages", // 页面
|
|
19
|
+
"providers", // LLM provider
|
|
20
|
+
"extensions", // 扩展
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export class Context {
|
|
24
|
+
constructor(parent = null, { access = PLUGIN_ACCESS.RESTRICTED } = {}) {
|
|
25
|
+
this.parent = parent;
|
|
26
|
+
this.access = access; // P2⑧: 当前插件权限位
|
|
27
|
+
this._services = new Map(); // key -> value
|
|
28
|
+
this._disposers = []; // 可逆效果 (dispose 时逆序执行)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// P2⑧: 切换权限位 —— 返回共享同一存储的包装 (原型继承), 仅 access 不同。
|
|
32
|
+
// 这样 restricted 插件注册的普通服务, 顶层 ctx.consume 也能查到 (服务仍全局可见),
|
|
33
|
+
// 但敏感 key 注册会被拒绝。
|
|
34
|
+
withAccess(access) {
|
|
35
|
+
const wrapper = Object.create(this);
|
|
36
|
+
wrapper.access = access;
|
|
37
|
+
return wrapper;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 注册服务 (返回 value 便于链式)
|
|
41
|
+
provide(key, value) {
|
|
42
|
+
// P2⑧: 敏感服务权限校验 —— restricted 插件注册敏感 key 被拒
|
|
43
|
+
if (SENSITIVE_SERVICES.has(key) && this.access !== PLUGIN_ACCESS.FULL) {
|
|
44
|
+
throw new Error(`[插件权限] 注册 ${key} 需要 full-access, 当前 ${this.access}`);
|
|
45
|
+
}
|
|
46
|
+
this._services.set(key, value);
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 消费服务: 先查自己, 再向上查父 context
|
|
51
|
+
consume(key) {
|
|
52
|
+
if (this._services.has(key)) return this._services.get(key);
|
|
53
|
+
if (this.parent) return this.parent.consume(key);
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
has(key) {
|
|
58
|
+
if (this._services.has(key)) return true;
|
|
59
|
+
if (this.parent) return this.parent.has(key);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 注册可逆效果 (卸载时执行), 返回取消函数
|
|
64
|
+
onDispose(fn) {
|
|
65
|
+
this._disposers.push(fn);
|
|
66
|
+
return () => {
|
|
67
|
+
const i = this._disposers.indexOf(fn);
|
|
68
|
+
if (i >= 0) this._disposers.splice(i, 1);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 卸载: 逆序执行所有 disposer
|
|
73
|
+
async dispose() {
|
|
74
|
+
for (const fn of [...this._disposers].reverse()) {
|
|
75
|
+
try { await fn(); } catch (e) { debug(`[plugin/context] 已忽略异常: ${e && e.message ? e.message : e}`); }
|
|
76
|
+
}
|
|
77
|
+
this._disposers = [];
|
|
78
|
+
this._services.clear();
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/plugin/index.js
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
// src/plugin/index.js - 插件装配器
|
|
2
|
-
// compose(ctx, plugins): 按顺序装配插件 (同步), 返回 ctx 便于链式
|
|
3
|
-
// 插件 = (ctx) => void, 在函数内用 ctx.provide 注册服务、ctx.consume 消费依赖、ctx.onDispose 挂卸载钩子
|
|
4
|
-
// P2⑧: 插件可声明权限 (plugin.access = 'full-access'), 声明方式: 插件函数挂 access 属性 或 导出对象带 access。
|
|
5
|
-
import fs from "node:fs";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import { createRequire } from "node:module";
|
|
8
|
-
import { info, warn } from "../utils/logger.js";
|
|
9
|
-
import { Context, PLUGIN_ACCESS } from "./context.js";
|
|
10
|
-
|
|
11
|
-
export { Context } from "./context.js";
|
|
12
|
-
|
|
13
|
-
// 解析插件权限: 函数自身属性 > 导出对象 access 字段; 默认 restricted
|
|
14
|
-
export function pluginAccess(plugin) {
|
|
15
|
-
if (typeof plugin === "function" && plugin.access) return plugin.access;
|
|
16
|
-
if (plugin && plugin.access) return plugin.access;
|
|
17
|
-
return PLUGIN_ACCESS.RESTRICTED;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// 装配插件树: 顺序执行每个插件的 setup
|
|
21
|
-
// v1.0.9: 单个插件 setup 抛错不中断整条装配链 (隔离失败, 其余插件照常)
|
|
22
|
-
// P2⑧: 每个插件在自己的权限 context 里运行 (敏感服务注册受控)。
|
|
23
|
-
// full-access 插件直接用父 ctx (服务注册到顶层, agent.consume 可见);
|
|
24
|
-
// restricted 插件用 withAccess 子 context (注册到子, 父链向上可查, 但敏感 key 被拒)。
|
|
25
|
-
export function compose(ctx, plugins = []) {
|
|
26
|
-
for (const p of plugins) {
|
|
27
|
-
try {
|
|
28
|
-
const access = pluginAccess(p);
|
|
29
|
-
const pCtx = access === PLUGIN_ACCESS.FULL ? ctx : ctx.withAccess(access);
|
|
30
|
-
if (typeof p === "function") p(pCtx);
|
|
31
|
-
else if (p && typeof p.setup === "function") p.setup(pCtx);
|
|
32
|
-
} catch (e) {
|
|
33
|
-
warn(`[plugins] setup 失败已隔离: ${(e && e.message) || e}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return ctx;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// 扫描 plugins/ 目录加载用户插件 (声明式, 不改源码扩展 agent)
|
|
40
|
-
// 每个 .cjs 导出插件函数 (ctx) => void 或 { setup(ctx) }
|
|
41
|
-
export function loadPlugins(pluginsDir) {
|
|
42
|
-
if (!pluginsDir || !fs.existsSync(pluginsDir)) return [];
|
|
43
|
-
const require = createRequire(import.meta.url);
|
|
44
|
-
let files = [];
|
|
45
|
-
try { files = fs.readdirSync(pluginsDir); } catch { return []; }
|
|
46
|
-
const candidates = files.filter((f) => f.endsWith(".cjs") || f.endsWith(".js")).sort();
|
|
47
|
-
const plugins = [];
|
|
48
|
-
for (const f of candidates) {
|
|
49
|
-
const full = path.join(pluginsDir, f);
|
|
50
|
-
try {
|
|
51
|
-
const mod = require(full);
|
|
52
|
-
const plugin = mod && mod.default ? mod.default : mod;
|
|
53
|
-
if (typeof plugin === "function") plugins.push(plugin);
|
|
54
|
-
else if (plugin && typeof plugin.setup === "function") plugins.push(plugin);
|
|
55
|
-
else warn(`[plugins] ${f} 需导出插件函数 (ctx) => void, 跳过`);
|
|
56
|
-
} catch (e) {
|
|
57
|
-
warn(`[plugins] 加载 ${f} 失败: ${e.message}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if (plugins.length) info(`[plugins] 已加载 ${plugins.length} 个自定义插件`);
|
|
61
|
-
return plugins;
|
|
62
|
-
}
|
|
1
|
+
// src/plugin/index.js - 插件装配器
|
|
2
|
+
// compose(ctx, plugins): 按顺序装配插件 (同步), 返回 ctx 便于链式
|
|
3
|
+
// 插件 = (ctx) => void, 在函数内用 ctx.provide 注册服务、ctx.consume 消费依赖、ctx.onDispose 挂卸载钩子
|
|
4
|
+
// P2⑧: 插件可声明权限 (plugin.access = 'full-access'), 声明方式: 插件函数挂 access 属性 或 导出对象带 access。
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { info, warn } from "../utils/logger.js";
|
|
9
|
+
import { Context, PLUGIN_ACCESS } from "./context.js";
|
|
10
|
+
|
|
11
|
+
export { Context } from "./context.js";
|
|
12
|
+
|
|
13
|
+
// 解析插件权限: 函数自身属性 > 导出对象 access 字段; 默认 restricted
|
|
14
|
+
export function pluginAccess(plugin) {
|
|
15
|
+
if (typeof plugin === "function" && plugin.access) return plugin.access;
|
|
16
|
+
if (plugin && plugin.access) return plugin.access;
|
|
17
|
+
return PLUGIN_ACCESS.RESTRICTED;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 装配插件树: 顺序执行每个插件的 setup
|
|
21
|
+
// v1.0.9: 单个插件 setup 抛错不中断整条装配链 (隔离失败, 其余插件照常)
|
|
22
|
+
// P2⑧: 每个插件在自己的权限 context 里运行 (敏感服务注册受控)。
|
|
23
|
+
// full-access 插件直接用父 ctx (服务注册到顶层, agent.consume 可见);
|
|
24
|
+
// restricted 插件用 withAccess 子 context (注册到子, 父链向上可查, 但敏感 key 被拒)。
|
|
25
|
+
export function compose(ctx, plugins = []) {
|
|
26
|
+
for (const p of plugins) {
|
|
27
|
+
try {
|
|
28
|
+
const access = pluginAccess(p);
|
|
29
|
+
const pCtx = access === PLUGIN_ACCESS.FULL ? ctx : ctx.withAccess(access);
|
|
30
|
+
if (typeof p === "function") p(pCtx);
|
|
31
|
+
else if (p && typeof p.setup === "function") p.setup(pCtx);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
warn(`[plugins] setup 失败已隔离: ${(e && e.message) || e}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return ctx;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 扫描 plugins/ 目录加载用户插件 (声明式, 不改源码扩展 agent)
|
|
40
|
+
// 每个 .cjs 导出插件函数 (ctx) => void 或 { setup(ctx) }
|
|
41
|
+
export function loadPlugins(pluginsDir) {
|
|
42
|
+
if (!pluginsDir || !fs.existsSync(pluginsDir)) return [];
|
|
43
|
+
const require = createRequire(import.meta.url);
|
|
44
|
+
let files = [];
|
|
45
|
+
try { files = fs.readdirSync(pluginsDir); } catch { return []; }
|
|
46
|
+
const candidates = files.filter((f) => f.endsWith(".cjs") || f.endsWith(".js")).sort();
|
|
47
|
+
const plugins = [];
|
|
48
|
+
for (const f of candidates) {
|
|
49
|
+
const full = path.join(pluginsDir, f);
|
|
50
|
+
try {
|
|
51
|
+
const mod = require(full);
|
|
52
|
+
const plugin = mod && mod.default ? mod.default : mod;
|
|
53
|
+
if (typeof plugin === "function") plugins.push(plugin);
|
|
54
|
+
else if (plugin && typeof plugin.setup === "function") plugins.push(plugin);
|
|
55
|
+
else warn(`[plugins] ${f} 需导出插件函数 (ctx) => void, 跳过`);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
warn(`[plugins] 加载 ${f} 失败: ${e.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (plugins.length) info(`[plugins] 已加载 ${plugins.length} 个自定义插件`);
|
|
61
|
+
return plugins;
|
|
62
|
+
}
|
package/src/plugin/v3.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// src/plugin/v3.js - v3.0 新层装配插件 (protocol/session/permissions/hooks/commands/evidence)
|
|
2
|
+
// codex 对齐升级: 权限引擎 + 钩子链 + 斜杠命令 + 目标看板, 全部走「一切皆插件」装配, 可被用户插件替换。
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createHookRegistry } from "../hooks/index.js";
|
|
5
|
+
import {
|
|
6
|
+
createPermissionEngine,
|
|
7
|
+
AskForApproval,
|
|
8
|
+
SandboxPolicy,
|
|
9
|
+
} from "../permissions/index.js";
|
|
10
|
+
import { createRegistryWithUserCommands } from "../commands/index.js";
|
|
11
|
+
import { createGoalBoard } from "../evidence/index.js";
|
|
12
|
+
import { createProtocolBus } from "../protocol/index.js";
|
|
13
|
+
import { info } from "../utils/logger.js";
|
|
14
|
+
|
|
15
|
+
// 钩子链插件: claude-code 六事件 (PreToolUse/PostToolUse/PreCompact/SessionStart/...)
|
|
16
|
+
export const hooksPlugin = (ctx) => {
|
|
17
|
+
ctx.provide("hooks", createHookRegistry());
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// 权限引擎插件: codex AskForApproval 四档 + SandboxPolicy 三档 + opencode 规则链
|
|
21
|
+
// 配置: config.agent.approval_mode / config.agent.sandbox / config.agent.network_access
|
|
22
|
+
export const permissionsPlugin = (ctx) => {
|
|
23
|
+
const config = ctx.consume("config");
|
|
24
|
+
const agentCfg = config.agent || {};
|
|
25
|
+
const engine = createPermissionEngine({
|
|
26
|
+
approvalMode: agentCfg.approval_mode || AskForApproval.ON_REQUEST,
|
|
27
|
+
sandbox: agentCfg.sandbox || SandboxPolicy.WORKSPACE_WRITE,
|
|
28
|
+
workspaceRoot: ctx.consume("root"),
|
|
29
|
+
networkAccess: agentCfg.network_access !== false,
|
|
30
|
+
rules: Array.isArray(agentCfg.permission_rules) ? agentCfg.permission_rules : [],
|
|
31
|
+
});
|
|
32
|
+
// 用户自定义规则: [{pattern, action}] (opencode 风格, 有序 last-match-wins)
|
|
33
|
+
for (const r of Array.isArray(agentCfg.permission_rules) ? agentCfg.permission_rules : []) {
|
|
34
|
+
if (r && r.pattern && r.action) engine.addRule(r.pattern, r.action);
|
|
35
|
+
}
|
|
36
|
+
ctx.provide("permissions", engine);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// 斜杠命令插件: claude-code 统一命令模型 + 用户命令目录 .ppx/commands/*.md
|
|
40
|
+
export const commandsPlugin = (ctx) => {
|
|
41
|
+
const root = ctx.consume("root");
|
|
42
|
+
const registry = createRegistryWithUserCommands(path.join(root, ".ppx", "commands"));
|
|
43
|
+
ctx.provide("commands", registry);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// 证据/看板插件: OMH prepared/observed 边界 + goal board 目标看板
|
|
47
|
+
export const evidencePlugin = (ctx) => {
|
|
48
|
+
ctx.provide("goalBoard", createGoalBoard());
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// 协议总线插件: codex SQ/EQ 双队列
|
|
52
|
+
// 2026-10-03 接线 (P2): WAL 原硬编码 null (注释宣称"默认 data/protocol/eq.jsonl" 但从未开启) →
|
|
53
|
+
// 默认落盘 data/protocol/eq.wal.jsonl (config.protocol.wal_enabled=false 可关), 支持崩溃后 replay()。
|
|
54
|
+
// EQ 事件另有真实消费方: channels/http.js 订阅后经 SSE /events 广播给 Web UI 时间线。
|
|
55
|
+
export const protocolPlugin = (ctx) => {
|
|
56
|
+
const dataDir = ctx.consume("dataDir");
|
|
57
|
+
const config = ctx.consume("config") || {};
|
|
58
|
+
const walEnabled = config?.protocol?.wal_enabled !== false;
|
|
59
|
+
const bus = createProtocolBus({
|
|
60
|
+
walPath: walEnabled && dataDir ? path.join(dataDir, "protocol", "eq.wal.jsonl") : null,
|
|
61
|
+
});
|
|
62
|
+
ctx.provide("protocolBus", bus);
|
|
63
|
+
info(`[v3] 协议总线就绪 (SQ/EQ${walEnabled ? " + WAL" : ""})`);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// v3 全量插件组 (builtinPlugins 之后追加装配)
|
|
67
|
+
export const v3Plugins = [
|
|
68
|
+
hooksPlugin,
|
|
69
|
+
permissionsPlugin,
|
|
70
|
+
commandsPlugin,
|
|
71
|
+
evidencePlugin,
|
|
72
|
+
protocolPlugin,
|
|
73
|
+
];
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// src/protocol/index.js - SQ/EQ 双队列事件流 (对齐 OpenAI Codex SubmissionQueue/EventQueue)
|
|
2
|
+
// 通道层与内核解耦的唯一总线:
|
|
3
|
+
// SubmissionQueue: 用户操作入队 (op = {id, type, payload}), 先提交先处理
|
|
4
|
+
// EventQueue: 结构化事件出队 + 订阅回调 + WAL 回放 (JSONL)
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
// 操作类型 (用户侧动作, 进 SQ)
|
|
9
|
+
export const OpType = {
|
|
10
|
+
USER_TURN: "user_turn",
|
|
11
|
+
INTERRUPT: "interrupt",
|
|
12
|
+
RESUME: "resume",
|
|
13
|
+
APPROVE: "approve",
|
|
14
|
+
DENY: "deny",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// 事件类型 (内核产出, 供 Web UI 时间线渲染, 进 EQ)
|
|
18
|
+
export const EventType = {
|
|
19
|
+
TASK_STARTED: "task_started",
|
|
20
|
+
AGENT_MESSAGE: "agent_message",
|
|
21
|
+
TOOL_CALLED: "tool_called",
|
|
22
|
+
TOOL_OUTPUT: "tool_output",
|
|
23
|
+
APPROVAL_REQUESTED: "approval_requested",
|
|
24
|
+
APPROVAL_RESOLVED: "approval_resolved",
|
|
25
|
+
TASK_COMPLETE: "task_complete",
|
|
26
|
+
TASK_ABORTED: "task_aborted",
|
|
27
|
+
TURN_DIFF: "turn_diff",
|
|
28
|
+
ERROR: "error",
|
|
29
|
+
TOKEN_COUNT: "token_count",
|
|
30
|
+
PLAN_UPDATED: "plan_updated",
|
|
31
|
+
COMPACTED: "compacted",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// SubmissionQueue: 用户操作入队, 内部数组队列, 先提交先处理
|
|
35
|
+
export function createSubmissionQueue() {
|
|
36
|
+
let _seq = 0;
|
|
37
|
+
const queue = [];
|
|
38
|
+
|
|
39
|
+
// 默认 id: t_<时间戳>_<自增序号> (对齐 codex Id::UnixMs 唯一性)
|
|
40
|
+
function _newId() {
|
|
41
|
+
_seq += 1;
|
|
42
|
+
return `t_${Date.now()}_${_seq}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 提交一个操作, 返回分配/给定的 id
|
|
46
|
+
function submit(op = {}) {
|
|
47
|
+
const id = op.id || _newId();
|
|
48
|
+
queue.push({ id, type: op.type, payload: op.payload });
|
|
49
|
+
return id;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 取出最早一个未处理 op 并出队 (无则 null) — 对齐 codex nextIdle 语义
|
|
53
|
+
function nextIdle() {
|
|
54
|
+
return queue.length ? queue.shift() : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 取走全部 op 并清空队列
|
|
58
|
+
function drain() {
|
|
59
|
+
const out = queue.splice(0, queue.length);
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { submit, nextIdle, drain };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// EventQueue: 结构化事件出队 + 订阅回调 + WAL 回放
|
|
67
|
+
export function createEventQueue({ walPath = null } = {}) {
|
|
68
|
+
let _seq = 0;
|
|
69
|
+
const events = []; // 内存历史
|
|
70
|
+
const subs = []; // 订阅回调
|
|
71
|
+
|
|
72
|
+
// WAL 目录不存在先建 (递归)
|
|
73
|
+
if (walPath) {
|
|
74
|
+
const dir = path.dirname(walPath);
|
|
75
|
+
if (dir && !fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 推送一个事件: ev = {id?, ts?, type, payload}; seq 自增
|
|
79
|
+
function push(ev) {
|
|
80
|
+
_seq += 1;
|
|
81
|
+
const full = {
|
|
82
|
+
id: ev.id || `e_${Date.now()}_${_seq}`,
|
|
83
|
+
ts: ev.ts || Date.now(),
|
|
84
|
+
seq: _seq,
|
|
85
|
+
type: ev.type,
|
|
86
|
+
payload: ev.payload,
|
|
87
|
+
};
|
|
88
|
+
events.push(full);
|
|
89
|
+
// 同步落 WAL (JSONL, 一行一 JSON)
|
|
90
|
+
if (walPath) {
|
|
91
|
+
fs.appendFileSync(walPath, JSON.stringify(full) + "\n", "utf8");
|
|
92
|
+
}
|
|
93
|
+
// 同步通知订阅回调 (单个回调异常不影响事件流)
|
|
94
|
+
for (const fn of subs) {
|
|
95
|
+
try { fn(full); } catch { /* 订阅回调不应中断事件流 */ }
|
|
96
|
+
}
|
|
97
|
+
return full;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 订阅: 返回取消订阅函数
|
|
101
|
+
function subscribe(fn) {
|
|
102
|
+
subs.push(fn);
|
|
103
|
+
return () => {
|
|
104
|
+
const i = subs.indexOf(fn);
|
|
105
|
+
if (i >= 0) subs.splice(i, 1);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 回放 WAL: 读文件返回全部事件
|
|
110
|
+
function replay() {
|
|
111
|
+
if (!walPath || !fs.existsSync(walPath)) return [];
|
|
112
|
+
const out = [];
|
|
113
|
+
for (const line of fs.readFileSync(walPath, "utf8").split("\n").filter(Boolean)) {
|
|
114
|
+
try { out.push(JSON.parse(line)); } catch { /* 跳过损坏行 */ }
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 内存历史快照
|
|
120
|
+
function history() {
|
|
121
|
+
return [...events];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { push, subscribe, replay, history };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// createProtocolBus: 便捷工厂 — 把 SQ/EQ 与常用提交动作打包
|
|
128
|
+
export function createProtocolBus({ walPath } = {}) {
|
|
129
|
+
const sq = createSubmissionQueue();
|
|
130
|
+
const eq = createEventQueue({ walPath });
|
|
131
|
+
|
|
132
|
+
// 提交一个用户回合 (USER_TURN op)
|
|
133
|
+
function submitUserTurn(text) {
|
|
134
|
+
return sq.submit({ type: OpType.USER_TURN, payload: { text } });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 中断当前回合 (INTERRUPT op)
|
|
138
|
+
function interrupt() {
|
|
139
|
+
return sq.submit({ type: OpType.INTERRUPT, payload: {} });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 关闭总线 (无持久连接, 留作未来扩展点)
|
|
143
|
+
function close() {
|
|
144
|
+
/* noop */
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { sq, eq, submitUserTurn, interrupt, close };
|
|
148
|
+
}
|