chatccc 0.2.222 → 0.2.224
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/config.sample.json +40 -39
- package/package.json +1 -1
- package/src/__tests__/builtin-chat-session.test.ts +33 -0
- package/src/__tests__/builtin-config.test.ts +1 -0
- package/src/__tests__/card-plain-text.test.ts +7 -6
- package/src/__tests__/cards.test.ts +179 -178
- package/src/__tests__/ccc-adapter.test.ts +20 -0
- package/src/__tests__/config-reload.test.ts +52 -52
- package/src/__tests__/config-sample.test.ts +41 -40
- package/src/__tests__/orchestrator.test.ts +836 -803
- package/src/__tests__/progress-reducer.test.ts +110 -0
- package/src/__tests__/session.test.ts +1183 -1173
- package/src/__tests__/terminal-renderer.test.ts +143 -0
- package/src/adapters/ccc-adapter.ts +1 -0
- package/src/builtin/cli.ts +42 -1
- package/src/builtin/index.ts +10 -0
- package/src/cards.ts +280 -275
- package/src/config.ts +203 -192
- package/src/progress/reducer.ts +108 -0
- package/src/progress/terminal-renderer.ts +190 -0
- package/src/progress/view.ts +77 -0
- package/src/session.ts +938 -937
- package/src/web-ui.ts +610 -607
package/src/cards.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Button helpers
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
import { ABD_HELP_LINE } from "./shared-prefix.ts";
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Button helpers
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import { ABD_HELP_LINE } from "./shared-prefix.ts";
|
|
6
|
+
import type { ProgressView } from "./progress/view.ts";
|
|
7
|
+
|
|
8
|
+
export interface ButtonDef {
|
|
8
9
|
text: string;
|
|
9
10
|
value: string;
|
|
10
11
|
type?: "primary" | "default" | "danger";
|
|
@@ -100,13 +101,17 @@ export function normalizeToolName(name: string): string {
|
|
|
100
101
|
// ---------------------------------------------------------------------------
|
|
101
102
|
|
|
102
103
|
// CardKit schema 2.0 进度卡片(带停止按钮,支持流式更新)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
// 输入是平台无关的 ProgressView(与终端过程区块共享同一视图模型):
|
|
105
|
+
// - view.text → main_content(正文)
|
|
106
|
+
// - view.showStop → 是否展示状态/停止按钮
|
|
107
|
+
// - view.headerTitle → 卡片头部标题(生成中阶段的动态标题)
|
|
108
|
+
// - view.headerTemplate → 卡片头部颜色模板
|
|
109
|
+
export function buildProgressCard(view: ProgressView): string {
|
|
110
|
+
const showStop = view.showStop;
|
|
111
|
+
const headerTitle = view.headerTitle;
|
|
112
|
+
const headerTemplate = view.headerTemplate;
|
|
108
113
|
const elements: object[] = [
|
|
109
|
-
{ tag: "markdown", content: truncateContent(text), element_id: "main_content" },
|
|
114
|
+
{ tag: "markdown", content: truncateContent(view.text), element_id: "main_content" },
|
|
110
115
|
];
|
|
111
116
|
if (showStop) {
|
|
112
117
|
elements.push({ tag: "hr" });
|
|
@@ -152,16 +157,16 @@ export function buildHelpCard(
|
|
|
152
157
|
`发送 **/new** 创建新会话(使用 /cd 设置的目录,默认 ${defaultToolLabel})`,
|
|
153
158
|
"发送 **/new claude** 创建新 Claude 会话",
|
|
154
159
|
"发送 **/new cursor** 创建新 Cursor 会话",
|
|
155
|
-
"发送 **/new codex** 创建新 Codex 会话",
|
|
156
|
-
"发送 **/new ccc** 创建新 CCC Agent 会话",
|
|
160
|
+
"发送 **/new codex** 创建新 Codex 会话",
|
|
161
|
+
"发送 **/new ccc** 创建新 CCC Agent 会话",
|
|
157
162
|
"发送 **/newh** 重置当前会话(沿用当前工作目录,不切换)",
|
|
158
163
|
"发送 **/plan** 以规划模式提问(只读,不执行写操作)",
|
|
159
164
|
"发送 **/ask** 以问答模式提问(只读,不执行写操作)",
|
|
160
|
-
"发送 **/usage** 查看当前 Agent 的用量或余额",
|
|
161
|
-
"发送 **/restart** 重启 ChatCCC 进程",
|
|
162
|
-
"发送 **/update** 更新并重启(仅 npm 全局安装可用)",
|
|
163
|
-
ABD_HELP_LINE,
|
|
164
|
-
].join("\n");
|
|
165
|
+
"发送 **/usage** 查看当前 Agent 的用量或余额",
|
|
166
|
+
"发送 **/restart** 重启 ChatCCC 进程",
|
|
167
|
+
"发送 **/update** 更新并重启(仅 npm 全局安装可用)",
|
|
168
|
+
ABD_HELP_LINE,
|
|
169
|
+
].join("\n");
|
|
165
170
|
return JSON.stringify({
|
|
166
171
|
config: { wide_screen_mode: true },
|
|
167
172
|
header: { template: "blue", title: { content: "ChatCCC", tag: "plain_text" } },
|
|
@@ -172,8 +177,8 @@ export function buildHelpCard(
|
|
|
172
177
|
{ text: `新建默认会话(/new,${defaultToolLabel})`, value: JSON.stringify({ cmd: "new" }), type: "primary" },
|
|
173
178
|
{ text: "新建 Claude 会话(/new claude)", value: JSON.stringify({ cmd: "new claude" }), type: "primary" },
|
|
174
179
|
{ text: "新建 Cursor 会话(/new cursor)", value: JSON.stringify({ cmd: "new cursor" }), type: "primary" },
|
|
175
|
-
{ text: "新建 Codex 会话(/new codex)", value: JSON.stringify({ cmd: "new codex" }), type: "primary" },
|
|
176
|
-
{ text: "新建 CCC Agent 会话(/new ccc)", value: JSON.stringify({ cmd: "new ccc" }), type: "primary" },
|
|
180
|
+
{ text: "新建 Codex 会话(/new codex)", value: JSON.stringify({ cmd: "new codex" }), type: "primary" },
|
|
181
|
+
{ text: "新建 CCC Agent 会话(/new ccc)", value: JSON.stringify({ cmd: "new ccc" }), type: "primary" },
|
|
177
182
|
{ text: "重启 ChatCCC(/restart)", value: JSON.stringify({ cmd: "restart" }), type: "danger" },
|
|
178
183
|
{ text: "更新并重启(/update)", value: JSON.stringify({ cmd: "update" }), type: "danger" },
|
|
179
184
|
{ text: "切换工作路径(/cd)", value: JSON.stringify({ cmd: "cd" }), type: "default" },
|
|
@@ -198,9 +203,9 @@ export function buildCdContent(
|
|
|
198
203
|
? `**当前会话工作路径:** \`${currentCwd}\``
|
|
199
204
|
: "";
|
|
200
205
|
|
|
201
|
-
const statusLine = isUpdate
|
|
202
|
-
? `**后续新建会话默认工作路径(已切换):** \`${dirPath}\``
|
|
203
|
-
: `**后续新建会话默认工作路径:** \`${dirPath}\``;
|
|
206
|
+
const statusLine = isUpdate
|
|
207
|
+
? `**后续新建会话默认工作路径(已切换):** \`${dirPath}\``
|
|
208
|
+
: `**后续新建会话默认工作路径:** \`${dirPath}\``;
|
|
204
209
|
|
|
205
210
|
const lines: string[] = [];
|
|
206
211
|
if (currentLine) lines.push(currentLine, "");
|
|
@@ -247,10 +252,10 @@ export function buildCdCard(
|
|
|
247
252
|
text: { tag: "lark_md", content: `**当前会话工作路径:** \`${sessionCwd}\`` },
|
|
248
253
|
});
|
|
249
254
|
}
|
|
250
|
-
elements.push({
|
|
251
|
-
tag: "div",
|
|
252
|
-
text: { tag: "lark_md", content: `**后续新建会话默认工作路径:** \`${dirPath}\`` },
|
|
253
|
-
});
|
|
255
|
+
elements.push({
|
|
256
|
+
tag: "div",
|
|
257
|
+
text: { tag: "lark_md", content: `**后续新建会话默认工作路径:** \`${dirPath}\`` },
|
|
258
|
+
});
|
|
254
259
|
|
|
255
260
|
if (recentDirs.length > 0) {
|
|
256
261
|
elements.push({ tag: "hr" });
|
|
@@ -295,78 +300,78 @@ export function buildCdCard(
|
|
|
295
300
|
});
|
|
296
301
|
}
|
|
297
302
|
|
|
298
|
-
function sessionToolLabel(tool: string): string {
|
|
299
|
-
if (tool === "cursor") return "Cursor";
|
|
300
|
-
if (tool === "codex") return "Codex";
|
|
301
|
-
if (tool === "ccc") return "CCC Agent";
|
|
302
|
-
return "Claude Code";
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function pushSessionGroup(
|
|
306
|
-
lines: string[],
|
|
307
|
-
title: string,
|
|
308
|
-
sessions: Array<{
|
|
309
|
-
sessionId: string;
|
|
310
|
-
chatName: string;
|
|
311
|
-
chatId: string;
|
|
312
|
-
active: boolean;
|
|
313
|
-
turnCount: number;
|
|
314
|
-
elapsedSeconds: number | null;
|
|
315
|
-
model: string;
|
|
316
|
-
tool: string;
|
|
317
|
-
}>,
|
|
318
|
-
formatSession: (session: {
|
|
319
|
-
sessionId: string;
|
|
320
|
-
chatName: string;
|
|
321
|
-
chatId: string;
|
|
322
|
-
active: boolean;
|
|
323
|
-
turnCount: number;
|
|
324
|
-
elapsedSeconds: number | null;
|
|
325
|
-
model: string;
|
|
326
|
-
tool: string;
|
|
327
|
-
}, index: number) => string,
|
|
328
|
-
index: { value: number },
|
|
329
|
-
): void {
|
|
330
|
-
if (sessions.length === 0) return;
|
|
331
|
-
if (index.value > 0) lines.push("", `**${title}:**`, "");
|
|
332
|
-
else lines.push(`**${title}:**`, "");
|
|
333
|
-
for (const session of sessions) {
|
|
334
|
-
lines.push(formatSession(session, index.value++));
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// 所有会话列表卡片(Claude Code 优先,然后 Cursor)
|
|
339
|
-
export function buildSessionsCard(sessions: Array<{
|
|
340
|
-
sessionId: string;
|
|
341
|
-
chatName: string;
|
|
342
|
-
chatId: string;
|
|
343
|
-
chatType?: string;
|
|
344
|
-
active: boolean;
|
|
303
|
+
function sessionToolLabel(tool: string): string {
|
|
304
|
+
if (tool === "cursor") return "Cursor";
|
|
305
|
+
if (tool === "codex") return "Codex";
|
|
306
|
+
if (tool === "ccc") return "CCC Agent";
|
|
307
|
+
return "Claude Code";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function pushSessionGroup(
|
|
311
|
+
lines: string[],
|
|
312
|
+
title: string,
|
|
313
|
+
sessions: Array<{
|
|
314
|
+
sessionId: string;
|
|
315
|
+
chatName: string;
|
|
316
|
+
chatId: string;
|
|
317
|
+
active: boolean;
|
|
318
|
+
turnCount: number;
|
|
319
|
+
elapsedSeconds: number | null;
|
|
320
|
+
model: string;
|
|
321
|
+
tool: string;
|
|
322
|
+
}>,
|
|
323
|
+
formatSession: (session: {
|
|
324
|
+
sessionId: string;
|
|
325
|
+
chatName: string;
|
|
326
|
+
chatId: string;
|
|
327
|
+
active: boolean;
|
|
328
|
+
turnCount: number;
|
|
329
|
+
elapsedSeconds: number | null;
|
|
330
|
+
model: string;
|
|
331
|
+
tool: string;
|
|
332
|
+
}, index: number) => string,
|
|
333
|
+
index: { value: number },
|
|
334
|
+
): void {
|
|
335
|
+
if (sessions.length === 0) return;
|
|
336
|
+
if (index.value > 0) lines.push("", `**${title}:**`, "");
|
|
337
|
+
else lines.push(`**${title}:**`, "");
|
|
338
|
+
for (const session of sessions) {
|
|
339
|
+
lines.push(formatSession(session, index.value++));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 所有会话列表卡片(Claude Code 优先,然后 Cursor)
|
|
344
|
+
export function buildSessionsCard(sessions: Array<{
|
|
345
|
+
sessionId: string;
|
|
346
|
+
chatName: string;
|
|
347
|
+
chatId: string;
|
|
348
|
+
chatType?: string;
|
|
349
|
+
active: boolean;
|
|
345
350
|
turnCount: number;
|
|
346
351
|
elapsedSeconds: number | null;
|
|
347
352
|
model: string;
|
|
348
353
|
tool: string;
|
|
349
|
-
}>, opts: { defaultToolLabel?: string; fixedPrivateSession?: boolean } = {}): string {
|
|
350
|
-
const defaultToolLabel = opts.defaultToolLabel ?? "Claude Code";
|
|
351
|
-
const fixedPrivateSession = opts.fixedPrivateSession ?? false;
|
|
352
|
-
// 按 tool 分组排序:Claude Code 在前,Cursor 其次,Codex 最后
|
|
353
|
-
const claudeCodeSessions = sessions.filter(s => s.tool !== "cursor" && s.tool !== "codex" && s.tool !== "ccc");
|
|
354
|
-
const cursorSessions = sessions.filter(s => s.tool === "cursor");
|
|
355
|
-
const codexSessions = sessions.filter(s => s.tool === "codex");
|
|
356
|
-
const cccSessions = sessions.filter(s => s.tool === "ccc");
|
|
357
|
-
const hasClaudeCode = claudeCodeSessions.length > 0;
|
|
358
|
-
const hasCursor = cursorSessions.length > 0;
|
|
359
|
-
const hasCodex = codexSessions.length > 0;
|
|
360
|
-
const hasCcc = cccSessions.length > 0;
|
|
354
|
+
}>, opts: { defaultToolLabel?: string; fixedPrivateSession?: boolean } = {}): string {
|
|
355
|
+
const defaultToolLabel = opts.defaultToolLabel ?? "Claude Code";
|
|
356
|
+
const fixedPrivateSession = opts.fixedPrivateSession ?? false;
|
|
357
|
+
// 按 tool 分组排序:Claude Code 在前,Cursor 其次,Codex 最后
|
|
358
|
+
const claudeCodeSessions = sessions.filter(s => s.tool !== "cursor" && s.tool !== "codex" && s.tool !== "ccc");
|
|
359
|
+
const cursorSessions = sessions.filter(s => s.tool === "cursor");
|
|
360
|
+
const codexSessions = sessions.filter(s => s.tool === "codex");
|
|
361
|
+
const cccSessions = sessions.filter(s => s.tool === "ccc");
|
|
362
|
+
const hasClaudeCode = claudeCodeSessions.length > 0;
|
|
363
|
+
const hasCursor = cursorSessions.length > 0;
|
|
364
|
+
const hasCodex = codexSessions.length > 0;
|
|
365
|
+
const hasCcc = cccSessions.length > 0;
|
|
361
366
|
|
|
362
367
|
if (sessions.length === 0) {
|
|
363
368
|
return JSON.stringify({
|
|
364
369
|
config: { wide_screen_mode: true },
|
|
365
370
|
header: { template: "blue", title: { content: "所有会话", tag: "plain_text" } },
|
|
366
371
|
elements: [
|
|
367
|
-
{ tag: "div", text: { tag: "lark_md", content: fixedPrivateSession
|
|
368
|
-
? `当前没有会话记录。\n\n直接发送普通消息即可创建飞书私聊专属 ${defaultToolLabel} 会话;默认 Agent 变化后,下一条普通消息会创建对应 Agent 的新空会话。发送 **/new**、**/new claude**、**/new cursor**、**/new codex** 或 **/new ccc** 会另外创建会话群。`
|
|
369
|
-
: `当前没有会话记录。\n\n使用 **/new**(默认 ${defaultToolLabel})、**/new claude**、**/new cursor**、**/new codex** 或 **/new ccc** 创建新会话。\n创建后可在任意会话群内发送 **/sessions** 查看列表,用 **/session 数字** 切换会话。` } },
|
|
372
|
+
{ tag: "div", text: { tag: "lark_md", content: fixedPrivateSession
|
|
373
|
+
? `当前没有会话记录。\n\n直接发送普通消息即可创建飞书私聊专属 ${defaultToolLabel} 会话;默认 Agent 变化后,下一条普通消息会创建对应 Agent 的新空会话。发送 **/new**、**/new claude**、**/new cursor**、**/new codex** 或 **/new ccc** 会另外创建会话群。`
|
|
374
|
+
: `当前没有会话记录。\n\n使用 **/new**(默认 ${defaultToolLabel})、**/new claude**、**/new cursor**、**/new codex** 或 **/new ccc** 创建新会话。\n创建后可在任意会话群内发送 **/sessions** 查看列表,用 **/session 数字** 切换会话。` } },
|
|
370
375
|
{ tag: "hr" },
|
|
371
376
|
{ tag: "action", actions: [{ tag: "button", text: { tag: "plain_text", content: "收起" }, type: "default", value: { action: "close" } }] },
|
|
372
377
|
],
|
|
@@ -382,24 +387,24 @@ export function buildSessionsCard(sessions: Array<{
|
|
|
382
387
|
const secs = s.elapsedSeconds % 60;
|
|
383
388
|
extra = ` | 本轮: ${mins}分${secs}秒`;
|
|
384
389
|
}
|
|
385
|
-
const toolLabel = sessionToolLabel(s.tool);
|
|
390
|
+
const toolLabel = sessionToolLabel(s.tool);
|
|
386
391
|
const namePart = s.chatName ? `**${s.chatName}** ` : "";
|
|
387
|
-
const chatTag = !s.chatId
|
|
388
|
-
? " (chat id缺失)"
|
|
389
|
-
: s.chatType === "p2p"
|
|
390
|
-
? " (私聊)"
|
|
391
|
-
: s.chatType === "group" || (s.chatType == null && s.chatId.startsWith("oc_"))
|
|
392
|
-
? " (群聊)"
|
|
393
|
-
: "";
|
|
392
|
+
const chatTag = !s.chatId
|
|
393
|
+
? " (chat id缺失)"
|
|
394
|
+
: s.chatType === "p2p"
|
|
395
|
+
? " (私聊)"
|
|
396
|
+
: s.chatType === "group" || (s.chatType == null && s.chatId.startsWith("oc_"))
|
|
397
|
+
? " (群聊)"
|
|
398
|
+
: "";
|
|
394
399
|
return `**${i + 1}.** ${namePart}${chatTag} \`${shortId}\` ${status} | 工具: ${toolLabel} | 轮数: ${s.turnCount} | ${s.model}${extra}`;
|
|
395
400
|
};
|
|
396
401
|
|
|
397
402
|
const lines: string[] = [`共 **${sessions.length}** 个会话:`, ""];
|
|
398
|
-
const idx = { value: 0 };
|
|
399
|
-
if (hasClaudeCode) pushSessionGroup(lines, "Claude Code 会话", claudeCodeSessions, formatSession, idx);
|
|
400
|
-
if (hasCursor) pushSessionGroup(lines, "Cursor 会话", cursorSessions, formatSession, idx);
|
|
401
|
-
if (hasCodex) pushSessionGroup(lines, "Codex 会话", codexSessions, formatSession, idx);
|
|
402
|
-
if (hasCcc) pushSessionGroup(lines, "CCC Agent 会话", cccSessions, formatSession, idx);
|
|
403
|
+
const idx = { value: 0 };
|
|
404
|
+
if (hasClaudeCode) pushSessionGroup(lines, "Claude Code 会话", claudeCodeSessions, formatSession, idx);
|
|
405
|
+
if (hasCursor) pushSessionGroup(lines, "Cursor 会话", cursorSessions, formatSession, idx);
|
|
406
|
+
if (hasCodex) pushSessionGroup(lines, "Codex 会话", codexSessions, formatSession, idx);
|
|
407
|
+
if (hasCcc) pushSessionGroup(lines, "CCC Agent 会话", cccSessions, formatSession, idx);
|
|
403
408
|
|
|
404
409
|
return JSON.stringify({
|
|
405
410
|
config: { wide_screen_mode: true },
|
|
@@ -407,9 +412,9 @@ export function buildSessionsCard(sessions: Array<{
|
|
|
407
412
|
elements: [
|
|
408
413
|
{ tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
|
|
409
414
|
{ tag: "hr" },
|
|
410
|
-
{ tag: "div", text: { tag: "lark_md", content: fixedPrivateSession
|
|
411
|
-
? "当前飞书私聊使用专属会话;默认 Agent 变化后,下一条普通消息会自动创建对应 Agent 的新空会话。发送 **/newh** 可在私聊中原地重置。群聊会话请回到对应群聊继续,私聊不支持 **/session** 切换。"
|
|
412
|
-
: "在会话群内发送 **/newh** 可重置当前会话(创建新 Session,保留工作目录和群聊)。\n发送 **/session 数字**(如 `/session 1`)可将当前群聊切换到列表中对应编号的会话。" } },
|
|
415
|
+
{ tag: "div", text: { tag: "lark_md", content: fixedPrivateSession
|
|
416
|
+
? "当前飞书私聊使用专属会话;默认 Agent 变化后,下一条普通消息会自动创建对应 Agent 的新空会话。发送 **/newh** 可在私聊中原地重置。群聊会话请回到对应群聊继续,私聊不支持 **/session** 切换。"
|
|
417
|
+
: "在会话群内发送 **/newh** 可重置当前会话(创建新 Session,保留工作目录和群聊)。\n发送 **/session 数字**(如 `/session 1`)可将当前群聊切换到列表中对应编号的会话。" } },
|
|
413
418
|
{ tag: "hr" },
|
|
414
419
|
{
|
|
415
420
|
tag: "action",
|
|
@@ -466,11 +471,11 @@ export function buildQueueFullCard(): string {
|
|
|
466
471
|
}
|
|
467
472
|
|
|
468
473
|
// 状态卡片(带关闭按钮)
|
|
469
|
-
export function buildStatusCard(statusText: string, template = "blue"): string {
|
|
470
|
-
return JSON.stringify({
|
|
471
|
-
config: { wide_screen_mode: true },
|
|
472
|
-
header: { template, title: { content: "会话状态", tag: "plain_text" } },
|
|
473
|
-
elements: [
|
|
474
|
+
export function buildStatusCard(statusText: string, template = "blue"): string {
|
|
475
|
+
return JSON.stringify({
|
|
476
|
+
config: { wide_screen_mode: true },
|
|
477
|
+
header: { template, title: { content: "会话状态", tag: "plain_text" } },
|
|
478
|
+
elements: [
|
|
474
479
|
{ tag: "div", text: { tag: "lark_md", content: statusText } },
|
|
475
480
|
{ tag: "hr" },
|
|
476
481
|
{
|
|
@@ -482,82 +487,82 @@ export function buildStatusCard(statusText: string, template = "blue"): string {
|
|
|
482
487
|
value: { action: "close" },
|
|
483
488
|
}],
|
|
484
489
|
},
|
|
485
|
-
],
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
export function buildCodexUsageCard(content: string, resetCreditsAvailable: number | null): string {
|
|
490
|
-
const elements: object[] = [
|
|
491
|
-
{ tag: "div", text: { tag: "lark_md", content } },
|
|
492
|
-
];
|
|
493
|
-
if (resetCreditsAvailable !== null && resetCreditsAvailable > 0) {
|
|
494
|
-
elements.push({ tag: "hr" });
|
|
495
|
-
elements.push({
|
|
496
|
-
tag: "action",
|
|
497
|
-
actions: [{
|
|
498
|
-
tag: "button",
|
|
499
|
-
text: { tag: "plain_text", content: "发起重置" },
|
|
500
|
-
type: "primary",
|
|
501
|
-
value: { action: "codex_reset_request", availableCount: resetCreditsAvailable },
|
|
502
|
-
}],
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
return JSON.stringify({
|
|
507
|
-
config: { wide_screen_mode: true },
|
|
508
|
-
header: { template: "blue", title: { content: "Codex Usage", tag: "plain_text" } },
|
|
509
|
-
elements,
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
export function buildCodexResetConfirmCard(params: {
|
|
514
|
-
availableCount: number;
|
|
515
|
-
parentMessageId: string;
|
|
516
|
-
requestId: string;
|
|
517
|
-
}): string {
|
|
518
|
-
const valueBase = {
|
|
519
|
-
parentMessageId: params.parentMessageId,
|
|
520
|
-
requestId: params.requestId,
|
|
521
|
-
};
|
|
522
|
-
return JSON.stringify({
|
|
523
|
-
config: { wide_screen_mode: true },
|
|
524
|
-
header: { template: "yellow", title: { content: "确认 Codex 主动重置", tag: "plain_text" } },
|
|
525
|
-
elements: [
|
|
526
|
-
{
|
|
527
|
-
tag: "div",
|
|
528
|
-
text: {
|
|
529
|
-
tag: "lark_md",
|
|
530
|
-
content: `当前可用主动重置次数:**${params.availableCount}**。\n\n确认后会消耗 1 次主动重置,并重置当前可重置的 Codex 用量窗口。`,
|
|
531
|
-
},
|
|
532
|
-
},
|
|
533
|
-
{ tag: "hr" },
|
|
534
|
-
{
|
|
535
|
-
tag: "action",
|
|
536
|
-
actions: [
|
|
537
|
-
{
|
|
538
|
-
tag: "button",
|
|
539
|
-
text: { tag: "plain_text", content: "是,发起重置" },
|
|
540
|
-
type: "danger",
|
|
541
|
-
value: { action: "codex_reset_confirm", decision: "yes", ...valueBase },
|
|
542
|
-
},
|
|
543
|
-
{
|
|
544
|
-
tag: "button",
|
|
545
|
-
text: { tag: "plain_text", content: "否" },
|
|
546
|
-
type: "default",
|
|
547
|
-
value: { action: "codex_reset_confirm", decision: "no", ...valueBase },
|
|
548
|
-
},
|
|
549
|
-
],
|
|
550
|
-
},
|
|
551
|
-
],
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
/** 模型切换卡片(/model 命令,飞书专用,含切换按钮) */
|
|
556
|
-
export function buildModelCard(
|
|
557
|
-
currentModel: string,
|
|
558
|
-
models: string[],
|
|
559
|
-
tool?: string,
|
|
560
|
-
): string {
|
|
490
|
+
],
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function buildCodexUsageCard(content: string, resetCreditsAvailable: number | null): string {
|
|
495
|
+
const elements: object[] = [
|
|
496
|
+
{ tag: "div", text: { tag: "lark_md", content } },
|
|
497
|
+
];
|
|
498
|
+
if (resetCreditsAvailable !== null && resetCreditsAvailable > 0) {
|
|
499
|
+
elements.push({ tag: "hr" });
|
|
500
|
+
elements.push({
|
|
501
|
+
tag: "action",
|
|
502
|
+
actions: [{
|
|
503
|
+
tag: "button",
|
|
504
|
+
text: { tag: "plain_text", content: "发起重置" },
|
|
505
|
+
type: "primary",
|
|
506
|
+
value: { action: "codex_reset_request", availableCount: resetCreditsAvailable },
|
|
507
|
+
}],
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return JSON.stringify({
|
|
512
|
+
config: { wide_screen_mode: true },
|
|
513
|
+
header: { template: "blue", title: { content: "Codex Usage", tag: "plain_text" } },
|
|
514
|
+
elements,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function buildCodexResetConfirmCard(params: {
|
|
519
|
+
availableCount: number;
|
|
520
|
+
parentMessageId: string;
|
|
521
|
+
requestId: string;
|
|
522
|
+
}): string {
|
|
523
|
+
const valueBase = {
|
|
524
|
+
parentMessageId: params.parentMessageId,
|
|
525
|
+
requestId: params.requestId,
|
|
526
|
+
};
|
|
527
|
+
return JSON.stringify({
|
|
528
|
+
config: { wide_screen_mode: true },
|
|
529
|
+
header: { template: "yellow", title: { content: "确认 Codex 主动重置", tag: "plain_text" } },
|
|
530
|
+
elements: [
|
|
531
|
+
{
|
|
532
|
+
tag: "div",
|
|
533
|
+
text: {
|
|
534
|
+
tag: "lark_md",
|
|
535
|
+
content: `当前可用主动重置次数:**${params.availableCount}**。\n\n确认后会消耗 1 次主动重置,并重置当前可重置的 Codex 用量窗口。`,
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
{ tag: "hr" },
|
|
539
|
+
{
|
|
540
|
+
tag: "action",
|
|
541
|
+
actions: [
|
|
542
|
+
{
|
|
543
|
+
tag: "button",
|
|
544
|
+
text: { tag: "plain_text", content: "是,发起重置" },
|
|
545
|
+
type: "danger",
|
|
546
|
+
value: { action: "codex_reset_confirm", decision: "yes", ...valueBase },
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
tag: "button",
|
|
550
|
+
text: { tag: "plain_text", content: "否" },
|
|
551
|
+
type: "default",
|
|
552
|
+
value: { action: "codex_reset_confirm", decision: "no", ...valueBase },
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
},
|
|
556
|
+
],
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** 模型切换卡片(/model 命令,飞书专用,含切换按钮) */
|
|
561
|
+
export function buildModelCard(
|
|
562
|
+
currentModel: string,
|
|
563
|
+
models: string[],
|
|
564
|
+
tool?: string,
|
|
565
|
+
): string {
|
|
561
566
|
const toolLabel = tool ? ` (${tool})` : "";
|
|
562
567
|
const currentLine = currentModel
|
|
563
568
|
? `**当前模型:** \`${currentModel}\``
|
|
@@ -574,12 +579,12 @@ export function buildModelCard(
|
|
|
574
579
|
lines.push("", "没有可切换的模型。请在 config.json 中配置模型字段。");
|
|
575
580
|
}
|
|
576
581
|
|
|
577
|
-
if (tool === "codex") {
|
|
578
|
-
lines.push("输入 `/fast` 查看或切换当前会话的 Fast 模式");
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const buttons: ButtonDef[] = [];
|
|
582
|
-
for (const m of models.slice(0, 20)) {
|
|
582
|
+
if (tool === "codex") {
|
|
583
|
+
lines.push("输入 `/fast` 查看或切换当前会话的 Fast 模式");
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const buttons: ButtonDef[] = [];
|
|
587
|
+
for (const m of models.slice(0, 20)) {
|
|
583
588
|
const shortName = m.includes("/") ? m.slice(m.lastIndexOf("/") + 1) : m;
|
|
584
589
|
buttons.push({
|
|
585
590
|
text: `/model ${shortName}`,
|
|
@@ -595,85 +600,85 @@ export function buildModelCard(
|
|
|
595
600
|
{ tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
|
|
596
601
|
{ tag: "hr" },
|
|
597
602
|
buildButtons(buttons),
|
|
598
|
-
],
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
export function buildFastModeCard(enabled: boolean): string {
|
|
603
|
-
const mode = enabled ? "ON (Fast)" : "OFF (Standard)";
|
|
604
|
-
return JSON.stringify({
|
|
605
|
-
config: { wide_screen_mode: true },
|
|
606
|
-
header: {
|
|
607
|
-
template: enabled ? "green" : "blue",
|
|
608
|
-
title: { content: "Codex Fast 模式", tag: "plain_text" },
|
|
609
|
-
},
|
|
610
|
-
elements: [
|
|
611
|
-
{
|
|
612
|
-
tag: "div",
|
|
613
|
-
text: {
|
|
614
|
-
tag: "lark_md",
|
|
615
|
-
content: [
|
|
616
|
-
`**当前模式:** ${mode}`,
|
|
617
|
-
"",
|
|
618
|
-
"切换将在下一条消息生效,当前生成不中断。",
|
|
619
|
-
].join("\n"),
|
|
620
|
-
},
|
|
621
|
-
},
|
|
622
|
-
{ tag: "hr" },
|
|
623
|
-
buildButtons([
|
|
624
|
-
{
|
|
625
|
-
text: "ON",
|
|
626
|
-
value: JSON.stringify({ cmd: "/fast on" }),
|
|
627
|
-
type: enabled ? "primary" : "default",
|
|
628
|
-
},
|
|
629
|
-
{
|
|
630
|
-
text: "OFF",
|
|
631
|
-
value: JSON.stringify({ cmd: "/fast off" }),
|
|
632
|
-
type: enabled ? "default" : "primary",
|
|
633
|
-
},
|
|
634
|
-
]),
|
|
635
|
-
],
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
export function buildEffortCard(
|
|
640
|
-
currentEffort: string,
|
|
641
|
-
efforts: string[],
|
|
642
|
-
tool?: string,
|
|
643
|
-
): string {
|
|
644
|
-
const toolLabel = tool ? ` (${tool})` : "";
|
|
645
|
-
const currentLine = currentEffort
|
|
646
|
-
? `**当前 effort:** \`${currentEffort}\``
|
|
647
|
-
: "**当前 effort:** 未指定";
|
|
648
|
-
|
|
649
|
-
const lines: string[] = [currentLine];
|
|
650
|
-
if (efforts.length > 0) {
|
|
651
|
-
lines.push("", "**可切换 effort**");
|
|
652
|
-
for (const effort of efforts) {
|
|
653
|
-
lines.push(`- \`${effort}\``);
|
|
654
|
-
}
|
|
655
|
-
lines.push("", "点击按钮切换 effort,或输入 `/effort clear` 恢复默认");
|
|
656
|
-
} else {
|
|
657
|
-
lines.push("", "当前 agent 不支持 effort 切换。");
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
const buttons: ButtonDef[] = [];
|
|
661
|
-
for (const effort of efforts.slice(0, 20)) {
|
|
662
|
-
buttons.push({
|
|
663
|
-
text: `/effort ${effort}`,
|
|
664
|
-
value: JSON.stringify({ cmd: `/effort ${effort}` }),
|
|
665
|
-
type: "primary",
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
return JSON.stringify({
|
|
670
|
-
config: { wide_screen_mode: true },
|
|
671
|
-
header: { template: "blue", title: { content: `Effort 切换${toolLabel}`, tag: "plain_text" } },
|
|
672
|
-
elements: [
|
|
673
|
-
{ tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
|
|
674
|
-
{ tag: "hr" },
|
|
675
|
-
buildButtons(buttons),
|
|
676
|
-
],
|
|
677
|
-
});
|
|
678
|
-
}
|
|
603
|
+
],
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export function buildFastModeCard(enabled: boolean): string {
|
|
608
|
+
const mode = enabled ? "ON (Fast)" : "OFF (Standard)";
|
|
609
|
+
return JSON.stringify({
|
|
610
|
+
config: { wide_screen_mode: true },
|
|
611
|
+
header: {
|
|
612
|
+
template: enabled ? "green" : "blue",
|
|
613
|
+
title: { content: "Codex Fast 模式", tag: "plain_text" },
|
|
614
|
+
},
|
|
615
|
+
elements: [
|
|
616
|
+
{
|
|
617
|
+
tag: "div",
|
|
618
|
+
text: {
|
|
619
|
+
tag: "lark_md",
|
|
620
|
+
content: [
|
|
621
|
+
`**当前模式:** ${mode}`,
|
|
622
|
+
"",
|
|
623
|
+
"切换将在下一条消息生效,当前生成不中断。",
|
|
624
|
+
].join("\n"),
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
{ tag: "hr" },
|
|
628
|
+
buildButtons([
|
|
629
|
+
{
|
|
630
|
+
text: "ON",
|
|
631
|
+
value: JSON.stringify({ cmd: "/fast on" }),
|
|
632
|
+
type: enabled ? "primary" : "default",
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
text: "OFF",
|
|
636
|
+
value: JSON.stringify({ cmd: "/fast off" }),
|
|
637
|
+
type: enabled ? "default" : "primary",
|
|
638
|
+
},
|
|
639
|
+
]),
|
|
640
|
+
],
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
export function buildEffortCard(
|
|
645
|
+
currentEffort: string,
|
|
646
|
+
efforts: string[],
|
|
647
|
+
tool?: string,
|
|
648
|
+
): string {
|
|
649
|
+
const toolLabel = tool ? ` (${tool})` : "";
|
|
650
|
+
const currentLine = currentEffort
|
|
651
|
+
? `**当前 effort:** \`${currentEffort}\``
|
|
652
|
+
: "**当前 effort:** 未指定";
|
|
653
|
+
|
|
654
|
+
const lines: string[] = [currentLine];
|
|
655
|
+
if (efforts.length > 0) {
|
|
656
|
+
lines.push("", "**可切换 effort**");
|
|
657
|
+
for (const effort of efforts) {
|
|
658
|
+
lines.push(`- \`${effort}\``);
|
|
659
|
+
}
|
|
660
|
+
lines.push("", "点击按钮切换 effort,或输入 `/effort clear` 恢复默认");
|
|
661
|
+
} else {
|
|
662
|
+
lines.push("", "当前 agent 不支持 effort 切换。");
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const buttons: ButtonDef[] = [];
|
|
666
|
+
for (const effort of efforts.slice(0, 20)) {
|
|
667
|
+
buttons.push({
|
|
668
|
+
text: `/effort ${effort}`,
|
|
669
|
+
value: JSON.stringify({ cmd: `/effort ${effort}` }),
|
|
670
|
+
type: "primary",
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return JSON.stringify({
|
|
675
|
+
config: { wide_screen_mode: true },
|
|
676
|
+
header: { template: "blue", title: { content: `Effort 切换${toolLabel}`, tag: "plain_text" } },
|
|
677
|
+
elements: [
|
|
678
|
+
{ tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
|
|
679
|
+
{ tag: "hr" },
|
|
680
|
+
buildButtons(buttons),
|
|
681
|
+
],
|
|
682
|
+
});
|
|
683
|
+
}
|
|
679
684
|
|