chatccc 0.2.252 → 0.2.253

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 (280) hide show
  1. package/README.md +1 -1
  2. package/bin/cccagent.mjs +12 -3
  3. package/bin/chatccc.mjs +15 -6
  4. package/deepccc-agent/os-prompts/darwin.md +8 -8
  5. package/deepccc-agent/os-prompts/linux.md +8 -8
  6. package/deepccc-agent/os-prompts/win32.md +11 -11
  7. package/deepccc-agent/package.json +65 -65
  8. package/dist/deepccc-agent/src/cli.js +634 -0
  9. package/dist/deepccc-agent/src/config.js +76 -0
  10. package/dist/deepccc-agent/src/context.js +348 -0
  11. package/dist/deepccc-agent/src/file-log.js +34 -0
  12. package/dist/deepccc-agent/src/file-tools.js +1208 -0
  13. package/dist/deepccc-agent/src/index.js +571 -0
  14. package/dist/deepccc-agent/src/permissions.js +170 -0
  15. package/dist/deepccc-agent/src/privacy.js +124 -0
  16. package/dist/deepccc-agent/src/proc-tree-kill.js +60 -0
  17. package/dist/deepccc-agent/src/progress/cards-helpers.js +70 -0
  18. package/dist/deepccc-agent/src/progress/reducer.js +102 -0
  19. package/dist/deepccc-agent/src/progress/terminal-renderer.js +264 -0
  20. package/dist/deepccc-agent/src/progress/view.js +30 -0
  21. package/dist/deepccc-agent/src/raw-stream-log.js +106 -0
  22. package/dist/deepccc-agent/src/session-search.js +276 -0
  23. package/dist/deepccc-agent/src/session-select.js +23 -0
  24. package/dist/deepccc-agent/src/sigint.js +26 -0
  25. package/dist/deepccc-agent/src/skills.js +178 -0
  26. package/dist/deepccc-agent/src/web-tools.js +246 -0
  27. package/dist/src/adapters/adapter-interface.js +19 -0
  28. package/dist/src/adapters/ccc-adapter.js +112 -0
  29. package/dist/src/adapters/claude-adapter.js +497 -0
  30. package/dist/src/adapters/claude-session-meta-store.js +92 -0
  31. package/dist/src/adapters/codex-adapter.js +279 -0
  32. package/dist/src/adapters/codex-session-meta-store.js +94 -0
  33. package/dist/src/adapters/cursor-adapter.js +491 -0
  34. package/dist/src/adapters/cursor-session-meta-store.js +116 -0
  35. package/dist/src/adapters/jsonl-stream.js +104 -0
  36. package/{src/adapters/proc-tree-kill.ts → dist/src/adapters/proc-tree-kill.js} +94 -97
  37. package/dist/src/adapters/raw-stream-log.js +106 -0
  38. package/dist/src/adapters/resource-monitor.js +113 -0
  39. package/dist/src/agent-activity.js +133 -0
  40. package/dist/src/agent-delegate-task-rpc.js +129 -0
  41. package/dist/src/agent-delegate-task.js +48 -0
  42. package/dist/src/agent-file-rpc.js +152 -0
  43. package/dist/src/agent-image-rpc.js +148 -0
  44. package/dist/src/agent-platform-routing.js +13 -0
  45. package/dist/src/agent-reload-config-rpc.js +23 -0
  46. package/dist/src/agent-rpc-body.js +87 -0
  47. package/dist/src/agent-stop-stuck.js +110 -0
  48. package/dist/src/card-action-routing.js +7 -0
  49. package/dist/src/card-plain-text.js +101 -0
  50. package/dist/src/cardkit.js +158 -0
  51. package/dist/src/cards.js +573 -0
  52. package/dist/src/chatgpt-subscription-rpc.js +18 -0
  53. package/dist/src/chatgpt-subscription.js +199 -0
  54. package/dist/src/chrome-devtools-guard.js +238 -0
  55. package/dist/src/claude-sdk-installer.js +249 -0
  56. package/dist/src/codex-reset-actions.js +143 -0
  57. package/dist/src/config-utils.js +149 -0
  58. package/dist/src/config.js +804 -0
  59. package/dist/src/cursor-usage.js +77 -0
  60. package/dist/src/exit-banner.js +28 -0
  61. package/dist/src/feishu-api.js +1404 -0
  62. package/dist/src/feishu-message-ingress.js +137 -0
  63. package/dist/src/feishu-platform.js +97 -0
  64. package/dist/src/format-message.js +252 -0
  65. package/dist/src/git-command.js +155 -0
  66. package/dist/src/im-skills.js +121 -0
  67. package/dist/src/index.js +833 -0
  68. package/dist/src/litellm-proxy.js +300 -0
  69. package/dist/src/orchestrator.js +2078 -0
  70. package/dist/src/package-root.js +26 -0
  71. package/dist/src/platform-adapter.js +7 -0
  72. package/dist/src/platform-startup.js +6 -0
  73. package/dist/src/privacy.js +100 -0
  74. package/dist/src/progress/reducer.js +102 -0
  75. package/dist/src/progress/terminal-renderer.js +264 -0
  76. package/dist/src/progress/view.js +30 -0
  77. package/dist/src/response-stall.js +14 -0
  78. package/dist/src/runtime-entry.js +13 -0
  79. package/dist/src/runtime-reload.js +19 -0
  80. package/dist/src/session-chat-binding.js +183 -0
  81. package/dist/src/session-name.js +7 -0
  82. package/dist/src/session.js +2144 -0
  83. package/dist/src/shared-prefix.js +16 -0
  84. package/dist/src/shared.js +493 -0
  85. package/dist/src/sim-agent.js +105 -0
  86. package/dist/src/sim-platform.js +142 -0
  87. package/dist/src/sim-store.js +231 -0
  88. package/dist/src/simplify.js +99 -0
  89. package/dist/src/startup-lifecycle.js +209 -0
  90. package/dist/src/stream-state.js +141 -0
  91. package/dist/src/terminal-error.js +100 -0
  92. package/dist/src/trace.js +50 -0
  93. package/dist/src/turn-cards.js +92 -0
  94. package/dist/src/update-command-guard.js +114 -0
  95. package/{src/web-ui.ts → dist/src/web-ui.js} +749 -823
  96. package/dist/src/wechat-platform.js +545 -0
  97. package/package.json +76 -74
  98. package/deepccc-agent/LICENSE +0 -201
  99. package/deepccc-agent/bin/deepccc.mjs +0 -26
  100. package/deepccc-agent/docs/cache-hit-rate-1.jpg +0 -0
  101. package/deepccc-agent/docs/cache-hit-rate-2.jpg +0 -0
  102. package/deepccc-agent/package-lock.json +0 -2027
  103. package/deepccc-agent/src/__tests__/chat-session.test.ts +0 -877
  104. package/deepccc-agent/src/__tests__/cli-json.test.ts +0 -49
  105. package/deepccc-agent/src/__tests__/config.test.ts +0 -34
  106. package/deepccc-agent/src/__tests__/context.test.ts +0 -341
  107. package/deepccc-agent/src/__tests__/file-tools.test.ts +0 -240
  108. package/deepccc-agent/src/__tests__/permissions.test.ts +0 -199
  109. package/deepccc-agent/src/__tests__/privacy.test.ts +0 -318
  110. package/deepccc-agent/src/__tests__/progress-reducer.test.ts +0 -121
  111. package/deepccc-agent/src/__tests__/session-search.test.ts +0 -262
  112. package/deepccc-agent/src/__tests__/session-select.test.ts +0 -116
  113. package/deepccc-agent/src/__tests__/sigint.test.ts +0 -56
  114. package/deepccc-agent/src/__tests__/skills.test.ts +0 -284
  115. package/deepccc-agent/src/__tests__/terminal-renderer.test.ts +0 -247
  116. package/deepccc-agent/src/__tests__/web-tools.test.ts +0 -220
  117. package/deepccc-agent/src/cli.ts +0 -682
  118. package/deepccc-agent/src/config.ts +0 -101
  119. package/deepccc-agent/src/context.ts +0 -465
  120. package/deepccc-agent/src/file-log.ts +0 -38
  121. package/deepccc-agent/src/file-tools.ts +0 -1493
  122. package/deepccc-agent/src/index.ts +0 -710
  123. package/deepccc-agent/src/permissions.ts +0 -226
  124. package/deepccc-agent/src/privacy.ts +0 -141
  125. package/deepccc-agent/src/proc-tree-kill.ts +0 -61
  126. package/deepccc-agent/src/progress/cards-helpers.ts +0 -76
  127. package/deepccc-agent/src/progress/reducer.ts +0 -113
  128. package/deepccc-agent/src/progress/terminal-renderer.ts +0 -294
  129. package/deepccc-agent/src/progress/view.ts +0 -77
  130. package/deepccc-agent/src/raw-stream-log.ts +0 -124
  131. package/deepccc-agent/src/session-search.ts +0 -370
  132. package/deepccc-agent/src/session-select.ts +0 -48
  133. package/deepccc-agent/src/sigint.ts +0 -50
  134. package/deepccc-agent/src/skills.ts +0 -205
  135. package/deepccc-agent/src/web-tools.ts +0 -313
  136. package/deepccc-agent/tsconfig.build.json +0 -13
  137. package/deepccc-agent/tsconfig.json +0 -13
  138. package/deepccc-agent/vitest.config.ts +0 -7
  139. package/src/__tests__/adapter-interface.test.ts +0 -152
  140. package/src/__tests__/agent-activity.test.ts +0 -86
  141. package/src/__tests__/agent-delegate-task-rpc.test.ts +0 -165
  142. package/src/__tests__/agent-image-rpc.test.ts +0 -34
  143. package/src/__tests__/agent-platform-routing.test.ts +0 -26
  144. package/src/__tests__/agent-reload-config-rpc.test.ts +0 -99
  145. package/src/__tests__/agent-rpc-body.test.ts +0 -42
  146. package/src/__tests__/builtin-chat-session.test.ts +0 -532
  147. package/src/__tests__/builtin-cli-json.test.ts +0 -39
  148. package/src/__tests__/builtin-config.test.ts +0 -26
  149. package/src/__tests__/builtin-context.test.ts +0 -319
  150. package/src/__tests__/builtin-file-tools.test.ts +0 -240
  151. package/src/__tests__/builtin-permissions.test.ts +0 -219
  152. package/src/__tests__/builtin-session-search.test.ts +0 -262
  153. package/src/__tests__/builtin-session-select.test.ts +0 -116
  154. package/src/__tests__/builtin-sigint.test.ts +0 -56
  155. package/src/__tests__/builtin-skills.test.ts +0 -284
  156. package/src/__tests__/builtin-web-tools.test.ts +0 -220
  157. package/src/__tests__/card-action-routing.test.ts +0 -18
  158. package/src/__tests__/card-plain-text.test.ts +0 -45
  159. package/src/__tests__/cardkit.test.ts +0 -60
  160. package/src/__tests__/cards.test.ts +0 -607
  161. package/src/__tests__/ccc-adapter.test.ts +0 -194
  162. package/src/__tests__/chatgpt-subscription-rpc.test.ts +0 -89
  163. package/src/__tests__/chatgpt-subscription.test.ts +0 -135
  164. package/src/__tests__/chrome-devtools-guard.test.ts +0 -165
  165. package/src/__tests__/claude-adapter.test.ts +0 -614
  166. package/src/__tests__/claude-raw-stream-log.test.ts +0 -96
  167. package/src/__tests__/claude-sdk-installer.test.ts +0 -285
  168. package/src/__tests__/codex-adapter.test.ts +0 -331
  169. package/src/__tests__/codex-raw-stream-log.test.ts +0 -170
  170. package/src/__tests__/codex-reset-actions.test.ts +0 -146
  171. package/src/__tests__/config-reload.test.ts +0 -284
  172. package/src/__tests__/config-sample.test.ts +0 -97
  173. package/src/__tests__/config-utils.test.ts +0 -40
  174. package/src/__tests__/config.test.ts +0 -395
  175. package/src/__tests__/crash-logging.test.ts +0 -360
  176. package/src/__tests__/cursor-adapter.test.ts +0 -890
  177. package/src/__tests__/cursor-session-meta-store.test.ts +0 -212
  178. package/src/__tests__/feishu-api.test.ts +0 -60
  179. package/src/__tests__/feishu-avatar.test.ts +0 -504
  180. package/src/__tests__/feishu-message-ingress.test.ts +0 -138
  181. package/src/__tests__/feishu-platform.test.ts +0 -75
  182. package/src/__tests__/fixtures/codex_simple_text.jsonl +0 -4
  183. package/src/__tests__/fixtures/codex_with_tool.jsonl +0 -6
  184. package/src/__tests__/fixtures/cursor_partial_only.jsonl +0 -5
  185. package/src/__tests__/fixtures/cursor_partial_with_final.jsonl +0 -13
  186. package/src/__tests__/fixtures/cursor_with_tool_call.jsonl +0 -12
  187. package/src/__tests__/format-message.test.ts +0 -316
  188. package/src/__tests__/git-command.test.ts +0 -288
  189. package/src/__tests__/im-skills.test.ts +0 -125
  190. package/src/__tests__/jsonl-stream.test.ts +0 -79
  191. package/src/__tests__/orchestrator.test.ts +0 -1268
  192. package/src/__tests__/package-files.test.ts +0 -24
  193. package/src/__tests__/platform-startup.test.ts +0 -19
  194. package/src/__tests__/privacy.test.ts +0 -198
  195. package/src/__tests__/proc-tree-kill.test.ts +0 -108
  196. package/src/__tests__/progress-reducer.test.ts +0 -121
  197. package/src/__tests__/raw-stream-log.test.ts +0 -106
  198. package/src/__tests__/response-stall.test.ts +0 -49
  199. package/src/__tests__/restart.test.ts +0 -232
  200. package/src/__tests__/session-ccc-config.test.ts +0 -66
  201. package/src/__tests__/session.test.ts +0 -3004
  202. package/src/__tests__/shared-prefix.test.ts +0 -36
  203. package/src/__tests__/sim-agent.test.ts +0 -174
  204. package/src/__tests__/sim-platform.test.ts +0 -93
  205. package/src/__tests__/sim-store.test.ts +0 -214
  206. package/src/__tests__/simplify.test.ts +0 -283
  207. package/src/__tests__/startup-lifecycle.test.ts +0 -231
  208. package/src/__tests__/stop-session.test.ts +0 -162
  209. package/src/__tests__/stream-state.test.ts +0 -164
  210. package/src/__tests__/terminal-error.test.ts +0 -54
  211. package/src/__tests__/terminal-renderer.test.ts +0 -247
  212. package/src/__tests__/update-command-guard.test.ts +0 -144
  213. package/src/__tests__/web-ui.test.ts +0 -438
  214. package/src/__tests__/wechat-platform.test.ts +0 -111
  215. package/src/adapters/adapter-interface.ts +0 -217
  216. package/src/adapters/ccc-adapter.ts +0 -150
  217. package/src/adapters/claude-adapter.ts +0 -673
  218. package/src/adapters/claude-session-meta-store.ts +0 -120
  219. package/src/adapters/codex-adapter.ts +0 -426
  220. package/src/adapters/codex-session-meta-store.ts +0 -131
  221. package/src/adapters/cursor-adapter.ts +0 -681
  222. package/src/adapters/cursor-session-meta-store.ts +0 -154
  223. package/src/adapters/jsonl-stream.ts +0 -157
  224. package/src/adapters/raw-stream-log.ts +0 -124
  225. package/src/adapters/resource-monitor.ts +0 -141
  226. package/src/agent-activity.ts +0 -175
  227. package/src/agent-delegate-task-rpc.ts +0 -153
  228. package/src/agent-delegate-task.ts +0 -91
  229. package/src/agent-file-rpc.ts +0 -172
  230. package/src/agent-image-rpc.ts +0 -168
  231. package/src/agent-platform-routing.ts +0 -28
  232. package/src/agent-reload-config-rpc.ts +0 -34
  233. package/src/agent-rpc-body.ts +0 -92
  234. package/src/agent-stop-stuck.ts +0 -129
  235. package/src/card-action-routing.ts +0 -14
  236. package/src/card-plain-text.ts +0 -108
  237. package/src/cardkit.ts +0 -179
  238. package/src/cards.ts +0 -684
  239. package/src/chatgpt-subscription-rpc.ts +0 -27
  240. package/src/chatgpt-subscription.ts +0 -299
  241. package/src/chrome-devtools-guard.ts +0 -318
  242. package/src/claude-sdk-installer.ts +0 -324
  243. package/src/codex-reset-actions.ts +0 -184
  244. package/src/config-utils.ts +0 -211
  245. package/src/config.ts +0 -1063
  246. package/src/cursor-usage.ts +0 -128
  247. package/src/exit-banner.ts +0 -33
  248. package/src/feishu-api.ts +0 -1616
  249. package/src/feishu-message-ingress.ts +0 -195
  250. package/src/feishu-platform.ts +0 -159
  251. package/src/format-message.ts +0 -293
  252. package/src/git-command.ts +0 -202
  253. package/src/im-skills.ts +0 -149
  254. package/src/index.ts +0 -1089
  255. package/src/litellm-proxy.ts +0 -374
  256. package/src/orchestrator.ts +0 -2543
  257. package/src/platform-adapter.ts +0 -70
  258. package/src/platform-startup.ts +0 -16
  259. package/src/privacy.ts +0 -118
  260. package/src/progress/reducer.ts +0 -113
  261. package/src/progress/terminal-renderer.ts +0 -294
  262. package/src/progress/view.ts +0 -77
  263. package/src/response-stall.ts +0 -28
  264. package/src/runtime-reload.ts +0 -34
  265. package/src/session-chat-binding.ts +0 -292
  266. package/src/session-name.ts +0 -8
  267. package/src/session.ts +0 -2659
  268. package/src/shared-prefix.ts +0 -29
  269. package/src/shared.ts +0 -552
  270. package/src/sim-agent.ts +0 -167
  271. package/src/sim-platform.ts +0 -177
  272. package/src/sim-store.ts +0 -317
  273. package/src/simplify.ts +0 -120
  274. package/src/startup-lifecycle.ts +0 -250
  275. package/src/stream-state.ts +0 -177
  276. package/src/terminal-error.ts +0 -129
  277. package/src/trace.ts +0 -51
  278. package/src/turn-cards.ts +0 -118
  279. package/src/update-command-guard.ts +0 -165
  280. package/src/wechat-platform.ts +0 -680
@@ -1,395 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
-
3
- // 注意:这里特意 import 自 `../config-utils.ts` 而不是 `../config.ts`。
4
- // `../config.ts` 在模块顶层会执行 loadConfig()——在生产环境下,当 config.json
5
- // 不存在时它会从 config.sample.json 自动复制一份过去。我们额外靠 VITEST 环境
6
- // 变量在 loadConfig() 里跳过这次写文件,但能不触发就尽量不触发。
7
- import { existsSync } from "node:fs";
8
- import { join } from "node:path";
9
-
10
- import {
11
- DEFAULT_GIT_TIMEOUT_SECONDS,
12
- MAX_GIT_TIMEOUT_SECONDS,
13
- MIN_GIT_TIMEOUT_SECONDS,
14
- autoDetectCodexPath,
15
- autoDetectCursorPath,
16
- normalizeOptionalConfigField,
17
- parseGitTimeoutSeconds,
18
- readToolCliPath,
19
- resolveCccEnabled,
20
- } from "../config-utils.ts";
21
-
22
- describe("resolveCccEnabled", () => {
23
- it("never enables CCC Agent without a ChatCCC API key", () => {
24
- expect(resolveCccEnabled(true, "")).toBe(false);
25
- expect(resolveCccEnabled(true, " ")).toBe(false);
26
- expect(resolveCccEnabled(undefined, undefined)).toBe(false);
27
- });
28
-
29
- it("respects the explicit enabled flag when a ChatCCC API key exists", () => {
30
- expect(resolveCccEnabled(undefined, "sk-chatccc")).toBe(true);
31
- expect(resolveCccEnabled(true, "sk-chatccc")).toBe(true);
32
- expect(resolveCccEnabled(false, "sk-chatccc")).toBe(false);
33
- });
34
- });
35
-
36
- describe("parseGitTimeoutSeconds", () => {
37
- it("returns default when raw is undefined", () => {
38
- const r = parseGitTimeoutSeconds(undefined);
39
- expect(r.seconds).toBe(DEFAULT_GIT_TIMEOUT_SECONDS);
40
- expect(r.valid).toBe(true);
41
- expect(r.usingDefault).toBe(true);
42
- expect(r.raw).toBeUndefined();
43
- });
44
-
45
- it("returns default when raw is empty / whitespace only", () => {
46
- expect(parseGitTimeoutSeconds("")).toMatchObject({
47
- seconds: DEFAULT_GIT_TIMEOUT_SECONDS,
48
- valid: true,
49
- usingDefault: true,
50
- });
51
- expect(parseGitTimeoutSeconds(" ")).toMatchObject({
52
- seconds: DEFAULT_GIT_TIMEOUT_SECONDS,
53
- valid: true,
54
- usingDefault: true,
55
- });
56
- });
57
-
58
- it("trims and uses user value when valid integer in range", () => {
59
- expect(parseGitTimeoutSeconds("90")).toMatchObject({
60
- seconds: 90,
61
- valid: true,
62
- usingDefault: false,
63
- raw: "90",
64
- });
65
- expect(parseGitTimeoutSeconds(" 600 ")).toMatchObject({
66
- seconds: 600,
67
- valid: true,
68
- usingDefault: false,
69
- raw: "600",
70
- });
71
- });
72
-
73
- it("accepts boundary values MIN and MAX", () => {
74
- expect(parseGitTimeoutSeconds(String(MIN_GIT_TIMEOUT_SECONDS))).toMatchObject({
75
- seconds: MIN_GIT_TIMEOUT_SECONDS,
76
- valid: true,
77
- usingDefault: false,
78
- });
79
- expect(parseGitTimeoutSeconds(String(MAX_GIT_TIMEOUT_SECONDS))).toMatchObject({
80
- seconds: MAX_GIT_TIMEOUT_SECONDS,
81
- valid: true,
82
- usingDefault: false,
83
- });
84
- });
85
-
86
- it("falls back to default for non-integer / non-numeric", () => {
87
- expect(parseGitTimeoutSeconds("abc")).toMatchObject({
88
- seconds: DEFAULT_GIT_TIMEOUT_SECONDS,
89
- valid: false,
90
- usingDefault: true,
91
- raw: "abc",
92
- });
93
- expect(parseGitTimeoutSeconds("12.5")).toMatchObject({
94
- seconds: DEFAULT_GIT_TIMEOUT_SECONDS,
95
- valid: false,
96
- usingDefault: true,
97
- });
98
- expect(parseGitTimeoutSeconds("NaN")).toMatchObject({
99
- valid: false,
100
- usingDefault: true,
101
- });
102
- });
103
-
104
- it("falls back to default for out-of-range values", () => {
105
- expect(parseGitTimeoutSeconds("0")).toMatchObject({
106
- seconds: DEFAULT_GIT_TIMEOUT_SECONDS,
107
- valid: false,
108
- usingDefault: true,
109
- });
110
- expect(parseGitTimeoutSeconds("-5")).toMatchObject({
111
- valid: false,
112
- usingDefault: true,
113
- });
114
- expect(parseGitTimeoutSeconds(String(MAX_GIT_TIMEOUT_SECONDS + 1))).toMatchObject({
115
- seconds: DEFAULT_GIT_TIMEOUT_SECONDS,
116
- valid: false,
117
- usingDefault: true,
118
- });
119
- });
120
-
121
- it("respects custom default parameter", () => {
122
- expect(parseGitTimeoutSeconds(undefined, 30)).toMatchObject({
123
- seconds: 30,
124
- usingDefault: true,
125
- });
126
- // 用户值有效则忽略 default 参数
127
- expect(parseGitTimeoutSeconds("45", 30)).toMatchObject({
128
- seconds: 45,
129
- usingDefault: false,
130
- });
131
- // 用户值非法时回退到自定义 default
132
- expect(parseGitTimeoutSeconds("abc", 30)).toMatchObject({
133
- seconds: 30,
134
- usingDefault: true,
135
- });
136
- });
137
- });
138
-
139
- describe("normalizeOptionalConfigField", () => {
140
- it("returns fallback (default empty string) when value is undefined", () => {
141
- expect(normalizeOptionalConfigField(undefined, { label: "x" })).toBe("");
142
- });
143
-
144
- it("returns fallback (default empty string) when value is null", () => {
145
- expect(normalizeOptionalConfigField(null, { label: "x" })).toBe("");
146
- });
147
-
148
- it("returns fallback when value is not a string (number / boolean / object)", () => {
149
- expect(normalizeOptionalConfigField(123, { label: "x" })).toBe("");
150
- expect(normalizeOptionalConfigField(true, { label: "x" })).toBe("");
151
- expect(normalizeOptionalConfigField({}, { label: "x" })).toBe("");
152
- });
153
-
154
- it("uses explicit fallback when value is missing", () => {
155
- expect(
156
- normalizeOptionalConfigField(undefined, { label: "x", fallback: "FB" }),
157
- ).toBe("FB");
158
- expect(
159
- normalizeOptionalConfigField(42, { label: "x", fallback: "FB" }),
160
- ).toBe("FB");
161
- });
162
-
163
- it("returns empty string as-is (treated as 'do not pass to SDK/CLI')", () => {
164
- expect(normalizeOptionalConfigField("", { label: "x" })).toBe("");
165
- expect(
166
- normalizeOptionalConfigField("", { label: "x", fallback: "FB" }),
167
- ).toBe("");
168
- });
169
-
170
- it("returns value as-is when it is a normal non-default string", () => {
171
- expect(normalizeOptionalConfigField("high", { label: "x" })).toBe("high");
172
- expect(
173
- normalizeOptionalConfigField("claude-sonnet-4-6", { label: "x" }),
174
- ).toBe("claude-sonnet-4-6");
175
- });
176
-
177
- it("preserves surrounding whitespace (trim handled by callers)", () => {
178
- expect(normalizeOptionalConfigField(" high ", { label: "x" })).toBe(
179
- " high ",
180
- );
181
- });
182
-
183
- it("treats 'default' (any case, with whitespace) as empty string and warns", () => {
184
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
185
- try {
186
- expect(normalizeOptionalConfigField("default", { label: "x" })).toBe("");
187
- expect(normalizeOptionalConfigField("DEFAULT", { label: "x" })).toBe("");
188
- expect(normalizeOptionalConfigField("Default", { label: "x" })).toBe("");
189
- expect(normalizeOptionalConfigField(" default ", { label: "x" })).toBe(
190
- "",
191
- );
192
- expect(warn).toHaveBeenCalledTimes(4);
193
- const msg = String(warn.mock.calls[0]?.[0] ?? "");
194
- expect(msg).toContain("已废弃");
195
- expect(msg).toContain("x");
196
- } finally {
197
- warn.mockRestore();
198
- }
199
- });
200
-
201
- it("ignores fallback for 'default' (still returns empty string)", () => {
202
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
203
- try {
204
- expect(
205
- normalizeOptionalConfigField("default", {
206
- label: "x",
207
- fallback: "FB",
208
- }),
209
- ).toBe("");
210
- } finally {
211
- warn.mockRestore();
212
- }
213
- });
214
-
215
- it("does NOT treat substrings like 'default-foo' as the deprecated literal", () => {
216
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
217
- try {
218
- expect(
219
- normalizeOptionalConfigField("default-foo", { label: "x" }),
220
- ).toBe("default-foo");
221
- expect(warn).not.toHaveBeenCalled();
222
- } finally {
223
- warn.mockRestore();
224
- }
225
- });
226
- });
227
-
228
- describe("GIT timeout constants", () => {
229
- it("DEFAULT_GIT_TIMEOUT_SECONDS is 180", () => {
230
- expect(DEFAULT_GIT_TIMEOUT_SECONDS).toBe(180);
231
- });
232
-
233
- it("MIN < DEFAULT < MAX", () => {
234
- expect(MIN_GIT_TIMEOUT_SECONDS).toBeGreaterThan(0);
235
- expect(MIN_GIT_TIMEOUT_SECONDS).toBeLessThan(DEFAULT_GIT_TIMEOUT_SECONDS);
236
- expect(DEFAULT_GIT_TIMEOUT_SECONDS).toBeLessThan(MAX_GIT_TIMEOUT_SECONDS);
237
- });
238
- });
239
-
240
- describe("autoDetectCursorPath", () => {
241
- it("Windows 优先返回 LocalAppData 默认安装路径(agent.cmd 存在)", () => {
242
- const calls: string[] = [];
243
- const result = autoDetectCursorPath({
244
- platform: "win32",
245
- localAppData: "C:\\Users\\u\\AppData\\Local",
246
- existsSync: (p) => {
247
- calls.push(p);
248
- return p.endsWith("agent.cmd");
249
- },
250
- whichSync: () => {
251
- throw new Error("不该走 PATH 查找");
252
- },
253
- });
254
- expect(result).toMatch(/cursor-agent[\\/]agent\.cmd$/);
255
- // 至少检查过一次默认安装路径
256
- expect(calls.some((c) => c.endsWith("agent.cmd"))).toBe(true);
257
- });
258
-
259
- it("Windows 但 LocalAppData 不存在 → 退回 whichSync(cursor-agent)", () => {
260
- const result = autoDetectCursorPath({
261
- platform: "win32",
262
- localAppData: "C:\\Users\\u\\AppData\\Local",
263
- existsSync: () => false,
264
- whichSync: (cmd) => (cmd === "cursor-agent" ? "C:\\tools\\cursor-agent.exe" : null),
265
- });
266
- expect(result).toBe("C:\\tools\\cursor-agent.exe");
267
- });
268
-
269
- it("非 Windows 跳过 LocalAppData,直接用 whichSync 查 cursor-agent → agent", () => {
270
- const tried: string[] = [];
271
- const result = autoDetectCursorPath({
272
- platform: "linux",
273
- localAppData: undefined,
274
- existsSync: () => {
275
- throw new Error("非 Windows 不应该 stat LocalAppData");
276
- },
277
- whichSync: (cmd) => {
278
- tried.push(cmd);
279
- return cmd === "agent" ? "/usr/local/bin/agent" : null;
280
- },
281
- });
282
- expect(result).toBe("/usr/local/bin/agent");
283
- expect(tried).toEqual(["cursor-agent", "agent"]);
284
- });
285
-
286
- it("全部命中失败时返回 null(不抛、由调用方决定留空)", () => {
287
- const result = autoDetectCursorPath({
288
- platform: "darwin",
289
- localAppData: undefined,
290
- existsSync: () => false,
291
- whichSync: () => null,
292
- });
293
- expect(result).toBeNull();
294
- });
295
- });
296
-
297
- describe("autoDetectCodexPath", () => {
298
- it("命中 PATH 中的 codex → 返回绝对路径", () => {
299
- const result = autoDetectCodexPath({
300
- whichSync: (cmd) => (cmd === "codex" ? "/usr/local/bin/codex" : null),
301
- });
302
- expect(result).toBe("/usr/local/bin/codex");
303
- });
304
-
305
- it("PATH 中没有 codex → 返回 null", () => {
306
- const result = autoDetectCodexPath({ whichSync: () => null });
307
- expect(result).toBeNull();
308
- });
309
- });
310
-
311
- describe("readToolCliPath", () => {
312
- it("优先返回新字段 path(command 同时存在也忽略)", () => {
313
- const onLegacy = vi.fn();
314
- expect(
315
- readToolCliPath(
316
- { path: "/new/path", command: "/old/cmd" },
317
- { label: "cursor", onLegacyField: onLegacy },
318
- ),
319
- ).toBe("/new/path");
320
- expect(onLegacy).not.toHaveBeenCalled();
321
- });
322
-
323
- it("仅有旧字段 command 时 → 用 command 的值并触发 onLegacyField 回调", () => {
324
- const onLegacy = vi.fn();
325
- expect(
326
- readToolCliPath(
327
- { command: "/old/cmd" },
328
- { label: "codex", onLegacyField: onLegacy },
329
- ),
330
- ).toBe("/old/cmd");
331
- expect(onLegacy).toHaveBeenCalledWith("codex", "/old/cmd");
332
- });
333
-
334
- it("path 为空字符串 / 全空白 → 视作未填,回退到 command", () => {
335
- const onLegacy = vi.fn();
336
- expect(
337
- readToolCliPath(
338
- { path: " ", command: "/fallback" },
339
- { label: "cursor", onLegacyField: onLegacy },
340
- ),
341
- ).toBe("/fallback");
342
- expect(onLegacy).toHaveBeenCalledTimes(1);
343
- });
344
-
345
- it("两边都空 / undefined → 返回 '',不触发回调", () => {
346
- const onLegacy = vi.fn();
347
- expect(readToolCliPath(undefined, { label: "x", onLegacyField: onLegacy })).toBe("");
348
- expect(readToolCliPath({}, { label: "x", onLegacyField: onLegacy })).toBe("");
349
- expect(
350
- readToolCliPath({ path: "", command: "" }, { label: "x", onLegacyField: onLegacy }),
351
- ).toBe("");
352
- expect(onLegacy).not.toHaveBeenCalled();
353
- });
354
-
355
- it("非字符串字段 → 当作未填", () => {
356
- expect(
357
- readToolCliPath({ path: 42, command: null } as unknown as { path?: unknown }, {
358
- label: "cursor",
359
- }),
360
- ).toBe("");
361
- });
362
- });
363
-
364
- describe("test environment side-effects (regression guard)", () => {
365
- // 历史 bug:跑单测会让仓库根目录"自己长出" config.json——
366
- // src/config.ts 顶层执行的 loadConfig() 在生产环境下会自动把
367
- // config.sample.json 复制成 config.json。这一行为不应在测试环境发生:
368
- // (1) 单测不应改动工作区文件;
369
- // (2) 误生成的 config.json 会污染 git status,干扰真实改动的提交。
370
- it("仅 import config 模块不会让仓库根目录自动生成 config.json", async () => {
371
- // 这里通过 import.meta.url 反推项目根目录,避免依赖 PROJECT_ROOT(后者来自 config.ts)。
372
- const projectRoot = join(
373
- new URL(".", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1"),
374
- "..",
375
- "..",
376
- );
377
- const configJsonPath = join(projectRoot, "config.json");
378
- const sampleExists = existsSync(join(projectRoot, "config.sample.json"));
379
- expect(sampleExists).toBe(true);
380
-
381
- const existedBefore = existsSync(configJsonPath);
382
-
383
- // 触发 config.ts 顶层副作用(其它单测也会通过 session.ts/adapters/* 间接 import 它,
384
- // 这里直接 import 模拟最坏情况)。
385
- await import("../config.ts");
386
-
387
- const existsAfter = existsSync(configJsonPath);
388
- // 如果 import 之前就不存在,import 之后也不应该被创建出来。
389
- if (!existedBefore) {
390
- expect(existsAfter).toBe(false);
391
- } else {
392
- expect(existsAfter).toBe(true);
393
- }
394
- });
395
- });