@dyyz1993/create-agent 1.9.0

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 (358) hide show
  1. package/package.json +25 -0
  2. package/src/__tests__/cli.test.ts +85 -0
  3. package/src/__tests__/copy.test.ts +168 -0
  4. package/src/__tests__/templates.test.ts +87 -0
  5. package/src/cli.ts +65 -0
  6. package/src/commands/create.ts +49 -0
  7. package/src/commands/list.ts +12 -0
  8. package/src/commands/status.ts +85 -0
  9. package/src/commands/workspace.ts +129 -0
  10. package/src/lib/copy.ts +285 -0
  11. package/src/lib/templates.ts +57 -0
  12. package/src/lib/types.ts +6 -0
  13. package/templates/agent/.env.example +7 -0
  14. package/templates/agent/LICENSE +21 -0
  15. package/templates/agent/README.md +87 -0
  16. package/templates/agent/bun.lock +1279 -0
  17. package/templates/agent/electrobun.config.ts +27 -0
  18. package/templates/agent/eslint.config.mjs +63 -0
  19. package/templates/agent/llms.txt +24 -0
  20. package/templates/agent/package-lock.json +3670 -0
  21. package/templates/agent/package.json +59 -0
  22. package/templates/agent/postcss.config.js +6 -0
  23. package/templates/agent/scripts/dev.ts +138 -0
  24. package/templates/agent/src/__tests__/server-config.test.ts +59 -0
  25. package/templates/agent/src/bun/index.ts +73 -0
  26. package/templates/agent/src/gateway/__tests__/ws-handler.test.ts +48 -0
  27. package/templates/agent/src/gateway/http-routes.ts +1 -0
  28. package/templates/agent/src/gateway/ipc-transport.ts +68 -0
  29. package/templates/agent/src/gateway/ws-handler.ts +86 -0
  30. package/templates/agent/src/mainview/App.tsx +41 -0
  31. package/templates/agent/src/mainview/__tests__/components/ChatPanel.test.tsx +114 -0
  32. package/templates/agent/src/mainview/__tests__/components/ExplorerSidebar.test.tsx +141 -0
  33. package/templates/agent/src/mainview/__tests__/components/MessageBubble.test.tsx +72 -0
  34. package/templates/agent/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
  35. package/templates/agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +217 -0
  36. package/templates/agent/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
  37. package/templates/agent/src/mainview/__tests__/i18n/i18n.test.ts +48 -0
  38. package/templates/agent/src/mainview/__tests__/setup.ts +37 -0
  39. package/templates/agent/src/mainview/__tests__/stores/use-chat-store.test.ts +83 -0
  40. package/templates/agent/src/mainview/__tests__/stores/use-sidebar-store.test.ts +85 -0
  41. package/templates/agent/src/mainview/components/activity-bar/ActivityBar.tsx +36 -0
  42. package/templates/agent/src/mainview/components/activity-bar/MobileTabBar.tsx +36 -0
  43. package/templates/agent/src/mainview/components/activity-bar/__tests__/ActivityBar.test.tsx +44 -0
  44. package/templates/agent/src/mainview/components/activity-bar/__tests__/MobileTabBar.test.tsx +44 -0
  45. package/templates/agent/src/mainview/components/bash/BashPanel.tsx +206 -0
  46. package/templates/agent/src/mainview/components/bash/__tests__/BashPanel.test.tsx +103 -0
  47. package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +102 -0
  48. package/templates/agent/src/mainview/components/chat/MessageBubble.tsx +86 -0
  49. package/templates/agent/src/mainview/components/common/ErrorBoundary.tsx +58 -0
  50. package/templates/agent/src/mainview/components/common/LanguageSwitcher.tsx +14 -0
  51. package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +24 -0
  52. package/templates/agent/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
  53. package/templates/agent/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
  54. package/templates/agent/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
  55. package/templates/agent/src/mainview/components/debug/DebugPanel.tsx +120 -0
  56. package/templates/agent/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +102 -0
  57. package/templates/agent/src/mainview/components/diff/DiffViewerPanel.tsx +106 -0
  58. package/templates/agent/src/mainview/components/diff/__tests__/DiffViewerPanel.test.tsx +73 -0
  59. package/templates/agent/src/mainview/components/explorer/ConfirmDialog.tsx +53 -0
  60. package/templates/agent/src/mainview/components/explorer/ContextMenu.tsx +82 -0
  61. package/templates/agent/src/mainview/components/explorer/ExplorerSidebar.tsx +221 -0
  62. package/templates/agent/src/mainview/components/explorer/InlineInput.tsx +55 -0
  63. package/templates/agent/src/mainview/components/explorer/TreeNodeItem.tsx +121 -0
  64. package/templates/agent/src/mainview/components/explorer/__tests__/ConfirmDialog.test.tsx +37 -0
  65. package/templates/agent/src/mainview/components/explorer/__tests__/ContextMenu.test.tsx +49 -0
  66. package/templates/agent/src/mainview/components/explorer/__tests__/InlineInput.test.tsx +48 -0
  67. package/templates/agent/src/mainview/components/explorer/__tests__/TreeNodeItem.test.tsx +146 -0
  68. package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +239 -0
  69. package/templates/agent/src/mainview/components/feed/__tests__/FeedPanel.test.tsx +108 -0
  70. package/templates/agent/src/mainview/components/file-preview/FilePreviewOverlay.tsx +56 -0
  71. package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +99 -0
  72. package/templates/agent/src/mainview/components/file-preview/__tests__/FilePreviewOverlay.test.tsx +96 -0
  73. package/templates/agent/src/mainview/components/file-preview/__tests__/VirtualizedCodeView.test.tsx +58 -0
  74. package/templates/agent/src/mainview/components/git/GitBranchSelector.tsx +98 -0
  75. package/templates/agent/src/mainview/components/git/GitCommitInput.tsx +60 -0
  76. package/templates/agent/src/mainview/components/git/GitPanel.tsx +528 -0
  77. package/templates/agent/src/mainview/components/git/__tests__/GitBranchSelector.test.tsx +76 -0
  78. package/templates/agent/src/mainview/components/git/__tests__/GitCommitInput.test.tsx +91 -0
  79. package/templates/agent/src/mainview/components/git/__tests__/GitPanel.test.tsx +174 -0
  80. package/templates/agent/src/mainview/components/layout/AppLayout.tsx +170 -0
  81. package/templates/agent/src/mainview/components/layout/__tests__/AppLayout.test.tsx +163 -0
  82. package/templates/agent/src/mainview/components/rules/RulesPanel.tsx +117 -0
  83. package/templates/agent/src/mainview/components/rules/__tests__/RulesPanel.test.tsx +91 -0
  84. package/templates/agent/src/mainview/components/search/SearchPanel.tsx +404 -0
  85. package/templates/agent/src/mainview/components/search/__tests__/SearchPanel.test.tsx +50 -0
  86. package/templates/agent/src/mainview/components/sidebar/PinButton.tsx +20 -0
  87. package/templates/agent/src/mainview/components/sidebar/__tests__/PinButton.test.tsx +48 -0
  88. package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +141 -0
  89. package/templates/agent/src/mainview/components/todo/__tests__/TodoPanel.test.tsx +91 -0
  90. package/templates/agent/src/mainview/global.d.ts +13 -0
  91. package/templates/agent/src/mainview/hooks/__tests__/use-breakpoint.test.ts +147 -0
  92. package/templates/agent/src/mainview/hooks/use-breakpoint.ts +34 -0
  93. package/templates/agent/src/mainview/hooks/use-input-history.ts +84 -0
  94. package/templates/agent/src/mainview/hooks/use-rpc-init.ts +61 -0
  95. package/templates/agent/src/mainview/hooks/use-sidebar-resize.ts +40 -0
  96. package/templates/agent/src/mainview/index.css +61 -0
  97. package/templates/agent/src/mainview/index.html +12 -0
  98. package/templates/agent/src/mainview/lib/api-client.ts +192 -0
  99. package/templates/agent/src/mainview/lib/i18n/index.ts +30 -0
  100. package/templates/agent/src/mainview/lib/i18n/locales/en.json +157 -0
  101. package/templates/agent/src/mainview/lib/i18n/locales/zh.json +157 -0
  102. package/templates/agent/src/mainview/lib/rpc-cache.ts +94 -0
  103. package/templates/agent/src/mainview/main.tsx +24 -0
  104. package/templates/agent/src/mainview/stores/__tests__/use-app-store.test.ts +86 -0
  105. package/templates/agent/src/mainview/stores/__tests__/use-bash-store.test.ts +99 -0
  106. package/templates/agent/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
  107. package/templates/agent/src/mainview/stores/__tests__/use-explorer-store.test.ts +130 -0
  108. package/templates/agent/src/mainview/stores/__tests__/use-feed-store.test.ts +132 -0
  109. package/templates/agent/src/mainview/stores/__tests__/use-git-store.test.ts +161 -0
  110. package/templates/agent/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  111. package/templates/agent/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
  112. package/templates/agent/src/mainview/stores/__tests__/use-notification-store.test.ts +102 -0
  113. package/templates/agent/src/mainview/stores/__tests__/use-rules-store.test.ts +75 -0
  114. package/templates/agent/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  115. package/templates/agent/src/mainview/stores/__tests__/use-todo-store.test.ts +75 -0
  116. package/templates/agent/src/mainview/stores/message-batcher.ts +25 -0
  117. package/templates/agent/src/mainview/stores/use-app-store.ts +99 -0
  118. package/templates/agent/src/mainview/stores/use-bash-store.ts +93 -0
  119. package/templates/agent/src/mainview/stores/use-chat-store.ts +37 -0
  120. package/templates/agent/src/mainview/stores/use-connection-store.ts +42 -0
  121. package/templates/agent/src/mainview/stores/use-explorer-store.ts +320 -0
  122. package/templates/agent/src/mainview/stores/use-feed-store.ts +129 -0
  123. package/templates/agent/src/mainview/stores/use-git-store.ts +291 -0
  124. package/templates/agent/src/mainview/stores/use-locale-store.ts +27 -0
  125. package/templates/agent/src/mainview/stores/use-log-store.ts +16 -0
  126. package/templates/agent/src/mainview/stores/use-notification-store.ts +82 -0
  127. package/templates/agent/src/mainview/stores/use-rules-store.ts +54 -0
  128. package/templates/agent/src/mainview/stores/use-sidebar-store.ts +99 -0
  129. package/templates/agent/src/mainview/stores/use-theme-store.ts +46 -0
  130. package/templates/agent/src/mainview/stores/use-todo-store.ts +54 -0
  131. package/templates/agent/src/mainview/types/index.ts +33 -0
  132. package/templates/agent/src/mainview/utils/constants.ts +1 -0
  133. package/templates/agent/src/mainview/utils/drop-handler.ts +150 -0
  134. package/templates/agent/src/mainview/utils/file-icon.tsx +24 -0
  135. package/templates/agent/src/mainview/utils/file-utils.ts +35 -0
  136. package/templates/agent/src/server-config.ts +43 -0
  137. package/templates/agent/src/server.ts +89 -0
  138. package/templates/agent/src/shared/handlers/__tests__/bash-handler.test.ts +135 -0
  139. package/templates/agent/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
  140. package/templates/agent/src/shared/handlers/__tests__/feed-handler.test.ts +108 -0
  141. package/templates/agent/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
  142. package/templates/agent/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
  143. package/templates/agent/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
  144. package/templates/agent/src/shared/handlers/bash.ts +89 -0
  145. package/templates/agent/src/shared/handlers/chat.ts +179 -0
  146. package/templates/agent/src/shared/handlers/feed.ts +53 -0
  147. package/templates/agent/src/shared/handlers/file.ts +99 -0
  148. package/templates/agent/src/shared/handlers/git.ts +263 -0
  149. package/templates/agent/src/shared/handlers/index.ts +10 -0
  150. package/templates/agent/src/shared/handlers/rules.ts +45 -0
  151. package/templates/agent/src/shared/handlers/system.ts +27 -0
  152. package/templates/agent/src/shared/handlers/timer.ts +34 -0
  153. package/templates/agent/src/shared/handlers/todo.ts +45 -0
  154. package/templates/agent/src/shared/lib/__tests__/bash-security.test.ts +125 -0
  155. package/templates/agent/src/shared/lib/__tests__/logger.test.ts +92 -0
  156. package/templates/agent/src/shared/lib/__tests__/path-security.test.ts +77 -0
  157. package/templates/agent/src/shared/lib/bash-security.ts +64 -0
  158. package/templates/agent/src/shared/lib/logger.ts +130 -0
  159. package/templates/agent/src/shared/lib/path-security.ts +38 -0
  160. package/templates/agent/src/shared/lib/port-registry.ts +103 -0
  161. package/templates/agent/src/shared/modules/bash.ts +19 -0
  162. package/templates/agent/src/shared/modules/chat.ts +20 -0
  163. package/templates/agent/src/shared/modules/feed.ts +30 -0
  164. package/templates/agent/src/shared/modules/file.ts +40 -0
  165. package/templates/agent/src/shared/modules/git.ts +81 -0
  166. package/templates/agent/src/shared/modules/rules.ts +25 -0
  167. package/templates/agent/src/shared/modules/system.ts +8 -0
  168. package/templates/agent/src/shared/modules/timer.ts +11 -0
  169. package/templates/agent/src/shared/modules/todo.ts +27 -0
  170. package/templates/agent/src/shared/register-all-handlers.ts +36 -0
  171. package/templates/agent/src/shared/rpc-schema.ts +35 -0
  172. package/templates/agent/tailwind.config.js +15 -0
  173. package/templates/agent/tsconfig.json +26 -0
  174. package/templates/agent/vite.config.ts +3 -0
  175. package/templates/agent/vitest.config.ts +3 -0
  176. package/templates/chat/.env.example +6 -0
  177. package/templates/chat/LICENSE +21 -0
  178. package/templates/chat/README.md +56 -0
  179. package/templates/chat/bun.lock +1203 -0
  180. package/templates/chat/electrobun.config.ts +27 -0
  181. package/templates/chat/eslint.config.mjs +56 -0
  182. package/templates/chat/llms.txt +24 -0
  183. package/templates/chat/package-lock.json +3670 -0
  184. package/templates/chat/package.json +58 -0
  185. package/templates/chat/postcss.config.js +6 -0
  186. package/templates/chat/scripts/dev.ts +138 -0
  187. package/templates/chat/src/__tests__/server-config.test.ts +59 -0
  188. package/templates/chat/src/bun/index.ts +72 -0
  189. package/templates/chat/src/gateway/__tests__/ws-handler.test.ts +48 -0
  190. package/templates/chat/src/gateway/http-routes.ts +1 -0
  191. package/templates/chat/src/gateway/ipc-transport.ts +68 -0
  192. package/templates/chat/src/gateway/ws-handler.ts +86 -0
  193. package/templates/chat/src/mainview/App.tsx +108 -0
  194. package/templates/chat/src/mainview/__tests__/components/ChatPanel.test.tsx +76 -0
  195. package/templates/chat/src/mainview/__tests__/components/MessageBubble.test.tsx +48 -0
  196. package/templates/chat/src/mainview/__tests__/i18n/i18n.test.ts +48 -0
  197. package/templates/chat/src/mainview/__tests__/setup-verify.test.ts +8 -0
  198. package/templates/chat/src/mainview/__tests__/setup.ts +37 -0
  199. package/templates/chat/src/mainview/__tests__/stores/use-app-store.test.ts +46 -0
  200. package/templates/chat/src/mainview/__tests__/stores/use-chat-store.test.ts +104 -0
  201. package/templates/chat/src/mainview/__tests__/stores/use-connection-store.test.ts +35 -0
  202. package/templates/chat/src/mainview/__tests__/stores/use-log-store.test.ts +45 -0
  203. package/templates/chat/src/mainview/__tests__/stores/use-notification-store.test.ts +102 -0
  204. package/templates/chat/src/mainview/__tests__/stores/use-sidebar-store.test.ts +89 -0
  205. package/templates/chat/src/mainview/components/activity-bar/ActivityBar.tsx +26 -0
  206. package/templates/chat/src/mainview/components/activity-bar/MobileTabBar.tsx +20 -0
  207. package/templates/chat/src/mainview/components/chat/ChatPanel.tsx +66 -0
  208. package/templates/chat/src/mainview/components/chat/MessageBubble.tsx +85 -0
  209. package/templates/chat/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
  210. package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +24 -0
  211. package/templates/chat/src/mainview/components/sidebar/PinButton.tsx +20 -0
  212. package/templates/chat/src/mainview/global.d.ts +13 -0
  213. package/templates/chat/src/mainview/hooks/use-breakpoint.ts +34 -0
  214. package/templates/chat/src/mainview/hooks/use-input-history.ts +84 -0
  215. package/templates/chat/src/mainview/index.css +61 -0
  216. package/templates/chat/src/mainview/index.html +12 -0
  217. package/templates/chat/src/mainview/lib/api-client.ts +175 -0
  218. package/templates/chat/src/mainview/lib/i18n/index.ts +30 -0
  219. package/templates/chat/src/mainview/lib/i18n/locales/en.json +157 -0
  220. package/templates/chat/src/mainview/lib/i18n/locales/zh.json +157 -0
  221. package/templates/chat/src/mainview/main.tsx +20 -0
  222. package/templates/chat/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  223. package/templates/chat/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  224. package/templates/chat/src/mainview/stores/message-batcher.ts +25 -0
  225. package/templates/chat/src/mainview/stores/use-app-store.ts +50 -0
  226. package/templates/chat/src/mainview/stores/use-chat-store.ts +37 -0
  227. package/templates/chat/src/mainview/stores/use-connection-store.ts +42 -0
  228. package/templates/chat/src/mainview/stores/use-locale-store.ts +27 -0
  229. package/templates/chat/src/mainview/stores/use-log-store.ts +16 -0
  230. package/templates/chat/src/mainview/stores/use-notification-store.ts +82 -0
  231. package/templates/chat/src/mainview/stores/use-sidebar-store.ts +98 -0
  232. package/templates/chat/src/mainview/stores/use-theme-store.ts +46 -0
  233. package/templates/chat/src/mainview/types/index.ts +8 -0
  234. package/templates/chat/src/mainview/utils/constants.ts +1 -0
  235. package/templates/chat/src/server-config.ts +42 -0
  236. package/templates/chat/src/server.ts +89 -0
  237. package/templates/chat/src/shared/__tests__/chat-handler.test.ts +71 -0
  238. package/templates/chat/src/shared/__tests__/system-handler.test.ts +70 -0
  239. package/templates/chat/src/shared/handlers/chat.ts +186 -0
  240. package/templates/chat/src/shared/handlers/index.ts +2 -0
  241. package/templates/chat/src/shared/handlers/system.ts +27 -0
  242. package/templates/chat/src/shared/lib/logger.ts +130 -0
  243. package/templates/chat/src/shared/lib/path-security.ts +38 -0
  244. package/templates/chat/src/shared/lib/port-registry.ts +103 -0
  245. package/templates/chat/src/shared/modules/chat.ts +20 -0
  246. package/templates/chat/src/shared/modules/system.ts +8 -0
  247. package/templates/chat/src/shared/register-all-handlers.ts +36 -0
  248. package/templates/chat/src/shared/rpc-schema.ts +11 -0
  249. package/templates/chat/tailwind.config.js +15 -0
  250. package/templates/chat/tsconfig.json +26 -0
  251. package/templates/chat/vite.config.ts +3 -0
  252. package/templates/chat/vitest.config.ts +3 -0
  253. package/templates/general/.env.example +7 -0
  254. package/templates/general/LICENSE +21 -0
  255. package/templates/general/README.md +113 -0
  256. package/templates/general/bun.lock +1266 -0
  257. package/templates/general/electrobun.config.ts +27 -0
  258. package/templates/general/eslint.config.mjs +56 -0
  259. package/templates/general/llms.txt +24 -0
  260. package/templates/general/package-lock.json +3670 -0
  261. package/templates/general/package.json +59 -0
  262. package/templates/general/postcss.config.js +6 -0
  263. package/templates/general/scripts/dev.ts +138 -0
  264. package/templates/general/src/__tests__/server-config.test.ts +59 -0
  265. package/templates/general/src/bun/index.ts +72 -0
  266. package/templates/general/src/gateway/http-routes.ts +1 -0
  267. package/templates/general/src/gateway/ipc-transport.ts +68 -0
  268. package/templates/general/src/gateway/ws-handler.ts +86 -0
  269. package/templates/general/src/mainview/App.tsx +40 -0
  270. package/templates/general/src/mainview/__tests__/components/ChatPanel.test.tsx +82 -0
  271. package/templates/general/src/mainview/__tests__/components/FeedPanel.test.tsx +109 -0
  272. package/templates/general/src/mainview/__tests__/components/GitPanel.test.tsx +151 -0
  273. package/templates/general/src/mainview/__tests__/i18n/i18n.test.ts +48 -0
  274. package/templates/general/src/mainview/__tests__/setup-verify.test.ts +8 -0
  275. package/templates/general/src/mainview/__tests__/setup.ts +37 -0
  276. package/templates/general/src/mainview/components/activity-bar/ActivityBar.tsx +40 -0
  277. package/templates/general/src/mainview/components/activity-bar/MobileTabBar.tsx +29 -0
  278. package/templates/general/src/mainview/components/chat/ChatPanel.tsx +66 -0
  279. package/templates/general/src/mainview/components/chat/MessageBubble.tsx +85 -0
  280. package/templates/general/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
  281. package/templates/general/src/mainview/components/common/ThemeToggle.tsx +24 -0
  282. package/templates/general/src/mainview/components/debug/DebugPanel.tsx +176 -0
  283. package/templates/general/src/mainview/components/diff/DiffViewerPanel.tsx +108 -0
  284. package/templates/general/src/mainview/components/explorer/ConfirmDialog.tsx +52 -0
  285. package/templates/general/src/mainview/components/explorer/ContextMenu.tsx +82 -0
  286. package/templates/general/src/mainview/components/explorer/ExplorerSidebar.tsx +225 -0
  287. package/templates/general/src/mainview/components/explorer/InlineInput.tsx +55 -0
  288. package/templates/general/src/mainview/components/explorer/TreeNodeItem.tsx +121 -0
  289. package/templates/general/src/mainview/components/feed/FeedPanel.tsx +248 -0
  290. package/templates/general/src/mainview/components/file-preview/FilePreviewOverlay.tsx +55 -0
  291. package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +99 -0
  292. package/templates/general/src/mainview/components/git/GitBranchSelector.tsx +93 -0
  293. package/templates/general/src/mainview/components/git/GitCommitInput.tsx +60 -0
  294. package/templates/general/src/mainview/components/git/GitPanel.tsx +533 -0
  295. package/templates/general/src/mainview/components/layout/AppLayout.tsx +146 -0
  296. package/templates/general/src/mainview/components/search/SearchPanel.tsx +405 -0
  297. package/templates/general/src/mainview/components/sidebar/PinButton.tsx +20 -0
  298. package/templates/general/src/mainview/global.d.ts +13 -0
  299. package/templates/general/src/mainview/hooks/use-breakpoint.ts +34 -0
  300. package/templates/general/src/mainview/hooks/use-input-history.ts +84 -0
  301. package/templates/general/src/mainview/hooks/use-rpc-init.ts +61 -0
  302. package/templates/general/src/mainview/hooks/use-sidebar-resize.ts +40 -0
  303. package/templates/general/src/mainview/index.css +61 -0
  304. package/templates/general/src/mainview/index.html +12 -0
  305. package/templates/general/src/mainview/lib/api-client.ts +175 -0
  306. package/templates/general/src/mainview/lib/i18n/index.ts +30 -0
  307. package/templates/general/src/mainview/lib/i18n/locales/en.json +157 -0
  308. package/templates/general/src/mainview/lib/i18n/locales/zh.json +157 -0
  309. package/templates/general/src/mainview/main.tsx +20 -0
  310. package/templates/general/src/mainview/stores/__tests__/use-chat-store.test.ts +104 -0
  311. package/templates/general/src/mainview/stores/__tests__/use-connection-store.test.ts +35 -0
  312. package/templates/general/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  313. package/templates/general/src/mainview/stores/__tests__/use-log-store.test.ts +45 -0
  314. package/templates/general/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  315. package/templates/general/src/mainview/stores/message-batcher.ts +25 -0
  316. package/templates/general/src/mainview/stores/use-app-store.ts +104 -0
  317. package/templates/general/src/mainview/stores/use-chat-store.ts +37 -0
  318. package/templates/general/src/mainview/stores/use-connection-store.ts +42 -0
  319. package/templates/general/src/mainview/stores/use-explorer-store.ts +315 -0
  320. package/templates/general/src/mainview/stores/use-feed-store.ts +129 -0
  321. package/templates/general/src/mainview/stores/use-git-store.ts +284 -0
  322. package/templates/general/src/mainview/stores/use-locale-store.ts +27 -0
  323. package/templates/general/src/mainview/stores/use-log-store.ts +16 -0
  324. package/templates/general/src/mainview/stores/use-notification-store.ts +82 -0
  325. package/templates/general/src/mainview/stores/use-sidebar-store.ts +99 -0
  326. package/templates/general/src/mainview/stores/use-theme-store.ts +46 -0
  327. package/templates/general/src/mainview/types/index.ts +33 -0
  328. package/templates/general/src/mainview/utils/constants.ts +1 -0
  329. package/templates/general/src/mainview/utils/drop-handler.ts +150 -0
  330. package/templates/general/src/mainview/utils/file-icon.tsx +24 -0
  331. package/templates/general/src/mainview/utils/file-utils.ts +35 -0
  332. package/templates/general/src/server-config.ts +42 -0
  333. package/templates/general/src/server.ts +89 -0
  334. package/templates/general/src/shared/handlers/__tests__/chat.test.ts +183 -0
  335. package/templates/general/src/shared/handlers/__tests__/system.test.ts +94 -0
  336. package/templates/general/src/shared/handlers/__tests__/timer.test.ts +113 -0
  337. package/templates/general/src/shared/handlers/chat.ts +179 -0
  338. package/templates/general/src/shared/handlers/feed.ts +53 -0
  339. package/templates/general/src/shared/handlers/file.ts +99 -0
  340. package/templates/general/src/shared/handlers/git.ts +263 -0
  341. package/templates/general/src/shared/handlers/index.ts +7 -0
  342. package/templates/general/src/shared/handlers/system.ts +27 -0
  343. package/templates/general/src/shared/handlers/timer.ts +34 -0
  344. package/templates/general/src/shared/lib/logger.ts +130 -0
  345. package/templates/general/src/shared/lib/path-security.ts +38 -0
  346. package/templates/general/src/shared/lib/port-registry.ts +103 -0
  347. package/templates/general/src/shared/modules/chat.ts +20 -0
  348. package/templates/general/src/shared/modules/feed.ts +30 -0
  349. package/templates/general/src/shared/modules/file.ts +40 -0
  350. package/templates/general/src/shared/modules/git.ts +81 -0
  351. package/templates/general/src/shared/modules/system.ts +8 -0
  352. package/templates/general/src/shared/modules/timer.ts +11 -0
  353. package/templates/general/src/shared/register-all-handlers.ts +36 -0
  354. package/templates/general/src/shared/rpc-schema.ts +31 -0
  355. package/templates/general/tailwind.config.js +15 -0
  356. package/templates/general/tsconfig.json +26 -0
  357. package/templates/general/vite.config.ts +3 -0
  358. package/templates/general/vitest.config.ts +3 -0
@@ -0,0 +1,114 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ vi.mock("@tanstack/react-virtual", () => ({
4
+ useVirtualizer: (options: { count: number }) => ({
5
+ getVirtualItems: () =>
6
+ Array.from({ length: options.count }).map((_, i) => ({
7
+ index: i,
8
+ key: i,
9
+ start: i * 80,
10
+ size: 80,
11
+ })),
12
+ getTotalSize: () => options.count * 80,
13
+ scrollToIndex: vi.fn(),
14
+ }),
15
+ }));
16
+
17
+ import { render, screen } from "@testing-library/react";
18
+ import userEvent from "@testing-library/user-event";
19
+ import { ChatPanel } from "../../components/chat/ChatPanel";
20
+ import { useChatStore } from "../../stores/use-chat-store";
21
+ import { act } from "@testing-library/react";
22
+
23
+ vi.mock("../../lib/i18n", () => ({
24
+ default: {
25
+ isInitialized: true,
26
+ language: "en",
27
+ changeLanguage: vi.fn(),
28
+ t: (key: string) => key,
29
+ getResource: vi.fn(),
30
+ },
31
+ supportedLocales: ["en", "zh"],
32
+ }));
33
+
34
+ vi.mock("react-i18next", () => ({
35
+ useTranslation: () => ({
36
+ t: (key: string) => key,
37
+ i18n: { language: "en", changeLanguage: vi.fn() },
38
+ }),
39
+ initReactI18next: { type: "3rdParty", init: vi.fn() },
40
+ }));
41
+
42
+ vi.mock("../../lib/api-client", () => ({
43
+ apiClient: {
44
+ call: vi.fn().mockResolvedValue({ ok: true }),
45
+ },
46
+ }));
47
+
48
+ vi.mock("../../stores/use-log-store", () => ({
49
+ useLogStore: {
50
+ getState: () => ({ addLog: vi.fn() }),
51
+ },
52
+ }));
53
+
54
+ describe("ChatPanel", () => {
55
+ beforeEach(() => {
56
+ act(() => {
57
+ useChatStore.setState({ messages: [], inputText: "" });
58
+ });
59
+ });
60
+
61
+ it("renders empty state", () => {
62
+ render(<ChatPanel />);
63
+ expect(screen.getByText("chat.empty")).toBeInTheDocument();
64
+ });
65
+
66
+ it("renders messages header", () => {
67
+ render(<ChatPanel />);
68
+ expect(screen.getByText("chat.title")).toBeInTheDocument();
69
+ });
70
+
71
+ it("shows message count badge", () => {
72
+ act(() => {
73
+ useChatStore.setState({
74
+ messages: [
75
+ { id: "1", role: "user", content: "hi", timestamp: 1000 },
76
+ ],
77
+ });
78
+ });
79
+ render(<ChatPanel />);
80
+ expect(screen.getByText("1")).toBeInTheDocument();
81
+ });
82
+
83
+ it("renders message list", () => {
84
+ act(() => {
85
+ useChatStore.setState({
86
+ messages: [
87
+ { id: "1", role: "user", content: "Hello", timestamp: 1000 },
88
+ { id: "2", role: "assistant", content: "World", timestamp: 2000 },
89
+ ],
90
+ });
91
+ });
92
+ render(<ChatPanel />);
93
+ expect(screen.getByText("Hello")).toBeInTheDocument();
94
+ expect(screen.getByText("World")).toBeInTheDocument();
95
+ });
96
+
97
+ it("has input field", () => {
98
+ render(<ChatPanel />);
99
+ expect(screen.getByPlaceholderText("chat.placeholder")).toBeInTheDocument();
100
+ });
101
+
102
+ it("has send button", () => {
103
+ render(<ChatPanel />);
104
+ expect(screen.getByRole("button", { name: /send/i })).toBeInTheDocument();
105
+ });
106
+
107
+ it("typing updates input", async () => {
108
+ const user = userEvent.setup();
109
+ render(<ChatPanel />);
110
+ const input = screen.getByPlaceholderText("chat.placeholder");
111
+ await user.type(input, "hello");
112
+ expect(input).toHaveValue("hello");
113
+ });
114
+ });
@@ -0,0 +1,141 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { render, screen, fireEvent } from "@testing-library/react";
3
+
4
+ vi.mock("react-i18next", () => ({
5
+ useTranslation: () => ({
6
+ t: (key: string) => key,
7
+ i18n: { language: "en", changeLanguage: vi.fn() },
8
+ }),
9
+ initReactI18next: { type: "3rdParty", init: vi.fn() },
10
+ }));
11
+
12
+ vi.mock("../../stores/use-explorer-store", () => {
13
+ const toggleNode = vi.fn();
14
+ const openFile = vi.fn();
15
+ const setCurrentPath = vi.fn();
16
+ const listRootDir = vi.fn();
17
+ const startEditing = vi.fn();
18
+ const cancelEditing = vi.fn();
19
+ const createFile = vi.fn();
20
+ const createDir = vi.fn();
21
+ const renameNode = vi.fn();
22
+ const deleteNode = vi.fn();
23
+ const importFiles = vi.fn();
24
+
25
+ const state = {
26
+ treeNodes: [
27
+ {
28
+ name: "src",
29
+ path: "/project/src",
30
+ type: "directory" as const,
31
+ children: [],
32
+ expanded: false,
33
+ loaded: false,
34
+ },
35
+ {
36
+ name: "package.json",
37
+ path: "/project/package.json",
38
+ type: "file" as const,
39
+ },
40
+ ],
41
+ currentPath: "/project",
42
+ selectedPath: null,
43
+ editingNode: null,
44
+ toggleNode,
45
+ loadDirectory: vi.fn(),
46
+ openFile,
47
+ setCurrentPath,
48
+ listRootDir,
49
+ startEditing,
50
+ cancelEditing,
51
+ createFile,
52
+ createDir,
53
+ renameNode,
54
+ deleteNode,
55
+ importFiles,
56
+ };
57
+
58
+ return {
59
+ useExplorerStore: (selector: (s: typeof state) => unknown) => selector(state),
60
+ };
61
+ });
62
+
63
+ vi.mock("../../stores/use-sidebar-store", () => ({
64
+ useSidebarStore: () => ({ isPinned: true, setPinned: vi.fn(), breakpoint: "desktop" }),
65
+ }));
66
+
67
+ vi.mock("../../utils/drop-handler", () => ({
68
+ readDropItems: vi.fn().mockResolvedValue([]),
69
+ }));
70
+
71
+ vi.mock("../../utils/file-icon", () => ({
72
+ getFileIcon: () => <span data-testid="file-icon" />,
73
+ }));
74
+
75
+ describe("ExplorerSidebar (no props drilling)", () => {
76
+ beforeEach(() => {
77
+ vi.clearAllMocks();
78
+ });
79
+
80
+ it("renders with zero data/action props — only store", async () => {
81
+ const { ExplorerSidebar } = await import(
82
+ "../../components/explorer/ExplorerSidebar"
83
+ );
84
+ render(<ExplorerSidebar />);
85
+
86
+ expect(screen.getByText("src")).toBeInTheDocument();
87
+ expect(screen.getByText("package.json")).toBeInTheDocument();
88
+ });
89
+
90
+ it("calls toggleNode from store on directory click", async () => {
91
+ const { useExplorerStore } = await import(
92
+ "../../stores/use-explorer-store"
93
+ );
94
+ const { ExplorerSidebar } = await import(
95
+ "../../components/explorer/ExplorerSidebar"
96
+ );
97
+ render(<ExplorerSidebar />);
98
+
99
+ const srcNode = screen.getByText("src");
100
+ fireEvent.click(srcNode);
101
+
102
+ const store = useExplorerStore((s: any) => s);
103
+ expect(store.toggleNode).toHaveBeenCalledWith("/project/src");
104
+ });
105
+
106
+ it("renders with hideOuterShell to remove outer wrapper", async () => {
107
+ const { ExplorerSidebar } = await import(
108
+ "../../components/explorer/ExplorerSidebar"
109
+ );
110
+ const { container } = render(<ExplorerSidebar hideOuterShell />);
111
+
112
+ const outer = container.querySelector(".sidebar-outer");
113
+ expect(outer).toBeNull();
114
+ });
115
+
116
+ it("renders path input with currentPath from store", async () => {
117
+ const { ExplorerSidebar } = await import(
118
+ "../../components/explorer/ExplorerSidebar"
119
+ );
120
+ render(<ExplorerSidebar />);
121
+
122
+ const input = screen.getByPlaceholderText("explorer.pathPlaceholder") as HTMLInputElement;
123
+ expect(input.value).toBe("/project");
124
+ });
125
+
126
+ it("calls listRootDir from store on refresh button click", async () => {
127
+ const { useExplorerStore } = await import(
128
+ "../../stores/use-explorer-store"
129
+ );
130
+ const { ExplorerSidebar } = await import(
131
+ "../../components/explorer/ExplorerSidebar"
132
+ );
133
+ render(<ExplorerSidebar />);
134
+
135
+ const refreshBtn = screen.getByTitle("explorer.listDirectory");
136
+ fireEvent.click(refreshBtn);
137
+
138
+ const store = useExplorerStore((s: any) => s);
139
+ expect(store.listRootDir).toHaveBeenCalled();
140
+ });
141
+ });
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { MessageBubble } from "../../components/chat/MessageBubble";
4
+ import type { ChatMessage } from "../../types";
5
+
6
+ function createMessage(overrides: Partial<ChatMessage> = {}): ChatMessage {
7
+ return {
8
+ id: "msg-1",
9
+ role: "user",
10
+ content: "Hello world",
11
+ timestamp: Date.now(),
12
+ ...overrides,
13
+ };
14
+ }
15
+
16
+ describe("MessageBubble", () => {
17
+ it("renders user message with content", () => {
18
+ render(<MessageBubble message={createMessage()} />);
19
+ expect(screen.getByText("Hello world")).toBeInTheDocument();
20
+ });
21
+
22
+ it("renders assistant message", () => {
23
+ render(
24
+ <MessageBubble message={createMessage({ role: "assistant", content: "Hi there" })} />
25
+ );
26
+ expect(screen.getByText("Hi there")).toBeInTheDocument();
27
+ });
28
+
29
+ it("shows bot icon for assistant", () => {
30
+ const { container } = render(
31
+ <MessageBubble message={createMessage({ role: "assistant" })} />
32
+ );
33
+ const svg = container.querySelector("svg");
34
+ expect(svg).toBeInTheDocument();
35
+ });
36
+
37
+ it("renders markdown for assistant", () => {
38
+ render(
39
+ <MessageBubble
40
+ message={createMessage({
41
+ role: "assistant",
42
+ content: "**bold text** and `code`",
43
+ })}
44
+ />
45
+ );
46
+ expect(screen.getByText("bold text")).toBeInTheDocument();
47
+ });
48
+
49
+ it("shows timestamp when present", () => {
50
+ const ts = new Date(2024, 0, 1, 10, 30).getTime();
51
+ const { container } = render(
52
+ <MessageBubble message={createMessage({ timestamp: ts })} />
53
+ );
54
+ const timeEl = container.querySelector('[class*="text-\\[10px\\]"]');
55
+ if (timeEl) {
56
+ expect(timeEl.textContent).toContain("10:30");
57
+ }
58
+ });
59
+
60
+ it("applies different styles for user vs assistant", () => {
61
+ const { container: userContainer } = render(
62
+ <MessageBubble message={createMessage({ role: "user" })} />
63
+ );
64
+ const { container: assistantContainer } = render(
65
+ <MessageBubble message={createMessage({ role: "assistant" })} />
66
+ );
67
+ const userBubble = userContainer.querySelector('[class*="bg-\\[var\\(--color-accent\\)\\]"]');
68
+ const assistantBubble = assistantContainer.querySelector('[class*="bg-\\[var\\(--color-bg-tertiary\\)\\]"]');
69
+ expect(userBubble).toBeInTheDocument();
70
+ expect(assistantBubble).toBeInTheDocument();
71
+ });
72
+ });
@@ -0,0 +1,125 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { renderHook, act } from "@testing-library/react";
3
+ import { useInputHistory } from "../../hooks/use-input-history";
4
+
5
+ describe("useInputHistory", () => {
6
+ beforeEach(() => {
7
+ window.localStorage.clear();
8
+ });
9
+
10
+ it("saveToHistory stores entry", () => {
11
+ const { result } = renderHook(() => useInputHistory("test-session"));
12
+ act(() => {
13
+ result.current.saveToHistory("hello");
14
+ });
15
+ expect(result.current.hasPrev).toBe(true);
16
+ });
17
+
18
+ it("saveToHistory ignores empty", () => {
19
+ const { result } = renderHook(() => useInputHistory("test-session"));
20
+ act(() => {
21
+ result.current.saveToHistory(" ");
22
+ });
23
+ expect(result.current.hasPrev).toBe(false);
24
+ });
25
+
26
+ it("navigatePrev returns items", () => {
27
+ const { result } = renderHook(() => useInputHistory("test-session"));
28
+ act(() => {
29
+ result.current.saveToHistory("first");
30
+ result.current.saveToHistory("second");
31
+ });
32
+ let value: string | null = null;
33
+ act(() => {
34
+ value = result.current.navigatePrev();
35
+ });
36
+ expect(value).toBe("second");
37
+
38
+ act(() => {
39
+ value = result.current.navigatePrev();
40
+ });
41
+ expect(value).toBe("first");
42
+ });
43
+
44
+ it("navigateNext returns empty string at end", () => {
45
+ const { result } = renderHook(() => useInputHistory("test-session"));
46
+ act(() => {
47
+ result.current.saveToHistory("hello");
48
+ });
49
+ act(() => {
50
+ result.current.navigatePrev();
51
+ });
52
+ let value: string | null = null;
53
+ act(() => {
54
+ value = result.current.navigateNext();
55
+ });
56
+ expect(value).toBe("");
57
+ });
58
+
59
+ it("clearHistory removes all", () => {
60
+ const { result } = renderHook(() => useInputHistory("test-session"));
61
+ act(() => {
62
+ result.current.saveToHistory("a");
63
+ result.current.saveToHistory("b");
64
+ });
65
+ act(() => {
66
+ result.current.clearHistory();
67
+ });
68
+ expect(result.current.hasPrev).toBe(false);
69
+ });
70
+
71
+ it("deduplicates entries", () => {
72
+ const { result } = renderHook(() => useInputHistory("test-session"));
73
+ act(() => {
74
+ result.current.saveToHistory("hello");
75
+ result.current.saveToHistory("hello");
76
+ });
77
+ let count = 0;
78
+ while (result.current.hasPrev) {
79
+ act(() => {
80
+ result.current.navigatePrev();
81
+ });
82
+ count++;
83
+ if (count > 20) break;
84
+ }
85
+ expect(count).toBe(1);
86
+ });
87
+
88
+ it("respects max 10 items", () => {
89
+ const { result } = renderHook(() => useInputHistory("test-session"));
90
+ act(() => {
91
+ for (let i = 0; i < 15; i++) {
92
+ result.current.saveToHistory(`item-${i}`);
93
+ }
94
+ });
95
+ let count = 0;
96
+ while (result.current.hasPrev) {
97
+ act(() => {
98
+ result.current.navigatePrev();
99
+ });
100
+ count++;
101
+ if (count > 20) break;
102
+ }
103
+ expect(count).toBe(10);
104
+ });
105
+
106
+ it("resetIndex goes back to -1", () => {
107
+ const { result } = renderHook(() => useInputHistory("test-session"));
108
+ act(() => {
109
+ result.current.saveToHistory("first");
110
+ result.current.saveToHistory("second");
111
+ result.current.saveToHistory("third");
112
+ });
113
+ act(() => {
114
+ result.current.navigatePrev();
115
+ });
116
+ act(() => {
117
+ result.current.navigatePrev();
118
+ });
119
+ expect(result.current.hasNext).toBe(true);
120
+ act(() => {
121
+ result.current.resetIndex();
122
+ });
123
+ expect(result.current.hasNext).toBe(false);
124
+ });
125
+ });
@@ -0,0 +1,217 @@
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 mockListRootDir = vi.fn();
7
+ const mockSubscribe = vi.fn();
8
+ const mockUnsubscribe = vi.fn();
9
+ const mockCall = vi.fn();
10
+
11
+ const mockChatAddMessage = vi.fn();
12
+ const mockChatSetMessages = vi.fn();
13
+
14
+ let mockConnectionState: Record<string, any>;
15
+ let mockLogState: Record<string, any>;
16
+
17
+ function resetMockConnectionState(overrides?: Partial<Record<string, any>>) {
18
+ mockConnectionState = {
19
+ ready: false,
20
+ initializeConnection: mockInitializeConnection,
21
+ ...overrides,
22
+ };
23
+ }
24
+
25
+ function resetMockLogState(overrides?: Partial<Record<string, any>>) {
26
+ mockLogState = {
27
+ addLog: mockAddLog,
28
+ ...overrides,
29
+ };
30
+ }
31
+
32
+ vi.mock("../../stores/use-connection-store", () => {
33
+ return {
34
+ useConnectionStore: Object.assign(
35
+ (selector: (s: any) => any) => selector(mockConnectionState),
36
+ {
37
+ getState: () => mockConnectionState,
38
+ setState: (partial: any) => {
39
+ const next = typeof partial === "function" ? partial(mockConnectionState) : partial;
40
+ mockConnectionState = { ...mockConnectionState, ...next };
41
+ },
42
+ }
43
+ ),
44
+ };
45
+ });
46
+
47
+ vi.mock("../../stores/use-log-store", () => {
48
+ return {
49
+ useLogStore: Object.assign(
50
+ (selector: (s: any) => any) => selector(mockLogState),
51
+ {
52
+ getState: () => mockLogState,
53
+ setState: (partial: any) => {
54
+ const next = typeof partial === "function" ? partial(mockLogState) : partial;
55
+ mockLogState = { ...mockLogState, ...next };
56
+ },
57
+ }
58
+ ),
59
+ };
60
+ });
61
+
62
+ vi.mock("../../stores/use-explorer-store", () => ({
63
+ useExplorerStore: Object.assign(
64
+ (selector: (s: any) => any) =>
65
+ selector({ listRootDir: mockListRootDir }),
66
+ { getState: () => ({ listRootDir: mockListRootDir }) }
67
+ ),
68
+ }));
69
+
70
+ vi.mock("../../stores/use-chat-store", () => ({
71
+ useChatStore: Object.assign(
72
+ (selector: (s: any) => any) =>
73
+ selector({ addMessage: mockChatAddMessage, setMessages: mockChatSetMessages }),
74
+ {
75
+ getState: () => ({
76
+ addMessage: mockChatAddMessage,
77
+ setMessages: mockChatSetMessages,
78
+ }),
79
+ }
80
+ ),
81
+ }));
82
+
83
+ vi.mock("../../lib/api-client", () => ({
84
+ apiClient: {
85
+ subscribe: mockSubscribe,
86
+ unsubscribe: mockUnsubscribe,
87
+ call: mockCall,
88
+ },
89
+ }));
90
+
91
+ describe("useRpcInit", () => {
92
+ beforeEach(() => {
93
+ vi.clearAllMocks();
94
+ mockSubscribe.mockResolvedValue("sub-1");
95
+ mockCall.mockResolvedValue({ messages: [] });
96
+ resetMockLogState();
97
+ });
98
+
99
+ it("should call initializeConnection on mount", async () => {
100
+ resetMockConnectionState({ ready: false });
101
+ const { useRpcInit } = await import("../../hooks/use-rpc-init");
102
+ renderHook(() => useRpcInit());
103
+ expect(mockInitializeConnection).toHaveBeenCalledTimes(1);
104
+ });
105
+
106
+ it("should not subscribe when not ready", async () => {
107
+ resetMockConnectionState({ ready: false });
108
+ const { useRpcInit } = await import("../../hooks/use-rpc-init");
109
+ renderHook(() => useRpcInit());
110
+ expect(mockSubscribe).not.toHaveBeenCalled();
111
+ expect(mockListRootDir).not.toHaveBeenCalled();
112
+ expect(mockCall).not.toHaveBeenCalled();
113
+ });
114
+
115
+ it("should subscribe, load history and init explorer when ready", async () => {
116
+ resetMockConnectionState({ ready: true });
117
+ const { useRpcInit } = await import("../../hooks/use-rpc-init");
118
+ renderHook(() => useRpcInit());
119
+
120
+ await waitFor(() => {
121
+ expect(mockListRootDir).toHaveBeenCalledTimes(1);
122
+ });
123
+
124
+ await waitFor(() => {
125
+ expect(mockSubscribe).toHaveBeenCalledWith(
126
+ "chat.message",
127
+ expect.any(Function),
128
+ {}
129
+ );
130
+ });
131
+
132
+ await waitFor(() => {
133
+ expect(mockCall).toHaveBeenCalledWith("chat.list", { limit: 100 });
134
+ });
135
+ });
136
+
137
+ it("should load history messages into chat store", async () => {
138
+ const messages = [
139
+ { id: "1", role: "user" as const, content: "hello", timestamp: 1000 },
140
+ { id: "2", role: "assistant" as const, content: "world", timestamp: 2000 },
141
+ ];
142
+ mockCall.mockResolvedValue({ messages });
143
+
144
+ resetMockConnectionState({ ready: true });
145
+ const { useRpcInit } = await import("../../hooks/use-rpc-init");
146
+ renderHook(() => useRpcInit());
147
+
148
+ await waitFor(() => {
149
+ expect(mockChatSetMessages).toHaveBeenCalledWith(
150
+ messages.map((m) => ({
151
+ id: m.id,
152
+ role: m.role,
153
+ content: m.content,
154
+ timestamp: m.timestamp,
155
+ }))
156
+ );
157
+ });
158
+
159
+ expect(mockAddLog).toHaveBeenCalledWith("Loaded 2 history messages");
160
+ });
161
+
162
+ it("should unsubscribe on cleanup when ready", async () => {
163
+ resetMockConnectionState({ ready: true });
164
+ const { useRpcInit } = await import("../../hooks/use-rpc-init");
165
+ const { unmount } = renderHook(() => useRpcInit());
166
+
167
+ await waitFor(() => {
168
+ expect(mockSubscribe).toHaveBeenCalled();
169
+ });
170
+
171
+ unmount();
172
+ expect(mockUnsubscribe).toHaveBeenCalledWith("sub-1");
173
+ });
174
+
175
+ it("should handle history load failure gracefully", async () => {
176
+ mockCall.mockRejectedValue(new Error("Network error"));
177
+ resetMockConnectionState({ ready: true });
178
+ const { useRpcInit } = await import("../../hooks/use-rpc-init");
179
+ renderHook(() => useRpcInit());
180
+
181
+ await waitFor(() => {
182
+ expect(mockAddLog).toHaveBeenCalledWith(
183
+ expect.stringContaining("Failed to load history")
184
+ );
185
+ });
186
+ });
187
+
188
+ it("should forward chat.message payloads to chat store", async () => {
189
+ let subscriptionHandler: ((payload: any) => void) | undefined;
190
+ mockSubscribe.mockImplementation(async (_event: string, handler: (p: any) => void) => {
191
+ subscriptionHandler = handler;
192
+ return "sub-1";
193
+ });
194
+
195
+ resetMockConnectionState({ ready: true });
196
+ const { useRpcInit } = await import("../../hooks/use-rpc-init");
197
+ renderHook(() => useRpcInit());
198
+
199
+ await waitFor(() => {
200
+ expect(mockSubscribe).toHaveBeenCalled();
201
+ });
202
+
203
+ subscriptionHandler!({
204
+ id: "msg-1",
205
+ role: "user",
206
+ content: "test message",
207
+ timestamp: 12345,
208
+ });
209
+
210
+ expect(mockChatAddMessage).toHaveBeenCalledWith({
211
+ id: "msg-1",
212
+ role: "user",
213
+ content: "test message",
214
+ timestamp: 12345,
215
+ });
216
+ });
217
+ });