chatccc 0.2.188 → 0.2.190

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 (67) hide show
  1. package/agent-prompts/claude_specific.md +45 -45
  2. package/agent-prompts/codex_specific.md +2 -2
  3. package/agent-prompts/cursor_specific.md +13 -13
  4. package/config.sample.json +14 -14
  5. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  6. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  7. package/im-skills/feishu-skill/skill.md +3 -3
  8. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  9. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  10. package/im-skills/wechat-file-skill/skill.md +10 -10
  11. package/im-skills/wechat-image-skill/skill.md +10 -10
  12. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  13. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  14. package/im-skills/wechat-video-skill/skill.md +10 -10
  15. package/package.json +1 -1
  16. package/scripts/postinstall-sharp-check.mjs +58 -58
  17. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  18. package/src/__tests__/builtin-config.test.ts +33 -33
  19. package/src/__tests__/card-plain-text.test.ts +5 -5
  20. package/src/__tests__/cardkit.test.ts +60 -60
  21. package/src/__tests__/cards.test.ts +77 -77
  22. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  23. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  24. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  25. package/src/__tests__/claude-adapter.test.ts +592 -592
  26. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  27. package/src/__tests__/config-reload.test.ts +4 -4
  28. package/src/__tests__/config-sample.test.ts +17 -17
  29. package/src/__tests__/feishu-api.test.ts +60 -60
  30. package/src/__tests__/feishu-platform.test.ts +22 -22
  31. package/src/__tests__/format-message.test.ts +47 -47
  32. package/src/__tests__/orchestrator.test.ts +116 -112
  33. package/src/__tests__/privacy.test.ts +198 -198
  34. package/src/__tests__/raw-stream-log.test.ts +106 -106
  35. package/src/__tests__/session.test.ts +17 -17
  36. package/src/__tests__/shared-prefix.test.ts +36 -36
  37. package/src/__tests__/stream-state.test.ts +42 -42
  38. package/src/__tests__/web-ui.test.ts +209 -130
  39. package/src/adapters/claude-adapter.ts +566 -566
  40. package/src/adapters/claude-session-meta-store.ts +120 -120
  41. package/src/adapters/codex-adapter.ts +27 -28
  42. package/src/adapters/cursor-adapter.ts +46 -46
  43. package/src/adapters/raw-stream-log.ts +124 -124
  44. package/src/adapters/resource-monitor.ts +140 -140
  45. package/src/agent-delegate-task-rpc.ts +153 -153
  46. package/src/agent-delegate-task.ts +81 -81
  47. package/src/agent-stop-stuck.ts +129 -129
  48. package/src/builtin/cli.ts +189 -189
  49. package/src/builtin/index.ts +170 -168
  50. package/src/cards.ts +137 -137
  51. package/src/chatgpt-subscription-rpc.ts +27 -27
  52. package/src/chatgpt-subscription.ts +299 -299
  53. package/src/chrome-devtools-guard.ts +318 -318
  54. package/src/codex-reset-actions.ts +184 -184
  55. package/src/config.ts +86 -86
  56. package/src/feishu-platform.ts +20 -20
  57. package/src/format-message.ts +293 -293
  58. package/src/litellm-proxy.ts +374 -374
  59. package/src/orchestrator.ts +143 -143
  60. package/src/privacy.ts +118 -118
  61. package/src/session-chat-binding.ts +6 -6
  62. package/src/session-name.ts +8 -8
  63. package/src/session.ts +98 -98
  64. package/src/shared-prefix.ts +29 -29
  65. package/src/sim-platform.ts +20 -20
  66. package/src/turn-cards.ts +117 -117
  67. package/src/web-ui.ts +142 -24
@@ -1,168 +1,170 @@
1
- /**
2
- * builtin/index.ts — ChatCCC 内置 Agent 核心 API
3
- *
4
- * ChatSession 是程序化入口,既可以被 CLI 调用,也可以被其他模块(如 ToolAdapter)调用。
5
- */
6
-
7
- import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
8
- import { streamText } from "ai";
9
-
10
- import { config as appConfig } from "../config.ts";
11
-
12
- // ---------------------------------------------------------------------------
13
- // 系统提示词 — 编译期冻结常量
14
- // ---------------------------------------------------------------------------
15
-
16
- const SYSTEM_PROMPT = [
17
- "你是 ChatCCC 内置 AI 编程助手,运行在终端环境中。",
18
- "",
19
- "## 基本规则",
20
- "- 用中文回复,但代码、命令、文件名保持原文",
21
- "- 优先给出直接可用的方案,而非长篇解释",
22
- "- 如果用户的问题涉及代码,直接给出代码并说明用法",
23
- "- 保持简洁,一次聚焦一个问题",
24
- ].join("\n");
25
-
26
- // ---------------------------------------------------------------------------
27
- // 类型定义
28
- // ---------------------------------------------------------------------------
29
-
30
- export interface ChatSessionConfig {
31
- /** DeepSeek API 兼容的服务地址;传入时覆盖 config.ccc.DEEPSEEK_BASE_URL */
32
- baseURL?: string;
33
- /** API Key;传入时覆盖 config.ccc.DEEPSEEK_API_KEY */
34
- apiKey?: string;
35
- /** 模型名称;传入时覆盖 config.ccc.model */
36
- model?: string;
37
- }
38
-
39
- export interface ChatSessionOptions {
40
- /** 会话工作目录 */
41
- cwd?: string;
42
- /** 自定义系统提示词(会拼接到默认提示词之后) */
43
- systemPrompt?: string;
44
- }
45
-
46
- /**
47
- * 流式响应事件
48
- */
49
- export type ChatEvent =
50
- | { type: "text"; text: string; accumulated: string }
51
- | { type: "done"; text: string }
52
- | { type: "error"; message: string };
53
-
54
- // ---------------------------------------------------------------------------
55
- // ChatSession
56
- // ---------------------------------------------------------------------------
57
-
58
- /** 消息角色 */
59
- type MessageRole = "system" | "user" | "assistant" | "tool";
60
-
61
- /** 内部消息类型 */
62
- interface ChatMessage {
63
- role: MessageRole;
64
- content: string;
65
- }
66
-
67
- export class ChatSession {
68
- private model: any;
69
- private messages: ChatMessage[];
70
-
71
- constructor(
72
- overrides: ChatSessionConfig = {},
73
- options: ChatSessionOptions = {},
74
- ) {
75
- const apiKey = overrides.apiKey ?? appConfig.ccc.DEEPSEEK_API_KEY;
76
- if (!apiKey) {
77
- throw new Error(
78
- "ccc.DEEPSEEK_API_KEY 未设置。请在 config.json 中配置,或通过 --api-key 临时传入",
79
- );
80
- }
81
-
82
- const baseURL = overrides.baseURL ?? appConfig.ccc.DEEPSEEK_BASE_URL;
83
- const modelId = overrides.model ?? appConfig.ccc.model;
84
-
85
- const provider = createOpenAICompatible({
86
- name: "deepseek",
87
- baseURL,
88
- apiKey,
89
- });
90
- this.model = provider(modelId);
91
-
92
- // 构建系统提示词
93
- const systemContent = [SYSTEM_PROMPT];
94
- if (options?.systemPrompt) {
95
- systemContent.push("", options.systemPrompt);
96
- }
97
- if (options?.cwd) {
98
- systemContent.push("", `当前工作目录: ${options.cwd}`);
99
- }
100
-
101
- this.messages = [{ role: "system", content: systemContent.join("\n") }];
102
- }
103
-
104
- /**
105
- * 发送用户消息,返回异步可迭代的文本流。
106
- *
107
- * 使用方式:
108
- * ```typescript
109
- * const session = new ChatSession();
110
- * for await (const event of session.chat("帮我看看 package.json")) {
111
- * if (event.type === "text") process.stdout.write(event.text);
112
- * }
113
- * console.log("完成");
114
- * ```
115
- */
116
- async *chat(
117
- userMessage: string,
118
- signal?: AbortSignal,
119
- ): AsyncIterable<ChatEvent> {
120
- this.messages.push({ role: "user", content: userMessage });
121
-
122
- let fullText = "";
123
-
124
- try {
125
- const result = streamText({
126
- model: this.model,
127
- messages: this.messages as any,
128
- abortSignal: signal,
129
- });
130
-
131
- for await (const chunk of result.textStream) {
132
- fullText += chunk;
133
- yield { type: "text", text: chunk, accumulated: fullText };
134
- }
135
-
136
- this.messages.push({ role: "assistant", content: fullText });
137
- yield { type: "done", text: fullText };
138
- } catch (err) {
139
- const message = err instanceof Error ? err.message : String(err);
140
- if ((err as Error).name === "AbortError" || signal?.aborted) {
141
- // 被中断时,不保存不完整的助手消息
142
- if (fullText) {
143
- this.messages.push({ role: "assistant", content: fullText + "\n[已中断]" });
144
- }
145
- yield { type: "done", text: fullText };
146
- return;
147
- }
148
- yield { type: "error", message };
149
- throw err;
150
- }
151
- }
152
-
153
- /** 返回当前的会话历史(只读) */
154
- get history(): ReadonlyArray<ChatMessage> {
155
- return this.messages;
156
- }
157
-
158
- /** 返回当前轮数(不含 system 消息) */
159
- get turnCount(): number {
160
- return this.messages.length - 1;
161
- }
162
-
163
- /** 清空会话历史,保留 system 消息 */
164
- reset(): void {
165
- const system = this.messages[0];
166
- this.messages = [system];
167
- }
168
- }
1
+ /**
2
+ * builtin/index.ts — ChatCCC 内置 Agent 核心 API
3
+ *
4
+ * ChatSession 是程序化入口,既可以被 CLI 调用,也可以被其他模块(如 ToolAdapter)调用。
5
+ */
6
+
7
+ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
8
+ import { streamText } from "ai";
9
+
10
+ import { config as appConfig } from "../config.ts";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // 系统提示词 — 编译期冻结常量
14
+ // ---------------------------------------------------------------------------
15
+
16
+ const SYSTEM_PROMPT = [
17
+ "你是 ChatCCC 内置 AI 编程助手,运行在终端环境中。",
18
+ "",
19
+ "## 基本规则",
20
+ "- 用中文回复,但代码、命令、文件名保持原文",
21
+ "- 优先给出直接可用的方案,而非长篇解释",
22
+ "- 如果用户的问题涉及代码,直接给出代码并说明用法",
23
+ "- 保持简洁,一次聚焦一个问题",
24
+ ].join("\n");
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // 类型定义
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export interface ChatSessionConfig {
31
+ /** DeepSeek API 兼容的服务地址;传入时覆盖 config.ccc.DEEPSEEK_BASE_URL */
32
+ baseURL?: string;
33
+ /** API Key;传入时覆盖 config.ccc.DEEPSEEK_API_KEY */
34
+ apiKey?: string;
35
+ /** 模型名称;传入时覆盖 config.ccc.model */
36
+ model?: string;
37
+ }
38
+
39
+ export interface ChatSessionOptions {
40
+ /** 会话工作目录 */
41
+ cwd?: string;
42
+ /** 自定义系统提示词(会拼接到默认提示词之后) */
43
+ systemPrompt?: string;
44
+ }
45
+
46
+ /**
47
+ * 流式响应事件
48
+ */
49
+ export type ChatEvent =
50
+ | { type: "text"; text: string; accumulated: string }
51
+ | { type: "done"; text: string }
52
+ | { type: "error"; message: string };
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // ChatSession
56
+ // ---------------------------------------------------------------------------
57
+
58
+ /** 消息角色 */
59
+ type MessageRole = "system" | "user" | "assistant" | "tool";
60
+
61
+ /** 内部消息类型 */
62
+ interface ChatMessage {
63
+ role: MessageRole;
64
+ content: string;
65
+ }
66
+
67
+ export class ChatSession {
68
+ private model: any;
69
+ private systemPrompt: string;
70
+ private messages: ChatMessage[];
71
+
72
+ constructor(
73
+ overrides: ChatSessionConfig = {},
74
+ options: ChatSessionOptions = {},
75
+ ) {
76
+ const apiKey = overrides.apiKey ?? appConfig.ccc.DEEPSEEK_API_KEY;
77
+ if (!apiKey) {
78
+ throw new Error(
79
+ "ccc.DEEPSEEK_API_KEY 未设置。请在 config.json 中配置,或通过 --api-key 临时传入",
80
+ );
81
+ }
82
+
83
+ const baseURL = overrides.baseURL ?? appConfig.ccc.DEEPSEEK_BASE_URL;
84
+ const modelId = overrides.model ?? appConfig.ccc.model;
85
+
86
+ const provider = createOpenAICompatible({
87
+ name: "deepseek",
88
+ baseURL,
89
+ apiKey,
90
+ });
91
+ this.model = provider(modelId);
92
+
93
+ // 构建系统提示词
94
+ const systemContent = [SYSTEM_PROMPT];
95
+ if (options?.systemPrompt) {
96
+ systemContent.push("", options.systemPrompt);
97
+ }
98
+ if (options?.cwd) {
99
+ systemContent.push("", `当前工作目录: ${options.cwd}`);
100
+ }
101
+
102
+ this.systemPrompt = systemContent.join("\n");
103
+ this.messages = [];
104
+ }
105
+
106
+ /**
107
+ * 发送用户消息,返回异步可迭代的文本流。
108
+ *
109
+ * 使用方式:
110
+ * ```typescript
111
+ * const session = new ChatSession();
112
+ * for await (const event of session.chat("帮我看看 package.json")) {
113
+ * if (event.type === "text") process.stdout.write(event.text);
114
+ * }
115
+ * console.log("完成");
116
+ * ```
117
+ */
118
+ async *chat(
119
+ userMessage: string,
120
+ signal?: AbortSignal,
121
+ ): AsyncIterable<ChatEvent> {
122
+ this.messages.push({ role: "user", content: userMessage });
123
+
124
+ let fullText = "";
125
+
126
+ try {
127
+ const result = streamText({
128
+ model: this.model,
129
+ system: this.systemPrompt,
130
+ messages: this.messages as any,
131
+ abortSignal: signal,
132
+ });
133
+
134
+ for await (const chunk of result.textStream) {
135
+ fullText += chunk;
136
+ yield { type: "text", text: chunk, accumulated: fullText };
137
+ }
138
+
139
+ this.messages.push({ role: "assistant", content: fullText });
140
+ yield { type: "done", text: fullText };
141
+ } catch (err) {
142
+ const message = err instanceof Error ? err.message : String(err);
143
+ if ((err as Error).name === "AbortError" || signal?.aborted) {
144
+ // 被中断时,不保存不完整的助手消息
145
+ if (fullText) {
146
+ this.messages.push({ role: "assistant", content: fullText + "\n[已中断]" });
147
+ }
148
+ yield { type: "done", text: fullText };
149
+ return;
150
+ }
151
+ yield { type: "error", message };
152
+ throw err;
153
+ }
154
+ }
155
+
156
+ /** 返回当前的会话历史(只读) */
157
+ get history(): ReadonlyArray<ChatMessage> {
158
+ return [{ role: "system", content: this.systemPrompt }, ...this.messages];
159
+ }
160
+
161
+ /** 返回当前轮数(不含 system 消息) */
162
+ get turnCount(): number {
163
+ return this.messages.length;
164
+ }
165
+
166
+ /** 清空会话历史,保留 system 消息 */
167
+ reset(): void {
168
+ this.messages = [];
169
+ }
170
+ }
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/周用量,以及查询/使用主动重置卡",
141
- "发送 **/restart** 重启 ChatCCC 进程",
142
- "发送 **/update** 更新并重启(仅 npm 全局安装可用)",
143
- ABD_HELP_LINE,
144
- ].join("\n");
140
+ "发送 **/usage** 查看 Codex 5h/周用量,以及查询/使用主动重置卡",
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" } },
@@ -410,11 +410,11 @@ export function buildQueueFullCard(): string {
410
410
  }
411
411
 
412
412
  // 状态卡片(带关闭按钮)
413
- export function buildStatusCard(statusText: string, template = "blue"): string {
414
- return JSON.stringify({
415
- config: { wide_screen_mode: true },
416
- header: { template, title: { content: "会话状态", tag: "plain_text" } },
417
- elements: [
413
+ export function buildStatusCard(statusText: string, template = "blue"): string {
414
+ return JSON.stringify({
415
+ config: { wide_screen_mode: true },
416
+ header: { template, title: { content: "会话状态", tag: "plain_text" } },
417
+ elements: [
418
418
  { tag: "div", text: { tag: "lark_md", content: statusText } },
419
419
  { tag: "hr" },
420
420
  {
@@ -426,82 +426,82 @@ export function buildStatusCard(statusText: string, template = "blue"): string {
426
426
  value: { action: "close" },
427
427
  }],
428
428
  },
429
- ],
430
- });
431
- }
432
-
433
- export function buildCodexUsageCard(content: string, resetCreditsAvailable: number | null): string {
434
- const elements: object[] = [
435
- { tag: "div", text: { tag: "lark_md", content } },
436
- ];
437
- if (resetCreditsAvailable !== null && resetCreditsAvailable > 0) {
438
- elements.push({ tag: "hr" });
439
- elements.push({
440
- tag: "action",
441
- actions: [{
442
- tag: "button",
443
- text: { tag: "plain_text", content: "发起重置" },
444
- type: "primary",
445
- value: { action: "codex_reset_request", availableCount: resetCreditsAvailable },
446
- }],
447
- });
448
- }
449
-
450
- return JSON.stringify({
451
- config: { wide_screen_mode: true },
452
- header: { template: "blue", title: { content: "Codex Usage", tag: "plain_text" } },
453
- elements,
454
- });
455
- }
456
-
457
- export function buildCodexResetConfirmCard(params: {
458
- availableCount: number;
459
- parentMessageId: string;
460
- requestId: string;
461
- }): string {
462
- const valueBase = {
463
- parentMessageId: params.parentMessageId,
464
- requestId: params.requestId,
465
- };
466
- return JSON.stringify({
467
- config: { wide_screen_mode: true },
468
- header: { template: "yellow", title: { content: "确认 Codex 主动重置", tag: "plain_text" } },
469
- elements: [
470
- {
471
- tag: "div",
472
- text: {
473
- tag: "lark_md",
474
- content: `当前可用主动重置次数:**${params.availableCount}**。\n\n确认后会消耗 1 次主动重置,并重置当前可重置的 Codex 用量窗口。`,
475
- },
476
- },
477
- { tag: "hr" },
478
- {
479
- tag: "action",
480
- actions: [
481
- {
482
- tag: "button",
483
- text: { tag: "plain_text", content: "是,发起重置" },
484
- type: "danger",
485
- value: { action: "codex_reset_confirm", decision: "yes", ...valueBase },
486
- },
487
- {
488
- tag: "button",
489
- text: { tag: "plain_text", content: "否" },
490
- type: "default",
491
- value: { action: "codex_reset_confirm", decision: "no", ...valueBase },
492
- },
493
- ],
494
- },
495
- ],
496
- });
497
- }
498
-
499
- /** 模型切换卡片(/model 命令,飞书专用,含切换按钮) */
500
- export function buildModelCard(
501
- currentModel: string,
502
- models: string[],
503
- tool?: string,
504
- ): string {
429
+ ],
430
+ });
431
+ }
432
+
433
+ export function buildCodexUsageCard(content: string, resetCreditsAvailable: number | null): string {
434
+ const elements: object[] = [
435
+ { tag: "div", text: { tag: "lark_md", content } },
436
+ ];
437
+ if (resetCreditsAvailable !== null && resetCreditsAvailable > 0) {
438
+ elements.push({ tag: "hr" });
439
+ elements.push({
440
+ tag: "action",
441
+ actions: [{
442
+ tag: "button",
443
+ text: { tag: "plain_text", content: "发起重置" },
444
+ type: "primary",
445
+ value: { action: "codex_reset_request", availableCount: resetCreditsAvailable },
446
+ }],
447
+ });
448
+ }
449
+
450
+ return JSON.stringify({
451
+ config: { wide_screen_mode: true },
452
+ header: { template: "blue", title: { content: "Codex Usage", tag: "plain_text" } },
453
+ elements,
454
+ });
455
+ }
456
+
457
+ export function buildCodexResetConfirmCard(params: {
458
+ availableCount: number;
459
+ parentMessageId: string;
460
+ requestId: string;
461
+ }): string {
462
+ const valueBase = {
463
+ parentMessageId: params.parentMessageId,
464
+ requestId: params.requestId,
465
+ };
466
+ return JSON.stringify({
467
+ config: { wide_screen_mode: true },
468
+ header: { template: "yellow", title: { content: "确认 Codex 主动重置", tag: "plain_text" } },
469
+ elements: [
470
+ {
471
+ tag: "div",
472
+ text: {
473
+ tag: "lark_md",
474
+ content: `当前可用主动重置次数:**${params.availableCount}**。\n\n确认后会消耗 1 次主动重置,并重置当前可重置的 Codex 用量窗口。`,
475
+ },
476
+ },
477
+ { tag: "hr" },
478
+ {
479
+ tag: "action",
480
+ actions: [
481
+ {
482
+ tag: "button",
483
+ text: { tag: "plain_text", content: "是,发起重置" },
484
+ type: "danger",
485
+ value: { action: "codex_reset_confirm", decision: "yes", ...valueBase },
486
+ },
487
+ {
488
+ tag: "button",
489
+ text: { tag: "plain_text", content: "否" },
490
+ type: "default",
491
+ value: { action: "codex_reset_confirm", decision: "no", ...valueBase },
492
+ },
493
+ ],
494
+ },
495
+ ],
496
+ });
497
+ }
498
+
499
+ /** 模型切换卡片(/model 命令,飞书专用,含切换按钮) */
500
+ export function buildModelCard(
501
+ currentModel: string,
502
+ models: string[],
503
+ tool?: string,
504
+ ): string {
505
505
  const toolLabel = tool ? ` (${tool})` : "";
506
506
  const currentLine = currentModel
507
507
  ? `**当前模型:** \`${currentModel}\``
@@ -535,48 +535,48 @@ export function buildModelCard(
535
535
  { tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
536
536
  { tag: "hr" },
537
537
  buildButtons(buttons),
538
- ],
539
- });
540
- }
541
-
542
- export function buildEffortCard(
543
- currentEffort: string,
544
- efforts: string[],
545
- tool?: string,
546
- ): string {
547
- const toolLabel = tool ? ` (${tool})` : "";
548
- const currentLine = currentEffort
549
- ? `**当前 effort:** \`${currentEffort}\``
550
- : "**当前 effort:** 未指定";
551
-
552
- const lines: string[] = [currentLine];
553
- if (efforts.length > 0) {
554
- lines.push("", "**可切换 effort**");
555
- for (const effort of efforts) {
556
- lines.push(`- \`${effort}\``);
557
- }
558
- lines.push("", "点击按钮切换 effort,或输入 `/effort clear` 恢复默认");
559
- } else {
560
- lines.push("", "当前 agent 不支持 effort 切换。");
561
- }
562
-
563
- const buttons: ButtonDef[] = [];
564
- for (const effort of efforts.slice(0, 20)) {
565
- buttons.push({
566
- text: `/effort ${effort}`,
567
- value: JSON.stringify({ cmd: `/effort ${effort}` }),
568
- type: "primary",
569
- });
570
- }
571
-
572
- return JSON.stringify({
573
- config: { wide_screen_mode: true },
574
- header: { template: "blue", title: { content: `Effort 切换${toolLabel}`, tag: "plain_text" } },
575
- elements: [
576
- { tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
577
- { tag: "hr" },
578
- buildButtons(buttons),
579
- ],
580
- });
581
- }
538
+ ],
539
+ });
540
+ }
541
+
542
+ export function buildEffortCard(
543
+ currentEffort: string,
544
+ efforts: string[],
545
+ tool?: string,
546
+ ): string {
547
+ const toolLabel = tool ? ` (${tool})` : "";
548
+ const currentLine = currentEffort
549
+ ? `**当前 effort:** \`${currentEffort}\``
550
+ : "**当前 effort:** 未指定";
551
+
552
+ const lines: string[] = [currentLine];
553
+ if (efforts.length > 0) {
554
+ lines.push("", "**可切换 effort**");
555
+ for (const effort of efforts) {
556
+ lines.push(`- \`${effort}\``);
557
+ }
558
+ lines.push("", "点击按钮切换 effort,或输入 `/effort clear` 恢复默认");
559
+ } else {
560
+ lines.push("", "当前 agent 不支持 effort 切换。");
561
+ }
562
+
563
+ const buttons: ButtonDef[] = [];
564
+ for (const effort of efforts.slice(0, 20)) {
565
+ buttons.push({
566
+ text: `/effort ${effort}`,
567
+ value: JSON.stringify({ cmd: `/effort ${effort}` }),
568
+ type: "primary",
569
+ });
570
+ }
571
+
572
+ return JSON.stringify({
573
+ config: { wide_screen_mode: true },
574
+ header: { template: "blue", title: { content: `Effort 切换${toolLabel}`, tag: "plain_text" } },
575
+ elements: [
576
+ { tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
577
+ { tag: "hr" },
578
+ buildButtons(buttons),
579
+ ],
580
+ });
581
+ }
582
582