agent-purge 0.2.9 → 0.2.11
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/CONTRIBUTING.md +64 -0
- package/LICENSE +21 -21
- package/README.md +176 -118
- package/backends/index.mjs +204 -244
- package/bench/bench-armor.mjs +844 -832
- package/bench/capture-hot.mjs +58 -0
- package/bench/capture-provider.mjs +66 -0
- package/bench/capture-report.mjs +15 -0
- package/bench/capture-server-hot.mjs +55 -0
- package/bench/capture-system.mjs +36 -0
- package/bench/judge-armor.mjs +119 -119
- package/bin/agent-purge.mjs +906 -772
- package/docs/LEGAL.md +27 -0
- package/docs/architecture.md +39 -0
- package/docs/compatibility.md +78 -0
- package/docs/images/console-desktop.png +0 -0
- package/docs/images/console-mobile.png +0 -0
- package/docs/releasing.md +87 -0
- package/docs/usage.md +158 -0
- package/lib/activation.mjs +38 -0
- package/lib/apply.mjs +227 -0
- package/lib/armed.mjs +38 -38
- package/lib/armor.mjs +225 -222
- package/lib/caps.mjs +182 -173
- package/lib/capture.mjs +324 -0
- package/lib/clean.mjs +124 -0
- package/lib/control.mjs +60 -0
- package/lib/detect.mjs +74 -70
- package/lib/doctor.mjs +7 -7
- package/lib/dsh.mjs +495 -453
- package/lib/environment.mjs +36 -0
- package/lib/evidence.mjs +41 -0
- package/lib/extension.mjs +140 -0
- package/lib/footprints.mjs +59 -0
- package/lib/home.mjs +47 -47
- package/lib/launch-conflicts.mjs +24 -0
- package/lib/launch.mjs +57 -0
- package/lib/opencode.mjs +62 -0
- package/lib/paths.mjs +25 -0
- package/lib/persist.mjs +439 -280
- package/lib/personas.mjs +17 -7
- package/lib/private-server.mjs +54 -0
- package/lib/project.mjs +134 -0
- package/lib/restore.mjs +131 -114
- package/lib/rewrite.mjs +124 -0
- package/lib/root.mjs +37 -0
- package/lib/shim.mjs +203 -160
- package/lib/slashes.mjs +227 -628
- package/lib/spawn.mjs +182 -126
- package/lib/state.mjs +85 -137
- package/lib/templates/personas/neutral.md +30 -30
- package/lib/update.mjs +137 -0
- package/lib/verification.mjs +23 -0
- package/lib/window.mjs +58 -0
- package/lib/wrappers.mjs +49 -0
- package/magpie-plugin/README.md +44 -0
- package/matrix.json +325 -341
- package/package.json +30 -4
- package/web/app.js +525 -0
- package/web/assets/agent-purge.ico +0 -0
- package/web/assets/claude.ico +0 -0
- package/web/assets/codebuddy.png +0 -0
- package/web/assets/codex.webp +0 -0
- package/web/assets/dsh.ico +0 -0
- package/web/assets/grok.ico +0 -0
- package/web/assets/hermes.ico +0 -0
- package/web/assets/omp.png +0 -0
- package/web/assets/opencode.ico +0 -0
- package/web/assets/opencode2.ico +0 -0
- package/web/assets/pi.png +0 -0
- package/web/assets/zcode.png +0 -0
- package/web/model.mjs +49 -0
- package/web/server.mjs +309 -194
- package/web/styles.css +917 -0
- package/web/ui.html +64 -1163
package/lib/caps.mjs
CHANGED
|
@@ -1,173 +1,182 @@
|
|
|
1
|
-
// 通道能力表:这家 harness 到底能不能用这条通道,不能用是为什么。
|
|
2
|
-
//
|
|
3
|
-
// 为什么要有这张表:UI 上给一个能点的开关、点了却什么都没发生,比不给开关更糟
|
|
4
|
-
// (实测踩过:zcode 是闭源桌面,双击启动根本不经过 PATH,裸命令接管对它永远无效)。
|
|
5
|
-
// 真相只有一处:矩阵(matrix.json)+ 持久化后端表(persist.mjs)+ /purge 支持表(slashes.mjs)。
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
export const
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
},
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
return {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
1
|
+
// 通道能力表:这家 harness 到底能不能用这条通道,不能用是为什么。
|
|
2
|
+
//
|
|
3
|
+
// 为什么要有这张表:UI 上给一个能点的开关、点了却什么都没发生,比不给开关更糟
|
|
4
|
+
// (实测踩过:zcode 是闭源桌面,双击启动根本不经过 PATH,裸命令接管对它永远无效)。
|
|
5
|
+
// 真相只有一处:矩阵(matrix.json)+ 持久化后端表(persist.mjs)+ /purge 支持表(slashes.mjs)。
|
|
6
|
+
import { liveProfiles } from "./dsh.mjs";
|
|
7
|
+
import { targets as persistTargets } from "./persist.mjs";
|
|
8
|
+
import { projectTargets, PROJECT_NOTE } from "./project.mjs";
|
|
9
|
+
import { resolveReal } from "./shim.mjs";
|
|
10
|
+
import { readResource } from "./root.mjs";
|
|
11
|
+
|
|
12
|
+
/** 通道存在、但本机没能真跑一次的(模型白名单 / 没有 CLI):UI 标「文档」,不标「实测」 */
|
|
13
|
+
const NOT_LIVE_TESTED = new Set(["claude", "zcode"]);
|
|
14
|
+
const matrix = readMatrix();
|
|
15
|
+
|
|
16
|
+
function readMatrix() {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(readResource("matrix.json").toString("utf8"));
|
|
19
|
+
} catch (e) {
|
|
20
|
+
throw new Error(`matrix.json 读不了:${e.message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 有 /purge 机制的 harness —— **只算能把内容写进系统提示词的**(pi/omp 的扩展钩子、dsh 的 cordis 插件) */
|
|
25
|
+
export const SLASH_IDS = new Set(["pi", "dsh", "omp"]);
|
|
26
|
+
/** 有可写配置文件/插件的 harness(其余家只能点名启动或打补丁) */
|
|
27
|
+
export const PERSIST_IDS = new Set(Object.keys(persistTargets));
|
|
28
|
+
/** 能把内容真的送进 system 数组的层;user 层(上下文块/用户消息)一律不对外提供 */
|
|
29
|
+
const SYS_LAYER = "sys";
|
|
30
|
+
/** 被隐藏的落点叫什么(给 UI 写一行实话用);只有“曾经真有过落点”的才写 */
|
|
31
|
+
const HIDDEN_LABEL = {
|
|
32
|
+
claude: { install: "~/.claude/CLAUDE.md(<system-reminder> 块)", slash: "~/.claude/commands/purge.md" },
|
|
33
|
+
codex: { slash: "~/.codex/prompts/purge.md" },
|
|
34
|
+
opencode: { slash: "~/.opencode/command/purge.md" },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** 桌面形态优先、终端只是附带的:双击启动绕开 PATH,shim 注定拦不到 */
|
|
38
|
+
const DESKTOP_FIRST = new Set(["zcode", "antigravity"]);
|
|
39
|
+
|
|
40
|
+
/** 人工核过的特例原因(写不出通用条件的那种) */const WHY = {
|
|
41
|
+
antigravity: {
|
|
42
|
+
shim: "桌面版按二进制启动,PATH 那条路拦不到;只有 binary patch 路线",
|
|
43
|
+
install: "只有二进制补丁(notes/),没有配置可写",
|
|
44
|
+
},
|
|
45
|
+
// dsh 的 CLI 只能起 headless:接管裸命令会把桌面/网页入口静默改道
|
|
46
|
+
dsh: { shim: "dsh 命令行只能起 headless:接管裸命令会让双击/网页入口变成终端会话;要三形态就装常驻插件(install)" },
|
|
47
|
+
codebuddy: {
|
|
48
|
+
install: "只有 settings.json(插件开关/模型),没有能把文本塞进提示的词条;实测只能走启动参数或壳",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** 整家不可注入时的统一说明(matrix.reason 没有覆盖到的人工补) */
|
|
53
|
+
const BLOCKED = { antigravity: "二进制补丁才能改它的系统提示,配置层没有可写入口" };
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 会话内热切 = 进程每轮重拼 system。触发面分两种:
|
|
57
|
+
* command —— 会话里打 /purge(只有 pi 的扩展钩子能做到)
|
|
58
|
+
* state —— 改设置/换人设,下一轮就生效(插件每轮读 state.json)
|
|
59
|
+
*/
|
|
60
|
+
const HOT = {
|
|
61
|
+
pi: { kind: "command", needs: "slash", how: "扩展钩子 before_agent_start:/purge 或改设置,下一轮生效" },
|
|
62
|
+
omp: { kind: "command", needs: "slash", how: "扩展钩子 before_agent_start 改写 systemPrompt 数组:/purge 或改设置,下一轮生效" },
|
|
63
|
+
opencode: { kind: "state", needs: "install", how: "插件钩子 chat.system.transform 每轮读 state.json + active.md:改设置下一轮生效" },
|
|
64
|
+
dsh: { kind: "command", needs: "slash", how: "cordis 插件注册 /purge:改 state.json,下一句生效,不产生模型消息" },
|
|
65
|
+
opencode2: { kind: "state", needs: "install", how: "v2 插件 ctx.session.hook('context') 每轮读 state.json + active.md:改设置下一轮生效" },
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** 没有每轮拼装点的:说明为什么只能重启 */
|
|
69
|
+
const NOT_HOT = {
|
|
70
|
+
claude: "system 只在启动时拼一次;会话内只能往 user 消息里塞(实测 0/2)",
|
|
71
|
+
codex: "config.toml 启动时读;会话内没有能重拼 system 的钩子",
|
|
72
|
+
grok: "AGENTS.md 启动时读一次",
|
|
73
|
+
hermes: "SOUL.md 只在会话首次/重建时读;续会话沿用 session DB 里的 system 提示词,改文件不当场生效(agent/conversation_loop.py::_restore_or_build_system_prompt)",
|
|
74
|
+
zcode: "桌面版闭源,没有插件与命令钩子",
|
|
75
|
+
codebuddy: "只有启动参数",
|
|
76
|
+
antigravity: "客户端常量,只能二进制补丁",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function reason(id, channel) {
|
|
80
|
+
return WHY[id]?.[channel] ?? null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 单家能力。ctx 可传 { bin, found }(来自 detect,省一次 PATH 扫描);不传就现查。
|
|
85
|
+
* 返回 { injectable, shim:{ok,why}, install:{ok,why}, slash:{ok,why} }
|
|
86
|
+
*/
|
|
87
|
+
export function capsFor(id, ctx = {}) {
|
|
88
|
+
const spec = matrix.harnesses[id] ?? {};
|
|
89
|
+
const injectable = spec.inject === false || spec.class === "unsupported" ? false : true;
|
|
90
|
+
const blocked = !injectable
|
|
91
|
+
? `这个工具注入不进去${spec.reason ? ":" + spec.reason : BLOCKED[id] ? ":" + BLOCKED[id] : typeof spec.override === "string" ? ":" + spec.override : ""}`
|
|
92
|
+
: null;
|
|
93
|
+
|
|
94
|
+
const cliPath = ctx.bin ?? null;
|
|
95
|
+
const hasCli = Object.hasOwn(ctx, "bin") ? Boolean(cliPath) : Boolean(resolveReal(id));
|
|
96
|
+
const desktop = DESKTOP_FIRST.has(id);
|
|
97
|
+
|
|
98
|
+
const shimWhy = blocked
|
|
99
|
+
? blocked
|
|
100
|
+
: !hasCli
|
|
101
|
+
? desktop
|
|
102
|
+
? "本机没有命令行入口;桌面版双击/开始菜单启动也不经过 PATH —— 裸命令接管对它无效"
|
|
103
|
+
: `本机 PATH 里找不到 ${id}(先把它装到 PATH 上)`
|
|
104
|
+
: desktop
|
|
105
|
+
? `只对终端里敲 \`${id}\` 生效;桌面图标启动绕开 PATH`
|
|
106
|
+
: reason(id, "shim");
|
|
107
|
+
|
|
108
|
+
const t = persistTargets[id];
|
|
109
|
+
// dsh 的插件只能装进已经存在的 profile;没跑过 dsh 时按钮不该显示“可用”。
|
|
110
|
+
const noProfilesWhy =
|
|
111
|
+
id === "dsh" && liveProfiles().length === 0
|
|
112
|
+
? "找不到任何 dsh profile(~/.dsh/profiles/*/package.json)—— 先启动一次 dsh,让它把 profile 建好"
|
|
113
|
+
: null;
|
|
114
|
+
// user 层不算能力:写下去也压不住 harness 自己的身份,装了等于白装
|
|
115
|
+
const layerWhy =
|
|
116
|
+
t && t.layer && t.layer !== SYS_LAYER
|
|
117
|
+
? `${id} 的常驻文件只落到 ${t.layer} 层(${t.note ?? "上下文块"})——压不住系统提示词;要 sys 层只能启动时换(agent-purge ${id})`
|
|
118
|
+
: null;
|
|
119
|
+
const installWhy = blocked ? blocked : !PERSIST_IDS.has(id) ? (reason(id, "install") ?? "没有可写的配置文件") : (noProfilesWhy ?? layerWhy);
|
|
120
|
+
|
|
121
|
+
const slashWhy = blocked
|
|
122
|
+
? blocked
|
|
123
|
+
: !SLASH_IDS.has(id)
|
|
124
|
+
? `${id} 没有能改系统提示词的 /purge(/purge 展开成用户消息,实测无效;只有 pi/omp 的扩展和 dsh 的 cordis 插件能做到)`
|
|
125
|
+
: noProfilesWhy;
|
|
126
|
+
|
|
127
|
+
// 项目级通道:写进当前目录的 AGENTS.md / CLAUDE.md,没有该文件的家不摆开关
|
|
128
|
+
const projectName = projectTargets[id] ?? null;
|
|
129
|
+
const projectWhy = blocked ?? (projectName ? null : `${id} 没有项目级规则文件(AGENTS.md/CLAUDE.md)可写`);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
injectable,
|
|
133
|
+
shim: { ok: !shimWhy || (desktop && hasCli), why: shimWhy },
|
|
134
|
+
install: {
|
|
135
|
+
ok: !installWhy,
|
|
136
|
+
why: installWhy,
|
|
137
|
+
// 有落点、但落点压不住系统提示词 → 不当作可选项给出去(UI 也不显示)
|
|
138
|
+
hidden: Boolean(layerWhy),
|
|
139
|
+
hiddenLabel: layerWhy ? (HIDDEN_LABEL[id]?.install ?? t?.file?.()) : null,
|
|
140
|
+
layer: t?.layer ?? null,
|
|
141
|
+
note: t?.note ?? null,
|
|
142
|
+
verified: NOT_LIVE_TESTED.has(id) ? false : true,
|
|
143
|
+
},
|
|
144
|
+
hot: (() => {
|
|
145
|
+
const h = HOT[id];
|
|
146
|
+
if (!h) return { ok: false, kind: null, why: NOT_HOT[id] ?? `${id} 的 system 提示只在启动时拼一次`, how: null, needs: null };
|
|
147
|
+
// 热切靠哪个通道:那个通道不可用就不算热切能力
|
|
148
|
+
const dependent = h.needs === "slash" ? !slashWhy : !installWhy;
|
|
149
|
+
return {
|
|
150
|
+
ok: Boolean(dependent),
|
|
151
|
+
kind: h.kind,
|
|
152
|
+
why: dependent ? null : h.needs === "slash" ? slashWhy : installWhy,
|
|
153
|
+
how: h.how,
|
|
154
|
+
needs: h.needs,
|
|
155
|
+
};
|
|
156
|
+
})(),
|
|
157
|
+
slash: {
|
|
158
|
+
ok: !slashWhy,
|
|
159
|
+
why: slashWhy,
|
|
160
|
+
// 不能改系统提示词的 /purge 一律不摆出来:一个点了没反应的按钮比没有更糟
|
|
161
|
+
hidden: Boolean(slashWhy),
|
|
162
|
+
hiddenLabel: HIDDEN_LABEL[id]?.slash ?? null,
|
|
163
|
+
},
|
|
164
|
+
project: {
|
|
165
|
+
ok: !projectWhy,
|
|
166
|
+
why: projectWhy,
|
|
167
|
+
file: projectName,
|
|
168
|
+
note: projectName ? PROJECT_NOTE : null,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** 一次算全部(CLI --all / Web 首屏用) */
|
|
174
|
+
export function capsAll(ids, detectMap = {}) {
|
|
175
|
+
return Object.fromEntries(ids.map((id) => [id, capsFor(id, detectMap[id] ?? {})]));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** 至少有一条通道能用吗?全不能用 = 这家只能「原样启动」 */
|
|
179
|
+
export function anyChannel(id, ctx) {
|
|
180
|
+
const c = capsFor(id, ctx);
|
|
181
|
+
return c.shim.ok || c.install.ok || c.slash.ok;
|
|
182
|
+
}
|