deepccc 0.2.0 → 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 +18 -16
- package/bin/deepccc-cli.mjs +17 -0
- package/bin/deepccc.mjs +19 -13
- package/dist/cli.d.ts +3 -4
- package/dist/cli.js +7 -24
- package/dist/web-entry.d.ts +7 -0
- package/dist/web-entry.js +72 -0
- package/dist/web-page.d.ts +1 -1
- package/dist/web-page.js +10 -8
- package/dist/web-runtime.d.ts +2 -0
- package/dist/web-runtime.js +17 -4
- package/dist/web-server.d.ts +22 -0
- package/dist/web-server.js +167 -10
- package/dist/web-session-store.d.ts +14 -0
- package/dist/web-session-store.js +33 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -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
|
运行要求:
|
|
@@ -109,11 +109,13 @@ $env:DEEPCCC_STREAMING="true"
|
|
|
109
109
|
全局安装后可以直接启动本地网页版:
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
|
-
deepccc
|
|
112
|
+
deepccc
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
默认只监听 `http://127.0.0.1:28080/`,不会暴露到局域网。可在
|
|
116
|
-
`~/.deepccc/config.json` 的 `web.port` 修改端口,`web.openOnStart` 控制启动时是否自动打开浏览器;也可以临时使用 `deepccc
|
|
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` 启动终端模式。
|
|
117
119
|
|
|
118
120
|
Web UI 支持新建、恢复、重命名和删除多会话,多个会话可以同时运行,即使它们指向同一个工作目录。每个会话可独立选择 model、subModel 和 effort,并持续复用 CLI 已保存的历史。注意:当前版本不自动创建 Git worktree;同目录的多个运行中 Agent 直接修改同一组文件,页面会提示覆盖与冲突风险。
|
|
119
121
|
|
|
@@ -166,45 +168,45 @@ API 拒绝(context length exceeded),实际窗口以模型与所用服务
|
|
|
166
168
|
在当前目录启动一个交互式 Agent:
|
|
167
169
|
|
|
168
170
|
```bash
|
|
169
|
-
deepccc
|
|
171
|
+
deepccc-cli
|
|
170
172
|
```
|
|
171
173
|
|
|
172
174
|
指定其他模型或 OpenAI-compatible 接口:
|
|
173
175
|
|
|
174
176
|
```bash
|
|
175
|
-
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
|
|
176
178
|
```
|
|
177
179
|
|
|
178
180
|
使用 Anthropic Messages 协议(同样支持流式输出):
|
|
179
181
|
|
|
180
182
|
```bash
|
|
181
|
-
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
|
|
182
184
|
```
|
|
183
185
|
|
|
184
186
|
指定工作目录:
|
|
185
187
|
|
|
186
188
|
```bash
|
|
187
|
-
deepccc --cwd /path/to/project
|
|
189
|
+
deepccc-cli --cwd /path/to/project
|
|
188
190
|
```
|
|
189
191
|
|
|
190
192
|
恢复当前工作目录最近一次会话:
|
|
191
193
|
|
|
192
194
|
```bash
|
|
193
|
-
deepccc --resume
|
|
195
|
+
deepccc-cli --resume
|
|
194
196
|
```
|
|
195
197
|
|
|
196
198
|
设置工具调用步数上限:
|
|
197
199
|
|
|
198
200
|
```bash
|
|
199
|
-
deepccc --max-steps 20
|
|
201
|
+
deepccc-cli --max-steps 20
|
|
200
202
|
```
|
|
201
203
|
|
|
202
|
-
默认情况下,`deepccc` 不设置固定步数上限,会让模型自然完成工具循环。
|
|
204
|
+
默认情况下,`deepccc-cli` 不设置固定步数上限,会让模型自然完成工具循环。
|
|
203
205
|
|
|
204
206
|
设置推理强度(reasoning effort):
|
|
205
207
|
|
|
206
208
|
```bash
|
|
207
|
-
deepccc --effort high
|
|
209
|
+
deepccc-cli --effort high
|
|
208
210
|
```
|
|
209
211
|
|
|
210
212
|
可选值:`none` / `minimal` / `low` / `medium` / `high` / `xhigh` / `max`(留空则不传 `reasoning_effort` 请求字段)。
|
|
@@ -212,7 +214,7 @@ deepccc --effort high
|
|
|
212
214
|
限制主对话最大输出 token:
|
|
213
215
|
|
|
214
216
|
```bash
|
|
215
|
-
deepccc --max-output-tokens 8192
|
|
217
|
+
deepccc-cli --max-output-tokens 8192
|
|
216
218
|
```
|
|
217
219
|
|
|
218
220
|
不设置时使用 Provider 默认值。
|
|
@@ -267,7 +269,7 @@ deepccc --max-output-tokens 8192
|
|
|
267
269
|
`--stream-json` 或程序化调用(无终端可交互)时,高危命令**安全默认拒绝**。需要全自动场景可显式传入:
|
|
268
270
|
|
|
269
271
|
```bash
|
|
270
|
-
deepccc --dangerously-bypass-permissions
|
|
272
|
+
deepccc-cli --dangerously-bypass-permissions
|
|
271
273
|
```
|
|
272
274
|
|
|
273
275
|
该参数与 `ChatSession` 的 `permissionMode: "bypass"` 等价,也是 chatccc 集成 deepccc 时使用的模式(对齐 chatccc 调用 Claude Code / Codex 的 bypass 方式)。
|
|
@@ -281,7 +283,7 @@ deepccc --dangerously-bypass-permissions
|
|
|
281
283
|
如果终端渲染出现异常,可以强制回退为纯文本流式输出:
|
|
282
284
|
|
|
283
285
|
```bash
|
|
284
|
-
deepccc --plain
|
|
286
|
+
deepccc-cli --plain
|
|
285
287
|
```
|
|
286
288
|
|
|
287
289
|
## JSONL 流式输出
|
|
@@ -289,13 +291,13 @@ deepccc --plain
|
|
|
289
291
|
JSONL 模式适合脚本、服务端集成或其他上层系统调用:
|
|
290
292
|
|
|
291
293
|
```bash
|
|
292
|
-
deepccc --stream-json --prompt "检查这个仓库并总结测试命令"
|
|
294
|
+
deepccc-cli --stream-json --prompt "检查这个仓库并总结测试命令"
|
|
293
295
|
```
|
|
294
296
|
|
|
295
297
|
也可以从 stdin 传入提示词:
|
|
296
298
|
|
|
297
299
|
```bash
|
|
298
|
-
echo "运行测试并解释失败原因" | deepccc --stream-json
|
|
300
|
+
echo "运行测试并解释失败原因" | deepccc-cli --stream-json
|
|
299
301
|
```
|
|
300
302
|
|
|
301
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,10 +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"
|
|
8
|
-
* node bin/deepccc.mjs web
|
|
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"
|
|
9
8
|
*
|
|
10
9
|
* 交互模式(TTY)下,单轮回复渲染为固定"过程区块":状态行 + 折叠工具行 +
|
|
11
10
|
* 原地更新正文,不再滚屏刷 JSON;完成/停止/异常后定型留在屏幕上。
|
package/dist/cli.js
CHANGED
|
@@ -2,10 +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"
|
|
8
|
-
* node bin/deepccc.mjs web
|
|
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"
|
|
9
8
|
*
|
|
10
9
|
* 交互模式(TTY)下,单轮回复渲染为固定"过程区块":状态行 + 折叠工具行 +
|
|
11
10
|
* 原地更新正文,不再滚屏刷 JSON;完成/停止/异常后定型留在屏幕上。
|
|
@@ -129,7 +128,7 @@ function printHelp(appConfig) {
|
|
|
129
128
|
console.log([
|
|
130
129
|
"DeepCCC terminal agent",
|
|
131
130
|
"",
|
|
132
|
-
"Usage: deepccc [options]",
|
|
131
|
+
"Usage: deepccc-cli [options]",
|
|
133
132
|
"",
|
|
134
133
|
"Options:",
|
|
135
134
|
` --provider <name> API protocol: openai or anthropic (current default ${appConfig.provider})`,
|
|
@@ -146,7 +145,6 @@ function printHelp(appConfig) {
|
|
|
146
145
|
" --stream-json One-shot mode: write JSONL events to stdout",
|
|
147
146
|
" --prompt <text> Prompt text for --stream-json",
|
|
148
147
|
" --plain Force plain streaming output (no progress block renderer)",
|
|
149
|
-
" web Start the local Web UI (default http://127.0.0.1:28080)",
|
|
150
148
|
" --dangerously-bypass-permissions Skip all permission prompts (aligns with chatccc's bypass mode)",
|
|
151
149
|
" --help, -h Show help",
|
|
152
150
|
"",
|
|
@@ -567,7 +565,7 @@ async function runRepl(args) {
|
|
|
567
565
|
});
|
|
568
566
|
}
|
|
569
567
|
/**
|
|
570
|
-
* skill create 子命令:deepccc skill create <name> [--scope global|project] [--description "..."]
|
|
568
|
+
* skill create 子命令:deepccc-cli skill create <name> [--scope global|project] [--description "..."]
|
|
571
569
|
* 默认创建为全局技能(~/.deepccc/skills/<name>/SKILL.md,Codex 结构);
|
|
572
570
|
* --scope project 创建为项目技能(<cwd>/.deepccc/skills/<name>/SKILL.md)。
|
|
573
571
|
* 新技能在下一次对话自动生效(技能索引每次 chat() 前重扫)。
|
|
@@ -576,7 +574,7 @@ function runSkillCreate(argv) {
|
|
|
576
574
|
const positional = argv.filter((a) => !a.startsWith("--"));
|
|
577
575
|
const name = positional[0];
|
|
578
576
|
if (!name) {
|
|
579
|
-
console.error("usage: deepccc skill create <name> [--scope global|project] [--description \"...\"]");
|
|
577
|
+
console.error("usage: deepccc-cli skill create <name> [--scope global|project] [--description \"...\"]");
|
|
580
578
|
process.exit(1);
|
|
581
579
|
}
|
|
582
580
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name)) {
|
|
@@ -610,22 +608,7 @@ function runSkillCreate(argv) {
|
|
|
610
608
|
console.log("hot reload: 下一次对话自动生效,无需重启");
|
|
611
609
|
}
|
|
612
610
|
async function main() {
|
|
613
|
-
|
|
614
|
-
const webArgs = process.argv.slice(3);
|
|
615
|
-
const portIndex = webArgs.indexOf("--port");
|
|
616
|
-
const port = portIndex >= 0 && webArgs[portIndex + 1] ? Number(webArgs[portIndex + 1]) : undefined;
|
|
617
|
-
if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65_535)) {
|
|
618
|
-
throw new Error("--port must be an integer between 1 and 65535");
|
|
619
|
-
}
|
|
620
|
-
const { startDeepCccWebServer } = await import("./web-server.js");
|
|
621
|
-
const handle = await startDeepCccWebServer({
|
|
622
|
-
...(port ? { port } : {}),
|
|
623
|
-
...(webArgs.includes("--no-open") ? { openBrowser: false } : {}),
|
|
624
|
-
});
|
|
625
|
-
console.log(`DeepCCC Web UI: ${handle.url}${handle.reused ? " (reused existing server)" : ""}`);
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
// skill create 子命令:deepccc skill create <name> [--scope global|project] [--description "..."]
|
|
611
|
+
// skill create 子命令:deepccc-cli skill create <name> [--scope global|project] [--description "..."]
|
|
629
612
|
// 默认创建在全局 ~/.deepccc/skills(Codex 目录结构),--scope project 创建到 <cwd>/.deepccc/skills。
|
|
630
613
|
if (process.argv[2] === "skill" && process.argv[3] === "create") {
|
|
631
614
|
runSkillCreate(process.argv.slice(4));
|
|
@@ -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
|
+
}
|
package/dist/web-page.d.ts
CHANGED
|
@@ -1 +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:dark;--bg:#0c0e13;--panel:#13161d;--panel2:#181c25;--soft:#202530;--line:#2a303d;--text:#f1f3f8;--muted:#929aaa;--accent:#8b7cf6;--accent2:#5ee4b1;--danger:#ff6f89;--warning:#f2b84b;--shadow:0 24px 70px rgba(0,0,0,.36)}\n*{box-sizing:border-box}html,body{height:100%}body{margin:0;background:radial-gradient(circle at 75% -15%,rgba(112,87,246,.17),transparent 35%),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:rgba(18,21,28,.94);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:linear-gradient(135deg,#7f6df4,#b25bdb);box-shadow:0 9px 25px rgba(127,109,244,.32);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(--panel2);font-weight:700}.primary{background:linear-gradient(135deg,#7666eb,#9a67e8);border:0;box-shadow:0 8px 22px rgba(119,102,235,.25)}.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.1)}\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@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-actions{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=\"approval-modal\" class=\"modal-backdrop hidden\"><div class=\"modal approval\"><span class=\"approval-badge\">\u9700\u8981\u6388\u6743</span><h2>Agent \u8BF7\u6C42\u6267\u884C\u9AD8\u98CE\u9669\u64CD\u4F5C</h2><p id=\"approval-detail\" class=\"subtitle\"></p><pre id=\"approval-action\"></pre><div class=\"approval-actions\"><button data-answer=\"deny\" class=\"danger\">\u62D2\u7EDD</button><button data-answer=\"allow\" class=\"ghost\">\u5141\u8BB8\u4E00\u6B21</button><button data-answer=\"allow-session\" class=\"ghost\">\u672C\u4F1A\u8BDD\u5141\u8BB8</button><button data-answer=\"allow-always\" class=\"primary\">\u6C38\u4E45\u5141\u8BB8</button></div></div></div>\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 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||'');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();renderApproval();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 function renderApproval(){var approval=state.detail&&state.detail.pendingApproval;if(!approval){closeModal('approval-modal');return;}$('approval-detail').textContent=approval.detail||('\u5DE5\u5177\uFF1A'+approval.tool);$('approval-action').textContent=approval.action;openModal('approval-modal');}\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 document.querySelectorAll('[data-answer]').forEach(function(button){button.addEventListener('click',async function(){var approval=state.detail&&state.detail.pendingApproval;if(!approval)return;try{await api('/api/approvals/'+encodeURIComponent(approval.approvalId),{method:'POST',body:JSON.stringify({answer:button.dataset.answer})});closeModal('approval-modal');scheduleDetail();}catch(error){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>";
|
|
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>";
|
package/dist/web-page.js
CHANGED
|
@@ -5,15 +5,17 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
6
|
<title>DeepCCC</title>
|
|
7
7
|
<style>
|
|
8
|
-
:root{color-scheme:
|
|
9
|
-
*{box-sizing:border-box}html,body{height:100%}body{margin:0;background:
|
|
10
|
-
.sidebar{min-width:0;background:
|
|
8
|
+
: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)}
|
|
9
|
+
*{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)}
|
|
10
|
+
.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)}
|
|
11
11
|
.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}
|
|
12
12
|
.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)}
|
|
13
13
|
.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}
|
|
14
14
|
.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}
|
|
15
15
|
.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}
|
|
16
|
-
|
|
16
|
+
.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}
|
|
17
|
+
.sidebar{height:100%;overflow:hidden}.main{min-height:0!important;overflow:hidden}
|
|
18
|
+
@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}}
|
|
17
19
|
</style>
|
|
18
20
|
</head>
|
|
19
21
|
<body>
|
|
@@ -38,7 +40,6 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
38
40
|
|
|
39
41
|
<div id="rename-modal" class="modal-backdrop hidden"><form id="rename-form" class="modal"><h2>重命名会话</h2><div class="field"><label>名称</label><input id="rename-title" required maxlength="120"></div><div class="modal-actions"><button type="button" data-close="rename-modal" class="ghost">取消</button><button class="primary">保存</button></div></form></div>
|
|
40
42
|
|
|
41
|
-
<div id="approval-modal" class="modal-backdrop hidden"><div class="modal approval"><span class="approval-badge">需要授权</span><h2>Agent 请求执行高风险操作</h2><p id="approval-detail" class="subtitle"></p><pre id="approval-action"></pre><div class="approval-actions"><button data-answer="deny" class="danger">拒绝</button><button data-answer="allow" class="ghost">允许一次</button><button data-answer="allow-session" class="ghost">本会话允许</button><button data-answer="allow-always" class="primary">永久允许</button></div></div></div>
|
|
42
43
|
<div id="toast" class="toast hidden"></div>
|
|
43
44
|
<script>
|
|
44
45
|
(function(){
|
|
@@ -53,9 +54,10 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
53
54
|
function renderSessions(){var root=$('session-list');root.innerHTML='';if(!state.sessions.length){root.innerHTML='<div class="session-empty">还没有会话<br>点击上方按钮开始</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||'默认模型')+' · '+escapeHtml(session.effort||'默认 effort')+'</div><div class="session-meta">'+escapeHtml(session.cwd)+'</div>';button.addEventListener('click',function(){selectSession(session.sessionId);});root.appendChild(button);});}
|
|
54
55
|
function messageHtml(role,content){return '<article class="message '+role+'"><div class="avatar">'+(role==='user'?'你':'D')+'</div><div class="bubble"><div class="role">'+(role==='user'?'YOU':'DEEPCCC')+'</div><div class="markdown">'+markdown(content)+'</div></div></article>';}
|
|
55
56
|
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'?'调用工具 · '+(item.name||'tool'):'工具结果 · '+(item.name||item.tool_use_id||'tool'))+'</summary><pre>'+escapeHtml(JSON.stringify(item.type==='tool_use'?item.input:item.content,null,2))+'</pre></details>';}
|
|
56
|
-
function
|
|
57
|
+
function approvalAnswerLabel(answer){var labels={allow:'已允许一次','allow-session':'本会话已允许','allow-always':'已永久允许',deny:'已拒绝'};return labels[answer]||'等待决定';}
|
|
58
|
+
function approvalHtml(approval){var resolved=approval.status==='resolved';var html='<section class="approval-card'+(resolved?' resolved':'')+'"><div class="approval-head"><strong>⚠ 高风险操作审批</strong><span class="approval-status">'+escapeHtml(resolved?approvalAnswerLabel(approval.answer):'等待批准')+'</span></div><div class="approval-detail">'+escapeHtml(approval.detail||('工具:'+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)+'">拒绝</button><button class="ghost" data-approval-answer="allow" data-approval-id="'+escapeHtml(approval.approvalId)+'">允许一次</button><button class="ghost" data-approval-answer="allow-session" data-approval-id="'+escapeHtml(approval.approvalId)+'">本会话允许</button><button class="primary" data-approval-answer="allow-always" data-approval-id="'+escapeHtml(approval.approvalId)+'">永久允许</button></div>';return html+'</section>';}
|
|
59
|
+
function renderDetail(){var detail=state.detail;if(!detail)return;$('session-title').textContent=detail.title;$('session-cwd').textContent=detail.cwd+' · '+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>准备开始这个会话</strong><span>在下方输入任务。DeepCCC 会读取当前项目指令、调用工具并持续保存上下文。</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 正在工作</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;});}
|
|
57
60
|
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='⚠ 当前目录还有 '+conflicts.length+' 个 Agent 会话正在运行。多个会话直接修改同一目录,可能产生覆盖或冲突。';el.classList.remove('hidden');}else el.classList.add('hidden');}
|
|
58
|
-
function renderApproval(){var approval=state.detail&&state.detail.pendingApproval;if(!approval){closeModal('approval-modal');return;}$('approval-detail').textContent=approval.detail||('工具:'+approval.tool);$('approval-action').textContent=approval.action;openModal('approval-modal');}
|
|
59
61
|
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;}
|
|
60
62
|
async function loadDetail(){if(!state.selectedId)return;var result=await api('/api/sessions/'+encodeURIComponent(state.selectedId));state.detail=result.session;renderDetail();await loadSessions();}
|
|
61
63
|
function scheduleDetail(){clearTimeout(state.reloadTimer);state.reloadTimer=setTimeout(function(){loadDetail().catch(function(error){toast(error.message,true);});},120);}
|
|
@@ -67,7 +69,7 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
67
69
|
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);}}
|
|
68
70
|
$('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('已请求停止');}catch(error){toast(error.message,true);}});
|
|
69
71
|
$('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('删除这个会话及其本地历史?不会删除工作目录中的文件。'))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='选择或创建一个会话';$('content').innerHTML='<div class="welcome"><div class="welcome-card"><div class="welcome-mark">✦</div><h2>会话已删除</h2><p>从左侧选择其他会话或创建一个新的任务。</p></div></div>';await loadSessions();}catch(error){toast(error.message,true);}});
|
|
70
|
-
|
|
72
|
+
$('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);}});
|
|
71
73
|
Promise.all([loadSessions(),loadConfig()]).then(function(){if(state.sessions[0])selectSession(state.sessions[0].sessionId);}).catch(function(error){toast(error.message,true);});
|
|
72
74
|
})();
|
|
73
75
|
</script>
|
package/dist/web-runtime.d.ts
CHANGED
|
@@ -63,6 +63,7 @@ export declare class DeepCccWebRuntime {
|
|
|
63
63
|
effort: string;
|
|
64
64
|
createdAt: string;
|
|
65
65
|
updatedAt: string;
|
|
66
|
+
approvals: import("./web-session-store.js").WebApprovalRecord[];
|
|
66
67
|
}[]>;
|
|
67
68
|
createSession(input: CreateWebSessionInput): Promise<WebSessionMeta>;
|
|
68
69
|
getSession(sessionId: string): Promise<{
|
|
@@ -72,6 +73,7 @@ export declare class DeepCccWebRuntime {
|
|
|
72
73
|
summary: string;
|
|
73
74
|
events: WebRuntimeEvent[];
|
|
74
75
|
pendingApproval: PendingWebApproval | null;
|
|
76
|
+
approvals: import("./web-session-store.js").WebApprovalRecord[];
|
|
75
77
|
schemaVersion: 1;
|
|
76
78
|
sessionId: string;
|
|
77
79
|
title: string;
|
package/dist/web-runtime.js
CHANGED
|
@@ -66,6 +66,7 @@ export class DeepCccWebRuntime {
|
|
|
66
66
|
summary: context?.summary ?? "",
|
|
67
67
|
events: this.events.get(sessionId) ?? [],
|
|
68
68
|
pendingApproval,
|
|
69
|
+
approvals: meta.approvals,
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
async updateSession(sessionId, patch) {
|
|
@@ -120,6 +121,7 @@ export class DeepCccWebRuntime {
|
|
|
120
121
|
return false;
|
|
121
122
|
clearTimeout(waiter.timeout);
|
|
122
123
|
this.approvals.delete(approvalId);
|
|
124
|
+
await this.store.resolveApproval(waiter.approval.sessionId, approvalId, answer);
|
|
123
125
|
waiter.resolve(answer);
|
|
124
126
|
this.emit(waiter.approval.sessionId, "approval_resolved", { approvalId, answer });
|
|
125
127
|
return true;
|
|
@@ -167,12 +169,23 @@ export class DeepCccWebRuntime {
|
|
|
167
169
|
return new Promise((resolve) => {
|
|
168
170
|
const timeout = setTimeout(() => {
|
|
169
171
|
this.approvals.delete(approvalId);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
+
void this.store.resolveApproval(sessionId, approvalId, "deny").finally(() => {
|
|
173
|
+
resolve("deny");
|
|
174
|
+
this.emit(sessionId, "approval_resolved", { approvalId, answer: "deny", timedOut: true });
|
|
175
|
+
});
|
|
172
176
|
}, this.approvalTimeoutMs);
|
|
173
177
|
timeout.unref?.();
|
|
174
|
-
this.
|
|
175
|
-
|
|
178
|
+
void this.store.addApproval(sessionId, {
|
|
179
|
+
...approval,
|
|
180
|
+
status: "pending",
|
|
181
|
+
}).then(() => {
|
|
182
|
+
this.approvals.set(approvalId, { approval, resolve, timeout });
|
|
183
|
+
this.emit(sessionId, "approval", approval);
|
|
184
|
+
}).catch(() => {
|
|
185
|
+
clearTimeout(timeout);
|
|
186
|
+
this.approvals.delete(approvalId);
|
|
187
|
+
resolve("deny");
|
|
188
|
+
});
|
|
176
189
|
});
|
|
177
190
|
}
|
|
178
191
|
emit(sessionId, type, data) {
|
package/dist/web-server.d.ts
CHANGED
|
@@ -23,11 +23,16 @@ export interface DeepCccWebRequestHandlerOptions {
|
|
|
23
23
|
getPublicConfig: () => PublicDeepCccConfig;
|
|
24
24
|
saveConfig: (patch: DeepCccConfigPatch) => Promise<PublicDeepCccConfig> | PublicDeepCccConfig;
|
|
25
25
|
defaultCwd: string;
|
|
26
|
+
instance?: DeepCccWebInstance & {
|
|
27
|
+
shutdown: () => void;
|
|
28
|
+
};
|
|
26
29
|
}
|
|
27
30
|
export interface StartDeepCccWebOptions {
|
|
28
31
|
port?: number;
|
|
29
32
|
openBrowser?: boolean;
|
|
30
33
|
defaultCwd?: string;
|
|
34
|
+
reuseExisting?: boolean;
|
|
35
|
+
stateFile?: string;
|
|
31
36
|
}
|
|
32
37
|
export interface DeepCccWebHandle {
|
|
33
38
|
url: string;
|
|
@@ -35,6 +40,23 @@ export interface DeepCccWebHandle {
|
|
|
35
40
|
reused: boolean;
|
|
36
41
|
close(): Promise<void>;
|
|
37
42
|
}
|
|
43
|
+
export interface DeepCccWebInstance {
|
|
44
|
+
pid: number;
|
|
45
|
+
port: number;
|
|
46
|
+
startedAt: string;
|
|
47
|
+
token: string;
|
|
48
|
+
}
|
|
49
|
+
export interface LaunchDeepCccWebProcessOptions extends StartDeepCccWebOptions {
|
|
50
|
+
}
|
|
51
|
+
export declare const DEEPCCC_WEB_STATE_FILE: string;
|
|
38
52
|
export declare function toPublicConfig(config: DeepCccConfig, defaultCwd?: string): PublicDeepCccConfig;
|
|
39
53
|
export declare function createDeepCccWebRequestHandler(options: DeepCccWebRequestHandlerOptions): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
40
54
|
export declare function startDeepCccWebServer(options?: StartDeepCccWebOptions): Promise<DeepCccWebHandle>;
|
|
55
|
+
export declare function launchDeepCccWebProcess(options?: LaunchDeepCccWebProcessOptions): Promise<{
|
|
56
|
+
url: string;
|
|
57
|
+
port: number;
|
|
58
|
+
reused: boolean;
|
|
59
|
+
}>;
|
|
60
|
+
export declare function inspectDeepCccWebServer(port: number): Promise<(DeepCccWebInstance & {
|
|
61
|
+
service: "deepccc-web";
|
|
62
|
+
}) | null>;
|
package/dist/web-server.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
4
|
import { createServer } from "node:http";
|
|
3
5
|
import { stat } from "node:fs/promises";
|
|
4
|
-
import {
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { DEEPCCC_HOME, loadConfig, saveConfigPatch, } from "./config.js";
|
|
10
|
+
import { killProcessTree } from "./proc-tree-kill.js";
|
|
5
11
|
import { DeepCccWebRuntime } from "./web-runtime.js";
|
|
6
12
|
import { DEEPCCC_WEB_PAGE } from "./web-page.js";
|
|
7
13
|
const MAX_BODY_BYTES = 512 * 1024;
|
|
8
14
|
const HOST = "127.0.0.1";
|
|
9
15
|
let defaultHandle = null;
|
|
16
|
+
export const DEEPCCC_WEB_STATE_FILE = join(DEEPCCC_HOME, "web", "server.json");
|
|
10
17
|
function runtimeConfig() {
|
|
11
18
|
const config = loadConfig();
|
|
12
19
|
return {
|
|
@@ -47,7 +54,24 @@ export function createDeepCccWebRequestHandler(options) {
|
|
|
47
54
|
return textReply(res, 200, DEEPCCC_WEB_PAGE, "text/html; charset=utf-8");
|
|
48
55
|
}
|
|
49
56
|
if (method === "GET" && path === "/api/health") {
|
|
50
|
-
return jsonReply(res, 200, {
|
|
57
|
+
return jsonReply(res, 200, {
|
|
58
|
+
ok: true,
|
|
59
|
+
service: "deepccc-web",
|
|
60
|
+
...(options.instance ? {
|
|
61
|
+
pid: options.instance.pid,
|
|
62
|
+
port: options.instance.port,
|
|
63
|
+
startedAt: options.instance.startedAt,
|
|
64
|
+
instanceToken: options.instance.token,
|
|
65
|
+
} : {}),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (method === "POST" && path === "/api/shutdown") {
|
|
69
|
+
if (!options.instance || req.headers["x-deepccc-instance-token"] !== options.instance.token) {
|
|
70
|
+
throw new WebHttpError(403, "DeepCCC Web shutdown token mismatch");
|
|
71
|
+
}
|
|
72
|
+
jsonReply(res, 202, { ok: true, shuttingDown: true });
|
|
73
|
+
setTimeout(options.instance.shutdown, 0).unref?.();
|
|
74
|
+
return;
|
|
51
75
|
}
|
|
52
76
|
if (method === "GET" && path === "/api/config") {
|
|
53
77
|
return jsonReply(res, 200, { ok: true, config: { ...options.getPublicConfig(), defaultCwd: options.defaultCwd } });
|
|
@@ -125,23 +149,38 @@ export async function startDeepCccWebServer(options = {}) {
|
|
|
125
149
|
const port = options.port ?? config.web.port;
|
|
126
150
|
const url = `http://${HOST}:${port}/`;
|
|
127
151
|
if (defaultHandle?.port === port) {
|
|
128
|
-
if (options.
|
|
129
|
-
openBrowser
|
|
130
|
-
|
|
152
|
+
if (options.reuseExisting) {
|
|
153
|
+
if (options.openBrowser !== false)
|
|
154
|
+
openBrowser(url);
|
|
155
|
+
return defaultHandle;
|
|
156
|
+
}
|
|
157
|
+
await defaultHandle.close();
|
|
131
158
|
}
|
|
132
|
-
|
|
159
|
+
const stateFile = options.stateFile ?? DEEPCCC_WEB_STATE_FILE;
|
|
160
|
+
const existing = await inspectDeepCccWebServer(port);
|
|
161
|
+
if (existing && options.reuseExisting) {
|
|
133
162
|
const reused = { url, port, reused: true, close: async () => { } };
|
|
134
163
|
if (options.openBrowser !== false)
|
|
135
164
|
openBrowser(url);
|
|
136
165
|
return reused;
|
|
137
166
|
}
|
|
167
|
+
if (existing)
|
|
168
|
+
await stopOwnedDeepCccWebServer(existing, stateFile);
|
|
138
169
|
const runtime = new DeepCccWebRuntime({ loadConfig: runtimeConfig });
|
|
139
170
|
const defaultCwd = options.defaultCwd ?? process.cwd();
|
|
171
|
+
const instance = {
|
|
172
|
+
pid: process.pid,
|
|
173
|
+
port,
|
|
174
|
+
startedAt: new Date().toISOString(),
|
|
175
|
+
token: randomUUID(),
|
|
176
|
+
};
|
|
177
|
+
let closeServer = () => { };
|
|
140
178
|
const handler = createDeepCccWebRequestHandler({
|
|
141
179
|
runtime,
|
|
142
180
|
defaultCwd,
|
|
143
181
|
getPublicConfig: () => toPublicConfig(loadConfig(), defaultCwd),
|
|
144
182
|
saveConfig: (patch) => toPublicConfig(saveConfigPatch(patch), defaultCwd),
|
|
183
|
+
instance: { ...instance, shutdown: () => closeServer() },
|
|
145
184
|
});
|
|
146
185
|
const server = createServer((req, res) => { void handler(req, res); });
|
|
147
186
|
await new Promise((resolve, reject) => {
|
|
@@ -156,13 +195,62 @@ export async function startDeepCccWebServer(options = {}) {
|
|
|
156
195
|
url,
|
|
157
196
|
port,
|
|
158
197
|
reused: false,
|
|
159
|
-
close: () => new Promise((resolve, reject) =>
|
|
198
|
+
close: () => new Promise((resolve, reject) => {
|
|
199
|
+
server.close((err) => {
|
|
200
|
+
if (err)
|
|
201
|
+
return reject(err);
|
|
202
|
+
removeOwnedState(stateFile, instance.token);
|
|
203
|
+
if (defaultHandle?.port === port)
|
|
204
|
+
defaultHandle = null;
|
|
205
|
+
resolve();
|
|
206
|
+
});
|
|
207
|
+
// SSE and keep-alive sockets would otherwise keep a replaced standalone
|
|
208
|
+
// process alive after it has released the listening port.
|
|
209
|
+
server.closeAllConnections?.();
|
|
210
|
+
}),
|
|
160
211
|
};
|
|
212
|
+
closeServer = () => { void handle.close(); };
|
|
161
213
|
defaultHandle = handle;
|
|
214
|
+
writeOwnedState(stateFile, instance);
|
|
162
215
|
if (options.openBrowser ?? config.web.openOnStart)
|
|
163
216
|
openBrowser(url);
|
|
164
217
|
return handle;
|
|
165
218
|
}
|
|
219
|
+
export async function launchDeepCccWebProcess(options = {}) {
|
|
220
|
+
const config = loadConfig();
|
|
221
|
+
const port = options.port ?? config.web.port;
|
|
222
|
+
const url = `http://${HOST}:${port}/`;
|
|
223
|
+
if (options.reuseExisting && await inspectDeepCccWebServer(port)) {
|
|
224
|
+
if (options.openBrowser !== false)
|
|
225
|
+
openBrowser(url);
|
|
226
|
+
return { url, port, reused: true };
|
|
227
|
+
}
|
|
228
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
229
|
+
const compiledEntry = join(moduleDir, "web-entry.js");
|
|
230
|
+
const args = [
|
|
231
|
+
...(options.reuseExisting ? ["--reuse-existing"] : []),
|
|
232
|
+
"--port", String(port),
|
|
233
|
+
...(options.openBrowser === false ? ["--no-open"] : []),
|
|
234
|
+
];
|
|
235
|
+
const require = createRequire(import.meta.url);
|
|
236
|
+
const commandArgs = existsSync(compiledEntry)
|
|
237
|
+
? [compiledEntry, ...args]
|
|
238
|
+
: [require.resolve("tsx/cli"), join(moduleDir, "web-entry.ts"), ...args];
|
|
239
|
+
const child = spawn(process.execPath, commandArgs, {
|
|
240
|
+
cwd: options.defaultCwd ?? process.cwd(),
|
|
241
|
+
detached: true,
|
|
242
|
+
stdio: "ignore",
|
|
243
|
+
windowsHide: true,
|
|
244
|
+
});
|
|
245
|
+
child.unref();
|
|
246
|
+
const deadline = Date.now() + 8_000;
|
|
247
|
+
while (Date.now() < deadline) {
|
|
248
|
+
if (await inspectDeepCccWebServer(port))
|
|
249
|
+
return { url, port, reused: false };
|
|
250
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
251
|
+
}
|
|
252
|
+
throw new Error(`DeepCCC Web did not become ready on port ${port}`);
|
|
253
|
+
}
|
|
166
254
|
function openEventStream(req, res, sessionId, runtime) {
|
|
167
255
|
res.writeHead(200, {
|
|
168
256
|
"content-type": "text/event-stream; charset=utf-8",
|
|
@@ -226,14 +314,83 @@ class WebHttpError extends Error {
|
|
|
226
314
|
this.status = status;
|
|
227
315
|
}
|
|
228
316
|
}
|
|
229
|
-
async function
|
|
317
|
+
export async function inspectDeepCccWebServer(port) {
|
|
230
318
|
try {
|
|
231
319
|
const response = await fetch(`http://${HOST}:${port}/api/health`, { signal: AbortSignal.timeout(800) });
|
|
232
|
-
|
|
320
|
+
if (!response.ok)
|
|
321
|
+
return null;
|
|
322
|
+
const health = await response.json();
|
|
323
|
+
if (health.service !== "deepccc-web")
|
|
324
|
+
return null;
|
|
325
|
+
return {
|
|
326
|
+
service: "deepccc-web",
|
|
327
|
+
pid: typeof health.pid === "number" ? health.pid : 0,
|
|
328
|
+
port: typeof health.port === "number" ? health.port : port,
|
|
329
|
+
startedAt: typeof health.startedAt === "string" ? health.startedAt : "",
|
|
330
|
+
token: typeof health.instanceToken === "string" ? health.instanceToken : "",
|
|
331
|
+
};
|
|
233
332
|
}
|
|
234
333
|
catch {
|
|
235
|
-
return
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function stopOwnedDeepCccWebServer(existing, stateFile) {
|
|
338
|
+
const owned = readOwnedState(stateFile);
|
|
339
|
+
if (!owned || !sameInstance(owned, existing)) {
|
|
340
|
+
throw new Error(`Port ${existing.port} is occupied by an unverified DeepCCC Web instance; use --reuse-existing or stop it manually`);
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
await fetch(`http://${HOST}:${existing.port}/api/shutdown`, {
|
|
344
|
+
method: "POST",
|
|
345
|
+
headers: { "x-deepccc-instance-token": existing.token },
|
|
346
|
+
signal: AbortSignal.timeout(1_500),
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
catch { /* fall through to verified force-kill */ }
|
|
350
|
+
const deadline = Date.now() + 3_000;
|
|
351
|
+
while (Date.now() < deadline) {
|
|
352
|
+
if (!await inspectDeepCccWebServer(existing.port))
|
|
353
|
+
return;
|
|
354
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
355
|
+
}
|
|
356
|
+
const current = await inspectDeepCccWebServer(existing.port);
|
|
357
|
+
if (current && sameInstance(owned, current))
|
|
358
|
+
await killProcessTree(owned.pid);
|
|
359
|
+
const finalDeadline = Date.now() + 3_000;
|
|
360
|
+
while (Date.now() < finalDeadline) {
|
|
361
|
+
if (!await inspectDeepCccWebServer(existing.port))
|
|
362
|
+
return;
|
|
363
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
364
|
+
}
|
|
365
|
+
throw new Error(`Verified DeepCCC Web process ${owned.pid} did not release port ${existing.port}`);
|
|
366
|
+
}
|
|
367
|
+
function sameInstance(left, right) {
|
|
368
|
+
return left.pid > 0 && left.pid === right.pid && left.port === right.port && !!left.token && left.token === right.token && left.startedAt === right.startedAt;
|
|
369
|
+
}
|
|
370
|
+
function readOwnedState(path) {
|
|
371
|
+
try {
|
|
372
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
373
|
+
if (typeof value.pid !== "number" || typeof value.port !== "number" || typeof value.startedAt !== "string" || typeof value.token !== "string")
|
|
374
|
+
return null;
|
|
375
|
+
return value;
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function writeOwnedState(path, instance) {
|
|
382
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
383
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
384
|
+
writeFileSync(temp, `${JSON.stringify(instance, null, 2)}\n`, "utf8");
|
|
385
|
+
renameSync(temp, path);
|
|
386
|
+
}
|
|
387
|
+
function removeOwnedState(path, token) {
|
|
388
|
+
if (readOwnedState(path)?.token !== token)
|
|
389
|
+
return;
|
|
390
|
+
try {
|
|
391
|
+
unlinkSync(path);
|
|
236
392
|
}
|
|
393
|
+
catch { /* already removed */ }
|
|
237
394
|
}
|
|
238
395
|
function openBrowser(url) {
|
|
239
396
|
const command = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
@@ -8,6 +8,18 @@ export interface WebSessionMeta {
|
|
|
8
8
|
effort: string;
|
|
9
9
|
createdAt: string;
|
|
10
10
|
updatedAt: string;
|
|
11
|
+
approvals: WebApprovalRecord[];
|
|
12
|
+
}
|
|
13
|
+
export interface WebApprovalRecord {
|
|
14
|
+
approvalId: string;
|
|
15
|
+
tool: string;
|
|
16
|
+
action: string;
|
|
17
|
+
reason: string;
|
|
18
|
+
detail: string;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
status: "pending" | "resolved";
|
|
21
|
+
answer?: "allow" | "allow-session" | "allow-always" | "deny";
|
|
22
|
+
resolvedAt?: string;
|
|
11
23
|
}
|
|
12
24
|
export interface CreateWebSessionInput {
|
|
13
25
|
cwd: string;
|
|
@@ -37,6 +49,8 @@ export declare class WebSessionStore {
|
|
|
37
49
|
list(): Promise<WebSessionMeta[]>;
|
|
38
50
|
update(sessionId: string, patch: UpdateWebSessionInput): Promise<WebSessionMeta>;
|
|
39
51
|
delete(sessionId: string): Promise<boolean>;
|
|
52
|
+
addApproval(sessionId: string, approval: WebApprovalRecord): Promise<void>;
|
|
53
|
+
resolveApproval(sessionId: string, approvalId: string, answer: NonNullable<WebApprovalRecord["answer"]>): Promise<void>;
|
|
40
54
|
private require;
|
|
41
55
|
private save;
|
|
42
56
|
private metaPath;
|
|
@@ -25,6 +25,7 @@ export class WebSessionStore {
|
|
|
25
25
|
effort: input.effort?.trim() ?? "",
|
|
26
26
|
createdAt: timestamp,
|
|
27
27
|
updatedAt: timestamp,
|
|
28
|
+
approvals: [],
|
|
28
29
|
};
|
|
29
30
|
await this.save(meta);
|
|
30
31
|
return meta;
|
|
@@ -50,6 +51,7 @@ export class WebSessionStore {
|
|
|
50
51
|
effort: "",
|
|
51
52
|
createdAt: new Date(context.createdAt).toISOString(),
|
|
52
53
|
updatedAt: new Date(context.updatedAt).toISOString(),
|
|
54
|
+
approvals: [],
|
|
53
55
|
};
|
|
54
56
|
}
|
|
55
57
|
return null;
|
|
@@ -90,6 +92,21 @@ export class WebSessionStore {
|
|
|
90
92
|
await rm(join(this.rootDir, normalized), { recursive: true, force: true });
|
|
91
93
|
return true;
|
|
92
94
|
}
|
|
95
|
+
async addApproval(sessionId, approval) {
|
|
96
|
+
const meta = await this.require(sessionId);
|
|
97
|
+
await this.save({ ...meta, approvals: [...meta.approvals, approval].slice(-200), updatedAt: this.now().toISOString() });
|
|
98
|
+
}
|
|
99
|
+
async resolveApproval(sessionId, approvalId, answer) {
|
|
100
|
+
const meta = await this.require(sessionId);
|
|
101
|
+
const resolvedAt = this.now().toISOString();
|
|
102
|
+
await this.save({
|
|
103
|
+
...meta,
|
|
104
|
+
updatedAt: resolvedAt,
|
|
105
|
+
approvals: meta.approvals.map((approval) => approval.approvalId === approvalId
|
|
106
|
+
? { ...approval, status: "resolved", answer, resolvedAt }
|
|
107
|
+
: approval),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
93
110
|
async require(sessionId) {
|
|
94
111
|
const meta = await this.get(sessionId);
|
|
95
112
|
if (!meta)
|
|
@@ -126,5 +143,20 @@ function parseMeta(value) {
|
|
|
126
143
|
if (typeof meta[field] !== "string")
|
|
127
144
|
throw new Error(`Invalid web session ${field}`);
|
|
128
145
|
}
|
|
129
|
-
|
|
146
|
+
const approvals = Array.isArray(meta.approvals)
|
|
147
|
+
? meta.approvals.filter(isApprovalRecord)
|
|
148
|
+
: [];
|
|
149
|
+
return { ...meta, approvals };
|
|
150
|
+
}
|
|
151
|
+
function isApprovalRecord(value) {
|
|
152
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
153
|
+
return false;
|
|
154
|
+
const approval = value;
|
|
155
|
+
return typeof approval.approvalId === "string"
|
|
156
|
+
&& typeof approval.tool === "string"
|
|
157
|
+
&& typeof approval.action === "string"
|
|
158
|
+
&& typeof approval.reason === "string"
|
|
159
|
+
&& typeof approval.detail === "string"
|
|
160
|
+
&& typeof approval.createdAt === "string"
|
|
161
|
+
&& (approval.status === "pending" || approval.status === "resolved");
|
|
130
162
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepccc",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "A lightweight coding agent with OpenAI-compatible and Anthropic Messages API support.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"bin": {
|
|
32
|
-
"deepccc": "bin/deepccc.mjs"
|
|
32
|
+
"deepccc": "bin/deepccc.mjs",
|
|
33
|
+
"deepccc-cli": "bin/deepccc-cli.mjs"
|
|
33
34
|
},
|
|
34
35
|
"files": [
|
|
35
36
|
"dist/",
|
|
@@ -41,6 +42,8 @@
|
|
|
41
42
|
"LICENSE"
|
|
42
43
|
],
|
|
43
44
|
"scripts": {
|
|
45
|
+
"dev": "tsx src/web-entry.ts",
|
|
46
|
+
"dev:cli": "tsx src/cli.ts",
|
|
44
47
|
"build": "node node_modules/typescript/bin/tsc -p tsconfig.build.json",
|
|
45
48
|
"prepack": "node node_modules/typescript/bin/tsc -p tsconfig.build.json",
|
|
46
49
|
"test": "vitest run",
|