chatccc 0.2.123 → 0.2.125
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 +14 -22
- package/package.json +1 -1
- package/src/__tests__/orchestrator.test.ts +1 -43
- package/src/adapters/adapter-interface.ts +0 -7
- package/src/adapters/claude-adapter.ts +3 -7
- package/src/adapters/cursor-adapter.ts +1 -11
- package/src/cards.ts +0 -2
- package/src/config.ts +0 -18
- package/src/orchestrator.ts +15 -214
- package/src/session-chat-binding.ts +0 -2
- package/src/session.ts +2 -12
package/README.md
CHANGED
|
@@ -296,28 +296,20 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
296
296
|
|
|
297
297
|
## 可用指令
|
|
298
298
|
|
|
299
|
-
| 指令 | 作用 |
|
|
300
|
-
| --- | --- |
|
|
301
|
-
| `/new` | 使用默认 Agent 创建新会话 |
|
|
302
|
-
| `/new claude` | 创建 Claude Code 会话 |
|
|
303
|
-
| `/new cursor` | 创建 Cursor 会话 |
|
|
304
|
-
| `/new codex` | 创建 Codex 会话 |
|
|
305
|
-
| `/newh` | 重置当前会话,保留工作目录 |
|
|
306
|
-
| `/
|
|
307
|
-
| `/
|
|
308
|
-
| `/state` | 查看当前会话状态 |
|
|
309
|
-
| `/
|
|
310
|
-
| `/
|
|
311
|
-
| `/
|
|
312
|
-
| `/
|
|
313
|
-
| `/model clear` | 清除模型覆盖,恢复默认 | 全部 |
|
|
314
|
-
| `/cd` | 查看当前工作目录(含最近目录快捷按钮) | 全部 |
|
|
315
|
-
| `/cd <路径>` | 设置新建会话的默认工作目录 | 全部 |
|
|
316
|
-
| `/plan <消息>` | Claude Code 使用 CLI 只读/规划模式;其他 Agent 会收到包含 `/plan` 前缀的原始消息,可能通过消息内容自行支持 | Claude Code(CLI 模式)/ 全部(消息透传) |
|
|
317
|
-
| `/ask <消息>` | Cursor 使用 CLI 只读/问答模式;其他 Agent 会收到包含 `/ask` 前缀的原始消息,可能通过消息内容自行支持 | Cursor(CLI 模式)/ 全部(消息透传) |
|
|
318
|
-
| `/git <子命令>` | 在当前会话工作目录执行 git 命令并回传输出 | 全部 |
|
|
319
|
-
| `/deleteg` | 解散当前群聊,保留 Agent 会话 | 全部(仅群聊) |
|
|
320
|
-
| `/restart` | 重启 ChatCCC 服务进程 | 全部 |
|
|
299
|
+
| 指令 | 作用 |
|
|
300
|
+
| --- | --- |
|
|
301
|
+
| `/new` | 使用默认 Agent 创建新会话 |
|
|
302
|
+
| `/new claude` | 创建 Claude Code 会话 |
|
|
303
|
+
| `/new cursor` | 创建 Cursor 会话 |
|
|
304
|
+
| `/new codex` | 创建 Codex 会话 |
|
|
305
|
+
| `/newh` | 重置当前会话,保留工作目录 |
|
|
306
|
+
| `/model` | 查看或切换当前会话的模型 |
|
|
307
|
+
| `/stop` | 停止当前回复 |
|
|
308
|
+
| `/state` | 查看当前会话状态 |
|
|
309
|
+
| `/cd` | 查看或设置当前会话工作目录 |
|
|
310
|
+
| `/sessions` | 查看所有会话状态 |
|
|
311
|
+
| `/git <子命令>` | 在当前会话工作目录执行 `git ...` 并回传输出 |
|
|
312
|
+
| `/restart` | 重启机器人进程 |
|
|
321
313
|
|
|
322
314
|
> **模型切换**:`/model` 查看可选模型清单,`/model <名称>` 模糊匹配切换,`/model clear` 恢复默认。当前仅 Claude Code 支持模型切换(需同时填写 `claude.model` 和 `claude.subagentModel`),Cursor / Codex 切换正在开发中。
|
|
323
315
|
|
package/package.json
CHANGED
|
@@ -169,48 +169,6 @@ 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
|
-
|
|
214
172
|
it("cleans stale Feishu p2p binding, creates a group, and sends the private message as first prompt", async () => {
|
|
215
173
|
const platform = mockPlatform("feishu");
|
|
216
174
|
const prompt = vi.fn(async function* (_sessionId: string, userText: string) {
|
|
@@ -219,7 +177,7 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
219
177
|
blocks: [{ type: "text" as const, text: `收到: ${userText}` }],
|
|
220
178
|
};
|
|
221
179
|
});
|
|
222
|
-
_setAdapterForToolForTest("
|
|
180
|
+
_setAdapterForToolForTest("cursor", {
|
|
223
181
|
displayName: "Claude",
|
|
224
182
|
sessionDescPrefix: "Claude Session:",
|
|
225
183
|
createSession: vi.fn(async () => ({ sessionId: "sid-feishu-new" })),
|
|
@@ -128,13 +128,6 @@ 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";
|
|
138
131
|
}
|
|
139
132
|
|
|
140
133
|
// ---------------------------------------------------------------------------
|
|
@@ -274,18 +274,15 @@ function buildCliArgs(
|
|
|
274
274
|
isEmpty: (value: string) => boolean,
|
|
275
275
|
mcpConfigJson: string | null,
|
|
276
276
|
extraArgs: string[],
|
|
277
|
-
permissionMode?: "plan" | "ask",
|
|
278
277
|
): string[] {
|
|
279
|
-
const permMode = permissionMode === "plan" ? "plan" : permissionMode === "ask" ? "default" : "bypassPermissions";
|
|
280
|
-
const skipPermissions = permissionMode !== "plan" && permissionMode !== "ask";
|
|
281
278
|
const args = [
|
|
282
279
|
"-p",
|
|
283
280
|
"--output-format", "stream-json",
|
|
284
281
|
"--verbose",
|
|
285
282
|
"--setting-sources", "user,project,local",
|
|
286
|
-
"--permission-mode",
|
|
287
|
-
|
|
288
|
-
"--settings", "{\"maxTurns\":0
|
|
283
|
+
"--permission-mode", "bypassPermissions",
|
|
284
|
+
"--dangerously-skip-permissions",
|
|
285
|
+
"--settings", "{\"maxTurns\":0}",
|
|
289
286
|
];
|
|
290
287
|
|
|
291
288
|
if (!isEmpty(model)) args.push("--model", model);
|
|
@@ -435,7 +432,6 @@ class ClaudeAdapter implements ToolAdapter {
|
|
|
435
432
|
const args = buildCliArgs(
|
|
436
433
|
this.model, this.effort, this.isEmpty, mcpConfigJson,
|
|
437
434
|
["--resume", sessionId, "--input-format", "stream-json", "--replay-user-messages"],
|
|
438
|
-
options?.permissionMode,
|
|
439
435
|
);
|
|
440
436
|
|
|
441
437
|
const proc = spawnCli(args, cwd, env, true);
|
|
@@ -261,18 +261,8 @@ function spawnAgent(
|
|
|
261
261
|
cwd?: string,
|
|
262
262
|
stdinText?: string,
|
|
263
263
|
modelOverride?: string,
|
|
264
|
-
permissionMode?: "plan" | "ask",
|
|
265
264
|
): ChildProcess {
|
|
266
265
|
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
|
-
}
|
|
276
266
|
if (modelOverride) {
|
|
277
267
|
// 替换全局 --model 为 per-session override
|
|
278
268
|
const modelIdx = allArgs.findIndex((a, i) => a === "--model" && i + 1 < allArgs.length);
|
|
@@ -380,7 +370,7 @@ class CursorAdapter implements ToolAdapter {
|
|
|
380
370
|
options?: ToolPromptOptions,
|
|
381
371
|
): AsyncIterable<UnifiedStreamMessage> {
|
|
382
372
|
console.log(`[Cursor debug] prompt start: sessionId=${sessionId}, cwd=${cwd}, userTextLen=${userText.length}`);
|
|
383
|
-
const proc = spawnAgent(["--resume", sessionId], cwd, userText, this.modelOverride
|
|
373
|
+
const proc = spawnAgent(["--resume", sessionId], cwd, userText, this.modelOverride);
|
|
384
374
|
this.activeProcs.add(proc);
|
|
385
375
|
if (proc.pid !== undefined) options?.onProcessStart?.({ pid: proc.pid });
|
|
386
376
|
|
package/src/cards.ts
CHANGED
|
@@ -131,8 +131,6 @@ export function buildHelpCard(
|
|
|
131
131
|
"发送 **/new cursor** 创建新 Cursor 会话",
|
|
132
132
|
"发送 **/new codex** 创建新 Codex 会话",
|
|
133
133
|
"发送 **/newh** 重置当前会话(沿用当前工作目录,不切换)",
|
|
134
|
-
"发送 **/plan <消息>** 在 Claude 会话中以只读规划模式提问",
|
|
135
|
-
"发送 **/ask <消息>** 在 Cursor 会话中以只读问答模式提问",
|
|
136
134
|
].join("\n");
|
|
137
135
|
return JSON.stringify({
|
|
138
136
|
config: { wide_screen_mode: true },
|
package/src/config.ts
CHANGED
|
@@ -122,24 +122,6 @@ 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
|
-
|
|
143
125
|
/** 获取指定 agent 配置中所有模型相关的值(最多 100 个,去重) */
|
|
144
126
|
export function getAllModelsForTool(tool: AgentTool, cfg: AppConfig = config): string[] {
|
|
145
127
|
const seen = new Set<string>();
|
package/src/orchestrator.ts
CHANGED
|
@@ -19,8 +19,6 @@ import {
|
|
|
19
19
|
CONFIG_FILE,
|
|
20
20
|
GIT_TIMEOUT_MS,
|
|
21
21
|
PROJECT_ROOT,
|
|
22
|
-
AGENT_MODE_COMMANDS,
|
|
23
|
-
AGENT_TOOLS,
|
|
24
22
|
anthropicConfigDisplay,
|
|
25
23
|
config,
|
|
26
24
|
fileLog,
|
|
@@ -127,85 +125,6 @@ function isNonWechatP2p(platform: PlatformAdapter, chatType: string): boolean {
|
|
|
127
125
|
return chatType === "p2p" && platform.kind !== "wechat";
|
|
128
126
|
}
|
|
129
127
|
|
|
130
|
-
/** 根据 Agent 类型构建欢迎卡片中的指令说明 */
|
|
131
|
-
function buildWelcomeCommands(tool: string): string {
|
|
132
|
-
const lines = [
|
|
133
|
-
"发送 **/cd** 切换新建会话的默认目录。",
|
|
134
|
-
"发送 **/model** 查看或切换当前会话的模型。",
|
|
135
|
-
"发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。",
|
|
136
|
-
"发送 **/sessions** 查看所有会话状态。",
|
|
137
|
-
];
|
|
138
|
-
const modeCmds = AGENT_MODE_COMMANDS[tool as AgentTool] ?? [];
|
|
139
|
-
for (const mc of modeCmds) {
|
|
140
|
-
lines.push(`发送 **${mc.command} <消息>** ${mc.description}。`);
|
|
141
|
-
}
|
|
142
|
-
return lines.join("\n");
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** 根据指令名从 AGENT_MODE_COMMANDS 查找对应的 mode 和支持的 agent 列表 */
|
|
146
|
-
function resolveModeCommand(cmd: string): { mode: "plan" | "ask"; supportedTools: string[] } | null {
|
|
147
|
-
const supportedTools: string[] = [];
|
|
148
|
-
let mode: "plan" | "ask" | null = null;
|
|
149
|
-
for (const tool of AGENT_TOOLS) {
|
|
150
|
-
for (const mc of AGENT_MODE_COMMANDS[tool]) {
|
|
151
|
-
if (mc.command === cmd) {
|
|
152
|
-
supportedTools.push(tool);
|
|
153
|
-
mode = mc.mode;
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return mode ? { mode, supportedTools } : null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** 如果当前群名是未命名会话("新会话" 或 "新会话-xxx"),用消息内容前缀重命名 */
|
|
162
|
-
async function renameUntitledSession(
|
|
163
|
-
platform: PlatformAdapter,
|
|
164
|
-
chatId: string,
|
|
165
|
-
chatType: string,
|
|
166
|
-
sessionId: string,
|
|
167
|
-
tool: string,
|
|
168
|
-
namePrefix: string,
|
|
169
|
-
description: string | undefined,
|
|
170
|
-
chatInfo: { name: string } | undefined,
|
|
171
|
-
): Promise<void> {
|
|
172
|
-
const MAX_PREFIX = 10;
|
|
173
|
-
const prefix = namePrefix.slice(0, MAX_PREFIX);
|
|
174
|
-
|
|
175
|
-
if (chatType !== "p2p" && chatInfo && isUntitledSessionChatName(chatInfo.name)) {
|
|
176
|
-
const adapter = getAdapterForTool(tool, sessionId);
|
|
177
|
-
const info = await adapter.getSessionInfo(sessionId).catch(() => undefined);
|
|
178
|
-
const sessionCwd = info?.cwd ?? (await getDefaultCwd(chatId));
|
|
179
|
-
const newName = sessionChatName(prefix, sessionCwd);
|
|
180
|
-
try {
|
|
181
|
-
await platform.updateChatInfo(chatId, newName, description!);
|
|
182
|
-
console.log(`[${ts()}] [RENAME] First message → group renamed to "${newName}"`);
|
|
183
|
-
await recordSessionRegistry({ chatId, sessionId, tool, chatName: newName }).catch(() => {});
|
|
184
|
-
await saveSessionTool(sessionId, tool, newName).catch(() => {});
|
|
185
|
-
} catch (err) {
|
|
186
|
-
console.error(`[${ts()}] [RENAME] Failed: ${(err as Error).message}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (chatType === "p2p" && platform.kind === "wechat") {
|
|
191
|
-
try {
|
|
192
|
-
const reg = await loadSessionRegistryForBinding();
|
|
193
|
-
const rec = reg[chatId];
|
|
194
|
-
if (rec && rec.sessionId === sessionId && isUntitledSessionChatName(rec.chatName ?? "")) {
|
|
195
|
-
const adapter = getAdapterForTool(tool, sessionId);
|
|
196
|
-
const info = await adapter.getSessionInfo(sessionId).catch(() => undefined);
|
|
197
|
-
const sessionCwd = info?.cwd ?? (await getDefaultCwd(chatId));
|
|
198
|
-
const newName2 = sessionChatName(prefix, sessionCwd);
|
|
199
|
-
await recordSessionRegistry({ chatId, sessionId, tool, chatName: newName2 }).catch(() => {});
|
|
200
|
-
await saveSessionTool(sessionId, tool, newName2).catch(() => {});
|
|
201
|
-
console.log(`[${ts()}] [RENAME] WeChat P2P → "${newName2}"`);
|
|
202
|
-
}
|
|
203
|
-
} catch (err) {
|
|
204
|
-
console.error(`[${ts()}] [RENAME] WeChat P2P failed: ${(err as Error).message}`);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
128
|
async function cleanupNonWechatP2pBinding(
|
|
210
129
|
platform: PlatformAdapter,
|
|
211
130
|
chatId: string,
|
|
@@ -510,7 +429,11 @@ export async function handleCommand(
|
|
|
510
429
|
`**Session ID:** ${sessionId}\n` +
|
|
511
430
|
`**工作目录:** \`${cwd}\`\n\n` +
|
|
512
431
|
`直接在这里发消息即可与 ${toolLabel} 对话。\n\n` +
|
|
513
|
-
|
|
432
|
+
`发送 **/cd** 切换新建会话的默认目录。\n` +
|
|
433
|
+
`发送 **/model** 查看或切换当前会话的模型。\n` +
|
|
434
|
+
`发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。\n` +
|
|
435
|
+
`发送 **/sessions** 查看所有会话状态。\n` +
|
|
436
|
+
`发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。`,
|
|
514
437
|
"green",
|
|
515
438
|
);
|
|
516
439
|
console.log(
|
|
@@ -592,7 +515,11 @@ export async function handleCommand(
|
|
|
592
515
|
`**Session ID:** ${sessionId}\n` +
|
|
593
516
|
`**工作目录:** \`${cwd}\`\n\n` +
|
|
594
517
|
`直接在这里发消息即可与 ${toolLabel} 对话。\n\n` +
|
|
595
|
-
|
|
518
|
+
`发送 **/cd** 切换新建会话的默认目录。\n` +
|
|
519
|
+
`发送 **/model** 查看或切换当前会话的模型。\n` +
|
|
520
|
+
`发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。\n` +
|
|
521
|
+
`发送 **/sessions** 查看所有会话状态。\n` +
|
|
522
|
+
`发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。`,
|
|
596
523
|
"green",
|
|
597
524
|
);
|
|
598
525
|
|
|
@@ -950,8 +877,9 @@ export async function handleCommand(
|
|
|
950
877
|
`会话已重置为新的 **${toolLabel}** 会话。\n\n` +
|
|
951
878
|
`**Session ID:** ${newSessionId}\n` +
|
|
952
879
|
`**工作目录:** \`${cwd}\`(沿用当前会话目录)\n\n` +
|
|
953
|
-
`直接在这里发消息即可继续对话。\n
|
|
954
|
-
|
|
880
|
+
`直接在这里发消息即可继续对话。\n` +
|
|
881
|
+
`发送 **/cd** 可切换新建会话的默认目录。\n` +
|
|
882
|
+
`发送 **/model** 查看或切换当前会话的模型。`,
|
|
955
883
|
"green",
|
|
956
884
|
);
|
|
957
885
|
|
|
@@ -1253,134 +1181,6 @@ export async function handleCommand(
|
|
|
1253
1181
|
return;
|
|
1254
1182
|
}
|
|
1255
1183
|
|
|
1256
|
-
// /plan <message> — 只读/规划模式
|
|
1257
|
-
if (textLower.startsWith("/plan ")) {
|
|
1258
|
-
const planText = text.slice(6).trim();
|
|
1259
|
-
const resolved = resolveModeCommand("/plan");
|
|
1260
|
-
if (!planText) {
|
|
1261
|
-
await platform.sendText(chatId, "用法:`/plan <消息>` — 在只读/规划模式下提问。").catch(() => {});
|
|
1262
|
-
logTrace(tid, "DONE", { outcome: "plan_no_message" });
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
if (resolved?.supportedTools.includes(descriptionTool) === true) {
|
|
1266
|
-
logTrace(tid, "BRANCH", { cmd: "/plan", sessionId, tool: descriptionTool });
|
|
1267
|
-
|
|
1268
|
-
await renameUntitledSession(
|
|
1269
|
-
platform, chatId, chatType, sessionId, descriptionTool,
|
|
1270
|
-
planText, description, chatInfo,
|
|
1271
|
-
);
|
|
1272
|
-
|
|
1273
|
-
if (isSessionRunning(sessionId)) {
|
|
1274
|
-
const queued = enqueueMessage(sessionId, {
|
|
1275
|
-
text, chatId, openId, msgTimestamp, chatType, traceId: tid,
|
|
1276
|
-
});
|
|
1277
|
-
if (queued) {
|
|
1278
|
-
logTrace(tid, "QUEUED", { sessionId, mode: "plan" });
|
|
1279
|
-
if (platform.kind === "wechat") {
|
|
1280
|
-
await platform.sendText(chatId, "当前会话正在生成中,你的消息已进入缓存队列,生成完成后会立即处理。发送 /cancel 可取消缓存。").catch(() => {});
|
|
1281
|
-
} else {
|
|
1282
|
-
await platform.sendRawCard(chatId, buildQueuedCard(planText)).catch(() => {});
|
|
1283
|
-
}
|
|
1284
|
-
} else {
|
|
1285
|
-
logTrace(tid, "QUEUE_FULL", { sessionId });
|
|
1286
|
-
if (platform.kind === "wechat") {
|
|
1287
|
-
await platform.sendText(chatId, "当前缓存队列中已有消息等待处理,请等待或发送 /stop(停止生成)或 /cancel(取消缓存)。").catch(() => {});
|
|
1288
|
-
} else {
|
|
1289
|
-
await platform.sendRawCard(chatId, buildQueueFullCard()).catch(() => {});
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
return;
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
if (shouldSendWechatProcessingAck(platform, planText.toLowerCase(), chatType)) {
|
|
1296
|
-
await platform.sendText(chatId, "生成中...").catch(() => {});
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
try {
|
|
1300
|
-
logTrace(tid, "RESUME", { sessionId, tool: descriptionTool, mode: "plan" });
|
|
1301
|
-
await resumeAndPrompt(
|
|
1302
|
-
sessionId, planText, platform, chatId, msgTimestamp,
|
|
1303
|
-
descriptionTool, tid, "plan",
|
|
1304
|
-
);
|
|
1305
|
-
logTrace(tid, "DONE", { outcome: "plan_done", sessionId });
|
|
1306
|
-
} catch (err) {
|
|
1307
|
-
logTrace(tid, "DONE", { outcome: "plan_fail", error: (err as Error).message });
|
|
1308
|
-
await platform.sendCard(
|
|
1309
|
-
chatId, "Error",
|
|
1310
|
-
`Failed to run plan mode:\n${(err as Error).message}`,
|
|
1311
|
-
"red",
|
|
1312
|
-
);
|
|
1313
|
-
}
|
|
1314
|
-
return;
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
logTrace(tid, "BRANCH", { cmd: "/plan", sessionId, tool: descriptionTool, fallback: "raw_message" });
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
// /ask <message> — 只读/问答模式
|
|
1321
|
-
if (textLower.startsWith("/ask ")) {
|
|
1322
|
-
const askText = text.slice(5).trim();
|
|
1323
|
-
const resolved = resolveModeCommand("/ask");
|
|
1324
|
-
if (!askText) {
|
|
1325
|
-
await platform.sendText(chatId, "用法:`/ask <消息>` — 在只读/问答模式下提问。").catch(() => {});
|
|
1326
|
-
logTrace(tid, "DONE", { outcome: "ask_no_message" });
|
|
1327
|
-
return;
|
|
1328
|
-
}
|
|
1329
|
-
if (resolved?.supportedTools.includes(descriptionTool) === true) {
|
|
1330
|
-
logTrace(tid, "BRANCH", { cmd: "/ask", sessionId, tool: descriptionTool });
|
|
1331
|
-
|
|
1332
|
-
await renameUntitledSession(
|
|
1333
|
-
platform, chatId, chatType, sessionId, descriptionTool,
|
|
1334
|
-
askText, description, chatInfo,
|
|
1335
|
-
);
|
|
1336
|
-
|
|
1337
|
-
if (isSessionRunning(sessionId)) {
|
|
1338
|
-
const queued = enqueueMessage(sessionId, {
|
|
1339
|
-
text, chatId, openId, msgTimestamp, chatType, traceId: tid,
|
|
1340
|
-
});
|
|
1341
|
-
if (queued) {
|
|
1342
|
-
logTrace(tid, "QUEUED", { sessionId, mode: "ask" });
|
|
1343
|
-
if (platform.kind === "wechat") {
|
|
1344
|
-
await platform.sendText(chatId, "当前会话正在生成中,你的消息已进入缓存队列,生成完成后会立即处理。发送 /cancel 可取消缓存。").catch(() => {});
|
|
1345
|
-
} else {
|
|
1346
|
-
await platform.sendRawCard(chatId, buildQueuedCard(askText)).catch(() => {});
|
|
1347
|
-
}
|
|
1348
|
-
} else {
|
|
1349
|
-
logTrace(tid, "QUEUE_FULL", { sessionId });
|
|
1350
|
-
if (platform.kind === "wechat") {
|
|
1351
|
-
await platform.sendText(chatId, "当前缓存队列中已有消息等待处理,请等待或发送 /stop(停止生成)或 /cancel(取消缓存)。").catch(() => {});
|
|
1352
|
-
} else {
|
|
1353
|
-
await platform.sendRawCard(chatId, buildQueueFullCard()).catch(() => {});
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
if (shouldSendWechatProcessingAck(platform, askText.toLowerCase(), chatType)) {
|
|
1360
|
-
await platform.sendText(chatId, "生成中...").catch(() => {});
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
try {
|
|
1364
|
-
logTrace(tid, "RESUME", { sessionId, tool: descriptionTool, mode: "ask" });
|
|
1365
|
-
await resumeAndPrompt(
|
|
1366
|
-
sessionId, askText, platform, chatId, msgTimestamp,
|
|
1367
|
-
descriptionTool, tid, "ask",
|
|
1368
|
-
);
|
|
1369
|
-
logTrace(tid, "DONE", { outcome: "ask_done", sessionId });
|
|
1370
|
-
} catch (err) {
|
|
1371
|
-
logTrace(tid, "DONE", { outcome: "ask_fail", error: (err as Error).message });
|
|
1372
|
-
await platform.sendCard(
|
|
1373
|
-
chatId, "Error",
|
|
1374
|
-
`Failed to run ask mode:\n${(err as Error).message}`,
|
|
1375
|
-
"red",
|
|
1376
|
-
);
|
|
1377
|
-
}
|
|
1378
|
-
return;
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
logTrace(tid, "BRANCH", { cmd: "/ask", sessionId, tool: descriptionTool, fallback: "raw_message" });
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
1184
|
const lastTs = lastMsgTimestamps.get(chatId);
|
|
1385
1185
|
if (lastTs !== undefined && msgTimestamp <= lastTs) {
|
|
1386
1186
|
logTrace(tid, "DONE", {
|
|
@@ -1619,7 +1419,8 @@ export async function handleCommand(
|
|
|
1619
1419
|
`**Session ID:** ${sessionId}\n` +
|
|
1620
1420
|
`**工作目录:** \`${cwd}\`\n\n` +
|
|
1621
1421
|
`下面会自动把你的私聊消息作为第一句话发送给 ${toolLabel}。\n\n` +
|
|
1622
|
-
|
|
1422
|
+
`发送 **/model** 查看或切换当前会话的模型。\n` +
|
|
1423
|
+
`发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。`,
|
|
1623
1424
|
"green",
|
|
1624
1425
|
);
|
|
1625
1426
|
platform.setChatAvatar(newChatId, tool, "new").catch(() => {});
|
|
@@ -151,8 +151,6 @@ export interface DisplayCardState {
|
|
|
151
151
|
lastSentFinalReply?: string;
|
|
152
152
|
/** 点点点动画计数器(统一 display loop 每个卡片独立计数) */
|
|
153
153
|
dotCount: number;
|
|
154
|
-
/** stream-state 连续读取失败次数,超过阈值才删除 display entry */
|
|
155
|
-
readFailCount?: number;
|
|
156
154
|
}
|
|
157
155
|
|
|
158
156
|
export const displayCards = new Map<string, DisplayCardState>();
|
package/src/session.ts
CHANGED
|
@@ -832,9 +832,8 @@ export async function resumeAndPrompt(
|
|
|
832
832
|
msgTimestamp: number,
|
|
833
833
|
tool: string,
|
|
834
834
|
traceId?: string,
|
|
835
|
-
permissionMode?: "plan" | "ask",
|
|
836
835
|
): Promise<void> {
|
|
837
|
-
return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId
|
|
836
|
+
return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId);
|
|
838
837
|
}
|
|
839
838
|
|
|
840
839
|
// ---------------------------------------------------------------------------
|
|
@@ -849,7 +848,6 @@ export async function runAgentSession(
|
|
|
849
848
|
msgTimestamp: number,
|
|
850
849
|
tool: string,
|
|
851
850
|
traceId?: string,
|
|
852
|
-
permissionMode?: "plan" | "ask",
|
|
853
851
|
): Promise<void> {
|
|
854
852
|
const tid = traceId ?? "";
|
|
855
853
|
|
|
@@ -1108,7 +1106,6 @@ export async function runAgentSession(
|
|
|
1108
1106
|
clearPromptProcessMonitor(sessionId);
|
|
1109
1107
|
if (exitInfo.pid !== undefined) unregisterProcess(exitInfo.pid);
|
|
1110
1108
|
},
|
|
1111
|
-
permissionMode,
|
|
1112
1109
|
})) {
|
|
1113
1110
|
for (const block of unifiedMsg.blocks) {
|
|
1114
1111
|
accumulateBlockContent(block, state, toolCallMap);
|
|
@@ -1282,21 +1279,15 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
1282
1279
|
const sessionId = display.sessionId;
|
|
1283
1280
|
const state = await readStreamState(sessionId);
|
|
1284
1281
|
if (!state) {
|
|
1285
|
-
|
|
1286
|
-
if (display.readFailCount >= 3) {
|
|
1287
|
-
console.log(`[${ts()}] [DISPLAY] readStreamState ${display.readFailCount} consecutive failures for ${sessionId}, removing display entry for chat ${chatId}`);
|
|
1288
|
-
displayCards.delete(chatId);
|
|
1289
|
-
}
|
|
1282
|
+
displayCards.delete(chatId);
|
|
1290
1283
|
continue;
|
|
1291
1284
|
}
|
|
1292
|
-
display.readFailCount = 0;
|
|
1293
1285
|
|
|
1294
1286
|
// 交叉验证:chat 当前绑定的 session 是否仍是 display 记录的 session。
|
|
1295
1287
|
// 若 chat 已被切换到其他 session(如 /newh),旧 display 必须停推。
|
|
1296
1288
|
const currentSessionForChat = sessionInfoMap.get(chatId)?.sessionId;
|
|
1297
1289
|
if (currentSessionForChat && currentSessionForChat !== sessionId) {
|
|
1298
1290
|
if (state.status !== "running") {
|
|
1299
|
-
console.log(`[${ts()}] [DISPLAY] chat ${chatId} now bound to ${currentSessionForChat}, removing stale display for ${sessionId} (terminal: ${state.status})`);
|
|
1300
1291
|
displayCards.delete(chatId);
|
|
1301
1292
|
}
|
|
1302
1293
|
continue;
|
|
@@ -1306,7 +1297,6 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
1306
1297
|
const lastActive = getLastActiveChat(sessionId);
|
|
1307
1298
|
if (lastActive !== chatId) {
|
|
1308
1299
|
if (state.status !== "running") {
|
|
1309
|
-
console.log(`[${ts()}] [DISPLAY] lastActive mismatch for ${sessionId}: display chat=${chatId} lastActive=${lastActive ?? "undefined"}, removing display entry (terminal: ${state.status})`);
|
|
1310
1300
|
displayCards.delete(chatId);
|
|
1311
1301
|
}
|
|
1312
1302
|
continue;
|