chatccc 0.2.223 → 0.2.224

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.
@@ -3,14 +3,14 @@
3
3
  "appId": "",
4
4
  "appSecret": ""
5
5
  },
6
- "platforms": {
7
- "feishu": { "enabled": true, "platformType": "feishu" },
8
- "ilink": { "enabled": true, "reuseTokenOnStart": true }
9
- },
10
- "webUi": {
11
- "openOnStart": true
12
- },
13
- "chromeDevtools": {
6
+ "platforms": {
7
+ "feishu": { "enabled": true, "platformType": "feishu" },
8
+ "ilink": { "enabled": true, "reuseTokenOnStart": true }
9
+ },
10
+ "webUi": {
11
+ "openOnStart": true
12
+ },
13
+ "chromeDevtools": {
14
14
  "enabled": false,
15
15
  "port": 15166,
16
16
  "chromePath": ""
@@ -31,19 +31,19 @@
31
31
  "retentionDays": 7,
32
32
  "keepCompleted": false
33
33
  },
34
- "codex": {
35
- "enabled": false,
36
- "maxBytesPerTurn": 52428800,
37
- "retentionDays": 7,
38
- "keepCompleted": false
39
- },
40
- "ccc": {
41
- "enabled": false,
42
- "maxBytesPerTurn": 52428800,
43
- "retentionDays": 7,
44
- "keepCompleted": false
45
- }
46
- },
34
+ "codex": {
35
+ "enabled": false,
36
+ "maxBytesPerTurn": 52428800,
37
+ "retentionDays": 7,
38
+ "keepCompleted": false
39
+ },
40
+ "ccc": {
41
+ "enabled": false,
42
+ "maxBytesPerTurn": 52428800,
43
+ "retentionDays": 7,
44
+ "keepCompleted": false
45
+ }
46
+ },
47
47
  "claude": {
48
48
  "enabled": false,
49
49
  "defaultAgent": true,
@@ -63,21 +63,22 @@
63
63
  "avatarBatteryMode": "apiPercent",
64
64
  "onDemandMonthlyBudget": 1000
65
65
  },
66
- "codex": {
67
- "enabled": false,
68
- "defaultAgent": false,
69
- "path": "",
70
- "model": "",
71
- "alternativeModel": "",
72
- "effort": "",
73
- "fastMode": false
74
- },
75
- "ccc": {
76
- "enabled": false,
77
- "defaultAgent": false,
78
- "DEEPSEEK_API_KEY": "",
79
- "DEEPSEEK_BASE_URL": "https://api.deepseek.com/v1",
80
- "model": "deepseek-v4-pro",
81
- "alternativeModel": ""
82
- }
83
- }
66
+ "codex": {
67
+ "enabled": false,
68
+ "defaultAgent": false,
69
+ "path": "",
70
+ "model": "",
71
+ "alternativeModel": "",
72
+ "effort": "",
73
+ "fastMode": false
74
+ },
75
+ "ccc": {
76
+ "enabled": false,
77
+ "defaultAgent": false,
78
+ "DEEPSEEK_API_KEY": "",
79
+ "DEEPSEEK_BASE_URL": "https://api.deepseek.com/v1",
80
+ "model": "deepseek-v4-pro",
81
+ "alternativeModel": "",
82
+ "effort": ""
83
+ }
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.223",
3
+ "version": "0.2.224",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -315,3 +315,36 @@ describe("ChatSession context management", () => {
315
315
  expect(rawLogCloseMock).toHaveBeenCalledWith({ keep: true });
316
316
  });
317
317
  });
318
+
319
+ describe("ChatSession effort passthrough", () => {
320
+ it("passes reasoningEffort via providerOptions when effort is configured", async () => {
321
+ const { ChatSession } = await import("../builtin/index.ts");
322
+ const dir = await mkdtemp(join(tmpdir(), "chatccc-session-effort-high-"));
323
+ streamTextMock.mockReturnValueOnce({ textStream: textStream("done") });
324
+
325
+ const session = new ChatSession(
326
+ { apiKey: "sk-test", effort: "high" },
327
+ { cwd: dir, sessionId: "effort-high" },
328
+ );
329
+ await collect(session.chat("hi"));
330
+
331
+ expect(streamTextMock).toHaveBeenLastCalledWith(expect.objectContaining({
332
+ providerOptions: { deepseek: { reasoningEffort: "high" } },
333
+ }));
334
+ });
335
+
336
+ it("omits providerOptions when effort is not set", async () => {
337
+ const { ChatSession } = await import("../builtin/index.ts");
338
+ const dir = await mkdtemp(join(tmpdir(), "chatccc-session-effort-none-"));
339
+ streamTextMock.mockReturnValueOnce({ textStream: textStream("done") });
340
+
341
+ const session = new ChatSession(
342
+ { apiKey: "sk-test" },
343
+ { cwd: dir, sessionId: "effort-none" },
344
+ );
345
+ await collect(session.chat("hi"));
346
+
347
+ const lastCall = streamTextMock.mock.calls.at(-1)?.[0] as Record<string, unknown>;
348
+ expect(lastCall.providerOptions).toBeUndefined();
349
+ });
350
+ });
@@ -24,6 +24,7 @@ describe("builtin ChatSession config", () => {
24
24
  DEEPSEEK_BASE_URL: "https://api.deepseek.com/v1",
25
25
  model: "deepseek-v4-pro",
26
26
  alternativeModel: "",
27
+ effort: "",
27
28
  };
28
29
  process.env.DEEPSEEK_API_KEY = "sk-env-should-not-be-used";
29
30
 
@@ -113,4 +113,24 @@ describe("createCccAdapter", () => {
113
113
  { type: "assistant", blocks: [], isFinalResponse: true },
114
114
  ]);
115
115
  });
116
+
117
+ it("passes effort into ChatSession so streamText receives reasoningEffort", async () => {
118
+ const { createCccAdapter } = await import("../adapters/ccc-adapter.ts");
119
+ const contextDir = await mkdtemp(join(tmpdir(), "chatccc-ccc-adapter-effort-"));
120
+ const adapter = createCccAdapter({
121
+ apiKey: "sk-test",
122
+ contextDir,
123
+ effort: "xhigh",
124
+ });
125
+ const { sessionId } = await adapter.createSession("F:\\repo");
126
+ streamTextMock.mockReturnValueOnce({ textStream: textStream("ok") });
127
+
128
+ for await (const _message of adapter.prompt(sessionId, "hi", "F:\\repo")) {
129
+ // drain
130
+ }
131
+
132
+ expect(streamTextMock).toHaveBeenLastCalledWith(expect.objectContaining({
133
+ providerOptions: { deepseek: { reasoningEffort: "xhigh" } },
134
+ }));
135
+ });
116
136
  });
@@ -38,20 +38,20 @@ import {
38
38
  // 原地切换复用同一 server,重新读端口只会引入混乱)
39
39
  // ---------------------------------------------------------------------------
40
40
 
41
- const baseAppConfig: AppConfig = {
42
- feishu: { appId: "INITIAL_APP", appSecret: "INITIAL_SECRET" },
43
- platforms: { feishu: { enabled: true }, ilink: { enabled: true } },
44
- webUi: { openOnStart: true },
45
- chromeDevtools: { enabled: false, port: 15166, chromePath: "" },
41
+ const baseAppConfig: AppConfig = {
42
+ feishu: { appId: "INITIAL_APP", appSecret: "INITIAL_SECRET" },
43
+ platforms: { feishu: { enabled: true }, ilink: { enabled: true } },
44
+ webUi: { openOnStart: true },
45
+ chromeDevtools: { enabled: false, port: 15166, chromePath: "" },
46
46
  port: 18080,
47
47
  gitTimeoutSeconds: 180,
48
48
  allowInterrupt: false,
49
- rawStreamLogs: {
50
- claude: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
51
- cursor: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
52
- codex: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
53
- ccc: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
54
- },
49
+ rawStreamLogs: {
50
+ claude: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
51
+ cursor: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
52
+ codex: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
53
+ ccc: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
54
+ },
55
55
  claude: {
56
56
  enabled: true,
57
57
  defaultAgent: true,
@@ -70,10 +70,10 @@ const baseAppConfig: AppConfig = {
70
70
  alternativeModel: "initial-cursor-alt-model",
71
71
  avatarBatteryMode: "apiPercent",
72
72
  onDemandMonthlyBudget: 1000,
73
- },
74
- codex: { enabled: true, defaultAgent: false, path: "/initial/codex", model: "initial-codex-model", alternativeModel: "initial-codex-alt-model", effort: "initial-codex-effort", fastMode: false },
75
- ccc: { enabled: true, defaultAgent: false, DEEPSEEK_API_KEY: "initial-ccc-key", DEEPSEEK_BASE_URL: "https://initial.deepseek.test/v1", model: "initial-ccc-model", alternativeModel: "initial-ccc-alt-model" },
76
- };
73
+ },
74
+ codex: { enabled: true, defaultAgent: false, path: "/initial/codex", model: "initial-codex-model", alternativeModel: "initial-codex-alt-model", effort: "initial-codex-effort", fastMode: false },
75
+ ccc: { enabled: true, defaultAgent: false, DEEPSEEK_API_KEY: "initial-ccc-key", DEEPSEEK_BASE_URL: "https://initial.deepseek.test/v1", model: "initial-ccc-model", alternativeModel: "initial-ccc-alt-model", effort: "initial-ccc-effort" },
76
+ };
77
77
 
78
78
  // 把 module 状态抢救快照:每个 it 跑前重置回这个状态,避免污染相邻测试。
79
79
  // 不直接用启动时的 config 引用做 snapshot——它可能已经被前一个 it 改写。
@@ -177,18 +177,18 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
177
177
  expect(CURSOR_AGENT_COMMAND).toBe("C:/custom/cursor.exe");
178
178
  });
179
179
 
180
- it("/model 候选列表包含 Cursor/Codex/CCC 的单个备选模型并保持主模型优先", () => {
180
+ it("/model 候选列表包含 Cursor/Codex/CCC 的单个备选模型并保持主模型优先", () => {
181
181
  const cfg = structuredClone(baseAppConfig);
182
182
  cfg.cursor.model = "cursor-main";
183
183
  cfg.cursor.alternativeModel = "cursor-alt";
184
184
  cfg.codex.model = "codex-main";
185
- cfg.codex.alternativeModel = "codex-main";
186
- cfg.ccc.model = "ccc-main";
187
- cfg.ccc.alternativeModel = "ccc-alt";
185
+ cfg.codex.alternativeModel = "codex-main";
186
+ cfg.ccc.model = "ccc-main";
187
+ cfg.ccc.alternativeModel = "ccc-alt";
188
188
 
189
189
  expect(getAllModelsForTool("cursor", cfg)).toEqual(["cursor-main", "cursor-alt"]);
190
- expect(getAllModelsForTool("codex", cfg)).toEqual(["codex-main"]);
191
- expect(getAllModelsForTool("ccc", cfg)).toEqual(["ccc-main", "ccc-alt"]);
190
+ expect(getAllModelsForTool("codex", cfg)).toEqual(["codex-main"]);
191
+ expect(getAllModelsForTool("ccc", cfg)).toEqual(["ccc-main", "ccc-alt"]);
192
192
  });
193
193
 
194
194
  it("不修改 CHATCCC_PORT(端口在 setup 切换时必须保持不变)", () => {
@@ -210,18 +210,18 @@ describe("applyLoadedConfig — config 对象引用契约", () => {
210
210
  applyLoadedConfig({
211
211
  ...structuredClone(baseAppConfig),
212
212
  feishu: { appId: "REF_TEST_APP", appSecret: "REF_TEST_SECRET" },
213
- codex: { enabled: true, defaultAgent: false, path: "/refresh/codex", model: "fresh-model", alternativeModel: "", effort: "low", fastMode: true },
213
+ codex: { enabled: true, defaultAgent: false, path: "/refresh/codex", model: "fresh-model", alternativeModel: "", effort: "low", fastMode: true },
214
214
  });
215
215
 
216
216
  // 必须是同一个引用:codex-adapter 等下游模块"直接 import config",
217
217
  // 替换引用会破坏它们对 config.codex.* 的访问。
218
218
  expect(config).toBe(refBefore);
219
- expect(config.feishu.appId).toBe("REF_TEST_APP");
220
- expect(config.codex.path).toBe("/refresh/codex");
221
- expect(config.codex.fastMode).toBe(true);
222
- });
219
+ expect(config.feishu.appId).toBe("REF_TEST_APP");
220
+ expect(config.codex.path).toBe("/refresh/codex");
221
+ expect(config.codex.fastMode).toBe(true);
222
+ });
223
223
 
224
- it("刷新 chromeDevtools 配置但保持 config 引用", () => {
224
+ it("刷新 chromeDevtools 配置但保持 config 引用", () => {
225
225
  const refBefore = config;
226
226
 
227
227
  applyLoadedConfig({
@@ -231,19 +231,19 @@ describe("applyLoadedConfig — config 对象引用契约", () => {
231
231
 
232
232
  expect(config).toBe(refBefore);
233
233
  expect(config.chromeDevtools).toEqual({ enabled: true, port: 15166, chromePath: "C:/Chrome/chrome.exe" });
234
- });
235
-
236
- it("刷新 webUi 启动开关但保持 config 引用", () => {
237
- const refBefore = config;
238
-
239
- applyLoadedConfig({
240
- ...structuredClone(baseAppConfig),
241
- webUi: { openOnStart: false },
242
- });
243
-
244
- expect(config).toBe(refBefore);
245
- expect(config.webUi).toEqual({ openOnStart: false });
246
- });
234
+ });
235
+
236
+ it("刷新 webUi 启动开关但保持 config 引用", () => {
237
+ const refBefore = config;
238
+
239
+ applyLoadedConfig({
240
+ ...structuredClone(baseAppConfig),
241
+ webUi: { openOnStart: false },
242
+ });
243
+
244
+ expect(config).toBe(refBefore);
245
+ expect(config.webUi).toEqual({ openOnStart: false });
246
+ });
247
247
 
248
248
  it("空 feishu 凭证也能正确刷入(向导回滚到空的反向场景)", () => {
249
249
  applyLoadedConfig({
@@ -265,20 +265,20 @@ describe("resolveDefaultAgentTool", () => {
265
265
  expect(resolveDefaultAgentTool(cfg)).toBe("cursor");
266
266
  });
267
267
 
268
- it("defaultAgent 指向未启用 Agent 时回退到第一个已启用 Agent", () => {
268
+ it("defaultAgent 指向未启用 Agent 时回退到第一个已启用 Agent", () => {
269
269
  const cfg = structuredClone(baseAppConfig);
270
270
  cfg.claude.enabled = false;
271
271
  cfg.claude.defaultAgent = true;
272
272
  cfg.cursor.defaultAgent = false;
273
273
 
274
- expect(resolveDefaultAgentTool(cfg)).toBe("cursor");
275
- });
276
-
277
- it("支持把已启用的 CCC Agent 设为默认 Agent", () => {
278
- const cfg = structuredClone(baseAppConfig);
279
- for (const tool of ["claude", "cursor", "codex"] as const) cfg[tool].defaultAgent = false;
280
- cfg.ccc.defaultAgent = true;
281
-
282
- expect(resolveDefaultAgentTool(cfg)).toBe("ccc");
283
- });
284
- });
274
+ expect(resolveDefaultAgentTool(cfg)).toBe("cursor");
275
+ });
276
+
277
+ it("支持把已启用的 CCC Agent 设为默认 Agent", () => {
278
+ const cfg = structuredClone(baseAppConfig);
279
+ for (const tool of ["claude", "cursor", "codex"] as const) cfg[tool].defaultAgent = false;
280
+ cfg.ccc.defaultAgent = true;
281
+
282
+ expect(resolveDefaultAgentTool(cfg)).toBe("ccc");
283
+ });
284
+ });
@@ -28,34 +28,35 @@ describe("config.sample.json", () => {
28
28
  expect(sample.claude?.subagentModel).toBe("");
29
29
  });
30
30
 
31
- it("leaves alternative models empty and Codex Fast mode off by default", () => {
32
- const configSamplePath = join(process.cwd(), "config.sample.json");
33
- const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
34
- cursor?: { alternativeModel?: unknown };
35
- codex?: { alternativeModel?: unknown; fastMode?: unknown };
36
- ccc?: { alternativeModel?: unknown };
37
- };
38
-
39
- expect(sample.cursor?.alternativeModel).toBe("");
40
- expect(sample.codex?.alternativeModel).toBe("");
41
- expect(sample.ccc?.alternativeModel).toBe("");
42
- expect(sample.codex?.fastMode).toBe(false);
43
- });
44
-
45
- it("sets ccc agent DeepSeek defaults in the sample config", () => {
46
- const configSamplePath = join(process.cwd(), "config.sample.json");
47
- const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
48
- ccc?: { enabled?: unknown; defaultAgent?: unknown; DEEPSEEK_API_KEY?: unknown; DEEPSEEK_BASE_URL?: unknown; model?: unknown };
49
- };
50
-
51
- expect(sample.ccc?.enabled).toBe(false);
52
- expect(sample.ccc?.defaultAgent).toBe(false);
53
- expect(sample.ccc?.DEEPSEEK_API_KEY).toBe("");
54
- expect(sample.ccc?.DEEPSEEK_BASE_URL).toBe("https://api.deepseek.com/v1");
55
- expect(sample.ccc?.model).toBe("deepseek-v4-pro");
56
- });
57
-
58
- it("keeps Chrome CDP guard disabled by default with port 15166", () => {
31
+ it("leaves alternative models empty and Codex Fast mode off by default", () => {
32
+ const configSamplePath = join(process.cwd(), "config.sample.json");
33
+ const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
34
+ cursor?: { alternativeModel?: unknown };
35
+ codex?: { alternativeModel?: unknown; fastMode?: unknown };
36
+ ccc?: { alternativeModel?: unknown };
37
+ };
38
+
39
+ expect(sample.cursor?.alternativeModel).toBe("");
40
+ expect(sample.codex?.alternativeModel).toBe("");
41
+ expect(sample.ccc?.alternativeModel).toBe("");
42
+ expect(sample.codex?.fastMode).toBe(false);
43
+ });
44
+
45
+ it("sets ccc agent DeepSeek defaults in the sample config", () => {
46
+ const configSamplePath = join(process.cwd(), "config.sample.json");
47
+ const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
48
+ ccc?: { enabled?: unknown; defaultAgent?: unknown; DEEPSEEK_API_KEY?: unknown; DEEPSEEK_BASE_URL?: unknown; model?: unknown; effort?: unknown };
49
+ };
50
+
51
+ expect(sample.ccc?.enabled).toBe(false);
52
+ expect(sample.ccc?.defaultAgent).toBe(false);
53
+ expect(sample.ccc?.DEEPSEEK_API_KEY).toBe("");
54
+ expect(sample.ccc?.DEEPSEEK_BASE_URL).toBe("https://api.deepseek.com/v1");
55
+ expect(sample.ccc?.model).toBe("deepseek-v4-pro");
56
+ expect(sample.ccc?.effort).toBe("");
57
+ });
58
+
59
+ it("keeps Chrome CDP guard disabled by default with port 15166", () => {
59
60
  const configSamplePath = join(process.cwd(), "config.sample.json");
60
61
  const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
61
62
  chromeDevtools?: { enabled?: unknown; port?: unknown; chromePath?: unknown };
@@ -64,16 +65,16 @@ describe("config.sample.json", () => {
64
65
  expect(sample.chromeDevtools?.enabled).toBe(false);
65
66
  expect(sample.chromeDevtools?.port).toBe(15166);
66
67
  expect(sample.chromeDevtools?.chromePath).toBe("");
67
- });
68
-
69
- it("opens the Web UI on direct startup by default", () => {
70
- const configSamplePath = join(process.cwd(), "config.sample.json");
71
- const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
72
- webUi?: { openOnStart?: unknown };
73
- };
74
-
75
- expect(sample.webUi?.openOnStart).toBe(true);
76
- });
68
+ });
69
+
70
+ it("opens the Web UI on direct startup by default", () => {
71
+ const configSamplePath = join(process.cwd(), "config.sample.json");
72
+ const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
73
+ webUi?: { openOnStart?: unknown };
74
+ };
75
+
76
+ expect(sample.webUi?.openOnStart).toBe(true);
77
+ });
77
78
 
78
79
  it("keeps raw stream logs disabled by default for every agent", () => {
79
80
  const configSamplePath = join(process.cwd(), "config.sample.json");
@@ -86,11 +87,11 @@ describe("config.sample.json", () => {
86
87
  }>;
87
88
  };
88
89
 
89
- for (const tool of ["claude", "cursor", "codex", "ccc"]) {
90
+ for (const tool of ["claude", "cursor", "codex", "ccc"]) {
90
91
  expect(sample.rawStreamLogs?.[tool]?.enabled).toBe(false);
91
92
  expect(sample.rawStreamLogs?.[tool]?.maxBytesPerTurn).toBe(52_428_800);
92
93
  expect(sample.rawStreamLogs?.[tool]?.retentionDays).toBe(7);
93
94
  expect(sample.rawStreamLogs?.[tool]?.keepCompleted).toBe(false);
94
95
  }
95
96
  });
96
- });
97
+ });