chatccc 0.2.229 → 0.2.231
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/.agents/skills/create-chatccc-feishu-app/SKILL.md +85 -85
- package/.claude/skills/create-chatccc-feishu-app/SKILL.md +85 -85
- package/.cursor/skills/create-chatccc-feishu-app/SKILL.md +85 -85
- package/README.md +90 -90
- package/agent-prompts/claude_specific.md +45 -45
- package/agent-prompts/codex_specific.md +2 -2
- package/agent-prompts/cursor_specific.md +13 -13
- package/bin/cccagent.mjs +17 -17
- package/im-skills/feishu-skill/receive-send-file.md +63 -63
- package/im-skills/feishu-skill/receive-send-image.md +24 -24
- package/im-skills/feishu-skill/skill.md +3 -3
- package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
- package/im-skills/wechat-file-skill/send-file.mjs +83 -83
- package/im-skills/wechat-file-skill/skill.md +10 -10
- package/im-skills/wechat-image-skill/skill.md +10 -10
- package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
- package/im-skills/wechat-video-skill/send-video.mjs +79 -79
- package/im-skills/wechat-video-skill/skill.md +10 -10
- package/package.json +1 -1
- package/scripts/postinstall-sharp-check.mjs +58 -58
- package/src/__tests__/agent-activity.test.ts +86 -76
- package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
- package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
- package/src/__tests__/builtin-chat-session.test.ts +65 -1
- package/src/__tests__/builtin-cli-json.test.ts +39 -39
- package/src/__tests__/builtin-config.test.ts +26 -26
- package/src/__tests__/builtin-context.test.ts +202 -163
- package/src/__tests__/builtin-permissions.test.ts +211 -211
- package/src/__tests__/builtin-session-select.test.ts +116 -116
- package/src/__tests__/builtin-sigint.test.ts +56 -56
- package/src/__tests__/builtin-web-tools.test.ts +220 -220
- package/src/__tests__/card-action-routing.test.ts +18 -18
- package/src/__tests__/cardkit.test.ts +60 -60
- package/src/__tests__/ccc-adapter.test.ts +170 -136
- package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
- package/src/__tests__/chatgpt-subscription.test.ts +135 -135
- package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
- package/src/__tests__/claude-adapter.test.ts +614 -614
- package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
- package/src/__tests__/codex-adapter.test.ts +58 -58
- package/src/__tests__/codex-raw-stream-log.test.ts +170 -170
- package/src/__tests__/codex-reset-actions.test.ts +146 -146
- package/src/__tests__/cursor-adapter.test.ts +268 -268
- package/src/__tests__/feishu-api.test.ts +60 -60
- package/src/__tests__/feishu-avatar.test.ts +164 -164
- package/src/__tests__/feishu-message-ingress.test.ts +138 -138
- package/src/__tests__/feishu-platform.test.ts +22 -22
- package/src/__tests__/format-message.test.ts +47 -47
- package/src/__tests__/jsonl-stream.test.ts +79 -79
- package/src/__tests__/package-files.test.ts +24 -24
- package/src/__tests__/privacy.test.ts +198 -198
- package/src/__tests__/progress-reducer.test.ts +121 -110
- package/src/__tests__/raw-stream-log.test.ts +106 -106
- package/src/__tests__/response-stall.test.ts +49 -49
- package/src/__tests__/session.test.ts +174 -16
- package/src/__tests__/shared-prefix.test.ts +36 -36
- package/src/__tests__/sim-platform.test.ts +16 -16
- package/src/__tests__/startup-lifecycle.test.ts +231 -231
- package/src/__tests__/stop-session.test.ts +34 -34
- package/src/__tests__/stream-state.test.ts +42 -42
- package/src/__tests__/terminal-error.test.ts +54 -0
- package/src/__tests__/terminal-renderer.test.ts +247 -247
- package/src/__tests__/update-command-guard.test.ts +144 -144
- package/src/__tests__/web-ui.test.ts +326 -326
- package/src/adapters/adapter-interface.ts +24 -18
- package/src/adapters/ccc-adapter.ts +141 -131
- package/src/adapters/claude-adapter.ts +620 -620
- package/src/adapters/claude-session-meta-store.ts +120 -120
- package/src/adapters/codex-adapter.ts +426 -426
- package/src/adapters/cursor-adapter.ts +681 -681
- package/src/adapters/jsonl-stream.ts +157 -157
- package/src/adapters/raw-stream-log.ts +124 -124
- package/src/adapters/resource-monitor.ts +140 -140
- package/src/agent-activity.ts +175 -170
- package/src/agent-delegate-task-rpc.ts +153 -153
- package/src/agent-delegate-task.ts +91 -91
- package/src/agent-reload-config-rpc.ts +34 -34
- package/src/agent-stop-stuck.ts +129 -129
- package/src/builtin/cli.ts +9 -2
- package/src/builtin/config.ts +84 -84
- package/src/builtin/context.ts +62 -4
- package/src/builtin/file-log.ts +38 -38
- package/src/builtin/index.ts +83 -19
- package/src/builtin/proc-tree-kill.ts +61 -61
- package/src/builtin/progress/cards-helpers.ts +76 -76
- package/src/builtin/progress/reducer.ts +113 -108
- package/src/builtin/progress/terminal-renderer.ts +294 -294
- package/src/builtin/progress/view.ts +77 -77
- package/src/builtin/raw-stream-log.ts +124 -124
- package/src/builtin/session-select.ts +48 -48
- package/src/builtin/sigint.ts +50 -50
- package/src/builtin/web-tools.ts +313 -313
- package/src/card-action-routing.ts +14 -14
- package/src/chatgpt-subscription-rpc.ts +27 -27
- package/src/chatgpt-subscription.ts +299 -299
- package/src/chrome-devtools-guard.ts +318 -318
- package/src/codex-reset-actions.ts +184 -184
- package/src/feishu-api.ts +193 -193
- package/src/feishu-message-ingress.ts +195 -195
- package/src/feishu-platform.ts +20 -20
- package/src/format-message.ts +293 -293
- package/src/litellm-proxy.ts +374 -374
- package/src/orchestrator.ts +8 -3
- package/src/platform-adapter.ts +6 -6
- package/src/privacy.ts +118 -118
- package/src/progress/reducer.ts +113 -108
- package/src/progress/terminal-renderer.ts +294 -294
- package/src/progress/view.ts +77 -77
- package/src/response-stall.ts +28 -28
- package/src/runtime-reload.ts +34 -34
- package/src/session-chat-binding.ts +82 -82
- package/src/session-name.ts +8 -8
- package/src/session.ts +149 -26
- package/src/shared-prefix.ts +29 -29
- package/src/sim-platform.ts +20 -20
- package/src/startup-lifecycle.ts +250 -250
- package/src/stream-state.ts +21 -18
- package/src/terminal-error.ts +129 -0
- package/src/turn-cards.ts +117 -117
- package/src/update-command-guard.ts +165 -165
package/src/session.ts
CHANGED
|
@@ -43,6 +43,12 @@ import { resourceMonitor, registerProcess, unregisterProcess } from "./adapters/
|
|
|
43
43
|
import { buildImSkillsPromptCached, exportSkillSubDocs, clearImSkillsPromptCache } from "./im-skills.ts";
|
|
44
44
|
import type { PlatformAdapter } from "./platform-adapter.ts";
|
|
45
45
|
import { hasResponseStalled, observeResponseProgress } from "./response-stall.ts";
|
|
46
|
+
import {
|
|
47
|
+
classifyTerminalError,
|
|
48
|
+
formatTerminalErrorNotice,
|
|
49
|
+
formatTerminalErrorReason,
|
|
50
|
+
type TerminalErrorInfo,
|
|
51
|
+
} from "./terminal-error.ts";
|
|
46
52
|
import {
|
|
47
53
|
MAX_PROCESSED,
|
|
48
54
|
clearFeishuMessageLedgerMemory,
|
|
@@ -176,7 +182,7 @@ export const RESPONSE_STALL_RECOVERY_PROMPT = "完成了吗?如果没完成继
|
|
|
176
182
|
export const RESPONSE_STALL_RECOVERY_NOTICE =
|
|
177
183
|
`检测到会话停滞,正在自动确认并继续。\n\n${RESPONSE_STALL_RECOVERY_PROMPT}`;
|
|
178
184
|
export const RESPONSE_STALL_RECOVERY_EXHAUSTED_NOTICE =
|
|
179
|
-
"⚠️ 自动续跑仍连续 3
|
|
185
|
+
"⚠️ 自动续跑仍连续 3 分钟没有生成新回复,本次不再自动继续。";
|
|
180
186
|
const RESPONSE_STALL_RECOVERY_DELAY_MS = 200;
|
|
181
187
|
let processMonitorIntervalMs = DEFAULT_PROCESS_MONITOR_INTERVAL_MS;
|
|
182
188
|
let responseStallTimeoutMs = DEFAULT_RESPONSE_STALL_TIMEOUT_MS;
|
|
@@ -305,13 +311,18 @@ function scheduleFinalResponseCloseGuard(
|
|
|
305
311
|
runningPrompt.finalResponseCloseTimer = handle;
|
|
306
312
|
}
|
|
307
313
|
|
|
308
|
-
function formatTerminalHeader(
|
|
314
|
+
function formatTerminalHeader(
|
|
315
|
+
status: "running" | "done" | "stopped" | "error" | "auto_ended",
|
|
316
|
+
terminalError?: TerminalErrorInfo,
|
|
317
|
+
): {
|
|
309
318
|
title: string;
|
|
310
319
|
template?: string;
|
|
311
320
|
} {
|
|
312
321
|
if (status === "auto_ended") return { title: "已自动结束 · 3分钟无新内容", template: "orange" };
|
|
313
322
|
if (status === "stopped") return { title: "已停止", template: "red" };
|
|
314
|
-
if (status === "error")
|
|
323
|
+
if (status === "error") {
|
|
324
|
+
return { title: terminalError ? `异常结束 · ${terminalError.title}` : "异常结束", template: "red" };
|
|
325
|
+
}
|
|
315
326
|
return { title: "完成" };
|
|
316
327
|
}
|
|
317
328
|
|
|
@@ -320,28 +331,59 @@ function turnFinalStatus(status: "running" | "done" | "stopped" | "error" | "aut
|
|
|
320
331
|
}
|
|
321
332
|
|
|
322
333
|
function formatAutoEndedReply(finalReply: string): string {
|
|
323
|
-
const reason = "⚠️
|
|
334
|
+
const reason = "⚠️ 已自动结束:生成回复阶段连续 3 分钟没有字符变化。";
|
|
324
335
|
return finalReply
|
|
325
336
|
? `${reason}以下回复可能不完整。\n\n${finalReply}`
|
|
326
337
|
: `${reason}本轮没有可发送的回复内容。`;
|
|
327
338
|
}
|
|
328
339
|
|
|
329
340
|
/**
|
|
330
|
-
*
|
|
331
|
-
*
|
|
341
|
+
* 只监控明确的回复生成阶段。启动、压缩、思考、工具调用和搜索都有各自的
|
|
342
|
+
* 状态或资源保护,不能把它们消耗的时间算入回复停滞窗口。
|
|
332
343
|
*/
|
|
333
344
|
function monitorsOutputProgress(kind: AgentActivityKind): boolean {
|
|
334
|
-
return kind === "
|
|
345
|
+
return kind === "responding";
|
|
335
346
|
}
|
|
336
347
|
|
|
337
348
|
function formatTerminalReply(
|
|
338
349
|
status: "running" | "done" | "stopped" | "error" | "auto_ended",
|
|
339
350
|
finalReply: string,
|
|
351
|
+
terminalError?: TerminalErrorInfo,
|
|
340
352
|
): string | null {
|
|
341
353
|
if (status === "auto_ended") return formatAutoEndedReply(finalReply);
|
|
354
|
+
if (status === "error") {
|
|
355
|
+
const error = terminalError ?? {
|
|
356
|
+
kind: "unknown" as const,
|
|
357
|
+
title: "原因未记录",
|
|
358
|
+
message: "当前状态中没有可用的错误详情,请查看运行日志。",
|
|
359
|
+
occurredAt: Date.now(),
|
|
360
|
+
};
|
|
361
|
+
return formatTerminalErrorNotice(error, finalReply);
|
|
362
|
+
}
|
|
342
363
|
return finalReply || null;
|
|
343
364
|
}
|
|
344
365
|
|
|
366
|
+
function formatTerminalCardContent(state: {
|
|
367
|
+
status: "running" | "done" | "stopped" | "error" | "auto_ended";
|
|
368
|
+
accumulatedContent: string;
|
|
369
|
+
finalReply: string;
|
|
370
|
+
terminalError?: TerminalErrorInfo;
|
|
371
|
+
}): string {
|
|
372
|
+
const content = state.accumulatedContent + state.finalReply;
|
|
373
|
+
if (state.status !== "error") return content;
|
|
374
|
+
|
|
375
|
+
const error = state.terminalError ?? {
|
|
376
|
+
kind: "unknown" as const,
|
|
377
|
+
title: "原因未记录",
|
|
378
|
+
message: "当前状态中没有可用的错误详情,请查看运行日志。",
|
|
379
|
+
occurredAt: Date.now(),
|
|
380
|
+
};
|
|
381
|
+
const reason = formatTerminalErrorReason(error);
|
|
382
|
+
return content.trim()
|
|
383
|
+
? `${content}\n\n${reason}\n以上内容可能不完整。`
|
|
384
|
+
: reason;
|
|
385
|
+
}
|
|
386
|
+
|
|
345
387
|
function isCardKitSequenceConflict(err: unknown): boolean {
|
|
346
388
|
return err instanceof Error && err.message.includes("300317");
|
|
347
389
|
}
|
|
@@ -912,6 +954,8 @@ export function accumulateBlockContent(
|
|
|
912
954
|
`\n\n🔄 上下文压缩(${triggerLabel}): **${block.pre_tokens}** → **${block.post_tokens}** tokens\n`; // 🔄 / →
|
|
913
955
|
break;
|
|
914
956
|
}
|
|
957
|
+
case "agent_status":
|
|
958
|
+
break;
|
|
915
959
|
}
|
|
916
960
|
}
|
|
917
961
|
|
|
@@ -1110,7 +1154,7 @@ export async function resumeAndPrompt(
|
|
|
1110
1154
|
msgTimestamp: number,
|
|
1111
1155
|
tool: string,
|
|
1112
1156
|
traceId?: string,
|
|
1113
|
-
): Promise<
|
|
1157
|
+
): Promise<SessionRunOutcome> {
|
|
1114
1158
|
return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId);
|
|
1115
1159
|
}
|
|
1116
1160
|
|
|
@@ -1126,6 +1170,8 @@ interface RunAgentSessionOptions {
|
|
|
1126
1170
|
autoRecovery?: boolean;
|
|
1127
1171
|
}
|
|
1128
1172
|
|
|
1173
|
+
export type SessionRunOutcome = "busy" | "done" | "stopped" | "error" | "auto_ended";
|
|
1174
|
+
|
|
1129
1175
|
export async function runAgentSession(
|
|
1130
1176
|
sessionId: string,
|
|
1131
1177
|
userText: string,
|
|
@@ -1135,7 +1181,7 @@ export async function runAgentSession(
|
|
|
1135
1181
|
tool: string,
|
|
1136
1182
|
traceId?: string,
|
|
1137
1183
|
options: RunAgentSessionOptions = {},
|
|
1138
|
-
): Promise<
|
|
1184
|
+
): Promise<SessionRunOutcome> {
|
|
1139
1185
|
const tid = traceId ?? "";
|
|
1140
1186
|
|
|
1141
1187
|
// runAgentSession 是飞书用户消息、队列消息与内部自动恢复共同使用的唯一
|
|
@@ -1162,7 +1208,7 @@ export async function runAgentSession(
|
|
|
1162
1208
|
? "当前正在生成回复中,请等待完成后再发送消息。也可以发送 /stop 结束,已完成的步骤不会丢失。"
|
|
1163
1209
|
: "该会话正在生成回复中,请等待完成后再发送消息。也可以发送 /stop 结束,已完成的步骤不会丢失。";
|
|
1164
1210
|
await platform.sendText(_chatId, busyMsg).catch(() => {});
|
|
1165
|
-
return;
|
|
1211
|
+
return "busy";
|
|
1166
1212
|
}
|
|
1167
1213
|
|
|
1168
1214
|
// 立即标记活跃,确保 /sessions、isSessionRunning 等查询在异步准备阶段就能看到运行状态。
|
|
@@ -1290,7 +1336,7 @@ export async function runAgentSession(
|
|
|
1290
1336
|
// 再开始缓存问题对应的任务"。
|
|
1291
1337
|
const prevState = await readStreamState(sessionId);
|
|
1292
1338
|
if (prevState && prevState.status !== "running") {
|
|
1293
|
-
const prevTerminalReply = formatTerminalReply(prevState.status, prevState.finalReply);
|
|
1339
|
+
const prevTerminalReply = formatTerminalReply(prevState.status, prevState.finalReply, prevState.terminalError);
|
|
1294
1340
|
const displayChatId = pickDisplayChat(sessionId);
|
|
1295
1341
|
if (displayChatId) {
|
|
1296
1342
|
const pp = platformForChat(displayChatId);
|
|
@@ -1309,8 +1355,8 @@ export async function runAgentSession(
|
|
|
1309
1355
|
setSessionChatAvatar(pp, displayChatId, prevState.tool, "idle", sessionId).catch(() => {});
|
|
1310
1356
|
} else {
|
|
1311
1357
|
const nextSeq = display.sequence + 1;
|
|
1312
|
-
const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(prevState.status);
|
|
1313
|
-
const cardContent = truncateContent(prevState
|
|
1358
|
+
const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(prevState.status, prevState.terminalError);
|
|
1359
|
+
const cardContent = truncateContent(formatTerminalCardContent(prevState)) || " ";
|
|
1314
1360
|
const doneCard = buildProgressCard(progressView({ text: cardContent, status: "done", showStop: false, headerTitle, headerTemplate }));
|
|
1315
1361
|
await pp.cardUpdate(display.cardId, doneCard, nextSeq).catch(err => {
|
|
1316
1362
|
console.error(`[${ts()}] [DISPLAY] prevState final cardUpdate failed: ${(err as Error).message}`);
|
|
@@ -1405,15 +1451,16 @@ export async function runAgentSession(
|
|
|
1405
1451
|
const FILE_WRITE_INTERVAL_MS = 2000;
|
|
1406
1452
|
const toolCallMap = new Map<string, { name: string; input: unknown }>();
|
|
1407
1453
|
let streamErrored = false;
|
|
1454
|
+
let streamTerminalError: TerminalErrorInfo | undefined;
|
|
1455
|
+
let runOutcome: SessionRunOutcome = "error";
|
|
1408
1456
|
|
|
1409
1457
|
const runningPrompt = activePrompts.get(sessionId);
|
|
1410
1458
|
if (runningPrompt) {
|
|
1411
|
-
//
|
|
1412
|
-
//
|
|
1413
|
-
// “正在启动 Agent”,也永远触发不了三分钟保护。
|
|
1459
|
+
// 在消费第一个事件前建立阶段感知的零字符基线;启动阶段不计时,只有后续
|
|
1460
|
+
// 收到明确的 responding 状态后才会启动三分钟回复停滞保护。
|
|
1414
1461
|
runningPrompt.responseProgress = observeResponseProgress(
|
|
1415
1462
|
undefined,
|
|
1416
|
-
|
|
1463
|
+
monitorsOutputProgress(activityTracker.activity.kind),
|
|
1417
1464
|
0,
|
|
1418
1465
|
activityTracker.activity.startedAt,
|
|
1419
1466
|
);
|
|
@@ -1486,7 +1533,7 @@ export async function runAgentSession(
|
|
|
1486
1533
|
current.controller.abort();
|
|
1487
1534
|
await killProcessTree(current.processPid);
|
|
1488
1535
|
console.warn(
|
|
1489
|
-
`[${ts()}] [RESPONSE-STALL] Session ${sessionId} auto-ended after 3 minutes without
|
|
1536
|
+
`[${ts()}] [RESPONSE-STALL] Session ${sessionId} auto-ended after 3 minutes without reply progress`,
|
|
1490
1537
|
);
|
|
1491
1538
|
};
|
|
1492
1539
|
|
|
@@ -1580,6 +1627,7 @@ export async function runAgentSession(
|
|
|
1580
1627
|
}
|
|
1581
1628
|
} catch (streamErr) {
|
|
1582
1629
|
streamErrored = true;
|
|
1630
|
+
streamTerminalError = classifyTerminalError(streamErr);
|
|
1583
1631
|
console.error(`[${ts()}] [STREAM] Error in stream loop for ${sessionId}: ${(streamErr as Error).message}`);
|
|
1584
1632
|
} finally {
|
|
1585
1633
|
// 标记 prompt 结束
|
|
@@ -1629,6 +1677,23 @@ export async function runAgentSession(
|
|
|
1629
1677
|
? "stopped"
|
|
1630
1678
|
: "done";
|
|
1631
1679
|
const finalReply = pickFinalReply(state).trim();
|
|
1680
|
+
const terminalError = streamTerminalError
|
|
1681
|
+
?? (wasAbnormalExit
|
|
1682
|
+
? {
|
|
1683
|
+
kind: "process" as const,
|
|
1684
|
+
title: "Agent 进程意外退出",
|
|
1685
|
+
message: "Agent CLI 进程已退出。若回复不完整,请重新发送上一条指令。",
|
|
1686
|
+
occurredAt: Date.now(),
|
|
1687
|
+
}
|
|
1688
|
+
: wasResourceStuck
|
|
1689
|
+
? {
|
|
1690
|
+
kind: "resource" as const,
|
|
1691
|
+
title: "Agent 进程失去响应",
|
|
1692
|
+
message: "Agent 进程长时间没有 CPU 或内存变化,已被强制停止。若回复不完整,请重新发送上一条指令。",
|
|
1693
|
+
occurredAt: Date.now(),
|
|
1694
|
+
}
|
|
1695
|
+
: undefined);
|
|
1696
|
+
runOutcome = finalStatus;
|
|
1632
1697
|
|
|
1633
1698
|
// stop-stuck-loop 接口可能在 fire-and-forget 中已写入带 final_reply 的
|
|
1634
1699
|
// stream state,finally 不应覆盖它。同时保留 stuckAt 标记,防止
|
|
@@ -1659,6 +1724,7 @@ export async function runAgentSession(
|
|
|
1659
1724
|
tool,
|
|
1660
1725
|
...(preserveStuckAt ? { stuckAt: preserveStuckAt } : {}),
|
|
1661
1726
|
...(autoEndedAt !== undefined ? { autoEndedAt } : {}),
|
|
1727
|
+
...(terminalError ? { terminalError } : {}),
|
|
1662
1728
|
});
|
|
1663
1729
|
|
|
1664
1730
|
// display loop 下一轮会读到最终状态并发送消息
|
|
@@ -1730,7 +1796,7 @@ export async function runAgentSession(
|
|
|
1730
1796
|
autoRecoveryTarget = { chatId: activeAutoEnded, platform: pp };
|
|
1731
1797
|
}
|
|
1732
1798
|
}
|
|
1733
|
-
console.warn(`[${ts()}] Session ${sessionId} auto-ended after stalled
|
|
1799
|
+
console.warn(`[${ts()}] Session ${sessionId} auto-ended after stalled response output (content chunks: ${state.chunkCount})`);
|
|
1734
1800
|
if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "response_stall", chunks: state.chunkCount });
|
|
1735
1801
|
} else if (wasAbnormalExit) {
|
|
1736
1802
|
for (const cid of finalizationChatIds) {
|
|
@@ -1749,6 +1815,52 @@ export async function runAgentSession(
|
|
|
1749
1815
|
if (activeErr) setSessionChatAvatar(platform, activeErr, tool, "idle", sessionId).catch(() => {});
|
|
1750
1816
|
console.log(`[${ts()}] Session ${sessionId} process exited unexpectedly (content chunks: ${state.chunkCount})`);
|
|
1751
1817
|
if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "process_missing", chunks: state.chunkCount });
|
|
1818
|
+
} else if (streamErrored || wasResourceStuck) {
|
|
1819
|
+
for (const cid of finalizationChatIds) {
|
|
1820
|
+
const finfo = sessionInfoMap.get(cid);
|
|
1821
|
+
await recordSessionRegistry({
|
|
1822
|
+
chatId: cid,
|
|
1823
|
+
sessionId,
|
|
1824
|
+
tool,
|
|
1825
|
+
turnCount: finfo?.turnCount ?? nextTurnCount,
|
|
1826
|
+
lastContextTokens: finfo?.lastContextTokens ?? nextContextTokens,
|
|
1827
|
+
startTime: finfo?.startTime ?? now,
|
|
1828
|
+
running: false,
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
const activeErr = getLastActiveChat(sessionId) ?? finalizationChatIds[0];
|
|
1832
|
+
if (activeErr) {
|
|
1833
|
+
const pp = platformForChat(activeErr) ?? platform;
|
|
1834
|
+
const terminalState = await readStreamState(sessionId);
|
|
1835
|
+
if (
|
|
1836
|
+
terminalError
|
|
1837
|
+
&& !displayCards.has(activeErr)
|
|
1838
|
+
&& (!terminalState || !isFinalReplySentForTurn(terminalState))
|
|
1839
|
+
) {
|
|
1840
|
+
await sendFinalReplyTextOnce(
|
|
1841
|
+
pp,
|
|
1842
|
+
activeErr,
|
|
1843
|
+
sessionId,
|
|
1844
|
+
nextTurnCount,
|
|
1845
|
+
formatTerminalErrorNotice(terminalError, finalReplyToWrite),
|
|
1846
|
+
);
|
|
1847
|
+
}
|
|
1848
|
+
setSessionChatAvatar(pp, activeErr, tool, "idle", sessionId).catch(() => {});
|
|
1849
|
+
}
|
|
1850
|
+
const errorOutcome = wasResourceStuck ? "resource_stuck" : "stream_error";
|
|
1851
|
+
console.error(
|
|
1852
|
+
`[${ts()}] Session ${sessionId} ended with ${errorOutcome}` +
|
|
1853
|
+
`${terminalError ? ` (${terminalError.kind}: ${terminalError.title})` : ""} (content chunks: ${state.chunkCount})`,
|
|
1854
|
+
);
|
|
1855
|
+
if (tid) {
|
|
1856
|
+
logTrace(tid, "SESSION_END", {
|
|
1857
|
+
sessionId,
|
|
1858
|
+
outcome: errorOutcome,
|
|
1859
|
+
errorKind: terminalError?.kind,
|
|
1860
|
+
errorTitle: terminalError?.title,
|
|
1861
|
+
chunks: state.chunkCount,
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1752
1864
|
} else {
|
|
1753
1865
|
for (const cid of finalizationChatIds) {
|
|
1754
1866
|
const finfo = sessionInfoMap.get(cid);
|
|
@@ -1862,6 +1974,7 @@ export async function runAgentSession(
|
|
|
1862
1974
|
clearSessionFinalizing(sessionId);
|
|
1863
1975
|
}
|
|
1864
1976
|
}
|
|
1977
|
+
return runOutcome;
|
|
1865
1978
|
}
|
|
1866
1979
|
|
|
1867
1980
|
// ---------------------------------------------------------------------------
|
|
@@ -1941,9 +2054,11 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
1941
2054
|
const tail = "━━━ 回答结束 ━━━";
|
|
1942
2055
|
const finalMsg = state.status === "auto_ended"
|
|
1943
2056
|
? formatAutoEndedReply(remaining)
|
|
1944
|
-
:
|
|
1945
|
-
? remaining
|
|
1946
|
-
:
|
|
2057
|
+
: state.status === "error"
|
|
2058
|
+
? formatTerminalReply(state.status, remaining, state.terminalError) ?? tail
|
|
2059
|
+
: remaining
|
|
2060
|
+
? remaining + "\n" + tail
|
|
2061
|
+
: tail;
|
|
1947
2062
|
if (!isFinalReplySentForTurn(state)) {
|
|
1948
2063
|
await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, finalMsg);
|
|
1949
2064
|
}
|
|
@@ -1965,8 +2080,8 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
1965
2080
|
let terminalCardUpdateAccepted = terminalCardAlreadyUpdated;
|
|
1966
2081
|
if (!terminalCardAlreadyUpdated) {
|
|
1967
2082
|
const nextSeq = display.sequence + 1;
|
|
1968
|
-
const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(state.status);
|
|
1969
|
-
const cardContent = truncateContent(state
|
|
2083
|
+
const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(state.status, state.terminalError);
|
|
2084
|
+
const cardContent = truncateContent(formatTerminalCardContent(state)) || " ";
|
|
1970
2085
|
const doneCard = buildProgressCard(progressView({ text: cardContent, status: "done", showStop: false, headerTitle, headerTemplate }));
|
|
1971
2086
|
await p.cardUpdate(display.cardId, doneCard, nextSeq).then(() => {
|
|
1972
2087
|
display.sequence = nextSeq;
|
|
@@ -1993,8 +2108,16 @@ export function startUnifiedDisplayLoop(): void {
|
|
|
1993
2108
|
}
|
|
1994
2109
|
|
|
1995
2110
|
let terminalTextDelivered = true;
|
|
1996
|
-
const terminalReply = formatTerminalReply(state.status, state.finalReply);
|
|
1997
|
-
|
|
2111
|
+
const terminalReply = formatTerminalReply(state.status, state.finalReply, state.terminalError);
|
|
2112
|
+
const errorWasDeliveredByCard =
|
|
2113
|
+
state.status === "error"
|
|
2114
|
+
&& !state.finalReply.trim()
|
|
2115
|
+
&& terminalCardUpdateAccepted;
|
|
2116
|
+
if (errorWasDeliveredByCard) {
|
|
2117
|
+
if (!isFinalReplySentForTurn(state)) {
|
|
2118
|
+
await markFinalReplySent(sessionId, state.turnCount);
|
|
2119
|
+
}
|
|
2120
|
+
} else if (terminalReply) {
|
|
1998
2121
|
if (!isFinalReplySentForTurn(state)) {
|
|
1999
2122
|
terminalTextDelivered = await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, terminalReply);
|
|
2000
2123
|
}
|
package/src/shared-prefix.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
export const ABD_PREFIX = "/abd";
|
|
2
|
-
|
|
3
|
-
export const ABD_APPEND_PROMPT =
|
|
4
|
-
"请从第一性原理出发挖掘我的真实需求。你觉得我的需求合理吗?开始实现之前有什么问题要问我的?";
|
|
5
|
-
|
|
6
|
-
export const ABD_HELP_LINE =
|
|
7
|
-
`发送 **${ABD_PREFIX}** 前缀消息,将以第一性原理追问真实需求、合理性和实现前问题`;
|
|
8
|
-
|
|
9
|
-
export interface SharedPrefixResult {
|
|
10
|
-
matched: boolean;
|
|
11
|
-
text: string;
|
|
12
|
-
body: string;
|
|
13
|
-
prefix?: typeof ABD_PREFIX;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function applySharedPrefix(text: string): SharedPrefixResult {
|
|
17
|
-
if (!text.toLowerCase().startsWith(ABD_PREFIX)) {
|
|
18
|
-
return { matched: false, text, body: text };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const body = text.slice(ABD_PREFIX.length).replace(/^\s+/, "");
|
|
22
|
-
const appendix = ["---", ABD_APPEND_PROMPT].join("\n");
|
|
23
|
-
return {
|
|
24
|
-
matched: true,
|
|
25
|
-
prefix: ABD_PREFIX,
|
|
26
|
-
body,
|
|
27
|
-
text: body ? [body, appendix].join("\n\n") : appendix,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
1
|
+
export const ABD_PREFIX = "/abd";
|
|
2
|
+
|
|
3
|
+
export const ABD_APPEND_PROMPT =
|
|
4
|
+
"请从第一性原理出发挖掘我的真实需求。你觉得我的需求合理吗?开始实现之前有什么问题要问我的?";
|
|
5
|
+
|
|
6
|
+
export const ABD_HELP_LINE =
|
|
7
|
+
`发送 **${ABD_PREFIX}** 前缀消息,将以第一性原理追问真实需求、合理性和实现前问题`;
|
|
8
|
+
|
|
9
|
+
export interface SharedPrefixResult {
|
|
10
|
+
matched: boolean;
|
|
11
|
+
text: string;
|
|
12
|
+
body: string;
|
|
13
|
+
prefix?: typeof ABD_PREFIX;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function applySharedPrefix(text: string): SharedPrefixResult {
|
|
17
|
+
if (!text.toLowerCase().startsWith(ABD_PREFIX)) {
|
|
18
|
+
return { matched: false, text, body: text };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const body = text.slice(ABD_PREFIX.length).replace(/^\s+/, "");
|
|
22
|
+
const appendix = ["---", ABD_APPEND_PROMPT].join("\n");
|
|
23
|
+
return {
|
|
24
|
+
matched: true,
|
|
25
|
+
prefix: ABD_PREFIX,
|
|
26
|
+
body,
|
|
27
|
+
text: body ? [body, appendix].join("\n\n") : appendix,
|
|
28
|
+
};
|
|
29
|
+
}
|
package/src/sim-platform.ts
CHANGED
|
@@ -123,25 +123,25 @@ export const SimulatedPlatform: FeishuPlatform = {
|
|
|
123
123
|
},
|
|
124
124
|
|
|
125
125
|
// ---- 头像 ----
|
|
126
|
-
async setChatAvatar(_token, _chatId, _tool, _status) {
|
|
127
|
-
// 模拟模式不需要头像
|
|
128
|
-
},
|
|
129
|
-
|
|
130
|
-
async getCodexUsageSummary() {
|
|
131
|
-
return {
|
|
132
|
-
fiveHour: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
|
|
133
|
-
weekly: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
|
|
134
|
-
rateLimitResetCreditsAvailable: null,
|
|
135
|
-
rateLimitResetCredits: null,
|
|
136
|
-
};
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
async consumeCodexRateLimitResetCredit(_redeemRequestId) {
|
|
140
|
-
return { code: "no_credit" as const, windowsReset: 0 };
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
// ---- 图片下载 ----
|
|
144
|
-
async getOrDownloadImage(_token, _messageId, fileKey) {
|
|
126
|
+
async setChatAvatar(_token, _chatId, _tool, _status) {
|
|
127
|
+
// 模拟模式不需要头像
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
async getCodexUsageSummary() {
|
|
131
|
+
return {
|
|
132
|
+
fiveHour: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
|
|
133
|
+
weekly: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
|
|
134
|
+
rateLimitResetCreditsAvailable: null,
|
|
135
|
+
rateLimitResetCredits: null,
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
async consumeCodexRateLimitResetCredit(_redeemRequestId) {
|
|
140
|
+
return { code: "no_credit" as const, windowsReset: 0 };
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// ---- 图片下载 ----
|
|
144
|
+
async getOrDownloadImage(_token, _messageId, fileKey) {
|
|
145
145
|
return join(SIM_DIR, "images", fileKey);
|
|
146
146
|
},
|
|
147
147
|
|
|
@@ -174,4 +174,4 @@ export const SimulatedPlatform: FeishuPlatform = {
|
|
|
174
174
|
};
|
|
175
175
|
|
|
176
176
|
/** 模拟模式下的默认 chat_id(重新导出以保持向后兼容) */
|
|
177
|
-
export { SIM_DEFAULT_CHAT_ID } from "./sim-store.ts";
|
|
177
|
+
export { SIM_DEFAULT_CHAT_ID } from "./sim-store.ts";
|