chatccc 0.2.200 → 0.2.202

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.
Files changed (92) hide show
  1. package/README.md +17 -15
  2. package/agent-prompts/claude_specific.md +45 -45
  3. package/agent-prompts/codex_specific.md +2 -2
  4. package/agent-prompts/cursor_specific.md +13 -13
  5. package/bin/cccagent.mjs +17 -17
  6. package/config.sample.json +27 -27
  7. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  8. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  9. package/im-skills/feishu-skill/skill.md +3 -3
  10. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  11. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  12. package/im-skills/wechat-file-skill/skill.md +10 -10
  13. package/im-skills/wechat-image-skill/skill.md +10 -10
  14. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  15. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  16. package/im-skills/wechat-video-skill/skill.md +10 -10
  17. package/package.json +1 -1
  18. package/scripts/postinstall-sharp-check.mjs +58 -58
  19. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  20. package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
  21. package/src/__tests__/builtin-chat-session.test.ts +277 -277
  22. package/src/__tests__/builtin-cli-json.test.ts +39 -39
  23. package/src/__tests__/builtin-config.test.ts +33 -33
  24. package/src/__tests__/builtin-context.test.ts +163 -163
  25. package/src/__tests__/builtin-file-tools.test.ts +224 -224
  26. package/src/__tests__/builtin-session-select.test.ts +116 -116
  27. package/src/__tests__/builtin-sigint.test.ts +56 -56
  28. package/src/__tests__/card-plain-text.test.ts +5 -5
  29. package/src/__tests__/cardkit.test.ts +60 -60
  30. package/src/__tests__/cards.test.ts +133 -122
  31. package/src/__tests__/ccc-adapter.test.ts +114 -114
  32. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  33. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  34. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  35. package/src/__tests__/claude-adapter.test.ts +592 -592
  36. package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
  37. package/src/__tests__/codex-raw-stream-log.test.ts +163 -163
  38. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  39. package/src/__tests__/config-reload.test.ts +10 -10
  40. package/src/__tests__/config-sample.test.ts +18 -18
  41. package/src/__tests__/cursor-adapter.test.ts +226 -226
  42. package/src/__tests__/feishu-api.test.ts +60 -60
  43. package/src/__tests__/feishu-avatar.test.ts +90 -90
  44. package/src/__tests__/feishu-platform.test.ts +22 -22
  45. package/src/__tests__/format-message.test.ts +47 -47
  46. package/src/__tests__/jsonl-stream.test.ts +79 -79
  47. package/src/__tests__/orchestrator.test.ts +464 -301
  48. package/src/__tests__/privacy.test.ts +198 -198
  49. package/src/__tests__/raw-stream-log.test.ts +106 -106
  50. package/src/__tests__/session.test.ts +46 -44
  51. package/src/__tests__/shared-prefix.test.ts +36 -36
  52. package/src/__tests__/sim-platform.test.ts +12 -12
  53. package/src/__tests__/stream-state.test.ts +42 -42
  54. package/src/__tests__/web-ui.test.ts +209 -209
  55. package/src/adapters/ccc-adapter.ts +121 -121
  56. package/src/adapters/claude-adapter.ts +603 -603
  57. package/src/adapters/claude-session-meta-store.ts +120 -120
  58. package/src/adapters/codex-adapter.ts +380 -380
  59. package/src/adapters/cursor-adapter.ts +673 -673
  60. package/src/adapters/jsonl-stream.ts +157 -157
  61. package/src/adapters/raw-stream-log.ts +124 -124
  62. package/src/adapters/resource-monitor.ts +140 -140
  63. package/src/agent-delegate-task-rpc.ts +153 -153
  64. package/src/agent-delegate-task.ts +81 -81
  65. package/src/agent-reload-config-rpc.ts +34 -34
  66. package/src/agent-stop-stuck.ts +129 -129
  67. package/src/builtin/cli.ts +473 -473
  68. package/src/builtin/context.ts +323 -323
  69. package/src/builtin/file-tools.ts +1072 -1072
  70. package/src/builtin/index.ts +404 -404
  71. package/src/builtin/session-select.ts +48 -48
  72. package/src/builtin/sigint.ts +50 -50
  73. package/src/cards.ts +222 -210
  74. package/src/chatgpt-subscription-rpc.ts +27 -27
  75. package/src/chatgpt-subscription.ts +299 -299
  76. package/src/chrome-devtools-guard.ts +318 -318
  77. package/src/codex-reset-actions.ts +184 -184
  78. package/src/config.ts +125 -125
  79. package/src/feishu-api.ts +118 -118
  80. package/src/feishu-platform.ts +20 -20
  81. package/src/format-message.ts +293 -293
  82. package/src/litellm-proxy.ts +374 -374
  83. package/src/orchestrator.ts +348 -329
  84. package/src/privacy.ts +118 -118
  85. package/src/runtime-reload.ts +34 -34
  86. package/src/session-chat-binding.ts +6 -6
  87. package/src/session-name.ts +8 -8
  88. package/src/session.ts +177 -169
  89. package/src/shared-prefix.ts +29 -29
  90. package/src/sim-platform.ts +20 -20
  91. package/src/turn-cards.ts +117 -117
  92. package/src/web-ui.ts +205 -205
package/src/cards.ts CHANGED
@@ -1,10 +1,10 @@
1
- // ---------------------------------------------------------------------------
2
- // Button helpers
3
- // ---------------------------------------------------------------------------
4
-
5
- import { ABD_HELP_LINE } from "./shared-prefix.ts";
6
-
7
- export interface ButtonDef {
1
+ // ---------------------------------------------------------------------------
2
+ // Button helpers
3
+ // ---------------------------------------------------------------------------
4
+
5
+ import { ABD_HELP_LINE } from "./shared-prefix.ts";
6
+
7
+ export interface ButtonDef {
8
8
  text: string;
9
9
  value: string;
10
10
  type?: "primary" | "default" | "danger";
@@ -137,11 +137,11 @@ export function buildHelpCard(
137
137
  "发送 **/newh** 重置当前会话(沿用当前工作目录,不切换)",
138
138
  "发送 **/plan** 以规划模式提问(只读,不执行写操作)",
139
139
  "发送 **/ask** 以问答模式提问(只读,不执行写操作)",
140
- "发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡",
141
- "发送 **/restart** 重启 ChatCCC 进程",
142
- "发送 **/update** 更新并重启(仅 npm 全局安装可用)",
143
- ABD_HELP_LINE,
144
- ].join("\n");
140
+ "发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡",
141
+ "发送 **/restart** 重启 ChatCCC 进程",
142
+ "发送 **/update** 更新并重启(仅 npm 全局安装可用)",
143
+ ABD_HELP_LINE,
144
+ ].join("\n");
145
145
  return JSON.stringify({
146
146
  config: { wide_screen_mode: true },
147
147
  header: { template: "blue", title: { content: "ChatCCC", tag: "plain_text" } },
@@ -177,9 +177,9 @@ export function buildCdContent(
177
177
  ? `**当前会话工作路径:** \`${currentCwd}\``
178
178
  : "";
179
179
 
180
- const statusLine = isUpdate
181
- ? `**本会话默认工作路径(已切换):** \`${dirPath}\``
182
- : `**本会话默认工作路径:** \`${dirPath}\``;
180
+ const statusLine = isUpdate
181
+ ? `**后续新建会话默认工作路径(已切换):** \`${dirPath}\``
182
+ : `**后续新建会话默认工作路径:** \`${dirPath}\``;
183
183
 
184
184
  const lines: string[] = [];
185
185
  if (currentLine) lines.push(currentLine, "");
@@ -226,10 +226,10 @@ export function buildCdCard(
226
226
  text: { tag: "lark_md", content: `**当前会话工作路径:** \`${sessionCwd}\`` },
227
227
  });
228
228
  }
229
- elements.push({
230
- tag: "div",
231
- text: { tag: "lark_md", content: `**本会话默认工作路径:** \`${dirPath}\`` },
232
- });
229
+ elements.push({
230
+ tag: "div",
231
+ text: { tag: "lark_md", content: `**后续新建会话默认工作路径:** \`${dirPath}\`` },
232
+ });
233
233
 
234
234
  if (recentDirs.length > 0) {
235
235
  elements.push({ tag: "hr" });
@@ -274,74 +274,78 @@ export function buildCdCard(
274
274
  });
275
275
  }
276
276
 
277
- function sessionToolLabel(tool: string): string {
278
- if (tool === "cursor") return "Cursor";
279
- if (tool === "codex") return "Codex";
280
- if (tool === "ccc") return "CCC Agent";
281
- return "Claude Code";
282
- }
283
-
284
- function pushSessionGroup(
285
- lines: string[],
286
- title: string,
287
- sessions: Array<{
288
- sessionId: string;
289
- chatName: string;
290
- chatId: string;
291
- active: boolean;
292
- turnCount: number;
293
- elapsedSeconds: number | null;
294
- model: string;
295
- tool: string;
296
- }>,
297
- formatSession: (session: {
298
- sessionId: string;
299
- chatName: string;
300
- chatId: string;
301
- active: boolean;
302
- turnCount: number;
303
- elapsedSeconds: number | null;
304
- model: string;
305
- tool: string;
306
- }, index: number) => string,
307
- index: { value: number },
308
- ): void {
309
- if (sessions.length === 0) return;
310
- if (index.value > 0) lines.push("", `**${title}:**`, "");
311
- else lines.push(`**${title}:**`, "");
312
- for (const session of sessions) {
313
- lines.push(formatSession(session, index.value++));
314
- }
315
- }
316
-
317
- // 所有会话列表卡片(Claude Code 优先,然后 Cursor)
318
- export function buildSessionsCard(sessions: Array<{
319
- sessionId: string;
320
- chatName: string;
321
- chatId: string;
322
- active: boolean;
277
+ function sessionToolLabel(tool: string): string {
278
+ if (tool === "cursor") return "Cursor";
279
+ if (tool === "codex") return "Codex";
280
+ if (tool === "ccc") return "CCC Agent";
281
+ return "Claude Code";
282
+ }
283
+
284
+ function pushSessionGroup(
285
+ lines: string[],
286
+ title: string,
287
+ sessions: Array<{
288
+ sessionId: string;
289
+ chatName: string;
290
+ chatId: string;
291
+ active: boolean;
292
+ turnCount: number;
293
+ elapsedSeconds: number | null;
294
+ model: string;
295
+ tool: string;
296
+ }>,
297
+ formatSession: (session: {
298
+ sessionId: string;
299
+ chatName: string;
300
+ chatId: string;
301
+ active: boolean;
302
+ turnCount: number;
303
+ elapsedSeconds: number | null;
304
+ model: string;
305
+ tool: string;
306
+ }, index: number) => string,
307
+ index: { value: number },
308
+ ): void {
309
+ if (sessions.length === 0) return;
310
+ if (index.value > 0) lines.push("", `**${title}:**`, "");
311
+ else lines.push(`**${title}:**`, "");
312
+ for (const session of sessions) {
313
+ lines.push(formatSession(session, index.value++));
314
+ }
315
+ }
316
+
317
+ // 所有会话列表卡片(Claude Code 优先,然后 Cursor)
318
+ export function buildSessionsCard(sessions: Array<{
319
+ sessionId: string;
320
+ chatName: string;
321
+ chatId: string;
322
+ chatType?: string;
323
+ active: boolean;
323
324
  turnCount: number;
324
325
  elapsedSeconds: number | null;
325
326
  model: string;
326
327
  tool: string;
327
- }>, opts: { defaultToolLabel?: string } = {}): string {
328
- const defaultToolLabel = opts.defaultToolLabel ?? "Claude Code";
329
- // tool 分组排序:Claude Code 在前,Cursor 其次,Codex 最后
330
- const claudeCodeSessions = sessions.filter(s => s.tool !== "cursor" && s.tool !== "codex" && s.tool !== "ccc");
331
- const cursorSessions = sessions.filter(s => s.tool === "cursor");
332
- const codexSessions = sessions.filter(s => s.tool === "codex");
333
- const cccSessions = sessions.filter(s => s.tool === "ccc");
334
- const hasClaudeCode = claudeCodeSessions.length > 0;
335
- const hasCursor = cursorSessions.length > 0;
336
- const hasCodex = codexSessions.length > 0;
337
- const hasCcc = cccSessions.length > 0;
328
+ }>, opts: { defaultToolLabel?: string; fixedPrivateSession?: boolean } = {}): string {
329
+ const defaultToolLabel = opts.defaultToolLabel ?? "Claude Code";
330
+ const fixedPrivateSession = opts.fixedPrivateSession ?? false;
331
+ // tool 分组排序:Claude Code 在前,Cursor 其次,Codex 最后
332
+ const claudeCodeSessions = sessions.filter(s => s.tool !== "cursor" && s.tool !== "codex" && s.tool !== "ccc");
333
+ const cursorSessions = sessions.filter(s => s.tool === "cursor");
334
+ const codexSessions = sessions.filter(s => s.tool === "codex");
335
+ const cccSessions = sessions.filter(s => s.tool === "ccc");
336
+ const hasClaudeCode = claudeCodeSessions.length > 0;
337
+ const hasCursor = cursorSessions.length > 0;
338
+ const hasCodex = codexSessions.length > 0;
339
+ const hasCcc = cccSessions.length > 0;
338
340
 
339
341
  if (sessions.length === 0) {
340
342
  return JSON.stringify({
341
343
  config: { wide_screen_mode: true },
342
344
  header: { template: "blue", title: { content: "所有会话", tag: "plain_text" } },
343
345
  elements: [
344
- { tag: "div", text: { tag: "lark_md", content: `当前没有会话记录。\n\n使用 **/new**(默认 ${defaultToolLabel})、**/new claude**、**/new cursor** 或 **/new codex** 创建新会话。\n创建后可在任意会话群内发送 **/sessions** 查看列表,用 **/session 数字** 切换会话。` } },
346
+ { tag: "div", text: { tag: "lark_md", content: fixedPrivateSession
347
+ ? `当前没有会话记录。\n\n直接发送普通消息即可创建飞书私聊专属 ${defaultToolLabel} 会话;发送 **/new**、**/new claude**、**/new cursor** 或 **/new codex** 会另外创建会话群。`
348
+ : `当前没有会话记录。\n\n使用 **/new**(默认 ${defaultToolLabel})、**/new claude**、**/new cursor** 或 **/new codex** 创建新会话。\n创建后可在任意会话群内发送 **/sessions** 查看列表,用 **/session 数字** 切换会话。` } },
345
349
  { tag: "hr" },
346
350
  { tag: "action", actions: [{ tag: "button", text: { tag: "plain_text", content: "收起" }, type: "default", value: { action: "close" } }] },
347
351
  ],
@@ -357,18 +361,24 @@ export function buildSessionsCard(sessions: Array<{
357
361
  const secs = s.elapsedSeconds % 60;
358
362
  extra = ` | 本轮: ${mins}分${secs}秒`;
359
363
  }
360
- const toolLabel = sessionToolLabel(s.tool);
364
+ const toolLabel = sessionToolLabel(s.tool);
361
365
  const namePart = s.chatName ? `**${s.chatName}** ` : "";
362
- const chatTag = !s.chatId ? " (chat id缺失)" : s.chatId.startsWith("oc_") ? " (群聊)" : "";
366
+ const chatTag = !s.chatId
367
+ ? " (chat id缺失)"
368
+ : s.chatType === "p2p"
369
+ ? " (私聊)"
370
+ : s.chatType === "group" || (s.chatType == null && s.chatId.startsWith("oc_"))
371
+ ? " (群聊)"
372
+ : "";
363
373
  return `**${i + 1}.** ${namePart}${chatTag} \`${shortId}\` ${status} | 工具: ${toolLabel} | 轮数: ${s.turnCount} | ${s.model}${extra}`;
364
374
  };
365
375
 
366
376
  const lines: string[] = [`共 **${sessions.length}** 个会话:`, ""];
367
- const idx = { value: 0 };
368
- if (hasClaudeCode) pushSessionGroup(lines, "Claude Code 会话", claudeCodeSessions, formatSession, idx);
369
- if (hasCursor) pushSessionGroup(lines, "Cursor 会话", cursorSessions, formatSession, idx);
370
- if (hasCodex) pushSessionGroup(lines, "Codex 会话", codexSessions, formatSession, idx);
371
- if (hasCcc) pushSessionGroup(lines, "CCC Agent 会话", cccSessions, formatSession, idx);
377
+ const idx = { value: 0 };
378
+ if (hasClaudeCode) pushSessionGroup(lines, "Claude Code 会话", claudeCodeSessions, formatSession, idx);
379
+ if (hasCursor) pushSessionGroup(lines, "Cursor 会话", cursorSessions, formatSession, idx);
380
+ if (hasCodex) pushSessionGroup(lines, "Codex 会话", codexSessions, formatSession, idx);
381
+ if (hasCcc) pushSessionGroup(lines, "CCC Agent 会话", cccSessions, formatSession, idx);
372
382
 
373
383
  return JSON.stringify({
374
384
  config: { wide_screen_mode: true },
@@ -376,7 +386,9 @@ export function buildSessionsCard(sessions: Array<{
376
386
  elements: [
377
387
  { tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
378
388
  { tag: "hr" },
379
- { tag: "div", text: { tag: "lark_md", content: "在会话群内发送 **/newh** 可重置当前会话(创建新 Session,保留工作目录和群聊)。\n发送 **/session 数字**(如 `/session 1`)可将当前群聊切换到列表中对应编号的会话。" } },
389
+ { tag: "div", text: { tag: "lark_md", content: fixedPrivateSession
390
+ ? "当前飞书私聊使用固定的专属会话;发送 **/newh** 可在私聊中原地重置。群聊会话请回到对应群聊继续,私聊不支持 **/session** 切换。"
391
+ : "在会话群内发送 **/newh** 可重置当前会话(创建新 Session,保留工作目录和群聊)。\n发送 **/session 数字**(如 `/session 1`)可将当前群聊切换到列表中对应编号的会话。" } },
380
392
  { tag: "hr" },
381
393
  {
382
394
  tag: "action",
@@ -433,11 +445,11 @@ export function buildQueueFullCard(): string {
433
445
  }
434
446
 
435
447
  // 状态卡片(带关闭按钮)
436
- export function buildStatusCard(statusText: string, template = "blue"): string {
437
- return JSON.stringify({
438
- config: { wide_screen_mode: true },
439
- header: { template, title: { content: "会话状态", tag: "plain_text" } },
440
- elements: [
448
+ export function buildStatusCard(statusText: string, template = "blue"): string {
449
+ return JSON.stringify({
450
+ config: { wide_screen_mode: true },
451
+ header: { template, title: { content: "会话状态", tag: "plain_text" } },
452
+ elements: [
441
453
  { tag: "div", text: { tag: "lark_md", content: statusText } },
442
454
  { tag: "hr" },
443
455
  {
@@ -449,82 +461,82 @@ export function buildStatusCard(statusText: string, template = "blue"): string {
449
461
  value: { action: "close" },
450
462
  }],
451
463
  },
452
- ],
453
- });
454
- }
455
-
456
- export function buildCodexUsageCard(content: string, resetCreditsAvailable: number | null): string {
457
- const elements: object[] = [
458
- { tag: "div", text: { tag: "lark_md", content } },
459
- ];
460
- if (resetCreditsAvailable !== null && resetCreditsAvailable > 0) {
461
- elements.push({ tag: "hr" });
462
- elements.push({
463
- tag: "action",
464
- actions: [{
465
- tag: "button",
466
- text: { tag: "plain_text", content: "发起重置" },
467
- type: "primary",
468
- value: { action: "codex_reset_request", availableCount: resetCreditsAvailable },
469
- }],
470
- });
471
- }
472
-
473
- return JSON.stringify({
474
- config: { wide_screen_mode: true },
475
- header: { template: "blue", title: { content: "Codex Usage", tag: "plain_text" } },
476
- elements,
477
- });
478
- }
479
-
480
- export function buildCodexResetConfirmCard(params: {
481
- availableCount: number;
482
- parentMessageId: string;
483
- requestId: string;
484
- }): string {
485
- const valueBase = {
486
- parentMessageId: params.parentMessageId,
487
- requestId: params.requestId,
488
- };
489
- return JSON.stringify({
490
- config: { wide_screen_mode: true },
491
- header: { template: "yellow", title: { content: "确认 Codex 主动重置", tag: "plain_text" } },
492
- elements: [
493
- {
494
- tag: "div",
495
- text: {
496
- tag: "lark_md",
497
- content: `当前可用主动重置次数:**${params.availableCount}**。\n\n确认后会消耗 1 次主动重置,并重置当前可重置的 Codex 用量窗口。`,
498
- },
499
- },
500
- { tag: "hr" },
501
- {
502
- tag: "action",
503
- actions: [
504
- {
505
- tag: "button",
506
- text: { tag: "plain_text", content: "是,发起重置" },
507
- type: "danger",
508
- value: { action: "codex_reset_confirm", decision: "yes", ...valueBase },
509
- },
510
- {
511
- tag: "button",
512
- text: { tag: "plain_text", content: "否" },
513
- type: "default",
514
- value: { action: "codex_reset_confirm", decision: "no", ...valueBase },
515
- },
516
- ],
517
- },
518
- ],
519
- });
520
- }
521
-
522
- /** 模型切换卡片(/model 命令,飞书专用,含切换按钮) */
523
- export function buildModelCard(
524
- currentModel: string,
525
- models: string[],
526
- tool?: string,
527
- ): string {
464
+ ],
465
+ });
466
+ }
467
+
468
+ export function buildCodexUsageCard(content: string, resetCreditsAvailable: number | null): string {
469
+ const elements: object[] = [
470
+ { tag: "div", text: { tag: "lark_md", content } },
471
+ ];
472
+ if (resetCreditsAvailable !== null && resetCreditsAvailable > 0) {
473
+ elements.push({ tag: "hr" });
474
+ elements.push({
475
+ tag: "action",
476
+ actions: [{
477
+ tag: "button",
478
+ text: { tag: "plain_text", content: "发起重置" },
479
+ type: "primary",
480
+ value: { action: "codex_reset_request", availableCount: resetCreditsAvailable },
481
+ }],
482
+ });
483
+ }
484
+
485
+ return JSON.stringify({
486
+ config: { wide_screen_mode: true },
487
+ header: { template: "blue", title: { content: "Codex Usage", tag: "plain_text" } },
488
+ elements,
489
+ });
490
+ }
491
+
492
+ export function buildCodexResetConfirmCard(params: {
493
+ availableCount: number;
494
+ parentMessageId: string;
495
+ requestId: string;
496
+ }): string {
497
+ const valueBase = {
498
+ parentMessageId: params.parentMessageId,
499
+ requestId: params.requestId,
500
+ };
501
+ return JSON.stringify({
502
+ config: { wide_screen_mode: true },
503
+ header: { template: "yellow", title: { content: "确认 Codex 主动重置", tag: "plain_text" } },
504
+ elements: [
505
+ {
506
+ tag: "div",
507
+ text: {
508
+ tag: "lark_md",
509
+ content: `当前可用主动重置次数:**${params.availableCount}**。\n\n确认后会消耗 1 次主动重置,并重置当前可重置的 Codex 用量窗口。`,
510
+ },
511
+ },
512
+ { tag: "hr" },
513
+ {
514
+ tag: "action",
515
+ actions: [
516
+ {
517
+ tag: "button",
518
+ text: { tag: "plain_text", content: "是,发起重置" },
519
+ type: "danger",
520
+ value: { action: "codex_reset_confirm", decision: "yes", ...valueBase },
521
+ },
522
+ {
523
+ tag: "button",
524
+ text: { tag: "plain_text", content: "否" },
525
+ type: "default",
526
+ value: { action: "codex_reset_confirm", decision: "no", ...valueBase },
527
+ },
528
+ ],
529
+ },
530
+ ],
531
+ });
532
+ }
533
+
534
+ /** 模型切换卡片(/model 命令,飞书专用,含切换按钮) */
535
+ export function buildModelCard(
536
+ currentModel: string,
537
+ models: string[],
538
+ tool?: string,
539
+ ): string {
528
540
  const toolLabel = tool ? ` (${tool})` : "";
529
541
  const currentLine = currentModel
530
542
  ? `**当前模型:** \`${currentModel}\``
@@ -558,48 +570,48 @@ export function buildModelCard(
558
570
  { tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
559
571
  { tag: "hr" },
560
572
  buildButtons(buttons),
561
- ],
562
- });
563
- }
564
-
565
- export function buildEffortCard(
566
- currentEffort: string,
567
- efforts: string[],
568
- tool?: string,
569
- ): string {
570
- const toolLabel = tool ? ` (${tool})` : "";
571
- const currentLine = currentEffort
572
- ? `**当前 effort:** \`${currentEffort}\``
573
- : "**当前 effort:** 未指定";
574
-
575
- const lines: string[] = [currentLine];
576
- if (efforts.length > 0) {
577
- lines.push("", "**可切换 effort**");
578
- for (const effort of efforts) {
579
- lines.push(`- \`${effort}\``);
580
- }
581
- lines.push("", "点击按钮切换 effort,或输入 `/effort clear` 恢复默认");
582
- } else {
583
- lines.push("", "当前 agent 不支持 effort 切换。");
584
- }
585
-
586
- const buttons: ButtonDef[] = [];
587
- for (const effort of efforts.slice(0, 20)) {
588
- buttons.push({
589
- text: `/effort ${effort}`,
590
- value: JSON.stringify({ cmd: `/effort ${effort}` }),
591
- type: "primary",
592
- });
593
- }
594
-
595
- return JSON.stringify({
596
- config: { wide_screen_mode: true },
597
- header: { template: "blue", title: { content: `Effort 切换${toolLabel}`, tag: "plain_text" } },
598
- elements: [
599
- { tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
600
- { tag: "hr" },
601
- buildButtons(buttons),
602
- ],
603
- });
604
- }
573
+ ],
574
+ });
575
+ }
576
+
577
+ export function buildEffortCard(
578
+ currentEffort: string,
579
+ efforts: string[],
580
+ tool?: string,
581
+ ): string {
582
+ const toolLabel = tool ? ` (${tool})` : "";
583
+ const currentLine = currentEffort
584
+ ? `**当前 effort:** \`${currentEffort}\``
585
+ : "**当前 effort:** 未指定";
586
+
587
+ const lines: string[] = [currentLine];
588
+ if (efforts.length > 0) {
589
+ lines.push("", "**可切换 effort**");
590
+ for (const effort of efforts) {
591
+ lines.push(`- \`${effort}\``);
592
+ }
593
+ lines.push("", "点击按钮切换 effort,或输入 `/effort clear` 恢复默认");
594
+ } else {
595
+ lines.push("", "当前 agent 不支持 effort 切换。");
596
+ }
597
+
598
+ const buttons: ButtonDef[] = [];
599
+ for (const effort of efforts.slice(0, 20)) {
600
+ buttons.push({
601
+ text: `/effort ${effort}`,
602
+ value: JSON.stringify({ cmd: `/effort ${effort}` }),
603
+ type: "primary",
604
+ });
605
+ }
606
+
607
+ return JSON.stringify({
608
+ config: { wide_screen_mode: true },
609
+ header: { template: "blue", title: { content: `Effort 切换${toolLabel}`, tag: "plain_text" } },
610
+ elements: [
611
+ { tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
612
+ { tag: "hr" },
613
+ buildButtons(buttons),
614
+ ],
615
+ });
616
+ }
605
617
 
@@ -1,27 +1,27 @@
1
- import type { IncomingMessage, ServerResponse } from "node:http";
2
-
3
- import { getChatGptSubscriptionStatus } from "./chatgpt-subscription.ts";
4
-
5
- export const CHATGPT_SUBSCRIPTION_PATH = "/api/chatgpt/subscription";
6
-
7
- function jsonReply(res: ServerResponse, code: number, data: unknown): void {
8
- res.writeHead(code, { "Content-Type": "application/json; charset=utf-8" });
9
- res.end(JSON.stringify(data));
10
- }
11
-
12
- export async function handleChatGptSubscriptionRequest(
13
- req: IncomingMessage,
14
- res: ServerResponse,
15
- ): Promise<boolean> {
16
- const method = req.method ?? "GET";
17
- const url = new URL(req.url ?? "/", "http://127.0.0.1");
18
- if (url.pathname !== CHATGPT_SUBSCRIPTION_PATH) return false;
19
-
20
- if (method !== "POST") {
21
- jsonReply(res, 405, { ok: false, code: "method_not_allowed", reason: "Use POST." });
22
- return true;
23
- }
24
-
25
- jsonReply(res, 200, await getChatGptSubscriptionStatus());
26
- return true;
27
- }
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+
3
+ import { getChatGptSubscriptionStatus } from "./chatgpt-subscription.ts";
4
+
5
+ export const CHATGPT_SUBSCRIPTION_PATH = "/api/chatgpt/subscription";
6
+
7
+ function jsonReply(res: ServerResponse, code: number, data: unknown): void {
8
+ res.writeHead(code, { "Content-Type": "application/json; charset=utf-8" });
9
+ res.end(JSON.stringify(data));
10
+ }
11
+
12
+ export async function handleChatGptSubscriptionRequest(
13
+ req: IncomingMessage,
14
+ res: ServerResponse,
15
+ ): Promise<boolean> {
16
+ const method = req.method ?? "GET";
17
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
18
+ if (url.pathname !== CHATGPT_SUBSCRIPTION_PATH) return false;
19
+
20
+ if (method !== "POST") {
21
+ jsonReply(res, 405, { ok: false, code: "method_not_allowed", reason: "Use POST." });
22
+ return true;
23
+ }
24
+
25
+ jsonReply(res, 200, await getChatGptSubscriptionStatus());
26
+ return true;
27
+ }