fix: Per-agent terminal isolation, floating terminal z-index, and char-by-char input
- Add :key to PromptBar to force remount on agent switch, fixing shared terminal session bug - Raise AgentTerminal z-index above PromptBar backdrop so floating terminal is visible/clickable - Send prompt text char-by-char (15ms delay) matching FloatingVoice pattern for Claude Code compat - Guard xterm dispose against unloaded addons to prevent errors on agent switch - Widen PromptBar panel from 360px to 420px to fit all ChatInput buttons
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { jsonResponse, errorResponse } from '../utils/cors'
|
||||
import { PORT_TERMINAL } from '../config'
|
||||
import { existsSync, readFileSync } from 'fs'
|
||||
import { setActiveSession, getIncrementalMessages } from '../services/transcript-engine'
|
||||
|
||||
type ClaudeStatus = 'idle' | 'processing' | 'toolUse' | 'toolDone' | 'reading' | 'writing' | 'sessionStart' | 'subagentStart' | 'subagentStop' | 'notification' | 'permissionRequest' | 'thinking'
|
||||
|
||||
@@ -118,6 +119,30 @@ export async function handleClaudeHook(req: Request): Promise<Response | null> {
|
||||
console.error('[claude-hook] Failed to forward status to terminal server:', e)
|
||||
}
|
||||
|
||||
// 3. Incremental transcript reading for real-time chat
|
||||
if (body.session_id && body.transcript_path) {
|
||||
const agentName = agent || 'main'
|
||||
setActiveSession(agentName, body.session_id, body.transcript_path as string)
|
||||
|
||||
const newMessages = getIncrementalMessages(body.session_id, body.transcript_path as string)
|
||||
if (newMessages.length > 0) {
|
||||
try {
|
||||
await fetch(`http://localhost:${PORT_TERMINAL}/transcript-update`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
sessionId: body.session_id,
|
||||
agent: agentName,
|
||||
messages: newMessages,
|
||||
hookEvent: body.hook_event_name
|
||||
})
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('[claude-hook] Failed to forward transcript update:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return jsonResponse({ success: true, event: body.hook_event_name, agent: agent || 'main' })
|
||||
} catch (e) {
|
||||
return errorResponse('Invalid JSON body', 400)
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
handleAgentsPlugins, handleAgentsMcpJson,
|
||||
handleAgentsConfigPermissions, handleAgentsConfigHooks, handleAgentsConfigMcp
|
||||
} from './agents'
|
||||
import { handleTranscript, handleTranscriptSessions } from './transcript'
|
||||
import { handleTranscript, handleTranscriptSessions, handleTranscriptActive, handleClaudeStats, handleClaudeUsage } from './transcript'
|
||||
|
||||
export async function handleRequest(req: Request): Promise<Response> {
|
||||
const url = new URL(req.url)
|
||||
@@ -278,7 +278,21 @@ export async function handleRequest(req: Request): Promise<Response> {
|
||||
return handleGitFile(url)
|
||||
}
|
||||
|
||||
// Claude usage limits (estimated)
|
||||
if (path === '/api/claude-usage') {
|
||||
return handleClaudeUsage()
|
||||
}
|
||||
|
||||
// Claude stats (global)
|
||||
if (path === '/api/claude-stats') {
|
||||
return handleClaudeStats()
|
||||
}
|
||||
|
||||
// Transcript
|
||||
if (path === '/api/transcript/active' && req.method === 'GET') {
|
||||
return handleTranscriptActive(req, url)
|
||||
}
|
||||
|
||||
if (path === '/api/transcript/sessions' && req.method === 'GET') {
|
||||
return handleTranscriptSessions()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,44 @@
|
||||
import { jsonResponse, errorResponse } from '../utils/cors'
|
||||
import { getTranscriptAnalysis, listSessions } from '../services/transcript-engine'
|
||||
import { getTranscriptAnalysis, listSessions, getActiveSession, resetSessionOffset, getClaudeStats, getClaudeUsage } from '../services/transcript-engine'
|
||||
import type { TranscriptAnalysis } from '../services/transcript-engine'
|
||||
|
||||
export function handleClaudeUsage(): Response {
|
||||
const usage = getClaudeUsage()
|
||||
if (!usage) return errorResponse('Usage data not available', 404)
|
||||
return jsonResponse(usage)
|
||||
}
|
||||
|
||||
export function handleClaudeStats(): Response {
|
||||
const stats = getClaudeStats()
|
||||
if (!stats) return errorResponse('Stats not available', 404)
|
||||
return jsonResponse(stats)
|
||||
}
|
||||
|
||||
export function handleTranscriptSessions(): Response {
|
||||
const sessions = listSessions()
|
||||
return jsonResponse(sessions)
|
||||
}
|
||||
|
||||
export function handleTranscriptActive(req: Request, url: URL): Response {
|
||||
if (req.method !== 'GET') return errorResponse('Method not allowed', 405)
|
||||
|
||||
const agent = url.searchParams.get('agent') || 'main'
|
||||
const activeSession = getActiveSession(agent)
|
||||
|
||||
const sessionId = activeSession?.sessionId
|
||||
const analysis = getTranscriptAnalysis(sessionId, activeSession?.transcriptPath)
|
||||
|
||||
if (!analysis) return errorResponse('No active session found', 404)
|
||||
|
||||
// Reset offset so future incrementals start from here
|
||||
resetSessionOffset(analysis.sessionId)
|
||||
|
||||
return jsonResponse({
|
||||
...analysis,
|
||||
messages: analysis.messages.filter(m => !m.isMeta)
|
||||
})
|
||||
}
|
||||
|
||||
export function handleTranscript(req: Request, url: URL, sessionId: string): Response {
|
||||
if (req.method !== 'GET') return errorResponse('Method not allowed', 405)
|
||||
|
||||
@@ -63,7 +95,8 @@ function handleSection(analysis: TranscriptAnalysis, section: string): Response
|
||||
version: analysis.version,
|
||||
duration: analysis.duration,
|
||||
startTime: analysis.startTime,
|
||||
endTime: analysis.endTime
|
||||
endTime: analysis.endTime,
|
||||
lastStopReason: analysis.lastStopReason
|
||||
})
|
||||
case 'files':
|
||||
return jsonResponse({
|
||||
|
||||
Reference in New Issue
Block a user