jettypod 4.4.120 → 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 (208) hide show
  1. package/.env +2 -1
  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 +54 -49
  8. package/apps/dashboard/app/demo/gates/page.tsx +3 -5
  9. package/apps/dashboard/app/design-system/page.tsx +1 -1
  10. package/apps/dashboard/app/globals.css +74 -2
  11. package/apps/dashboard/app/install-claude/page.tsx +3 -5
  12. package/apps/dashboard/app/login/page.tsx +17 -20
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +60 -12
  15. package/apps/dashboard/app/signup/page.tsx +14 -17
  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 +12 -15
  19. package/apps/dashboard/app/work/[id]/page.tsx +90 -75
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +70 -61
  22. package/apps/dashboard/components/CardMenu.tsx +0 -1
  23. package/apps/dashboard/components/ClaudePanel.tsx +541 -283
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +23 -4
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +1 -5
  26. package/apps/dashboard/components/CopyableId.tsx +1 -2
  27. package/apps/dashboard/components/DetailReviewActions.tsx +11 -20
  28. package/apps/dashboard/components/DragContext.tsx +132 -62
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +5 -6
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +6 -12
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +0 -1
  34. package/apps/dashboard/components/ElapsedTimer.tsx +15 -3
  35. package/apps/dashboard/components/EpicGroup.tsx +100 -70
  36. package/apps/dashboard/components/GateCard.tsx +0 -1
  37. package/apps/dashboard/components/GateChoiceCard.tsx +1 -2
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +1 -5
  39. package/apps/dashboard/components/JettyLoader.tsx +0 -1
  40. package/apps/dashboard/components/KanbanBoard.tsx +319 -173
  41. package/apps/dashboard/components/KanbanCard.tsx +341 -107
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +0 -1
  44. package/apps/dashboard/components/MainNav.tsx +24 -25
  45. package/apps/dashboard/components/MessageBlock.tsx +93 -16
  46. package/apps/dashboard/components/ModeStartCard.tsx +0 -1
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +0 -1
  48. package/apps/dashboard/components/PlaceholderCard.tsx +0 -1
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +20 -20
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +47 -26
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +308 -223
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +303 -160
  53. package/apps/dashboard/components/ReviewFooter.tsx +12 -14
  54. package/apps/dashboard/components/SessionList.tsx +0 -1
  55. package/apps/dashboard/components/SubscribeContent.tsx +40 -11
  56. package/apps/dashboard/components/TestTree.tsx +1 -2
  57. package/apps/dashboard/components/TipCard.tsx +2 -4
  58. package/apps/dashboard/components/Toast.tsx +0 -1
  59. package/apps/dashboard/components/TypeIcon.tsx +7 -8
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +5 -17
  62. package/apps/dashboard/components/WelcomeScreen.tsx +2 -6
  63. package/apps/dashboard/components/WorkItemHeader.tsx +0 -1
  64. package/apps/dashboard/components/WorkItemTree.tsx +2 -4
  65. package/apps/dashboard/components/settings/AccountSection.tsx +27 -13
  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 +20 -73
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +137 -26
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +0 -1
  72. package/apps/dashboard/components/ui/Button.tsx +1 -1
  73. package/apps/dashboard/components/ui/Input.tsx +1 -1
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +611 -358
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +0 -1
  77. package/apps/dashboard/contexts/UsageContext.tsx +62 -31
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  81. package/apps/dashboard/index.html +73 -0
  82. package/apps/dashboard/lib/data-bridge.ts +722 -0
  83. package/apps/dashboard/lib/db.ts +69 -1302
  84. package/apps/dashboard/lib/environment-config.ts +173 -0
  85. package/apps/dashboard/lib/environment-verification.ts +119 -0
  86. package/apps/dashboard/lib/kanban-utils.ts +226 -26
  87. package/apps/dashboard/lib/proof-run.ts +495 -0
  88. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  89. package/apps/dashboard/lib/service-recovery.ts +326 -0
  90. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  91. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  92. package/apps/dashboard/lib/session-stream-manager.ts +253 -122
  93. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  94. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  95. package/apps/dashboard/lib/tauri.ts +106 -0
  96. package/apps/dashboard/lib/utils.ts +3 -3
  97. package/apps/dashboard/next-env.d.ts +1 -1
  98. package/apps/dashboard/package.json +21 -33
  99. package/apps/dashboard/public/bug-icon.png +0 -0
  100. package/apps/dashboard/public/buoy-icon.png +0 -0
  101. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  102. package/apps/dashboard/public/pier-icon.png +0 -0
  103. package/apps/dashboard/public/star-icon.png +0 -0
  104. package/apps/dashboard/public/wrench-icon.png +0 -0
  105. package/apps/dashboard/scripts/tauri-build.js +228 -0
  106. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  107. package/apps/dashboard/src/main.tsx +12 -0
  108. package/apps/dashboard/src/router.tsx +107 -0
  109. package/apps/dashboard/src/vite-env.d.ts +1 -0
  110. package/apps/dashboard/tsconfig.json +7 -12
  111. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  112. package/apps/dashboard/vite.config.ts +33 -0
  113. package/apps/update-server/src/index.ts +167 -30
  114. package/claude-hooks/global-guardrails.js +14 -13
  115. package/crates/jettypod-cli/Cargo.toml +19 -0
  116. package/crates/jettypod-cli/src/commands.rs +1249 -0
  117. package/crates/jettypod-cli/src/main.rs +595 -0
  118. package/crates/jettypod-core/Cargo.toml +26 -0
  119. package/crates/jettypod-core/build.rs +98 -0
  120. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  121. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  122. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  123. package/crates/jettypod-core/src/auth.rs +294 -0
  124. package/crates/jettypod-core/src/config.rs +397 -0
  125. package/crates/jettypod-core/src/db/mod.rs +507 -0
  126. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  127. package/crates/jettypod-core/src/db/startup.rs +101 -0
  128. package/crates/jettypod-core/src/db/validate.rs +149 -0
  129. package/crates/jettypod-core/src/error.rs +76 -0
  130. package/crates/jettypod-core/src/git.rs +458 -0
  131. package/crates/jettypod-core/src/lib.rs +20 -0
  132. package/crates/jettypod-core/src/sessions.rs +625 -0
  133. package/crates/jettypod-core/src/skills.rs +556 -0
  134. package/crates/jettypod-core/src/work.rs +1086 -0
  135. package/crates/jettypod-core/src/worktree.rs +628 -0
  136. package/crates/jettypod-core/src/ws.rs +767 -0
  137. package/cucumber-test.cjs +6 -0
  138. package/jettypod.js +96 -4
  139. package/lib/bdd-preflight.js +96 -0
  140. package/lib/merge-lock.js +111 -253
  141. package/lib/migrations/030-rejection-round-columns.js +54 -0
  142. package/lib/migrations/031-session-isolation-index.js +17 -0
  143. package/lib/work-commands/index.js +58 -16
  144. package/lib/work-tracking/index.js +108 -8
  145. package/package.json +1 -1
  146. package/skills-templates/bug-mode/SKILL.md +43 -1
  147. package/skills-templates/chore-mode/SKILL.md +40 -1
  148. package/skills-templates/design-system-selection/SKILL.md +273 -0
  149. package/skills-templates/epic-planning/SKILL.md +14 -0
  150. package/skills-templates/feature-planning/SKILL.md +90 -1
  151. package/skills-templates/production-mode/SKILL.md +20 -0
  152. package/skills-templates/simple-improvement/SKILL.md +39 -2
  153. package/skills-templates/speed-mode/SKILL.md +10 -15
  154. package/skills-templates/stable-mode/SKILL.md +47 -0
  155. package/apps/dashboard/README.md +0 -36
  156. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -446
  157. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  158. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -280
  159. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  160. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -525
  161. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  162. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  163. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  164. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  165. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  166. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  167. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  168. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  169. package/apps/dashboard/app/api/tests/route.ts +0 -9
  170. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  171. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  172. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  173. package/apps/dashboard/app/api/usage/route.ts +0 -17
  174. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  175. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  176. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  177. package/apps/dashboard/app/api/work/[id]/route.ts +0 -35
  178. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -63
  179. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  180. package/apps/dashboard/app/layout.tsx +0 -55
  181. package/apps/dashboard/components/UpgradeBanner.tsx +0 -30
  182. package/apps/dashboard/electron/ipc-handlers.js +0 -1026
  183. package/apps/dashboard/electron/main.js +0 -2306
  184. package/apps/dashboard/electron/preload.js +0 -125
  185. package/apps/dashboard/electron/session-manager.js +0 -163
  186. package/apps/dashboard/electron-builder.config.js +0 -357
  187. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  188. package/apps/dashboard/lib/backlog-parser.ts +0 -50
  189. package/apps/dashboard/lib/claude-process-manager.ts +0 -529
  190. package/apps/dashboard/lib/db-bridge.ts +0 -283
  191. package/apps/dashboard/lib/prototypes.ts +0 -202
  192. package/apps/dashboard/lib/test-results-db.ts +0 -307
  193. package/apps/dashboard/lib/tests.ts +0 -282
  194. package/apps/dashboard/next.config.js +0 -66
  195. package/apps/dashboard/postcss.config.mjs +0 -7
  196. package/apps/dashboard/public/bug-icon.svg +0 -9
  197. package/apps/dashboard/public/buoy-icon.svg +0 -9
  198. package/apps/dashboard/public/file.svg +0 -1
  199. package/apps/dashboard/public/globe.svg +0 -1
  200. package/apps/dashboard/public/in-flight-seagull.svg +0 -9
  201. package/apps/dashboard/public/next.svg +0 -1
  202. package/apps/dashboard/public/pier-icon.svg +0 -14
  203. package/apps/dashboard/public/star-icon.svg +0 -9
  204. package/apps/dashboard/public/vercel.svg +0 -1
  205. package/apps/dashboard/public/window.svg +0 -1
  206. package/apps/dashboard/public/wrench-icon.svg +0 -9
  207. package/apps/dashboard/scripts/download-node.js +0 -104
  208. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
@@ -1,280 +0,0 @@
1
- import { spawnSync } from 'child_process';
2
- import { NextRequest, NextResponse } from 'next/server';
3
- import path from 'path';
4
- import fs from 'fs';
5
- import { registerSession, appendSessionContentByWorkItem } from '@/lib/db';
6
- import {
7
- getOrCreateProcess,
8
- sendMessage as sendProcessMessage,
9
- killProcess,
10
- } from '@/lib/claude-process-manager';
11
-
12
- // Import worktree facade for worktree management
13
- // eslint-disable-next-line @typescript-eslint/no-require-imports
14
- const worktreeFacade = require('../../../../../../lib/worktree-facade');
15
-
16
- /**
17
- * Get the project root path for Claude CLI operations.
18
- * In packaged Electron apps, process.cwd() returns the app bundle's Resources directory,
19
- * so we use JETTYPOD_PROJECT_PATH env var which is set correctly by the Electron main process.
20
- */
21
- function getProjectRoot(): string {
22
- return process.env.JETTYPOD_PROJECT_PATH || path.resolve(process.cwd());
23
- }
24
-
25
- /**
26
- * Get the settings path if it exists, otherwise return undefined.
27
- * Claude CLI can run without explicit settings, so this is optional.
28
- */
29
- function getSettingsPath(projectRoot: string): string | undefined {
30
- const settingsPath = path.join(projectRoot, '.claude/settings.json');
31
- return fs.existsSync(settingsPath) ? settingsPath : undefined;
32
- }
33
-
34
- export const dynamic = 'force-dynamic';
35
-
36
- function isClaudeCliAvailable(): boolean {
37
- const result = spawnSync('which', ['claude'], { encoding: 'utf-8' });
38
- return result.status === 0 && result.stdout.trim().length > 0;
39
- }
40
-
41
- function isValidWorkItemId(id: string): boolean {
42
- const parsed = parseInt(id, 10);
43
- return !isNaN(parsed) && parsed > 0 && String(parsed) === id;
44
- }
45
-
46
- export async function POST(
47
- request: NextRequest,
48
- { params }: { params: Promise<{ workItemId: string }> }
49
- ) {
50
- const { workItemId } = await params;
51
-
52
- // Validate work item ID
53
- if (!isValidWorkItemId(workItemId)) {
54
- return NextResponse.json(
55
- { type: 'error', message: 'Invalid work item' },
56
- { status: 400 }
57
- );
58
- }
59
-
60
- // Check if Claude CLI is available
61
- if (!isClaudeCliAvailable()) {
62
- return NextResponse.json(
63
- { type: 'error', message: 'Claude CLI not found' },
64
- { status: 503 }
65
- );
66
- }
67
-
68
- // Get the work item context to build the prompt
69
- const body = await request.json().catch(() => ({}));
70
- const { title, description, type } = body;
71
-
72
- // Determine work item type label for the prompt
73
- const workItemType = type || 'chore';
74
-
75
- // Get or create worktree for this work item
76
- const workItem = {
77
- id: parseInt(workItemId, 10),
78
- title: title || `Work item ${workItemId}`
79
- };
80
-
81
- // Determine the repo path - use env var in packaged apps, fallback to cwd
82
- const repoPath = getProjectRoot();
83
-
84
- // Check for existing worktree or create one
85
- const workResult = await worktreeFacade.startWork(workItem, { repoPath });
86
-
87
- // Use worktree path if available, otherwise fall back to main repo
88
- const claudeCwd = workResult.path;
89
-
90
- // Build the prompt for Claude based on work item type
91
- const prompt = `You are working on ${workItemType} #${workItemId}: ${title || 'Unknown task'}
92
- ${description ? `\nDescription: ${description}` : ''}
93
-
94
- Please start working on this ${workItemType}. Use the appropriate tools to implement the required changes.`;
95
-
96
- // Register session in database before spawning Claude
97
- const sessionTitle = title || `Work item ${workItemId}`;
98
- registerSession(parseInt(workItemId, 10), sessionTitle);
99
-
100
- // Use claude-process-manager for proper stdout buffering and gate detection.
101
- // The inline spawn previously used here had no buffering — split JSON messages
102
- // were silently lost, and no synthetic gate events (cards) were emitted.
103
- const settingsPath = getSettingsPath(repoPath);
104
- const processSessionId = `wi-${workItemId}`;
105
- const workItemIdNum = parseInt(workItemId, 10);
106
-
107
- const processResult = getOrCreateProcess(processSessionId, claudeCwd, settingsPath);
108
-
109
- if ('error' in processResult) {
110
- return NextResponse.json(
111
- { type: 'error', message: processResult.error },
112
- { status: 503 }
113
- );
114
- }
115
-
116
- const { emitter } = processResult;
117
-
118
- // Create SSE stream from the process emitter
119
- const encoder = new TextEncoder();
120
- let cleanupStreamListeners: (() => void) | null = null;
121
-
122
- const stream = new ReadableStream({
123
- start(controller) {
124
- let assistantResponse = '';
125
- let responseComplete = false;
126
- let responseSaved = false;
127
-
128
- // Safe enqueue — prevents crashes when stream is already closed
129
- const safeEnqueue = (data: Uint8Array): boolean => {
130
- try {
131
- controller.enqueue(data);
132
- return true;
133
- } catch {
134
- if (!responseComplete) {
135
- responseComplete = true;
136
- saveAssistantResponse();
137
- emitter.off('data', onData);
138
- emitter.off('error', onError);
139
- emitter.off('close', onClose);
140
- if (heartbeatInterval) clearInterval(heartbeatInterval);
141
- }
142
- return false;
143
- }
144
- };
145
-
146
- // SSE heartbeat to prevent connection timeouts during long tool executions
147
- const heartbeatInterval = setInterval(() => {
148
- if (responseComplete) {
149
- clearInterval(heartbeatInterval);
150
- return;
151
- }
152
- safeEnqueue(encoder.encode(': heartbeat\n\n'));
153
- }, 15_000);
154
-
155
- const saveAssistantResponse = () => {
156
- if (!responseSaved && assistantResponse.trim()) {
157
- appendSessionContentByWorkItem(workItemIdNum, {
158
- role: 'assistant',
159
- content: assistantResponse.trim(),
160
- timestamp: new Date().toISOString()
161
- });
162
- responseSaved = true;
163
- }
164
- };
165
-
166
- const onData = (parsed: Record<string, unknown>) => {
167
- if (responseComplete) return;
168
-
169
- // Collect text content for session storage
170
- if (parsed.type === 'assistant' && parsed.message) {
171
- const msg = parsed.message as { content?: Array<{ type: string; text?: string; name?: string }> };
172
- if (msg.content) {
173
- for (const block of msg.content) {
174
- if (block.type === 'text' && block.text) {
175
- if (assistantResponse && !assistantResponse.endsWith('\n')) {
176
- assistantResponse += '\n\n';
177
- }
178
- assistantResponse += block.text;
179
- }
180
- }
181
- }
182
- } else if (parsed.type === 'content_block_delta') {
183
- const delta = parsed.delta as { text?: string } | undefined;
184
- if (delta?.text) {
185
- assistantResponse += delta.text;
186
- }
187
- }
188
-
189
- // Check for result message - Claude is done with this turn
190
- if (parsed.type === 'result') {
191
- responseComplete = true;
192
- clearInterval(heartbeatInterval);
193
- saveAssistantResponse();
194
-
195
- safeEnqueue(encoder.encode(`data: ${JSON.stringify(parsed)}\n\n`));
196
- safeEnqueue(encoder.encode(`data: ${JSON.stringify({ type: 'done', exitCode: 0 })}\n\n`));
197
-
198
- emitter.off('data', onData);
199
- emitter.off('error', onError);
200
- emitter.off('close', onClose);
201
- try { controller.close(); } catch { /* already closed */ }
202
- return;
203
- }
204
-
205
- safeEnqueue(encoder.encode(`data: ${JSON.stringify(parsed)}\n\n`));
206
- };
207
-
208
- const onError = (err: { type: string; content: string }) => {
209
- if (responseComplete) return;
210
- safeEnqueue(encoder.encode(`data: ${JSON.stringify({ type: 'error', content: err.content })}\n\n`));
211
- };
212
-
213
- const onClose = (info: { exitCode: number }) => {
214
- if (responseComplete) return;
215
- responseComplete = true;
216
- clearInterval(heartbeatInterval);
217
- saveAssistantResponse();
218
-
219
- safeEnqueue(encoder.encode(`data: ${JSON.stringify({ type: 'done', exitCode: info.exitCode })}\n\n`));
220
- try { controller.close(); } catch { /* already closed */ }
221
- };
222
-
223
- cleanupStreamListeners = () => {
224
- if (responseComplete) return;
225
- responseComplete = true;
226
- clearInterval(heartbeatInterval);
227
- saveAssistantResponse();
228
- emitter.off('data', onData);
229
- emitter.off('error', onError);
230
- emitter.off('close', onClose);
231
- };
232
-
233
- emitter.on('data', onData);
234
- emitter.on('error', onError);
235
- emitter.on('close', onClose);
236
-
237
- // Send the initial prompt as first message
238
- let sent = sendProcessMessage(processSessionId, prompt);
239
-
240
- // If send failed, retry with fresh process
241
- if (!sent) {
242
- killProcess(processSessionId);
243
- const retryResult = getOrCreateProcess(processSessionId, claudeCwd, settingsPath);
244
- if ('error' in retryResult) {
245
- clearInterval(heartbeatInterval);
246
- safeEnqueue(encoder.encode(`data: ${JSON.stringify({ type: 'error', content: retryResult.error })}\n\n`));
247
- safeEnqueue(encoder.encode(`data: ${JSON.stringify({ type: 'done', exitCode: 1 })}\n\n`));
248
- try { controller.close(); } catch { /* already closed */ }
249
- return;
250
- }
251
- const { emitter: newEmitter } = retryResult;
252
- emitter.off('data', onData);
253
- emitter.off('error', onError);
254
- emitter.off('close', onClose);
255
- newEmitter.on('data', onData);
256
- newEmitter.on('error', onError);
257
- newEmitter.on('close', onClose);
258
- sent = sendProcessMessage(processSessionId, prompt);
259
- }
260
-
261
- if (!sent) {
262
- clearInterval(heartbeatInterval);
263
- safeEnqueue(encoder.encode(`data: ${JSON.stringify({ type: 'error', content: 'Claude process unavailable' })}\n\n`));
264
- safeEnqueue(encoder.encode(`data: ${JSON.stringify({ type: 'done', exitCode: 1 })}\n\n`));
265
- try { controller.close(); } catch { /* already closed */ }
266
- }
267
- },
268
- cancel() {
269
- cleanupStreamListeners?.();
270
- },
271
- });
272
-
273
- return new Response(stream, {
274
- headers: {
275
- 'Content-Type': 'text/event-stream',
276
- 'Cache-Control': 'no-cache',
277
- 'Connection': 'keep-alive',
278
- },
279
- });
280
- }
@@ -1,52 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { getSessionContent, getSessionContentByWorkItem } from '@/lib/db';
3
-
4
- export const dynamic = 'force-dynamic';
5
-
6
- function isValidSessionId(id: string): boolean {
7
- const parsed = parseInt(id, 10);
8
- return !isNaN(parsed) && parsed > 0 && String(parsed) === id;
9
- }
10
-
11
- // GET /api/claude/sessions/[sessionId]/content - Get session conversation history
12
- // Use ?by=workitem to look up by work_item_id instead of session id
13
- export async function GET(
14
- request: NextRequest,
15
- { params }: { params: Promise<{ sessionId: string }> }
16
- ) {
17
- const { sessionId } = await params;
18
- const { searchParams } = new URL(request.url);
19
- const lookupBy = searchParams.get('by');
20
-
21
- if (!isValidSessionId(sessionId)) {
22
- return NextResponse.json(
23
- { error: 'Invalid session ID' },
24
- { status: 400 }
25
- );
26
- }
27
-
28
- try {
29
- const id = parseInt(sessionId, 10);
30
- const dbContent = lookupBy === 'workitem'
31
- ? getSessionContentByWorkItem(id)
32
- : getSessionContent(id);
33
-
34
- // Transform DB format (role) to frontend format (type)
35
- // DB stores: { role: 'user'|'assistant'|'error', content, timestamp }
36
- // Frontend expects: { type: 'user'|'assistant'|'error', content, timestamp }
37
- // Includes error messages persisted during conversation (#1000098, #1000099)
38
- const content = dbContent.map(msg => ({
39
- type: msg.role,
40
- content: msg.content,
41
- timestamp: msg.timestamp,
42
- }));
43
-
44
- return NextResponse.json({ content });
45
- } catch (error) {
46
- console.error('Failed to get session content:', error);
47
- return NextResponse.json(
48
- { error: 'Failed to get session content' },
49
- { status: 500 }
50
- );
51
- }
52
- }