chatccc 0.2.121 → 0.2.122
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 +22 -14
- package/package.json +63 -63
- package/src/__tests__/claude-adapter.test.ts +441 -441
- package/src/__tests__/orchestrator.test.ts +43 -1
- package/src/adapters/adapter-interface.ts +7 -0
- package/src/adapters/claude-adapter.ts +6 -2
- package/src/adapters/cursor-adapter.ts +11 -1
- package/src/adapters/resource-monitor.ts +140 -140
- package/src/cards.ts +2 -0
- package/src/config.ts +18 -0
- package/src/orchestrator.ts +214 -15
- package/src/session.ts +4 -1
|
@@ -169,6 +169,48 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
169
169
|
expect(platform.sendText).toHaveBeenCalledWith("wx-chat", "生成中...");
|
|
170
170
|
});
|
|
171
171
|
|
|
172
|
+
it.each([
|
|
173
|
+
{ tool: "claude" as const, sessionId: "sid-claude", text: "/ask 只回答不要改文件" },
|
|
174
|
+
{ tool: "cursor" as const, sessionId: "sid-cursor", text: "/plan 先给我计划" },
|
|
175
|
+
])("passes unsupported mode commands through as raw prompts for $tool", async ({ tool, sessionId, text }) => {
|
|
176
|
+
const platform = mockPlatform();
|
|
177
|
+
const prompt = vi.fn(async function* (_sessionId: string, userText: string) {
|
|
178
|
+
yield {
|
|
179
|
+
type: "assistant" as const,
|
|
180
|
+
blocks: [{ type: "text" as const, text: `收到: ${userText}` }],
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
_setAdapterForToolForTest(tool, {
|
|
184
|
+
displayName: tool,
|
|
185
|
+
sessionDescPrefix: `${tool} Session:`,
|
|
186
|
+
createSession: vi.fn(async () => ({ sessionId })),
|
|
187
|
+
prompt,
|
|
188
|
+
getSessionInfo: async (sessionId: string): Promise<SessionInfo> => ({
|
|
189
|
+
sessionId,
|
|
190
|
+
cwd: "F:\\repo",
|
|
191
|
+
}),
|
|
192
|
+
closeSession: async () => {},
|
|
193
|
+
});
|
|
194
|
+
await recordSessionRegistry({
|
|
195
|
+
chatId: "wx-chat",
|
|
196
|
+
sessionId,
|
|
197
|
+
tool,
|
|
198
|
+
chatName: "ready-session",
|
|
199
|
+
running: false,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
await handleCommand(platform, text, "wx-chat", "wx-user", Date.now(), "p2p");
|
|
203
|
+
|
|
204
|
+
expect(platform.sendText).not.toHaveBeenCalledWith("wx-chat", expect.stringContaining("仅在以下 Agent 会话中可用"));
|
|
205
|
+
expect(prompt).toHaveBeenCalledWith(
|
|
206
|
+
sessionId,
|
|
207
|
+
expect.stringContaining(text),
|
|
208
|
+
"F:\\repo",
|
|
209
|
+
expect.any(AbortSignal),
|
|
210
|
+
expect.objectContaining({ permissionMode: undefined }),
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
|
|
172
214
|
it("cleans stale Feishu p2p binding, creates a group, and sends the private message as first prompt", async () => {
|
|
173
215
|
const platform = mockPlatform("feishu");
|
|
174
216
|
const prompt = vi.fn(async function* (_sessionId: string, userText: string) {
|
|
@@ -177,7 +219,7 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
177
219
|
blocks: [{ type: "text" as const, text: `收到: ${userText}` }],
|
|
178
220
|
};
|
|
179
221
|
});
|
|
180
|
-
_setAdapterForToolForTest("
|
|
222
|
+
_setAdapterForToolForTest("claude", {
|
|
181
223
|
displayName: "Claude",
|
|
182
224
|
sessionDescPrefix: "Claude Session:",
|
|
183
225
|
createSession: vi.fn(async () => ({ sessionId: "sid-feishu-new" })),
|
|
@@ -128,6 +128,13 @@ export interface ToolPromptOptions {
|
|
|
128
128
|
onProcessStart?: (info: ToolProcessInfo) => void;
|
|
129
129
|
/** Called when the adapter leaves the prompt process scope normally or by abort. */
|
|
130
130
|
onProcessExit?: (info: ToolProcessInfo) => void;
|
|
131
|
+
/**
|
|
132
|
+
* 权限模式覆盖:
|
|
133
|
+
* - "plan": Claude Code --permission-mode plan(只读/规划)
|
|
134
|
+
* - "ask": Cursor Agent --mode ask(只读/问答)
|
|
135
|
+
* 不传或为 undefined 时使用适配器默认行为(正常编辑模式)。
|
|
136
|
+
*/
|
|
137
|
+
permissionMode?: "plan" | "ask";
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
// ---------------------------------------------------------------------------
|
|
@@ -274,14 +274,17 @@ function buildCliArgs(
|
|
|
274
274
|
isEmpty: (value: string) => boolean,
|
|
275
275
|
mcpConfigJson: string | null,
|
|
276
276
|
extraArgs: string[],
|
|
277
|
+
permissionMode?: "plan" | "ask",
|
|
277
278
|
): string[] {
|
|
279
|
+
const permMode = permissionMode === "plan" ? "plan" : permissionMode === "ask" ? "default" : "bypassPermissions";
|
|
280
|
+
const skipPermissions = permissionMode !== "plan" && permissionMode !== "ask";
|
|
278
281
|
const args = [
|
|
279
282
|
"-p",
|
|
280
283
|
"--output-format", "stream-json",
|
|
281
284
|
"--verbose",
|
|
282
285
|
"--setting-sources", "user,project,local",
|
|
283
|
-
"--permission-mode",
|
|
284
|
-
"--dangerously-skip-permissions",
|
|
286
|
+
"--permission-mode", permMode,
|
|
287
|
+
...(skipPermissions ? ["--dangerously-skip-permissions"] : []),
|
|
285
288
|
"--settings", "{\"maxTurns\":0}",
|
|
286
289
|
];
|
|
287
290
|
|
|
@@ -432,6 +435,7 @@ class ClaudeAdapter implements ToolAdapter {
|
|
|
432
435
|
const args = buildCliArgs(
|
|
433
436
|
this.model, this.effort, this.isEmpty, mcpConfigJson,
|
|
434
437
|
["--resume", sessionId, "--input-format", "stream-json", "--replay-user-messages"],
|
|
438
|
+
options?.permissionMode,
|
|
435
439
|
);
|
|
436
440
|
|
|
437
441
|
const proc = spawnCli(args, cwd, env, true);
|
|
@@ -261,8 +261,18 @@ function spawnAgent(
|
|
|
261
261
|
cwd?: string,
|
|
262
262
|
stdinText?: string,
|
|
263
263
|
modelOverride?: string,
|
|
264
|
+
permissionMode?: "plan" | "ask",
|
|
264
265
|
): ChildProcess {
|
|
265
266
|
let allArgs = [...CURSOR_AGENT_ARGS, ...extraArgs];
|
|
267
|
+
if (permissionMode === "ask") {
|
|
268
|
+
// 替换已有的 --mode 或追加
|
|
269
|
+
const modeIdx = allArgs.findIndex((a, i) => a === "--mode" && i + 1 < allArgs.length);
|
|
270
|
+
if (modeIdx >= 0) {
|
|
271
|
+
allArgs[modeIdx + 1] = "ask";
|
|
272
|
+
} else {
|
|
273
|
+
allArgs.push("--mode", "ask");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
266
276
|
if (modelOverride) {
|
|
267
277
|
// 替换全局 --model 为 per-session override
|
|
268
278
|
const modelIdx = allArgs.findIndex((a, i) => a === "--model" && i + 1 < allArgs.length);
|
|
@@ -370,7 +380,7 @@ class CursorAdapter implements ToolAdapter {
|
|
|
370
380
|
options?: ToolPromptOptions,
|
|
371
381
|
): AsyncIterable<UnifiedStreamMessage> {
|
|
372
382
|
console.log(`[Cursor debug] prompt start: sessionId=${sessionId}, cwd=${cwd}, userTextLen=${userText.length}`);
|
|
373
|
-
const proc = spawnAgent(["--resume", sessionId], cwd, userText, this.modelOverride);
|
|
383
|
+
const proc = spawnAgent(["--resume", sessionId], cwd, userText, this.modelOverride, options?.permissionMode);
|
|
374
384
|
this.activeProcs.add(proc);
|
|
375
385
|
if (proc.pid !== undefined) options?.onProcessStart?.({ pid: proc.pid });
|
|
376
386
|
|
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
// =============================================================================
|
|
2
|
-
// resource-monitor.ts — CLI 进程资源监控(CPU + 内存)
|
|
3
|
-
// =============================================================================
|
|
4
|
-
// 对所有 chatccc 启动的 CLI 进程持续监控 CPU 和内存占用。
|
|
5
|
-
// 若连续 3 分钟两项指标均无变化,判定为僵死,发出 "stuck" 事件。
|
|
6
|
-
// =============================================================================
|
|
7
|
-
|
|
8
|
-
import { exec } from "node:child_process";
|
|
9
|
-
import { EventEmitter } from "node:events";
|
|
10
|
-
|
|
11
|
-
const CHECK_INTERVAL_MS = 30_000; // 30 秒检查一次
|
|
12
|
-
const STUCK_THRESHOLD = 6; // 连续 6 次无变化 = 3 分钟
|
|
13
|
-
|
|
14
|
-
interface ProcessSnapshot {
|
|
15
|
-
cpu: number;
|
|
16
|
-
memory: number;
|
|
17
|
-
unchangedCount: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface TrackedProcess {
|
|
21
|
-
pid: number;
|
|
22
|
-
sessionId: string;
|
|
23
|
-
snapshot: ProcessSnapshot;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const resourceMonitor = new EventEmitter();
|
|
27
|
-
|
|
28
|
-
const tracked = new Map<number, TrackedProcess>();
|
|
29
|
-
|
|
30
|
-
let timer: ReturnType<typeof setInterval> | null = null;
|
|
31
|
-
|
|
32
|
-
function startIfNeeded(): void {
|
|
33
|
-
if (timer) return;
|
|
34
|
-
timer = setInterval(checkAll, CHECK_INTERVAL_MS);
|
|
35
|
-
timer.unref?.();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function stopIfIdle(): void {
|
|
39
|
-
if (tracked.size > 0) return;
|
|
40
|
-
if (timer) { clearInterval(timer); timer = null; }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function registerProcess(pid: number, sessionId: string): void {
|
|
44
|
-
tracked.set(pid, { pid, sessionId, snapshot: { cpu: -1, memory: -1, unchangedCount: 0 } });
|
|
45
|
-
startIfNeeded();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function unregisterProcess(pid: number): void {
|
|
49
|
-
tracked.delete(pid);
|
|
50
|
-
stopIfIdle();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
// 批量查询进程指标(Windows PowerShell)
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
function execPowerShell(script: string, timeoutMs: number): Promise<string> {
|
|
58
|
-
return new Promise((resolve, reject) => {
|
|
59
|
-
exec(
|
|
60
|
-
`powershell -NoProfile -Command "${script}"`,
|
|
61
|
-
{ timeout: timeoutMs, windowsHide: true },
|
|
62
|
-
(err, stdout) => {
|
|
63
|
-
if (err) reject(err);
|
|
64
|
-
else resolve(stdout);
|
|
65
|
-
},
|
|
66
|
-
);
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function getProcessMetrics(pids: number[]): Promise<Map<number, { cpu: number; memory: number }>> {
|
|
71
|
-
const result = new Map<number, { cpu: number; memory: number }>();
|
|
72
|
-
if (pids.length === 0) return result;
|
|
73
|
-
|
|
74
|
-
const psScript = `Get-Process -Id ${pids.join(",")} -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Id)|$($_.CPU)|$($_.WorkingSet64)" }`;
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
const stdout = await execPowerShell(psScript, 10_000);
|
|
78
|
-
for (const line of stdout.trim().split(/\r?\n/)) {
|
|
79
|
-
const trimmed = line.trim();
|
|
80
|
-
if (!trimmed) continue;
|
|
81
|
-
const [idStr, cpuStr, memStr] = trimmed.split("|");
|
|
82
|
-
const id = parseInt(idStr, 10);
|
|
83
|
-
const cpu = parseFloat(cpuStr);
|
|
84
|
-
const memory = parseInt(memStr, 10);
|
|
85
|
-
if (!isNaN(id) && !isNaN(cpu) && !isNaN(memory)) {
|
|
86
|
-
result.set(id, { cpu, memory });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
} catch {
|
|
90
|
-
// PowerShell 查询失败时跳过本轮,下次重试
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return result;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// 定时检查
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
|
|
100
|
-
async function checkAll(): Promise<void> {
|
|
101
|
-
if (tracked.size === 0) return;
|
|
102
|
-
|
|
103
|
-
const pids = [...tracked.keys()];
|
|
104
|
-
const metrics = await getProcessMetrics(pids);
|
|
105
|
-
|
|
106
|
-
for (const [pid, tp] of tracked) {
|
|
107
|
-
const m = metrics.get(pid);
|
|
108
|
-
if (!m) {
|
|
109
|
-
// 进程已不存在,停止追踪
|
|
110
|
-
tracked.delete(pid);
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const prev = tp.snapshot;
|
|
115
|
-
const cpuChanged = m.cpu !== prev.cpu;
|
|
116
|
-
// 内存允许 ±1% 波动,避免正常抖动触发误判
|
|
117
|
-
const memTolerance = prev.memory > 0 ? Math.max(prev.memory * 0.01, 1024 * 1024) : 1024 * 1024;
|
|
118
|
-
const memChanged = Math.abs(m.memory - prev.memory) > memTolerance;
|
|
119
|
-
|
|
120
|
-
if (!cpuChanged && !memChanged) {
|
|
121
|
-
tp.snapshot.unchangedCount++;
|
|
122
|
-
if (tp.snapshot.unchangedCount >= STUCK_THRESHOLD) {
|
|
123
|
-
const idleMinutes = Math.round(
|
|
124
|
-
(tp.snapshot.unchangedCount * CHECK_INTERVAL_MS) / 60_000,
|
|
125
|
-
);
|
|
126
|
-
resourceMonitor.emit("stuck", {
|
|
127
|
-
pid: tp.pid,
|
|
128
|
-
sessionId: tp.sessionId,
|
|
129
|
-
idleMinutes,
|
|
130
|
-
});
|
|
131
|
-
tracked.delete(pid);
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
tp.snapshot.unchangedCount = 0;
|
|
135
|
-
}
|
|
136
|
-
tp.snapshot.cpu = m.cpu;
|
|
137
|
-
tp.snapshot.memory = m.memory;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
stopIfIdle();
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// resource-monitor.ts — CLI 进程资源监控(CPU + 内存)
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// 对所有 chatccc 启动的 CLI 进程持续监控 CPU 和内存占用。
|
|
5
|
+
// 若连续 3 分钟两项指标均无变化,判定为僵死,发出 "stuck" 事件。
|
|
6
|
+
// =============================================================================
|
|
7
|
+
|
|
8
|
+
import { exec } from "node:child_process";
|
|
9
|
+
import { EventEmitter } from "node:events";
|
|
10
|
+
|
|
11
|
+
const CHECK_INTERVAL_MS = 30_000; // 30 秒检查一次
|
|
12
|
+
const STUCK_THRESHOLD = 6; // 连续 6 次无变化 = 3 分钟
|
|
13
|
+
|
|
14
|
+
interface ProcessSnapshot {
|
|
15
|
+
cpu: number;
|
|
16
|
+
memory: number;
|
|
17
|
+
unchangedCount: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface TrackedProcess {
|
|
21
|
+
pid: number;
|
|
22
|
+
sessionId: string;
|
|
23
|
+
snapshot: ProcessSnapshot;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const resourceMonitor = new EventEmitter();
|
|
27
|
+
|
|
28
|
+
const tracked = new Map<number, TrackedProcess>();
|
|
29
|
+
|
|
30
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
31
|
+
|
|
32
|
+
function startIfNeeded(): void {
|
|
33
|
+
if (timer) return;
|
|
34
|
+
timer = setInterval(checkAll, CHECK_INTERVAL_MS);
|
|
35
|
+
timer.unref?.();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function stopIfIdle(): void {
|
|
39
|
+
if (tracked.size > 0) return;
|
|
40
|
+
if (timer) { clearInterval(timer); timer = null; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function registerProcess(pid: number, sessionId: string): void {
|
|
44
|
+
tracked.set(pid, { pid, sessionId, snapshot: { cpu: -1, memory: -1, unchangedCount: 0 } });
|
|
45
|
+
startIfNeeded();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function unregisterProcess(pid: number): void {
|
|
49
|
+
tracked.delete(pid);
|
|
50
|
+
stopIfIdle();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// 批量查询进程指标(Windows PowerShell)
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
function execPowerShell(script: string, timeoutMs: number): Promise<string> {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
exec(
|
|
60
|
+
`powershell -NoProfile -Command "${script}"`,
|
|
61
|
+
{ timeout: timeoutMs, windowsHide: true },
|
|
62
|
+
(err, stdout) => {
|
|
63
|
+
if (err) reject(err);
|
|
64
|
+
else resolve(stdout);
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function getProcessMetrics(pids: number[]): Promise<Map<number, { cpu: number; memory: number }>> {
|
|
71
|
+
const result = new Map<number, { cpu: number; memory: number }>();
|
|
72
|
+
if (pids.length === 0) return result;
|
|
73
|
+
|
|
74
|
+
const psScript = `Get-Process -Id ${pids.join(",")} -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Id)|$($_.CPU)|$($_.WorkingSet64)" }`;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const stdout = await execPowerShell(psScript, 10_000);
|
|
78
|
+
for (const line of stdout.trim().split(/\r?\n/)) {
|
|
79
|
+
const trimmed = line.trim();
|
|
80
|
+
if (!trimmed) continue;
|
|
81
|
+
const [idStr, cpuStr, memStr] = trimmed.split("|");
|
|
82
|
+
const id = parseInt(idStr, 10);
|
|
83
|
+
const cpu = parseFloat(cpuStr);
|
|
84
|
+
const memory = parseInt(memStr, 10);
|
|
85
|
+
if (!isNaN(id) && !isNaN(cpu) && !isNaN(memory)) {
|
|
86
|
+
result.set(id, { cpu, memory });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// PowerShell 查询失败时跳过本轮,下次重试
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// 定时检查
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
async function checkAll(): Promise<void> {
|
|
101
|
+
if (tracked.size === 0) return;
|
|
102
|
+
|
|
103
|
+
const pids = [...tracked.keys()];
|
|
104
|
+
const metrics = await getProcessMetrics(pids);
|
|
105
|
+
|
|
106
|
+
for (const [pid, tp] of tracked) {
|
|
107
|
+
const m = metrics.get(pid);
|
|
108
|
+
if (!m) {
|
|
109
|
+
// 进程已不存在,停止追踪
|
|
110
|
+
tracked.delete(pid);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const prev = tp.snapshot;
|
|
115
|
+
const cpuChanged = m.cpu !== prev.cpu;
|
|
116
|
+
// 内存允许 ±1% 波动,避免正常抖动触发误判
|
|
117
|
+
const memTolerance = prev.memory > 0 ? Math.max(prev.memory * 0.01, 1024 * 1024) : 1024 * 1024;
|
|
118
|
+
const memChanged = Math.abs(m.memory - prev.memory) > memTolerance;
|
|
119
|
+
|
|
120
|
+
if (!cpuChanged && !memChanged) {
|
|
121
|
+
tp.snapshot.unchangedCount++;
|
|
122
|
+
if (tp.snapshot.unchangedCount >= STUCK_THRESHOLD) {
|
|
123
|
+
const idleMinutes = Math.round(
|
|
124
|
+
(tp.snapshot.unchangedCount * CHECK_INTERVAL_MS) / 60_000,
|
|
125
|
+
);
|
|
126
|
+
resourceMonitor.emit("stuck", {
|
|
127
|
+
pid: tp.pid,
|
|
128
|
+
sessionId: tp.sessionId,
|
|
129
|
+
idleMinutes,
|
|
130
|
+
});
|
|
131
|
+
tracked.delete(pid);
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
tp.snapshot.unchangedCount = 0;
|
|
135
|
+
}
|
|
136
|
+
tp.snapshot.cpu = m.cpu;
|
|
137
|
+
tp.snapshot.memory = m.memory;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
stopIfIdle();
|
|
141
141
|
}
|
package/src/cards.ts
CHANGED
|
@@ -131,6 +131,8 @@ export function buildHelpCard(
|
|
|
131
131
|
"发送 **/new cursor** 创建新 Cursor 会话",
|
|
132
132
|
"发送 **/new codex** 创建新 Codex 会话",
|
|
133
133
|
"发送 **/newh** 重置当前会话(沿用当前工作目录,不切换)",
|
|
134
|
+
"发送 **/plan <消息>** 在 Claude 会话中以只读规划模式提问",
|
|
135
|
+
"发送 **/ask <消息>** 在 Cursor 会话中以只读问答模式提问",
|
|
134
136
|
].join("\n");
|
|
135
137
|
return JSON.stringify({
|
|
136
138
|
config: { wide_screen_mode: true },
|
package/src/config.ts
CHANGED
|
@@ -122,6 +122,24 @@ export interface AppConfig {
|
|
|
122
122
|
export type AgentTool = "claude" | "cursor" | "codex";
|
|
123
123
|
export const AGENT_TOOLS: AgentTool[] = ["claude", "cursor", "codex"];
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Agent 专属模式指令(内部配置,非用户可配)。
|
|
127
|
+
* 映射每个 Agent 支持的特殊指令及其对应的 CLI 权限模式。
|
|
128
|
+
*
|
|
129
|
+
* - claude: /plan → --permission-mode plan(只读/规划)
|
|
130
|
+
* - cursor: /ask → --mode ask(只读/问答)
|
|
131
|
+
* - codex: 暂无原生只读模式,不注册任何模式指令
|
|
132
|
+
*/
|
|
133
|
+
export const AGENT_MODE_COMMANDS: Record<AgentTool, { command: string; mode: "plan" | "ask"; description: string }[]> = {
|
|
134
|
+
claude: [
|
|
135
|
+
{ command: "/plan", mode: "plan", description: "以只读规划模式提问(Claude 专属)" },
|
|
136
|
+
],
|
|
137
|
+
cursor: [
|
|
138
|
+
{ command: "/ask", mode: "ask", description: "以只读问答模式提问(Cursor 专属)" },
|
|
139
|
+
],
|
|
140
|
+
codex: [],
|
|
141
|
+
};
|
|
142
|
+
|
|
125
143
|
/** 获取指定 agent 配置中所有模型相关的值(最多 100 个,去重) */
|
|
126
144
|
export function getAllModelsForTool(tool: AgentTool, cfg: AppConfig = config): string[] {
|
|
127
145
|
const seen = new Set<string>();
|