chatccc 0.2.243 → 0.2.245
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 +6 -6
- package/bin/cccagent.mjs +17 -17
- package/deepccc-agent/README.md +14 -8
- package/deepccc-agent/bin/deepccc.mjs +26 -26
- package/deepccc-agent/os-prompts/darwin.md +8 -0
- package/deepccc-agent/os-prompts/linux.md +8 -0
- package/deepccc-agent/os-prompts/win32.md +9 -0
- package/deepccc-agent/package-lock.json +2 -2
- package/deepccc-agent/package.json +63 -62
- package/deepccc-agent/src/__tests__/chat-session.test.ts +682 -578
- package/deepccc-agent/src/__tests__/cli-json.test.ts +49 -49
- package/deepccc-agent/src/__tests__/config.test.ts +26 -26
- package/deepccc-agent/src/__tests__/context.test.ts +319 -319
- package/deepccc-agent/src/__tests__/file-tools.test.ts +240 -240
- package/deepccc-agent/src/__tests__/permissions.test.ts +195 -195
- package/deepccc-agent/src/__tests__/privacy.test.ts +8 -5
- package/deepccc-agent/src/__tests__/progress-reducer.test.ts +121 -121
- package/deepccc-agent/src/__tests__/session-search.test.ts +262 -262
- package/deepccc-agent/src/__tests__/session-select.test.ts +116 -116
- package/deepccc-agent/src/__tests__/sigint.test.ts +56 -56
- package/deepccc-agent/src/__tests__/skills.test.ts +284 -284
- package/deepccc-agent/src/__tests__/terminal-renderer.test.ts +247 -247
- package/deepccc-agent/src/__tests__/web-tools.test.ts +220 -220
- package/deepccc-agent/src/cli.ts +7 -6
- package/deepccc-agent/src/config.ts +88 -84
- package/deepccc-agent/src/context.ts +465 -465
- package/deepccc-agent/src/file-log.ts +38 -38
- package/deepccc-agent/src/index.ts +103 -36
- package/deepccc-agent/src/proc-tree-kill.ts +61 -61
- package/deepccc-agent/src/progress/cards-helpers.ts +76 -76
- package/deepccc-agent/src/progress/reducer.ts +113 -113
- package/deepccc-agent/src/progress/terminal-renderer.ts +294 -294
- package/deepccc-agent/src/progress/view.ts +77 -77
- package/deepccc-agent/src/raw-stream-log.ts +124 -124
- package/deepccc-agent/src/session-search.ts +370 -370
- package/deepccc-agent/src/session-select.ts +48 -48
- package/deepccc-agent/src/sigint.ts +50 -50
- package/deepccc-agent/src/skills.ts +205 -205
- package/deepccc-agent/src/web-tools.ts +313 -313
- package/deepccc-agent/tsconfig.build.json +13 -13
- package/deepccc-agent/tsconfig.json +13 -13
- package/deepccc-agent/vitest.config.ts +7 -7
- package/package.json +1 -1
- package/src/__tests__/builtin-chat-session.test.ts +522 -522
- package/src/__tests__/builtin-config.test.ts +26 -26
- package/src/__tests__/builtin-context.test.ts +319 -319
- package/src/__tests__/builtin-file-tools.test.ts +240 -240
- package/src/__tests__/builtin-permissions.test.ts +211 -211
- package/src/__tests__/builtin-session-search.test.ts +262 -262
- package/src/__tests__/builtin-session-select.test.ts +116 -116
- package/src/__tests__/builtin-sigint.test.ts +56 -56
- package/src/__tests__/builtin-skills.test.ts +284 -284
- package/src/__tests__/builtin-web-tools.test.ts +220 -220
- package/src/__tests__/ccc-adapter.test.ts +15 -0
- package/src/__tests__/config.test.ts +17 -17
- package/src/__tests__/progress-reducer.test.ts +121 -121
- package/src/__tests__/session-ccc-config.test.ts +45 -45
- package/src/__tests__/session.test.ts +369 -306
- package/src/adapters/adapter-interface.ts +8 -2
- package/src/adapters/ccc-adapter.ts +149 -145
- package/src/config-utils.ts +13 -13
- package/src/config.ts +13 -13
- package/src/progress/reducer.ts +113 -113
- package/src/session-chat-binding.ts +83 -83
- package/src/session.ts +323 -320
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,23 +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
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
//
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
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
|
+
);
|
|
1537
1538
|
|
|
1538
1539
|
const checkResponseStall = async () => {
|
|
1539
1540
|
const current = activePrompts.get(sessionId);
|
|
@@ -1603,18 +1604,20 @@ export async function runAgentSession(
|
|
|
1603
1604
|
current.controller.abort();
|
|
1604
1605
|
await killProcessTree(current.processPid);
|
|
1605
1606
|
console.warn(
|
|
1606
|
-
`[${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`,
|
|
1607
1608
|
);
|
|
1608
1609
|
};
|
|
1609
1610
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
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
|
+
}
|
|
1618
1621
|
|
|
1619
1622
|
try {
|
|
1620
1623
|
for await (const unifiedMsg of adapter.prompt(sessionId, userTextWithCapabilities, cwd, controller.signal, {
|
|
@@ -1664,7 +1667,7 @@ export async function runAgentSession(
|
|
|
1664
1667
|
}
|
|
1665
1668
|
|
|
1666
1669
|
const prompt = activePrompts.get(sessionId);
|
|
1667
|
-
if (prompt && !prompt.autoEnded) {
|
|
1670
|
+
if (prompt && !prompt.autoEnded) {
|
|
1668
1671
|
const totalChars = state.accumulatedContent.length + pickFinalReply(state).length;
|
|
1669
1672
|
prompt.responseProgress = observeResponseProgress(
|
|
1670
1673
|
// starting → responding 本身是一次有效进展,即便首个文本块仍为空也应
|
|
@@ -1695,10 +1698,10 @@ export async function runAgentSession(
|
|
|
1695
1698
|
});
|
|
1696
1699
|
}
|
|
1697
1700
|
}
|
|
1698
|
-
} catch (streamErr) {
|
|
1699
|
-
streamErrored = true;
|
|
1700
|
-
streamTerminalError = classifyTerminalError(streamErr);
|
|
1701
|
-
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}`);
|
|
1702
1705
|
} finally {
|
|
1703
1706
|
// 标记 prompt 结束
|
|
1704
1707
|
resourceMonitor.off("stuck", onResourceStuck);
|
|
@@ -1712,10 +1715,10 @@ export async function runAgentSession(
|
|
|
1712
1715
|
const wasAutoEnded = timeoutTriggered && !completedAtTimeoutBoundary;
|
|
1713
1716
|
const wasAutoRecovery = prompt?.autoRecovery ?? false;
|
|
1714
1717
|
const autoEndedAt = wasAutoEnded ? prompt?.autoEndedAt : undefined;
|
|
1715
|
-
clearPromptResponseStallMonitor(sessionId);
|
|
1716
|
-
clearPromptProcessMonitor(sessionId);
|
|
1717
|
-
clearPromptAvatarRefreshTimer(sessionId);
|
|
1718
|
-
clearPromptFinalResponseCloseTimer(sessionId);
|
|
1718
|
+
clearPromptResponseStallMonitor(sessionId);
|
|
1719
|
+
clearPromptProcessMonitor(sessionId);
|
|
1720
|
+
clearPromptAvatarRefreshTimer(sessionId);
|
|
1721
|
+
clearPromptFinalResponseCloseTimer(sessionId);
|
|
1719
1722
|
markSessionFinalizing(sessionId);
|
|
1720
1723
|
activePrompts.delete(sessionId);
|
|
1721
1724
|
|
|
@@ -1747,24 +1750,24 @@ export async function runAgentSession(
|
|
|
1747
1750
|
: wasStopped
|
|
1748
1751
|
? "stopped"
|
|
1749
1752
|
: "done";
|
|
1750
|
-
const finalReply = pickFinalReply(state).trim();
|
|
1751
|
-
const terminalError = streamTerminalError
|
|
1752
|
-
?? (wasAbnormalExit
|
|
1753
|
-
? {
|
|
1754
|
-
kind: "process" as const,
|
|
1755
|
-
title: "Agent 进程意外退出",
|
|
1756
|
-
message: "Agent CLI 进程已退出。若回复不完整,请重新发送上一条指令。",
|
|
1757
|
-
occurredAt: Date.now(),
|
|
1758
|
-
}
|
|
1759
|
-
: wasResourceStuck
|
|
1760
|
-
? {
|
|
1761
|
-
kind: "resource" as const,
|
|
1762
|
-
title: "Agent 进程失去响应",
|
|
1763
|
-
message: "Agent 进程长时间没有 CPU 或内存变化,已被强制停止。若回复不完整,请重新发送上一条指令。",
|
|
1764
|
-
occurredAt: Date.now(),
|
|
1765
|
-
}
|
|
1766
|
-
: undefined);
|
|
1767
|
-
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;
|
|
1768
1771
|
|
|
1769
1772
|
// stop-stuck-loop 接口可能在 fire-and-forget 中已写入带 final_reply 的
|
|
1770
1773
|
// stream state,finally 不应覆盖它。同时保留 stuckAt 标记,防止
|
|
@@ -1794,9 +1797,9 @@ export async function runAgentSession(
|
|
|
1794
1797
|
cwd,
|
|
1795
1798
|
tool,
|
|
1796
1799
|
...(preserveStuckAt ? { stuckAt: preserveStuckAt } : {}),
|
|
1797
|
-
...(autoEndedAt !== undefined ? { autoEndedAt } : {}),
|
|
1798
|
-
...(terminalError ? { terminalError } : {}),
|
|
1799
|
-
});
|
|
1800
|
+
...(autoEndedAt !== undefined ? { autoEndedAt } : {}),
|
|
1801
|
+
...(terminalError ? { terminalError } : {}),
|
|
1802
|
+
});
|
|
1800
1803
|
|
|
1801
1804
|
// display loop 下一轮会读到最终状态并发送消息
|
|
1802
1805
|
|
|
@@ -1867,9 +1870,9 @@ export async function runAgentSession(
|
|
|
1867
1870
|
autoRecoveryTarget = { chatId: activeAutoEnded, platform: pp };
|
|
1868
1871
|
}
|
|
1869
1872
|
}
|
|
1870
|
-
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})`);
|
|
1871
1874
|
if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "response_stall", chunks: state.chunkCount });
|
|
1872
|
-
} else if (wasAbnormalExit) {
|
|
1875
|
+
} else if (wasAbnormalExit) {
|
|
1873
1876
|
for (const cid of finalizationChatIds) {
|
|
1874
1877
|
const finfo = sessionInfoMap.get(cid);
|
|
1875
1878
|
await recordSessionRegistry({
|
|
@@ -1884,55 +1887,55 @@ export async function runAgentSession(
|
|
|
1884
1887
|
}
|
|
1885
1888
|
const activeErr = getLastActiveChat(sessionId) ?? finalizationChatIds[0];
|
|
1886
1889
|
if (activeErr) setSessionChatAvatar(platform, activeErr, tool, "idle", sessionId).catch(() => {});
|
|
1887
|
-
console.log(`[${ts()}] Session ${sessionId} process exited unexpectedly (content chunks: ${state.chunkCount})`);
|
|
1888
|
-
if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "process_missing", chunks: state.chunkCount });
|
|
1889
|
-
} else if (streamErrored || wasResourceStuck) {
|
|
1890
|
-
for (const cid of finalizationChatIds) {
|
|
1891
|
-
const finfo = sessionInfoMap.get(cid);
|
|
1892
|
-
await recordSessionRegistry({
|
|
1893
|
-
chatId: cid,
|
|
1894
|
-
sessionId,
|
|
1895
|
-
tool,
|
|
1896
|
-
turnCount: finfo?.turnCount ?? nextTurnCount,
|
|
1897
|
-
lastContextTokens: finfo?.lastContextTokens ?? nextContextTokens,
|
|
1898
|
-
startTime: finfo?.startTime ?? now,
|
|
1899
|
-
running: false,
|
|
1900
|
-
});
|
|
1901
|
-
}
|
|
1902
|
-
const activeErr = getLastActiveChat(sessionId) ?? finalizationChatIds[0];
|
|
1903
|
-
if (activeErr) {
|
|
1904
|
-
const pp = platformForChat(activeErr) ?? platform;
|
|
1905
|
-
const terminalState = await readStreamState(sessionId);
|
|
1906
|
-
if (
|
|
1907
|
-
terminalError
|
|
1908
|
-
&& !displayCards.has(activeErr)
|
|
1909
|
-
&& (!terminalState || !isFinalReplySentForTurn(terminalState))
|
|
1910
|
-
) {
|
|
1911
|
-
await sendFinalReplyTextOnce(
|
|
1912
|
-
pp,
|
|
1913
|
-
activeErr,
|
|
1914
|
-
sessionId,
|
|
1915
|
-
nextTurnCount,
|
|
1916
|
-
formatTerminalErrorNotice(terminalError, finalReplyToWrite),
|
|
1917
|
-
);
|
|
1918
|
-
}
|
|
1919
|
-
setSessionChatAvatar(pp, activeErr, tool, "idle", sessionId).catch(() => {});
|
|
1920
|
-
}
|
|
1921
|
-
const errorOutcome = wasResourceStuck ? "resource_stuck" : "stream_error";
|
|
1922
|
-
console.error(
|
|
1923
|
-
`[${ts()}] Session ${sessionId} ended with ${errorOutcome}` +
|
|
1924
|
-
`${terminalError ? ` (${terminalError.kind}: ${terminalError.title})` : ""} (content chunks: ${state.chunkCount})`,
|
|
1925
|
-
);
|
|
1926
|
-
if (tid) {
|
|
1927
|
-
logTrace(tid, "SESSION_END", {
|
|
1928
|
-
sessionId,
|
|
1929
|
-
outcome: errorOutcome,
|
|
1930
|
-
errorKind: terminalError?.kind,
|
|
1931
|
-
errorTitle: terminalError?.title,
|
|
1932
|
-
chunks: state.chunkCount,
|
|
1933
|
-
});
|
|
1934
|
-
}
|
|
1935
|
-
} 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 {
|
|
1936
1939
|
for (const cid of finalizationChatIds) {
|
|
1937
1940
|
const finfo = sessionInfoMap.get(cid);
|
|
1938
1941
|
await recordSessionRegistry({
|
|
@@ -2044,9 +2047,9 @@ export async function runAgentSession(
|
|
|
2044
2047
|
} finally {
|
|
2045
2048
|
clearSessionFinalizing(sessionId);
|
|
2046
2049
|
}
|
|
2047
|
-
}
|
|
2048
|
-
return runOutcome;
|
|
2049
|
-
}
|
|
2050
|
+
}
|
|
2051
|
+
return runOutcome;
|
|
2052
|
+
}
|
|
2050
2053
|
|
|
2051
2054
|
// ---------------------------------------------------------------------------
|
|
2052
2055
|
// startUnifiedDisplayLoop — 全局统一 display 循环,遍历 displayCards 更新卡片
|
|
@@ -2123,13 +2126,13 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
2123
2126
|
if (activePrompts.has(sessionId)) continue;
|
|
2124
2127
|
|
|
2125
2128
|
const tail = "━━━ 回答结束 ━━━";
|
|
2126
|
-
const finalMsg = state.status === "auto_ended"
|
|
2127
|
-
? formatAutoEndedReply(remaining)
|
|
2128
|
-
: state.status === "error"
|
|
2129
|
-
? formatTerminalReply(state.status, remaining, state.terminalError) ?? tail
|
|
2130
|
-
: remaining
|
|
2131
|
-
? remaining + "\n" + tail
|
|
2132
|
-
: 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;
|
|
2133
2136
|
if (!isFinalReplySentForTurn(state)) {
|
|
2134
2137
|
await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, finalMsg);
|
|
2135
2138
|
}
|
|
@@ -2151,8 +2154,8 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
2151
2154
|
let terminalCardUpdateAccepted = terminalCardAlreadyUpdated;
|
|
2152
2155
|
if (!terminalCardAlreadyUpdated) {
|
|
2153
2156
|
const nextSeq = display.sequence + 1;
|
|
2154
|
-
const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(state.status, state.terminalError);
|
|
2155
|
-
const cardContent = truncateContent(formatTerminalCardContent(state)) || " ";
|
|
2157
|
+
const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(state.status, state.terminalError);
|
|
2158
|
+
const cardContent = truncateContent(formatTerminalCardContent(state)) || " ";
|
|
2156
2159
|
const doneCard = buildProgressCard(progressView({ text: cardContent, status: "done", showStop: false, headerTitle, headerTemplate }));
|
|
2157
2160
|
await p.cardUpdate(display.cardId, doneCard, nextSeq).then(() => {
|
|
2158
2161
|
display.sequence = nextSeq;
|
|
@@ -2179,19 +2182,19 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
2179
2182
|
}
|
|
2180
2183
|
|
|
2181
2184
|
let terminalTextDelivered = true;
|
|
2182
|
-
const terminalReply = formatTerminalReply(state.status, state.finalReply, state.terminalError);
|
|
2183
|
-
const errorWasDeliveredByCard =
|
|
2184
|
-
state.status === "error"
|
|
2185
|
-
&& !state.finalReply.trim()
|
|
2186
|
-
&& terminalCardUpdateAccepted;
|
|
2187
|
-
if (errorWasDeliveredByCard) {
|
|
2188
|
-
if (!isFinalReplySentForTurn(state)) {
|
|
2189
|
-
await markFinalReplySent(sessionId, state.turnCount);
|
|
2190
|
-
}
|
|
2191
|
-
} else if (terminalReply) {
|
|
2192
|
-
if (!isFinalReplySentForTurn(state)) {
|
|
2193
|
-
terminalTextDelivered = await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, terminalReply);
|
|
2194
|
-
}
|
|
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
|
+
}
|
|
2195
2198
|
} else if (state.accumulatedContent.trim()) {
|
|
2196
2199
|
const short = truncateContent(state.accumulatedContent, 30, 4000);
|
|
2197
2200
|
terminalTextDelivered = await p.sendText(chatId, `[生成过程]\n${short}`).then((ok) => ok !== false).catch(() => false);
|
|
@@ -2422,11 +2425,11 @@ export function stopSession(sessionId: string): boolean {
|
|
|
2422
2425
|
}
|
|
2423
2426
|
return false;
|
|
2424
2427
|
}
|
|
2425
|
-
prompt.stopped = true;
|
|
2426
|
-
clearPromptResponseStallMonitor(sessionId);
|
|
2427
|
-
clearPromptProcessMonitor(sessionId);
|
|
2428
|
-
clearPromptAvatarRefreshTimer(sessionId);
|
|
2429
|
-
clearPromptFinalResponseCloseTimer(sessionId);
|
|
2428
|
+
prompt.stopped = true;
|
|
2429
|
+
clearPromptResponseStallMonitor(sessionId);
|
|
2430
|
+
clearPromptProcessMonitor(sessionId);
|
|
2431
|
+
clearPromptAvatarRefreshTimer(sessionId);
|
|
2432
|
+
clearPromptFinalResponseCloseTimer(sessionId);
|
|
2430
2433
|
cancelQueuedMessage(sessionId);
|
|
2431
2434
|
|
|
2432
2435
|
// 先发起整棵进程树清理,再触发 close/abort。Windows 上 CLI 由
|