diff --git a/.claude-ejecutor/settings.json b/.claude-ejecutor/settings.json index 63ebf53..651a7f5 100644 --- a/.claude-ejecutor/settings.json +++ b/.claude-ejecutor/settings.json @@ -26,7 +26,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 ejecutor", + "command": "powershell -NoProfile -File hooks/notify.ps1 ejecutor", "timeout": 5000 } ] @@ -38,7 +38,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 ejecutor", + "command": "powershell -NoProfile -File hooks/notify.ps1 ejecutor", "timeout": 5000 } ] @@ -50,7 +50,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 ejecutor", + "command": "powershell -NoProfile -File hooks/notify.ps1 ejecutor", "timeout": 5000 } ] @@ -62,7 +62,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 ejecutor", + "command": "powershell -NoProfile -File hooks/notify.ps1 ejecutor", "timeout": 5000 } ] @@ -73,7 +73,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 ejecutor", + "command": "powershell -NoProfile -File hooks/notify.ps1 ejecutor", "timeout": 5000 } ] @@ -84,7 +84,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 ejecutor", + "command": "powershell -NoProfile -File hooks/notify.ps1 ejecutor", "timeout": 5000 } ] @@ -96,7 +96,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 ejecutor", + "command": "powershell -NoProfile -File hooks/notify.ps1 ejecutor", "timeout": 5000 } ] @@ -108,7 +108,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 ejecutor", + "command": "powershell -NoProfile -File hooks/notify.ps1 ejecutor", "timeout": 5000 } ] @@ -129,7 +129,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 ejecutor", + "command": "powershell -NoProfile -File hooks/notify.ps1 ejecutor", "timeout": 10000 } ] diff --git a/.claude-ejecutor/settings.local.json b/.claude-ejecutor/settings.local.json deleted file mode 100644 index d05f3e0..0000000 --- a/.claude-ejecutor/settings.local.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "hooks": { - "UserPromptSubmit": [ - { - "hooks": [ - { - "type": "command", - "command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"thinking\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"", - "timeout": 5000 - } - ] - } - ], - "PreToolUse": [ - { - "matcher": ".*", - "hooks": [ - { - "type": "command", - "command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"toolUse\\\",\\\"tool\\\":\\\"$CLAUDE_TOOL_NAME\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"", - "timeout": 5000 - } - ] - } - ], - "PostToolUse": [ - { - "matcher": ".*", - "hooks": [ - { - "type": "command", - "command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"thinking\\\",\\\"tool\\\":\\\"$CLAUDE_TOOL_NAME\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"", - "timeout": 5000 - } - ] - } - ], - "PostToolUseFailure": [ - { - "matcher": ".*", - "hooks": [ - { - "type": "command", - "command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"error\\\",\\\"tool\\\":\\\"$CLAUDE_TOOL_NAME\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"", - "timeout": 5000 - } - ] - } - ], - "SessionEnd": [ - { - "hooks": [ - { - "type": "command", - "command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"sessionEnd\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"", - "timeout": 5000 - } - ] - } - ], - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"idle\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"", - "timeout": 5000 - } - ] - } - ] - } -} diff --git a/.claude-nucleo000/settings.json b/.claude-nucleo000/settings.json index fe55590..e60e78e 100644 --- a/.claude-nucleo000/settings.json +++ b/.claude-nucleo000/settings.json @@ -12,7 +12,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 nucleo000", + "command": "powershell -NoProfile -File hooks/notify.ps1 nucleo000", "timeout": 5000 } ] @@ -24,7 +24,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 nucleo000", + "command": "powershell -NoProfile -File hooks/notify.ps1 nucleo000", "timeout": 5000 } ] @@ -36,7 +36,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 nucleo000", + "command": "powershell -NoProfile -File hooks/notify.ps1 nucleo000", "timeout": 5000 } ] @@ -48,7 +48,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 nucleo000", + "command": "powershell -NoProfile -File hooks/notify.ps1 nucleo000", "timeout": 5000 } ] @@ -59,7 +59,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 nucleo000", + "command": "powershell -NoProfile -File hooks/notify.ps1 nucleo000", "timeout": 5000 } ] @@ -70,7 +70,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 nucleo000", + "command": "powershell -NoProfile -File hooks/notify.ps1 nucleo000", "timeout": 5000 } ] @@ -82,7 +82,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 nucleo000", + "command": "powershell -NoProfile -File hooks/notify.ps1 nucleo000", "timeout": 5000 } ] @@ -94,7 +94,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 nucleo000", + "command": "powershell -NoProfile -File hooks/notify.ps1 nucleo000", "timeout": 5000 } ] @@ -115,7 +115,7 @@ "hooks": [ { "type": "command", - "command": "powershell -NoProfile -File hooks/forward-hook.ps1 nucleo000", + "command": "powershell -NoProfile -File hooks/notify.ps1 nucleo000", "timeout": 10000 } ] diff --git a/frontend/src/components/HookNotifications.vue b/frontend/src/components/HookNotifications.vue index 5347096..b688862 100644 --- a/frontend/src/components/HookNotifications.vue +++ b/frontend/src/components/HookNotifications.vue @@ -83,14 +83,8 @@ function typeClass(n: HookNotification) { {{ n.detail }} - -
- - -
- - + diff --git a/frontend/src/stores/claude-hooks.ts b/frontend/src/stores/claude-hooks.ts index 6d9fe20..6f4e3ba 100644 --- a/frontend/src/stores/claude-hooks.ts +++ b/frontend/src/stores/claude-hooks.ts @@ -1,6 +1,5 @@ import { ref, computed } from 'vue' import { defineStore } from 'pinia' -import { apiFetch } from '@/lib/tauri' export interface HookNotification { id: string @@ -11,10 +10,6 @@ export interface HookNotification { type: 'info' | 'success' | 'warning' | 'error' timestamp: number persistent?: boolean - // Permission-specific - requestId?: string - toolName?: string - toolInput?: unknown } export const useClaudeHooksStore = defineStore('claude-hooks', () => { @@ -144,47 +139,7 @@ export const useClaudeHooksStore = defineStore('claude-hooks', () => { } } - // Process a claude-permission WS message - function processPermission(data: Record) { - const id = `perm_${Date.now()}_${Math.random().toString(36).slice(2, 6)}` - const toolName = data.tool_name || 'Unknown' - const input = data.tool_input || {} - let detail = '' - if (toolName === 'Bash') { - const cmd = input.command || '' - detail = cmd.length > 120 ? cmd.slice(0, 120) + '...' : cmd - } else if (toolName === 'Edit' || toolName === 'Write') { - detail = input.file_path ? shortPath(input.file_path) : '' - } else { - detail = JSON.stringify(input).slice(0, 100) - } - - add({ - id, event: 'PermissionRequest', type: 'error', - icon: '', title: `Permission: ${toolName}`, - detail, - timestamp: Date.now(), - persistent: true, - requestId: data.requestId, - toolName, - toolInput: input - }) - } - - async function respondPermission(notifId: string, requestId: string, decision: 'allow' | 'deny') { - try { - await apiFetch('/api/claude-permission-respond', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ requestId, decision }) - }) - } catch (e) { - console.error('[Hooks] Failed to respond permission:', e) - } - remove(notifId) - } - - return { notifications, visible, add, remove, clear, processHook, processPermission, respondPermission } + return { notifications, visible, add, remove, clear, processHook } }) function shortPath(p: string): string { diff --git a/hooks/forward-hook.ps1 b/hooks/forward-hook.ps1 deleted file mode 100644 index 56a7a16..0000000 --- a/hooks/forward-hook.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -# Forward hook stdin to agent-ui backend (fire-and-forget) -# Usage: powershell -NoProfile -File hooks/forward-hook.ps1 -param([string]$agent = "ejecutor") -$b = [Console]::In.ReadToEnd() -try { - Invoke-RestMethod -Uri "http://localhost:4101/api/claude-hook?agent=$agent" -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3 | Out-Null -} catch {} diff --git a/server/routes/claude-hook.ts b/server/routes/claude-hook.ts index f1c6a2a..11d86f0 100644 --- a/server/routes/claude-hook.ts +++ b/server/routes/claude-hook.ts @@ -2,7 +2,7 @@ import { jsonResponse, errorResponse } from '../utils/cors' import { PORT_TERMINAL } from '../config' import { existsSync, readFileSync } from 'fs' import { setActiveSession, getIncrementalMessages } from '../services/transcript-engine' -import { deriveStatus, sessionState, type HookPayload } from '../services/session-state' +import { sessionState, type HookPayload } from '../services/session-state' export async function handleClaudeHook(req: Request): Promise { if (req.method !== 'POST') return null @@ -68,34 +68,7 @@ export async function handleClaudeHook(req: Request): Promise { console.error('[claude-hook] Failed to forward hook to terminal server:', e) } - // 2. Forward PermissionRequest to /claude-permission for hooks approval system - if (body.hook_event_name === 'PermissionRequest') { - try { - await fetch(`http://localhost:${PORT_TERMINAL}/claude-permission`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(hookData) - }) - } catch (e) { - console.error('[claude-hook] Failed to forward permission to terminal server:', e) - } - } - - // 3. Derive status and broadcast via WebSocket - const derived = deriveStatus(body) - if (derived) { - try { - await fetch(`http://localhost:${PORT_TERMINAL}/claude-status`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ status: derived.status, tool: derived.tool, agent }) - }) - } catch (e) { - console.error('[claude-hook] Failed to forward status to terminal server:', e) - } - } - - // 4. Incremental transcript reading for real-time chat + // 2. Incremental transcript reading for real-time chat if (body.session_id && body.transcript_path) { setActiveSession(agent, body.session_id, body.transcript_path as string) diff --git a/server/routes/claude-permission.ts b/server/routes/claude-permission.ts deleted file mode 100644 index 77b6b7b..0000000 --- a/server/routes/claude-permission.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { jsonResponse, errorResponse } from '../utils/cors' -import { PORT_TERMINAL } from '../config' - -interface PermissionPayload { - hook_event_name?: string - session_id?: string - tool_name?: string - tool_input?: unknown - cwd?: string - [key: string]: unknown -} - -interface PendingPermission { - resolve: (decision: string) => void - timer: ReturnType - payload: PermissionPayload -} - -// Map of requestId -> pending permission promise resolver -const pendingPermissions = new Map() - -const PERMISSION_TIMEOUT_MS = 60_000 - -export async function handleClaudePermission(req: Request): Promise { - if (req.method !== 'POST') return null - - try { - const body = await req.json() as PermissionPayload - const requestId = `perm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` - - // Broadcast permission request to UI via WebSocket - try { - await fetch(`http://localhost:${PORT_TERMINAL}/claude-permission`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ requestId, ...body }) - }) - } catch (e) { - console.error('[claude-permission] Failed to broadcast to terminal server:', e) - } - - // Also broadcast claude-status for backward compat (animations) - try { - await fetch(`http://localhost:${PORT_TERMINAL}/claude-status`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ status: 'permissionRequest', tool: body.tool_name }) - }) - } catch (e) { - console.error('[claude-permission] Failed to forward status:', e) - } - - // Wait for UI decision (or timeout) - const decision = await new Promise((resolve) => { - const timer = setTimeout(() => { - pendingPermissions.delete(requestId) - resolve('ask') - }, PERMISSION_TIMEOUT_MS) - - pendingPermissions.set(requestId, { resolve, timer, payload: body }) - }) - - return jsonResponse({ decision, requestId }) - } catch (e) { - return errorResponse('Invalid JSON body', 400) - } -} - -export async function handleClaudePermissionRespond(req: Request): Promise { - if (req.method !== 'POST') return null - - try { - const body = await req.json() as { requestId: string, decision: 'allow' | 'deny' } - - if (!body.requestId || !body.decision) { - return errorResponse('Missing requestId or decision', 400) - } - - if (!['allow', 'deny'].includes(body.decision)) { - return errorResponse('Decision must be "allow" or "deny"', 400) - } - - const pending = pendingPermissions.get(body.requestId) - if (!pending) { - return errorResponse('Permission request not found or already expired', 404) - } - - // Resolve the pending promise - clearTimeout(pending.timer) - pendingPermissions.delete(body.requestId) - pending.resolve(body.decision) - - return jsonResponse({ success: true, requestId: body.requestId, decision: body.decision }) - } catch (e) { - return errorResponse('Invalid JSON body', 400) - } -} - -// List pending permissions (useful for UI to recover state) -export async function handleClaudePermissionList(req: Request): Promise { - if (req.method !== 'GET') return null - - const pending = Array.from(pendingPermissions.entries()).map(([id, p]) => ({ - requestId: id, - tool_name: p.payload.tool_name, - tool_input: p.payload.tool_input, - session_id: p.payload.session_id - })) - - return jsonResponse({ pending }) -} diff --git a/server/routes/index.ts b/server/routes/index.ts index 9442625..f8e34e5 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -12,7 +12,6 @@ import { handleWhisperRoutes } from './whisper' import { handleRecordingsRoutes } from './recordings' import { handleClaudeStatus } from './claude-status' import { handleClaudeHook } from './claude-hook' -import { handleClaudePermission, handleClaudePermissionRespond, handleClaudePermissionList } from './claude-permission' import { handleSnapshots, handleSnapshotById } from './snapshots' import { handleGitStatus, handleGitDiff, handleGitLog, handleGitLogCommit, handleGitCompare, handleGitBranches, handleGitCurrentBranch, handleGitTree, handleGitFile } from './git' import { @@ -81,22 +80,6 @@ export async function handleRequest(req: Request): Promise { if (res) return res } - // Claude Code permission request/respond - if (path === '/api/claude-permission') { - if (req.method === 'GET') { - const res = await handleClaudePermissionList(req) - if (res) return res - } else { - const res = await handleClaudePermission(req) - if (res) return res - } - } - - if (path === '/api/claude-permission-respond') { - const res = await handleClaudePermissionRespond(req) - if (res) return res - } - // Components if (path === '/api/components') { const res = await handleComponents(req) diff --git a/server/services/terminal.ts b/server/services/terminal.ts index 5b00cc6..cfe70be 100644 --- a/server/services/terminal.ts +++ b/server/services/terminal.ts @@ -257,6 +257,7 @@ export async function startAgentInSession(agentId: string, force = false): Promi export function startTerminalServer() { const server = Bun.serve({ + hostname: '0.0.0.0', port: PORT_TERMINAL, async fetch(req, server) { const url = new URL(req.url) @@ -316,17 +317,6 @@ export function startTerminalServer() { } } - // Claude permission request broadcast endpoint - if (url.pathname === '/claude-permission' && req.method === 'POST') { - try { - const body = await req.json() - broadcastPermissionRequest(body) - return Response.json({ success: true }, { headers: corsHeaders }) - } catch { - return Response.json({ error: 'Invalid JSON' }, { status: 400, headers: corsHeaders }) - } - } - // Agent sessions info if (url.pathname === '/agent-sessions' && req.method === 'GET') { const result: Record = {} @@ -762,8 +752,6 @@ export function broadcastClaudeStatus(status: ClaudeStatus, tool?: string, agent console.log(`[Terminal] Claude status broadcast: ${status}${tool ? ` (${tool})` : ''} → ${clientCount} clients`) // Note: session state is updated via broadcastClaudeHook which has full payload context. - // Direct /claude-status POSTs (from ejecutor's settings.local.json) are lightweight - // and don't carry enough context to update full session state. } // Broadcast full Claude hook data to ALL clients @@ -783,18 +771,6 @@ export function broadcastClaudeHook(data: Record) { console.log(`[Terminal] Claude hook broadcast: ${data.hook_event_name || 'unknown'}${data.tool_name ? ` (${data.tool_name})` : ''} → ${clientCount} clients`) } -// Broadcast permission request to ALL clients -export function broadcastPermissionRequest(data: Record) { - const message = JSON.stringify({ - type: 'claude-permission', - ...data, - timestamp: Date.now() - }) - - const clientCount = broadcastToAll(message) - console.log(`[Terminal] Permission request broadcast: ${data.tool_name || 'unknown'} (${data.requestId}) → ${clientCount} clients`) -} - // Broadcast transcript updates to ALL clients export function broadcastTranscriptUpdate(data: Record) { const message = JSON.stringify({