chatccc 0.2.245 → 0.2.246
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 +8 -8
- package/deepccc-agent/README.md +28 -14
- package/deepccc-agent/os-prompts/darwin.md +8 -8
- package/deepccc-agent/os-prompts/linux.md +8 -8
- package/deepccc-agent/os-prompts/win32.md +9 -9
- package/deepccc-agent/package-lock.json +70 -2
- package/deepccc-agent/package.json +65 -63
- package/deepccc-agent/src/__tests__/chat-session.test.ts +741 -682
- package/deepccc-agent/src/__tests__/config.test.ts +34 -26
- package/deepccc-agent/src/__tests__/permissions.test.ts +199 -195
- package/deepccc-agent/src/__tests__/privacy.test.ts +12 -8
- package/deepccc-agent/src/cli.ts +20 -9
- package/deepccc-agent/src/config.ts +101 -88
- package/deepccc-agent/src/index.ts +86 -61
- package/package.json +74 -73
- package/src/__tests__/builtin-chat-session.test.ts +532 -522
- package/src/__tests__/builtin-permissions.test.ts +219 -211
- package/src/__tests__/ccc-adapter.test.ts +194 -185
- package/src/__tests__/session.test.ts +369 -369
- package/src/adapters/adapter-interface.ts +42 -42
- package/src/adapters/ccc-adapter.ts +150 -149
- package/src/session.ts +323 -323
package/src/session.ts
CHANGED
|
@@ -41,14 +41,14 @@ import { createCccAdapter } from "./adapters/ccc-adapter.ts";
|
|
|
41
41
|
import { killProcessTree } from "./adapters/proc-tree-kill.ts";
|
|
42
42
|
import { resourceMonitor, registerProcess, unregisterProcess } from "./adapters/resource-monitor.ts";
|
|
43
43
|
import { buildImSkillsPromptCached, exportSkillSubDocs, clearImSkillsPromptCache } from "./im-skills.ts";
|
|
44
|
-
import type { PlatformAdapter } from "./platform-adapter.ts";
|
|
45
|
-
import { hasResponseStalled, observeResponseProgress } from "./response-stall.ts";
|
|
46
|
-
import {
|
|
47
|
-
classifyTerminalError,
|
|
48
|
-
formatTerminalErrorNotice,
|
|
49
|
-
formatTerminalErrorReason,
|
|
50
|
-
type TerminalErrorInfo,
|
|
51
|
-
} from "./terminal-error.ts";
|
|
44
|
+
import type { PlatformAdapter } from "./platform-adapter.ts";
|
|
45
|
+
import { hasResponseStalled, observeResponseProgress } from "./response-stall.ts";
|
|
46
|
+
import {
|
|
47
|
+
classifyTerminalError,
|
|
48
|
+
formatTerminalErrorNotice,
|
|
49
|
+
formatTerminalErrorReason,
|
|
50
|
+
type TerminalErrorInfo,
|
|
51
|
+
} from "./terminal-error.ts";
|
|
52
52
|
import {
|
|
53
53
|
MAX_PROCESSED,
|
|
54
54
|
clearFeishuMessageLedgerMemory,
|
|
@@ -174,20 +174,20 @@ function platformForChat(chatId: string): PlatformAdapter | null {
|
|
|
174
174
|
return chatPlatformMap.get(chatId) ?? platformRef;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
const DEFAULT_PROCESS_MONITOR_INTERVAL_MS = 5000;
|
|
178
|
-
const DEFAULT_AVATAR_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
|
|
179
|
-
const DEFAULT_RESPONSE_STALL_TIMEOUT_MS = 3 * 60 * 1000;
|
|
177
|
+
const DEFAULT_PROCESS_MONITOR_INTERVAL_MS = 5000;
|
|
178
|
+
const DEFAULT_AVATAR_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
|
|
179
|
+
const DEFAULT_RESPONSE_STALL_TIMEOUT_MS = 3 * 60 * 1000;
|
|
180
180
|
const DEFAULT_RESPONSE_STALL_CHECK_INTERVAL_MS = 5000;
|
|
181
181
|
const DEFAULT_FINAL_RESPONSE_CLOSE_TIMEOUT_MS = 10_000;
|
|
182
182
|
export const RESPONSE_STALL_RECOVERY_PROMPT = "完成了吗?如果没完成继续";
|
|
183
183
|
export const RESPONSE_STALL_RECOVERY_NOTICE =
|
|
184
184
|
`检测到会话停滞,正在自动确认并继续。\n\n${RESPONSE_STALL_RECOVERY_PROMPT}`;
|
|
185
|
-
export const RESPONSE_STALL_RECOVERY_EXHAUSTED_NOTICE =
|
|
186
|
-
"⚠️ 自动续跑仍连续 3 分钟没有生成新回复,本次不再自动继续。";
|
|
185
|
+
export const RESPONSE_STALL_RECOVERY_EXHAUSTED_NOTICE =
|
|
186
|
+
"⚠️ 自动续跑仍连续 3 分钟没有生成新回复,本次不再自动继续。";
|
|
187
187
|
const RESPONSE_STALL_RECOVERY_DELAY_MS = 200;
|
|
188
|
-
let processMonitorIntervalMs = DEFAULT_PROCESS_MONITOR_INTERVAL_MS;
|
|
189
|
-
let avatarRefreshIntervalMs = DEFAULT_AVATAR_REFRESH_INTERVAL_MS;
|
|
190
|
-
let responseStallTimeoutMs = DEFAULT_RESPONSE_STALL_TIMEOUT_MS;
|
|
188
|
+
let processMonitorIntervalMs = DEFAULT_PROCESS_MONITOR_INTERVAL_MS;
|
|
189
|
+
let avatarRefreshIntervalMs = DEFAULT_AVATAR_REFRESH_INTERVAL_MS;
|
|
190
|
+
let responseStallTimeoutMs = DEFAULT_RESPONSE_STALL_TIMEOUT_MS;
|
|
191
191
|
let responseStallCheckIntervalMs = DEFAULT_RESPONSE_STALL_CHECK_INTERVAL_MS;
|
|
192
192
|
let finalResponseCloseTimeoutMs = DEFAULT_FINAL_RESPONSE_CLOSE_TIMEOUT_MS;
|
|
193
193
|
let isProcessAliveImpl = (pid: number): boolean => {
|
|
@@ -218,17 +218,17 @@ export function _setProcessMonitorIntervalForTest(ms: number): void {
|
|
|
218
218
|
processMonitorIntervalMs = ms;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
export function _resetProcessMonitorIntervalForTest(): void {
|
|
222
|
-
processMonitorIntervalMs = DEFAULT_PROCESS_MONITOR_INTERVAL_MS;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function _setAvatarRefreshIntervalForTest(ms: number): void {
|
|
226
|
-
avatarRefreshIntervalMs = ms;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export function _resetAvatarRefreshIntervalForTest(): void {
|
|
230
|
-
avatarRefreshIntervalMs = DEFAULT_AVATAR_REFRESH_INTERVAL_MS;
|
|
231
|
-
}
|
|
221
|
+
export function _resetProcessMonitorIntervalForTest(): void {
|
|
222
|
+
processMonitorIntervalMs = DEFAULT_PROCESS_MONITOR_INTERVAL_MS;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function _setAvatarRefreshIntervalForTest(ms: number): void {
|
|
226
|
+
avatarRefreshIntervalMs = ms;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function _resetAvatarRefreshIntervalForTest(): void {
|
|
230
|
+
avatarRefreshIntervalMs = DEFAULT_AVATAR_REFRESH_INTERVAL_MS;
|
|
231
|
+
}
|
|
232
232
|
|
|
233
233
|
export function _setResponseStallTimeoutForTest(ms: number): void {
|
|
234
234
|
responseStallTimeoutMs = ms;
|
|
@@ -261,21 +261,21 @@ function clearPromptProcessMonitor(sessionId: string): void {
|
|
|
261
261
|
prompt.processMonitor = undefined;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
function clearPromptResponseStallMonitor(sessionId: string): void {
|
|
265
|
-
const prompt = activePrompts.get(sessionId);
|
|
266
|
-
if (!prompt?.responseStallMonitor) return;
|
|
267
|
-
clearInterval(prompt.responseStallMonitor);
|
|
268
|
-
prompt.responseStallMonitor = undefined;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function clearPromptAvatarRefreshTimer(sessionId: string): void {
|
|
272
|
-
const prompt = activePrompts.get(sessionId);
|
|
273
|
-
if (!prompt?.avatarRefreshTimer) return;
|
|
274
|
-
clearInterval(prompt.avatarRefreshTimer);
|
|
275
|
-
prompt.avatarRefreshTimer = undefined;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function clearPromptFinalResponseCloseTimer(sessionId: string): void {
|
|
264
|
+
function clearPromptResponseStallMonitor(sessionId: string): void {
|
|
265
|
+
const prompt = activePrompts.get(sessionId);
|
|
266
|
+
if (!prompt?.responseStallMonitor) return;
|
|
267
|
+
clearInterval(prompt.responseStallMonitor);
|
|
268
|
+
prompt.responseStallMonitor = undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function clearPromptAvatarRefreshTimer(sessionId: string): void {
|
|
272
|
+
const prompt = activePrompts.get(sessionId);
|
|
273
|
+
if (!prompt?.avatarRefreshTimer) return;
|
|
274
|
+
clearInterval(prompt.avatarRefreshTimer);
|
|
275
|
+
prompt.avatarRefreshTimer = undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function clearPromptFinalResponseCloseTimer(sessionId: string): void {
|
|
279
279
|
const prompt = activePrompts.get(sessionId);
|
|
280
280
|
if (!prompt?.finalResponseCloseTimer) return;
|
|
281
281
|
clearTimeout(prompt.finalResponseCloseTimer);
|
|
@@ -328,18 +328,18 @@ function scheduleFinalResponseCloseGuard(
|
|
|
328
328
|
runningPrompt.finalResponseCloseTimer = handle;
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
function formatTerminalHeader(
|
|
332
|
-
status: "running" | "done" | "stopped" | "error" | "auto_ended",
|
|
333
|
-
terminalError?: TerminalErrorInfo,
|
|
334
|
-
): {
|
|
331
|
+
function formatTerminalHeader(
|
|
332
|
+
status: "running" | "done" | "stopped" | "error" | "auto_ended",
|
|
333
|
+
terminalError?: TerminalErrorInfo,
|
|
334
|
+
): {
|
|
335
335
|
title: string;
|
|
336
336
|
template?: string;
|
|
337
337
|
} {
|
|
338
338
|
if (status === "auto_ended") return { title: "已自动结束 · 3分钟无新内容", template: "orange" };
|
|
339
339
|
if (status === "stopped") return { title: "已停止", template: "red" };
|
|
340
|
-
if (status === "error") {
|
|
341
|
-
return { title: terminalError ? `异常结束 · ${terminalError.title}` : "异常结束", template: "red" };
|
|
342
|
-
}
|
|
340
|
+
if (status === "error") {
|
|
341
|
+
return { title: terminalError ? `异常结束 · ${terminalError.title}` : "异常结束", template: "red" };
|
|
342
|
+
}
|
|
343
343
|
return { title: "完成" };
|
|
344
344
|
}
|
|
345
345
|
|
|
@@ -347,59 +347,59 @@ function turnFinalStatus(status: "running" | "done" | "stopped" | "error" | "aut
|
|
|
347
347
|
return status === "stopped" || status === "error" || status === "auto_ended" ? "stopped" : "done";
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
function formatAutoEndedReply(finalReply: string): string {
|
|
351
|
-
const reason = "⚠️ 已自动结束:生成回复阶段连续 3 分钟没有字符变化。";
|
|
350
|
+
function formatAutoEndedReply(finalReply: string): string {
|
|
351
|
+
const reason = "⚠️ 已自动结束:生成回复阶段连续 3 分钟没有字符变化。";
|
|
352
352
|
return finalReply
|
|
353
353
|
? `${reason}以下回复可能不完整。\n\n${finalReply}`
|
|
354
354
|
: `${reason}本轮没有可发送的回复内容。`;
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
/**
|
|
358
|
-
* 只监控明确的回复生成阶段。启动、压缩、思考、工具调用和搜索都有各自的
|
|
359
|
-
* 状态或资源保护,不能把它们消耗的时间算入回复停滞窗口。
|
|
358
|
+
* 只监控明确的回复生成阶段。启动、压缩、思考、工具调用和搜索都有各自的
|
|
359
|
+
* 状态或资源保护,不能把它们消耗的时间算入回复停滞窗口。
|
|
360
360
|
*/
|
|
361
|
-
function monitorsOutputProgress(kind: AgentActivityKind): boolean {
|
|
362
|
-
return kind === "responding";
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function formatTerminalReply(
|
|
366
|
-
status: "running" | "done" | "stopped" | "error" | "auto_ended",
|
|
367
|
-
finalReply: string,
|
|
368
|
-
terminalError?: TerminalErrorInfo,
|
|
369
|
-
): string | null {
|
|
370
|
-
if (status === "auto_ended") return formatAutoEndedReply(finalReply);
|
|
371
|
-
if (status === "error") {
|
|
372
|
-
const error = terminalError ?? {
|
|
373
|
-
kind: "unknown" as const,
|
|
374
|
-
title: "原因未记录",
|
|
375
|
-
message: "当前状态中没有可用的错误详情,请查看运行日志。",
|
|
376
|
-
occurredAt: Date.now(),
|
|
377
|
-
};
|
|
378
|
-
return formatTerminalErrorNotice(error, finalReply);
|
|
379
|
-
}
|
|
380
|
-
return finalReply || null;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function formatTerminalCardContent(state: {
|
|
384
|
-
status: "running" | "done" | "stopped" | "error" | "auto_ended";
|
|
385
|
-
accumulatedContent: string;
|
|
386
|
-
finalReply: string;
|
|
387
|
-
terminalError?: TerminalErrorInfo;
|
|
388
|
-
}): string {
|
|
389
|
-
const content = state.accumulatedContent + state.finalReply;
|
|
390
|
-
if (state.status !== "error") return content;
|
|
391
|
-
|
|
392
|
-
const error = state.terminalError ?? {
|
|
393
|
-
kind: "unknown" as const,
|
|
394
|
-
title: "原因未记录",
|
|
395
|
-
message: "当前状态中没有可用的错误详情,请查看运行日志。",
|
|
396
|
-
occurredAt: Date.now(),
|
|
397
|
-
};
|
|
398
|
-
const reason = formatTerminalErrorReason(error);
|
|
399
|
-
return content.trim()
|
|
400
|
-
? `${content}\n\n${reason}\n以上内容可能不完整。`
|
|
401
|
-
: reason;
|
|
402
|
-
}
|
|
361
|
+
function monitorsOutputProgress(kind: AgentActivityKind): boolean {
|
|
362
|
+
return kind === "responding";
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function formatTerminalReply(
|
|
366
|
+
status: "running" | "done" | "stopped" | "error" | "auto_ended",
|
|
367
|
+
finalReply: string,
|
|
368
|
+
terminalError?: TerminalErrorInfo,
|
|
369
|
+
): string | null {
|
|
370
|
+
if (status === "auto_ended") return formatAutoEndedReply(finalReply);
|
|
371
|
+
if (status === "error") {
|
|
372
|
+
const error = terminalError ?? {
|
|
373
|
+
kind: "unknown" as const,
|
|
374
|
+
title: "原因未记录",
|
|
375
|
+
message: "当前状态中没有可用的错误详情,请查看运行日志。",
|
|
376
|
+
occurredAt: Date.now(),
|
|
377
|
+
};
|
|
378
|
+
return formatTerminalErrorNotice(error, finalReply);
|
|
379
|
+
}
|
|
380
|
+
return finalReply || null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function formatTerminalCardContent(state: {
|
|
384
|
+
status: "running" | "done" | "stopped" | "error" | "auto_ended";
|
|
385
|
+
accumulatedContent: string;
|
|
386
|
+
finalReply: string;
|
|
387
|
+
terminalError?: TerminalErrorInfo;
|
|
388
|
+
}): string {
|
|
389
|
+
const content = state.accumulatedContent + state.finalReply;
|
|
390
|
+
if (state.status !== "error") return content;
|
|
391
|
+
|
|
392
|
+
const error = state.terminalError ?? {
|
|
393
|
+
kind: "unknown" as const,
|
|
394
|
+
title: "原因未记录",
|
|
395
|
+
message: "当前状态中没有可用的错误详情,请查看运行日志。",
|
|
396
|
+
occurredAt: Date.now(),
|
|
397
|
+
};
|
|
398
|
+
const reason = formatTerminalErrorReason(error);
|
|
399
|
+
return content.trim()
|
|
400
|
+
? `${content}\n\n${reason}\n以上内容可能不完整。`
|
|
401
|
+
: reason;
|
|
402
|
+
}
|
|
403
403
|
|
|
404
404
|
function isCardKitSequenceConflict(err: unknown): boolean {
|
|
405
405
|
return err instanceof Error && err.message.includes("300317");
|
|
@@ -530,11 +530,11 @@ export function resetState(): void {
|
|
|
530
530
|
clearFeishuMessageLedgerMemory();
|
|
531
531
|
lastMsgTimestamps.clear();
|
|
532
532
|
chatPlatformMap.clear();
|
|
533
|
-
for (const prompt of activePrompts.values()) {
|
|
534
|
-
if (prompt.processMonitor) clearInterval(prompt.processMonitor);
|
|
535
|
-
if (prompt.responseStallMonitor) clearInterval(prompt.responseStallMonitor);
|
|
536
|
-
if (prompt.avatarRefreshTimer) clearInterval(prompt.avatarRefreshTimer);
|
|
537
|
-
if (prompt.finalResponseCloseTimer) clearTimeout(prompt.finalResponseCloseTimer);
|
|
533
|
+
for (const prompt of activePrompts.values()) {
|
|
534
|
+
if (prompt.processMonitor) clearInterval(prompt.processMonitor);
|
|
535
|
+
if (prompt.responseStallMonitor) clearInterval(prompt.responseStallMonitor);
|
|
536
|
+
if (prompt.avatarRefreshTimer) clearInterval(prompt.avatarRefreshTimer);
|
|
537
|
+
if (prompt.finalResponseCloseTimer) clearTimeout(prompt.finalResponseCloseTimer);
|
|
538
538
|
}
|
|
539
539
|
activePrompts.clear();
|
|
540
540
|
displayCards.clear();
|
|
@@ -600,7 +600,7 @@ export function getEffectiveFastModeForTool(tool: string, sessionId?: string): b
|
|
|
600
600
|
return config.codex.fastMode;
|
|
601
601
|
}
|
|
602
602
|
|
|
603
|
-
function setSessionChatAvatar(
|
|
603
|
+
function setSessionChatAvatar(
|
|
604
604
|
platform: PlatformAdapter,
|
|
605
605
|
chatId: string,
|
|
606
606
|
tool: string,
|
|
@@ -609,59 +609,59 @@ function setSessionChatAvatar(
|
|
|
609
609
|
): Promise<void> {
|
|
610
610
|
return getEffectiveFastModeForTool(tool, sessionId)
|
|
611
611
|
? platform.setChatAvatar(chatId, tool, status, { fastMode: true })
|
|
612
|
-
: platform.setChatAvatar(chatId, tool, status);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
async function refreshBusySessionAvatar(
|
|
616
|
-
sessionId: string,
|
|
617
|
-
tool: string,
|
|
618
|
-
fallbackPlatform: PlatformAdapter,
|
|
619
|
-
): Promise<void> {
|
|
620
|
-
const chatId = pickDisplayChat(sessionId) ?? getLastActiveChat(sessionId) ?? getChatsForSession(sessionId)[0];
|
|
621
|
-
if (!chatId) return;
|
|
622
|
-
const platform = platformForChat(chatId) ?? fallbackPlatform;
|
|
623
|
-
await setSessionChatAvatar(platform, chatId, tool, "busy", sessionId);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
function startPromptAvatarRefresh(
|
|
627
|
-
sessionId: string,
|
|
628
|
-
tool: string,
|
|
629
|
-
fallbackPlatform: PlatformAdapter,
|
|
630
|
-
runningPrompt: NonNullable<ReturnType<typeof activePrompts.get>>,
|
|
631
|
-
): void {
|
|
632
|
-
let refreshInFlight = false;
|
|
633
|
-
const timer = setInterval(() => {
|
|
634
|
-
const current = activePrompts.get(sessionId);
|
|
635
|
-
if (!current || current !== runningPrompt) {
|
|
636
|
-
clearInterval(timer);
|
|
637
|
-
if (runningPrompt.avatarRefreshTimer === timer) {
|
|
638
|
-
runningPrompt.avatarRefreshTimer = undefined;
|
|
639
|
-
}
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
if (
|
|
643
|
-
refreshInFlight
|
|
644
|
-
|| current.stopped
|
|
645
|
-
|| current.abnormalExit
|
|
646
|
-
|| current.resourceStuck
|
|
647
|
-
|| current.autoEnded
|
|
648
|
-
|| current.finalResponseObserved
|
|
649
|
-
) {
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
refreshInFlight = true;
|
|
654
|
-
void refreshBusySessionAvatar(sessionId, tool, fallbackPlatform)
|
|
655
|
-
.catch((err) => {
|
|
656
|
-
console.warn(`[${ts()}] [AVATAR] Periodic refresh failed for ${sessionId}: ${(err as Error).message}`);
|
|
657
|
-
})
|
|
658
|
-
.finally(() => {
|
|
659
|
-
refreshInFlight = false;
|
|
660
|
-
});
|
|
661
|
-
}, avatarRefreshIntervalMs);
|
|
662
|
-
timer.unref?.();
|
|
663
|
-
runningPrompt.avatarRefreshTimer = timer;
|
|
664
|
-
}
|
|
612
|
+
: platform.setChatAvatar(chatId, tool, status);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async function refreshBusySessionAvatar(
|
|
616
|
+
sessionId: string,
|
|
617
|
+
tool: string,
|
|
618
|
+
fallbackPlatform: PlatformAdapter,
|
|
619
|
+
): Promise<void> {
|
|
620
|
+
const chatId = pickDisplayChat(sessionId) ?? getLastActiveChat(sessionId) ?? getChatsForSession(sessionId)[0];
|
|
621
|
+
if (!chatId) return;
|
|
622
|
+
const platform = platformForChat(chatId) ?? fallbackPlatform;
|
|
623
|
+
await setSessionChatAvatar(platform, chatId, tool, "busy", sessionId);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function startPromptAvatarRefresh(
|
|
627
|
+
sessionId: string,
|
|
628
|
+
tool: string,
|
|
629
|
+
fallbackPlatform: PlatformAdapter,
|
|
630
|
+
runningPrompt: NonNullable<ReturnType<typeof activePrompts.get>>,
|
|
631
|
+
): void {
|
|
632
|
+
let refreshInFlight = false;
|
|
633
|
+
const timer = setInterval(() => {
|
|
634
|
+
const current = activePrompts.get(sessionId);
|
|
635
|
+
if (!current || current !== runningPrompt) {
|
|
636
|
+
clearInterval(timer);
|
|
637
|
+
if (runningPrompt.avatarRefreshTimer === timer) {
|
|
638
|
+
runningPrompt.avatarRefreshTimer = undefined;
|
|
639
|
+
}
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (
|
|
643
|
+
refreshInFlight
|
|
644
|
+
|| current.stopped
|
|
645
|
+
|| current.abnormalExit
|
|
646
|
+
|| current.resourceStuck
|
|
647
|
+
|| current.autoEnded
|
|
648
|
+
|| current.finalResponseObserved
|
|
649
|
+
) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
refreshInFlight = true;
|
|
654
|
+
void refreshBusySessionAvatar(sessionId, tool, fallbackPlatform)
|
|
655
|
+
.catch((err) => {
|
|
656
|
+
console.warn(`[${ts()}] [AVATAR] Periodic refresh failed for ${sessionId}: ${(err as Error).message}`);
|
|
657
|
+
})
|
|
658
|
+
.finally(() => {
|
|
659
|
+
refreshInFlight = false;
|
|
660
|
+
});
|
|
661
|
+
}, avatarRefreshIntervalMs);
|
|
662
|
+
timer.unref?.();
|
|
663
|
+
runningPrompt.avatarRefreshTimer = timer;
|
|
664
|
+
}
|
|
665
665
|
|
|
666
666
|
/** 为指定 session 设置模型覆盖(/model <name>) */
|
|
667
667
|
export function setSessionModelOverride(sessionId: string, model: string): void {
|
|
@@ -707,13 +707,13 @@ export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter
|
|
|
707
707
|
effort: effectiveEffort || undefined,
|
|
708
708
|
fastMode: effectiveFastMode,
|
|
709
709
|
});
|
|
710
|
-
} else if (tool === "ccc") {
|
|
711
|
-
adapter = createCccAdapter({
|
|
712
|
-
apiKey: config.ccc.DEEPSEEK_API_KEY,
|
|
713
|
-
baseURL: config.ccc.DEEPSEEK_BASE_URL,
|
|
714
|
-
model: effectiveModel || undefined,
|
|
715
|
-
effort: effectiveEffort || undefined,
|
|
716
|
-
});
|
|
710
|
+
} else if (tool === "ccc") {
|
|
711
|
+
adapter = createCccAdapter({
|
|
712
|
+
apiKey: config.ccc.DEEPSEEK_API_KEY,
|
|
713
|
+
baseURL: config.ccc.DEEPSEEK_BASE_URL,
|
|
714
|
+
model: effectiveModel || undefined,
|
|
715
|
+
effort: effectiveEffort || undefined,
|
|
716
|
+
});
|
|
717
717
|
} else {
|
|
718
718
|
adapter = createClaudeAdapter({
|
|
719
719
|
model: effectiveModel,
|
|
@@ -1019,16 +1019,16 @@ export function accumulateBlockContent(
|
|
|
1019
1019
|
// 覆盖而非追加:适配器已保证这是一段完整最终文本(如 Cursor 流末快照)
|
|
1020
1020
|
state.finalCompleteText = block.text;
|
|
1021
1021
|
break;
|
|
1022
|
-
case "compact_boundary": {
|
|
1022
|
+
case "compact_boundary": {
|
|
1023
1023
|
const triggerLabel = block.trigger === "manual" ? "手动" : "自动"; // 手动 / 自动
|
|
1024
1024
|
state.accumulatedContent +=
|
|
1025
1025
|
`\n\n🔄 上下文压缩(${triggerLabel}): **${block.pre_tokens}** → **${block.post_tokens}** tokens\n`; // 🔄 / →
|
|
1026
|
-
break;
|
|
1027
|
-
}
|
|
1028
|
-
case "agent_status":
|
|
1029
|
-
break;
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1026
|
+
break;
|
|
1027
|
+
}
|
|
1028
|
+
case "agent_status":
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
1032
|
|
|
1033
1033
|
// ---------------------------------------------------------------------------
|
|
1034
1034
|
// switchChatBinding — /newh、/session N 共用的事务式"切换 chat 绑定"
|
|
@@ -1217,7 +1217,7 @@ export async function initClaudeSession(tool: string, overrideCwd?: string, chat
|
|
|
1217
1217
|
return { sessionId, cwd };
|
|
1218
1218
|
}
|
|
1219
1219
|
|
|
1220
|
-
export async function resumeAndPrompt(
|
|
1220
|
+
export async function resumeAndPrompt(
|
|
1221
1221
|
sessionId: string,
|
|
1222
1222
|
userText: string,
|
|
1223
1223
|
platform: PlatformAdapter,
|
|
@@ -1225,25 +1225,25 @@ export async function resumeAndPrompt(
|
|
|
1225
1225
|
msgTimestamp: number,
|
|
1226
1226
|
tool: string,
|
|
1227
1227
|
traceId?: string,
|
|
1228
|
-
): Promise<SessionRunOutcome> {
|
|
1229
|
-
return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId);
|
|
1230
|
-
}
|
|
1228
|
+
): Promise<SessionRunOutcome> {
|
|
1229
|
+
return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId);
|
|
1230
|
+
}
|
|
1231
1231
|
|
|
1232
1232
|
// ---------------------------------------------------------------------------
|
|
1233
1233
|
// runAgentSession — session 中心的 agent prompt(文件持久化 + display 解耦)
|
|
1234
1234
|
// ---------------------------------------------------------------------------
|
|
1235
1235
|
|
|
1236
|
-
interface RunAgentSessionOptions {
|
|
1236
|
+
interface RunAgentSessionOptions {
|
|
1237
1237
|
/**
|
|
1238
1238
|
* 标记本轮是 response-stall 后的唯一一次内部续跑。若本轮再次因相同原因
|
|
1239
1239
|
* 停滞,不再递归创建第三轮,避免服务异常期间无限消耗 token。
|
|
1240
1240
|
*/
|
|
1241
1241
|
autoRecovery?: boolean;
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
export type SessionRunOutcome = "busy" | "done" | "stopped" | "error" | "auto_ended";
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
export type SessionRunOutcome = "busy" | "done" | "stopped" | "error" | "auto_ended";
|
|
1245
1245
|
|
|
1246
|
-
export async function runAgentSession(
|
|
1246
|
+
export async function runAgentSession(
|
|
1247
1247
|
sessionId: string,
|
|
1248
1248
|
userText: string,
|
|
1249
1249
|
platform: PlatformAdapter,
|
|
@@ -1252,7 +1252,7 @@ export async function runAgentSession(
|
|
|
1252
1252
|
tool: string,
|
|
1253
1253
|
traceId?: string,
|
|
1254
1254
|
options: RunAgentSessionOptions = {},
|
|
1255
|
-
): Promise<SessionRunOutcome> {
|
|
1255
|
+
): Promise<SessionRunOutcome> {
|
|
1256
1256
|
const tid = traceId ?? "";
|
|
1257
1257
|
|
|
1258
1258
|
// runAgentSession 是飞书用户消息、队列消息与内部自动恢复共同使用的唯一
|
|
@@ -1279,7 +1279,7 @@ export async function runAgentSession(
|
|
|
1279
1279
|
? "当前正在生成回复中,请等待完成后再发送消息。也可以发送 /stop 结束,已完成的步骤不会丢失。"
|
|
1280
1280
|
: "该会话正在生成回复中,请等待完成后再发送消息。也可以发送 /stop 结束,已完成的步骤不会丢失。";
|
|
1281
1281
|
await platform.sendText(_chatId, busyMsg).catch(() => {});
|
|
1282
|
-
return "busy";
|
|
1282
|
+
return "busy";
|
|
1283
1283
|
}
|
|
1284
1284
|
|
|
1285
1285
|
// 立即标记活跃,确保 /sessions、isSessionRunning 等查询在异步准备阶段就能看到运行状态。
|
|
@@ -1407,7 +1407,7 @@ export async function runAgentSession(
|
|
|
1407
1407
|
// 再开始缓存问题对应的任务"。
|
|
1408
1408
|
const prevState = await readStreamState(sessionId);
|
|
1409
1409
|
if (prevState && prevState.status !== "running") {
|
|
1410
|
-
const prevTerminalReply = formatTerminalReply(prevState.status, prevState.finalReply, prevState.terminalError);
|
|
1410
|
+
const prevTerminalReply = formatTerminalReply(prevState.status, prevState.finalReply, prevState.terminalError);
|
|
1411
1411
|
const displayChatId = pickDisplayChat(sessionId);
|
|
1412
1412
|
if (displayChatId) {
|
|
1413
1413
|
const pp = platformForChat(displayChatId);
|
|
@@ -1426,8 +1426,8 @@ export async function runAgentSession(
|
|
|
1426
1426
|
setSessionChatAvatar(pp, displayChatId, prevState.tool, "idle", sessionId).catch(() => {});
|
|
1427
1427
|
} else {
|
|
1428
1428
|
const nextSeq = display.sequence + 1;
|
|
1429
|
-
const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(prevState.status, prevState.terminalError);
|
|
1430
|
-
const cardContent = truncateContent(formatTerminalCardContent(prevState)) || " ";
|
|
1429
|
+
const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(prevState.status, prevState.terminalError);
|
|
1430
|
+
const cardContent = truncateContent(formatTerminalCardContent(prevState)) || " ";
|
|
1431
1431
|
const doneCard = buildProgressCard(progressView({ text: cardContent, status: "done", showStop: false, headerTitle, headerTemplate }));
|
|
1432
1432
|
await pp.cardUpdate(display.cardId, doneCard, nextSeq).catch(err => {
|
|
1433
1433
|
console.error(`[${ts()}] [DISPLAY] prevState final cardUpdate failed: ${(err as Error).message}`);
|
|
@@ -1506,7 +1506,7 @@ export async function runAgentSession(
|
|
|
1506
1506
|
}
|
|
1507
1507
|
|
|
1508
1508
|
// 设置最后活跃群头像为 busy
|
|
1509
|
-
refreshBusySessionAvatar(sessionId, tool, platform).catch(() => {});
|
|
1509
|
+
refreshBusySessionAvatar(sessionId, tool, platform).catch(() => {});
|
|
1510
1510
|
|
|
1511
1511
|
const state: AccumulatorState = {
|
|
1512
1512
|
accumulatedContent: "",
|
|
@@ -1517,24 +1517,24 @@ export async function runAgentSession(
|
|
|
1517
1517
|
|
|
1518
1518
|
let lastFileWrite = Date.now();
|
|
1519
1519
|
const FILE_WRITE_INTERVAL_MS = 2000;
|
|
1520
|
-
const toolCallMap = new Map<string, { name: string; input: unknown }>();
|
|
1521
|
-
let streamErrored = false;
|
|
1522
|
-
let streamTerminalError: TerminalErrorInfo | undefined;
|
|
1523
|
-
let runOutcome: SessionRunOutcome = "error";
|
|
1524
|
-
const responseStallDetectionEnabled = adapter.responseStallDetectionEnabled !== false;
|
|
1525
|
-
|
|
1526
|
-
const runningPrompt = activePrompts.get(sessionId);
|
|
1527
|
-
if (runningPrompt) {
|
|
1528
|
-
startPromptAvatarRefresh(sessionId, tool, platform, runningPrompt);
|
|
1529
|
-
|
|
1530
|
-
// 在消费第一个事件前建立阶段感知的零字符基线;启动阶段不计时,只有后续
|
|
1531
|
-
// 收到明确的 responding 状态后才会启动三分钟回复停滞保护。
|
|
1532
|
-
runningPrompt.responseProgress = observeResponseProgress(
|
|
1533
|
-
undefined,
|
|
1534
|
-
monitorsOutputProgress(activityTracker.activity.kind),
|
|
1535
|
-
0,
|
|
1536
|
-
activityTracker.activity.startedAt,
|
|
1537
|
-
);
|
|
1520
|
+
const toolCallMap = new Map<string, { name: string; input: unknown }>();
|
|
1521
|
+
let streamErrored = false;
|
|
1522
|
+
let streamTerminalError: TerminalErrorInfo | undefined;
|
|
1523
|
+
let runOutcome: SessionRunOutcome = "error";
|
|
1524
|
+
const responseStallDetectionEnabled = adapter.responseStallDetectionEnabled !== false;
|
|
1525
|
+
|
|
1526
|
+
const runningPrompt = activePrompts.get(sessionId);
|
|
1527
|
+
if (runningPrompt) {
|
|
1528
|
+
startPromptAvatarRefresh(sessionId, tool, platform, runningPrompt);
|
|
1529
|
+
|
|
1530
|
+
// 在消费第一个事件前建立阶段感知的零字符基线;启动阶段不计时,只有后续
|
|
1531
|
+
// 收到明确的 responding 状态后才会启动三分钟回复停滞保护。
|
|
1532
|
+
runningPrompt.responseProgress = observeResponseProgress(
|
|
1533
|
+
undefined,
|
|
1534
|
+
monitorsOutputProgress(activityTracker.activity.kind),
|
|
1535
|
+
0,
|
|
1536
|
+
activityTracker.activity.startedAt,
|
|
1537
|
+
);
|
|
1538
1538
|
|
|
1539
1539
|
const checkResponseStall = async () => {
|
|
1540
1540
|
const current = activePrompts.get(sessionId);
|
|
@@ -1604,20 +1604,20 @@ export async function runAgentSession(
|
|
|
1604
1604
|
current.controller.abort();
|
|
1605
1605
|
await killProcessTree(current.processPid);
|
|
1606
1606
|
console.warn(
|
|
1607
|
-
`[${ts()}] [RESPONSE-STALL] Session ${sessionId} auto-ended after 3 minutes without reply progress`,
|
|
1607
|
+
`[${ts()}] [RESPONSE-STALL] Session ${sessionId} auto-ended after 3 minutes without reply progress`,
|
|
1608
1608
|
);
|
|
1609
1609
|
};
|
|
1610
1610
|
|
|
1611
|
-
if (responseStallDetectionEnabled) {
|
|
1612
|
-
const responseStallMonitor = setInterval(() => {
|
|
1613
|
-
void checkResponseStall().catch((err) => {
|
|
1614
|
-
console.warn(`[${ts()}] [RESPONSE-STALL] check failed for ${sessionId}: ${(err as Error).message}`);
|
|
1615
|
-
});
|
|
1616
|
-
}, responseStallCheckIntervalMs);
|
|
1617
|
-
responseStallMonitor.unref?.();
|
|
1618
|
-
runningPrompt.responseStallMonitor = responseStallMonitor;
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1611
|
+
if (responseStallDetectionEnabled) {
|
|
1612
|
+
const responseStallMonitor = setInterval(() => {
|
|
1613
|
+
void checkResponseStall().catch((err) => {
|
|
1614
|
+
console.warn(`[${ts()}] [RESPONSE-STALL] check failed for ${sessionId}: ${(err as Error).message}`);
|
|
1615
|
+
});
|
|
1616
|
+
}, responseStallCheckIntervalMs);
|
|
1617
|
+
responseStallMonitor.unref?.();
|
|
1618
|
+
runningPrompt.responseStallMonitor = responseStallMonitor;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
1621
|
|
|
1622
1622
|
try {
|
|
1623
1623
|
for await (const unifiedMsg of adapter.prompt(sessionId, userTextWithCapabilities, cwd, controller.signal, {
|
|
@@ -1667,7 +1667,7 @@ export async function runAgentSession(
|
|
|
1667
1667
|
}
|
|
1668
1668
|
|
|
1669
1669
|
const prompt = activePrompts.get(sessionId);
|
|
1670
|
-
if (prompt && !prompt.autoEnded) {
|
|
1670
|
+
if (prompt && !prompt.autoEnded) {
|
|
1671
1671
|
const totalChars = state.accumulatedContent.length + pickFinalReply(state).length;
|
|
1672
1672
|
prompt.responseProgress = observeResponseProgress(
|
|
1673
1673
|
// starting → responding 本身是一次有效进展,即便首个文本块仍为空也应
|
|
@@ -1698,10 +1698,10 @@ export async function runAgentSession(
|
|
|
1698
1698
|
});
|
|
1699
1699
|
}
|
|
1700
1700
|
}
|
|
1701
|
-
} catch (streamErr) {
|
|
1702
|
-
streamErrored = true;
|
|
1703
|
-
streamTerminalError = classifyTerminalError(streamErr);
|
|
1704
|
-
console.error(`[${ts()}] [STREAM] Error in stream loop for ${sessionId}: ${(streamErr as Error).message}`);
|
|
1701
|
+
} catch (streamErr) {
|
|
1702
|
+
streamErrored = true;
|
|
1703
|
+
streamTerminalError = classifyTerminalError(streamErr);
|
|
1704
|
+
console.error(`[${ts()}] [STREAM] Error in stream loop for ${sessionId}: ${(streamErr as Error).message}`);
|
|
1705
1705
|
} finally {
|
|
1706
1706
|
// 标记 prompt 结束
|
|
1707
1707
|
resourceMonitor.off("stuck", onResourceStuck);
|
|
@@ -1715,10 +1715,10 @@ export async function runAgentSession(
|
|
|
1715
1715
|
const wasAutoEnded = timeoutTriggered && !completedAtTimeoutBoundary;
|
|
1716
1716
|
const wasAutoRecovery = prompt?.autoRecovery ?? false;
|
|
1717
1717
|
const autoEndedAt = wasAutoEnded ? prompt?.autoEndedAt : undefined;
|
|
1718
|
-
clearPromptResponseStallMonitor(sessionId);
|
|
1719
|
-
clearPromptProcessMonitor(sessionId);
|
|
1720
|
-
clearPromptAvatarRefreshTimer(sessionId);
|
|
1721
|
-
clearPromptFinalResponseCloseTimer(sessionId);
|
|
1718
|
+
clearPromptResponseStallMonitor(sessionId);
|
|
1719
|
+
clearPromptProcessMonitor(sessionId);
|
|
1720
|
+
clearPromptAvatarRefreshTimer(sessionId);
|
|
1721
|
+
clearPromptFinalResponseCloseTimer(sessionId);
|
|
1722
1722
|
markSessionFinalizing(sessionId);
|
|
1723
1723
|
activePrompts.delete(sessionId);
|
|
1724
1724
|
|
|
@@ -1750,24 +1750,24 @@ export async function runAgentSession(
|
|
|
1750
1750
|
: wasStopped
|
|
1751
1751
|
? "stopped"
|
|
1752
1752
|
: "done";
|
|
1753
|
-
const finalReply = pickFinalReply(state).trim();
|
|
1754
|
-
const terminalError = streamTerminalError
|
|
1755
|
-
?? (wasAbnormalExit
|
|
1756
|
-
? {
|
|
1757
|
-
kind: "process" as const,
|
|
1758
|
-
title: "Agent 进程意外退出",
|
|
1759
|
-
message: "Agent CLI 进程已退出。若回复不完整,请重新发送上一条指令。",
|
|
1760
|
-
occurredAt: Date.now(),
|
|
1761
|
-
}
|
|
1762
|
-
: wasResourceStuck
|
|
1763
|
-
? {
|
|
1764
|
-
kind: "resource" as const,
|
|
1765
|
-
title: "Agent 进程失去响应",
|
|
1766
|
-
message: "Agent 进程长时间没有 CPU 或内存变化,已被强制停止。若回复不完整,请重新发送上一条指令。",
|
|
1767
|
-
occurredAt: Date.now(),
|
|
1768
|
-
}
|
|
1769
|
-
: undefined);
|
|
1770
|
-
runOutcome = finalStatus;
|
|
1753
|
+
const finalReply = pickFinalReply(state).trim();
|
|
1754
|
+
const terminalError = streamTerminalError
|
|
1755
|
+
?? (wasAbnormalExit
|
|
1756
|
+
? {
|
|
1757
|
+
kind: "process" as const,
|
|
1758
|
+
title: "Agent 进程意外退出",
|
|
1759
|
+
message: "Agent CLI 进程已退出。若回复不完整,请重新发送上一条指令。",
|
|
1760
|
+
occurredAt: Date.now(),
|
|
1761
|
+
}
|
|
1762
|
+
: wasResourceStuck
|
|
1763
|
+
? {
|
|
1764
|
+
kind: "resource" as const,
|
|
1765
|
+
title: "Agent 进程失去响应",
|
|
1766
|
+
message: "Agent 进程长时间没有 CPU 或内存变化,已被强制停止。若回复不完整,请重新发送上一条指令。",
|
|
1767
|
+
occurredAt: Date.now(),
|
|
1768
|
+
}
|
|
1769
|
+
: undefined);
|
|
1770
|
+
runOutcome = finalStatus;
|
|
1771
1771
|
|
|
1772
1772
|
// stop-stuck-loop 接口可能在 fire-and-forget 中已写入带 final_reply 的
|
|
1773
1773
|
// stream state,finally 不应覆盖它。同时保留 stuckAt 标记,防止
|
|
@@ -1797,9 +1797,9 @@ export async function runAgentSession(
|
|
|
1797
1797
|
cwd,
|
|
1798
1798
|
tool,
|
|
1799
1799
|
...(preserveStuckAt ? { stuckAt: preserveStuckAt } : {}),
|
|
1800
|
-
...(autoEndedAt !== undefined ? { autoEndedAt } : {}),
|
|
1801
|
-
...(terminalError ? { terminalError } : {}),
|
|
1802
|
-
});
|
|
1800
|
+
...(autoEndedAt !== undefined ? { autoEndedAt } : {}),
|
|
1801
|
+
...(terminalError ? { terminalError } : {}),
|
|
1802
|
+
});
|
|
1803
1803
|
|
|
1804
1804
|
// display loop 下一轮会读到最终状态并发送消息
|
|
1805
1805
|
|
|
@@ -1870,9 +1870,9 @@ export async function runAgentSession(
|
|
|
1870
1870
|
autoRecoveryTarget = { chatId: activeAutoEnded, platform: pp };
|
|
1871
1871
|
}
|
|
1872
1872
|
}
|
|
1873
|
-
console.warn(`[${ts()}] Session ${sessionId} auto-ended after stalled response output (content chunks: ${state.chunkCount})`);
|
|
1873
|
+
console.warn(`[${ts()}] Session ${sessionId} auto-ended after stalled response output (content chunks: ${state.chunkCount})`);
|
|
1874
1874
|
if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "response_stall", chunks: state.chunkCount });
|
|
1875
|
-
} else if (wasAbnormalExit) {
|
|
1875
|
+
} else if (wasAbnormalExit) {
|
|
1876
1876
|
for (const cid of finalizationChatIds) {
|
|
1877
1877
|
const finfo = sessionInfoMap.get(cid);
|
|
1878
1878
|
await recordSessionRegistry({
|
|
@@ -1887,55 +1887,55 @@ export async function runAgentSession(
|
|
|
1887
1887
|
}
|
|
1888
1888
|
const activeErr = getLastActiveChat(sessionId) ?? finalizationChatIds[0];
|
|
1889
1889
|
if (activeErr) setSessionChatAvatar(platform, activeErr, tool, "idle", sessionId).catch(() => {});
|
|
1890
|
-
console.log(`[${ts()}] Session ${sessionId} process exited unexpectedly (content chunks: ${state.chunkCount})`);
|
|
1891
|
-
if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "process_missing", chunks: state.chunkCount });
|
|
1892
|
-
} else if (streamErrored || wasResourceStuck) {
|
|
1893
|
-
for (const cid of finalizationChatIds) {
|
|
1894
|
-
const finfo = sessionInfoMap.get(cid);
|
|
1895
|
-
await recordSessionRegistry({
|
|
1896
|
-
chatId: cid,
|
|
1897
|
-
sessionId,
|
|
1898
|
-
tool,
|
|
1899
|
-
turnCount: finfo?.turnCount ?? nextTurnCount,
|
|
1900
|
-
lastContextTokens: finfo?.lastContextTokens ?? nextContextTokens,
|
|
1901
|
-
startTime: finfo?.startTime ?? now,
|
|
1902
|
-
running: false,
|
|
1903
|
-
});
|
|
1904
|
-
}
|
|
1905
|
-
const activeErr = getLastActiveChat(sessionId) ?? finalizationChatIds[0];
|
|
1906
|
-
if (activeErr) {
|
|
1907
|
-
const pp = platformForChat(activeErr) ?? platform;
|
|
1908
|
-
const terminalState = await readStreamState(sessionId);
|
|
1909
|
-
if (
|
|
1910
|
-
terminalError
|
|
1911
|
-
&& !displayCards.has(activeErr)
|
|
1912
|
-
&& (!terminalState || !isFinalReplySentForTurn(terminalState))
|
|
1913
|
-
) {
|
|
1914
|
-
await sendFinalReplyTextOnce(
|
|
1915
|
-
pp,
|
|
1916
|
-
activeErr,
|
|
1917
|
-
sessionId,
|
|
1918
|
-
nextTurnCount,
|
|
1919
|
-
formatTerminalErrorNotice(terminalError, finalReplyToWrite),
|
|
1920
|
-
);
|
|
1921
|
-
}
|
|
1922
|
-
setSessionChatAvatar(pp, activeErr, tool, "idle", sessionId).catch(() => {});
|
|
1923
|
-
}
|
|
1924
|
-
const errorOutcome = wasResourceStuck ? "resource_stuck" : "stream_error";
|
|
1925
|
-
console.error(
|
|
1926
|
-
`[${ts()}] Session ${sessionId} ended with ${errorOutcome}` +
|
|
1927
|
-
`${terminalError ? ` (${terminalError.kind}: ${terminalError.title})` : ""} (content chunks: ${state.chunkCount})`,
|
|
1928
|
-
);
|
|
1929
|
-
if (tid) {
|
|
1930
|
-
logTrace(tid, "SESSION_END", {
|
|
1931
|
-
sessionId,
|
|
1932
|
-
outcome: errorOutcome,
|
|
1933
|
-
errorKind: terminalError?.kind,
|
|
1934
|
-
errorTitle: terminalError?.title,
|
|
1935
|
-
chunks: state.chunkCount,
|
|
1936
|
-
});
|
|
1937
|
-
}
|
|
1938
|
-
} else {
|
|
1890
|
+
console.log(`[${ts()}] Session ${sessionId} process exited unexpectedly (content chunks: ${state.chunkCount})`);
|
|
1891
|
+
if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "process_missing", chunks: state.chunkCount });
|
|
1892
|
+
} else if (streamErrored || wasResourceStuck) {
|
|
1893
|
+
for (const cid of finalizationChatIds) {
|
|
1894
|
+
const finfo = sessionInfoMap.get(cid);
|
|
1895
|
+
await recordSessionRegistry({
|
|
1896
|
+
chatId: cid,
|
|
1897
|
+
sessionId,
|
|
1898
|
+
tool,
|
|
1899
|
+
turnCount: finfo?.turnCount ?? nextTurnCount,
|
|
1900
|
+
lastContextTokens: finfo?.lastContextTokens ?? nextContextTokens,
|
|
1901
|
+
startTime: finfo?.startTime ?? now,
|
|
1902
|
+
running: false,
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
const activeErr = getLastActiveChat(sessionId) ?? finalizationChatIds[0];
|
|
1906
|
+
if (activeErr) {
|
|
1907
|
+
const pp = platformForChat(activeErr) ?? platform;
|
|
1908
|
+
const terminalState = await readStreamState(sessionId);
|
|
1909
|
+
if (
|
|
1910
|
+
terminalError
|
|
1911
|
+
&& !displayCards.has(activeErr)
|
|
1912
|
+
&& (!terminalState || !isFinalReplySentForTurn(terminalState))
|
|
1913
|
+
) {
|
|
1914
|
+
await sendFinalReplyTextOnce(
|
|
1915
|
+
pp,
|
|
1916
|
+
activeErr,
|
|
1917
|
+
sessionId,
|
|
1918
|
+
nextTurnCount,
|
|
1919
|
+
formatTerminalErrorNotice(terminalError, finalReplyToWrite),
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
setSessionChatAvatar(pp, activeErr, tool, "idle", sessionId).catch(() => {});
|
|
1923
|
+
}
|
|
1924
|
+
const errorOutcome = wasResourceStuck ? "resource_stuck" : "stream_error";
|
|
1925
|
+
console.error(
|
|
1926
|
+
`[${ts()}] Session ${sessionId} ended with ${errorOutcome}` +
|
|
1927
|
+
`${terminalError ? ` (${terminalError.kind}: ${terminalError.title})` : ""} (content chunks: ${state.chunkCount})`,
|
|
1928
|
+
);
|
|
1929
|
+
if (tid) {
|
|
1930
|
+
logTrace(tid, "SESSION_END", {
|
|
1931
|
+
sessionId,
|
|
1932
|
+
outcome: errorOutcome,
|
|
1933
|
+
errorKind: terminalError?.kind,
|
|
1934
|
+
errorTitle: terminalError?.title,
|
|
1935
|
+
chunks: state.chunkCount,
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
} else {
|
|
1939
1939
|
for (const cid of finalizationChatIds) {
|
|
1940
1940
|
const finfo = sessionInfoMap.get(cid);
|
|
1941
1941
|
await recordSessionRegistry({
|
|
@@ -2047,9 +2047,9 @@ export async function runAgentSession(
|
|
|
2047
2047
|
} finally {
|
|
2048
2048
|
clearSessionFinalizing(sessionId);
|
|
2049
2049
|
}
|
|
2050
|
-
}
|
|
2051
|
-
return runOutcome;
|
|
2052
|
-
}
|
|
2050
|
+
}
|
|
2051
|
+
return runOutcome;
|
|
2052
|
+
}
|
|
2053
2053
|
|
|
2054
2054
|
// ---------------------------------------------------------------------------
|
|
2055
2055
|
// startUnifiedDisplayLoop — 全局统一 display 循环,遍历 displayCards 更新卡片
|
|
@@ -2126,13 +2126,13 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
2126
2126
|
if (activePrompts.has(sessionId)) continue;
|
|
2127
2127
|
|
|
2128
2128
|
const tail = "━━━ 回答结束 ━━━";
|
|
2129
|
-
const finalMsg = state.status === "auto_ended"
|
|
2130
|
-
? formatAutoEndedReply(remaining)
|
|
2131
|
-
: state.status === "error"
|
|
2132
|
-
? formatTerminalReply(state.status, remaining, state.terminalError) ?? tail
|
|
2133
|
-
: remaining
|
|
2134
|
-
? remaining + "\n" + tail
|
|
2135
|
-
: tail;
|
|
2129
|
+
const finalMsg = state.status === "auto_ended"
|
|
2130
|
+
? formatAutoEndedReply(remaining)
|
|
2131
|
+
: state.status === "error"
|
|
2132
|
+
? formatTerminalReply(state.status, remaining, state.terminalError) ?? tail
|
|
2133
|
+
: remaining
|
|
2134
|
+
? remaining + "\n" + tail
|
|
2135
|
+
: tail;
|
|
2136
2136
|
if (!isFinalReplySentForTurn(state)) {
|
|
2137
2137
|
await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, finalMsg);
|
|
2138
2138
|
}
|
|
@@ -2154,8 +2154,8 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
2154
2154
|
let terminalCardUpdateAccepted = terminalCardAlreadyUpdated;
|
|
2155
2155
|
if (!terminalCardAlreadyUpdated) {
|
|
2156
2156
|
const nextSeq = display.sequence + 1;
|
|
2157
|
-
const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(state.status, state.terminalError);
|
|
2158
|
-
const cardContent = truncateContent(formatTerminalCardContent(state)) || " ";
|
|
2157
|
+
const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(state.status, state.terminalError);
|
|
2158
|
+
const cardContent = truncateContent(formatTerminalCardContent(state)) || " ";
|
|
2159
2159
|
const doneCard = buildProgressCard(progressView({ text: cardContent, status: "done", showStop: false, headerTitle, headerTemplate }));
|
|
2160
2160
|
await p.cardUpdate(display.cardId, doneCard, nextSeq).then(() => {
|
|
2161
2161
|
display.sequence = nextSeq;
|
|
@@ -2182,19 +2182,19 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
2182
2182
|
}
|
|
2183
2183
|
|
|
2184
2184
|
let terminalTextDelivered = true;
|
|
2185
|
-
const terminalReply = formatTerminalReply(state.status, state.finalReply, state.terminalError);
|
|
2186
|
-
const errorWasDeliveredByCard =
|
|
2187
|
-
state.status === "error"
|
|
2188
|
-
&& !state.finalReply.trim()
|
|
2189
|
-
&& terminalCardUpdateAccepted;
|
|
2190
|
-
if (errorWasDeliveredByCard) {
|
|
2191
|
-
if (!isFinalReplySentForTurn(state)) {
|
|
2192
|
-
await markFinalReplySent(sessionId, state.turnCount);
|
|
2193
|
-
}
|
|
2194
|
-
} else if (terminalReply) {
|
|
2195
|
-
if (!isFinalReplySentForTurn(state)) {
|
|
2196
|
-
terminalTextDelivered = await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, terminalReply);
|
|
2197
|
-
}
|
|
2185
|
+
const terminalReply = formatTerminalReply(state.status, state.finalReply, state.terminalError);
|
|
2186
|
+
const errorWasDeliveredByCard =
|
|
2187
|
+
state.status === "error"
|
|
2188
|
+
&& !state.finalReply.trim()
|
|
2189
|
+
&& terminalCardUpdateAccepted;
|
|
2190
|
+
if (errorWasDeliveredByCard) {
|
|
2191
|
+
if (!isFinalReplySentForTurn(state)) {
|
|
2192
|
+
await markFinalReplySent(sessionId, state.turnCount);
|
|
2193
|
+
}
|
|
2194
|
+
} else if (terminalReply) {
|
|
2195
|
+
if (!isFinalReplySentForTurn(state)) {
|
|
2196
|
+
terminalTextDelivered = await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, terminalReply);
|
|
2197
|
+
}
|
|
2198
2198
|
} else if (state.accumulatedContent.trim()) {
|
|
2199
2199
|
const short = truncateContent(state.accumulatedContent, 30, 4000);
|
|
2200
2200
|
terminalTextDelivered = await p.sendText(chatId, `[生成过程]\n${short}`).then((ok) => ok !== false).catch(() => false);
|
|
@@ -2425,11 +2425,11 @@ export function stopSession(sessionId: string): boolean {
|
|
|
2425
2425
|
}
|
|
2426
2426
|
return false;
|
|
2427
2427
|
}
|
|
2428
|
-
prompt.stopped = true;
|
|
2429
|
-
clearPromptResponseStallMonitor(sessionId);
|
|
2430
|
-
clearPromptProcessMonitor(sessionId);
|
|
2431
|
-
clearPromptAvatarRefreshTimer(sessionId);
|
|
2432
|
-
clearPromptFinalResponseCloseTimer(sessionId);
|
|
2428
|
+
prompt.stopped = true;
|
|
2429
|
+
clearPromptResponseStallMonitor(sessionId);
|
|
2430
|
+
clearPromptProcessMonitor(sessionId);
|
|
2431
|
+
clearPromptAvatarRefreshTimer(sessionId);
|
|
2432
|
+
clearPromptFinalResponseCloseTimer(sessionId);
|
|
2433
2433
|
cancelQueuedMessage(sessionId);
|
|
2434
2434
|
|
|
2435
2435
|
// 先发起整棵进程树清理,再触发 close/abort。Windows 上 CLI 由
|