chatccc 0.2.214 → 0.2.215
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 +12 -7
- package/config.sample.json +2 -1
- package/package.json +1 -1
- package/src/__tests__/cards.test.ts +37 -1
- package/src/__tests__/codex-adapter.test.ts +18 -3
- package/src/__tests__/config-reload.test.ts +6 -5
- package/src/__tests__/config-sample.test.ts +8 -7
- package/src/__tests__/orchestrator.test.ts +60 -1
- package/src/__tests__/session.test.ts +23 -1
- package/src/__tests__/web-ui.test.ts +17 -2
- package/src/adapters/codex-adapter.ts +40 -6
- package/src/cards.ts +43 -2
- package/src/config.ts +10 -6
- package/src/orchestrator.ts +116 -21
- package/src/session.ts +26 -5
- package/src/web-ui.ts +54 -37
package/README.md
CHANGED
|
@@ -295,10 +295,11 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
295
295
|
},
|
|
296
296
|
"codex": {
|
|
297
297
|
"enabled": false,
|
|
298
|
-
"defaultAgent": false,
|
|
299
|
-
"path": "",
|
|
300
|
-
"model": "",
|
|
301
|
-
"effort": ""
|
|
298
|
+
"defaultAgent": false,
|
|
299
|
+
"path": "",
|
|
300
|
+
"model": "",
|
|
301
|
+
"effort": "",
|
|
302
|
+
"fastMode": false
|
|
302
303
|
}
|
|
303
304
|
}
|
|
304
305
|
```
|
|
@@ -319,7 +320,8 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
319
320
|
| `allowInterrupt` | 是否允许新消息中断正在运行的任务;默认 false |
|
|
320
321
|
| `*.enabled` | 是否启用对应 AI Agent |
|
|
321
322
|
| `*.defaultAgent` | `/new` 未指定 Agent 时使用哪个工具;飞书私聊会在下一条普通消息到达时跟随变化并创建新的空会话 |
|
|
322
|
-
| `cursor.path` / `codex.path` | CLI 可执行文件路径;留空时自动探测或使用 PATH |
|
|
323
|
+
| `cursor.path` / `codex.path` | CLI 可执行文件路径;留空时自动探测或使用 PATH |
|
|
324
|
+
| `codex.fastMode` | Codex Fast 模式的全局默认值;默认 `false`,每次调用都会显式覆盖 Codex CLI 的 service tier |
|
|
323
325
|
| `cursor.avatarBatteryMode` | Cursor 头像电量显示来源:`apiPercent` 或 `onDemandUse` |
|
|
324
326
|
| `cursor.onDemandMonthlyBudget` | `avatarBatteryMode=onDemandUse` 时用于计算电量的月预算 |
|
|
325
327
|
| `claude.model` / `claude.subagentModel` / `claude.effort` | 选填;设置后传给 Claude Agent SDK,留空以 `~/.claude/settings.json` 为准 |
|
|
@@ -345,8 +347,9 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
345
347
|
| `/new cursor` | 创建 Cursor 会话;飞书中会创建新群 |
|
|
346
348
|
| `/new codex` | 创建 Codex 会话;飞书中会创建新群 |
|
|
347
349
|
| `/newh` | 在当前聊天原地重置会话;群聊保留工作目录,飞书私聊固定使用系统用户目录 |
|
|
348
|
-
| `/model` | 查看或切换当前会话的模型 |
|
|
349
|
-
| `/
|
|
350
|
+
| `/model` | 查看或切换当前会话的模型 |
|
|
351
|
+
| `/fast` | 查看当前 Codex 会话的 Fast 模式;使用 `/fast on` 或 `/fast off` 切换 |
|
|
352
|
+
| `/stop` | 停止当前回复 |
|
|
350
353
|
| `/cancel` | 取消当前会话里排队等待处理的消息 |
|
|
351
354
|
| `/state` | 查看当前会话状态 |
|
|
352
355
|
| `/cd` | 查看或设置后续新建会话的默认工作目录,不改变当前会话;飞书私聊自身始终使用系统用户目录 |
|
|
@@ -364,6 +367,8 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
364
367
|
`/update` 会在执行 npm 更新前把飞书消息或按钮事件 ID 原子写入 `~/.chatccc/state/update-command-guard.json`。同一 ID 跨重启重投时会静默忽略;用户主动发送的新 `/update` 因事件 ID 不同,仍可立即执行。该保护仅作用于 `/update`,普通消息与 `/restart` 的处理不变。
|
|
365
368
|
|
|
366
369
|
> **模型切换**:`/model` 查看当前会话 Agent 的可选模型清单,`/model <名称>` 模糊匹配切换,`/model clear` 恢复默认。可选模型来自当前 Agent 的配置:Claude 使用 `claude.model` / `claude.subagentModel`,Cursor 使用 `cursor.model`,Codex 使用 `codex.model`。
|
|
370
|
+
|
|
371
|
+
> **Codex Fast 模式**:Web UI 中的“Fast 模式”设置新 Codex 会话的全局默认值,默认关闭。进入 Codex 会话后,`/fast` 查询当前状态,`/fast on` 和 `/fast off` 只覆盖当前会话并从下一条消息生效。ChatCCC 会显式向 Codex CLI 传入 `service_tier="fast"` 或 `service_tier="default"`,因此关闭时不会继承用户 `config.toml` 中可能开启的 Fast。
|
|
367
372
|
|
|
368
373
|
---
|
|
369
374
|
|
package/config.sample.json
CHANGED
package/package.json
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
buildStatusCard,
|
|
9
9
|
buildCodexUsageCard,
|
|
10
10
|
buildCodexResetConfirmCard,
|
|
11
|
+
buildFastModeCard,
|
|
12
|
+
buildModelCard,
|
|
11
13
|
buildButtons,
|
|
12
14
|
truncateContent,
|
|
13
15
|
getToolEmoji,
|
|
@@ -18,7 +20,7 @@ import { ABD_HELP_LINE } from "../shared-prefix.ts";
|
|
|
18
20
|
// truncateContent
|
|
19
21
|
// ---------------------------------------------------------------------------
|
|
20
22
|
|
|
21
|
-
describe("truncateContent", () => {
|
|
23
|
+
describe("truncateContent", () => {
|
|
22
24
|
it("returns original text when under limits", () => {
|
|
23
25
|
expect(truncateContent("hello")).toBe("hello");
|
|
24
26
|
});
|
|
@@ -180,6 +182,40 @@ describe("buildHelpCard", () => {
|
|
|
180
182
|
expect(text).not.toContain("CCC Agent");
|
|
181
183
|
});
|
|
182
184
|
});
|
|
185
|
+
|
|
186
|
+
describe("Codex Fast mode cards", () => {
|
|
187
|
+
it("shows the current mode and exposes ON/OFF commands", () => {
|
|
188
|
+
const card = JSON.parse(buildFastModeCard(false)) as {
|
|
189
|
+
header: { title: { content: string } };
|
|
190
|
+
elements: Array<{
|
|
191
|
+
tag: string;
|
|
192
|
+
text?: { content: string };
|
|
193
|
+
actions?: Array<{ text: { content: string }; value: string }>;
|
|
194
|
+
}>;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
expect(card.header.title.content).toBe("Codex Fast 模式");
|
|
198
|
+
expect(card.elements.find((element) => element.tag === "div")?.text?.content).toContain("OFF");
|
|
199
|
+
const actions = card.elements.find((element) => element.tag === "action")?.actions ?? [];
|
|
200
|
+
expect(actions.map((action) => JSON.parse(action.value).cmd)).toEqual([
|
|
201
|
+
"/fast on",
|
|
202
|
+
"/fast off",
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("places /fast help immediately after /model help for Codex", () => {
|
|
207
|
+
const card = JSON.parse(buildModelCard("gpt-5", ["gpt-5"], "codex")) as {
|
|
208
|
+
elements: Array<{ tag: string; text?: { content: string } }>;
|
|
209
|
+
};
|
|
210
|
+
const content = card.elements.find((element) => element.tag === "div")?.text?.content ?? "";
|
|
211
|
+
const modelHelpIndex = content.indexOf("/model");
|
|
212
|
+
const fastHelpIndex = content.indexOf("/fast");
|
|
213
|
+
|
|
214
|
+
expect(modelHelpIndex).toBeGreaterThanOrEqual(0);
|
|
215
|
+
expect(fastHelpIndex).toBeGreaterThan(modelHelpIndex);
|
|
216
|
+
expect(content.slice(modelHelpIndex, fastHelpIndex).split("\n")).toHaveLength(2);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
183
219
|
|
|
184
220
|
// ---------------------------------------------------------------------------
|
|
185
221
|
// buildCdContent
|
|
@@ -2,8 +2,9 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join, dirname } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
buildCodexInvocationArgs,
|
|
7
|
+
normalizeCodexMessage,
|
|
7
8
|
createCodexAdapter,
|
|
8
9
|
type CreateCodexAdapterOptions,
|
|
9
10
|
} from "../adapters/codex-adapter.ts";
|
|
@@ -14,7 +15,21 @@ import {
|
|
|
14
15
|
} from "../adapters/codex-session-meta-store.ts";
|
|
15
16
|
import { accumulateBlockContent, pickFinalReply, type AccumulatorState } from "../session.ts";
|
|
16
17
|
|
|
17
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
|
|
20
|
+
describe("buildCodexInvocationArgs", () => {
|
|
21
|
+
it("forces the priority service tier when Fast mode is on", () => {
|
|
22
|
+
expect(buildCodexInvocationArgs(["exec", "--json"], "", "", true)).toContain(
|
|
23
|
+
'service_tier="fast"',
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("forces the standard service tier when Fast mode is off", () => {
|
|
28
|
+
const args = buildCodexInvocationArgs(["exec", "--json"], "", "", false);
|
|
29
|
+
expect(args).toContain('service_tier="default"');
|
|
30
|
+
expect(args).not.toContain('service_tier="fast"');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
18
33
|
|
|
19
34
|
function readFixture(name: string): unknown[] {
|
|
20
35
|
const raw = readFileSync(join(__dirname, "fixtures", name), "utf-8");
|
|
@@ -71,7 +71,7 @@ const baseAppConfig: AppConfig = {
|
|
|
71
71
|
avatarBatteryMode: "apiPercent",
|
|
72
72
|
onDemandMonthlyBudget: 1000,
|
|
73
73
|
},
|
|
74
|
-
codex: { enabled: true, defaultAgent: false, path: "/initial/codex", model: "initial-codex-model", alternativeModel: "initial-codex-alt-model", effort: "initial-codex-effort" },
|
|
74
|
+
codex: { enabled: true, defaultAgent: false, path: "/initial/codex", model: "initial-codex-model", alternativeModel: "initial-codex-alt-model", effort: "initial-codex-effort", fastMode: false },
|
|
75
75
|
ccc: { DEEPSEEK_API_KEY: "initial-ccc-key", DEEPSEEK_BASE_URL: "https://initial.deepseek.test/v1", model: "initial-ccc-model" },
|
|
76
76
|
};
|
|
77
77
|
|
|
@@ -207,15 +207,16 @@ describe("applyLoadedConfig — config 对象引用契约", () => {
|
|
|
207
207
|
applyLoadedConfig({
|
|
208
208
|
...structuredClone(baseAppConfig),
|
|
209
209
|
feishu: { appId: "REF_TEST_APP", appSecret: "REF_TEST_SECRET" },
|
|
210
|
-
codex: { enabled: true, defaultAgent: false, path: "/refresh/codex", model: "fresh-model", alternativeModel: "", effort: "low" },
|
|
210
|
+
codex: { enabled: true, defaultAgent: false, path: "/refresh/codex", model: "fresh-model", alternativeModel: "", effort: "low", fastMode: true },
|
|
211
211
|
});
|
|
212
212
|
|
|
213
213
|
// 必须是同一个引用:codex-adapter 等下游模块"直接 import config",
|
|
214
214
|
// 替换引用会破坏它们对 config.codex.* 的访问。
|
|
215
215
|
expect(config).toBe(refBefore);
|
|
216
|
-
expect(config.feishu.appId).toBe("REF_TEST_APP");
|
|
217
|
-
expect(config.codex.path).toBe("/refresh/codex");
|
|
218
|
-
|
|
216
|
+
expect(config.feishu.appId).toBe("REF_TEST_APP");
|
|
217
|
+
expect(config.codex.path).toBe("/refresh/codex");
|
|
218
|
+
expect(config.codex.fastMode).toBe(true);
|
|
219
|
+
});
|
|
219
220
|
|
|
220
221
|
it("刷新 chromeDevtools 配置但保持 config 引用", () => {
|
|
221
222
|
const refBefore = config;
|
|
@@ -28,15 +28,16 @@ describe("config.sample.json", () => {
|
|
|
28
28
|
expect(sample.claude?.subagentModel).toBe("");
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
it("leaves
|
|
32
|
-
const configSamplePath = join(process.cwd(), "config.sample.json");
|
|
33
|
-
const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
|
|
34
|
-
cursor?: { alternativeModel?: unknown };
|
|
35
|
-
codex?: { alternativeModel?: unknown };
|
|
36
|
-
};
|
|
37
|
-
|
|
31
|
+
it("leaves alternative models empty and Codex Fast mode off by default", () => {
|
|
32
|
+
const configSamplePath = join(process.cwd(), "config.sample.json");
|
|
33
|
+
const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
|
|
34
|
+
cursor?: { alternativeModel?: unknown };
|
|
35
|
+
codex?: { alternativeModel?: unknown; fastMode?: unknown };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
38
|
expect(sample.cursor?.alternativeModel).toBe("");
|
|
39
39
|
expect(sample.codex?.alternativeModel).toBe("");
|
|
40
|
+
expect(sample.codex?.fastMode).toBe(false);
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
it("sets ccc agent DeepSeek defaults in the sample config", () => {
|
|
@@ -86,6 +86,7 @@ import {
|
|
|
86
86
|
recordSessionRegistry,
|
|
87
87
|
resetState,
|
|
88
88
|
sessionInfoMap,
|
|
89
|
+
getEffectiveFastModeForTool,
|
|
89
90
|
} from "../session.ts";
|
|
90
91
|
import {
|
|
91
92
|
activePrompts,
|
|
@@ -146,6 +147,7 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
146
147
|
config.claude.defaultAgent = true;
|
|
147
148
|
config.cursor.defaultAgent = false;
|
|
148
149
|
config.codex.defaultAgent = false;
|
|
150
|
+
config.codex.fastMode = false;
|
|
149
151
|
mockStreamStates.clear();
|
|
150
152
|
mockGetCodexUsageSummary.mockReset();
|
|
151
153
|
mockGetCursorUsageSummary.mockReset();
|
|
@@ -660,6 +662,55 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
660
662
|
expect(registry["feishu-p2p"]?.sessionId).toBe("sid-feishu-private");
|
|
661
663
|
});
|
|
662
664
|
|
|
665
|
+
it("queries and switches Fast mode for the current Codex session", async () => {
|
|
666
|
+
const platform = mockPlatform("feishu");
|
|
667
|
+
await recordSessionRegistry({
|
|
668
|
+
chatId: "feishu-codex",
|
|
669
|
+
sessionId: "sid-codex-fast",
|
|
670
|
+
tool: "codex",
|
|
671
|
+
chatType: "p2p",
|
|
672
|
+
chatName: "Codex",
|
|
673
|
+
running: false,
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
await handleCommand(platform, "/fast", "feishu-codex", "ou-user", Date.now(), "p2p");
|
|
677
|
+
let card = JSON.parse(
|
|
678
|
+
vi.mocked(platform.sendRawCard).mock.calls.at(-1)?.[1] ?? "{}",
|
|
679
|
+
) as { elements?: Array<{ tag: string; text?: { content: string } }> };
|
|
680
|
+
expect(card.elements?.find((element) => element.tag === "div")?.text?.content).toContain("OFF");
|
|
681
|
+
|
|
682
|
+
await handleCommand(platform, "/fast on", "feishu-codex", "ou-user", Date.now(), "p2p");
|
|
683
|
+
expect(getEffectiveFastModeForTool("codex", "sid-codex-fast")).toBe(true);
|
|
684
|
+
|
|
685
|
+
await handleCommand(platform, "/fast off", "feishu-codex", "ou-user", Date.now(), "p2p");
|
|
686
|
+
expect(getEffectiveFastModeForTool("codex", "sid-codex-fast")).toBe(false);
|
|
687
|
+
card = JSON.parse(
|
|
688
|
+
vi.mocked(platform.sendRawCard).mock.calls.at(-1)?.[1] ?? "{}",
|
|
689
|
+
) as { elements?: Array<{ tag: string; text?: { content: string } }> };
|
|
690
|
+
expect(card.elements?.find((element) => element.tag === "div")?.text?.content).toContain("OFF");
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it("uses a text fallback for Fast mode commands on WeChat", async () => {
|
|
694
|
+
const platform = mockPlatform("wechat");
|
|
695
|
+
await recordSessionRegistry({
|
|
696
|
+
chatId: "wechat-codex",
|
|
697
|
+
sessionId: "sid-wechat-codex-fast",
|
|
698
|
+
tool: "codex",
|
|
699
|
+
chatType: "p2p",
|
|
700
|
+
chatName: "Codex",
|
|
701
|
+
running: false,
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
await handleCommand(platform, "/fast on", "wechat-codex", "wx-user", Date.now(), "p2p");
|
|
705
|
+
|
|
706
|
+
expect(getEffectiveFastModeForTool("codex", "sid-wechat-codex-fast")).toBe(true);
|
|
707
|
+
expect(platform.sendText).toHaveBeenCalledWith(
|
|
708
|
+
"wechat-codex",
|
|
709
|
+
expect.stringContaining("ON (Fast)"),
|
|
710
|
+
);
|
|
711
|
+
expect(platform.sendRawCard).not.toHaveBeenCalled();
|
|
712
|
+
});
|
|
713
|
+
|
|
663
714
|
it("keeps the Feishu p2p session bound when /new creates a separate group", async () => {
|
|
664
715
|
const platform = mockPlatform("feishu");
|
|
665
716
|
const createSession = vi.fn(async () => ({ sessionId: "sid-feishu-group" }));
|
|
@@ -1023,7 +1074,15 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
1023
1074
|
"green",
|
|
1024
1075
|
);
|
|
1025
1076
|
|
|
1026
|
-
const
|
|
1077
|
+
const codexReadyCall = vi.mocked(codexPlatform.sendCard).mock.calls.find(
|
|
1078
|
+
([chatId, title]) => chatId === "feishu-group" && title === "Codex Session Ready",
|
|
1079
|
+
);
|
|
1080
|
+
const modelHelpIndex = codexReadyCall?.[2].indexOf("/model") ?? -1;
|
|
1081
|
+
const fastHelpIndex = codexReadyCall?.[2].indexOf("/fast") ?? -1;
|
|
1082
|
+
expect(modelHelpIndex).toBeGreaterThanOrEqual(0);
|
|
1083
|
+
expect(fastHelpIndex).toBeGreaterThan(modelHelpIndex);
|
|
1084
|
+
|
|
1085
|
+
const cursorPlatform = mockPlatform("feishu");
|
|
1027
1086
|
_setAdapterForToolForTest("cursor", mockAdapter("sid-cursor"));
|
|
1028
1087
|
|
|
1029
1088
|
await handleCommand(cursorPlatform, "/new cursor", "feishu-p2p-cursor", "ou-user", Date.now(), "p2p");
|
|
@@ -126,9 +126,12 @@ import {
|
|
|
126
126
|
_setFinalResponseCloseTimeoutForTest,
|
|
127
127
|
_resetFinalResponseCloseTimeoutForTest,
|
|
128
128
|
setSessionEffortOverride,
|
|
129
|
+
getEffectiveFastModeForTool,
|
|
130
|
+
setSessionFastModeOverride,
|
|
129
131
|
RESPONSE_STALL_RECOVERY_PROMPT,
|
|
130
132
|
RESPONSE_STALL_RECOVERY_EXHAUSTED_NOTICE,
|
|
131
133
|
} from "../session.ts";
|
|
134
|
+
import { config } from "../config.ts";
|
|
132
135
|
import {
|
|
133
136
|
activePrompts,
|
|
134
137
|
bindChatToSession,
|
|
@@ -145,7 +148,26 @@ import {
|
|
|
145
148
|
} from "../session-chat-binding.ts";
|
|
146
149
|
import type { AccumulatorState } from "../session.ts";
|
|
147
150
|
import type { ToolAdapter, ToolPromptOptions, UnifiedBlock, SessionInfo } from "../adapters/adapter-interface.ts";
|
|
148
|
-
import type { PlatformAdapter } from "../platform-adapter.ts";
|
|
151
|
+
import type { PlatformAdapter } from "../platform-adapter.ts";
|
|
152
|
+
|
|
153
|
+
describe("Codex Fast mode overrides", () => {
|
|
154
|
+
it("uses the global default until the current session overrides it", () => {
|
|
155
|
+
const previousFastMode = config.codex.fastMode;
|
|
156
|
+
config.codex.fastMode = false;
|
|
157
|
+
resetState();
|
|
158
|
+
|
|
159
|
+
expect(getEffectiveFastModeForTool("codex", "sid-fast")).toBe(false);
|
|
160
|
+
setSessionFastModeOverride("sid-fast", true);
|
|
161
|
+
expect(getEffectiveFastModeForTool("codex", "sid-fast")).toBe(true);
|
|
162
|
+
setSessionFastModeOverride("sid-fast", false);
|
|
163
|
+
expect(getEffectiveFastModeForTool("codex", "sid-fast")).toBe(false);
|
|
164
|
+
expect(getEffectiveFastModeForTool("claude", "sid-fast")).toBe(false);
|
|
165
|
+
|
|
166
|
+
resetState();
|
|
167
|
+
expect(getEffectiveFastModeForTool("codex", "sid-fast")).toBe(false);
|
|
168
|
+
config.codex.fastMode = previousFastMode;
|
|
169
|
+
});
|
|
170
|
+
});
|
|
149
171
|
|
|
150
172
|
// Helper to create a mock active session entry
|
|
151
173
|
function mockActiveSession(chatId: string, overrides: Partial<{
|
|
@@ -68,11 +68,12 @@ describe("unflattenConfig", () => {
|
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
it("maps Cursor and Codex
|
|
71
|
+
it("maps Cursor and Codex runtime settings into agent config", () => {
|
|
72
72
|
expect(
|
|
73
73
|
unflattenConfig({
|
|
74
74
|
CHATCCC_CURSOR_ALTERNATIVE_MODEL: "gpt-5.5-high",
|
|
75
75
|
CHATCCC_CODEX_ALTERNATIVE_MODEL: "gpt-5.3-codex",
|
|
76
|
+
CHATCCC_CODEX_FAST_MODE: true,
|
|
76
77
|
}),
|
|
77
78
|
).toEqual({
|
|
78
79
|
cursor: {
|
|
@@ -80,6 +81,7 @@ describe("unflattenConfig", () => {
|
|
|
80
81
|
},
|
|
81
82
|
codex: {
|
|
82
83
|
alternativeModel: "gpt-5.3-codex",
|
|
84
|
+
fastMode: true,
|
|
83
85
|
},
|
|
84
86
|
});
|
|
85
87
|
});
|
|
@@ -149,6 +151,7 @@ describe("getRestartRequiredReasons", () => {
|
|
|
149
151
|
model: "",
|
|
150
152
|
alternativeModel: "",
|
|
151
153
|
effort: "",
|
|
154
|
+
fastMode: false,
|
|
152
155
|
},
|
|
153
156
|
};
|
|
154
157
|
|
|
@@ -159,7 +162,13 @@ describe("getRestartRequiredReasons", () => {
|
|
|
159
162
|
chromeDevtools: { enabled: true, port: 15167, chromePath: "C:/Chrome/chrome.exe" },
|
|
160
163
|
claude: { ...baseConfig.claude, model: "claude-sonnet", apiKey: "sk-test", maxTurn: 8 },
|
|
161
164
|
cursor: { ...baseConfig.cursor, path: "C:/cursor-agent.cmd", model: "cursor-model" },
|
|
162
|
-
codex: {
|
|
165
|
+
codex: {
|
|
166
|
+
...baseConfig.codex,
|
|
167
|
+
path: "C:/codex.cmd",
|
|
168
|
+
model: "gpt-5.3-codex",
|
|
169
|
+
effort: "high",
|
|
170
|
+
fastMode: true,
|
|
171
|
+
},
|
|
163
172
|
}),
|
|
164
173
|
).toEqual([]);
|
|
165
174
|
});
|
|
@@ -211,6 +220,12 @@ describe("dashboard edit modal", () => {
|
|
|
211
220
|
expect(PAGE_HTML).toContain("生效范围:飞书开关、App ID、App Secret 或平台类型变更需要重启 ChatCCC");
|
|
212
221
|
});
|
|
213
222
|
|
|
223
|
+
it("shows the Codex Fast mode switch in both the wizard and dashboard", () => {
|
|
224
|
+
expect(PAGE_HTML).toContain('id="field-CHATCCC_CODEX_FAST_MODE"');
|
|
225
|
+
expect(PAGE_HTML).toContain('id="cfg-CODEX_FAST_MODE"');
|
|
226
|
+
expect(PAGE_HTML).toContain("Fast 模式");
|
|
227
|
+
});
|
|
228
|
+
|
|
214
229
|
it("shows the Web UI startup switch in both the wizard and dashboard", () => {
|
|
215
230
|
expect(PAGE_HTML).toContain('id="field-CHATCCC_WEB_UI_OPEN_ON_START"');
|
|
216
231
|
expect(PAGE_HTML).toContain('id="cfg-WEB_UI_OPEN_ON_START"');
|
|
@@ -199,13 +199,12 @@ export function normalizeCodexMessage(
|
|
|
199
199
|
// 子进程辅助函数
|
|
200
200
|
// ---------------------------------------------------------------------------
|
|
201
201
|
|
|
202
|
-
function
|
|
202
|
+
export function buildCodexInvocationArgs(
|
|
203
203
|
args: string[],
|
|
204
|
-
cwd?: string,
|
|
205
|
-
stdinText?: string,
|
|
206
204
|
modelOverride?: string,
|
|
207
205
|
effortOverride?: string,
|
|
208
|
-
|
|
206
|
+
fastModeOverride?: boolean,
|
|
207
|
+
): string[] {
|
|
209
208
|
const allArgs = [...args];
|
|
210
209
|
const model = modelOverride ?? resolveCodexModel();
|
|
211
210
|
if (model) {
|
|
@@ -217,6 +216,25 @@ function spawnCodex(
|
|
|
217
216
|
if (effort) {
|
|
218
217
|
allArgs.push("-c", `model_reasoning_effort="${effort}"`);
|
|
219
218
|
}
|
|
219
|
+
const fastMode = fastModeOverride ?? config.codex.fastMode;
|
|
220
|
+
allArgs.push("-c", `service_tier="${fastMode ? "fast" : "default"}"`);
|
|
221
|
+
return allArgs;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function spawnCodex(
|
|
225
|
+
args: string[],
|
|
226
|
+
cwd?: string,
|
|
227
|
+
stdinText?: string,
|
|
228
|
+
modelOverride?: string,
|
|
229
|
+
effortOverride?: string,
|
|
230
|
+
fastModeOverride?: boolean,
|
|
231
|
+
): ChildProcess {
|
|
232
|
+
const allArgs = buildCodexInvocationArgs(
|
|
233
|
+
args,
|
|
234
|
+
modelOverride,
|
|
235
|
+
effortOverride,
|
|
236
|
+
fastModeOverride,
|
|
237
|
+
);
|
|
220
238
|
|
|
221
239
|
const proc = spawn(detectCodexCommand(), allArgs, {
|
|
222
240
|
cwd,
|
|
@@ -269,11 +287,18 @@ class CodexAdapter implements ToolAdapter {
|
|
|
269
287
|
private metaStore: CodexSessionMetaStore;
|
|
270
288
|
private modelOverride: string | undefined;
|
|
271
289
|
private effortOverride: string | undefined;
|
|
290
|
+
private fastModeOverride: boolean | undefined;
|
|
272
291
|
|
|
273
|
-
constructor(
|
|
292
|
+
constructor(
|
|
293
|
+
metaStore: CodexSessionMetaStore,
|
|
294
|
+
modelOverride?: string,
|
|
295
|
+
effortOverride?: string,
|
|
296
|
+
fastModeOverride?: boolean,
|
|
297
|
+
) {
|
|
274
298
|
this.metaStore = metaStore;
|
|
275
299
|
this.modelOverride = modelOverride;
|
|
276
300
|
this.effortOverride = effortOverride;
|
|
301
|
+
this.fastModeOverride = fastModeOverride;
|
|
277
302
|
}
|
|
278
303
|
|
|
279
304
|
// createSession: 生成 sessionId,记录 cwd,不创建 Codex 线程(延迟到首次 prompt)
|
|
@@ -304,7 +329,14 @@ class CodexAdapter implements ToolAdapter {
|
|
|
304
329
|
? [...baseArgs, "-C", cwd, "-"]
|
|
305
330
|
: [...baseArgs, "resume", threadId, "-"];
|
|
306
331
|
|
|
307
|
-
const proc = spawnCodex(
|
|
332
|
+
const proc = spawnCodex(
|
|
333
|
+
args,
|
|
334
|
+
cwd,
|
|
335
|
+
buildCodexPromptText(userText),
|
|
336
|
+
this.modelOverride,
|
|
337
|
+
this.effortOverride,
|
|
338
|
+
this.fastModeOverride,
|
|
339
|
+
);
|
|
308
340
|
if (proc.pid !== undefined) options?.onProcessStart?.({ pid: proc.pid });
|
|
309
341
|
|
|
310
342
|
const rawLogConfig = config.rawStreamLogs.codex;
|
|
@@ -379,6 +411,7 @@ export interface CreateCodexAdapterOptions {
|
|
|
379
411
|
/** per-session 模型覆盖(/model 命令);传了就用,不传走全局 codex.model */
|
|
380
412
|
model?: string;
|
|
381
413
|
effort?: string;
|
|
414
|
+
fastMode?: boolean;
|
|
382
415
|
}
|
|
383
416
|
|
|
384
417
|
export function createCodexAdapter(
|
|
@@ -388,5 +421,6 @@ export function createCodexAdapter(
|
|
|
388
421
|
options.metaStore ?? defaultCodexSessionMetaStore,
|
|
389
422
|
options.model,
|
|
390
423
|
options.effort,
|
|
424
|
+
options.fastMode,
|
|
391
425
|
);
|
|
392
426
|
}
|
package/src/cards.ts
CHANGED
|
@@ -553,8 +553,12 @@ export function buildModelCard(
|
|
|
553
553
|
lines.push("", "没有可切换的模型。请在 config.json 中配置模型字段。");
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
-
|
|
557
|
-
|
|
556
|
+
if (tool === "codex") {
|
|
557
|
+
lines.push("输入 `/fast` 查看或切换当前会话的 Fast 模式");
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const buttons: ButtonDef[] = [];
|
|
561
|
+
for (const m of models.slice(0, 20)) {
|
|
558
562
|
const shortName = m.includes("/") ? m.slice(m.lastIndexOf("/") + 1) : m;
|
|
559
563
|
buttons.push({
|
|
560
564
|
text: `/model ${shortName}`,
|
|
@@ -574,6 +578,43 @@ export function buildModelCard(
|
|
|
574
578
|
});
|
|
575
579
|
}
|
|
576
580
|
|
|
581
|
+
export function buildFastModeCard(enabled: boolean): string {
|
|
582
|
+
const mode = enabled ? "ON (Fast)" : "OFF (Standard)";
|
|
583
|
+
return JSON.stringify({
|
|
584
|
+
config: { wide_screen_mode: true },
|
|
585
|
+
header: {
|
|
586
|
+
template: enabled ? "green" : "blue",
|
|
587
|
+
title: { content: "Codex Fast 模式", tag: "plain_text" },
|
|
588
|
+
},
|
|
589
|
+
elements: [
|
|
590
|
+
{
|
|
591
|
+
tag: "div",
|
|
592
|
+
text: {
|
|
593
|
+
tag: "lark_md",
|
|
594
|
+
content: [
|
|
595
|
+
`**当前模式:** ${mode}`,
|
|
596
|
+
"",
|
|
597
|
+
"切换将在下一条消息生效,当前生成不中断。",
|
|
598
|
+
].join("\n"),
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
{ tag: "hr" },
|
|
602
|
+
buildButtons([
|
|
603
|
+
{
|
|
604
|
+
text: "ON",
|
|
605
|
+
value: JSON.stringify({ cmd: "/fast on" }),
|
|
606
|
+
type: enabled ? "primary" : "default",
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
text: "OFF",
|
|
610
|
+
value: JSON.stringify({ cmd: "/fast off" }),
|
|
611
|
+
type: enabled ? "default" : "primary",
|
|
612
|
+
},
|
|
613
|
+
]),
|
|
614
|
+
],
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
577
618
|
export function buildEffortCard(
|
|
578
619
|
currentEffort: string,
|
|
579
620
|
efforts: string[],
|
package/src/config.ts
CHANGED
|
@@ -97,8 +97,10 @@ export interface CodexConfig {
|
|
|
97
97
|
path: string;
|
|
98
98
|
model: string;
|
|
99
99
|
/** /model 可切换的单个备选模型;留空则不加入候选列表 */
|
|
100
|
-
alternativeModel: string;
|
|
100
|
+
alternativeModel: string;
|
|
101
101
|
effort: string;
|
|
102
|
+
/** Codex Priority service tier. False explicitly forces the standard tier. */
|
|
103
|
+
fastMode: boolean;
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
export interface CccConfig {
|
|
@@ -442,7 +444,7 @@ function loadConfig(): AppConfig {
|
|
|
442
444
|
avatarBatteryMode: "apiPercent",
|
|
443
445
|
onDemandMonthlyBudget: 1000,
|
|
444
446
|
},
|
|
445
|
-
codex: { enabled: false, defaultAgent: false, path: "", model: "", alternativeModel: "", effort: "" },
|
|
447
|
+
codex: { enabled: false, defaultAgent: false, path: "", model: "", alternativeModel: "", effort: "", fastMode: false },
|
|
446
448
|
ccc: { DEEPSEEK_API_KEY: "", DEEPSEEK_BASE_URL: DEFAULT_CCC_DEEPSEEK_BASE_URL, model: DEFAULT_CCC_MODEL },
|
|
447
449
|
};
|
|
448
450
|
|
|
@@ -495,7 +497,7 @@ function loadConfig(): AppConfig {
|
|
|
495
497
|
avatarBatteryMode?: unknown;
|
|
496
498
|
onDemandMonthlyBudget?: unknown;
|
|
497
499
|
};
|
|
498
|
-
codex?: { enabled?: unknown; defaultAgent?: unknown; path?: unknown; command?: unknown; model?: unknown; alternativeModel?: unknown; effort?: unknown };
|
|
500
|
+
codex?: { enabled?: unknown; defaultAgent?: unknown; path?: unknown; command?: unknown; model?: unknown; alternativeModel?: unknown; effort?: unknown; fastMode?: unknown };
|
|
499
501
|
ccc?: { DEEPSEEK_API_KEY?: unknown; DEEPSEEK_BASE_URL?: unknown; model?: unknown };
|
|
500
502
|
webUi?: { openOnStart?: unknown };
|
|
501
503
|
chromeDevtools?: { enabled?: unknown; port?: unknown; chromePath?: unknown };
|
|
@@ -555,9 +557,10 @@ function loadConfig(): AppConfig {
|
|
|
555
557
|
Boolean(
|
|
556
558
|
(typeof codexRaw.path === "string" && codexRaw.path.trim()) ||
|
|
557
559
|
(typeof codexRaw.command === "string" && (codexRaw.command as string).trim()) ||
|
|
558
|
-
(typeof codexRaw.model === "string" && (codexRaw.model as string).trim()) ||
|
|
559
|
-
(typeof codexRaw.alternativeModel === "string" && (codexRaw.alternativeModel as string).trim()) ||
|
|
560
|
-
(typeof codexRaw.effort === "string" && (codexRaw.effort as string).trim())
|
|
560
|
+
(typeof codexRaw.model === "string" && (codexRaw.model as string).trim()) ||
|
|
561
|
+
(typeof codexRaw.alternativeModel === "string" && (codexRaw.alternativeModel as string).trim()) ||
|
|
562
|
+
(typeof codexRaw.effort === "string" && (codexRaw.effort as string).trim()) ||
|
|
563
|
+
codexRaw.fastMode === true,
|
|
561
564
|
);
|
|
562
565
|
|
|
563
566
|
const claudeEnabled = resolveEnabled(claude.enabled, claudeNonEmpty);
|
|
@@ -649,6 +652,7 @@ function loadConfig(): AppConfig {
|
|
|
649
652
|
model: normalizeOptionalConfigField(codexRaw.model, { label: "codex.model" }),
|
|
650
653
|
alternativeModel: normalizeOptionalConfigField(codexRaw.alternativeModel, { label: "codex.alternativeModel" }),
|
|
651
654
|
effort: normalizeOptionalConfigField(codexRaw.effort, { label: "codex.effort" }),
|
|
655
|
+
fastMode: codexRaw.fastMode === true,
|
|
652
656
|
},
|
|
653
657
|
ccc: {
|
|
654
658
|
DEEPSEEK_API_KEY: normalizeOptionalConfigField(cccRaw.DEEPSEEK_API_KEY, { label: "ccc.DEEPSEEK_API_KEY" }),
|
package/src/orchestrator.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
import {
|
|
40
40
|
buildHelpCard,
|
|
41
41
|
buildEffortCard,
|
|
42
|
+
buildFastModeCard,
|
|
42
43
|
buildModelCard,
|
|
43
44
|
buildStatusCard,
|
|
44
45
|
buildCdContent,
|
|
@@ -69,6 +70,8 @@ import {
|
|
|
69
70
|
getAdapterForTool,
|
|
70
71
|
getEffectiveModelForTool,
|
|
71
72
|
getEffectiveEffortForTool,
|
|
73
|
+
getEffectiveFastModeForTool,
|
|
74
|
+
setSessionFastModeOverride,
|
|
72
75
|
stopSession,
|
|
73
76
|
loadSessionRegistryForBinding,
|
|
74
77
|
removeSessionRegistryRecord,
|
|
@@ -325,11 +328,33 @@ function formatCursorUsageSummary(usage: CursorUsageSummary): string {
|
|
|
325
328
|
].filter(Boolean).join("\n");
|
|
326
329
|
}
|
|
327
330
|
|
|
328
|
-
function usageHelpLine(tool: string): string {
|
|
331
|
+
function usageHelpLine(tool: string): string {
|
|
329
332
|
if (tool === "codex") return "\n发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡。";
|
|
330
333
|
if (tool === "cursor") return "\n发送 **/usage** 查看 Cursor 用量。";
|
|
331
|
-
return "";
|
|
332
|
-
}
|
|
334
|
+
return "";
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function fastHelpAfterModel(tool: string): string {
|
|
338
|
+
return tool === "codex"
|
|
339
|
+
? "\n发送 **/fast** 查看或切换当前会话的 Fast 模式。"
|
|
340
|
+
: "";
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function sendFastModeStatus(
|
|
344
|
+
platform: PlatformAdapter,
|
|
345
|
+
chatId: string,
|
|
346
|
+
enabled: boolean,
|
|
347
|
+
): Promise<void> {
|
|
348
|
+
if (platform.kind === "wechat") {
|
|
349
|
+
const mode = enabled ? "ON (Fast)" : "OFF (Standard)";
|
|
350
|
+
await platform.sendText(
|
|
351
|
+
chatId,
|
|
352
|
+
`Codex Fast 模式: ${mode}\n输入 /fast on 或 /fast off 切换。切换将在下一条消息生效,当前生成不中断。`,
|
|
353
|
+
);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
await platform.sendRawCard(chatId, buildFastModeCard(enabled));
|
|
357
|
+
}
|
|
333
358
|
|
|
334
359
|
async function resolveUsageTarget(chatId: string): Promise<{ tool: "codex" | "cursor"; sessionId?: string }> {
|
|
335
360
|
try {
|
|
@@ -1021,7 +1046,7 @@ export async function handleCommand(
|
|
|
1021
1046
|
`**工作目录:** \`${cwd}\`\n\n` +
|
|
1022
1047
|
`直接在这里发消息即可与 ${toolLabel} 对话。\n\n` +
|
|
1023
1048
|
`发送 **/cd** 切换新建会话的默认目录。\n` +
|
|
1024
|
-
`发送 **/model**
|
|
1049
|
+
`发送 **/model** 查看或切换当前会话的模型。${fastHelpAfterModel(tool)}\n` +
|
|
1025
1050
|
`发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。\n` +
|
|
1026
1051
|
`发送 **/sessions** 查看所有会话状态。\n` +
|
|
1027
1052
|
`发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。` +
|
|
@@ -1109,7 +1134,7 @@ export async function handleCommand(
|
|
|
1109
1134
|
`**工作目录:** \`${cwd}\`\n\n` +
|
|
1110
1135
|
`直接在这里发消息即可与 ${toolLabel} 对话。\n\n` +
|
|
1111
1136
|
`发送 **/cd** 切换新建会话的默认目录。\n` +
|
|
1112
|
-
`发送 **/model**
|
|
1137
|
+
`发送 **/model** 查看或切换当前会话的模型。${fastHelpAfterModel(tool)}\n` +
|
|
1113
1138
|
`发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。\n` +
|
|
1114
1139
|
`发送 **/sessions** 查看所有会话状态。\n` +
|
|
1115
1140
|
`发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。` +
|
|
@@ -1531,7 +1556,7 @@ export async function handleCommand(
|
|
|
1531
1556
|
`**工作目录:** \`${cwd}\`${isFeishuP2p(platform, chatType) ? "(飞书私聊固定使用系统用户目录)" : "(沿用当前会话目录)"}\n\n` +
|
|
1532
1557
|
`直接在这里发消息即可继续对话。\n` +
|
|
1533
1558
|
`发送 **/cd** 可切换新建会话的默认目录。\n` +
|
|
1534
|
-
`发送 **/model**
|
|
1559
|
+
`发送 **/model** 查看或切换当前会话的模型。${fastHelpAfterModel(descriptionTool)}`,
|
|
1535
1560
|
"green",
|
|
1536
1561
|
);
|
|
1537
1562
|
|
|
@@ -1705,7 +1730,7 @@ export async function handleCommand(
|
|
|
1705
1730
|
`**Session ID:** ${target.sessionId}\n` +
|
|
1706
1731
|
`**工作目录:** \`${cwd2}\`\n\n` +
|
|
1707
1732
|
`直接在这里发消息即可继续对话。\n` +
|
|
1708
|
-
`发送 **/model** 查看或切换当前会话的模型。${busyNote}`,
|
|
1733
|
+
`发送 **/model** 查看或切换当前会话的模型。${fastHelpAfterModel(descriptionTool)}${busyNote}`,
|
|
1709
1734
|
"green",
|
|
1710
1735
|
);
|
|
1711
1736
|
|
|
@@ -1718,7 +1743,44 @@ export async function handleCommand(
|
|
|
1718
1743
|
return;
|
|
1719
1744
|
}
|
|
1720
1745
|
|
|
1721
|
-
|
|
1746
|
+
if (isCommandText && (textLower === "/fast" || textLower.startsWith("/fast "))) {
|
|
1747
|
+
const fastArg = text.slice(5).trim().toLowerCase();
|
|
1748
|
+
logTrace(tid, "BRANCH", { cmd: "/fast", arg: fastArg, sessionId, tool: descriptionTool });
|
|
1749
|
+
|
|
1750
|
+
if (descriptionTool !== "codex") {
|
|
1751
|
+
const msg = `当前 ${toolLabel} 会话不支持 Fast 模式;/fast 仅适用于 Codex。`;
|
|
1752
|
+
await (platform.kind === "wechat"
|
|
1753
|
+
? platform.sendText(chatId, msg)
|
|
1754
|
+
: platform.sendCard(chatId, "Codex Fast 模式", msg, "yellow")
|
|
1755
|
+
).catch(() => {});
|
|
1756
|
+
logTrace(tid, "DONE", { outcome: "fast_unsupported", tool: descriptionTool });
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
if (fastArg && fastArg !== "on" && fastArg !== "off") {
|
|
1761
|
+
const msg = "用法: /fast、/fast on 或 /fast off";
|
|
1762
|
+
await (platform.kind === "wechat"
|
|
1763
|
+
? platform.sendText(chatId, msg)
|
|
1764
|
+
: platform.sendCard(chatId, "Codex Fast 模式", msg, "yellow")
|
|
1765
|
+
).catch(() => {});
|
|
1766
|
+
logTrace(tid, "DONE", { outcome: "fast_invalid", arg: fastArg });
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
if (fastArg) {
|
|
1771
|
+
setSessionFastModeOverride(sessionId, fastArg === "on");
|
|
1772
|
+
}
|
|
1773
|
+
const enabled = getEffectiveFastModeForTool("codex", sessionId);
|
|
1774
|
+
await sendFastModeStatus(platform, chatId, enabled).catch(() => {});
|
|
1775
|
+
logTrace(tid, "DONE", {
|
|
1776
|
+
outcome: fastArg ? "fast_switched" : "fast_query",
|
|
1777
|
+
enabled,
|
|
1778
|
+
sessionId,
|
|
1779
|
+
});
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// /model clear — 清除当前 session 的模型覆盖
|
|
1722
1784
|
if (isCommandText && textLower === "/model clear") {
|
|
1723
1785
|
logTrace(tid, "BRANCH", { cmd: "/model clear", sessionId });
|
|
1724
1786
|
clearSessionModelOverride(sessionId);
|
|
@@ -1776,12 +1838,15 @@ export async function handleCommand(
|
|
|
1776
1838
|
if (platform.kind === "wechat") {
|
|
1777
1839
|
const lines = [currentModel ? `当前模型 (${toolLabel}): ${currentModel}` : `当前模型 (${toolLabel}): 未指定`];
|
|
1778
1840
|
if (models.length > 0) {
|
|
1779
|
-
lines.push("", "可切换模型:");
|
|
1780
|
-
for (const m of models) lines.push(` ${m}`);
|
|
1781
|
-
lines.push("", "输入 /model <模型名> 切换模型");
|
|
1782
|
-
} else {
|
|
1783
|
-
lines.push("", "没有可切换的模型。请在 config.json 中配置模型字段。");
|
|
1784
|
-
}
|
|
1841
|
+
lines.push("", "可切换模型:");
|
|
1842
|
+
for (const m of models) lines.push(` ${m}`);
|
|
1843
|
+
lines.push("", "输入 /model <模型名> 切换模型");
|
|
1844
|
+
} else {
|
|
1845
|
+
lines.push("", "没有可切换的模型。请在 config.json 中配置模型字段。");
|
|
1846
|
+
}
|
|
1847
|
+
if (descriptionTool === "codex") {
|
|
1848
|
+
lines.push("输入 /fast 查看或切换当前会话的 Fast 模式");
|
|
1849
|
+
}
|
|
1785
1850
|
await platform.sendText(chatId, lines.join("\n")).catch(() => {});
|
|
1786
1851
|
} else {
|
|
1787
1852
|
const card = buildModelCard(currentModel, models, descriptionTool);
|
|
@@ -2022,7 +2087,34 @@ export async function handleCommand(
|
|
|
2022
2087
|
return;
|
|
2023
2088
|
}
|
|
2024
2089
|
|
|
2025
|
-
|
|
2090
|
+
if (isCommandText && (textLower === "/fast" || textLower.startsWith("/fast "))) {
|
|
2091
|
+
const defaultTool = resolveDefaultAgentTool();
|
|
2092
|
+
const fastArg = text.slice(5).trim().toLowerCase();
|
|
2093
|
+
if (defaultTool !== "codex") {
|
|
2094
|
+
const msg = `当前默认 Agent (${toolDisplayName(defaultTool)}) 不支持 Fast 模式;/fast 仅适用于 Codex。`;
|
|
2095
|
+
await (platform.kind === "wechat"
|
|
2096
|
+
? platform.sendText(chatId, msg)
|
|
2097
|
+
: platform.sendCard(chatId, "Codex Fast 模式", msg, "yellow")
|
|
2098
|
+
).catch(() => {});
|
|
2099
|
+
logTrace(tid, "DONE", { outcome: "fast_unsupported", defaultTool });
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
if (fastArg) {
|
|
2103
|
+
const msg = "当前没有绑定 Codex 会话,无法设置会话覆盖。请先创建或进入 Codex 会话;全局默认值可在 Web UI 中设置。";
|
|
2104
|
+
await (platform.kind === "wechat"
|
|
2105
|
+
? platform.sendText(chatId, msg)
|
|
2106
|
+
: platform.sendCard(chatId, "Codex Fast 模式", msg, "yellow")
|
|
2107
|
+
).catch(() => {});
|
|
2108
|
+
logTrace(tid, "DONE", { outcome: "fast_no_session", arg: fastArg });
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
const enabled = getEffectiveFastModeForTool("codex");
|
|
2112
|
+
await sendFastModeStatus(platform, chatId, enabled).catch(() => {});
|
|
2113
|
+
logTrace(tid, "DONE", { outcome: "fast_query", enabled, defaultTool });
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
// 无会话上下文 → 检查是否是 /model 查询
|
|
2026
2118
|
if (isCommandText && textLower === "/model") {
|
|
2027
2119
|
const defaultTool = resolveDefaultAgentTool();
|
|
2028
2120
|
const models = getAllModelsForTool(defaultTool);
|
|
@@ -2034,12 +2126,15 @@ export async function handleCommand(
|
|
|
2034
2126
|
if (platform.kind === "wechat") {
|
|
2035
2127
|
const lines = [currentModel ? `当前模型 (${defaultTool}): ${currentModel}` : `当前模型 (${defaultTool}): 未指定`];
|
|
2036
2128
|
if (models.length > 0) {
|
|
2037
|
-
lines.push("", "可切换模型:");
|
|
2038
|
-
for (const m of models) lines.push(` ${m}`);
|
|
2039
|
-
lines.push("", "在会话中输入 /model <模型名> 切换模型");
|
|
2040
|
-
} else {
|
|
2041
|
-
lines.push("", "没有可切换的模型。请在 config.json 中配置模型字段。");
|
|
2042
|
-
}
|
|
2129
|
+
lines.push("", "可切换模型:");
|
|
2130
|
+
for (const m of models) lines.push(` ${m}`);
|
|
2131
|
+
lines.push("", "在会话中输入 /model <模型名> 切换模型");
|
|
2132
|
+
} else {
|
|
2133
|
+
lines.push("", "没有可切换的模型。请在 config.json 中配置模型字段。");
|
|
2134
|
+
}
|
|
2135
|
+
if (defaultTool === "codex") {
|
|
2136
|
+
lines.push("输入 /fast 查看当前 Codex Fast 模式");
|
|
2137
|
+
}
|
|
2043
2138
|
await platform.sendText(chatId, lines.join("\n")).catch(() => {});
|
|
2044
2139
|
} else {
|
|
2045
2140
|
const card = buildModelCard(currentModel, models, defaultTool);
|
package/src/session.ts
CHANGED
|
@@ -475,6 +475,7 @@ export function resetState(): void {
|
|
|
475
475
|
displayCards.clear();
|
|
476
476
|
sessionModelOverrides.clear();
|
|
477
477
|
sessionEffortOverrides.clear();
|
|
478
|
+
sessionFastModeOverrides.clear();
|
|
478
479
|
adapterCache.clear();
|
|
479
480
|
stopUnifiedDisplayLoop();
|
|
480
481
|
console.log(`[${ts()}] [RESET] State cleared (dedup + active sessions + bindings)`);
|
|
@@ -492,6 +493,7 @@ const adapterCache = new Map<string, ToolAdapter>();
|
|
|
492
493
|
// Per-session 模型覆盖(/model 命令设置,不持久化)
|
|
493
494
|
const sessionModelOverrides = new Map<string, string>();
|
|
494
495
|
const sessionEffortOverrides = new Map<string, string>();
|
|
496
|
+
const sessionFastModeOverrides = new Map<string, boolean>();
|
|
495
497
|
|
|
496
498
|
/** 返回 session 的生效模型:优先 per-session 覆盖,其次全局配置(Claude) */
|
|
497
499
|
function getModelForSession(sessionId?: string): string {
|
|
@@ -524,6 +526,14 @@ export function getEffectiveEffortForTool(tool: string, sessionId?: string): str
|
|
|
524
526
|
}
|
|
525
527
|
return "";
|
|
526
528
|
}
|
|
529
|
+
|
|
530
|
+
export function getEffectiveFastModeForTool(tool: string, sessionId?: string): boolean {
|
|
531
|
+
if (tool !== "codex") return false;
|
|
532
|
+
if (sessionId && sessionFastModeOverrides.has(sessionId)) {
|
|
533
|
+
return sessionFastModeOverrides.get(sessionId) === true;
|
|
534
|
+
}
|
|
535
|
+
return config.codex.fastMode;
|
|
536
|
+
}
|
|
527
537
|
|
|
528
538
|
/** 为指定 session 设置模型覆盖(/model <name>) */
|
|
529
539
|
export function setSessionModelOverride(sessionId: string, model: string): void {
|
|
@@ -547,10 +557,16 @@ export function clearSessionEffortOverride(sessionId: string): void {
|
|
|
547
557
|
adapterCache.clear();
|
|
548
558
|
}
|
|
549
559
|
|
|
560
|
+
export function setSessionFastModeOverride(sessionId: string, fastMode: boolean): void {
|
|
561
|
+
sessionFastModeOverrides.set(sessionId, fastMode);
|
|
562
|
+
adapterCache.clear();
|
|
563
|
+
}
|
|
564
|
+
|
|
550
565
|
export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter {
|
|
551
566
|
const effectiveModel = getEffectiveModelForTool(tool, sessionId);
|
|
552
567
|
const effectiveEffort = getEffectiveEffortForTool(tool, sessionId);
|
|
553
|
-
const
|
|
568
|
+
const effectiveFastMode = getEffectiveFastModeForTool(tool, sessionId);
|
|
569
|
+
const cacheKey = `${tool}:${effectiveModel || ""}:${effectiveEffort || ""}:${effectiveFastMode ? "fast" : "default"}`;
|
|
554
570
|
const cached = adapterCache.get(cacheKey);
|
|
555
571
|
if (cached) return cached;
|
|
556
572
|
|
|
@@ -558,7 +574,11 @@ export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter
|
|
|
558
574
|
if (tool === "cursor") {
|
|
559
575
|
adapter = createCursorAdapter({ model: effectiveModel || undefined });
|
|
560
576
|
} else if (tool === "codex") {
|
|
561
|
-
adapter = createCodexAdapter({
|
|
577
|
+
adapter = createCodexAdapter({
|
|
578
|
+
model: effectiveModel || undefined,
|
|
579
|
+
effort: effectiveEffort || undefined,
|
|
580
|
+
fastMode: effectiveFastMode,
|
|
581
|
+
});
|
|
562
582
|
} else if (tool === "ccc") {
|
|
563
583
|
adapter = createCccAdapter({ model: effectiveModel || undefined });
|
|
564
584
|
} else {
|
|
@@ -1009,7 +1029,7 @@ function formatToolConfigForLog(tool: string, sessionModel?: string, sessionId?:
|
|
|
1009
1029
|
const effortStr = e.trim() !== ""
|
|
1010
1030
|
? `effort=${e}`
|
|
1011
1031
|
: "effort=(由 codex config.toml 决定)";
|
|
1012
|
-
return `model=${modelStr}, ${effortStr}`;
|
|
1032
|
+
return `model=${modelStr}, ${effortStr}, fast=${getEffectiveFastModeForTool(tool, sessionId) ? "on" : "off"}`;
|
|
1013
1033
|
}
|
|
1014
1034
|
if (tool === "ccc") {
|
|
1015
1035
|
const m = getEffectiveModelForTool(tool, sessionId);
|
|
@@ -2406,9 +2426,10 @@ export async function getAllSessionsStatus(): Promise<SessionsListEntry[]> {
|
|
|
2406
2426
|
export function _setAdapterForToolForTest(tool: string, adapter: ToolAdapter): void {
|
|
2407
2427
|
adapterCache.set(tool, adapter);
|
|
2408
2428
|
// 同时设置当前配置模型对应的 key(getAdapterForTool 会优先 lookup 含 model 的 key)
|
|
2409
|
-
const effective = getEffectiveModelForTool(tool);
|
|
2429
|
+
const effective = getEffectiveModelForTool(tool);
|
|
2410
2430
|
const effort = getEffectiveEffortForTool(tool);
|
|
2411
|
-
|
|
2431
|
+
const fastMode = getEffectiveFastModeForTool(tool);
|
|
2432
|
+
adapterCache.set(`${tool}:${effective || ""}:${effort || ""}:${fastMode ? "fast" : "default"}`, adapter);
|
|
2412
2433
|
if (effective) adapterCache.set(`${tool}:${effective}`, adapter);
|
|
2413
2434
|
}
|
|
2414
2435
|
|
package/src/web-ui.ts
CHANGED
|
@@ -53,7 +53,7 @@ interface AppConfig {
|
|
|
53
53
|
avatarBatteryMode?: string;
|
|
54
54
|
onDemandMonthlyBudget?: number;
|
|
55
55
|
};
|
|
56
|
-
codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; alternativeModel?: string; effort?: string };
|
|
56
|
+
codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; alternativeModel?: string; effort?: string; fastMode?: boolean };
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// ---------------------------------------------------------------------------
|
|
@@ -488,10 +488,13 @@ export function unflattenConfig(flat: Record<string, unknown>): Record<string, u
|
|
|
488
488
|
} else if (key === "CHATCCC_CODEX_ALTERNATIVE_MODEL") {
|
|
489
489
|
result.codex = result.codex || {};
|
|
490
490
|
(result.codex as Record<string, unknown>).alternativeModel = val;
|
|
491
|
-
} else if (key === "CHATCCC_CODEX_EFFORT") {
|
|
492
|
-
result.codex = result.codex || {};
|
|
493
|
-
(result.codex as Record<string, unknown>).effort = val;
|
|
494
|
-
} else if (key === "
|
|
491
|
+
} else if (key === "CHATCCC_CODEX_EFFORT") {
|
|
492
|
+
result.codex = result.codex || {};
|
|
493
|
+
(result.codex as Record<string, unknown>).effort = val;
|
|
494
|
+
} else if (key === "CHATCCC_CODEX_FAST_MODE") {
|
|
495
|
+
result.codex = result.codex || {};
|
|
496
|
+
(result.codex as Record<string, unknown>).fastMode = val === true || val === "true";
|
|
497
|
+
} else if (key === "CHATCCC_CODEX_ENABLED") {
|
|
495
498
|
result.codex = result.codex || {};
|
|
496
499
|
(result.codex as Record<string, unknown>).enabled = val === true || val === "true";
|
|
497
500
|
} else if (key === "CHATCCC_CODEX_DEFAULT_AGENT") {
|
|
@@ -928,11 +931,16 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
928
931
|
<label>备选模型(选填)</label>
|
|
929
932
|
<input type="text" id="field-CHATCCC_CODEX_ALTERNATIVE_MODEL" placeholder="加入 /model 列表,便于会话内切换">
|
|
930
933
|
</div>
|
|
931
|
-
<div class="form-group">
|
|
932
|
-
<label>努力程度 (Effort)</label>
|
|
933
|
-
<input type="text" id="field-CHATCCC_CODEX_EFFORT" placeholder="留空由 codex config.toml 决定">
|
|
934
|
-
</div>
|
|
935
|
-
<
|
|
934
|
+
<div class="form-group">
|
|
935
|
+
<label>努力程度 (Effort)</label>
|
|
936
|
+
<input type="text" id="field-CHATCCC_CODEX_EFFORT" placeholder="留空由 codex config.toml 决定">
|
|
937
|
+
</div>
|
|
938
|
+
<div class="form-group">
|
|
939
|
+
<label style="display:flex;align-items:center;gap:8px">
|
|
940
|
+
<input type="checkbox" id="field-CHATCCC_CODEX_FAST_MODE"> Fast 模式
|
|
941
|
+
</label>
|
|
942
|
+
</div>
|
|
943
|
+
<button class="btn btn-outline" onclick="validateCli('codex')" style="margin-bottom:12px">检测 Codex CLI</button>
|
|
936
944
|
<div id="codex-validate-result"></div>
|
|
937
945
|
</fieldset>
|
|
938
946
|
</div>
|
|
@@ -1060,8 +1068,9 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
1060
1068
|
<div class="section-detail">
|
|
1061
1069
|
<div class="config-row"><span class="key">CLI 路径</span><span class="val" id="cfg-CODEX_PATH">-</span></div>
|
|
1062
1070
|
<div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CODEX_MODEL">-</span></div>
|
|
1063
|
-
<div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CODEX_ALTERNATIVE_MODEL">-</span></div>
|
|
1071
|
+
<div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CODEX_ALTERNATIVE_MODEL">-</span></div>
|
|
1064
1072
|
<div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-CODEX_EFFORT">-</span></div>
|
|
1073
|
+
<div class="config-row"><span class="key">Fast 模式</span><span class="val" id="cfg-CODEX_FAST_MODE">-</span></div>
|
|
1065
1074
|
<label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-codex" onchange="setDashboardDefaultAgent('codex', this.checked)"> 设为默认 Agent</label>
|
|
1066
1075
|
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
|
|
1067
1076
|
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('codex')">编辑</button>
|
|
@@ -1111,7 +1120,7 @@ var step2InputBound = false;
|
|
|
1111
1120
|
const AGENT_FIELDS = {
|
|
1112
1121
|
claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL','CHATCCC_ANTHROPIC_MAX_TURN'],
|
|
1113
1122
|
cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL','CHATCCC_CURSOR_ALTERNATIVE_MODEL','CHATCCC_CURSOR_AVATAR_BATTERY_MODE','CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'],
|
|
1114
|
-
codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT']
|
|
1123
|
+
codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT','CHATCCC_CODEX_FAST_MODE']
|
|
1115
1124
|
};
|
|
1116
1125
|
const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
|
|
1117
1126
|
const WEB_UI_FIELDS = ['CHATCCC_WEB_UI_OPEN_ON_START'];
|
|
@@ -1402,7 +1411,7 @@ function isAgentEnabled(node, keys) {
|
|
|
1402
1411
|
|
|
1403
1412
|
var CLAUDE_FALLBACK_KEYS = ['model','subagentModel','effort','maxTurn'];
|
|
1404
1413
|
var CURSOR_FALLBACK_KEYS = ['path','command','model','alternativeModel'];
|
|
1405
|
-
var CODEX_FALLBACK_KEYS = ['path','command','model','alternativeModel','effort'];
|
|
1414
|
+
var CODEX_FALLBACK_KEYS = ['path','command','model','alternativeModel','effort','fastMode'];
|
|
1406
1415
|
|
|
1407
1416
|
function renderStep2() {
|
|
1408
1417
|
var c = state.config || {};
|
|
@@ -1427,12 +1436,14 @@ function renderStep2() {
|
|
|
1427
1436
|
var cursorBatteryModeEl = document.getElementById('field-CHATCCC_CURSOR_AVATAR_BATTERY_MODE');
|
|
1428
1437
|
if (cursorBatteryModeEl && !cursorBatteryModeEl.value) cursorBatteryModeEl.value = 'apiPercent';
|
|
1429
1438
|
onCursorBatteryModeChange('field-', cursorBatteryModeEl ? cursorBatteryModeEl.value : 'apiPercent');
|
|
1430
|
-
if (c.codex) {
|
|
1431
|
-
prefillNested('field-CHATCCC_CODEX_PATH', c.codex.path || c.codex.command);
|
|
1432
|
-
prefillNested('field-CHATCCC_CODEX_MODEL', c.codex.model);
|
|
1433
|
-
prefillNested('field-CHATCCC_CODEX_ALTERNATIVE_MODEL', c.codex.alternativeModel);
|
|
1434
|
-
prefillNested('field-CHATCCC_CODEX_EFFORT', c.codex.effort);
|
|
1435
|
-
}
|
|
1439
|
+
if (c.codex) {
|
|
1440
|
+
prefillNested('field-CHATCCC_CODEX_PATH', c.codex.path || c.codex.command);
|
|
1441
|
+
prefillNested('field-CHATCCC_CODEX_MODEL', c.codex.model);
|
|
1442
|
+
prefillNested('field-CHATCCC_CODEX_ALTERNATIVE_MODEL', c.codex.alternativeModel);
|
|
1443
|
+
prefillNested('field-CHATCCC_CODEX_EFFORT', c.codex.effort);
|
|
1444
|
+
}
|
|
1445
|
+
var codexFastModeEl = document.getElementById('field-CHATCCC_CODEX_FAST_MODE');
|
|
1446
|
+
if (codexFastModeEl) codexFastModeEl.checked = !!(c.codex && c.codex.fastMode === true);
|
|
1436
1447
|
|
|
1437
1448
|
// 按已有 config 决定每个 Agent 默认是否开启:优先 enabled 字段,缺省时按"任一字段非空"
|
|
1438
1449
|
var claudeOn = isAgentEnabled(c.claude, CLAUDE_FALLBACK_KEYS);
|
|
@@ -1512,11 +1523,13 @@ function collectAllFields() {
|
|
|
1512
1523
|
if (el && el.value.trim()) vars[key] = el.value.trim();
|
|
1513
1524
|
});
|
|
1514
1525
|
}
|
|
1515
|
-
if (state.agentsEnabled.codex) {
|
|
1516
|
-
AGENT_FIELDS.codex.forEach(function(key){
|
|
1517
|
-
var el = document.getElementById('field-' + key);
|
|
1518
|
-
if (el
|
|
1519
|
-
|
|
1526
|
+
if (state.agentsEnabled.codex) {
|
|
1527
|
+
AGENT_FIELDS.codex.forEach(function(key){
|
|
1528
|
+
var el = document.getElementById('field-' + key);
|
|
1529
|
+
if (!el) return;
|
|
1530
|
+
if (key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
|
|
1531
|
+
else if (el.value.trim()) vars[key] = el.value.trim();
|
|
1532
|
+
});
|
|
1520
1533
|
}
|
|
1521
1534
|
return vars;
|
|
1522
1535
|
}
|
|
@@ -1585,8 +1598,9 @@ function renderStep3() {
|
|
|
1585
1598
|
lines.push('<h4 style="margin:10px 0 4px;color:#334155">Codex</h4>');
|
|
1586
1599
|
if (vars.CHATCCC_CODEX_PATH) lines.push('<div class="config-row"><span class="key">CLI 路径</span><span class="val">' + vars.CHATCCC_CODEX_PATH + '</span></div>');
|
|
1587
1600
|
lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CODEX_MODEL || '(留空)') + '</span></div>');
|
|
1588
|
-
lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CODEX_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
|
|
1589
|
-
lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CODEX_EFFORT || '(留空)') + '</span></div>');
|
|
1601
|
+
lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CODEX_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
|
|
1602
|
+
lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CODEX_EFFORT || '(留空)') + '</span></div>');
|
|
1603
|
+
lines.push('<div class="config-row"><span class="key">Fast 模式</span><span class="val">' + (vars.CHATCCC_CODEX_FAST_MODE ? '已启用' : '已禁用') + '</span></div>');
|
|
1590
1604
|
}
|
|
1591
1605
|
});
|
|
1592
1606
|
document.getElementById('review-content').innerHTML = lines.join('');
|
|
@@ -1793,8 +1807,9 @@ function updateDashboardUI() {
|
|
|
1793
1807
|
document.getElementById('cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET').textContent = String((c.cursor && c.cursor.onDemandMonthlyBudget) || 1000);
|
|
1794
1808
|
document.getElementById('cfg-CODEX_PATH').textContent = (c.codex && (c.codex.path || c.codex.command)) || 'codex';
|
|
1795
1809
|
document.getElementById('cfg-CODEX_MODEL').textContent = (c.codex && c.codex.model) || '(留空)';
|
|
1796
|
-
document.getElementById('cfg-CODEX_ALTERNATIVE_MODEL').textContent = (c.codex && c.codex.alternativeModel) || '(留空)';
|
|
1797
|
-
document.getElementById('cfg-CODEX_EFFORT').textContent = (c.codex && c.codex.effort) || '(留空)';
|
|
1810
|
+
document.getElementById('cfg-CODEX_ALTERNATIVE_MODEL').textContent = (c.codex && c.codex.alternativeModel) || '(留空)';
|
|
1811
|
+
document.getElementById('cfg-CODEX_EFFORT').textContent = (c.codex && c.codex.effort) || '(留空)';
|
|
1812
|
+
document.getElementById('cfg-CODEX_FAST_MODE').textContent = c.codex && c.codex.fastMode === true ? '已启用' : '已禁用';
|
|
1798
1813
|
}
|
|
1799
1814
|
|
|
1800
1815
|
function pollStatus() {
|
|
@@ -1867,7 +1882,8 @@ function editSection(section) {
|
|
|
1867
1882
|
'CHATCCC_CURSOR_PATH': 'CLI 路径', 'CHATCCC_CURSOR_MODEL': '模型', 'CHATCCC_CURSOR_ALTERNATIVE_MODEL': '备选模型',
|
|
1868
1883
|
'CHATCCC_CURSOR_AVATAR_BATTERY_MODE': '头像电池电量',
|
|
1869
1884
|
'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET': '每月On demand use预算',
|
|
1870
|
-
'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CODEX_EFFORT': 'Effort'
|
|
1885
|
+
'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CODEX_EFFORT': 'Effort',
|
|
1886
|
+
'CHATCCC_CODEX_FAST_MODE': 'Fast 模式'
|
|
1871
1887
|
};
|
|
1872
1888
|
var hintMap = {
|
|
1873
1889
|
'CHATCCC_WEB_UI_OPEN_ON_START': '关闭后可继续手动访问 http://localhost:<端口>/;/restart、/update 和 Web UI 重启无论此项为何值都不会自动打开。',
|
|
@@ -1908,13 +1924,14 @@ function editSection(section) {
|
|
|
1908
1924
|
else if (key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET') val = (state.config.cursor.onDemandMonthlyBudget != null) ? String(state.config.cursor.onDemandMonthlyBudget) : '1000';
|
|
1909
1925
|
} else if (section === 'codex' && state.config.codex) {
|
|
1910
1926
|
if (key === 'CHATCCC_CODEX_PATH') val = state.config.codex.path || state.config.codex.command || '';
|
|
1911
|
-
else if (key === 'CHATCCC_CODEX_MODEL') val = state.config.codex.model || '';
|
|
1912
|
-
else if (key === 'CHATCCC_CODEX_ALTERNATIVE_MODEL') val = state.config.codex.alternativeModel || '';
|
|
1913
|
-
else if (key === 'CHATCCC_CODEX_EFFORT') val = state.config.codex.effort || '';
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1927
|
+
else if (key === 'CHATCCC_CODEX_MODEL') val = state.config.codex.model || '';
|
|
1928
|
+
else if (key === 'CHATCCC_CODEX_ALTERNATIVE_MODEL') val = state.config.codex.alternativeModel || '';
|
|
1929
|
+
else if (key === 'CHATCCC_CODEX_EFFORT') val = state.config.codex.effort || '';
|
|
1930
|
+
else if (key === 'CHATCCC_CODEX_FAST_MODE') val = state.config.codex.fastMode === true ? 'true' : 'false';
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
var isSecret = key.includes('SECRET') || key.includes('API_KEY');
|
|
1934
|
+
if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') {
|
|
1918
1935
|
var checked = val === true || val === 'true';
|
|
1919
1936
|
var changeHandler = key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' ? ' onchange="toggleEditChromeDevtoolsFields(this.checked)"' : '';
|
|
1920
1937
|
html += '<div class="form-group"><label style="display:flex;align-items:center;gap:8px"><input type="checkbox" id="edit-' + key + '"' + (checked ? ' checked' : '') + changeHandler + '> ' + (labelMap[key] || key) + '</label>';
|
|
@@ -1982,7 +1999,7 @@ async function saveEdit() {
|
|
|
1982
1999
|
fields.forEach(function(key){
|
|
1983
2000
|
var el = document.getElementById('edit-' + key);
|
|
1984
2001
|
if (!el) return;
|
|
1985
|
-
if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED') vars[key] = !!el.checked;
|
|
2002
|
+
if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
|
|
1986
2003
|
else vars[key] = el.value.trim();
|
|
1987
2004
|
});
|
|
1988
2005
|
if (editSectionType === 'chromeDevtools' && !vars.CHATCCC_CHROME_DEVTOOLS_PORT) {
|