feat: Rich hook forwarding, permission bridge, and toast notifications

Replace hardcoded PowerShell status hooks with stdin-forwarding hooks
that send full Claude Code hook data (tool_input, tool_response, prompt,
session_id, model, etc.) to /api/claude-hook endpoint.

- PowerShell hooks read stdin JSON and POST to /api/claude-hook
- Server derives status for backward-compat FAB animations
- Server extracts assistant_response from transcript on Stop events
- New /api/claude-permission endpoint with Promise-based allow/deny flow
- HookNotifications.vue: toast system showing session, prompt, tool use,
  tool results, notifications, and final assistant response
- WebSocket broadcast for claude-hook and claude-permission message types
This commit is contained in:
2026-02-15 16:16:59 -06:00
parent 4aaeb8844f
commit 816a8d9abe
8 changed files with 897 additions and 30 deletions

View File

@@ -7,6 +7,7 @@ import FloatingTerminal from './components/FloatingTerminal.vue'
import FloatingResponse from './components/FloatingResponse.vue'
import FloatingVoice from './components/FloatingVoice.vue'
import AgentBar from './components/AgentBar.vue'
import HookNotifications from './components/HookNotifications.vue'
import PwaInstallBanner from './components/PwaInstallBanner.vue'
import { initWebMCP, getWebMCP } from './services/webmcp'
import { initTorch, destroyTorch } from './services/torch'
@@ -16,6 +17,7 @@ import { setTerminalControls } from './services/tools/handlers/terminalHandlers'
import { setResponseControls } from './services/tools/handlers/responseHandlers'
import { useCanvasStore } from './stores/canvas'
import { useProjectCanvasStore } from './stores/projectCanvas'
import { useClaudeHooksStore } from './stores/claude-hooks'
const route = useRoute()
const router = useRouter()
@@ -68,6 +70,7 @@ const responseRef = ref<InstanceType<typeof FloatingResponse> | null>(null)
const voiceRef = ref<InstanceType<typeof FloatingVoice> | null>(null)
const canvasStore = useCanvasStore()
const projectCanvasStore = useProjectCanvasStore()
const hooksStore = useClaudeHooksStore()
// Voice FAB push-to-talk state
const voicePTTActive = ref(false)
@@ -231,6 +234,16 @@ function connectStatusWs() {
break
}
}
// Rich hook data → toast notifications
if (msg.type === 'claude-hook') {
hooksStore.processHook(msg)
}
// Permission request → persistent toast with allow/deny
if (msg.type === 'claude-permission') {
hooksStore.processPermission(msg)
}
} catch { /* ignore non-JSON messages */ }
}
@@ -528,6 +541,9 @@ watch(() => route.name, (newPage) => {
<!-- Floating Response (Agent UI messages) -->
<FloatingResponse ref="responseRef" />
<!-- Hook Notifications (toasts from Claude Code hooks) -->
<HookNotifications />
<!-- Floating Voice Input -->
<FloatingVoice ref="voiceRef" v-model="showVoice" />