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.
- package/.env +2 -1
- package/Cargo.lock +6450 -0
- package/Cargo.toml +35 -0
- package/README.md +5 -1
- package/TAURI-MIGRATION-PLAN.md +840 -0
- package/apps/dashboard/app/connect-claude/page.tsx +5 -6
- package/apps/dashboard/app/decision/[id]/page.tsx +54 -49
- package/apps/dashboard/app/demo/gates/page.tsx +3 -5
- package/apps/dashboard/app/design-system/page.tsx +1 -1
- package/apps/dashboard/app/globals.css +74 -2
- package/apps/dashboard/app/install-claude/page.tsx +3 -5
- package/apps/dashboard/app/login/page.tsx +17 -20
- package/apps/dashboard/app/page.tsx +101 -48
- package/apps/dashboard/app/settings/page.tsx +60 -12
- package/apps/dashboard/app/signup/page.tsx +14 -17
- package/apps/dashboard/app/subscribe/page.tsx +0 -2
- package/apps/dashboard/app/tests/page.tsx +37 -4
- package/apps/dashboard/app/welcome/page.tsx +12 -15
- package/apps/dashboard/app/work/[id]/page.tsx +90 -75
- package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
- package/apps/dashboard/components/AppShell.tsx +70 -61
- package/apps/dashboard/components/CardMenu.tsx +0 -1
- package/apps/dashboard/components/ClaudePanel.tsx +541 -283
- package/apps/dashboard/components/ClaudePanelInput.tsx +23 -4
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +1 -5
- package/apps/dashboard/components/CopyableId.tsx +1 -2
- package/apps/dashboard/components/DetailReviewActions.tsx +11 -20
- package/apps/dashboard/components/DragContext.tsx +132 -62
- package/apps/dashboard/components/DraggableCard.tsx +3 -5
- package/apps/dashboard/components/DropZone.tsx +5 -6
- package/apps/dashboard/components/EditableDetailDescription.tsx +6 -12
- package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
- package/apps/dashboard/components/EditableTitle.tsx +0 -1
- package/apps/dashboard/components/ElapsedTimer.tsx +15 -3
- package/apps/dashboard/components/EpicGroup.tsx +100 -70
- package/apps/dashboard/components/GateCard.tsx +0 -1
- package/apps/dashboard/components/GateChoiceCard.tsx +1 -2
- package/apps/dashboard/components/InstallClaudeScreen.tsx +1 -5
- package/apps/dashboard/components/JettyLoader.tsx +0 -1
- package/apps/dashboard/components/KanbanBoard.tsx +319 -173
- package/apps/dashboard/components/KanbanCard.tsx +341 -107
- package/apps/dashboard/components/LazyCard.tsx +62 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +0 -1
- package/apps/dashboard/components/MainNav.tsx +24 -25
- package/apps/dashboard/components/MessageBlock.tsx +93 -16
- package/apps/dashboard/components/ModeStartCard.tsx +0 -1
- package/apps/dashboard/components/OnboardingWelcome.tsx +0 -1
- package/apps/dashboard/components/PlaceholderCard.tsx +0 -1
- package/apps/dashboard/components/ProjectSwitcher.tsx +20 -20
- package/apps/dashboard/components/PrototypeTimeline.tsx +47 -26
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +308 -223
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +303 -160
- package/apps/dashboard/components/ReviewFooter.tsx +12 -14
- package/apps/dashboard/components/SessionList.tsx +0 -1
- package/apps/dashboard/components/SubscribeContent.tsx +40 -11
- package/apps/dashboard/components/TestTree.tsx +1 -2
- package/apps/dashboard/components/TipCard.tsx +2 -4
- package/apps/dashboard/components/Toast.tsx +0 -1
- package/apps/dashboard/components/TypeIcon.tsx +7 -8
- package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +5 -17
- package/apps/dashboard/components/WelcomeScreen.tsx +2 -6
- package/apps/dashboard/components/WorkItemHeader.tsx +0 -1
- package/apps/dashboard/components/WorkItemTree.tsx +2 -4
- package/apps/dashboard/components/settings/AccountSection.tsx +27 -13
- package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
- package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
- package/apps/dashboard/components/settings/EnvVarsSection.tsx +20 -73
- package/apps/dashboard/components/settings/GeneralSection.tsx +137 -26
- package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
- package/apps/dashboard/components/settings/SettingsLayout.tsx +0 -1
- package/apps/dashboard/components/ui/Button.tsx +1 -1
- package/apps/dashboard/components/ui/Input.tsx +1 -1
- package/apps/dashboard/components.json +1 -1
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +611 -358
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +0 -1
- package/apps/dashboard/contexts/UsageContext.tsx +62 -31
- package/apps/dashboard/dev.sh +35 -0
- package/apps/dashboard/eslint.config.mjs +9 -9
- package/apps/dashboard/hooks/useWebSocket.ts +138 -83
- package/apps/dashboard/index.html +73 -0
- package/apps/dashboard/lib/data-bridge.ts +722 -0
- package/apps/dashboard/lib/db.ts +69 -1302
- package/apps/dashboard/lib/environment-config.ts +173 -0
- package/apps/dashboard/lib/environment-verification.ts +119 -0
- package/apps/dashboard/lib/kanban-utils.ts +226 -26
- package/apps/dashboard/lib/proof-run.ts +495 -0
- package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
- package/apps/dashboard/lib/service-recovery.ts +326 -0
- package/apps/dashboard/lib/session-state-machine.ts +1 -0
- package/apps/dashboard/lib/session-state-utils.ts +0 -164
- package/apps/dashboard/lib/session-stream-manager.ts +253 -122
- package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
- package/apps/dashboard/lib/tauri-bridge.ts +102 -0
- package/apps/dashboard/lib/tauri.ts +106 -0
- package/apps/dashboard/lib/utils.ts +3 -3
- package/apps/dashboard/next-env.d.ts +1 -1
- package/apps/dashboard/package.json +21 -33
- package/apps/dashboard/public/bug-icon.png +0 -0
- package/apps/dashboard/public/buoy-icon.png +0 -0
- package/apps/dashboard/public/in-flight-seagull.png +0 -0
- package/apps/dashboard/public/pier-icon.png +0 -0
- package/apps/dashboard/public/star-icon.png +0 -0
- package/apps/dashboard/public/wrench-icon.png +0 -0
- package/apps/dashboard/scripts/tauri-build.js +228 -0
- package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
- package/apps/dashboard/src/main.tsx +12 -0
- package/apps/dashboard/src/router.tsx +107 -0
- package/apps/dashboard/src/vite-env.d.ts +1 -0
- package/apps/dashboard/tsconfig.json +7 -12
- package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
- package/apps/dashboard/vite.config.ts +33 -0
- package/apps/update-server/src/index.ts +167 -30
- package/claude-hooks/global-guardrails.js +14 -13
- package/crates/jettypod-cli/Cargo.toml +19 -0
- package/crates/jettypod-cli/src/commands.rs +1249 -0
- package/crates/jettypod-cli/src/main.rs +595 -0
- package/crates/jettypod-core/Cargo.toml +26 -0
- package/crates/jettypod-core/build.rs +98 -0
- package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
- package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
- package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
- package/crates/jettypod-core/src/auth.rs +294 -0
- package/crates/jettypod-core/src/config.rs +397 -0
- package/crates/jettypod-core/src/db/mod.rs +507 -0
- package/crates/jettypod-core/src/db/recovery.rs +114 -0
- package/crates/jettypod-core/src/db/startup.rs +101 -0
- package/crates/jettypod-core/src/db/validate.rs +149 -0
- package/crates/jettypod-core/src/error.rs +76 -0
- package/crates/jettypod-core/src/git.rs +458 -0
- package/crates/jettypod-core/src/lib.rs +20 -0
- package/crates/jettypod-core/src/sessions.rs +625 -0
- package/crates/jettypod-core/src/skills.rs +556 -0
- package/crates/jettypod-core/src/work.rs +1086 -0
- package/crates/jettypod-core/src/worktree.rs +628 -0
- package/crates/jettypod-core/src/ws.rs +767 -0
- package/cucumber-test.cjs +6 -0
- package/jettypod.js +96 -4
- package/lib/bdd-preflight.js +96 -0
- package/lib/merge-lock.js +111 -253
- package/lib/migrations/030-rejection-round-columns.js +54 -0
- package/lib/migrations/031-session-isolation-index.js +17 -0
- package/lib/work-commands/index.js +58 -16
- package/lib/work-tracking/index.js +108 -8
- package/package.json +1 -1
- package/skills-templates/bug-mode/SKILL.md +43 -1
- package/skills-templates/chore-mode/SKILL.md +40 -1
- package/skills-templates/design-system-selection/SKILL.md +273 -0
- package/skills-templates/epic-planning/SKILL.md +14 -0
- package/skills-templates/feature-planning/SKILL.md +90 -1
- package/skills-templates/production-mode/SKILL.md +20 -0
- package/skills-templates/simple-improvement/SKILL.md +39 -2
- package/skills-templates/speed-mode/SKILL.md +10 -15
- package/skills-templates/stable-mode/SKILL.md +47 -0
- package/apps/dashboard/README.md +0 -36
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -446
- package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -280
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -525
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
- package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
- package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
- package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
- package/apps/dashboard/app/api/kanban/route.ts +0 -15
- package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
- package/apps/dashboard/app/api/settings/general/route.ts +0 -21
- package/apps/dashboard/app/api/tests/route.ts +0 -9
- package/apps/dashboard/app/api/tests/run/route.ts +0 -82
- package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
- package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
- package/apps/dashboard/app/api/usage/route.ts +0 -17
- package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/route.ts +0 -35
- package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -63
- package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
- package/apps/dashboard/app/layout.tsx +0 -55
- package/apps/dashboard/components/UpgradeBanner.tsx +0 -30
- package/apps/dashboard/electron/ipc-handlers.js +0 -1026
- package/apps/dashboard/electron/main.js +0 -2306
- package/apps/dashboard/electron/preload.js +0 -125
- package/apps/dashboard/electron/session-manager.js +0 -163
- package/apps/dashboard/electron-builder.config.js +0 -357
- package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
- package/apps/dashboard/lib/backlog-parser.ts +0 -50
- package/apps/dashboard/lib/claude-process-manager.ts +0 -529
- package/apps/dashboard/lib/db-bridge.ts +0 -283
- package/apps/dashboard/lib/prototypes.ts +0 -202
- package/apps/dashboard/lib/test-results-db.ts +0 -307
- package/apps/dashboard/lib/tests.ts +0 -282
- package/apps/dashboard/next.config.js +0 -66
- package/apps/dashboard/postcss.config.mjs +0 -7
- package/apps/dashboard/public/bug-icon.svg +0 -9
- package/apps/dashboard/public/buoy-icon.svg +0 -9
- package/apps/dashboard/public/file.svg +0 -1
- package/apps/dashboard/public/globe.svg +0 -1
- package/apps/dashboard/public/in-flight-seagull.svg +0 -9
- package/apps/dashboard/public/next.svg +0 -1
- package/apps/dashboard/public/pier-icon.svg +0 -14
- package/apps/dashboard/public/star-icon.svg +0 -9
- package/apps/dashboard/public/vercel.svg +0 -1
- package/apps/dashboard/public/window.svg +0 -1
- package/apps/dashboard/public/wrench-icon.svg +0 -9
- package/apps/dashboard/scripts/download-node.js +0 -104
- 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
|
-
}
|