monk-pi 0.2.0 → 0.10.2
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/README.md +71 -8
- package/dist/{chunk-6OMOTI3T.js → chunk-GGAUER4O.js} +203 -254
- package/dist/cli.js +170 -26
- package/dist/index.d.ts +9 -3
- package/dist/index.js +5 -1
- package/dist/monk-extension.ts +853 -0
- package/package.json +1 -1
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type {
|
|
4
|
+
ExtensionAPI,
|
|
5
|
+
ExtensionCommandContext,
|
|
6
|
+
ExtensionContext,
|
|
7
|
+
InputEvent,
|
|
8
|
+
ToolCallEvent,
|
|
9
|
+
} from "@earendil-works/pi-coding-agent";
|
|
10
|
+
|
|
11
|
+
const MONK_MODELS = [
|
|
12
|
+
{ id: "monk-coding", label: "monk-coding (代码主力 - 推荐 · 深度工具调用)" },
|
|
13
|
+
{ id: "monk-fast", label: "monk-fast (极速推理 · 快速问答与轻量任务)" },
|
|
14
|
+
{ id: "monk", label: "monk (融合旗舰 · 质量优先与综合推理)" },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export const PRO_TIPS = [
|
|
18
|
+
"在输入框输入 @diff 即可秒级把当前未提交改动带入分析",
|
|
19
|
+
"输入 /commit 可按 Conventional Commits 规范自动生成中文提交",
|
|
20
|
+
"AI 改乱了代码不用慌,随时输入 /undo 或 /回退 瞬间一键复原",
|
|
21
|
+
"输入 /review 可从缺陷、性能、架构四个维度对代码进行深度审查",
|
|
22
|
+
"输入 /monk 可呼出图形化控制台,秒级免重启切换主力模型",
|
|
23
|
+
"输入 @recent 快速提取最近变动的项目核心源文件列表",
|
|
24
|
+
"输入 /monk cn 随时切换中文工程精炼模式(零客套·行动优先)",
|
|
25
|
+
"终端输入 monk-pi -r 可视化恢复以往任意历史任务断点续写",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const OVERFLOW_PATTERNS = [
|
|
29
|
+
/context.*length/i,
|
|
30
|
+
/maximum.*tokens/i,
|
|
31
|
+
/token.*limit/i,
|
|
32
|
+
/too many tokens/i,
|
|
33
|
+
/prompt.*too long/i,
|
|
34
|
+
/request.*too large/i,
|
|
35
|
+
/context_window_exceeded/i,
|
|
36
|
+
/exceed.*context/i,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export const CHINESE_ENGINEERING_PROMPT = `
|
|
40
|
+
# 中文工程交互规范 (Monk Chinese Engineering Persona)
|
|
41
|
+
1. 语言表达准则:
|
|
42
|
+
- 全程使用干练、专业、自然的中文进行沟通与技术分析。
|
|
43
|
+
- 严禁任何形式的客套与铺垫废话(严禁输出诸如“好的”、“没问题”、“收到”、“接下来我将……”、“这是一个很好的问题”等无意义填充词)。
|
|
44
|
+
- 代码中的标识符、类名、函数名、库名、Git 命令、参数选项及业界通用技术名词(如 JWT、Promise、Hook、WebSocket、Props 等)保持英文原貌,切勿生硬翻译。
|
|
45
|
+
2. 任务执行准则 (Action First):
|
|
46
|
+
- 行动先于解释。凡是需要阅读文件、修改代码或执行系统命令的场景,直接调用相应工具 (read / edit / write / bash),禁止在调用工具前陈述冗余的操作计划。
|
|
47
|
+
- 工具调用后,用最简练的一至两句话总结改动要点(明确指出修改了哪个模块、解决了什么问题),必要时提供验证命令(如测试或启动命令)。
|
|
48
|
+
3. 代码质量准则:
|
|
49
|
+
- 严守既有代码库的代码风格与架构规范。
|
|
50
|
+
- 修改代码必须精准微调,避免不必要的格式洗牌或整文件盲目重写。
|
|
51
|
+
- 复杂算法或关键业务逻辑处增加简洁明了的中文行内注释。
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
interface FileBackup {
|
|
55
|
+
path: string;
|
|
56
|
+
relativePath: string;
|
|
57
|
+
existed: boolean;
|
|
58
|
+
content?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface TurnCheckpoint {
|
|
62
|
+
turnIndex: number;
|
|
63
|
+
timestamp: number;
|
|
64
|
+
files: Map<string, FileBackup>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default function monkExtension(pi: ExtensionAPI) {
|
|
68
|
+
let turnCount = 0;
|
|
69
|
+
let chinesePromptEnabled = true;
|
|
70
|
+
|
|
71
|
+
// Undo Checkpoint Storage
|
|
72
|
+
const undoStack: TurnCheckpoint[] = [];
|
|
73
|
+
let currentTurnBackups = new Map<string, FileBackup>();
|
|
74
|
+
|
|
75
|
+
function updateStatus(ctx: ExtensionContext) {
|
|
76
|
+
if (!ctx.ui) return;
|
|
77
|
+
const model = ctx.model;
|
|
78
|
+
const isMonk = model?.provider === "monk";
|
|
79
|
+
|
|
80
|
+
if (!isMonk) {
|
|
81
|
+
ctx.ui.setStatus("monk", undefined);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const theme = ctx.ui.theme;
|
|
86
|
+
const indicator = theme.fg("success", "●");
|
|
87
|
+
const label = theme.fg("accent", `Monk: ${model.id}`);
|
|
88
|
+
const cnBadge = chinesePromptEnabled ? theme.fg("dim", " [中]") : "";
|
|
89
|
+
const undoBadge =
|
|
90
|
+
undoStack.length > 0 ? theme.fg("warning", ` [可撤销:${undoStack.length}]`) : "";
|
|
91
|
+
const meta = theme.fg("dim", " (1M ctx)");
|
|
92
|
+
ctx.ui.setStatus("monk", `${indicator} ${label}${cnBadge}${undoBadge}${meta}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 1. Lifecycle Events
|
|
96
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
97
|
+
updateStatus(ctx);
|
|
98
|
+
|
|
99
|
+
// Register Magic Context Macro Autocomplete (@diff, @recent, @git, @staged)
|
|
100
|
+
try {
|
|
101
|
+
if (ctx.ui && typeof ctx.ui.addAutocompleteProvider === "function") {
|
|
102
|
+
ctx.ui.addAutocompleteProvider((current: any) => ({
|
|
103
|
+
triggerCharacters: ["@"],
|
|
104
|
+
async getSuggestions(lines: string[], cursorLine: number, cursorCol: number, options: any) {
|
|
105
|
+
const line = lines[cursorLine] ?? "";
|
|
106
|
+
const beforeCursor = line.slice(0, cursorCol);
|
|
107
|
+
const match = beforeCursor.match(/(?:^|[ \t])@([a-zA-Z]*)$/);
|
|
108
|
+
|
|
109
|
+
const macros = [
|
|
110
|
+
{ value: "@diff", label: "@diff", description: "当前未提交的 Git 改动代码 (Working Tree Diff)" },
|
|
111
|
+
{ value: "@staged", label: "@staged", description: "当前 Git 暂存区改动 (git diff --staged)" },
|
|
112
|
+
{ value: "@recent", label: "@recent", description: "最近修改过的相关项目文件列表" },
|
|
113
|
+
{ value: "@git", label: "@git", description: "当前 Git 分支状态与最新提交记录" },
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
if (!match) {
|
|
117
|
+
return current.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const query = match[1]?.toLowerCase() || "";
|
|
121
|
+
const filtered = macros.filter((m) => m.value.slice(1).startsWith(query));
|
|
122
|
+
|
|
123
|
+
const baseResult = await current.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
124
|
+
const baseItems = baseResult?.items || [];
|
|
125
|
+
|
|
126
|
+
if (filtered.length > 0) {
|
|
127
|
+
return {
|
|
128
|
+
items: [...filtered, ...baseItems],
|
|
129
|
+
prefix: `@${query}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return baseResult;
|
|
134
|
+
},
|
|
135
|
+
applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: any, prefix: string) {
|
|
136
|
+
return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
137
|
+
},
|
|
138
|
+
shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number) {
|
|
139
|
+
return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
|
|
140
|
+
},
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// Ignore if autocomplete provider stacking is not supported
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Display Startup Smart Navigator Widget (Auto-dismisses on first prompt)
|
|
148
|
+
if (ctx.ui && typeof ctx.ui.setWidget === "function") {
|
|
149
|
+
try {
|
|
150
|
+
const { stack, gitInfo } = await getProjectContext(pi);
|
|
151
|
+
const randomTip = PRO_TIPS[Math.floor(Math.random() * PRO_TIPS.length)];
|
|
152
|
+
const theme = ctx.ui.theme;
|
|
153
|
+
|
|
154
|
+
const widgetLines = [
|
|
155
|
+
theme.fg("accent", `⚡ Monk 极客导航 · ${stack} (${gitInfo})`),
|
|
156
|
+
theme.fg("dim", " 推荐指令: @diff (改动) · /commit (自动提交) · /undo (一键撤销) · /tips"),
|
|
157
|
+
theme.fg("warning", ` 💡 技巧: ${randomTip}`),
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
ctx.ui.setWidget("monk-starter", widgetLines, { placement: "aboveEditor" });
|
|
161
|
+
} catch {
|
|
162
|
+
// Ignore widget errors
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
pi.on("model_select", async (_event, ctx) => {
|
|
168
|
+
updateStatus(ctx);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
pi.on("turn_start", async (event, ctx) => {
|
|
172
|
+
turnCount = event.turnIndex || turnCount + 1;
|
|
173
|
+
currentTurnBackups = new Map<string, FileBackup>();
|
|
174
|
+
|
|
175
|
+
// Clear starter widget once conversation starts to keep interface clean
|
|
176
|
+
if (ctx.ui && typeof ctx.ui.setWidget === "function") {
|
|
177
|
+
try {
|
|
178
|
+
ctx.ui.setWidget("monk-starter", undefined);
|
|
179
|
+
} catch {}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!ctx.ui) return;
|
|
183
|
+
const model = ctx.model;
|
|
184
|
+
if (model?.provider === "monk") {
|
|
185
|
+
const theme = ctx.ui.theme;
|
|
186
|
+
const spinner = theme.fg("warning", "▲");
|
|
187
|
+
const label = theme.fg("accent", `Monk: ${model.id}`);
|
|
188
|
+
const text = theme.fg("dim", ` [Turn ${turnCount}]`);
|
|
189
|
+
ctx.ui.setStatus("monk", `${spinner} ${label}${text}`);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
pi.on("turn_end", async (_event, ctx) => {
|
|
194
|
+
// If files were modified during this turn, save to undoStack
|
|
195
|
+
if (currentTurnBackups.size > 0) {
|
|
196
|
+
undoStack.push({
|
|
197
|
+
turnIndex: turnCount,
|
|
198
|
+
timestamp: Date.now(),
|
|
199
|
+
files: new Map(currentTurnBackups),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (undoStack.length > 10) {
|
|
203
|
+
undoStack.shift();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Contextual safety nudge: remind user /undo is available
|
|
207
|
+
if (ctx.ui) {
|
|
208
|
+
const count = currentTurnBackups.size;
|
|
209
|
+
if (count === 1) {
|
|
210
|
+
const [firstFile] = currentTurnBackups.values();
|
|
211
|
+
ctx.ui.notify(
|
|
212
|
+
`已修改 ${firstFile.relativePath} · 如需撤销可随时输入 /undo 一键还原`,
|
|
213
|
+
"info"
|
|
214
|
+
);
|
|
215
|
+
} else {
|
|
216
|
+
ctx.ui.notify(
|
|
217
|
+
`已修改 ${count} 个文件 · 如需撤销可随时输入 /undo 一键还原`,
|
|
218
|
+
"info"
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
currentTurnBackups = new Map<string, FileBackup>();
|
|
224
|
+
updateStatus(ctx);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// 2. Magic Context Macro Expansion (@diff, @recent, @git, @staged)
|
|
228
|
+
pi.on("input", async (event: InputEvent, ctx: ExtensionContext) => {
|
|
229
|
+
if (event.source === "extension") return { action: "continue" };
|
|
230
|
+
|
|
231
|
+
let text = event.text || "";
|
|
232
|
+
let modified = false;
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
text.includes("@diff") ||
|
|
236
|
+
text.includes("@staged") ||
|
|
237
|
+
text.includes("@git") ||
|
|
238
|
+
text.includes("@recent")
|
|
239
|
+
) {
|
|
240
|
+
if (text.includes("@diff")) {
|
|
241
|
+
const diffBlock = await getGitDiff(pi);
|
|
242
|
+
text = text.replaceAll("@diff", diffBlock);
|
|
243
|
+
modified = true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (text.includes("@staged")) {
|
|
247
|
+
const stagedBlock = await getGitStagedDiff(pi);
|
|
248
|
+
text = text.replaceAll("@staged", stagedBlock);
|
|
249
|
+
modified = true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (text.includes("@git")) {
|
|
253
|
+
const gitBlock = await getGitSummary(pi);
|
|
254
|
+
text = text.replaceAll("@git", gitBlock);
|
|
255
|
+
modified = true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (text.includes("@recent")) {
|
|
259
|
+
const recentBlock = await getRecentFiles(pi);
|
|
260
|
+
text = text.replaceAll("@recent", recentBlock);
|
|
261
|
+
modified = true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (modified) {
|
|
265
|
+
ctx.ui?.notify("已成功解析并展开上下文宏 (@diff / @recent / @git)", "info");
|
|
266
|
+
return { action: "transform", text };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// 3. Intercept Tool Calls: File Pre-Snapshot for Undo
|
|
272
|
+
pi.on("tool_call", async (event: ToolCallEvent) => {
|
|
273
|
+
if (event.toolName === "edit" || event.toolName === "write") {
|
|
274
|
+
const rawPath = (event.input as { path?: string })?.path;
|
|
275
|
+
if (typeof rawPath === "string" && rawPath.trim()) {
|
|
276
|
+
const absPath = path.resolve(process.cwd(), rawPath.trim());
|
|
277
|
+
|
|
278
|
+
// Only backup the FIRST time a file is touched in the current turn
|
|
279
|
+
if (!currentTurnBackups.has(absPath)) {
|
|
280
|
+
const relPath = path.relative(process.cwd(), absPath);
|
|
281
|
+
try {
|
|
282
|
+
if (fs.existsSync(absPath)) {
|
|
283
|
+
const content = fs.readFileSync(absPath, "utf-8");
|
|
284
|
+
currentTurnBackups.set(absPath, {
|
|
285
|
+
path: absPath,
|
|
286
|
+
relativePath: relPath,
|
|
287
|
+
existed: true,
|
|
288
|
+
content,
|
|
289
|
+
});
|
|
290
|
+
} else {
|
|
291
|
+
currentTurnBackups.set(absPath, {
|
|
292
|
+
path: absPath,
|
|
293
|
+
relativePath: relPath,
|
|
294
|
+
existed: false,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
} catch {
|
|
298
|
+
// Ignore read errors
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// 4. Chinese Engineering System Prompt Injection
|
|
306
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
307
|
+
if (!chinesePromptEnabled) return;
|
|
308
|
+
const model = ctx.model;
|
|
309
|
+
const isMonk = model && model.provider === "monk";
|
|
310
|
+
if (!isMonk) return;
|
|
311
|
+
|
|
312
|
+
const currentPrompt = event.systemPrompt || "";
|
|
313
|
+
if (!currentPrompt.includes("中文工程交互规范")) {
|
|
314
|
+
return {
|
|
315
|
+
systemPrompt: `${currentPrompt}\n\n${CHINESE_ENGINEERING_PROMPT.trim()}`,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// 5. Intelligent Context Overflow & Compaction Recovery
|
|
321
|
+
pi.on("message_end", async (event, ctx) => {
|
|
322
|
+
const message = event.message;
|
|
323
|
+
if (!message || message.role !== "assistant") return;
|
|
324
|
+
if (message.stopReason !== "error") return;
|
|
325
|
+
|
|
326
|
+
const isMonk = message.provider === "monk" || ctx.model?.provider === "monk";
|
|
327
|
+
if (!isMonk) return;
|
|
328
|
+
|
|
329
|
+
const errorMsg = message.errorMessage || "";
|
|
330
|
+
if (errorMsg.includes("context_length_exceeded")) return;
|
|
331
|
+
|
|
332
|
+
const isOverflow = OVERFLOW_PATTERNS.some((p) => p.test(errorMsg));
|
|
333
|
+
if (isOverflow) {
|
|
334
|
+
ctx.ui?.notify(
|
|
335
|
+
"Monk: 检测到上下文超长,已自动重写错误并触发智能压缩 (Compaction) 重试...",
|
|
336
|
+
"warning"
|
|
337
|
+
);
|
|
338
|
+
return {
|
|
339
|
+
message: {
|
|
340
|
+
...message,
|
|
341
|
+
errorMessage: `context_length_exceeded: ${errorMsg}`,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// 6. Custom Slash Command: /tips (查看技巧清单)
|
|
348
|
+
pi.registerCommand("tips", {
|
|
349
|
+
description: "查看 Monk-Pi 极客技巧清单 (/tips)",
|
|
350
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
351
|
+
if (!ctx.ui) return;
|
|
352
|
+
await ctx.ui.select(
|
|
353
|
+
"Monk-Pi 极客实用技巧清单:",
|
|
354
|
+
PRO_TIPS.map((t, i) => `${i + 1}. ${t}`)
|
|
355
|
+
);
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// 7. Custom Slash Command: /undo (一键撤销)
|
|
360
|
+
pi.registerCommand("undo", {
|
|
361
|
+
description: "一键撤销上一次 AI 对文件的所有修改 (/undo)",
|
|
362
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
363
|
+
await handleUndoCommand(ctx, undoStack, () => updateStatus(ctx));
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
pi.registerCommand("回退", {
|
|
368
|
+
description: "一键撤销上一次 AI 对文件的所有修改 (/回退)",
|
|
369
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
370
|
+
await handleUndoCommand(ctx, undoStack, () => updateStatus(ctx));
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// 8. Custom Slash Command: /commit (自动中文语义化提交)
|
|
375
|
+
pi.registerCommand("commit", {
|
|
376
|
+
description: "检查 Git 改动并生成语义化中文 Commit 提交 (/commit [附加说明])",
|
|
377
|
+
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
378
|
+
await handleCommitCommand(pi, ctx, args);
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// 9. Custom Slash Command: /review (深度中文代码审查)
|
|
383
|
+
pi.registerCommand("review", {
|
|
384
|
+
description: "对当前 Git 改动或指定文件进行专业中文代码审查 (/review [路径/分支])",
|
|
385
|
+
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
386
|
+
await handleReviewCommand(pi, ctx, args);
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// 10. Custom Slash Command: /monk
|
|
391
|
+
pi.registerCommand("monk", {
|
|
392
|
+
description: "Monk 专属控制台 (/monk [model|undo|commit|review|prompt|tips|ping|status|account])",
|
|
393
|
+
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
394
|
+
const sub = args.trim().toLowerCase();
|
|
395
|
+
|
|
396
|
+
if (sub === "undo" || sub === "rollback") {
|
|
397
|
+
await handleUndoCommand(ctx, undoStack, () => updateStatus(ctx));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (sub === "tips" || sub === "help") {
|
|
402
|
+
if (ctx.ui) {
|
|
403
|
+
await ctx.ui.select(
|
|
404
|
+
"Monk-Pi 极客技巧清单:",
|
|
405
|
+
PRO_TIPS.map((t, i) => `${i + 1}. ${t}`)
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (sub === "model" || sub === "switch") {
|
|
412
|
+
await handleModelSwitch(pi, ctx);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (sub === "commit" || sub === "ci") {
|
|
417
|
+
await handleCommitCommand(pi, ctx, "");
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (sub === "review" || sub === "cr") {
|
|
422
|
+
await handleReviewCommand(pi, ctx, "");
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (sub === "prompt" || sub === "cn") {
|
|
427
|
+
chinesePromptEnabled = !chinesePromptEnabled;
|
|
428
|
+
const state = chinesePromptEnabled ? "已启用 (精炼干练)" : "已关闭 (恢复原生默认)";
|
|
429
|
+
ctx.ui?.notify(`Monk 中文工程系统提示词: ${state}`, "info");
|
|
430
|
+
updateStatus(ctx);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (sub === "ping" || sub === "status") {
|
|
435
|
+
await handlePing(ctx);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (sub === "account" || sub === "quota") {
|
|
440
|
+
ctx.ui?.notify("Monk 用量与到期查询地址: https://monk.party/account/", "info");
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Default: interactive menu
|
|
445
|
+
await handleMenu(
|
|
446
|
+
pi,
|
|
447
|
+
ctx,
|
|
448
|
+
undoStack,
|
|
449
|
+
() => {
|
|
450
|
+
chinesePromptEnabled = !chinesePromptEnabled;
|
|
451
|
+
updateStatus(ctx);
|
|
452
|
+
return chinesePromptEnabled;
|
|
453
|
+
},
|
|
454
|
+
() => updateStatus(ctx)
|
|
455
|
+
);
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ============================================================================
|
|
461
|
+
// Context Macro Resolvers (@diff, @staged, @git, @recent)
|
|
462
|
+
// ============================================================================
|
|
463
|
+
|
|
464
|
+
async function getGitDiff(pi: ExtensionAPI): Promise<string> {
|
|
465
|
+
const check = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
|
|
466
|
+
if (check.code !== 0) return "[当前目录非 Git 仓库,无法获取 @diff]";
|
|
467
|
+
|
|
468
|
+
const res = await pi.exec("git", ["diff"]);
|
|
469
|
+
const stagedRes = await pi.exec("git", ["diff", "--staged"]);
|
|
470
|
+
const combined = [res.stdout?.trim(), stagedRes.stdout?.trim()].filter(Boolean).join("\n\n");
|
|
471
|
+
|
|
472
|
+
if (!combined) return "[当前 Git 工作区干净,暂无未提交的代码改动]";
|
|
473
|
+
|
|
474
|
+
const lines = combined.split("\n");
|
|
475
|
+
if (lines.length > 500) {
|
|
476
|
+
const truncated = lines.slice(0, 500).join("\n");
|
|
477
|
+
return `\n\`\`\`diff\n# [Git Diff 变更代码 - 共 ${lines.length} 行,展示前 500 行]\n${truncated}\n\`\`\`\n`;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return `\n\`\`\`diff\n# [Git Diff 变更代码 - 共 ${lines.length} 行]\n${combined}\n\`\`\`\n`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function getGitStagedDiff(pi: ExtensionAPI): Promise<string> {
|
|
484
|
+
const check = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
|
|
485
|
+
if (check.code !== 0) return "[当前目录非 Git 仓库,无法获取 @staged]";
|
|
486
|
+
|
|
487
|
+
const res = await pi.exec("git", ["diff", "--staged"]);
|
|
488
|
+
const diff = res.stdout?.trim();
|
|
489
|
+
if (!diff) return "[当前 Git 暂存区暂无改动 (Staged is empty)]";
|
|
490
|
+
|
|
491
|
+
return `\n\`\`\`diff\n# [Git 暂存区代码变动 (Staged Diff)]\n${diff}\n\`\`\`\n`;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async function getGitSummary(pi: ExtensionAPI): Promise<string> {
|
|
495
|
+
const check = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
|
|
496
|
+
if (check.code !== 0) return "[当前目录非 Git 仓库,无法获取 @git]";
|
|
497
|
+
|
|
498
|
+
const branchRes = await pi.exec("git", ["branch", "--show-current"]);
|
|
499
|
+
const statusRes = await pi.exec("git", ["status", "-s"]);
|
|
500
|
+
const logRes = await pi.exec("git", ["log", "-n", "3", "--oneline"]);
|
|
501
|
+
|
|
502
|
+
const branch = branchRes.stdout?.trim() || "HEAD";
|
|
503
|
+
const status = statusRes.stdout?.trim() || "(工作区干净)";
|
|
504
|
+
const log = logRes.stdout?.trim() || "(暂无提交记录)";
|
|
505
|
+
|
|
506
|
+
return `\n\`\`\`\n# [Git 当前分支与状态信息]\n分支: ${branch}\n状态变动:\n${status}\n\n最近 3 条提交:\n${log}\n\`\`\`\n`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async function getRecentFiles(pi: ExtensionAPI): Promise<string> {
|
|
510
|
+
const check = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
|
|
511
|
+
if (check.code === 0) {
|
|
512
|
+
const statusRes = await pi.exec("git", ["status", "--porcelain"]);
|
|
513
|
+
const logRes = await pi.exec("git", ["log", "-n", "5", "--name-only", "--format="]);
|
|
514
|
+
const fileSet = new Set<string>();
|
|
515
|
+
|
|
516
|
+
if (statusRes.stdout) {
|
|
517
|
+
statusRes.stdout.split("\n").forEach((line) => {
|
|
518
|
+
const p = line.slice(3).trim();
|
|
519
|
+
if (p) fileSet.add(p);
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (logRes.stdout) {
|
|
524
|
+
logRes.stdout.split("\n").forEach((line) => {
|
|
525
|
+
const p = line.trim();
|
|
526
|
+
if (p) fileSet.add(p);
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const files = Array.from(fileSet).slice(0, 15);
|
|
531
|
+
if (files.length > 0) {
|
|
532
|
+
return `\n# [最近变动关联的项目源文件列表]\n${files.map((f) => `- ${f}`).join("\n")}\n`;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return "[暂未找到最近变动的关联文件]";
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ============================================================================
|
|
540
|
+
// Project Context Profiler
|
|
541
|
+
// ============================================================================
|
|
542
|
+
|
|
543
|
+
async function getProjectContext(pi: ExtensionAPI): Promise<{ stack: string; gitInfo: string }> {
|
|
544
|
+
let stack = "通用项目";
|
|
545
|
+
const cwd = process.cwd();
|
|
546
|
+
|
|
547
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
548
|
+
if (fs.existsSync(pkgPath)) {
|
|
549
|
+
try {
|
|
550
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
551
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
552
|
+
if (deps["vue"] || deps["nuxt"]) stack = "Vue 3 技术栈";
|
|
553
|
+
else if (deps["next"]) stack = "Next.js 全栈";
|
|
554
|
+
else if (deps["react"]) stack = "React 前端";
|
|
555
|
+
else if (deps["@earendil-works/pi-coding-agent"]) stack = "Monk-Pi / Agent 工程";
|
|
556
|
+
else stack = "Node.js 工程";
|
|
557
|
+
} catch {
|
|
558
|
+
stack = "Node.js 工程";
|
|
559
|
+
}
|
|
560
|
+
} else if (fs.existsSync(path.join(cwd, "go.mod"))) {
|
|
561
|
+
stack = "Go 后端工程";
|
|
562
|
+
} else if (fs.existsSync(path.join(cwd, "Cargo.toml"))) {
|
|
563
|
+
stack = "Rust 系统工程";
|
|
564
|
+
} else if (
|
|
565
|
+
fs.existsSync(path.join(cwd, "requirements.txt")) ||
|
|
566
|
+
fs.existsSync(path.join(cwd, "pyproject.toml"))
|
|
567
|
+
) {
|
|
568
|
+
stack = "Python 工程";
|
|
569
|
+
} else if (
|
|
570
|
+
fs.existsSync(path.join(cwd, "pom.xml")) ||
|
|
571
|
+
fs.existsSync(path.join(cwd, "build.gradle"))
|
|
572
|
+
) {
|
|
573
|
+
stack = "Java / Spring 工程";
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
let gitInfo = "无 Git";
|
|
577
|
+
const gitCheck = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
|
|
578
|
+
if (gitCheck.code === 0) {
|
|
579
|
+
const branchRes = await pi.exec("git", ["branch", "--show-current"]);
|
|
580
|
+
const statusRes = await pi.exec("git", ["status", "--porcelain"]);
|
|
581
|
+
const branch = branchRes.stdout?.trim() || "HEAD";
|
|
582
|
+
const changedCount = statusRes.stdout
|
|
583
|
+
? statusRes.stdout.split("\n").filter((l) => l.trim()).length
|
|
584
|
+
: 0;
|
|
585
|
+
gitInfo = changedCount > 0 ? `${branch}: ${changedCount} 改动` : `${branch}: 干净`;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return { stack, gitInfo };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// ============================================================================
|
|
592
|
+
// Undo, Commit, Review & Control Handlers
|
|
593
|
+
// ============================================================================
|
|
594
|
+
|
|
595
|
+
async function handleUndoCommand(
|
|
596
|
+
ctx: ExtensionCommandContext,
|
|
597
|
+
undoStack: TurnCheckpoint[],
|
|
598
|
+
refreshStatus: () => void
|
|
599
|
+
) {
|
|
600
|
+
if (undoStack.length === 0) {
|
|
601
|
+
ctx.ui?.notify("当前没有可撤销的 AI 改动记录 (暂无检查点)", "info");
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const checkpoint = undoStack[undoStack.length - 1];
|
|
606
|
+
const fileBackups = Array.from(checkpoint.files.values());
|
|
607
|
+
|
|
608
|
+
if (fileBackups.length === 0) {
|
|
609
|
+
undoStack.pop();
|
|
610
|
+
ctx.ui?.notify(`上一个轮次 (Turn ${checkpoint.turnIndex}) 未产生文件改动`, "info");
|
|
611
|
+
refreshStatus();
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const fileSummaries = fileBackups.map((f) => {
|
|
616
|
+
return f.existed ? ` ↺ 恢复原状: ${f.relativePath}` : ` 🗑 删除新建: ${f.relativePath}`;
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
const promptText = `确认撤销第 ${checkpoint.turnIndex} 轮的 AI 文件改动?\n${fileSummaries.join("\n")}`;
|
|
620
|
+
const choice = await ctx.ui?.select(promptText, [
|
|
621
|
+
`确认撤销 (${fileBackups.length} 个文件)`,
|
|
622
|
+
"取消",
|
|
623
|
+
]);
|
|
624
|
+
|
|
625
|
+
if (!choice || choice.includes("取消")) {
|
|
626
|
+
ctx.ui?.notify("已取消撤销操作", "info");
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
undoStack.pop();
|
|
631
|
+
let restoredCount = 0;
|
|
632
|
+
let deletedCount = 0;
|
|
633
|
+
const errors: string[] = [];
|
|
634
|
+
|
|
635
|
+
for (const file of fileBackups) {
|
|
636
|
+
try {
|
|
637
|
+
if (!file.existed) {
|
|
638
|
+
if (fs.existsSync(file.path)) {
|
|
639
|
+
fs.unlinkSync(file.path);
|
|
640
|
+
deletedCount++;
|
|
641
|
+
}
|
|
642
|
+
} else if (file.content !== undefined) {
|
|
643
|
+
fs.writeFileSync(file.path, file.content, "utf-8");
|
|
644
|
+
restoredCount++;
|
|
645
|
+
}
|
|
646
|
+
} catch (err: unknown) {
|
|
647
|
+
errors.push(`${file.relativePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
refreshStatus();
|
|
652
|
+
|
|
653
|
+
if (errors.length > 0) {
|
|
654
|
+
ctx.ui?.notify(`撤销部分完成,但存在错误: ${errors.join("; ")}`, "warning");
|
|
655
|
+
} else {
|
|
656
|
+
ctx.ui?.notify(
|
|
657
|
+
`已成功撤销改动!共恢复 ${restoredCount} 个文件,清理 ${deletedCount} 个新建文件`,
|
|
658
|
+
"info"
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
async function handleCommitCommand(
|
|
664
|
+
pi: ExtensionAPI,
|
|
665
|
+
ctx: ExtensionCommandContext,
|
|
666
|
+
extraArgs: string
|
|
667
|
+
) {
|
|
668
|
+
const gitCheck = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
|
|
669
|
+
if (gitCheck.code !== 0) {
|
|
670
|
+
ctx.ui?.notify("当前目录不是 Git 仓库,无法执行 /commit", "warning");
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const statusCheck = await pi.exec("git", ["status", "--porcelain"]);
|
|
675
|
+
if (!statusCheck.stdout || !statusCheck.stdout.trim()) {
|
|
676
|
+
ctx.ui?.notify("Git 工作区干净,没有待提交的改动 (Working tree clean)", "info");
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
ctx.ui?.notify("正在分析 Git 改动并准备中文语义化提交...", "info");
|
|
681
|
+
|
|
682
|
+
const prompt = [
|
|
683
|
+
"请检查当前的 Git 改动,并按照 Conventional Commits 规范生成地道的中文语义化提交信息并完成提交:",
|
|
684
|
+
"1. 先调用 bash 查看当前变更状态 (`git status -s`) 与代码改动 (`git diff --stat` 和 `git diff`)。",
|
|
685
|
+
"2. 根据变更内容确定规范的提交信息:",
|
|
686
|
+
" 格式:<type>(<scope>): <简明清晰的中文提交主题>",
|
|
687
|
+
" 常见类型:feat(新特性)、fix(修复)、docs(文档)、refactor(重构)、perf(性能)、test(测试)、chore(杂项)。",
|
|
688
|
+
"3. 直接调用 bash 执行 `git add -A` 并使用 `git commit -m \"...\"` 执行提交。",
|
|
689
|
+
"4. 提交完成后,用两句话简要汇报 commit hash 与提交信息。",
|
|
690
|
+
extraArgs ? `\n附加要求:${extraArgs}` : "",
|
|
691
|
+
].join("\n");
|
|
692
|
+
|
|
693
|
+
pi.sendUserMessage(prompt);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async function handleReviewCommand(
|
|
697
|
+
pi: ExtensionAPI,
|
|
698
|
+
ctx: ExtensionCommandContext,
|
|
699
|
+
targetPath: string
|
|
700
|
+
) {
|
|
701
|
+
const target = targetPath.trim();
|
|
702
|
+
|
|
703
|
+
if (target) {
|
|
704
|
+
ctx.ui?.notify(`正在针对指定目标发起深度代码审查: ${target} ...`, "info");
|
|
705
|
+
const prompt = [
|
|
706
|
+
`请对指定目标进行专业、深入的代码审查 (Code Review):${target}`,
|
|
707
|
+
"1. 调用 read 工具阅读相关源代码及项目上下文。",
|
|
708
|
+
"2. 从以下四个关键维度输出结构化中文审查报告:",
|
|
709
|
+
" - 🚨【潜在缺陷与边界异常】:空指针/未定义、并发竞争、未捕获异常、边界越界等隐患。",
|
|
710
|
+
" - ⚡【性能与资源消耗】:无意义循环、内存泄漏、过多重绘、昂贵计算或连接未释放。",
|
|
711
|
+
" - 🏗【设计与可维护性】:单一职责原则、模块解耦、代码复用、命名规范与可读性。",
|
|
712
|
+
" - 💡【具体改进建议】:指出具体有问题的代码位置,并给出优化后的参考实现片段。",
|
|
713
|
+
"3. 语言保持客观专业,突出高价值修改意见。",
|
|
714
|
+
].join("\n");
|
|
715
|
+
|
|
716
|
+
pi.sendUserMessage(prompt);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const gitCheck = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
|
|
721
|
+
if (gitCheck.code !== 0) {
|
|
722
|
+
ctx.ui?.notify("当前目录不是 Git 仓库,请指定具体文件路径审查,例如: /review src/index.ts", "warning");
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const statusCheck = await pi.exec("git", ["status", "--porcelain"]);
|
|
727
|
+
if (!statusCheck.stdout || !statusCheck.stdout.trim()) {
|
|
728
|
+
ctx.ui?.notify("当前 Git 工作区无未提交改动。可指定特定文件审查,例如: /review src/index.ts", "info");
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
ctx.ui?.notify("正在针对当前未提交的 Git 改动发起深度代码审查...", "info");
|
|
733
|
+
|
|
734
|
+
const prompt = [
|
|
735
|
+
"请对当前工作区的所有未提交改动 (Uncommitted Changes) 进行专业、深入的中文代码审查 (Code Review):",
|
|
736
|
+
"1. 调用 bash 执行 `git diff` 及 `git diff --staged` 获取完整改动代码。",
|
|
737
|
+
"2. 从以下四个关键维度进行分析并输出结构清晰的 Markdown 审查报告:",
|
|
738
|
+
" - 🚨【潜在缺陷与边界异常】:空指针/未定义、逻辑死角、未处理的错误、边界异常。",
|
|
739
|
+
" - ⚡【性能与资源消耗】:不必要的循环、重复渲染、未清理的副作用、高耗时操作。",
|
|
740
|
+
" - 🏗【设计与架构质量】:代码解耦、单一职责、命名契合度与可读性。",
|
|
741
|
+
" - 💡【关键重构建议与片段】:给出具体的修改方案与推荐的代码重构写法。",
|
|
742
|
+
"3. 如果改动整体质量优秀,请明确指出亮点并予以肯定。",
|
|
743
|
+
].join("\n");
|
|
744
|
+
|
|
745
|
+
pi.sendUserMessage(prompt);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
async function handleModelSwitch(pi: ExtensionAPI, ctx: ExtensionCommandContext) {
|
|
749
|
+
if (!ctx.ui) return;
|
|
750
|
+
|
|
751
|
+
const choices = MONK_MODELS.map((m) => m.label);
|
|
752
|
+
const selectedLabel = await ctx.ui.select("选择当前会话的 Monk 模型:", choices);
|
|
753
|
+
|
|
754
|
+
if (!selectedLabel) return;
|
|
755
|
+
|
|
756
|
+
const matched = MONK_MODELS.find((m) => m.label === selectedLabel);
|
|
757
|
+
if (!matched) return;
|
|
758
|
+
|
|
759
|
+
const model = ctx.modelRegistry.find("monk", matched.id);
|
|
760
|
+
if (!model) {
|
|
761
|
+
ctx.ui.notify(`未在配置中找到模型: monk/${matched.id}`, "error");
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const success = await pi.setModel(model);
|
|
766
|
+
if (success) {
|
|
767
|
+
ctx.ui.notify(`主力模型已切换至: ${matched.id}`, "info");
|
|
768
|
+
} else {
|
|
769
|
+
ctx.ui.notify(`切换失败,未能设置模型: ${matched.id}`, "error");
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
async function handlePing(ctx: ExtensionCommandContext) {
|
|
774
|
+
if (!ctx.ui) return;
|
|
775
|
+
|
|
776
|
+
const startTime = Date.now();
|
|
777
|
+
const apiKey = process.env.MONK_API_KEY || "";
|
|
778
|
+
|
|
779
|
+
try {
|
|
780
|
+
const controller = new AbortController();
|
|
781
|
+
const timeout = setTimeout(() => controller.abort(), 6000);
|
|
782
|
+
|
|
783
|
+
const res = await fetch("https://monk.party/v1/models", {
|
|
784
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
785
|
+
signal: controller.signal,
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
clearTimeout(timeout);
|
|
789
|
+
const latency = Date.now() - startTime;
|
|
790
|
+
|
|
791
|
+
if (res.ok) {
|
|
792
|
+
ctx.ui.notify(`Monk API 连通正常 · 延迟 ${latency}ms`, "info");
|
|
793
|
+
} else {
|
|
794
|
+
ctx.ui.notify(`Monk API 响应异常 (HTTP ${res.status})`, "warning");
|
|
795
|
+
}
|
|
796
|
+
} catch (err: unknown) {
|
|
797
|
+
ctx.ui.notify(
|
|
798
|
+
`Monk API 连接失败: ${err instanceof Error ? err.message : String(err)}`,
|
|
799
|
+
"error"
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
async function handleMenu(
|
|
805
|
+
pi: ExtensionAPI,
|
|
806
|
+
ctx: ExtensionCommandContext,
|
|
807
|
+
undoStack: TurnCheckpoint[],
|
|
808
|
+
toggleCnPrompt: () => boolean,
|
|
809
|
+
refreshStatus: () => void
|
|
810
|
+
) {
|
|
811
|
+
if (!ctx.ui) return;
|
|
812
|
+
|
|
813
|
+
const undoText =
|
|
814
|
+
undoStack.length > 0
|
|
815
|
+
? `2. 一键撤销上次改动 (/undo · ${undoStack.length} 个可回退)`
|
|
816
|
+
: "2. 一键撤销上次改动 (/undo · 暂无)";
|
|
817
|
+
|
|
818
|
+
const choice = await ctx.ui.select("Monk API 控制台", [
|
|
819
|
+
"1. 切换会话模型 (Switch Model)",
|
|
820
|
+
undoText,
|
|
821
|
+
"3. 语义化 Git 提交 (/commit)",
|
|
822
|
+
"4. 深度代码审查 (/review)",
|
|
823
|
+
"5. 查看极客技巧速查 (/tips)",
|
|
824
|
+
"6. 切换中文工程提示词开关 (Toggle Chinese Prompt)",
|
|
825
|
+
"7. 探测网络延迟 (Ping API)",
|
|
826
|
+
"8. 查询用量与到期时间 (Account Info)",
|
|
827
|
+
]);
|
|
828
|
+
|
|
829
|
+
if (!choice) return;
|
|
830
|
+
|
|
831
|
+
if (choice.startsWith("1")) {
|
|
832
|
+
await handleModelSwitch(pi, ctx);
|
|
833
|
+
} else if (choice.startsWith("2")) {
|
|
834
|
+
await handleUndoCommand(ctx, undoStack, refreshStatus);
|
|
835
|
+
} else if (choice.startsWith("3")) {
|
|
836
|
+
await handleCommitCommand(pi, ctx, "");
|
|
837
|
+
} else if (choice.startsWith("4")) {
|
|
838
|
+
await handleReviewCommand(pi, ctx, "");
|
|
839
|
+
} else if (choice.startsWith("5")) {
|
|
840
|
+
await ctx.ui.select(
|
|
841
|
+
"Monk-Pi 实用极客技巧清单:",
|
|
842
|
+
PRO_TIPS.map((t, i) => `${i + 1}. ${t}`)
|
|
843
|
+
);
|
|
844
|
+
} else if (choice.startsWith("6")) {
|
|
845
|
+
const nowEnabled = toggleCnPrompt();
|
|
846
|
+
const state = nowEnabled ? "已启用 (零废话·行动优先)" : "已关闭 (恢复原生)";
|
|
847
|
+
ctx.ui.notify(`中文工程系统提示词: ${state}`, "info");
|
|
848
|
+
} else if (choice.startsWith("7")) {
|
|
849
|
+
await handlePing(ctx);
|
|
850
|
+
} else if (choice.startsWith("8")) {
|
|
851
|
+
ctx.ui.notify("请在浏览器打开 https://monk.party/account/ 查看用量与剩余天数", "info");
|
|
852
|
+
}
|
|
853
|
+
}
|