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,84 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { isAgentTool } from "../../agent-tool.js";
6
+ export class JsonMainAgentBindingRepository {
7
+ rootDir;
8
+ operationQueue = Promise.resolve();
9
+ constructor(options = {}) {
10
+ this.rootDir = options.rootDir ?? join(homedir(), ".chatccc", "agent-team", "main-agent-bindings");
11
+ }
12
+ async get(projectId) {
13
+ try {
14
+ return parseBinding(JSON.parse(await readFile(this.pathFor(projectId), "utf8")));
15
+ }
16
+ catch (err) {
17
+ if (err.code === "ENOENT")
18
+ return null;
19
+ throw err;
20
+ }
21
+ }
22
+ async save(binding) {
23
+ const parsed = parseBinding(binding);
24
+ await this.exclusive(async () => {
25
+ await mkdir(this.rootDir, { recursive: true });
26
+ const path = this.pathFor(parsed.projectId);
27
+ const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
28
+ await writeFile(tempPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
29
+ try {
30
+ await rename(tempPath, path);
31
+ }
32
+ catch (err) {
33
+ await unlink(tempPath).catch(() => { });
34
+ throw err;
35
+ }
36
+ });
37
+ }
38
+ pathFor(projectId) {
39
+ if (!/^[a-zA-Z0-9_-]+$/.test(projectId))
40
+ throw new Error(`Invalid project id: ${projectId}`);
41
+ return join(this.rootDir, `${projectId}.json`);
42
+ }
43
+ async exclusive(operation) {
44
+ const previous = this.operationQueue;
45
+ let release;
46
+ this.operationQueue = new Promise((resolveQueue) => { release = resolveQueue; });
47
+ await previous;
48
+ try {
49
+ return await operation();
50
+ }
51
+ finally {
52
+ release();
53
+ }
54
+ }
55
+ }
56
+ function parseBinding(value) {
57
+ if (!value || typeof value !== "object" || Array.isArray(value))
58
+ throw new Error("Invalid main Agent binding");
59
+ const binding = value;
60
+ if (binding.schemaVersion !== 1)
61
+ throw new Error("Unsupported main Agent binding schema");
62
+ if (typeof binding.projectId !== "string" || !binding.projectId)
63
+ throw new Error("Main Agent binding is missing projectId");
64
+ if (binding.platform !== "feishu")
65
+ throw new Error("Unsupported main Agent platform");
66
+ if (!isAgentTool(binding.agentId))
67
+ throw new Error("Unsupported main Agent tool");
68
+ if (binding.namingPolicy !== "project-fixed")
69
+ throw new Error("Unsupported main Agent naming policy");
70
+ if (!["provisioning", "ready", "error"].includes(binding.status)) {
71
+ throw new Error("Unsupported main Agent binding status");
72
+ }
73
+ if (typeof binding.ownerOpenId !== "string" || !binding.ownerOpenId)
74
+ throw new Error("Main Agent binding is missing ownerOpenId");
75
+ if (typeof binding.updatedAt !== "string" || !binding.updatedAt)
76
+ throw new Error("Main Agent binding is missing updatedAt");
77
+ if (binding.chatId !== undefined && typeof binding.chatId !== "string")
78
+ throw new Error("Invalid main Agent chatId");
79
+ if (binding.sessionId !== undefined && typeof binding.sessionId !== "string")
80
+ throw new Error("Invalid main Agent sessionId");
81
+ if (binding.lastError !== undefined && typeof binding.lastError !== "string")
82
+ throw new Error("Invalid main Agent lastError");
83
+ return binding;
84
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,219 @@
1
+ export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
6
+ <title>Agent Team</title>
7
+ <style>
8
+ :root{color-scheme:light;--bg:#f5f7fb;--panel:#fff;--panel2:#fff;--soft:#f8f9fc;--line:#e3e7ef;--text:#182033;--muted:#737d91;--accent:#6758e8;--accent-soft:#eeeaff;--danger:#c2415a;--success:#23825d;--shadow:0 8px 28px rgba(26,35,58,.07)}
9
+ *{box-sizing:border-box}body{margin:0;min-height:100vh;font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei",sans-serif;background:var(--bg);color:var(--text)}button,input,textarea,select{font:inherit}.app-shell{display:grid;grid-template-columns:210px minmax(0,1fr);min-height:100vh}
10
+ .sidebar{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;gap:24px;padding:24px 18px;background:var(--panel);border-right:1px solid var(--line)}.brand{display:flex;align-items:center;gap:11px;color:var(--text);text-decoration:none;font-size:17px;font-weight:800}.brand-icon{width:34px;height:34px;display:grid;place-items:center;border-radius:11px;background:var(--accent);color:#fff;box-shadow:0 8px 18px rgba(103,88,232,.25)}.nav{display:grid;gap:7px}.nav-item{width:100%;display:flex;align-items:center;gap:11px;padding:10px 12px;border:0;border-radius:10px;background:transparent;color:var(--muted);font-size:13px;font-weight:650;text-align:left}.nav-item:hover{border:0;background:var(--soft)}.nav-item.active{background:var(--accent-soft);color:var(--accent)}.nav-icon{width:18px;text-align:center}.side-note{margin-top:auto;padding:13px;border:1px solid var(--line);background:var(--soft);border-radius:12px;font-size:11px;line-height:1.55;color:var(--muted)}.side-note strong{color:var(--success)}
11
+ .shell{min-width:320px;padding:24px}.top{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;margin-bottom:18px}.eyebrow{font-size:11px;font-weight:800;letter-spacing:.12em;color:var(--accent);margin-bottom:6px}.top h1{font-size:27px;line-height:1.2;letter-spacing:-.025em;margin:0}.top p{font-size:12px;color:var(--muted);margin:6px 0 0}.top a{height:38px;display:inline-flex;align-items:center;padding:0 13px;border:1px solid var(--line);border-radius:10px;background:var(--panel);color:var(--text);text-decoration:none;font-size:12px;font-weight:700}
12
+ .workspace-bar{display:grid;grid-template-columns:minmax(180px,220px) minmax(280px,1fr) auto auto;gap:9px;padding:11px;background:var(--panel);border:1px solid var(--line);border-radius:15px;margin-bottom:12px;box-shadow:var(--shadow)}input,textarea,select{border:1px solid var(--line);background:var(--soft);color:var(--text);border-radius:9px;padding:10px 12px;outline:none}input:focus,textarea:focus,select:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(103,88,232,.13)}button{cursor:pointer;border:1px solid var(--line);border-radius:9px;padding:10px 14px;background:var(--panel);color:var(--text);font-weight:700}button:hover{border-color:#c8cde0;background:#fbfbfe}button.primary{background:var(--accent);border-color:var(--accent);color:#fff;box-shadow:0 7px 17px rgba(103,88,232,.22)}button.primary:hover{background:#5949d6;border-color:#5949d6}button.ghost{background:var(--soft)}button.danger{background:#fff1f3;border-color:#f3c4ce;color:var(--danger)}.hidden{display:none!important}
13
+ .main-agent-bar{display:grid;grid-template-columns:minmax(180px,1fr) minmax(170px,230px) auto;align-items:center;gap:12px;padding:13px 15px;background:var(--panel);border:1px solid var(--line);border-radius:15px;margin-bottom:12px;box-shadow:var(--shadow)}.main-agent-copy{min-width:0}.main-agent-copy strong{display:block;font-size:13px;margin-bottom:3px}.main-agent-copy span{display:block;color:var(--muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.main-agent-controls{display:flex;gap:8px}.main-agent-controls select{min-width:130px}.agent-status{display:inline-flex;align-items:center;gap:6px;color:var(--muted)}.agent-status::before{content:"●";font-size:9px;color:#a3aabd}.agent-status.ready::before{color:var(--success)}.agent-status.error::before{color:var(--danger)}.agent-status.provisioning::before{color:#b7791f}
14
+ .status-row{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 3px 10px;color:var(--muted);font-size:11px}.workspace-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.save-state{min-width:78px;text-align:right;color:var(--success);font-weight:700}.save-state::before{content:"● ";font-size:9px}.save-state.saving{color:#b7791f}.save-state.error{color:var(--danger)}
15
+ .board-scroll{overflow-x:auto;padding-bottom:12px}.board{display:grid;grid-template-columns:repeat(5,minmax(230px,1fr));gap:11px;min-width:1200px}.column{display:flex;flex-direction:column;min-height:calc(100vh - 230px);background:var(--soft);border:1px solid var(--line);border-radius:15px;overflow:hidden}.column.drag-over{border-color:var(--accent);box-shadow:0 0 0 3px rgba(103,88,232,.13)}.column-head{display:flex;align-items:center;gap:8px;padding:14px 13px 9px}.column-dot{width:7px;height:7px;border-radius:50%;background:var(--accent)}.column-title{font-size:13px;font-weight:800}.count{margin-left:auto;display:inline-grid;place-items:center;min-width:23px;height:23px;padding:0 7px;border-radius:999px;background:var(--panel);border:1px solid var(--line);color:var(--muted);font-size:10px}.task-list{flex:1;min-height:100px;padding:4px 10px 10px}.task{position:relative;background:var(--panel2);border:1px solid var(--line);border-radius:11px;padding:12px;margin:8px 0;box-shadow:0 4px 15px rgba(34,42,65,.045);cursor:grab}.task:hover{border-color:#d1d5e1;box-shadow:0 8px 22px rgba(34,42,65,.08)}.task:active{cursor:grabbing}.task.dragging{opacity:.35}.task h3{font-size:13px;line-height:1.45;margin:0 42px 6px 0}.task p{font-size:11px;line-height:1.55;color:var(--muted);margin:0;white-space:pre-wrap;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}.task-actions{display:flex;gap:4px;position:absolute;right:7px;top:7px;opacity:0;transition:opacity .15s}.task:hover .task-actions,.task:focus-within .task-actions{opacity:1}.icon-btn{padding:3px 6px;border-radius:6px;background:var(--soft);color:var(--muted);font-size:10px}.add{margin:0 10px 11px;background:transparent;border:1px dashed #c7cdd9;color:var(--muted);font-size:11px}.empty{padding:20px 8px;text-align:center;color:#9aa3b4;font-size:11px}
16
+ .modal-backdrop{position:fixed;inset:0;z-index:20;background:rgba(24,32,51,.36);display:grid;place-items:center;padding:20px;backdrop-filter:blur(3px)}.modal{width:min(560px,100%);background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:20px;box-shadow:0 28px 90px rgba(35,43,65,.24)}.modal h2{margin:0 0 16px}.field{display:grid;gap:7px;margin-bottom:14px}.field label{font-size:13px;color:var(--text);font-weight:650}.field textarea{min-height:150px;resize:vertical}.modal-actions{display:flex;justify-content:flex-end;gap:9px}.directory-modal{width:min(820px,100%)}.directory-layout{display:grid;grid-template-columns:180px minmax(0,1fr);min-height:430px;border:1px solid var(--line);border-radius:12px;overflow:hidden;margin-bottom:16px}.directory-sidebar{padding:12px;background:var(--soft);border-right:1px solid var(--line)}.directory-label{margin:2px 6px 9px;color:var(--muted);font-size:11px;font-weight:800;letter-spacing:.05em}.directory-locations{display:grid;gap:4px}.directory-location{width:100%;padding:8px 9px;border:0;background:transparent;text-align:left;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.directory-location:hover{border:0;background:var(--accent-soft);color:var(--accent)}.directory-main{min-width:0;display:flex;flex-direction:column;padding:12px}.directory-toolbar{display:grid;grid-template-columns:auto auto minmax(0,1fr);gap:7px}.directory-toolbar button{padding:8px 11px}.directory-toolbar input{min-width:0;padding:8px 10px}.directory-list{flex:1;height:310px;overflow:auto;margin-top:10px;border:1px solid var(--line);border-radius:10px;background:var(--soft);padding:6px}.directory-row{width:100%;display:flex;align-items:center;gap:9px;padding:9px 10px;border:0;background:transparent;text-align:left;font-size:12px}.directory-row:hover{border:0;background:#fff}.directory-row.selected{border:0;background:var(--accent-soft);color:var(--accent)}.directory-folder-icon{color:#b7791f;font-size:15px}.directory-empty{padding:46px 16px;text-align:center;color:var(--muted);font-size:12px}.directory-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:10px;color:var(--muted);font-size:11px}.directory-footer label{display:flex;align-items:center;gap:6px;white-space:nowrap}.directory-footer input{width:15px;height:15px;margin:0}.directory-selected{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right}.toast{position:fixed;right:22px;bottom:22px;z-index:30;max-width:min(420px,calc(100vw - 44px));padding:12px 15px;border-radius:10px;background:#253047;color:#fff;border:1px solid #35415a;box-shadow:0 16px 50px rgba(35,43,65,.25)}.toast.error{border-color:#ef9bad;background:#9f2942}
17
+ @media(max-width:1100px){.app-shell{grid-template-columns:78px minmax(0,1fr)}.sidebar{padding:22px 12px;align-items:center}.brand span:last-child,.nav-item span:last-child,.side-note{display:none}.nav-item{width:45px;height:45px;justify-content:center;padding:0}.workspace-bar{grid-template-columns:1fr 1fr}.workspace-bar input{grid-column:1/-1}.main-agent-bar{grid-template-columns:1fr auto}.main-agent-copy{grid-column:1/-1}.board{min-width:1200px}}
18
+ @media(max-width:700px){.app-shell{display:block}.sidebar{display:none}.shell{padding:14px}.top{align-items:flex-start}.workspace-bar{grid-template-columns:1fr}.workspace-bar input{grid-column:auto}.main-agent-bar{grid-template-columns:1fr}.main-agent-copy{grid-column:auto}.main-agent-controls{display:grid;grid-template-columns:1fr auto}.main-agent-controls select{min-width:0}.board{min-width:1180px}.directory-layout{grid-template-columns:1fr}.directory-sidebar{border-right:0;border-bottom:1px solid var(--line)}.directory-locations{display:flex;overflow-x:auto}.directory-location{width:auto;min-width:max-content}.directory-toolbar{grid-template-columns:auto auto 1fr}.directory-list{height:270px}}
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <div class="app-shell">
23
+ <aside class="sidebar">
24
+ <a class="brand" href="/agent-team"><span class="brand-icon">✦</span><span>Agent Team</span></a>
25
+ <nav class="nav" aria-label="Agent Team 导航">
26
+ <span class="nav-item active"><span class="nav-icon">▦</span><span>任务看板</span></span>
27
+ <button type="button" class="nav-item" data-feature="工作目录"><span class="nav-icon">⌕</span><span>工作目录</span></button>
28
+ <button type="button" class="nav-item" data-feature="同步设置"><span class="nav-icon">↻</span><span>同步设置</span></button>
29
+ </nav>
30
+ <div class="side-note">本地优先<br>数据保存在 ~/.chatccc<br><strong>● Node 后端已连接</strong></div>
31
+ </aside>
32
+ <main class="shell">
33
+ <div class="top"><div><div class="eyebrow">LOCAL WORKSPACE</div><h1>Agent Team</h1><p>每个工作目录拥有独立的本地任务看板</p></div><a href="/">返回设置</a></div>
34
+ <section class="workspace-bar">
35
+ <select id="recent" aria-label="最近工作目录"><option value="">最近工作目录</option></select>
36
+ <input id="workspace-path" type="text" spellcheck="false" placeholder="输入本机工作目录绝对路径">
37
+ <button id="pick" class="ghost">选择文件夹</button>
38
+ <button id="relink" class="hidden">重新关联</button>
39
+ </section>
40
+ <section class="main-agent-bar" aria-label="项目主 Agent">
41
+ <div class="main-agent-copy"><strong>项目主 Agent</strong><span id="main-agent-status" class="agent-status">请先打开一个项目</span></div>
42
+ <div class="main-agent-controls"><select id="main-agent" disabled aria-label="选择主 Agent"><option value="">选择主 Agent</option></select><button id="save-main-agent" class="primary" disabled>设置</button></div>
43
+ </section>
44
+ <div class="status-row"><span id="workspace-name" class="workspace-name">尚未打开工作目录</span><span id="save-state" class="save-state">本地 JSON</span></div>
45
+ <div class="board-scroll"><section class="board" id="board">
46
+ <article class="column" data-column="brainstorm"><header class="column-head"><span class="column-dot"></span><span class="column-title">头脑风暴</span><span class="count">0</span></header><div class="task-list"></div><button class="add" data-add="brainstorm">+ 添加任务</button></article>
47
+ <article class="column" data-column="todo"><header class="column-head"><span class="column-dot"></span><span class="column-title">Todo</span><span class="count">0</span></header><div class="task-list"></div><button class="add" data-add="todo">+ 添加任务</button></article>
48
+ <article class="column" data-column="doing"><header class="column-head"><span class="column-dot"></span><span class="column-title">Doing</span><span class="count">0</span></header><div class="task-list"></div><button class="add" data-add="doing">+ 添加任务</button></article>
49
+ <article class="column" data-column="done"><header class="column-head"><span class="column-dot"></span><span class="column-title">Done</span><span class="count">0</span></header><div class="task-list"></div><button class="add" data-add="done">+ 添加任务</button></article>
50
+ <article class="column" data-column="on_hold"><header class="column-head"><span class="column-dot"></span><span class="column-title">搁置</span><span class="count">0</span></header><div class="task-list"></div><button class="add" data-add="on_hold">+ 添加任务</button></article>
51
+ </section></div>
52
+ </main>
53
+ </div>
54
+ <div id="modal" class="modal-backdrop hidden" role="dialog" aria-modal="true">
55
+ <form class="modal" id="task-form">
56
+ <h2 id="modal-title">添加任务</h2>
57
+ <div class="field"><label for="task-title">标题</label><input id="task-title" maxlength="200" required></div>
58
+ <div class="field"><label for="task-description">描述</label><textarea id="task-description" maxlength="20000"></textarea></div>
59
+ <div class="modal-actions"><button type="button" id="cancel" class="ghost">取消</button><button type="submit" class="primary">保存</button></div>
60
+ </form>
61
+ </div>
62
+ <div id="dm-modal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="dm-modal-title">
63
+ <div class="modal">
64
+ <h2 id="dm-modal-title">需要识别飞书用户</h2>
65
+ <p style="color:var(--muted);line-height:1.7;margin:0 0 16px">请先给飞书机器人私聊发送任意消息。检测到私聊后,网页会自动继续创建主 Agent 群。</p>
66
+ <div id="dm-detect-status" class="agent-status provisioning" style="margin-bottom:18px">正在等待私聊消息…</div>
67
+ <div class="modal-actions"><button type="button" id="dm-cancel" class="ghost">取消</button><button type="button" id="dm-check" class="primary">立即检测</button></div>
68
+ </div>
69
+ </div>
70
+ <div id="directory-modal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="directory-modal-title">
71
+ <div class="modal directory-modal">
72
+ <h2 id="directory-modal-title">选择工作目录</h2>
73
+ <div class="directory-layout">
74
+ <aside class="directory-sidebar"><div class="directory-label">快捷位置</div><div id="directory-locations" class="directory-locations"></div></aside>
75
+ <section class="directory-main">
76
+ <div class="directory-toolbar"><button type="button" id="directory-up" class="ghost" title="返回上级">↑</button><button type="button" id="directory-refresh" class="ghost" title="刷新">↻</button><input id="directory-path" aria-label="目录路径" spellcheck="false"></div>
77
+ <div id="directory-list" class="directory-list"><div class="directory-empty">正在读取目录…</div></div>
78
+ <div class="directory-footer"><label><input type="checkbox" id="directory-show-hidden">显示隐藏目录</label><span id="directory-selected" class="directory-selected">请选择当前目录或一个子目录</span></div>
79
+ </section>
80
+ </div>
81
+ <div class="modal-actions"><button type="button" id="directory-cancel" class="ghost">取消</button><button type="button" id="directory-confirm" class="primary">选择当前文件夹</button></div>
82
+ </div>
83
+ </div>
84
+ <div id="feature-modal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="feature-modal-title">
85
+ <div class="modal">
86
+ <h2 id="feature-modal-title">功能尚未实现</h2>
87
+ <p id="feature-modal-message" style="color:var(--muted);line-height:1.7;margin:0 0 16px"></p>
88
+ <div class="modal-actions"><button type="button" id="feature-modal-ok" class="primary">知道了</button></div>
89
+ </div>
90
+ </div>
91
+ <div id="create-board-modal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="create-board-modal-title">
92
+ <div class="modal">
93
+ <h2 id="create-board-modal-title">新建看板</h2>
94
+ <p style="color:var(--muted);line-height:1.7;margin:0 0 8px">这个路径还没有创建过看板,是否为这个路径新建看板?</p>
95
+ <p id="create-board-path" style="word-break:break-all;background:var(--soft);border:1px solid var(--line);border-radius:9px;padding:10px 12px;margin:0 0 18px"></p>
96
+ <div class="modal-actions"><button type="button" id="create-board-cancel" class="ghost">取消</button><button type="button" id="create-board-confirm" class="primary">新建看板</button></div>
97
+ </div>
98
+ </div>
99
+ <div id="toast" class="toast hidden"></div>
100
+ <script>
101
+ (function(){
102
+ var state={board:null,binding:null,workspaces:[],agentOptions:[],dragTaskId:null,modalTaskId:null,modalColumnId:null,relinkBoardId:null,pendingAgentId:null,pendingWorkspacePath:null,workspaceSelectionVersion:0,directoryLocations:[],directoryPath:null,directoryParentPath:null,directorySelectedPath:null,directoryBrowseVersion:0,contactTimer:null,contactChecking:false,mutationQueue:Promise.resolve()};
103
+ var columns=['brainstorm','todo','doing','done','on_hold'];
104
+ var recent=document.getElementById('recent');
105
+ var pathInput=document.getElementById('workspace-path');
106
+ var relinkButton=document.getElementById('relink');
107
+ var saveState=document.getElementById('save-state');
108
+ var mainAgentSelect=document.getElementById('main-agent');
109
+ var mainAgentButton=document.getElementById('save-main-agent');
110
+
111
+ async function api(path,options){
112
+ var response=await fetch(path,Object.assign({headers:{'content-type':'application/json; charset=utf-8'}},options||{}));
113
+ var data=await response.json().catch(function(){return {error:'响应不是有效 JSON'};});
114
+ if(!response.ok){var error=new Error(data.error||('HTTP '+response.status));error.code=data.code;throw error;}
115
+ return data;
116
+ }
117
+ function request(method,path,body){return api(path,{method:method,body:body===undefined?undefined:JSON.stringify(body)});}
118
+ function setSaving(label,kind){saveState.textContent=label;saveState.className='save-state '+(kind||'');}
119
+ function toast(message,error){var el=document.getElementById('toast');el.textContent=message;el.className='toast'+(error?' error':'');clearTimeout(toast.timer);toast.timer=setTimeout(function(){el.className='toast hidden';},3500);}
120
+ function mutate(operation){
121
+ if(!state.board)return Promise.resolve(false);
122
+ state.mutationQueue=state.mutationQueue.then(async function(){
123
+ setSaving('保存中…','saving');
124
+ try{var result=await operation();state.board=result.board;if(Object.prototype.hasOwnProperty.call(result,'binding'))state.binding=result.binding;renderBoard();setSaving('已保存','');return true;}
125
+ catch(error){setSaving('保存失败','error');if(error.code==='revision_conflict'){try{await reloadBoard();toast('其他页面修改了看板,已载入最新版本',true);}catch(reloadError){toast(reloadError.message,true);}}else toast(error.message,true);return false;}
126
+ });
127
+ return state.mutationQueue;
128
+ }
129
+ async function refreshWorkspaces(selectBoardId){
130
+ var data=await api('/api/agent-team/workspaces');state.workspaces=data.workspaces||[];state.agentOptions=data.agentOptions||[];recent.innerHTML='<option value="">最近工作目录</option>';
131
+ state.workspaces.forEach(function(item){var option=document.createElement('option');option.value=item.boardId;option.textContent=(item.exists?'':'⚠ ')+item.workspacePath;recent.appendChild(option);});
132
+ if(selectBoardId)recent.value=selectBoardId;
133
+ return data.defaultWorkspace;
134
+ }
135
+ async function openWorkspace(path,selectionVersion){
136
+ if(!path||!path.trim())return toast('请输入工作目录',true);
137
+ var version=selectionVersion===undefined?++state.workspaceSelectionVersion:selectionVersion;
138
+ await state.mutationQueue;
139
+ setSaving('加载中…','saving');
140
+ try{var result=await request('POST','/api/agent-team/open',{workspacePath:path.trim()});if(version!==state.workspaceSelectionVersion)return;state.board=result.board;state.binding=result.binding||null;state.relinkBoardId=null;relinkButton.classList.add('hidden');pathInput.value=state.board.workspacePath;await refreshWorkspaces(state.board.boardId);if(version!==state.workspaceSelectionVersion)return;renderBoard();setSaving('已保存','');}
141
+ catch(error){if(version!==state.workspaceSelectionVersion)return;setSaving('打开失败','error');toast(error.message,true);}
142
+ }
143
+ async function selectWorkspacePath(path){
144
+ if(!path||!path.trim())return toast('请输入工作目录',true);
145
+ var workspacePath=path.trim();var version=++state.workspaceSelectionVersion;
146
+ await state.mutationQueue;
147
+ setSaving('检查中…','saving');
148
+ try{var lookup=await request('POST','/api/agent-team/lookup',{workspacePath:workspacePath});if(version!==state.workspaceSelectionVersion)return;if(lookup.exists)return openWorkspace(workspacePath,version);state.pendingWorkspacePath=workspacePath;pathInput.value=workspacePath;document.getElementById('create-board-path').textContent=workspacePath;document.getElementById('create-board-modal').classList.remove('hidden');setSaving(state.board?'已保存':'本地 JSON','');}
149
+ catch(error){if(version!==state.workspaceSelectionVersion)return;setSaving('打开失败','error');toast(error.message,true);}
150
+ }
151
+ function closeCreateBoardModal(keepPath){document.getElementById('create-board-modal').classList.add('hidden');state.pendingWorkspacePath=null;if(!keepPath&&state.board)pathInput.value=state.board.workspacePath;}
152
+ function showFeatureModal(feature){document.getElementById('feature-modal-message').textContent='“'+feature+'”功能还未实现,后续版本会开放。';document.getElementById('feature-modal').classList.remove('hidden');}
153
+ function closeFeatureModal(){document.getElementById('feature-modal').classList.add('hidden');}
154
+ function renderDirectoryLocations(){var root=document.getElementById('directory-locations');root.innerHTML='';state.directoryLocations.forEach(function(location){var button=document.createElement('button');button.type='button';button.className='directory-location';button.textContent=(location.kind==='drive'?'▣ ':'⌂ ')+location.label;button.title=location.path;button.addEventListener('click',function(){browseDirectory(location.path);});root.appendChild(button);});}
155
+ function renderDirectoryEntries(entries){var root=document.getElementById('directory-list');root.innerHTML='';if(!entries.length){var empty=document.createElement('div');empty.className='directory-empty';empty.textContent='这个目录中没有可显示的子目录';root.appendChild(empty);return;}entries.forEach(function(entry){var row=document.createElement('button');row.type='button';row.className='directory-row';row.title='双击进入 '+entry.path;var icon=document.createElement('span');icon.className='directory-folder-icon';icon.textContent='▰';var name=document.createElement('span');name.textContent=entry.name;row.append(icon,name);row.addEventListener('click',function(){state.directorySelectedPath=entry.path;root.querySelectorAll('.directory-row').forEach(function(item){item.classList.remove('selected');});row.classList.add('selected');document.getElementById('directory-selected').textContent=entry.path;document.getElementById('directory-confirm').textContent='选择此文件夹';});row.addEventListener('dblclick',function(){browseDirectory(entry.path);});root.appendChild(row);});}
156
+ async function browseDirectory(path){if(!path)return false;var version=++state.directoryBrowseVersion;var root=document.getElementById('directory-list');root.innerHTML='<div class="directory-empty">正在读取目录…</div>';document.getElementById('directory-path').value=path;try{var result=await request('POST','/api/agent-team/filesystem/directories',{path:path,showHidden:document.getElementById('directory-show-hidden').checked});if(version!==state.directoryBrowseVersion)return false;state.directoryPath=result.directory.path;state.directoryParentPath=result.directory.parentPath;state.directorySelectedPath=null;document.getElementById('directory-path').value=result.directory.path;document.getElementById('directory-up').disabled=!result.directory.parentPath;document.getElementById('directory-selected').textContent=result.directory.path;document.getElementById('directory-confirm').textContent='选择当前文件夹';renderDirectoryEntries(result.directory.entries||[]);return true;}catch(error){if(version!==state.directoryBrowseVersion)return false;root.innerHTML='';var empty=document.createElement('div');empty.className='directory-empty';empty.textContent=error.message;root.appendChild(empty);return false;}}
157
+ async function openDirectoryModal(){state.directoryPath=null;state.directoryParentPath=null;state.directorySelectedPath=null;document.getElementById('directory-modal').classList.remove('hidden');document.getElementById('directory-locations').innerHTML='';document.getElementById('directory-list').innerHTML='<div class="directory-empty">正在读取目录…</div>';try{var result=await api('/api/agent-team/filesystem/locations');state.directoryLocations=result.locations||[];renderDirectoryLocations();var candidate=pathInput.value.trim()||(state.board&&state.board.workspacePath)||(state.directoryLocations[0]&&state.directoryLocations[0].path);var opened=await browseDirectory(candidate);if(!opened&&state.directoryLocations[0]&&state.directoryLocations[0].path!==candidate)await browseDirectory(state.directoryLocations[0].path);}catch(error){var root=document.getElementById('directory-list');root.innerHTML='';var empty=document.createElement('div');empty.className='directory-empty';empty.textContent=error.message;root.appendChild(empty);}}
158
+ function closeDirectoryModal(){state.directoryBrowseVersion++;document.getElementById('directory-modal').classList.add('hidden');}
159
+ async function confirmDirectorySelection(){var target=state.directorySelectedPath||state.directoryPath;if(!target)return;var button=document.getElementById('directory-confirm');button.disabled=true;try{var result=await request('POST','/api/agent-team/filesystem/validate-directory',{path:target});closeDirectoryModal();pathInput.value=result.path;if(!state.relinkBoardId)selectWorkspacePath(result.path);}catch(error){toast(error.message,true);}finally{button.disabled=false;}}
160
+ async function reloadBoard(){if(!state.board)return;var result=await api('/api/agent-team/boards/'+encodeURIComponent(state.board.boardId));state.board=result.board;state.binding=result.binding||null;renderBoard();}
161
+ function activeTasks(columnId){return state.board?state.board.tasks.filter(function(task){return !task.deletedAt&&task.columnId===columnId;}).sort(function(a,b){return a.order-b.order;}):[];}
162
+ function renderBoard(){
163
+ document.getElementById('workspace-name').textContent=state.board?state.board.workspacePath:'尚未打开工作目录';
164
+ renderMainAgent();
165
+ columns.forEach(function(columnId){var column=document.querySelector('[data-column="'+columnId+'"]');var list=column.querySelector('.task-list');var tasks=activeTasks(columnId);column.querySelector('.count').textContent=String(tasks.length);list.innerHTML='';
166
+ if(!tasks.length){var empty=document.createElement('div');empty.className='empty';empty.textContent='暂无任务';list.appendChild(empty);}
167
+ tasks.forEach(function(task){var card=document.createElement('article');card.className='task';card.draggable=true;card.dataset.taskId=task.id;
168
+ var title=document.createElement('h3');title.textContent=task.title;card.appendChild(title);if(task.description){var desc=document.createElement('p');desc.textContent=task.description;card.appendChild(desc);}
169
+ var actions=document.createElement('div');actions.className='task-actions';var edit=document.createElement('button');edit.type='button';edit.className='icon-btn';edit.textContent='编辑';edit.addEventListener('click',function(event){event.stopPropagation();showEdit(task);});var del=document.createElement('button');del.type='button';del.className='icon-btn';del.textContent='删';del.addEventListener('click',function(event){event.stopPropagation();removeTask(task);});actions.append(edit,del);card.appendChild(actions);
170
+ card.addEventListener('dblclick',function(){showEdit(task);});card.addEventListener('dragstart',function(event){state.dragTaskId=task.id;card.classList.add('dragging');event.dataTransfer.effectAllowed='move';});card.addEventListener('dragend',function(){state.dragTaskId=null;card.classList.remove('dragging');document.querySelectorAll('.column').forEach(function(el){el.classList.remove('drag-over');});});list.appendChild(card);
171
+ });
172
+ });
173
+ }
174
+ function renderMainAgent(){
175
+ var selected=state.board?(state.board.primaryAgentId||(state.binding&&state.binding.agentId)||''):'';mainAgentSelect.innerHTML='<option value="">选择主 Agent</option>';
176
+ state.agentOptions.forEach(function(agent){var option=document.createElement('option');option.value=agent.id;option.textContent=agent.label;mainAgentSelect.appendChild(option);});
177
+ mainAgentSelect.value=selected;mainAgentSelect.disabled=!state.board;mainAgentButton.disabled=!state.board||!mainAgentSelect.value;
178
+ var status=document.getElementById('main-agent-status');var binding=state.binding;
179
+ if(!state.board){status.textContent='请先打开一个项目';status.className='agent-status';mainAgentButton.textContent='设置';return;}
180
+ if(!selected){status.textContent='尚未设置;设置后将创建“主Agent-'+state.board.workspacePath.split(/[\\/]/).pop()+'”群聊';status.className='agent-status';mainAgentButton.textContent='设置';return;}
181
+ if(binding&&binding.status==='ready'){status.textContent='主 Agent 群已就绪';status.className='agent-status ready';}
182
+ else if(binding&&binding.status==='error'){status.textContent='创建失败,可重新设置后重试';status.className='agent-status error';}
183
+ else{status.textContent='正在准备主 Agent 群';status.className='agent-status provisioning';}
184
+ mainAgentButton.textContent='保存';
185
+ }
186
+ function closeDmModal(){document.getElementById('dm-modal').classList.add('hidden');if(state.contactTimer){clearInterval(state.contactTimer);state.contactTimer=null;}state.contactChecking=false;}
187
+ function showDmModal(){document.getElementById('dm-modal').classList.remove('hidden');document.getElementById('dm-detect-status').textContent='正在等待私聊消息…';if(state.contactTimer)clearInterval(state.contactTimer);state.contactTimer=setInterval(checkFeishuContact,1500);}
188
+ async function checkFeishuContact(){if(state.contactChecking)return;state.contactChecking=true;try{var result=await api('/api/agent-team/feishu-contact');if(result.contact){var pending=state.pendingAgentId;closeDmModal();toast('已识别飞书私聊用户,继续创建主 Agent 群');if(pending)saveMainAgent(pending);}}catch(error){document.getElementById('dm-detect-status').textContent='检测失败:'+error.message;}finally{state.contactChecking=false;}}
189
+ function saveMainAgent(agentId){
190
+ if(!state.board||!agentId)return;state.pendingAgentId=agentId;
191
+ state.mutationQueue=state.mutationQueue.then(async function(){setSaving('创建主 Agent…','saving');mainAgentButton.disabled=true;try{var result=await request('POST','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/main-agent',{expectedRevision:state.board.revision,agentId:agentId});state.board=result.board;state.binding=result.binding;state.pendingAgentId=null;renderBoard();setSaving('已保存','');toast('主 Agent 群已就绪');return true;}catch(error){setSaving('设置失败','error');renderBoard();if(error.code==='feishu_dm_required')showDmModal();else if(error.code==='revision_conflict'){try{await reloadBoard();toast('其他页面修改了项目,已载入最新版本',true);}catch(reloadError){toast(reloadError.message,true);}}else toast(error.message,true);return false;}});return state.mutationQueue;
192
+ }
193
+ function showCreate(columnId){if(!state.board)return toast('请先打开一个工作目录',true);state.modalTaskId=null;state.modalColumnId=columnId;document.getElementById('modal-title').textContent='添加任务';document.getElementById('task-title').value='';document.getElementById('task-description').value='';document.getElementById('modal').classList.remove('hidden');setTimeout(function(){document.getElementById('task-title').focus();},0);}
194
+ function showEdit(task){state.modalTaskId=task.id;state.modalColumnId=task.columnId;document.getElementById('modal-title').textContent='编辑任务';document.getElementById('task-title').value=task.title;document.getElementById('task-description').value=task.description;document.getElementById('modal').classList.remove('hidden');}
195
+ function closeModal(){document.getElementById('modal').classList.add('hidden');state.modalTaskId=null;}
196
+ function removeTask(task){if(!confirm('删除任务“'+task.title+'”?'))return;mutate(function(){return request('DELETE','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks/'+encodeURIComponent(task.id),{expectedRevision:state.board.revision});});}
197
+
198
+ document.querySelectorAll('[data-add]').forEach(function(button){button.addEventListener('click',function(){showCreate(button.dataset.add);});});
199
+ document.querySelectorAll('.column').forEach(function(column){
200
+ column.addEventListener('dragover',function(event){if(!state.dragTaskId)return;event.preventDefault();column.classList.add('drag-over');event.dataTransfer.dropEffect='move';});column.addEventListener('dragleave',function(event){if(!column.contains(event.relatedTarget))column.classList.remove('drag-over');});
201
+ column.addEventListener('drop',function(event){event.preventDefault();column.classList.remove('drag-over');if(!state.dragTaskId||!state.board)return;var columnId=column.dataset.column;var cards=Array.from(column.querySelectorAll('.task:not(.dragging)'));var index=cards.findIndex(function(card){return event.clientY<card.getBoundingClientRect().top+card.getBoundingClientRect().height/2;});if(index<0)index=cards.length;var taskId=state.dragTaskId;mutate(function(){return request('POST','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks/'+encodeURIComponent(taskId)+'/move',{expectedRevision:state.board.revision,columnId:columnId,index:index});});});
202
+ });
203
+ document.getElementById('task-form').addEventListener('submit',function(event){event.preventDefault();var title=document.getElementById('task-title').value;var description=document.getElementById('task-description').value;var taskId=state.modalTaskId;var columnId=state.modalColumnId;closeModal();mutate(function(){var root='/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks';return taskId?request('PATCH',root+'/'+encodeURIComponent(taskId),{expectedRevision:state.board.revision,title:title,description:description}):request('POST',root,{expectedRevision:state.board.revision,title:title,description:description,columnId:columnId});});});
204
+ document.getElementById('cancel').addEventListener('click',closeModal);document.getElementById('modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeModal();});
205
+ document.querySelectorAll('[data-feature]').forEach(function(button){button.addEventListener('click',function(){showFeatureModal(button.dataset.feature);});});
206
+ document.getElementById('feature-modal-ok').addEventListener('click',closeFeatureModal);document.getElementById('feature-modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeFeatureModal();});
207
+ document.getElementById('directory-cancel').addEventListener('click',closeDirectoryModal);document.getElementById('directory-modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeDirectoryModal();});document.getElementById('directory-confirm').addEventListener('click',confirmDirectorySelection);document.getElementById('directory-up').addEventListener('click',function(){if(state.directoryParentPath)browseDirectory(state.directoryParentPath);});document.getElementById('directory-refresh').addEventListener('click',function(){if(state.directoryPath)browseDirectory(state.directoryPath);});document.getElementById('directory-show-hidden').addEventListener('change',function(){if(state.directoryPath)browseDirectory(state.directoryPath);});document.getElementById('directory-path').addEventListener('keydown',function(event){if(event.key==='Enter'){event.preventDefault();browseDirectory(event.currentTarget.value);}});
208
+ document.getElementById('create-board-cancel').addEventListener('click',function(){closeCreateBoardModal(false);});document.getElementById('create-board-modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeCreateBoardModal(false);});document.getElementById('create-board-confirm').addEventListener('click',function(){var path=state.pendingWorkspacePath;if(!path)return;closeCreateBoardModal(true);openWorkspace(path);});
209
+ mainAgentSelect.addEventListener('change',function(){mainAgentButton.disabled=!state.board||!mainAgentSelect.value;});mainAgentButton.addEventListener('click',function(){saveMainAgent(mainAgentSelect.value);});
210
+ document.getElementById('dm-cancel').addEventListener('click',function(){state.pendingAgentId=null;closeDmModal();});document.getElementById('dm-check').addEventListener('click',checkFeishuContact);
211
+ pathInput.addEventListener('change',function(){if(!state.relinkBoardId)selectWorkspacePath(pathInput.value);});pathInput.addEventListener('keydown',function(event){if(event.key==='Enter'){event.preventDefault();if(!state.relinkBoardId)selectWorkspacePath(pathInput.value);}});
212
+ document.getElementById('pick').addEventListener('click',openDirectoryModal);
213
+ recent.addEventListener('change',function(){var item=state.workspaces.find(function(candidate){return candidate.boardId===recent.value;});if(!item)return;pathInput.value=item.workspacePath;if(item.exists){state.relinkBoardId=null;relinkButton.classList.add('hidden');openWorkspace(item.workspacePath);}else{state.relinkBoardId=item.boardId;relinkButton.classList.remove('hidden');api('/api/agent-team/boards/'+encodeURIComponent(item.boardId)).then(function(result){state.board=result.board;state.binding=result.binding||null;renderBoard();toast('原目录不存在,请选择新目录后重新关联',true);}).catch(function(error){toast(error.message,true);});}});
214
+ relinkButton.addEventListener('click',function(){if(!state.relinkBoardId||!state.board)return toast('请先打开或选择需要关联的看板',true);mutate(function(){return request('POST','/api/agent-team/boards/'+encodeURIComponent(state.relinkBoardId)+'/relink',{expectedRevision:state.board.revision,workspacePath:pathInput.value});}).then(function(saved){if(!saved)return;refreshWorkspaces(state.board.boardId);state.relinkBoardId=null;relinkButton.classList.add('hidden');});});
215
+ refreshWorkspaces().then(function(defaultWorkspace){pathInput.value=defaultWorkspace;return selectWorkspacePath(defaultWorkspace);}).catch(function(error){toast(error.message,true);});
216
+ })();
217
+ </script>
218
+ </body>
219
+ </html>`;
@@ -0,0 +1,10 @@
1
+ export const AGENT_TOOLS = ["claude", "cursor", "codex", "ccc"];
2
+ export const AGENT_TOOL_OPTIONS = [
3
+ { id: "ccc", label: "CCC" },
4
+ { id: "claude", label: "Claude" },
5
+ { id: "cursor", label: "Cursor" },
6
+ { id: "codex", label: "Codex" },
7
+ ];
8
+ export function isAgentTool(value) {
9
+ return typeof value === "string" && AGENT_TOOLS.includes(value);
10
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Feishu card action callbacks do not include the chat type. Recover it from
3
+ * the persisted binding so buttons clicked in a private chat stay in p2p.
4
+ */
5
+ export function resolveFeishuCardActionChatType(chatId, registry) {
6
+ return registry[chatId]?.chatType === "p2p" ? "p2p" : "group";
7
+ }
@@ -0,0 +1,101 @@
1
+ function isObject(value) {
2
+ return !!value && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+ function stringValue(value) {
5
+ return typeof value === "string" && value.trim() ? value.trim() : null;
6
+ }
7
+ function textContent(value) {
8
+ if (typeof value === "string")
9
+ return stringValue(value);
10
+ if (!isObject(value))
11
+ return null;
12
+ return stringValue(value.content);
13
+ }
14
+ function parseButtonValue(value) {
15
+ if (typeof value !== "string")
16
+ return value;
17
+ try {
18
+ return JSON.parse(value);
19
+ }
20
+ catch {
21
+ return value;
22
+ }
23
+ }
24
+ function commandFromValue(value) {
25
+ const parsed = parseButtonValue(value);
26
+ if (typeof parsed === "string") {
27
+ const trimmed = parsed.trim();
28
+ return trimmed.startsWith("/") ? trimmed : null;
29
+ }
30
+ if (!isObject(parsed))
31
+ return null;
32
+ const cmd = stringValue(parsed.cmd);
33
+ if (cmd)
34
+ return cmd.startsWith("/") ? cmd : `/${cmd}`;
35
+ const action = stringValue(parsed.action);
36
+ if (!action)
37
+ return null;
38
+ if (action === "cd") {
39
+ const path = stringValue(parsed.path);
40
+ return path ? `/cd ${path}` : "/cd";
41
+ }
42
+ return action.startsWith("/") ? action : `/${action}`;
43
+ }
44
+ function buttonText(element) {
45
+ const label = textContent(element.text);
46
+ const command = commandFromValue(element.value);
47
+ if (label && command)
48
+ return `${label}: ${command}`;
49
+ return label ?? command;
50
+ }
51
+ function elementToText(element) {
52
+ if (!isObject(element))
53
+ return [];
54
+ const tag = stringValue(element.tag);
55
+ if (tag === "div") {
56
+ const content = textContent(element.text);
57
+ return content ? [content] : [];
58
+ }
59
+ if (tag === "markdown") {
60
+ const content = stringValue(element.content);
61
+ return content ? [content] : [];
62
+ }
63
+ if (tag === "button") {
64
+ const content = buttonText(element);
65
+ return content ? [content] : [];
66
+ }
67
+ if (tag === "action" && Array.isArray(element.actions)) {
68
+ return element.actions.flatMap(elementToText);
69
+ }
70
+ return [];
71
+ }
72
+ function rootElements(card) {
73
+ if (Array.isArray(card.elements))
74
+ return card.elements;
75
+ if (isObject(card.body) && Array.isArray(card.body.elements)) {
76
+ return card.body.elements;
77
+ }
78
+ return [];
79
+ }
80
+ export function cardJsonToPlainText(cardJson) {
81
+ let card;
82
+ try {
83
+ card = JSON.parse(cardJson);
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ if (!isObject(card))
89
+ return null;
90
+ const title = isObject(card.header) && isObject(card.header.title)
91
+ ? textContent(card.header.title)
92
+ : null;
93
+ const lines = rootElements(card).flatMap(elementToText);
94
+ const parts = title ? [`# ${title}`, ...lines] : lines;
95
+ const text = parts
96
+ .map((part) => part.trim())
97
+ .filter(Boolean)
98
+ .join("\n\n")
99
+ .trim();
100
+ return text || null;
101
+ }
@@ -0,0 +1,158 @@
1
+ import { BASE_URL, fileLog, ts } from "./config.js";
2
+ // CardKit 是飞书的卡片流式更新 API,通过 cardElement.content 端点
3
+ // 对卡片上指定 element_id 的元素进行增量内容更新,实现打字机效果。
4
+ // 与普通卡片 PATCH 的区别:PATCH 是全量替换卡片 JSON,CardKit 是
5
+ // 由飞书客户端自行渲染增量文本,用户看到的是逐字出现的效果。
6
+ // 参考: https://open.feishu.cn/document/uYjLw4iNukDO6YDN4ITM4MjM
7
+ const CARDKIT_REQUEST_TIMEOUT_MS = 15_000;
8
+ async function fetchCardKit(url, init, operation) {
9
+ const controller = new AbortController();
10
+ const timer = setTimeout(() => controller.abort(), CARDKIT_REQUEST_TIMEOUT_MS);
11
+ try {
12
+ const resp = await fetch(url, { ...init, signal: controller.signal });
13
+ const respText = await resp.text();
14
+ return { resp, respText };
15
+ }
16
+ catch (err) {
17
+ if (err.name === "AbortError") {
18
+ console.error(`[${ts()}] [CARDIKT] ${operation} TIMEOUT after ${CARDKIT_REQUEST_TIMEOUT_MS}ms`);
19
+ fileLog.flush();
20
+ throw new Error(`${operation} timeout after ${CARDKIT_REQUEST_TIMEOUT_MS}ms`);
21
+ }
22
+ throw err;
23
+ }
24
+ finally {
25
+ clearTimeout(timer);
26
+ }
27
+ }
28
+ export async function createCardKitCard(token, cardJson) {
29
+ const body = JSON.stringify({ type: "card_json", data: cardJson });
30
+ const { resp, respText } = await fetchCardKit(`${BASE_URL}/cardkit/v1/cards`, {
31
+ method: "POST",
32
+ headers: {
33
+ Authorization: `Bearer ${token}`,
34
+ "Content-Type": "application/json",
35
+ },
36
+ body,
37
+ }, "createCard");
38
+ let data;
39
+ try {
40
+ data = JSON.parse(respText);
41
+ }
42
+ catch {
43
+ console.error(`[${ts()}] [CARDIKT] createCard: invalid JSON response (status=${resp.status}) body=${respText.slice(0, 500)}`);
44
+ fileLog.flush();
45
+ throw new Error(`CardKit create: invalid JSON response (status=${resp.status})`);
46
+ }
47
+ if (data.code !== 0) {
48
+ console.error(`[${ts()}] [CARDIKT] createCard FAIL: status=${resp.status} code=${data.code} msg="${data.msg}" body=${respText.slice(0, 500)}`);
49
+ fileLog.flush();
50
+ throw new Error(`CardKit create: [${data.code}] ${data.msg}`);
51
+ }
52
+ console.log(`[${ts()}] [CARDIKT] createCard OK cardId=${data.data?.card_id ?? "(none)"}`);
53
+ return data.data?.card_id ?? "";
54
+ }
55
+ export async function setCardKitSettings(token, cardId, settings, sequence) {
56
+ const { resp, respText } = await fetchCardKit(`${BASE_URL}/cardkit/v1/cards/${cardId}/settings`, {
57
+ method: "PATCH",
58
+ headers: {
59
+ Authorization: `Bearer ${token}`,
60
+ "Content-Type": "application/json",
61
+ },
62
+ body: JSON.stringify({ settings: JSON.stringify(settings), sequence }),
63
+ }, `settings cardId=${cardId} seq=${sequence}`);
64
+ let data;
65
+ try {
66
+ data = JSON.parse(respText);
67
+ }
68
+ catch {
69
+ console.error(`[${ts()}] [CARDIKT] settings FAIL: invalid JSON cardId=${cardId} seq=${sequence} status=${resp.status}`);
70
+ fileLog.flush();
71
+ throw new Error(`CardKit settings: invalid JSON (status=${resp.status})`);
72
+ }
73
+ if (data.code !== 0) {
74
+ console.error(`[${ts()}] [CARDIKT] settings FAIL: cardId=${cardId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
75
+ fileLog.flush();
76
+ throw new Error(`CardKit settings: [${data.code}] ${data.msg}`);
77
+ }
78
+ console.log(`[${ts()}] [CARDIKT] settings OK cardId=${cardId} seq=${sequence}`);
79
+ }
80
+ export async function streamCardKitElement(token, cardId, elementId, content, sequence) {
81
+ const { resp, respText } = await fetchCardKit(`${BASE_URL}/cardkit/v1/cards/${cardId}/elements/${elementId}/content`, {
82
+ method: "PUT",
83
+ headers: {
84
+ Authorization: `Bearer ${token}`,
85
+ "Content-Type": "application/json",
86
+ },
87
+ body: JSON.stringify({ content, sequence }),
88
+ }, `streamElement cardId=${cardId} element=${elementId} seq=${sequence}`);
89
+ let data;
90
+ try {
91
+ data = JSON.parse(respText);
92
+ }
93
+ catch {
94
+ console.error(`[${ts()}] [CARDIKT] streamElement FAIL: invalid JSON cardId=${cardId} element=${elementId} seq=${sequence} status=${resp.status}`);
95
+ fileLog.flush();
96
+ throw new Error(`CardKit stream: invalid JSON (status=${resp.status})`);
97
+ }
98
+ if (data.code !== 0) {
99
+ console.error(`[${ts()}] [CARDIKT] streamElement FAIL: cardId=${cardId} element=${elementId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
100
+ fileLog.flush();
101
+ throw new Error(`CardKit stream: [${data.code}] ${data.msg}`);
102
+ }
103
+ // success log is intentionally sparse — uncomment to debug streaming throughput
104
+ // console.log(`[${ts()}] [CARDIKT] streamElement OK cardId=${cardId} seq=${sequence}`);
105
+ }
106
+ export async function updateCardKitCard(token, cardId, cardJson, sequence) {
107
+ const { resp, respText } = await fetchCardKit(`${BASE_URL}/cardkit/v1/cards/${cardId}`, {
108
+ method: "PUT",
109
+ headers: {
110
+ Authorization: `Bearer ${token}`,
111
+ "Content-Type": "application/json",
112
+ },
113
+ body: JSON.stringify({ card: { type: "card_json", data: cardJson }, sequence }),
114
+ }, `updateCard cardId=${cardId} seq=${sequence}`);
115
+ let data;
116
+ try {
117
+ data = JSON.parse(respText);
118
+ }
119
+ catch {
120
+ console.error(`[${ts()}] [CARDIKT] updateCard FAIL: invalid JSON cardId=${cardId} seq=${sequence} status=${resp.status}`);
121
+ fileLog.flush();
122
+ throw new Error(`CardKit update: invalid JSON (status=${resp.status})`);
123
+ }
124
+ if (data.code !== 0) {
125
+ console.error(`[${ts()}] [CARDIKT] updateCard FAIL: cardId=${cardId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
126
+ fileLog.flush();
127
+ throw new Error(`CardKit update: [${data.code}] ${data.msg}`);
128
+ }
129
+ console.log(`[${ts()}] [CARDIKT] updateCard OK cardId=${cardId} seq=${sequence}`);
130
+ }
131
+ export async function sendCardKitMessage(token, chatId, cardId) {
132
+ const payload = { receive_id: chatId, msg_type: "interactive", content: JSON.stringify({ type: "card", data: { card_id: cardId } }) };
133
+ const url = `${BASE_URL}/im/v1/messages?receive_id_type=chat_id`;
134
+ const { resp, respText } = await fetchCardKit(url, {
135
+ method: "POST",
136
+ headers: {
137
+ Authorization: `Bearer ${token}`,
138
+ "Content-Type": "application/json",
139
+ },
140
+ body: JSON.stringify(payload),
141
+ }, `sendMessage cardId=${cardId} chatId=${chatId}`);
142
+ let data;
143
+ try {
144
+ data = JSON.parse(respText);
145
+ }
146
+ catch {
147
+ console.error(`[${ts()}] [CARDIKT] sendMessage FAIL: invalid JSON cardId=${cardId} chatId=${chatId} status=${resp.status}`);
148
+ fileLog.flush();
149
+ throw new Error(`sendCardKitMessage: invalid JSON (status=${resp.status})`);
150
+ }
151
+ if (data.code !== 0) {
152
+ console.error(`[${ts()}] [CARDIKT] sendMessage FAIL: status=${resp.status} code=${data.code} msg="${data.msg}" cardId=${cardId} chatId=${chatId}`);
153
+ fileLog.flush();
154
+ throw new Error(`[${data.code}] ${data.msg}`);
155
+ }
156
+ console.log(`[${ts()}] [CARDIKT] sendMessage OK cardId=${cardId} chatId=${chatId} msgId=${data.data?.message_id ?? "(none)"}`);
157
+ return data.data?.message_id ?? "";
158
+ }