@dyyz1993/create-agent 2.1.0 → 2.1.2

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 (349) hide show
  1. package/package.json +5 -4
  2. package/src/__tests__/cli.test.ts +2 -0
  3. package/src/__tests__/copy.test.ts +110 -122
  4. package/src/__tests__/create-args.test.ts +113 -0
  5. package/src/__tests__/templates.test.ts +29 -3
  6. package/src/__tests__/update.test.ts +129 -0
  7. package/src/cli.ts +30 -32
  8. package/src/commands/create.ts +83 -24
  9. package/src/commands/update.ts +174 -0
  10. package/src/commands/workspace.ts +26 -6
  11. package/src/lib/copy.ts +233 -93
  12. package/src/lib/templates.ts +12 -0
  13. package/templates/agent/.husky/commit-msg +4 -0
  14. package/templates/agent/.husky/pre-commit +11 -0
  15. package/templates/agent/.husky/pre-push +6 -0
  16. package/templates/agent/.prettierignore +6 -0
  17. package/templates/agent/.prettierrc +9 -0
  18. package/templates/agent/CHANGELOG.md +15 -0
  19. package/templates/agent/commitlint.config.js +8 -0
  20. package/templates/agent/package.json +19 -4
  21. package/templates/agent/src/gateway/__tests__/ws-handler-token.test.ts +53 -39
  22. package/templates/agent/src/mainview/App.tsx +14 -12
  23. package/templates/agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +194 -190
  24. package/templates/agent/src/mainview/components/bash/BashPanel.tsx +210 -189
  25. package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +45 -20
  26. package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +41 -21
  27. package/templates/agent/src/mainview/components/debug/DebugPanel.tsx +181 -114
  28. package/templates/agent/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +80 -80
  29. package/templates/agent/src/mainview/components/diff/__tests__/DiffViewerPanel.test.tsx +61 -58
  30. package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +43 -41
  31. package/templates/agent/src/mainview/components/file-preview/FilePreviewOverlay.tsx +69 -50
  32. package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +31 -18
  33. package/templates/agent/src/mainview/components/git/GitPanel.tsx +731 -490
  34. package/templates/agent/src/mainview/components/git/__tests__/GitPanel.test.tsx +157 -139
  35. package/templates/agent/src/mainview/components/layout/AppLayout.tsx +210 -161
  36. package/templates/agent/src/mainview/components/layout/__tests__/AppLayout.test.tsx +130 -122
  37. package/templates/agent/src/mainview/components/rules/RulesPanel.tsx +119 -109
  38. package/templates/agent/src/mainview/components/search/SearchPanel.tsx +123 -117
  39. package/templates/agent/src/mainview/components/search/__tests__/SearchPanel.test.tsx +64 -44
  40. package/templates/agent/src/mainview/components/sidebar/__tests__/PinButton.test.tsx +38 -38
  41. package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +40 -38
  42. package/templates/agent/src/mainview/components/todo/__tests__/TodoPanel.test.tsx +72 -72
  43. package/templates/agent/src/mainview/lib/i18n/locales/en.json +175 -155
  44. package/templates/agent/src/mainview/lib/i18n/locales/zh.json +173 -155
  45. package/templates/agent/src/mainview/stores/use-app-store.ts +96 -86
  46. package/templates/agent/src/mainview/stores/use-explorer-store.ts +292 -275
  47. package/templates/agent/src/mainview/types/index.ts +23 -22
  48. package/templates/agent/src/mainview/utils/file-icon.tsx +17 -21
  49. package/templates/agent/src/shared/handlers/chat.ts +53 -53
  50. package/templates/agent/src/shared/handlers/debug.ts +13 -3
  51. package/templates/browser-agent/.env.example +19 -0
  52. package/templates/browser-agent/.husky/commit-msg +4 -0
  53. package/templates/browser-agent/.husky/pre-commit +11 -0
  54. package/templates/browser-agent/.husky/pre-push +6 -0
  55. package/templates/browser-agent/.prettierignore +6 -0
  56. package/templates/browser-agent/.prettierrc +9 -0
  57. package/templates/browser-agent/AGENTS.md +396 -0
  58. package/templates/browser-agent/CHANGELOG.md +15 -0
  59. package/templates/browser-agent/LICENSE +21 -0
  60. package/templates/browser-agent/README.md +103 -0
  61. package/templates/browser-agent/commitlint.config.js +8 -0
  62. package/templates/browser-agent/electrobun.config.ts +27 -0
  63. package/templates/browser-agent/electron/main.js +46 -0
  64. package/templates/browser-agent/electron/preload.js +5 -0
  65. package/templates/browser-agent/electron-builder.json +40 -0
  66. package/templates/browser-agent/eslint.config.mjs +79 -0
  67. package/templates/browser-agent/llms.txt +24 -0
  68. package/templates/browser-agent/package.json +140 -0
  69. package/templates/browser-agent/postcss.config.js +6 -0
  70. package/templates/browser-agent/scripts/dev.ts +138 -0
  71. package/templates/browser-agent/src/__tests__/hybrid-mode.test.ts +126 -0
  72. package/templates/browser-agent/src/__tests__/server-config-security.test.ts +55 -0
  73. package/templates/browser-agent/src/__tests__/server-config.test.ts +59 -0
  74. package/templates/browser-agent/src/bun/index.ts +130 -0
  75. package/templates/browser-agent/src/bun/three.d.ts +1 -0
  76. package/templates/browser-agent/src/gateway/http-routes.ts +1 -0
  77. package/templates/browser-agent/src/gateway/ipc-transport.ts +68 -0
  78. package/templates/browser-agent/src/gateway/sse-transport.ts +221 -0
  79. package/templates/browser-agent/src/mainview/App.tsx +45 -0
  80. package/templates/browser-agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +202 -0
  81. package/templates/browser-agent/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
  82. package/templates/browser-agent/src/mainview/__tests__/i18n/i18n.test.ts +49 -0
  83. package/templates/browser-agent/src/mainview/__tests__/setup.ts +40 -0
  84. package/templates/browser-agent/src/mainview/__tests__/stores/use-chat-store.test.ts +73 -0
  85. package/templates/browser-agent/src/mainview/__tests__/stores/use-sidebar-store.test.ts +61 -0
  86. package/templates/browser-agent/src/mainview/__tests__/theme-variables.test.ts +36 -0
  87. package/templates/browser-agent/src/mainview/components/assets/AssetsPanel.tsx +139 -0
  88. package/templates/browser-agent/src/mainview/components/chat/ChatPanel.tsx +219 -0
  89. package/templates/browser-agent/src/mainview/components/chat/CommandBar.tsx +184 -0
  90. package/templates/browser-agent/src/mainview/components/chat/MessageBubble.tsx +249 -0
  91. package/templates/browser-agent/src/mainview/components/chat/ToolPicker.tsx +115 -0
  92. package/templates/browser-agent/src/mainview/components/common/ErrorBoundary.tsx +1 -0
  93. package/templates/browser-agent/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
  94. package/templates/browser-agent/src/mainview/components/common/ThemeToggle.tsx +45 -0
  95. package/templates/browser-agent/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
  96. package/templates/browser-agent/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
  97. package/templates/browser-agent/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
  98. package/templates/browser-agent/src/mainview/components/dev/NetworkPanel.tsx +155 -0
  99. package/templates/browser-agent/src/mainview/components/layout/AppLayout.tsx +311 -0
  100. package/templates/browser-agent/src/mainview/components/onboarding/SetupWizard.tsx +310 -0
  101. package/templates/browser-agent/src/mainview/components/process/ProcessPanel.tsx +329 -0
  102. package/templates/browser-agent/src/mainview/components/sidebar/SessionSidebar.tsx +122 -0
  103. package/templates/browser-agent/src/mainview/components/sidebar/SkillSidebar.tsx +115 -0
  104. package/templates/browser-agent/src/mainview/components/topbar/TopBar.tsx +350 -0
  105. package/templates/browser-agent/src/mainview/global.d.ts +13 -0
  106. package/templates/browser-agent/src/mainview/hooks/__tests__/use-breakpoint.test.ts +151 -0
  107. package/templates/browser-agent/src/mainview/hooks/use-agent-chat.ts +157 -0
  108. package/templates/browser-agent/src/mainview/hooks/use-breakpoint.ts +68 -0
  109. package/templates/browser-agent/src/mainview/hooks/use-rpc-init.ts +71 -0
  110. package/templates/browser-agent/src/mainview/hooks/use-sidebar-resize.ts +40 -0
  111. package/templates/browser-agent/src/mainview/index.css +71 -0
  112. package/templates/browser-agent/src/mainview/index.html +12 -0
  113. package/templates/browser-agent/src/mainview/lib/api-client.ts +397 -0
  114. package/templates/browser-agent/src/mainview/lib/i18n/index.ts +30 -0
  115. package/templates/browser-agent/src/mainview/lib/i18n/locales/en.json +130 -0
  116. package/templates/browser-agent/src/mainview/lib/i18n/locales/zh.json +128 -0
  117. package/templates/browser-agent/src/mainview/lib/network-bus.ts +92 -0
  118. package/templates/browser-agent/src/mainview/lib/rpc-cache.ts +94 -0
  119. package/templates/browser-agent/src/mainview/main.tsx +18 -0
  120. package/templates/browser-agent/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
  121. package/templates/browser-agent/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  122. package/templates/browser-agent/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
  123. package/templates/browser-agent/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  124. package/templates/browser-agent/src/mainview/stores/message-batcher.ts +25 -0
  125. package/templates/browser-agent/src/mainview/stores/use-asset-store.ts +65 -0
  126. package/templates/browser-agent/src/mainview/stores/use-chat-store.ts +200 -0
  127. package/templates/browser-agent/src/mainview/stores/use-connection-store.ts +173 -0
  128. package/templates/browser-agent/src/mainview/stores/use-locale-store.ts +27 -0
  129. package/templates/browser-agent/src/mainview/stores/use-log-store.ts +16 -0
  130. package/templates/browser-agent/src/mainview/stores/use-network-panel-store.ts +18 -0
  131. package/templates/browser-agent/src/mainview/stores/use-record-store.ts +147 -0
  132. package/templates/browser-agent/src/mainview/stores/use-session-store.ts +151 -0
  133. package/templates/browser-agent/src/mainview/stores/use-sidebar-store.ts +161 -0
  134. package/templates/browser-agent/src/mainview/stores/use-theme-store.ts +46 -0
  135. package/templates/browser-agent/src/mainview/stores/use-view-store.ts +20 -0
  136. package/templates/browser-agent/src/mainview/types/index.ts +6 -0
  137. package/templates/browser-agent/src/server-config.ts +45 -0
  138. package/templates/browser-agent/src/server.ts +78 -0
  139. package/templates/browser-agent/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
  140. package/templates/browser-agent/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
  141. package/templates/browser-agent/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
  142. package/templates/browser-agent/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
  143. package/templates/browser-agent/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
  144. package/templates/browser-agent/src/shared/handlers/browser.ts +596 -0
  145. package/templates/browser-agent/src/shared/handlers/chat.ts +213 -0
  146. package/templates/browser-agent/src/shared/handlers/file.ts +99 -0
  147. package/templates/browser-agent/src/shared/handlers/index.ts +7 -0
  148. package/templates/browser-agent/src/shared/handlers/session.ts +167 -0
  149. package/templates/browser-agent/src/shared/handlers/system.ts +27 -0
  150. package/templates/browser-agent/src/shared/handlers/timer.ts +34 -0
  151. package/templates/browser-agent/src/shared/http-routes.ts +437 -0
  152. package/templates/browser-agent/src/shared/lib/__tests__/logger.test.ts +92 -0
  153. package/templates/browser-agent/src/shared/lib/__tests__/path-security.test.ts +77 -0
  154. package/templates/browser-agent/src/shared/lib/__tests__/web-server.test.ts +203 -0
  155. package/templates/browser-agent/src/shared/lib/agent.ts +384 -0
  156. package/templates/browser-agent/src/shared/lib/cdp.ts +448 -0
  157. package/templates/browser-agent/src/shared/lib/generate.ts +111 -0
  158. package/templates/browser-agent/src/shared/lib/logger.ts +152 -0
  159. package/templates/browser-agent/src/shared/lib/mock-stream.ts +147 -0
  160. package/templates/browser-agent/src/shared/lib/path-security.ts +38 -0
  161. package/templates/browser-agent/src/shared/lib/port-registry.ts +103 -0
  162. package/templates/browser-agent/src/shared/lib/web-server.ts +127 -0
  163. package/templates/browser-agent/src/shared/lib/xhs-extract.ts +71 -0
  164. package/templates/browser-agent/src/shared/modules/browser.ts +86 -0
  165. package/templates/browser-agent/src/shared/modules/chat.ts +20 -0
  166. package/templates/browser-agent/src/shared/modules/file.ts +40 -0
  167. package/templates/browser-agent/src/shared/modules/session.ts +55 -0
  168. package/templates/browser-agent/src/shared/modules/system.ts +8 -0
  169. package/templates/browser-agent/src/shared/modules/timer.ts +11 -0
  170. package/templates/browser-agent/src/shared/register-all-handlers.ts +36 -0
  171. package/templates/browser-agent/src/shared/rpc-schema.ts +34 -0
  172. package/templates/browser-agent/tailwind.config.js +15 -0
  173. package/templates/browser-agent/tsconfig.ipc.json +14 -0
  174. package/templates/browser-agent/tsconfig.json +36 -0
  175. package/templates/browser-agent/vite.config.ts +3 -0
  176. package/templates/browser-agent/vitest.config.ts +3 -0
  177. package/templates/chat/.husky/commit-msg +4 -0
  178. package/templates/chat/.husky/pre-commit +11 -0
  179. package/templates/chat/.husky/pre-push +6 -0
  180. package/templates/chat/CHANGELOG.md +15 -0
  181. package/templates/chat/commitlint.config.js +8 -0
  182. package/templates/chat/package.json +19 -4
  183. package/templates/chat/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
  184. package/templates/chat/src/mainview/__tests__/setup.ts +32 -29
  185. package/templates/chat/src/mainview/components/chat/ChatPanel.tsx +80 -55
  186. package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +40 -20
  187. package/templates/chat/src/mainview/lib/i18n/locales/en.json +175 -155
  188. package/templates/chat/src/mainview/lib/i18n/locales/zh.json +173 -155
  189. package/templates/chat/src/shared/handlers/chat.ts +17 -19
  190. package/templates/chat/src/shared/handlers/debug.ts +12 -2
  191. package/templates/cowork/.env.example +8 -0
  192. package/templates/cowork/.prettierignore +6 -0
  193. package/templates/cowork/.prettierrc +9 -0
  194. package/templates/cowork/AGENTS.md +236 -0
  195. package/templates/cowork/CHANGELOG.md +15 -0
  196. package/templates/cowork/LICENSE +21 -0
  197. package/templates/cowork/README.md +103 -0
  198. package/templates/cowork/commitlint.config.js +8 -0
  199. package/templates/cowork/docs/CODE-VIEW-PLAN.md +637 -0
  200. package/templates/cowork/docs/CODE-VIEW-V2.md +196 -0
  201. package/templates/cowork/docs/CODE-VIEW-V4.md +150 -0
  202. package/templates/cowork/electrobun.config.ts +27 -0
  203. package/templates/cowork/eslint.config.mjs +79 -0
  204. package/templates/cowork/llms.txt +24 -0
  205. package/templates/cowork/package.json +87 -0
  206. package/templates/cowork/postcss.config.js +6 -0
  207. package/templates/cowork/scripts/dev.ts +138 -0
  208. package/templates/cowork/src/__tests__/hybrid-mode.test.ts +126 -0
  209. package/templates/cowork/src/__tests__/server-config-security.test.ts +55 -0
  210. package/templates/cowork/src/__tests__/server-config.test.ts +59 -0
  211. package/templates/cowork/src/bun/index.ts +130 -0
  212. package/templates/cowork/src/bun/three.d.ts +1 -0
  213. package/templates/cowork/src/gateway/http-routes.ts +1 -0
  214. package/templates/cowork/src/gateway/ipc-transport.ts +68 -0
  215. package/templates/cowork/src/gateway/sse-transport.ts +231 -0
  216. package/templates/cowork/src/mainview/App.tsx +45 -0
  217. package/templates/cowork/src/mainview/__tests__/hooks/use-rpc-init.test.ts +202 -0
  218. package/templates/cowork/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
  219. package/templates/cowork/src/mainview/__tests__/i18n/i18n.test.ts +49 -0
  220. package/templates/cowork/src/mainview/__tests__/setup.ts +40 -0
  221. package/templates/cowork/src/mainview/__tests__/stores/use-chat-store.test.ts +73 -0
  222. package/templates/cowork/src/mainview/__tests__/stores/use-sidebar-store.test.ts +61 -0
  223. package/templates/cowork/src/mainview/__tests__/theme-variables.test.ts +36 -0
  224. package/templates/cowork/src/mainview/components/chat/TaskChat.tsx +311 -0
  225. package/templates/cowork/src/mainview/components/chat/__tests__/localhost-links.test.ts +76 -0
  226. package/templates/cowork/src/mainview/components/common/ErrorBoundary.tsx +1 -0
  227. package/templates/cowork/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
  228. package/templates/cowork/src/mainview/components/common/ThemeToggle.tsx +45 -0
  229. package/templates/cowork/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
  230. package/templates/cowork/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
  231. package/templates/cowork/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
  232. package/templates/cowork/src/mainview/components/dev/NetworkPanel.tsx +155 -0
  233. package/templates/cowork/src/mainview/components/layout/AppLayout.tsx +216 -0
  234. package/templates/cowork/src/mainview/components/right/ArtifactsPanel.tsx +54 -0
  235. package/templates/cowork/src/mainview/components/right/ContextPanel.tsx +133 -0
  236. package/templates/cowork/src/mainview/components/right/ElementPicker.tsx +206 -0
  237. package/templates/cowork/src/mainview/components/right/PreviewBlock.tsx +155 -0
  238. package/templates/cowork/src/mainview/components/right/ProgressPanel.tsx +85 -0
  239. package/templates/cowork/src/mainview/components/sidebar/TaskSidebar.tsx +211 -0
  240. package/templates/cowork/src/mainview/components/topbar/TopBar.tsx +86 -0
  241. package/templates/cowork/src/mainview/global.d.ts +13 -0
  242. package/templates/cowork/src/mainview/hooks/__tests__/use-breakpoint.test.ts +123 -0
  243. package/templates/cowork/src/mainview/hooks/use-breakpoint.ts +53 -0
  244. package/templates/cowork/src/mainview/hooks/use-rpc-init.ts +26 -0
  245. package/templates/cowork/src/mainview/hooks/use-sidebar-resize.ts +40 -0
  246. package/templates/cowork/src/mainview/index.css +89 -0
  247. package/templates/cowork/src/mainview/index.html +12 -0
  248. package/templates/cowork/src/mainview/lib/api-client.ts +404 -0
  249. package/templates/cowork/src/mainview/lib/i18n/index.ts +30 -0
  250. package/templates/cowork/src/mainview/lib/i18n/locales/en.json +130 -0
  251. package/templates/cowork/src/mainview/lib/i18n/locales/zh.json +128 -0
  252. package/templates/cowork/src/mainview/lib/network-bus.ts +92 -0
  253. package/templates/cowork/src/mainview/lib/rpc-cache.ts +94 -0
  254. package/templates/cowork/src/mainview/main.tsx +18 -0
  255. package/templates/cowork/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
  256. package/templates/cowork/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  257. package/templates/cowork/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
  258. package/templates/cowork/src/mainview/stores/__tests__/use-preview-store.test.ts +193 -0
  259. package/templates/cowork/src/mainview/stores/__tests__/use-sidebar-store.test.ts +81 -0
  260. package/templates/cowork/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  261. package/templates/cowork/src/mainview/stores/message-batcher.ts +25 -0
  262. package/templates/cowork/src/mainview/stores/use-chat-store.ts +198 -0
  263. package/templates/cowork/src/mainview/stores/use-connection-store.ts +168 -0
  264. package/templates/cowork/src/mainview/stores/use-context-store.ts +68 -0
  265. package/templates/cowork/src/mainview/stores/use-locale-store.ts +27 -0
  266. package/templates/cowork/src/mainview/stores/use-log-store.ts +16 -0
  267. package/templates/cowork/src/mainview/stores/use-network-panel-store.ts +18 -0
  268. package/templates/cowork/src/mainview/stores/use-output-store.ts +47 -0
  269. package/templates/cowork/src/mainview/stores/use-preview-store.ts +97 -0
  270. package/templates/cowork/src/mainview/stores/use-sidebar-store.ts +272 -0
  271. package/templates/cowork/src/mainview/stores/use-task-store.ts +86 -0
  272. package/templates/cowork/src/mainview/stores/use-theme-store.ts +46 -0
  273. package/templates/cowork/src/mainview/stores/use-view-store.ts +29 -0
  274. package/templates/cowork/src/mainview/types/index.ts +6 -0
  275. package/templates/cowork/src/server-config.ts +44 -0
  276. package/templates/cowork/src/server.ts +78 -0
  277. package/templates/cowork/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
  278. package/templates/cowork/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
  279. package/templates/cowork/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
  280. package/templates/cowork/src/shared/handlers/__tests__/preview-handler.test.ts +172 -0
  281. package/templates/cowork/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
  282. package/templates/cowork/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
  283. package/templates/cowork/src/shared/handlers/chat.ts +213 -0
  284. package/templates/cowork/src/shared/handlers/context.ts +72 -0
  285. package/templates/cowork/src/shared/handlers/file.ts +99 -0
  286. package/templates/cowork/src/shared/handlers/index.ts +9 -0
  287. package/templates/cowork/src/shared/handlers/output.ts +59 -0
  288. package/templates/cowork/src/shared/handlers/preview.ts +81 -0
  289. package/templates/cowork/src/shared/handlers/system.ts +27 -0
  290. package/templates/cowork/src/shared/handlers/task.ts +101 -0
  291. package/templates/cowork/src/shared/handlers/timer.ts +34 -0
  292. package/templates/cowork/src/shared/http-routes.ts +444 -0
  293. package/templates/cowork/src/shared/lib/__tests__/logger.test.ts +92 -0
  294. package/templates/cowork/src/shared/lib/__tests__/path-security.test.ts +77 -0
  295. package/templates/cowork/src/shared/lib/__tests__/web-server.test.ts +203 -0
  296. package/templates/cowork/src/shared/lib/logger.ts +152 -0
  297. package/templates/cowork/src/shared/lib/path-security.ts +38 -0
  298. package/templates/cowork/src/shared/lib/port-registry.ts +103 -0
  299. package/templates/cowork/src/shared/lib/web-server.ts +127 -0
  300. package/templates/cowork/src/shared/modules/chat.ts +20 -0
  301. package/templates/cowork/src/shared/modules/context.ts +32 -0
  302. package/templates/cowork/src/shared/modules/file.ts +40 -0
  303. package/templates/cowork/src/shared/modules/output.ts +20 -0
  304. package/templates/cowork/src/shared/modules/preview.ts +44 -0
  305. package/templates/cowork/src/shared/modules/system.ts +8 -0
  306. package/templates/cowork/src/shared/modules/task.ts +31 -0
  307. package/templates/cowork/src/shared/modules/timer.ts +11 -0
  308. package/templates/cowork/src/shared/register-all-handlers.ts +36 -0
  309. package/templates/cowork/src/shared/rpc-schema.ts +38 -0
  310. package/templates/cowork/tailwind.config.js +15 -0
  311. package/templates/cowork/test-upload.txt +0 -0
  312. package/templates/cowork/tsconfig.ipc.json +14 -0
  313. package/templates/cowork/tsconfig.json +36 -0
  314. package/templates/cowork/vite.config.ts +3 -0
  315. package/templates/cowork/vitest.config.ts +3 -0
  316. package/templates/general/.husky/commit-msg +4 -0
  317. package/templates/general/.husky/pre-commit +11 -0
  318. package/templates/general/.husky/pre-push +6 -0
  319. package/templates/general/CHANGELOG.md +15 -0
  320. package/templates/general/commitlint.config.js +8 -0
  321. package/templates/general/package.json +19 -4
  322. package/templates/general/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
  323. package/templates/general/src/mainview/__tests__/setup.ts +32 -29
  324. package/templates/general/src/mainview/components/chat/ChatPanel.tsx +80 -55
  325. package/templates/general/src/mainview/components/common/ThemeToggle.tsx +40 -20
  326. package/templates/general/src/mainview/components/debug/DebugPanel.tsx +170 -156
  327. package/templates/general/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +169 -0
  328. package/templates/general/src/mainview/components/explorer/ConfirmDialog.tsx +42 -42
  329. package/templates/general/src/mainview/components/explorer/ContextMenu.tsx +72 -67
  330. package/templates/general/src/mainview/components/feed/FeedPanel.tsx +7 -5
  331. package/templates/general/src/mainview/components/file-preview/FilePreviewOverlay.tsx +64 -45
  332. package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +24 -7
  333. package/templates/general/src/mainview/components/git/GitPanel.tsx +700 -473
  334. package/templates/general/src/mainview/components/layout/AppLayout.tsx +123 -114
  335. package/templates/general/src/mainview/components/search/SearchPanel.tsx +15 -19
  336. package/templates/general/src/mainview/lib/i18n/locales/en.json +175 -155
  337. package/templates/general/src/mainview/lib/i18n/locales/zh.json +173 -155
  338. package/templates/general/src/mainview/stores/__tests__/use-explorer-store.test.ts +259 -0
  339. package/templates/general/src/mainview/stores/__tests__/use-feed-store.test.ts +160 -0
  340. package/templates/general/src/mainview/stores/__tests__/use-git-store.test.ts +313 -0
  341. package/templates/general/src/mainview/stores/__tests__/use-notification-store.test.ts +115 -0
  342. package/templates/general/src/mainview/stores/__tests__/use-sidebar-store.test.ts +120 -0
  343. package/templates/general/src/mainview/stores/use-explorer-store.ts +277 -260
  344. package/templates/general/src/mainview/types/index.ts +21 -20
  345. package/templates/general/src/shared/handlers/chat.ts +1 -1
  346. package/templates/general/src/shared/handlers/debug.ts +12 -2
  347. package/templates/shared/components/ErrorBoundary.tsx +0 -1
  348. package/templates/shared/vite-base.config.ts +8 -7
  349. /package/templates/{shared → browser-agent}/test-upload.txt +0 -0
@@ -0,0 +1,231 @@
1
+ /**
2
+ * SSE + HTTP Transport for the web gateway.
3
+ *
4
+ * Replaces the WebSocket handler. Two HTTP endpoints:
5
+ * GET /api/events?token=xxx → opens an SSE stream (server→client push + response delivery)
6
+ * POST /api/rpc?token=xxx → sends an RPC request (client→server)
7
+ *
8
+ * Each SSE client gets its own RPCServer instance (matching the previous
9
+ * per-connection WebSocket model). POST /api/rpc carries the clientId to
10
+ * route the message into the correct RPCServer.
11
+ */
12
+
13
+ import type { IncomingMessage, Server, ServerResponse } from 'http';
14
+ import { randomUUID, timingSafeEqual } from 'crypto';
15
+ import { RPCServer, type Transport } from '@dyyz1993/rpc-core';
16
+ import { registerAllHandlers } from '../shared/register-all-handlers';
17
+ import { createLogger } from '../shared/lib/logger';
18
+
19
+ const log = createLogger('gateway');
20
+
21
+ function safeEqual(a: string, b: string): boolean {
22
+ const bufA = Buffer.from(a);
23
+ const bufB = Buffer.from(b);
24
+ if (bufA.length !== bufB.length) return false;
25
+ return timingSafeEqual(bufA as unknown as Buffer, bufB as unknown as Buffer);
26
+ }
27
+
28
+ export interface SseHandlerDeps {
29
+ config: { readonly port: number; readonly authToken: string; readonly maxUploadSize: number };
30
+ }
31
+
32
+ interface SseClient {
33
+ id: string;
34
+ res: ServerResponse;
35
+ rpcServer: RPCServer;
36
+ transport: Transport;
37
+ messageHandlers: Set<(msg: unknown) => void>;
38
+ cleanup: () => void;
39
+ }
40
+
41
+ export interface SseHandler {
42
+ clients: Map<string, SseClient>;
43
+ handleSseConnect: (req: IncomingMessage, res: ServerResponse) => void;
44
+ handleRpcPost: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
45
+ close: () => void;
46
+ }
47
+
48
+ /**
49
+ * Build a Transport that bridges an SSE response stream (for server→client)
50
+ * with the accumulated message handlers (fed by POST /api/rpc).
51
+ */
52
+ function createSseTransport(client: SseClient): Transport {
53
+ const send = async (message: unknown): Promise<void> => {
54
+ if (client.res.writableEnded) return;
55
+ client.res.write(`data: ${JSON.stringify(message)}\n\n`);
56
+ };
57
+
58
+ const onMessage = (handler: (msg: unknown) => void): (() => void) => {
59
+ client.messageHandlers.add(handler);
60
+ return () => {
61
+ client.messageHandlers.delete(handler);
62
+ };
63
+ };
64
+
65
+ const onError = (): (() => void) => {
66
+ return () => {};
67
+ };
68
+
69
+ const onDisconnect = (handler: () => void): (() => void) => {
70
+ // onClose is wired in handleSseConnect; expose a registration stub
71
+ client.cleanup = handler;
72
+ return () => {
73
+ if (client.cleanup === handler) client.cleanup = () => {};
74
+ };
75
+ };
76
+
77
+ return {
78
+ send,
79
+ onMessage,
80
+ onError,
81
+ onDisconnect,
82
+ isConnected: () => !client.res.writableEnded,
83
+ close: () => {},
84
+ };
85
+ }
86
+
87
+ export function createSseHandler(_httpServer: Server, deps: SseHandlerDeps): SseHandler {
88
+ void deps; // deps kept for API symmetry with createWsHandler; config is enforced by http-routes auth
89
+ const clients = new Map<string, SseClient>();
90
+
91
+ function handleSseConnect(req: IncomingMessage, res: ServerResponse): void {
92
+ // SSE response headers — disable proxy buffering for real-time streaming
93
+ res.writeHead(200, {
94
+ 'Content-Type': 'text/event-stream',
95
+ 'Cache-Control': 'no-cache, no-transform',
96
+ Connection: 'keep-alive',
97
+ 'X-Accel-Buffering': 'no',
98
+ 'Access-Control-Allow-Origin': '*',
99
+ });
100
+
101
+ const id = randomUUID();
102
+ const messageHandlers = new Set<(msg: unknown) => void>();
103
+
104
+ const client: SseClient = {
105
+ id,
106
+ res,
107
+ messageHandlers,
108
+ rpcServer: undefined as unknown as RPCServer,
109
+ transport: undefined as unknown as Transport,
110
+ cleanup: () => {},
111
+ };
112
+
113
+ client.transport = createSseTransport(client);
114
+ client.rpcServer = new RPCServer(client.transport);
115
+ registerAllHandlers(client.rpcServer, { platform: 'web' });
116
+
117
+ clients.set(id, client);
118
+
119
+ // Send the clientId as the first event so the client can use it for POSTs
120
+ res.write(`event: ready\ndata: ${JSON.stringify({ clientId: id })}\n\n`);
121
+
122
+ // Heartbeat every 25s to keep proxies from timing out
123
+ const heartbeat = setInterval(() => {
124
+ if (res.writableEnded) return;
125
+ res.write(': ping\n\n');
126
+ }, 25000);
127
+
128
+ log.info('SSE client connected', { id, total: clients.size });
129
+
130
+ const onClose = (): void => {
131
+ clearInterval(heartbeat);
132
+ clients.delete(id);
133
+ client.cleanup();
134
+ client.rpcServer.close();
135
+ log.info('SSE client disconnected', { id, total: clients.size });
136
+ };
137
+
138
+ req.on('close', onClose);
139
+ req.on('error', onClose);
140
+ }
141
+
142
+ async function handleRpcPost(req: IncomingMessage, res: ServerResponse): Promise<void> {
143
+ const url = req.url ? new URL(req.url, 'http://localhost') : null;
144
+ const clientId = url?.searchParams.get('clientId');
145
+
146
+ if (!clientId) {
147
+ res.writeHead(400, { 'Content-Type': 'application/json' });
148
+ res.end(JSON.stringify({ error: 'Missing clientId' }));
149
+ return;
150
+ }
151
+
152
+ const client = clients.get(clientId);
153
+ if (!client) {
154
+ res.writeHead(404, { 'Content-Type': 'application/json' });
155
+ res.end(JSON.stringify({ error: 'Unknown clientId (SSE not connected)' }));
156
+ return;
157
+ }
158
+
159
+ // Read JSON body
160
+ let body = '';
161
+ for await (const chunk of req) {
162
+ body += typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8');
163
+ }
164
+
165
+ let message: unknown;
166
+ try {
167
+ message = JSON.parse(body);
168
+ } catch {
169
+ res.writeHead(400, { 'Content-Type': 'application/json' });
170
+ res.end(JSON.stringify({ error: 'Invalid JSON' }));
171
+ return;
172
+ }
173
+
174
+ // Acknowledge receipt — the actual RPC response will arrive via the SSE stream
175
+ res.writeHead(202, { 'Content-Type': 'application/json' });
176
+ res.end(JSON.stringify({ accepted: true }));
177
+
178
+ // Route the message into the client's RPCServer via its transport handlers
179
+ const msg = message as Record<string, unknown>;
180
+ const msgType = msg?.type;
181
+ const msgMethod = msg?.method;
182
+ log.info('RPC routing', {
183
+ clientId,
184
+ msgType,
185
+ msgMethod,
186
+ handlerCount: client.messageHandlers.size,
187
+ });
188
+ for (const handler of client.messageHandlers) {
189
+ try {
190
+ handler(message);
191
+ } catch (err) {
192
+ log.error('RPC handler error', {
193
+ clientId,
194
+ msgMethod,
195
+ error: err instanceof Error ? err.message : String(err),
196
+ });
197
+ }
198
+ }
199
+ }
200
+
201
+ return {
202
+ clients,
203
+ handleSseConnect,
204
+ handleRpcPost,
205
+ close: (): void => {
206
+ for (const client of clients.values()) {
207
+ client.rpcServer.close();
208
+ if (!client.res.writableEnded) {
209
+ client.res.end();
210
+ }
211
+ }
212
+ clients.clear();
213
+ },
214
+ };
215
+ }
216
+
217
+ export function verifyToken(req: IncomingMessage, authToken: string): boolean {
218
+ const auth = req.headers['authorization'];
219
+ if (auth && safeEqual(auth, `Bearer ${authToken}`)) return true;
220
+
221
+ if (req.url) {
222
+ try {
223
+ const url = new URL(req.url, 'http://localhost');
224
+ const token = url.searchParams.get('token');
225
+ if (token && safeEqual(token, authToken)) return true;
226
+ } catch {
227
+ /* invalid URL */
228
+ }
229
+ }
230
+ return false;
231
+ }
@@ -0,0 +1,45 @@
1
+ import type { ReactNode } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import { useConnectionStore } from "./stores/use-connection-store";
4
+ import { useBreakpointSync } from "./hooks/use-breakpoint";
5
+ import { useRpcInit } from "./hooks/use-rpc-init";
6
+ import { useSidebarResize } from "./hooks/use-sidebar-resize";
7
+ import { AppLayout } from "./components/layout/AppLayout";
8
+ import { ErrorBoundary as _EB } from "./components/common/ErrorBoundary";
9
+
10
+ const ErrorBoundary = _EB as unknown as React.FC<{ children: ReactNode }>;
11
+
12
+ function App() {
13
+ const { t } = useTranslation();
14
+ const ready = useConnectionStore((s) => s.ready);
15
+
16
+ useBreakpointSync();
17
+ useRpcInit();
18
+
19
+ const { sidebarWidth, handleResizeStart } = useSidebarResize();
20
+
21
+ // RPC 连接尚未建立时显示加载
22
+ if (!ready) {
23
+ return (
24
+ <div className="h-screen bg-[var(--color-bg-primary)] flex items-center justify-center">
25
+ <div className="text-center">
26
+ <div className="inline-block w-8 h-8 border-2 border-[var(--color-text-accent)] border-t-transparent rounded-full animate-spin mb-4" />
27
+ <div className="text-[var(--color-text-tertiary)] text-sm">
28
+ {t("app.connecting")}
29
+ </div>
30
+ </div>
31
+ </div>
32
+ );
33
+ }
34
+
35
+ return (
36
+ <ErrorBoundary>
37
+ <AppLayout
38
+ sidebarWidth={sidebarWidth}
39
+ handleResizeStart={handleResizeStart}
40
+ />
41
+ </ErrorBoundary>
42
+ );
43
+ }
44
+
45
+ export default App;
@@ -0,0 +1,202 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { renderHook, waitFor } from '@testing-library/react';
3
+
4
+ const mockInitializeConnection = vi.fn();
5
+ const mockAddLog = vi.fn();
6
+ const mockSubscribe = vi.fn();
7
+ const mockUnsubscribe = vi.fn();
8
+ const mockCall = vi.fn();
9
+
10
+ const mockChatSetMessages = vi.fn();
11
+ const mockChatSetState = vi.fn();
12
+
13
+ let mockConnectionState: Record<string, unknown>;
14
+ let mockLogState: Record<string, unknown>;
15
+ let mockChatState: Record<string, unknown>;
16
+
17
+ function resetMockConnectionState(overrides?: Partial<Record<string, unknown>>) {
18
+ mockConnectionState = {
19
+ ready: false,
20
+ initializeConnection: mockInitializeConnection,
21
+ ...overrides,
22
+ };
23
+ }
24
+
25
+ function resetMockLogState(overrides?: Partial<Record<string, unknown>>) {
26
+ mockLogState = {
27
+ addLog: mockAddLog,
28
+ ...overrides,
29
+ };
30
+ }
31
+
32
+ function resetMockChatState(overrides?: Partial<Record<string, unknown>>) {
33
+ mockChatState = {
34
+ setMessages: mockChatSetMessages,
35
+ messages: [],
36
+ ...overrides,
37
+ };
38
+ }
39
+
40
+ vi.mock('../../stores/use-connection-store', () => {
41
+ return {
42
+ useConnectionStore: Object.assign(
43
+ (selector: (s: Record<string, unknown>) => unknown) => selector(mockConnectionState),
44
+ {
45
+ getState: () => mockConnectionState,
46
+ setState: (
47
+ partial:
48
+ | Partial<Record<string, unknown>>
49
+ | ((prev: Record<string, unknown>) => Partial<Record<string, unknown>>),
50
+ ) => {
51
+ const next = typeof partial === 'function' ? partial(mockConnectionState) : partial;
52
+ mockConnectionState = { ...mockConnectionState, ...next };
53
+ },
54
+ },
55
+ ),
56
+ };
57
+ });
58
+
59
+ vi.mock('../../stores/use-log-store', () => {
60
+ return {
61
+ useLogStore: Object.assign(
62
+ (selector: (s: Record<string, unknown>) => unknown) => selector(mockLogState),
63
+ {
64
+ getState: () => mockLogState,
65
+ setState: (
66
+ partial:
67
+ | Partial<Record<string, unknown>>
68
+ | ((prev: Record<string, unknown>) => Partial<Record<string, unknown>>),
69
+ ) => {
70
+ const next = typeof partial === 'function' ? partial(mockLogState) : partial;
71
+ mockLogState = { ...mockLogState, ...next };
72
+ },
73
+ },
74
+ ),
75
+ };
76
+ });
77
+
78
+ vi.mock('../../stores/use-chat-store', () => ({
79
+ useChatStore: Object.assign(
80
+ (selector: (s: Record<string, unknown>) => unknown) => selector(mockChatState),
81
+ {
82
+ getState: () => mockChatState,
83
+ setState: mockChatSetState,
84
+ },
85
+ ),
86
+ }));
87
+
88
+ vi.mock('../../lib/api-client', () => ({
89
+ apiClient: {
90
+ subscribe: mockSubscribe,
91
+ unsubscribe: mockUnsubscribe,
92
+ call: mockCall,
93
+ },
94
+ }));
95
+
96
+ describe('useRpcInit', () => {
97
+ beforeEach(() => {
98
+ vi.clearAllMocks();
99
+ mockSubscribe.mockResolvedValue('sub-1');
100
+ mockCall.mockResolvedValue({ messages: [] });
101
+ resetMockLogState();
102
+ resetMockChatState();
103
+ });
104
+
105
+ it('should call initializeConnection on mount', async () => {
106
+ resetMockConnectionState({ ready: false });
107
+ const { useRpcInit } = await import('../../hooks/use-rpc-init');
108
+ renderHook(() => useRpcInit());
109
+ expect(mockInitializeConnection).toHaveBeenCalledTimes(1);
110
+ });
111
+
112
+ it('should not subscribe when not ready', async () => {
113
+ resetMockConnectionState({ ready: false });
114
+ const { useRpcInit } = await import('../../hooks/use-rpc-init');
115
+ renderHook(() => useRpcInit());
116
+ expect(mockSubscribe).not.toHaveBeenCalled();
117
+ expect(mockCall).not.toHaveBeenCalled();
118
+ });
119
+
120
+ it('should subscribe and load history when ready', async () => {
121
+ resetMockConnectionState({ ready: true });
122
+ const { useRpcInit } = await import('../../hooks/use-rpc-init');
123
+ renderHook(() => useRpcInit());
124
+
125
+ await waitFor(() => {
126
+ expect(mockSubscribe).toHaveBeenCalledWith('chat.message', expect.any(Function), {});
127
+ });
128
+
129
+ await waitFor(() => {
130
+ expect(mockCall).toHaveBeenCalledWith('chat.list', { limit: 100 });
131
+ });
132
+ });
133
+
134
+ it('should load history messages into chat store', async () => {
135
+ const messages = [
136
+ { id: '1', role: 'user' as const, content: 'hello', timestamp: 1000 },
137
+ { id: '2', role: 'assistant' as const, content: 'world', timestamp: 2000 },
138
+ ];
139
+ mockCall.mockResolvedValue({ messages });
140
+
141
+ resetMockConnectionState({ ready: true });
142
+ const { useRpcInit } = await import('../../hooks/use-rpc-init');
143
+ renderHook(() => useRpcInit());
144
+
145
+ await waitFor(() => {
146
+ expect(mockChatSetMessages).toHaveBeenCalled();
147
+ });
148
+
149
+ expect(mockAddLog).toHaveBeenCalledWith('Loaded 2 history messages');
150
+ });
151
+
152
+ it('should unsubscribe on cleanup when ready', async () => {
153
+ resetMockConnectionState({ ready: true });
154
+ const { useRpcInit } = await import('../../hooks/use-rpc-init');
155
+ const { unmount } = renderHook(() => useRpcInit());
156
+
157
+ await waitFor(() => {
158
+ expect(mockSubscribe).toHaveBeenCalled();
159
+ });
160
+
161
+ unmount();
162
+ expect(mockUnsubscribe).toHaveBeenCalledWith('sub-1');
163
+ });
164
+
165
+ it('should handle history load failure gracefully', async () => {
166
+ mockCall.mockRejectedValue(new Error('Network error'));
167
+ resetMockConnectionState({ ready: true });
168
+ const { useRpcInit } = await import('../../hooks/use-rpc-init');
169
+ renderHook(() => useRpcInit());
170
+
171
+ await waitFor(() => {
172
+ expect(mockAddLog).toHaveBeenCalledWith(expect.stringContaining('Failed to load history'));
173
+ });
174
+ });
175
+
176
+ it('should forward chat.message payloads to chat store via setState', async () => {
177
+ let subscriptionHandler: ((payload: Record<string, unknown>) => void) | undefined;
178
+ mockSubscribe.mockImplementation(
179
+ async (_event: string, handler: (p: Record<string, unknown>) => void) => {
180
+ subscriptionHandler = handler;
181
+ return 'sub-1';
182
+ },
183
+ );
184
+
185
+ resetMockConnectionState({ ready: true });
186
+ const { useRpcInit } = await import('../../hooks/use-rpc-init');
187
+ renderHook(() => useRpcInit());
188
+
189
+ await waitFor(() => {
190
+ expect(mockSubscribe).toHaveBeenCalled();
191
+ });
192
+
193
+ subscriptionHandler!({
194
+ id: 'msg-1',
195
+ role: 'assistant',
196
+ content: 'test message',
197
+ timestamp: 12345,
198
+ });
199
+
200
+ expect(mockChatSetState).toHaveBeenCalled();
201
+ });
202
+ });
@@ -0,0 +1,127 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { renderHook, act } from "@testing-library/react";
3
+ import { useSidebarStore } from "../../stores/use-sidebar-store";
4
+
5
+ describe("useSidebarResize", () => {
6
+ beforeEach(() => {
7
+ act(() => {
8
+ useSidebarStore.setState({
9
+ sidebarWidth: 280,
10
+ isPinned: true,
11
+ activePanel: "explorer",
12
+ breakpoint: "desktop",
13
+ drawerOpen: false,
14
+ });
15
+ });
16
+ });
17
+
18
+ it("should return current sidebar width", async () => {
19
+ const { useSidebarResize } = await import("../../hooks/use-sidebar-resize");
20
+ const { result } = renderHook(() => useSidebarResize());
21
+ expect(result.current.sidebarWidth).toBe(280);
22
+ });
23
+
24
+ it("should provide handleResizeStart function", async () => {
25
+ const { useSidebarResize } = await import("../../hooks/use-sidebar-resize");
26
+ const { result } = renderHook(() => useSidebarResize());
27
+ expect(typeof result.current.handleResizeStart).toBe("function");
28
+ });
29
+
30
+ it("should update width on mouse drag", async () => {
31
+ const { useSidebarResize } = await import("../../hooks/use-sidebar-resize");
32
+ const { result } = renderHook(() => useSidebarResize());
33
+
34
+ const mouseDownEvent = new MouseEvent("mousedown", {
35
+ clientX: 280,
36
+ bubbles: true,
37
+ });
38
+ Object.defineProperty(mouseDownEvent, "preventDefault", {
39
+ value: vi.fn(),
40
+ });
41
+
42
+ act(() => {
43
+ result.current.handleResizeStart(mouseDownEvent);
44
+ });
45
+
46
+ const moveEvent = new MouseEvent("mousemove", {
47
+ clientX: 330,
48
+ bubbles: true,
49
+ });
50
+
51
+ act(() => {
52
+ window.dispatchEvent(moveEvent);
53
+ });
54
+
55
+ expect(useSidebarStore.getState().sidebarWidth).toBe(330);
56
+
57
+ const upEvent = new MouseEvent("mouseup");
58
+ act(() => {
59
+ window.dispatchEvent(upEvent);
60
+ });
61
+ });
62
+
63
+ it("should clean up cursor and listeners on mouseup", async () => {
64
+ const { useSidebarResize } = await import("../../hooks/use-sidebar-resize");
65
+ const { result } = renderHook(() => useSidebarResize());
66
+
67
+ const mouseDownEvent = new MouseEvent("mousedown", {
68
+ clientX: 280,
69
+ bubbles: true,
70
+ });
71
+ Object.defineProperty(mouseDownEvent, "preventDefault", {
72
+ value: vi.fn(),
73
+ });
74
+
75
+ act(() => {
76
+ result.current.handleResizeStart(mouseDownEvent);
77
+ });
78
+
79
+ expect(document.body.style.cursor).toBe("col-resize");
80
+ expect(document.body.style.userSelect).toBe("none");
81
+
82
+ const upEvent = new MouseEvent("mouseup");
83
+ act(() => {
84
+ window.dispatchEvent(upEvent);
85
+ });
86
+
87
+ expect(document.body.style.cursor).toBe("");
88
+ expect(document.body.style.userSelect).toBe("");
89
+ });
90
+
91
+ it("should clamp width to min/max bounds", async () => {
92
+ act(() => {
93
+ useSidebarStore.setState({ sidebarWidth: 200 });
94
+ });
95
+
96
+ const { useSidebarResize } = await import("../../hooks/use-sidebar-resize");
97
+ const { result } = renderHook(() => useSidebarResize());
98
+
99
+ const mouseDownEvent = new MouseEvent("mousedown", {
100
+ clientX: 200,
101
+ bubbles: true,
102
+ });
103
+ Object.defineProperty(mouseDownEvent, "preventDefault", {
104
+ value: vi.fn(),
105
+ });
106
+
107
+ act(() => {
108
+ result.current.handleResizeStart(mouseDownEvent);
109
+ });
110
+
111
+ const moveEvent = new MouseEvent("mousemove", {
112
+ clientX: 0,
113
+ bubbles: true,
114
+ });
115
+
116
+ act(() => {
117
+ window.dispatchEvent(moveEvent);
118
+ });
119
+
120
+ expect(useSidebarStore.getState().sidebarWidth).toBeGreaterThanOrEqual(180);
121
+
122
+ const upEvent = new MouseEvent("mouseup");
123
+ act(() => {
124
+ window.dispatchEvent(upEvent);
125
+ });
126
+ });
127
+ });
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+
3
+ describe("i18n configuration", () => {
4
+ beforeEach(async () => {
5
+ vi.resetModules();
6
+ });
7
+
8
+ it("should export configured i18n instance", async () => {
9
+ const i18n = await import("../../lib/i18n");
10
+ expect(i18n.default).toBeDefined();
11
+ expect(i18n.default.isInitialized).toBe(true);
12
+ });
13
+
14
+ it("should have English translations", async () => {
15
+ const i18n = await import("../../lib/i18n");
16
+ expect(
17
+ i18n.default.getResource("en", "translation", "app.title")
18
+ ).toBeDefined();
19
+ });
20
+
21
+ it("should have Chinese translations", async () => {
22
+ const i18n = await import("../../lib/i18n");
23
+ expect(
24
+ i18n.default.getResource("zh", "translation", "app.title")
25
+ ).toBeDefined();
26
+ });
27
+
28
+ it("should change language", async () => {
29
+ const i18n = await import("../../lib/i18n");
30
+ await i18n.default.changeLanguage("zh");
31
+ expect(i18n.default.language).toBe("zh");
32
+
33
+ await i18n.default.changeLanguage("en");
34
+ expect(i18n.default.language).toBe("en");
35
+ });
36
+
37
+ it("should translate keys correctly in English", async () => {
38
+ const i18n = await import("../../lib/i18n");
39
+ await i18n.default.changeLanguage("en");
40
+ expect(i18n.default.t("app.title")).toBe("Browser Agent");
41
+ });
42
+
43
+ it("should translate keys correctly in Chinese", async () => {
44
+ const i18n = await import("../../lib/i18n");
45
+ await i18n.default.changeLanguage("zh");
46
+ expect(i18n.default.t("app.title")).toBe("Browser Agent");
47
+ expect(i18n.default.t("sidebar.explorer")).toBe("资源管理器");
48
+ });
49
+ });
@@ -0,0 +1,40 @@
1
+ import { expect } from "vitest";
2
+ import * as matchers from "@testing-library/jest-dom/matchers";
3
+
4
+ expect.extend(matchers);
5
+
6
+ Object.defineProperty(window, "matchMedia", {
7
+ writable: true,
8
+ value: (query: string) => ({
9
+ matches: false,
10
+ media: query,
11
+ onchange: null,
12
+ addListener: () => {},
13
+ removeListener: () => {},
14
+ addEventListener: () => {},
15
+ removeEventListener: () => {},
16
+ dispatchEvent: () => false,
17
+ }),
18
+ });
19
+
20
+ const localStorageMock = (() => {
21
+ let store: Record<string, string> = {};
22
+ return {
23
+ getItem: (key: string) => store[key] ?? null,
24
+ setItem: (key: string, value: string) => {
25
+ store[key] = String(value);
26
+ },
27
+ removeItem: (key: string) => {
28
+ delete store[key];
29
+ },
30
+ clear: () => {
31
+ store = {};
32
+ },
33
+ get length() {
34
+ return Object.keys(store).length;
35
+ },
36
+ key: (i: number) => Object.keys(store)[i] ?? null,
37
+ };
38
+ })();
39
+
40
+ Object.defineProperty(window, "localStorage", { value: localStorageMock });