@keyou007/agent-bot 0.1.0

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 (212) hide show
  1. package/.env.example +8 -0
  2. package/CHANGELOG.md +11 -0
  3. package/LICENSE +675 -0
  4. package/README.md +187 -0
  5. package/README.zh.md +187 -0
  6. package/SECURITY.md +13 -0
  7. package/config.example.yaml +56 -0
  8. package/dist/acp/AcpJsonRpcConnection.js +155 -0
  9. package/dist/acp/AcpJsonRpcConnection.js.map +1 -0
  10. package/dist/acp/AcpProcessManager.js +65 -0
  11. package/dist/acp/AcpProcessManager.js.map +1 -0
  12. package/dist/acp/AcpSessionManager.js +164 -0
  13. package/dist/acp/AcpSessionManager.js.map +1 -0
  14. package/dist/acp/acpTypes.js +10 -0
  15. package/dist/acp/acpTypes.js.map +1 -0
  16. package/dist/cli/FeishuAppConfiguration.js +349 -0
  17. package/dist/cli/FeishuAppConfiguration.js.map +1 -0
  18. package/dist/cli/FeishuAppRegistration.js +160 -0
  19. package/dist/cli/FeishuAppRegistration.js.map +1 -0
  20. package/dist/cli/Initializer.js +112 -0
  21. package/dist/cli/Initializer.js.map +1 -0
  22. package/dist/cli/LocalControlClient.js +47 -0
  23. package/dist/cli/LocalControlClient.js.map +1 -0
  24. package/dist/cli/LocalControlServer.js +66 -0
  25. package/dist/cli/LocalControlServer.js.map +1 -0
  26. package/dist/cli/SkillRegistry.js +126 -0
  27. package/dist/cli/SkillRegistry.js.map +1 -0
  28. package/dist/cli/controlProtocol.js +10 -0
  29. package/dist/cli/controlProtocol.js.map +1 -0
  30. package/dist/cli/taskChatRoute.js +23 -0
  31. package/dist/cli/taskChatRoute.js.map +1 -0
  32. package/dist/cli.js +616 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/codex/AppServerConnection.js +131 -0
  35. package/dist/codex/AppServerConnection.js.map +1 -0
  36. package/dist/codex/CodexEventMapper.js +296 -0
  37. package/dist/codex/CodexEventMapper.js.map +1 -0
  38. package/dist/codex/CodexLocalActivityDetector.js +124 -0
  39. package/dist/codex/CodexLocalActivityDetector.js.map +1 -0
  40. package/dist/codex/CodexProcessManager.js +64 -0
  41. package/dist/codex/CodexProcessManager.js.map +1 -0
  42. package/dist/codex/CodexRuntime.js +825 -0
  43. package/dist/codex/CodexRuntime.js.map +1 -0
  44. package/dist/codex/ProjectlessWorkspace.js +74 -0
  45. package/dist/codex/ProjectlessWorkspace.js.map +1 -0
  46. package/dist/codex/protocol.js +10 -0
  47. package/dist/codex/protocol.js.map +1 -0
  48. package/dist/commands/CommandRouter.js +176 -0
  49. package/dist/commands/CommandRouter.js.map +1 -0
  50. package/dist/commands/commandTypes.js +2 -0
  51. package/dist/commands/commandTypes.js.map +1 -0
  52. package/dist/config/loadConfig.js +107 -0
  53. package/dist/config/loadConfig.js.map +1 -0
  54. package/dist/config/paths.js +32 -0
  55. package/dist/config/paths.js.map +1 -0
  56. package/dist/config/schema.js +38 -0
  57. package/dist/config/schema.js.map +1 -0
  58. package/dist/console/ConsoleConnector.js +39 -0
  59. package/dist/console/ConsoleConnector.js.map +1 -0
  60. package/dist/console/ConsoleTurnPresenter.js +60 -0
  61. package/dist/console/ConsoleTurnPresenter.js.map +1 -0
  62. package/dist/feishu/CardRenderer.js +972 -0
  63. package/dist/feishu/CardRenderer.js.map +1 -0
  64. package/dist/feishu/CardUpdateScheduler.js +148 -0
  65. package/dist/feishu/CardUpdateScheduler.js.map +1 -0
  66. package/dist/feishu/ConsoleFeishuClient.js +18 -0
  67. package/dist/feishu/ConsoleFeishuClient.js.map +1 -0
  68. package/dist/feishu/FeishuConnector.js +257 -0
  69. package/dist/feishu/FeishuConnector.js.map +1 -0
  70. package/dist/feishu/FeishuMarkdown.js +37 -0
  71. package/dist/feishu/FeishuMarkdown.js.map +1 -0
  72. package/dist/feishu/FeishuMessageClient.js +459 -0
  73. package/dist/feishu/FeishuMessageClient.js.map +1 -0
  74. package/dist/feishu/FeishuTurnPresenter.js +425 -0
  75. package/dist/feishu/FeishuTurnPresenter.js.map +1 -0
  76. package/dist/feishu/GroupAvatarGenerator.js +86 -0
  77. package/dist/feishu/GroupAvatarGenerator.js.map +1 -0
  78. package/dist/feishu/LocalCardImage.js +13 -0
  79. package/dist/feishu/LocalCardImage.js.map +1 -0
  80. package/dist/feishu/LocalImageMarkdown.js +89 -0
  81. package/dist/feishu/LocalImageMarkdown.js.map +1 -0
  82. package/dist/feishu/contextKey.js +12 -0
  83. package/dist/feishu/contextKey.js.map +1 -0
  84. package/dist/feishu/transport.js +14 -0
  85. package/dist/feishu/transport.js.map +1 -0
  86. package/dist/feishu/types.js +2 -0
  87. package/dist/feishu/types.js.map +1 -0
  88. package/dist/index.js +213 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/logging/logger.js +22 -0
  91. package/dist/logging/logger.js.map +1 -0
  92. package/dist/presentation/OutboundRouter.js +127 -0
  93. package/dist/presentation/OutboundRouter.js.map +1 -0
  94. package/dist/presentation/TurnStateReducer.js +307 -0
  95. package/dist/presentation/TurnStateReducer.js.map +1 -0
  96. package/dist/presentation/splitMarkdown.js +341 -0
  97. package/dist/presentation/splitMarkdown.js.map +1 -0
  98. package/dist/presentation/turnViewTypes.js +2 -0
  99. package/dist/presentation/turnViewTypes.js.map +1 -0
  100. package/dist/proxy/ProxySessionController.js +2819 -0
  101. package/dist/proxy/ProxySessionController.js.map +1 -0
  102. package/dist/runtime/AcpRuntimeAdapter.js +271 -0
  103. package/dist/runtime/AcpRuntimeAdapter.js.map +1 -0
  104. package/dist/runtime/AgentRuntimeRegistry.js +18 -0
  105. package/dist/runtime/AgentRuntimeRegistry.js.map +1 -0
  106. package/dist/runtime/agentEnvironment.js +11 -0
  107. package/dist/runtime/agentEnvironment.js.map +1 -0
  108. package/dist/runtime/types.js +2 -0
  109. package/dist/runtime/types.js.map +1 -0
  110. package/dist/startup/SessionMetadataHydrator.js +46 -0
  111. package/dist/startup/SessionMetadataHydrator.js.map +1 -0
  112. package/dist/startup/StartupNotifier.js +78 -0
  113. package/dist/startup/StartupNotifier.js.map +1 -0
  114. package/dist/startup/startFeishu.js +6 -0
  115. package/dist/startup/startFeishu.js.map +1 -0
  116. package/dist/state/StateStore.js +893 -0
  117. package/dist/state/StateStore.js.map +1 -0
  118. package/dist/state/migrations.js +129 -0
  119. package/dist/state/migrations.js.map +1 -0
  120. package/dist/supervision/SafeRestartNotifier.js +116 -0
  121. package/dist/supervision/SafeRestartNotifier.js.map +1 -0
  122. package/dist/supervision/SafeRestartScheduler.js +123 -0
  123. package/dist/supervision/SafeRestartScheduler.js.map +1 -0
  124. package/dist/supervision/replacementSupervisor.js +8 -0
  125. package/dist/supervision/replacementSupervisor.js.map +1 -0
  126. package/dist/supervision/restartPolicy.js +16 -0
  127. package/dist/supervision/restartPolicy.js.map +1 -0
  128. package/dist/supervisor.js +94 -0
  129. package/dist/supervisor.js.map +1 -0
  130. package/dist/utils/executeShellCommand.js +76 -0
  131. package/dist/utils/executeShellCommand.js.map +1 -0
  132. package/dist/utils/id.js +5 -0
  133. package/dist/utils/id.js.map +1 -0
  134. package/dist/utils/markdown.js +29 -0
  135. package/dist/utils/markdown.js.map +1 -0
  136. package/dist/utils/spawnCommand.js +21 -0
  137. package/dist/utils/spawnCommand.js.map +1 -0
  138. package/dist/utils/taskTitle.js +10 -0
  139. package/dist/utils/taskTitle.js.map +1 -0
  140. package/docs/technical-reference.md +294 -0
  141. package/docs/technical-reference.zh.md +294 -0
  142. package/npm-shrinkwrap.json +4525 -0
  143. package/package.json +87 -0
  144. package/skills/agent-bot/SKILL.md +141 -0
  145. package/skills/agent-bot/agents/openai.yaml +4 -0
  146. package/src/acp/AcpJsonRpcConnection.ts +212 -0
  147. package/src/acp/AcpProcessManager.ts +87 -0
  148. package/src/acp/AcpSessionManager.ts +227 -0
  149. package/src/acp/acpTypes.ts +99 -0
  150. package/src/cli/FeishuAppConfiguration.ts +473 -0
  151. package/src/cli/FeishuAppRegistration.ts +207 -0
  152. package/src/cli/Initializer.ts +159 -0
  153. package/src/cli/LocalControlClient.ts +48 -0
  154. package/src/cli/LocalControlServer.ts +67 -0
  155. package/src/cli/SkillRegistry.ts +151 -0
  156. package/src/cli/controlProtocol.ts +34 -0
  157. package/src/cli/taskChatRoute.ts +31 -0
  158. package/src/cli.ts +630 -0
  159. package/src/codex/AppServerConnection.ts +159 -0
  160. package/src/codex/CodexEventMapper.ts +351 -0
  161. package/src/codex/CodexLocalActivityDetector.ts +148 -0
  162. package/src/codex/CodexProcessManager.ts +63 -0
  163. package/src/codex/CodexRuntime.ts +1025 -0
  164. package/src/codex/ProjectlessWorkspace.ts +90 -0
  165. package/src/codex/protocol.ts +37 -0
  166. package/src/commands/CommandRouter.ts +179 -0
  167. package/src/commands/commandTypes.ts +28 -0
  168. package/src/config/loadConfig.ts +115 -0
  169. package/src/config/paths.ts +37 -0
  170. package/src/config/schema.ts +46 -0
  171. package/src/console/ConsoleConnector.ts +40 -0
  172. package/src/console/ConsoleTurnPresenter.ts +64 -0
  173. package/src/feishu/CardRenderer.ts +1157 -0
  174. package/src/feishu/CardUpdateScheduler.ts +165 -0
  175. package/src/feishu/ConsoleFeishuClient.ts +22 -0
  176. package/src/feishu/FeishuConnector.ts +277 -0
  177. package/src/feishu/FeishuMarkdown.ts +47 -0
  178. package/src/feishu/FeishuMessageClient.ts +649 -0
  179. package/src/feishu/FeishuTurnPresenter.ts +539 -0
  180. package/src/feishu/GroupAvatarGenerator.ts +102 -0
  181. package/src/feishu/LocalCardImage.ts +13 -0
  182. package/src/feishu/LocalImageMarkdown.ts +92 -0
  183. package/src/feishu/contextKey.ts +14 -0
  184. package/src/feishu/transport.ts +24 -0
  185. package/src/feishu/types.ts +83 -0
  186. package/src/index.ts +228 -0
  187. package/src/logging/logger.ts +28 -0
  188. package/src/presentation/OutboundRouter.ts +205 -0
  189. package/src/presentation/TurnStateReducer.ts +341 -0
  190. package/src/presentation/splitMarkdown.ts +358 -0
  191. package/src/presentation/turnViewTypes.ts +54 -0
  192. package/src/proxy/ProxySessionController.ts +3311 -0
  193. package/src/runtime/AcpRuntimeAdapter.ts +324 -0
  194. package/src/runtime/AgentRuntimeRegistry.ts +20 -0
  195. package/src/runtime/agentEnvironment.ts +14 -0
  196. package/src/runtime/types.ts +215 -0
  197. package/src/startup/SessionMetadataHydrator.ts +46 -0
  198. package/src/startup/StartupNotifier.ts +92 -0
  199. package/src/startup/startFeishu.ts +19 -0
  200. package/src/state/StateStore.ts +1249 -0
  201. package/src/state/migrations.ts +128 -0
  202. package/src/supervision/SafeRestartNotifier.ts +122 -0
  203. package/src/supervision/SafeRestartScheduler.ts +149 -0
  204. package/src/supervision/replacementSupervisor.ts +10 -0
  205. package/src/supervision/restartPolicy.ts +19 -0
  206. package/src/supervisor.ts +106 -0
  207. package/src/utils/executeShellCommand.ts +97 -0
  208. package/src/utils/id.ts +5 -0
  209. package/src/utils/markdown.ts +25 -0
  210. package/src/utils/spawnCommand.ts +35 -0
  211. package/src/utils/taskTitle.ts +9 -0
  212. package/tsconfig.json +19 -0
@@ -0,0 +1,972 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ import { truncateMiddle, truncateText } from "../utils/markdown.js";
4
+ import { localCardImage } from "./LocalCardImage.js";
5
+ export class CardRenderer {
6
+ renderReasoningSelector(view) {
7
+ const elements = [
8
+ markdown([
9
+ `**模型**:${inlineCode(view.model)}`,
10
+ `**当前思考模式**:${inlineCode(view.currentEffort ?? "默认")}`,
11
+ ...(view.notice ? [view.notice] : []),
12
+ ].join("\n")),
13
+ { tag: "hr" },
14
+ ];
15
+ if (view.options.length === 0) {
16
+ elements.push(markdown("该模型没有可配置的思考模式。"));
17
+ }
18
+ else {
19
+ elements.push(...view.options.map((option) => {
20
+ const isCurrent = option.value === view.currentEffort;
21
+ return {
22
+ tag: "column_set",
23
+ flex_mode: "none",
24
+ horizontal_spacing: "8px",
25
+ vertical_align: "center",
26
+ columns: [
27
+ {
28
+ tag: "column",
29
+ width: "weighted",
30
+ weight: 1,
31
+ vertical_align: "center",
32
+ elements: [markdown(inlineCode(option.value))],
33
+ },
34
+ {
35
+ tag: "column",
36
+ width: "auto",
37
+ vertical_align: "center",
38
+ elements: isCurrent
39
+ ? [markdown("✅ 当前")]
40
+ : [taskActionElement({
41
+ text: "Switch",
42
+ value: {
43
+ action: "reasoning_select",
44
+ sessionId: view.sessionId,
45
+ contextKey: view.contextKey,
46
+ model: view.model,
47
+ effort: option.value,
48
+ },
49
+ })],
50
+ },
51
+ ],
52
+ };
53
+ }));
54
+ }
55
+ elements.push({ tag: "hr" }, taskActionRow([{
56
+ text: "Back",
57
+ value: {
58
+ action: "model_open",
59
+ sessionId: view.sessionId,
60
+ contextKey: view.contextKey,
61
+ },
62
+ }]));
63
+ return sectionCard("思考模式", elements, view.notice ? "green" : "blue");
64
+ }
65
+ renderModelSelector(view) {
66
+ const elements = [
67
+ markdown([
68
+ `**当前模型**:${inlineCode(view.currentModel ?? "默认")}`,
69
+ `**思考强度**:${inlineCode(view.reasoningEffort ?? "默认")}`,
70
+ ...(view.notice ? [view.notice] : []),
71
+ ].join("\n")),
72
+ { tag: "hr" },
73
+ ];
74
+ if (view.models.length === 0) {
75
+ elements.push(markdown("当前运行时未返回可用模型。"));
76
+ }
77
+ else {
78
+ elements.push(...view.models.map((model) => {
79
+ const isCurrent = model.id === view.currentModel;
80
+ return {
81
+ tag: "column_set",
82
+ flex_mode: "none",
83
+ horizontal_spacing: "8px",
84
+ vertical_align: "center",
85
+ columns: [
86
+ {
87
+ tag: "column",
88
+ width: "weighted",
89
+ weight: 1,
90
+ vertical_align: "center",
91
+ elements: [markdown(`${inlineCode(model.id)}${model.isDefault ? " · 默认" : ""}`)],
92
+ },
93
+ {
94
+ tag: "column",
95
+ width: "auto",
96
+ vertical_align: "center",
97
+ elements: isCurrent
98
+ ? [markdown("✅ 当前")]
99
+ : [taskActionElement({
100
+ text: "Switch",
101
+ value: {
102
+ action: "model_select",
103
+ sessionId: view.sessionId,
104
+ contextKey: view.contextKey,
105
+ model: model.id,
106
+ },
107
+ })],
108
+ },
109
+ ],
110
+ };
111
+ }));
112
+ }
113
+ return sectionCard("模型", elements, view.notice ? "green" : "blue");
114
+ }
115
+ renderPromptQueue(view) {
116
+ const elements = view.prompts.length === 0
117
+ ? [markdown("队列为空")]
118
+ : view.prompts.map((prompt, index) => ({
119
+ tag: "column_set",
120
+ flex_mode: "stretch",
121
+ horizontal_spacing: "8px",
122
+ vertical_align: "center",
123
+ columns: [
124
+ {
125
+ tag: "column",
126
+ width: "weighted",
127
+ weight: 1,
128
+ vertical_align: "center",
129
+ elements: [markdown(escapeCardHtml(`${index + 1}. ${truncateText(prompt.text.replace(/\s+/g, " ").trim(), 180)}`))],
130
+ },
131
+ {
132
+ tag: "column",
133
+ width: "auto",
134
+ vertical_align: "center",
135
+ elements: [{
136
+ tag: "button",
137
+ text: { tag: "plain_text", content: "Cancel" },
138
+ type: "default",
139
+ size: "tiny",
140
+ behaviors: [{
141
+ type: "callback",
142
+ value: {
143
+ action: "queued_prompt_cancel",
144
+ promptId: prompt.id,
145
+ sessionId: view.sessionId,
146
+ contextKey: view.contextKey,
147
+ },
148
+ }],
149
+ }],
150
+ },
151
+ ],
152
+ }));
153
+ return {
154
+ schema: "2.0",
155
+ config: {
156
+ update_multi: true,
157
+ width_mode: "fill",
158
+ },
159
+ header: {
160
+ template: "grey",
161
+ title: { tag: "plain_text", content: `排队 Prompt · ${view.prompts.length}` },
162
+ padding: "8px 12px 8px 12px",
163
+ },
164
+ body: {
165
+ direction: "vertical",
166
+ vertical_spacing: "4px",
167
+ padding: "8px 12px 8px 12px",
168
+ elements,
169
+ },
170
+ };
171
+ }
172
+ renderSafeRestartStatus(view) {
173
+ const countdown = view.phase === "countdown"
174
+ ? `${Math.max(0, Math.ceil((view.remainingMs ?? 0) / 1_000))}s`
175
+ : view.phase === "restarting"
176
+ ? "0s"
177
+ : "等待阻塞项清空后开始";
178
+ const status = view.phase === "waiting_tasks"
179
+ ? "🟠 等待任务完成"
180
+ : view.phase === "waiting_delivery"
181
+ ? "🟠 等待最终结果投递"
182
+ : view.phase === "countdown"
183
+ ? "🟡 空闲确认中"
184
+ : "🔄 正在重启";
185
+ const lines = [
186
+ `**状态**:${status}`,
187
+ `**重启原因**:${inlineCode(view.reason)}`,
188
+ `**重启倒计时**:${countdown}`,
189
+ `**待投递结果**:${view.pendingFinalDeliveries} 条`,
190
+ ];
191
+ const elements = [markdown(lines.join("\n"))];
192
+ if (view.waitingTasks.length > 0) {
193
+ const visible = view.waitingTasks.slice(0, 10);
194
+ const taskLines = visible.map((task, index) => `${index + 1}. ${task.title ? `${inlineCode(truncateText(task.title, 80))} · ` : ""}${inlineCode(task.id)}`);
195
+ if (view.waitingTasks.length > visible.length) {
196
+ taskLines.push(`… 还有 ${view.waitingTasks.length - visible.length} 个任务`);
197
+ }
198
+ elements.push({ tag: "hr" }, markdown(`**当前等待的任务(${view.waitingTasks.length})**\n${taskLines.join("\n")}`));
199
+ }
200
+ else {
201
+ elements.push({ tag: "hr" }, markdown("**当前等待的任务**:无"));
202
+ }
203
+ return sectionCard("Agent Bot 安全重启", elements, view.phase === "restarting" ? "blue" : "orange");
204
+ }
205
+ renderStartupStatus(view) {
206
+ const workspaceLine = view.workspaceKind === "projectless"
207
+ ? "**任务范围**:未指定项目"
208
+ : `**工作目录**:${inlineCode(view.cwd)}`;
209
+ const lines = view.currentTask
210
+ ? [
211
+ `**当前任务**:${inlineCode(view.currentTask.title ?? view.currentTask.id)}`,
212
+ workspaceLine,
213
+ `**模型 / 思考强度 / 权限**:${inlineCode(view.currentTask.model ?? "默认")} / ${inlineCode(view.currentTask.reasoningEffort ?? "自动")} / ${inlineCode(permissionModeLabel(view.currentTask.permissionMode))}`,
214
+ `**任务状态 / Agent**:${persistedTaskStatus(view.currentTask.sessionStatus, view.currentTask.lastTurnStatus)} / ${inlineCode(view.currentTask.agentName)}`,
215
+ `**任务 ID**:${inlineCode(view.currentTask.id)}`,
216
+ ]
217
+ : [
218
+ "**当前任务**:无,下一条普通消息会创建新任务",
219
+ workspaceLine,
220
+ `**模型 / 思考强度 / 权限**:${inlineCode("默认")} / ${inlineCode("自动")} / ${inlineCode(permissionModeLabel())}`,
221
+ `**默认 Agent**:${view.defaultAgentTitle} (${inlineCode(view.defaultAgentName)})`,
222
+ ];
223
+ lines.push(`**服务状态 / 启动时间**:🟢 在线 / ${formatStartupTime(view.startedAt)}`, `**重启原因**:${inlineCode(view.restartReason)}`, "> 发送消息即可开始对话;发送 `/new` 创建新任务;发送 `/help` 查看帮助。");
224
+ return sectionCard("Agent Bot 已启动", [markdown(lines.join("\n"))], "green");
225
+ }
226
+ renderTurn(state) {
227
+ const elements = renderTurnElements(state, "hidden");
228
+ if (isTurnStoppable(state.status)) {
229
+ elements.push({ tag: "hr" }, taskActionRow([{
230
+ text: "Stop",
231
+ type: "danger",
232
+ value: { action: "turn_cancel", sessionId: state.sessionId, turnId: state.turnId },
233
+ }]));
234
+ }
235
+ return turnCard(turnTitle(state.status, state.prompt ?? state.taskTitle), turnTemplate(state.status), elements, renderTurnSubtitle(state));
236
+ }
237
+ renderTurnDetails(state) {
238
+ const title = state.taskTitle
239
+ ? `Codex 执行详情:${truncateText(state.taskTitle.replace(/\s+/g, " ").trim(), 60)}`
240
+ : "Codex 执行详情";
241
+ return turnCard(title, "blue", renderTurnElements(state, "always"), renderTurnSubtitle(state));
242
+ }
243
+ renderActivityHistory(state, requestedPage) {
244
+ const pages = activityPages(turnActivities(state));
245
+ if (pages.length === 0) {
246
+ return this.renderSectionsCard("思考活动历史", [{ lines: ["没有保存到活动记录。"] }]);
247
+ }
248
+ const page = Math.max(0, Math.min(Math.trunc(requestedPage), pages.length - 1));
249
+ const actions = [
250
+ ...(pages.length > 1 ? [{
251
+ text: "最新页",
252
+ value: { action: "activity_history", turnId: state.turnId, page: "latest" },
253
+ }] : []),
254
+ ...(page > 0 ? [{
255
+ text: "上一页",
256
+ value: { action: "activity_history", turnId: state.turnId, page: String(page - 1) },
257
+ }] : []),
258
+ ...(page < pages.length - 2 ? [{
259
+ text: "下一页",
260
+ value: { action: "activity_history", turnId: state.turnId, page: String(page + 1) },
261
+ }] : []),
262
+ ];
263
+ const elements = [];
264
+ if (actions.length > 0)
265
+ elements.push(taskActionRow(actions), { tag: "hr" });
266
+ elements.push(...renderActivities(pages[page] ?? [], state.projectCwd, true));
267
+ return sectionCard(`思考活动历史 · ${page + 1}/${pages.length}`, elements.length > 0 ? elements : [markdown("无")]);
268
+ }
269
+ renderSessionStarted(session) {
270
+ return this.baseCard("ACP 会话已创建", "green", [
271
+ markdown(`**Agent**: ${session.agentName}\n**Session**: ${session.localSessionId}\n**CWD**: ${session.cwd}`),
272
+ ]);
273
+ }
274
+ renderSessionUpdate(session, update) {
275
+ const updateType = String(update.sessionUpdate ?? "update");
276
+ return this.baseCard(`ACP 更新:${updateType}`, "blue", [
277
+ markdown(`**Agent**: ${session.agentName}\n**Session**: ${session.localSessionId}`),
278
+ markdown(truncateText(formatUpdate(update), 6000)),
279
+ ]);
280
+ }
281
+ renderPermissionRequest(session, permissionId, toolTitle, options) {
282
+ return this.baseCard("需要确认", "orange", [
283
+ markdown(`**Session**: ${session.localSessionId}\n**Tool**: ${toolTitle}`),
284
+ {
285
+ tag: "action",
286
+ actions: options.map((option) => ({
287
+ tag: "button",
288
+ text: {
289
+ tag: "plain_text",
290
+ content: acpPermissionOptionLabel(option),
291
+ },
292
+ type: option.kind.startsWith("allow") ? "primary" : "default",
293
+ value: {
294
+ action: "permission",
295
+ permissionId,
296
+ optionId: option.optionId,
297
+ },
298
+ })),
299
+ },
300
+ ]);
301
+ }
302
+ renderStatus(status) {
303
+ return this.baseCard("Agent Bot 状态", "blue", [markdown(status)]);
304
+ }
305
+ renderSectionsCard(title, sections, actions = []) {
306
+ const elements = [];
307
+ sections.forEach((section, index) => {
308
+ if (index > 0)
309
+ elements.push({ tag: "hr" });
310
+ const content = section.lines.join("\n");
311
+ if (section.collapsible && section.title) {
312
+ elements.push(collapsiblePanel(section.title, content, { elementId: section.elementId }));
313
+ }
314
+ else {
315
+ const heading = section.title ? `**${section.title}**\n` : "";
316
+ elements.push(markdown(`${heading}${content}`));
317
+ }
318
+ });
319
+ if (actions.length > 0)
320
+ elements.push({ tag: "hr" }, taskActionRow(actions));
321
+ return sectionCard(title, elements);
322
+ }
323
+ renderTaskListCard(title, sectionTitle, entries, footerLines, footerAction) {
324
+ const elements = [markdown(`**${sectionTitle}**`)];
325
+ if (entries.length === 0) {
326
+ elements.push(markdown("无"));
327
+ }
328
+ else {
329
+ entries.forEach((entry, index) => {
330
+ elements.push(markdown(entry.lines.join("\n")));
331
+ if (entry.actions?.length) {
332
+ elements.push(taskActionRow(entry.actions));
333
+ }
334
+ if (index < entries.length - 1)
335
+ elements.push({ tag: "hr" });
336
+ });
337
+ }
338
+ if (footerLines.length > 0) {
339
+ elements.push({ tag: "hr" }, markdown(footerLines.join("\n")));
340
+ }
341
+ if (footerAction) {
342
+ elements.push({
343
+ tag: "column_set",
344
+ flex_mode: "none",
345
+ columns: [{
346
+ tag: "column",
347
+ width: "weighted",
348
+ weight: 1,
349
+ elements: [{
350
+ tag: "button",
351
+ text: { tag: "plain_text", content: footerAction.text },
352
+ type: footerAction.type ?? "default",
353
+ width: "fill",
354
+ behaviors: [{
355
+ type: "callback",
356
+ value: footerAction.value,
357
+ }],
358
+ }],
359
+ }],
360
+ });
361
+ }
362
+ return {
363
+ schema: "2.0",
364
+ config: {
365
+ update_multi: true,
366
+ width_mode: "fill",
367
+ },
368
+ header: {
369
+ template: "blue",
370
+ title: {
371
+ tag: "plain_text",
372
+ content: title,
373
+ },
374
+ },
375
+ body: {
376
+ elements,
377
+ },
378
+ };
379
+ }
380
+ baseCard(title, template, elements) {
381
+ return {
382
+ config: {
383
+ wide_screen_mode: true,
384
+ update_multi: true,
385
+ },
386
+ header: {
387
+ template,
388
+ title: {
389
+ tag: "plain_text",
390
+ content: title,
391
+ },
392
+ },
393
+ elements,
394
+ };
395
+ }
396
+ }
397
+ function renderTurnSubtitle(state) {
398
+ const elapsed = state.durationMs ?? Math.max(0, Date.now() - state.startedAt);
399
+ const activityTools = turnActivities(state).filter((activity) => activity.kind === "tool").length;
400
+ const totalTools = state.totalToolCount ?? activityTools;
401
+ return [
402
+ `耗时 ${formatTurnDuration(elapsed)}`,
403
+ state.totalTokens !== undefined ? `${formatTokenCount(state.totalTokens)} tokens` : undefined,
404
+ totalTools > 0 ? `${totalTools} 个工具` : undefined,
405
+ state.fileSummary.length > 0 ? `${state.fileSummary.length} 个文件` : undefined,
406
+ ].filter(Boolean).join(" · ");
407
+ }
408
+ function isTurnStoppable(status) {
409
+ return status === "running" || status === "tool_running" || status === "waiting_for_approval";
410
+ }
411
+ function renderTurnElements(state, assistantTextMode) {
412
+ const elements = [];
413
+ const allActivities = turnActivities(state);
414
+ const pages = activityPages(allActivities);
415
+ const visibleActivities = pages.at(-1) ?? [];
416
+ if (state.plan.length > 0)
417
+ elements.push(planPanel(state.plan));
418
+ if (pages.length > 1) {
419
+ elements.push(taskActionRow([{
420
+ text: `查看历史思考(共 ${pages.length} 页)`,
421
+ value: {
422
+ action: "activity_history",
423
+ turnId: state.turnId,
424
+ page: String(pages.length - 2),
425
+ },
426
+ }]));
427
+ }
428
+ if (state.activitiesTruncated || pages.length > 1)
429
+ elements.push(markdown("…"));
430
+ elements.push(...renderActivities(visibleActivities, state.projectCwd));
431
+ if (state.fileSummary.length > 0)
432
+ elements.push(fileSummaryPanel(state));
433
+ if (state.approval) {
434
+ const request = state.approval;
435
+ elements.push(markdown([
436
+ `**${request.title}**`,
437
+ request.command ? codeBlock(request.command, 800) : undefined,
438
+ request.reason,
439
+ ].filter(Boolean).join("\n")));
440
+ elements.push({
441
+ tag: "column_set",
442
+ flex_mode: "flow",
443
+ horizontal_spacing: "8px",
444
+ vertical_spacing: "8px",
445
+ columns: request.options.map((option) => ({
446
+ tag: "column",
447
+ width: "auto",
448
+ elements: [{
449
+ tag: "button",
450
+ text: { tag: "plain_text", content: approvalDecisionLabel(option.id) },
451
+ type: option.id === "accept" || option.id === "acceptForSession" ? "primary" : option.id === "cancel" ? "danger" : "default",
452
+ behaviors: [{
453
+ type: "callback",
454
+ value: {
455
+ action: "approval",
456
+ sessionId: state.sessionId,
457
+ turnId: state.turnId,
458
+ requestId: request.id,
459
+ decision: option.id,
460
+ },
461
+ }],
462
+ }],
463
+ })),
464
+ });
465
+ }
466
+ if (state.error)
467
+ elements.push(markdown(codeBlock(state.error, 2_000)));
468
+ const showAssistantText = state.assistantText && assistantTextMode === "always";
469
+ if (showAssistantText) {
470
+ if (elements.length > 0)
471
+ elements.push({ tag: "hr" });
472
+ const heading = state.status === "completed" ? "回答" : "回答生成中";
473
+ elements.push(markdown(`**${heading}**\n${truncateText(state.assistantText, 3_000)}`));
474
+ }
475
+ if (elements.length === 0)
476
+ elements.push(markdown(emptyTurnText(state.status)));
477
+ return elements;
478
+ }
479
+ function emptyTurnText(status) {
480
+ if (status === "starting")
481
+ return "正在连接 Codex…";
482
+ if (status === "completed")
483
+ return "本轮已完成。";
484
+ if (status === "cancelled")
485
+ return "本轮已停止。";
486
+ return "正在等待 Codex 返回进度…";
487
+ }
488
+ function planPanel(plan) {
489
+ const completed = plan.filter((step) => step.status === "completed").length;
490
+ return collapsiblePanel(`计划 · ${completed}/${plan.length}`, plan.map(renderPlanStep).join("\n"), {
491
+ expanded: true,
492
+ borderColor: "blue",
493
+ });
494
+ }
495
+ function fileSummaryPanel(state) {
496
+ return collapsiblePanel(`文件变更 · ${state.fileSummary.length}`, state.fileSummary
497
+ .map((file) => `- ${displayFilePath(file.path, state.projectCwd)} +${file.additions ?? 0} -${file.deletions ?? 0}`)
498
+ .join("\n"), { elementId: "turn_files" });
499
+ }
500
+ function renderPlanStep(step) {
501
+ const marker = step.status === "completed" ? "✅" : step.status === "in_progress" ? "🔄" : "○";
502
+ return `${marker} ${step.text}`;
503
+ }
504
+ function renderActivity(activity, projectCwd, fullAssistantText = false) {
505
+ if (activity.kind === "user") {
506
+ const text = activity.text.trim();
507
+ if (!text)
508
+ return [];
509
+ const chunks = fullAssistantText
510
+ ? splitText(text, ACTIVITY_TEXT_CHUNK)
511
+ : [truncateText(text, MAX_LIVE_ASSISTANT_TEXT)];
512
+ return chunks.map((chunk, index) => markdown(boldUserActivity(chunk, index === 0)));
513
+ }
514
+ if (activity.kind === "assistant" || (activity.kind === "reasoning" && activity.id.startsWith("commentary:"))) {
515
+ const text = activity.text.trim();
516
+ if (!text)
517
+ return [];
518
+ return fullAssistantText
519
+ ? splitText(text, ACTIVITY_TEXT_CHUNK).map(markdown)
520
+ : [markdown(truncateText(text, MAX_LIVE_ASSISTANT_TEXT))];
521
+ }
522
+ if (activity.kind === "reasoning") {
523
+ return renderReasoningGroup([activity]);
524
+ }
525
+ return [toolPanel(activity.tool, projectCwd)];
526
+ }
527
+ function boldUserActivity(text, showIcon) {
528
+ return text
529
+ .split("\n")
530
+ .map((line, index) => {
531
+ const content = showIcon && index === 0 ? `🙋 ${line}` : line;
532
+ return content ? `**${content}**` : "";
533
+ })
534
+ .join("\n");
535
+ }
536
+ function renderActivities(activities, projectCwd, fullAssistantText = false) {
537
+ const elements = [];
538
+ let reasoningGroup = [];
539
+ const flushReasoning = () => {
540
+ if (reasoningGroup.length === 0)
541
+ return;
542
+ elements.push(...renderReasoningGroup(reasoningGroup));
543
+ reasoningGroup = [];
544
+ };
545
+ for (const activity of activities) {
546
+ if (isRawReasoning(activity)) {
547
+ reasoningGroup.push(activity);
548
+ continue;
549
+ }
550
+ flushReasoning();
551
+ elements.push(...renderActivity(activity, projectCwd, fullAssistantText));
552
+ }
553
+ flushReasoning();
554
+ return elements;
555
+ }
556
+ function renderReasoningGroup(activities) {
557
+ const sections = activities
558
+ .map((activity) => activity.text.trim())
559
+ .filter(Boolean)
560
+ .map((text) => removeMarkdownBold(truncateText(text, 2_000)));
561
+ if (sections.length === 0)
562
+ return [];
563
+ const quotedReasoning = sections
564
+ .map((text) => `💭 ${text.replaceAll("\n", "\n> ")}`)
565
+ .join("\n> ");
566
+ return [markdown(`> ${quotedReasoning}`)];
567
+ }
568
+ function isRawReasoning(activity) {
569
+ return activity.kind === "reasoning" && !activity.id.startsWith("commentary:");
570
+ }
571
+ const ACTIVITIES_PER_PAGE = 40;
572
+ const MAX_LIVE_ASSISTANT_TEXT = 2_000;
573
+ const ACTIVITY_TEXT_CHUNK = 2_500;
574
+ function activityPages(activities) {
575
+ if (activities.length === 0)
576
+ return [];
577
+ if (activities.length <= ACTIVITIES_PER_PAGE)
578
+ return [activities];
579
+ const pages = [];
580
+ const firstPageSize = activities.length % ACTIVITIES_PER_PAGE || ACTIVITIES_PER_PAGE;
581
+ pages.push(activities.slice(0, firstPageSize));
582
+ for (let index = firstPageSize; index < activities.length; index += ACTIVITIES_PER_PAGE) {
583
+ pages.push(activities.slice(index, index + ACTIVITIES_PER_PAGE));
584
+ }
585
+ return pages;
586
+ }
587
+ function splitText(value, maxLength) {
588
+ if (!value)
589
+ return [];
590
+ const chunks = [];
591
+ for (let offset = 0; offset < value.length; offset += maxLength) {
592
+ chunks.push(value.slice(offset, offset + maxLength));
593
+ }
594
+ return chunks;
595
+ }
596
+ function removeMarkdownBold(value) {
597
+ return value
598
+ .replace(/\*\*([\s\S]+?)\*\*/g, "$1")
599
+ .replace(/__([\s\S]+?)__/g, "$1");
600
+ }
601
+ function turnActivities(state) {
602
+ if (state.activities?.length)
603
+ return state.activities;
604
+ const activities = [];
605
+ if (state.progressText) {
606
+ activities.push({ kind: "reasoning", id: "legacy-progress", text: state.progressText });
607
+ }
608
+ const tools = [state.activeTool, ...state.failedTools, ...state.completedTools].filter((tool) => tool !== undefined);
609
+ const seen = new Set();
610
+ for (const tool of tools) {
611
+ if (seen.has(tool.id))
612
+ continue;
613
+ seen.add(tool.id);
614
+ activities.push({ kind: "tool", id: tool.id, tool });
615
+ }
616
+ return activities;
617
+ }
618
+ function toolPanel(tool, projectCwd) {
619
+ const elements = [markdown(renderToolDetails(tool, projectCwd))];
620
+ if (tool.kind === "image_view" && tool.imagePath) {
621
+ elements.push(localCardImage(tool.imagePath, "view_image 图片"));
622
+ }
623
+ return collapsiblePanel(toolPanelTitle(tool), elements, { elementId: toolPanelElementId(tool.id) });
624
+ }
625
+ function toolPanelElementId(toolId) {
626
+ const digest = createHash("sha256").update(toolId).digest("hex").slice(0, 16);
627
+ return `turn_tool_${digest}`;
628
+ }
629
+ function collapsiblePanel(title, content, options = {}) {
630
+ return {
631
+ tag: "collapsible_panel",
632
+ ...(options.elementId ? { element_id: options.elementId } : {}),
633
+ direction: "vertical",
634
+ vertical_spacing: "4px",
635
+ padding: "8px",
636
+ margin: "0px",
637
+ expanded: options.expanded ?? false,
638
+ header: {
639
+ title: { tag: "plain_text", content: title },
640
+ vertical_align: "center",
641
+ padding: "4px 8px 4px 8px",
642
+ },
643
+ border: {
644
+ color: options.borderColor ?? "grey",
645
+ corner_radius: "5px",
646
+ },
647
+ elements: typeof content === "string" ? [markdown(content)] : content,
648
+ };
649
+ }
650
+ function renderToolDetails(tool, projectCwd) {
651
+ const command = tool.command ?? tool.title;
652
+ const fileSummary = tool.files?.length
653
+ ? tool.files
654
+ .map((file) => `${displayFilePath(file.path, projectCwd)} +${file.additions ?? 0} -${file.deletions ?? 0}`)
655
+ .join("\n")
656
+ : undefined;
657
+ const result = tool.error ?? tool.output ?? fileSummary;
658
+ const normalizedCommand = stripAnsi(command).trim();
659
+ const displayCommand = tool.kind === "command"
660
+ ? unwrapPowerShellCommand(normalizedCommand) ?? normalizedCommand
661
+ : normalizedCommand;
662
+ const commandText = truncateText(displayCommand, 800);
663
+ const resultText = result ? truncateMiddle(stripAnsi(result).trim(), 1_200) : undefined;
664
+ return codeBlock([`$ ${commandText}`, resultText].filter((part) => part !== undefined).join("\n"), 2_003);
665
+ }
666
+ function displayFilePath(filePath, projectCwd) {
667
+ if (!projectCwd)
668
+ return filePath;
669
+ const pathApi = usesWindowsPaths(projectCwd, filePath) ? path.win32 : path;
670
+ const normalizedCwd = pathApi.resolve(projectCwd);
671
+ const absolutePath = pathApi.isAbsolute(filePath)
672
+ ? pathApi.normalize(filePath)
673
+ : pathApi.resolve(normalizedCwd, filePath);
674
+ const relativePath = pathApi.relative(normalizedCwd, absolutePath);
675
+ const isInsideProject = relativePath === ""
676
+ || (!pathApi.isAbsolute(relativePath)
677
+ && relativePath !== ".."
678
+ && !relativePath.startsWith(`..${pathApi.sep}`));
679
+ return isInsideProject ? relativePath || "." : absolutePath;
680
+ }
681
+ function usesWindowsPaths(...values) {
682
+ return values.some((value) => /^[A-Za-z]:[\\/]/.test(value) || /^\\\\/.test(value));
683
+ }
684
+ function toolPanelTitle(tool) {
685
+ const icon = tool.status === "failed" ? "❌" : tool.status === "running" ? "⏳" : "✅";
686
+ const command = stripAnsi(tool.command ?? tool.title).trim();
687
+ const meaningfulCommand = tool.kind === "web_search"
688
+ ? tool.title
689
+ : unwrapPowerShellCommand(command) ?? tool.title;
690
+ const duration = toolDuration(tool);
691
+ const prefix = `${icon} `;
692
+ const suffix = duration ? ` · ${duration}` : "";
693
+ const title = truncateText(meaningfulCommand.replace(/\s+/g, " ").trim(), Math.max(20, 100 - prefix.length - suffix.length));
694
+ return `${prefix}${title}${suffix}`;
695
+ }
696
+ function toolDuration(tool) {
697
+ if (tool.startedAt === undefined)
698
+ return undefined;
699
+ const endedAt = tool.status === "running" ? Date.now() : tool.completedAt;
700
+ if (endedAt === undefined)
701
+ return undefined;
702
+ return formatCompactDuration(endedAt - tool.startedAt);
703
+ }
704
+ function formatCompactDuration(durationMs) {
705
+ const totalTenths = Math.max(0, Math.round(durationMs / 100));
706
+ const tenths = totalTenths % 10;
707
+ const totalSeconds = Math.floor(totalTenths / 10);
708
+ const seconds = totalSeconds % 60;
709
+ const totalMinutes = Math.floor(totalSeconds / 60);
710
+ const minutes = totalMinutes % 60;
711
+ const hours = Math.floor(totalMinutes / 60);
712
+ const secondsText = `${String(seconds).padStart(2, "0")}.${tenths}s`;
713
+ if (hours > 0) {
714
+ return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${secondsText}`;
715
+ }
716
+ if (totalMinutes > 0)
717
+ return `${String(totalMinutes).padStart(2, "0")}:${secondsText}`;
718
+ return `${seconds}.${tenths}s`;
719
+ }
720
+ function formatTurnDuration(durationMs) {
721
+ const normalized = Math.max(0, durationMs);
722
+ if (Math.round(normalized / 100) < 100)
723
+ return formatCompactDuration(normalized);
724
+ const totalSeconds = Math.round(normalized / 1_000);
725
+ const seconds = totalSeconds % 60;
726
+ const totalMinutes = Math.floor(totalSeconds / 60);
727
+ const minutes = totalMinutes % 60;
728
+ const hours = Math.floor(totalMinutes / 60);
729
+ if (hours > 0) {
730
+ return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
731
+ }
732
+ if (totalMinutes > 0)
733
+ return `${String(totalMinutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
734
+ return `${seconds}s`;
735
+ }
736
+ function unwrapPowerShellCommand(command) {
737
+ const executable = command.match(/^(?:"[^"]*(?:pwsh|powershell)\.exe"|(?:\S*[\\/])?(?:pwsh|powershell)(?:\.exe)?)(?=\s|$)/i);
738
+ if (!executable)
739
+ return undefined;
740
+ const args = command.slice(executable[0].length);
741
+ const commandFlag = /(?:^|\s)-(?:Command|c)(?:\s+|$)/i.exec(args);
742
+ if (!commandFlag)
743
+ return undefined;
744
+ const payload = args.slice(commandFlag.index + commandFlag[0].length).trim();
745
+ if (!payload)
746
+ return undefined;
747
+ const quote = payload[0];
748
+ if ((quote === "\"" || quote === "'") && payload.at(-1) === quote)
749
+ return payload.slice(1, -1).trim();
750
+ return payload;
751
+ }
752
+ function codeBlock(value, maxLength) {
753
+ const clean = stripAnsi(value).trim();
754
+ return `\`\`\`\n${truncateText(clean, maxLength).replaceAll("```", "''' ")}\n\`\`\``;
755
+ }
756
+ function stripAnsi(value) {
757
+ return value.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "");
758
+ }
759
+ function inlineCode(value) {
760
+ return `\`${value.replaceAll("`", "'")}\``;
761
+ }
762
+ function formatStartupTime(value) {
763
+ return new Intl.DateTimeFormat("zh-CN", {
764
+ year: "numeric",
765
+ month: "2-digit",
766
+ day: "2-digit",
767
+ hour: "2-digit",
768
+ minute: "2-digit",
769
+ second: "2-digit",
770
+ hourCycle: "h23",
771
+ }).format(value);
772
+ }
773
+ function permissionModeLabel(mode) {
774
+ return mode === "confirm" ? "执行前确认" : "自动执行";
775
+ }
776
+ function persistedTaskStatus(sessionStatus, lastTurnStatus) {
777
+ if (sessionStatus === "running" || lastTurnStatus === "running") {
778
+ return "上次运行中,可在下一条消息时恢复";
779
+ }
780
+ const labels = {
781
+ starting: "上次正在启动",
782
+ ready: "就绪",
783
+ completed: "上次已完成",
784
+ cancelled: "上次已停止",
785
+ closed: "已关闭",
786
+ failed: "上次失败",
787
+ };
788
+ return labels[lastTurnStatus ?? sessionStatus] ?? lastTurnStatus ?? sessionStatus;
789
+ }
790
+ function turnTitle(status, prompt) {
791
+ const reactionEmoji = status === "completed"
792
+ ? "✅"
793
+ : status === "failed"
794
+ ? "❌"
795
+ : status === "cancelled"
796
+ ? "⏹️"
797
+ : status === "waiting_for_approval"
798
+ ? "🙋"
799
+ : "⏳";
800
+ const currentStatus = status === "completed"
801
+ ? "已完成"
802
+ : status === "failed"
803
+ ? "执行失败"
804
+ : status === "cancelled"
805
+ ? "已停止"
806
+ : status === "waiting_for_approval"
807
+ ? "等待确认"
808
+ : "正在处理";
809
+ const compactPrompt = truncateTurnPrompt(prompt);
810
+ const title = compactPrompt ? `${currentStatus}:${compactPrompt}` : currentStatus;
811
+ return `${reactionEmoji} ${title}`;
812
+ }
813
+ function truncateTurnPrompt(prompt) {
814
+ const characters = Array.from(prompt?.replace(/\s+/g, " ").trim() ?? "");
815
+ if (characters.length <= 40)
816
+ return characters.join("");
817
+ return `${characters.slice(0, 37).join("")}...`;
818
+ }
819
+ function turnTemplate(status) {
820
+ if (status === "completed")
821
+ return "green";
822
+ if (status === "failed")
823
+ return "red";
824
+ if (status === "cancelled")
825
+ return "grey";
826
+ if (status === "waiting_for_approval")
827
+ return "orange";
828
+ return "blue";
829
+ }
830
+ function formatTokenCount(tokens) {
831
+ const rounded = Math.max(0, Math.round(tokens));
832
+ if (rounded < 10_000) {
833
+ return new Intl.NumberFormat("zh-CN", { maximumFractionDigits: 0 }).format(rounded);
834
+ }
835
+ return new Intl.NumberFormat("en-US", {
836
+ notation: "compact",
837
+ compactDisplay: "short",
838
+ maximumSignificantDigits: 3,
839
+ }).format(rounded);
840
+ }
841
+ function markdown(content) {
842
+ return {
843
+ tag: "markdown",
844
+ content,
845
+ };
846
+ }
847
+ function escapeCardHtml(value) {
848
+ return value
849
+ .replaceAll("&", "&amp;")
850
+ .replaceAll("<", "&lt;")
851
+ .replaceAll(">", "&gt;")
852
+ .replaceAll('"', "&quot;")
853
+ .replaceAll("'", "&#39;");
854
+ }
855
+ function approvalDecisionLabel(decision) {
856
+ const labels = {
857
+ accept: "Allow Once",
858
+ acceptForSession: "Allow for Session",
859
+ decline: "Deny",
860
+ cancel: "Cancel Task",
861
+ };
862
+ return labels[decision];
863
+ }
864
+ function acpPermissionOptionLabel(option) {
865
+ const labels = {
866
+ allow_once: "Allow Once",
867
+ allow_always: "Always Allow",
868
+ reject_once: "Deny Once",
869
+ reject_always: "Always Deny",
870
+ };
871
+ return labels[option.kind] ?? (/^[\x20-\x7E]+$/.test(option.name) ? option.name : "Select");
872
+ }
873
+ function taskActionRow(actions) {
874
+ return {
875
+ tag: "column_set",
876
+ flex_mode: "flow",
877
+ horizontal_spacing: "8px",
878
+ margin: "2px 0 0 0",
879
+ columns: actions.map((action) => ({
880
+ tag: "column",
881
+ width: "auto",
882
+ vertical_align: "center",
883
+ elements: [taskActionElement(action)],
884
+ })),
885
+ };
886
+ }
887
+ function taskActionElement(action) {
888
+ return {
889
+ tag: "interactive_container",
890
+ margin: "0px",
891
+ padding: "0px",
892
+ has_border: false,
893
+ elements: [markdown(`<font color='${action.type === "danger" ? "red" : "blue"}'>${escapeCardHtml(action.text)}</font>`)],
894
+ behaviors: [{
895
+ type: "callback",
896
+ value: action.value,
897
+ }],
898
+ };
899
+ }
900
+ function turnCard(title, template, elements, subtitle) {
901
+ return {
902
+ schema: "2.0",
903
+ config: {
904
+ update_multi: true,
905
+ width_mode: "fill",
906
+ },
907
+ header: {
908
+ template,
909
+ title: {
910
+ tag: "plain_text",
911
+ content: title,
912
+ },
913
+ subtitle: {
914
+ tag: "plain_text",
915
+ content: subtitle,
916
+ },
917
+ padding: "12px 12px 12px 12px",
918
+ },
919
+ body: {
920
+ direction: "vertical",
921
+ vertical_spacing: "8px",
922
+ padding: "12px 12px 12px 12px",
923
+ elements,
924
+ },
925
+ };
926
+ }
927
+ function sectionCard(title, elements, template = "blue") {
928
+ return {
929
+ schema: "2.0",
930
+ config: {
931
+ update_multi: true,
932
+ width_mode: "fill",
933
+ },
934
+ header: {
935
+ template,
936
+ title: {
937
+ tag: "plain_text",
938
+ content: title,
939
+ },
940
+ padding: "12px 12px 12px 12px",
941
+ },
942
+ body: {
943
+ direction: "vertical",
944
+ vertical_spacing: "8px",
945
+ padding: "12px 12px 12px 12px",
946
+ elements,
947
+ },
948
+ };
949
+ }
950
+ function formatUpdate(update) {
951
+ const updateType = update.sessionUpdate;
952
+ if (updateType === "agent_message_chunk" && isObject(update.content)) {
953
+ const content = update.content;
954
+ if (content.type === "text" && typeof content.text === "string") {
955
+ return content.text;
956
+ }
957
+ }
958
+ if (updateType === "tool_call" || updateType === "tool_call_update") {
959
+ return [
960
+ update.title ? `**${String(update.title)}**` : undefined,
961
+ update.status ? `状态:${String(update.status)}` : undefined,
962
+ update.kind ? `类型:${String(update.kind)}` : undefined,
963
+ ]
964
+ .filter(Boolean)
965
+ .join("\n");
966
+ }
967
+ return `\`\`\`json\n${JSON.stringify(update, null, 2)}\n\`\`\``;
968
+ }
969
+ function isObject(value) {
970
+ return typeof value === "object" && value !== null && !Array.isArray(value);
971
+ }
972
+ //# sourceMappingURL=CardRenderer.js.map