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.
Files changed (72) hide show
  1. package/agent-prompts/claude_specific.md +45 -45
  2. package/agent-prompts/codex_specific.md +2 -2
  3. package/agent-prompts/cursor_specific.md +13 -13
  4. package/config.sample.json +14 -14
  5. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  6. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  7. package/im-skills/feishu-skill/skill.md +3 -3
  8. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  9. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  10. package/im-skills/wechat-file-skill/skill.md +10 -10
  11. package/im-skills/wechat-image-skill/skill.md +10 -10
  12. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  13. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  14. package/im-skills/wechat-video-skill/skill.md +10 -10
  15. package/package.json +1 -1
  16. package/scripts/postinstall-sharp-check.mjs +58 -58
  17. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  18. package/src/__tests__/builtin-chat-session.test.ts +76 -0
  19. package/src/__tests__/builtin-config.test.ts +33 -33
  20. package/src/__tests__/builtin-context.test.ts +126 -0
  21. package/src/__tests__/builtin-sigint.test.ts +56 -0
  22. package/src/__tests__/card-plain-text.test.ts +5 -5
  23. package/src/__tests__/cardkit.test.ts +60 -60
  24. package/src/__tests__/cards.test.ts +77 -77
  25. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  26. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  27. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  28. package/src/__tests__/claude-adapter.test.ts +592 -592
  29. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  30. package/src/__tests__/config-reload.test.ts +4 -4
  31. package/src/__tests__/config-sample.test.ts +17 -17
  32. package/src/__tests__/feishu-api.test.ts +60 -60
  33. package/src/__tests__/feishu-platform.test.ts +22 -22
  34. package/src/__tests__/format-message.test.ts +47 -47
  35. package/src/__tests__/orchestrator.test.ts +120 -120
  36. package/src/__tests__/privacy.test.ts +198 -198
  37. package/src/__tests__/raw-stream-log.test.ts +106 -106
  38. package/src/__tests__/session.test.ts +17 -17
  39. package/src/__tests__/shared-prefix.test.ts +36 -36
  40. package/src/__tests__/stream-state.test.ts +42 -42
  41. package/src/__tests__/web-ui.test.ts +209 -209
  42. package/src/adapters/claude-adapter.ts +566 -566
  43. package/src/adapters/claude-session-meta-store.ts +120 -120
  44. package/src/adapters/codex-adapter.ts +30 -30
  45. package/src/adapters/cursor-adapter.ts +46 -46
  46. package/src/adapters/raw-stream-log.ts +124 -124
  47. package/src/adapters/resource-monitor.ts +140 -140
  48. package/src/agent-delegate-task-rpc.ts +153 -153
  49. package/src/agent-delegate-task.ts +81 -81
  50. package/src/agent-stop-stuck.ts +129 -129
  51. package/src/builtin/cli.ts +211 -189
  52. package/src/builtin/context.ts +225 -0
  53. package/src/builtin/index.ts +232 -170
  54. package/src/builtin/sigint.ts +50 -0
  55. package/src/cards.ts +137 -137
  56. package/src/chatgpt-subscription-rpc.ts +27 -27
  57. package/src/chatgpt-subscription.ts +299 -299
  58. package/src/chrome-devtools-guard.ts +318 -318
  59. package/src/codex-reset-actions.ts +184 -184
  60. package/src/config.ts +86 -86
  61. package/src/feishu-platform.ts +20 -20
  62. package/src/format-message.ts +293 -293
  63. package/src/litellm-proxy.ts +374 -374
  64. package/src/orchestrator.ts +143 -143
  65. package/src/privacy.ts +118 -118
  66. package/src/session-chat-binding.ts +6 -6
  67. package/src/session-name.ts +8 -8
  68. package/src/session.ts +98 -98
  69. package/src/shared-prefix.ts +29 -29
  70. package/src/sim-platform.ts +20 -20
  71. package/src/turn-cards.ts +117 -117
  72. package/src/web-ui.ts +205 -205
@@ -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
- const SYSTEM_PROMPT = [
17
- "你是 ChatCCC 内置 AI 编程助手,运行在终端环境中。",
18
- "",
19
- "## 基本规则",
20
- "- 用中文回复,但代码、命令、文件名保持原文",
21
- "- 优先给出直接可用的方案,而非长篇解释",
22
- "- 如果用户的问题涉及代码,直接给出代码并说明用法",
23
- "- 保持简洁,一次聚焦一个问题",
24
- ].join("\n");
25
-
26
- // ---------------------------------------------------------------------------
27
- // 类型定义
28
- // ---------------------------------------------------------------------------
29
-
30
- export interface ChatSessionConfig {
31
- /** DeepSeek API 兼容的服务地址;传入时覆盖 config.ccc.DEEPSEEK_BASE_URL */
32
- baseURL?: string;
33
- /** API Key;传入时覆盖 config.ccc.DEEPSEEK_API_KEY */
34
- apiKey?: string;
35
- /** 模型名称;传入时覆盖 config.ccc.model */
36
- model?: string;
37
- }
38
-
39
- export interface ChatSessionOptions {
40
- /** 会话工作目录 */
41
- cwd?: string;
42
- /** 自定义系统提示词(会拼接到默认提示词之后) */
43
- systemPrompt?: string;
44
- }
45
-
46
- /**
47
- * 流式响应事件
48
- */
49
- export type ChatEvent =
50
- | { type: "text"; text: string; accumulated: string }
51
- | { type: "done"; text: string }
52
- | { type: "error"; message: string };
53
-
54
- // ---------------------------------------------------------------------------
55
- // ChatSession
56
- // ---------------------------------------------------------------------------
57
-
58
- /** 消息角色 */
59
- type MessageRole = "system" | "user" | "assistant" | "tool";
60
-
61
- /** 内部消息类型 */
62
- interface ChatMessage {
63
- role: MessageRole;
64
- content: string;
65
- }
66
-
67
- export class ChatSession {
68
- private model: any;
69
- private systemPrompt: string;
70
- private messages: ChatMessage[];
71
-
72
- constructor(
73
- overrides: ChatSessionConfig = {},
74
- options: ChatSessionOptions = {},
75
- ) {
76
- const apiKey = overrides.apiKey ?? appConfig.ccc.DEEPSEEK_API_KEY;
77
- if (!apiKey) {
78
- throw new Error(
79
- "ccc.DEEPSEEK_API_KEY 未设置。请在 config.json 中配置,或通过 --api-key 临时传入",
80
- );
81
- }
82
-
83
- const baseURL = overrides.baseURL ?? appConfig.ccc.DEEPSEEK_BASE_URL;
84
- const modelId = overrides.model ?? appConfig.ccc.model;
85
-
86
- const provider = createOpenAICompatible({
87
- name: "deepseek",
88
- baseURL,
89
- apiKey,
90
- });
91
- this.model = provider(modelId);
92
-
93
- // 构建系统提示词
94
- const systemContent = [SYSTEM_PROMPT];
95
- if (options?.systemPrompt) {
96
- systemContent.push("", options.systemPrompt);
97
- }
98
- if (options?.cwd) {
99
- systemContent.push("", `当前工作目录: ${options.cwd}`);
100
- }
101
-
102
- this.systemPrompt = systemContent.join("\n");
103
- this.messages = [];
104
- }
105
-
106
- /**
107
- * 发送用户消息,返回异步可迭代的文本流。
108
- *
109
- * 使用方式:
110
- * ```typescript
111
- * const session = new ChatSession();
112
- * for await (const event of session.chat("帮我看看 package.json")) {
113
- * if (event.type === "text") process.stdout.write(event.text);
114
- * }
115
- * console.log("完成");
116
- * ```
117
- */
118
- async *chat(
119
- userMessage: string,
120
- signal?: AbortSignal,
121
- ): AsyncIterable<ChatEvent> {
122
- this.messages.push({ role: "user", content: userMessage });
123
-
124
- let fullText = "";
125
-
126
- try {
127
- const result = streamText({
128
- model: this.model,
129
- system: this.systemPrompt,
130
- messages: this.messages as any,
131
- abortSignal: signal,
132
- });
133
-
134
- for await (const chunk of result.textStream) {
135
- fullText += chunk;
136
- yield { type: "text", text: chunk, accumulated: fullText };
137
- }
138
-
139
- this.messages.push({ role: "assistant", content: fullText });
140
- yield { type: "done", text: fullText };
141
- } catch (err) {
142
- const message = err instanceof Error ? err.message : String(err);
143
- if ((err as Error).name === "AbortError" || signal?.aborted) {
144
- // 被中断时,不保存不完整的助手消息
145
- if (fullText) {
146
- this.messages.push({ role: "assistant", content: fullText + "\n[已中断]" });
147
- }
148
- yield { type: "done", text: fullText };
149
- return;
150
- }
151
- yield { type: "error", message };
152
- throw err;
153
- }
154
- }
155
-
156
- /** 返回当前的会话历史(只读) */
157
- get history(): ReadonlyArray<ChatMessage> {
158
- return [{ role: "system", content: this.systemPrompt }, ...this.messages];
159
- }
160
-
161
- /** 返回当前轮数(不含 system 消息) */
162
- get turnCount(): number {
163
- return this.messages.length;
164
- }
165
-
166
- /** 清空会话历史,保留 system 消息 */
167
- reset(): void {
168
- this.messages = [];
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
+ }