pi-web-ui 0.68.2 → 0.70.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/dist/server/agent-service.js +170 -54
- package/dist/server/attachments.js +7 -2
- package/dist/server/client-state.js +71 -17
- package/dist/server/dsh/dsh-agent-service.js +142 -38
- package/dist/server/dsh/dsh-client.js +9 -8
- package/dist/server/dsh/dsh-sessions.js +4 -3
- package/dist/server/edit-soft-tool.js +33 -26
- package/dist/server/files-service.js +12 -7
- package/dist/server/goal-service.js +85 -24
- package/dist/server/i18n.js +157 -0
- package/dist/server/index.js +77 -18
- package/dist/server/locales.js +55 -1
- package/dist/server/managed.js +61 -0
- package/dist/server/marker-service.js +37 -20
- package/dist/server/markers/builtins/notify.js +19 -6
- package/dist/server/markers/builtins/rename.js +41 -8
- package/dist/server/markers/builtins/todo.js +107 -30
- package/dist/server/markers/registry.js +2 -2
- package/dist/server/mcp-bridge.js +3 -1
- package/dist/server/model-admin.js +25 -14
- package/dist/server/plugin-catalog.js +7 -3
- package/dist/server/plugin-updater.js +6 -2
- package/dist/server/plugins.js +40 -17
- package/dist/server/prompt-composer.js +42 -16
- package/dist/server/protocol-version.js +1 -1
- package/dist/server/scm.js +18 -25
- package/dist/server/serialize.js +1 -0
- package/dist/server/settings-service.js +21 -1
- package/dist/server/subagent-templates.js +105 -0
- package/dist/server/subagents.js +164 -56
- package/dist/server/tabs.js +87 -0
- package/dist/server/terminals.js +99 -48
- package/dist/server/update-check.js +7 -2
- package/dist/server/vision-bridge.js +34 -12
- package/dist/server/webui-context.js +9 -0
- package/package.json +2 -1
- package/themes/cyberpunk.css +88 -81
- package/themes/dazzle.css +88 -81
- package/themes/md-preview.css +105 -98
- package/themes/white.css +167 -160
- package/web/dist/assets/{TerminalPanel-BQ5NTB9Y.js → TerminalPanel-UxgczH4c.js} +1 -1
- package/web/dist/assets/index-BH5QutUc.css +10 -0
- package/web/dist/assets/index-CaiIOwy7.js +335 -0
- package/web/dist/assets/{markdown-DOsihKaR.js → markdown-Cpo0pNcR.js} +1 -1
- package/web/dist/assets/{react-DIP6JKYk.js → react-CtudoG1_.js} +1 -1
- package/web/dist/index.html +4 -4
- package/web/dist/assets/index-C_I-6Zul.css +0 -10
- package/web/dist/assets/index-Ck5pa3XK.js +0 -333
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* 本模块是纯函数(不 import SDK / node),浏览器端可复用(SettingsModal 需要
|
|
18
18
|
* DEFAULT_PROMPT_TEMPLATE 与 token 元数据)。
|
|
19
19
|
*/
|
|
20
|
+
import { getServerBlock, pick } from "./i18n.js";
|
|
20
21
|
/** 全部来源 token。默认模板顺序即此数组顺序。 */
|
|
21
22
|
export const PROMPT_TOKENS = [
|
|
22
23
|
"soul", // 内置灵魂提示词(persona;有 SYSTEM.md 时其内容)
|
|
@@ -78,8 +79,25 @@ export function overrideOf(overrides, token) {
|
|
|
78
79
|
}
|
|
79
80
|
/** 内置默认灵魂段落(buildSystemPrompt 默认分支的开头,与 SDK 同步维护)。 */
|
|
80
81
|
export const BUILTIN_SOUL = "You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.";
|
|
82
|
+
/**
|
|
83
|
+
* 内置默认灵魂段落的中文版(issue #91):lang === "zh" 且调用方未自定义
|
|
84
|
+
* builtinSoul 时 {{soul}} 用它。英文版 BUILTIN_SOUL 保持原样(英文默认)。
|
|
85
|
+
*/
|
|
86
|
+
export const BUILTIN_SOUL_ZH = "你是运行在 pi(一个编码智能体框架)中的专业编码助手。你通过读取文件、执行命令、编辑代码和新建文件来帮助用户。";
|
|
81
87
|
/** Pi documentation 段模板 —— 与 SDK buildSystemPrompt 默认分支一致(路径由调用方注入)。 */
|
|
82
|
-
export function buildPiDocsText(readme, docs, examples) {
|
|
88
|
+
export function buildPiDocsText(readme, docs, examples, lang = "en") {
|
|
89
|
+
if (lang === "zh") {
|
|
90
|
+
return [
|
|
91
|
+
"Pi 文档(仅当用户问及 pi 本身、其 SDK、扩展、主题、技能或 TUI 时阅读):",
|
|
92
|
+
`- 主文档:${readme}`,
|
|
93
|
+
`- 更多文档:${docs}`,
|
|
94
|
+
`- 示例:${examples}(扩展、自定义工具、SDK)`,
|
|
95
|
+
"- 阅读 pi 文档或示例时,在「更多文档」下找 docs/...、在「示例」下找 examples/...,不要按当前工作目录解析",
|
|
96
|
+
"- 被问及以下主题时:扩展(docs/extensions.md、examples/extensions/)、主题(docs/themes.md)、技能(docs/skills.md)、提示词模板(docs/prompt-templates.md)、TUI 组件(docs/tui.md)、快捷键(docs/keybindings.md)、SDK 集成(docs/sdk.md)、自定义服务商(docs/custom-provider.md)、添加模型(docs/models.md)、pi 包(docs/packages.md)、环境变量(docs/environment-variables.md)",
|
|
97
|
+
"- 处理 pi 相关主题时,先阅读文档和示例,并跟随其中的 .md 交叉引用,再动手实现",
|
|
98
|
+
"- pi 的 .md 文件务必通读全文,并跟随其中指向相关文档的链接(例如 TUI API 细节见 tui.md)",
|
|
99
|
+
].join("\n");
|
|
100
|
+
}
|
|
83
101
|
return [
|
|
84
102
|
"Pi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):",
|
|
85
103
|
`- Main documentation: ${readme}`,
|
|
@@ -94,6 +112,7 @@ export function buildPiDocsText(readme, docs, examples) {
|
|
|
94
112
|
/** Guidelines 段:文件探索引导 + 工具 promptGuidelines(去重)+ 固定两行。
|
|
95
113
|
* 与 buildSystemPrompt 默认分支的聚合规则一致。 */
|
|
96
114
|
function buildGuidelinesText(inputs) {
|
|
115
|
+
const lang = inputs.lang ?? "en";
|
|
97
116
|
const selected = new Set(inputs.selectedTools);
|
|
98
117
|
const lines = [];
|
|
99
118
|
const add = (g) => {
|
|
@@ -104,14 +123,14 @@ function buildGuidelinesText(inputs) {
|
|
|
104
123
|
const has = (n) => selected.has(n);
|
|
105
124
|
if ((has("bash") || has("powershell")) && !has("grep") && !has("find") && !has("ls")) {
|
|
106
125
|
add(has("bash") && has("powershell")
|
|
107
|
-
? "Use bash or PowerShell for file operations like listing, searching, and finding files"
|
|
108
|
-
: "Use bash for file operations like ls, rg, find");
|
|
126
|
+
? pick(lang, "涉及列出、搜索、查找文件等文件操作时,使用 bash 或 PowerShell", "Use bash or PowerShell for file operations like listing, searching, and finding files", "prompt.guidelines.bash.powershell")
|
|
127
|
+
: pick(lang, "涉及 ls、rg、find 等文件操作时,使用 bash", "Use bash for file operations like ls, rg, find", "prompt.guidelines.bash.basic"));
|
|
109
128
|
}
|
|
110
129
|
for (const g of inputs.toolGuidelines)
|
|
111
130
|
add(g);
|
|
112
|
-
add("Be concise in your responses");
|
|
113
|
-
add("Show file paths clearly when working with files");
|
|
114
|
-
return
|
|
131
|
+
add(pick(lang, "回答要简洁", "Be concise in your responses", "prompt.guidelines.be.concise"));
|
|
132
|
+
add(pick(lang, "处理文件时清楚地给出文件路径", "Show file paths clearly when working with files", "prompt.guidelines.show.paths"));
|
|
133
|
+
return `${pick(lang, "指导原则:", "Guidelines:", "prompt.guidelines.title")}\n${lines.map((l) => `- ${l}`).join("\n")}`;
|
|
115
134
|
}
|
|
116
135
|
function escapeXml(s) {
|
|
117
136
|
return s
|
|
@@ -122,14 +141,14 @@ function escapeXml(s) {
|
|
|
122
141
|
.replace(/'/g, "'");
|
|
123
142
|
}
|
|
124
143
|
/** 技能段文本(不含前导空行)。与 SDK formatSkillsForPrompt 一致。 */
|
|
125
|
-
export function buildSkillsText(skills) {
|
|
144
|
+
export function buildSkillsText(skills, lang = "en") {
|
|
126
145
|
const visible = skills.filter((s) => !s.disableModelInvocation);
|
|
127
146
|
if (visible.length === 0)
|
|
128
147
|
return "";
|
|
129
148
|
const lines = [
|
|
130
|
-
"The following skills provide specialized instructions for specific tasks.",
|
|
131
|
-
"Use the read tool to load a skill's file when the task matches its description.",
|
|
132
|
-
"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.",
|
|
149
|
+
pick(lang, "以下技能为特定任务提供专门的指令。", "The following skills provide specialized instructions for specific tasks.", "prompt.skills.intro.specialized"),
|
|
150
|
+
pick(lang, "当任务与某技能的描述相符时,用 read 工具加载该技能文件。", "Use the read tool to load a skill's file when the task matches its description.", "prompt.skills.intro.use.read"),
|
|
151
|
+
pick(lang, "当技能文件引用相对路径时,以技能目录(SKILL.md 的父目录 / 该路径的 dirname)为基准解析,并在工具命令中使用解析后的绝对路径。", "When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.", "prompt.skills.intro.resolve.path"),
|
|
133
152
|
"",
|
|
134
153
|
"<available_skills>",
|
|
135
154
|
];
|
|
@@ -144,13 +163,13 @@ export function buildSkillsText(skills) {
|
|
|
144
163
|
return lines.join("\n");
|
|
145
164
|
}
|
|
146
165
|
/** 项目上下文块(不含前导空行)。 */
|
|
147
|
-
function buildContextText(files) {
|
|
166
|
+
function buildContextText(files, lang = "en") {
|
|
148
167
|
if (files.length === 0)
|
|
149
168
|
return "";
|
|
150
169
|
return [
|
|
151
170
|
"<project_context>",
|
|
152
171
|
"",
|
|
153
|
-
"Project-specific instructions and guidelines:",
|
|
172
|
+
pick(lang, "项目专属指令与规范:", "Project-specific instructions and guidelines:", "prompt.context.title"),
|
|
154
173
|
"",
|
|
155
174
|
...files.map((f) => `<project_instructions path="${f.path}">\n${f.content}\n</project_instructions>`),
|
|
156
175
|
"",
|
|
@@ -184,17 +203,24 @@ export function buildToolsSchemaText(tools) {
|
|
|
184
203
|
/** 计算每个 token 的自动内容(无覆盖时的展开值)。 */
|
|
185
204
|
export function resolveSectionTexts(inputs) {
|
|
186
205
|
const cwd = inputs.cwd.replace(/\\/g, "/");
|
|
206
|
+
const lang = inputs.lang ?? "en";
|
|
207
|
+
// soul:用户 SYSTEM.md 优先;无则按 lang 选内置默认(调用方自定义的 builtinSoul 原样保留)。
|
|
208
|
+
let soul = inputs.systemPromptFile?.trim() ? inputs.systemPromptFile : inputs.builtinSoul;
|
|
209
|
+
if (lang === "zh" && soul === BUILTIN_SOUL)
|
|
210
|
+
soul = BUILTIN_SOUL_ZH;
|
|
211
|
+
else if (soul === BUILTIN_SOUL)
|
|
212
|
+
soul = getServerBlock(lang, "prompt.soul", BUILTIN_SOUL_ZH.split("\n"), BUILTIN_SOUL.split("\n")).join("\n");
|
|
187
213
|
return {
|
|
188
|
-
soul
|
|
214
|
+
soul,
|
|
189
215
|
tools: buildToolsText(inputs),
|
|
190
216
|
guidelines: buildGuidelinesText(inputs),
|
|
191
|
-
pi_docs: buildPiDocsText(inputs.piReadme, inputs.piDocs, inputs.piExamples),
|
|
217
|
+
pi_docs: buildPiDocsText(inputs.piReadme, inputs.piDocs, inputs.piExamples, lang),
|
|
192
218
|
append: inputs.appendFiles.join("\n\n"),
|
|
193
219
|
persona: inputs.windowsPersona,
|
|
194
220
|
terminal: inputs.terminalGuidance,
|
|
195
221
|
markers: inputs.markersGuidance,
|
|
196
|
-
context: buildContextText(inputs.contextFiles),
|
|
197
|
-
skills: buildSkillsText(inputs.skills),
|
|
222
|
+
context: buildContextText(inputs.contextFiles, lang),
|
|
223
|
+
skills: buildSkillsText(inputs.skills, lang),
|
|
198
224
|
cwd: `Current working directory: ${cwd}`,
|
|
199
225
|
};
|
|
200
226
|
}
|
package/dist/server/scm.js
CHANGED
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { execFile } from "node:child_process";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
|
+
import { pick } from "./i18n.js";
|
|
11
12
|
const exec = promisify(execFile);
|
|
12
13
|
/** Per-command timeout / output cap — a stuck repo must not hang the panel. */
|
|
13
14
|
const GIT_TIMEOUT_MS = 15_000;
|
|
14
15
|
const MAX_GIT_OUTPUT = 16 * 1024 * 1024;
|
|
15
16
|
/** Run one git command; throws Error with a readable message on failure. */
|
|
16
|
-
async function git(cwd, args) {
|
|
17
|
+
async function git(cwd, args, lang) {
|
|
18
|
+
const l = lang?.() ?? "en";
|
|
17
19
|
try {
|
|
18
20
|
const { stdout } = await exec("git", ["-c", "core.quotepath=false", ...args], {
|
|
19
21
|
cwd,
|
|
@@ -26,11 +28,11 @@ async function git(cwd, args) {
|
|
|
26
28
|
catch (err) {
|
|
27
29
|
const e = err;
|
|
28
30
|
if (e.code === "ENOENT")
|
|
29
|
-
throw new Error("未找到 git 命令——请确认已安装 Git 并在 PATH 中");
|
|
31
|
+
throw new Error(pick(l, "未找到 git 命令——请确认已安装 Git 并在 PATH 中", "git command not found — make sure Git is installed and on PATH", "scm.git.not.found"));
|
|
30
32
|
if (e.killed)
|
|
31
|
-
throw new Error("git 命令超时");
|
|
33
|
+
throw new Error(pick(l, "git 命令超时", "git command timed out", "scm.git.timeout"));
|
|
32
34
|
const detail = (e.stderr ?? e.message ?? "").trim().split("\n")[0];
|
|
33
|
-
throw new Error(detail || "git 命令失败");
|
|
35
|
+
throw new Error(detail || pick(l, "git 命令失败", "git command failed", "scm.git.failed"));
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
/** Absolute path of the repo's git dir (handles worktrees/submodules), or
|
|
@@ -244,16 +246,16 @@ const HISTORY_ARGS = [
|
|
|
244
246
|
];
|
|
245
247
|
/** Commit graph for the history tab — fetched lazily so the common
|
|
246
248
|
* "changes" view never pays for it on huge repos. */
|
|
247
|
-
export async function scmHistory(cwd) {
|
|
248
|
-
return parseCommitHistory(await git(cwd, HISTORY_ARGS));
|
|
249
|
+
export async function scmHistory(cwd, lang) {
|
|
250
|
+
return parseCommitHistory(await git(cwd, HISTORY_ARGS, lang));
|
|
249
251
|
}
|
|
250
252
|
/** Status refresh payload (status + branches + numstat) — parallel. */
|
|
251
|
-
export async function scmStatus(cwd) {
|
|
253
|
+
export async function scmStatus(cwd, lang) {
|
|
252
254
|
const [statusText, branchText, statText, cachedStatText] = await Promise.all([
|
|
253
|
-
git(cwd, ["status", "--porcelain=v1", "-b", "--find-renames"]),
|
|
254
|
-
git(cwd, ["for-each-ref", "refs/heads", "refs/remotes", "--format=%(refname)%09%(HEAD)"]),
|
|
255
|
-
git(cwd, ["diff", "--numstat"]),
|
|
256
|
-
git(cwd, ["diff", "--cached", "--numstat"]),
|
|
255
|
+
git(cwd, ["status", "--porcelain=v1", "-b", "--find-renames"], lang),
|
|
256
|
+
git(cwd, ["for-each-ref", "refs/heads", "refs/remotes", "--format=%(refname)%09%(HEAD)"], lang),
|
|
257
|
+
git(cwd, ["diff", "--numstat"], lang),
|
|
258
|
+
git(cwd, ["diff", "--cached", "--numstat"], lang),
|
|
257
259
|
]);
|
|
258
260
|
const header = parseStatusHeader(statusText
|
|
259
261
|
.split("\n")
|
|
@@ -282,23 +284,14 @@ export async function scmStatus(cwd) {
|
|
|
282
284
|
};
|
|
283
285
|
}
|
|
284
286
|
/** Staged + worktree diffs for one file (empty strings when no diff). */
|
|
285
|
-
export async function scmFileDiff(cwd, path) {
|
|
287
|
+
export async function scmFileDiff(cwd, path, lang) {
|
|
286
288
|
const [staged, worktree] = await Promise.all([
|
|
287
|
-
git(cwd, ["diff", "--cached", "--no-color", "--no-ext-diff", "--", path]).catch(() => ""),
|
|
288
|
-
git(cwd, ["diff", "--no-color", "--no-ext-diff", "--", path]).catch(() => ""),
|
|
289
|
+
git(cwd, ["diff", "--cached", "--no-color", "--no-ext-diff", "--", path], lang).catch(() => ""),
|
|
290
|
+
git(cwd, ["diff", "--no-color", "--no-ext-diff", "--", path], lang).catch(() => ""),
|
|
289
291
|
]);
|
|
290
292
|
return { staged, worktree };
|
|
291
293
|
}
|
|
292
294
|
/** Full patch of one commit (`git show`). */
|
|
293
|
-
export async function scmCommitDetail(cwd, hash) {
|
|
294
|
-
return git(cwd, [
|
|
295
|
-
"show",
|
|
296
|
-
"--no-color",
|
|
297
|
-
"--no-ext-diff",
|
|
298
|
-
"--find-renames",
|
|
299
|
-
"--format=fuller",
|
|
300
|
-
"--stat",
|
|
301
|
-
"--patch",
|
|
302
|
-
hash,
|
|
303
|
-
]);
|
|
295
|
+
export async function scmCommitDetail(cwd, hash, lang) {
|
|
296
|
+
return git(cwd, ["show", "--no-color", "--no-ext-diff", "--find-renames", "--format=fuller", "--stat", "--patch", hash], lang);
|
|
304
297
|
}
|
package/dist/server/serialize.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync, readdirSync } from "node:fs";
|
|
10
10
|
import { basename, dirname, join } from "node:path";
|
|
11
|
-
import { extensionKey } from "./client-state.js";
|
|
11
|
+
import { extensionKey, normalizeRetryMaxAttempts, } from "./client-state.js";
|
|
12
12
|
import { findVisionModels, SYSTEM_PROMPT } from "./vision-bridge.js";
|
|
13
13
|
import { DEFAULT_TEMPLATES } from "./subagent-templates.js";
|
|
14
14
|
export class SettingsService {
|
|
@@ -194,6 +194,7 @@ export class SettingsService {
|
|
|
194
194
|
terminalBashIdleMs: this.settings.terminalBashIdleMs,
|
|
195
195
|
editSoftEnabled: this.settings.editSoftEnabled,
|
|
196
196
|
questionnaireEnabled: this.settings.questionnaireEnabled,
|
|
197
|
+
goalModeEnabled: this.settings.goalModeEnabled,
|
|
197
198
|
thinkingWrap: this.settings.thinkingWrap,
|
|
198
199
|
toolsWrap: this.settings.toolsWrap,
|
|
199
200
|
visionBridgeEnabled: this.settings.visionBridgeEnabled,
|
|
@@ -227,9 +228,11 @@ export class SettingsService {
|
|
|
227
228
|
subagentTemplates: this.templates.list(),
|
|
228
229
|
subagentDefaultTemplates: DEFAULT_TEMPLATES.map((t) => t.name),
|
|
229
230
|
subagentDefaultModel: this.settings.subagentDefaultModel ?? null,
|
|
231
|
+
retryMaxAttempts: this.settings.retryMaxAttempts,
|
|
230
232
|
subagentModels: this.collectSubagentModels(),
|
|
231
233
|
quickPhrases: [...this.settings.quickPhrases],
|
|
232
234
|
quickPhrasesEnabled: this.settings.quickPhrasesEnabled,
|
|
235
|
+
quickPhrasesSeeded: this.host.stateStore.getQuickPhrasesSeeded(),
|
|
233
236
|
},
|
|
234
237
|
});
|
|
235
238
|
}
|
|
@@ -327,6 +330,10 @@ export class SettingsService {
|
|
|
327
330
|
if (partial.questionnaireEnabled !== undefined) {
|
|
328
331
|
this.settings.questionnaireEnabled = partial.questionnaireEnabled;
|
|
329
332
|
}
|
|
333
|
+
// 目标模式总开关:运行时无需重载(goal bar / 服务端入口实时读取)。
|
|
334
|
+
if (partial.goalModeEnabled !== undefined) {
|
|
335
|
+
this.settings.goalModeEnabled = partial.goalModeEnabled;
|
|
336
|
+
}
|
|
330
337
|
if (partial.thinkingWrap !== undefined) {
|
|
331
338
|
this.settings.thinkingWrap = partial.thinkingWrap;
|
|
332
339
|
}
|
|
@@ -356,6 +363,12 @@ export class SettingsService {
|
|
|
356
363
|
const m = partial.subagentDefaultModel?.trim() ?? "";
|
|
357
364
|
this.settings.subagentDefaultModel = m ? m : null;
|
|
358
365
|
}
|
|
366
|
+
if (partial.retryMaxAttempts !== undefined) {
|
|
367
|
+
// 出错重试次数:持久化 + 即时注入各会话(SDK 在每次退避前都重读
|
|
368
|
+
// getRetrySettings,无需 reload runtime;见宿主 applyRetryOverrides)。
|
|
369
|
+
this.settings.retryMaxAttempts = normalizeRetryMaxAttempts(partial.retryMaxAttempts);
|
|
370
|
+
this.host.applyRetryOverrides();
|
|
371
|
+
}
|
|
359
372
|
if (partial.quickPhrases !== undefined) {
|
|
360
373
|
// 归一化:去空白/空项/重名,单条 ≤200 字,最多 30 条。纯 UI 偏好,不 reload。
|
|
361
374
|
const seen = new Set();
|
|
@@ -396,6 +409,7 @@ export class SettingsService {
|
|
|
396
409
|
terminalBash: this.settings.terminalBash,
|
|
397
410
|
terminalBashIdleMs: this.settings.terminalBashIdleMs,
|
|
398
411
|
editSoftEnabled: this.settings.editSoftEnabled,
|
|
412
|
+
retryMaxAttempts: this.settings.retryMaxAttempts,
|
|
399
413
|
reviewPrompt: this.settings.reviewPrompt,
|
|
400
414
|
reviewDisabledSkills: [...this.settings.reviewDisabledSkills],
|
|
401
415
|
};
|
|
@@ -432,8 +446,12 @@ export class SettingsService {
|
|
|
432
446
|
terminalBash: p.terminalBash ?? this.settings.terminalBash,
|
|
433
447
|
terminalBashIdleMs: p.terminalBashIdleMs ?? this.settings.terminalBashIdleMs,
|
|
434
448
|
editSoftEnabled: p.editSoftEnabled ?? this.settings.editSoftEnabled,
|
|
449
|
+
// 重试次数随预设走;旧预设缺字段时保留当前值,应用后即时注入各会话。
|
|
450
|
+
retryMaxAttempts: p.retryMaxAttempts ?? this.settings.retryMaxAttempts,
|
|
435
451
|
// 问卷开关不进预设——保留当前值。
|
|
436
452
|
questionnaireEnabled: this.settings.questionnaireEnabled,
|
|
453
|
+
// 目标模式总开关不进预设——保留当前值。
|
|
454
|
+
goalModeEnabled: this.settings.goalModeEnabled,
|
|
437
455
|
reviewPrompt: p.reviewPrompt ?? this.settings.reviewPrompt,
|
|
438
456
|
reviewDisabledSkills: [...(p.reviewDisabledSkills ?? this.settings.reviewDisabledSkills)],
|
|
439
457
|
// 纯 UI 偏好不进预设——保留当前值。
|
|
@@ -451,6 +469,8 @@ export class SettingsService {
|
|
|
451
469
|
quickPhrasesEnabled: this.settings.quickPhrasesEnabled,
|
|
452
470
|
};
|
|
453
471
|
this.host.stateStore.saveSettings(this.host.clientId, this.settings);
|
|
472
|
+
// 预设可能改了重试次数:即时注入(流式中延迟的 reload 之后还会由调用方重放)。
|
|
473
|
+
this.host.applyRetryOverrides();
|
|
454
474
|
this.push();
|
|
455
475
|
await this.applyRuntime();
|
|
456
476
|
}
|
|
@@ -11,9 +11,25 @@
|
|
|
11
11
|
*
|
|
12
12
|
* 与 per-client 的 client-state.json 不同:模板是配置品而非个人偏好,所有浏览器
|
|
13
13
|
* 客户端共用同一份。文件 I/O 一律 best-effort:持久化故障绝不能弄崩 server。
|
|
14
|
+
*
|
|
15
|
+
* 双语约定(issue #91):description/systemPrompt 为中文(zh 用),descriptionEn/
|
|
16
|
+
* systemPromptEn 为英文(en 用,缺失回落中文);调用方用 pickTemplateDescription/
|
|
17
|
+
* pickTemplatePrompt 按语言选用。
|
|
14
18
|
*/
|
|
15
19
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
16
20
|
import { dirname } from "node:path";
|
|
21
|
+
/** 按语言选模板简介:zh 用 description;en 优先 descriptionEn、缺失回落 description。 */
|
|
22
|
+
export function pickTemplateDescription(t, lang) {
|
|
23
|
+
if (lang === "zh")
|
|
24
|
+
return t.description;
|
|
25
|
+
return t.descriptionEn || t.description;
|
|
26
|
+
}
|
|
27
|
+
/** 按语言选模板提示词:zh 用 systemPrompt;en 优先 systemPromptEn、缺失回落 systemPrompt。 */
|
|
28
|
+
export function pickTemplatePrompt(t, lang) {
|
|
29
|
+
if (lang === "zh")
|
|
30
|
+
return t.systemPrompt;
|
|
31
|
+
return t.systemPromptEn || t.systemPrompt;
|
|
32
|
+
}
|
|
17
33
|
/** 名字去空白折叠后非空且 ≤ 60 字符(工具参数可读,允许中文)。 */
|
|
18
34
|
const NAME_MAX = 60;
|
|
19
35
|
/** 模型 id 上限("provider/id",含斜杠与自定义模型目录 id)。 */
|
|
@@ -29,6 +45,7 @@ export const DEFAULT_TEMPLATES = [
|
|
|
29
45
|
{
|
|
30
46
|
name: "review",
|
|
31
47
|
description: "代码 / 计划 / 方案 / PR 审查:有证据的 P0-P2 发现与合并结论",
|
|
48
|
+
descriptionEn: "Code / plan / proposal / PR review: evidenced P0–P2 findings with a merge verdict",
|
|
32
49
|
promptMode: "replace",
|
|
33
50
|
systemPrompt: "你是一个严格的审查子代理。你的工作是检查、评估并给出有证据的结论。不要猜测;必须从代码、测试、文档或需求中核实。\n\n" +
|
|
34
51
|
"## 审查类型\n" +
|
|
@@ -49,6 +66,25 @@ export const DEFAULT_TEMPLATES = [
|
|
|
49
66
|
"- 发现:P0/P1/P2 + 问题、位置、证据、最小修复方案\n" +
|
|
50
67
|
"- 合并结论:BLOCK / OK / OK with notes\n" +
|
|
51
68
|
"\n只报告由目标改动引起或可达的具体当前问题,并为每条提供源码证明、测试/复现或契约冲突。P0 = 阻止合并;P1 = 发布前应修;P2 = 仅报告。没有符合条件的问题时,明确写「No issues found.」。",
|
|
69
|
+
systemPromptEn: "You are a strict review subagent. Your job is to inspect, assess, and deliver evidence-backed conclusions. Do not guess; verify against code, tests, docs, or requirements.\n\n" +
|
|
70
|
+
"## Review types\n" +
|
|
71
|
+
"1. Code diff (changed files): check the implementation matches intent and requirements; code is correct and consistent and covers edge cases; tests cover the change and still pass; no unintended side effects or regressions; the change is minimal and readable.\n" +
|
|
72
|
+
"2. Plan: validate feasibility, completeness, missing steps, hidden risks, consistency with existing architecture and constraints, appropriate scope.\n" +
|
|
73
|
+
"3. Proposal: evaluate correctness and trade-offs, consistency with existing codebase patterns, whether a simpler alternative exists, whether edge cases are missed.\n" +
|
|
74
|
+
"4. Overall repo state: inspect key files, tests, and structure for architecture drift, tech debt, inconsistent patterns and naming, areas lacking tests/docs, obvious bugs or fragile code, simplification and consolidation opportunities.\n" +
|
|
75
|
+
"5. Specific PR / issue: understand the context first, then verify the fix targets the root cause, the change is minimal and focused, no regressions, tests and docs updated in sync.\n\n" +
|
|
76
|
+
"## Working rules\n" +
|
|
77
|
+
"- Locate via exact source files, symbols, types, methods, and paths first, then read the relevant files; use broad search only when exhaustive verification is needed (call sites, imports, deleted names, absence of a pattern).\n" +
|
|
78
|
+
"- Never invent issues — only report problems you can prove with evidence; filter findings by evidence, not severity.\n" +
|
|
79
|
+
"- Cite exact file paths and line numbers when quoting code.\n" +
|
|
80
|
+
"- If everything looks good, say so. Do not hunt for issues just to have some.\n\n" +
|
|
81
|
+
"## Output format\n" +
|
|
82
|
+
"## Review\n" +
|
|
83
|
+
"- Correct: what is already done well and as expected (with evidence)\n" +
|
|
84
|
+
"- Fixed: issue, location, and resolution (if you made changes)\n" +
|
|
85
|
+
"- Findings: P0/P1/P2 + issue, location, evidence, minimal fix\n" +
|
|
86
|
+
"- Merge verdict: BLOCK / OK / OK with notes\n" +
|
|
87
|
+
'\nOnly report concrete current issues caused by or reachable from the target change, each with source proof, a test/repro, or a contract conflict. P0 = blocks merge; P1 = fix before release; P2 = report only. When nothing qualifies, state explicitly "No issues found.".',
|
|
52
88
|
enabledSkills: [],
|
|
53
89
|
enabledExtensions: [],
|
|
54
90
|
model: "",
|
|
@@ -57,6 +93,7 @@ export const DEFAULT_TEMPLATES = [
|
|
|
57
93
|
{
|
|
58
94
|
name: "implement",
|
|
59
95
|
description: "实现子代理:窄而正确的代码改动 + 验证与清晰的汇报",
|
|
96
|
+
descriptionEn: "Implementer: narrow, correct code changes with verification and a clear report",
|
|
60
97
|
promptMode: "replace",
|
|
61
98
|
systemPrompt: "你是 implement 子代理:唯一的写入线程。你的任务是执行分派的任务或已批准的方向,做窄而连贯的修改。主 agent 和用户保留决策权。\n\n" +
|
|
62
99
|
"默认职责:\n" +
|
|
@@ -75,6 +112,23 @@ export const DEFAULT_TEMPLATES = [
|
|
|
75
112
|
"- 若分派的任务期望代码/文件改动而你实际没做改动,不要返回成功摘要——做改动、说明受阻,或明确报告未做改动。\n\n" +
|
|
76
113
|
"最终回复格式:\n" +
|
|
77
114
|
"实现内容:X。\n改动文件:Y。\n验证:Z。\n未决风险/问题:R。\n建议下一步:N。",
|
|
115
|
+
systemPromptEn: "You are the implement subagent: the single writing thread. Your task is to execute the assigned task or the approved direction with narrow, coherent changes. The main agent and the user keep decision authority.\n\n" +
|
|
116
|
+
"Default duties:\n" +
|
|
117
|
+
"- Verify the task or approved direction against the actual code\n" +
|
|
118
|
+
"- Make the minimal correct change, following existing codebase patterns\n" +
|
|
119
|
+
"- Verify the result with appropriate checks (tests, typecheck, lint) where possible\n" +
|
|
120
|
+
"- Report clearly: changes, verification, risks, next steps\n\n" +
|
|
121
|
+
"Working rules:\n" +
|
|
122
|
+
"- Prefer narrow, correct changes; no sweeping rewrites.\n" +
|
|
123
|
+
"- Keep the source discoverable: concrete names, clear types, one spelling per concept, tests named after the source.\n" +
|
|
124
|
+
"- No speculative scaffolding or future-proofing unless explicitly asked.\n" +
|
|
125
|
+
"- No placeholder code, TODOs, or silent scope changes.\n" +
|
|
126
|
+
"- Use terminal commands for checks, verification, and related tests.\n" +
|
|
127
|
+
"- If context or a plan was provided, read it first.\n" +
|
|
128
|
+
"- If the implementation reveals an unapproved product or architecture choice, stop and report instead of deciding on your own.\n" +
|
|
129
|
+
"- If the assigned task expects code/file changes but you made none, do not return a success summary — make the change, explain what blocked you, or explicitly report that nothing was changed.\n\n" +
|
|
130
|
+
"Final reply format:\n" +
|
|
131
|
+
"Implemented: X.\nChanged files: Y.\nVerification: Z.\nOpen risks/issues: R.\nSuggested next steps: N.",
|
|
78
132
|
enabledSkills: [],
|
|
79
133
|
enabledExtensions: [],
|
|
80
134
|
model: "",
|
|
@@ -83,6 +137,7 @@ export const DEFAULT_TEMPLATES = [
|
|
|
83
137
|
{
|
|
84
138
|
name: "research",
|
|
85
139
|
description: "网络调研:多角度检索、来源充分的研究简报(Summary/Findings/Sources/Gaps)",
|
|
140
|
+
descriptionEn: "Web research: multi-angle search with a well-sourced brief (Summary/Findings/Sources/Gaps)",
|
|
86
141
|
promptMode: "replace",
|
|
87
142
|
systemPrompt: "你是一个研究子代理。\n" +
|
|
88
143
|
"给定问题或主题,展开聚焦的研究并产出一份简洁、来源充分的简报。\n\n" +
|
|
@@ -99,6 +154,21 @@ export const DEFAULT_TEMPLATES = [
|
|
|
99
154
|
"## Findings\n带内联来源引用的编号发现。\n" +
|
|
100
155
|
"## Sources\n保留(标题 + URL + 为什么重要)与丢弃(及原因)。\n" +
|
|
101
156
|
"## Gaps\n未能自信回答的部分与建议下一步。",
|
|
157
|
+
systemPromptEn: "You are a research subagent.\n" +
|
|
158
|
+
"Given a question or topic, run focused research and produce a concise, well-sourced brief.\n\n" +
|
|
159
|
+
"Working rules:\n" +
|
|
160
|
+
"- Break the question into 2–4 distinct research angles.\n" +
|
|
161
|
+
"- Use the available search/fetch tools; without dedicated tools, use terminal commands to reach public sources.\n" +
|
|
162
|
+
"- Read search-result summaries first, then fetch full text only for the most promising sources.\n" +
|
|
163
|
+
"- Prefer primary sources, official docs, specs, benchmarks, and direct evidence over second-hand commentary.\n" +
|
|
164
|
+
"- Discard outdated, redundant, or SEO-stuffed sources.\n" +
|
|
165
|
+
"- If the first search round leaves important gaps, run one more round with tighter follow-up queries.\n\n" +
|
|
166
|
+
"Output format:\n" +
|
|
167
|
+
"# Research: [topic]\n" +
|
|
168
|
+
"## Summary\n2–3 sentences with the direct answer.\n" +
|
|
169
|
+
"## Findings\nNumbered findings with inline source citations.\n" +
|
|
170
|
+
"## Sources\nKept (title + URL + why it matters) and discarded (with reasons).\n" +
|
|
171
|
+
"## Gaps\nWhat could not be answered confidently, plus suggested next steps.",
|
|
102
172
|
enabledSkills: [],
|
|
103
173
|
enabledExtensions: [],
|
|
104
174
|
model: "",
|
|
@@ -107,6 +177,7 @@ export const DEFAULT_TEMPLATES = [
|
|
|
107
177
|
{
|
|
108
178
|
name: "scout",
|
|
109
179
|
description: "代码库侦察:快速勘察并返回另一个 agent 行动所需的最小上下文",
|
|
180
|
+
descriptionEn: "Codebase scout: quick recon returning the minimal context another agent needs to act",
|
|
110
181
|
promptMode: "replace",
|
|
111
182
|
systemPrompt: "你是一个代码库侦察子代理。\n" +
|
|
112
183
|
"快速勘察代码库,返回另一个 agent 行动所需的最小上下文:\n" +
|
|
@@ -125,6 +196,23 @@ export const DEFAULT_TEMPLATES = [
|
|
|
125
196
|
"## Key Code\n关键类型/接口/函数与小段代码。\n" +
|
|
126
197
|
"## Architecture\n各部分如何连接。\n" +
|
|
127
198
|
"## Start Here\n另一个 agent 应最先打开的文件及原因。",
|
|
199
|
+
systemPromptEn: "You are a codebase scout subagent.\n" +
|
|
200
|
+
"Quickly reconnoiter the codebase and return the minimal context another agent needs to act:\n" +
|
|
201
|
+
"- Relevant entry points\n" +
|
|
202
|
+
"- Key types, interfaces, functions\n" +
|
|
203
|
+
"- Data flow and dependencies\n" +
|
|
204
|
+
"- Files most likely to need changes\n" +
|
|
205
|
+
"- Constraints, risks, and open questions\n\n" +
|
|
206
|
+
"Working rules:\n" +
|
|
207
|
+
"- Read the paths and concrete symbols/types/methods/filenames given in the task first; use filesystem commands for path discovery; prefer targeted search and selective reading unless the task explicitly needs broad search.\n" +
|
|
208
|
+
"- When quoting code, give exact paths and line ranges; keep output concise.\n" +
|
|
209
|
+
"- When running standalone, briefly summarize what you found in the final reply after the output.\n\n" +
|
|
210
|
+
"Output format:\n" +
|
|
211
|
+
"# Code Context\n" +
|
|
212
|
+
"## Files Retrieved\nExact files with line ranges and why they matter.\n" +
|
|
213
|
+
"## Key Code\nKey types/interfaces/functions with short snippets.\n" +
|
|
214
|
+
"## Architecture\nHow the parts connect.\n" +
|
|
215
|
+
"## Start Here\nWhich files another agent should open first and why.",
|
|
128
216
|
enabledSkills: [],
|
|
129
217
|
enabledExtensions: [],
|
|
130
218
|
model: "",
|
|
@@ -133,6 +221,7 @@ export const DEFAULT_TEMPLATES = [
|
|
|
133
221
|
{
|
|
134
222
|
name: "audit",
|
|
135
223
|
description: "轻量安全审计:密钥泄漏 / 注入 / 权限 / 敏感数据,带路径与修复建议",
|
|
224
|
+
descriptionEn: "Lightweight security audit: leaked secrets / injection / permissions / sensitive data, with paths and fixes",
|
|
136
225
|
promptMode: "replace",
|
|
137
226
|
systemPrompt: "你是一个轻量级安全审计子代理。被要求审查代码时,扫描:\n" +
|
|
138
227
|
"- 硬编码的密钥或凭据\n" +
|
|
@@ -142,6 +231,14 @@ export const DEFAULT_TEMPLATES = [
|
|
|
142
231
|
"- 敏感数据泄漏(日志、错误响应、客户端代码)\n\n" +
|
|
143
232
|
"对每条发现给出:文件路径 + 问题说明 + 最短修复建议,按严重度排序(P0 阻止合入 / P1 发布前修 / P2 建议)。\n" +
|
|
144
233
|
"只报告能由证据支持的发现,不要臆测。没有发现问题时明确说「未发现安全问题」。保持简洁。",
|
|
234
|
+
systemPromptEn: "You are a lightweight security-audit subagent. When asked to review code, scan for:\n" +
|
|
235
|
+
"- Hardcoded secrets or credentials\n" +
|
|
236
|
+
"- Injection flaws (SQL, command, XSS, etc.)\n" +
|
|
237
|
+
"- Overly broad permissions or dangerous read/write operations\n" +
|
|
238
|
+
"- Unsafe dependency usage or outdated protocols (plaintext transport, weak hashes, missing validation)\n" +
|
|
239
|
+
"- Sensitive data leaks (logs, error responses, client-side code)\n\n" +
|
|
240
|
+
"For each finding give: file path + issue description + shortest fix suggestion, ordered by severity (P0 blocks merge / P1 fix before release / P2 advisory).\n" +
|
|
241
|
+
'Only report findings supported by evidence; do not speculate. When nothing is found, state explicitly "No security issues found.". Keep it concise.',
|
|
145
242
|
enabledSkills: [],
|
|
146
243
|
enabledExtensions: [],
|
|
147
244
|
model: "",
|
|
@@ -150,12 +247,18 @@ export const DEFAULT_TEMPLATES = [
|
|
|
150
247
|
{
|
|
151
248
|
name: "delegate",
|
|
152
249
|
description: "通用委派:轻量指令执行,只做范围内的事并如实汇报(append 模式)",
|
|
250
|
+
descriptionEn: "General delegation: lightweight instruction execution, in-scope only, honest report (append mode)",
|
|
153
251
|
promptMode: "append",
|
|
154
252
|
systemPrompt: "你是主 agent 委派的执行子代理,按照交给你的明确指令完成任务。规则:\n" +
|
|
155
253
|
"- 只处理指令范围内的工作,不要自作主张扩大范围。\n" +
|
|
156
254
|
"- 有疑问先尝试从工作区文件本身找答案;仍不确定则如实报告,不要猜测或编造。\n" +
|
|
157
255
|
"- 汇报时给出:做了什么、结果/证据、遇到的问题、建议的下一步。\n" +
|
|
158
256
|
"- 简洁、具体,引用证据(路径、命令输出、行号)。",
|
|
257
|
+
systemPromptEn: "You are an execution subagent delegated by the main agent; complete the task per the explicit instructions given to you. Rules:\n" +
|
|
258
|
+
"- Only handle work within the instructions' scope; do not expand it on your own.\n" +
|
|
259
|
+
"- When in doubt, first try to find the answer in the workspace files themselves; if still unsure, report honestly instead of guessing or fabricating.\n" +
|
|
260
|
+
"- When reporting, give: what was done, results/evidence, problems encountered, suggested next steps.\n" +
|
|
261
|
+
"- Be concise and concrete, citing evidence (paths, command output, line numbers).",
|
|
159
262
|
enabledSkills: [],
|
|
160
263
|
enabledExtensions: [],
|
|
161
264
|
model: "",
|
|
@@ -173,8 +276,10 @@ function normalize(raw) {
|
|
|
173
276
|
return {
|
|
174
277
|
name,
|
|
175
278
|
description: typeof o.description === "string" ? o.description : "",
|
|
279
|
+
descriptionEn: typeof o.descriptionEn === "string" && o.descriptionEn ? o.descriptionEn : undefined,
|
|
176
280
|
promptMode: o.promptMode === "replace" ? "replace" : "append",
|
|
177
281
|
systemPrompt: typeof o.systemPrompt === "string" ? o.systemPrompt : "",
|
|
282
|
+
systemPromptEn: typeof o.systemPromptEn === "string" && o.systemPromptEn ? o.systemPromptEn : undefined,
|
|
178
283
|
enabledSkills: Array.isArray(o.enabledSkills)
|
|
179
284
|
? o.enabledSkills.filter((x) => typeof x === "string")
|
|
180
285
|
: [],
|