agent-purge 0.2.10 → 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.
Files changed (75) hide show
  1. package/CONTRIBUTING.md +64 -0
  2. package/LICENSE +21 -21
  3. package/README.md +169 -138
  4. package/backends/index.mjs +204 -244
  5. package/bench/bench-armor.mjs +844 -832
  6. package/bench/capture-hot.mjs +58 -0
  7. package/bench/capture-provider.mjs +66 -0
  8. package/bench/capture-report.mjs +15 -0
  9. package/bench/capture-server-hot.mjs +55 -0
  10. package/bench/capture-system.mjs +36 -0
  11. package/bench/judge-armor.mjs +119 -119
  12. package/bin/agent-purge.mjs +906 -780
  13. package/docs/LEGAL.md +27 -0
  14. package/docs/architecture.md +39 -0
  15. package/docs/compatibility.md +78 -0
  16. package/docs/images/console-desktop.png +0 -0
  17. package/docs/images/console-mobile.png +0 -0
  18. package/docs/releasing.md +87 -0
  19. package/docs/usage.md +158 -0
  20. package/lib/activation.mjs +38 -0
  21. package/lib/apply.mjs +227 -0
  22. package/lib/armed.mjs +38 -38
  23. package/lib/armor.mjs +225 -222
  24. package/lib/caps.mjs +182 -173
  25. package/lib/capture.mjs +324 -0
  26. package/lib/clean.mjs +124 -0
  27. package/lib/control.mjs +60 -0
  28. package/lib/detect.mjs +74 -70
  29. package/lib/doctor.mjs +7 -7
  30. package/lib/dsh.mjs +495 -453
  31. package/lib/environment.mjs +36 -0
  32. package/lib/evidence.mjs +41 -0
  33. package/lib/extension.mjs +140 -0
  34. package/lib/footprints.mjs +59 -0
  35. package/lib/home.mjs +47 -47
  36. package/lib/launch-conflicts.mjs +24 -0
  37. package/lib/launch.mjs +57 -0
  38. package/lib/opencode.mjs +62 -0
  39. package/lib/paths.mjs +25 -0
  40. package/lib/persist.mjs +439 -280
  41. package/lib/personas.mjs +17 -7
  42. package/lib/private-server.mjs +54 -0
  43. package/lib/project.mjs +134 -0
  44. package/lib/restore.mjs +131 -114
  45. package/lib/rewrite.mjs +124 -0
  46. package/lib/root.mjs +37 -0
  47. package/lib/shim.mjs +203 -160
  48. package/lib/slashes.mjs +227 -628
  49. package/lib/spawn.mjs +182 -126
  50. package/lib/state.mjs +85 -137
  51. package/lib/templates/personas/neutral.md +30 -30
  52. package/lib/update.mjs +137 -0
  53. package/lib/verification.mjs +23 -0
  54. package/lib/window.mjs +58 -0
  55. package/lib/wrappers.mjs +49 -0
  56. package/magpie-plugin/README.md +44 -0
  57. package/matrix.json +325 -341
  58. package/package.json +30 -4
  59. package/web/app.js +525 -0
  60. package/web/assets/agent-purge.ico +0 -0
  61. package/web/assets/claude.ico +0 -0
  62. package/web/assets/codebuddy.png +0 -0
  63. package/web/assets/codex.webp +0 -0
  64. package/web/assets/dsh.ico +0 -0
  65. package/web/assets/grok.ico +0 -0
  66. package/web/assets/hermes.ico +0 -0
  67. package/web/assets/omp.png +0 -0
  68. package/web/assets/opencode.ico +0 -0
  69. package/web/assets/opencode2.ico +0 -0
  70. package/web/assets/pi.png +0 -0
  71. package/web/assets/zcode.png +0 -0
  72. package/web/model.mjs +49 -0
  73. package/web/server.mjs +309 -194
  74. package/web/styles.css +917 -0
  75. package/web/ui.html +64 -1163
@@ -1,244 +1,204 @@
1
- // 每家的 build() 是纯函数:只描述"怎么启动",不 spawn、不写盘。
2
- // setup/cleanup 由入口统一调用,保证能按哈希回滚。
3
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { SESSION as DSH_SESSION, addPlugin, removePlugin, sessionSource } from "../lib/dsh.mjs";
6
- import { opencodePlugins } from "../lib/persist.mjs";
7
-
8
- const inline = (text) => text.replace(/\s+$/, "");
9
-
10
- export const backends = {
11
- // pi:命令行已试过 --system-prompt,但 pi.cmd 走 cmd.exe,10K 人设会撞 8191 上限(实测“命令行太长”)。
12
- // 所以 pi 的注入靠扩展(sys 层、无长度限制、跟 state.json 走),这里只透传。
13
- // 扩展由 launch() 在启动前自动确保安装。
14
- pi: ({ args }) => ({ cmd: "pi", args }),
15
-
16
- codex: ({ personaPath, personaText, mode, args }) => ({
17
- cmd: "codex",
18
- args:
19
- mode === "off"
20
- ? [...args]
21
- : mode === "append"
22
- ? ["-c", `developer_instructions=${inline(personaText)}`, ...args]
23
- : ["-c", `model_instructions_file=${personaPath.replace(/\\/g, "/")}`, ...args],
24
- }),
25
-
26
- // CodeBuddy(腾讯云,npm @tencent-ai/codebuddy-code):原生整段替换,无残留
27
- codebuddy: ({ personaPath, personaText, mode, args }) => ({
28
- cmd: "codebuddy",
29
- args:
30
- mode === "off"
31
- ? [...args]
32
- : mode === "append"
33
- ? ["--append-system-prompt", inline(personaText), ...args]
34
- // replace 走文件:内联 9.6K 会把裸命令(.cmd 包装器)撞上 8191 上限
35
- : ["--system-prompt-file", personaPath, ...args],
36
- }),
37
-
38
- claude: ({ personaPath, mode, args }) => ({
39
- cmd: "claude",
40
- args:
41
- mode === "off"
42
- ? [...args]
43
- : mode === "append"
44
- ? ["--append-system-prompt-file", personaPath, ...args]
45
- : ["--system-prompt-file", personaPath, ...args],
46
- residueHint: mode === "off" ? "" : "不补丁时残留 62 字身份句",
47
- }),
48
-
49
- grok: ({ personaText, mode, args }) => ({
50
- cmd: "grok",
51
- args:
52
- mode === "off"
53
- ? [...args]
54
- : mode === "append"
55
- ? ["--rules", inline(personaText), ...args]
56
- : ["--system-prompt-override", inline(personaText), ...args],
57
- }),
58
-
59
- omp: ({ personaText, mode, args }) =>
60
- mode === "off"
61
- ? { cmd: "omp", args: [...args] }
62
- : {
63
- cmd: "omp",
64
- args: ["--system-prompt", inline(personaText), ...args],
65
- residueHint: "MCP tool routes 仍会追加(10K 量级),关掉 MCP server 可缩",
66
- },
67
-
68
- // opencode 靠 cwd 下 .opencode/plugin/*.ts 发现插件,必须原地 splice
69
- opencode: ({ personaText, mode, cwd, args }) => {
70
- if (mode === "off") return { cmd: "opencode", args: [...args] };
71
- const dir = join(cwd, ".opencode", "plugin");
72
- const file = join(dir, "agent-purge.ts");
73
- const src = `export const AgentPurge = async () => ({
74
- "experimental.chat.system.transform": async (_input, output) => {
75
- ${mode === "append" ? `output.system.push(${JSON.stringify(inline(personaText))});` : `output.system.splice(0, output.system.length, ${JSON.stringify(inline(personaText))});`}
76
- },
77
- });
78
- `;
79
- return {
80
- cmd: "opencode",
81
- note: `[plugin] .opencode/plugin/agent-purge.ts(${mode === "append" ? "push" : "原地 splice"} output.system)`,
82
- args,
83
- setup: () => {
84
- mkdirSync(dir, { recursive: true });
85
- writeFileSync(file, src);
86
- },
87
- cleanup: () => rmSync(file, { force: true }),
88
- };
89
- },
90
-
91
- // dsh(DeepSeek Harness):CLI 只能起 headless(desktop 被 Electron 独占)。
92
- // 一次性注入用 agent-purge-session 这个独立插件名,不会覆盖常驻的 agent-purge。
93
- dsh: ({ personaText, mode, args }) => {
94
- if (mode === "off") return { cmd: "dsh", args: ["--profile", "headless", ...args] };
95
- return {
96
- cmd: "dsh",
97
- args: ["--profile", "headless", ...args],
98
- note: `[plugin] ${DSH_SESSION}(三个 profile;patch: - insert)`,
99
- residueHint: "desktop profile 由 Electron 独占,一次性注入只对本次 CLI 进程有效",
100
- setup: () => addPlugin(DSH_SESSION, sessionSource(personaText, mode)),
101
- cleanup: () => removePlugin(DSH_SESSION),
102
- };
103
- },
104
-
105
- // opencode v2(@opencode/cli,bin 名 opencode2):新插件 API,V1 的
106
- // experimental.chat.system.transform 在 2.x 已废。插件放全局 plugins 目录。
107
- opencode2: ({ personaText, mode, args }) => {
108
- if (mode === "off") return { cmd: "opencode2", args: [...args] };
109
- const dir = opencodePlugins();
110
- // 与 V1 常驻插件(agent-purge.ts)分开,否则一次性注入会顶掉常驻接管
111
- const file = join(dir, "agent-purge-v2-session.ts");
112
- const persona = inline(personaText);
113
- const mutate =
114
- mode === "append"
115
- ? `event.system.push({ type: "text", text: PERSONA });`
116
- : `event.system.splice(0, event.system.length, { type: "text", text: PERSONA });`;
117
- const src = `// 由 agent-purge 生成;删除本文件即恢复宿主人设。
118
- const PERSONA = ${JSON.stringify(persona)};
119
-
120
- export default {
121
- id: "agent-purge-v2-session",
122
- async setup(ctx) {
123
- await ctx.session.hook("context", (event) => {
124
- ${mutate}
125
- });
126
- },
127
- };
128
- `;
129
- const write = () => {
130
- mkdirSync(dir, { recursive: true });
131
- writeFileSync(file, src);
132
- };
133
- return {
134
- cmd: "opencode2",
135
- args,
136
- note: `[plugin] ${file.replace(/\\/g, "/")}(ctx.session.hook("context") 改 event.system)`,
137
- setup: write,
138
- cleanup: () => rmSync(file, { force: true }),
139
- };
140
- },
141
-
142
- // 需要落盘或调用方配合,CLI 不代劳,只给可复制的配方
143
- zcode: () => ({
144
- manual: [
145
- "CLI 拿不到 customSystemPrompt(只有 app-server/SDK 能传);CLI 只有 UserPromptSubmit hook 注入,且落在 user 角色。",
146
- "注入配方: ~/.zcode/cli/config.json 里 hooks.enabled=true + UserPromptSubmit hook 返回 additionalContext。",
147
- "归零配方: 等长替换 resources/glm/zcode.cjs 里的 IJs(42 字),但 cli_prefix 段仍留 42 字。",
148
- ],
149
- }),
150
-
151
- hermes: () => ({
152
- manual: [
153
- "hermes 从 $HERMES_HOME/SOUL.md 取身份段。",
154
- "配方: 复制现有 HERMES_HOME 到 .sandbox/hermes-home,覆盖 SOUL.md,再 HERMES_HOME=<该目录> 启动。",
155
- "必须加 --ignore-rules: Hermes 自带的 rules 层会把人设顶回去(实测不加时它自称 Hermes 并明说“这一条我不照办”,加了立刻完全采纳)。",
156
- "注意首次启动会自装 Python 依赖(约 80 秒)。",
157
- ],
158
- }),
159
-
160
- antigravity: () => ({
161
- manual: [
162
- "prompt 在 resources/bin/language_server.exe(Go embed.FS)。",
163
- "闭源 App 的身份句在二进制里,只能等长替换;改动会使签名失效、更新被覆盖,所以默认不做。",
164
- ],
165
- }),
166
-
167
- qoder: ({ reason }) => ({ unsupported: reason }),
168
- workbuddy: ({ reason }) => ({ unsupported: reason }),
169
- };
170
-
171
-
172
-
173
-
174
- // 一次性问答(headless)规格:Web UI 与 `agent-purge <h> --ask` 共用。
175
- // 只收录实测过参数的 harness,没验过的不猜。
176
- export const oneshots = {
177
- pi: ({ prompt }) => ({
178
- cmd: "pi",
179
- args: ["-p", prompt],
180
- note: "注入靠扩展(sys 层);人设走 state.json,不占命令行",
181
- }),
182
- claude: ({ prompt, personaPath, mode }) => ({
183
- cmd: "claude",
184
- args: ["-p", prompt, ...(mode === "off" ? [] : [mode === "append" ? "--append-system-prompt-file" : "--system-prompt-file", personaPath])],
185
- }),
186
- grok: ({ prompt, personaText, mode }) => ({
187
- cmd: "grok",
188
- args: ["-p", prompt, "--always-approve", ...(mode === "off" ? [] : [mode === "append" ? "--rules" : "--system-prompt-override", inline(personaText)])],
189
- }),
190
- omp: ({ prompt, personaText, mode }) => ({
191
- cmd: "omp",
192
- args: ["-p", ...(mode === "off" ? [] : ["--system-prompt", inline(personaText)]), prompt],
193
- }),
194
- codex: ({ prompt, personaPath, personaText, mode }) => ({
195
- cmd: "codex",
196
- args: [
197
- ...(mode === "off"
198
- ? []
199
- : ["-c", mode === "append" ? `developer_instructions=${inline(personaText)}` : `model_instructions_file=${personaPath.replace(/\\/g, "/")}`]),
200
- "exec",
201
- prompt,
202
- ],
203
- }),
204
- opencode: ({ prompt, personaText, mode, cwd }) => {
205
- if (mode === "off") return { cmd: "opencode", args: ["run", prompt] };
206
- const b = backends.opencode({ personaText, mode, cwd, args: ["run", prompt] });
207
- return b;
208
- },
209
- opencode2: ({ prompt, personaText, mode }) => {
210
- if (mode === "off") return { cmd: "opencode2", args: ["run", prompt] };
211
- const b = backends.opencode2({ personaText, mode, args: ["run", prompt] });
212
- return b;
213
- },
214
- dsh: ({ prompt, personaText, mode }) => {
215
- if (mode === "off") return { cmd: "dsh", args: ["--profile", "headless", prompt] };
216
- // backends.dsh 会自己补 --profile headless,这里只给 prompt(否则重复)
217
- return backends.dsh({ personaText, mode, args: [prompt] });
218
- },
219
- zcode: ({ prompt }) => ({
220
- cmd: "zcode",
221
- args: ["--prompt", prompt, "--mode", "yolo", "--output-format", "stream-json", "--no-color"],
222
- note: "persona 对 zcode CLI 不生效:customSystemPrompt 只有 app-server/SDK 能传",
223
- }),
224
- codebuddy: ({ prompt, personaPath, personaText, mode }) => ({
225
- cmd: "codebuddy",
226
- args: [
227
- "-p",
228
- prompt,
229
- "--max-turns",
230
- "5",
231
- ...(mode === "off"
232
- ? []
233
- : mode === "append"
234
- ? ["--append-system-prompt", inline(personaText)]
235
- : ["--system-prompt-file", personaPath]),
236
- ],
237
- }),
238
- hermes: ({ prompt, mode }) => ({
239
- cmd: "hermes",
240
- // --ignore-rules 是关键:不加时 Hermes 自带 rules 层会把人设顶回去(模型自称 Hermes、明说“不照办”)。
241
- args: ["-z", prompt, ...(mode === "off" ? [] : ["--ignore-rules"])],
242
- note: "身份段来自 $HERMES_HOME/SOUL.md;注入时必须带 --ignore-rules,关电源时不带",
243
- }),
244
- };
1
+ // 每家的 build() 是纯函数:只描述"怎么启动",不 spawn、不写盘。
2
+ // setup/cleanup 由入口统一调用,保证能按哈希回滚。
3
+ import { mkdirSync, readFileSync, existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { SESSION as DSH_SESSION, addPlugin, sessionSource } from "../lib/dsh.mjs";
6
+ import { opencodePlugins, targets } from "../lib/persist.mjs";
7
+ import { opencodeSource } from "../lib/opencode.mjs";
8
+ import { dataHome } from "../lib/home.mjs";
9
+ import { swapFile } from "../lib/restore.mjs";
10
+
11
+ function temporaryPlugin(harness, file, root, args) {
12
+ let undo = null;
13
+ return { cmd: harness, args, runtime: true,
14
+ setup: () => { mkdirSync(join(file, ".."), { recursive: true }); if (!undo) undo = swapFile(file, opencodeSource(root, harness, { temporary: true })); },
15
+ cleanup: () => { if (undo) { undo(); undo = null; } },
16
+ };
17
+ }
18
+
19
+ const inline = (text) => text.replace(/\s+$/, "");
20
+ // codex 的 -c 值按 TOML 解析:整段人设必须包成 basic string,否则空格/引号/换行会解析失败。
21
+ const tomlStr = (value) => JSON.stringify(String(value));
22
+
23
+ export const backends = {
24
+ // pi:命令行已试过 --system-prompt,但 pi.cmd 走 cmd.exe,10K 人设会撞 8191 上限(实测“命令行太长”)。
25
+ // 所以 pi 的注入靠扩展(sys 层、无长度限制、跟 state.json 走),这里只透传。
26
+ // 扩展由 launch() 在启动前自动确保安装。
27
+ pi: ({ args }) => ({ cmd: "pi", args }),
28
+
29
+ codex: ({ personaPath, personaText, mode, args }) => ({
30
+ cmd: "codex",
31
+ args:
32
+ mode === "off"
33
+ ? [...args]
34
+ : mode === "append"
35
+ ? ["-c", `developer_instructions=${tomlStr(inline(personaText))}`, ...args]
36
+ : ["-c", `model_instructions_file=${tomlStr(personaPath.replace(/\\/g, "/"))}`, ...args],
37
+ }),
38
+
39
+ // CodeBuddy(腾讯云,npm @tencent-ai/codebuddy-code):原生整段替换,无残留
40
+ codebuddy: ({ personaPath, personaText, mode, args }) => ({
41
+ cmd: "codebuddy",
42
+ args:
43
+ mode === "off"
44
+ ? [...args]
45
+ : mode === "append"
46
+ ? ["--append-system-prompt", inline(personaText), ...args]
47
+ // replace 走文件:内联 9.6K 会把裸命令(.cmd 包装器)撞上 8191 上限
48
+ : ["--system-prompt-file", personaPath, ...args],
49
+ }),
50
+
51
+ claude: ({ personaPath, mode, args }) => ({
52
+ cmd: "claude",
53
+ args:
54
+ mode === "off"
55
+ ? [...args]
56
+ : mode === "append"
57
+ ? ["--append-system-prompt-file", personaPath, ...args]
58
+ : ["--system-prompt-file", personaPath, ...args],
59
+ residueHint: mode === "off" ? "" : "不补丁时残留 62 字身份句",
60
+ }),
61
+
62
+ grok: ({ personaText, mode, args }) => ({
63
+ cmd: "grok",
64
+ args:
65
+ mode === "off"
66
+ ? [...args]
67
+ : mode === "append"
68
+ ? ["--rules", inline(personaText), ...args]
69
+ : ["--system-prompt-override", inline(personaText), ...args],
70
+ }),
71
+
72
+ omp: ({ personaText, mode, args }) =>
73
+ mode === "off"
74
+ ? { cmd: "omp", args: [...args] }
75
+ : {
76
+ cmd: "omp",
77
+ args: [mode === "append" ? "--append-system-prompt" : "--system-prompt", inline(personaText), ...args],
78
+ residueHint: "MCP tool routes 仍会追加(10K 量级),关掉 MCP server 可缩",
79
+ },
80
+
81
+ // 临时插件与常驻插件不共享许可;人设正文不编译进孤儿文件。
82
+ opencode: ({ mode, cwd, args, root = dataHome() }) => {
83
+ if (mode === "off") return { cmd: "opencode", args: [...args] };
84
+ return temporaryPlugin("opencode", join(cwd, ".opencode", "plugin", "agent-purge.ts"), root, args);
85
+ },
86
+ dsh: ({ mode, args, root = dataHome() }) => {
87
+ if (mode === "off") return { cmd: "dsh", args: ["--profile", "headless", ...args] };
88
+ let temporary = null;
89
+ return { cmd: "dsh", args: ["--profile", "headless", ...args], runtime: true,
90
+ setup: () => { if (!temporary) temporary = addPlugin(DSH_SESSION, sessionSource("", mode, root), { temporary: true }); },
91
+ cleanup: () => { if (temporary) { temporary.cleanup(); temporary = null; } } };
92
+ },
93
+ opencode2: ({ mode, args, root = dataHome() }) => {
94
+ if (mode === "off") return { cmd: "opencode2", args: [...args], privateServer: true };
95
+ return { ...temporaryPlugin("opencode2", join(opencodePlugins(), "agent-purge-v2-session.ts"), root, args), privateServer: true };
96
+ },
97
+
98
+ // 需要落盘或调用方配合,CLI 不代劳,只给可复制的配方
99
+ zcode: () => ({
100
+ manual: [
101
+ "CLI 拿不到 customSystemPrompt(只有 app-server/SDK 能传);CLI 只有 UserPromptSubmit hook 注入,且落在 user 角色。",
102
+ "注入配方: ~/.zcode/cli/config.json 里 hooks.enabled=true + UserPromptSubmit hook 返回 additionalContext。",
103
+ "归零配方: 等长替换 resources/glm/zcode.cjs 里的 IJs(42 字),但 cli_prefix 段仍留 42 字。",
104
+ ],
105
+ }),
106
+
107
+ // 交互式:SOUL.md 是唯一身份入口。启动前写入、退出还原(在 UI 里接管着就一直留着)。
108
+ // 注意它只在会话首次/重建时读 SOUL.md:续会话从 session DB 还原 system 提示词,改文件不当场生效
109
+ // (源码 agent/conversation_loop.py::_restore_or_build_system_prompt)。
110
+ hermes: ({ personaText, personaPath, mode, args = [] }) => {
111
+ let undo = null;
112
+ return {
113
+ cmd: "hermes",
114
+ args: [...(mode === "off" ? [] : ["--ignore-rules"]), ...args],
115
+ note: "SOUL.md 仅供新会话读取;退出后恢复启动前的原字节,不更改常驻选择",
116
+ setup: () => {
117
+ if (mode === "off" || undo) return;
118
+ const file = targets.hermes.file();
119
+ mkdirSync(join(file, ".."), { recursive: true });
120
+ const original = existsSync(file) ? readFileSync(file, "utf8") : "";
121
+ const body = personaText ?? readFileSync(personaPath, "utf8");
122
+ undo = swapFile(file, mode === "append" ? original + "\n\n" + body : body);
123
+ },
124
+ cleanup: () => { if (undo) { undo(); undo = null; } },
125
+ };
126
+ },
127
+
128
+ antigravity: () => ({
129
+ manual: [
130
+ "prompt 在 resources/bin/language_server.exe(Go embed.FS)。",
131
+ "闭源 App 的身份句在二进制里,只能等长替换;改动会使签名失效、更新被覆盖,所以默认不做。",
132
+ ],
133
+ }),
134
+ };
135
+
136
+
137
+
138
+
139
+ // 一次性问答(headless)规格:Web UI 与 `agent-purge <h> --ask` 共用。
140
+ // 只收录实测过参数的 harness,没验过的不猜。
141
+ export const oneshots = {
142
+ pi: ({ prompt }) => ({
143
+ cmd: "pi",
144
+ args: ["-p", prompt],
145
+ note: "注入靠扩展(sys 层);人设走 state.json,不占命令行",
146
+ }),
147
+ claude: ({ prompt, personaPath, mode }) => ({
148
+ cmd: "claude",
149
+ args: ["-p", prompt, ...(mode === "off" ? [] : [mode === "append" ? "--append-system-prompt-file" : "--system-prompt-file", personaPath])],
150
+ }),
151
+ grok: ({ prompt, personaText, mode }) => ({
152
+ cmd: "grok",
153
+ args: ["-p", prompt, "--always-approve", ...(mode === "off" ? [] : [mode === "append" ? "--rules" : "--system-prompt-override", inline(personaText)])],
154
+ }),
155
+ omp: ({ prompt, personaText, mode }) => ({
156
+ cmd: "omp",
157
+ args: ["-p", ...(mode === "off" ? [] : [mode === "append" ? "--append-system-prompt" : "--system-prompt", inline(personaText)]), prompt],
158
+ }),
159
+ codex: ({ prompt, personaPath, personaText, mode }) => ({
160
+ cmd: "codex",
161
+ args: [
162
+ ...(mode === "off"
163
+ ? []
164
+ : ["-c", mode === "append" ? `developer_instructions=${tomlStr(inline(personaText))}` : `model_instructions_file=${tomlStr(personaPath.replace(/\\/g, "/"))}`]),
165
+ "exec",
166
+ prompt,
167
+ ],
168
+ }),
169
+ opencode: ({ prompt, personaText, mode, cwd, root }) => {
170
+ if (mode === "off") return { cmd: "opencode", args: ["run", prompt] };
171
+ const b = backends.opencode({ personaText, mode, cwd, root, args: ["run", prompt] });
172
+ return b;
173
+ },
174
+ opencode2: ({ prompt, personaText, mode, root }) => {
175
+ if (mode === "off") return { cmd: "opencode2", args: ["run", prompt], privateServer: true };
176
+ const b = backends.opencode2({ personaText, mode, root, args: ["run", prompt] });
177
+ return b;
178
+ },
179
+ dsh: ({ prompt, personaText, mode, root }) => {
180
+ if (mode === "off") return { cmd: "dsh", args: ["--profile", "headless", prompt] };
181
+ // backends.dsh 会自己补 --profile headless,这里只给 prompt(否则重复)
182
+ return backends.dsh({ personaText, mode, root, args: [prompt] });
183
+ },
184
+ zcode: ({ prompt }) => ({
185
+ cmd: "zcode",
186
+ args: ["--prompt", prompt, "--mode", "yolo", "--output-format", "stream-json", "--no-color"],
187
+ note: "persona 对 zcode CLI 不生效:customSystemPrompt 只有 app-server/SDK 能传",
188
+ }),
189
+ codebuddy: ({ prompt, personaPath, personaText, mode }) => ({
190
+ cmd: "codebuddy",
191
+ args: [
192
+ "-p",
193
+ prompt,
194
+ "--max-turns",
195
+ "5",
196
+ ...(mode === "off"
197
+ ? []
198
+ : mode === "append"
199
+ ? ["--append-system-prompt", inline(personaText)]
200
+ : ["--system-prompt-file", personaPath]),
201
+ ],
202
+ }),
203
+ hermes: ({ prompt, personaText, personaPath, mode }) => backends.hermes({ personaText, personaPath, mode, args: ["-z", prompt] }),
204
+ };