feat: Add transcript-debug page with multi-agent support, hooks approval, and message selection

- Transcript debug: JSONL viewer, parsed chat view, realtime WebSocket updates, session selector
- Multi-agent: ejecutor, nucleo000, and claude (global ~/.claude/projects/) with agent switcher
- Hooks approval: permission/plan request forwarding via PowerShell hooks, long-poll API, UI modals
- Chat features: session ID copy, select mode with checkboxes, multi-select copy, select all/deselect all
- File watchers for all agent transcript directories with polling fallback on Windows
This commit is contained in:
2026-02-18 23:55:09 -06:00
parent d0fdd04132
commit 9bd6123f97
37 changed files with 5663 additions and 30 deletions

View File

@@ -8,6 +8,8 @@ import FloatingResponse from './components/FloatingResponse.vue'
import FloatingVoice from './components/FloatingVoice.vue'
import AgentBar from './components/AgentBar.vue'
import PwaInstallBanner from './components/PwaInstallBanner.vue'
import HooksApprovalModal from './components/HooksApprovalModal.vue'
import { useGlobalApproval } from './composables/useGlobalApproval'
import { initWebMCP, getWebMCP } from './services/webmcp'
import { initTorch, destroyTorch } from './services/torch'
import { endpoints } from './config/endpoints'
@@ -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 { totalPending, modalVisible, connect: connectApproval, disconnect: disconnectApproval, fetchPending: fetchApprovalPending } = useGlobalApproval()
// Voice FAB push-to-talk state
const voicePTTActive = ref(false)
let voiceTouchStarted = false
@@ -264,6 +267,10 @@ onMounted(async () => {
// Connect to WebSocket for Claude status updates
connectStatusWs()
// Connect global hooks approval WS
connectApproval()
fetchApprovalPending()
// Fire torch connection early (don't await yet)
const torchReady = initTorch()
@@ -349,6 +356,7 @@ onMounted(async () => {
onUnmounted(() => {
destroyTorch()
disconnectApproval()
if (statusReconnectTimeout) clearTimeout(statusReconnectTimeout)
if (processingTimeout) clearTimeout(processingTimeout)
if (sessionStartTimeout) clearTimeout(sessionStartTimeout)
@@ -398,6 +406,18 @@ watch(() => route.name, (newPage) => {
<PwaInstallBanner />
</div>
<div class="header-right">
<button
v-if="totalPending > 0 || modalVisible"
class="approval-badge-btn"
:class="{ active: modalVisible, pulse: totalPending > 0 }"
@click="modalVisible = !modalVisible"
title="Hooks Approval"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
</svg>
<span v-if="totalPending > 0" class="approval-count">{{ totalPending }}</span>
</button>
<button class="refresh-btn" @click="hardRefresh" title="Hard refresh (Ctrl+F5)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
@@ -532,6 +552,9 @@ watch(() => route.name, (newPage) => {
<!-- Floating Voice Input -->
<FloatingVoice ref="voiceRef" v-model="showVoice" />
<!-- Global Hooks Approval Modal -->
<HooksApprovalModal />
<!-- Debug Console Panel -->
<Teleport to="body">
<Transition name="debug-slide">
@@ -800,6 +823,56 @@ watch(() => route.name, (newPage) => {
transform: rotate(180deg);
}
/* Approval badge button */
.approval-badge-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 3px;
width: auto;
min-width: 28px;
height: 28px;
padding: 0 6px;
background: transparent;
border: 1px solid var(--border-color);
border-radius: 5px;
color: #f59e0b;
cursor: pointer;
transition: all 0.15s ease;
position: relative;
}
.approval-badge-btn:hover {
background: var(--bg-hover);
border-color: #f59e0b;
}
.approval-badge-btn.active {
background: rgba(245, 158, 11, 0.15);
border-color: #f59e0b;
}
.approval-badge-btn.pulse {
animation: approval-badge-pulse 2s ease-in-out infinite;
}
.approval-count {
background: #ef4444;
color: white;
font-size: 9px;
font-weight: 700;
padding: 0 4px;
border-radius: 8px;
min-width: 14px;
text-align: center;
line-height: 1.4;
}
@keyframes approval-badge-pulse {
0%, 100% { box-shadow: none; }
50% { box-shadow: 0 0 8px rgba(245, 158, 11, 0.4); }
}
.app-main {
display: flex;
flex: 1;