deepccc 0.1.28 → 0.2.1

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