@akiojin/gwt 2.13.0 → 2.14.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 +33 -0
  2. package/README.md +31 -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 +14 -2
  14. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  15. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +1 -1
  16. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +3 -3
  17. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +1 -1
  18. package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
  19. package/dist/cli/ui/utils/continueSession.js +1 -1
  20. package/dist/cli/ui/utils/continueSession.js.map +1 -1
  21. package/dist/client/assets/index-DPWWHorC.js +72 -0
  22. package/dist/client/assets/index-DsDNCy5f.css +1 -0
  23. package/dist/client/index.html +2 -2
  24. package/dist/codex.d.ts.map +1 -1
  25. package/dist/codex.js +21 -11
  26. package/dist/codex.js.map +1 -1
  27. package/dist/config/builtin-tools.d.ts.map +1 -1
  28. package/dist/config/builtin-tools.js +3 -2
  29. package/dist/config/builtin-tools.js.map +1 -1
  30. package/dist/config/shared-env.d.ts +41 -0
  31. package/dist/config/shared-env.d.ts.map +1 -0
  32. package/dist/config/shared-env.js +114 -0
  33. package/dist/config/shared-env.js.map +1 -0
  34. package/dist/gemini.d.ts.map +1 -1
  35. package/dist/gemini.js +20 -17
  36. package/dist/gemini.js.map +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +49 -7
  39. package/dist/index.js.map +1 -1
  40. package/dist/logging/logger.d.ts.map +1 -1
  41. package/dist/logging/logger.js +4 -1
  42. package/dist/logging/logger.js.map +1 -1
  43. package/dist/qwen.d.ts.map +1 -1
  44. package/dist/qwen.js +15 -11
  45. package/dist/qwen.js.map +1 -1
  46. package/dist/services/aiToolResolver.d.ts +41 -0
  47. package/dist/services/aiToolResolver.d.ts.map +1 -0
  48. package/dist/services/aiToolResolver.js +194 -0
  49. package/dist/services/aiToolResolver.js.map +1 -0
  50. package/dist/services/customToolResolver.d.ts +10 -0
  51. package/dist/services/customToolResolver.d.ts.map +1 -0
  52. package/dist/services/customToolResolver.js +71 -0
  53. package/dist/services/customToolResolver.js.map +1 -0
  54. package/dist/shared/aiToolConstants.d.ts +9 -0
  55. package/dist/shared/aiToolConstants.d.ts.map +1 -0
  56. package/dist/shared/aiToolConstants.js +29 -0
  57. package/dist/shared/aiToolConstants.js.map +1 -0
  58. package/dist/types/tools.d.ts +12 -0
  59. package/dist/types/tools.d.ts.map +1 -1
  60. package/dist/utils/prompt.d.ts.map +1 -1
  61. package/dist/utils/prompt.js.map +1 -1
  62. package/dist/utils/session.d.ts.map +1 -1
  63. package/dist/utils/session.js +15 -6
  64. package/dist/utils/session.js.map +1 -1
  65. package/dist/utils/terminal.d.ts +12 -3
  66. package/dist/utils/terminal.d.ts.map +1 -1
  67. package/dist/utils/terminal.js +5 -34
  68. package/dist/utils/terminal.js.map +1 -1
  69. package/dist/utils/webui.d.ts +8 -0
  70. package/dist/utils/webui.d.ts.map +1 -0
  71. package/dist/utils/webui.js +35 -0
  72. package/dist/utils/webui.js.map +1 -0
  73. package/dist/web/client/src/components/AIToolLaunchModal.d.ts +9 -0
  74. package/dist/web/client/src/components/AIToolLaunchModal.d.ts.map +1 -0
  75. package/dist/web/client/src/components/AIToolLaunchModal.js +363 -0
  76. package/dist/web/client/src/components/AIToolLaunchModal.js.map +1 -0
  77. package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
  78. package/dist/web/client/src/components/BranchGraph.js +46 -49
  79. package/dist/web/client/src/components/BranchGraph.js.map +1 -1
  80. package/dist/web/client/src/components/CustomToolForm.d.ts +23 -0
  81. package/dist/web/client/src/components/CustomToolForm.d.ts.map +1 -0
  82. package/dist/web/client/src/components/CustomToolForm.js +209 -0
  83. package/dist/web/client/src/components/CustomToolForm.js.map +1 -0
  84. package/dist/web/client/src/components/CustomToolList.d.ts +10 -0
  85. package/dist/web/client/src/components/CustomToolList.d.ts.map +1 -0
  86. package/dist/web/client/src/components/CustomToolList.js +57 -0
  87. package/dist/web/client/src/components/CustomToolList.js.map +1 -0
  88. package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
  89. package/dist/web/client/src/components/EnvEditor.js +33 -26
  90. package/dist/web/client/src/components/EnvEditor.js.map +1 -1
  91. package/dist/web/client/src/components/EnvironmentEditor.d.ts +17 -0
  92. package/dist/web/client/src/components/EnvironmentEditor.d.ts.map +1 -0
  93. package/dist/web/client/src/components/EnvironmentEditor.js +22 -0
  94. package/dist/web/client/src/components/EnvironmentEditor.js.map +1 -0
  95. package/dist/web/client/src/components/Terminal.d.ts.map +1 -1
  96. package/dist/web/client/src/components/Terminal.js +10 -3
  97. package/dist/web/client/src/components/Terminal.js.map +1 -1
  98. package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts +10 -0
  99. package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts.map +1 -0
  100. package/dist/web/client/src/components/branch-detail/BranchInfoCards.js +104 -0
  101. package/dist/web/client/src/components/branch-detail/BranchInfoCards.js.map +1 -0
  102. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts +22 -0
  103. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts.map +1 -0
  104. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js +79 -0
  105. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js.map +1 -0
  106. package/dist/web/client/src/components/branch-detail/TerminalPanel.d.ts +11 -0
  107. package/dist/web/client/src/components/branch-detail/TerminalPanel.d.ts.map +1 -0
  108. package/dist/web/client/src/components/branch-detail/TerminalPanel.js +32 -0
  109. package/dist/web/client/src/components/branch-detail/TerminalPanel.js.map +1 -0
  110. package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts +40 -0
  111. package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts.map +1 -0
  112. package/dist/web/client/src/components/branch-detail/ToolLauncher.js +147 -0
  113. package/dist/web/client/src/components/branch-detail/ToolLauncher.js.map +1 -0
  114. package/dist/web/client/src/components/branch-detail/index.d.ts +5 -0
  115. package/dist/web/client/src/components/branch-detail/index.d.ts.map +1 -0
  116. package/dist/web/client/src/components/branch-detail/index.js +5 -0
  117. package/dist/web/client/src/components/branch-detail/index.js.map +1 -0
  118. package/dist/web/client/src/components/common/BranchCard.d.ts +17 -0
  119. package/dist/web/client/src/components/common/BranchCard.d.ts.map +1 -0
  120. package/dist/web/client/src/components/common/BranchCard.js +36 -0
  121. package/dist/web/client/src/components/common/BranchCard.js.map +1 -0
  122. package/dist/web/client/src/components/common/MetricCard.d.ts +10 -0
  123. package/dist/web/client/src/components/common/MetricCard.d.ts.map +1 -0
  124. package/dist/web/client/src/components/common/MetricCard.js +10 -0
  125. package/dist/web/client/src/components/common/MetricCard.js.map +1 -0
  126. package/dist/web/client/src/components/common/PageHeader.d.ts +12 -0
  127. package/dist/web/client/src/components/common/PageHeader.d.ts.map +1 -0
  128. package/dist/web/client/src/components/common/PageHeader.js +14 -0
  129. package/dist/web/client/src/components/common/PageHeader.js.map +1 -0
  130. package/dist/web/client/src/components/common/SearchInput.d.ts +14 -0
  131. package/dist/web/client/src/components/common/SearchInput.d.ts.map +1 -0
  132. package/dist/web/client/src/components/common/SearchInput.js +15 -0
  133. package/dist/web/client/src/components/common/SearchInput.js.map +1 -0
  134. package/dist/web/client/src/components/common/StatusBadge.d.ts +10 -0
  135. package/dist/web/client/src/components/common/StatusBadge.d.ts.map +1 -0
  136. package/dist/web/client/src/components/common/StatusBadge.js +15 -0
  137. package/dist/web/client/src/components/common/StatusBadge.js.map +1 -0
  138. package/dist/web/client/src/components/common/index.d.ts +6 -0
  139. package/dist/web/client/src/components/common/index.d.ts.map +1 -0
  140. package/dist/web/client/src/components/common/index.js +6 -0
  141. package/dist/web/client/src/components/common/index.js.map +1 -0
  142. package/dist/web/client/src/components/ui/alert.d.ts +9 -0
  143. package/dist/web/client/src/components/ui/alert.d.ts.map +1 -0
  144. package/dist/web/client/src/components/ui/alert.js +25 -0
  145. package/dist/web/client/src/components/ui/alert.js.map +1 -0
  146. package/dist/web/client/src/components/ui/badge.d.ts +10 -0
  147. package/dist/web/client/src/components/ui/badge.d.ts.map +1 -0
  148. package/dist/web/client/src/components/ui/badge.js +25 -0
  149. package/dist/web/client/src/components/ui/badge.js.map +1 -0
  150. package/dist/web/client/src/components/ui/button.d.ts +12 -0
  151. package/dist/web/client/src/components/ui/button.d.ts.map +1 -0
  152. package/dist/web/client/src/components/ui/button.js +33 -0
  153. package/dist/web/client/src/components/ui/button.js.map +1 -0
  154. package/dist/web/client/src/components/ui/card.d.ts +9 -0
  155. package/dist/web/client/src/components/ui/card.d.ts.map +1 -0
  156. package/dist/web/client/src/components/ui/card.js +16 -0
  157. package/dist/web/client/src/components/ui/card.js.map +1 -0
  158. package/dist/web/client/src/components/ui/index.d.ts +8 -0
  159. package/dist/web/client/src/components/ui/index.d.ts.map +1 -0
  160. package/dist/web/client/src/components/ui/index.js +8 -0
  161. package/dist/web/client/src/components/ui/index.js.map +1 -0
  162. package/dist/web/client/src/components/ui/input.d.ts +4 -0
  163. package/dist/web/client/src/components/ui/input.d.ts.map +1 -0
  164. package/dist/web/client/src/components/ui/input.js +8 -0
  165. package/dist/web/client/src/components/ui/input.js.map +1 -0
  166. package/dist/web/client/src/components/ui/select.d.ts +14 -0
  167. package/dist/web/client/src/components/ui/select.d.ts.map +1 -0
  168. package/dist/web/client/src/components/ui/select.js +39 -0
  169. package/dist/web/client/src/components/ui/select.js.map +1 -0
  170. package/dist/web/client/src/components/ui/table.d.ts +11 -0
  171. package/dist/web/client/src/components/ui/table.d.ts.map +1 -0
  172. package/dist/web/client/src/components/ui/table.js +21 -0
  173. package/dist/web/client/src/components/ui/table.js.map +1 -0
  174. package/dist/web/client/src/hooks/useSessions.d.ts.map +1 -1
  175. package/dist/web/client/src/hooks/useSessions.js +6 -1
  176. package/dist/web/client/src/hooks/useSessions.js.map +1 -1
  177. package/dist/web/client/src/lib/utils.d.ts +7 -0
  178. package/dist/web/client/src/lib/utils.d.ts.map +1 -0
  179. package/dist/web/client/src/lib/utils.js +10 -0
  180. package/dist/web/client/src/lib/utils.js.map +1 -0
  181. package/dist/web/client/src/lib/websocket.d.ts +7 -0
  182. package/dist/web/client/src/lib/websocket.d.ts.map +1 -1
  183. package/dist/web/client/src/lib/websocket.js +44 -0
  184. package/dist/web/client/src/lib/websocket.js.map +1 -1
  185. package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
  186. package/dist/web/client/src/pages/BranchDetailPage.js +113 -361
  187. package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
  188. package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
  189. package/dist/web/client/src/pages/BranchListPage.js +89 -127
  190. package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
  191. package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
  192. package/dist/web/client/src/pages/ConfigManagementPage.js +46 -41
  193. package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
  194. package/dist/web/client/src/pages/ConfigPage.d.ts +3 -0
  195. package/dist/web/client/src/pages/ConfigPage.d.ts.map +1 -0
  196. package/dist/web/client/src/pages/ConfigPage.js +216 -0
  197. package/dist/web/client/src/pages/ConfigPage.js.map +1 -0
  198. package/dist/web/client/vite.config.d.ts.map +1 -1
  199. package/dist/web/client/vite.config.js +8 -1
  200. package/dist/web/client/vite.config.js.map +1 -1
  201. package/dist/web/server/index.d.ts +24 -2
  202. package/dist/web/server/index.d.ts.map +1 -1
  203. package/dist/web/server/index.js +46 -15
  204. package/dist/web/server/index.js.map +1 -1
  205. package/dist/web/server/pty/manager.d.ts +12 -10
  206. package/dist/web/server/pty/manager.d.ts.map +1 -1
  207. package/dist/web/server/pty/manager.js +76 -43
  208. package/dist/web/server/pty/manager.js.map +1 -1
  209. package/dist/web/server/routes/sessions.d.ts.map +1 -1
  210. package/dist/web/server/routes/sessions.js +35 -2
  211. package/dist/web/server/routes/sessions.js.map +1 -1
  212. package/dist/web/server/routes/worktrees.js +2 -2
  213. package/dist/web/server/routes/worktrees.js.map +1 -1
  214. package/dist/web/server/services/worktrees.d.ts.map +1 -1
  215. package/dist/web/server/services/worktrees.js +7 -1
  216. package/dist/web/server/services/worktrees.js.map +1 -1
  217. package/dist/web/server/tray.d.ts +24 -0
  218. package/dist/web/server/tray.d.ts.map +1 -0
  219. package/dist/web/server/tray.js +79 -0
  220. package/dist/web/server/tray.js.map +1 -0
  221. package/dist/web/server/websocket/handler.d.ts +18 -2
  222. package/dist/web/server/websocket/handler.d.ts.map +1 -1
  223. package/dist/web/server/websocket/handler.js +82 -9
  224. package/dist/web/server/websocket/handler.js.map +1 -1
  225. package/package.json +15 -2
  226. package/src/claude.ts +26 -15
  227. package/src/cli/ui/__tests__/components/common/Select.test.tsx +11 -0
  228. package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +17 -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 +43 -23
  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 +54 -7
  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 +222 -694
  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 +93 -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
@@ -25,13 +25,12 @@ const REASONING_LABELS: Record<string, string> = {
25
25
  };
26
26
 
27
27
  const formatReasoning = (level?: string | null) =>
28
- level ? REASONING_LABELS[level] ?? level : "Default";
28
+ level ? (REASONING_LABELS[level] ?? level) : "Default";
29
29
 
30
30
  const formatSkip = (skip?: boolean | null) =>
31
31
  skip === true ? "Yes" : skip === false ? "No" : "No";
32
32
 
33
- const supportsReasoning = (toolId?: string | null) =>
34
- toolId === "codex-cli";
33
+ const supportsReasoning = (toolId?: string | null) => toolId === "codex-cli";
35
34
 
36
35
  const describe = (opt: BranchQuickStartOption, includeSessionId = true) => {
37
36
  const parts = [`Model: ${opt.model ?? "default"}`];
@@ -103,45 +102,45 @@ export function BranchQuickStartScreen({
103
102
  const items: QuickStartItem[] = previousOptions.length
104
103
  ? (() => {
105
104
  const order = ["Claude", "Codex", "Gemini", "Qwen", "Other"];
106
- const sorted = [...previousOptions].sort((a, b) => {
107
- const ca = resolveCategory(a.toolId).label;
108
- const cb = resolveCategory(b.toolId).label;
109
- return order.indexOf(ca) - order.indexOf(cb);
110
- });
105
+ const sorted = [...previousOptions].sort((a, b) => {
106
+ const ca = resolveCategory(a.toolId).label;
107
+ const cb = resolveCategory(b.toolId).label;
108
+ return order.indexOf(ca) - order.indexOf(cb);
109
+ });
111
110
 
112
- const flat: QuickStartItem[] = [];
111
+ const flat: QuickStartItem[] = [];
113
112
  sorted.forEach((opt, idx) => {
114
113
  const cat = resolveCategory(opt.toolId);
115
114
  const prevCat =
116
115
  idx > 0 ? resolveCategory(sorted[idx - 1]?.toolId).label : null;
117
116
  const isNewCategory = prevCat !== cat.label;
118
117
 
119
- flat.push(
120
- {
121
- label: "Resume",
122
- value: `reuse-continue:${opt.toolId ?? "unknown"}:${idx}`,
123
- action: "reuse-continue",
124
- toolId: opt.toolId ?? null,
125
- description: describe(opt, true),
126
- groupStart: isNewCategory && flat.length > 0,
127
- category: cat.label,
128
- categoryColor: cat.color,
129
- },
130
- {
131
- label: "New",
132
- value: `reuse-new:${opt.toolId ?? "unknown"}:${idx}`,
133
- action: "reuse-new",
134
- toolId: opt.toolId ?? null,
135
- description: describe(opt, false),
136
- groupStart: false,
137
- category: cat.label,
138
- categoryColor: cat.color,
139
- },
140
- );
141
- });
142
-
143
- return flat;
144
- })()
118
+ flat.push(
119
+ {
120
+ label: "Resume",
121
+ value: `reuse-continue:${opt.toolId ?? "unknown"}:${idx}`,
122
+ action: "reuse-continue",
123
+ toolId: opt.toolId ?? null,
124
+ description: describe(opt, true),
125
+ groupStart: isNewCategory && flat.length > 0,
126
+ category: cat.label,
127
+ categoryColor: cat.color,
128
+ },
129
+ {
130
+ label: "New",
131
+ value: `reuse-new:${opt.toolId ?? "unknown"}:${idx}`,
132
+ action: "reuse-new",
133
+ toolId: opt.toolId ?? null,
134
+ description: describe(opt, false),
135
+ groupStart: false,
136
+ category: cat.label,
137
+ categoryColor: cat.color,
138
+ },
139
+ );
140
+ });
141
+
142
+ return flat;
143
+ })()
145
144
  : [
146
145
  {
147
146
  label: "Resume with previous settings",
@@ -180,11 +179,7 @@ export function BranchQuickStartScreen({
180
179
 
181
180
  return (
182
181
  <Box flexDirection="column" height={containerHeight}>
183
- <Header
184
- title="Quick Start"
185
- titleColor="cyan"
186
- version={version}
187
- />
182
+ <Header title="Quick Start" titleColor="cyan" version={version} />
188
183
 
189
184
  <Box flexDirection="column" flexGrow={1} marginTop={1}>
190
185
  <Box marginBottom={1} flexDirection="column">
@@ -197,6 +192,7 @@ export function BranchQuickStartScreen({
197
192
  </Box>
198
193
  <Select
199
194
  items={items}
195
+ disabled={loading}
200
196
  onSelect={(item: QuickStartItem) => {
201
197
  if (item.disabled) return;
202
198
  onSelect(item.action, item.toolId ?? null);
@@ -204,13 +200,12 @@ export function BranchQuickStartScreen({
204
200
  renderItem={(item: QuickStartItem, isSelected) => (
205
201
  <Box
206
202
  flexDirection="column"
207
- marginTop={item.groupStart ? 1 : item.category === "Other" ? 1 : 0}
203
+ marginTop={
204
+ item.groupStart ? 1 : item.category === "Other" ? 1 : 0
205
+ }
208
206
  >
209
207
  <Text>
210
- <Text
211
- color={item.categoryColor}
212
- inverse={isSelected}
213
- >
208
+ <Text color={item.categoryColor} inverse={isSelected}>
214
209
  {`[${item.category}] `}
215
210
  </Text>
216
211
  <Text inverse={isSelected}>
@@ -219,7 +214,7 @@ export function BranchQuickStartScreen({
219
214
  </Text>
220
215
  </Text>
221
216
  {item.description && (
222
- <Text color="gray"> {item.description}</Text>
217
+ <Text color="gray"> {item.description}</Text>
223
218
  )}
224
219
  </Box>
225
220
  )}
@@ -17,13 +17,7 @@ export interface ContinueSessionContext {
17
17
  export async function resolveContinueSessionId(
18
18
  context: ContinueSessionContext,
19
19
  ): Promise<string | null> {
20
- const {
21
- history,
22
- sessionData,
23
- branch,
24
- toolId,
25
- repoRoot,
26
- } = context;
20
+ const { history, sessionData, branch, toolId, repoRoot: _repoRoot } = context;
27
21
 
28
22
  // 1) 履歴から最新マッチを探す(末尾から遡る)
29
23
  for (let i = history.length - 1; i >= 0; i -= 1) {
package/src/codex.ts CHANGED
@@ -3,10 +3,7 @@ import chalk from "chalk";
3
3
  import { platform } from "os";
4
4
  import { existsSync } from "fs";
5
5
  import { createChildStdio, getTerminalStreams } from "./utils/terminal.js";
6
- import {
7
- findLatestCodexSession,
8
- waitForCodexSessionId,
9
- } from "./utils/session.js";
6
+ import { findLatestCodexSession } from "./utils/session.js";
10
7
 
11
8
  const CODEX_CLI_PACKAGE = "@openai/codex@latest";
12
9
 
@@ -85,11 +82,9 @@ export async function launchCodexCLI(
85
82
  options.sessionId && options.sessionId.trim().length > 0
86
83
  ? options.sessionId.trim()
87
84
  : null;
88
-
89
- // Start polling session files immediately to catch the session created right after launch.
90
- const sessionProbe = waitForCodexSessionId({ startedAt, cwd: worktreePath }).catch(
91
- () => null,
92
- );
85
+ const usedExplicitSessionId =
86
+ Boolean(resumeSessionId) &&
87
+ (options.mode === "continue" || options.mode === "resume");
93
88
 
94
89
  switch (options.mode) {
95
90
  case "continue":
@@ -141,15 +136,23 @@ export async function launchCodexCLI(
141
136
 
142
137
  const childStdio = createChildStdio();
143
138
 
144
- const env = { ...process.env, ...(options.envOverrides ?? {}) };
139
+ const env = Object.fromEntries(
140
+ Object.entries({
141
+ ...process.env,
142
+ ...(options.envOverrides ?? {}),
143
+ }).filter(
144
+ (entry): entry is [string, string] => typeof entry[1] === "string",
145
+ ),
146
+ );
145
147
 
146
148
  try {
147
- const execChild = async (child: any) => {
149
+ const execChild = async (child: Promise<unknown>) => {
148
150
  try {
149
151
  await child;
150
- } catch (execError: any) {
152
+ } catch (execError: unknown) {
151
153
  // Treat SIGINT/SIGTERM as normal exit (user pressed Ctrl+C)
152
- if (execError.signal === "SIGINT" || execError.signal === "SIGTERM") {
154
+ const signal = (execError as { signal?: unknown })?.signal;
155
+ if (signal === "SIGINT" || signal === "SIGTERM") {
153
156
  return;
154
157
  }
155
158
  throw execError;
@@ -163,7 +166,7 @@ export async function launchCodexCLI(
163
166
  stdout: childStdio.stdout,
164
167
  stderr: childStdio.stderr,
165
168
  env,
166
- } as any);
169
+ });
167
170
  await execChild(child);
168
171
  } finally {
169
172
  childStdio.cleanup();
@@ -181,10 +184,13 @@ export async function launchCodexCLI(
181
184
  windowMs: 10 * 60 * 1000,
182
185
  cwd: worktreePath,
183
186
  });
184
- // Priority: latest on disk > resumeSessionId
185
- capturedSessionId = latest?.id ?? resumeSessionId ?? null;
187
+ const detectedSessionId = latest?.id ?? null;
188
+ // When we explicitly resumed a specific session, keep that ID as the source of truth.
189
+ capturedSessionId = usedExplicitSessionId
190
+ ? resumeSessionId
191
+ : detectedSessionId;
186
192
  } catch {
187
- capturedSessionId = resumeSessionId ?? null;
193
+ capturedSessionId = usedExplicitSessionId ? resumeSessionId : null;
188
194
  }
189
195
 
190
196
  if (capturedSessionId) {
@@ -201,11 +207,13 @@ export async function launchCodexCLI(
201
207
  }
202
208
 
203
209
  return capturedSessionId ? { sessionId: capturedSessionId } : {};
204
- } catch (error: any) {
210
+ } catch (error: unknown) {
211
+ const err = error as NodeJS.ErrnoException;
212
+ const details = error instanceof Error ? error.message : String(error);
205
213
  const errorMessage =
206
- error.code === "ENOENT"
214
+ err.code === "ENOENT"
207
215
  ? "bunx command not found. Please ensure Bun is installed so Codex CLI can run via bunx."
208
- : `Failed to launch Codex CLI: ${error.message || "Unknown error"}`;
216
+ : `Failed to launch Codex CLI: ${details || "Unknown error"}`;
209
217
 
210
218
  if (platform() === "win32") {
211
219
  console.error(chalk.red("\n💡 Windows troubleshooting tips:"));
@@ -234,8 +242,9 @@ export async function isCodexAvailable(): Promise<boolean> {
234
242
  try {
235
243
  await execa("bunx", [CODEX_CLI_PACKAGE, "--help"]);
236
244
  return true;
237
- } catch (error: any) {
238
- if (error.code === "ENOENT") {
245
+ } catch (error: unknown) {
246
+ const err = error as NodeJS.ErrnoException;
247
+ if (err.code === "ENOENT") {
239
248
  console.error(chalk.yellow("\n⚠️ bunx command not found"));
240
249
  }
241
250
  return false;
@@ -5,6 +5,10 @@
5
5
  */
6
6
 
7
7
  import type { CustomAITool } from "../types/tools.js";
8
+ import {
9
+ CLAUDE_PERMISSION_SKIP_ARGS,
10
+ CODEX_DEFAULT_ARGS,
11
+ } from "../shared/aiToolConstants.js";
8
12
 
9
13
  /**
10
14
  * Claude Code のビルトイン定義
@@ -19,7 +23,7 @@ export const CLAUDE_CODE_TOOL: CustomAITool = {
19
23
  continue: ["-c"],
20
24
  resume: ["-r"],
21
25
  },
22
- permissionSkipArgs: ["--yes"],
26
+ permissionSkipArgs: Array.from(CLAUDE_PERMISSION_SKIP_ARGS),
23
27
  };
24
28
 
25
29
  /**
@@ -30,7 +34,7 @@ export const CODEX_CLI_TOOL: CustomAITool = {
30
34
  displayName: "Codex",
31
35
  type: "bunx",
32
36
  command: "@openai/codex@latest",
33
- defaultArgs: ["--auto-approve", "--verbose"],
37
+ defaultArgs: Array.from(CODEX_DEFAULT_ARGS),
34
38
  modeArgs: {
35
39
  normal: [],
36
40
  continue: ["resume", "--last"],
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Shared environment variable helpers.
3
+ *
4
+ * 管理画面やサーバー側の設定ロジックから共通で利用する
5
+ * 環境変数スキーマ/バリデーション/マージ処理を集約する。
6
+ */
7
+
8
+ const ENV_KEY_PATTERN = /^[A-Z0-9_]+$/;
9
+ const MAX_KEY_LENGTH = 100;
10
+ const MAX_VALUE_LENGTH = 500;
11
+
12
+ const DEFAULT_BOOTSTRAP_ENV_KEYS = [
13
+ "ANTHROPIC_API_KEY",
14
+ "ANTHROPIC_API_KEY_PATH",
15
+ "ANTHROPIC_API_BASE",
16
+ "OPENAI_API_KEY",
17
+ "OPENAI_BASE_URL",
18
+ "OPENAI_API_BASE",
19
+ "OPENAI_ORG_ID",
20
+ "OPENAI_PROJECT",
21
+ "OPENAI_API_TYPE",
22
+ "OPENAI_API_VERSION",
23
+ "GITHUB_TOKEN",
24
+ "GH_TOKEN",
25
+ "GITLAB_TOKEN",
26
+ "AZURE_OPENAI_API_KEY",
27
+ "AZURE_OPENAI_ENDPOINT",
28
+ ] as const;
29
+
30
+ export type EnvironmentRecord = Record<string, string>;
31
+
32
+ /**
33
+ * 環境変数キーが有効かどうかを判定。
34
+ */
35
+ export function isValidEnvKey(key: string): boolean {
36
+ if (!key || typeof key !== "string") {
37
+ return false;
38
+ }
39
+ if (key.length === 0 || key.length > MAX_KEY_LENGTH) {
40
+ return false;
41
+ }
42
+ return ENV_KEY_PATTERN.test(key);
43
+ }
44
+
45
+ /**
46
+ * 許容されるキー長を取得。
47
+ */
48
+ export function getEnvConstraints(): {
49
+ maxKeyLength: number;
50
+ maxValueLength: number;
51
+ } {
52
+ return {
53
+ maxKeyLength: MAX_KEY_LENGTH,
54
+ maxValueLength: MAX_VALUE_LENGTH,
55
+ };
56
+ }
57
+
58
+ /**
59
+ * 任意のレコードをサニタイズして string -> string のみに整形する。
60
+ */
61
+ export function sanitizeEnvRecord(
62
+ record?: Record<string, unknown>,
63
+ ): EnvironmentRecord {
64
+ if (!record || typeof record !== "object") {
65
+ return {};
66
+ }
67
+ const result: EnvironmentRecord = {};
68
+ for (const [key, value] of Object.entries(record)) {
69
+ if (value === undefined || value === null) {
70
+ continue;
71
+ }
72
+ result[key] = String(value);
73
+ }
74
+ return result;
75
+ }
76
+
77
+ /**
78
+ * envレコードを検証し、フォーマットが不正な場合は例外を投げる。
79
+ */
80
+ export function validateEnvRecord(record: EnvironmentRecord): void {
81
+ for (const [key, value] of Object.entries(record)) {
82
+ if (!isValidEnvKey(key)) {
83
+ throw new Error(
84
+ `Invalid environment variable key: "${key}". Use 1-${MAX_KEY_LENGTH} characters (A-Z, 0-9, underscore).`,
85
+ );
86
+ }
87
+ if (value.length === 0) {
88
+ throw new Error(`Environment variable "${key}" must not be empty.`);
89
+ }
90
+ if (value.length > MAX_VALUE_LENGTH) {
91
+ throw new Error(
92
+ `Environment variable "${key}" exceeds the maximum length (${MAX_VALUE_LENGTH}).`,
93
+ );
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * 起動時にOSから読み取って取り込みたい環境変数キー一覧を返す。
100
+ * CLAUDE_WORKTREE_BOOTSTRAP_ENV_KEYS=KEY1,KEY2 で上書き可能。
101
+ */
102
+ export function getBootstrapEnvKeys(): string[] {
103
+ const override = process.env.CLAUDE_WORKTREE_BOOTSTRAP_ENV_KEYS;
104
+ if (!override) {
105
+ return [...DEFAULT_BOOTSTRAP_ENV_KEYS];
106
+ }
107
+ return override
108
+ .split(",")
109
+ .map((entry) => entry.trim())
110
+ .filter(Boolean)
111
+ .map((entry) => entry.toUpperCase());
112
+ }
113
+
114
+ /**
115
+ * 現在のenvレコードへ、指定されたキーのみOS環境変数から補完する。
116
+ */
117
+ export function mergeWithBootstrapEnv(
118
+ current: EnvironmentRecord,
119
+ sourceEnv: NodeJS.ProcessEnv,
120
+ keys: string[] = getBootstrapEnvKeys(),
121
+ ): { merged: EnvironmentRecord; addedKeys: string[] } {
122
+ const merged: EnvironmentRecord = { ...current };
123
+ const addedKeys: string[] = [];
124
+
125
+ for (const key of keys) {
126
+ if (merged[key] !== undefined) {
127
+ continue; // 既に設定済み
128
+ }
129
+ const value = sourceEnv[key];
130
+ if (typeof value === "string" && value.length > 0) {
131
+ merged[key] = value;
132
+ addedKeys.push(key);
133
+ }
134
+ }
135
+
136
+ return { merged, addedKeys };
137
+ }
138
+
139
+ export { ENV_KEY_PATTERN };
package/src/gemini.ts CHANGED
@@ -50,6 +50,9 @@ export async function launchGeminiCLI(
50
50
  options.sessionId && options.sessionId.trim().length > 0
51
51
  ? options.sessionId.trim()
52
52
  : null;
53
+ const usedExplicitSessionId =
54
+ Boolean(resumeSessionId) &&
55
+ (options.mode === "continue" || options.mode === "resume");
53
56
 
54
57
  const buildArgs = (useResumeId: boolean) => {
55
58
  const a: string[] = [];
@@ -59,12 +62,6 @@ export async function launchGeminiCLI(
59
62
 
60
63
  switch (options.mode) {
61
64
  case "continue":
62
- if (useResumeId && resumeSessionId) {
63
- a.push("--resume", resumeSessionId);
64
- } else {
65
- a.push("--resume");
66
- }
67
- break;
68
65
  case "resume":
69
66
  if (useResumeId && resumeSessionId) {
70
67
  a.push("--resume", resumeSessionId);
@@ -123,10 +120,14 @@ export async function launchGeminiCLI(
123
120
  }
124
121
  terminal.exitRawMode();
125
122
 
126
- const baseEnv = {
127
- ...process.env,
128
- ...(options.envOverrides ?? {}),
129
- };
123
+ const baseEnv = Object.fromEntries(
124
+ Object.entries({
125
+ ...process.env,
126
+ ...(options.envOverrides ?? {}),
127
+ }).filter(
128
+ (entry): entry is [string, string] => typeof entry[1] === "string",
129
+ ),
130
+ );
130
131
 
131
132
  const childStdio = createChildStdio();
132
133
 
@@ -156,7 +157,9 @@ export async function launchGeminiCLI(
156
157
  }
157
158
  };
158
159
 
159
- const runGemini = async (runArgs: string[]): Promise<string | undefined> => {
160
+ const runGemini = async (
161
+ runArgs: string[],
162
+ ): Promise<string | undefined> => {
160
163
  // Capture stdout while passing through to terminal
161
164
  // Store chunks to extract session ID after process exits
162
165
  const outputChunks: string[] = [];
@@ -169,7 +172,7 @@ export async function launchGeminiCLI(
169
172
  stdout: "pipe",
170
173
  stderr: childStdio.stderr,
171
174
  env: baseEnv,
172
- } as any);
175
+ });
173
176
 
174
177
  // Pass stdout through to terminal while capturing
175
178
  child.stdout?.on("data", (chunk: Buffer) => {
@@ -203,6 +206,7 @@ export async function launchGeminiCLI(
203
206
  };
204
207
 
205
208
  let output: string | undefined;
209
+ let fellBackToLatest = false;
206
210
  try {
207
211
  // Try with explicit session ID first (if any), then fallback to --resume (latest) once
208
212
  try {
@@ -212,6 +216,7 @@ export async function launchGeminiCLI(
212
216
  (options.mode === "resume" || options.mode === "continue") &&
213
217
  resumeSessionId;
214
218
  if (shouldRetry) {
219
+ fellBackToLatest = true;
215
220
  console.log(
216
221
  chalk.yellow(
217
222
  ` ⚠️ Failed to resume session ${resumeSessionId}. Retrying with latest session...`,
@@ -229,17 +234,22 @@ export async function launchGeminiCLI(
229
234
  // Extract session ID from Gemini's exit summary output
230
235
  extractSessionId(output);
231
236
 
232
- // Fallback to file-based detection if stdout capture failed
237
+ const explicitResumeSucceeded = usedExplicitSessionId && !fellBackToLatest;
238
+
239
+ // If we explicitly resumed a specific session (and did not fall back), keep that ID.
240
+ if (explicitResumeSucceeded) {
241
+ capturedSessionId = resumeSessionId;
242
+ }
243
+
244
+ // Fallback to file-based detection if stdout capture failed (and we don't have an explicit-resume ID)
233
245
  if (!capturedSessionId) {
234
246
  try {
235
247
  capturedSessionId =
236
248
  (await findLatestGeminiSessionId(worktreePath, {
237
249
  cwd: worktreePath,
238
- })) ??
239
- resumeSessionId ??
240
- null;
250
+ })) ?? null;
241
251
  } catch {
242
- capturedSessionId = resumeSessionId ?? null;
252
+ capturedSessionId = null;
243
253
  }
244
254
  }
245
255
 
@@ -257,11 +267,12 @@ export async function launchGeminiCLI(
257
267
  }
258
268
 
259
269
  return capturedSessionId ? { sessionId: capturedSessionId } : {};
260
- } catch (error: any) {
270
+ } catch (error: unknown) {
261
271
  const hasLocalGemini = await isGeminiCommandAvailable();
262
272
  let errorMessage: string;
273
+ const err = error as NodeJS.ErrnoException;
263
274
 
264
- if (error.code === "ENOENT") {
275
+ if (err.code === "ENOENT") {
265
276
  if (hasLocalGemini) {
266
277
  errorMessage =
267
278
  "gemini command not found. Please ensure Gemini CLI is properly installed.";
@@ -270,7 +281,8 @@ export async function launchGeminiCLI(
270
281
  "bunx command not found. Please ensure Bun is installed so Gemini CLI can run via bunx.";
271
282
  }
272
283
  } else {
273
- errorMessage = `Failed to launch Gemini CLI: ${error.message || "Unknown error"}`;
284
+ const details = error instanceof Error ? error.message : String(error);
285
+ errorMessage = `Failed to launch Gemini CLI: ${details || "Unknown error"}`;
274
286
  }
275
287
 
276
288
  if (process.platform === "win32") {
@@ -331,8 +343,9 @@ export async function isGeminiCLIAvailable(): Promise<boolean> {
331
343
  try {
332
344
  await execa("bunx", [GEMINI_CLI_PACKAGE, "--version"], { shell: true });
333
345
  return true;
334
- } catch (error: any) {
335
- if (error.code === "ENOENT") {
346
+ } catch (error: unknown) {
347
+ const err = error as NodeJS.ErrnoException;
348
+ if (err.code === "ENOENT") {
336
349
  console.error(chalk.yellow("\n⚠️ bunx command not found"));
337
350
  console.error(
338
351
  chalk.gray(
package/src/index.ts CHANGED
@@ -33,6 +33,7 @@ import {
33
33
  getTerminalStreams,
34
34
  waitForUserAcknowledgement,
35
35
  } from "./utils/terminal.js";
36
+ import { resolveWebUiPort, isPortInUse } from "./utils/webui.js";
36
37
  import { createLogger } from "./logging/logger.js";
37
38
  import { getToolById, getSharedEnvironment } from "./config/tools.js";
38
39
  import { launchCustomAITool } from "./launcher.js";
@@ -552,12 +553,12 @@ export async function handleAIToolWorkflow(
552
553
  { skipHistory: true },
553
554
  );
554
555
 
555
- // Lookup saved session ID for continue/resume
556
+ // Lookup saved session ID for Continue (auto attach)
556
557
  let resumeSessionId: string | null =
557
558
  selectedSessionId && selectedSessionId.length > 0
558
559
  ? selectedSessionId
559
560
  : null;
560
- if (mode === "continue" || mode === "resume") {
561
+ if (mode === "continue") {
561
562
  const existingSession = await loadSession(repoRoot);
562
563
  const history = existingSession?.history ?? [];
563
564
 
@@ -700,14 +701,15 @@ export async function handleAIToolWorkflow(
700
701
 
701
702
  if (!finalSessionId && tool === "claude-code") {
702
703
  try {
703
- finalSessionId = (await findLatestClaudeSessionId(worktreePath)) ?? null;
704
+ finalSessionId =
705
+ (await findLatestClaudeSessionId(worktreePath)) ?? null;
704
706
  } catch {
705
707
  finalSessionId = null;
706
708
  }
707
709
  }
708
710
  const finishedAt = Date.now();
709
711
 
710
- if (tool === "codex-cli") {
712
+ if (!finalSessionId && tool === "codex-cli") {
711
713
  try {
712
714
  const latest = await findLatestCodexSession({
713
715
  since: launchStartedAt - 60_000,
@@ -722,7 +724,7 @@ export async function handleAIToolWorkflow(
722
724
  } catch {
723
725
  // ignore fallback failure
724
726
  }
725
- } else if (tool === "claude-code") {
727
+ } else if (!finalSessionId && tool === "claude-code") {
726
728
  try {
727
729
  const latestClaude = await findLatestClaudeSession(worktreePath, {
728
730
  since: launchStartedAt - 60_000,
@@ -736,7 +738,7 @@ export async function handleAIToolWorkflow(
736
738
  } catch {
737
739
  // ignore
738
740
  }
739
- } else if (tool === "gemini-cli") {
741
+ } else if (!finalSessionId && tool === "gemini-cli") {
740
742
  try {
741
743
  const latestGemini = await findLatestGeminiSession(worktreePath, {
742
744
  since: launchStartedAt - 60_000,
@@ -874,7 +876,52 @@ export async function main(): Promise<void> {
874
876
  process.exit(1);
875
877
  }
876
878
 
877
- await runInteractiveLoop();
879
+ // Start Web UI server in background (skip if port is in use)
880
+ const port = resolveWebUiPort();
881
+ const portInUse = await isPortInUse(port);
882
+ let webServerHandlePromise: Promise<{
883
+ close: () => Promise<void>;
884
+ } | null> | null = null;
885
+ if (portInUse) {
886
+ printWarning(`Port ${port} is already in use. Skipping Web UI server.`);
887
+ } else {
888
+ const { startWebServer } = await import("./web/server/index.js");
889
+ webServerHandlePromise = startWebServer({ background: true }).catch(
890
+ (err) => {
891
+ appLogger.warn({ err }, "Web UI server failed to start");
892
+ return null;
893
+ },
894
+ );
895
+ printInfo(`Web UI available at http://localhost:${port}`);
896
+ }
897
+
898
+ try {
899
+ await runInteractiveLoop();
900
+ } finally {
901
+ if (webServerHandlePromise) {
902
+ const shutdownTimeoutMs = 2000;
903
+ const handleOrTimeout = await Promise.race([
904
+ webServerHandlePromise,
905
+ new Promise<"timeout">((resolve) => {
906
+ const timer = setTimeout(() => resolve("timeout"), shutdownTimeoutMs);
907
+ timer.unref?.();
908
+ }),
909
+ ]);
910
+
911
+ if (handleOrTimeout === "timeout") {
912
+ appLogger.warn(
913
+ { timeoutMs: shutdownTimeoutMs },
914
+ "Web UI server startup did not finish before shutdown timeout; skipping stop",
915
+ );
916
+ } else if (handleOrTimeout) {
917
+ try {
918
+ await handleOrTimeout.close();
919
+ } catch (err) {
920
+ appLogger.warn({ err }, "Web UI server failed to stop");
921
+ }
922
+ }
923
+ }
924
+ }
878
925
  }
879
926
 
880
927
  // Run the application if this module is executed directly