chatccc 0.2.129 → 0.2.131
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/README.md +2 -1
- package/config.sample.json +2 -1
- package/package.json +1 -1
- package/src/__tests__/claude-adapter.test.ts +3 -3
- package/src/adapters/claude-adapter.ts +15 -8
- package/src/config.ts +7 -1
- package/src/session.ts +2 -0
- package/src/web-ui.ts +10 -4
package/README.md
CHANGED
|
@@ -205,7 +205,7 @@ ChatCCC 只负责把聊天消息转给本地 AI 工具,不捆绑这些 CLI。
|
|
|
205
205
|
|
|
206
206
|
#### Claude Code
|
|
207
207
|
|
|
208
|
-
本机完成 Claude CLI 登录后即可使用。同时支持官方和第三方 Anthropic 兼容 API:使用官方服务无需额外配置,使用第三方 API 则填写 `claude.apiKey` 和 `claude.baseUrl`。`claude.model`、`claude.subagentModel`、`claude.apiKey`、`claude.baseUrl`
|
|
208
|
+
本机完成 Claude CLI 登录后即可使用。同时支持官方和第三方 Anthropic 兼容 API:使用官方服务无需额外配置,使用第三方 API 则填写 `claude.apiKey` 和 `claude.baseUrl`。`claude.model`、`claude.subagentModel`、`claude.effort`、`claude.apiKey`、`claude.baseUrl` 均为选填;`claude.maxTurn` 控制每次对话的最大轮数(默认 25)。填写后会把对应配置传给 Claude CLI 子进程;留空则以 `~/.claude/settings.json` 为准。
|
|
209
209
|
|
|
210
210
|
#### Cursor Agent CLI
|
|
211
211
|
|
|
@@ -285,6 +285,7 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
285
285
|
| `cursor.path` / `codex.path` | CLI 可执行文件路径;留空时自动探测或使用 PATH |
|
|
286
286
|
| `claude.model` / `claude.subagentModel` / `claude.effort` | 选填;设置后传给 Claude CLI 子进程,留空以 `~/.claude/settings.json` 为准 |
|
|
287
287
|
| `claude.apiKey` / `claude.baseUrl` | 选填;设置后传给 Claude CLI 子进程,留空以 `~/.claude/settings.json` 为准 |
|
|
288
|
+
| `claude.maxTurn` | 选填;Claude CLI 最大对话轮数,默认 25,可在 Web UI 编辑 |
|
|
288
289
|
|
|
289
290
|
> 当前 ChatCCC 以 `bypassPermissions` 模式运行,会跳过 Agent 操作确认。请只在可信环境中使用。
|
|
290
291
|
|
package/config.sample.json
CHANGED
package/package.json
CHANGED
|
@@ -130,9 +130,9 @@ describe("normalizeSdkMessage", () => {
|
|
|
130
130
|
expect((result!.blocks[0] as any).content).toEqual(content);
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
-
it("skips text blocks in user messages
|
|
134
|
-
// user
|
|
135
|
-
//
|
|
133
|
+
it("skips text blocks in user messages", () => {
|
|
134
|
+
// user text is host input, not assistant output. If the CLI emits it for
|
|
135
|
+
// any reason, it should not appear in the final reply.
|
|
136
136
|
const result = normalizeSdkMessage({
|
|
137
137
|
type: "user",
|
|
138
138
|
message: {
|
|
@@ -127,6 +127,8 @@ export interface ClaudeAdapterOptions {
|
|
|
127
127
|
isEmpty: (value: string) => boolean;
|
|
128
128
|
/** 注入自定义 meta 持久化 store(测试用);未提供时使用全局默认实例。 */
|
|
129
129
|
metaStore?: ClaudeSessionMetaStore;
|
|
130
|
+
/** Claude CLI maxTurns 设置,默认 25 */
|
|
131
|
+
maxTurn?: number;
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
// ---------------------------------------------------------------------------
|
|
@@ -242,12 +244,12 @@ export function normalizeSdkMessage(msg: SdkMessageLike): UnifiedStreamMessage |
|
|
|
242
244
|
type: "search_result",
|
|
243
245
|
query: block.query ?? "",
|
|
244
246
|
});
|
|
245
|
-
} else if (block.type === "text" && block.text) {
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
if (msg.type === "user") continue;
|
|
249
|
-
blocks.push({ type: "text", text: block.text });
|
|
250
|
-
}
|
|
247
|
+
} else if (block.type === "text" && block.text) {
|
|
248
|
+
// user text is host input, not assistant output. Keep it out of the
|
|
249
|
+
// final reply if the CLI ever emits it.
|
|
250
|
+
if (msg.type === "user") continue;
|
|
251
|
+
blocks.push({ type: "text", text: block.text });
|
|
252
|
+
}
|
|
251
253
|
}
|
|
252
254
|
return { type: msg.type, blocks };
|
|
253
255
|
}
|
|
@@ -281,6 +283,7 @@ function buildCliArgs(
|
|
|
281
283
|
isEmpty: (value: string) => boolean,
|
|
282
284
|
mcpConfigJson: string | null,
|
|
283
285
|
extraArgs: string[],
|
|
286
|
+
maxTurn: number,
|
|
284
287
|
planMode?: boolean,
|
|
285
288
|
): string[] {
|
|
286
289
|
const args = [
|
|
@@ -289,7 +292,7 @@ function buildCliArgs(
|
|
|
289
292
|
"--verbose",
|
|
290
293
|
"--setting-sources", "user,project,local",
|
|
291
294
|
"--permission-mode", planMode ? "plan" : "bypassPermissions",
|
|
292
|
-
"--settings",
|
|
295
|
+
"--settings", `{"maxTurns":${maxTurn},"maxBudget":999999999}`,
|
|
293
296
|
];
|
|
294
297
|
if (!planMode) args.push("--dangerously-skip-permissions");
|
|
295
298
|
|
|
@@ -417,6 +420,7 @@ class ClaudeAdapter implements ToolAdapter {
|
|
|
417
420
|
private baseUrl: string | undefined;
|
|
418
421
|
private isEmpty: (value: string) => boolean;
|
|
419
422
|
private metaStore: ClaudeSessionMetaStore;
|
|
423
|
+
private maxTurn: number;
|
|
420
424
|
|
|
421
425
|
constructor(options: ClaudeAdapterOptions) {
|
|
422
426
|
this.model = options.model;
|
|
@@ -426,6 +430,7 @@ class ClaudeAdapter implements ToolAdapter {
|
|
|
426
430
|
this.baseUrl = options.baseUrl;
|
|
427
431
|
this.isEmpty = options.isEmpty;
|
|
428
432
|
this.metaStore = options.metaStore ?? defaultClaudeSessionMetaStore;
|
|
433
|
+
this.maxTurn = options.maxTurn ?? 25;
|
|
429
434
|
}
|
|
430
435
|
|
|
431
436
|
async createSession(cwd: string): Promise<CreateSessionResult> {
|
|
@@ -435,6 +440,7 @@ class ClaudeAdapter implements ToolAdapter {
|
|
|
435
440
|
const args = buildCliArgs(
|
|
436
441
|
this.model, this.effort, this.isEmpty, mcpConfigJson,
|
|
437
442
|
["ok"],
|
|
443
|
+
this.maxTurn,
|
|
438
444
|
);
|
|
439
445
|
|
|
440
446
|
const proc = spawnCli(args, cwd, env, false);
|
|
@@ -465,7 +471,8 @@ class ClaudeAdapter implements ToolAdapter {
|
|
|
465
471
|
const env = buildCliEnv(this.subagentModel, this.apiKey, this.baseUrl);
|
|
466
472
|
const args = buildCliArgs(
|
|
467
473
|
this.model, this.effort, this.isEmpty, mcpConfigJson,
|
|
468
|
-
["--resume", sessionId, "--input-format", "stream-json"
|
|
474
|
+
["--resume", sessionId, "--input-format", "stream-json"],
|
|
475
|
+
this.maxTurn,
|
|
469
476
|
options?.planMode,
|
|
470
477
|
);
|
|
471
478
|
|
package/src/config.ts
CHANGED
|
@@ -69,6 +69,8 @@ export interface ClaudeConfig {
|
|
|
69
69
|
apiKey: string;
|
|
70
70
|
/** Anthropic 兼容 API Base URL(选填,留空则使用默认端点) */
|
|
71
71
|
baseUrl: string;
|
|
72
|
+
/** Claude CLI maxTurns 设置,默认 25 */
|
|
73
|
+
maxTurn: number;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
export interface CursorConfig {
|
|
@@ -324,7 +326,7 @@ function loadConfig(): AppConfig {
|
|
|
324
326
|
port: 18080,
|
|
325
327
|
gitTimeoutSeconds: 180,
|
|
326
328
|
allowInterrupt: false,
|
|
327
|
-
claude: { enabled: false, defaultAgent: true, model: "", subagentModel: "", effort: "", apiKey: "", baseUrl: "" },
|
|
329
|
+
claude: { enabled: false, defaultAgent: true, model: "", subagentModel: "", effort: "", apiKey: "", baseUrl: "", maxTurn: 25 },
|
|
328
330
|
cursor: { enabled: false, defaultAgent: false, path: "", model: "claude-opus-4-7-max" },
|
|
329
331
|
codex: { enabled: false, defaultAgent: false, path: "", model: "", effort: "" },
|
|
330
332
|
};
|
|
@@ -468,6 +470,9 @@ function loadConfig(): AppConfig {
|
|
|
468
470
|
effort: normalizeOptionalConfigField(claude.effort, { label: "claude.effort" }),
|
|
469
471
|
apiKey: normalizeOptionalConfigField(claude.apiKey, { label: "claude.apiKey" }),
|
|
470
472
|
baseUrl: normalizeOptionalConfigField(claude.baseUrl, { label: "claude.baseUrl" }),
|
|
473
|
+
maxTurn: typeof (claude as Record<string, unknown>).maxTurn === "number"
|
|
474
|
+
? (claude as Record<string, unknown>).maxTurn as number
|
|
475
|
+
: 25,
|
|
471
476
|
},
|
|
472
477
|
cursor: {
|
|
473
478
|
enabled: cursorEnabled,
|
|
@@ -521,6 +526,7 @@ export const LOCAL_RELAY_URL = `ws://127.0.0.1:${CHATCCC_PORT}`;
|
|
|
521
526
|
export let CLAUDE_MODEL = config.claude.model;
|
|
522
527
|
export let CLAUDE_SUBAGENT_MODEL = config.claude.subagentModel;
|
|
523
528
|
export let CLAUDE_EFFORT = config.claude.effort;
|
|
529
|
+
export let CLAUDE_MAX_TURN = config.claude.maxTurn;
|
|
524
530
|
export let CLAUDE_API_KEY = config.claude.apiKey;
|
|
525
531
|
export let CLAUDE_BASE_URL = config.claude.baseUrl;
|
|
526
532
|
|
package/src/session.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
CLAUDE_API_KEY,
|
|
6
6
|
CLAUDE_BASE_URL,
|
|
7
7
|
CLAUDE_EFFORT,
|
|
8
|
+
CLAUDE_MAX_TURN,
|
|
8
9
|
CLAUDE_MODEL,
|
|
9
10
|
CLAUDE_SUBAGENT_MODEL,
|
|
10
11
|
CHATCCC_PORT,
|
|
@@ -367,6 +368,7 @@ export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter
|
|
|
367
368
|
apiKey: CLAUDE_API_KEY,
|
|
368
369
|
baseUrl: CLAUDE_BASE_URL,
|
|
369
370
|
isEmpty: isAnthropicConfigEmpty,
|
|
371
|
+
maxTurn: CLAUDE_MAX_TURN,
|
|
370
372
|
});
|
|
371
373
|
}
|
|
372
374
|
adapterCache.set(cacheKey, adapter);
|
package/src/web-ui.ts
CHANGED
|
@@ -31,7 +31,7 @@ interface AppConfig {
|
|
|
31
31
|
platforms?: { feishu?: { enabled?: boolean }; ilink?: { enabled?: boolean } };
|
|
32
32
|
port?: number;
|
|
33
33
|
gitTimeoutSeconds?: number;
|
|
34
|
-
claude?: { enabled?: boolean; defaultAgent?: boolean; model?: string; subagentModel?: string; effort?: string; apiKey?: string; baseUrl?: string };
|
|
34
|
+
claude?: { enabled?: boolean; defaultAgent?: boolean; model?: string; subagentModel?: string; effort?: string; apiKey?: string; baseUrl?: string; maxTurn?: number };
|
|
35
35
|
// `command` 是已废弃的旧字段名,保留只读以兼容升级前的 config.json
|
|
36
36
|
cursor?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string };
|
|
37
37
|
codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; effort?: string };
|
|
@@ -319,6 +319,9 @@ export function unflattenConfig(flat: Record<string, unknown>): Record<string, u
|
|
|
319
319
|
} else if (key === "CHATCCC_ANTHROPIC_BASE_URL") {
|
|
320
320
|
result.claude = result.claude || {};
|
|
321
321
|
(result.claude as Record<string, unknown>).baseUrl = val;
|
|
322
|
+
} else if (key === "CHATCCC_ANTHROPIC_MAX_TURN") {
|
|
323
|
+
result.claude = result.claude || {};
|
|
324
|
+
(result.claude as Record<string, unknown>).maxTurn = parseInt(val as string, 10) || 25;
|
|
322
325
|
} else if (key === "CHATCCC_CLAUDE_ENABLED") {
|
|
323
326
|
result.claude = result.claude || {};
|
|
324
327
|
(result.claude as Record<string, unknown>).enabled = val === true || val === "true";
|
|
@@ -785,6 +788,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
785
788
|
<div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-ANTHROPIC_EFFORT">-</span></div>
|
|
786
789
|
<div class="config-row"><span class="key">API Key</span><span class="val" id="cfg-ANTHROPIC_API_KEY">-</span></div>
|
|
787
790
|
<div class="config-row"><span class="key">Base URL</span><span class="val" id="cfg-ANTHROPIC_BASE_URL">-</span></div>
|
|
791
|
+
<div class="config-row"><span class="key">Max Turns</span><span class="val" id="cfg-ANTHROPIC_MAX_TURN">-</span></div>
|
|
788
792
|
<label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-claude" onchange="setDashboardDefaultAgent('claude', this.checked)"> 设为默认 Agent</label>
|
|
789
793
|
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('claude')">编辑</button>
|
|
790
794
|
</div>
|
|
@@ -851,7 +855,7 @@ let state = {
|
|
|
851
855
|
var step2InputBound = false;
|
|
852
856
|
|
|
853
857
|
const AGENT_FIELDS = {
|
|
854
|
-
claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL'],
|
|
858
|
+
claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL','CHATCCC_ANTHROPIC_MAX_TURN'],
|
|
855
859
|
cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL'],
|
|
856
860
|
codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_EFFORT']
|
|
857
861
|
};
|
|
@@ -1087,7 +1091,7 @@ function isAgentEnabled(node, keys) {
|
|
|
1087
1091
|
return false;
|
|
1088
1092
|
}
|
|
1089
1093
|
|
|
1090
|
-
var CLAUDE_FALLBACK_KEYS = ['model','subagentModel','effort'];
|
|
1094
|
+
var CLAUDE_FALLBACK_KEYS = ['model','subagentModel','effort','maxTurn'];
|
|
1091
1095
|
var CURSOR_FALLBACK_KEYS = ['path','command','model'];
|
|
1092
1096
|
var CODEX_FALLBACK_KEYS = ['path','command','model','effort'];
|
|
1093
1097
|
|
|
@@ -1399,6 +1403,7 @@ function updateDashboardUI() {
|
|
|
1399
1403
|
document.getElementById('cfg-ANTHROPIC_EFFORT').textContent = (c.claude && c.claude.effort) || '(留空)';
|
|
1400
1404
|
document.getElementById('cfg-ANTHROPIC_API_KEY').textContent = (c.claude && c.claude.apiKey) ? '***已设置***' : '(留空)';
|
|
1401
1405
|
document.getElementById('cfg-ANTHROPIC_BASE_URL').textContent = (c.claude && c.claude.baseUrl) || '(留空)';
|
|
1406
|
+
document.getElementById('cfg-ANTHROPIC_MAX_TURN').textContent = (c.claude && c.claude.maxTurn != null) ? String(c.claude.maxTurn) : '25';
|
|
1402
1407
|
document.getElementById('cfg-CURSOR_PATH').textContent = (c.cursor && (c.cursor.path || c.cursor.command)) || '-';
|
|
1403
1408
|
document.getElementById('cfg-CURSOR_MODEL').textContent = (c.cursor && c.cursor.model) || '(留空)';
|
|
1404
1409
|
document.getElementById('cfg-CODEX_PATH').textContent = (c.codex && (c.codex.path || c.codex.command)) || 'codex';
|
|
@@ -1459,7 +1464,7 @@ function editSection(section) {
|
|
|
1459
1464
|
var labelMap = {
|
|
1460
1465
|
'CHATCCC_APP_ID': 'App ID', 'CHATCCC_APP_SECRET': 'App Secret',
|
|
1461
1466
|
'CHATCCC_ANTHROPIC_MODEL': '模型', 'CHATCCC_ANTHROPIC_SUBAGENT_MODEL': 'Subagent 模型', 'CHATCCC_ANTHROPIC_EFFORT': 'Effort',
|
|
1462
|
-
'CHATCCC_ANTHROPIC_API_KEY': 'API Key', 'CHATCCC_ANTHROPIC_BASE_URL': 'Base URL',
|
|
1467
|
+
'CHATCCC_ANTHROPIC_API_KEY': 'API Key', 'CHATCCC_ANTHROPIC_BASE_URL': 'Base URL', 'CHATCCC_ANTHROPIC_MAX_TURN': 'Max Turns',
|
|
1463
1468
|
'CHATCCC_CURSOR_PATH': 'CLI 路径', 'CHATCCC_CURSOR_MODEL': '模型',
|
|
1464
1469
|
'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_EFFORT': 'Effort'
|
|
1465
1470
|
};
|
|
@@ -1477,6 +1482,7 @@ function editSection(section) {
|
|
|
1477
1482
|
else if (key === 'CHATCCC_ANTHROPIC_EFFORT') val = state.config.claude.effort || '';
|
|
1478
1483
|
else if (key === 'CHATCCC_ANTHROPIC_API_KEY') val = state.config.claude.apiKey || '';
|
|
1479
1484
|
else if (key === 'CHATCCC_ANTHROPIC_BASE_URL') val = state.config.claude.baseUrl || '';
|
|
1485
|
+
else if (key === 'CHATCCC_ANTHROPIC_MAX_TURN') val = (state.config.claude.maxTurn != null) ? String(state.config.claude.maxTurn) : '25';
|
|
1480
1486
|
} else if (section === 'cursor' && state.config.cursor) {
|
|
1481
1487
|
if (key === 'CHATCCC_CURSOR_PATH') val = state.config.cursor.path || state.config.cursor.command || '';
|
|
1482
1488
|
else if (key === 'CHATCCC_CURSOR_MODEL') val = state.config.cursor.model || '';
|