deepccc 0.1.28 → 0.2.1
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 +41 -20
- package/bin/deepccc-cli.mjs +17 -0
- package/bin/deepccc.mjs +19 -13
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +7 -7
- package/dist/config.d.ts +12 -0
- package/dist/config.js +49 -4
- package/dist/context.d.ts +1 -0
- package/dist/context.js +12 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -1
- package/dist/web-entry.d.ts +7 -0
- package/dist/web-entry.js +72 -0
- package/dist/web-page.d.ts +1 -0
- package/dist/web-page.js +76 -0
- package/dist/web-runtime.d.ts +101 -0
- package/dist/web-runtime.js +204 -0
- package/dist/web-server.d.ts +62 -0
- package/dist/web-server.js +400 -0
- package/dist/web-session-store.d.ts +57 -0
- package/dist/web-session-store.js +162 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`deepccc` 是一个轻量级本地编程 Agent,针对 DeepSeek 使用体验做了优化,同时支持其他 OpenAI-compatible 模型接口。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
它提供本地 Web UI、交互式命令行、JSONL 流式输出、本地文件工具、命令执行、项目提示词自动注入和持久化上下文。
|
|
6
6
|
|
|
7
7
|
## 当前状态
|
|
8
8
|
|
|
@@ -23,7 +23,7 @@ git clone https://github.com/wzj998/deepccc-agent.git
|
|
|
23
23
|
cd deepccc-agent
|
|
24
24
|
npm install
|
|
25
25
|
npm run build
|
|
26
|
-
node bin/deepccc.mjs --help
|
|
26
|
+
node bin/deepccc-cli.mjs --help
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
运行要求:
|
|
@@ -91,14 +91,35 @@ $env:DEEPCCC_STREAMING="true"
|
|
|
91
91
|
"email": "20184052+wzj998@users.noreply.github.com"
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
|
-
"rawStreamLogs": {
|
|
94
|
+
"rawStreamLogs": {
|
|
95
95
|
"enabled": true,
|
|
96
96
|
"maxBytesPerTurn": 1048576,
|
|
97
97
|
"retentionDays": 7,
|
|
98
|
-
"keepCompleted": false
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
"keepCompleted": false
|
|
99
|
+
},
|
|
100
|
+
"web": {
|
|
101
|
+
"port": 28080,
|
|
102
|
+
"openOnStart": true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Web UI
|
|
108
|
+
|
|
109
|
+
全局安装后可以直接启动本地网页版:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
deepccc
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
默认只监听 `http://127.0.0.1:28080/`,不会暴露到局域网。可在
|
|
116
|
+
`~/.deepccc/config.json` 的 `web.port` 修改端口,`web.openOnStart` 控制启动时是否自动打开浏览器;也可以临时使用 `deepccc --port 28081 --no-open`。默认启动会安全替换经过实例身份验证的旧 DeepCCC Web;传入 `--reuse-existing` 时复用已有实例。`deepccc web` 保留为兼容别名。
|
|
117
|
+
|
|
118
|
+
从源码开发时,`npm run dev` 启动 Web Server 并打开页面(不启用 watch);`npm run dev:cli` 启动终端模式。
|
|
119
|
+
|
|
120
|
+
Web UI 支持新建、恢复、重命名和删除多会话,多个会话可以同时运行,即使它们指向同一个工作目录。每个会话可独立选择 model、subModel 和 effort,并持续复用 CLI 已保存的历史。注意:当前版本不自动创建 Git worktree;同目录的多个运行中 Agent 直接修改同一组文件,页面会提示覆盖与冲突风险。
|
|
121
|
+
|
|
122
|
+
API 设置采用单一 Provider 配置,支持 OpenAI-compatible 与 Anthropic Messages。完整 API Key 只保存在本机 `~/.deepccc/config.json`,浏览器读取设置时仅返回掩码。高风险命令会在网页中暂停并请求“拒绝、允许一次、本会话允许、永久允许”,浏览器断开或审批超时默认拒绝。
|
|
102
123
|
|
|
103
124
|
`git.coAuthor.enabled` 默认开启。DeepCCC 通过 `run_command` 创建 Git 提交时会保留用户为
|
|
104
125
|
主 Author,并追加 `Co-authored-by: DeepCCC <20184052+wzj998@users.noreply.github.com>`。
|
|
@@ -147,45 +168,45 @@ API 拒绝(context length exceeded),实际窗口以模型与所用服务
|
|
|
147
168
|
在当前目录启动一个交互式 Agent:
|
|
148
169
|
|
|
149
170
|
```bash
|
|
150
|
-
deepccc
|
|
171
|
+
deepccc-cli
|
|
151
172
|
```
|
|
152
173
|
|
|
153
174
|
指定其他模型或 OpenAI-compatible 接口:
|
|
154
175
|
|
|
155
176
|
```bash
|
|
156
|
-
deepccc --base-url https://api.openai.com/v1 --api-key "$OPENAI_API_KEY" --model gpt-4.1
|
|
177
|
+
deepccc-cli --base-url https://api.openai.com/v1 --api-key "$OPENAI_API_KEY" --model gpt-4.1
|
|
157
178
|
```
|
|
158
179
|
|
|
159
180
|
使用 Anthropic Messages 协议(同样支持流式输出):
|
|
160
181
|
|
|
161
182
|
```bash
|
|
162
|
-
deepccc --provider anthropic --base-url https://api.example.com --api-key "$API_KEY" --model claude-sonnet-4-6
|
|
183
|
+
deepccc-cli --provider anthropic --base-url https://api.example.com --api-key "$API_KEY" --model claude-sonnet-4-6
|
|
163
184
|
```
|
|
164
185
|
|
|
165
186
|
指定工作目录:
|
|
166
187
|
|
|
167
188
|
```bash
|
|
168
|
-
deepccc --cwd /path/to/project
|
|
189
|
+
deepccc-cli --cwd /path/to/project
|
|
169
190
|
```
|
|
170
191
|
|
|
171
192
|
恢复当前工作目录最近一次会话:
|
|
172
193
|
|
|
173
194
|
```bash
|
|
174
|
-
deepccc --resume
|
|
195
|
+
deepccc-cli --resume
|
|
175
196
|
```
|
|
176
197
|
|
|
177
198
|
设置工具调用步数上限:
|
|
178
199
|
|
|
179
200
|
```bash
|
|
180
|
-
deepccc --max-steps 20
|
|
201
|
+
deepccc-cli --max-steps 20
|
|
181
202
|
```
|
|
182
203
|
|
|
183
|
-
默认情况下,`deepccc` 不设置固定步数上限,会让模型自然完成工具循环。
|
|
204
|
+
默认情况下,`deepccc-cli` 不设置固定步数上限,会让模型自然完成工具循环。
|
|
184
205
|
|
|
185
206
|
设置推理强度(reasoning effort):
|
|
186
207
|
|
|
187
208
|
```bash
|
|
188
|
-
deepccc --effort high
|
|
209
|
+
deepccc-cli --effort high
|
|
189
210
|
```
|
|
190
211
|
|
|
191
212
|
可选值:`none` / `minimal` / `low` / `medium` / `high` / `xhigh` / `max`(留空则不传 `reasoning_effort` 请求字段)。
|
|
@@ -193,7 +214,7 @@ deepccc --effort high
|
|
|
193
214
|
限制主对话最大输出 token:
|
|
194
215
|
|
|
195
216
|
```bash
|
|
196
|
-
deepccc --max-output-tokens 8192
|
|
217
|
+
deepccc-cli --max-output-tokens 8192
|
|
197
218
|
```
|
|
198
219
|
|
|
199
220
|
不设置时使用 Provider 默认值。
|
|
@@ -248,7 +269,7 @@ deepccc --max-output-tokens 8192
|
|
|
248
269
|
`--stream-json` 或程序化调用(无终端可交互)时,高危命令**安全默认拒绝**。需要全自动场景可显式传入:
|
|
249
270
|
|
|
250
271
|
```bash
|
|
251
|
-
deepccc --dangerously-bypass-permissions
|
|
272
|
+
deepccc-cli --dangerously-bypass-permissions
|
|
252
273
|
```
|
|
253
274
|
|
|
254
275
|
该参数与 `ChatSession` 的 `permissionMode: "bypass"` 等价,也是 chatccc 集成 deepccc 时使用的模式(对齐 chatccc 调用 Claude Code / Codex 的 bypass 方式)。
|
|
@@ -262,7 +283,7 @@ deepccc --dangerously-bypass-permissions
|
|
|
262
283
|
如果终端渲染出现异常,可以强制回退为纯文本流式输出:
|
|
263
284
|
|
|
264
285
|
```bash
|
|
265
|
-
deepccc --plain
|
|
286
|
+
deepccc-cli --plain
|
|
266
287
|
```
|
|
267
288
|
|
|
268
289
|
## JSONL 流式输出
|
|
@@ -270,13 +291,13 @@ deepccc --plain
|
|
|
270
291
|
JSONL 模式适合脚本、服务端集成或其他上层系统调用:
|
|
271
292
|
|
|
272
293
|
```bash
|
|
273
|
-
deepccc --stream-json --prompt "检查这个仓库并总结测试命令"
|
|
294
|
+
deepccc-cli --stream-json --prompt "检查这个仓库并总结测试命令"
|
|
274
295
|
```
|
|
275
296
|
|
|
276
297
|
也可以从 stdin 传入提示词:
|
|
277
298
|
|
|
278
299
|
```bash
|
|
279
|
-
echo "运行测试并解释失败原因" | deepccc --stream-json
|
|
300
|
+
echo "运行测试并解释失败原因" | deepccc-cli --stream-json
|
|
280
301
|
```
|
|
281
302
|
|
|
282
303
|
输出是逐行 JSON:
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const pkgRoot = dirname(require.resolve("../package.json"));
|
|
9
|
+
const distCli = join(pkgRoot, "dist", "cli.js");
|
|
10
|
+
let args;
|
|
11
|
+
if (existsSync(distCli)) {
|
|
12
|
+
args = [distCli, ...process.argv.slice(2)];
|
|
13
|
+
} else {
|
|
14
|
+
args = [require.resolve("tsx/cli"), join(pkgRoot, "src", "cli.ts"), ...process.argv.slice(2)];
|
|
15
|
+
}
|
|
16
|
+
const result = spawnSync(process.execPath, args, { stdio: "inherit", cwd: process.cwd(), env: process.env });
|
|
17
|
+
process.exit(result.status === null ? 1 : result.status);
|
package/bin/deepccc.mjs
CHANGED
|
@@ -6,21 +6,27 @@ import { dirname, join } from "node:path";
|
|
|
6
6
|
|
|
7
7
|
const require = createRequire(import.meta.url);
|
|
8
8
|
const pkgRoot = dirname(require.resolve("../package.json"));
|
|
9
|
+
const rawArgs = process.argv.slice(2);
|
|
10
|
+
const distWebEntry = join(pkgRoot, "dist", "web-entry.js");
|
|
9
11
|
const distCli = join(pkgRoot, "dist", "cli.js");
|
|
12
|
+
const sourceMode = !existsSync(distWebEntry);
|
|
13
|
+
const isLegacy = rawArgs.length > 0 && rawArgs[0] !== "web" && !["--reuse-existing", "--port", "--no-open", "--help", "-h"].includes(rawArgs[0]);
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
args =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
15
|
+
if (isLegacy) {
|
|
16
|
+
console.error("[DeepCCC] CLI commands have moved to deepccc-cli; forwarding this invocation for compatibility.");
|
|
17
|
+
const args = sourceMode
|
|
18
|
+
? [require.resolve("tsx/cli"), join(pkgRoot, "src", "cli.ts"), ...rawArgs]
|
|
19
|
+
: [distCli, ...rawArgs];
|
|
20
|
+
const result = spawnSync(process.execPath, args, { stdio: "inherit", cwd: process.cwd(), env: process.env });
|
|
21
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
if (sourceMode) {
|
|
25
|
+
const result = spawnSync(process.execPath, [require.resolve("tsx/cli"), join(pkgRoot, "src", "web-entry.ts"), ...rawArgs], {
|
|
26
|
+
stdio: "inherit", cwd: process.cwd(), env: process.env,
|
|
27
|
+
});
|
|
28
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
29
|
+
}
|
|
25
30
|
|
|
26
|
-
|
|
31
|
+
const { runWebEntry } = await import(new URL("../dist/web-entry.js", import.meta.url));
|
|
32
|
+
await runWebEntry(rawArgs);
|
package/dist/cli.d.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* DeepCCC terminal REPL and JSONL streaming entrypoint — 同步自 ChatCCC
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* node bin/deepccc.mjs
|
|
6
|
-
* node bin/deepccc.mjs --model deepseek-v4-pro
|
|
7
|
-
* node bin/deepccc.mjs --stream-json --prompt "hello"
|
|
5
|
+
* node bin/deepccc-cli.mjs
|
|
6
|
+
* node bin/deepccc-cli.mjs --model deepseek-v4-pro
|
|
7
|
+
* node bin/deepccc-cli.mjs --stream-json --prompt "hello"
|
|
8
8
|
*
|
|
9
9
|
* 交互模式(TTY)下,单轮回复渲染为固定"过程区块":状态行 + 折叠工具行 +
|
|
10
10
|
* 原地更新正文,不再滚屏刷 JSON;完成/停止/异常后定型留在屏幕上。
|
package/dist/cli.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* DeepCCC terminal REPL and JSONL streaming entrypoint — 同步自 ChatCCC
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* node bin/deepccc.mjs
|
|
6
|
-
* node bin/deepccc.mjs --model deepseek-v4-pro
|
|
7
|
-
* node bin/deepccc.mjs --stream-json --prompt "hello"
|
|
5
|
+
* node bin/deepccc-cli.mjs
|
|
6
|
+
* node bin/deepccc-cli.mjs --model deepseek-v4-pro
|
|
7
|
+
* node bin/deepccc-cli.mjs --stream-json --prompt "hello"
|
|
8
8
|
*
|
|
9
9
|
* 交互模式(TTY)下,单轮回复渲染为固定"过程区块":状态行 + 折叠工具行 +
|
|
10
10
|
* 原地更新正文,不再滚屏刷 JSON;完成/停止/异常后定型留在屏幕上。
|
|
@@ -128,7 +128,7 @@ function printHelp(appConfig) {
|
|
|
128
128
|
console.log([
|
|
129
129
|
"DeepCCC terminal agent",
|
|
130
130
|
"",
|
|
131
|
-
"Usage: deepccc [options]",
|
|
131
|
+
"Usage: deepccc-cli [options]",
|
|
132
132
|
"",
|
|
133
133
|
"Options:",
|
|
134
134
|
` --provider <name> API protocol: openai or anthropic (current default ${appConfig.provider})`,
|
|
@@ -565,7 +565,7 @@ async function runRepl(args) {
|
|
|
565
565
|
});
|
|
566
566
|
}
|
|
567
567
|
/**
|
|
568
|
-
* skill create 子命令:deepccc skill create <name> [--scope global|project] [--description "..."]
|
|
568
|
+
* skill create 子命令:deepccc-cli skill create <name> [--scope global|project] [--description "..."]
|
|
569
569
|
* 默认创建为全局技能(~/.deepccc/skills/<name>/SKILL.md,Codex 结构);
|
|
570
570
|
* --scope project 创建为项目技能(<cwd>/.deepccc/skills/<name>/SKILL.md)。
|
|
571
571
|
* 新技能在下一次对话自动生效(技能索引每次 chat() 前重扫)。
|
|
@@ -574,7 +574,7 @@ function runSkillCreate(argv) {
|
|
|
574
574
|
const positional = argv.filter((a) => !a.startsWith("--"));
|
|
575
575
|
const name = positional[0];
|
|
576
576
|
if (!name) {
|
|
577
|
-
console.error("usage: deepccc skill create <name> [--scope global|project] [--description \"...\"]");
|
|
577
|
+
console.error("usage: deepccc-cli skill create <name> [--scope global|project] [--description \"...\"]");
|
|
578
578
|
process.exit(1);
|
|
579
579
|
}
|
|
580
580
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name)) {
|
|
@@ -608,7 +608,7 @@ function runSkillCreate(argv) {
|
|
|
608
608
|
console.log("hot reload: 下一次对话自动生效,无需重启");
|
|
609
609
|
}
|
|
610
610
|
async function main() {
|
|
611
|
-
// skill create 子命令:deepccc skill create <name> [--scope global|project] [--description "..."]
|
|
611
|
+
// skill create 子命令:deepccc-cli skill create <name> [--scope global|project] [--description "..."]
|
|
612
612
|
// 默认创建在全局 ~/.deepccc/skills(Codex 目录结构),--scope project 创建到 <cwd>/.deepccc/skills。
|
|
613
613
|
if (process.argv[2] === "skill" && process.argv[3] === "create") {
|
|
614
614
|
runSkillCreate(process.argv.slice(4));
|
package/dist/config.d.ts
CHANGED
|
@@ -34,13 +34,25 @@ export interface DeepCccConfig {
|
|
|
34
34
|
retentionDays: number;
|
|
35
35
|
keepCompleted: boolean;
|
|
36
36
|
};
|
|
37
|
+
web: {
|
|
38
|
+
port: number;
|
|
39
|
+
openOnStart: boolean;
|
|
40
|
+
};
|
|
37
41
|
}
|
|
38
42
|
export declare const DEEPCCC_HOME: string;
|
|
39
43
|
export declare const RAW_STREAM_LOGS_DIR: string;
|
|
44
|
+
export declare const CONFIG_PATH: string;
|
|
40
45
|
/**
|
|
41
46
|
* 默认配置(不读环境/文件)。导出供测试断言默认值;运行时请用 loadConfig 结果。
|
|
42
47
|
*/
|
|
43
48
|
export declare const DEFAULT_CONFIG: DeepCccConfig;
|
|
49
|
+
export declare function readConfigFile(): Partial<DeepCccConfig>;
|
|
44
50
|
export declare function normalizeDeepCccProvider(value: unknown): DeepCccProvider;
|
|
51
|
+
export declare function loadConfig(): DeepCccConfig;
|
|
45
52
|
export declare function ensureConfigDir(): void;
|
|
53
|
+
export type DeepCccConfigPatch = Partial<Pick<DeepCccConfig, "provider" | "apiKey" | "baseURL" | "model" | "subModel" | "effort" | "maxOutputTokens" | "streaming" | "contextWindow">> & {
|
|
54
|
+
web?: Partial<DeepCccConfig["web"]>;
|
|
55
|
+
};
|
|
56
|
+
/** Persist only user-editable Web fields while preserving advanced configuration. */
|
|
57
|
+
export declare function saveConfigPatch(patch: DeepCccConfigPatch): DeepCccConfig;
|
|
46
58
|
export declare const config: DeepCccConfig;
|
package/dist/config.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
export const DEEPCCC_HOME = join(homedir(), ".deepccc");
|
|
5
5
|
export const RAW_STREAM_LOGS_DIR = join(DEEPCCC_HOME, "raw-stream-logs");
|
|
6
|
-
const CONFIG_PATH = join(DEEPCCC_HOME, "config.json");
|
|
6
|
+
export const CONFIG_PATH = join(DEEPCCC_HOME, "config.json");
|
|
7
7
|
/**
|
|
8
8
|
* 默认配置(不读环境/文件)。导出供测试断言默认值;运行时请用 loadConfig 结果。
|
|
9
9
|
*/
|
|
@@ -31,8 +31,12 @@ export const DEFAULT_CONFIG = {
|
|
|
31
31
|
retentionDays: 7,
|
|
32
32
|
keepCompleted: false,
|
|
33
33
|
},
|
|
34
|
+
web: {
|
|
35
|
+
port: 28_080,
|
|
36
|
+
openOnStart: true,
|
|
37
|
+
},
|
|
34
38
|
};
|
|
35
|
-
function readConfigFile() {
|
|
39
|
+
export function readConfigFile() {
|
|
36
40
|
if (!existsSync(CONFIG_PATH))
|
|
37
41
|
return {};
|
|
38
42
|
const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
@@ -70,13 +74,14 @@ export function normalizeDeepCccProvider(value) {
|
|
|
70
74
|
return normalized;
|
|
71
75
|
throw new Error(`DEEPCCC_PROVIDER/provider must be "openai" or "anthropic", received: ${String(value)}`);
|
|
72
76
|
}
|
|
73
|
-
function loadConfig() {
|
|
77
|
+
export function loadConfig() {
|
|
74
78
|
const file = readConfigFile();
|
|
75
79
|
const rawLogs = file.rawStreamLogs && typeof file.rawStreamLogs === "object"
|
|
76
80
|
? file.rawStreamLogs
|
|
77
81
|
: {};
|
|
78
82
|
const git = file.git && typeof file.git === "object" ? file.git : {};
|
|
79
83
|
const coAuthor = git.coAuthor && typeof git.coAuthor === "object" ? git.coAuthor : {};
|
|
84
|
+
const web = file.web && typeof file.web === "object" ? file.web : {};
|
|
80
85
|
return {
|
|
81
86
|
provider: normalizeDeepCccProvider(env("DEEPCCC_PROVIDER") ?? file.provider ?? DEFAULT_CONFIG.provider),
|
|
82
87
|
apiKey: env("DEEPCCC_API_KEY") ?? env("DEEPSEEK_API_KEY") ?? file.apiKey ?? DEFAULT_CONFIG.apiKey,
|
|
@@ -101,9 +106,49 @@ function loadConfig() {
|
|
|
101
106
|
retentionDays: numberEnv("DEEPCCC_RAW_STREAM_RETENTION_DAYS") ?? rawLogs.retentionDays ?? DEFAULT_CONFIG.rawStreamLogs.retentionDays,
|
|
102
107
|
keepCompleted: boolEnv("DEEPCCC_RAW_STREAM_KEEP_COMPLETED") ?? rawLogs.keepCompleted ?? DEFAULT_CONFIG.rawStreamLogs.keepCompleted,
|
|
103
108
|
},
|
|
109
|
+
web: {
|
|
110
|
+
port: normalizeWebPort(web.port),
|
|
111
|
+
openOnStart: web.openOnStart ?? DEFAULT_CONFIG.web.openOnStart,
|
|
112
|
+
},
|
|
104
113
|
};
|
|
105
114
|
}
|
|
106
115
|
export function ensureConfigDir() {
|
|
107
116
|
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
108
117
|
}
|
|
118
|
+
/** Persist only user-editable Web fields while preserving advanced configuration. */
|
|
119
|
+
export function saveConfigPatch(patch) {
|
|
120
|
+
const existing = readConfigFile();
|
|
121
|
+
const { web: webPatch, ...flatPatch } = patch;
|
|
122
|
+
const next = {
|
|
123
|
+
...existing,
|
|
124
|
+
...flatPatch,
|
|
125
|
+
...(webPatch ? { web: { ...DEFAULT_CONFIG.web, ...(existing.web ?? {}), ...webPatch } } : {}),
|
|
126
|
+
};
|
|
127
|
+
if (patch.provider !== undefined)
|
|
128
|
+
next.provider = normalizeDeepCccProvider(patch.provider);
|
|
129
|
+
if (patch.baseURL !== undefined && !patch.baseURL.trim())
|
|
130
|
+
throw new Error("baseURL must not be empty");
|
|
131
|
+
if (patch.model !== undefined && !patch.model.trim())
|
|
132
|
+
throw new Error("model must not be empty");
|
|
133
|
+
if (patch.effort !== undefined && !["", "none", "minimal", "low", "medium", "high", "xhigh", "max"].includes(patch.effort)) {
|
|
134
|
+
throw new Error(`Unsupported effort: ${patch.effort}`);
|
|
135
|
+
}
|
|
136
|
+
if (patch.contextWindow !== undefined && (!Number.isInteger(patch.contextWindow) || patch.contextWindow <= 0)) {
|
|
137
|
+
throw new Error("contextWindow must be a positive integer");
|
|
138
|
+
}
|
|
139
|
+
if (webPatch?.port !== undefined) {
|
|
140
|
+
next.web = { ...(next.web ?? DEFAULT_CONFIG.web), port: normalizeWebPort(webPatch.port) };
|
|
141
|
+
}
|
|
142
|
+
ensureConfigDir();
|
|
143
|
+
const tempPath = `${CONFIG_PATH}.${process.pid}.tmp`;
|
|
144
|
+
writeFileSync(tempPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
145
|
+
renameSync(tempPath, CONFIG_PATH);
|
|
146
|
+
return loadConfig();
|
|
147
|
+
}
|
|
148
|
+
function normalizeWebPort(value) {
|
|
149
|
+
const parsed = Number(value ?? DEFAULT_CONFIG.web.port);
|
|
150
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65_535)
|
|
151
|
+
return DEFAULT_CONFIG.web.port;
|
|
152
|
+
return parsed;
|
|
153
|
+
}
|
|
109
154
|
export const config = loadConfig();
|
package/dist/context.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ export declare function normalizeBuiltinSessionId(value: string): string;
|
|
|
66
66
|
export declare function defaultBuiltinSessionId(cwd?: string): string;
|
|
67
67
|
export declare function newBuiltinSessionId(now?: Date, suffix?: string): string;
|
|
68
68
|
export declare function getBuiltinContextSession(sessionId: string, contextDir?: string): BuiltinContextSessionInfo | null;
|
|
69
|
+
export declare function readBuiltinContextState(sessionId: string, contextDir?: string): BuiltinContextState | null;
|
|
69
70
|
export declare function listBuiltinContextSessions(contextDir?: string): BuiltinContextSessionInfo[];
|
|
70
71
|
export declare function latestBuiltinSessionForCwd(cwd: string, contextDir?: string): BuiltinContextSessionInfo | null;
|
|
71
72
|
/**
|
package/dist/context.js
CHANGED
|
@@ -135,6 +135,18 @@ function readSessionInfo(contextDir, sessionId) {
|
|
|
135
135
|
export function getBuiltinContextSession(sessionId, contextDir = DEFAULT_BUILTIN_CONTEXT_DIR) {
|
|
136
136
|
return readSessionInfo(contextDir, sessionId);
|
|
137
137
|
}
|
|
138
|
+
export function readBuiltinContextState(sessionId, contextDir = DEFAULT_BUILTIN_CONTEXT_DIR) {
|
|
139
|
+
const normalizedSessionId = normalizeBuiltinSessionId(sessionId);
|
|
140
|
+
const filePath = contextFilePath(contextDir, normalizedSessionId);
|
|
141
|
+
if (!existsSync(filePath))
|
|
142
|
+
return null;
|
|
143
|
+
try {
|
|
144
|
+
return normalizeState(JSON.parse(readFileSync(filePath, "utf8")), normalizedSessionId);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
138
150
|
export function listBuiltinContextSessions(contextDir = DEFAULT_BUILTIN_CONTEXT_DIR) {
|
|
139
151
|
if (!existsSync(contextDir))
|
|
140
152
|
return [];
|
package/dist/index.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface ChatSessionConfig {
|
|
|
39
39
|
effort?: string;
|
|
40
40
|
/** Maximum output tokens for the main conversation; unset uses the Provider default. */
|
|
41
41
|
maxOutputTokens?: number;
|
|
42
|
+
/** Override whether the main conversation uses streaming requests. */
|
|
43
|
+
streaming?: boolean;
|
|
42
44
|
}
|
|
43
45
|
export interface ChatSessionOptions {
|
|
44
46
|
/** Session working directory. */
|
|
@@ -141,6 +143,7 @@ export declare class ChatSession {
|
|
|
141
143
|
private maxSteps?;
|
|
142
144
|
private effort;
|
|
143
145
|
private maxOutputTokens?;
|
|
146
|
+
private streaming;
|
|
144
147
|
private permissionMode;
|
|
145
148
|
private permissionResolver?;
|
|
146
149
|
private permissionGate;
|
package/dist/index.js
CHANGED
|
@@ -249,6 +249,7 @@ export class ChatSession {
|
|
|
249
249
|
maxSteps;
|
|
250
250
|
effort;
|
|
251
251
|
maxOutputTokens;
|
|
252
|
+
streaming;
|
|
252
253
|
permissionMode;
|
|
253
254
|
permissionResolver;
|
|
254
255
|
permissionGate;
|
|
@@ -316,6 +317,7 @@ export class ChatSession {
|
|
|
316
317
|
this.provider = normalizeDeepCccProvider(overrides.provider ?? appConfig.provider);
|
|
317
318
|
this.effort = (overrides.effort ?? appConfig.effort ?? "").trim();
|
|
318
319
|
this.maxOutputTokens = normalizeMaxOutputTokens(overrides.maxOutputTokens ?? appConfig.maxOutputTokens);
|
|
320
|
+
this.streaming = overrides.streaming ?? appConfig.streaming;
|
|
319
321
|
this.apiKey = apiKey;
|
|
320
322
|
this.baseURL = baseURL;
|
|
321
323
|
const provider = this.provider === "anthropic"
|
|
@@ -470,7 +472,7 @@ export class ChatSession {
|
|
|
470
472
|
messages: attemptMessages,
|
|
471
473
|
};
|
|
472
474
|
let stream;
|
|
473
|
-
if (
|
|
475
|
+
if (this.streaming) {
|
|
474
476
|
const result = streamText(generationOptions);
|
|
475
477
|
stream = result.fullStream ?? textStreamToFullStream(result.textStream);
|
|
476
478
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type StartDeepCccWebOptions } from "./web-server.js";
|
|
2
|
+
export interface ParsedWebEntryArgs extends Pick<StartDeepCccWebOptions, "port" | "openBrowser" | "reuseExisting"> {
|
|
3
|
+
}
|
|
4
|
+
export declare function isLegacyCliInvocation(args: string[]): boolean;
|
|
5
|
+
export declare function parseWebEntryArgs(rawArgs: string[]): ParsedWebEntryArgs;
|
|
6
|
+
export declare function printWebHelp(): void;
|
|
7
|
+
export declare function runWebEntry(rawArgs?: string[]): Promise<void>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { startDeepCccWebServer } from "./web-server.js";
|
|
4
|
+
const WEB_FLAGS = new Set(["web", "--reuse-existing", "--port", "--no-open", "--help", "-h"]);
|
|
5
|
+
export function isLegacyCliInvocation(args) {
|
|
6
|
+
if (!args.length)
|
|
7
|
+
return false;
|
|
8
|
+
if (args[0] === "web")
|
|
9
|
+
return false;
|
|
10
|
+
return !WEB_FLAGS.has(args[0]);
|
|
11
|
+
}
|
|
12
|
+
export function parseWebEntryArgs(rawArgs) {
|
|
13
|
+
const args = rawArgs[0] === "web" ? rawArgs.slice(1) : [...rawArgs];
|
|
14
|
+
if (args.includes("--help") || args.includes("-h"))
|
|
15
|
+
return {};
|
|
16
|
+
let port;
|
|
17
|
+
let openBrowser;
|
|
18
|
+
let reuseExisting = false;
|
|
19
|
+
for (let index = 0; index < args.length; index++) {
|
|
20
|
+
const arg = args[index];
|
|
21
|
+
if (arg === "--reuse-existing")
|
|
22
|
+
reuseExisting = true;
|
|
23
|
+
else if (arg === "--no-open")
|
|
24
|
+
openBrowser = false;
|
|
25
|
+
else if (arg === "--port") {
|
|
26
|
+
const value = Number(args[++index]);
|
|
27
|
+
if (!Number.isInteger(value) || value < 1 || value > 65_535)
|
|
28
|
+
throw new Error("--port must be an integer between 1 and 65535");
|
|
29
|
+
port = value;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
throw new Error(`Unknown DeepCCC Web option: ${arg}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
reuseExisting,
|
|
37
|
+
...(port ? { port } : {}),
|
|
38
|
+
...(openBrowser === false ? { openBrowser: false } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function printWebHelp() {
|
|
42
|
+
console.log([
|
|
43
|
+
"DeepCCC Web UI",
|
|
44
|
+
"",
|
|
45
|
+
"Usage: deepccc [web] [options]",
|
|
46
|
+
"",
|
|
47
|
+
" --reuse-existing Reuse an existing verified DeepCCC Web instance",
|
|
48
|
+
" --port <port> Override config.web.port for this run",
|
|
49
|
+
" --no-open Do not open the browser automatically",
|
|
50
|
+
" --help Show this help",
|
|
51
|
+
"",
|
|
52
|
+
"Terminal CLI: deepccc-cli [options]",
|
|
53
|
+
].join("\n"));
|
|
54
|
+
}
|
|
55
|
+
export async function runWebEntry(rawArgs = process.argv.slice(2)) {
|
|
56
|
+
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
57
|
+
printWebHelp();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const options = parseWebEntryArgs(rawArgs);
|
|
61
|
+
const handle = await startDeepCccWebServer(options);
|
|
62
|
+
console.log(`DeepCCC Web UI: ${handle.url}${handle.reused ? " (reused existing server)" : ""}`);
|
|
63
|
+
}
|
|
64
|
+
function isDirectInvocation() {
|
|
65
|
+
return resolve(fileURLToPath(import.meta.url)) === resolve(process.argv[1] ?? "");
|
|
66
|
+
}
|
|
67
|
+
if (isDirectInvocation()) {
|
|
68
|
+
runWebEntry().catch((err) => {
|
|
69
|
+
console.error(`DeepCCC Web startup failed: ${err.message}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const DEEPCCC_WEB_PAGE = "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>DeepCCC</title>\n<style>\n:root{color-scheme:light;--bg:#f5f7fb;--panel:#fff;--panel2:#fff;--soft:#f8f9fc;--line:#e3e7ef;--text:#182033;--muted:#737d91;--accent:#6758e8;--accent-soft:#eeeaff;--accent2:#23825d;--danger:#c2415a;--warning:#b7791f;--shadow:0 8px 28px rgba(26,35,58,.08)}\n*{box-sizing:border-box}html,body{height:100%}body{margin:0;background:var(--bg);color:var(--text);font:14px/1.5 Inter,-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"Microsoft YaHei\",sans-serif;overflow:hidden}button,input,select,textarea{font:inherit}button{cursor:pointer}.app{height:100%;display:grid;grid-template-columns:286px minmax(0,1fr)}\n.sidebar{min-width:0;background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column}.brand{height:70px;display:flex;align-items:center;gap:12px;padding:0 18px;border-bottom:1px solid var(--line)}.mark{width:36px;height:36px;border-radius:12px;display:grid;place-items:center;background:var(--accent);color:#fff;box-shadow:0 9px 25px rgba(103,88,232,.25);font-weight:900}.brand-copy strong{display:block;font-size:16px;letter-spacing:.01em}.brand-copy span{font-size:10px;color:var(--muted);letter-spacing:.13em}.sidebar-actions{display:flex;gap:8px;padding:14px}.primary,.ghost,.icon{border:1px solid var(--line);border-radius:10px;color:var(--text);background:var(--panel);font-weight:700}.primary{background:var(--accent);border-color:var(--accent);color:#fff;box-shadow:0 8px 22px rgba(103,88,232,.22)}.sidebar-actions .primary{flex:1;padding:10px}.icon{width:40px;padding:0;font-size:17px}.ghost{padding:9px 12px}.primary:hover,.ghost:hover,.icon:hover{filter:brightness(1.03)}\n.session-list{flex:1;overflow:auto;padding:0 10px 14px}.session-empty{padding:38px 18px;color:var(--muted);text-align:center}.session-item{width:100%;display:block;padding:12px;border:1px solid transparent;border-radius:12px;background:transparent;color:var(--text);text-align:left;margin:3px 0}.session-item:hover{background:var(--panel2)}.session-item.active{background:linear-gradient(135deg,rgba(124,105,241,.2),rgba(93,74,207,.08));border-color:rgba(139,124,246,.35)}.session-title{display:flex;align-items:center;gap:8px;font-weight:750}.status-dot{width:7px;height:7px;border-radius:50%;background:#626a78}.status-dot.running{background:var(--accent2);box-shadow:0 0 0 4px rgba(94,228,177,.1)}.session-meta{margin-top:5px;color:var(--muted);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sidebar-foot{padding:12px 16px;border-top:1px solid var(--line);color:var(--muted);font-size:10px;display:flex;justify-content:space-between}\n.main{min-width:0;height:100%;display:flex;flex-direction:column}.topbar{height:70px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 22px;border-bottom:1px solid var(--line);background:rgba(12,14,19,.7);backdrop-filter:blur(18px)}.session-heading{min-width:0}.session-heading h1{font-size:16px;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.session-heading p{font-size:11px;color:var(--muted);margin:3px 0 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.top-actions{display:flex;gap:8px}.top-actions button{height:38px}.content{flex:1;min-height:0;overflow-y:auto;scroll-behavior:smooth}.welcome{height:100%;display:grid;place-items:center;padding:30px}.welcome-card{max-width:650px;text-align:center}.welcome-mark{width:72px;height:72px;margin:0 auto 20px;border-radius:24px;display:grid;place-items:center;background:linear-gradient(135deg,#7764eb,#b45fd7);font-size:29px;box-shadow:0 20px 50px rgba(119,100,235,.28)}.welcome h2{font-size:28px;margin:0 0 10px;letter-spacing:-.02em}.welcome p{color:var(--muted);margin:0 auto;max-width:480px}.hint-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:28px}.hint{padding:14px;border:1px solid var(--line);border-radius:13px;background:rgba(24,28,37,.65);font-size:12px;color:var(--muted)}\n.thread{width:min(900px,calc(100% - 36px));margin:0 auto;padding:28px 0 150px}.empty-thread{margin:80px auto 0;max-width:520px;padding:28px;border:1px dashed #353c4a;border-radius:18px;background:rgba(20,23,31,.55);text-align:center}.empty-thread strong{display:block;font-size:18px;margin-bottom:7px}.empty-thread span{color:var(--muted);font-size:12px}.message{display:grid;grid-template-columns:30px minmax(0,1fr);gap:12px;margin:0 0 22px}.avatar{width:30px;height:30px;border-radius:10px;display:grid;place-items:center;background:var(--soft);font-size:11px;font-weight:900}.message.user .avatar{background:#304058}.message.assistant .avatar{background:linear-gradient(135deg,#7363e8,#9a61dc)}.bubble{min-width:0;padding:2px 0;color:#dfe3eb}.bubble .role{font-size:11px;color:var(--muted);font-weight:800;margin-bottom:7px}.markdown{font-size:14px;line-height:1.75;overflow-wrap:anywhere}.markdown p{margin:.35em 0}.markdown pre{padding:14px;border:1px solid var(--line);border-radius:11px;background:#0a0c11;overflow:auto;font:12px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace}.markdown code{padding:2px 5px;border-radius:5px;background:var(--soft);font:12px ui-monospace,SFMono-Regular,Consolas,monospace}.markdown pre code{padding:0;background:transparent}.tool-card{margin:8px 0;border:1px solid var(--line);border-radius:10px;background:var(--panel);overflow:hidden}.tool-card summary{padding:9px 11px;color:#bbc2d0;font-size:12px;font-weight:700}.tool-card pre{margin:0;border:0;border-top:1px solid var(--line);border-radius:0;max-height:260px}.run-state{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:12px;margin:8px 0 20px}.pulse{width:8px;height:8px;border-radius:50%;background:var(--accent2);animation:pulse 1.4s infinite}@keyframes pulse{50%{opacity:.35;transform:scale(.72)}}.workspace-warning{width:min(900px,calc(100% - 36px));margin:14px auto 0;padding:9px 12px;border:1px solid rgba(242,184,75,.3);background:rgba(242,184,75,.08);border-radius:10px;color:#e8c879;font-size:11px}\n.composer-shell{position:absolute;left:286px;right:0;bottom:0;padding:0 20px 18px;background:linear-gradient(transparent,var(--bg) 32%)}.composer{width:min(900px,100%);margin:auto;border:1px solid #343b49;border-radius:17px;background:rgba(25,29,38,.96);box-shadow:var(--shadow);overflow:hidden}.composer textarea{width:100%;min-height:68px;max-height:180px;resize:none;border:0;outline:0;padding:15px 16px 8px;background:transparent;color:var(--text)}.composer textarea::placeholder{color:#737b8b}.composer-bar{display:flex;align-items:center;gap:8px;padding:7px 9px}.composer-bar input,.composer-bar select{min-width:0;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--muted);padding:7px 9px;font-size:11px}.composer-bar input{width:180px}.composer-bar select{width:104px}.send{margin-left:auto;width:36px;height:36px;border:0;border-radius:10px;background:var(--accent);color:#fff;font-size:17px}.stop{margin-left:auto;height:36px;border:1px solid rgba(255,111,137,.4);border-radius:10px;background:rgba(255,111,137,.12);color:#ff91a5;padding:0 13px;font-weight:700}.composer-disabled{opacity:.5;pointer-events:none}\n.modal-backdrop{position:fixed;inset:0;z-index:30;display:grid;place-items:center;padding:20px;background:rgba(4,5,8,.66);backdrop-filter:blur(7px)}.modal-backdrop.hidden{display:none}.modal{width:min(560px,100%);max-height:calc(100vh - 40px);overflow:auto;border:1px solid #353c4a;border-radius:18px;background:#171a22;box-shadow:0 30px 100px rgba(0,0,0,.55);padding:22px}.modal h2{margin:0 0 5px;font-size:20px}.modal>.subtitle{margin:0 0 20px;color:var(--muted);font-size:12px}.field{display:grid;gap:7px;margin:13px 0}.field label{font-size:11px;color:#b7becb;font-weight:800}.field input,.field select{width:100%;border:1px solid var(--line);border-radius:10px;background:#10131a;color:var(--text);padding:10px 11px;outline:none}.field input:focus,.field select:focus{border-color:var(--accent)}.row{display:grid;grid-template-columns:1fr 1fr;gap:10px}.check{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:12px}.modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.danger{border:1px solid rgba(255,111,137,.35);background:rgba(255,111,137,.1);color:#ff849b;border-radius:10px;padding:9px 12px;font-weight:700}.approval{width:min(620px,100%)}.approval-badge{display:inline-flex;padding:5px 8px;border-radius:999px;background:rgba(242,184,75,.13);color:#f1c565;font-size:10px;font-weight:900}.approval pre{white-space:pre-wrap;overflow-wrap:anywhere;padding:13px;border:1px solid var(--line);border-radius:10px;background:#0d0f15;color:#dce0e9}.approval-actions{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.toast{position:fixed;right:20px;top:20px;z-index:50;max-width:420px;padding:11px 14px;border-radius:10px;background:#283143;border:1px solid #3b465b;box-shadow:var(--shadow)}.toast.error{background:#602536;border-color:#913a52}.hidden{display:none!important}\n.topbar{background:rgba(255,255,255,.86)}.welcome-mark{background:var(--accent);color:#fff}.hint,.empty-thread{background:var(--panel);border-color:var(--line)}.session-item.active{background:var(--accent-soft);border-color:#d8d1ff}.status-dot{background:#a3aabd}.status-dot.running{background:var(--accent2);box-shadow:0 0 0 4px rgba(35,130,93,.1)}.message.user .avatar{background:#e8edf6}.message.assistant .avatar{background:var(--accent);color:#fff}.bubble{color:var(--text)}.markdown pre,.tool-card pre{background:var(--soft);color:var(--text)}.tool-card summary{color:var(--muted)}.composer{border-color:var(--line);background:rgba(255,255,255,.98)}.composer textarea::placeholder{color:#9aa3b4}.composer-bar input,.composer-bar select{background:var(--soft)}.modal-backdrop{background:rgba(24,32,51,.36)}.modal{border-color:var(--line);background:var(--panel);box-shadow:0 28px 90px rgba(35,43,65,.24)}.field label{color:var(--text)}.field input,.field select{background:var(--soft);color:var(--text)}.danger{border-color:#f3c4ce;background:#fff1f3;color:var(--danger)}.toast{background:#253047;color:#fff}.toast.error{background:#9f2942}.approval-card{margin:16px 0;padding:14px;border:1px solid #ead49b;border-radius:13px;background:#fffaf0;box-shadow:0 5px 18px rgba(94,70,15,.06)}.approval-card.resolved{border-color:var(--line);background:var(--soft)}.approval-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px}.approval-head strong{font-size:12px}.approval-status{padding:3px 7px;border-radius:999px;background:#fff0c2;color:#8a6110;font-size:10px;font-weight:800}.approval-card.resolved .approval-status{background:#e5f5ee;color:var(--accent2)}.approval-detail{color:var(--muted);font-size:11px;margin-bottom:8px}.approval-command{margin:0;padding:10px;border:1px solid var(--line);border-radius:9px;background:#fff;white-space:pre-wrap;overflow-wrap:anywhere;font:12px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace}.approval-inline-actions{display:flex;flex-wrap:wrap;gap:7px;margin-top:10px}.approval-inline-actions button{padding:7px 10px;font-size:11px}\n.sidebar{height:100%;overflow:hidden}.main{min-height:0!important;overflow:hidden}\n@media(max-width:760px){.app{grid-template-columns:78px minmax(0,1fr)}.brand{padding:0;justify-content:center}.brand-copy,.sidebar-actions .primary,.session-item>div:not(.session-title),.session-title span:last-child,.sidebar-foot{display:none}.sidebar-actions{justify-content:center;padding:12px 8px}.session-item{padding:11px;display:grid;place-items:center}.composer-shell{left:78px}.hint-grid{grid-template-columns:1fr}.topbar{padding:0 14px}.thread{width:calc(100% - 24px)}.row{grid-template-columns:1fr}.approval-inline-actions{display:grid;grid-template-columns:1fr 1fr}}\n</style>\n</head>\n<body>\n<div class=\"app\">\n <aside class=\"sidebar\">\n <div class=\"brand\"><div class=\"mark\">D</div><div class=\"brand-copy\"><strong>DeepCCC</strong><span>LOCAL AGENT</span></div></div>\n <div class=\"sidebar-actions\"><button id=\"new-session\" class=\"primary\">\uFF0B \u65B0\u5EFA\u4F1A\u8BDD</button><button id=\"open-settings\" class=\"icon\" title=\"API \u8BBE\u7F6E\">\u2699</button></div>\n <div id=\"session-list\" class=\"session-list\"></div>\n <div class=\"sidebar-foot\"><span>127.0.0.1</span><span id=\"server-port\">28080</span></div>\n </aside>\n <main class=\"main\">\n <header class=\"topbar\"><div class=\"session-heading\"><h1 id=\"session-title\">DeepCCC</h1><p id=\"session-cwd\">\u9009\u62E9\u6216\u521B\u5EFA\u4E00\u4E2A\u4F1A\u8BDD</p></div><div class=\"top-actions\"><button id=\"rename-session\" class=\"ghost hidden\">\u91CD\u547D\u540D</button><button id=\"delete-session\" class=\"danger hidden\">\u5220\u9664</button></div></header>\n <div id=\"warning\" class=\"workspace-warning hidden\"></div>\n <section id=\"content\" class=\"content\"><div class=\"welcome\"><div class=\"welcome-card\"><div class=\"welcome-mark\">\u2726</div><h2>\u628A\u5DE5\u4F5C\u4EA4\u7ED9 DeepCCC</h2><p>\u5728\u6D4F\u89C8\u5668\u91CC\u7BA1\u7406\u591A\u4E2A\u672C\u5730 Coding Agent \u4F1A\u8BDD\uFF0C\u5207\u6362\u6A21\u578B\u4E0E\u63A8\u7406\u5F3A\u5EA6\uFF0C\u5E76\u5B9E\u65F6\u5BA1\u6279\u9AD8\u98CE\u9669\u64CD\u4F5C\u3002</p><div class=\"hint-grid\"><div class=\"hint\">\u591A\u4F1A\u8BDD\u5E76\u884C\u8FD0\u884C</div><div class=\"hint\">\u672C\u5730\u6587\u4EF6\u4E0E\u5386\u53F2</div><div class=\"hint\">API \u81EA\u7531\u914D\u7F6E</div></div></div></div></section>\n </main>\n</div>\n<div id=\"composer-shell\" class=\"composer-shell hidden\"><div class=\"composer\"><textarea id=\"prompt\" placeholder=\"\u63CF\u8FF0\u4F60\u60F3\u5B8C\u6210\u7684\u4EFB\u52A1\u2026\"></textarea><div class=\"composer-bar\"><input id=\"session-model\" list=\"recent-models\" aria-label=\"\u6A21\u578B\"><datalist id=\"recent-models\"></datalist><select id=\"session-effort\" aria-label=\"\u63A8\u7406\u5F3A\u5EA6\"><option value=\"\">\u9ED8\u8BA4 effort</option><option>none</option><option>minimal</option><option>low</option><option>medium</option><option>high</option><option>xhigh</option><option>max</option></select><button id=\"send\" class=\"send\" title=\"\u53D1\u9001\">\u2191</button><button id=\"stop\" class=\"stop hidden\">\u25A0 \u505C\u6B62</button></div></div></div>\n\n<div id=\"new-modal\" class=\"modal-backdrop hidden\"><form id=\"new-form\" class=\"modal\"><h2>\u65B0\u5EFA Agent \u4F1A\u8BDD</h2><p class=\"subtitle\">\u6BCF\u4E2A\u4F1A\u8BDD\u62E5\u6709\u72EC\u7ACB\u4E0A\u4E0B\u6587\uFF1B\u540C\u76EE\u5F55\u4F1A\u8BDD\u53EF\u4EE5\u5E76\u53D1\uFF0C\u4F46\u6587\u4EF6\u4FEE\u6539\u53EF\u80FD\u51B2\u7A81\u3002</p><div class=\"field\"><label>\u5DE5\u4F5C\u76EE\u5F55</label><input id=\"new-cwd\" required></div><div class=\"field\"><label>\u4F1A\u8BDD\u540D\u79F0\uFF08\u53EF\u9009\uFF09</label><input id=\"new-title\" placeholder=\"\u9ED8\u8BA4\u4F7F\u7528\u76EE\u5F55\u540D\"></div><div class=\"row\"><div class=\"field\"><label>\u6A21\u578B</label><input id=\"new-model\" list=\"recent-models\"></div><div class=\"field\"><label>Effort</label><select id=\"new-effort\"><option value=\"\">\u9ED8\u8BA4</option><option>none</option><option>minimal</option><option>low</option><option>medium</option><option>high</option><option>xhigh</option><option>max</option></select></div></div><div class=\"modal-actions\"><button type=\"button\" data-close=\"new-modal\" class=\"ghost\">\u53D6\u6D88</button><button class=\"primary\" type=\"submit\">\u521B\u5EFA\u4F1A\u8BDD</button></div></form></div>\n\n<div id=\"settings-modal\" class=\"modal-backdrop hidden\"><form id=\"settings-form\" class=\"modal\"><h2>API \u4E0E Web \u8BBE\u7F6E</h2><p class=\"subtitle\">\u5355\u4E00 API \u914D\u7F6E\u4F5C\u4E3A\u6240\u6709\u65B0\u4F1A\u8BDD\u7684\u9ED8\u8BA4\u503C\uFF1B\u4F1A\u8BDD\u53EF\u5355\u72EC\u8986\u76D6\u6A21\u578B\u548C effort\u3002</p><div class=\"row\"><div class=\"field\"><label>API \u534F\u8BAE</label><select id=\"cfg-provider\"><option value=\"openai\">OpenAI-compatible</option><option value=\"anthropic\">Anthropic Messages</option></select></div><div class=\"field\"><label>API Key</label><input id=\"cfg-api-key\" type=\"password\" placeholder=\"\u7559\u7A7A\u4FDD\u7559\u5F53\u524D Key\"></div></div><div class=\"field\"><label>Base URL</label><input id=\"cfg-base-url\" required></div><div class=\"row\"><div class=\"field\"><label>\u9ED8\u8BA4\u6A21\u578B</label><input id=\"cfg-model\" required></div><div class=\"field\"><label>\u5B50\u6A21\u578B\uFF08\u53EF\u9009\uFF09</label><input id=\"cfg-sub-model\"></div></div><div class=\"row\"><div class=\"field\"><label>\u9ED8\u8BA4 Effort</label><select id=\"cfg-effort\"><option value=\"\">\u4E0D\u4F20</option><option>none</option><option>minimal</option><option>low</option><option>medium</option><option>high</option><option>xhigh</option><option>max</option></select></div><div class=\"field\"><label>Context Window</label><input id=\"cfg-context\" type=\"number\" min=\"1\"></div></div><div class=\"row\"><div class=\"field\"><label>Web \u7AEF\u53E3</label><input id=\"cfg-port\" type=\"number\" min=\"1\" max=\"65535\"></div><div class=\"field\"><label>Max Output Tokens\uFF08\u53EF\u9009\uFF09</label><input id=\"cfg-max-output\" type=\"number\" min=\"1\"></div></div><label class=\"check\"><input id=\"cfg-streaming\" type=\"checkbox\">\u542F\u7528\u6D41\u5F0F\u8BF7\u6C42</label><label class=\"check\"><input id=\"cfg-open\" type=\"checkbox\">\u8FD0\u884C deepccc web \u65F6\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668</label><div id=\"key-state\" class=\"subtitle\"></div><div class=\"modal-actions\"><button type=\"button\" data-close=\"settings-modal\" class=\"ghost\">\u53D6\u6D88</button><button class=\"primary\" type=\"submit\">\u4FDD\u5B58\u8BBE\u7F6E</button></div></form></div>\n\n<div id=\"rename-modal\" class=\"modal-backdrop hidden\"><form id=\"rename-form\" class=\"modal\"><h2>\u91CD\u547D\u540D\u4F1A\u8BDD</h2><div class=\"field\"><label>\u540D\u79F0</label><input id=\"rename-title\" required maxlength=\"120\"></div><div class=\"modal-actions\"><button type=\"button\" data-close=\"rename-modal\" class=\"ghost\">\u53D6\u6D88</button><button class=\"primary\">\u4FDD\u5B58</button></div></form></div>\n\n<div id=\"toast\" class=\"toast hidden\"></div>\n<script>\n(function(){\n var state={sessions:[],selectedId:null,detail:null,config:null,eventSource:null,reloadTimer:null};\n var $=function(id){return document.getElementById(id);};\n async function api(path,options){var response=await fetch(path,Object.assign({headers:{'content-type':'application/json; charset=utf-8'}},options||{}));var data=await response.json().catch(function(){return {error:'\u54CD\u5E94\u4E0D\u662F\u6709\u6548 JSON'};});if(!response.ok)throw new Error(data.error||('HTTP '+response.status));return data;}\n function toast(message,error){var el=$('toast');el.textContent=message;el.className='toast'+(error?' error':'');clearTimeout(toast.timer);toast.timer=setTimeout(function(){el.className='toast hidden';},3500);}\n function openModal(id){$(id).classList.remove('hidden');}function closeModal(id){$(id).classList.add('hidden');}\n function escapeHtml(value){return String(value||'').replace(/[&<>\"']/g,function(ch){return {'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[ch];});}\n function markdown(value){var text=escapeHtml(value);var blocks=[];var tick=String.fromCharCode(96);var fence=tick+tick+tick;var pattern=new RegExp(fence+'([a-z0-9_-]*)\\\\n([\\\\s\\\\S]*?)'+fence,'gi');text=text.replace(pattern,function(_,lang,code){blocks.push('<pre><code data-lang=\"'+lang+'\">'+code+'</code></pre>');return '@@BLOCK'+(blocks.length-1)+'@@';});text=text.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>').replace(new RegExp(tick+'([^'+tick+']+)'+tick,'g'),'<code>$1</code>').replace(/\\n/g,'<br>');text=text.replace(/@@BLOCK(\\d+)@@/g,function(_,index){return blocks[Number(index)];});return text;}\n function statusText(status){return status==='running'?'\u6267\u884C\u4E2D':'\u7A7A\u95F2';}\n function renderSessions(){var root=$('session-list');root.innerHTML='';if(!state.sessions.length){root.innerHTML='<div class=\"session-empty\">\u8FD8\u6CA1\u6709\u4F1A\u8BDD<br>\u70B9\u51FB\u4E0A\u65B9\u6309\u94AE\u5F00\u59CB</div>';return;}state.sessions.forEach(function(session){var button=document.createElement('button');button.className='session-item'+(session.sessionId===state.selectedId?' active':'');button.innerHTML='<div class=\"session-title\"><i class=\"status-dot '+session.status+'\"></i><span>'+escapeHtml(session.title)+'</span></div><div class=\"session-meta\">'+escapeHtml(session.model||'\u9ED8\u8BA4\u6A21\u578B')+' \u00B7 '+escapeHtml(session.effort||'\u9ED8\u8BA4 effort')+'</div><div class=\"session-meta\">'+escapeHtml(session.cwd)+'</div>';button.addEventListener('click',function(){selectSession(session.sessionId);});root.appendChild(button);});}\n function messageHtml(role,content){return '<article class=\"message '+role+'\"><div class=\"avatar\">'+(role==='user'?'\u4F60':'D')+'</div><div class=\"bubble\"><div class=\"role\">'+(role==='user'?'YOU':'DEEPCCC')+'</div><div class=\"markdown\">'+markdown(content)+'</div></div></article>';}\n function toolHtml(event){var data=event.data||{};var item=data.type==='agent'?data.data:data;return '<details class=\"tool-card\"><summary>'+escapeHtml(item.type==='tool_use'?'\u8C03\u7528\u5DE5\u5177 \u00B7 '+(item.name||'tool'):'\u5DE5\u5177\u7ED3\u679C \u00B7 '+(item.name||item.tool_use_id||'tool'))+'</summary><pre>'+escapeHtml(JSON.stringify(item.type==='tool_use'?item.input:item.content,null,2))+'</pre></details>';}\n function approvalAnswerLabel(answer){var labels={allow:'\u5DF2\u5141\u8BB8\u4E00\u6B21','allow-session':'\u672C\u4F1A\u8BDD\u5DF2\u5141\u8BB8','allow-always':'\u5DF2\u6C38\u4E45\u5141\u8BB8',deny:'\u5DF2\u62D2\u7EDD'};return labels[answer]||'\u7B49\u5F85\u51B3\u5B9A';}\n function approvalHtml(approval){var resolved=approval.status==='resolved';var html='<section class=\"approval-card'+(resolved?' resolved':'')+'\"><div class=\"approval-head\"><strong>\u26A0 \u9AD8\u98CE\u9669\u64CD\u4F5C\u5BA1\u6279</strong><span class=\"approval-status\">'+escapeHtml(resolved?approvalAnswerLabel(approval.answer):'\u7B49\u5F85\u6279\u51C6')+'</span></div><div class=\"approval-detail\">'+escapeHtml(approval.detail||('\u5DE5\u5177\uFF1A'+approval.tool))+'</div><pre class=\"approval-command\">'+escapeHtml(approval.action)+'</pre>';if(!resolved)html+='<div class=\"approval-inline-actions\"><button class=\"danger\" data-approval-answer=\"deny\" data-approval-id=\"'+escapeHtml(approval.approvalId)+'\">\u62D2\u7EDD</button><button class=\"ghost\" data-approval-answer=\"allow\" data-approval-id=\"'+escapeHtml(approval.approvalId)+'\">\u5141\u8BB8\u4E00\u6B21</button><button class=\"ghost\" data-approval-answer=\"allow-session\" data-approval-id=\"'+escapeHtml(approval.approvalId)+'\">\u672C\u4F1A\u8BDD\u5141\u8BB8</button><button class=\"primary\" data-approval-answer=\"allow-always\" data-approval-id=\"'+escapeHtml(approval.approvalId)+'\">\u6C38\u4E45\u5141\u8BB8</button></div>';return html+'</section>';}\n function renderDetail(){var detail=state.detail;if(!detail)return;$('session-title').textContent=detail.title;$('session-cwd').textContent=detail.cwd+' \u00B7 '+statusText(detail.status);$('rename-session').classList.remove('hidden');$('delete-session').classList.remove('hidden');$('composer-shell').classList.remove('hidden');$('session-model').value=detail.model||'';$('session-effort').value=detail.effort||'';var html='<div class=\"thread\">';if(!(detail.messages||[]).length&&detail.status!=='running')html+='<div class=\"empty-thread\"><strong>\u51C6\u5907\u5F00\u59CB\u8FD9\u4E2A\u4F1A\u8BDD</strong><span>\u5728\u4E0B\u65B9\u8F93\u5165\u4EFB\u52A1\u3002DeepCCC \u4F1A\u8BFB\u53D6\u5F53\u524D\u9879\u76EE\u6307\u4EE4\u3001\u8C03\u7528\u5DE5\u5177\u5E76\u6301\u7EED\u4FDD\u5B58\u4E0A\u4E0B\u6587\u3002</span></div>';(detail.messages||[]).forEach(function(message){html+=messageHtml(message.role,message.content);});if(detail.status==='running'){var live=(detail.events||[]).slice().reverse().find(function(event){return event.type==='agent'&&event.data&&event.data.type==='text';});var current=(detail.events||[]).filter(function(event){return event.type==='agent'&&event.data&&(event.data.type==='tool_use'||event.data.type==='tool_result');});current.forEach(function(event){html+=toolHtml(event);});if(live)html+=messageHtml('assistant',live.data.accumulated||live.data.text||'');}var approvals=(detail.approvals||[]).slice();if(detail.pendingApproval&&!approvals.some(function(item){return item.approvalId===detail.pendingApproval.approvalId;}))approvals.push(Object.assign({status:'pending'},detail.pendingApproval));approvals.forEach(function(approval){html+=approvalHtml(approval);});if(detail.status==='running')html+='<div class=\"run-state\"><i class=\"pulse\"></i>Agent \u6B63\u5728\u5DE5\u4F5C</div>';html+='</div>';$('content').innerHTML=html;$('send').classList.toggle('hidden',detail.status==='running');$('stop').classList.toggle('hidden',detail.status!=='running');$('prompt').disabled=detail.status==='running';$('session-model').disabled=detail.status==='running';$('session-effort').disabled=detail.status==='running';renderWarning();requestAnimationFrame(function(){$('content').scrollTop=$('content').scrollHeight;});}\n function renderWarning(){if(!state.detail)return;var conflicts=state.sessions.filter(function(item){return item.sessionId!==state.detail.sessionId&&item.status==='running'&&item.cwd===state.detail.cwd;});var el=$('warning');if(conflicts.length){el.textContent='\u26A0 \u5F53\u524D\u76EE\u5F55\u8FD8\u6709 '+conflicts.length+' \u4E2A Agent \u4F1A\u8BDD\u6B63\u5728\u8FD0\u884C\u3002\u591A\u4E2A\u4F1A\u8BDD\u76F4\u63A5\u4FEE\u6539\u540C\u4E00\u76EE\u5F55\uFF0C\u53EF\u80FD\u4EA7\u751F\u8986\u76D6\u6216\u51B2\u7A81\u3002';el.classList.remove('hidden');}else el.classList.add('hidden');}\n async function loadSessions(){var result=await api('/api/sessions');state.sessions=result.sessions||[];renderSessions();if(state.selectedId&&!state.sessions.some(function(item){return item.sessionId===state.selectedId;}))state.selectedId=null;}\n async function loadDetail(){if(!state.selectedId)return;var result=await api('/api/sessions/'+encodeURIComponent(state.selectedId));state.detail=result.session;renderDetail();await loadSessions();}\n function scheduleDetail(){clearTimeout(state.reloadTimer);state.reloadTimer=setTimeout(function(){loadDetail().catch(function(error){toast(error.message,true);});},120);}\n async function selectSession(id){state.selectedId=id;renderSessions();if(state.eventSource)state.eventSource.close();state.eventSource=new EventSource('/api/sessions/'+encodeURIComponent(id)+'/events');state.eventSource.onmessage=scheduleDetail;await loadDetail();}\n async function loadConfig(){var result=await api('/api/config');state.config=result.config;$('cfg-provider').value=state.config.provider;$('cfg-base-url').value=state.config.baseURL;$('cfg-model').value=state.config.model;$('cfg-sub-model').value=state.config.subModel||'';$('cfg-effort').value=state.config.effort||'';$('cfg-context').value=state.config.contextWindow;$('cfg-max-output').value=state.config.maxOutputTokens||'';$('cfg-streaming').checked=state.config.streaming!==false;$('cfg-port').value=state.config.web.port;$('cfg-open').checked=state.config.web.openOnStart!==false;$('server-port').textContent=state.config.web.port;$('key-state').textContent=state.config.apiKeyConfigured?'\u5F53\u524D API Key\uFF1A'+state.config.apiKeyMask:'\u5C1A\u672A\u914D\u7F6E API Key';$('new-cwd').value=state.config.defaultCwd||'';$('new-model').value=state.config.model;var models=[state.config.model].concat(state.sessions.map(function(item){return item.model;})).filter(Boolean).filter(function(value,index,array){return array.indexOf(value)===index;});$('recent-models').innerHTML=models.map(function(model){return '<option value=\"'+escapeHtml(model)+'\">';}).join('');}\n $('new-session').addEventListener('click',function(){openModal('new-modal');setTimeout(function(){$('new-cwd').focus();},0);});$('open-settings').addEventListener('click',function(){loadConfig().then(function(){openModal('settings-modal');});});document.querySelectorAll('[data-close]').forEach(function(button){button.addEventListener('click',function(){closeModal(button.dataset.close);});});\n $('new-form').addEventListener('submit',async function(event){event.preventDefault();try{var result=await api('/api/sessions',{method:'POST',body:JSON.stringify({cwd:$('new-cwd').value,title:$('new-title').value,model:$('new-model').value,effort:$('new-effort').value})});closeModal('new-modal');$('new-title').value='';await loadSessions();await selectSession(result.session.sessionId);}catch(error){toast(error.message,true);}});\n $('settings-form').addEventListener('submit',async function(event){event.preventDefault();var body={provider:$('cfg-provider').value,baseURL:$('cfg-base-url').value,model:$('cfg-model').value,subModel:$('cfg-sub-model').value,effort:$('cfg-effort').value,contextWindow:Number($('cfg-context').value),streaming:$('cfg-streaming').checked,web:{port:Number($('cfg-port').value),openOnStart:$('cfg-open').checked}};if($('cfg-max-output').value)body.maxOutputTokens=Number($('cfg-max-output').value);if($('cfg-api-key').value)body.apiKey=$('cfg-api-key').value;try{await api('/api/config',{method:'PUT',body:JSON.stringify(body)});$('cfg-api-key').value='';closeModal('settings-modal');toast('\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u65B0\u914D\u7F6E\u4ECE\u4E0B\u4E00\u6B21\u65B0\u5EFA\u4F1A\u8BDD\u751F\u6548');await loadConfig();}catch(error){toast(error.message,true);}});\n async function send(){if(!state.selectedId||!$('prompt').value.trim())return;try{if($('session-model').value!==state.detail.model||$('session-effort').value!==state.detail.effort){await api('/api/sessions/'+encodeURIComponent(state.selectedId),{method:'PATCH',body:JSON.stringify({model:$('session-model').value,effort:$('session-effort').value})});}var text=$('prompt').value;$('prompt').value='';await api('/api/sessions/'+encodeURIComponent(state.selectedId)+'/messages',{method:'POST',body:JSON.stringify({text:text})});await loadDetail();}catch(error){toast(error.message,true);}}\n $('send').addEventListener('click',send);$('prompt').addEventListener('keydown',function(event){if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send();}});$('stop').addEventListener('click',async function(){try{await api('/api/sessions/'+encodeURIComponent(state.selectedId)+'/stop',{method:'POST',body:'{}'});toast('\u5DF2\u8BF7\u6C42\u505C\u6B62');}catch(error){toast(error.message,true);}});\n $('rename-session').addEventListener('click',function(){$('rename-title').value=state.detail.title;openModal('rename-modal');});$('rename-form').addEventListener('submit',async function(event){event.preventDefault();try{await api('/api/sessions/'+encodeURIComponent(state.selectedId),{method:'PATCH',body:JSON.stringify({title:$('rename-title').value})});closeModal('rename-modal');await loadDetail();}catch(error){toast(error.message,true);}});$('delete-session').addEventListener('click',async function(){if(!confirm('\u5220\u9664\u8FD9\u4E2A\u4F1A\u8BDD\u53CA\u5176\u672C\u5730\u5386\u53F2\uFF1F\u4E0D\u4F1A\u5220\u9664\u5DE5\u4F5C\u76EE\u5F55\u4E2D\u7684\u6587\u4EF6\u3002'))return;try{await api('/api/sessions/'+encodeURIComponent(state.selectedId),{method:'DELETE'});if(state.eventSource)state.eventSource.close();state.selectedId=null;state.detail=null;$('composer-shell').classList.add('hidden');$('rename-session').classList.add('hidden');$('delete-session').classList.add('hidden');$('session-title').textContent='DeepCCC';$('session-cwd').textContent='\u9009\u62E9\u6216\u521B\u5EFA\u4E00\u4E2A\u4F1A\u8BDD';$('content').innerHTML='<div class=\"welcome\"><div class=\"welcome-card\"><div class=\"welcome-mark\">\u2726</div><h2>\u4F1A\u8BDD\u5DF2\u5220\u9664</h2><p>\u4ECE\u5DE6\u4FA7\u9009\u62E9\u5176\u4ED6\u4F1A\u8BDD\u6216\u521B\u5EFA\u4E00\u4E2A\u65B0\u7684\u4EFB\u52A1\u3002</p></div></div>';await loadSessions();}catch(error){toast(error.message,true);}});\n $('content').addEventListener('click',async function(event){var button=event.target.closest('[data-approval-answer]');if(!button)return;button.disabled=true;try{await api('/api/approvals/'+encodeURIComponent(button.dataset.approvalId),{method:'POST',body:JSON.stringify({answer:button.dataset.approvalAnswer})});await loadDetail();}catch(error){button.disabled=false;toast(error.message,true);}});\n Promise.all([loadSessions(),loadConfig()]).then(function(){if(state.sessions[0])selectSession(state.sessions[0].sessionId);}).catch(function(error){toast(error.message,true);});\n})();\n</script>\n</body></html>";
|