chatccc 0.2.133 → 0.2.134
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 +4 -4
- package/agent-prompts/claude_specific.md +2 -2
- package/package.json +2 -2
- package/src/__tests__/claude-adapter.test.ts +48 -0
- package/src/__tests__/config-reload.test.ts +9 -7
- package/src/adapters/claude-adapter.ts +461 -534
- package/src/adapters/claude-session-meta-store.ts +3 -3
- package/src/config.ts +2 -2
- package/src/web-ui.ts +1 -1
package/README.md
CHANGED
|
@@ -205,7 +205,7 @@ ChatCCC 只负责把聊天消息转给本地 AI 工具,不捆绑这些 CLI。
|
|
|
205
205
|
|
|
206
206
|
#### Claude Code
|
|
207
207
|
|
|
208
|
-
本机完成 Claude
|
|
208
|
+
本机完成 Claude Code 登录后即可使用。ChatCCC 通过 Anthropic Claude Agent SDK 调用 Claude Code 能力,同时支持官方和第三方 Anthropic 兼容 API:使用官方服务无需额外配置,使用第三方 API 则填写 `claude.apiKey` 和 `claude.baseUrl`。`claude.model`、`claude.subagentModel`、`claude.effort`、`claude.apiKey`、`claude.baseUrl` 均为选填;`claude.maxTurn` 控制每次对话的最大轮数(默认 25)。填写后会把对应配置传给 Claude Agent SDK;留空则以 `~/.claude/settings.json` 为准。
|
|
209
209
|
|
|
210
210
|
#### Cursor Agent CLI
|
|
211
211
|
|
|
@@ -283,9 +283,9 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
283
283
|
| `*.enabled` | 是否启用对应 AI Agent |
|
|
284
284
|
| `*.defaultAgent` | `/new` 未指定 Agent 时使用哪个工具 |
|
|
285
285
|
| `cursor.path` / `codex.path` | CLI 可执行文件路径;留空时自动探测或使用 PATH |
|
|
286
|
-
| `claude.model` / `claude.subagentModel` / `claude.effort` | 选填;设置后传给 Claude
|
|
287
|
-
| `claude.apiKey` / `claude.baseUrl` | 选填;设置后传给 Claude
|
|
288
|
-
| `claude.maxTurn` | 选填;Claude
|
|
286
|
+
| `claude.model` / `claude.subagentModel` / `claude.effort` | 选填;设置后传给 Claude Agent SDK,留空以 `~/.claude/settings.json` 为准 |
|
|
287
|
+
| `claude.apiKey` / `claude.baseUrl` | 选填;设置后传给 Claude Agent SDK,留空以 `~/.claude/settings.json` 为准 |
|
|
288
|
+
| `claude.maxTurn` | 选填;Claude 最大对话轮数,默认 25,可在 Web UI 编辑 |
|
|
289
289
|
|
|
290
290
|
> 当前 ChatCCC 以 `bypassPermissions` 模式运行,会跳过 Agent 操作确认。请只在可信环境中使用。
|
|
291
291
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Claude-Specific Injection Prompt
|
|
2
2
|
|
|
3
|
-
Use this prompt as the Claude
|
|
3
|
+
Use this prompt as the Claude Agent SDK injection prompt for this project.
|
|
4
4
|
|
|
5
5
|
Project workspace:
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ f:\users\weizhangjian\feishuclauderprivate
|
|
|
10
10
|
|
|
11
11
|
## Repeated Successful Command Guard
|
|
12
12
|
|
|
13
|
-
When working in this project through Claude
|
|
13
|
+
When working in this project through Claude Agent SDK, repeated successful shell commands are a completion signal, not a reason to keep using tools.
|
|
14
14
|
|
|
15
15
|
A shell command is considered the same command when all of these are true:
|
|
16
16
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chatccc",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.134",
|
|
4
4
|
"description": "Feishu bot bridge for Claude Code",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
44
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
44
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.153",
|
|
45
45
|
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
46
46
|
"@openilink/openilink-sdk-node": "^0.6.0",
|
|
47
47
|
"ai": "^6.0.184",
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
normalizeSdkMessage,
|
|
4
4
|
createClaudeAdapter,
|
|
5
5
|
buildClaudePromptText,
|
|
6
|
+
buildSdkEnv,
|
|
6
7
|
} from "../adapters/claude-adapter.ts";
|
|
7
8
|
import type { UnifiedStreamMessage } from "../adapters/adapter-interface.ts";
|
|
8
9
|
import type {
|
|
@@ -463,3 +464,50 @@ describe("buildClaudePromptText", () => {
|
|
|
463
464
|
expect(buildClaudePromptText("hello", null)).toBe("hello");
|
|
464
465
|
});
|
|
465
466
|
});
|
|
467
|
+
|
|
468
|
+
describe("buildSdkEnv", () => {
|
|
469
|
+
it("returns undefined when no SDK env override is configured", () => {
|
|
470
|
+
expect(buildSdkEnv("", " ", undefined)).toBeUndefined();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("sets requested SDK env overrides and removes conflicting Claude auth/model vars", () => {
|
|
474
|
+
const original = {
|
|
475
|
+
ANTHROPIC_AUTH_TOKEN: process.env.ANTHROPIC_AUTH_TOKEN,
|
|
476
|
+
CLAUDE_CODE_OAUTH_TOKEN: process.env.CLAUDE_CODE_OAUTH_TOKEN,
|
|
477
|
+
ANTHROPIC_MODEL: process.env.ANTHROPIC_MODEL,
|
|
478
|
+
CLAUDE_CODE_EFFORT_LEVEL: process.env.CLAUDE_CODE_EFFORT_LEVEL,
|
|
479
|
+
CLAUDE_CODE_SUBAGENT_MODEL: process.env.CLAUDE_CODE_SUBAGENT_MODEL,
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
process.env.ANTHROPIC_AUTH_TOKEN = "token";
|
|
483
|
+
process.env.CLAUDE_CODE_OAUTH_TOKEN = "oauth";
|
|
484
|
+
process.env.ANTHROPIC_MODEL = "old-model";
|
|
485
|
+
process.env.CLAUDE_CODE_EFFORT_LEVEL = "old-effort";
|
|
486
|
+
process.env.CLAUDE_CODE_SUBAGENT_MODEL = "old-subagent";
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
const env = buildSdkEnv(
|
|
490
|
+
" claude-haiku-4-5-20251001 ",
|
|
491
|
+
" sk-test ",
|
|
492
|
+
" https://api.example.com ",
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
expect(env).toBeDefined();
|
|
496
|
+
expect(env!.ANTHROPIC_AUTH_TOKEN).toBeUndefined();
|
|
497
|
+
expect(env!.CLAUDE_CODE_OAUTH_TOKEN).toBeUndefined();
|
|
498
|
+
expect(env!.ANTHROPIC_MODEL).toBeUndefined();
|
|
499
|
+
expect(env!.CLAUDE_CODE_EFFORT_LEVEL).toBeUndefined();
|
|
500
|
+
expect(env!.CLAUDE_CODE_SUBAGENT_MODEL).toBe("claude-haiku-4-5-20251001");
|
|
501
|
+
expect(env!.ANTHROPIC_API_KEY).toBe("sk-test");
|
|
502
|
+
expect(env!.ANTHROPIC_BASE_URL).toBe("https://api.example.com");
|
|
503
|
+
} finally {
|
|
504
|
+
for (const [key, value] of Object.entries(original)) {
|
|
505
|
+
if (value === undefined) {
|
|
506
|
+
delete process.env[key];
|
|
507
|
+
} else {
|
|
508
|
+
process.env[key] = value;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
});
|
|
@@ -47,9 +47,10 @@ const baseAppConfig: AppConfig = {
|
|
|
47
47
|
defaultAgent: true,
|
|
48
48
|
model: "initial-model",
|
|
49
49
|
subagentModel: "initial-subagent-model",
|
|
50
|
-
effort: "initial-effort",
|
|
51
|
-
apiKey: "",
|
|
52
|
-
baseUrl: "",
|
|
50
|
+
effort: "initial-effort",
|
|
51
|
+
apiKey: "",
|
|
52
|
+
baseUrl: "",
|
|
53
|
+
maxTurn: 25,
|
|
53
54
|
},
|
|
54
55
|
cursor: { enabled: true, defaultAgent: false, path: "/initial/cursor", model: "initial-cursor-model" },
|
|
55
56
|
codex: { enabled: true, defaultAgent: false, path: "/initial/codex", model: "initial-codex-model", effort: "initial-codex-effort" },
|
|
@@ -88,10 +89,11 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
|
|
|
88
89
|
defaultAgent: true,
|
|
89
90
|
model: "claude-sonnet-4-6",
|
|
90
91
|
subagentModel: "claude-haiku-4-5-20251001",
|
|
91
|
-
effort: "high",
|
|
92
|
-
apiKey: "sk-test",
|
|
93
|
-
baseUrl: "https://api.example.com",
|
|
94
|
-
|
|
92
|
+
effort: "high",
|
|
93
|
+
apiKey: "sk-test",
|
|
94
|
+
baseUrl: "https://api.example.com",
|
|
95
|
+
maxTurn: 25,
|
|
96
|
+
},
|
|
95
97
|
});
|
|
96
98
|
|
|
97
99
|
expect(CLAUDE_MODEL).toBe("claude-sonnet-4-6");
|
|
@@ -1,538 +1,465 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
UnifiedBlock,
|
|
19
|
-
UnifiedStreamMessage,
|
|
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
|
-
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
171
|
-
try {
|
|
172
|
-
if (!existsSync(settingsPath)) return null;
|
|
173
|
-
const raw = readFileSync(settingsPath, "utf-8");
|
|
174
|
-
const settings = JSON.parse(raw);
|
|
175
|
-
const mcpServers = settings?.mcpServers;
|
|
176
|
-
if (!mcpServers || Object.keys(mcpServers).length === 0) return null;
|
|
177
|
-
// CLI 期望 {"mcpServers": {...}} 格式,而非裸 mcpServers 值
|
|
178
|
-
return JSON.stringify({ mcpServers });
|
|
179
|
-
} catch {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ---------------------------------------------------------------------------
|
|
185
|
-
// 诊断日志
|
|
186
|
-
// ---------------------------------------------------------------------------
|
|
187
|
-
|
|
188
|
-
function logMcpConfig(): void {
|
|
189
|
-
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
190
|
-
const ts = new Date().toISOString();
|
|
191
|
-
try {
|
|
192
|
-
if (!existsSync(settingsPath)) {
|
|
193
|
-
console.log(`[${ts}] [MCP-DIAG] settings.json not found at ${settingsPath}`);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
const raw = readFileSync(settingsPath, "utf-8");
|
|
197
|
-
const settings = JSON.parse(raw);
|
|
198
|
-
const mcpServers = settings?.mcpServers;
|
|
199
|
-
if (!mcpServers || Object.keys(mcpServers).length === 0) {
|
|
200
|
-
console.log(`[${ts}] [MCP-DIAG] No mcpServers configured in settings.json`);
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
console.log(`[${ts}] [MCP-DIAG] mcpServers found: ${JSON.stringify(Object.keys(mcpServers))}`);
|
|
204
|
-
for (const [name, cfg] of Object.entries(mcpServers) as [string, { type?: string; command?: string; args?: string[] }][]) {
|
|
205
|
-
console.log(`[${ts}] [MCP-DIAG] ${name}: type=${cfg.type}, command=${cfg.command}, args=${JSON.stringify(cfg.args)}`);
|
|
206
|
-
}
|
|
207
|
-
} catch (err) {
|
|
208
|
-
console.log(`[${ts}] [MCP-DIAG] Failed to read settings.json: ${(err as Error).message}`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ---------------------------------------------------------------------------
|
|
213
|
-
// normalizeSdkMessage — CLI JSONL 消息 → UnifiedStreamMessage | null
|
|
214
|
-
// (CLI 与 SDK 使用相同 JSONL 格式,导出名保留以兼容现有测试)
|
|
215
|
-
// ---------------------------------------------------------------------------
|
|
216
|
-
|
|
217
|
-
export function normalizeSdkMessage(msg: SdkMessageLike): UnifiedStreamMessage | null {
|
|
218
|
-
if (
|
|
219
|
-
(msg.type === "assistant" || msg.type === "user") &&
|
|
220
|
-
msg.message?.content
|
|
221
|
-
) {
|
|
222
|
-
const blocks: UnifiedBlock[] = [];
|
|
223
|
-
for (const block of msg.message.content) {
|
|
224
|
-
if (block.type === "thinking" && block.thinking) {
|
|
225
|
-
blocks.push({ type: "thinking", thinking: block.thinking });
|
|
226
|
-
} else if (block.type === "tool_use") {
|
|
227
|
-
blocks.push({
|
|
228
|
-
type: "tool_use",
|
|
229
|
-
id: (block as { id?: string }).id,
|
|
230
|
-
name: block.name ?? "unknown",
|
|
231
|
-
input: block.input,
|
|
232
|
-
});
|
|
233
|
-
} else if (block.type === "tool_result") {
|
|
234
|
-
blocks.push({
|
|
235
|
-
type: "tool_result",
|
|
236
|
-
tool_use_id: block.tool_use_id ?? "",
|
|
237
|
-
content: block.content,
|
|
238
|
-
is_error: block.is_error,
|
|
239
|
-
});
|
|
240
|
-
} else if (block.type === "redacted_thinking") {
|
|
241
|
-
blocks.push({ type: "redacted_thinking" });
|
|
242
|
-
} else if (block.type === "search_result") {
|
|
243
|
-
blocks.push({
|
|
244
|
-
type: "search_result",
|
|
245
|
-
query: block.query ?? "",
|
|
246
|
-
});
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
getSessionInfo as sdkGetSessionInfo,
|
|
8
|
+
query,
|
|
9
|
+
type Options as ClaudeSdkOptions,
|
|
10
|
+
type SDKMessage,
|
|
11
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
CreateSessionResult,
|
|
15
|
+
SessionInfo,
|
|
16
|
+
ToolAdapter,
|
|
17
|
+
ToolPromptOptions,
|
|
18
|
+
UnifiedBlock,
|
|
19
|
+
UnifiedStreamMessage,
|
|
20
|
+
} from "./adapter-interface.ts";
|
|
21
|
+
import {
|
|
22
|
+
defaultClaudeSessionMetaStore,
|
|
23
|
+
type ClaudeSessionMetaStore,
|
|
24
|
+
} from "./claude-session-meta-store.ts";
|
|
25
|
+
|
|
26
|
+
const PROJECT_ROOT = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
27
|
+
const CLAUDE_SPECIFIC_PROMPT_PATH = join(
|
|
28
|
+
PROJECT_ROOT,
|
|
29
|
+
"agent-prompts",
|
|
30
|
+
"claude_specific.md",
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
type SettingSource = "user" | "project" | "local";
|
|
34
|
+
|
|
35
|
+
interface SdkContentBlock {
|
|
36
|
+
type: string;
|
|
37
|
+
text?: string;
|
|
38
|
+
thinking?: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
input?: unknown;
|
|
41
|
+
tool_use_id?: string;
|
|
42
|
+
content?: unknown;
|
|
43
|
+
is_error?: boolean;
|
|
44
|
+
query?: string;
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface SdkMessageLike {
|
|
49
|
+
type?: string;
|
|
50
|
+
subtype?: string;
|
|
51
|
+
message?: { content?: SdkContentBlock[] };
|
|
52
|
+
compact_metadata?: {
|
|
53
|
+
trigger?: "manual" | "auto";
|
|
54
|
+
pre_tokens?: number;
|
|
55
|
+
post_tokens?: number;
|
|
56
|
+
};
|
|
57
|
+
session_id?: string;
|
|
58
|
+
model?: string;
|
|
59
|
+
cwd?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ClaudeAdapterOptions {
|
|
63
|
+
model: string;
|
|
64
|
+
subagentModel?: string;
|
|
65
|
+
effort: string;
|
|
66
|
+
apiKey?: string;
|
|
67
|
+
baseUrl?: string;
|
|
68
|
+
isEmpty: (value: string) => boolean;
|
|
69
|
+
metaStore?: ClaudeSessionMetaStore;
|
|
70
|
+
maxTurn?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function buildSdkEnv(
|
|
74
|
+
subagentModel: string | undefined,
|
|
75
|
+
apiKey: string | undefined,
|
|
76
|
+
baseUrl: string | undefined,
|
|
77
|
+
): Record<string, string | undefined> | undefined {
|
|
78
|
+
const subagentModelTrim = (subagentModel ?? "").trim();
|
|
79
|
+
const apiKeyTrim = (apiKey ?? "").trim();
|
|
80
|
+
const baseUrlTrim = (baseUrl ?? "").trim();
|
|
81
|
+
|
|
82
|
+
if (!subagentModelTrim && !apiKeyTrim && !baseUrlTrim) return undefined;
|
|
83
|
+
|
|
84
|
+
const env: Record<string, string | undefined> = { ...process.env };
|
|
85
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
86
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
87
|
+
delete env.ANTHROPIC_MODEL;
|
|
88
|
+
delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
89
|
+
delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
90
|
+
delete env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
91
|
+
delete env.CLAUDE_CODE_EFFORT_LEVEL;
|
|
92
|
+
delete env.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
93
|
+
|
|
94
|
+
if (subagentModelTrim) env.CLAUDE_CODE_SUBAGENT_MODEL = subagentModelTrim;
|
|
95
|
+
if (apiKeyTrim) env.ANTHROPIC_API_KEY = apiKeyTrim;
|
|
96
|
+
if (baseUrlTrim) env.ANTHROPIC_BASE_URL = baseUrlTrim;
|
|
97
|
+
|
|
98
|
+
return env;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function readMcpServersConfig(): Record<string, unknown> | undefined {
|
|
102
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
103
|
+
try {
|
|
104
|
+
if (!existsSync(settingsPath)) return undefined;
|
|
105
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
106
|
+
const settings = JSON.parse(raw) as { mcpServers?: Record<string, unknown> };
|
|
107
|
+
const mcpServers = settings.mcpServers;
|
|
108
|
+
if (!mcpServers || Object.keys(mcpServers).length === 0) return undefined;
|
|
109
|
+
return mcpServers;
|
|
110
|
+
} catch {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function logMcpConfig(): void {
|
|
116
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
117
|
+
const ts = new Date().toISOString();
|
|
118
|
+
try {
|
|
119
|
+
if (!existsSync(settingsPath)) {
|
|
120
|
+
console.log(`[${ts}] [MCP-DIAG] settings.json not found at ${settingsPath}`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
124
|
+
const settings = JSON.parse(raw) as { mcpServers?: Record<string, unknown> };
|
|
125
|
+
const mcpServers = settings.mcpServers;
|
|
126
|
+
if (!mcpServers || Object.keys(mcpServers).length === 0) {
|
|
127
|
+
console.log(`[${ts}] [MCP-DIAG] No mcpServers configured in settings.json`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
console.log(`[${ts}] [MCP-DIAG] mcpServers found: ${JSON.stringify(Object.keys(mcpServers))}`);
|
|
131
|
+
for (const [name, cfg] of Object.entries(mcpServers)) {
|
|
132
|
+
const item = cfg as { type?: string; command?: string; args?: string[] };
|
|
133
|
+
console.log(`[${ts}] [MCP-DIAG] ${name}: type=${item.type}, command=${item.command}, args=${JSON.stringify(item.args)}`);
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.log(`[${ts}] [MCP-DIAG] Failed to read settings.json: ${(err as Error).message}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function normalizeSdkMessage(msg: SdkMessageLike): UnifiedStreamMessage | null {
|
|
141
|
+
if (
|
|
142
|
+
(msg.type === "assistant" || msg.type === "user") &&
|
|
143
|
+
msg.message?.content
|
|
144
|
+
) {
|
|
145
|
+
const blocks: UnifiedBlock[] = [];
|
|
146
|
+
for (const block of msg.message.content) {
|
|
147
|
+
if (block.type === "thinking" && block.thinking) {
|
|
148
|
+
blocks.push({ type: "thinking", thinking: block.thinking });
|
|
149
|
+
} else if (block.type === "tool_use") {
|
|
150
|
+
blocks.push({
|
|
151
|
+
type: "tool_use",
|
|
152
|
+
id: (block as { id?: string }).id,
|
|
153
|
+
name: block.name ?? "unknown",
|
|
154
|
+
input: block.input,
|
|
155
|
+
});
|
|
156
|
+
} else if (block.type === "tool_result") {
|
|
157
|
+
blocks.push({
|
|
158
|
+
type: "tool_result",
|
|
159
|
+
tool_use_id: block.tool_use_id ?? "",
|
|
160
|
+
content: block.content,
|
|
161
|
+
is_error: block.is_error,
|
|
162
|
+
});
|
|
163
|
+
} else if (block.type === "redacted_thinking") {
|
|
164
|
+
blocks.push({ type: "redacted_thinking" });
|
|
165
|
+
} else if (block.type === "search_result") {
|
|
166
|
+
blocks.push({
|
|
167
|
+
type: "search_result",
|
|
168
|
+
query: block.query ?? "",
|
|
169
|
+
});
|
|
247
170
|
} 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
171
|
if (msg.type === "user") continue;
|
|
251
172
|
blocks.push({ type: "text", text: block.text });
|
|
252
173
|
}
|
|
253
|
-
}
|
|
254
|
-
return { type: msg.type, blocks };
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (msg.type === "system" && msg.subtype === "compact_boundary") {
|
|
258
|
-
const meta = msg.compact_metadata;
|
|
259
|
-
if (!meta) return null;
|
|
260
|
-
return {
|
|
261
|
-
type: "system",
|
|
262
|
-
blocks: [
|
|
263
|
-
{
|
|
264
|
-
type: "compact_boundary",
|
|
265
|
-
trigger: meta.trigger ?? "auto",
|
|
266
|
-
pre_tokens: meta.pre_tokens ?? 0,
|
|
267
|
-
post_tokens: meta.post_tokens,
|
|
268
|
-
},
|
|
269
|
-
],
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
"
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
await
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}
|
|
174
|
+
}
|
|
175
|
+
return { type: msg.type, blocks };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (msg.type === "system" && msg.subtype === "compact_boundary") {
|
|
179
|
+
const meta = msg.compact_metadata;
|
|
180
|
+
if (!meta) return null;
|
|
181
|
+
return {
|
|
182
|
+
type: "system",
|
|
183
|
+
blocks: [
|
|
184
|
+
{
|
|
185
|
+
type: "compact_boundary",
|
|
186
|
+
trigger: meta.trigger ?? "auto",
|
|
187
|
+
pre_tokens: meta.pre_tokens ?? 0,
|
|
188
|
+
post_tokens: meta.post_tokens,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function readClaudeSpecificInjectionPrompt(): string | null {
|
|
198
|
+
try {
|
|
199
|
+
if (!existsSync(CLAUDE_SPECIFIC_PROMPT_PATH)) return null;
|
|
200
|
+
const prompt = readFileSync(CLAUDE_SPECIFIC_PROMPT_PATH, "utf-8").trim();
|
|
201
|
+
return prompt.length > 0 ? prompt : null;
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function buildClaudePromptText(
|
|
208
|
+
userText: string,
|
|
209
|
+
injectionPrompt: string | null = readClaudeSpecificInjectionPrompt(),
|
|
210
|
+
): string {
|
|
211
|
+
const prompt = injectionPrompt?.trim();
|
|
212
|
+
if (!prompt) return userText;
|
|
213
|
+
|
|
214
|
+
return [
|
|
215
|
+
"[ChatCCC Claude-specific injection prompt]",
|
|
216
|
+
prompt,
|
|
217
|
+
"[/ChatCCC Claude-specific injection prompt]",
|
|
218
|
+
"",
|
|
219
|
+
userText,
|
|
220
|
+
].join("\n");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function buildSdkOptions(args: {
|
|
224
|
+
cwd: string;
|
|
225
|
+
model: string;
|
|
226
|
+
effort: string;
|
|
227
|
+
isEmpty: (value: string) => boolean;
|
|
228
|
+
subagentModel?: string;
|
|
229
|
+
apiKey?: string;
|
|
230
|
+
baseUrl?: string;
|
|
231
|
+
maxTurn: number;
|
|
232
|
+
planMode?: boolean;
|
|
233
|
+
resume?: string;
|
|
234
|
+
abortController?: AbortController;
|
|
235
|
+
}): ClaudeSdkOptions {
|
|
236
|
+
const {
|
|
237
|
+
cwd,
|
|
238
|
+
model,
|
|
239
|
+
effort,
|
|
240
|
+
isEmpty,
|
|
241
|
+
subagentModel,
|
|
242
|
+
apiKey,
|
|
243
|
+
baseUrl,
|
|
244
|
+
maxTurn,
|
|
245
|
+
planMode,
|
|
246
|
+
resume,
|
|
247
|
+
abortController,
|
|
248
|
+
} = args;
|
|
249
|
+
|
|
250
|
+
const options: ClaudeSdkOptions = {
|
|
251
|
+
cwd,
|
|
252
|
+
abortController,
|
|
253
|
+
settingSources: ["user", "project", "local"] as SettingSource[],
|
|
254
|
+
permissionMode: planMode ? "plan" : "bypassPermissions",
|
|
255
|
+
maxTurns: maxTurn,
|
|
256
|
+
maxBudgetUsd: 999999999,
|
|
257
|
+
skills: "all",
|
|
258
|
+
stderr: (data) => {
|
|
259
|
+
const trimmed = data.trim();
|
|
260
|
+
if (!trimmed) return;
|
|
261
|
+
const ts = new Date().toISOString();
|
|
262
|
+
console.log(`[${ts}] [CLAUDE-STDERR] ${trimmed.slice(0, 2000)}`);
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
if (!planMode) {
|
|
267
|
+
options.allowDangerouslySkipPermissions = true;
|
|
268
|
+
}
|
|
269
|
+
if (!isEmpty(model)) {
|
|
270
|
+
options.model = model;
|
|
271
|
+
}
|
|
272
|
+
if (!isEmpty(effort)) {
|
|
273
|
+
options.effort = effort as ClaudeSdkOptions["effort"];
|
|
274
|
+
}
|
|
275
|
+
if (resume) {
|
|
276
|
+
options.resume = resume;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const env = buildSdkEnv(subagentModel, apiKey, baseUrl);
|
|
280
|
+
if (env) {
|
|
281
|
+
options.env = env;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const mcpServers = readMcpServersConfig();
|
|
285
|
+
if (mcpServers) {
|
|
286
|
+
options.mcpServers = mcpServers as ClaudeSdkOptions["mcpServers"];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return options;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function toMessageLike(message: SDKMessage): SdkMessageLike {
|
|
293
|
+
return message as unknown as SdkMessageLike;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function bridgeAbortSignal(
|
|
297
|
+
signal: AbortSignal | undefined,
|
|
298
|
+
controller: AbortController,
|
|
299
|
+
): (() => void) | undefined {
|
|
300
|
+
if (!signal) return undefined;
|
|
301
|
+
const onAbort = () => controller.abort();
|
|
302
|
+
if (signal.aborted) {
|
|
303
|
+
controller.abort();
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
307
|
+
return () => signal.removeEventListener("abort", onAbort);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
class ClaudeAdapter implements ToolAdapter {
|
|
311
|
+
readonly displayName = "Claude Code";
|
|
312
|
+
readonly sessionDescPrefix = "Claude Code Session:";
|
|
313
|
+
private model: string;
|
|
314
|
+
private effort: string;
|
|
315
|
+
private subagentModel: string | undefined;
|
|
316
|
+
private apiKey: string | undefined;
|
|
317
|
+
private baseUrl: string | undefined;
|
|
318
|
+
private isEmpty: (value: string) => boolean;
|
|
319
|
+
private metaStore: ClaudeSessionMetaStore;
|
|
320
|
+
private maxTurn: number;
|
|
321
|
+
|
|
322
|
+
constructor(options: ClaudeAdapterOptions) {
|
|
323
|
+
this.model = options.model;
|
|
324
|
+
this.effort = options.effort;
|
|
325
|
+
this.subagentModel = options.subagentModel;
|
|
326
|
+
this.apiKey = options.apiKey;
|
|
327
|
+
this.baseUrl = options.baseUrl;
|
|
328
|
+
this.isEmpty = options.isEmpty;
|
|
329
|
+
this.metaStore = options.metaStore ?? defaultClaudeSessionMetaStore;
|
|
330
|
+
this.maxTurn = options.maxTurn ?? 25;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async createSession(cwd: string): Promise<CreateSessionResult> {
|
|
334
|
+
logMcpConfig();
|
|
335
|
+
const abortController = new AbortController();
|
|
336
|
+
const stream = query({
|
|
337
|
+
prompt: "ok",
|
|
338
|
+
options: buildSdkOptions({
|
|
339
|
+
cwd,
|
|
340
|
+
model: this.model,
|
|
341
|
+
effort: this.effort,
|
|
342
|
+
isEmpty: this.isEmpty,
|
|
343
|
+
subagentModel: this.subagentModel,
|
|
344
|
+
apiKey: this.apiKey,
|
|
345
|
+
baseUrl: this.baseUrl,
|
|
346
|
+
maxTurn: this.maxTurn,
|
|
347
|
+
abortController,
|
|
348
|
+
}),
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
for await (const raw of stream) {
|
|
353
|
+
const msg = toMessageLike(raw);
|
|
354
|
+
if (msg.session_id) {
|
|
355
|
+
const sessionId = msg.session_id;
|
|
356
|
+
await this.metaStore.set(sessionId, {
|
|
357
|
+
cwd: msg.cwd ?? cwd,
|
|
358
|
+
model: msg.model,
|
|
359
|
+
}).catch(() => {});
|
|
360
|
+
const ts = new Date().toISOString();
|
|
361
|
+
console.log(`[${ts}] [CLAUDE-SDK] createSession: ${sessionId}`);
|
|
362
|
+
return { sessionId };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
} finally {
|
|
366
|
+
abortController.abort();
|
|
367
|
+
stream.close();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
throw new Error("No session ID in Claude init event");
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async *prompt(
|
|
374
|
+
sessionId: string,
|
|
375
|
+
userText: string,
|
|
376
|
+
cwd: string,
|
|
377
|
+
signal?: AbortSignal,
|
|
378
|
+
options?: ToolPromptOptions,
|
|
379
|
+
): AsyncIterable<UnifiedStreamMessage> {
|
|
380
|
+
const abortController = new AbortController();
|
|
381
|
+
const removeAbortListener = bridgeAbortSignal(signal, abortController);
|
|
382
|
+
if (abortController.signal.aborted) return;
|
|
383
|
+
|
|
384
|
+
const stream = query({
|
|
385
|
+
prompt: buildClaudePromptText(userText),
|
|
386
|
+
options: buildSdkOptions({
|
|
387
|
+
cwd,
|
|
388
|
+
model: this.model,
|
|
389
|
+
effort: this.effort,
|
|
390
|
+
isEmpty: this.isEmpty,
|
|
391
|
+
subagentModel: this.subagentModel,
|
|
392
|
+
apiKey: this.apiKey,
|
|
393
|
+
baseUrl: this.baseUrl,
|
|
394
|
+
maxTurn: this.maxTurn,
|
|
395
|
+
planMode: options?.planMode,
|
|
396
|
+
resume: sessionId,
|
|
397
|
+
abortController,
|
|
398
|
+
}),
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
for await (const raw of stream) {
|
|
403
|
+
if (abortController.signal.aborted) break;
|
|
404
|
+
|
|
405
|
+
const msg = toMessageLike(raw);
|
|
406
|
+
if (msg.type === "system" && msg.subtype === "init" && msg.session_id) {
|
|
407
|
+
const meta: { cwd?: string; model?: string } = {};
|
|
408
|
+
if (msg.cwd) meta.cwd = msg.cwd;
|
|
409
|
+
if (msg.model) meta.model = msg.model;
|
|
410
|
+
if (Object.keys(meta).length > 0) {
|
|
411
|
+
this.metaStore.set(msg.session_id, meta).catch(() => {});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const normalized = normalizeSdkMessage(msg);
|
|
416
|
+
if (normalized) yield normalized;
|
|
417
|
+
|
|
418
|
+
if (msg.type === "result") {
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
} finally {
|
|
423
|
+
removeAbortListener?.();
|
|
424
|
+
abortController.abort();
|
|
425
|
+
stream.close();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async getSessionInfo(sessionId: string): Promise<SessionInfo | undefined> {
|
|
430
|
+
const meta = await this.metaStore.get(sessionId);
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
const sdkInfo = await sdkGetSessionInfo(
|
|
434
|
+
sessionId,
|
|
435
|
+
meta?.cwd ? { dir: meta.cwd } : undefined,
|
|
436
|
+
);
|
|
437
|
+
if (sdkInfo) {
|
|
438
|
+
return {
|
|
439
|
+
sessionId: sdkInfo.sessionId,
|
|
440
|
+
cwd: sdkInfo.cwd ?? meta?.cwd,
|
|
441
|
+
summary: sdkInfo.summary,
|
|
442
|
+
lastModified: sdkInfo.lastModified,
|
|
443
|
+
model: meta?.model,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
// Fall back to the local meta store below.
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (!meta) return { sessionId };
|
|
451
|
+
return meta.model
|
|
452
|
+
? { sessionId, cwd: meta.cwd, model: meta.model }
|
|
453
|
+
: { sessionId, cwd: meta.cwd };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async closeSession(_sessionId: string): Promise<void> {
|
|
457
|
+
// SDK query streams are request scoped and are closed in prompt/createSession.
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function createClaudeAdapter(
|
|
462
|
+
options: ClaudeAdapterOptions,
|
|
463
|
+
): ToolAdapter {
|
|
464
|
+
return new ClaudeAdapter(options);
|
|
465
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// =============================================================================
|
|
2
2
|
// claude-session-meta-store.ts — Claude 会话 sessionId → meta 持久化映射
|
|
3
3
|
// =============================================================================
|
|
4
|
-
//
|
|
5
|
-
// ChatCCC
|
|
4
|
+
// 背景:Claude Agent SDK 的 getSessionInfo 可能因 cwd / 本地记录缺失而查不到。
|
|
5
|
+
// ChatCCC 额外维护 sessionId → { cwd, model } 映射作为展示与恢复兜底。
|
|
6
6
|
//
|
|
7
7
|
// 存储:
|
|
8
8
|
// 文件 state/claude-session-meta.json,结构:
|
|
@@ -117,4 +117,4 @@ export function createClaudeSessionMetaStore(
|
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
export const defaultClaudeSessionMetaStore = createClaudeSessionMetaStore();
|
|
120
|
+
export const defaultClaudeSessionMetaStore = createClaudeSessionMetaStore();
|
package/src/config.ts
CHANGED
|
@@ -65,11 +65,11 @@ export interface ClaudeConfig {
|
|
|
65
65
|
model: string;
|
|
66
66
|
subagentModel: string;
|
|
67
67
|
effort: string;
|
|
68
|
-
/** Anthropic API Key(选填,留空则使用 Claude
|
|
68
|
+
/** Anthropic API Key(选填,留空则使用 Claude Code 默认认证) */
|
|
69
69
|
apiKey: string;
|
|
70
70
|
/** Anthropic 兼容 API Base URL(选填,留空则使用默认端点) */
|
|
71
71
|
baseUrl: string;
|
|
72
|
-
/** Claude
|
|
72
|
+
/** Claude Agent SDK maxTurns 设置,默认 25 */
|
|
73
73
|
maxTurn: number;
|
|
74
74
|
}
|
|
75
75
|
|
package/src/web-ui.ts
CHANGED
|
@@ -646,7 +646,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
646
646
|
</div>
|
|
647
647
|
<div class="form-group">
|
|
648
648
|
<label>API Key(选填)</label>
|
|
649
|
-
<input type="password" id="field-CHATCCC_ANTHROPIC_API_KEY" placeholder="留空使用 Claude
|
|
649
|
+
<input type="password" id="field-CHATCCC_ANTHROPIC_API_KEY" placeholder="留空使用 Claude Code 默认认证">
|
|
650
650
|
</div>
|
|
651
651
|
<div class="form-group">
|
|
652
652
|
<label>Base URL(选填)</label>
|