@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
@@ -5,7 +5,7 @@
5
5
  * 仕様: specs/SPEC-d5e56259/contracts/websocket.md
6
6
  */
7
7
 
8
- import type { FastifyRequest } from "fastify";
8
+ import type { FastifyRequest, FastifyBaseLogger } from "fastify";
9
9
  import type { WebSocket } from "@fastify/websocket";
10
10
  import type { PTYManager } from "../pty/manager.js";
11
11
  import type {
@@ -22,17 +22,18 @@ import type {
22
22
  * WebSocketハンドラー
23
23
  */
24
24
  export class WebSocketHandler {
25
- constructor(private ptyManager: PTYManager) {}
25
+ private cleanupTimers: Map<string, NodeJS.Timeout> = new Map();
26
+
27
+ constructor(
28
+ private ptyManager: PTYManager,
29
+ private logger: FastifyBaseLogger,
30
+ ) {}
26
31
 
27
32
  /**
28
33
  * WebSocket接続を処理
29
34
  */
30
35
  public handle(connection: WebSocket, request: FastifyRequest): void {
31
- const url = new URL(
32
- request.url,
33
- `http://${request.hostname || "localhost"}`,
34
- );
35
- const sessionId = url.searchParams.get("sessionId");
36
+ const sessionId = resolveSessionId(request);
36
37
 
37
38
  if (!sessionId) {
38
39
  this.sendError(connection, "Missing sessionId parameter");
@@ -47,7 +48,14 @@ export class WebSocketHandler {
47
48
  return;
48
49
  }
49
50
 
51
+ this.logger.info(
52
+ `WebSocket connection established for session ${sessionId} (pid=${instance.ptyProcess.pid})`,
53
+ );
54
+
55
+ this.clearCleanupTimer(sessionId);
56
+
50
57
  const { ptyProcess } = instance;
58
+ let hasExited = false;
51
59
 
52
60
  // セッションステータスを更新
53
61
  this.ptyManager.updateStatus(sessionId, "running");
@@ -59,6 +67,8 @@ export class WebSocketHandler {
59
67
 
60
68
  // PTYプロセス終了時の処理
61
69
  ptyProcess.onExit(({ exitCode, signal }) => {
70
+ hasExited = true;
71
+ this.clearCleanupTimer(sessionId);
62
72
  this.ptyManager.updateStatus(sessionId, "completed", exitCode);
63
73
  this.sendExit(connection, exitCode, signal);
64
74
  connection.close();
@@ -66,6 +76,10 @@ export class WebSocketHandler {
66
76
 
67
77
  // クライアントからのメッセージを処理
68
78
  connection.on("message", (rawMessage) => {
79
+ if (hasExited) {
80
+ this.sendError(connection, "Session already exited");
81
+ return;
82
+ }
69
83
  try {
70
84
  const message: ClientMessage = JSON.parse(rawMessage.toString());
71
85
  this.handleClientMessage(message, ptyProcess, connection);
@@ -77,7 +91,10 @@ export class WebSocketHandler {
77
91
 
78
92
  // 接続エラー時の処理
79
93
  connection.on("error", (error) => {
80
- console.error(`WebSocket error for session ${sessionId}:`, error);
94
+ this.logger.error(
95
+ { err: error, sessionId },
96
+ `WebSocket error for session ${sessionId}`,
97
+ );
81
98
  this.ptyManager.updateStatus(
82
99
  sessionId,
83
100
  "failed",
@@ -87,9 +104,17 @@ export class WebSocketHandler {
87
104
  });
88
105
 
89
106
  // 接続クローズ時の処理
90
- connection.on("close", () => {
91
- console.log(`WebSocket closed for session ${sessionId}`);
92
- // PTYプロセスは残す(バックグラウンドで実行継続)
107
+ connection.on("close", (code, reason) => {
108
+ const reasonText = reason?.toString()?.trim();
109
+ this.logger.info(
110
+ `WebSocket closed for session ${sessionId} (code=${code}${reasonText ? `, reason=${reasonText}` : ""})`,
111
+ );
112
+
113
+ if (hasExited) {
114
+ return;
115
+ }
116
+
117
+ this.scheduleCleanup(sessionId);
93
118
  });
94
119
  }
95
120
 
@@ -104,12 +129,22 @@ export class WebSocketHandler {
104
129
  switch (message.type) {
105
130
  case "input": {
106
131
  const inputMsg = message as InputMessage;
107
- ptyProcess.write(inputMsg.data);
132
+ try {
133
+ ptyProcess.write(inputMsg.data);
134
+ } catch (error) {
135
+ const reason = error instanceof Error ? error.message : String(error);
136
+ this.sendError(connection, `Failed to write to session: ${reason}`);
137
+ }
108
138
  break;
109
139
  }
110
140
  case "resize": {
111
141
  const resizeMsg = message as ResizeMessage;
112
- ptyProcess.resize(resizeMsg.data.cols, resizeMsg.data.rows);
142
+ try {
143
+ ptyProcess.resize(resizeMsg.data.cols, resizeMsg.data.rows);
144
+ } catch (error) {
145
+ const reason = error instanceof Error ? error.message : String(error);
146
+ this.sendError(connection, `Failed to resize terminal: ${reason}`);
147
+ }
113
148
  break;
114
149
  }
115
150
  case "ping": {
@@ -177,4 +212,75 @@ export class WebSocketHandler {
177
212
  };
178
213
  connection.send(JSON.stringify(message));
179
214
  }
215
+
216
+ private scheduleCleanup(sessionId: string): void {
217
+ this.clearCleanupTimer(sessionId);
218
+ const timer = setTimeout(() => {
219
+ this.cleanupTimers.delete(sessionId);
220
+ const tracked = this.ptyManager.get(sessionId);
221
+ if (!tracked) {
222
+ return;
223
+ }
224
+
225
+ if (
226
+ tracked.session.status === "running" ||
227
+ tracked.session.status === "pending"
228
+ ) {
229
+ this.logger.warn(
230
+ `Auto-cleaning session ${sessionId} after unexpected client disconnect`,
231
+ );
232
+ this.ptyManager.updateStatus(
233
+ sessionId,
234
+ "failed",
235
+ undefined,
236
+ "Client disconnected",
237
+ );
238
+ this.ptyManager.delete(sessionId);
239
+ }
240
+ }, CLEANUP_GRACE_PERIOD_MS);
241
+ this.cleanupTimers.set(sessionId, timer);
242
+ }
243
+
244
+ private clearCleanupTimer(sessionId: string): void {
245
+ const timer = this.cleanupTimers.get(sessionId);
246
+ if (timer) {
247
+ clearTimeout(timer);
248
+ this.cleanupTimers.delete(sessionId);
249
+ this.logger.info(`Cleared cleanup timer for session ${sessionId}`);
250
+ }
251
+ }
252
+ }
253
+
254
+ const CLEANUP_GRACE_PERIOD_MS = Number(process.env.WS_CLEANUP_GRACE_MS ?? 3000);
255
+
256
+ interface RequestLike {
257
+ params?: { sessionId?: string } | undefined;
258
+ url: string;
259
+ hostname?: string;
260
+ headers?: { host?: string | undefined };
261
+ }
262
+
263
+ export function resolveSessionId(
264
+ request: FastifyRequest | RequestLike,
265
+ ): string | null {
266
+ const paramsId = (request as RequestLike).params?.sessionId;
267
+ if (typeof paramsId === "string" && paramsId.length > 0) {
268
+ return paramsId;
269
+ }
270
+
271
+ try {
272
+ const host =
273
+ (request as FastifyRequest).headers?.host ??
274
+ (request as RequestLike).hostname ??
275
+ "localhost";
276
+ const parsed = new URL(request.url, `http://${host}`);
277
+ const queryId = parsed.searchParams.get("sessionId");
278
+ if (queryId && queryId.length > 0) {
279
+ return queryId;
280
+ }
281
+ } catch {
282
+ // ignore
283
+ }
284
+
285
+ return null;
180
286
  }
@@ -1 +0,0 @@
1
- .xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;inset:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;inset:0;z-index:10;color:transparent;pointer-events:none}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}:root{--color-background: #050912;--color-surface: #0f172a;--color-surface-muted: #111c30;--color-card: rgba(15, 23, 42, .92);--color-border: rgba(148, 163, 184, .2);--color-border-strong: rgba(148, 163, 184, .45);--color-text: #e2e8f0;--color-muted: #94a3b8;--color-accent: #5eead4;--color-accent-strong: #2dd4bf;--color-warning: #fbbf24;--color-error: #fb7185;--color-success: #34d399;--color-remote: #60a5fa;--color-local: #c084fc;--shadow-card: 0 20px 45px rgba(2, 6, 23, .55);--shadow-soft: 0 10px 25px rgba(15, 23, 42, .45);--radius-lg: 24px;--radius-md: 16px;--radius-pill: 999px;font-family:Inter,Noto Sans JP,-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica Neue,Arial,sans-serif}*{box-sizing:border-box;margin:0;padding:0}body{font-family:Inter,Noto Sans JP,-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica Neue,Arial,sans-serif;background:radial-gradient(circle at top,#0b1221,#050912 55%);color:var(--color-text);min-height:100vh;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#root{min-height:100vh}a{color:var(--color-accent);text-decoration:none}a:hover{color:var(--color-accent-strong)}button{font:inherit}.app-shell{min-height:100vh;padding:2.5rem 1.75rem 3rem;background:linear-gradient(135deg,#17255473,#0891b214)}@media(max-width:768px){.app-shell{padding:1.5rem 1rem 2rem}}.page-hero{background:radial-gradient(circle at 15% 20%,rgba(94,234,212,.25),transparent 55%),radial-gradient(circle at 80% 0%,rgba(14,165,233,.2),transparent 45%),var(--color-card);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:2rem;margin-bottom:2rem;box-shadow:var(--shadow-card)}.page-hero--compact{padding:1.75rem 2rem}.page-hero__eyebrow{letter-spacing:.3em;text-transform:uppercase;color:var(--color-muted);font-size:.8rem;margin-bottom:.75rem}.page-hero h1{font-size:clamp(1.8rem,3vw,2.8rem);margin-bottom:.5rem}.page-hero__subtitle,.page-hero__meta{color:var(--color-muted);margin-top:.5rem;font-size:.95rem}.page-hero__actions{display:flex;flex-wrap:wrap;gap:.8rem;margin-top:1.5rem}.page-content{width:min(1400px,100%);margin:0 auto;display:flex;flex-direction:column;gap:1.5rem}.page-content--wide{width:min(1600px,100%)}.page-content--narrow{max-width:960px}.metrics-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:1rem}.metric-card{background:var(--color-card);border:1px solid var(--color-border);border-radius:var(--radius-md);padding:1.25rem;box-shadow:var(--shadow-soft)}.metric-card__label{font-size:.85rem;text-transform:uppercase;letter-spacing:.1em;color:var(--color-muted)}.metric-card__value{font-size:2rem;font-weight:600;margin-top:.25rem}.metric-card__hint{margin-top:.35rem;color:var(--color-muted);font-size:.85rem}.page-layout{display:flex;flex-direction:column;gap:1rem}.page-layout--split{display:grid;grid-template-columns:minmax(320px,420px) minmax(0,1fr);gap:1.5rem;align-items:start}@media(max-width:1024px){.page-layout--split{grid-template-columns:1fr}}.info-stack{display:flex;flex-direction:column;gap:1rem}.terminal-column{position:relative;min-height:100%}.toolbar{display:flex;gap:1rem;align-items:center;flex-wrap:wrap}.toolbar__field{flex:1;display:flex;align-items:center;gap:.5rem;background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);padding:.75rem 1rem;color:var(--color-muted)}.toolbar__icon{font-size:1rem}.search-input{flex:1;border:none;background:transparent;color:var(--color-text);font-size:1rem}.search-input:focus{outline:none}.toolbar__count{color:var(--color-muted);font-size:.9rem}.page-state{background:var(--color-card);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:2rem;box-shadow:var(--shadow-soft)}.page-state h1,.page-state h2,.page-state h3{margin-bottom:.5rem}.page-state--card{text-align:left}.page-state--centered{text-align:center}.empty-state{text-align:center;padding:2.5rem 2rem;border-radius:var(--radius-lg);border:1px dashed var(--color-border-strong);background:#0f172ab3}.branch-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:1.25rem}.branch-card{background:var(--color-card);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:1.5rem;display:flex;flex-direction:column;gap:1rem;box-shadow:var(--shadow-soft)}.branch-card__header{display:flex;justify-content:space-between;gap:1rem;flex-wrap:wrap}.branch-card__header h2{font-size:1.2rem;word-break:break-all}.branch-card__eyebrow{color:var(--color-muted);text-transform:uppercase;letter-spacing:.1em;font-size:.75rem;margin-bottom:.35rem}.branch-card__commit{color:var(--color-muted);line-height:1.5}.branch-card__actions{display:flex;justify-content:space-between;gap:.75rem;flex-wrap:wrap;align-items:center}.badge-group{display:flex;gap:.4rem;flex-wrap:wrap}.status-badge{padding:.25rem .75rem;border-radius:var(--radius-pill);font-size:.8rem;font-weight:600;border:1px solid transparent}.status-badge--local{color:#fdf4ff;background:#c084fc26;border-color:#c084fc59}.status-badge--remote{color:#dbeafe;background:#60a5fa1f;border-color:#60a5fa59}.status-badge--success{color:#042f2e;background:#34d399d9;border:none}.status-badge--warning{color:#451a03;background:#fbbf24e6;border:none}.status-badge--muted{color:var(--color-muted);background:#94a3b826;border-color:#94a3b866}.pill-group{display:flex;gap:.5rem;flex-wrap:wrap}.pill{padding:.25rem .85rem;border-radius:var(--radius-pill);background:#94a3b81f;color:var(--color-muted);font-size:.85rem}.pill--success{background:#34d39926;color:var(--color-success)}.pill--warning{background:#fbbf242e;color:var(--color-warning)}.info-pill{font-size:.85rem;padding:.3rem .85rem;border-radius:var(--radius-pill);border:1px solid var(--color-border);background:#0f172aa6;color:var(--color-muted)}.info-pill--success{border-color:#34d39973;color:var(--color-success)}.info-pill--warning{border-color:#fbbf2473;color:var(--color-warning)}.section-card{background:var(--color-card);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:1.75rem;box-shadow:var(--shadow-soft);display:flex;flex-direction:column;gap:.85rem}.section-card__body{color:var(--color-muted);line-height:1.6}.metadata-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:1rem}.metadata-grid--compact{grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}.metadata-grid dt{font-size:.8rem;text-transform:uppercase;letter-spacing:.08em;color:var(--color-muted)}.metadata-grid dd{font-weight:600;margin-top:.25rem}.list-muted{list-style:none;color:var(--color-muted);line-height:1.7}.inline-banner{margin-top:1rem;padding:.85rem 1.25rem;border-radius:var(--radius-md);font-weight:500}.inline-banner--success{background:#34d39926;color:var(--color-success)}.inline-banner--error{background:#fb71852e;color:var(--color-error)}.inline-banner--info{background:#5eead41f;color:var(--color-accent)}.terminal-section{gap:1rem}.terminal-surface{border-radius:var(--radius-md);border:1px solid var(--color-border);background:#050912;min-height:clamp(380px,70vh,85vh);height:clamp(380px,70vh,85vh);overflow:hidden}.button{border-radius:var(--radius-pill);padding:.65rem 1.5rem;border:1px solid transparent;font-weight:600;cursor:pointer;transition:transform .15s ease,box-shadow .15s ease}.button--primary{background:linear-gradient(135deg,var(--color-accent),var(--color-accent-strong));color:#04121a;box-shadow:0 10px 25px #2dd4bf66}.button--secondary{border-color:#5eead480;color:var(--color-accent);background:transparent}.button--ghost{border-color:var(--color-border);background:transparent;color:var(--color-text)}.button:disabled{opacity:.55;cursor:not-allowed;transform:none;box-shadow:none}.button:not(:disabled):hover{transform:translateY(-1px)}.link-back{display:inline-flex;align-items:center;font-size:.9rem;color:var(--color-muted);margin-bottom:.75rem}.session-hint{border-style:dashed;border-color:#5eead459}.terminal-section__header{display:flex;justify-content:space-between;gap:1rem;flex-wrap:wrap;align-items:flex-start}.terminal-section__controls{display:flex;gap:.5rem}.terminal-overlay-backdrop{position:fixed;inset:0;background:#020617bf;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);z-index:900}.terminal-section--fullscreen{position:fixed;inset:2rem;z-index:1000;width:calc(100vw - 4rem);max-width:calc(100vw - 4rem);height:calc(100vh - 4rem);box-shadow:0 30px 60px #020617b3;display:flex;flex-direction:column}@media(max-width:768px){.terminal-section--fullscreen{inset:1rem;width:calc(100vw - 2rem);max-width:calc(100vw - 2rem);height:calc(100vh - 2rem)}}.terminal-section--fullscreen .terminal-surface{flex:1;height:auto;min-height:unset}.terminal-section__close{position:absolute;top:1rem;right:1rem;width:2.5rem;height:2.5rem;border-radius:50%;border:1px solid rgba(255,255,255,.2);background:#0f172acc;color:var(--color-text);font-size:1.25rem;cursor:pointer}.terminal-section__close:hover{background:#0f172af2}.branch-graph-panel{background:var(--color-card);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:1.75rem 2rem;box-shadow:var(--shadow-card)}.branch-graph-panel+.metrics-grid{margin-top:1.5rem}.branch-graph-panel__header{display:flex;justify-content:space-between;gap:1.5rem;flex-wrap:wrap;margin-bottom:1.5rem}.branch-graph-panel__eyebrow{letter-spacing:.3em;text-transform:uppercase;color:var(--color-muted);font-size:.8rem;margin-bottom:.25rem}.branch-graph-panel__legend{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}.branch-graph-panel__empty{text-align:center;color:var(--color-muted)}.graph-chip{font-size:.75rem;letter-spacing:.1em;text-transform:uppercase;padding:.35rem .75rem;border-radius:var(--radius-pill);border:1px solid var(--color-border);background:#0f172ab3}.graph-chip--base{border-color:var(--color-accent);color:var(--color-accent)}.graph-chip--local{border-color:var(--color-local);color:var(--color-local)}.graph-chip--remote{border-color:var(--color-remote);color:var(--color-remote)}.graph-chip--worktree{border-color:var(--color-success);color:var(--color-success)}.branch-graph{display:flex;flex-direction:column;gap:1.25rem}.branch-graph__lane{display:flex;flex-direction:column;gap:.75rem;padding-bottom:.75rem;border-bottom:1px solid rgba(148,163,184,.2)}.branch-graph__lane:last-child{border-bottom:none;padding-bottom:0}.branch-graph__lane-heading{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;flex-wrap:wrap}.branch-graph__lane-label{font-weight:600}.branch-graph__lane-meta{margin-left:.5rem;font-size:.75rem;letter-spacing:.1em;color:var(--color-muted)}.lane-meta--muted{color:#94a3b899}.branch-graph__lane-count{color:var(--color-muted);font-size:.85rem}.branch-graph__track{display:flex;align-items:center;gap:1rem;overflow-x:auto;padding-bottom:.5rem}.branch-graph__node-link{position:relative;text-decoration:none;min-width:180px;flex-shrink:0}.branch-graph__node{position:relative;border:1px solid var(--color-border);border-radius:var(--radius-md);padding:.85rem 1rem;background:var(--color-surface-muted);box-shadow:var(--shadow-soft)}.branch-graph__node:before{content:"";position:absolute;width:28px;height:2px;background:var(--color-border-strong);left:-22px;top:50%;transform:translateY(-50%);opacity:.6}.branch-graph__node--base:before{display:none}.branch-graph__node--local{border-color:#c084fccc;box-shadow:0 15px 35px #9449ff40}.branch-graph__node--remote{border-color:#60a5facc;box-shadow:0 15px 35px #0ea5e940}.branch-graph__node--merged{background:#2563eb40}.branch-graph__node--active{background:#0891b240}.branch-graph__node-label{font-weight:600;display:block}.branch-graph__node-meta{font-size:.75rem;color:var(--color-muted)}.branch-graph__tooltip{position:absolute;bottom:calc(100% + .5rem);left:0;background:var(--color-surface);border:1px solid var(--color-border-strong);border-radius:var(--radius-md);padding:.75rem;width:min(260px,60vw);box-shadow:var(--shadow-card);opacity:0;pointer-events:none;transform:translateY(.4rem);transition:opacity .15s ease,transform .15s ease;z-index:5}.branch-graph__tooltip p{font-size:.8rem;color:var(--color-text)}.branch-graph__tooltip p+p{margin-top:.25rem;color:var(--color-muted)}.branch-graph__node:hover .branch-graph__tooltip,.branch-graph__node:focus-within .branch-graph__tooltip{opacity:1;transform:translateY(0)}@media(max-width:768px){.branch-graph__track{flex-direction:column;align-items:flex-start}.branch-graph__node:before{width:2px;height:24px;left:50%;top:-18px}.branch-graph__node-link{width:100%}}