@akiojin/gwt 2.13.0 → 3.0.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 (295) hide show
  1. package/README.ja.md +41 -0
  2. package/README.md +38 -0
  3. package/dist/claude.d.ts.map +1 -1
  4. package/dist/claude.js +17 -11
  5. package/dist/claude.js.map +1 -1
  6. package/dist/cli/ui/components/App.d.ts +0 -6
  7. package/dist/cli/ui/components/App.d.ts.map +1 -1
  8. package/dist/cli/ui/components/App.js +27 -88
  9. package/dist/cli/ui/components/App.js.map +1 -1
  10. package/dist/cli/ui/components/common/Select.js +2 -2
  11. package/dist/cli/ui/components/common/Select.js.map +1 -1
  12. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  13. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  14. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +1 -1
  15. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +3 -3
  16. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +1 -1
  17. package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
  18. package/dist/cli/ui/utils/continueSession.js +1 -1
  19. package/dist/cli/ui/utils/continueSession.js.map +1 -1
  20. package/dist/client/assets/index-DsDNCy5f.css +1 -0
  21. package/dist/client/assets/index-f5D2XwDh.js +72 -0
  22. package/dist/client/index.html +2 -2
  23. package/dist/codex.d.ts.map +1 -1
  24. package/dist/codex.js +21 -11
  25. package/dist/codex.js.map +1 -1
  26. package/dist/config/builtin-tools.d.ts.map +1 -1
  27. package/dist/config/builtin-tools.js +3 -2
  28. package/dist/config/builtin-tools.js.map +1 -1
  29. package/dist/config/shared-env.d.ts +41 -0
  30. package/dist/config/shared-env.d.ts.map +1 -0
  31. package/dist/config/shared-env.js +114 -0
  32. package/dist/config/shared-env.js.map +1 -0
  33. package/dist/gemini.d.ts.map +1 -1
  34. package/dist/gemini.js +20 -17
  35. package/dist/gemini.js.map +1 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +13 -8
  38. package/dist/index.js.map +1 -1
  39. package/dist/logging/logger.d.ts.map +1 -1
  40. package/dist/logging/logger.js +4 -1
  41. package/dist/logging/logger.js.map +1 -1
  42. package/dist/qwen.d.ts.map +1 -1
  43. package/dist/qwen.js +15 -11
  44. package/dist/qwen.js.map +1 -1
  45. package/dist/services/aiToolResolver.d.ts +41 -0
  46. package/dist/services/aiToolResolver.d.ts.map +1 -0
  47. package/dist/services/aiToolResolver.js +194 -0
  48. package/dist/services/aiToolResolver.js.map +1 -0
  49. package/dist/services/customToolResolver.d.ts +10 -0
  50. package/dist/services/customToolResolver.d.ts.map +1 -0
  51. package/dist/services/customToolResolver.js +71 -0
  52. package/dist/services/customToolResolver.js.map +1 -0
  53. package/dist/shared/aiToolConstants.d.ts +9 -0
  54. package/dist/shared/aiToolConstants.d.ts.map +1 -0
  55. package/dist/shared/aiToolConstants.js +29 -0
  56. package/dist/shared/aiToolConstants.js.map +1 -0
  57. package/dist/types/tools.d.ts +12 -0
  58. package/dist/types/tools.d.ts.map +1 -1
  59. package/dist/utils/prompt.d.ts.map +1 -1
  60. package/dist/utils/prompt.js.map +1 -1
  61. package/dist/utils/session.d.ts.map +1 -1
  62. package/dist/utils/session.js +15 -6
  63. package/dist/utils/session.js.map +1 -1
  64. package/dist/utils/terminal.d.ts +12 -3
  65. package/dist/utils/terminal.d.ts.map +1 -1
  66. package/dist/utils/terminal.js +5 -34
  67. package/dist/utils/terminal.js.map +1 -1
  68. package/dist/utils/webui.d.ts +8 -0
  69. package/dist/utils/webui.d.ts.map +1 -0
  70. package/dist/utils/webui.js +35 -0
  71. package/dist/utils/webui.js.map +1 -0
  72. package/dist/web/client/src/components/AIToolLaunchModal.d.ts +9 -0
  73. package/dist/web/client/src/components/AIToolLaunchModal.d.ts.map +1 -0
  74. package/dist/web/client/src/components/AIToolLaunchModal.js +363 -0
  75. package/dist/web/client/src/components/AIToolLaunchModal.js.map +1 -0
  76. package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
  77. package/dist/web/client/src/components/BranchGraph.js +46 -49
  78. package/dist/web/client/src/components/BranchGraph.js.map +1 -1
  79. package/dist/web/client/src/components/CustomToolForm.d.ts +23 -0
  80. package/dist/web/client/src/components/CustomToolForm.d.ts.map +1 -0
  81. package/dist/web/client/src/components/CustomToolForm.js +209 -0
  82. package/dist/web/client/src/components/CustomToolForm.js.map +1 -0
  83. package/dist/web/client/src/components/CustomToolList.d.ts +10 -0
  84. package/dist/web/client/src/components/CustomToolList.d.ts.map +1 -0
  85. package/dist/web/client/src/components/CustomToolList.js +57 -0
  86. package/dist/web/client/src/components/CustomToolList.js.map +1 -0
  87. package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
  88. package/dist/web/client/src/components/EnvEditor.js +33 -26
  89. package/dist/web/client/src/components/EnvEditor.js.map +1 -1
  90. package/dist/web/client/src/components/EnvironmentEditor.d.ts +17 -0
  91. package/dist/web/client/src/components/EnvironmentEditor.d.ts.map +1 -0
  92. package/dist/web/client/src/components/EnvironmentEditor.js +22 -0
  93. package/dist/web/client/src/components/EnvironmentEditor.js.map +1 -0
  94. package/dist/web/client/src/components/Terminal.d.ts.map +1 -1
  95. package/dist/web/client/src/components/Terminal.js +10 -3
  96. package/dist/web/client/src/components/Terminal.js.map +1 -1
  97. package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts +10 -0
  98. package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts.map +1 -0
  99. package/dist/web/client/src/components/branch-detail/BranchInfoCards.js +104 -0
  100. package/dist/web/client/src/components/branch-detail/BranchInfoCards.js.map +1 -0
  101. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts +22 -0
  102. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts.map +1 -0
  103. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js +79 -0
  104. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js.map +1 -0
  105. package/dist/web/client/src/components/branch-detail/TerminalPanel.d.ts +11 -0
  106. package/dist/web/client/src/components/branch-detail/TerminalPanel.d.ts.map +1 -0
  107. package/dist/web/client/src/components/branch-detail/TerminalPanel.js +32 -0
  108. package/dist/web/client/src/components/branch-detail/TerminalPanel.js.map +1 -0
  109. package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts +40 -0
  110. package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts.map +1 -0
  111. package/dist/web/client/src/components/branch-detail/ToolLauncher.js +147 -0
  112. package/dist/web/client/src/components/branch-detail/ToolLauncher.js.map +1 -0
  113. package/dist/web/client/src/components/branch-detail/index.d.ts +5 -0
  114. package/dist/web/client/src/components/branch-detail/index.d.ts.map +1 -0
  115. package/dist/web/client/src/components/branch-detail/index.js +5 -0
  116. package/dist/web/client/src/components/branch-detail/index.js.map +1 -0
  117. package/dist/web/client/src/components/common/BranchCard.d.ts +17 -0
  118. package/dist/web/client/src/components/common/BranchCard.d.ts.map +1 -0
  119. package/dist/web/client/src/components/common/BranchCard.js +36 -0
  120. package/dist/web/client/src/components/common/BranchCard.js.map +1 -0
  121. package/dist/web/client/src/components/common/MetricCard.d.ts +10 -0
  122. package/dist/web/client/src/components/common/MetricCard.d.ts.map +1 -0
  123. package/dist/web/client/src/components/common/MetricCard.js +10 -0
  124. package/dist/web/client/src/components/common/MetricCard.js.map +1 -0
  125. package/dist/web/client/src/components/common/PageHeader.d.ts +12 -0
  126. package/dist/web/client/src/components/common/PageHeader.d.ts.map +1 -0
  127. package/dist/web/client/src/components/common/PageHeader.js +14 -0
  128. package/dist/web/client/src/components/common/PageHeader.js.map +1 -0
  129. package/dist/web/client/src/components/common/SearchInput.d.ts +14 -0
  130. package/dist/web/client/src/components/common/SearchInput.d.ts.map +1 -0
  131. package/dist/web/client/src/components/common/SearchInput.js +15 -0
  132. package/dist/web/client/src/components/common/SearchInput.js.map +1 -0
  133. package/dist/web/client/src/components/common/StatusBadge.d.ts +10 -0
  134. package/dist/web/client/src/components/common/StatusBadge.d.ts.map +1 -0
  135. package/dist/web/client/src/components/common/StatusBadge.js +15 -0
  136. package/dist/web/client/src/components/common/StatusBadge.js.map +1 -0
  137. package/dist/web/client/src/components/common/index.d.ts +6 -0
  138. package/dist/web/client/src/components/common/index.d.ts.map +1 -0
  139. package/dist/web/client/src/components/common/index.js +6 -0
  140. package/dist/web/client/src/components/common/index.js.map +1 -0
  141. package/dist/web/client/src/components/ui/alert.d.ts +9 -0
  142. package/dist/web/client/src/components/ui/alert.d.ts.map +1 -0
  143. package/dist/web/client/src/components/ui/alert.js +25 -0
  144. package/dist/web/client/src/components/ui/alert.js.map +1 -0
  145. package/dist/web/client/src/components/ui/badge.d.ts +10 -0
  146. package/dist/web/client/src/components/ui/badge.d.ts.map +1 -0
  147. package/dist/web/client/src/components/ui/badge.js +25 -0
  148. package/dist/web/client/src/components/ui/badge.js.map +1 -0
  149. package/dist/web/client/src/components/ui/button.d.ts +12 -0
  150. package/dist/web/client/src/components/ui/button.d.ts.map +1 -0
  151. package/dist/web/client/src/components/ui/button.js +33 -0
  152. package/dist/web/client/src/components/ui/button.js.map +1 -0
  153. package/dist/web/client/src/components/ui/card.d.ts +9 -0
  154. package/dist/web/client/src/components/ui/card.d.ts.map +1 -0
  155. package/dist/web/client/src/components/ui/card.js +16 -0
  156. package/dist/web/client/src/components/ui/card.js.map +1 -0
  157. package/dist/web/client/src/components/ui/index.d.ts +8 -0
  158. package/dist/web/client/src/components/ui/index.d.ts.map +1 -0
  159. package/dist/web/client/src/components/ui/index.js +8 -0
  160. package/dist/web/client/src/components/ui/index.js.map +1 -0
  161. package/dist/web/client/src/components/ui/input.d.ts +4 -0
  162. package/dist/web/client/src/components/ui/input.d.ts.map +1 -0
  163. package/dist/web/client/src/components/ui/input.js +8 -0
  164. package/dist/web/client/src/components/ui/input.js.map +1 -0
  165. package/dist/web/client/src/components/ui/select.d.ts +14 -0
  166. package/dist/web/client/src/components/ui/select.d.ts.map +1 -0
  167. package/dist/web/client/src/components/ui/select.js +39 -0
  168. package/dist/web/client/src/components/ui/select.js.map +1 -0
  169. package/dist/web/client/src/components/ui/table.d.ts +11 -0
  170. package/dist/web/client/src/components/ui/table.d.ts.map +1 -0
  171. package/dist/web/client/src/components/ui/table.js +21 -0
  172. package/dist/web/client/src/components/ui/table.js.map +1 -0
  173. package/dist/web/client/src/hooks/useSessions.d.ts.map +1 -1
  174. package/dist/web/client/src/hooks/useSessions.js +6 -1
  175. package/dist/web/client/src/hooks/useSessions.js.map +1 -1
  176. package/dist/web/client/src/lib/utils.d.ts +7 -0
  177. package/dist/web/client/src/lib/utils.d.ts.map +1 -0
  178. package/dist/web/client/src/lib/utils.js +10 -0
  179. package/dist/web/client/src/lib/utils.js.map +1 -0
  180. package/dist/web/client/src/lib/websocket.d.ts +7 -0
  181. package/dist/web/client/src/lib/websocket.d.ts.map +1 -1
  182. package/dist/web/client/src/lib/websocket.js +44 -0
  183. package/dist/web/client/src/lib/websocket.js.map +1 -1
  184. package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
  185. package/dist/web/client/src/pages/BranchDetailPage.js +125 -376
  186. package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
  187. package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
  188. package/dist/web/client/src/pages/BranchListPage.js +89 -127
  189. package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
  190. package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
  191. package/dist/web/client/src/pages/ConfigManagementPage.js +46 -41
  192. package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
  193. package/dist/web/client/src/pages/ConfigPage.d.ts +3 -0
  194. package/dist/web/client/src/pages/ConfigPage.d.ts.map +1 -0
  195. package/dist/web/client/src/pages/ConfigPage.js +216 -0
  196. package/dist/web/client/src/pages/ConfigPage.js.map +1 -0
  197. package/dist/web/client/vite.config.d.ts.map +1 -1
  198. package/dist/web/client/vite.config.js +8 -1
  199. package/dist/web/client/vite.config.js.map +1 -1
  200. package/dist/web/server/index.d.ts +24 -2
  201. package/dist/web/server/index.d.ts.map +1 -1
  202. package/dist/web/server/index.js +46 -15
  203. package/dist/web/server/index.js.map +1 -1
  204. package/dist/web/server/pty/manager.d.ts +12 -10
  205. package/dist/web/server/pty/manager.d.ts.map +1 -1
  206. package/dist/web/server/pty/manager.js +76 -43
  207. package/dist/web/server/pty/manager.js.map +1 -1
  208. package/dist/web/server/routes/sessions.d.ts.map +1 -1
  209. package/dist/web/server/routes/sessions.js +35 -2
  210. package/dist/web/server/routes/sessions.js.map +1 -1
  211. package/dist/web/server/routes/worktrees.js +2 -2
  212. package/dist/web/server/routes/worktrees.js.map +1 -1
  213. package/dist/web/server/services/worktrees.d.ts.map +1 -1
  214. package/dist/web/server/services/worktrees.js +7 -1
  215. package/dist/web/server/services/worktrees.js.map +1 -1
  216. package/dist/web/server/tray.d.ts +25 -0
  217. package/dist/web/server/tray.d.ts.map +1 -0
  218. package/dist/web/server/tray.js +98 -0
  219. package/dist/web/server/tray.js.map +1 -0
  220. package/dist/web/server/websocket/handler.d.ts +18 -2
  221. package/dist/web/server/websocket/handler.d.ts.map +1 -1
  222. package/dist/web/server/websocket/handler.js +82 -9
  223. package/dist/web/server/websocket/handler.js.map +1 -1
  224. package/package.json +15 -2
  225. package/src/claude.ts +26 -15
  226. package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +1 -1
  227. package/src/cli/ui/__tests__/components/common/Select.test.tsx +11 -0
  228. package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +3 -1
  229. package/src/cli/ui/__tests__/components/screens/BranchQuickStartScreen.test.tsx +4 -4
  230. package/src/cli/ui/components/App.tsx +33 -133
  231. package/src/cli/ui/components/common/Select.tsx +2 -2
  232. package/src/cli/ui/components/screens/BranchListScreen.tsx +28 -21
  233. package/src/cli/ui/components/screens/BranchQuickStartScreen.tsx +41 -46
  234. package/src/cli/ui/utils/continueSession.ts +1 -7
  235. package/src/codex.ts +31 -22
  236. package/src/config/builtin-tools.ts +6 -2
  237. package/src/config/shared-env.ts +139 -0
  238. package/src/gemini.ts +35 -22
  239. package/src/index.ts +13 -8
  240. package/src/logging/logger.ts +5 -2
  241. package/src/qwen.ts +28 -19
  242. package/src/services/aiToolResolver.ts +276 -0
  243. package/src/services/customToolResolver.ts +98 -0
  244. package/src/shared/aiToolConstants.ts +30 -0
  245. package/src/trayicon.d.ts +30 -0
  246. package/src/types/tools.ts +15 -0
  247. package/src/utils/prompt.ts +15 -9
  248. package/src/utils/session.ts +80 -26
  249. package/src/utils/terminal.ts +11 -41
  250. package/src/utils/webui.ts +43 -0
  251. package/src/web/client/components.json +21 -0
  252. package/src/web/client/src/components/AIToolLaunchModal.tsx +575 -0
  253. package/src/web/client/src/components/BranchGraph.tsx +95 -75
  254. package/src/web/client/src/components/CustomToolForm.tsx +386 -0
  255. package/src/web/client/src/components/CustomToolList.tsx +119 -0
  256. package/src/web/client/src/components/EnvEditor.tsx +91 -81
  257. package/src/web/client/src/components/EnvironmentEditor.tsx +97 -0
  258. package/src/web/client/src/components/Terminal.tsx +11 -3
  259. package/src/web/client/src/components/branch-detail/BranchInfoCards.tsx +179 -0
  260. package/src/web/client/src/components/branch-detail/SessionHistoryTable.tsx +181 -0
  261. package/src/web/client/src/components/branch-detail/TerminalPanel.tsx +92 -0
  262. package/src/web/client/src/components/branch-detail/ToolLauncher.tsx +327 -0
  263. package/src/web/client/src/components/branch-detail/index.ts +4 -0
  264. package/src/web/client/src/components/common/BranchCard.tsx +117 -0
  265. package/src/web/client/src/components/common/MetricCard.tsx +22 -0
  266. package/src/web/client/src/components/common/PageHeader.tsx +44 -0
  267. package/src/web/client/src/components/common/SearchInput.tsx +40 -0
  268. package/src/web/client/src/components/common/StatusBadge.tsx +37 -0
  269. package/src/web/client/src/components/common/index.ts +5 -0
  270. package/src/web/client/src/components/ui/alert.tsx +63 -0
  271. package/src/web/client/src/components/ui/badge.tsx +44 -0
  272. package/src/web/client/src/components/ui/button.tsx +57 -0
  273. package/src/web/client/src/components/ui/card.tsx +82 -0
  274. package/src/web/client/src/components/ui/index.ts +32 -0
  275. package/src/web/client/src/components/ui/input.tsx +21 -0
  276. package/src/web/client/src/components/ui/select.tsx +156 -0
  277. package/src/web/client/src/components/ui/table.tsx +119 -0
  278. package/src/web/client/src/hooks/useSessions.ts +10 -1
  279. package/src/web/client/src/index.css +46 -816
  280. package/src/web/client/src/lib/utils.ts +10 -0
  281. package/src/web/client/src/lib/websocket.ts +48 -1
  282. package/src/web/client/src/pages/BranchDetailPage.tsx +247 -723
  283. package/src/web/client/src/pages/BranchListPage.tsx +190 -236
  284. package/src/web/client/src/pages/ConfigManagementPage.tsx +94 -76
  285. package/src/web/client/src/pages/ConfigPage.tsx +362 -0
  286. package/src/web/client/vite.config.ts +8 -1
  287. package/src/web/server/index.ts +72 -15
  288. package/src/web/server/pty/manager.ts +128 -55
  289. package/src/web/server/routes/sessions.ts +59 -7
  290. package/src/web/server/routes/worktrees.ts +3 -3
  291. package/src/web/server/services/worktrees.ts +12 -4
  292. package/src/web/server/tray.ts +120 -0
  293. package/src/web/server/websocket/handler.ts +119 -13
  294. package/dist/client/assets/index-DeNwPosA.css +0 -1
  295. package/dist/client/assets/index-Dl798X5w.js +0 -32
@@ -16,14 +16,42 @@ import { registerRoutes } from "./routes/index.js";
16
16
  import { importOsEnvIntoSharedConfig } from "./env/importer.js";
17
17
  import { createLogger } from "../../logging/logger.js";
18
18
  import type { WebFastifyInstance } from "./types.js";
19
+ import { disposeSystemTray, startSystemTray } from "./tray.js";
20
+ import { resolveWebUiPort } from "../../utils/webui.js";
19
21
 
20
22
  const __filename = fileURLToPath(import.meta.url);
21
23
  const __dirname = dirname(__filename);
22
24
 
23
25
  /**
24
- * Webサーバーを起動
26
+ * Web UI サーバーのライフサイクル操作ハンドル。
27
+ *
28
+ * `close()` は Web UI サーバーを停止し、関連リソース(PTY/トレイ等)を解放します。
29
+ */
30
+ export interface WebServerHandle {
31
+ close: () => Promise<void>;
32
+ }
33
+
34
+ /**
35
+ * `startWebServer` の起動オプション。
25
36
  */
26
- export async function startWebServer(): Promise<void> {
37
+ export interface StartWebServerOptions {
38
+ /**
39
+ * true の場合、Web UI サーバーが CLI 本体の終了をブロックしないようにします。
40
+ * (内部で `server.unref()` を呼び、サーバーの存在がプロセス生存を維持しないようにします)
41
+ */
42
+ background?: boolean;
43
+ }
44
+
45
+ /**
46
+ * Web UI サーバーを起動します。
47
+ *
48
+ * @param options - 起動オプション
49
+ * @returns Web UI サーバー停止用のハンドル
50
+ * @throws サーバー起動(listen/初期化)に失敗した場合
51
+ */
52
+ export async function startWebServer(
53
+ options: StartWebServerOptions = {},
54
+ ): Promise<WebServerHandle> {
27
55
  const serverLogger = createLogger({ category: "server" });
28
56
 
29
57
  const fastify: WebFastifyInstance = Fastify({
@@ -32,7 +60,7 @@ export async function startWebServer(): Promise<void> {
32
60
 
33
61
  // PTYマネージャーとWebSocketハンドラーを初期化
34
62
  const ptyManager = new PTYManager();
35
- const wsHandler = new WebSocketHandler(ptyManager);
63
+ const wsHandler = new WebSocketHandler(ptyManager, fastify.log);
36
64
 
37
65
  // WebSocketサポートを追加
38
66
  await fastify.register(fastifyWebsocket);
@@ -60,17 +88,46 @@ export async function startWebServer(): Promise<void> {
60
88
  });
61
89
 
62
90
  // サーバー起動
63
- try {
64
- const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
65
- // Docker環境からホストOSでアクセスできるよう、0.0.0.0でリッスン
66
- // IPv4/IPv6両方対応のため、listenOnStart: false も検討可能
67
- const host = process.env.HOST || "0.0.0.0";
68
-
69
- await fastify.listen({ port, host });
70
- console.log(`Web UI server running at http://${host}:${port}`);
71
- console.log(`Access from host: http://localhost:${port}`);
72
- } catch (err) {
73
- fastify.log.error(err);
74
- process.exit(1);
91
+ const port = resolveWebUiPort();
92
+ // Docker環境からホストOSでアクセスできるよう、0.0.0.0でリッスン
93
+ // IPv4/IPv6両方対応のため、listenOnStart: false も検討可能
94
+ const host = process.env.HOST || "0.0.0.0";
95
+
96
+ await fastify.listen({ port, host });
97
+ const accessUrl = `http://localhost:${port}`;
98
+ serverLogger.info({ host, port, accessUrl }, "Web UI server started");
99
+ await startSystemTray(accessUrl);
100
+
101
+ if (options.background) {
102
+ fastify.server?.unref?.();
75
103
  }
104
+
105
+ let closed = false;
106
+ return {
107
+ close: async () => {
108
+ if (closed) return;
109
+ closed = true;
110
+
111
+ try {
112
+ try {
113
+ disposeSystemTray();
114
+ } catch (err) {
115
+ serverLogger.warn({ err }, "System tray cleanup failed");
116
+ }
117
+
118
+ for (const session of ptyManager.list()) {
119
+ try {
120
+ ptyManager.delete(session.sessionId);
121
+ } catch (err) {
122
+ serverLogger.warn(
123
+ { err, sessionId: session.sessionId },
124
+ "Failed to delete PTY session",
125
+ );
126
+ }
127
+ }
128
+ } finally {
129
+ await fastify.close();
130
+ }
131
+ },
132
+ };
76
133
  }
@@ -9,6 +9,14 @@ import * as pty from "node-pty";
9
9
  import type { IPty } from "node-pty";
10
10
  import { randomUUID } from "node:crypto";
11
11
  import type { AIToolSession } from "../../../types/api.js";
12
+ import {
13
+ resolveClaudeCommand,
14
+ resolveCodexCommand,
15
+ resolveCustomToolCommand,
16
+ AIToolResolutionError,
17
+ type ResolvedCommand,
18
+ } from "../../../services/aiToolResolver.js";
19
+ import { loadToolsConfig } from "../../../config/tools.js";
12
20
 
13
21
  export interface PTYInstance {
14
22
  ptyProcess: IPty;
@@ -24,37 +32,80 @@ export class PTYManager {
24
32
  /**
25
33
  * 新しいPTYセッションを作成
26
34
  */
27
- public spawn(
35
+ public async spawn(
28
36
  toolType: "claude-code" | "codex-cli" | "custom",
29
37
  worktreePath: string,
30
38
  mode: "normal" | "continue" | "resume",
31
- toolName?: string | null,
32
- cols = 80,
33
- rows = 24,
34
- ): { sessionId: string; session: AIToolSession } {
39
+ options: {
40
+ toolName?: string | null;
41
+ cols?: number;
42
+ rows?: number;
43
+ skipPermissions?: boolean;
44
+ bypassApprovals?: boolean;
45
+ extraArgs?: string[];
46
+ customToolId?: string | null;
47
+ } = {},
48
+ ): Promise<{ sessionId: string; session: AIToolSession }> {
49
+ const cols = options.cols ?? 80;
50
+ const rows = options.rows ?? 24;
51
+ const toolName = options.toolName ?? null;
35
52
  const sessionId = randomUUID();
36
53
 
37
- // AI Toolコマンドを構築
38
- const command = this.buildCommand(toolType, mode, toolName);
39
- const args = this.buildArgs(toolType, mode);
54
+ const resolverOptions: {
55
+ toolName?: string | null;
56
+ skipPermissions?: boolean;
57
+ bypassApprovals?: boolean;
58
+ extraArgs?: string[];
59
+ customToolId?: string | null;
60
+ } = {};
61
+
62
+ if (toolName !== null) {
63
+ resolverOptions.toolName = toolName;
64
+ }
65
+ if (options.skipPermissions !== undefined) {
66
+ resolverOptions.skipPermissions = options.skipPermissions;
67
+ }
68
+ if (options.bypassApprovals !== undefined) {
69
+ resolverOptions.bypassApprovals = options.bypassApprovals;
70
+ }
71
+ if (options.extraArgs && options.extraArgs.length > 0) {
72
+ resolverOptions.extraArgs = options.extraArgs;
73
+ }
74
+ if (options.customToolId !== undefined) {
75
+ resolverOptions.customToolId = options.customToolId;
76
+ }
77
+
78
+ const resolved = await this.resolveCommand(toolType, mode, resolverOptions);
79
+ const sharedEnv = await this.loadSharedEnv();
80
+
81
+ const env: NodeJS.ProcessEnv = {
82
+ ...process.env,
83
+ ...sharedEnv,
84
+ TERM: "xterm-256color",
85
+ COLORTERM: "truecolor",
86
+ };
87
+
88
+ if (resolved.env) {
89
+ Object.assign(env, resolved.env);
90
+ }
91
+
92
+ if (toolType === "claude-code" && options.skipPermissions && isRootUser()) {
93
+ env.IS_SANDBOX = "1";
94
+ }
40
95
 
41
96
  // PTYプロセスをスポーン
42
- const ptyProcess = pty.spawn(command, args, {
97
+ const ptyProcess = pty.spawn(resolved.command, resolved.args, {
43
98
  name: "xterm-256color",
44
99
  cols,
45
100
  rows,
46
101
  cwd: worktreePath,
47
- env: {
48
- ...process.env,
49
- TERM: "xterm-256color",
50
- COLORTERM: "truecolor",
51
- },
102
+ env,
52
103
  });
53
104
 
54
105
  const session: AIToolSession = {
55
106
  sessionId,
56
107
  toolType,
57
- toolName: toolName || null,
108
+ toolName: options.customToolId ?? toolName ?? null,
58
109
  mode,
59
110
  worktreePath,
60
111
  ptyPid: ptyProcess.pid,
@@ -133,57 +184,79 @@ export class PTYManager {
133
184
  return Array.from(this.instances.values()).map((inst) => inst.session);
134
185
  }
135
186
 
136
- /**
137
- * AI Toolのコマンドを構築
138
- */
139
- private buildCommand(
187
+ private async resolveCommand(
140
188
  toolType: "claude-code" | "codex-cli" | "custom",
141
189
  mode: "normal" | "continue" | "resume",
142
- toolName?: string | null,
143
- ): string {
144
- if (toolType === "custom" && toolName) {
145
- // カスタムツールは別途config.jsonから取得する必要があるが、
146
- // ここでは簡易実装としてtoolNameをそのままコマンドとして使用
147
- return toolName;
148
- }
149
-
150
- if (toolType === "codex-cli") {
151
- return "codex";
152
- }
153
-
154
- // claude-code
155
- return "claude";
156
- }
157
-
158
- /**
159
- * AI Toolの引数を構築
160
- */
161
- private buildArgs(
162
- toolType: "claude-code" | "codex-cli" | "custom",
163
- mode: "normal" | "continue" | "resume",
164
- ): string[] {
190
+ options: {
191
+ toolName?: string | null;
192
+ skipPermissions?: boolean;
193
+ bypassApprovals?: boolean;
194
+ extraArgs?: string[];
195
+ customToolId?: string | null;
196
+ },
197
+ ): Promise<ResolvedCommand> {
165
198
  if (toolType === "custom") {
166
- // カスタムツールの引数は別途config.jsonから取得
167
- return [];
199
+ const toolId = options.customToolId ?? options.toolName;
200
+ if (!toolId) {
201
+ throw new AIToolResolutionError(
202
+ "COMMAND_NOT_FOUND",
203
+ "Custom tool identifier is required to start a session.",
204
+ );
205
+ }
206
+
207
+ return resolveCustomToolCommand({
208
+ toolId,
209
+ mode,
210
+ ...(options.skipPermissions !== undefined
211
+ ? { skipPermissions: options.skipPermissions }
212
+ : {}),
213
+ ...(options.extraArgs ? { extraArgs: options.extraArgs } : {}),
214
+ });
168
215
  }
169
216
 
170
217
  if (toolType === "codex-cli") {
171
- if (mode === "continue") {
172
- return ["--continue"];
218
+ const codexOptions: {
219
+ mode: "normal" | "continue" | "resume";
220
+ bypassApprovals?: boolean;
221
+ extraArgs?: string[];
222
+ } = { mode };
223
+
224
+ if (options.bypassApprovals !== undefined) {
225
+ codexOptions.bypassApprovals = options.bypassApprovals;
173
226
  }
174
- if (mode === "resume") {
175
- return ["--resume"];
227
+ if (options.extraArgs && options.extraArgs.length > 0) {
228
+ codexOptions.extraArgs = options.extraArgs;
176
229
  }
177
- return [];
230
+
231
+ return resolveCodexCommand(codexOptions);
178
232
  }
179
233
 
180
- // claude-code
181
- if (mode === "continue") {
182
- return ["--continue"];
234
+ const claudeOptions: {
235
+ mode: "normal" | "continue" | "resume";
236
+ skipPermissions?: boolean;
237
+ extraArgs?: string[];
238
+ } = { mode };
239
+
240
+ if (options.skipPermissions !== undefined) {
241
+ claudeOptions.skipPermissions = options.skipPermissions;
183
242
  }
184
- if (mode === "resume") {
185
- return ["--resume"];
243
+ if (options.extraArgs && options.extraArgs.length > 0) {
244
+ claudeOptions.extraArgs = options.extraArgs;
186
245
  }
187
- return [];
246
+
247
+ return resolveClaudeCommand(claudeOptions);
248
+ }
249
+
250
+ private async loadSharedEnv(): Promise<Record<string, string>> {
251
+ const config = await loadToolsConfig();
252
+ return { ...(config.env ?? {}) };
253
+ }
254
+ }
255
+
256
+ function isRootUser(): boolean {
257
+ try {
258
+ return typeof process.getuid === "function" && process.getuid() === 0;
259
+ } catch {
260
+ return false;
188
261
  }
189
262
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { PTYManager } from "../pty/manager.js";
8
+ import { AIToolResolutionError } from "../../../services/aiToolResolver.js";
8
9
  import type {
9
10
  ApiResponse,
10
11
  AIToolSession,
@@ -28,7 +29,7 @@ export async function registerSessionRoutes(
28
29
  try {
29
30
  const sessions = ptyManager.list();
30
31
  return { success: true, data: sessions };
31
- } catch (error) {
32
+ } catch (error: unknown) {
32
33
  const errorMsg = error instanceof Error ? error.message : String(error);
33
34
  reply.code(500);
34
35
  return {
@@ -46,13 +47,55 @@ export async function registerSessionRoutes(
46
47
  Reply: ApiResponse<AIToolSession>;
47
48
  }>("/api/sessions", async (request, reply) => {
48
49
  try {
49
- const { toolType, toolName, mode, worktreePath } = request.body;
50
+ const {
51
+ toolType,
52
+ toolName,
53
+ mode,
54
+ worktreePath,
55
+ skipPermissions,
56
+ bypassApprovals,
57
+ extraArgs,
58
+ customToolId,
59
+ } = request.body;
60
+
61
+ const spawnOptions: {
62
+ toolName?: string | null;
63
+ skipPermissions?: boolean;
64
+ bypassApprovals?: boolean;
65
+ extraArgs?: string[];
66
+ customToolId?: string | null;
67
+ } = {};
68
+
69
+ if (typeof toolName !== "undefined") {
70
+ spawnOptions.toolName = toolName;
71
+ }
72
+ if (typeof skipPermissions !== "undefined") {
73
+ spawnOptions.skipPermissions = skipPermissions;
74
+ }
75
+ if (typeof bypassApprovals !== "undefined") {
76
+ spawnOptions.bypassApprovals = bypassApprovals;
77
+ }
78
+ if (Array.isArray(extraArgs) && extraArgs.length > 0) {
79
+ spawnOptions.extraArgs = extraArgs;
80
+ }
81
+ if (typeof customToolId !== "undefined") {
82
+ spawnOptions.customToolId = customToolId;
83
+ }
50
84
 
51
- const { session } = ptyManager.spawn(
85
+ if (toolType === "custom" && !toolName && !customToolId) {
86
+ reply.code(400);
87
+ return {
88
+ success: false,
89
+ error: "Custom tool requires toolName or customToolId",
90
+ details: null,
91
+ };
92
+ }
93
+
94
+ const { session } = await ptyManager.spawn(
52
95
  toolType,
53
96
  worktreePath,
54
97
  mode,
55
- toolName,
98
+ spawnOptions,
56
99
  );
57
100
 
58
101
  // 履歴を永続化(best-effort)
@@ -95,7 +138,16 @@ export async function registerSessionRoutes(
95
138
 
96
139
  reply.code(201);
97
140
  return { success: true, data: session };
98
- } catch (error) {
141
+ } catch (error: unknown) {
142
+ if (error instanceof AIToolResolutionError) {
143
+ reply.code(400);
144
+ return {
145
+ success: false,
146
+ error: error.message,
147
+ details: error.hints?.join("\n") ?? null,
148
+ };
149
+ }
150
+
99
151
  const errorMsg = error instanceof Error ? error.message : String(error);
100
152
  reply.code(500);
101
153
  return {
@@ -125,7 +177,7 @@ export async function registerSessionRoutes(
125
177
  }
126
178
 
127
179
  return { success: true, data: instance.session };
128
- } catch (error) {
180
+ } catch (error: unknown) {
129
181
  const errorMsg = error instanceof Error ? error.message : String(error);
130
182
  reply.code(500);
131
183
  return {
@@ -157,7 +209,7 @@ export async function registerSessionRoutes(
157
209
  }
158
210
 
159
211
  return { success: true };
160
- } catch (error) {
212
+ } catch (error: unknown) {
161
213
  const errorMsg = error instanceof Error ? error.message : String(error);
162
214
  reply.code(500);
163
215
  return {
@@ -64,13 +64,13 @@ export async function registerWorktreeRoutes(
64
64
  }
65
65
  });
66
66
 
67
- // DELETE /api/worktrees - Worktreeを削除
67
+ // DELETE /api/worktrees/delete - Worktreeを削除
68
68
  fastify.delete<{
69
- Querystring: { path: string };
69
+ Querystring: { path: string; force?: boolean };
70
70
  Reply:
71
71
  | { success: true }
72
72
  | { success: false; error: string; details?: string | null };
73
- }>("/api/worktrees", async (request, reply) => {
73
+ }>("/api/worktrees/delete", async (request, reply) => {
74
74
  try {
75
75
  const { path } = request.query;
76
76
 
@@ -30,6 +30,8 @@ export async function listWorktrees(): Promise<Worktree[]> {
30
30
  isProtected: isProtectedBranchName(wt.branch),
31
31
  createdAt: null, // git worktreeからは取得不可
32
32
  lastAccessedAt: null, // git worktreeからは取得不可
33
+ divergence: null,
34
+ prInfo: null,
33
35
  }));
34
36
  }
35
37
 
@@ -50,16 +52,22 @@ export async function createNewWorktree(
50
52
  branchName: string,
51
53
  createBranch: boolean,
52
54
  ): Promise<Worktree> {
53
- const { getRepositoryRoot, getCurrentBranch } = await import(
54
- "../../../git.js"
55
- );
55
+ // 保護ブランチのチェック
56
+ if (isProtectedBranchName(branchName)) {
57
+ throw new Error(
58
+ `Cannot create worktree for protected branch: ${branchName}. Protected branches (main, develop, master) must remain in the main repository.`,
59
+ );
60
+ }
61
+
62
+ const { getRepositoryRoot, getCurrentBranch } =
63
+ await import("../../../git.js");
56
64
 
57
65
  const [repoRoot, currentBranch] = await Promise.all([
58
66
  getRepositoryRoot(),
59
67
  getCurrentBranch(),
60
68
  ]);
61
69
 
62
- const worktreePath = await generateWorktreePath(branchName, repoRoot);
70
+ const worktreePath = await generateWorktreePath(repoRoot, branchName);
63
71
 
64
72
  await createWorktreeCore({
65
73
  branchName,
@@ -0,0 +1,120 @@
1
+ import { execa } from "execa";
2
+ import { createLogger } from "../../logging/logger.js";
3
+
4
+ /**
5
+ * URL を開く関数の型定義
6
+ * @param url - 開くURL
7
+ */
8
+ export type OpenUrlFn = (url: string) => Promise<void> | void;
9
+
10
+ const TRAY_ICON_BASE64 =
11
+ "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAJ1BMVEUAAAAAvNQAvNQAvNQAvNQAvNQAvNQAvNQAvNQAvNQAvNQAvNT////J1ubyAAAAC3RSTlMAJYTcgyQJnJ3U3WXfUogAAAABYktHRAyBs1FjAAAAB3RJTUUH6QwMCRccbOpRBQAAAFdJREFUCNdjYEAARuXNriCarXr37t1tQEYmkN69M4GBoRvE2F3AwAqmd29kYIEwNjEwQxibGbghjN0MXDARJpgaqK6tDAzVUHMQJoPtKgPZyuq8S5WBAQBeRj51tvdhawAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyNS0xMi0xMlQwOToyMzoyOCswMDowMBPEA5UAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjUtMTItMTJUMDk6MjM6MjgrMDA6MDBimbspAAAAAElFTkSuQmCC";
12
+
13
+ let trayInitAttempted = false;
14
+ type TrayHandle = { dispose?: () => void; kill?: () => void };
15
+ let trayInstance: TrayHandle | null = null;
16
+ let trayInitPromise: Promise<TrayHandle> | null = null;
17
+
18
+ function shouldEnableTray(
19
+ platform: NodeJS.Platform = process.platform,
20
+ ): boolean {
21
+ // NOTE: `trayicon` is a win32-only dependency.
22
+ if (platform !== "win32") return false;
23
+ if (process.env.GWT_DISABLE_TRAY?.toLowerCase() === "true") return false;
24
+ if (process.env.GWT_DISABLE_TRAY === "1") return false;
25
+ if (process.env.CI) return false;
26
+ return true;
27
+ }
28
+
29
+ /**
30
+ * デフォルトブラウザでURLを開く
31
+ * @param url - 開くURL
32
+ */
33
+ export async function openUrl(url: string): Promise<void> {
34
+ const platform = process.platform;
35
+ try {
36
+ if (platform === "win32") {
37
+ await execa("explorer.exe", [url], { windowsHide: true });
38
+ return;
39
+ }
40
+ if (platform === "darwin") {
41
+ await execa("open", [url]);
42
+ return;
43
+ }
44
+ await execa("xdg-open", [url], { stdio: "ignore" });
45
+ } catch {
46
+ // Ignore errors to avoid disrupting CLI/Web UI
47
+ }
48
+ }
49
+
50
+ /**
51
+ * システムトレイアイコンを初期化
52
+ * @param url - Web UI のURL(ダブルクリック時に開く)
53
+ * @param opts - オプション設定
54
+ * @param opts.openUrl - URL を開くカスタム関数(テスト用)
55
+ */
56
+ export async function startSystemTray(
57
+ url: string,
58
+ opts?: { openUrl?: OpenUrlFn; platform?: NodeJS.Platform },
59
+ ): Promise<void> {
60
+ if (trayInitAttempted || !shouldEnableTray(opts?.platform)) return;
61
+ trayInitAttempted = true;
62
+
63
+ const logger = createLogger({ category: "tray" });
64
+
65
+ try {
66
+ const mod = (await import("trayicon")) as Record<string, unknown> & {
67
+ default?: Record<string, unknown>;
68
+ };
69
+ const create = mod.create ?? mod.default?.create;
70
+ if (typeof create !== "function") {
71
+ throw new Error("trayicon.create not available");
72
+ }
73
+
74
+ const icon = Buffer.from(TRAY_ICON_BASE64, "base64");
75
+ const open = opts?.openUrl ?? openUrl;
76
+
77
+ const initPromise = Promise.resolve(
78
+ create({
79
+ icon,
80
+ title: "gwt Web UI",
81
+ tooltip: "Double-click to open Web UI",
82
+ action: async () => {
83
+ await open(url);
84
+ },
85
+ }) as TrayHandle,
86
+ );
87
+ trayInitPromise = initPromise;
88
+
89
+ void initPromise
90
+ .then((tray) => {
91
+ if (trayInitPromise !== initPromise) {
92
+ tray.dispose?.();
93
+ tray.kill?.();
94
+ return;
95
+ }
96
+ trayInstance = tray;
97
+ })
98
+ .catch((err) => {
99
+ if (trayInitPromise !== initPromise) return;
100
+ logger.warn({ err }, "System tray failed to initialize");
101
+ });
102
+ } catch (err) {
103
+ logger.warn({ err }, "System tray failed to initialize");
104
+ }
105
+ }
106
+
107
+ /**
108
+ * システムトレイアイコンを破棄
109
+ */
110
+ export function disposeSystemTray(): void {
111
+ trayInitPromise = null;
112
+
113
+ const instance = trayInstance;
114
+ trayInstance = null;
115
+
116
+ instance?.dispose?.();
117
+ instance?.kill?.();
118
+
119
+ trayInitAttempted = false;
120
+ }