chatccc 0.2.198 → 0.2.199

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 (52) hide show
  1. package/agent-prompts/cursor_specific.md +13 -13
  2. package/bin/cccagent.mjs +17 -17
  3. package/config.sample.json +27 -27
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
  6. package/src/__tests__/builtin-chat-session.test.ts +277 -277
  7. package/src/__tests__/builtin-cli-json.test.ts +39 -39
  8. package/src/__tests__/builtin-config.test.ts +33 -33
  9. package/src/__tests__/builtin-context.test.ts +163 -163
  10. package/src/__tests__/builtin-file-tools.test.ts +224 -224
  11. package/src/__tests__/builtin-session-select.test.ts +116 -116
  12. package/src/__tests__/builtin-sigint.test.ts +56 -56
  13. package/src/__tests__/cards.test.ts +109 -109
  14. package/src/__tests__/ccc-adapter.test.ts +114 -114
  15. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  16. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  17. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  18. package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
  19. package/src/__tests__/codex-raw-stream-log.test.ts +163 -163
  20. package/src/__tests__/config-reload.test.ts +10 -10
  21. package/src/__tests__/config-sample.test.ts +18 -18
  22. package/src/__tests__/cursor-adapter.test.ts +66 -7
  23. package/src/__tests__/feishu-avatar.test.ts +40 -40
  24. package/src/__tests__/jsonl-stream.test.ts +79 -79
  25. package/src/__tests__/orchestrator.test.ts +200 -200
  26. package/src/__tests__/raw-stream-log.test.ts +106 -106
  27. package/src/__tests__/session.test.ts +40 -40
  28. package/src/__tests__/sim-platform.test.ts +12 -12
  29. package/src/__tests__/web-ui.test.ts +209 -209
  30. package/src/adapters/ccc-adapter.ts +121 -121
  31. package/src/adapters/claude-adapter.ts +603 -603
  32. package/src/adapters/codex-adapter.ts +380 -380
  33. package/src/adapters/cursor-adapter.ts +39 -6
  34. package/src/adapters/jsonl-stream.ts +157 -157
  35. package/src/adapters/raw-stream-log.ts +124 -124
  36. package/src/agent-reload-config-rpc.ts +34 -34
  37. package/src/builtin/cli.ts +473 -473
  38. package/src/builtin/context.ts +323 -323
  39. package/src/builtin/file-tools.ts +1072 -1072
  40. package/src/builtin/index.ts +404 -404
  41. package/src/builtin/session-select.ts +48 -48
  42. package/src/builtin/sigint.ts +50 -50
  43. package/src/cards.ts +195 -195
  44. package/src/chatgpt-subscription-rpc.ts +27 -27
  45. package/src/chatgpt-subscription.ts +299 -299
  46. package/src/chrome-devtools-guard.ts +318 -318
  47. package/src/config.ts +125 -125
  48. package/src/feishu-api.ts +49 -49
  49. package/src/orchestrator.ts +179 -179
  50. package/src/runtime-reload.ts +34 -34
  51. package/src/session.ts +141 -141
  52. package/src/web-ui.ts +205 -205
@@ -1,33 +1,33 @@
1
- import { afterEach, describe, expect, it } from "vitest";
2
-
3
- import { ChatSession } from "../builtin/index.ts";
4
- import { config } from "../config.ts";
5
-
6
- const originalDeepSeekApiKey = process.env.DEEPSEEK_API_KEY;
7
- const originalCccConfig = structuredClone(config.ccc);
8
-
9
- afterEach(() => {
10
- if (originalDeepSeekApiKey === undefined) {
11
- delete process.env.DEEPSEEK_API_KEY;
12
- } else {
13
- process.env.DEEPSEEK_API_KEY = originalDeepSeekApiKey;
14
- }
15
- config.ccc = structuredClone(originalCccConfig);
16
- });
17
-
18
- describe("builtin ChatSession config", () => {
19
- it("does not fall back to DEEPSEEK_API_KEY environment variable", () => {
20
- config.ccc = {
21
- DEEPSEEK_API_KEY: "",
22
- DEEPSEEK_BASE_URL: "https://api.deepseek.com/v1",
23
- model: "deepseek-v4-pro",
24
- };
25
- process.env.DEEPSEEK_API_KEY = "sk-env-should-not-be-used";
26
-
27
- expect(() => new ChatSession()).toThrow("ccc.DEEPSEEK_API_KEY 未设置");
28
- });
29
-
30
- it("allows constructor parameters to override config defaults", () => {
31
- expect(() => new ChatSession({ apiKey: "sk-test" })).not.toThrow();
32
- });
33
- });
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+
3
+ import { ChatSession } from "../builtin/index.ts";
4
+ import { config } from "../config.ts";
5
+
6
+ const originalDeepSeekApiKey = process.env.DEEPSEEK_API_KEY;
7
+ const originalCccConfig = structuredClone(config.ccc);
8
+
9
+ afterEach(() => {
10
+ if (originalDeepSeekApiKey === undefined) {
11
+ delete process.env.DEEPSEEK_API_KEY;
12
+ } else {
13
+ process.env.DEEPSEEK_API_KEY = originalDeepSeekApiKey;
14
+ }
15
+ config.ccc = structuredClone(originalCccConfig);
16
+ });
17
+
18
+ describe("builtin ChatSession config", () => {
19
+ it("does not fall back to DEEPSEEK_API_KEY environment variable", () => {
20
+ config.ccc = {
21
+ DEEPSEEK_API_KEY: "",
22
+ DEEPSEEK_BASE_URL: "https://api.deepseek.com/v1",
23
+ model: "deepseek-v4-pro",
24
+ };
25
+ process.env.DEEPSEEK_API_KEY = "sk-env-should-not-be-used";
26
+
27
+ expect(() => new ChatSession()).toThrow("ccc.DEEPSEEK_API_KEY 未设置");
28
+ });
29
+
30
+ it("allows constructor parameters to override config defaults", () => {
31
+ expect(() => new ChatSession({ apiKey: "sk-test" })).not.toThrow();
32
+ });
33
+ });
@@ -1,163 +1,163 @@
1
- import { mkdtemp, readFile } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
-
5
- import { describe, expect, it } from "vitest";
6
-
7
- import {
8
- BuiltinContextManager,
9
- estimateBuiltinContextTokens,
10
- listBuiltinContextSessions,
11
- newBuiltinSessionId,
12
- serializeMessagesForSummary,
13
- } from "../builtin/context.ts";
14
-
15
- describe("BuiltinContextManager", () => {
16
- it("persists and restores summary, messages, and total message count", async () => {
17
- const dir = await mkdtemp(join(tmpdir(), "chatccc-builtin-context-"));
18
-
19
- const first = new BuiltinContextManager({
20
- persist: true,
21
- contextDir: dir,
22
- sessionId: "persisted",
23
- });
24
- first.setSummary("## 用户目标\n- 保留这个摘要");
25
- first.appendMessage({ role: "user", content: "第一轮" });
26
- first.appendMessage({ role: "assistant", content: "第一轮回复" });
27
- first.save();
28
-
29
- const restored = new BuiltinContextManager({
30
- persist: true,
31
- contextDir: dir,
32
- sessionId: "persisted",
33
- });
34
-
35
- expect(restored.summary).toContain("保留这个摘要");
36
- expect(restored.messages).toEqual([
37
- { role: "user", content: "第一轮" },
38
- { role: "assistant", content: "第一轮回复" },
39
- ]);
40
- expect(restored.totalMessages).toBe(2);
41
- });
42
-
43
- it("selects only older messages for compaction and keeps recent messages raw", () => {
44
- const context = new BuiltinContextManager({
45
- compactAtTokens: 1,
46
- keepRecentMessages: 2,
47
- persist: false,
48
- });
49
- context.appendMessage({ role: "user", content: "旧用户消息" });
50
- context.appendMessage({ role: "assistant", content: "旧助手回复" });
51
- context.appendMessage({ role: "user", content: "近期用户消息" });
52
- context.appendMessage({ role: "assistant", content: "近期助手回复" });
53
-
54
- const plan = context.planCompaction();
55
-
56
- expect(plan).not.toBeNull();
57
- expect(plan?.oldMessages).toEqual([
58
- { role: "user", content: "旧用户消息" },
59
- { role: "assistant", content: "旧助手回复" },
60
- ]);
61
- expect(plan?.recentMessages).toEqual([
62
- { role: "user", content: "近期用户消息" },
63
- { role: "assistant", content: "近期助手回复" },
64
- ]);
65
- });
66
-
67
- it("applies a compacted summary and builds model messages with summary plus recent raw turns", () => {
68
- const context = new BuiltinContextManager({
69
- compactAtTokens: 1,
70
- keepRecentMessages: 1,
71
- persist: false,
72
- });
73
- context.appendMessage({ role: "user", content: "old" });
74
- context.appendMessage({ role: "assistant", content: "recent" });
75
-
76
- const plan = context.planCompaction();
77
- expect(plan).not.toBeNull();
78
- context.applyCompaction("## 当前任务状态\n- 已压缩旧上下文", plan!);
79
-
80
- expect(context.summary).toContain("已压缩旧上下文");
81
- expect(context.messages).toEqual([{ role: "assistant", content: "recent" }]);
82
- expect(context.buildModelMessages()).toEqual([
83
- {
84
- role: "user",
85
- content: expect.stringContaining("较早对话摘要"),
86
- },
87
- { role: "assistant", content: "recent" },
88
- ]);
89
- });
90
-
91
- it("reset clears memory and the persisted context file", async () => {
92
- const dir = await mkdtemp(join(tmpdir(), "chatccc-builtin-context-reset-"));
93
- const context = new BuiltinContextManager({
94
- persist: true,
95
- contextDir: dir,
96
- sessionId: "reset",
97
- });
98
- context.setSummary("summary");
99
- context.appendMessage({ role: "user", content: "hello" });
100
- context.save();
101
-
102
- context.reset();
103
-
104
- expect(context.summary).toBe("");
105
- expect(context.messages).toEqual([]);
106
- expect(context.totalMessages).toBe(0);
107
-
108
- const raw = await readFile(join(dir, "reset", "context.json"), "utf8");
109
- const persisted = JSON.parse(raw) as { summary: string; messages: unknown[]; totalMessages: number };
110
- expect(persisted.summary).toBe("");
111
- expect(persisted.messages).toEqual([]);
112
- expect(persisted.totalMessages).toBe(0);
113
- });
114
-
115
- it("persists cwd metadata and lists saved sessions newest first", async () => {
116
- const dir = await mkdtemp(join(tmpdir(), "chatccc-builtin-context-list-"));
117
-
118
- const first = new BuiltinContextManager({
119
- persist: true,
120
- contextDir: dir,
121
- sessionId: "older",
122
- cwd: "C:\\repo-a",
123
- });
124
- first.appendMessage({ role: "user", content: "old" });
125
-
126
- const second = new BuiltinContextManager({
127
- persist: true,
128
- contextDir: dir,
129
- sessionId: "newer",
130
- cwd: "C:\\repo-b",
131
- });
132
- second.appendMessage({ role: "user", content: "new" });
133
-
134
- const sessions = listBuiltinContextSessions(dir);
135
-
136
- expect(sessions.map((s) => s.sessionId)).toEqual(["newer", "older"]);
137
- expect(sessions[0]).toEqual(expect.objectContaining({
138
- cwd: "C:\\repo-b",
139
- totalMessages: 1,
140
- hasSummary: false,
141
- }));
142
- });
143
- });
144
-
145
- describe("builtin context helpers", () => {
146
- it("creates readable timestamp-based session ids", () => {
147
- const id = newBuiltinSessionId(new Date(2026, 6, 2, 12, 15, 30), "a1b2c3");
148
-
149
- expect(id).toBe("session-20260702-121530-a1b2c3");
150
- });
151
-
152
- it("estimates tokens from summary and messages", () => {
153
- expect(estimateBuiltinContextTokens("abc", [{ role: "user", content: "abcdef" }]))
154
- .toBeGreaterThanOrEqual(3);
155
- });
156
-
157
- it("serializes messages for summarization with stable role labels", () => {
158
- expect(serializeMessagesForSummary([
159
- { role: "user", content: "你好" },
160
- { role: "assistant", content: "收到" },
161
- ])).toContain("user");
162
- });
163
- });
1
+ import { mkdtemp, readFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+
5
+ import { describe, expect, it } from "vitest";
6
+
7
+ import {
8
+ BuiltinContextManager,
9
+ estimateBuiltinContextTokens,
10
+ listBuiltinContextSessions,
11
+ newBuiltinSessionId,
12
+ serializeMessagesForSummary,
13
+ } from "../builtin/context.ts";
14
+
15
+ describe("BuiltinContextManager", () => {
16
+ it("persists and restores summary, messages, and total message count", async () => {
17
+ const dir = await mkdtemp(join(tmpdir(), "chatccc-builtin-context-"));
18
+
19
+ const first = new BuiltinContextManager({
20
+ persist: true,
21
+ contextDir: dir,
22
+ sessionId: "persisted",
23
+ });
24
+ first.setSummary("## 用户目标\n- 保留这个摘要");
25
+ first.appendMessage({ role: "user", content: "第一轮" });
26
+ first.appendMessage({ role: "assistant", content: "第一轮回复" });
27
+ first.save();
28
+
29
+ const restored = new BuiltinContextManager({
30
+ persist: true,
31
+ contextDir: dir,
32
+ sessionId: "persisted",
33
+ });
34
+
35
+ expect(restored.summary).toContain("保留这个摘要");
36
+ expect(restored.messages).toEqual([
37
+ { role: "user", content: "第一轮" },
38
+ { role: "assistant", content: "第一轮回复" },
39
+ ]);
40
+ expect(restored.totalMessages).toBe(2);
41
+ });
42
+
43
+ it("selects only older messages for compaction and keeps recent messages raw", () => {
44
+ const context = new BuiltinContextManager({
45
+ compactAtTokens: 1,
46
+ keepRecentMessages: 2,
47
+ persist: false,
48
+ });
49
+ context.appendMessage({ role: "user", content: "旧用户消息" });
50
+ context.appendMessage({ role: "assistant", content: "旧助手回复" });
51
+ context.appendMessage({ role: "user", content: "近期用户消息" });
52
+ context.appendMessage({ role: "assistant", content: "近期助手回复" });
53
+
54
+ const plan = context.planCompaction();
55
+
56
+ expect(plan).not.toBeNull();
57
+ expect(plan?.oldMessages).toEqual([
58
+ { role: "user", content: "旧用户消息" },
59
+ { role: "assistant", content: "旧助手回复" },
60
+ ]);
61
+ expect(plan?.recentMessages).toEqual([
62
+ { role: "user", content: "近期用户消息" },
63
+ { role: "assistant", content: "近期助手回复" },
64
+ ]);
65
+ });
66
+
67
+ it("applies a compacted summary and builds model messages with summary plus recent raw turns", () => {
68
+ const context = new BuiltinContextManager({
69
+ compactAtTokens: 1,
70
+ keepRecentMessages: 1,
71
+ persist: false,
72
+ });
73
+ context.appendMessage({ role: "user", content: "old" });
74
+ context.appendMessage({ role: "assistant", content: "recent" });
75
+
76
+ const plan = context.planCompaction();
77
+ expect(plan).not.toBeNull();
78
+ context.applyCompaction("## 当前任务状态\n- 已压缩旧上下文", plan!);
79
+
80
+ expect(context.summary).toContain("已压缩旧上下文");
81
+ expect(context.messages).toEqual([{ role: "assistant", content: "recent" }]);
82
+ expect(context.buildModelMessages()).toEqual([
83
+ {
84
+ role: "user",
85
+ content: expect.stringContaining("较早对话摘要"),
86
+ },
87
+ { role: "assistant", content: "recent" },
88
+ ]);
89
+ });
90
+
91
+ it("reset clears memory and the persisted context file", async () => {
92
+ const dir = await mkdtemp(join(tmpdir(), "chatccc-builtin-context-reset-"));
93
+ const context = new BuiltinContextManager({
94
+ persist: true,
95
+ contextDir: dir,
96
+ sessionId: "reset",
97
+ });
98
+ context.setSummary("summary");
99
+ context.appendMessage({ role: "user", content: "hello" });
100
+ context.save();
101
+
102
+ context.reset();
103
+
104
+ expect(context.summary).toBe("");
105
+ expect(context.messages).toEqual([]);
106
+ expect(context.totalMessages).toBe(0);
107
+
108
+ const raw = await readFile(join(dir, "reset", "context.json"), "utf8");
109
+ const persisted = JSON.parse(raw) as { summary: string; messages: unknown[]; totalMessages: number };
110
+ expect(persisted.summary).toBe("");
111
+ expect(persisted.messages).toEqual([]);
112
+ expect(persisted.totalMessages).toBe(0);
113
+ });
114
+
115
+ it("persists cwd metadata and lists saved sessions newest first", async () => {
116
+ const dir = await mkdtemp(join(tmpdir(), "chatccc-builtin-context-list-"));
117
+
118
+ const first = new BuiltinContextManager({
119
+ persist: true,
120
+ contextDir: dir,
121
+ sessionId: "older",
122
+ cwd: "C:\\repo-a",
123
+ });
124
+ first.appendMessage({ role: "user", content: "old" });
125
+
126
+ const second = new BuiltinContextManager({
127
+ persist: true,
128
+ contextDir: dir,
129
+ sessionId: "newer",
130
+ cwd: "C:\\repo-b",
131
+ });
132
+ second.appendMessage({ role: "user", content: "new" });
133
+
134
+ const sessions = listBuiltinContextSessions(dir);
135
+
136
+ expect(sessions.map((s) => s.sessionId)).toEqual(["newer", "older"]);
137
+ expect(sessions[0]).toEqual(expect.objectContaining({
138
+ cwd: "C:\\repo-b",
139
+ totalMessages: 1,
140
+ hasSummary: false,
141
+ }));
142
+ });
143
+ });
144
+
145
+ describe("builtin context helpers", () => {
146
+ it("creates readable timestamp-based session ids", () => {
147
+ const id = newBuiltinSessionId(new Date(2026, 6, 2, 12, 15, 30), "a1b2c3");
148
+
149
+ expect(id).toBe("session-20260702-121530-a1b2c3");
150
+ });
151
+
152
+ it("estimates tokens from summary and messages", () => {
153
+ expect(estimateBuiltinContextTokens("abc", [{ role: "user", content: "abcdef" }]))
154
+ .toBeGreaterThanOrEqual(3);
155
+ });
156
+
157
+ it("serializes messages for summarization with stable role labels", () => {
158
+ expect(serializeMessagesForSummary([
159
+ { role: "user", content: "你好" },
160
+ { role: "assistant", content: "收到" },
161
+ ])).toContain("user");
162
+ });
163
+ });