jettypod 4.4.118 → 4.4.121

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 (240) hide show
  1. package/.env +4 -3
  2. package/Cargo.lock +6450 -0
  3. package/Cargo.toml +35 -0
  4. package/README.md +5 -1
  5. package/TAURI-MIGRATION-PLAN.md +840 -0
  6. package/apps/dashboard/app/connect-claude/page.tsx +5 -6
  7. package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
  8. package/apps/dashboard/app/demo/gates/page.tsx +43 -45
  9. package/apps/dashboard/app/design-system/page.tsx +868 -0
  10. package/apps/dashboard/app/globals.css +80 -4
  11. package/apps/dashboard/app/install-claude/page.tsx +4 -6
  12. package/apps/dashboard/app/login/page.tsx +72 -54
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +61 -13
  15. package/apps/dashboard/app/signup/page.tsx +242 -0
  16. package/apps/dashboard/app/subscribe/page.tsx +0 -2
  17. package/apps/dashboard/app/tests/page.tsx +37 -4
  18. package/apps/dashboard/app/welcome/page.tsx +13 -16
  19. package/apps/dashboard/app/work/[id]/page.tsx +117 -118
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +92 -85
  22. package/apps/dashboard/components/CardMenu.tsx +45 -12
  23. package/apps/dashboard/components/ClaudePanel.tsx +771 -850
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
  26. package/apps/dashboard/components/CopyableId.tsx +3 -4
  27. package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
  28. package/apps/dashboard/components/DragContext.tsx +134 -63
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +6 -7
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +26 -7
  34. package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
  35. package/apps/dashboard/components/EpicGroup.tsx +359 -0
  36. package/apps/dashboard/components/GateCard.tsx +79 -17
  37. package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
  39. package/apps/dashboard/components/JettyLoader.tsx +37 -0
  40. package/apps/dashboard/components/KanbanBoard.tsx +368 -958
  41. package/apps/dashboard/components/KanbanCard.tsx +740 -0
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
  44. package/apps/dashboard/components/MainNav.tsx +38 -73
  45. package/apps/dashboard/components/MessageBlock.tsx +468 -0
  46. package/apps/dashboard/components/ModeStartCard.tsx +15 -16
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
  48. package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
  53. package/apps/dashboard/components/ReviewFooter.tsx +139 -0
  54. package/apps/dashboard/components/SessionList.tsx +19 -19
  55. package/apps/dashboard/components/SubscribeContent.tsx +91 -47
  56. package/apps/dashboard/components/TestTree.tsx +16 -16
  57. package/apps/dashboard/components/TipCard.tsx +16 -17
  58. package/apps/dashboard/components/Toast.tsx +5 -6
  59. package/apps/dashboard/components/TypeIcon.tsx +55 -0
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
  62. package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
  63. package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
  64. package/apps/dashboard/components/WorkItemTree.tsx +11 -32
  65. package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
  66. package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
  67. package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
  68. package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
  72. package/apps/dashboard/components/ui/Button.tsx +104 -0
  73. package/apps/dashboard/components/ui/Input.tsx +78 -0
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
  77. package/apps/dashboard/contexts/UsageContext.tsx +87 -32
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
  81. package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
  82. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  83. package/apps/dashboard/index.html +73 -0
  84. package/apps/dashboard/lib/constants.ts +43 -0
  85. package/apps/dashboard/lib/data-bridge.ts +722 -0
  86. package/apps/dashboard/lib/db.ts +69 -1265
  87. package/apps/dashboard/lib/environment-config.ts +173 -0
  88. package/apps/dashboard/lib/environment-verification.ts +119 -0
  89. package/apps/dashboard/lib/kanban-utils.ts +270 -0
  90. package/apps/dashboard/lib/proof-run.ts +495 -0
  91. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  92. package/apps/dashboard/lib/run-migrations.js +27 -2
  93. package/apps/dashboard/lib/service-recovery.ts +326 -0
  94. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  95. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  96. package/apps/dashboard/lib/session-stream-manager.ts +308 -134
  97. package/apps/dashboard/lib/shadows.ts +7 -0
  98. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  99. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  100. package/apps/dashboard/lib/tauri.ts +106 -0
  101. package/apps/dashboard/lib/utils.ts +6 -0
  102. package/apps/dashboard/next-env.d.ts +1 -1
  103. package/apps/dashboard/package.json +21 -32
  104. package/apps/dashboard/public/bug-icon.png +0 -0
  105. package/apps/dashboard/public/buoy-icon.png +0 -0
  106. package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
  107. package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
  108. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  109. package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
  110. package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
  111. package/apps/dashboard/public/jettypod_logo.png +0 -0
  112. package/apps/dashboard/public/pier-icon.png +0 -0
  113. package/apps/dashboard/public/star-icon.png +0 -0
  114. package/apps/dashboard/public/wrench-icon.png +0 -0
  115. package/apps/dashboard/scripts/tauri-build.js +228 -0
  116. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  117. package/apps/dashboard/scripts/ws-server.js +191 -0
  118. package/apps/dashboard/src/main.tsx +12 -0
  119. package/apps/dashboard/src/router.tsx +107 -0
  120. package/apps/dashboard/src/vite-env.d.ts +1 -0
  121. package/apps/dashboard/tsconfig.json +7 -12
  122. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  123. package/apps/dashboard/vite.config.ts +33 -0
  124. package/apps/update-server/src/index.ts +228 -80
  125. package/claude-hooks/global-guardrails.js +14 -13
  126. package/crates/jettypod-cli/Cargo.toml +19 -0
  127. package/crates/jettypod-cli/src/commands.rs +1249 -0
  128. package/crates/jettypod-cli/src/main.rs +595 -0
  129. package/crates/jettypod-core/Cargo.toml +26 -0
  130. package/crates/jettypod-core/build.rs +98 -0
  131. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  132. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  133. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  134. package/crates/jettypod-core/src/auth.rs +294 -0
  135. package/crates/jettypod-core/src/config.rs +397 -0
  136. package/crates/jettypod-core/src/db/mod.rs +507 -0
  137. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  138. package/crates/jettypod-core/src/db/startup.rs +101 -0
  139. package/crates/jettypod-core/src/db/validate.rs +149 -0
  140. package/crates/jettypod-core/src/error.rs +76 -0
  141. package/crates/jettypod-core/src/git.rs +458 -0
  142. package/crates/jettypod-core/src/lib.rs +20 -0
  143. package/crates/jettypod-core/src/sessions.rs +625 -0
  144. package/crates/jettypod-core/src/skills.rs +556 -0
  145. package/crates/jettypod-core/src/work.rs +1086 -0
  146. package/crates/jettypod-core/src/worktree.rs +628 -0
  147. package/crates/jettypod-core/src/ws.rs +767 -0
  148. package/cucumber-test.cjs +6 -0
  149. package/cucumber.js +9 -3
  150. package/docs/COMMAND_REFERENCE.md +34 -0
  151. package/hooks/post-checkout +32 -75
  152. package/hooks/post-merge +111 -10
  153. package/jest.setup.js +1 -0
  154. package/jettypod.js +145 -116
  155. package/lib/bdd-preflight.js +96 -0
  156. package/lib/chore-taxonomy.js +33 -10
  157. package/lib/database.js +36 -16
  158. package/lib/db-watcher.js +1 -1
  159. package/lib/git-hooks/pre-commit +1 -1
  160. package/lib/jettypod-backup.js +27 -4
  161. package/lib/merge-lock.js +111 -253
  162. package/lib/migrations/027-plan-at-creation-column.js +3 -1
  163. package/lib/migrations/029-remove-autoincrement.js +307 -0
  164. package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
  165. package/lib/migrations/030-rejection-round-columns.js +54 -0
  166. package/lib/migrations/031-session-isolation-index.js +17 -0
  167. package/lib/migrations/index.js +47 -4
  168. package/lib/schema.js +10 -5
  169. package/lib/seed-onboarding.js +1 -1
  170. package/lib/update-command/index.js +9 -175
  171. package/lib/work-commands/index.js +144 -19
  172. package/lib/work-tracking/index.js +148 -27
  173. package/lib/worktree-diagnostics.js +16 -16
  174. package/lib/worktree-facade.js +1 -1
  175. package/lib/worktree-manager.js +8 -8
  176. package/lib/worktree-reconciler.js +5 -5
  177. package/package.json +9 -2
  178. package/scripts/ndjson-to-cucumber-json.js +152 -0
  179. package/scripts/postinstall.js +25 -0
  180. package/skills-templates/bug-mode/SKILL.md +79 -20
  181. package/skills-templates/bug-planning/SKILL.md +25 -29
  182. package/skills-templates/chore-mode/SKILL.md +171 -69
  183. package/skills-templates/chore-mode/verification.js +51 -10
  184. package/skills-templates/chore-planning/SKILL.md +47 -18
  185. package/skills-templates/design-system-selection/SKILL.md +273 -0
  186. package/skills-templates/epic-planning/SKILL.md +82 -48
  187. package/skills-templates/external-transition/SKILL.md +47 -47
  188. package/skills-templates/feature-planning/SKILL.md +173 -74
  189. package/skills-templates/production-mode/SKILL.md +69 -49
  190. package/skills-templates/request-routing/SKILL.md +4 -4
  191. package/skills-templates/simple-improvement/SKILL.md +74 -29
  192. package/skills-templates/speed-mode/SKILL.md +217 -141
  193. package/skills-templates/stable-mode/SKILL.md +148 -89
  194. package/apps/dashboard/README.md +0 -36
  195. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
  196. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  197. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
  198. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  199. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
  200. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  201. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  202. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  203. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  204. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  205. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  206. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  207. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  208. package/apps/dashboard/app/api/tests/route.ts +0 -9
  209. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  210. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  211. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  212. package/apps/dashboard/app/api/usage/route.ts +0 -17
  213. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  214. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  215. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  216. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
  217. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  218. package/apps/dashboard/app/layout.tsx +0 -43
  219. package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
  220. package/apps/dashboard/electron/ipc-handlers.js +0 -1028
  221. package/apps/dashboard/electron/main.js +0 -2124
  222. package/apps/dashboard/electron/preload.js +0 -123
  223. package/apps/dashboard/electron/session-manager.js +0 -141
  224. package/apps/dashboard/electron-builder.config.js +0 -357
  225. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  226. package/apps/dashboard/lib/claude-process-manager.ts +0 -492
  227. package/apps/dashboard/lib/db-bridge.ts +0 -282
  228. package/apps/dashboard/lib/prototypes.ts +0 -202
  229. package/apps/dashboard/lib/test-results-db.ts +0 -307
  230. package/apps/dashboard/lib/tests.ts +0 -282
  231. package/apps/dashboard/next.config.js +0 -50
  232. package/apps/dashboard/postcss.config.mjs +0 -7
  233. package/apps/dashboard/public/file.svg +0 -1
  234. package/apps/dashboard/public/globe.svg +0 -1
  235. package/apps/dashboard/public/next.svg +0 -1
  236. package/apps/dashboard/public/vercel.svg +0 -1
  237. package/apps/dashboard/public/window.svg +0 -1
  238. package/apps/dashboard/scripts/download-node.js +0 -104
  239. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
  240. package/docs/bdd-guidance.md +0 -390
@@ -1,299 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useCallback, useRef, useEffect } from 'react';
4
- import type { ClaudeMessage, StreamStatus } from '../lib/session-stream-manager';
5
-
6
- export interface ClaudeSession {
7
- workItemId: string;
8
- title: string;
9
- description?: string;
10
- messages: ClaudeMessage[];
11
- status: StreamStatus;
12
- error: string | null;
13
- exitCode: number | null;
14
- canRetry: boolean;
15
- abortController: AbortController | null;
16
- }
17
-
18
- interface UseClaudeSessionsReturn {
19
- sessions: Map<string, ClaudeSession>;
20
- activeSessionId: string | null;
21
- activeSession: ClaudeSession | null;
22
- startSession: (workItemId: string, title: string, description?: string) => void;
23
- switchSession: (workItemId: string) => void;
24
- stopSession: (workItemId: string) => void;
25
- retrySession: (workItemId: string) => void;
26
- closeSession: (workItemId: string, dbSessionId?: number) => Promise<void>;
27
- closeAllSessions: () => void;
28
- isSessionRunning: (workItemId: string) => boolean;
29
- }
30
-
31
- export function useClaudeSessions(): UseClaudeSessionsReturn {
32
- const [sessions, setSessions] = useState<Map<string, ClaudeSession>>(new Map());
33
- const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
34
- const sessionsRef = useRef<Map<string, ClaudeSession>>(new Map());
35
-
36
- // Keep ref in sync with state for cleanup
37
- useEffect(() => {
38
- sessionsRef.current = sessions;
39
- }, [sessions]);
40
-
41
- // Cleanup all sessions on unmount (browser close/refresh)
42
- useEffect(() => {
43
- return () => {
44
- sessionsRef.current.forEach((session) => {
45
- if (session.abortController) {
46
- session.abortController.abort();
47
- }
48
- });
49
- };
50
- }, []);
51
-
52
- const updateSession = useCallback((workItemId: string, updates: Partial<ClaudeSession>) => {
53
- setSessions((prev) => {
54
- const newMap = new Map(prev);
55
- const session = newMap.get(workItemId);
56
- if (session) {
57
- newMap.set(workItemId, { ...session, ...updates });
58
- }
59
- return newMap;
60
- });
61
- }, []);
62
-
63
- const startSession = useCallback(async (workItemId: string, title: string, description?: string) => {
64
- // Check if session already exists and is running
65
- const existingSession = sessionsRef.current.get(workItemId);
66
- if (existingSession?.status === 'streaming') {
67
- // Just switch to it
68
- setActiveSessionId(workItemId);
69
- return;
70
- }
71
-
72
- // Create or reset session
73
- const controller = new AbortController();
74
- const newSession: ClaudeSession = {
75
- workItemId,
76
- title,
77
- description,
78
- messages: [],
79
- status: 'connecting',
80
- error: null,
81
- exitCode: null,
82
- canRetry: false,
83
- abortController: controller,
84
- };
85
-
86
- setSessions((prev) => {
87
- const newMap = new Map(prev);
88
- newMap.set(workItemId, newSession);
89
- return newMap;
90
- });
91
- setActiveSessionId(workItemId);
92
-
93
- try {
94
- const response = await fetch(`/api/claude/${workItemId}`, {
95
- method: 'POST',
96
- headers: { 'Content-Type': 'application/json' },
97
- body: JSON.stringify({ title, description }),
98
- signal: controller.signal,
99
- });
100
-
101
- if (!response.ok) {
102
- let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
103
- try {
104
- const errorData = await response.json();
105
- if (errorData.message) {
106
- errorMessage = errorData.message;
107
- }
108
- } catch {
109
- // Response wasn't JSON
110
- }
111
- const retryable = response.status >= 500 || response.status === 503;
112
- updateSession(workItemId, {
113
- status: 'error',
114
- error: errorMessage,
115
- canRetry: retryable,
116
- });
117
- return;
118
- }
119
-
120
- if (!response.body) {
121
- updateSession(workItemId, {
122
- status: 'error',
123
- error: 'Response body is null',
124
- canRetry: true,
125
- });
126
- return;
127
- }
128
-
129
- updateSession(workItemId, { status: 'streaming' });
130
-
131
- const reader = response.body.getReader();
132
- const decoder = new TextDecoder();
133
- let buffer = '';
134
-
135
- while (true) {
136
- const { done, value } = await reader.read();
137
-
138
- if (done) {
139
- updateSession(workItemId, { status: 'done' });
140
- break;
141
- }
142
-
143
- buffer += decoder.decode(value, { stream: true });
144
- const lines = buffer.split('\n');
145
- buffer = lines.pop() || '';
146
-
147
- for (const line of lines) {
148
- if (line.startsWith('data: ')) {
149
- const data = line.slice(6);
150
- try {
151
- const parsed = JSON.parse(data);
152
- const message: ClaudeMessage = {
153
- ...parsed,
154
- timestamp: Date.now(),
155
- };
156
-
157
- setSessions((prev) => {
158
- const newMap = new Map(prev);
159
- const session = newMap.get(workItemId);
160
- if (session) {
161
- newMap.set(workItemId, {
162
- ...session,
163
- messages: [...session.messages, message],
164
- });
165
- }
166
- return newMap;
167
- });
168
-
169
- if (parsed.type === 'done') {
170
- if (parsed.exitCode !== undefined && parsed.exitCode !== 0) {
171
- updateSession(workItemId, {
172
- status: 'error',
173
- exitCode: parsed.exitCode,
174
- error: `Process exited with code ${parsed.exitCode}`,
175
- canRetry: true,
176
- });
177
- } else {
178
- updateSession(workItemId, { status: 'done' });
179
- }
180
- } else if (parsed.type === 'error') {
181
- updateSession(workItemId, {
182
- status: 'error',
183
- error: parsed.content || 'Unknown error',
184
- canRetry: true,
185
- });
186
- }
187
- } catch {
188
- // Non-JSON line, skip
189
- }
190
- }
191
- }
192
- }
193
- } catch (err) {
194
- if (err instanceof Error && err.name === 'AbortError') {
195
- updateSession(workItemId, { status: 'idle' });
196
- } else {
197
- const errorMessage = err instanceof Error ? err.message : 'Unknown error';
198
- const isNetworkError = err instanceof TypeError ||
199
- (err instanceof Error && (
200
- err.message.includes('network') ||
201
- err.message.includes('fetch') ||
202
- err.message.includes('Failed to fetch')
203
- ));
204
-
205
- updateSession(workItemId, {
206
- status: 'error',
207
- error: isNetworkError ? 'Connection lost' : errorMessage,
208
- canRetry: true,
209
- });
210
- }
211
- }
212
- }, [updateSession]);
213
-
214
- const switchSession = useCallback((workItemId: string) => {
215
- if (sessions.has(workItemId)) {
216
- setActiveSessionId(workItemId);
217
- }
218
- }, [sessions]);
219
-
220
- const stopSession = useCallback((workItemId: string) => {
221
- const session = sessions.get(workItemId);
222
- if (session?.abortController) {
223
- session.abortController.abort();
224
- }
225
- updateSession(workItemId, {
226
- status: 'idle',
227
- abortController: null,
228
- });
229
- }, [sessions, updateSession]);
230
-
231
- const retrySession = useCallback((workItemId: string) => {
232
- const session = sessions.get(workItemId);
233
- if (session) {
234
- startSession(workItemId, session.title, session.description);
235
- }
236
- }, [sessions, startSession]);
237
-
238
- const closeSession = useCallback(async (workItemId: string, dbSessionId?: number) => {
239
- const session = sessions.get(workItemId);
240
-
241
- // Abort if running
242
- if (session?.abortController) {
243
- session.abortController.abort();
244
- }
245
-
246
- // Call API to mark as completed in DB (if we have a DB session ID)
247
- if (dbSessionId) {
248
- try {
249
- await fetch(`/api/claude/sessions?sessionId=${dbSessionId}`, {
250
- method: 'DELETE',
251
- });
252
- } catch (error) {
253
- console.error('Failed to close session in DB:', error);
254
- }
255
- }
256
-
257
- // Remove from local state
258
- setSessions((prev) => {
259
- const newMap = new Map(prev);
260
- newMap.delete(workItemId);
261
- return newMap;
262
- });
263
-
264
- // If this was the active session, clear it
265
- if (activeSessionId === workItemId) {
266
- setActiveSessionId(null);
267
- }
268
- }, [sessions, activeSessionId]);
269
-
270
- const closeAllSessions = useCallback(() => {
271
- sessions.forEach((session) => {
272
- if (session.abortController) {
273
- session.abortController.abort();
274
- }
275
- });
276
- setSessions(new Map());
277
- setActiveSessionId(null);
278
- }, [sessions]);
279
-
280
- const isSessionRunning = useCallback((workItemId: string) => {
281
- const session = sessions.get(workItemId);
282
- return session?.status === 'streaming' || session?.status === 'connecting';
283
- }, [sessions]);
284
-
285
- const activeSession = activeSessionId ? sessions.get(activeSessionId) ?? null : null;
286
-
287
- return {
288
- sessions,
289
- activeSessionId,
290
- activeSession,
291
- startSession,
292
- switchSession,
293
- stopSession,
294
- retrySession,
295
- closeSession,
296
- closeAllSessions,
297
- isSessionRunning,
298
- };
299
- }