chatccc 0.2.139 → 0.2.141
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/agent-prompts/claude_specific.md +18 -42
- package/agent-prompts/codex_specific.md +0 -1
- package/agent-prompts/cursor_specific.md +0 -1
- package/package.json +1 -1
- package/src/__tests__/claude-adapter.test.ts +55 -1
- package/src/adapters/claude-adapter.ts +73 -14
- package/src/agent-stop-stuck.ts +89 -0
- package/src/index.ts +3 -2
- package/src/orchestrator.ts +8 -10
- package/src/session.ts +9 -3
|
@@ -1,53 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
## 重复成功命令保护
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
通过 Claude Agent SDK 在本项目中工作时,重复成功的 shell 命令是完成信号,不应继续使用工具。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
以下条件**全部**满足时,视为相同命令:
|
|
6
|
+
- 命令文本基本相同
|
|
7
|
+
- 工作目录相同
|
|
8
|
+
- 任务目标未变
|
|
9
|
+
- 没有新的用户输入改变任务
|
|
10
|
+
- 没有新的错误输出需要重新处理
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
f:\users\weizhangjian\feishuclauderprivate
|
|
9
|
-
```
|
|
12
|
+
同一命令**连续成功两次**后,停止调用工具,直接给出最终回复。不要"再验证一次"。
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
此规则适用于所有 shell 命令,包括测试、构建、git、安装、格式化、项目脚本、状态检查等。
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
## 硬停止规则
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
同一命令在本项目中**不得成功执行三次**。若连续两次成功,第三次必须是最终回复。
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
- The working directory is the same.
|
|
19
|
-
- The current task goal has not changed.
|
|
20
|
-
- There is no new user input that changes the task.
|
|
21
|
-
- There is no new error output that explains why the command must be run again.
|
|
20
|
+
## 卡住自检
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
如果你感觉任务已经完成,但发现自己陷入了循环(反复调用相同工具、输出 "stuck in a loop" 等),**立即**调用以下接口:
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
POST {{stop_stuck_url}}
|
|
25
|
+
Content-Type: application/json
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
{"session_id": "{{session_id}}"}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
- build commands
|
|
31
|
-
- git commands
|
|
32
|
-
- install commands
|
|
33
|
-
- formatting commands
|
|
34
|
-
- project scripts
|
|
35
|
-
- status or inspection commands
|
|
36
|
-
|
|
37
|
-
Do not "verify one more time" by repeating the same successful command. If verification is needed, use a different command that checks a different fact, or explain the current result to the user.
|
|
38
|
-
|
|
39
|
-
If you notice that you are about to repeat a successful command with the same arguments, stop using tools and respond to the user with the current result.
|
|
40
|
-
|
|
41
|
-
If you produce or observe text like "I'm stuck in a loop", do not call any more tools. Immediately send the final user-facing response explaining:
|
|
42
|
-
|
|
43
|
-
- what has completed,
|
|
44
|
-
- what is still uncertain, if anything,
|
|
45
|
-
- and what the user can do next, if action is needed.
|
|
46
|
-
|
|
47
|
-
## Hard Stop Rule
|
|
48
|
-
|
|
49
|
-
Never execute the same successful shell command three times for the same task in this project.
|
|
50
|
-
|
|
51
|
-
If the same command has already succeeded twice in a row, the next assistant action must be a final response to the user, not another tool call.
|
|
52
|
-
|
|
53
|
-
Successful repeated command execution is a terminal condition. Prefer a concise final response over further tool calls.
|
|
29
|
+
调用后停止所有工具调用,直接输出最终回复即可。
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
如果计划写好了等待你的审阅,将计划文件通过当前聊天软件发给用户
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
如果计划写好了等待你的审阅,将计划文件通过当前聊天软件发给用户
|
package/package.json
CHANGED
|
@@ -463,11 +463,65 @@ describe("buildClaudePromptText", () => {
|
|
|
463
463
|
expect(buildClaudePromptText("hello", " ")).toBe("hello");
|
|
464
464
|
expect(buildClaudePromptText("hello", null)).toBe("hello");
|
|
465
465
|
});
|
|
466
|
+
|
|
467
|
+
it("replaces {{stop_stuck_url}} and {{session_id}} placeholders when sessionId is provided", () => {
|
|
468
|
+
const result = buildClaudePromptText(
|
|
469
|
+
"[User message]\nhello\n[/User message]",
|
|
470
|
+
"Call POST {{stop_stuck_url}} with {\"session_id\": \"{{session_id}}\"}",
|
|
471
|
+
"test-sid-123",
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
expect(result).toContain("http://127.0.0.1:");
|
|
475
|
+
expect(result).toContain("/api/agent/stop-stuck-loop");
|
|
476
|
+
expect(result).toContain("\"session_id\": \"test-sid-123\"");
|
|
477
|
+
expect(result).not.toContain("{{stop_stuck_url}}");
|
|
478
|
+
expect(result).not.toContain("{{session_id}}");
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("does not replace placeholders when sessionId is not provided", () => {
|
|
482
|
+
const result = buildClaudePromptText(
|
|
483
|
+
"hello",
|
|
484
|
+
"Call POST {{stop_stuck_url}} with {\"session_id\": \"{{session_id}}\"}",
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
expect(result).toContain("{{stop_stuck_url}}");
|
|
488
|
+
expect(result).toContain("{{session_id}}");
|
|
489
|
+
});
|
|
466
490
|
});
|
|
467
491
|
|
|
468
492
|
describe("buildSdkEnv", () => {
|
|
469
493
|
it("returns undefined when no SDK env override is configured", () => {
|
|
470
|
-
|
|
494
|
+
const originalPath = process.env.PATH;
|
|
495
|
+
try {
|
|
496
|
+
process.env.PATH = process.platform === "win32"
|
|
497
|
+
? "C:\\Program Files\\Git\\usr\\bin;C:\\Windows\\System32"
|
|
498
|
+
: "/usr/bin:/bin";
|
|
499
|
+
expect(buildSdkEnv("", " ", undefined)).toBeUndefined();
|
|
500
|
+
} finally {
|
|
501
|
+
process.env.PATH = originalPath;
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("prefers Git Bash before WindowsApps for Claude SDK subprocesses on Windows", () => {
|
|
506
|
+
if (process.platform !== "win32") return;
|
|
507
|
+
const originalPath = process.env.PATH;
|
|
508
|
+
try {
|
|
509
|
+
process.env.PATH = [
|
|
510
|
+
"C:\\Users\\weizhangjian\\AppData\\Local\\Microsoft\\WindowsApps",
|
|
511
|
+
"C:\\Program Files\\Git\\usr\\bin",
|
|
512
|
+
"%PATH%",
|
|
513
|
+
"C:\\Windows\\System32",
|
|
514
|
+
].join(";");
|
|
515
|
+
|
|
516
|
+
const env = buildSdkEnv("", "", "");
|
|
517
|
+
|
|
518
|
+
expect(env).toBeDefined();
|
|
519
|
+
const parts = env!.PATH!.split(";");
|
|
520
|
+
expect(parts[0]).toBe("C:\\Program Files\\Git\\usr\\bin");
|
|
521
|
+
expect(parts).not.toContain("%PATH%");
|
|
522
|
+
} finally {
|
|
523
|
+
process.env.PATH = originalPath;
|
|
524
|
+
}
|
|
471
525
|
});
|
|
472
526
|
|
|
473
527
|
it("sets requested SDK env overrides and removes conflicting Claude auth/model vars", () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { delimiter, dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
6
|
import {
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
UnifiedBlock,
|
|
22
22
|
UnifiedStreamMessage,
|
|
23
23
|
} from "./adapter-interface.ts";
|
|
24
|
+
import { CHATCCC_PORT } from "../config.ts";
|
|
24
25
|
import {
|
|
25
26
|
defaultClaudeSessionMetaStore,
|
|
26
27
|
type ClaudeSessionMetaStore,
|
|
@@ -82,23 +83,73 @@ export function buildSdkEnv(
|
|
|
82
83
|
const apiKeyTrim = (apiKey ?? "").trim();
|
|
83
84
|
const baseUrlTrim = (baseUrl ?? "").trim();
|
|
84
85
|
|
|
85
|
-
if (!subagentModelTrim && !apiKeyTrim && !baseUrlTrim) return undefined;
|
|
86
|
-
|
|
87
86
|
const env: Record<string, string | undefined> = { ...process.env };
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
let mutated = preferGitBashOnWindows(env);
|
|
88
|
+
|
|
89
|
+
if (subagentModelTrim || apiKeyTrim || baseUrlTrim) {
|
|
90
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
91
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
92
|
+
delete env.ANTHROPIC_MODEL;
|
|
93
|
+
delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
94
|
+
delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
95
|
+
delete env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
96
|
+
delete env.CLAUDE_CODE_EFFORT_LEVEL;
|
|
97
|
+
delete env.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
98
|
+
mutated = true;
|
|
99
|
+
}
|
|
96
100
|
|
|
97
101
|
if (subagentModelTrim) env.CLAUDE_CODE_SUBAGENT_MODEL = subagentModelTrim;
|
|
98
102
|
if (apiKeyTrim) env.ANTHROPIC_API_KEY = apiKeyTrim;
|
|
99
103
|
if (baseUrlTrim) env.ANTHROPIC_BASE_URL = baseUrlTrim;
|
|
100
104
|
|
|
101
|
-
return env;
|
|
105
|
+
return mutated ? env : undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function preferGitBashOnWindows(env: Record<string, string | undefined>): boolean {
|
|
109
|
+
if (process.platform !== "win32") return false;
|
|
110
|
+
|
|
111
|
+
const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
112
|
+
const rawPath = env[pathKey];
|
|
113
|
+
if (!rawPath) return false;
|
|
114
|
+
|
|
115
|
+
const parts = rawPath.split(delimiter).filter((part) => part && part !== "%PATH%");
|
|
116
|
+
const preferred = findPreferredGitBashPath(parts);
|
|
117
|
+
if (!preferred) {
|
|
118
|
+
const nextPath = parts.join(delimiter);
|
|
119
|
+
if (nextPath !== rawPath) {
|
|
120
|
+
env[pathKey] = nextPath;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const preferredLower = preferred.toLowerCase();
|
|
127
|
+
const reordered = [
|
|
128
|
+
preferred,
|
|
129
|
+
...parts.filter((part) => part.toLowerCase() !== preferredLower),
|
|
130
|
+
];
|
|
131
|
+
const nextPath = reordered.join(delimiter);
|
|
132
|
+
if (nextPath === rawPath) return false;
|
|
133
|
+
env[pathKey] = nextPath;
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function findPreferredGitBashPath(pathParts: string[]): string | undefined {
|
|
138
|
+
const programFilesGit = join(
|
|
139
|
+
process.env.ProgramFiles ?? "C:\\Program Files",
|
|
140
|
+
"Git",
|
|
141
|
+
"usr",
|
|
142
|
+
"bin",
|
|
143
|
+
);
|
|
144
|
+
if (existsSync(join(programFilesGit, "bash.exe"))) return programFilesGit;
|
|
145
|
+
|
|
146
|
+
return pathParts.find((part) => {
|
|
147
|
+
if (!/(\\|\/)(Git)(\\|\/)usr(\\|\/)bin$/i.test(part) &&
|
|
148
|
+
!/(\\|\/)Fork(\\|\/)gitInstance(\\|\/)[^\\/]+(\\|\/)bin$/i.test(part)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return existsSync(join(part, "bash.exe"));
|
|
152
|
+
});
|
|
102
153
|
}
|
|
103
154
|
|
|
104
155
|
function readMcpServersConfig(): Record<string, unknown> | undefined {
|
|
@@ -210,10 +261,18 @@ export function readClaudeSpecificInjectionPrompt(): string | null {
|
|
|
210
261
|
export function buildClaudePromptText(
|
|
211
262
|
userText: string,
|
|
212
263
|
injectionPrompt: string | null = readClaudeSpecificInjectionPrompt(),
|
|
264
|
+
sessionId?: string,
|
|
213
265
|
): string {
|
|
214
|
-
|
|
266
|
+
let prompt = injectionPrompt?.trim();
|
|
215
267
|
if (!prompt) return userText;
|
|
216
268
|
|
|
269
|
+
// 动态替换注入提示词中的占位符(端口、session_id 等)
|
|
270
|
+
if (sessionId) {
|
|
271
|
+
prompt = prompt
|
|
272
|
+
.replace(/\{\{stop_stuck_url\}\}/g, `http://127.0.0.1:${CHATCCC_PORT}/api/agent/stop-stuck-loop`)
|
|
273
|
+
.replace(/\{\{session_id\}\}/g, sessionId);
|
|
274
|
+
}
|
|
275
|
+
|
|
217
276
|
return [
|
|
218
277
|
"[ChatCCC Claude-specific injection prompt]",
|
|
219
278
|
prompt,
|
|
@@ -411,7 +470,7 @@ class ClaudeAdapter implements ToolAdapter {
|
|
|
411
470
|
);
|
|
412
471
|
|
|
413
472
|
try {
|
|
414
|
-
await session.send(buildClaudePromptText(userText));
|
|
473
|
+
await session.send(buildClaudePromptText(userText, undefined, sessionId));
|
|
415
474
|
for await (const raw of session.stream()) {
|
|
416
475
|
if (abortController.signal.aborted) {
|
|
417
476
|
aborted = true;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
|
|
3
|
+
import { readUtf8JsonBody } from "./agent-rpc-body.ts";
|
|
4
|
+
import { activePrompts, getChatsForSession, cancelQueuedMessage } from "./session-chat-binding.ts";
|
|
5
|
+
import { getPlatformForChat } from "./session.ts";
|
|
6
|
+
import { readStreamState, writeStreamState } from "./stream-state.ts";
|
|
7
|
+
import { ts } from "./config.ts";
|
|
8
|
+
|
|
9
|
+
export const AGENT_STOP_STUCK_PATH = "/api/agent/stop-stuck-loop";
|
|
10
|
+
|
|
11
|
+
const MAX_REQUEST_BYTES = 8 * 1024;
|
|
12
|
+
|
|
13
|
+
function jsonReply(res: ServerResponse, status: number, data: unknown): void {
|
|
14
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
15
|
+
res.end(JSON.stringify(data));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function handleAgentStopStuckRequest(
|
|
19
|
+
req: IncomingMessage,
|
|
20
|
+
res: ServerResponse,
|
|
21
|
+
): Promise<boolean> {
|
|
22
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
23
|
+
if (url.pathname !== AGENT_STOP_STUCK_PATH) return false;
|
|
24
|
+
|
|
25
|
+
if (req.method !== "POST") {
|
|
26
|
+
jsonReply(res, 405, { error: "Method not allowed, use POST" });
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let body: { session_id?: string };
|
|
31
|
+
try {
|
|
32
|
+
body = await readUtf8JsonBody<{ session_id?: string }>(req, MAX_REQUEST_BYTES);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
jsonReply(res, 400, { error: `Invalid request body: ${(err as Error).message}` });
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const sessionId = typeof body?.session_id === "string" ? body.session_id.trim() : "";
|
|
39
|
+
if (!sessionId) {
|
|
40
|
+
jsonReply(res, 400, { error: "session_id is required" });
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const prompt = activePrompts.get(sessionId);
|
|
45
|
+
if (!prompt) {
|
|
46
|
+
jsonReply(res, 404, { error: "Session not found or not running" });
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 先发"卡住"提示消息给所有绑定的群聊
|
|
51
|
+
const chats = getChatsForSession(sessionId);
|
|
52
|
+
for (const chatId of chats) {
|
|
53
|
+
const platform = getPlatformForChat(chatId);
|
|
54
|
+
if (platform) {
|
|
55
|
+
await platform.sendText(chatId, "⚠️ Agent 检测到自己陷入了循环,正在结束生成…").catch(() => {});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 丢弃缓存队列中的消息
|
|
60
|
+
cancelQueuedMessage(sessionId);
|
|
61
|
+
|
|
62
|
+
// fire-and-forget:立即把 stream-state 标为 done(而非 stopped),
|
|
63
|
+
// 让 display loop 以"正常完成"而非"已停止"来渲染卡片和最终回复。
|
|
64
|
+
// 不设 prompt.stopped = true,这样 runAgentSession 的 finally 也会写 "done"。
|
|
65
|
+
void (async () => {
|
|
66
|
+
try {
|
|
67
|
+
const current = await readStreamState(sessionId);
|
|
68
|
+
if (!current) return;
|
|
69
|
+
if (current.status !== "running") return;
|
|
70
|
+
await writeStreamState({
|
|
71
|
+
...current,
|
|
72
|
+
status: "done",
|
|
73
|
+
updatedAt: Date.now(),
|
|
74
|
+
});
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.warn(
|
|
77
|
+
`[${ts()}] [STUCK-LOOP] writeStreamState(done) failed for ${sessionId}: ${(err as Error).message}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
|
|
82
|
+
// 不设 stopped 标记 → finally block 写 "done" → 卡片正常结束
|
|
83
|
+
prompt.controller.abort();
|
|
84
|
+
|
|
85
|
+
console.log(`[${ts()}] [STUCK-LOOP] Session ${sessionId} aborted as done (agent detected stuck loop)`);
|
|
86
|
+
|
|
87
|
+
jsonReply(res, 200, { ok: true });
|
|
88
|
+
return true;
|
|
89
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -78,6 +78,7 @@ import { SimulatedPlatform, SIM_DEFAULT_CHAT_ID } from "./sim-platform.ts";
|
|
|
78
78
|
import { setMessageHandler } from "./sim-store.ts";
|
|
79
79
|
import { handleAgentImageRequest } from "./agent-image-rpc.ts";
|
|
80
80
|
import { handleAgentFileRequest } from "./agent-file-rpc.ts";
|
|
81
|
+
import { handleAgentStopStuckRequest } from "./agent-stop-stuck.ts";
|
|
81
82
|
import {
|
|
82
83
|
createCardKitCard,
|
|
83
84
|
sendCardKitMessage,
|
|
@@ -689,7 +690,7 @@ async function main(): Promise<void> {
|
|
|
689
690
|
setExtraApiHandler(async (req, res) => {
|
|
690
691
|
const injected = await handleSimInjectMessage(req, res);
|
|
691
692
|
if (injected) return true;
|
|
692
|
-
return (await handleAgentImageRequest(req, res)) || (await handleAgentFileRequest(req, res));
|
|
693
|
+
return (await handleAgentImageRequest(req, res)) || (await handleAgentFileRequest(req, res)) || (await handleAgentStopStuckRequest(req, res));
|
|
693
694
|
});
|
|
694
695
|
|
|
695
696
|
const simServer = createServer(createUiRouter());
|
|
@@ -748,7 +749,7 @@ async function main(): Promise<void> {
|
|
|
748
749
|
});
|
|
749
750
|
});
|
|
750
751
|
setExtraApiHandler(async (req, res) => {
|
|
751
|
-
return (await handleAgentImageRequest(req, res)) || (await handleAgentFileRequest(req, res));
|
|
752
|
+
return (await handleAgentImageRequest(req, res)) || (await handleAgentFileRequest(req, res)) || (await handleAgentStopStuckRequest(req, res));
|
|
752
753
|
});
|
|
753
754
|
|
|
754
755
|
console.log(`[启动 2/7] 环境与凭证检查`);
|
package/src/orchestrator.ts
CHANGED
|
@@ -1235,11 +1235,10 @@ export async function handleCommand(
|
|
|
1235
1235
|
text,
|
|
1236
1236
|
platform,
|
|
1237
1237
|
chatId,
|
|
1238
|
-
msgTimestamp,
|
|
1239
|
-
descriptionTool,
|
|
1240
|
-
tid,
|
|
1241
|
-
|
|
1242
|
-
);
|
|
1238
|
+
msgTimestamp,
|
|
1239
|
+
descriptionTool,
|
|
1240
|
+
tid,
|
|
1241
|
+
);
|
|
1243
1242
|
logTrace(tid, "DONE", { outcome: "resume_done", sessionId });
|
|
1244
1243
|
console.log(`[${ts()}] [RESUME] Session ${sessionId} done`);
|
|
1245
1244
|
} catch (err) {
|
|
@@ -1435,11 +1434,10 @@ export async function handleCommand(
|
|
|
1435
1434
|
text,
|
|
1436
1435
|
platform,
|
|
1437
1436
|
newChatId,
|
|
1438
|
-
msgTimestamp,
|
|
1439
|
-
tool,
|
|
1440
|
-
tid,
|
|
1441
|
-
|
|
1442
|
-
);
|
|
1437
|
+
msgTimestamp,
|
|
1438
|
+
tool,
|
|
1439
|
+
tid,
|
|
1440
|
+
);
|
|
1443
1441
|
console.log(`[${ts()}] [AUTO-P2P 5/5] First prompt sent → OK`);
|
|
1444
1442
|
logTrace(tid, "DONE", { outcome: "auto_new_p2p_prompt_done", newChatId, sessionId, tool });
|
|
1445
1443
|
} catch (err) {
|
package/src/session.ts
CHANGED
|
@@ -1013,7 +1013,9 @@ export async function runAgentSession(
|
|
|
1013
1013
|
const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(prevState.status);
|
|
1014
1014
|
const cardContent = truncateContent(prevState.accumulatedContent + prevState.finalReply) || " ";
|
|
1015
1015
|
const doneCard = buildProgressCard(cardContent, { showStop: false, headerTitle, headerTemplate });
|
|
1016
|
-
await pp.cardUpdate(display.cardId, doneCard, nextSeq).catch(
|
|
1016
|
+
await pp.cardUpdate(display.cardId, doneCard, nextSeq).catch(err => {
|
|
1017
|
+
console.error(`[${ts()}] [DISPLAY] prevState final cardUpdate failed: ${(err as Error).message}`);
|
|
1018
|
+
});
|
|
1017
1019
|
// cardUpdate IO 期间统一 loop 可能也已处理此 display → 删前检查引用
|
|
1018
1020
|
const stillOursAfterUpdate = displayCards.get(displayChatId) === display;
|
|
1019
1021
|
displayCards.delete(displayChatId);
|
|
@@ -1344,7 +1346,9 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
1344
1346
|
const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(state.status);
|
|
1345
1347
|
const cardContent = truncateContent(state.accumulatedContent + state.finalReply) || " ";
|
|
1346
1348
|
const doneCard = buildProgressCard(cardContent, { showStop: false, headerTitle, headerTemplate });
|
|
1347
|
-
await p.cardUpdate(display.cardId, doneCard, nextSeq).catch(
|
|
1349
|
+
await p.cardUpdate(display.cardId, doneCard, nextSeq).catch(err => {
|
|
1350
|
+
console.error(`[${ts()}] [DISPLAY] terminal cardUpdate failed: ${(err as Error).message}`);
|
|
1351
|
+
});
|
|
1348
1352
|
|
|
1349
1353
|
// 若 session 仍在 activePrompts 中,说明 runAgentSession 的 finally
|
|
1350
1354
|
// 还没执行,当前 stream state 可能是 stopSession fire-and-forget
|
|
@@ -1419,7 +1423,9 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
1419
1423
|
const oldSeqBase = display.sequence;
|
|
1420
1424
|
const oldContent = state.accumulatedContent + state.finalReply;
|
|
1421
1425
|
const oldCard = buildProgressCard(truncateContent(oldContent) || " ", { showStop: false, headerTitle: "生成中(上轮)" });
|
|
1422
|
-
await p.cardUpdate(display.cardId, oldCard, oldSeqBase + 1).catch(
|
|
1426
|
+
await p.cardUpdate(display.cardId, oldCard, oldSeqBase + 1).catch(err => {
|
|
1427
|
+
console.error(`[${ts()}] [DISPLAY] rotation old cardUpdate failed: ${(err as Error).message}`);
|
|
1428
|
+
});
|
|
1423
1429
|
markCardDone(sessionId, display.turnCount, display.cardId).catch(() => {});
|
|
1424
1430
|
const newCardId = await p.cardCreate(buildProgressCard(
|
|
1425
1431
|
"",
|