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,313 +0,0 @@
1
- /**
2
- * web-tools.ts — websearch / webfetch 内置工具(agent 端联网,业界主流做法)
3
- *
4
- * 与 Claude Code 的 WebSearch/WebFetch、Codex 的 web search 相同形态:
5
- * 模型通过 function calling 调用,agent 进程执行 HTTP 请求,结果回填上下文。
6
- * 两者都是只读外部网络操作,不触碰本地文件系统,无需权限询问。
7
- *
8
- * - websearch:DuckDuckGo HTML 端点(免 API key),返回标题 + URL + 摘要
9
- * - webfetch:HTTP GET + HTML 转纯文本,控制大小与超时
10
- *
11
- * 零新依赖:使用 Node 20 内置 fetch / AbortSignal / Buffer。
12
- * 解析逻辑拆成纯函数导出,便于单测;fetch 可通过 options 注入 mock。
13
- */
14
-
15
- export interface WebSearchInput {
16
- query: string;
17
- /** 返回结果条数上限,默认 5,上限 10 */
18
- maxResults?: number;
19
- }
20
-
21
- export interface WebSearchResult {
22
- title: string;
23
- url: string;
24
- snippet: string;
25
- }
26
-
27
- export interface WebSearchOutput {
28
- query: string;
29
- results: WebSearchResult[];
30
- truncated: boolean;
31
- durationMs: number;
32
- }
33
-
34
- export interface WebFetchInput {
35
- url: string;
36
- /** 返回纯文本字符数上限,默认 10000,上限 100000 */
37
- maxChars?: number;
38
- }
39
-
40
- export interface WebFetchOutput {
41
- url: string;
42
- contentType: string;
43
- title: string;
44
- text: string;
45
- chars: number;
46
- truncated: boolean;
47
- durationMs: number;
48
- }
49
-
50
- export const WEB_SEARCH_TIMEOUT_MS = 15_000;
51
- export const WEB_FETCH_TIMEOUT_MS = 20_000;
52
- export const WEB_SEARCH_DEFAULT_RESULTS = 5;
53
- export const WEB_SEARCH_MAX_RESULTS = 10;
54
- export const WEB_FETCH_DEFAULT_CHARS = 10_000;
55
- export const WEB_FETCH_MAX_CHARS = 100_000;
56
- /** webfetch 读取响应体的大小上限(超出即截断),防止把整个大文件拉进上下文 */
57
- export const WEB_FETCH_MAX_BYTES = 512 * 1024;
58
-
59
- const SEARCH_UA =
60
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36";
61
-
62
- export type FetchLike = (
63
- input: string | URL | Request,
64
- init?: RequestInit,
65
- ) => Promise<Response>;
66
-
67
- export interface WebToolOptions {
68
- abortSignal?: AbortSignal;
69
- /** 测试注入用,默认全局 fetch */
70
- fetchImpl?: FetchLike;
71
- timeoutMs?: number;
72
- }
73
-
74
- function normalizeMaxResults(value: number | undefined): number {
75
- if (value === undefined) return WEB_SEARCH_DEFAULT_RESULTS;
76
- if (!Number.isInteger(value) || value <= 0) {
77
- throw new Error("maxResults must be a positive integer when provided");
78
- }
79
- return Math.min(value, WEB_SEARCH_MAX_RESULTS);
80
- }
81
-
82
- function normalizeMaxChars(value: number | undefined): number {
83
- if (value === undefined) return WEB_FETCH_DEFAULT_CHARS;
84
- if (!Number.isInteger(value) || value <= 0) {
85
- throw new Error("maxChars must be a positive integer when provided");
86
- }
87
- return Math.min(value, WEB_FETCH_MAX_CHARS);
88
- }
89
-
90
- function buildSignal(timeoutMs: number, abortSignal?: AbortSignal): AbortSignal {
91
- const signals: AbortSignal[] = [AbortSignal.timeout(timeoutMs)];
92
- if (abortSignal) signals.push(abortSignal);
93
- return signals.length === 1 ? signals[0] : AbortSignal.any(signals);
94
- }
95
-
96
- function decodeEntities(s: string): string {
97
- return s
98
- .replace(/&lt;/gi, "<")
99
- .replace(/&gt;/gi, ">")
100
- .replace(/&quot;/gi, '"')
101
- .replace(/&#0?39;/gi, "'")
102
- .replace(/&nbsp;/gi, " ")
103
- .replace(/&#(\d+);/g, (_, n: string) => {
104
- try {
105
- return String.fromCodePoint(Number(n));
106
- } catch {
107
- return "";
108
- }
109
- })
110
- .replace(/&#x([0-9a-f]+);/gi, (_, h: string) => {
111
- try {
112
- return String.fromCodePoint(parseInt(h, 16));
113
- } catch {
114
- return "";
115
- }
116
- })
117
- .replace(/&amp;/gi, "&");
118
- }
119
-
120
- function stripTags(s: string): string {
121
- return decodeEntities(s.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim());
122
- }
123
-
124
- /** 提取 <title> 文本(用于 webfetch 结果),无则返回空字符串 */
125
- export function extractHtmlTitle(html: string): string {
126
- const m = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
127
- return m ? stripTags(m[1]) : "";
128
- }
129
-
130
- /**
131
- * HTML 转纯文本:
132
- * 去掉 script/style/noscript/svg/注释 → 块级元素补换行 → 去标签 → 解实体 → 压缩空白。
133
- */
134
- export function htmlToPlainText(html: string): string {
135
- let text = html
136
- .replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ")
137
- .replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ")
138
- .replace(/<noscript\b[^>]*>[\s\S]*?<\/noscript>/gi, " ")
139
- .replace(/<svg\b[^>]*>[\s\S]*?<\/svg>/gi, " ")
140
- .replace(/<!--[\s\S]*?-->/g, " ")
141
- .replace(/<\/(p|div|li|tr|h[1-6]|pre|blockquote|section|article|table|ul|ol|header|footer|nav|dl|dd|dt)>/gi, "\n")
142
- .replace(/<(br|hr)\s*\/?>/gi, "\n")
143
- .replace(/<[^>]+>/g, "");
144
-
145
- text = decodeEntities(text);
146
-
147
- return text
148
- .split("\n")
149
- .map((line) => line.replace(/\s+/g, " ").trim())
150
- .filter((line, i, arr) => line !== "" || (i > 0 && arr[i - 1] !== ""))
151
- .join("\n")
152
- .trim();
153
- }
154
-
155
- /** 解码 DuckDuckGo 结果链接:/l/?uddg=<encoded> 还原为目标 URL */
156
- export function decodeDdgHref(href: string): string {
157
- let h = href.trim();
158
- if (h.startsWith("//")) h = "https:" + h;
159
- try {
160
- const u = new URL(h);
161
- if (u.hostname === "duckduckgo.com" && u.pathname.startsWith("/l/")) {
162
- const uddg = u.searchParams.get("uddg");
163
- if (uddg) return decodeURIComponent(uddg);
164
- }
165
- return u.toString();
166
- } catch {
167
- return h;
168
- }
169
- }
170
-
171
- /**
172
- * 解析 DuckDuckGo HTML 搜索结果页(html.duckduckgo.com/html)。
173
- * 提取 result__a(标题 + href)与 result__snippet(摘要)。
174
- */
175
- export function parseDuckDuckGoHtml(html: string): WebSearchResult[] {
176
- const results: WebSearchResult[] = [];
177
- const titleRe = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
178
- const snippetRe = /<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
179
-
180
- const titleMatches = [...html.matchAll(titleRe)];
181
- const snippetMatches = [...html.matchAll(snippetRe)];
182
-
183
- titleMatches.forEach((m, i) => {
184
- const title = stripTags(m[2]);
185
- const url = decodeDdgHref(m[1]);
186
- const snippet = snippetMatches[i] ? stripTags(snippetMatches[i][1]) : "";
187
- if (!title && !url) return;
188
- results.push({ title, url, snippet });
189
- });
190
-
191
- return results;
192
- }
193
-
194
- /** 读取响应体并限制字节数(超出截断),避免整页大文件进入上下文 */
195
- async function readBodyWithLimit(res: Response, maxBytes: number): Promise<{ text: string; truncated: boolean }> {
196
- if (!res.body) {
197
- const text = await res.text();
198
- return { text, truncated: Buffer.byteLength(text, "utf8") > maxBytes };
199
- }
200
- const reader = res.body.getReader();
201
- const chunks: Uint8Array[] = [];
202
- let total = 0;
203
- let truncated = false;
204
- for (;;) {
205
- const { done, value } = await reader.read();
206
- if (done) break;
207
- if (!value) continue;
208
- if (total + value.byteLength > maxBytes) {
209
- const remaining = maxBytes - total;
210
- if (remaining > 0) chunks.push(value.subarray(0, remaining));
211
- truncated = true;
212
- await reader.cancel().catch(() => {});
213
- break;
214
- }
215
- chunks.push(value);
216
- total += value.byteLength;
217
- }
218
- return { text: Buffer.concat(chunks.map((c) => Buffer.from(c))).toString("utf8"), truncated };
219
- }
220
-
221
- /**
222
- * websearch:DuckDuckGo HTML 搜索(免 API key),返回标题 + URL + 摘要。
223
- * 网络失败抛错(成为 tool-error),搜索无结果返回空数组(不是错误)。
224
- */
225
- export async function webSearchForTool(
226
- input: WebSearchInput,
227
- options: WebToolOptions = {},
228
- ): Promise<WebSearchOutput> {
229
- const query = input.query?.trim();
230
- if (!query) throw new Error("query is required");
231
-
232
- const maxResults = normalizeMaxResults(input.maxResults);
233
- const startedAt = Date.now();
234
- const fetchImpl = options.fetchImpl ?? fetch;
235
- const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
236
-
237
- const res = await fetchImpl(url, {
238
- headers: {
239
- "User-Agent": SEARCH_UA,
240
- Accept: "text/html,application/xhtml+xml",
241
- },
242
- redirect: "follow",
243
- signal: buildSignal(options.timeoutMs ?? WEB_SEARCH_TIMEOUT_MS, options.abortSignal),
244
- });
245
- if (!res.ok) {
246
- throw new Error(`web search failed: HTTP ${res.status} ${res.statusText}`);
247
- }
248
-
249
- const { text } = await readBodyWithLimit(res, WEB_FETCH_MAX_BYTES);
250
- const all = parseDuckDuckGoHtml(text);
251
- const results = all.slice(0, maxResults);
252
-
253
- return {
254
- query,
255
- results,
256
- truncated: all.length > results.length,
257
- durationMs: Date.now() - startedAt,
258
- };
259
- }
260
-
261
- /**
262
- * webfetch:HTTP GET + HTML 转纯文本。
263
- * 只允许 http/https(防 file:// 等本地协议),非 2xx 抛错。
264
- */
265
- export async function webFetchForTool(
266
- input: WebFetchInput,
267
- options: WebToolOptions = {},
268
- ): Promise<WebFetchOutput> {
269
- const raw = input.url?.trim();
270
- if (!raw) throw new Error("url is required");
271
-
272
- let parsed: URL;
273
- try {
274
- parsed = new URL(raw);
275
- } catch {
276
- throw new Error(`invalid URL: ${raw}`);
277
- }
278
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
279
- throw new Error(`unsupported protocol: ${parsed.protocol} (only http/https allowed)`);
280
- }
281
-
282
- const maxChars = normalizeMaxChars(input.maxChars);
283
- const startedAt = Date.now();
284
- const fetchImpl = options.fetchImpl ?? fetch;
285
-
286
- const res = await fetchImpl(parsed.toString(), {
287
- headers: {
288
- "User-Agent": SEARCH_UA,
289
- Accept: "text/html,application/xhtml+xml,text/plain;q=0.9,*/*;q=0.8",
290
- },
291
- redirect: "follow",
292
- signal: buildSignal(options.timeoutMs ?? WEB_FETCH_TIMEOUT_MS, options.abortSignal),
293
- });
294
- if (!res.ok) {
295
- throw new Error(`HTTP ${res.status} ${res.statusText} for ${parsed.toString()}`);
296
- }
297
-
298
- const contentType = res.headers.get("content-type") ?? "";
299
- const { text: body, truncated: bodyTruncated } = await readBodyWithLimit(res, WEB_FETCH_MAX_BYTES);
300
- const title = extractHtmlTitle(body);
301
- const plain = htmlToPlainText(body);
302
- const text = plain.length > maxChars ? plain.slice(0, maxChars) : plain;
303
-
304
- return {
305
- url: parsed.toString(),
306
- contentType,
307
- title,
308
- text,
309
- chars: text.length,
310
- truncated: bodyTruncated || plain.length > maxChars,
311
- durationMs: Date.now() - startedAt,
312
- };
313
- }
@@ -1,13 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "noEmit": false,
5
- "outDir": "./dist",
6
- "rootDir": "./src",
7
- "declaration": true,
8
- "declarationMap": false,
9
- "sourceMap": false
10
- },
11
- "include": ["src/*.ts"],
12
- "exclude": ["src/__tests__/**"]
13
- }
@@ -1,13 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "strict": true,
7
- "esModuleInterop": true,
8
- "skipLibCheck": true,
9
- "forceConsistentCasingInFileNames": true,
10
- "noEmit": true
11
- },
12
- "include": ["src/**/*.ts", "vitest.config.ts"]
13
- }
@@ -1,7 +0,0 @@
1
- import { defineConfig } from "vitest/config";
2
-
3
- export default defineConfig({
4
- test: {
5
- include: ["src/__tests__/**/*.test.ts"],
6
- },
7
- });
@@ -1,152 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import type {
3
- UnifiedBlock,
4
- UnifiedStreamMessage,
5
- UnifiedThinkingBlock,
6
- UnifiedTextBlock,
7
- UnifiedToolUseBlock,
8
- UnifiedToolResultBlock,
9
- UnifiedRedactedThinkingBlock,
10
- UnifiedSearchResultBlock,
11
- UnifiedCompactBoundaryBlock,
12
- CreateSessionResult,
13
- SessionInfo,
14
- ToolAdapter,
15
- } from "../adapters/adapter-interface.ts";
16
-
17
- // ---------------------------------------------------------------------------
18
- // 编译期类型验证:确保 UnifiedBlock 联合类型覆盖了所有预期的 block 形状
19
- // ---------------------------------------------------------------------------
20
-
21
- describe("UnifiedBlock union type coverage", () => {
22
- it("accepts thinking block", () => {
23
- const b: UnifiedBlock = { type: "thinking", thinking: "Let me think..." };
24
- expect(b.type).toBe("thinking");
25
- });
26
-
27
- it("accepts text block", () => {
28
- const b: UnifiedBlock = { type: "text", text: "Hello" };
29
- expect(b.type).toBe("text");
30
- });
31
-
32
- it("accepts tool_use block", () => {
33
- const b: UnifiedBlock = { type: "tool_use", name: "Read", input: { file_path: "/tmp" } };
34
- expect(b.type).toBe("tool_use");
35
- });
36
-
37
- it("accepts tool_result block with string content", () => {
38
- const b: UnifiedBlock = { type: "tool_result", tool_use_id: "abc123", content: "result" };
39
- expect(b.type).toBe("tool_result");
40
- });
41
-
42
- it("accepts tool_result block with error flag", () => {
43
- const b: UnifiedBlock = { type: "tool_result", tool_use_id: "abc123", content: "error", is_error: true };
44
- expect(b.is_error).toBe(true);
45
- });
46
-
47
- it("accepts redacted_thinking block", () => {
48
- const b: UnifiedBlock = { type: "redacted_thinking" };
49
- expect(b.type).toBe("redacted_thinking");
50
- });
51
-
52
- it("accepts search_result block", () => {
53
- const b: UnifiedBlock = { type: "search_result", query: "what is TypeScript" };
54
- expect(b.type).toBe("search_result");
55
- });
56
-
57
- it("accepts compact_boundary block", () => {
58
- const b: UnifiedBlock = { type: "compact_boundary", trigger: "auto", pre_tokens: 10000, post_tokens: 5000 };
59
- expect(b.type).toBe("compact_boundary");
60
- });
61
-
62
- it("accepts compact_boundary block without post_tokens", () => {
63
- const b: UnifiedBlock = { type: "compact_boundary", trigger: "manual", pre_tokens: 20000 };
64
- expect(b.type).toBe("compact_boundary");
65
- });
66
- });
67
-
68
- // ---------------------------------------------------------------------------
69
- // UnifiedStreamMessage
70
- // ---------------------------------------------------------------------------
71
-
72
- describe("UnifiedStreamMessage", () => {
73
- it("accepts assistant message with blocks", () => {
74
- const m: UnifiedStreamMessage = {
75
- type: "assistant",
76
- blocks: [
77
- { type: "thinking", thinking: "..." },
78
- { type: "text", text: "answer" },
79
- ],
80
- };
81
- expect(m.type).toBe("assistant");
82
- expect(m.blocks).toHaveLength(2);
83
- });
84
-
85
- it("accepts user message with tool_result blocks", () => {
86
- const m: UnifiedStreamMessage = {
87
- type: "user",
88
- blocks: [{ type: "tool_result", tool_use_id: "abc", content: "ok" }],
89
- };
90
- expect(m.type).toBe("user");
91
- });
92
-
93
- it("accepts system message", () => {
94
- const m: UnifiedStreamMessage = {
95
- type: "system",
96
- blocks: [{ type: "compact_boundary", trigger: "auto", pre_tokens: 100 }],
97
- };
98
- expect(m.type).toBe("system");
99
- });
100
- });
101
-
102
- // ---------------------------------------------------------------------------
103
- // CreateSessionResult
104
- // ---------------------------------------------------------------------------
105
-
106
- describe("CreateSessionResult", () => {
107
- it("accepts valid result", () => {
108
- const r: CreateSessionResult = { sessionId: "uuid-12345" };
109
- expect(r.sessionId).toBe("uuid-12345");
110
- });
111
- });
112
-
113
- // ---------------------------------------------------------------------------
114
- // SessionInfo
115
- // ---------------------------------------------------------------------------
116
-
117
- describe("SessionInfo", () => {
118
- it("accepts full info", () => {
119
- const info: SessionInfo = {
120
- sessionId: "abc",
121
- cwd: "/home/user",
122
- summary: "A session",
123
- lastModified: 1234567890,
124
- };
125
- expect(info.sessionId).toBe("abc");
126
- });
127
-
128
- it("accepts minimal info (only sessionId)", () => {
129
- const info: SessionInfo = { sessionId: "minimal" };
130
- expect(info.cwd).toBeUndefined();
131
- });
132
- });
133
-
134
- // ---------------------------------------------------------------------------
135
- // ToolAdapter interface 结构断言
136
- // ---------------------------------------------------------------------------
137
-
138
- describe("ToolAdapter interface structure", () => {
139
- it("has correct property names via mock object", () => {
140
- // 如果 ToolAdapter 接口的字段名或函数签名变更,该对象字面量将无法编译
141
- const mock: ToolAdapter = {
142
- displayName: "Test",
143
- sessionDescPrefix: "Test Session:",
144
- createSession: async () => ({ sessionId: "s" }),
145
- prompt: async function* () {},
146
- getSessionInfo: async () => undefined,
147
- closeSession: async () => {},
148
- };
149
- expect(mock.displayName).toBe("Test");
150
- expect(mock.sessionDescPrefix).toBe("Test Session:");
151
- });
152
- });
@@ -1,86 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import {
4
- createAgentActivityTracker,
5
- formatAgentActivityTitle,
6
- updateAgentActivity,
7
- } from "../agent-activity.ts";
8
-
9
- describe("agent activity", () => {
10
- it("starts with an explicit startup status", () => {
11
- const tracker = createAgentActivityTracker(1_000);
12
-
13
- expect(formatAgentActivityTitle(tracker.activity, 4_000)).toBe("正在启动 Agent · 3秒");
14
- });
15
-
16
- it("keeps the thinking timer stable across repeated thinking blocks", () => {
17
- const tracker = createAgentActivityTracker(1_000);
18
-
19
- expect(updateAgentActivity(tracker, { type: "thinking", thinking: "first" }, 2_000)).toBe(true);
20
- expect(updateAgentActivity(tracker, { type: "thinking", thinking: "second" }, 5_000)).toBe(false);
21
- expect(formatAgentActivityTitle(tracker.activity, 8_000)).toBe("思考中 · 6秒");
22
- });
23
-
24
- it("shows the active tool name and elapsed time", () => {
25
- const tracker = createAgentActivityTracker(1_000);
26
-
27
- updateAgentActivity(tracker, {
28
- type: "tool_use",
29
- id: "tool-1",
30
- name: "Bash",
31
- input: { command: "npm test" },
32
- }, 3_000);
33
-
34
- expect(formatAgentActivityTitle(tracker.activity, 38_000)).toBe("正在执行 Bash · 35秒");
35
- });
36
-
37
- it("tracks parallel tools and keeps the remaining tool after one result", () => {
38
- const tracker = createAgentActivityTracker(1_000);
39
-
40
- updateAgentActivity(tracker, { type: "tool_use", id: "read", name: "Read", input: {} }, 2_000);
41
- updateAgentActivity(tracker, { type: "tool_use", id: "grep", name: "Grep", input: {} }, 3_000);
42
- expect(formatAgentActivityTitle(tracker.activity, 4_000)).toBe("正在执行 Read 等 2 项 · 2秒");
43
-
44
- updateAgentActivity(tracker, {
45
- type: "tool_result",
46
- tool_use_id: "read",
47
- content: "done",
48
- }, 5_000);
49
- expect(formatAgentActivityTitle(tracker.activity, 7_000)).toBe("正在执行 Grep · 4秒");
50
-
51
- updateAgentActivity(tracker, {
52
- type: "tool_result",
53
- tool_use_id: "grep",
54
- content: "done",
55
- }, 8_000);
56
- expect(formatAgentActivityTitle(tracker.activity, 10_000)).toBe("正在处理工具结果 · 2秒");
57
- });
58
-
59
- it("switches to response and context-compaction statuses", () => {
60
- const tracker = createAgentActivityTracker(1_000);
61
-
62
- updateAgentActivity(tracker, { type: "text", text: "answer" }, 2_000);
63
- expect(formatAgentActivityTitle(tracker.activity, 3_000)).toBe("正在生成回复 · 1秒");
64
-
65
- updateAgentActivity(tracker, {
66
- type: "compact_boundary",
67
- trigger: "auto",
68
- pre_tokens: 100_000,
69
- }, 4_000);
70
- expect(formatAgentActivityTitle(tracker.activity, 5_000)).toBe("正在整理上下文 · 1秒");
71
- });
72
-
73
- it("uses explicit DeepCCC phase events before text is emitted", () => {
74
- const tracker = createAgentActivityTracker(1_000);
75
-
76
- expect(updateAgentActivity(tracker, { type: "agent_status", status: "compacting" }, 2_000)).toBe(true);
77
- expect(tracker.activity).toEqual({ kind: "compacting", startedAt: 2_000 });
78
-
79
- expect(updateAgentActivity(tracker, { type: "agent_status", status: "responding" }, 5_000)).toBe(true);
80
- expect(tracker.activity).toEqual({ kind: "responding", startedAt: 5_000 });
81
- });
82
-
83
- it("formats longer elapsed time without decorative animation", () => {
84
- expect(formatAgentActivityTitle({ kind: "thinking", startedAt: 1_000 }, 74_000)).toBe("思考中 · 1分13秒");
85
- });
86
- });