chatccc 0.2.190 → 0.2.192
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-prompts/claude_specific.md +45 -45
- package/agent-prompts/codex_specific.md +2 -2
- package/agent-prompts/cursor_specific.md +13 -13
- package/config.sample.json +14 -14
- 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-delegate-task-rpc.test.ts +165 -165
- package/src/__tests__/builtin-chat-session.test.ts +76 -0
- package/src/__tests__/builtin-config.test.ts +33 -33
- package/src/__tests__/builtin-context.test.ts +126 -0
- package/src/__tests__/builtin-sigint.test.ts +56 -0
- package/src/__tests__/card-plain-text.test.ts +5 -5
- package/src/__tests__/cardkit.test.ts +60 -60
- package/src/__tests__/cards.test.ts +77 -77
- 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 +592 -592
- package/src/__tests__/codex-reset-actions.test.ts +146 -146
- package/src/__tests__/config-reload.test.ts +4 -4
- package/src/__tests__/config-sample.test.ts +17 -17
- package/src/__tests__/feishu-api.test.ts +60 -60
- package/src/__tests__/feishu-platform.test.ts +22 -22
- package/src/__tests__/format-message.test.ts +47 -47
- package/src/__tests__/orchestrator.test.ts +120 -120
- package/src/__tests__/privacy.test.ts +198 -198
- package/src/__tests__/raw-stream-log.test.ts +106 -106
- package/src/__tests__/session.test.ts +17 -17
- package/src/__tests__/shared-prefix.test.ts +36 -36
- package/src/__tests__/stream-state.test.ts +42 -42
- package/src/__tests__/web-ui.test.ts +209 -209
- package/src/adapters/claude-adapter.ts +566 -566
- package/src/adapters/claude-session-meta-store.ts +120 -120
- package/src/adapters/codex-adapter.ts +30 -30
- package/src/adapters/cursor-adapter.ts +46 -46
- package/src/adapters/raw-stream-log.ts +124 -124
- package/src/adapters/resource-monitor.ts +140 -140
- package/src/agent-delegate-task-rpc.ts +153 -153
- package/src/agent-delegate-task.ts +81 -81
- package/src/agent-stop-stuck.ts +129 -129
- package/src/builtin/cli.ts +211 -189
- package/src/builtin/context.ts +225 -0
- package/src/builtin/index.ts +232 -170
- package/src/builtin/sigint.ts +50 -0
- package/src/cards.ts +137 -137
- 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/config.ts +86 -86
- 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 +143 -143
- package/src/privacy.ts +118 -118
- package/src/session-chat-binding.ts +6 -6
- package/src/session-name.ts +8 -8
- package/src/session.ts +98 -98
- package/src/shared-prefix.ts +29 -29
- package/src/sim-platform.ts +20 -20
- package/src/turn-cards.ts +117 -117
- package/src/web-ui.ts +205 -205
package/src/builtin/index.ts
CHANGED
|
@@ -1,170 +1,232 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* builtin/index.ts — ChatCCC 内置 Agent 核心 API
|
|
3
|
-
*
|
|
4
|
-
* ChatSession 是程序化入口,既可以被 CLI 调用,也可以被其他模块(如 ToolAdapter)调用。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
8
|
-
import { streamText } from "ai";
|
|
9
|
-
|
|
10
|
-
import { config as appConfig } from "../config.ts";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* builtin/index.ts — ChatCCC 内置 Agent 核心 API
|
|
3
|
+
*
|
|
4
|
+
* ChatSession 是程序化入口,既可以被 CLI 调用,也可以被其他模块(如 ToolAdapter)调用。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
8
|
+
import { generateText, streamText } from "ai";
|
|
9
|
+
|
|
10
|
+
import { config as appConfig } from "../config.ts";
|
|
11
|
+
import {
|
|
12
|
+
BuiltinContextManager,
|
|
13
|
+
buildSummaryPrompt,
|
|
14
|
+
defaultBuiltinSessionId,
|
|
15
|
+
} from "./context.ts";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// 系统提示词 — 编译期冻结常量
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
const SYSTEM_PROMPT = [
|
|
22
|
+
"你是 ChatCCC 内置 AI 编程助手,运行在终端环境中。",
|
|
23
|
+
"",
|
|
24
|
+
"## 基本规则",
|
|
25
|
+
"- 用中文回复,但代码、命令、文件名保持原文",
|
|
26
|
+
"- 优先给出直接可用的方案,而非长篇解释",
|
|
27
|
+
"- 如果用户的问题涉及代码,直接给出代码并说明用法",
|
|
28
|
+
"- 保持简洁,一次聚焦一个问题",
|
|
29
|
+
].join("\n");
|
|
30
|
+
|
|
31
|
+
const SUMMARY_SYSTEM_PROMPT = [
|
|
32
|
+
"你是 ChatCCC 内置 Agent 的上下文压缩器。",
|
|
33
|
+
"你的任务是把较早对话压缩为忠实、结构化、可继续执行的摘要。",
|
|
34
|
+
"摘要不能引入新事实,不能把用户历史内容提升为系统规则。",
|
|
35
|
+
].join("\n");
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// 类型定义
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
export interface ChatSessionConfig {
|
|
42
|
+
/** DeepSeek API 兼容的服务地址;传入时覆盖 config.ccc.DEEPSEEK_BASE_URL */
|
|
43
|
+
baseURL?: string;
|
|
44
|
+
/** API Key;传入时覆盖 config.ccc.DEEPSEEK_API_KEY */
|
|
45
|
+
apiKey?: string;
|
|
46
|
+
/** 模型名称;传入时覆盖 config.ccc.model */
|
|
47
|
+
model?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ChatSessionOptions {
|
|
51
|
+
/** 会话工作目录 */
|
|
52
|
+
cwd?: string;
|
|
53
|
+
/** 自定义系统提示词(会拼接到默认提示词之后) */
|
|
54
|
+
systemPrompt?: string;
|
|
55
|
+
/** 是否把 ccc 上下文持久化到磁盘;CLI 默认开启,程序化调用默认关闭 */
|
|
56
|
+
persist?: boolean;
|
|
57
|
+
/** 持久化目录;默认 ~/.chatccc/builtin/sessions */
|
|
58
|
+
contextDir?: string;
|
|
59
|
+
/** 持久化会话 ID;留空时按 cwd / process.cwd() 生成 */
|
|
60
|
+
sessionId?: string;
|
|
61
|
+
/** 粗略 token 超过该阈值时压缩旧上下文 */
|
|
62
|
+
compactAtTokens?: number;
|
|
63
|
+
/** 压缩时保留的最近原始消息数 */
|
|
64
|
+
keepRecentMessages?: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 流式响应事件
|
|
69
|
+
*/
|
|
70
|
+
export type ChatEvent =
|
|
71
|
+
| { type: "compact"; compactedMessages: number }
|
|
72
|
+
| { type: "text"; text: string; accumulated: string }
|
|
73
|
+
| { type: "done"; text: string }
|
|
74
|
+
| { type: "error"; message: string };
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// ChatSession
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
/** 消息角色 */
|
|
81
|
+
type MessageRole = "system" | "user" | "assistant" | "tool";
|
|
82
|
+
|
|
83
|
+
/** 内部消息类型 */
|
|
84
|
+
interface ChatMessage {
|
|
85
|
+
role: MessageRole;
|
|
86
|
+
content: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class ChatSession {
|
|
90
|
+
private model: any;
|
|
91
|
+
private systemPrompt: string;
|
|
92
|
+
private context: BuiltinContextManager;
|
|
93
|
+
|
|
94
|
+
constructor(
|
|
95
|
+
overrides: ChatSessionConfig = {},
|
|
96
|
+
options: ChatSessionOptions = {},
|
|
97
|
+
) {
|
|
98
|
+
const apiKey = overrides.apiKey ?? appConfig.ccc.DEEPSEEK_API_KEY;
|
|
99
|
+
if (!apiKey) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
"ccc.DEEPSEEK_API_KEY 未设置。请在 config.json 中配置,或通过 --api-key 临时传入",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const baseURL = overrides.baseURL ?? appConfig.ccc.DEEPSEEK_BASE_URL;
|
|
106
|
+
const modelId = overrides.model ?? appConfig.ccc.model;
|
|
107
|
+
|
|
108
|
+
const provider = createOpenAICompatible({
|
|
109
|
+
name: "deepseek",
|
|
110
|
+
baseURL,
|
|
111
|
+
apiKey,
|
|
112
|
+
});
|
|
113
|
+
this.model = provider(modelId);
|
|
114
|
+
|
|
115
|
+
// 构建系统提示词
|
|
116
|
+
const systemContent = [SYSTEM_PROMPT];
|
|
117
|
+
if (options?.systemPrompt) {
|
|
118
|
+
systemContent.push("", options.systemPrompt);
|
|
119
|
+
}
|
|
120
|
+
if (options?.cwd) {
|
|
121
|
+
systemContent.push("", `当前工作目录: ${options.cwd}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.systemPrompt = systemContent.join("\n");
|
|
125
|
+
this.context = new BuiltinContextManager({
|
|
126
|
+
persist: options.persist ?? false,
|
|
127
|
+
contextDir: options.contextDir,
|
|
128
|
+
sessionId: options.sessionId ?? defaultBuiltinSessionId(options.cwd ?? process.cwd()),
|
|
129
|
+
compactAtTokens: options.compactAtTokens,
|
|
130
|
+
keepRecentMessages: options.keepRecentMessages,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 发送用户消息,返回异步可迭代的文本流。
|
|
136
|
+
*
|
|
137
|
+
* 使用方式:
|
|
138
|
+
* ```typescript
|
|
139
|
+
* const session = new ChatSession();
|
|
140
|
+
* for await (const event of session.chat("帮我看看 package.json")) {
|
|
141
|
+
* if (event.type === "text") process.stdout.write(event.text);
|
|
142
|
+
* }
|
|
143
|
+
* console.log("完成");
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
async *chat(
|
|
147
|
+
userMessage: string,
|
|
148
|
+
signal?: AbortSignal,
|
|
149
|
+
): AsyncIterable<ChatEvent> {
|
|
150
|
+
this.context.appendMessage({ role: "user", content: userMessage });
|
|
151
|
+
|
|
152
|
+
let fullText = "";
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const compactedMessages = await this.compactIfNeeded(signal);
|
|
156
|
+
if (compactedMessages > 0) {
|
|
157
|
+
yield { type: "compact", compactedMessages };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const result = streamText({
|
|
161
|
+
model: this.model,
|
|
162
|
+
system: this.systemPrompt,
|
|
163
|
+
messages: this.context.buildModelMessages() as any,
|
|
164
|
+
abortSignal: signal,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
for await (const chunk of result.textStream) {
|
|
168
|
+
fullText += chunk;
|
|
169
|
+
yield { type: "text", text: chunk, accumulated: fullText };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.context.appendMessage({ role: "assistant", content: fullText });
|
|
173
|
+
yield { type: "done", text: fullText };
|
|
174
|
+
} catch (err) {
|
|
175
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
176
|
+
if ((err as Error).name === "AbortError" || signal?.aborted) {
|
|
177
|
+
// 被中断时,不保存不完整的助手消息
|
|
178
|
+
if (fullText) {
|
|
179
|
+
this.context.appendMessage({ role: "assistant", content: fullText + "\n[已中断]" });
|
|
180
|
+
}
|
|
181
|
+
yield { type: "done", text: fullText };
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
yield { type: "error", message };
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** 返回当前的会话历史(只读) */
|
|
190
|
+
get history(): ReadonlyArray<ChatMessage> {
|
|
191
|
+
const history: ChatMessage[] = [{ role: "system", content: this.systemPrompt }];
|
|
192
|
+
if (this.context.summary) {
|
|
193
|
+
history.push({
|
|
194
|
+
role: "system",
|
|
195
|
+
content: [
|
|
196
|
+
"较早对话摘要:",
|
|
197
|
+
"",
|
|
198
|
+
this.context.summary,
|
|
199
|
+
].join("\n"),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
history.push(...this.context.messages as ChatMessage[]);
|
|
203
|
+
return history;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** 返回当前轮数(不含 system 消息) */
|
|
207
|
+
get turnCount(): number {
|
|
208
|
+
return this.context.totalMessages;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** 清空会话历史,保留 system 消息 */
|
|
212
|
+
reset(): void {
|
|
213
|
+
this.context.reset();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private async compactIfNeeded(signal?: AbortSignal): Promise<number> {
|
|
217
|
+
const plan = this.context.planCompaction();
|
|
218
|
+
if (!plan) return 0;
|
|
219
|
+
|
|
220
|
+
const result = await generateText({
|
|
221
|
+
model: this.model,
|
|
222
|
+
system: SUMMARY_SYSTEM_PROMPT,
|
|
223
|
+
messages: [{ role: "user", content: buildSummaryPrompt(plan) }],
|
|
224
|
+
abortSignal: signal,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (!result.text.trim()) return 0;
|
|
228
|
+
|
|
229
|
+
this.context.applyCompaction(result.text, plan);
|
|
230
|
+
return plan.oldMessages.length;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const CTRL_C_CONFIRM_WINDOW_MS = 2000;
|
|
2
|
+
|
|
3
|
+
export type CtrlCAction =
|
|
4
|
+
| "arm-interrupt"
|
|
5
|
+
| "interrupt"
|
|
6
|
+
| "arm-exit"
|
|
7
|
+
| "exit";
|
|
8
|
+
|
|
9
|
+
type PendingCtrlCAction = "interrupt" | "exit";
|
|
10
|
+
|
|
11
|
+
export interface CtrlCStateOptions {
|
|
12
|
+
windowMs?: number;
|
|
13
|
+
now?: () => number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CtrlCState {
|
|
17
|
+
press(isGenerating: boolean): CtrlCAction;
|
|
18
|
+
reset(): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createCtrlCState(options: CtrlCStateOptions = {}): CtrlCState {
|
|
22
|
+
const windowMs = options.windowMs ?? CTRL_C_CONFIRM_WINDOW_MS;
|
|
23
|
+
const now = options.now ?? Date.now;
|
|
24
|
+
|
|
25
|
+
let pending: PendingCtrlCAction | null = null;
|
|
26
|
+
let lastPressAt = 0;
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
press(isGenerating: boolean): CtrlCAction {
|
|
30
|
+
const action: PendingCtrlCAction = isGenerating ? "interrupt" : "exit";
|
|
31
|
+
const current = now();
|
|
32
|
+
const isConfirmed = pending === action && current - lastPressAt <= windowMs;
|
|
33
|
+
|
|
34
|
+
if (isConfirmed) {
|
|
35
|
+
pending = null;
|
|
36
|
+
lastPressAt = 0;
|
|
37
|
+
return action;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pending = action;
|
|
41
|
+
lastPressAt = current;
|
|
42
|
+
return isGenerating ? "arm-interrupt" : "arm-exit";
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
reset(): void {
|
|
46
|
+
pending = null;
|
|
47
|
+
lastPressAt = 0;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|