chatccc 0.2.255 → 0.2.257
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 +3 -2
- package/config.sample.json +2 -1
- package/deepccc-agent/README.md +8 -1
- package/deepccc-agent/package.json +1 -1
- package/dist/deepccc-agent/src/config.js +2 -0
- package/dist/deepccc-agent/src/context.js +8 -2
- package/dist/deepccc-agent/src/index.js +5 -2
- package/dist/src/adapters/ccc-adapter.js +1 -0
- package/dist/src/cards.js +3 -3
- package/dist/src/config.js +3 -0
- package/dist/src/index.js +1 -1
- package/dist/src/orchestrator.js +11 -11
- package/dist/src/session-chat-binding.js +2 -2
- package/dist/src/session.js +2 -1
- package/dist/src/web-ui.js +106 -4
- package/dist/src/wechat-platform.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -157,7 +157,7 @@ chatccc
|
|
|
157
157
|
|
|
158
158
|
Agent Team 提供本地任务看板和项目主 Agent 入口,但暂不根据任务自动分配 Agent。每个规范化后的本机目录对应一个独立项目和看板,固定包含“头脑风暴、Todo、Doing、Done、搁置”五列,支持新增、编辑、拖动排序和删除任务。页面会自动保存,不需要手动提交。当前按路径识别项目,不检查 Git 仓库或 worktree 关系,任意子目录都可以作为独立项目。
|
|
159
159
|
|
|
160
|
-
打开项目后可以选择 CCC、Claude、Cursor 或 Codex 作为主 Agent。首次设置会创建固定命名为 `主Agent-<目录短名>` 的飞书群和空 Agent Session;后续切换 Agent 或重新关联目录会复用原群,运行中的主 Agent 不允许切换。建群成员取自机器人最近一次收到的飞书私聊;如果尚无私聊记录,网页会提示先给机器人发送任意私聊消息并自动检测。项目群名不会随第一句话或 `/
|
|
160
|
+
打开项目后可以选择 CCC、Claude、Cursor 或 Codex 作为主 Agent。首次设置会创建固定命名为 `主Agent-<目录短名>` 的飞书群和空 Agent Session;后续切换 Agent 或重新关联目录会复用原群,运行中的主 Agent 不允许切换。建群成员取自机器人最近一次收到的飞书私聊;如果尚无私聊记录,网页会提示先给机器人发送任意私聊消息并自动检测。项目群名不会随第一句话或 `/forget` 改变。
|
|
161
161
|
|
|
162
162
|
看板数据默认保存在 `~/.chatccc/agent-team/`,其中 `workspaces.json` 保存最近工作目录索引,`boards/` 保存按稳定 ID 分隔的看板 JSON,`main-agent-bindings/` 单独保存本机飞书群与 Session 绑定。目录移动或重命名后,可从最近目录列表重新关联。数据访问通过仓储接口隔离,后续接入飞书多维表格时可增加双向同步适配器,无需把本机群聊和 Session 状态混入任务同步模型。选择目录、创建群聊和启动 Agent 等操作都由本机 Node 后端执行;当前实现不依赖 Electron。
|
|
163
163
|
|
|
@@ -373,6 +373,7 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
373
373
|
| `ccc.DEEPSEEK_API_KEY` / `ccc.DEEPSEEK_BASE_URL` | CCC Agent 的 API Key 和服务地址;**不限于 DeepSeek**——可填任意 OpenAI 兼容端点(OpenAI、Kimi、通义、智谱、Ollama 本地等) |
|
|
374
374
|
| `ccc.model` | CCC Agent 默认模型 |
|
|
375
375
|
| `ccc.compactionTimeoutMs` | CCC Agent 上下文压缩单轮超时(毫秒),默认 300000(5 分钟);压缩超时会让整轮对话失败,建议保持默认或调大 |
|
|
376
|
+
| `ccc.contextWindow` | CCC Agent 模型上下文窗口(token),默认 1048576(1M,DeepSeek V4 Pro/Flash 原生规格);压缩阈值自动 = 窗口 × 80%;超过模型/服务端实际上限会被 API 拒绝,可在 Web UI 下拉选择或自定义(单位 k) |
|
|
376
377
|
|
|
377
378
|
> **权限控制**:普通消息以 `bypassPermissions` 模式运行,跳过 Agent 操作确认。使用 `/plan` 或 `/ask` 前缀时,ChatCCC 自动切换为只读模式:Claude SDK 仅放行 Read + stop-stuck-loop 网络请求,Codex 使用 `--sandbox read-only`,Cursor 使用 `--mode plan/ask`。请只在可信环境中使用。
|
|
378
379
|
|
|
@@ -395,7 +396,7 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
395
396
|
| `/new cursor` | 创建 Cursor 会话;飞书中会创建新群 |
|
|
396
397
|
| `/new codex` | 创建 Codex 会话;飞书中会创建新群 |
|
|
397
398
|
| `/new ccc` | 创建内置 CCC Agent 会话;飞书中会创建新群 |
|
|
398
|
-
| `/
|
|
399
|
+
| `/forget` | 在当前聊天原地重置会话(忘掉上下文);群聊保留工作目录,飞书私聊固定使用系统用户目录 |
|
|
399
400
|
| `/model` | 查看或切换当前会话的模型 |
|
|
400
401
|
| `/fast` | 查看当前 Codex 会话的 Fast 模式;使用 `/fast on` 或 `/fast off` 切换 |
|
|
401
402
|
| `/stop` | 停止当前回复 |
|
package/config.sample.json
CHANGED
package/deepccc-agent/README.md
CHANGED
|
@@ -77,6 +77,7 @@ $env:DEEPCCC_STREAMING="true"
|
|
|
77
77
|
"model": "deepseek-v4-pro",
|
|
78
78
|
"effort": "",
|
|
79
79
|
"streaming": true,
|
|
80
|
+
"contextWindow": 1048576,
|
|
80
81
|
"rawStreamLogs": {
|
|
81
82
|
"enabled": true,
|
|
82
83
|
"maxBytesPerTurn": 1048576,
|
|
@@ -90,11 +91,17 @@ $env:DEEPCCC_STREAMING="true"
|
|
|
90
91
|
`DEEPCCC_PROVIDER` 或命令行 `--provider` 覆盖:
|
|
91
92
|
|
|
92
93
|
- `openai` 使用 OpenAI-compatible Chat Completions 协议,兼容 DeepSeek、OpenAI、LiteLLM、vLLM 等服务。
|
|
93
|
-
- `anthropic` 使用 Anthropic Messages 协议。配置中的 `baseURL`
|
|
94
|
+
- `anthropic` 使用 Anthropic Messages 协议。配置中的 `baseURL` **完全按填写值使用,不自动补 `/v1`**,请填写到完整版本化地址,例如 DeepSeek 官方 Anthropic 端点为 `https://api.deepseek.com/anthropic/v1`(官方 Anthropic SDK 使用的 `https://api.deepseek.com/anthropic` 基址会拼接为 `.../anthropic/v1/messages`)。`effort` 是 OpenAI/DeepSeek 扩展参数,在 Anthropic 模式下不会发送。
|
|
94
95
|
|
|
95
96
|
`streaming` 控制主对话是否使用流式请求,默认 `true`;也可以通过
|
|
96
97
|
`DEEPCCC_STREAMING=true|false` 覆盖。关闭后,终端会在整条模型响应完成后一次性显示结果。
|
|
97
98
|
|
|
99
|
+
`contextWindow` 是模型上下文窗口(token),默认 `1048576`(1M,DeepSeek V4 Pro/Flash
|
|
100
|
+
原生规格);上下文压缩阈值自动 = `contextWindow × 0.8`(超出即把较早消息压缩为摘要)。
|
|
101
|
+
可通过 `DEEPCCC_CONTEXT_WINDOW` 环境变量覆盖。⚠️ 超过模型/服务端实际上限时请求会被
|
|
102
|
+
API 拒绝(context length exceeded),实际窗口以模型与所用服务端为准(如 litellm 的
|
|
103
|
+
`max_input_tokens`)。
|
|
104
|
+
|
|
98
105
|
`rawStreamLogs.enabled` 默认 `true`,通过 `DEEPCCC_RAW_STREAM_LOGS` 环境变量或配置 JSON 关闭。
|
|
99
106
|
开启时,每次对话的原始流按 gzip JSONL 落到 `~/.deepccc/raw-stream-logs/`,供
|
|
100
107
|
`session_search` 工具在会话被压缩后找回被压缩消息的精确原文(检索时设置
|
|
@@ -14,6 +14,7 @@ export const DEFAULT_CONFIG = {
|
|
|
14
14
|
model: "deepseek-v4-pro",
|
|
15
15
|
effort: "",
|
|
16
16
|
streaming: true,
|
|
17
|
+
contextWindow: 1_048_576,
|
|
17
18
|
rawStreamLogs: {
|
|
18
19
|
// 默认开启:压缩后可通过 session_search(include_raw_logs=true)找回原文。
|
|
19
20
|
// 如需关闭,在 ~/.deepccc/config.json 中设置 rawStreamLogs.enabled=false。
|
|
@@ -67,6 +68,7 @@ function loadConfig() {
|
|
|
67
68
|
model: env("DEEPCCC_MODEL") ?? env("DEEPSEEK_MODEL") ?? file.model ?? DEFAULT_CONFIG.model,
|
|
68
69
|
effort: env("DEEPCCC_EFFORT") ?? env("DEEPSEEK_EFFORT") ?? file.effort ?? DEFAULT_CONFIG.effort,
|
|
69
70
|
streaming: boolEnv("DEEPCCC_STREAMING") ?? file.streaming ?? DEFAULT_CONFIG.streaming,
|
|
71
|
+
contextWindow: numberEnv("DEEPCCC_CONTEXT_WINDOW") ?? file.contextWindow ?? DEFAULT_CONFIG.contextWindow,
|
|
70
72
|
rawStreamLogs: {
|
|
71
73
|
enabled: boolEnv("DEEPCCC_RAW_STREAM_LOGS") ?? rawLogs.enabled ?? DEFAULT_CONFIG.rawStreamLogs.enabled,
|
|
72
74
|
maxBytesPerTurn: numberEnv("DEEPCCC_RAW_STREAM_MAX_BYTES") ?? rawLogs.maxBytesPerTurn ?? DEFAULT_CONFIG.rawStreamLogs.maxBytesPerTurn,
|
|
@@ -3,7 +3,12 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFile
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
export const DEFAULT_BUILTIN_CONTEXT_DIR = join(homedir(), ".deepccc", "sessions");
|
|
6
|
-
|
|
6
|
+
/** 默认模型上下文窗口:1M(DeepSeek V4 Pro/Flash 原生规格)。 */
|
|
7
|
+
export const DEFAULT_CONTEXT_WINDOW_TOKENS = 1_048_576;
|
|
8
|
+
/** 压缩阈值 = 窗口 × 0.8(业界惯例:在窗口接近上限前主动压缩,留出安全余量)。 */
|
|
9
|
+
export const COMPACTION_THRESHOLD_RATIO = 0.8;
|
|
10
|
+
/** 默认压缩阈值 = 1M × 0.8。 */
|
|
11
|
+
export const DEFAULT_COMPACT_AT_TOKENS = Math.floor(DEFAULT_CONTEXT_WINDOW_TOKENS * COMPACTION_THRESHOLD_RATIO);
|
|
7
12
|
export const DEFAULT_KEEP_RECENT_MESSAGES = 16;
|
|
8
13
|
const RECENT_CONTEXT_BUDGET_RATIO = 0.6;
|
|
9
14
|
const MAX_COMPACTION_SUMMARY_CHARS = 8_000;
|
|
@@ -247,7 +252,8 @@ export class BuiltinContextManager {
|
|
|
247
252
|
this.contextDir = options.contextDir ?? DEFAULT_BUILTIN_CONTEXT_DIR;
|
|
248
253
|
this.sessionId = normalizeBuiltinSessionId(options.sessionId ?? defaultBuiltinSessionId());
|
|
249
254
|
this.cwd = options.cwd;
|
|
250
|
-
this.compactAtTokens = options.compactAtTokens
|
|
255
|
+
this.compactAtTokens = options.compactAtTokens
|
|
256
|
+
?? Math.floor((options.contextWindow ?? DEFAULT_CONTEXT_WINDOW_TOKENS) * COMPACTION_THRESHOLD_RATIO);
|
|
251
257
|
this.keepRecentMessages = Math.max(1, options.keepRecentMessages ?? DEFAULT_KEEP_RECENT_MESSAGES);
|
|
252
258
|
this.state = this.load();
|
|
253
259
|
}
|
|
@@ -210,8 +210,10 @@ function normalizeMaxSteps(value) {
|
|
|
210
210
|
return value;
|
|
211
211
|
}
|
|
212
212
|
function normalizeAnthropicBaseURL(baseURL) {
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
// 完全按用户填写的地址使用,不自动补 /v1(AI SDK 仅对官方 api.anthropic.com
|
|
214
|
+
// 特判补一次 /v1,其他地址原样拼接 /messages)。
|
|
215
|
+
// DeepSeek Anthropic 端点示例:https://api.deepseek.com/anthropic/v1。
|
|
216
|
+
return baseURL.trim().replace(/\/+$/, "");
|
|
215
217
|
}
|
|
216
218
|
export class ChatSession {
|
|
217
219
|
model;
|
|
@@ -265,6 +267,7 @@ export class ChatSession {
|
|
|
265
267
|
contextDir: options.contextDir,
|
|
266
268
|
sessionId: options.sessionId ?? defaultBuiltinSessionId(this.cwd),
|
|
267
269
|
cwd: this.cwd,
|
|
270
|
+
contextWindow: options.contextWindow ?? appConfig.contextWindow,
|
|
268
271
|
compactAtTokens: options.compactAtTokens,
|
|
269
272
|
keepRecentMessages: options.keepRecentMessages,
|
|
270
273
|
});
|
|
@@ -8,6 +8,7 @@ function toChatSessionOptions(sessionId, cwd, options) {
|
|
|
8
8
|
persist: true,
|
|
9
9
|
sessionId,
|
|
10
10
|
contextDir: options.contextDir,
|
|
11
|
+
contextWindow: options.contextWindow,
|
|
11
12
|
compactAtTokens: options.compactAtTokens,
|
|
12
13
|
keepRecentMessages: options.keepRecentMessages,
|
|
13
14
|
compactionTimeoutMs: options.compactionTimeoutMs,
|
package/dist/src/cards.js
CHANGED
|
@@ -137,7 +137,7 @@ export function buildHelpCard(userText, opts = {}) {
|
|
|
137
137
|
"发送 **/new cursor** 创建新 Cursor 会话",
|
|
138
138
|
"发送 **/new codex** 创建新 Codex 会话",
|
|
139
139
|
"发送 **/new ccc** 创建新 CCC Agent 会话",
|
|
140
|
-
"发送 **/
|
|
140
|
+
"发送 **/forget** 重置当前会话(忘掉上下文,沿用当前工作目录,不切换)",
|
|
141
141
|
"发送 **/plan** 以规划模式提问(只读,不执行写操作)",
|
|
142
142
|
"发送 **/ask** 以问答模式提问(只读,不执行写操作)",
|
|
143
143
|
"发送 **/usage** 查看当前 Agent 的用量或余额",
|
|
@@ -329,8 +329,8 @@ export function buildSessionsCard(sessions, opts = {}) {
|
|
|
329
329
|
{ tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
|
|
330
330
|
{ tag: "hr" },
|
|
331
331
|
{ tag: "div", text: { tag: "lark_md", content: fixedPrivateSession
|
|
332
|
-
? "当前飞书私聊使用专属会话;默认 Agent 变化后,下一条普通消息会自动创建对应 Agent 的新空会话。发送 **/
|
|
333
|
-
: "在会话群内发送 **/
|
|
332
|
+
? "当前飞书私聊使用专属会话;默认 Agent 变化后,下一条普通消息会自动创建对应 Agent 的新空会话。发送 **/forget** 可在私聊中原地重置(忘掉当前上下文)。群聊会话请回到对应群聊继续,私聊不支持 **/session** 切换。"
|
|
333
|
+
: "在会话群内发送 **/forget** 可重置当前会话(忘掉上下文,创建新 Session,保留工作目录和群聊)。\n发送 **/session 数字**(如 `/session 1`)可将当前群聊切换到列表中对应编号的会话。" } },
|
|
334
334
|
{ tag: "hr" },
|
|
335
335
|
{
|
|
336
336
|
tag: "action",
|
package/dist/src/config.js
CHANGED
|
@@ -86,6 +86,7 @@ const CONFIG_SAMPLE_FILE = join(PROJECT_ROOT, "config.sample.json");
|
|
|
86
86
|
export const DEFAULT_CCC_DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1";
|
|
87
87
|
export const DEFAULT_CCC_MODEL = "deepseek-v4-pro";
|
|
88
88
|
export const DEFAULT_CCC_COMPACTION_TIMEOUT_MS = 5 * 60 * 1000;
|
|
89
|
+
export const DEFAULT_CCC_CONTEXT_WINDOW_TOKENS = 1_048_576;
|
|
89
90
|
/**
|
|
90
91
|
* 将旧位置(PROJECT_ROOT)的持久化数据一次性迁移到 USER_DATA_DIR。
|
|
91
92
|
* 仅当 USER_DATA_DIR 下没有 config.json 且 PROJECT_ROOT 下有旧数据时才执行。
|
|
@@ -312,6 +313,7 @@ function loadConfig() {
|
|
|
312
313
|
effort: "",
|
|
313
314
|
provider: "",
|
|
314
315
|
compactionTimeoutMs: DEFAULT_CCC_COMPACTION_TIMEOUT_MS,
|
|
316
|
+
contextWindow: DEFAULT_CCC_CONTEXT_WINDOW_TOKENS,
|
|
315
317
|
},
|
|
316
318
|
};
|
|
317
319
|
if (!IS_TEST_ENV) {
|
|
@@ -503,6 +505,7 @@ function loadConfig() {
|
|
|
503
505
|
effort: normalizeOptionalConfigField(cccRaw.effort, { label: "ccc.effort" }),
|
|
504
506
|
provider: normalizeCccProviderOverride(cccRaw.provider),
|
|
505
507
|
compactionTimeoutMs: normalizePositiveInteger(cccRaw.compactionTimeoutMs, DEFAULT_CCC_COMPACTION_TIMEOUT_MS),
|
|
508
|
+
contextWindow: normalizePositiveInteger(cccRaw.contextWindow, DEFAULT_CCC_CONTEXT_WINDOW_TOKENS),
|
|
506
509
|
},
|
|
507
510
|
};
|
|
508
511
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -136,7 +136,7 @@ function parseCardAction(data) {
|
|
|
136
136
|
}
|
|
137
137
|
if (!cmd)
|
|
138
138
|
return null;
|
|
139
|
-
const CMD_MAP = { stop: "/stop", cancel: "/cancel", new: "/new", "new claude": "/new claude", "new cursor": "/new cursor", "new codex": "/new codex", restart: "/restart", update: "/update", state: "/state", cd: "/cd", sessions: "/sessions",
|
|
139
|
+
const CMD_MAP = { stop: "/stop", cancel: "/cancel", new: "/new", "new claude": "/new claude", "new cursor": "/new cursor", "new codex": "/new codex", restart: "/restart", update: "/update", state: "/state", cd: "/cd", sessions: "/sessions", forget: "/forget" };
|
|
140
140
|
let text = CMD_MAP[cmd] ?? "";
|
|
141
141
|
if (cmd === "cd" && typeof action.value === "object" && action.value !== null) {
|
|
142
142
|
const path = action.value.path;
|
package/dist/src/orchestrator.js
CHANGED
|
@@ -1021,7 +1021,7 @@ export async function handleCommand(platform, text, chatId, openId, msgTimestamp
|
|
|
1021
1021
|
// /new 始终创建独立群聊。飞书私聊自己的常驻 session 不在这里切换。
|
|
1022
1022
|
if (chatType === "p2p" && platform.kind === "wechat") {
|
|
1023
1023
|
// 先解绑旧 session(如果存在),避免旧 session 的 display loop
|
|
1024
|
-
// 继续往同一个 chat 推送内容(/
|
|
1024
|
+
// 继续往同一个 chat 推送内容(/forget 走 switchChatBinding 已有此逻辑,
|
|
1025
1025
|
// 但 /new p2p 之前遗漏了解绑)。
|
|
1026
1026
|
const oldRegistry = await loadSessionRegistryForBinding();
|
|
1027
1027
|
const oldRecord = oldRegistry[chatId];
|
|
@@ -1056,7 +1056,7 @@ export async function handleCommand(platform, text, chatId, openId, msgTimestamp
|
|
|
1056
1056
|
`直接在这里发消息即可与 ${toolLabel} 对话。\n\n` +
|
|
1057
1057
|
`发送 **/cd** 切换新建会话的默认目录。\n` +
|
|
1058
1058
|
`发送 **/model** 查看或切换当前会话的模型。${fastHelpAfterModel(tool)}\n` +
|
|
1059
|
-
`发送 **/new** 创建新会话,**/
|
|
1059
|
+
`发送 **/new** 创建新会话,**/forget** 重置当前会话(沿用工作目录)。\n` +
|
|
1060
1060
|
`发送 **/sessions** 查看所有会话状态。\n` +
|
|
1061
1061
|
`发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。` +
|
|
1062
1062
|
usageHelpLine(tool), "green");
|
|
@@ -1117,7 +1117,7 @@ export async function handleCommand(platform, text, chatId, openId, msgTimestamp
|
|
|
1117
1117
|
`直接在这里发消息即可与 ${toolLabel} 对话。\n\n` +
|
|
1118
1118
|
`发送 **/cd** 切换新建会话的默认目录。\n` +
|
|
1119
1119
|
`发送 **/model** 查看或切换当前会话的模型。${fastHelpAfterModel(tool)}\n` +
|
|
1120
|
-
`发送 **/new** 创建新会话,**/
|
|
1120
|
+
`发送 **/new** 创建新会话,**/forget** 重置当前会话(沿用工作目录)。\n` +
|
|
1121
1121
|
`发送 **/sessions** 查看所有会话状态。\n` +
|
|
1122
1122
|
`发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。` +
|
|
1123
1123
|
usageHelpLine(tool), "green");
|
|
@@ -1413,12 +1413,12 @@ export async function handleCommand(platform, text, chatId, openId, msgTimestamp
|
|
|
1413
1413
|
logTrace(tid, "DONE", { outcome: "sessions", ok, count: cardData.length });
|
|
1414
1414
|
return;
|
|
1415
1415
|
}
|
|
1416
|
-
if (isCommandText && textLower === "/
|
|
1417
|
-
logTrace(tid, "BRANCH", { cmd: "/
|
|
1416
|
+
if (isCommandText && textLower === "/forget") {
|
|
1417
|
+
logTrace(tid, "BRANCH", { cmd: "/forget" });
|
|
1418
1418
|
let cwd;
|
|
1419
1419
|
if (isFeishuP2p(platform, chatType)) {
|
|
1420
1420
|
// 飞书私聊不支持切换工作目录。即使 /cd 已为后续 /new 群聊
|
|
1421
|
-
// 保存了其他默认目录,/
|
|
1421
|
+
// 保存了其他默认目录,/forget 仍必须在运行 ChatCCC 的用户目录重建。
|
|
1422
1422
|
cwd = homedir();
|
|
1423
1423
|
}
|
|
1424
1424
|
else {
|
|
@@ -1440,7 +1440,7 @@ export async function handleCommand(platform, text, chatId, openId, msgTimestamp
|
|
|
1440
1440
|
}
|
|
1441
1441
|
catch (err) {
|
|
1442
1442
|
logTrace(tid, "DONE", {
|
|
1443
|
-
outcome: "
|
|
1443
|
+
outcome: "forget_session_fail",
|
|
1444
1444
|
error: err.message,
|
|
1445
1445
|
});
|
|
1446
1446
|
await platform.sendCard(chatId, "Error", `Failed to create new session:\n${err.message}`, "red");
|
|
@@ -1463,14 +1463,14 @@ export async function handleCommand(platform, text, chatId, openId, msgTimestamp
|
|
|
1463
1463
|
});
|
|
1464
1464
|
if (!switchResult.ok) {
|
|
1465
1465
|
logTrace(tid, "DONE", {
|
|
1466
|
-
outcome: "
|
|
1466
|
+
outcome: "forget_update_chat_fail",
|
|
1467
1467
|
error: switchResult.error?.message,
|
|
1468
1468
|
});
|
|
1469
1469
|
await platform.sendCard(chatId, "Error", `更新群描述失败,会话未切换(新 session 已创建但未启用):\n${switchResult.error?.message}`, "red");
|
|
1470
1470
|
return;
|
|
1471
1471
|
}
|
|
1472
1472
|
if (chatType !== "p2p") {
|
|
1473
|
-
console.log(`[${ts()}] [
|
|
1473
|
+
console.log(`[${ts()}] [FORGET] Group updated: name="${newName}" desc="${descPrefix} ${newSessionId}"`);
|
|
1474
1474
|
}
|
|
1475
1475
|
setChatAvatarForSession(platform, chatId, descriptionTool, "new", newSessionId).catch(() => { });
|
|
1476
1476
|
await platform.sendCard(chatId, `${toolLabel} Session Reset`, `会话已重置为新的 **${toolLabel}** 会话。\n\n` +
|
|
@@ -1479,8 +1479,8 @@ export async function handleCommand(platform, text, chatId, openId, msgTimestamp
|
|
|
1479
1479
|
`直接在这里发消息即可继续对话。\n` +
|
|
1480
1480
|
`发送 **/cd** 可切换新建会话的默认目录。\n` +
|
|
1481
1481
|
`发送 **/model** 查看或切换当前会话的模型。${fastHelpAfterModel(descriptionTool)}`, "green");
|
|
1482
|
-
console.log(`[${ts()}] [
|
|
1483
|
-
logTrace(tid, "DONE", { outcome: "
|
|
1482
|
+
console.log(`[${ts()}] [FORGET] Session ${sessionId} → ${newSessionId} (same cwd=${cwd})`);
|
|
1483
|
+
logTrace(tid, "DONE", { outcome: "forget", newSessionId, cwd });
|
|
1484
1484
|
return;
|
|
1485
1485
|
}
|
|
1486
1486
|
if (isCommandText && textLower === "/deleteg") {
|
|
@@ -30,7 +30,7 @@ export function unbindChatFromSession(sessionId, chatId) {
|
|
|
30
30
|
sessionChatsMap.delete(sessionId);
|
|
31
31
|
}
|
|
32
32
|
// 双保险:lastActiveChatMap 是 sessionId → 最后活跃 chatId 的快照,
|
|
33
|
-
// 若被解绑的 chatId 正好是该 session 的 lastActive(典型场景:/
|
|
33
|
+
// 若被解绑的 chatId 正好是该 session 的 lastActive(典型场景:/forget
|
|
34
34
|
// 把当前群从旧 session 改嫁到新 session),不清理会让旧 session 的
|
|
35
35
|
// display loop 继续向已离开的群推送卡片。
|
|
36
36
|
if (lastActiveChatMap.get(sessionId)?.chatId === chatId) {
|
|
@@ -99,7 +99,7 @@ export function getLastActiveChat(sessionId) {
|
|
|
99
99
|
* display loop 决定推送目标 chat 的纯函数:
|
|
100
100
|
* 仅当 lastActiveChat 仍然绑定到该 session 时才返回,否则返回 undefined。
|
|
101
101
|
*
|
|
102
|
-
* 修复 bug:用户 `/
|
|
102
|
+
* 修复 bug:用户 `/forget` 把当前群从旧 session 改嫁到新 session 后,旧
|
|
103
103
|
* session 的 lastActiveChatMap 残留旧 chatId 快照,display loop 会继续
|
|
104
104
|
* 在该群创建/更新卡片。这里通过交叉验证 sessionChatsMap 把这种悬挂记录
|
|
105
105
|
* 排除掉——loop 拿到 undefined 就走"无活跃群"分支自然停推。
|
package/dist/src/session.js
CHANGED
|
@@ -530,6 +530,7 @@ export function getAdapterForTool(tool, sessionId) {
|
|
|
530
530
|
model: effectiveModel || undefined,
|
|
531
531
|
effort: effectiveEffort || undefined,
|
|
532
532
|
compactionTimeoutMs: config.ccc.compactionTimeoutMs,
|
|
533
|
+
contextWindow: config.ccc.contextWindow,
|
|
533
534
|
// 留空("")不传 → ChatSession 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_PROVIDER)
|
|
534
535
|
...(config.ccc.provider ? { provider: config.ccc.provider } : {}),
|
|
535
536
|
});
|
|
@@ -1621,7 +1622,7 @@ export function startUnifiedDisplayLoop() {
|
|
|
1621
1622
|
continue;
|
|
1622
1623
|
}
|
|
1623
1624
|
// 交叉验证:chat 当前绑定的 session 是否仍是 display 记录的 session。
|
|
1624
|
-
// 若 chat 已被切换到其他 session(如 /
|
|
1625
|
+
// 若 chat 已被切换到其他 session(如 /forget),旧 display 必须停推。
|
|
1625
1626
|
const currentSessionForChat = sessionInfoMap.get(chatId)?.sessionId;
|
|
1626
1627
|
if (currentSessionForChat && currentSessionForChat !== sessionId) {
|
|
1627
1628
|
if (state.status !== "running") {
|
package/dist/src/web-ui.js
CHANGED
|
@@ -495,6 +495,10 @@ export function unflattenConfig(flat) {
|
|
|
495
495
|
result.ccc = result.ccc || {};
|
|
496
496
|
result.ccc.provider = val;
|
|
497
497
|
}
|
|
498
|
+
else if (key === "CHATCCC_CCC_CONTEXT_WINDOW") {
|
|
499
|
+
result.ccc = result.ccc || {};
|
|
500
|
+
result.ccc.contextWindow = parseInt(String(val), 10) || 1048576;
|
|
501
|
+
}
|
|
498
502
|
else if (key === "CHATCCC_CCC_ENABLED") {
|
|
499
503
|
result.ccc = result.ccc || {};
|
|
500
504
|
result.ccc.enabled = val === true || val === "true";
|
|
@@ -631,7 +635,7 @@ async function handleClaudeSdkInstall(_req, res) {
|
|
|
631
635
|
// ---------------------------------------------------------------------------
|
|
632
636
|
// HTML page (embedded template)
|
|
633
637
|
// ---------------------------------------------------------------------------
|
|
634
|
-
export const PAGE_HTML = `<!DOCTYPE html>
|
|
638
|
+
export const PAGE_HTML = `<!DOCTYPE html>
|
|
635
639
|
<html lang="zh-CN">
|
|
636
640
|
<head>
|
|
637
641
|
<meta charset="UTF-8">
|
|
@@ -886,6 +890,20 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
886
890
|
<option value="max">max - 最强推理</option>
|
|
887
891
|
</select>
|
|
888
892
|
</div>
|
|
893
|
+
<div class="form-group">
|
|
894
|
+
<label>上下文窗口(模型最大上下文)</label>
|
|
895
|
+
<select id="field-CHATCCC_CCC_CONTEXT_WINDOW" onchange="onContextWindowPresetChange('field-', this.value)">
|
|
896
|
+
<option value="1m">1M(1,048,576 tokens,推荐)</option>
|
|
897
|
+
<option value="512k">512K(524,288 tokens)</option>
|
|
898
|
+
<option value="256k">256K(262,144 tokens)</option>
|
|
899
|
+
<option value="128k">128K(131,072 tokens)</option>
|
|
900
|
+
<option value="custom">自定义(单位 k)</option>
|
|
901
|
+
</select>
|
|
902
|
+
<div id="field-CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM_ROW" style="margin-top:6px;display:none">
|
|
903
|
+
<input type="number" id="field-CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM" min="1" placeholder="例如 768 表示 768K(786,432 tokens)" style="width:100%;padding:8px 12px;border:1px solid #cbd5e1;border-radius:8px;font-size:14px;outline:none">
|
|
904
|
+
</div>
|
|
905
|
+
<div class="hint">压缩阈值自动 = 窗口 × 80%(超出即把较早消息压缩为摘要)。⚠️ 超过模型/服务端实际上限时请求会被 API 直接拒绝(context length exceeded);实际窗口以模型与所用服务端为准(如 litellm 代理的 max_input_tokens)。</div>
|
|
906
|
+
</div>
|
|
889
907
|
</fieldset>
|
|
890
908
|
</div>
|
|
891
909
|
|
|
@@ -1211,7 +1229,7 @@ const AGENT_FIELDS = {
|
|
|
1211
1229
|
claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL','CHATCCC_ANTHROPIC_MAX_TURN'],
|
|
1212
1230
|
cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL','CHATCCC_CURSOR_ALTERNATIVE_MODEL','CHATCCC_CURSOR_AVATAR_BATTERY_MODE','CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'],
|
|
1213
1231
|
codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT','CHATCCC_CODEX_FAST_MODE'],
|
|
1214
|
-
ccc: ['CHATCCC_CCC_API_KEY','CHATCCC_CCC_BASE_URL','CHATCCC_CCC_MODEL','CHATCCC_CCC_ALTERNATIVE_MODEL','CHATCCC_CCC_EFFORT','CHATCCC_CCC_PROVIDER']
|
|
1232
|
+
ccc: ['CHATCCC_CCC_API_KEY','CHATCCC_CCC_BASE_URL','CHATCCC_CCC_MODEL','CHATCCC_CCC_ALTERNATIVE_MODEL','CHATCCC_CCC_EFFORT','CHATCCC_CCC_PROVIDER','CHATCCC_CCC_CONTEXT_WINDOW']
|
|
1215
1233
|
};
|
|
1216
1234
|
const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
|
|
1217
1235
|
const WEB_UI_FIELDS = ['CHATCCC_WEB_UI_OPEN_ON_START'];
|
|
@@ -1221,6 +1239,58 @@ function cursorBatteryModeLabel(value) {
|
|
|
1221
1239
|
return value === 'onDemandUse' ? 'On demand use 金额' : 'API 使用比例';
|
|
1222
1240
|
}
|
|
1223
1241
|
|
|
1242
|
+
// 上下文窗口:预设档位 ↔ tokens。下拉值用小写 k / m 标签,自定义输入以 k 为单位。
|
|
1243
|
+
function contextWindowPresetFor(tokens) {
|
|
1244
|
+
if (tokens === 1048576) return '1m';
|
|
1245
|
+
if (tokens === 524288) return '512k';
|
|
1246
|
+
if (tokens === 262144) return '256k';
|
|
1247
|
+
if (tokens === 131072) return '128k';
|
|
1248
|
+
return 'custom';
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function contextWindowTokensLabel(tokens) {
|
|
1252
|
+
var t = Number(tokens) || 1048576;
|
|
1253
|
+
if (t >= 1048576) return (t / 1048576) + 'M (' + t.toLocaleString('en-US') + ' tokens)';
|
|
1254
|
+
return (t / 1024) + 'K (' + t.toLocaleString('en-US') + ' tokens)';
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// 根据 tokens 回填预设下拉 + 自定义输入;prefix 为 'field-'(向导)或 'edit-'(编辑弹窗)。
|
|
1258
|
+
function prefillContextWindow(prefix, tokens) {
|
|
1259
|
+
var t = Number(tokens) || 1048576;
|
|
1260
|
+
var selectEl = document.getElementById(prefix + 'CHATCCC_CCC_CONTEXT_WINDOW');
|
|
1261
|
+
if (!selectEl) return;
|
|
1262
|
+
var preset = contextWindowPresetFor(t);
|
|
1263
|
+
selectEl.value = preset;
|
|
1264
|
+
onContextWindowPresetChange(prefix, preset);
|
|
1265
|
+
if (preset === 'custom') {
|
|
1266
|
+
var customEl = document.getElementById(prefix + 'CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM');
|
|
1267
|
+
if (customEl) customEl.value = String(Math.round(t / 1024));
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// 预设切换:custom 时显示自定义输入框(单位 k)。
|
|
1272
|
+
function onContextWindowPresetChange(prefix, value) {
|
|
1273
|
+
var rowId = prefix === 'edit-' ? 'edit-CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM_ROW' : 'field-CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM_ROW';
|
|
1274
|
+
var row = document.getElementById(rowId);
|
|
1275
|
+
if (row) row.style.display = (value === 'custom') ? '' : 'none';
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// 把预设下拉 + 自定义输入转换为 tokens 数字字符串(用于提交给服务端)。
|
|
1279
|
+
function contextWindowToTokensValue(prefix) {
|
|
1280
|
+
var selectEl = document.getElementById(prefix + 'CHATCCC_CCC_CONTEXT_WINDOW');
|
|
1281
|
+
if (!selectEl || !selectEl.value) return null;
|
|
1282
|
+
var v = selectEl.value;
|
|
1283
|
+
if (v === '1m') return String(1048576);
|
|
1284
|
+
if (v === 'custom') {
|
|
1285
|
+
var customEl = document.getElementById(prefix + 'CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM');
|
|
1286
|
+
var k = parseInt((customEl && customEl.value.trim()) || '', 10);
|
|
1287
|
+
if (!Number.isFinite(k) || k <= 0) k = 1024; // 非法输入回退 1M
|
|
1288
|
+
return String(k * 1024);
|
|
1289
|
+
}
|
|
1290
|
+
var presetK = parseInt(v, 10); // '128k' → 128
|
|
1291
|
+
return String((presetK || 1024) * 1024);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1224
1294
|
function configEffectHint(section) {
|
|
1225
1295
|
if (section === 'feishu') return '生效范围:App ID、App Secret、平台类型或飞书开关变更需要重启 ChatCCC;其它未变更项仅保存。';
|
|
1226
1296
|
if (section === 'webUi') return '生效范围:下次直接启动生效;内部重启始终不打开。';
|
|
@@ -1548,6 +1618,7 @@ function renderStep2() {
|
|
|
1548
1618
|
prefillNested('field-CHATCCC_CCC_MODEL', c.ccc.model);
|
|
1549
1619
|
prefillNested('field-CHATCCC_CCC_ALTERNATIVE_MODEL', c.ccc.alternativeModel);
|
|
1550
1620
|
prefillNested('field-CHATCCC_CCC_EFFORT', c.ccc.effort);
|
|
1621
|
+
prefillContextWindow('field-', c.ccc.contextWindow);
|
|
1551
1622
|
}
|
|
1552
1623
|
|
|
1553
1624
|
// 按已有 config 决定每个 Agent 默认是否开启:优先 enabled 字段,缺省时按"任一字段非空"
|
|
@@ -1647,6 +1718,11 @@ function collectAllFields() {
|
|
|
1647
1718
|
}
|
|
1648
1719
|
if (state.agentsEnabled.ccc) {
|
|
1649
1720
|
AGENT_FIELDS.ccc.forEach(function(key){
|
|
1721
|
+
if (key === 'CHATCCC_CCC_CONTEXT_WINDOW') {
|
|
1722
|
+
var cw = contextWindowToTokensValue('field-');
|
|
1723
|
+
if (cw !== null) vars[key] = cw;
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1650
1726
|
var el = document.getElementById('field-' + key);
|
|
1651
1727
|
if (el && el.value.trim()) vars[key] = el.value.trim();
|
|
1652
1728
|
});
|
|
@@ -1728,6 +1804,7 @@ function renderStep3() {
|
|
|
1728
1804
|
lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CCC_MODEL || '(留空)') + '</span></div>');
|
|
1729
1805
|
lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CCC_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
|
|
1730
1806
|
lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CCC_EFFORT || '(留空)') + '</span></div>');
|
|
1807
|
+
lines.push('<div class="config-row"><span class="key">上下文窗口</span><span class="val">' + contextWindowTokensLabel(vars.CHATCCC_CCC_CONTEXT_WINDOW || 1048576) + '</span></div>');
|
|
1731
1808
|
}
|
|
1732
1809
|
});
|
|
1733
1810
|
document.getElementById('review-content').innerHTML = lines.join('');
|
|
@@ -2023,14 +2100,15 @@ function editSection(section) {
|
|
|
2023
2100
|
'CHATCCC_CODEX_FAST_MODE': 'Fast 模式',
|
|
2024
2101
|
'CHATCCC_CCC_API_KEY': 'API Key', 'CHATCCC_CCC_BASE_URL': 'Base URL',
|
|
2025
2102
|
'CHATCCC_CCC_PROVIDER': 'API 协议(选填)',
|
|
2026
|
-
'CHATCCC_CCC_MODEL': '模型', 'CHATCCC_CCC_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CCC_EFFORT': 'Effort'
|
|
2103
|
+
'CHATCCC_CCC_MODEL': '模型', 'CHATCCC_CCC_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CCC_EFFORT': 'Effort', 'CHATCCC_CCC_CONTEXT_WINDOW': '上下文窗口'
|
|
2027
2104
|
};
|
|
2028
2105
|
var hintMap = {
|
|
2029
2106
|
'CHATCCC_WEB_UI_OPEN_ON_START': '关闭后可继续手动访问 http://localhost:<端口>/;/restart、/update 和 Web UI 重启无论此项为何值都不会自动打开。',
|
|
2030
2107
|
'CHATCCC_CHROME_DEVTOOLS_ENABLED': '依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。',
|
|
2031
2108
|
'CHATCCC_CHROME_DEVTOOLS_PORT': '默认 15166,健康检查端点为 http://127.0.0.1:15166/json/version。',
|
|
2032
2109
|
'CHATCCC_CHROME_DEVTOOLS_PATH': '选填。留空时自动探测 Google Chrome。',
|
|
2033
|
-
'CHATCCC_CCC_PROVIDER': '与 Base URL 强相关:OpenAI 兼容端点选 openai;Anthropic Messages 端点选 anthropic。留空 = 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_PROVIDER),改动需重启 ChatCCC 生效。'
|
|
2110
|
+
'CHATCCC_CCC_PROVIDER': '与 Base URL 强相关:OpenAI 兼容端点选 openai;Anthropic Messages 端点选 anthropic。留空 = 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_PROVIDER),改动需重启 ChatCCC 生效。',
|
|
2111
|
+
'CHATCCC_CCC_CONTEXT_WINDOW': '压缩阈值自动 = 窗口 × 80%(超出即把较早消息压缩为摘要)。⚠️ 超过模型/服务端实际上限时请求会被 API 直接拒绝(context length exceeded),实际窗口以模型与所用服务端为准(如 litellm 代理的 max_input_tokens);单位 k = 1024 tokens,1M = 1,048,576 tokens。'
|
|
2034
2112
|
};
|
|
2035
2113
|
|
|
2036
2114
|
if (section === 'chromeDevtools') {
|
|
@@ -2076,6 +2154,7 @@ function editSection(section) {
|
|
|
2076
2154
|
else if (key === 'CHATCCC_CCC_MODEL') val = state.config.ccc.model || '';
|
|
2077
2155
|
else if (key === 'CHATCCC_CCC_ALTERNATIVE_MODEL') val = state.config.ccc.alternativeModel || '';
|
|
2078
2156
|
else if (key === 'CHATCCC_CCC_EFFORT') val = state.config.ccc.effort || '';
|
|
2157
|
+
else if (key === 'CHATCCC_CCC_CONTEXT_WINDOW') val = state.config.ccc.contextWindow || '1048576';
|
|
2079
2158
|
}
|
|
2080
2159
|
}
|
|
2081
2160
|
var isSecret = key.includes('SECRET') || key.includes('API_KEY');
|
|
@@ -2102,6 +2181,22 @@ function editSection(section) {
|
|
|
2102
2181
|
html += '</select>';
|
|
2103
2182
|
if (hintMap[key]) html += '<div class="hint" style="margin-top:6px;line-height:1.5">' + hintMap[key] + '</div>';
|
|
2104
2183
|
html += '</div>';
|
|
2184
|
+
} else if (key === 'CHATCCC_CCC_CONTEXT_WINDOW') {
|
|
2185
|
+
var cwVal = val || '1048576';
|
|
2186
|
+
var cwPreset = contextWindowPresetFor(cwVal);
|
|
2187
|
+
html += '<div class="form-group"><label>' + (labelMap[key] || key) + '</label>';
|
|
2188
|
+
html += '<select id="edit-' + key + '" onchange="onContextWindowPresetChange(\\'edit-\\', this.value)" style="width:100%;padding:8px 12px;border:1px solid #cbd5e1;border-radius:8px;font-size:14px;outline:none">';
|
|
2189
|
+
html += '<option value="1m"' + (cwPreset === '1m' ? ' selected' : '') + '>1M(1,048,576 tokens,推荐)</option>';
|
|
2190
|
+
html += '<option value="512k"' + (cwPreset === '512k' ? ' selected' : '') + '>512K(524,288 tokens)</option>';
|
|
2191
|
+
html += '<option value="256k"' + (cwPreset === '256k' ? ' selected' : '') + '>256K(262,144 tokens)</option>';
|
|
2192
|
+
html += '<option value="128k"' + (cwPreset === '128k' ? ' selected' : '') + '>128K(131,072 tokens)</option>';
|
|
2193
|
+
html += '<option value="custom"' + (cwPreset === 'custom' ? ' selected' : '') + '>自定义(单位 k)</option>';
|
|
2194
|
+
html += '</select>';
|
|
2195
|
+
html += '<div id="edit-CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM_ROW" style="margin-top:6px;display:' + (cwPreset === 'custom' ? '' : 'none') + '">';
|
|
2196
|
+
html += '<input type="number" id="edit-CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM" min="1" placeholder="例如 768 表示 768K(786,432 tokens)" style="width:100%;padding:8px 12px;border:1px solid #cbd5e1;border-radius:8px;font-size:14px;outline:none" value="' + (cwPreset === 'custom' ? Math.round(Number(cwVal) / 1024) : '') + '">';
|
|
2197
|
+
html += '</div>';
|
|
2198
|
+
if (hintMap[key]) html += '<div class="hint" style="margin-top:6px;line-height:1.5">' + hintMap[key] + '</div>';
|
|
2199
|
+
html += '</div>';
|
|
2105
2200
|
} else {
|
|
2106
2201
|
var rowId = key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'
|
|
2107
2202
|
? ' id="edit-cursor-on-demand-budget-row"'
|
|
@@ -2129,6 +2224,9 @@ function editSection(section) {
|
|
|
2129
2224
|
html += '</select></div>';
|
|
2130
2225
|
}
|
|
2131
2226
|
document.getElementById('edit-modal-fields').innerHTML = html;
|
|
2227
|
+
if (section === 'ccc') {
|
|
2228
|
+
prefillContextWindow('edit-', (state.config.ccc && state.config.ccc.contextWindow) || 1048576);
|
|
2229
|
+
}
|
|
2132
2230
|
if (section === 'cursor') {
|
|
2133
2231
|
var editModeEl = document.getElementById('edit-CHATCCC_CURSOR_AVATAR_BATTERY_MODE');
|
|
2134
2232
|
onCursorBatteryModeChange('edit-', editModeEl ? editModeEl.value : 'apiPercent');
|
|
@@ -2158,6 +2256,10 @@ async function saveEdit() {
|
|
|
2158
2256
|
var el = document.getElementById('edit-' + key);
|
|
2159
2257
|
if (!el) return;
|
|
2160
2258
|
if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
|
|
2259
|
+
else if (key === 'CHATCCC_CCC_CONTEXT_WINDOW') {
|
|
2260
|
+
var cw = contextWindowToTokensValue('edit-');
|
|
2261
|
+
if (cw !== null) vars[key] = cw;
|
|
2262
|
+
}
|
|
2161
2263
|
else vars[key] = el.value.trim();
|
|
2162
2264
|
});
|
|
2163
2265
|
if (editSectionType === 'chromeDevtools' && !vars.CHATCCC_CHROME_DEVTOOLS_PORT) {
|
|
@@ -537,7 +537,7 @@ async function handleWechatMessage(message, handler) {
|
|
|
537
537
|
appendChatLog(chatId, chatId, fullText);
|
|
538
538
|
// 用户回复,重置 claw 连发计数
|
|
539
539
|
consecutiveSendCount.set(chatId, 0);
|
|
540
|
-
// WeChat 中所有会话都视为 p2p,/new 复用 p2p 路径(等同飞书 /
|
|
540
|
+
// WeChat 中所有会话都视为 p2p,/new 复用 p2p 路径(等同飞书 /forget 效果)
|
|
541
541
|
// 不 await:避免长 prompt 阻塞后续消息处理(如 /cd、/stop 等命令)
|
|
542
542
|
handler(fullText, chatId, chatId, msgTimestamp, "p2p").catch((err) => {
|
|
543
543
|
platformLog(`消息处理失败: ${err.stack ?? err.message}`);
|