Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions web/components/agent-visualizer/session-canvas-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { CanvasZoomControl } from './canvas-zoom-control'
import { useSessionStatsDispatch, type SessionStats } from './session-stats-provider'
import { TIMING, type SimulationEvent, type TimelineEvent } from '@/lib/agent-types'
import type { EffectToggles } from '@/hooks/use-perf-settings'
import { dlog } from '@/lib/debug-flag'

/** Stable empty-events sentinel — keeps the externalEvents prop reference
* identical across idle renders so useAgentSimulation's animate callback
Expand Down Expand Up @@ -80,6 +81,8 @@ function SessionCanvasPanelImpl({
? (log.slice(consumedRef.current, sliceEnd) as SimulationEvent[])
: EMPTY_EVENTS

dlog('panel render', sessionId.slice(0, 8), 'logLen=', log.length, 'newEvents=', newEvents.length, 'consumed=', consumedRef.current)

const sim = useSessionSimulation(manager, sessionId, {
externalEvents: newEvents,
onExternalEventsConsumed: () => { consumedRef.current = sliceEnd },
Expand Down
13 changes: 12 additions & 1 deletion web/hooks/use-vscode-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useEffect, useMemo, useState, useCallback, useRef } from 'react'
import { vscodeBridge, type ConnectionStatus, type AgentEvent, type SessionInfo } from '@/lib/vscode-bridge'
import { SimulationEvent } from '@/lib/agent-types'
import { DEBUG_EVENTS, dlog } from '@/lib/debug-flag'

interface BridgeHookResult {
isVSCode: boolean
Expand Down Expand Up @@ -153,10 +154,12 @@ export function useVSCodeBridge(): BridgeHookResult {
// selectedSessionIdRef is updated synchronously (not via React state) so it's
// always current even before React re-renders.
const unsubEvent = bridge.onEvent((event: AgentEvent) => {
dlog('event', event.type, 'sid=', (event.sessionId ?? '').slice(0, 8), 'drop=', droppingRef.current, 'selected=', selectedSessionIdRef.current === event.sessionId)
// Drop the relay's initial backlog. Each event resets the idle timer;
// once IDLE_FLUSH_MS passes quietly, the backlog has finished arriving
// and we exit drop mode for normal live handling.
if (droppingRef.current) {
dlog(' dropped (warmup)')
if (idleTimerRef.current) clearTimeout(idleTimerRef.current)
idleTimerRef.current = setTimeout(() => exitDropRef.current(), IDLE_FLUSH_MS)
return
Expand All @@ -174,6 +177,7 @@ export function useVSCodeBridge(): BridgeHookResult {
const buf = sessionEventsRef.current.get(event.sessionId) || []
buf.push(simEvent)
sessionEventsRef.current.set(event.sessionId, buf)
dlog(' buffered, log size now', buf.length)
}

// Deliver to pending if session matches (ref is always current).
Expand All @@ -184,7 +188,11 @@ export function useVSCodeBridge(): BridgeHookResult {
pendingEventsRef.current.push(simEvent)
} else if (event.sessionId && event.sessionId !== selected) {
setSessionsWithActivity(prev => {
if (prev.has(event.sessionId!)) return prev
if (prev.has(event.sessionId!)) {
dlog(' sessionsWithActivity DEDUP (already had id)')
return prev
}
dlog(' sessionsWithActivity ADD')
const next = new Set(prev)
next.add(event.sessionId!)
return next
Expand All @@ -196,6 +204,7 @@ export function useVSCodeBridge(): BridgeHookResult {
if (!rafPendingRef.current) {
rafPendingRef.current = true
rafIdRef.current = requestAnimationFrame(() => {
dlog(' rAF eventVersion bump')
rafPendingRef.current = false
rafIdRef.current = null
setEventVersion(v => v + 1)
Expand All @@ -204,6 +213,7 @@ export function useVSCodeBridge(): BridgeHookResult {

// Re-add dismissed sessions when new events arrive
if (event.sessionId && dismissedSessionsRef.current.has(event.sessionId)) {
dlog(' dismissed-session re-add for', event.sessionId.slice(0, 8))
const saved = dismissedSessionsRef.current.get(event.sessionId)
dismissedSessionsRef.current.delete(event.sessionId)
if (saved) {
Expand Down Expand Up @@ -335,6 +345,7 @@ export function useVSCodeBridge(): BridgeHookResult {
/** Flush buffered events for the selected session into pending.
* Must be called from useLayoutEffect AFTER simulation state is saved/swapped. */
const flushSessionEvents = useCallback((sessionId: string, fromIndex = 0) => {
dlog('flushSessionEvents', sessionId.slice(0, 8), 'buffered=', (sessionEventsRef.current.get(sessionId)?.length ?? 0), 'fromIndex=', fromIndex)
sessionSwitchPendingRef.current = false
const buffered = sessionEventsRef.current.get(sessionId) || []
pendingEventsRef.current.length = 0
Expand Down
29 changes: 29 additions & 0 deletions web/lib/debug-flag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Diagnostic event tracing flag for the bridge → canvas event pipeline.
*
* Read once at module load. Enable by either:
* 1. URL: ?debug=events (or any value containing the substring "events")
* 2. localStorage: localStorage.setItem('AGENT_FLOW_DEBUG', 'events')
*
* Falsy/missing means silent. The flag is cached, so toggling at runtime
* requires a page refresh.
*
* Examples:
* http://localhost:3000/?debug=events
* localStorage.setItem('AGENT_FLOW_DEBUG', 'events') // then refresh
*/
export const DEBUG_EVENTS = (() => {
if (typeof window === 'undefined') return false
try {
const url = new URLSearchParams(window.location.search).get('debug') ?? ''
if (url.includes('events')) return true
const ls = window.localStorage?.getItem('AGENT_FLOW_DEBUG') ?? ''
if (ls.includes('events')) return true
} catch { /* ignore */ }
return false
})()

/** Cheap no-op when flag is off. Prefix all output with `[af]`. */
export function dlog(...args: unknown[]): void {
if (DEBUG_EVENTS) console.log('[af]', ...args)
}