@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,130 @@
1
+ import { BrowserWindow, BrowserView, Updater, ApplicationMenu } from "electrobun/bun";
2
+ import { RPCServer } from "@dyyz1993/rpc-core";
3
+ import { ElectrobunTransport } from "../gateway/ipc-transport";
4
+ import { registerAllHandlers } from "../shared/register-all-handlers";
5
+ import { createLogger, configureLogDir } from "../shared/lib/logger";
6
+ import { config } from "../server-config";
7
+
8
+ configureLogDir("logs");
9
+ const log = createLogger("server");
10
+
11
+ async function getMainViewUrl(): Promise<string> {
12
+ const channel = await Updater.localInfo.channel();
13
+ const DEV_SERVER_URL = "http://localhost:5173";
14
+ if (channel === "dev") {
15
+ try {
16
+ await fetch(DEV_SERVER_URL, { method: "HEAD" });
17
+ log.info(`HMR enabled: Using Vite dev server at ${DEV_SERVER_URL}`);
18
+ return DEV_SERVER_URL;
19
+ } catch {
20
+ log.info("Vite dev server not running.");
21
+ }
22
+ }
23
+ return "views://mainview/index.html";
24
+ }
25
+
26
+ const url = await getMainViewUrl();
27
+
28
+ const transport = new ElectrobunTransport();
29
+ const server = new RPCServer(transport);
30
+
31
+ // --- 注册 RPC handlers(自动导入 handlers barrel) ---
32
+ registerAllHandlers(server, { platform: "desktop" });
33
+
34
+ // --- 创建窗口 ---
35
+
36
+ const mainWindow = new BrowserWindow({
37
+ title: "Pi Agent",
38
+ url,
39
+ frame: {
40
+ width: 1200,
41
+ height: 800,
42
+ x: 200,
43
+ y: 200,
44
+ },
45
+ rpc: BrowserView.defineRPC({
46
+ maxRequestTime: 60000,
47
+ handlers: {
48
+ requests: {},
49
+ messages: {
50
+ // @ts-expect-error Electrobun's messages schema doesn't support custom keys at compile time
51
+ "rpc-message": (data: unknown) => {
52
+ try {
53
+ const message = typeof data === "string" ? JSON.parse(data) : data;
54
+ transport.handleMessage(message);
55
+ } catch (error) {
56
+ log.error("Failed to parse RPC message", { error });
57
+ }
58
+ },
59
+ },
60
+ },
61
+ }),
62
+ });
63
+
64
+ transport.setBrowserView(mainWindow.webview);
65
+
66
+ log.info("Pi Agent desktop app started!");
67
+
68
+ // ==============================
69
+ // Optional: Web service for LAN access (hybrid mode)
70
+ // ==============================
71
+ if (config.enableWebService) {
72
+ try {
73
+ const { createWebServer, getLocalIP } = await import("../shared/lib/web-server");
74
+
75
+ const webServer = await createWebServer({
76
+ port: config.port,
77
+ authToken: config.authToken,
78
+ maxUploadSize: config.maxUploadSize,
79
+ corsOrigin: "*",
80
+ });
81
+
82
+ const localIp = getLocalIP();
83
+ log.info(`[Hybrid] Web service started: http://${localIp}:${webServer.port}`);
84
+ log.info(`[Hybrid] RPC: POST http://${localIp}:${webServer.port}/api/rpc`);
85
+ log.info(`[Hybrid] SSE: GET http://${localIp}:${webServer.port}/api/events`);
86
+ log.info(
87
+ `[Hybrid] Share URL: http://${localIp}:${webServer.port}?token=${webServer.authToken}`
88
+ );
89
+
90
+ server.emitEvent("hybrid.ready", {
91
+ url: `http://${localIp}:${webServer.port}`,
92
+ rpcUrl: `http://${localIp}:${webServer.port}/api/rpc`,
93
+ sseUrl: `http://${localIp}:${webServer.port}/api/events`,
94
+ token: webServer.authToken,
95
+ });
96
+ } catch (err) {
97
+ log.error("[Hybrid] Failed to start web service", {
98
+ error: err instanceof Error ? err.message : String(err),
99
+ });
100
+ }
101
+ }
102
+
103
+ ApplicationMenu.setApplicationMenu([
104
+ {
105
+ label: "Pi Agent",
106
+ submenu: [
107
+ { role: "about" },
108
+ { type: "separator" },
109
+ { role: "hide" },
110
+ { role: "hideOthers" },
111
+ { role: "showAll" },
112
+ { type: "separator" },
113
+ { role: "quit" },
114
+ ],
115
+ },
116
+ {
117
+ label: "Edit",
118
+ submenu: [
119
+ { role: "undo" },
120
+ { role: "redo" },
121
+ { type: "separator" },
122
+ { role: "cut" },
123
+ { role: "copy" },
124
+ { role: "paste" },
125
+ { role: "selectAll" },
126
+ ],
127
+ },
128
+ { label: "View", submenu: [{ role: "toggleFullScreen" }] },
129
+ { label: "Window", submenu: [{ role: "minimize" }, { role: "zoom" }] },
130
+ ]);
@@ -0,0 +1 @@
1
+ declare module "three";
@@ -0,0 +1 @@
1
+ export { createHttpHandler, type HttpRouteDeps } from "../shared/http-routes";
@@ -0,0 +1,68 @@
1
+ import type { Transport, MessageHandler, ErrorHandler } from "@dyyz1993/rpc-core";
2
+
3
+ /**
4
+ * Transport that bridges between Bun and Webview via:
5
+ * - Bun → Browser: executeJavascript calls window.__piAgentIPC()
6
+ * - Browser → Bun: __electrobunBunBridge.postMessage with Electrobun message format
7
+ */
8
+ export class ElectrobunTransport implements Transport {
9
+ private messageHandlers: Set<MessageHandler> = new Set();
10
+ private errorHandlers: Set<ErrorHandler> = new Set();
11
+ private browserView: { executeJavascript: (js: string) => void } | null = null;
12
+ private closed = false;
13
+
14
+ setBrowserView(view: { executeJavascript: (js: string) => void }): void {
15
+ this.browserView = view;
16
+ }
17
+
18
+ async send(message: unknown): Promise<void> {
19
+ if (this.closed) {
20
+ throw new Error("Transport is closed");
21
+ }
22
+ if (!this.browserView) {
23
+ throw new Error("BrowserView not set");
24
+ }
25
+
26
+ // Pass JSON directly as a JS expression — JSON is valid JavaScript
27
+ const msgJson = JSON.stringify(message);
28
+ this.browserView.executeJavascript(
29
+ `if(typeof window.__piAgentIPC==="function"){window.__piAgentIPC(${msgJson})}`
30
+ );
31
+ }
32
+
33
+ onMessage(handler: MessageHandler): () => void {
34
+ this.messageHandlers.add(handler);
35
+ return () => {
36
+ this.messageHandlers.delete(handler);
37
+ };
38
+ }
39
+
40
+ onError(handler: ErrorHandler): () => void {
41
+ this.errorHandlers.add(handler);
42
+ return () => {
43
+ this.errorHandlers.delete(handler);
44
+ };
45
+ }
46
+
47
+ onDisconnect(): () => void {
48
+ return () => {};
49
+ }
50
+
51
+ isConnected(): boolean {
52
+ return this.browserView !== null && !this.closed;
53
+ }
54
+
55
+ close(): void {
56
+ this.closed = true;
57
+ this.messageHandlers.clear();
58
+ this.errorHandlers.clear();
59
+ this.browserView = null;
60
+ }
61
+
62
+ handleMessage(message: unknown): void {
63
+ if (this.closed) return;
64
+ for (const handler of this.messageHandlers) {
65
+ handler(message);
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,221 @@
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 msgType = (message as any)?.type;
180
+ const msgMethod = (message as any)?.method;
181
+ log.info("RPC routing", { clientId, msgType, msgMethod, handlerCount: client.messageHandlers.size });
182
+ for (const handler of client.messageHandlers) {
183
+ try {
184
+ handler(message);
185
+ } catch (err) {
186
+ log.error("RPC handler error", { clientId, msgMethod, error: err instanceof Error ? err.message : String(err) });
187
+ }
188
+ }
189
+ }
190
+
191
+ return {
192
+ clients,
193
+ handleSseConnect,
194
+ handleRpcPost,
195
+ close: (): void => {
196
+ for (const client of clients.values()) {
197
+ client.rpcServer.close();
198
+ if (!client.res.writableEnded) {
199
+ client.res.end();
200
+ }
201
+ }
202
+ clients.clear();
203
+ },
204
+ };
205
+ }
206
+
207
+ export function verifyToken(req: IncomingMessage, authToken: string): boolean {
208
+ const auth = req.headers["authorization"];
209
+ if (auth && safeEqual(auth, `Bearer ${authToken}`)) return true;
210
+
211
+ if (req.url) {
212
+ try {
213
+ const url = new URL(req.url, "http://localhost");
214
+ const token = url.searchParams.get("token");
215
+ if (token && safeEqual(token, authToken)) return true;
216
+ } catch {
217
+ /* invalid URL */
218
+ }
219
+ }
220
+ return false;
221
+ }
@@ -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
+ });