deepccc 0.2.30 → 0.2.31
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 +26 -24
- package/dist/file-tools.js +23 -2
- package/dist/web-page.js +6 -6
- package/docs/completion-session.md +6 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ npm run dev
|
|
|
68
68
|
- 本地工具:代码搜索、文件读写、补丁、命令执行、Git、网页搜索和抓取
|
|
69
69
|
- 权限审批:危险命令在会话时间线中暂停,支持拒绝、允许一次、会话允许和永久允许
|
|
70
70
|
- 上下文管理:自动压缩、原始流日志和跨会话历史检索
|
|
71
|
-
- 长会话校准:当前状态覆盖旧建议,摘要区分历史/事实/推断/局限;关键设定保留来源,区分用户确认、助手写入与助手推断;轮内工具结果有独立预算,避免多步调查持续膨胀
|
|
71
|
+
- 长会话校准:当前状态覆盖旧建议,摘要区分历史/事实/推断/局限;关键设定保留来源,区分用户确认、助手写入与助手推断;轮内工具结果有独立预算,避免多步调查持续膨胀
|
|
72
72
|
- 项目约定:自动加载 AGENTS.md、CLAUDE.md、系统提示和目录式 Skills
|
|
73
73
|
- 项目理解:可控搜索范围、按需本地项目地图、源文件变化即失效的证据笔记;不针对特定业务仓库,详见 [项目理解与搜索](docs/workspace-understanding.md)
|
|
74
74
|
- 自动化:`deepccc-cli --stream-json` 提供稳定 JSONL 事件接口
|
|
@@ -163,11 +163,11 @@ Web UI 支持新建、恢复、重命名和删除多会话,多个会话可以
|
|
|
163
163
|
|
|
164
164
|
模型文本、reasoning 心跳和工具事件通过 SSE 实时更新。每轮消息按真实发生顺序持久化和回放,因此刷新后仍会保持“阶段说明 → 工具调用 → 后续结论”的交错时间线;旧版会话没有顺序数据时,会兼容显示为“工具调用 → 最终回答”。每次工具调用与对应结果合并成一张卡片,折叠态显示 emoji、工具名、状态和关键参数;展开后调用参数默认保留前 8/后 4 行,工具结果保留前 12/后 6 行,省略内容可继续展开。工具卡及省略行的展开状态保存在当前浏览器标签页的 `sessionStorage`,持续生成、切换会话和刷新页面均不会自动收起。消息正文支持标题、表格、列表、引用、链接和代码块等常用 Markdown。
|
|
165
165
|
|
|
166
|
-
图片会转换成模型可见的原生视觉输入。Web 支持选择、粘贴和拖拽 PNG/JPEG/WebP/GIF;CLI 可用 `--image` 或交互命令 `/image`。每条消息最多 10 张、单图最大 20 MiB、图片合计最大 24 MiB,单边最多 8192 像素;超限时提示分图或缩小,保留原图细节。附件复制到 `~/.deepccc/attachments/<session-id>/`,聊天历史只保存引用,raw 日志过滤图片编码。删除 Web 会话时附件一并清理。
|
|
167
|
-
|
|
168
|
-
默认由当前模型直接看图。若主模型不支持视觉,可在 API 设置中指定 `visionModel`(如 `deepseek-flash`),或设置 `DEEPCCC_VISION_MODEL` / CLI `--vision-model`;该模型使用同一 API 协议、Base URL 和 Key,先分析图片,再由主模型结合观察继续任务。界面及 JSONL 会显示实际图片模型和模式。未配置图片模型的 `deepseek-v4-pro` 收图会明确报错;其他兼容模型需服务端支持图像,接口错误不会降级成“假装看过”。官方 DeepSeek 端点的旧 Flash 名称仅在视觉请求中映射到当前 `deepseek-flash`,自定义网关保留用户配置的名称。
|
|
169
|
-
|
|
170
|
-
Agent 可用 `read_image` 查看本地截图、图表或工具产物,并通过 `question` 指定需要观察的细节;`present_file` 用于把图片展示给用户。后续追问会按预算重新加载近期图片;较早图片保留路径供 `read_image` 重新查看。独立图片模型的观察按原文问题、模型、端点与图片内容哈希缓存;细节追问可重新调用 `read_image`。压缩后的摘要应保留重要图片引用,缺失或损坏图片会明确报错。本版本支持图片输入,不包含音频、视频或 PDF 原生输入。
|
|
166
|
+
图片会转换成模型可见的原生视觉输入。Web 支持选择、粘贴和拖拽 PNG/JPEG/WebP/GIF;CLI 可用 `--image` 或交互命令 `/image`。每条消息最多 10 张、单图最大 20 MiB、图片合计最大 24 MiB,单边最多 8192 像素;超限时提示分图或缩小,保留原图细节。附件复制到 `~/.deepccc/attachments/<session-id>/`,聊天历史只保存引用,raw 日志过滤图片编码。删除 Web 会话时附件一并清理。
|
|
167
|
+
|
|
168
|
+
默认由当前模型直接看图。若主模型不支持视觉,可在 API 设置中指定 `visionModel`(如 `deepseek-flash`),或设置 `DEEPCCC_VISION_MODEL` / CLI `--vision-model`;该模型使用同一 API 协议、Base URL 和 Key,先分析图片,再由主模型结合观察继续任务。界面及 JSONL 会显示实际图片模型和模式。未配置图片模型的 `deepseek-v4-pro` 收图会明确报错;其他兼容模型需服务端支持图像,接口错误不会降级成“假装看过”。官方 DeepSeek 端点的旧 Flash 名称仅在视觉请求中映射到当前 `deepseek-flash`,自定义网关保留用户配置的名称。
|
|
169
|
+
|
|
170
|
+
Agent 可用 `read_image` 查看本地截图、图表或工具产物,并通过 `question` 指定需要观察的细节;`present_file` 用于把图片展示给用户。后续追问会按预算重新加载近期图片;较早图片保留路径供 `read_image` 重新查看。独立图片模型的观察按原文问题、模型、端点与图片内容哈希缓存;细节追问可重新调用 `read_image`。压缩后的摘要应保留重要图片引用,缺失或损坏图片会明确报错。本版本支持图片输入,不包含音频、视频或 PDF 原生输入。
|
|
171
171
|
|
|
172
172
|
API 设置采用单一 Provider 配置,支持 OpenAI-compatible 与 Anthropic Messages。完整 API Key 只保存在本机 `~/.deepccc/config.json`,浏览器读取设置时仅返回掩码。危险命令会在会话中暂停并请求“拒绝、允许一次、本会话允许、永久允许”,浏览器断开或审批超时默认拒绝。
|
|
173
173
|
|
|
@@ -195,10 +195,10 @@ Provider 发送 `max_tokens`,使用模型服务端默认值。可通过 `DEEPC
|
|
|
195
195
|
默认取 `min(64000, 常规压缩阈值 × 25%)`,超过后会提前压缩,避免长会话积累大量低信号工具输出。
|
|
196
196
|
可通过 `DEEPCCC_CONTEXT_WINDOW` 环境变量覆盖。⚠️ 超过模型/服务端实际上限时请求会被
|
|
197
197
|
API 拒绝(context length exceeded),实际窗口以模型与所用服务端为准(如 litellm 的
|
|
198
|
-
`max_input_tokens`)。
|
|
199
|
-
|
|
200
|
-
同一轮连续工具调用也有独立的结果重放预算。较早的简短成功回执、JSON 结果和错误信息,
|
|
201
|
-
若比省略提示更短,会保留原结果;较大的结果仍按预算截断或省略,工具调用关联保持不变。
|
|
198
|
+
`max_input_tokens`)。
|
|
199
|
+
|
|
200
|
+
同一轮连续工具调用也有独立的结果重放预算。较早的简短成功回执、JSON 结果和错误信息,
|
|
201
|
+
若比省略提示更短,会保留原结果;较大的结果仍按预算截断或省略,工具调用关联保持不变。
|
|
202
202
|
|
|
203
203
|
`subModel` 是子模型(选填),默认 `""`(留空跟随主模型)。配置后,DeepCCC 内部的轻量
|
|
204
204
|
环节——上下文压缩摘要生成、`task` 子代理任务——使用子模型执行,主对话仍用主模型。
|
|
@@ -247,22 +247,22 @@ deepccc-cli --provider anthropic --base-url https://api.example.com --api-key "$
|
|
|
247
247
|
deepccc-cli --cwd /path/to/project
|
|
248
248
|
```
|
|
249
249
|
|
|
250
|
-
附加一张或多张本地图片(重复传入 `--image`;路径含空格时加引号):
|
|
250
|
+
附加一张或多张本地图片(重复传入 `--image`;路径含空格时加引号):
|
|
251
251
|
|
|
252
252
|
```bash
|
|
253
|
-
deepccc-cli --image ./error.png --image ./expected.webp
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
启动后再加图:输入 `/image "C:\截图\报错截图.png"`,看到“图片已加入下一条消息”后输入问题并回车。多图重复 `/image`;相对路径按会话工作目录(`--cwd`)解析。该命令在当前回复结束后使用。启动时的 `--image` 也只加入队列,随后输入问题发送。
|
|
257
|
-
|
|
258
|
-
脚本或纯命令模式可直接发送图片并获取 JSONL:
|
|
259
|
-
|
|
260
|
-
```bash
|
|
261
|
-
deepccc-cli --model deepseek-flash --stream-json --prompt "解释截图里的错误" --image "./error screenshot.png"
|
|
262
|
-
deepccc-cli --model deepseek-v4-pro --vision-model deepseek-flash --stream-json --prompt "对比两张图" --image ./before.png --image ./after.png
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
安装 ChatCCC 后提供的 `cccagent` 使用同一 CLI:将上述 `deepccc-cli` 换成 `cccagent` 即可。它读取 `~/.deepccc/config.json`、`DEEPCCC_*` 环境变量或命令行参数;ChatCCC 机器人里的 API 配置不会自动改写独立 CLI 配置。聊天窗口的图片粘贴/拖拽属于 Web 功能,纯终端请使用文件路径。
|
|
253
|
+
deepccc-cli --image ./error.png --image ./expected.webp
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
启动后再加图:输入 `/image "C:\截图\报错截图.png"`,看到“图片已加入下一条消息”后输入问题并回车。多图重复 `/image`;相对路径按会话工作目录(`--cwd`)解析。该命令在当前回复结束后使用。启动时的 `--image` 也只加入队列,随后输入问题发送。
|
|
257
|
+
|
|
258
|
+
脚本或纯命令模式可直接发送图片并获取 JSONL:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
deepccc-cli --model deepseek-flash --stream-json --prompt "解释截图里的错误" --image "./error screenshot.png"
|
|
262
|
+
deepccc-cli --model deepseek-v4-pro --vision-model deepseek-flash --stream-json --prompt "对比两张图" --image ./before.png --image ./after.png
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
安装 ChatCCC 后提供的 `cccagent` 使用同一 CLI:将上述 `deepccc-cli` 换成 `cccagent` 即可。它读取 `~/.deepccc/config.json`、`DEEPCCC_*` 环境变量或命令行参数;ChatCCC 机器人里的 API 配置不会自动改写独立 CLI 配置。聊天窗口的图片粘贴/拖拽属于 Web 功能,纯终端请使用文件路径。
|
|
266
266
|
|
|
267
267
|
恢复当前工作目录最近一次会话:
|
|
268
268
|
|
|
@@ -463,6 +463,8 @@ deepccc 的内核主战场在 ChatCCC 仓库的 `deepccc-agent/` 子目录;本
|
|
|
463
463
|
|
|
464
464
|
三个命令工具都支持独立 `cwd`、超时、取消、输出限长和运行中消息让位;返回非零退出码时不会直接被当成工具异常,模型可以读取结构化结果继续判断。Windows 下 `run_command` 会拒绝多行或嵌套同类引号的复杂 Python/Node 内联代码,并提示改用 `run_script`。
|
|
465
465
|
|
|
466
|
+
命令工具及后台命令查询/终止会先检查必需的命名参数。误把参数放进 `raw`、传 JSON 字符串或缺少必需字段时,会在权限处理与进程操作前返回明确的入参错误;有效脚本里的 JSON 内容保持原样,不会被当作参数重新解析。
|
|
467
|
+
|
|
466
468
|
## License
|
|
467
469
|
|
|
468
470
|
Apache-2.0
|
package/dist/file-tools.js
CHANGED
|
@@ -1474,6 +1474,15 @@ export function killAllBackgroundTasks() {
|
|
|
1474
1474
|
}
|
|
1475
1475
|
backgroundTasks.clear();
|
|
1476
1476
|
}
|
|
1477
|
+
function requireCommandArguments(name, input, requiredStrings, allowEmpty = []) {
|
|
1478
|
+
const value = input && typeof input === "object" && !Array.isArray(input)
|
|
1479
|
+
? input : undefined;
|
|
1480
|
+
if (!value || requiredStrings.some((key) => typeof value[key] !== "string"
|
|
1481
|
+
|| (!allowEmpty.includes(key) && !value[key].trim()))) {
|
|
1482
|
+
throw new Error(`Invalid ${name} arguments: expected a structured object with string fields ${requiredStrings.join(", ")}. `
|
|
1483
|
+
+ "Pass these named fields directly; do not wrap the arguments in raw or a JSON string.");
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1477
1486
|
export function createBuiltinFileTools(cwd, options = {}) {
|
|
1478
1487
|
const gate = options.permissionGate;
|
|
1479
1488
|
const runTask = options.runTask;
|
|
@@ -1601,6 +1610,7 @@ export function createBuiltinFileTools(cwd, options = {}) {
|
|
|
1601
1610
|
required: ["executable"],
|
|
1602
1611
|
}),
|
|
1603
1612
|
execute: async (input, execOptions) => {
|
|
1613
|
+
requireCommandArguments("run_process", input, ["executable"]);
|
|
1604
1614
|
const action = [input.executable, ...(input.args ?? [])].join(" ");
|
|
1605
1615
|
await guard({
|
|
1606
1616
|
tool: "run_process",
|
|
@@ -1631,6 +1641,10 @@ export function createBuiltinFileTools(cwd, options = {}) {
|
|
|
1631
1641
|
required: ["language", "code"],
|
|
1632
1642
|
}),
|
|
1633
1643
|
execute: async (input, execOptions) => {
|
|
1644
|
+
requireCommandArguments("run_script", input, ["language", "code"], ["code"]);
|
|
1645
|
+
if (input.language !== "node" && input.language !== "python") {
|
|
1646
|
+
throw new Error("Invalid run_script arguments: language must be node or python.");
|
|
1647
|
+
}
|
|
1634
1648
|
const runtime = input.executable?.trim() || input.language;
|
|
1635
1649
|
await guard({
|
|
1636
1650
|
tool: "run_script",
|
|
@@ -1654,6 +1668,7 @@ export function createBuiltinFileTools(cwd, options = {}) {
|
|
|
1654
1668
|
required: ["command"],
|
|
1655
1669
|
}),
|
|
1656
1670
|
execute: async (input, execOptions) => {
|
|
1671
|
+
requireCommandArguments("run_command", input, ["command"]);
|
|
1657
1672
|
await guard({
|
|
1658
1673
|
tool: "run_command",
|
|
1659
1674
|
action: input.command,
|
|
@@ -1674,7 +1689,10 @@ export function createBuiltinFileTools(cwd, options = {}) {
|
|
|
1674
1689
|
},
|
|
1675
1690
|
required: ["taskId"],
|
|
1676
1691
|
}),
|
|
1677
|
-
execute: (input) =>
|
|
1692
|
+
execute: async (input) => {
|
|
1693
|
+
requireCommandArguments("command_output", input, ["taskId"]);
|
|
1694
|
+
return commandOutputForTool(input.taskId, input, options.shouldYieldToInjection);
|
|
1695
|
+
},
|
|
1678
1696
|
}),
|
|
1679
1697
|
command_kill: tool({
|
|
1680
1698
|
description: "终止一个由 run_process、run_script 或 run_command 转入后台的进程。",
|
|
@@ -1686,7 +1704,10 @@ export function createBuiltinFileTools(cwd, options = {}) {
|
|
|
1686
1704
|
},
|
|
1687
1705
|
required: ["taskId"],
|
|
1688
1706
|
}),
|
|
1689
|
-
execute: (input) =>
|
|
1707
|
+
execute: async (input) => {
|
|
1708
|
+
requireCommandArguments("command_kill", input, ["taskId"]);
|
|
1709
|
+
return commandKillForTool(input.taskId);
|
|
1710
|
+
},
|
|
1690
1711
|
}),
|
|
1691
1712
|
task: tool({
|
|
1692
1713
|
description: `把独立子任务委派给子代理执行:子代理使用子模型、拥有独立上下文,不污染主对话上下文。可用 maxSteps 指定 ${MIN_TASK_MAX_STEPS}-${MAX_TASK_MAX_STEPS} 步预算。默认同步等待;runInBackground=true 时立即返回 taskId,之后用 task_output 查看或 task_stop 要求其停止调查并总结。子代理不能再次委派任务(禁止嵌套),结果会截断回传。`,
|
package/dist/web-page.js
CHANGED
|
@@ -36,11 +36,11 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
36
36
|
<section id="content" class="content"><div class="welcome"><div class="welcome-card"><div class="welcome-mark">✦</div><h2>把工作交给 DeepCCC</h2><p>在浏览器里管理多个本地 Coding Agent 会话,切换模型与推理强度,并在会话内审批操作。</p><div class="hint-grid"><div class="hint">多会话并行运行</div><div class="hint">本地文件与历史</div><div class="hint">API 自由配置</div></div></div></div></section>
|
|
37
37
|
</main>
|
|
38
38
|
</div>
|
|
39
|
-
<div id="composer-shell" class="composer-shell hidden"><div id="composer" class="composer"><textarea id="prompt" placeholder="描述你想完成的任务…可粘贴或拖入图片"></textarea><div id="attachment-drafts" class="attachment-drafts"></div><div class="composer-bar"><input id="attachment-input" type="file" accept="image/png,image/jpeg,image/webp,image/gif" multiple hidden><button id="attach-image" class="attach" type="button" title="添加图片">+</button><input id="session-model" list="recent-models" aria-label="模型"><input id="session-sub-model" list="recent-models" aria-label="子模型" placeholder="默认子模型"><datalist id="recent-models"></datalist><select id="session-effort" aria-label="推理强度"><option value="">默认 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="发送">↑</button><button id="stop" class="stop hidden">■ 停止</button></div></div></div>
|
|
39
|
+
<div id="composer-shell" class="composer-shell hidden"><div id="composer" class="composer"><textarea id="prompt" placeholder="描述你想完成的任务…可粘贴或拖入图片"></textarea><div id="attachment-drafts" class="attachment-drafts"></div><div class="composer-bar"><input id="attachment-input" type="file" accept="image/png,image/jpeg,image/webp,image/gif" multiple hidden><button id="attach-image" class="attach" type="button" title="添加图片">+</button><input id="session-model" list="recent-models" aria-label="模型"><input id="session-sub-model" list="recent-models" aria-label="子模型" placeholder="默认子模型"><datalist id="recent-models"></datalist><select id="session-effort" aria-label="推理强度"><option value="">默认 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="发送">↑</button><button id="stop" class="stop hidden">■ 停止</button></div></div></div>
|
|
40
40
|
|
|
41
41
|
<div id="new-modal" class="modal-backdrop hidden"><form id="new-form" class="modal"><h2>新建 Agent 会话</h2><p class="subtitle">每个会话拥有独立上下文;同目录会话可以并发,但文件修改可能冲突。</p><div class="field"><label>工作目录</label><input id="new-cwd" required></div><div class="field"><label>会话名称(可选)</label><input id="new-title" placeholder="默认使用目录名"></div><div class="row"><div class="field"><label>模型</label><input id="new-model" list="recent-models"></div><div class="field"><label>子模型(可选)</label><input id="new-sub-model" list="recent-models"></div></div><div class="field"><label>Effort</label><select id="new-effort"><option value="">默认</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="modal-actions"><button type="button" data-close="new-modal" class="ghost">取消</button><button class="primary" type="submit">创建会话</button></div></form></div>
|
|
42
42
|
|
|
43
|
-
<div id="settings-modal" class="modal-backdrop hidden"><form id="settings-form" class="modal"><h2>API 与 Web 设置</h2><p class="subtitle">单一 API 配置作为所有新会话的默认值;会话可单独覆盖模型和 effort。</p><div class="row"><div class="field"><label>API 协议</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="留空保留当前 Key"></div></div><div class="field"><label>Base URL</label><input id="cfg-base-url" required></div><div class="row"><div class="field"><label>默认模型</label><input id="cfg-model" required></div><div class="field"><label>子模型(可选)</label><input id="cfg-sub-model"></div></div><div class="field"><label>图片模型(选填)</label><input id="cfg-vision-model" placeholder="例如 deepseek-flash;留空使用主模型原生视觉"></div><div class="row"><div class="field"><label>默认 Effort</label><select id="cfg-effort"><option value="">不传</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 端口</label><input id="cfg-port" type="number" min="1" max="65535"></div><div class="field"><label>Max Output Tokens(可选)</label><input id="cfg-max-output" type="number" min="1"></div></div><label class="check"><input id="cfg-streaming" type="checkbox">启用流式请求</label><label class="check"><input id="cfg-open" type="checkbox">运行 deepccc web 时自动打开浏览器</label><div id="key-state" class="subtitle"></div><div class="modal-actions"><button type="button" data-close="settings-modal" class="ghost">取消</button><button class="primary" type="submit">保存设置</button></div></form></div>
|
|
43
|
+
<div id="settings-modal" class="modal-backdrop hidden"><form id="settings-form" class="modal"><h2>API 与 Web 设置</h2><p class="subtitle">单一 API 配置作为所有新会话的默认值;会话可单独覆盖模型和 effort。</p><div class="row"><div class="field"><label>API 协议</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="留空保留当前 Key"></div></div><div class="field"><label>Base URL</label><input id="cfg-base-url" required></div><div class="row"><div class="field"><label>默认模型</label><input id="cfg-model" required></div><div class="field"><label>子模型(可选)</label><input id="cfg-sub-model"></div></div><div class="field"><label>图片模型(选填)</label><input id="cfg-vision-model" placeholder="例如 deepseek-flash;留空使用主模型原生视觉"></div><div class="row"><div class="field"><label>默认 Effort</label><select id="cfg-effort"><option value="">不传</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 端口</label><input id="cfg-port" type="number" min="1" max="65535"></div><div class="field"><label>Max Output Tokens(可选)</label><input id="cfg-max-output" type="number" min="1"></div></div><label class="check"><input id="cfg-streaming" type="checkbox">启用流式请求</label><label class="check"><input id="cfg-open" type="checkbox">运行 deepccc web 时自动打开浏览器</label><div id="key-state" class="subtitle"></div><div class="modal-actions"><button type="button" data-close="settings-modal" class="ghost">取消</button><button class="primary" type="submit">保存设置</button></div></form></div>
|
|
44
44
|
|
|
45
45
|
<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>
|
|
46
46
|
|
|
@@ -326,7 +326,7 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
326
326
|
function clearToolSessionState(sessionId){try{var prefix='deepccc:tool-ui:v1:'+sessionId+':';for(var index=sessionStorage.length-1;index>=0;index--){var key=sessionStorage.key(index);if(key&&key.indexOf(prefix)===0)sessionStorage.removeItem(key);}}catch(error){}}
|
|
327
327
|
function toolSectionHtml(call,kind,value,headLines,tailLines){if(value===undefined)return'';var result=truncateToolPayload(value,headLines,tailLines);var stateKey=call.uiKey+':'+kind+'-full';var expanded=toolState(stateKey);var text=expanded?result.full:result.preview;var label=kind==='input'?'调用参数':'工具结果';var button=result.truncated?'<button type="button" class="tool-overflow-toggle" data-tool-overflow="'+escapeHtml(stateKey)+'">'+escapeHtml(expanded?'收起省略内容':(result.omittedLines>0?'展开省略的 '+result.omittedLines+' 行':'展开完整内容'))+'</button>':'';return'<section class="tool-section"><div class="tool-section-title">'+label+'</div><pre>'+escapeHtml(text)+'</pre>'+button+'</section>';}
|
|
328
328
|
function timelineHtml(timeline,messageIndex){var results={};(timeline||[]).forEach(function(item){if(item&&item.type==='tool_result'&&item.tool_use_id)results[item.tool_use_id]=item;});var renderedCalls={};var html='';(timeline||[]).forEach(function(item,index){if(!item)return;if(item.type==='text'){if(item.text)html+='<div class="markdown">'+markdown(item.text)+'</div>';return;}if(item.type==='tool_use'){var id=item.id||('timeline-'+messageIndex+'-'+index);var result=results[id];renderedCalls[id]=true;html+=toolCardHtml({uiKey:'tool:'+id,name:item.name||'tool',input:item.input,output:result?result.output:undefined,isError:!!(result&&result.is_error),pending:!result});return;}if(item.type==='tool_result'&&!renderedCalls[item.tool_use_id]){var fallbackId=item.tool_use_id||('result-'+messageIndex+'-'+index);renderedCalls[fallbackId]=true;html+=toolCardHtml({uiKey:'tool:'+fallbackId,name:item.name||'tool',output:item.output,isError:!!item.is_error,pending:false});}});return html;}
|
|
329
|
-
function liveTimeline(events){var timeline=[];(events||[]).forEach(function(event){if(event.type!=='agent'||!event.data)return;var item=event.data;if(item.type==='image_input'){timeline.push({type:'text',text:'[图片理解:'+item.model+','+item.count+' 张,'+(item.mode==='native'?'原生视觉':'图片模型分析')+']\\n'});return;}if(item.type==='text_reset'){timeline.length=0;return;}if(item.type==='text'){var previous=timeline[timeline.length-1];if(previous&&previous.type==='text')previous.text+=(item.text||'');else timeline.push({type:'text',text:item.text||''});return;}if(item.type==='tool_use')timeline.push({type:'tool_use',id:item.id,name:item.name,input:item.input});else if(item.type==='tool_result')timeline.push({type:'tool_result',tool_use_id:item.tool_use_id,name:item.name,output:item.content,is_error:!!item.is_error});});return timeline;}
|
|
329
|
+
function liveTimeline(events){var timeline=[];(events||[]).forEach(function(event){if(event.type!=='agent'||!event.data)return;var item=event.data;if(item.type==='image_input'){timeline.push({type:'text',text:'[图片理解:'+item.model+','+item.count+' 张,'+(item.mode==='native'?'原生视觉':'图片模型分析')+']\\n'});return;}if(item.type==='text_reset'){timeline.length=0;return;}if(item.type==='text'){var previous=timeline[timeline.length-1];if(previous&&previous.type==='text')previous.text+=(item.text||'');else timeline.push({type:'text',text:item.text||''});return;}if(item.type==='tool_use')timeline.push({type:'tool_use',id:item.id,name:item.name,input:item.input});else if(item.type==='tool_result')timeline.push({type:'tool_result',tool_use_id:item.tool_use_id,name:item.name,output:item.content,is_error:!!item.is_error});});return timeline;}
|
|
330
330
|
function legacyToolsHtml(toolCalls,messageIndex){var tools='';(toolCalls||[]).forEach(function(call,index){tools+=toolCardHtml({uiKey:call.id?'tool:'+call.id:'message:'+messageIndex+':tool:'+index,name:call.name,input:call.input,output:call.output,isError:!!call.is_error,pending:false});});return tools;}
|
|
331
331
|
function messageHtml(role,content,toolCalls,messageIndex,timeline){var decoded=role==='user'?decodeAttachmentMessage(content):{text:withoutToolTranscript(content),attachments:[]};var body;if(role==='user')body='<div class="markdown">'+markdown(decoded.text)+'</div>'+attachmentGallery(decoded.attachments,state.detail.sessionId);else if(timeline&&timeline.length)body=timelineHtml(timeline,messageIndex);else body=legacyToolsHtml(toolCalls,messageIndex)+'<div class="markdown">'+markdown(decoded.text)+'</div>';return '<article class="message '+role+'"><div class="avatar">'+(role==='user'?'你':'D')+'</div><div class="bubble"><div class="role">'+(role==='user'?'YOU':'DEEPCCC')+'</div>'+body+'</div></article>';}
|
|
332
332
|
function buildToolGroups(events){var groups=[];var byId={};var order=[];(events||[]).forEach(function(event){if(event.type!=='agent'||!event.data)return;var item=event.data;if(item.type==='tool_use'){var id=item.id||('call-'+order.length);var group={id:id,uiKey:'tool:'+id,name:item.name||'tool',input:item.input,output:undefined,isError:false,pending:true};byId[id]=group;order.push(id);groups.push(group);}else if(item.type==='tool_result'){var key=item.tool_use_id;var target=key?byId[key]:null;if(target){target.output=item.content;target.isError=!!item.is_error;target.pending=false;}}});return groups;}
|
|
@@ -392,12 +392,12 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
392
392
|
function scheduleSessions(){clearTimeout(state.sessionsReloadTimer);state.sessionsReloadTimer=setTimeout(function(){loadSessions().catch(function(error){toast(error.message,true);});},80);}
|
|
393
393
|
function handleSessionEvent(msg){var update;try{update=JSON.parse(msg.data);}catch(error){return;}if(update.sessionId!==state.selectedId||!state.detail)return;if(update.type==='agent'||update.type==='run_started'){state.detail.events=(state.detail.events||[]).concat([update]).slice(-500);if(update.type==='run_started')state.detail.status='running';scheduleRender();}else if(update.type==='run_finished'){state.detail.events=(state.detail.events||[]).concat([update]).slice(-500);state.detail.status='idle';loadDetail(state.selectedId).catch(function(error){toast(error.message,true);});}else if(update.type==='approval'||update.type==='approval_resolved'||update.type==='session_updated'){loadDetail(state.selectedId).catch(function(error){toast(error.message,true);});}}
|
|
394
394
|
async function selectSession(id){state.selectedId=id;state.detail=null;var token=++state.detailRequestToken;renderSessions();$('composer-shell').classList.add('hidden');$('content').innerHTML='<div class="welcome"><div class="welcome-card"><div class="welcome-mark">✦</div><h2>正在加载会话</h2><p>正在读取消息、工具记录与运行状态…</p></div></div>';if(state.eventSource)state.eventSource.close();state.eventSource=new EventSource('/api/sessions/'+encodeURIComponent(id)+'/events');state.eventSource.onmessage=handleSessionEvent;await loadDetail(id,token);}
|
|
395
|
-
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-vision-model').value=state.config.visionModel||'';$('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?'当前 API Key:'+state.config.apiKeyMask:'尚未配置 API Key';$('new-cwd').value=state.config.defaultCwd||'';$('new-model').value=state.config.model;$('new-sub-model').value=state.config.subModel||'';var models=[state.config.model,state.config.subModel].concat(state.sessions.reduce(function(all,item){return all.concat([item.model,item.subModel]);},[])).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('');}
|
|
395
|
+
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-vision-model').value=state.config.visionModel||'';$('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?'当前 API Key:'+state.config.apiKeyMask:'尚未配置 API Key';$('new-cwd').value=state.config.defaultCwd||'';$('new-model').value=state.config.model;$('new-sub-model').value=state.config.subModel||'';var models=[state.config.model,state.config.subModel].concat(state.sessions.reduce(function(all,item){return all.concat([item.model,item.subModel]);},[])).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('');}
|
|
396
396
|
$('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');}).catch(function(error){toast(error.message,true);});});document.querySelectorAll('[data-close]').forEach(function(button){button.addEventListener('click',function(){closeModal(button.dataset.close);});});
|
|
397
397
|
$('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,subModel:$('new-sub-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);}});
|
|
398
|
-
$('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,visionModel:$('cfg-vision-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('设置已保存,新配置从下一次新建会话生效');await loadConfig();}catch(error){toast(error.message,true);}});
|
|
398
|
+
$('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,visionModel:$('cfg-vision-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('设置已保存,新配置从下一次新建会话生效');await loadConfig();}catch(error){toast(error.message,true);}});
|
|
399
399
|
async function uploadAttachment(sessionId,file){var response=await fetch('/api/sessions/'+encodeURIComponent(sessionId)+'/attachments?name='+encodeURIComponent(file.name||'image'),{method:'POST',headers:{'content-type':file.type||'application/octet-stream'},body:file});var result=await response.json().catch(function(){return{error:'上传响应不是有效 JSON'};});if(!response.ok)throw new Error(result.error||('HTTP '+response.status));return result.attachment;}
|
|
400
|
-
async function handleAttachmentFiles(files){var sessionId=state.selectedId;if(!sessionId||state.uploading)return;var candidates=[].slice.call(files||[]).filter(function(file){return ['image/png','image/jpeg','image/webp','image/gif'].includes(file.type);});var existing=state.attachmentDrafts[sessionId]||[];if(existing.length+candidates.length>10){toast('每条消息最多添加 10 张图片',true);return;}if(!candidates.length){toast('仅支持 PNG、JPEG、WebP、GIF 图片',true);return;}state.uploading=true;if(state.detail)renderDetail();try{for(var i=0;i<candidates.length;i++){if(candidates[i].size>20*1024*1024)throw new Error('单张图片不能超过 20 MB:'+candidates[i].name);existing.push(await uploadAttachment(sessionId,candidates[i]));state.attachmentDrafts[sessionId]=existing;renderAttachmentDrafts();}}catch(error){toast(error.message,true);}finally{state.uploading=false;if(state.detail)renderDetail();$('attachment-input').value='';}}
|
|
400
|
+
async function handleAttachmentFiles(files){var sessionId=state.selectedId;if(!sessionId||state.uploading)return;var candidates=[].slice.call(files||[]).filter(function(file){return ['image/png','image/jpeg','image/webp','image/gif'].includes(file.type);});var existing=state.attachmentDrafts[sessionId]||[];if(existing.length+candidates.length>10){toast('每条消息最多添加 10 张图片',true);return;}if(!candidates.length){toast('仅支持 PNG、JPEG、WebP、GIF 图片',true);return;}state.uploading=true;if(state.detail)renderDetail();try{for(var i=0;i<candidates.length;i++){if(candidates[i].size>20*1024*1024)throw new Error('单张图片不能超过 20 MB:'+candidates[i].name);existing.push(await uploadAttachment(sessionId,candidates[i]));state.attachmentDrafts[sessionId]=existing;renderAttachmentDrafts();}}catch(error){toast(error.message,true);}finally{state.uploading=false;if(state.detail)renderDetail();$('attachment-input').value='';}}
|
|
401
401
|
async function removeAttachment(id){var sessionId=state.selectedId;if(!sessionId)return;try{await api('/api/sessions/'+encodeURIComponent(sessionId)+'/attachments/'+encodeURIComponent(id),{method:'DELETE'});state.attachmentDrafts[sessionId]=(state.attachmentDrafts[sessionId]||[]).filter(function(item){return item.attachmentId!==id;});renderAttachmentDrafts();}catch(error){toast(error.message,true);}}
|
|
402
402
|
async function send(){var sessionId=state.selectedId;var text=$('prompt').value;var attachments=currentAttachments();if(!sessionId||(!text.trim()&&!attachments.length)||state.uploading)return;try{var patch={model:$('session-model').value,subModel:$('session-sub-model').value,effort:$('session-effort').value};if(patch.model!==state.detail.model||patch.subModel!==state.detail.subModel||patch.effort!==state.detail.effort){var updated=await api('/api/sessions/'+encodeURIComponent(sessionId),{method:'PATCH',body:JSON.stringify(patch)});if(state.selectedId===sessionId){state.detail=Object.assign({},state.detail,updated.session);renderDetail();}await loadSessions();}await api('/api/sessions/'+encodeURIComponent(sessionId)+'/messages',{method:'POST',body:JSON.stringify({text:text,attachmentIds:attachments.map(function(item){return item.attachmentId;})})});delete state.configDrafts[sessionId];delete state.promptDrafts[sessionId];delete state.attachmentDrafts[sessionId];if(state.selectedId===sessionId){$('prompt').value='';renderAttachmentDrafts();state.detail.events=[];state.detail.status='running';renderDetail();}var token=++state.detailRequestToken;await loadDetail(sessionId,token);}catch(error){toast(error.message,true);}}
|
|
403
403
|
['session-model','session-sub-model','session-effort'].forEach(function(id){$(id).addEventListener('input',rememberDraft);$(id).addEventListener('change',rememberDraft);});
|
|
@@ -26,12 +26,12 @@ performed here. Completion is accepted only after a normal stop, never a length
|
|
|
26
26
|
limit or interrupted stream. Text mode preserves plain-text output. Parallel
|
|
27
27
|
calls do not share mutable history. `timeoutMs` covers stream consumption;
|
|
28
28
|
optional cancellation is the second argument. Context/response character limits
|
|
29
|
-
default to one million each. Since 0.2.28, oversized input returns
|
|
30
|
-
`status: 'context_limit'`, `diagnostic.code: 'context_budget_exceeded'` and zero
|
|
31
|
-
model calls before network activity. It does not return the input or partial
|
|
32
|
-
content. Invalid configuration budgets still throw. Other runtime failures return
|
|
33
|
-
fixed diagnostics without raw errors. Character limits are not token estimates;
|
|
34
|
-
callers must assemble bounded evidence rather than merely increase the limit.
|
|
29
|
+
default to one million each. Since 0.2.28, oversized input returns
|
|
30
|
+
`status: 'context_limit'`, `diagnostic.code: 'context_budget_exceeded'` and zero
|
|
31
|
+
model calls before network activity. It does not return the input or partial
|
|
32
|
+
content. Invalid configuration budgets still throw. Other runtime failures return
|
|
33
|
+
fixed diagnostics without raw errors. Character limits are not token estimates;
|
|
34
|
+
callers must assemble bounded evidence rather than merely increase the limit.
|
|
35
35
|
|
|
36
36
|
JSON responses are collected as text without constructing partial objects for
|
|
37
37
|
each streamed fragment. This avoids unnecessary parsing and allocation for large
|