feat: add interactive terminal and dev server management

Add new features for interactive terminal sessions and dev server control:

Terminal Component:
- New Terminal.tsx component using xterm.js for full terminal emulation
- WebSocket-based PTY communication with bidirectional I/O
- Cross-platform support (Windows via winpty, Unix via built-in pty)
- Auto-reconnection with exponential backoff
- Fix duplicate WebSocket connection bug by checking CONNECTING state
- Add manual close flag to prevent auto-reconnect race conditions
- Add project tracking to avoid duplicate connects on initial activation

Dev Server Management:
- New DevServerControl.tsx for starting/stopping dev servers
- DevServerManager service for subprocess management
- WebSocket streaming of dev server output
- Project configuration service for reading package.json scripts

Backend Infrastructure:
- Terminal router with WebSocket endpoint for PTY I/O
- DevServer router for server lifecycle management
- Terminal session manager with callback-based output streaming
- Enhanced WebSocket schemas for terminal and dev server messages

UI Integration:
- New Terminal and Dev Server tabs in the main application
- Updated DebugLogViewer with improved UI and functionality
- Extended useWebSocket hook for terminal message handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-01-12 10:35:36 +02:00
parent b1473cdfb9
commit c1985eb285
22 changed files with 3360 additions and 66 deletions

View File

@@ -13,12 +13,13 @@ import { ProgressDashboard } from './components/ProgressDashboard'
import { SetupWizard } from './components/SetupWizard'
import { AddFeatureForm } from './components/AddFeatureForm'
import { FeatureModal } from './components/FeatureModal'
import { DebugLogViewer } from './components/DebugLogViewer'
import { DebugLogViewer, type TabType } from './components/DebugLogViewer'
import { AgentThought } from './components/AgentThought'
import { AssistantFAB } from './components/AssistantFAB'
import { AssistantPanel } from './components/AssistantPanel'
import { ExpandProjectModal } from './components/ExpandProjectModal'
import { SettingsModal } from './components/SettingsModal'
import { DevServerControl } from './components/DevServerControl'
import { Loader2, Settings } from 'lucide-react'
import type { Feature } from './lib/types'
@@ -37,6 +38,7 @@ function App() {
const [setupComplete, setSetupComplete] = useState(true) // Start optimistic
const [debugOpen, setDebugOpen] = useState(false)
const [debugPanelHeight, setDebugPanelHeight] = useState(288) // Default height
const [debugActiveTab, setDebugActiveTab] = useState<TabType>('agent')
const [assistantOpen, setAssistantOpen] = useState(false)
const [showSettings, setShowSettings] = useState(false)
const [isSpecCreating, setIsSpecCreating] = useState(false)
@@ -88,6 +90,22 @@ function App() {
setDebugOpen(prev => !prev)
}
// T : Toggle terminal tab in debug panel
if (e.key === 't' || e.key === 'T') {
e.preventDefault()
if (!debugOpen) {
// If panel is closed, open it and switch to terminal tab
setDebugOpen(true)
setDebugActiveTab('terminal')
} else if (debugActiveTab === 'terminal') {
// If already on terminal tab, close the panel
setDebugOpen(false)
} else {
// If open but on different tab, switch to terminal
setDebugActiveTab('terminal')
}
}
// N : Add new feature (when project selected)
if ((e.key === 'n' || e.key === 'N') && selectedProject) {
e.preventDefault()
@@ -133,7 +151,7 @@ function App() {
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [selectedProject, showAddFeature, showExpandProject, selectedFeature, debugOpen, assistantOpen, features, showSettings, isSpecCreating])
}, [selectedProject, showAddFeature, showExpandProject, selectedFeature, debugOpen, debugActiveTab, assistantOpen, features, showSettings, isSpecCreating])
// Combine WebSocket progress with feature data
const progress = wsState.progress.total > 0 ? wsState.progress : {
@@ -178,6 +196,12 @@ function App() {
status={wsState.agentStatus}
/>
<DevServerControl
projectName={selectedProject}
status={wsState.devServerStatus}
url={wsState.devServerUrl}
/>
<button
onClick={() => setShowSettings(true)}
className="neo-btn text-sm py-2 px-3"
@@ -285,10 +309,15 @@ function App() {
{selectedProject && (
<DebugLogViewer
logs={wsState.logs}
devLogs={wsState.devLogs}
isOpen={debugOpen}
onToggle={() => setDebugOpen(!debugOpen)}
onClear={wsState.clearLogs}
onClearDevLogs={wsState.clearDevLogs}
onHeightChange={setDebugPanelHeight}
projectName={selectedProject}
activeTab={debugActiveTab}
onTabChange={setDebugActiveTab}
/>
)}