chatccc 0.2.230 → 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 +11 -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-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/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 +12 -11
- package/src/shared-prefix.ts +29 -29
- package/src/sim-platform.ts +20 -20
- package/src/startup-lifecycle.ts +250 -250
- package/src/turn-cards.ts +117 -117
- package/src/update-command-guard.ts +165 -165
package/src/progress/view.ts
CHANGED
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* progress/view.ts — 平台无关的"过程进度"视图模型
|
|
3
|
-
*
|
|
4
|
-
* 这是飞书过程卡片与终端过程区块共享的唯一数据模型:
|
|
5
|
-
* - 飞书:buildProgressCard(view) 把 ProgressView 渲染为卡片 JSON
|
|
6
|
-
* - 终端:TerminalProgressRenderer 把 ProgressView 渲染为 ANSI 区块
|
|
7
|
-
* - 事件流:reduceProgress(prev, event) 把 ChatEvent 增量合并进 ProgressView
|
|
8
|
-
*
|
|
9
|
-
* 语义与飞书卡片对齐:
|
|
10
|
-
* - headerTitle 是"生成中"阶段展示的标题(如"正在启动 Agent · 0秒")
|
|
11
|
-
* - status 决定终态外观(完成 / 已停止 / 异常结束)
|
|
12
|
-
* - text 是正文累积(对应卡片 main_content / 终端区块正文)
|
|
13
|
-
* - tools 是本轮工具调用列表(飞书卡片暂不展示,终端折叠为单行)
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
export type ProgressStatus = "generating" | "done" | "stopped" | "error";
|
|
17
|
-
|
|
18
|
-
export type ProgressToolStatus = "running" | "ok" | "error";
|
|
19
|
-
|
|
20
|
-
export interface ProgressToolCall {
|
|
21
|
-
/** 工具调用 ID(ChatEvent.tool_use.id,可能缺失) */
|
|
22
|
-
id: string;
|
|
23
|
-
/** 工具名,如 edit_file */
|
|
24
|
-
name: string;
|
|
25
|
-
status: ProgressToolStatus;
|
|
26
|
-
/** 工具输入摘要(终端折叠行展示,截断保存) */
|
|
27
|
-
detail?: string;
|
|
28
|
-
/** 工具结果摘要(成功/失败,截断保存) */
|
|
29
|
-
summary?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface ProgressView {
|
|
33
|
-
/** 当前阶段状态,决定终态外观 */
|
|
34
|
-
status: ProgressStatus;
|
|
35
|
-
/** 生成中阶段的头部标题(对应飞书卡片 header title) */
|
|
36
|
-
headerTitle: string;
|
|
37
|
-
/** 卡片头部颜色模板(飞书用),终端忽略 */
|
|
38
|
-
headerTemplate: string;
|
|
39
|
-
/** 正文累积内容 */
|
|
40
|
-
text: string;
|
|
41
|
-
/** 本轮工具调用列表 */
|
|
42
|
-
tools: ProgressToolCall[];
|
|
43
|
-
/** 是否展示停止按钮 / 停止提示 */
|
|
44
|
-
showStop: boolean;
|
|
45
|
-
/** 最近一次更新时间戳 */
|
|
46
|
-
updatedAt: number;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface ProgressViewInit {
|
|
50
|
-
status?: ProgressStatus;
|
|
51
|
-
headerTitle?: string;
|
|
52
|
-
headerTemplate?: string;
|
|
53
|
-
text?: string;
|
|
54
|
-
tools?: ProgressToolCall[];
|
|
55
|
-
showStop?: boolean;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** 创建 ProgressView,未提供的字段使用与飞书 buildProgressCard 一致的默认值 */
|
|
59
|
-
export function progressView(init: ProgressViewInit = {}): ProgressView {
|
|
60
|
-
return {
|
|
61
|
-
status: init.status ?? "generating",
|
|
62
|
-
headerTitle: init.headerTitle ?? "生成中...",
|
|
63
|
-
headerTemplate: init.headerTemplate ?? "blue",
|
|
64
|
-
text: init.text ?? "",
|
|
65
|
-
tools: init.tools ?? [],
|
|
66
|
-
showStop: init.showStop ?? true,
|
|
67
|
-
updatedAt: Date.now(),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** 只读浅拷贝并覆盖部分字段,返回新视图(reducer 的不可变更新用) */
|
|
72
|
-
export function withProgressView(
|
|
73
|
-
prev: ProgressView,
|
|
74
|
-
patch: Partial<ProgressView>,
|
|
75
|
-
): ProgressView {
|
|
76
|
-
return { ...prev, ...patch, updatedAt: Date.now() };
|
|
77
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* progress/view.ts — 平台无关的"过程进度"视图模型
|
|
3
|
+
*
|
|
4
|
+
* 这是飞书过程卡片与终端过程区块共享的唯一数据模型:
|
|
5
|
+
* - 飞书:buildProgressCard(view) 把 ProgressView 渲染为卡片 JSON
|
|
6
|
+
* - 终端:TerminalProgressRenderer 把 ProgressView 渲染为 ANSI 区块
|
|
7
|
+
* - 事件流:reduceProgress(prev, event) 把 ChatEvent 增量合并进 ProgressView
|
|
8
|
+
*
|
|
9
|
+
* 语义与飞书卡片对齐:
|
|
10
|
+
* - headerTitle 是"生成中"阶段展示的标题(如"正在启动 Agent · 0秒")
|
|
11
|
+
* - status 决定终态外观(完成 / 已停止 / 异常结束)
|
|
12
|
+
* - text 是正文累积(对应卡片 main_content / 终端区块正文)
|
|
13
|
+
* - tools 是本轮工具调用列表(飞书卡片暂不展示,终端折叠为单行)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type ProgressStatus = "generating" | "done" | "stopped" | "error";
|
|
17
|
+
|
|
18
|
+
export type ProgressToolStatus = "running" | "ok" | "error";
|
|
19
|
+
|
|
20
|
+
export interface ProgressToolCall {
|
|
21
|
+
/** 工具调用 ID(ChatEvent.tool_use.id,可能缺失) */
|
|
22
|
+
id: string;
|
|
23
|
+
/** 工具名,如 edit_file */
|
|
24
|
+
name: string;
|
|
25
|
+
status: ProgressToolStatus;
|
|
26
|
+
/** 工具输入摘要(终端折叠行展示,截断保存) */
|
|
27
|
+
detail?: string;
|
|
28
|
+
/** 工具结果摘要(成功/失败,截断保存) */
|
|
29
|
+
summary?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ProgressView {
|
|
33
|
+
/** 当前阶段状态,决定终态外观 */
|
|
34
|
+
status: ProgressStatus;
|
|
35
|
+
/** 生成中阶段的头部标题(对应飞书卡片 header title) */
|
|
36
|
+
headerTitle: string;
|
|
37
|
+
/** 卡片头部颜色模板(飞书用),终端忽略 */
|
|
38
|
+
headerTemplate: string;
|
|
39
|
+
/** 正文累积内容 */
|
|
40
|
+
text: string;
|
|
41
|
+
/** 本轮工具调用列表 */
|
|
42
|
+
tools: ProgressToolCall[];
|
|
43
|
+
/** 是否展示停止按钮 / 停止提示 */
|
|
44
|
+
showStop: boolean;
|
|
45
|
+
/** 最近一次更新时间戳 */
|
|
46
|
+
updatedAt: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ProgressViewInit {
|
|
50
|
+
status?: ProgressStatus;
|
|
51
|
+
headerTitle?: string;
|
|
52
|
+
headerTemplate?: string;
|
|
53
|
+
text?: string;
|
|
54
|
+
tools?: ProgressToolCall[];
|
|
55
|
+
showStop?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 创建 ProgressView,未提供的字段使用与飞书 buildProgressCard 一致的默认值 */
|
|
59
|
+
export function progressView(init: ProgressViewInit = {}): ProgressView {
|
|
60
|
+
return {
|
|
61
|
+
status: init.status ?? "generating",
|
|
62
|
+
headerTitle: init.headerTitle ?? "生成中...",
|
|
63
|
+
headerTemplate: init.headerTemplate ?? "blue",
|
|
64
|
+
text: init.text ?? "",
|
|
65
|
+
tools: init.tools ?? [],
|
|
66
|
+
showStop: init.showStop ?? true,
|
|
67
|
+
updatedAt: Date.now(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** 只读浅拷贝并覆盖部分字段,返回新视图(reducer 的不可变更新用) */
|
|
72
|
+
export function withProgressView(
|
|
73
|
+
prev: ProgressView,
|
|
74
|
+
patch: Partial<ProgressView>,
|
|
75
|
+
): ProgressView {
|
|
76
|
+
return { ...prev, ...patch, updatedAt: Date.now() };
|
|
77
|
+
}
|
package/src/response-stall.ts
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
/** A snapshot of visible output progress while the Agent is starting or replying. */
|
|
2
|
-
export interface ResponseProgressObservation {
|
|
3
|
-
totalChars: number;
|
|
4
|
-
unchangedSince: number;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Tracks how long the displayed output character count has remained unchanged.
|
|
9
|
-
* Leaving a monitored phase clears the window; returning starts a fresh one.
|
|
10
|
-
*/
|
|
11
|
-
export function observeResponseProgress(
|
|
12
|
-
previous: ResponseProgressObservation | undefined,
|
|
13
|
-
isMonitoredPhase: boolean,
|
|
14
|
-
totalChars: number,
|
|
15
|
-
now = Date.now(),
|
|
16
|
-
): ResponseProgressObservation | undefined {
|
|
17
|
-
if (!isMonitoredPhase) return undefined;
|
|
18
|
-
if (previous?.totalChars === totalChars) return previous;
|
|
19
|
-
return { totalChars, unchangedSince: now };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function hasResponseStalled(
|
|
23
|
-
observation: ResponseProgressObservation | undefined,
|
|
24
|
-
now: number,
|
|
25
|
-
timeoutMs: number,
|
|
26
|
-
): boolean {
|
|
27
|
-
return observation !== undefined && now - observation.unchangedSince >= timeoutMs;
|
|
28
|
-
}
|
|
1
|
+
/** A snapshot of visible output progress while the Agent is starting or replying. */
|
|
2
|
+
export interface ResponseProgressObservation {
|
|
3
|
+
totalChars: number;
|
|
4
|
+
unchangedSince: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tracks how long the displayed output character count has remained unchanged.
|
|
9
|
+
* Leaving a monitored phase clears the window; returning starts a fresh one.
|
|
10
|
+
*/
|
|
11
|
+
export function observeResponseProgress(
|
|
12
|
+
previous: ResponseProgressObservation | undefined,
|
|
13
|
+
isMonitoredPhase: boolean,
|
|
14
|
+
totalChars: number,
|
|
15
|
+
now = Date.now(),
|
|
16
|
+
): ResponseProgressObservation | undefined {
|
|
17
|
+
if (!isMonitoredPhase) return undefined;
|
|
18
|
+
if (previous?.totalChars === totalChars) return previous;
|
|
19
|
+
return { totalChars, unchangedSince: now };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function hasResponseStalled(
|
|
23
|
+
observation: ResponseProgressObservation | undefined,
|
|
24
|
+
now: number,
|
|
25
|
+
timeoutMs: number,
|
|
26
|
+
): boolean {
|
|
27
|
+
return observation !== undefined && now - observation.unchangedSince >= timeoutMs;
|
|
28
|
+
}
|
package/src/runtime-reload.ts
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
APP_ID,
|
|
3
|
-
CONFIG_FILE,
|
|
4
|
-
maskAppId,
|
|
5
|
-
reloadConfigFromDisk,
|
|
6
|
-
resolveDefaultAgentTool,
|
|
7
|
-
type AgentTool,
|
|
8
|
-
} from "./config.ts";
|
|
9
|
-
import { appendStartupTrace } from "./shared.ts";
|
|
10
|
-
import { clearAdapterCache } from "./session.ts";
|
|
11
|
-
import { startChromeDevtoolsGuard } from "./chrome-devtools-guard.ts";
|
|
12
|
-
|
|
13
|
-
export interface RuntimeReloadResult {
|
|
14
|
-
configPath: string;
|
|
15
|
-
defaultAgent: AgentTool;
|
|
16
|
-
reloadedAt: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function reloadRuntimeConfig(source: string): RuntimeReloadResult {
|
|
20
|
-
reloadConfigFromDisk();
|
|
21
|
-
clearAdapterCache();
|
|
22
|
-
startChromeDevtoolsGuard();
|
|
23
|
-
|
|
24
|
-
const result: RuntimeReloadResult = {
|
|
25
|
-
configPath: CONFIG_FILE,
|
|
26
|
-
defaultAgent: resolveDefaultAgentTool(),
|
|
27
|
-
reloadedAt: new Date().toISOString(),
|
|
28
|
-
};
|
|
29
|
-
appendStartupTrace(`${source}: config reloaded`, {
|
|
30
|
-
appIdMask: maskAppId(APP_ID),
|
|
31
|
-
defaultAgent: result.defaultAgent,
|
|
32
|
-
});
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
APP_ID,
|
|
3
|
+
CONFIG_FILE,
|
|
4
|
+
maskAppId,
|
|
5
|
+
reloadConfigFromDisk,
|
|
6
|
+
resolveDefaultAgentTool,
|
|
7
|
+
type AgentTool,
|
|
8
|
+
} from "./config.ts";
|
|
9
|
+
import { appendStartupTrace } from "./shared.ts";
|
|
10
|
+
import { clearAdapterCache } from "./session.ts";
|
|
11
|
+
import { startChromeDevtoolsGuard } from "./chrome-devtools-guard.ts";
|
|
12
|
+
|
|
13
|
+
export interface RuntimeReloadResult {
|
|
14
|
+
configPath: string;
|
|
15
|
+
defaultAgent: AgentTool;
|
|
16
|
+
reloadedAt: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function reloadRuntimeConfig(source: string): RuntimeReloadResult {
|
|
20
|
+
reloadConfigFromDisk();
|
|
21
|
+
clearAdapterCache();
|
|
22
|
+
startChromeDevtoolsGuard();
|
|
23
|
+
|
|
24
|
+
const result: RuntimeReloadResult = {
|
|
25
|
+
configPath: CONFIG_FILE,
|
|
26
|
+
defaultAgent: resolveDefaultAgentTool(),
|
|
27
|
+
reloadedAt: new Date().toISOString(),
|
|
28
|
+
};
|
|
29
|
+
appendStartupTrace(`${source}: config reloaded`, {
|
|
30
|
+
appIdMask: maskAppId(APP_ID),
|
|
31
|
+
defaultAgent: result.defaultAgent,
|
|
32
|
+
});
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// 由 session.ts 在初始化时调用 rebuildSessionChatsFromRegistry 重建
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
|
|
8
|
-
import type { PlatformAdapter } from "./platform-adapter.ts";
|
|
9
|
-
import type { ResponseProgressObservation } from "./response-stall.ts";
|
|
8
|
+
import type { PlatformAdapter } from "./platform-adapter.ts";
|
|
9
|
+
import type { ResponseProgressObservation } from "./response-stall.ts";
|
|
10
10
|
|
|
11
11
|
const sessionChatsMap = new Map<string, Set<string>>();
|
|
12
12
|
|
|
@@ -53,51 +53,51 @@ export function hasChatsForSession(sessionId: string): boolean {
|
|
|
53
53
|
return (sessionChatsMap.get(sessionId)?.size ?? 0) > 0;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// Agent generator 退出后,最终卡片、registry、头像等仍可能在异步收尾。该阶段
|
|
57
|
-
// 也必须阻止下一轮提前进入,否则旧会话的落盘可能覆盖新会话绑定。
|
|
58
|
-
const finalizingSessions = new Set<string>();
|
|
59
|
-
|
|
60
|
-
// response-stall 自动恢复预约只存在于当前进程内:用户已明确 ChatCCC 重启后
|
|
61
|
-
// 不需要补发恢复 prompt。预约从检测到第一次停滞起一直持有到恢复轮同步进入
|
|
62
|
-
// runAgentSession,填补旧轮收尾和新轮 activePrompts.set 之间的空窗,保证此时
|
|
63
|
-
// 到达的普通用户消息只能进入缓存队列,不能抢在恢复 prompt 前执行。
|
|
64
|
-
const autoRecoveryReservations = new Set<string>();
|
|
65
|
-
|
|
66
|
-
/** 检查 sessionId 是否有活跃 prompt、正在收尾或已预约自动恢复。 */
|
|
67
|
-
export function isSessionRunning(sessionId: string): boolean {
|
|
68
|
-
return activePrompts.has(sessionId)
|
|
69
|
-
|| finalizingSessions.has(sessionId)
|
|
70
|
-
|| autoRecoveryReservations.has(sessionId);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function markSessionFinalizing(sessionId: string): void {
|
|
74
|
-
finalizingSessions.add(sessionId);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function clearSessionFinalizing(sessionId: string): void {
|
|
78
|
-
finalizingSessions.delete(sessionId);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** 为 response-stall 后的内部恢复轮预留下一次执行权。 */
|
|
82
|
-
export function reserveAutoRecovery(sessionId: string): boolean {
|
|
83
|
-
if (autoRecoveryReservations.has(sessionId)) return false;
|
|
84
|
-
autoRecoveryReservations.add(sessionId);
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** 恢复轮启动前原子消费预约;返回 false 表示已被 /stop 取消。 */
|
|
89
|
-
export function consumeAutoRecoveryReservation(sessionId: string): boolean {
|
|
90
|
-
return autoRecoveryReservations.delete(sessionId);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** 取消尚未启动的恢复轮。 */
|
|
94
|
-
export function cancelAutoRecoveryReservation(sessionId: string): boolean {
|
|
95
|
-
return autoRecoveryReservations.delete(sessionId);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function hasAutoRecoveryReservation(sessionId: string): boolean {
|
|
99
|
-
return autoRecoveryReservations.has(sessionId);
|
|
100
|
-
}
|
|
56
|
+
// Agent generator 退出后,最终卡片、registry、头像等仍可能在异步收尾。该阶段
|
|
57
|
+
// 也必须阻止下一轮提前进入,否则旧会话的落盘可能覆盖新会话绑定。
|
|
58
|
+
const finalizingSessions = new Set<string>();
|
|
59
|
+
|
|
60
|
+
// response-stall 自动恢复预约只存在于当前进程内:用户已明确 ChatCCC 重启后
|
|
61
|
+
// 不需要补发恢复 prompt。预约从检测到第一次停滞起一直持有到恢复轮同步进入
|
|
62
|
+
// runAgentSession,填补旧轮收尾和新轮 activePrompts.set 之间的空窗,保证此时
|
|
63
|
+
// 到达的普通用户消息只能进入缓存队列,不能抢在恢复 prompt 前执行。
|
|
64
|
+
const autoRecoveryReservations = new Set<string>();
|
|
65
|
+
|
|
66
|
+
/** 检查 sessionId 是否有活跃 prompt、正在收尾或已预约自动恢复。 */
|
|
67
|
+
export function isSessionRunning(sessionId: string): boolean {
|
|
68
|
+
return activePrompts.has(sessionId)
|
|
69
|
+
|| finalizingSessions.has(sessionId)
|
|
70
|
+
|| autoRecoveryReservations.has(sessionId);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function markSessionFinalizing(sessionId: string): void {
|
|
74
|
+
finalizingSessions.add(sessionId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function clearSessionFinalizing(sessionId: string): void {
|
|
78
|
+
finalizingSessions.delete(sessionId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 为 response-stall 后的内部恢复轮预留下一次执行权。 */
|
|
82
|
+
export function reserveAutoRecovery(sessionId: string): boolean {
|
|
83
|
+
if (autoRecoveryReservations.has(sessionId)) return false;
|
|
84
|
+
autoRecoveryReservations.add(sessionId);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 恢复轮启动前原子消费预约;返回 false 表示已被 /stop 取消。 */
|
|
89
|
+
export function consumeAutoRecoveryReservation(sessionId: string): boolean {
|
|
90
|
+
return autoRecoveryReservations.delete(sessionId);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 取消尚未启动的恢复轮。 */
|
|
94
|
+
export function cancelAutoRecoveryReservation(sessionId: string): boolean {
|
|
95
|
+
return autoRecoveryReservations.delete(sessionId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function hasAutoRecoveryReservation(sessionId: string): boolean {
|
|
99
|
+
return autoRecoveryReservations.has(sessionId);
|
|
100
|
+
}
|
|
101
101
|
|
|
102
102
|
// ---------------------------------------------------------------------------
|
|
103
103
|
// activePrompts: sessionId → 活跃 prompt 控制
|
|
@@ -108,28 +108,28 @@ export interface ActivePrompt {
|
|
|
108
108
|
stopped: boolean;
|
|
109
109
|
startTime: number;
|
|
110
110
|
/** Root PID for the CLI process currently serving this prompt, if the adapter exposes one. */
|
|
111
|
-
processPid?: number;
|
|
112
|
-
processMonitor?: ReturnType<typeof setInterval>;
|
|
113
|
-
responseStallMonitor?: ReturnType<typeof setInterval>;
|
|
114
|
-
/** Grace timer that force-closes a stream which stays open after its authoritative final event. */
|
|
115
|
-
finalResponseCloseTimer?: ReturnType<typeof setTimeout>;
|
|
116
|
-
/** Character-count progress observed only while the activity is "responding". */
|
|
117
|
-
responseProgress?: ResponseProgressObservation;
|
|
118
|
-
/** Set before a response-stall auto-end begins so competing monitors cannot win the race. */
|
|
119
|
-
autoEnded?: boolean;
|
|
120
|
-
autoEndedAt?: number;
|
|
111
|
+
processPid?: number;
|
|
112
|
+
processMonitor?: ReturnType<typeof setInterval>;
|
|
113
|
+
responseStallMonitor?: ReturnType<typeof setInterval>;
|
|
114
|
+
/** Grace timer that force-closes a stream which stays open after its authoritative final event. */
|
|
115
|
+
finalResponseCloseTimer?: ReturnType<typeof setTimeout>;
|
|
116
|
+
/** Character-count progress observed only while the activity is "responding". */
|
|
117
|
+
responseProgress?: ResponseProgressObservation;
|
|
118
|
+
/** Set before a response-stall auto-end begins so competing monitors cannot win the race. */
|
|
119
|
+
autoEnded?: boolean;
|
|
120
|
+
autoEndedAt?: number;
|
|
121
121
|
/** Set when the watchdog detects that the CLI process disappeared before stream finalization. */
|
|
122
122
|
abnormalExit?: boolean;
|
|
123
123
|
abnormalExitNotified?: boolean;
|
|
124
|
-
/** Set when the resource monitor detects CPU + memory unchanged for 3 minutes. */
|
|
125
|
-
resourceStuck?: boolean;
|
|
126
|
-
/** True only for the single internal continuation turn after a response stall. */
|
|
127
|
-
autoRecovery?: boolean;
|
|
128
|
-
/** Adapter observed an authoritative completed final-response event. */
|
|
129
|
-
finalResponseObserved?: boolean;
|
|
130
|
-
/** Adapter-provided callback to close the underlying SDK session / subprocess.
|
|
131
|
-
* Called by stop-stuck-loop before controller.abort() to terminate the CLI
|
|
132
|
-
* process immediately, rather than waiting for the async generator to unblock. */
|
|
124
|
+
/** Set when the resource monitor detects CPU + memory unchanged for 3 minutes. */
|
|
125
|
+
resourceStuck?: boolean;
|
|
126
|
+
/** True only for the single internal continuation turn after a response stall. */
|
|
127
|
+
autoRecovery?: boolean;
|
|
128
|
+
/** Adapter observed an authoritative completed final-response event. */
|
|
129
|
+
finalResponseObserved?: boolean;
|
|
130
|
+
/** Adapter-provided callback to close the underlying SDK session / subprocess.
|
|
131
|
+
* Called by stop-stuck-loop before controller.abort() to terminate the CLI
|
|
132
|
+
* process immediately, rather than waiting for the async generator to unblock. */
|
|
133
133
|
closeSession?: () => void;
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -193,10 +193,10 @@ export interface DisplayCardState {
|
|
|
193
193
|
cardId: string;
|
|
194
194
|
sequence: number;
|
|
195
195
|
cardBusy: boolean;
|
|
196
|
-
cardCreatedAt: number;
|
|
197
|
-
lastSentContent: string;
|
|
198
|
-
/** Last rendered activity header; elapsed time can change without body output. */
|
|
199
|
-
lastSentHeaderTitle?: string;
|
|
196
|
+
cardCreatedAt: number;
|
|
197
|
+
lastSentContent: string;
|
|
198
|
+
/** Last rendered activity header; elapsed time can change without body output. */
|
|
199
|
+
lastSentHeaderTitle?: string;
|
|
200
200
|
streamErrorNotified: boolean;
|
|
201
201
|
/** 所属 session */
|
|
202
202
|
sessionId: string;
|
|
@@ -209,8 +209,8 @@ export interface DisplayCardState {
|
|
|
209
209
|
lastSentAccLen?: number;
|
|
210
210
|
/** WeChat delta: 上次发送时的 finalReply */
|
|
211
211
|
lastSentFinalReply?: string;
|
|
212
|
-
/** Liveness animation counter; the explicit activity header conveys Agent state. */
|
|
213
|
-
dotCount: number;
|
|
212
|
+
/** Liveness animation counter; the explicit activity header conveys Agent state. */
|
|
213
|
+
dotCount: number;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
export const displayCards = new Map<string, DisplayCardState>();
|
|
@@ -274,18 +274,18 @@ export function consumeQueuedMessage(platform: PlatformAdapter, msg: QueuedMessa
|
|
|
274
274
|
export function resetBindingState(): void {
|
|
275
275
|
sessionChatsMap.clear();
|
|
276
276
|
lastActiveChatMap.clear();
|
|
277
|
-
for (const prompt of activePrompts.values()) {
|
|
278
|
-
if (prompt.processMonitor) clearInterval(prompt.processMonitor);
|
|
279
|
-
if (prompt.responseStallMonitor) clearInterval(prompt.responseStallMonitor);
|
|
280
|
-
if (prompt.finalResponseCloseTimer) clearTimeout(prompt.finalResponseCloseTimer);
|
|
281
|
-
}
|
|
282
|
-
activePrompts.clear();
|
|
283
|
-
finalizingSessions.clear();
|
|
284
|
-
autoRecoveryReservations.clear();
|
|
285
|
-
queuedMessages.clear();
|
|
277
|
+
for (const prompt of activePrompts.values()) {
|
|
278
|
+
if (prompt.processMonitor) clearInterval(prompt.processMonitor);
|
|
279
|
+
if (prompt.responseStallMonitor) clearInterval(prompt.responseStallMonitor);
|
|
280
|
+
if (prompt.finalResponseCloseTimer) clearTimeout(prompt.finalResponseCloseTimer);
|
|
281
|
+
}
|
|
282
|
+
activePrompts.clear();
|
|
283
|
+
finalizingSessions.clear();
|
|
284
|
+
autoRecoveryReservations.clear();
|
|
285
|
+
queuedMessages.clear();
|
|
286
286
|
displayCards.clear();
|
|
287
287
|
if (unifiedDisplayLoopHandle !== null) {
|
|
288
288
|
clearInterval(unifiedDisplayLoopHandle);
|
|
289
289
|
unifiedDisplayLoopHandle = null;
|
|
290
290
|
}
|
|
291
|
-
}
|
|
291
|
+
}
|
package/src/session-name.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export function cwdDisplayName(cwd: string): string {
|
|
2
|
-
const trimmed = cwd.trim().replace(/[\\/]+$/, "");
|
|
3
|
-
return trimmed.split(/[\\/]/).filter(Boolean).pop() || trimmed || "cwd";
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export function sessionChatName(left: string, cwd: string): string {
|
|
7
|
-
return `${left}-${cwdDisplayName(cwd)}`;
|
|
8
|
-
}
|
|
1
|
+
export function cwdDisplayName(cwd: string): string {
|
|
2
|
+
const trimmed = cwd.trim().replace(/[\\/]+$/, "");
|
|
3
|
+
return trimmed.split(/[\\/]/).filter(Boolean).pop() || trimmed || "cwd";
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function sessionChatName(left: string, cwd: string): string {
|
|
7
|
+
return `${left}-${cwdDisplayName(cwd)}`;
|
|
8
|
+
}
|
package/src/session.ts
CHANGED
|
@@ -182,7 +182,7 @@ export const RESPONSE_STALL_RECOVERY_PROMPT = "完成了吗?如果没完成继
|
|
|
182
182
|
export const RESPONSE_STALL_RECOVERY_NOTICE =
|
|
183
183
|
`检测到会话停滞,正在自动确认并继续。\n\n${RESPONSE_STALL_RECOVERY_PROMPT}`;
|
|
184
184
|
export const RESPONSE_STALL_RECOVERY_EXHAUSTED_NOTICE =
|
|
185
|
-
"⚠️ 自动续跑仍连续 3
|
|
185
|
+
"⚠️ 自动续跑仍连续 3 分钟没有生成新回复,本次不再自动继续。";
|
|
186
186
|
const RESPONSE_STALL_RECOVERY_DELAY_MS = 200;
|
|
187
187
|
let processMonitorIntervalMs = DEFAULT_PROCESS_MONITOR_INTERVAL_MS;
|
|
188
188
|
let responseStallTimeoutMs = DEFAULT_RESPONSE_STALL_TIMEOUT_MS;
|
|
@@ -331,18 +331,18 @@ function turnFinalStatus(status: "running" | "done" | "stopped" | "error" | "aut
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
function formatAutoEndedReply(finalReply: string): string {
|
|
334
|
-
const reason = "⚠️
|
|
334
|
+
const reason = "⚠️ 已自动结束:生成回复阶段连续 3 分钟没有字符变化。";
|
|
335
335
|
return finalReply
|
|
336
336
|
? `${reason}以下回复可能不完整。\n\n${finalReply}`
|
|
337
337
|
: `${reason}本轮没有可发送的回复内容。`;
|
|
338
338
|
}
|
|
339
339
|
|
|
340
340
|
/**
|
|
341
|
-
*
|
|
342
|
-
*
|
|
341
|
+
* 只监控明确的回复生成阶段。启动、压缩、思考、工具调用和搜索都有各自的
|
|
342
|
+
* 状态或资源保护,不能把它们消耗的时间算入回复停滞窗口。
|
|
343
343
|
*/
|
|
344
344
|
function monitorsOutputProgress(kind: AgentActivityKind): boolean {
|
|
345
|
-
return kind === "
|
|
345
|
+
return kind === "responding";
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
function formatTerminalReply(
|
|
@@ -954,6 +954,8 @@ export function accumulateBlockContent(
|
|
|
954
954
|
`\n\n🔄 上下文压缩(${triggerLabel}): **${block.pre_tokens}** → **${block.post_tokens}** tokens\n`; // 🔄 / →
|
|
955
955
|
break;
|
|
956
956
|
}
|
|
957
|
+
case "agent_status":
|
|
958
|
+
break;
|
|
957
959
|
}
|
|
958
960
|
}
|
|
959
961
|
|
|
@@ -1454,12 +1456,11 @@ export async function runAgentSession(
|
|
|
1454
1456
|
|
|
1455
1457
|
const runningPrompt = activePrompts.get(sessionId);
|
|
1456
1458
|
if (runningPrompt) {
|
|
1457
|
-
//
|
|
1458
|
-
//
|
|
1459
|
-
// “正在启动 Agent”,也永远触发不了三分钟保护。
|
|
1459
|
+
// 在消费第一个事件前建立阶段感知的零字符基线;启动阶段不计时,只有后续
|
|
1460
|
+
// 收到明确的 responding 状态后才会启动三分钟回复停滞保护。
|
|
1460
1461
|
runningPrompt.responseProgress = observeResponseProgress(
|
|
1461
1462
|
undefined,
|
|
1462
|
-
|
|
1463
|
+
monitorsOutputProgress(activityTracker.activity.kind),
|
|
1463
1464
|
0,
|
|
1464
1465
|
activityTracker.activity.startedAt,
|
|
1465
1466
|
);
|
|
@@ -1532,7 +1533,7 @@ export async function runAgentSession(
|
|
|
1532
1533
|
current.controller.abort();
|
|
1533
1534
|
await killProcessTree(current.processPid);
|
|
1534
1535
|
console.warn(
|
|
1535
|
-
`[${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`,
|
|
1536
1537
|
);
|
|
1537
1538
|
};
|
|
1538
1539
|
|
|
@@ -1795,7 +1796,7 @@ export async function runAgentSession(
|
|
|
1795
1796
|
autoRecoveryTarget = { chatId: activeAutoEnded, platform: pp };
|
|
1796
1797
|
}
|
|
1797
1798
|
}
|
|
1798
|
-
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})`);
|
|
1799
1800
|
if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "response_stall", chunks: state.chunkCount });
|
|
1800
1801
|
} else if (wasAbnormalExit) {
|
|
1801
1802
|
for (const cid of finalizationChatIds) {
|
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
|
+
}
|