chatccc 0.2.252 → 0.2.254

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 (296) hide show
  1. package/README.md +10 -1
  2. package/bin/cccagent.mjs +12 -3
  3. package/bin/chatccc.mjs +15 -6
  4. package/config.sample.json +2 -1
  5. package/deepccc-agent/README.md +40 -35
  6. package/deepccc-agent/os-prompts/darwin.md +8 -8
  7. package/deepccc-agent/os-prompts/linux.md +8 -8
  8. package/deepccc-agent/os-prompts/win32.md +11 -11
  9. package/deepccc-agent/package.json +65 -65
  10. package/dist/deepccc-agent/src/cli.js +634 -0
  11. package/dist/deepccc-agent/src/config.js +81 -0
  12. package/dist/deepccc-agent/src/context.js +348 -0
  13. package/dist/deepccc-agent/src/file-log.js +34 -0
  14. package/dist/deepccc-agent/src/file-tools.js +1208 -0
  15. package/dist/deepccc-agent/src/index.js +601 -0
  16. package/dist/deepccc-agent/src/permissions.js +170 -0
  17. package/dist/deepccc-agent/src/privacy.js +124 -0
  18. package/dist/deepccc-agent/src/proc-tree-kill.js +60 -0
  19. package/dist/deepccc-agent/src/progress/cards-helpers.js +70 -0
  20. package/dist/deepccc-agent/src/progress/reducer.js +102 -0
  21. package/dist/deepccc-agent/src/progress/terminal-renderer.js +264 -0
  22. package/dist/deepccc-agent/src/progress/view.js +30 -0
  23. package/dist/deepccc-agent/src/raw-stream-log.js +106 -0
  24. package/dist/deepccc-agent/src/session-search.js +276 -0
  25. package/dist/deepccc-agent/src/session-select.js +23 -0
  26. package/dist/deepccc-agent/src/sigint.js +26 -0
  27. package/dist/deepccc-agent/src/skills.js +178 -0
  28. package/dist/deepccc-agent/src/web-tools.js +246 -0
  29. package/dist/src/adapters/adapter-interface.js +19 -0
  30. package/dist/src/adapters/ccc-adapter.js +112 -0
  31. package/dist/src/adapters/claude-adapter.js +497 -0
  32. package/dist/src/adapters/claude-session-meta-store.js +92 -0
  33. package/dist/src/adapters/codex-adapter.js +279 -0
  34. package/dist/src/adapters/codex-session-meta-store.js +94 -0
  35. package/dist/src/adapters/cursor-adapter.js +491 -0
  36. package/dist/src/adapters/cursor-session-meta-store.js +116 -0
  37. package/dist/src/adapters/jsonl-stream.js +104 -0
  38. package/{src/adapters/proc-tree-kill.ts → dist/src/adapters/proc-tree-kill.js} +94 -97
  39. package/dist/src/adapters/raw-stream-log.js +106 -0
  40. package/dist/src/adapters/resource-monitor.js +113 -0
  41. package/dist/src/agent-activity.js +133 -0
  42. package/dist/src/agent-delegate-task-rpc.js +129 -0
  43. package/dist/src/agent-delegate-task.js +48 -0
  44. package/dist/src/agent-file-rpc.js +152 -0
  45. package/dist/src/agent-image-rpc.js +148 -0
  46. package/dist/src/agent-platform-routing.js +13 -0
  47. package/dist/src/agent-reload-config-rpc.js +23 -0
  48. package/dist/src/agent-rpc-body.js +87 -0
  49. package/dist/src/agent-stop-stuck.js +110 -0
  50. package/dist/src/agent-team/application/board-service.js +142 -0
  51. package/dist/src/agent-team/application/main-agent-service.js +151 -0
  52. package/dist/src/agent-team/domain/board.js +69 -0
  53. package/dist/src/agent-team/http/board-routes.js +213 -0
  54. package/dist/src/agent-team/infrastructure/filesystem-browser.js +112 -0
  55. package/dist/src/agent-team/infrastructure/main-agent-session-runtime.js +30 -0
  56. package/dist/src/agent-team/main-agent-bootstrap.js +15 -0
  57. package/dist/src/agent-team/repositories/board-repository.js +10 -0
  58. package/dist/src/agent-team/repositories/feishu-p2p-contact-store.js +64 -0
  59. package/dist/src/agent-team/repositories/json-board-repository.js +209 -0
  60. package/dist/src/agent-team/repositories/main-agent-binding-repository.js +84 -0
  61. package/dist/src/agent-team/sync/board-sync-adapter.js +1 -0
  62. package/dist/src/agent-team/web/agent-team-page.js +219 -0
  63. package/dist/src/agent-tool.js +10 -0
  64. package/dist/src/card-action-routing.js +7 -0
  65. package/dist/src/card-plain-text.js +101 -0
  66. package/dist/src/cardkit.js +158 -0
  67. package/dist/src/cards.js +573 -0
  68. package/dist/src/chatgpt-subscription-rpc.js +18 -0
  69. package/dist/src/chatgpt-subscription.js +199 -0
  70. package/dist/src/chrome-devtools-guard.js +238 -0
  71. package/dist/src/claude-sdk-installer.js +249 -0
  72. package/dist/src/codex-reset-actions.js +143 -0
  73. package/dist/src/config-utils.js +149 -0
  74. package/dist/src/config.js +808 -0
  75. package/dist/src/cursor-usage.js +77 -0
  76. package/dist/src/exit-banner.js +28 -0
  77. package/dist/src/feishu-api.js +1404 -0
  78. package/dist/src/feishu-message-ingress.js +137 -0
  79. package/dist/src/feishu-platform.js +97 -0
  80. package/dist/src/format-message.js +252 -0
  81. package/dist/src/git-command.js +155 -0
  82. package/dist/src/im-skills.js +121 -0
  83. package/dist/src/index.js +835 -0
  84. package/dist/src/litellm-proxy.js +300 -0
  85. package/dist/src/orchestrator.js +2096 -0
  86. package/dist/src/package-root.js +26 -0
  87. package/dist/src/platform-adapter.js +7 -0
  88. package/dist/src/platform-startup.js +6 -0
  89. package/dist/src/privacy.js +100 -0
  90. package/dist/src/progress/reducer.js +102 -0
  91. package/dist/src/progress/terminal-renderer.js +264 -0
  92. package/dist/src/progress/view.js +30 -0
  93. package/dist/src/response-stall.js +14 -0
  94. package/dist/src/runtime-entry.js +13 -0
  95. package/dist/src/runtime-reload.js +19 -0
  96. package/dist/src/session-chat-binding.js +183 -0
  97. package/dist/src/session-name.js +7 -0
  98. package/dist/src/session.js +2147 -0
  99. package/dist/src/shared-prefix.js +16 -0
  100. package/dist/src/shared.js +493 -0
  101. package/dist/src/sim-agent.js +105 -0
  102. package/dist/src/sim-platform.js +142 -0
  103. package/dist/src/sim-store.js +231 -0
  104. package/dist/src/simplify.js +99 -0
  105. package/dist/src/startup-lifecycle.js +209 -0
  106. package/dist/src/stream-state.js +141 -0
  107. package/dist/src/terminal-error.js +100 -0
  108. package/dist/src/trace.js +50 -0
  109. package/dist/src/turn-cards.js +92 -0
  110. package/dist/src/update-command-guard.js +114 -0
  111. package/{src/web-ui.ts → dist/src/web-ui.js} +753 -839
  112. package/dist/src/wechat-platform.js +545 -0
  113. package/package.json +76 -74
  114. package/deepccc-agent/LICENSE +0 -201
  115. package/deepccc-agent/bin/deepccc.mjs +0 -26
  116. package/deepccc-agent/docs/cache-hit-rate-1.jpg +0 -0
  117. package/deepccc-agent/docs/cache-hit-rate-2.jpg +0 -0
  118. package/deepccc-agent/package-lock.json +0 -2027
  119. package/deepccc-agent/src/__tests__/chat-session.test.ts +0 -877
  120. package/deepccc-agent/src/__tests__/cli-json.test.ts +0 -49
  121. package/deepccc-agent/src/__tests__/config.test.ts +0 -34
  122. package/deepccc-agent/src/__tests__/context.test.ts +0 -341
  123. package/deepccc-agent/src/__tests__/file-tools.test.ts +0 -240
  124. package/deepccc-agent/src/__tests__/permissions.test.ts +0 -199
  125. package/deepccc-agent/src/__tests__/privacy.test.ts +0 -318
  126. package/deepccc-agent/src/__tests__/progress-reducer.test.ts +0 -121
  127. package/deepccc-agent/src/__tests__/session-search.test.ts +0 -262
  128. package/deepccc-agent/src/__tests__/session-select.test.ts +0 -116
  129. package/deepccc-agent/src/__tests__/sigint.test.ts +0 -56
  130. package/deepccc-agent/src/__tests__/skills.test.ts +0 -284
  131. package/deepccc-agent/src/__tests__/terminal-renderer.test.ts +0 -247
  132. package/deepccc-agent/src/__tests__/web-tools.test.ts +0 -220
  133. package/deepccc-agent/src/cli.ts +0 -682
  134. package/deepccc-agent/src/config.ts +0 -101
  135. package/deepccc-agent/src/context.ts +0 -465
  136. package/deepccc-agent/src/file-log.ts +0 -38
  137. package/deepccc-agent/src/file-tools.ts +0 -1493
  138. package/deepccc-agent/src/index.ts +0 -710
  139. package/deepccc-agent/src/permissions.ts +0 -226
  140. package/deepccc-agent/src/privacy.ts +0 -141
  141. package/deepccc-agent/src/proc-tree-kill.ts +0 -61
  142. package/deepccc-agent/src/progress/cards-helpers.ts +0 -76
  143. package/deepccc-agent/src/progress/reducer.ts +0 -113
  144. package/deepccc-agent/src/progress/terminal-renderer.ts +0 -294
  145. package/deepccc-agent/src/progress/view.ts +0 -77
  146. package/deepccc-agent/src/raw-stream-log.ts +0 -124
  147. package/deepccc-agent/src/session-search.ts +0 -370
  148. package/deepccc-agent/src/session-select.ts +0 -48
  149. package/deepccc-agent/src/sigint.ts +0 -50
  150. package/deepccc-agent/src/skills.ts +0 -205
  151. package/deepccc-agent/src/web-tools.ts +0 -313
  152. package/deepccc-agent/tsconfig.build.json +0 -13
  153. package/deepccc-agent/tsconfig.json +0 -13
  154. package/deepccc-agent/vitest.config.ts +0 -7
  155. package/src/__tests__/adapter-interface.test.ts +0 -152
  156. package/src/__tests__/agent-activity.test.ts +0 -86
  157. package/src/__tests__/agent-delegate-task-rpc.test.ts +0 -165
  158. package/src/__tests__/agent-image-rpc.test.ts +0 -34
  159. package/src/__tests__/agent-platform-routing.test.ts +0 -26
  160. package/src/__tests__/agent-reload-config-rpc.test.ts +0 -99
  161. package/src/__tests__/agent-rpc-body.test.ts +0 -42
  162. package/src/__tests__/builtin-chat-session.test.ts +0 -532
  163. package/src/__tests__/builtin-cli-json.test.ts +0 -39
  164. package/src/__tests__/builtin-config.test.ts +0 -26
  165. package/src/__tests__/builtin-context.test.ts +0 -319
  166. package/src/__tests__/builtin-file-tools.test.ts +0 -240
  167. package/src/__tests__/builtin-permissions.test.ts +0 -219
  168. package/src/__tests__/builtin-session-search.test.ts +0 -262
  169. package/src/__tests__/builtin-session-select.test.ts +0 -116
  170. package/src/__tests__/builtin-sigint.test.ts +0 -56
  171. package/src/__tests__/builtin-skills.test.ts +0 -284
  172. package/src/__tests__/builtin-web-tools.test.ts +0 -220
  173. package/src/__tests__/card-action-routing.test.ts +0 -18
  174. package/src/__tests__/card-plain-text.test.ts +0 -45
  175. package/src/__tests__/cardkit.test.ts +0 -60
  176. package/src/__tests__/cards.test.ts +0 -607
  177. package/src/__tests__/ccc-adapter.test.ts +0 -194
  178. package/src/__tests__/chatgpt-subscription-rpc.test.ts +0 -89
  179. package/src/__tests__/chatgpt-subscription.test.ts +0 -135
  180. package/src/__tests__/chrome-devtools-guard.test.ts +0 -165
  181. package/src/__tests__/claude-adapter.test.ts +0 -614
  182. package/src/__tests__/claude-raw-stream-log.test.ts +0 -96
  183. package/src/__tests__/claude-sdk-installer.test.ts +0 -285
  184. package/src/__tests__/codex-adapter.test.ts +0 -331
  185. package/src/__tests__/codex-raw-stream-log.test.ts +0 -170
  186. package/src/__tests__/codex-reset-actions.test.ts +0 -146
  187. package/src/__tests__/config-reload.test.ts +0 -284
  188. package/src/__tests__/config-sample.test.ts +0 -97
  189. package/src/__tests__/config-utils.test.ts +0 -40
  190. package/src/__tests__/config.test.ts +0 -395
  191. package/src/__tests__/crash-logging.test.ts +0 -360
  192. package/src/__tests__/cursor-adapter.test.ts +0 -890
  193. package/src/__tests__/cursor-session-meta-store.test.ts +0 -212
  194. package/src/__tests__/feishu-api.test.ts +0 -60
  195. package/src/__tests__/feishu-avatar.test.ts +0 -504
  196. package/src/__tests__/feishu-message-ingress.test.ts +0 -138
  197. package/src/__tests__/feishu-platform.test.ts +0 -75
  198. package/src/__tests__/fixtures/codex_simple_text.jsonl +0 -4
  199. package/src/__tests__/fixtures/codex_with_tool.jsonl +0 -6
  200. package/src/__tests__/fixtures/cursor_partial_only.jsonl +0 -5
  201. package/src/__tests__/fixtures/cursor_partial_with_final.jsonl +0 -13
  202. package/src/__tests__/fixtures/cursor_with_tool_call.jsonl +0 -12
  203. package/src/__tests__/format-message.test.ts +0 -316
  204. package/src/__tests__/git-command.test.ts +0 -288
  205. package/src/__tests__/im-skills.test.ts +0 -125
  206. package/src/__tests__/jsonl-stream.test.ts +0 -79
  207. package/src/__tests__/orchestrator.test.ts +0 -1268
  208. package/src/__tests__/package-files.test.ts +0 -24
  209. package/src/__tests__/platform-startup.test.ts +0 -19
  210. package/src/__tests__/privacy.test.ts +0 -198
  211. package/src/__tests__/proc-tree-kill.test.ts +0 -108
  212. package/src/__tests__/progress-reducer.test.ts +0 -121
  213. package/src/__tests__/raw-stream-log.test.ts +0 -106
  214. package/src/__tests__/response-stall.test.ts +0 -49
  215. package/src/__tests__/restart.test.ts +0 -232
  216. package/src/__tests__/session-ccc-config.test.ts +0 -66
  217. package/src/__tests__/session.test.ts +0 -3004
  218. package/src/__tests__/shared-prefix.test.ts +0 -36
  219. package/src/__tests__/sim-agent.test.ts +0 -174
  220. package/src/__tests__/sim-platform.test.ts +0 -93
  221. package/src/__tests__/sim-store.test.ts +0 -214
  222. package/src/__tests__/simplify.test.ts +0 -283
  223. package/src/__tests__/startup-lifecycle.test.ts +0 -231
  224. package/src/__tests__/stop-session.test.ts +0 -162
  225. package/src/__tests__/stream-state.test.ts +0 -164
  226. package/src/__tests__/terminal-error.test.ts +0 -54
  227. package/src/__tests__/terminal-renderer.test.ts +0 -247
  228. package/src/__tests__/update-command-guard.test.ts +0 -144
  229. package/src/__tests__/web-ui.test.ts +0 -438
  230. package/src/__tests__/wechat-platform.test.ts +0 -111
  231. package/src/adapters/adapter-interface.ts +0 -217
  232. package/src/adapters/ccc-adapter.ts +0 -150
  233. package/src/adapters/claude-adapter.ts +0 -673
  234. package/src/adapters/claude-session-meta-store.ts +0 -120
  235. package/src/adapters/codex-adapter.ts +0 -426
  236. package/src/adapters/codex-session-meta-store.ts +0 -131
  237. package/src/adapters/cursor-adapter.ts +0 -681
  238. package/src/adapters/cursor-session-meta-store.ts +0 -154
  239. package/src/adapters/jsonl-stream.ts +0 -157
  240. package/src/adapters/raw-stream-log.ts +0 -124
  241. package/src/adapters/resource-monitor.ts +0 -141
  242. package/src/agent-activity.ts +0 -175
  243. package/src/agent-delegate-task-rpc.ts +0 -153
  244. package/src/agent-delegate-task.ts +0 -91
  245. package/src/agent-file-rpc.ts +0 -172
  246. package/src/agent-image-rpc.ts +0 -168
  247. package/src/agent-platform-routing.ts +0 -28
  248. package/src/agent-reload-config-rpc.ts +0 -34
  249. package/src/agent-rpc-body.ts +0 -92
  250. package/src/agent-stop-stuck.ts +0 -129
  251. package/src/card-action-routing.ts +0 -14
  252. package/src/card-plain-text.ts +0 -108
  253. package/src/cardkit.ts +0 -179
  254. package/src/cards.ts +0 -684
  255. package/src/chatgpt-subscription-rpc.ts +0 -27
  256. package/src/chatgpt-subscription.ts +0 -299
  257. package/src/chrome-devtools-guard.ts +0 -318
  258. package/src/claude-sdk-installer.ts +0 -324
  259. package/src/codex-reset-actions.ts +0 -184
  260. package/src/config-utils.ts +0 -211
  261. package/src/config.ts +0 -1063
  262. package/src/cursor-usage.ts +0 -128
  263. package/src/exit-banner.ts +0 -33
  264. package/src/feishu-api.ts +0 -1616
  265. package/src/feishu-message-ingress.ts +0 -195
  266. package/src/feishu-platform.ts +0 -159
  267. package/src/format-message.ts +0 -293
  268. package/src/git-command.ts +0 -202
  269. package/src/im-skills.ts +0 -149
  270. package/src/index.ts +0 -1089
  271. package/src/litellm-proxy.ts +0 -374
  272. package/src/orchestrator.ts +0 -2543
  273. package/src/platform-adapter.ts +0 -70
  274. package/src/platform-startup.ts +0 -16
  275. package/src/privacy.ts +0 -118
  276. package/src/progress/reducer.ts +0 -113
  277. package/src/progress/terminal-renderer.ts +0 -294
  278. package/src/progress/view.ts +0 -77
  279. package/src/response-stall.ts +0 -28
  280. package/src/runtime-reload.ts +0 -34
  281. package/src/session-chat-binding.ts +0 -292
  282. package/src/session-name.ts +0 -8
  283. package/src/session.ts +0 -2659
  284. package/src/shared-prefix.ts +0 -29
  285. package/src/shared.ts +0 -552
  286. package/src/sim-agent.ts +0 -167
  287. package/src/sim-platform.ts +0 -177
  288. package/src/sim-store.ts +0 -317
  289. package/src/simplify.ts +0 -120
  290. package/src/startup-lifecycle.ts +0 -250
  291. package/src/stream-state.ts +0 -177
  292. package/src/terminal-error.ts +0 -129
  293. package/src/trace.ts +0 -51
  294. package/src/turn-cards.ts +0 -118
  295. package/src/update-command-guard.ts +0 -165
  296. package/src/wechat-platform.ts +0 -680
@@ -0,0 +1,573 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Button helpers
3
+ // ---------------------------------------------------------------------------
4
+ import { ABD_HELP_LINE } from "./shared-prefix.js";
5
+ export function buildButtons(buttons) {
6
+ return {
7
+ tag: "action",
8
+ actions: buttons.map((b) => ({
9
+ tag: "button",
10
+ text: { tag: "plain_text", content: b.text },
11
+ type: b.type ?? "primary",
12
+ value: b.value,
13
+ })),
14
+ };
15
+ }
16
+ // ---------------------------------------------------------------------------
17
+ // Content helpers
18
+ // ---------------------------------------------------------------------------
19
+ // 检测 markdown 代码块是否未闭合(``` 出现奇数次)
20
+ export function isCodeBlockOpen(text) {
21
+ const matches = text.match(/```/g);
22
+ return matches ? matches.length % 2 !== 0 : false;
23
+ }
24
+ export function truncateContent(text, maxLines = 20, maxChars = 8000) {
25
+ const lines = text.split("\n");
26
+ // 跳过开头空行
27
+ let startIdx = 0;
28
+ while (startIdx < lines.length && lines[startIdx].trim() === "") {
29
+ startIdx++;
30
+ }
31
+ const effectiveLines = lines.slice(startIdx);
32
+ let displayText;
33
+ if (effectiveLines.length > maxLines) {
34
+ const firstLine = effectiveLines[0];
35
+ const lastLines = effectiveLines.slice(-(maxLines - 1)).join("\n");
36
+ displayText = firstLine + "\n...\n" + lastLines;
37
+ }
38
+ else {
39
+ displayText = text;
40
+ }
41
+ // 截断后如果代码块未闭合,补上闭合标记,避免后续追加内容时误入代码块
42
+ if (isCodeBlockOpen(displayText)) {
43
+ displayText += "\n```";
44
+ }
45
+ return displayText;
46
+ }
47
+ const TOOL_EMOJI_MAP = {
48
+ Read: "\u{1F4D6}", // 📖
49
+ Write: "\u{270D}\u{FE0F}", // ✍️
50
+ Edit: "\u{270F}\u{FE0F}", // ✏️
51
+ Grep: "\u{1F50E}", // 🔎
52
+ Glob: "\u{1F4C2}", // 📂
53
+ Bash: "\u{1F5A5}\u{FE0F}", // 🖥️
54
+ WebSearch: "\u{1F310}", // 🌐
55
+ WebFetch: "\u{1F4E5}", // 📥
56
+ TodoWrite: "\u{2705}", // ✅
57
+ Agent: "\u{1F916}", // 🤖
58
+ NotebookEdit: "\u{1F4D3}", // 📓
59
+ AskUserQuestion: "\u{2753}", // ❓
60
+ // CCC 内置 Agent 下划线命名(getToolEmoji 会先把下划线转驼峰再查表,这里保留蛇形条目以便直接命中)
61
+ read_file: "\u{1F4D6}", // 📖
62
+ list_dir: "\u{1F4C2}", // 📂
63
+ search_code: "\u{1F50E}", // 🔎
64
+ run_command: "\u{1F5A5}\u{FE0F}", // 🖥️
65
+ edit_file: "\u{270F}\u{FE0F}", // ✏️
66
+ create_file: "\u{270D}\u{FE0F}", // ✍️
67
+ delete_file: "\u{1F5D1}\u{FE0F}", // 🗑️
68
+ move_file: "\u{1F4E6}", // 📦
69
+ apply_patch: "\u{1F4CB}", // 📋
70
+ };
71
+ export function getToolEmoji(name) {
72
+ return TOOL_EMOJI_MAP[name] ?? TOOL_EMOJI_MAP[normalizeToolName(name)] ?? "\u{1F527}"; // 🔧
73
+ }
74
+ /** 把下划线命名转成驼峰(read_file → ReadFile),用于兼容两种命名风格 */
75
+ export function normalizeToolName(name) {
76
+ return name
77
+ .split("_")
78
+ .filter((part) => part.length > 0)
79
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
80
+ .join("");
81
+ }
82
+ // ---------------------------------------------------------------------------
83
+ // Card builders
84
+ // ---------------------------------------------------------------------------
85
+ // CardKit schema 2.0 进度卡片(带停止按钮,支持流式更新)
86
+ // 输入是平台无关的 ProgressView(与终端过程区块共享同一视图模型):
87
+ // - view.text → main_content(正文)
88
+ // - view.showStop → 是否展示状态/停止按钮
89
+ // - view.headerTitle → 卡片头部标题(生成中阶段的动态标题)
90
+ // - view.headerTemplate → 卡片头部颜色模板
91
+ export function buildProgressCard(view) {
92
+ const showStop = view.showStop;
93
+ const headerTitle = view.headerTitle;
94
+ const headerTemplate = view.headerTemplate;
95
+ const elements = [
96
+ { tag: "markdown", content: truncateContent(view.text), element_id: "main_content" },
97
+ ];
98
+ if (showStop) {
99
+ elements.push({ tag: "hr" });
100
+ elements.push({
101
+ tag: "button",
102
+ text: { tag: "plain_text", content: "查看状态(/state)" },
103
+ type: "default",
104
+ value: { action: "state" },
105
+ element_id: "action_state",
106
+ });
107
+ elements.push({
108
+ tag: "button",
109
+ text: { tag: "plain_text", content: "停止生成(/stop)" },
110
+ type: "danger",
111
+ value: { action: "stop" },
112
+ element_id: "action_stop",
113
+ });
114
+ }
115
+ return JSON.stringify({
116
+ schema: "2.0",
117
+ config: {
118
+ update_multi: true,
119
+ streaming_mode: false,
120
+ },
121
+ header: {
122
+ template: headerTemplate,
123
+ title: { tag: "plain_text", content: headerTitle },
124
+ },
125
+ body: {
126
+ direction: "vertical",
127
+ elements,
128
+ },
129
+ });
130
+ }
131
+ export function buildHelpCard(userText, opts = {}) {
132
+ const greeting = opts.greeting ?? `你发送了: ${userText}`;
133
+ const defaultToolLabel = opts.defaultToolLabel ?? "Claude Code";
134
+ const lines = [
135
+ `发送 **/new** 创建新会话(使用 /cd 设置的目录,默认 ${defaultToolLabel})`,
136
+ "发送 **/new claude** 创建新 Claude 会话",
137
+ "发送 **/new cursor** 创建新 Cursor 会话",
138
+ "发送 **/new codex** 创建新 Codex 会话",
139
+ "发送 **/new ccc** 创建新 CCC Agent 会话",
140
+ "发送 **/newh** 重置当前会话(沿用当前工作目录,不切换)",
141
+ "发送 **/plan** 以规划模式提问(只读,不执行写操作)",
142
+ "发送 **/ask** 以问答模式提问(只读,不执行写操作)",
143
+ "发送 **/usage** 查看当前 Agent 的用量或余额",
144
+ "发送 **/restart** 重启 ChatCCC 进程",
145
+ "发送 **/update** 更新并重启(仅 npm 全局安装可用)",
146
+ ABD_HELP_LINE,
147
+ ].join("\n");
148
+ return JSON.stringify({
149
+ config: { wide_screen_mode: true },
150
+ header: { template: "blue", title: { content: "ChatCCC", tag: "plain_text" } },
151
+ elements: [
152
+ { tag: "div", text: { tag: "lark_md", content: greeting } },
153
+ { tag: "div", text: { tag: "lark_md", content: lines } },
154
+ buildButtons([
155
+ { text: `新建默认会话(/new,${defaultToolLabel})`, value: JSON.stringify({ cmd: "new" }), type: "primary" },
156
+ { text: "新建 Claude 会话(/new claude)", value: JSON.stringify({ cmd: "new claude" }), type: "primary" },
157
+ { text: "新建 Cursor 会话(/new cursor)", value: JSON.stringify({ cmd: "new cursor" }), type: "primary" },
158
+ { text: "新建 Codex 会话(/new codex)", value: JSON.stringify({ cmd: "new codex" }), type: "primary" },
159
+ { text: "新建 CCC Agent 会话(/new ccc)", value: JSON.stringify({ cmd: "new ccc" }), type: "primary" },
160
+ { text: "重启 ChatCCC(/restart)", value: JSON.stringify({ cmd: "restart" }), type: "danger" },
161
+ { text: "更新并重启(/update)", value: JSON.stringify({ cmd: "update" }), type: "danger" },
162
+ { text: "切换工作路径(/cd)", value: JSON.stringify({ cmd: "cd" }), type: "default" },
163
+ ]),
164
+ ],
165
+ });
166
+ }
167
+ // 工作路径内容(/cd 命令统一使用,返回 markdown 内容字符串)
168
+ export function buildCdContent(dirPath, entries, isUpdate, currentCwd, maxFiles = 100) {
169
+ const display = entries.slice(0, maxFiles);
170
+ const overflow = entries.length > maxFiles ? `\n...(共 ${entries.length} 个条目,仅显示前 ${maxFiles} 个)` : "";
171
+ const listing = display.map(e => e.isDir ? `📁 ${e.name}/` : `📄 ${e.name}`).join("\n");
172
+ const currentLine = currentCwd
173
+ ? `**当前会话工作路径:** \`${currentCwd}\``
174
+ : "";
175
+ const statusLine = isUpdate
176
+ ? `**后续新建会话默认工作路径(已切换):** \`${dirPath}\``
177
+ : `**后续新建会话默认工作路径:** \`${dirPath}\``;
178
+ const lines = [];
179
+ if (currentLine)
180
+ lines.push(currentLine, "");
181
+ lines.push(statusLine, ``, `此路径仅影响**本会话中新建**的会话工作路径。`, ``, `---`, `**目录内容** (最多 ${maxFiles} 个):`, listing, overflow);
182
+ return lines.join("\n");
183
+ }
184
+ // 工作路径卡片(/cd 无参数时使用,含最近使用路径按钮)
185
+ //
186
+ // 必须使用 v1 卡片格式(无 schema 字段、elements 放顶层)。原因:
187
+ // 此卡片通过 `/im/v1/messages?msg_type=interactive` 端点直接发出,content
188
+ // 字段就是卡片 JSON。该端点不接受 schema 2.0 的原始 JSON 作为 content
189
+ // (schema 2.0 卡片必须先通过 CardKit /cardkit/v1/cards 创建得到 card_id,
190
+ // 再以 `{type:"card", data:{card_id}}` 包装发出,参见 sendCardKitMessage)。
191
+ // 之前用过 schema 2.0 + body.elements 的结构,飞书会静默拒绝该消息,
192
+ // 导致 /cd 无任何回复——故此处与 buildSessionsCard/buildStatusCard 保持
193
+ // 同一 v1 结构。
194
+ export function buildCdCard(dirPath, entries, recentDirs, sessionCwd, maxFiles = 100) {
195
+ const display = entries.slice(0, maxFiles);
196
+ const overflow = entries.length > maxFiles ? `\n...(共 ${entries.length} 个条目,仅显示前 ${maxFiles} 个)` : "";
197
+ const listing = display.map(e => e.isDir ? `📁 ${e.name}/` : `📄 ${e.name}`).join("\n");
198
+ const elements = [];
199
+ if (sessionCwd) {
200
+ elements.push({
201
+ tag: "div",
202
+ text: { tag: "lark_md", content: `**当前会话工作路径:** \`${sessionCwd}\`` },
203
+ });
204
+ }
205
+ elements.push({
206
+ tag: "div",
207
+ text: { tag: "lark_md", content: `**后续新建会话默认工作路径:** \`${dirPath}\`` },
208
+ });
209
+ if (recentDirs.length > 0) {
210
+ elements.push({ tag: "hr" });
211
+ elements.push({
212
+ tag: "div",
213
+ text: { tag: "lark_md", content: "**最近使用过的路径:**" },
214
+ });
215
+ elements.push({
216
+ tag: "action",
217
+ actions: recentDirs.map(d => {
218
+ const label = d.length > 36 ? `...${d.slice(-33)}` : d;
219
+ return {
220
+ tag: "button",
221
+ text: { tag: "plain_text", content: label },
222
+ type: "default",
223
+ value: { action: "cd", path: d },
224
+ };
225
+ }),
226
+ });
227
+ }
228
+ elements.push({ tag: "hr" });
229
+ elements.push({
230
+ tag: "div",
231
+ text: {
232
+ tag: "lark_md",
233
+ content: [
234
+ `**目录内容** (最多 ${maxFiles} 个):`,
235
+ listing,
236
+ overflow,
237
+ ].join("\n"),
238
+ },
239
+ });
240
+ return JSON.stringify({
241
+ config: { wide_screen_mode: true },
242
+ header: {
243
+ template: "blue",
244
+ title: { tag: "plain_text", content: "工作路径" },
245
+ },
246
+ elements,
247
+ });
248
+ }
249
+ function sessionToolLabel(tool) {
250
+ if (tool === "cursor")
251
+ return "Cursor";
252
+ if (tool === "codex")
253
+ return "Codex";
254
+ if (tool === "ccc")
255
+ return "CCC Agent";
256
+ return "Claude Code";
257
+ }
258
+ function pushSessionGroup(lines, title, sessions, formatSession, index) {
259
+ if (sessions.length === 0)
260
+ return;
261
+ if (index.value > 0)
262
+ lines.push("", `**${title}:**`, "");
263
+ else
264
+ lines.push(`**${title}:**`, "");
265
+ for (const session of sessions) {
266
+ lines.push(formatSession(session, index.value++));
267
+ }
268
+ }
269
+ // 所有会话列表卡片(Claude Code 优先,然后 Cursor)
270
+ export function buildSessionsCard(sessions, opts = {}) {
271
+ const defaultToolLabel = opts.defaultToolLabel ?? "Claude Code";
272
+ const fixedPrivateSession = opts.fixedPrivateSession ?? false;
273
+ // 按 tool 分组排序:Claude Code 在前,Cursor 其次,Codex 最后
274
+ const claudeCodeSessions = sessions.filter(s => s.tool !== "cursor" && s.tool !== "codex" && s.tool !== "ccc");
275
+ const cursorSessions = sessions.filter(s => s.tool === "cursor");
276
+ const codexSessions = sessions.filter(s => s.tool === "codex");
277
+ const cccSessions = sessions.filter(s => s.tool === "ccc");
278
+ const hasClaudeCode = claudeCodeSessions.length > 0;
279
+ const hasCursor = cursorSessions.length > 0;
280
+ const hasCodex = codexSessions.length > 0;
281
+ const hasCcc = cccSessions.length > 0;
282
+ if (sessions.length === 0) {
283
+ return JSON.stringify({
284
+ config: { wide_screen_mode: true },
285
+ header: { template: "blue", title: { content: "所有会话", tag: "plain_text" } },
286
+ elements: [
287
+ { tag: "div", text: { tag: "lark_md", content: fixedPrivateSession
288
+ ? `当前没有会话记录。\n\n直接发送普通消息即可创建飞书私聊专属 ${defaultToolLabel} 会话;默认 Agent 变化后,下一条普通消息会创建对应 Agent 的新空会话。发送 **/new**、**/new claude**、**/new cursor**、**/new codex** 或 **/new ccc** 会另外创建会话群。`
289
+ : `当前没有会话记录。\n\n使用 **/new**(默认 ${defaultToolLabel})、**/new claude**、**/new cursor**、**/new codex** 或 **/new ccc** 创建新会话。\n创建后可在任意会话群内发送 **/sessions** 查看列表,用 **/session 数字** 切换会话。` } },
290
+ { tag: "hr" },
291
+ { tag: "action", actions: [{ tag: "button", text: { tag: "plain_text", content: "收起" }, type: "default", value: { action: "close" } }] },
292
+ ],
293
+ });
294
+ }
295
+ const formatSession = (s, i) => {
296
+ const status = s.active ? "🟢 活跃" : "⚪ 空闲";
297
+ const shortId = s.sessionId.length > 16 ? s.sessionId.slice(0, 16) + "..." : s.sessionId;
298
+ let extra = "";
299
+ if (s.active && s.elapsedSeconds !== null) {
300
+ const mins = Math.floor(s.elapsedSeconds / 60);
301
+ const secs = s.elapsedSeconds % 60;
302
+ extra = ` | 本轮: ${mins}分${secs}秒`;
303
+ }
304
+ const toolLabel = sessionToolLabel(s.tool);
305
+ const namePart = s.chatName ? `**${s.chatName}** ` : "";
306
+ const chatTag = !s.chatId
307
+ ? " (chat id缺失)"
308
+ : s.chatType === "p2p"
309
+ ? " (私聊)"
310
+ : s.chatType === "group" || (s.chatType == null && s.chatId.startsWith("oc_"))
311
+ ? " (群聊)"
312
+ : "";
313
+ return `**${i + 1}.** ${namePart}${chatTag} \`${shortId}\` ${status} | 工具: ${toolLabel} | 轮数: ${s.turnCount} | ${s.model}${extra}`;
314
+ };
315
+ const lines = [`共 **${sessions.length}** 个会话:`, ""];
316
+ const idx = { value: 0 };
317
+ if (hasClaudeCode)
318
+ pushSessionGroup(lines, "Claude Code 会话", claudeCodeSessions, formatSession, idx);
319
+ if (hasCursor)
320
+ pushSessionGroup(lines, "Cursor 会话", cursorSessions, formatSession, idx);
321
+ if (hasCodex)
322
+ pushSessionGroup(lines, "Codex 会话", codexSessions, formatSession, idx);
323
+ if (hasCcc)
324
+ pushSessionGroup(lines, "CCC Agent 会话", cccSessions, formatSession, idx);
325
+ return JSON.stringify({
326
+ config: { wide_screen_mode: true },
327
+ header: { template: "blue", title: { content: "所有会话", tag: "plain_text" } },
328
+ elements: [
329
+ { tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
330
+ { tag: "hr" },
331
+ { tag: "div", text: { tag: "lark_md", content: fixedPrivateSession
332
+ ? "当前飞书私聊使用专属会话;默认 Agent 变化后,下一条普通消息会自动创建对应 Agent 的新空会话。发送 **/newh** 可在私聊中原地重置。群聊会话请回到对应群聊继续,私聊不支持 **/session** 切换。"
333
+ : "在会话群内发送 **/newh** 可重置当前会话(创建新 Session,保留工作目录和群聊)。\n发送 **/session 数字**(如 `/session 1`)可将当前群聊切换到列表中对应编号的会话。" } },
334
+ { tag: "hr" },
335
+ {
336
+ tag: "action",
337
+ actions: [{
338
+ tag: "button",
339
+ text: { tag: "plain_text", content: "收起" },
340
+ type: "default",
341
+ value: { action: "close" },
342
+ }],
343
+ },
344
+ ],
345
+ });
346
+ }
347
+ // 队列缓存卡片(消息已进入缓存队列,带取消按钮)
348
+ export function buildQueuedCard(text) {
349
+ const preview = text.length > 100 ? text.slice(0, 100) + "…" : text;
350
+ return JSON.stringify({
351
+ config: { wide_screen_mode: true },
352
+ header: { template: "blue", title: { content: "消息已进入缓存队列", tag: "plain_text" } },
353
+ elements: [
354
+ { tag: "div", text: { tag: "lark_md", content: `当前会话正在生成中,你的消息已进入缓存队列,生成完成后会立即处理。\n\n> ${preview}` } },
355
+ { tag: "hr" },
356
+ {
357
+ tag: "action",
358
+ actions: [{
359
+ tag: "button",
360
+ text: { tag: "plain_text", content: "取消缓存(/cancel)" },
361
+ type: "danger",
362
+ value: { action: "cancel" },
363
+ }],
364
+ },
365
+ ],
366
+ });
367
+ }
368
+ // 队列满卡片(提示 /stop 或 /cancel)
369
+ export function buildQueueFullCard() {
370
+ return JSON.stringify({
371
+ config: { wide_screen_mode: true },
372
+ header: { template: "yellow", title: { content: "消息队列已满", tag: "plain_text" } },
373
+ elements: [
374
+ { tag: "div", text: { tag: "lark_md", content: "当前缓存队列中已有消息等待处理,请等待或发送指令:\n- **/stop** — 停止当前生成\n- **/cancel** — 取消队列中的消息" } },
375
+ { tag: "hr" },
376
+ {
377
+ tag: "action",
378
+ actions: [
379
+ { tag: "button", text: { tag: "plain_text", content: "取消缓存(/cancel)" }, type: "danger", value: { action: "cancel" } },
380
+ { tag: "button", text: { tag: "plain_text", content: "停止生成(/stop)" }, type: "default", value: { action: "stop" } },
381
+ ],
382
+ },
383
+ ],
384
+ });
385
+ }
386
+ // 状态卡片(带关闭按钮)
387
+ export function buildStatusCard(statusText, template = "blue") {
388
+ return JSON.stringify({
389
+ config: { wide_screen_mode: true },
390
+ header: { template, title: { content: "会话状态", tag: "plain_text" } },
391
+ elements: [
392
+ { tag: "div", text: { tag: "lark_md", content: statusText } },
393
+ { tag: "hr" },
394
+ {
395
+ tag: "action",
396
+ actions: [{
397
+ tag: "button",
398
+ text: { tag: "plain_text", content: "收起" },
399
+ type: "default",
400
+ value: { action: "close" },
401
+ }],
402
+ },
403
+ ],
404
+ });
405
+ }
406
+ export function buildCodexUsageCard(content, resetCreditsAvailable) {
407
+ const elements = [
408
+ { tag: "div", text: { tag: "lark_md", content } },
409
+ ];
410
+ if (resetCreditsAvailable !== null && resetCreditsAvailable > 0) {
411
+ elements.push({ tag: "hr" });
412
+ elements.push({
413
+ tag: "action",
414
+ actions: [{
415
+ tag: "button",
416
+ text: { tag: "plain_text", content: "发起重置" },
417
+ type: "primary",
418
+ value: { action: "codex_reset_request", availableCount: resetCreditsAvailable },
419
+ }],
420
+ });
421
+ }
422
+ return JSON.stringify({
423
+ config: { wide_screen_mode: true },
424
+ header: { template: "blue", title: { content: "Codex Usage", tag: "plain_text" } },
425
+ elements,
426
+ });
427
+ }
428
+ export function buildCodexResetConfirmCard(params) {
429
+ const valueBase = {
430
+ parentMessageId: params.parentMessageId,
431
+ requestId: params.requestId,
432
+ };
433
+ return JSON.stringify({
434
+ config: { wide_screen_mode: true },
435
+ header: { template: "yellow", title: { content: "确认 Codex 主动重置", tag: "plain_text" } },
436
+ elements: [
437
+ {
438
+ tag: "div",
439
+ text: {
440
+ tag: "lark_md",
441
+ content: `当前可用主动重置次数:**${params.availableCount}**。\n\n确认后会消耗 1 次主动重置,并重置当前可重置的 Codex 用量窗口。`,
442
+ },
443
+ },
444
+ { tag: "hr" },
445
+ {
446
+ tag: "action",
447
+ actions: [
448
+ {
449
+ tag: "button",
450
+ text: { tag: "plain_text", content: "是,发起重置" },
451
+ type: "danger",
452
+ value: { action: "codex_reset_confirm", decision: "yes", ...valueBase },
453
+ },
454
+ {
455
+ tag: "button",
456
+ text: { tag: "plain_text", content: "否" },
457
+ type: "default",
458
+ value: { action: "codex_reset_confirm", decision: "no", ...valueBase },
459
+ },
460
+ ],
461
+ },
462
+ ],
463
+ });
464
+ }
465
+ /** 模型切换卡片(/model 命令,飞书专用,含切换按钮) */
466
+ export function buildModelCard(currentModel, models, tool) {
467
+ const toolLabel = tool ? ` (${tool})` : "";
468
+ const currentLine = currentModel
469
+ ? `**当前模型:** \`${currentModel}\``
470
+ : "**当前模型:** 未指定";
471
+ const lines = [currentLine];
472
+ if (models.length > 0) {
473
+ lines.push("", "**可切换模型:**");
474
+ for (const m of models) {
475
+ lines.push(`- \`${m}\``);
476
+ }
477
+ lines.push("", "点击按钮切换模型,或输入 `/model clear` 恢复默认");
478
+ }
479
+ else {
480
+ lines.push("", "没有可切换的模型。请在 config.json 中配置模型字段。");
481
+ }
482
+ if (tool === "codex") {
483
+ lines.push("输入 `/fast` 查看或切换当前会话的 Fast 模式");
484
+ }
485
+ const buttons = [];
486
+ for (const m of models.slice(0, 20)) {
487
+ const shortName = m.includes("/") ? m.slice(m.lastIndexOf("/") + 1) : m;
488
+ buttons.push({
489
+ text: `/model ${shortName}`,
490
+ value: JSON.stringify({ cmd: `/model ${m}` }),
491
+ type: "primary",
492
+ });
493
+ }
494
+ return JSON.stringify({
495
+ config: { wide_screen_mode: true },
496
+ header: { template: "blue", title: { content: `模型切换${toolLabel}`, tag: "plain_text" } },
497
+ elements: [
498
+ { tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
499
+ { tag: "hr" },
500
+ buildButtons(buttons),
501
+ ],
502
+ });
503
+ }
504
+ export function buildFastModeCard(enabled) {
505
+ const mode = enabled ? "ON (Fast)" : "OFF (Standard)";
506
+ return JSON.stringify({
507
+ config: { wide_screen_mode: true },
508
+ header: {
509
+ template: enabled ? "green" : "blue",
510
+ title: { content: "Codex Fast 模式", tag: "plain_text" },
511
+ },
512
+ elements: [
513
+ {
514
+ tag: "div",
515
+ text: {
516
+ tag: "lark_md",
517
+ content: [
518
+ `**当前模式:** ${mode}`,
519
+ "",
520
+ "切换将在下一条消息生效,当前生成不中断。",
521
+ ].join("\n"),
522
+ },
523
+ },
524
+ { tag: "hr" },
525
+ buildButtons([
526
+ {
527
+ text: "ON",
528
+ value: JSON.stringify({ cmd: "/fast on" }),
529
+ type: enabled ? "primary" : "default",
530
+ },
531
+ {
532
+ text: "OFF",
533
+ value: JSON.stringify({ cmd: "/fast off" }),
534
+ type: enabled ? "default" : "primary",
535
+ },
536
+ ]),
537
+ ],
538
+ });
539
+ }
540
+ export function buildEffortCard(currentEffort, efforts, tool) {
541
+ const toolLabel = tool ? ` (${tool})` : "";
542
+ const currentLine = currentEffort
543
+ ? `**当前 effort:** \`${currentEffort}\``
544
+ : "**当前 effort:** 未指定";
545
+ const lines = [currentLine];
546
+ if (efforts.length > 0) {
547
+ lines.push("", "**可切换 effort**");
548
+ for (const effort of efforts) {
549
+ lines.push(`- \`${effort}\``);
550
+ }
551
+ lines.push("", "点击按钮切换 effort,或输入 `/effort clear` 恢复默认");
552
+ }
553
+ else {
554
+ lines.push("", "当前 agent 不支持 effort 切换。");
555
+ }
556
+ const buttons = [];
557
+ for (const effort of efforts.slice(0, 20)) {
558
+ buttons.push({
559
+ text: `/effort ${effort}`,
560
+ value: JSON.stringify({ cmd: `/effort ${effort}` }),
561
+ type: "primary",
562
+ });
563
+ }
564
+ return JSON.stringify({
565
+ config: { wide_screen_mode: true },
566
+ header: { template: "blue", title: { content: `Effort 切换${toolLabel}`, tag: "plain_text" } },
567
+ elements: [
568
+ { tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
569
+ { tag: "hr" },
570
+ buildButtons(buttons),
571
+ ],
572
+ });
573
+ }
@@ -0,0 +1,18 @@
1
+ import { getChatGptSubscriptionStatus } from "./chatgpt-subscription.js";
2
+ export const CHATGPT_SUBSCRIPTION_PATH = "/api/chatgpt/subscription";
3
+ function jsonReply(res, code, data) {
4
+ res.writeHead(code, { "Content-Type": "application/json; charset=utf-8" });
5
+ res.end(JSON.stringify(data));
6
+ }
7
+ export async function handleChatGptSubscriptionRequest(req, res) {
8
+ const method = req.method ?? "GET";
9
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
10
+ if (url.pathname !== CHATGPT_SUBSCRIPTION_PATH)
11
+ return false;
12
+ if (method !== "POST") {
13
+ jsonReply(res, 405, { ok: false, code: "method_not_allowed", reason: "Use POST." });
14
+ return true;
15
+ }
16
+ jsonReply(res, 200, await getChatGptSubscriptionStatus());
17
+ return true;
18
+ }