pi-web-ui 0.96.1 → 0.98.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/CHANGELOG.md +144 -2
- package/README.md +29 -3
- package/README.zh-CN.md +28 -3
- package/bin/pi-web-ui.mjs +75 -25
- package/dist/server/agent-service.js +1829 -262
- package/dist/server/approval-rules.js +5 -10
- package/dist/server/atomic-file.js +40 -0
- package/dist/server/attachments.js +22 -0
- package/dist/server/claim-store.js +5 -11
- package/dist/server/client-state.js +296 -11
- package/dist/server/compact-context-tool.js +3 -1
- package/dist/server/compacted-history.js +148 -0
- package/dist/server/composer-drafts.js +4 -6
- package/dist/server/control-socket.js +0 -40
- package/dist/server/delegate-mode.js +143 -0
- package/dist/server/delegate-task.js +10 -10
- package/dist/server/dsh/dsh-agent-service.js +66 -31
- package/dist/server/dsh/dsh-client.js +48 -10
- package/dist/server/dsh/dsh-serialize.js +12 -10
- package/dist/server/dsh/dsh-sessions.js +20 -6
- package/dist/server/dsh/runtime/goal-rpc.mjs +14 -1
- package/dist/server/dsh/runtime/runtime-root.mjs +1 -1
- package/dist/server/ensure-bash.js +3 -5
- package/dist/server/eval-tool.js +11 -5
- package/dist/server/goal-review-gate.js +250 -0
- package/dist/server/goal-service.js +1272 -448
- package/dist/server/index.js +118 -16
- package/dist/server/launch-origin.js +0 -4
- package/dist/server/locales.js +3 -5
- package/dist/server/lsp-tool.js +123 -24
- package/dist/server/markers/builtins/notify.js +4 -2
- package/dist/server/markers/builtins/rename.js +0 -1
- package/dist/server/markers/builtins/todo.js +6 -28
- package/dist/server/markers/marker.js +0 -15
- package/dist/server/markers/registry.js +0 -3
- package/dist/server/markers/store.js +0 -3
- package/dist/server/mcp-bridge.js +13 -0
- package/dist/server/mcp-hot-reload.js +12 -5
- package/dist/server/model-admin.js +19 -18
- package/dist/server/model-enrich.js +0 -3
- package/dist/server/patch-tool.js +22 -6
- package/dist/server/patch-turn-end-boundary.js +77 -0
- package/dist/server/permission-preset.js +69 -0
- package/dist/server/plan-manager.js +150 -5
- package/dist/server/plan-mode.js +414 -0
- package/dist/server/plugin-api-catalog.js +2 -0
- package/dist/server/plugin-catalog.js +6 -6
- package/dist/server/plugin-dom.js +3 -4
- package/dist/server/plugin-facilities.js +39 -8
- package/dist/server/plugin-grants.js +5 -8
- package/dist/server/plugin-permissions.js +4 -6
- package/dist/server/plugin-schedule.js +4 -7
- package/dist/server/plugin-updater.js +151 -38
- package/dist/server/plugins.js +22 -5
- package/dist/server/prompt-composer.js +12 -3
- package/dist/server/protocol-version.js +1 -1
- package/dist/server/resolve-global-sdk.js +8 -0
- package/dist/server/schedule-agent-tool.js +5 -1
- package/dist/server/scheduler-tasks.js +46 -27
- package/dist/server/sdk-origin.js +27 -0
- package/dist/server/serialize.js +33 -9
- package/dist/server/settings-service.js +18 -6
- package/dist/server/slash-commands.js +16 -0
- package/dist/server/subagent-templates.js +4 -10
- package/dist/server/subagents.js +379 -416
- package/dist/server/terminals.js +11 -6
- package/dist/server/text-quote.js +59 -0
- package/dist/server/text-sniff.js +15 -9
- package/dist/server/tool-manager.js +60 -7
- package/dist/server/tool-overrides.js +24 -0
- package/dist/server/update-check.js +80 -28
- package/dist/server/wait-subscription-scan.js +5 -1
- package/extensions/webui.ts +15 -1
- package/package.json +33 -21
- package/plugin-sdk/client-utils.mjs +92 -0
- package/plugin-sdk/index.d.ts +21 -0
- package/plugin-sdk/index.mjs +2 -0
- package/plugins/catalog.json +7 -0
- package/themes/zhupi-dark.css +5 -30
- package/themes/zhupi.css +5 -30
- package/web/dist/assets/{TerminalPanel-O3GyFIGG.js → TerminalPanel-C88r3r3Q.js} +1 -1
- package/web/dist/assets/index-2jDtjHvW.css +1 -0
- package/web/dist/assets/index-BjdK7s1M.js +368 -0
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +2 -1
- package/web/public/sw.js +2 -1
- package/web/dist/assets/index-B7Zp23Vc.css +0 -1
- package/web/dist/assets/index-ByfoaDwp.js +0 -366
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* 定时策略:单个 10s ticker 检查到期任务(cron 下次触发 / 间隔毫秒),
|
|
11
11
|
* running 集合防重叠;catchUp "once" 在启动时补跑一次漏掉的触发。
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { nextCronFire, parseCronSpec } from "./plugin-schedule.js";
|
|
16
|
+
import { writeJsonAtomicSync } from "./atomic-file.js";
|
|
16
17
|
export const SCHEDULER_FILE = "scheduler-tasks.json";
|
|
17
18
|
/** 持久间隔底线 60s(与 host.schedule 持久任务同口径,防 token 烧穿)。 */
|
|
18
19
|
export const SCHEDULER_MIN_INTERVAL_MS = 60_000;
|
|
@@ -30,50 +31,72 @@ function randomTaskId() {
|
|
|
30
31
|
s += abc[Math.floor(Math.random() * abc.length)];
|
|
31
32
|
return `task-${s}`;
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
+
export class SchedulerValidationError extends Error {
|
|
35
|
+
code;
|
|
36
|
+
messageZh;
|
|
37
|
+
messageEn;
|
|
38
|
+
constructor(code, messageZh, messageEn) {
|
|
39
|
+
super(messageZh);
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.messageZh = messageZh;
|
|
42
|
+
this.messageEn = messageEn;
|
|
43
|
+
this.name = "SchedulerValidationError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** 归一化 + 校验用户输入;非法抛 SchedulerValidationError。纯函数,可单测。 */
|
|
34
47
|
export function normalizeSchedulerInput(input, now = Date.now()) {
|
|
35
48
|
const idRaw = String(input.id ?? "").trim();
|
|
36
49
|
const id = idRaw || randomTaskId();
|
|
37
|
-
if (!SCHEDULER_ID_RE.test(id))
|
|
38
|
-
throw new
|
|
50
|
+
if (!SCHEDULER_ID_RE.test(id)) {
|
|
51
|
+
throw new SchedulerValidationError("invalid_id", "任务 id 非法(只收字母/数字/下划线/连字符,≤64 字)", "Invalid task id (letters/numbers/underscores/hyphens only, <= 64 chars)");
|
|
52
|
+
}
|
|
39
53
|
const name = String(input.name ?? "")
|
|
40
54
|
.trim()
|
|
41
55
|
.slice(0, 80);
|
|
42
|
-
if (!name)
|
|
43
|
-
throw new
|
|
56
|
+
if (!name) {
|
|
57
|
+
throw new SchedulerValidationError("empty_name", "任务名称不能为空", "Task name cannot be empty");
|
|
58
|
+
}
|
|
44
59
|
const description = String(input.description ?? "")
|
|
45
60
|
.trim()
|
|
46
61
|
.slice(0, 500);
|
|
47
62
|
const cwd = String(input.cwd ?? "").trim();
|
|
48
|
-
if (!cwd)
|
|
49
|
-
throw new
|
|
63
|
+
if (!cwd) {
|
|
64
|
+
throw new SchedulerValidationError("empty_cwd", "执行目标项目(cwd)不能为空", "Target project directory (cwd) cannot be empty");
|
|
65
|
+
}
|
|
50
66
|
const kind = input.kind === "interval" ? "interval" : "cron";
|
|
51
67
|
let spec;
|
|
52
68
|
if (kind === "cron") {
|
|
53
69
|
spec = String(input.spec ?? "")
|
|
54
70
|
.trim()
|
|
55
71
|
.replace(/\s+/g, " ");
|
|
56
|
-
if (!parseCronSpec(spec))
|
|
57
|
-
throw new
|
|
72
|
+
if (!parseCronSpec(spec)) {
|
|
73
|
+
throw new SchedulerValidationError("invalid_cron", "cron 表达式非法(要 5 字段:分 时 日 月 周,如 0 9 * * *)", "Invalid cron expression (requires 5 fields: min hour day month weekday, e.g. 0 9 * * *)");
|
|
74
|
+
}
|
|
58
75
|
}
|
|
59
76
|
else {
|
|
60
77
|
const ms = Math.floor(Number(input.spec));
|
|
61
|
-
if (!Number.isFinite(ms) || ms <= 0)
|
|
62
|
-
throw new
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
if (!Number.isFinite(ms) || ms <= 0) {
|
|
79
|
+
throw new SchedulerValidationError("invalid_interval", "间隔毫秒数非法", "Invalid interval milliseconds");
|
|
80
|
+
}
|
|
81
|
+
if (ms < SCHEDULER_MIN_INTERVAL_MS) {
|
|
82
|
+
throw new SchedulerValidationError("interval_too_short", `间隔太短(最短 ${SCHEDULER_MIN_INTERVAL_MS / 1000}s,防 token 烧穿)`, `Interval too short (minimum ${SCHEDULER_MIN_INTERVAL_MS / 1000}s)`);
|
|
83
|
+
}
|
|
84
|
+
if (ms > SCHEDULER_MAX_INTERVAL_MS) {
|
|
85
|
+
throw new SchedulerValidationError("interval_too_long", "间隔太长(最长 30 天)", "Interval too long (maximum 30 days)");
|
|
86
|
+
}
|
|
67
87
|
spec = String(ms);
|
|
68
88
|
}
|
|
69
89
|
const prompt = String(input.prompt ?? "").trim();
|
|
70
|
-
if (!prompt)
|
|
71
|
-
throw new
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
if (!prompt) {
|
|
91
|
+
throw new SchedulerValidationError("empty_prompt", "触发指令(prompt)不能为空", "Trigger instruction (prompt) cannot be empty");
|
|
92
|
+
}
|
|
93
|
+
if (prompt.length > 8000) {
|
|
94
|
+
throw new SchedulerValidationError("prompt_too_long", "触发指令超长(>8000 字),请裁剪后重试", "Trigger instruction too long (>8000 characters), please shorten and retry");
|
|
95
|
+
}
|
|
74
96
|
const model = String(input.model ?? "").trim();
|
|
75
|
-
if (model && !model.includes("/"))
|
|
76
|
-
throw new
|
|
97
|
+
if (model && !model.includes("/")) {
|
|
98
|
+
throw new SchedulerValidationError("invalid_model", "模型格式非法(应为 provider/id)", "Invalid model format (expected provider/id)");
|
|
99
|
+
}
|
|
77
100
|
const thinkingLevel = String(input.thinkingLevel ?? "").trim();
|
|
78
101
|
const catchUp = input.catchUp === "once" ? "once" : "skip";
|
|
79
102
|
const conversationId = String(input.conversationId ?? "")
|
|
@@ -267,14 +290,10 @@ export class SchedulerStore {
|
|
|
267
290
|
}
|
|
268
291
|
save() {
|
|
269
292
|
try {
|
|
270
|
-
mkdirSync(this.dataDir, { recursive: true });
|
|
271
|
-
const file = schedulerFile(this.dataDir);
|
|
272
|
-
const tmp = `${file}.tmp-${process.pid}`;
|
|
273
293
|
const tasks = {};
|
|
274
294
|
for (const [id, t] of this.tasks)
|
|
275
295
|
tasks[id] = t;
|
|
276
|
-
|
|
277
|
-
renameSync(tmp, file);
|
|
296
|
+
writeJsonAtomicSync(schedulerFile(this.dataDir), { v: 1, tasks });
|
|
278
297
|
}
|
|
279
298
|
catch (err) {
|
|
280
299
|
console.error("[scheduler] persist failed:", err);
|
|
@@ -37,6 +37,33 @@ export function sdkCopies(fromFile = import.meta.url) {
|
|
|
37
37
|
break;
|
|
38
38
|
dir = parent;
|
|
39
39
|
}
|
|
40
|
+
// 显式透传的宿主 SDK 路径(如 extensions/webui.ts 宿主传递,issue #482)
|
|
41
|
+
if (process.env.PI_WEB_SDK_DIR) {
|
|
42
|
+
const pj = join(process.env.PI_WEB_SDK_DIR, "package.json");
|
|
43
|
+
if (existsSync(pj) && !out.some((c) => c.path === pj)) {
|
|
44
|
+
try {
|
|
45
|
+
const version = JSON.parse(readFileSync(pj, "utf8")).version;
|
|
46
|
+
if (typeof version === "string" && version) {
|
|
47
|
+
out.push({ path: pj, version });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// 宿主 Pi Node 路径探测(~/.local/share/pi-node/current/...,issue #482)
|
|
54
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
55
|
+
if (home) {
|
|
56
|
+
const hostPj = join(home, ".local", "share", "pi-node", "current", "lib", "node_modules", PKG, "package.json");
|
|
57
|
+
if (existsSync(hostPj) && !out.some((c) => c.path === hostPj)) {
|
|
58
|
+
try {
|
|
59
|
+
const version = JSON.parse(readFileSync(hostPj, "utf8")).version;
|
|
60
|
+
if (typeof version === "string" && version) {
|
|
61
|
+
out.push({ path: hostPj, version });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
40
67
|
return out;
|
|
41
68
|
}
|
|
42
69
|
/** 点分版本号比较(够用即可:只比数字段,缺位当 0)。 */
|
package/dist/server/serialize.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
const
|
|
1
|
+
import { splitQuotedPrompt } from "./text-quote.js";
|
|
2
|
+
export const TEXT_CAP = 200_000;
|
|
3
|
+
export const TOOL_OUTPUT_CAP = 100_000;
|
|
4
|
+
export const ARGS_CAP = 20_000;
|
|
4
5
|
/**
|
|
5
6
|
* toolResult 里单张图片进快照的上限(dataUrl 字符数 ≈ 1.5MB 二进制)。
|
|
6
7
|
* 视口/元素截图随便过;超大整页截图回落成占位文本(模型侧不受影响 —— 图它
|
|
@@ -17,10 +18,11 @@ const TOOL_RESULT_IMAGE_MAX = 8;
|
|
|
17
18
|
* 前端还得写容错);工具作者应自己封顶(见 present-files-tool.ts 的摘录预算)。
|
|
18
19
|
*/
|
|
19
20
|
const TOOL_DETAILS_CAP = 64_000;
|
|
20
|
-
function truncate(s, cap) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
export function truncate(s, cap) {
|
|
22
|
+
const str = typeof s === "string" ? s : String(s ?? "");
|
|
23
|
+
if (str.length <= cap)
|
|
24
|
+
return { text: str, truncated: false };
|
|
25
|
+
return { text: `${str.slice(0, cap)}\n\n… [truncated]`, truncated: true };
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
28
|
* SDK/工具的图片块 → 前端可直接 <img> 的 UiImageBlock。
|
|
@@ -177,6 +179,17 @@ export function findEntryByUiId(entries, messageId, seqOf) {
|
|
|
177
179
|
if (uiMessageId(m, seqOf(m)) === messageId)
|
|
178
180
|
return entry;
|
|
179
181
|
}
|
|
182
|
+
else if (entry.type === "compaction") {
|
|
183
|
+
const ts = entry.timestamp ? new Date(entry.timestamp).getTime() : 0;
|
|
184
|
+
const m = {
|
|
185
|
+
role: "compactionSummary",
|
|
186
|
+
summary: entry.summary ?? "",
|
|
187
|
+
tokensBefore: entry.tokensBefore,
|
|
188
|
+
timestamp: ts,
|
|
189
|
+
};
|
|
190
|
+
if (uiMessageId(m, seqOf(m)) === messageId)
|
|
191
|
+
return entry;
|
|
192
|
+
}
|
|
180
193
|
}
|
|
181
194
|
return null;
|
|
182
195
|
}
|
|
@@ -193,13 +206,24 @@ export function serializeMessage(m, seq) {
|
|
|
193
206
|
}
|
|
194
207
|
const id = uiMessageId(m, seq);
|
|
195
208
|
switch (m.role) {
|
|
196
|
-
case "user":
|
|
209
|
+
case "user": {
|
|
210
|
+
const quotes = [];
|
|
211
|
+
const raw = typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content;
|
|
212
|
+
const content = raw.map((block) => {
|
|
213
|
+
if (block.type !== "text")
|
|
214
|
+
return block;
|
|
215
|
+
const parsed = splitQuotedPrompt(block.text);
|
|
216
|
+
quotes.push(...parsed.quotes);
|
|
217
|
+
return { ...block, text: parsed.text };
|
|
218
|
+
});
|
|
197
219
|
return {
|
|
198
220
|
id,
|
|
199
221
|
role: "user",
|
|
200
|
-
content: serializeUserContent(
|
|
222
|
+
content: serializeUserContent(content),
|
|
223
|
+
...(quotes.length ? { details: { quotes } } : {}),
|
|
201
224
|
timestamp: m.timestamp,
|
|
202
225
|
};
|
|
226
|
+
}
|
|
203
227
|
case "assistant":
|
|
204
228
|
return {
|
|
205
229
|
id,
|
|
@@ -13,6 +13,7 @@ import { extensionKey, normalizeDisabledPluginTools, normalizeRetryMaxAttempts,
|
|
|
13
13
|
import { normalizeSoftCapByModel, normalizeSoftCapTokens } from "./soft-cap.js";
|
|
14
14
|
import { findVisionModels, SYSTEM_PROMPT } from "./vision-bridge.js";
|
|
15
15
|
import { COMMITMSG_SYSTEM_PROMPT } from "./scm-commitmsg.js";
|
|
16
|
+
import { PLAN_MODE_SYSTEM_PROMPT } from "./plan-mode.js";
|
|
16
17
|
import { DEFAULT_TEMPLATES } from "./subagent-templates.js";
|
|
17
18
|
import { deriveLegacy, foldLegacyIntoDisabled, normalizeDisabledAgentTools } from "./tool-manager.js";
|
|
18
19
|
export class SettingsService {
|
|
@@ -54,12 +55,6 @@ export class SettingsService {
|
|
|
54
55
|
}
|
|
55
56
|
return false;
|
|
56
57
|
}
|
|
57
|
-
get reviewPrefs() {
|
|
58
|
-
return {
|
|
59
|
-
reviewPrompt: this.settings.reviewPrompt,
|
|
60
|
-
reviewDisabledSkills: this.settings.reviewDisabledSkills,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
58
|
hasPendingReload() {
|
|
64
59
|
return this.pendingReload;
|
|
65
60
|
}
|
|
@@ -314,6 +309,8 @@ export class SettingsService {
|
|
|
314
309
|
visionBridgePrompt: this.settings.visionBridgePrompt,
|
|
315
310
|
scmCommitMsgPromptMode: this.settings.scmCommitMsgPromptMode,
|
|
316
311
|
scmCommitMsgPrompt: this.settings.scmCommitMsgPrompt,
|
|
312
|
+
planModePromptMode: this.settings.planModePromptMode,
|
|
313
|
+
planModePrompt: this.settings.planModePrompt,
|
|
317
314
|
reviewPrompt: this.settings.reviewPrompt,
|
|
318
315
|
reviewDisabledSkills: [...this.settings.reviewDisabledSkills],
|
|
319
316
|
disabledPlugins: [...(this.settings.disabledPlugins ?? [])],
|
|
@@ -327,6 +324,7 @@ export class SettingsService {
|
|
|
327
324
|
toolsSchema: promptSnap.toolsSchema,
|
|
328
325
|
visionBridgeDefaultPrompt: SYSTEM_PROMPT,
|
|
329
326
|
scmCommitMsgDefaultPrompt: COMMITMSG_SYSTEM_PROMPT,
|
|
327
|
+
planModeDefaultPrompt: PLAN_MODE_SYSTEM_PROMPT,
|
|
330
328
|
visionModels: this.collectVisionModels(),
|
|
331
329
|
disabledSkills: [...this.settings.disabledSkills],
|
|
332
330
|
disabledExtensions: [...this.settings.disabledExtensions],
|
|
@@ -404,6 +402,7 @@ export class SettingsService {
|
|
|
404
402
|
// 统一工具开关 live 生效(ActiveSet 加减,无需 reload),见末尾 applyToolGating。
|
|
405
403
|
const toolGatingChanged = partial.disabledAgentTools !== undefined ||
|
|
406
404
|
partial.disabledPluginTools !== undefined ||
|
|
405
|
+
partial.disabledPlugins !== undefined ||
|
|
407
406
|
partial.terminalToolsEnabled !== undefined ||
|
|
408
407
|
partial.editSoftEnabled !== undefined ||
|
|
409
408
|
partial.questionnaireEnabled !== undefined;
|
|
@@ -484,8 +483,12 @@ export class SettingsService {
|
|
|
484
483
|
this.settings.toolApprovalEnabled = partial.toolApprovalEnabled;
|
|
485
484
|
}
|
|
486
485
|
// 目标模式总开关:运行时无需重载(goal bar / 服务端入口实时读取)。
|
|
486
|
+
// on→off 时立即停掉在飞的目标/调研 —— 不停的话循环照跑照派,开关名存实亡。
|
|
487
487
|
if (partial.goalModeEnabled !== undefined) {
|
|
488
|
+
const wasOn = this.settings.goalModeEnabled !== false;
|
|
488
489
|
this.settings.goalModeEnabled = partial.goalModeEnabled;
|
|
490
|
+
if (wasOn && partial.goalModeEnabled === false)
|
|
491
|
+
this.host.onGoalModeDisabled?.();
|
|
489
492
|
}
|
|
490
493
|
// 同项目并行提醒开关:同上,发送入口逐轮实时读取,无需 reload。
|
|
491
494
|
if (partial.parallelReminderEnabled !== undefined) {
|
|
@@ -529,6 +532,12 @@ export class SettingsService {
|
|
|
529
532
|
if (partial.scmCommitMsgPrompt !== undefined) {
|
|
530
533
|
this.settings.scmCommitMsgPrompt = partial.scmCommitMsgPrompt;
|
|
531
534
|
}
|
|
535
|
+
if (partial.planModePromptMode !== undefined) {
|
|
536
|
+
this.settings.planModePromptMode = partial.planModePromptMode;
|
|
537
|
+
}
|
|
538
|
+
if (partial.planModePrompt !== undefined) {
|
|
539
|
+
this.settings.planModePrompt = partial.planModePrompt;
|
|
540
|
+
}
|
|
532
541
|
if (partial.reviewPrompt !== undefined) {
|
|
533
542
|
this.settings.reviewPrompt = partial.reviewPrompt;
|
|
534
543
|
}
|
|
@@ -693,6 +702,9 @@ export class SettingsService {
|
|
|
693
702
|
// 「AI 提交信息」提示词同样不进预设——保留当前值。
|
|
694
703
|
scmCommitMsgPromptMode: this.settings.scmCommitMsgPromptMode,
|
|
695
704
|
scmCommitMsgPrompt: this.settings.scmCommitMsgPrompt,
|
|
705
|
+
// 计划模式提示词同样不进预设——保留当前值。
|
|
706
|
+
planModePromptMode: this.settings.planModePromptMode,
|
|
707
|
+
planModePrompt: this.settings.planModePrompt,
|
|
696
708
|
// 子代理默认模型也不进预设——保留当前值。
|
|
697
709
|
subagentDefaultModel: this.settings.subagentDefaultModel,
|
|
698
710
|
// 快捷短语是纯 UI 偏好,不进预设——保留当前值。
|
|
@@ -38,6 +38,13 @@ export const NATIVE_COMMANDS = [
|
|
|
38
38
|
argumentHint: "<路径>",
|
|
39
39
|
argumentHintEn: "<path>",
|
|
40
40
|
},
|
|
41
|
+
{
|
|
42
|
+
name: "plan",
|
|
43
|
+
description: "开启或关闭计划模式(/plan [on|off])",
|
|
44
|
+
descriptionEn: "Toggle plan mode (/plan [on|off])",
|
|
45
|
+
argumentHint: "[on|off]",
|
|
46
|
+
argumentHintEn: "[on|off]",
|
|
47
|
+
},
|
|
41
48
|
{
|
|
42
49
|
name: "thinking",
|
|
43
50
|
description: "设置思考强度",
|
|
@@ -249,6 +256,15 @@ export class SlashCommandsService {
|
|
|
249
256
|
await this.host.setCwd(args);
|
|
250
257
|
}
|
|
251
258
|
return true;
|
|
259
|
+
case "plan": {
|
|
260
|
+
const trimmed = args.trim().toLowerCase();
|
|
261
|
+
const current = this.host.getPlanMode ? this.host.getPlanMode() : false;
|
|
262
|
+
const next = trimmed === "on" ? true : trimmed === "off" ? false : !current;
|
|
263
|
+
if (this.host.setPlanMode) {
|
|
264
|
+
await this.host.setPlanMode(next);
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
252
268
|
case "thinking": {
|
|
253
269
|
const ALIAS = {
|
|
254
270
|
off: "off",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
* systemPromptEn 为英文(en 用,缺失回落中文);调用方用 pickTemplateDescription/
|
|
20
20
|
* pickTemplatePrompt 按语言选用。
|
|
21
21
|
*/
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
22
|
+
import { readFileSync } from "node:fs";
|
|
23
|
+
import { writeJsonAtomicSync } from "./atomic-file.js";
|
|
24
24
|
/**
|
|
25
25
|
* 思考强度取值:与 SDK 的 THINKING_LEVEL_OPTIONS(前端 ModelThinking 的 THINKING_VALUES)
|
|
26
26
|
* 一致。SDK 未从包根导出该常量,这里照抄一份作输入校验(写错的值一律当未配置)。
|
|
@@ -608,10 +608,7 @@ export class SubagentTemplatesStore {
|
|
|
608
608
|
}
|
|
609
609
|
persist() {
|
|
610
610
|
try {
|
|
611
|
-
|
|
612
|
-
const tmp = `${this.filePath}.${process.pid}.tmp`;
|
|
613
|
-
writeFileSync(tmp, JSON.stringify(this.templates, null, 2) + "\n");
|
|
614
|
-
renameSync(tmp, this.filePath);
|
|
611
|
+
writeJsonAtomicSync(this.filePath, this.templates);
|
|
615
612
|
}
|
|
616
613
|
catch {
|
|
617
614
|
// best effort
|
|
@@ -644,10 +641,7 @@ export class SubagentTemplatesStore {
|
|
|
644
641
|
saveSeeded(names) {
|
|
645
642
|
this.seededNames = names;
|
|
646
643
|
try {
|
|
647
|
-
|
|
648
|
-
const tmp = `${this.seededPath()}.tmp`;
|
|
649
|
-
writeFileSync(tmp, JSON.stringify([...names].sort(), null, 2));
|
|
650
|
-
renameSync(tmp, this.seededPath());
|
|
644
|
+
writeJsonAtomicSync(this.seededPath(), [...names].sort());
|
|
651
645
|
}
|
|
652
646
|
catch {
|
|
653
647
|
// best effort
|