diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx index 00c1790ba..fd17d4223 100644 --- a/packages/app/src/components/agent-stream-view.tsx +++ b/packages/app/src/components/agent-stream-view.tsx @@ -264,44 +264,6 @@ const AgentStreamViewComponent = forwardRef>({}); - useEffect(() => { - const prev = debugStreamPrevRef.current; - const curr: Record = { - // handleInlinePathPress deps (line 196-205) - "hip.agent.cwd": agent.cwd, - "hip.openFileExplorer": openFileExplorer, - "hip.requestDirectoryListing": requestDirectoryListing, - "hip.resolvedServerId": resolvedServerId, - "hip.router": router, - "hip.setExplorerTabForCheckout": setExplorerTabForCheckout, - "hip.onOpenWorkspaceFile": onOpenWorkspaceFile, - "hip.workspaceId": workspaceId, - // top-level deps - handleInlinePathPress, - "agent.status": agent.status, - streamRenderStrategy, - getGapBetween, - streamItems, - "streamItems.length": streamItems.length, - streamHead, - baseRenderModel, - }; - const changed: string[] = []; - for (const key of Object.keys(curr)) { - if (!Object.is(prev[key], curr[key])) { - changed.push(key); - } - } - if (changed.length > 0 && Object.keys(prev).length > 0) { - console.log("[AgentStreamView] deps changed:", changed.join(", ")); - } - debugStreamPrevRef.current = curr; - }); - const renderStreamItemContent = useCallback( ( item: StreamItem, diff --git a/packages/app/src/components/message.tsx b/packages/app/src/components/message.tsx index 7c65e5e55..3bc5eacb3 100644 --- a/packages/app/src/components/message.tsx +++ b/packages/app/src/components/message.tsx @@ -717,13 +717,6 @@ export const AssistantMessage = memo(function AssistantMessage({ workspaceRoot, disableOuterSpacing, }: AssistantMessageProps) { - // DEBUG: log when AssistantMessage actually renders (inside memo boundary) - console.log("[AssistantMessage] render", { - messageLength: message?.length, - timestamp, - hasOnInlinePathPress: !!onInlinePathPress, - }); - const { theme, rt } = useUnistyles(); const resolvedDisableOuterSpacing = useDisableOuterSpacing(disableOuterSpacing); @@ -1765,9 +1758,6 @@ export const ToolCall = memo(function ToolCall({ onInlineDetailsHoverChange, onInlineDetailsExpandedChange, }: ToolCallProps) { - // DEBUG: log when ToolCall actually renders (inside memo boundary) - console.log("[ToolCall] render", { toolName, status }); - const { openToolCall } = useToolCallSheet(); const [isExpanded, setIsExpanded] = useState(false); diff --git a/packages/app/src/components/split-container.tsx b/packages/app/src/components/split-container.tsx index e799cdc5d..8b521358f 100644 --- a/packages/app/src/components/split-container.tsx +++ b/packages/app/src/components/split-container.tsx @@ -177,18 +177,6 @@ const MountedTabSlot = memo(function MountedTabSlot({ paneId, buildPaneContentModel, }: MountedTabSlotProps) { - useEffect(() => { - if (tabDescriptor.target.kind !== "terminal") { - return; - } - console.log("[terminal-tab-slot]", { - paneId, - tabId: tabDescriptor.tabId, - terminalId: tabDescriptor.target.terminalId, - isVisible, - isPaneFocused, - }); - }, [isPaneFocused, isVisible, paneId, tabDescriptor]); const content = useMemo( () => diff --git a/packages/app/src/hooks/use-agent-screen-state-machine.test.ts b/packages/app/src/hooks/use-agent-screen-state-machine.test.ts index 137985743..59a9d165e 100644 --- a/packages/app/src/hooks/use-agent-screen-state-machine.test.ts +++ b/packages/app/src/hooks/use-agent-screen-state-machine.test.ts @@ -9,7 +9,6 @@ import { type ReadyState = Extract; type CatchingUpSyncState = Extract; -type SyncErrorSyncState = Extract; function createAgent(id: string): Agent { const now = new Date("2026-02-19T00:00:00.000Z"); @@ -74,7 +73,6 @@ function createBaseMemory( return { hasRenderedReady: false, lastReadyAgent: null, - activeToastLatch: "none", hadInitialSyncFailure: false, ...overrides, }; @@ -96,12 +94,8 @@ function expectCatchingUpSync(state: ReadyState): CatchingUpSyncState { return state.sync; } -function expectSyncErrorSync(state: ReadyState): SyncErrorSyncState { +function expectSyncErrorSync(state: ReadyState): void { expect(state.sync.status).toBe("sync_error"); - if (state.sync.status !== "sync_error") { - throw new Error("expected sync_error sync state"); - } - return state.sync; } describe("deriveAgentScreenViewState", () => { @@ -165,10 +159,9 @@ describe("deriveAgentScreenViewState", () => { const sync = expectCatchingUpSync(ready); expect(sync.ui).toBe("overlay"); - expect(sync.shouldEmitHistoryRefreshToast).toBe(false); }); - it("uses toast catching-up state for already-hydrated agents", () => { + it("uses silent catching-up state for already-hydrated agents", () => { const memory = createBaseMemory({ hasRenderedReady: true, lastReadyAgent: createAgent("agent-1"), @@ -183,8 +176,7 @@ describe("deriveAgentScreenViewState", () => { const ready = expectReadyState(result.state); const sync = expectCatchingUpSync(ready); - expect(sync.ui).toBe("toast"); - expect(sync.shouldEmitHistoryRefreshToast).toBe(true); + expect(sync.ui).toBe("silent"); }); it("keeps sync errors non-blocking once the screen was ready", () => { @@ -200,9 +192,7 @@ describe("deriveAgentScreenViewState", () => { const result = deriveAgentScreenViewState({ input, memory }); const ready = expectReadyState(result.state); - const sync = expectSyncErrorSync(ready); - - expect(sync.shouldEmitSyncErrorToast).toBe(true); + expectSyncErrorSync(ready); }); it("remembers first-load sync failure and keeps catch-up overlay off after error clears", () => { @@ -221,9 +211,7 @@ describe("deriveAgentScreenViewState", () => { memory: initialMemory, }); const errorReady = expectReadyState(errorResult.state); - const errorSync = expectSyncErrorSync(errorReady); - - expect(errorSync.shouldEmitSyncErrorToast).toBe(true); + expectSyncErrorSync(errorReady); expect(errorResult.memory.hadInitialSyncFailure).toBe(true); const retryInput: AgentScreenMachineInput = { @@ -239,7 +227,6 @@ describe("deriveAgentScreenViewState", () => { const retrySync = expectCatchingUpSync(retryReady); expect(retrySync.ui).toBe("silent"); - expect(retrySync.shouldEmitHistoryRefreshToast).toBe(false); expect(retryResult.memory.hadInitialSyncFailure).toBe(true); }); @@ -255,35 +242,10 @@ describe("deriveAgentScreenViewState", () => { const result = deriveAgentScreenViewState({ input, memory }); const ready = expectReadyState(result.state); - const sync = expectSyncErrorSync(ready); + expectSyncErrorSync(ready); expect(ready.source).toBe("stale"); expect(ready.agent.id).toBe("agent-1"); - expect(sync.shouldEmitSyncErrorToast).toBe(true); - }); - - it("emits sync error toast only on transition into sync_error", () => { - const memory = createBaseMemory({ - hasRenderedReady: true, - lastReadyAgent: createAgent("agent-1"), - }); - const input: AgentScreenMachineInput = { - ...createBaseInput(), - missingAgentState: { kind: "error", message: "network timeout" }, - }; - - const first = deriveAgentScreenViewState({ input, memory }); - const firstReady = expectReadyState(first.state); - const firstSync = expectSyncErrorSync(firstReady); - expect(firstSync.shouldEmitSyncErrorToast).toBe(true); - - const second = deriveAgentScreenViewState({ - input, - memory: first.memory, - }); - const secondReady = expectReadyState(second.state); - const secondSync = expectSyncErrorSync(secondReady); - expect(secondSync.shouldEmitSyncErrorToast).toBe(false); }); it("returns blocking error before first paint when refresh fails", () => { @@ -484,71 +446,6 @@ describe("deriveAgentScreenViewState", () => { expect(ready.agent.status).toBe("closed"); }); - it("emits history refresh toast only on transition into toast catch-up state", () => { - const memory = createBaseMemory({ - hasRenderedReady: true, - lastReadyAgent: createAgent("agent-1"), - }); - const input: AgentScreenMachineInput = { - ...createBaseInput(), - needsAuthoritativeSync: true, - hasHydratedHistoryBefore: true, - }; - - const first = deriveAgentScreenViewState({ input, memory }); - const firstReady = expectReadyState(first.state); - const firstSync = expectCatchingUpSync(firstReady); - - expect(firstSync.ui).toBe("toast"); - expect(firstSync.shouldEmitHistoryRefreshToast).toBe(true); - - const second = deriveAgentScreenViewState({ - input, - memory: first.memory, - }); - const secondReady = expectReadyState(second.state); - const secondSync = expectCatchingUpSync(secondReady); - - expect(secondSync.ui).toBe("toast"); - expect(secondSync.shouldEmitHistoryRefreshToast).toBe(false); - }); - - it("re-arms history refresh toast after leaving and re-entering catch-up", () => { - const baseInput: AgentScreenMachineInput = { - ...createBaseInput(), - hasHydratedHistoryBefore: true, - }; - const initialMemory = createBaseMemory({ - hasRenderedReady: true, - lastReadyAgent: createAgent("agent-1"), - }); - - const firstCatchingUp = deriveAgentScreenViewState({ - input: { ...baseInput, needsAuthoritativeSync: true }, - memory: initialMemory, - }); - const firstCatchingUpReady = expectReadyState(firstCatchingUp.state); - const firstCatchingUpSync = expectCatchingUpSync(firstCatchingUpReady); - expect(firstCatchingUpSync.ui).toBe("toast"); - expect(firstCatchingUpSync.shouldEmitHistoryRefreshToast).toBe(true); - - const idle = deriveAgentScreenViewState({ - input: { ...baseInput, needsAuthoritativeSync: false }, - memory: firstCatchingUp.memory, - }); - const idleReady = expectReadyState(idle.state); - expect(idleReady.sync.status).toBe("idle"); - - const secondCatchingUp = deriveAgentScreenViewState({ - input: { ...baseInput, needsAuthoritativeSync: true }, - memory: idle.memory, - }); - const secondCatchingUpReady = expectReadyState(secondCatchingUp.state); - const secondCatchingUpSync = expectCatchingUpSync(secondCatchingUpReady); - expect(secondCatchingUpSync.ui).toBe("toast"); - expect(secondCatchingUpSync.shouldEmitHistoryRefreshToast).toBe(true); - }); - it("clears initial sync failure memory after history is hydrated", () => { const memory = createBaseMemory({ hasRenderedReady: true, @@ -565,7 +462,7 @@ describe("deriveAgentScreenViewState", () => { const ready = expectReadyState(result.state); const sync = expectCatchingUpSync(ready); - expect(sync.ui).toBe("toast"); + expect(sync.ui).toBe("silent"); expect(result.memory.hadInitialSyncFailure).toBe(false); }); }); diff --git a/packages/app/src/hooks/use-agent-screen-state-machine.ts b/packages/app/src/hooks/use-agent-screen-state-machine.ts index 2c37b70a6..5bc29306e 100644 --- a/packages/app/src/hooks/use-agent-screen-state-machine.ts +++ b/packages/app/src/hooks/use-agent-screen-state-machine.ts @@ -39,12 +39,9 @@ function shouldBlockInitialAuthoritativeReadyState(input: AgentScreenMachineInpu ); } -export type AgentScreenToastLatch = "none" | "history_refresh" | "sync_error"; - export interface AgentScreenMachineMemory { hasRenderedReady: boolean; lastReadyAgent: AgentScreenAgent | null; - activeToastLatch: AgentScreenToastLatch; hadInitialSyncFailure: boolean; } @@ -54,17 +51,8 @@ export type AgentScreenReadySyncState = | { status: "catching_up"; ui: "overlay" | "silent"; - shouldEmitHistoryRefreshToast: false; } - | { - status: "catching_up"; - ui: "toast"; - shouldEmitHistoryRefreshToast: boolean; - } - | { - status: "sync_error"; - shouldEmitSyncErrorToast: boolean; - }; + | { status: "sync_error" }; export type AgentScreenViewState = | { @@ -98,7 +86,6 @@ export function deriveAgentScreenViewState({ const nextMemory: AgentScreenMachineMemory = { hasRenderedReady: memory.hasRenderedReady, lastReadyAgent: memory.lastReadyAgent, - activeToastLatch: memory.activeToastLatch, hadInitialSyncFailure: memory.hadInitialSyncFailure, }; @@ -180,45 +167,23 @@ export function deriveAgentScreenViewState({ let sync: AgentScreenReadySyncState; if (!input.isConnected) { - nextMemory.activeToastLatch = "none"; sync = { status: "reconnecting" }; } else if (input.missingAgentState.kind === "error") { - const shouldEmitSyncErrorToast = memory.activeToastLatch !== "sync_error"; - nextMemory.activeToastLatch = "sync_error"; - sync = { - status: "sync_error", - shouldEmitSyncErrorToast, - }; + sync = { status: "sync_error" }; } else if (input.needsAuthoritativeSync || input.isHistorySyncing) { - let ui: "overlay" | "toast" | "silent"; + let ui: "overlay" | "silent"; if (input.shouldUseOptimisticStream) { ui = "silent"; } else if (input.hasHydratedHistoryBefore) { - ui = "toast"; + ui = "silent"; } else if (nextMemory.hadInitialSyncFailure) { ui = "silent"; } else { ui = "overlay"; } - if (ui === "toast") { - const shouldEmitHistoryRefreshToast = memory.activeToastLatch !== "history_refresh"; - nextMemory.activeToastLatch = "history_refresh"; - sync = { - status: "catching_up", - ui, - shouldEmitHistoryRefreshToast, - }; - } else { - nextMemory.activeToastLatch = "none"; - sync = { - status: "catching_up", - ui, - shouldEmitHistoryRefreshToast: false, - }; - } + sync = { status: "catching_up", ui }; } else { - nextMemory.activeToastLatch = "none"; sync = { status: "idle" }; } @@ -245,7 +210,6 @@ export function useAgentScreenStateMachine({ const memoryRef = useRef({ hasRenderedReady: false, lastReadyAgent: null, - activeToastLatch: "none", hadInitialSyncFailure: false, }); @@ -254,7 +218,6 @@ export function useAgentScreenStateMachine({ memoryRef.current = { hasRenderedReady: false, lastReadyAgent: null, - activeToastLatch: "none", hadInitialSyncFailure: false, }; } diff --git a/packages/app/src/hooks/use-delayed-history-refresh-toast.tsx b/packages/app/src/hooks/use-delayed-history-refresh-toast.tsx deleted file mode 100644 index 3c1d1d772..000000000 --- a/packages/app/src/hooks/use-delayed-history-refresh-toast.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useEffect, useRef, type ReactNode } from "react"; -import { ActivityIndicator } from "react-native"; -import type { ToastShowOptions } from "@/components/toast-host"; - -const HISTORY_REFRESH_TOAST_DELAY_MS = 1000; -const HISTORY_REFRESH_TOAST_DURATION_MS = 2200; - -interface UseDelayedHistoryRefreshToastParams { - isCatchingUp: boolean; - indicatorColor: string; - showToast: (content: ReactNode, options?: ToastShowOptions) => void; -} - -export function useDelayedHistoryRefreshToast({ - isCatchingUp, - indicatorColor, - showToast, -}: UseDelayedHistoryRefreshToastParams): void { - const timerRef = useRef | null>(null); - const wasCatchingUpRef = useRef(false); - const isCatchingUpRef = useRef(false); - const showToastRef = useRef(showToast); - const indicatorColorRef = useRef(indicatorColor); - - useEffect(() => { - showToastRef.current = showToast; - }, [showToast]); - - useEffect(() => { - indicatorColorRef.current = indicatorColor; - }, [indicatorColor]); - - useEffect(() => { - isCatchingUpRef.current = isCatchingUp; - - const enteredCatchUp = !wasCatchingUpRef.current && isCatchingUp; - const exitedCatchUp = wasCatchingUpRef.current && !isCatchingUp; - - if (enteredCatchUp) { - if (timerRef.current) { - clearTimeout(timerRef.current); - } - timerRef.current = setTimeout(() => { - timerRef.current = null; - if (!isCatchingUpRef.current) { - return; - } - showToastRef.current("Refreshing", { - icon: , - durationMs: HISTORY_REFRESH_TOAST_DURATION_MS, - testID: "agent-history-refresh-toast", - }); - }, HISTORY_REFRESH_TOAST_DELAY_MS); - } else if (exitedCatchUp && timerRef.current) { - clearTimeout(timerRef.current); - timerRef.current = null; - } - - wasCatchingUpRef.current = isCatchingUp; - }, [isCatchingUp]); - - useEffect(() => { - return () => { - if (timerRef.current) { - clearTimeout(timerRef.current); - timerRef.current = null; - } - }; - }, []); -} diff --git a/packages/app/src/panels/agent-panel.tsx b/packages/app/src/panels/agent-panel.tsx index 96910ffa7..debf2ff29 100644 --- a/packages/app/src/panels/agent-panel.tsx +++ b/packages/app/src/panels/agent-panel.tsx @@ -21,7 +21,6 @@ import { type AgentScreenMissingState, } from "@/hooks/use-agent-screen-state-machine"; import { useArchiveAgent } from "@/hooks/use-archive-agent"; -import { useDelayedHistoryRefreshToast } from "@/hooks/use-delayed-history-refresh-toast"; import { useAgentInputDraft } from "@/hooks/use-agent-input-draft"; import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style"; import { useStableEvent } from "@/hooks/use-stable-event"; @@ -704,27 +703,6 @@ function AgentPanelBody({ shouldUseOptimisticStream, ]); - const isHistoryRefreshCatchingUp = - viewState.tag === "ready" && - viewState.sync.status === "catching_up" && - viewState.sync.ui === "toast"; - const shouldEmitSyncErrorToast = - viewState.tag === "ready" && - viewState.sync.status === "sync_error" && - viewState.sync.shouldEmitSyncErrorToast; - - useDelayedHistoryRefreshToast({ - isCatchingUp: isHistoryRefreshCatchingUp, - indicatorColor: theme.colors.primary, - showToast: panelToast.api.show, - }); - - useEffect(() => { - if (!shouldEmitSyncErrorToast) { - return; - } - panelToast.api.error("Failed to refresh agent. Retrying in background."); - }, [panelToast.api, shouldEmitSyncErrorToast]); if (viewState.tag === "not_found") { return ( diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx index ff241f11a..d4555c4ab 100644 --- a/packages/app/src/screens/workspace/workspace-screen.tsx +++ b/packages/app/src/screens/workspace/workspace-screen.tsx @@ -1308,29 +1308,6 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) [allTabDescriptorsById, handleCloseAgentTab, handleCloseDraftOrFileTab, handleCloseTerminalTab], ); - const prevCloseTabDeps = useRef({ - allTabDescriptorsById, - handleCloseAgentTab, - handleCloseDraftOrFileTab, - handleCloseTerminalTab, - }); - useEffect(() => { - const prev = prevCloseTabDeps.current; - const changed: string[] = []; - if (prev.allTabDescriptorsById !== allTabDescriptorsById) changed.push("allTabDescriptorsById"); - if (prev.handleCloseAgentTab !== handleCloseAgentTab) changed.push("handleCloseAgentTab"); - if (prev.handleCloseDraftOrFileTab !== handleCloseDraftOrFileTab) - changed.push("handleCloseDraftOrFileTab"); - if (prev.handleCloseTerminalTab !== handleCloseTerminalTab) - changed.push("handleCloseTerminalTab"); - if (changed.length > 0) console.log("[handleCloseTabById] deps changed:", changed.join(", ")); - prevCloseTabDeps.current = { - allTabDescriptorsById, - handleCloseAgentTab, - handleCloseDraftOrFileTab, - handleCloseTerminalTab, - }; - }); const handleCopyAgentId = useCallback( async (agentId: string) => { if (!agentId) return; @@ -1795,44 +1772,6 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) retargetWorkspaceTab, ], ); - const prevBuildDeps = useRef({ - handleCloseTabById, - handleOpenFileFromChat, - focusWorkspacePane, - navigateToTabId, - normalizedServerId, - normalizedWorkspaceId, - openWorkspaceTab, - persistenceKey, - retargetWorkspaceTab, - }); - useEffect(() => { - const prev = prevBuildDeps.current; - const changed: string[] = []; - if (prev.handleCloseTabById !== handleCloseTabById) changed.push("handleCloseTabById"); - if (prev.handleOpenFileFromChat !== handleOpenFileFromChat) - changed.push("handleOpenFileFromChat"); - if (prev.focusWorkspacePane !== focusWorkspacePane) changed.push("focusWorkspacePane"); - if (prev.navigateToTabId !== navigateToTabId) changed.push("navigateToTabId"); - if (prev.normalizedServerId !== normalizedServerId) changed.push("normalizedServerId"); - if (prev.normalizedWorkspaceId !== normalizedWorkspaceId) changed.push("normalizedWorkspaceId"); - if (prev.openWorkspaceTab !== openWorkspaceTab) changed.push("openWorkspaceTab"); - if (prev.persistenceKey !== persistenceKey) changed.push("persistenceKey"); - if (prev.retargetWorkspaceTab !== retargetWorkspaceTab) changed.push("retargetWorkspaceTab"); - if (changed.length > 0) - console.log("[buildPaneContentModel] deps changed:", changed.join(", ")); - prevBuildDeps.current = { - handleCloseTabById, - handleOpenFileFromChat, - focusWorkspacePane, - navigateToTabId, - normalizedServerId, - normalizedWorkspaceId, - openWorkspaceTab, - persistenceKey, - retargetWorkspaceTab, - }; - }); const focusedPaneId = focusedPaneTabState.pane?.id ?? null; const focusedPaneTabIds = useMemo(() => tabs.map((tab) => tab.tabId), [tabs]); const focusedPaneTabDescriptorMap = useStableTabDescriptorMap(tabs); diff --git a/packages/app/src/voice/audio-engine.native.ts b/packages/app/src/voice/audio-engine.native.ts index b88344953..f58056ade 100644 --- a/packages/app/src/voice/audio-engine.native.ts +++ b/packages/app/src/voice/audio-engine.native.ts @@ -14,20 +14,6 @@ interface AudioEngineTraceOptions { traceLabel?: string; } -let nextAudioEngineInstanceId = 1; - -interface BridgeStats { - windowStartedAtMs: number; - captureEvents: number; - captureBytes: number; - volumeEvents: number; - volumeMax: number; - playbackEvents: number; - playbackInputBytes: number; - playbackResampledBytes: number; - playbackDurationMs: number; -} - function parsePcmSampleRate(mimeType: string): number | null { const match = /rate=(\d+)/i.exec(mimeType); if (!match) { @@ -85,48 +71,6 @@ export function createAudioEngine( _options?: AudioEngineTraceOptions, ): AudioEngine { const native = require("@getpaseo/expo-two-way-audio"); - const instanceId = nextAudioEngineInstanceId++; - const bridgeStats: BridgeStats = { - windowStartedAtMs: Date.now(), - captureEvents: 0, - captureBytes: 0, - volumeEvents: 0, - volumeMax: 0, - playbackEvents: 0, - playbackInputBytes: 0, - playbackResampledBytes: 0, - playbackDurationMs: 0, - }; - - const toHexPreview = (bytes: Uint8Array, count = 12): string => - Array.from(bytes.slice(0, count)) - .map((value) => value.toString(16).padStart(2, "0")) - .join(" "); - - const maybeFlushBridgeStats = (reason: string): void => { - const now = Date.now(); - const elapsedMs = now - bridgeStats.windowStartedAtMs; - if (elapsedMs < 1000) { - return; - } - console.log( - `[AudioEngine.native#${instanceId}][bridge] ${reason} ` + - `capture=${bridgeStats.captureEvents}ev/${bridgeStats.captureBytes}B ` + - `volume=${bridgeStats.volumeEvents}ev max=${bridgeStats.volumeMax.toFixed(3)} ` + - `play=${bridgeStats.playbackEvents}ev/${bridgeStats.playbackInputBytes}B->${bridgeStats.playbackResampledBytes}B ` + - `playMs=${bridgeStats.playbackDurationMs.toFixed(1)} ` + - `windowMs=${elapsedMs}`, - ); - bridgeStats.windowStartedAtMs = now; - bridgeStats.captureEvents = 0; - bridgeStats.captureBytes = 0; - bridgeStats.volumeEvents = 0; - bridgeStats.volumeMax = 0; - bridgeStats.playbackEvents = 0; - bridgeStats.playbackInputBytes = 0; - bridgeStats.playbackResampledBytes = 0; - bridgeStats.playbackDurationMs = 0; - }; const refs: { initialized: boolean; @@ -140,8 +84,6 @@ export function createAudioEngine( reject: (error: Error) => void; settled: boolean; } | null; - sawFirstMicChunk: boolean; - sawFirstVolumeEvent: boolean; destroyed: boolean; } = { initialized: false, @@ -151,8 +93,6 @@ export function createAudioEngine( processingQueue: false, playbackTimeout: null, activePlayback: null, - sawFirstMicChunk: false, - sawFirstVolumeEvent: false, destroyed: false, }; @@ -163,15 +103,6 @@ export function createAudioEngine( return; } const pcm = event.data as Uint8Array; - if (!refs.sawFirstMicChunk) { - refs.sawFirstMicChunk = true; - console.log( - `[AudioEngine.native#${instanceId}] firstMicChunk bytes=${pcm.byteLength} head=${toHexPreview(pcm)}`, - ); - } - bridgeStats.captureEvents += 1; - bridgeStats.captureBytes += pcm.byteLength; - maybeFlushBridgeStats("capture"); callbacks.onCaptureData(pcm); }, ); @@ -182,26 +113,10 @@ export function createAudioEngine( return; } const level = refs.muted ? 0 : event.data; - bridgeStats.volumeEvents += 1; - bridgeStats.volumeMax = Math.max(bridgeStats.volumeMax, level); - if (!refs.sawFirstVolumeEvent) { - refs.sawFirstVolumeEvent = true; - console.log( - `[AudioEngine.native#${instanceId}] firstInputVolume level=${level.toFixed(3)} muted=${refs.muted}`, - ); - } - maybeFlushBridgeStats("volume"); callbacks.onVolumeLevel(level); }, ); - const outputVolumeSubscription = native.addExpoTwoWayAudioEventListener( - "onOutputVolumeLevelData", - (event: any) => { - console.log(`[AudioEngine.native#${instanceId}] outputVolume=${event.data}`); - }, - ); - async function ensureInitialized(): Promise { if (refs.initialized) { return; @@ -210,20 +125,13 @@ export function createAudioEngine( if (!success) { throw new Error("expo-two-way-audio: native initialize() returned false"); } - console.log(`[AudioEngine.native#${instanceId}] initialized successfully`); refs.initialized = true; } async function ensureMicrophonePermission(): Promise { let permission = await native.getMicrophonePermissionsAsync().catch(() => null); - console.log( - `[AudioEngine.native#${instanceId}] microphonePermission initial=${permission?.status ?? "unknown"} granted=${String(permission?.granted ?? false)}`, - ); if (!permission?.granted) { permission = await native.requestMicrophonePermissionsAsync().catch(() => null); - console.log( - `[AudioEngine.native#${instanceId}] microphonePermission requested=${permission?.status ?? "unknown"} granted=${String(permission?.granted ?? false)}`, - ); } if (!permission?.granted) { throw new Error( @@ -253,17 +161,6 @@ export function createAudioEngine( // Native AudioEngine expects 16kHz PCM16 const pcm16k = resamplePcm16(pcm, inputRate, 16000); const durationSec = pcm16k.length / 2 / 16000; - bridgeStats.playbackEvents += 1; - bridgeStats.playbackInputBytes += pcm.length; - bridgeStats.playbackResampledBytes += pcm16k.length; - bridgeStats.playbackDurationMs += durationSec * 1000; - - console.log( - `[AudioEngine.native#${instanceId}] playPCMData: inputRate=${inputRate} inputBytes=${pcm.length} ` + - `resampled=${pcm16k.length} durationSec=${durationSec.toFixed(3)} ` + - `pcmHead=${toHexPreview(pcm)} resampledHead=${toHexPreview(pcm16k)}`, - ); - maybeFlushBridgeStats("play"); native.resumePlayback(); native.playPCMData(pcm16k); @@ -334,26 +231,18 @@ export function createAudioEngine( } microphoneSubscription.remove(); volumeSubscription.remove(); - outputVolumeSubscription.remove(); }, async startCapture() { if (refs.captureActive) { - console.log(`[AudioEngine.native#${instanceId}] startCapture skipped: already active`); return; } try { - console.log(`[AudioEngine.native#${instanceId}] startCapture begin`); await ensureMicrophonePermission(); await ensureInitialized(); - refs.sawFirstMicChunk = false; - refs.sawFirstVolumeEvent = false; - const isRecording = native.toggleRecording(true); + native.toggleRecording(true); refs.captureActive = true; - console.log( - `[AudioEngine.native#${instanceId}] startCapture toggleRecording(true) => ${String(isRecording)}`, - ); } catch (error) { const wrapped = error instanceof Error ? error : new Error(String(error)); callbacks.onError?.(wrapped); @@ -363,10 +252,7 @@ export function createAudioEngine( async stopCapture() { if (refs.captureActive) { - const isRecording = native.toggleRecording(false); - console.log( - `[AudioEngine.native#${instanceId}] stopCapture toggleRecording(false) => ${String(isRecording)}`, - ); + native.toggleRecording(false); } refs.captureActive = false; refs.muted = false; diff --git a/packages/app/src/voice/voice-runtime.ts b/packages/app/src/voice/voice-runtime.ts index 5b26e9a99..dd56b4712 100644 --- a/packages/app/src/voice/voice-runtime.ts +++ b/packages/app/src/voice/voice-runtime.ts @@ -3,7 +3,6 @@ import type { AgentStreamEventPayload, SessionOutboundMessage } from "@server/sh import { resolveVoiceUnavailableMessage } from "@/utils/server-info-capabilities"; import type { DaemonServerInfo } from "@/stores/session-store"; import type { AudioEngine } from "@/voice/audio-engine-types"; -import { REALTIME_VOICE_VAD_CONFIG } from "@/voice/realtime-voice-config"; import { THINKING_TONE_NATIVE_PCM_BASE64, THINKING_TONE_NATIVE_PCM_DURATION_MS, @@ -82,8 +81,6 @@ interface RuntimeState { segmentDurationTimer: ReturnType | null; lastDisplayVolumePublishMs: number; serverSpeechStartedAt: number | null; - lastNoServerSpeechLogMs: number; - localAboveThresholdActive: boolean; } type AudioOutputPayload = Extract["payload"]; @@ -120,19 +117,6 @@ interface CueState { playing: boolean; } -interface RealtimeBridgeStats { - windowStartedAtMs: number; - captureEvents: number; - captureBytes: number; - uplinkEvents: number; - uplinkRawBytes: number; - uplinkBase64Chars: number; - outputEvents: number; - outputBytes: number; - outputGroups: number; - jsLagMaxMs: number; -} - const INITIAL_SNAPSHOT: VoiceRuntimeSnapshot = { phase: "disabled", isVoiceMode: false, @@ -210,8 +194,6 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { segmentDurationTimer: null, lastDisplayVolumePublishMs: 0, serverSpeechStartedAt: null, - lastNoServerSpeechLogMs: 0, - localAboveThresholdActive: false, }; const playback: RuntimePlaybackState = { groups: new Map(), @@ -220,18 +202,6 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { processing: false, generation: 0, }; - const bridgeStats: RealtimeBridgeStats = { - windowStartedAtMs: Date.now(), - captureEvents: 0, - captureBytes: 0, - uplinkEvents: 0, - uplinkRawBytes: 0, - uplinkBase64Chars: 0, - outputEvents: 0, - outputBytes: 0, - outputGroups: 0, - jsLagMaxMs: 0, - }; const cue: CueState = { active: false, token: 0, @@ -246,41 +216,6 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { return cuePcm16.buffer.slice(cuePcm16.byteOffset, cuePcm16.byteOffset + cuePcm16.byteLength); }, }; - let lagProbeLastMs = Date.now(); - const lagProbe = setInterval(() => { - const now = Date.now(); - const lagMs = Math.max(0, now - lagProbeLastMs - 100); - lagProbeLastMs = now; - if (lagMs > bridgeStats.jsLagMaxMs) { - bridgeStats.jsLagMaxMs = lagMs; - } - }, 100); - - function flushBridgeStats(reason: string): void { - const now = Date.now(); - const elapsedMs = now - bridgeStats.windowStartedAtMs; - if (elapsedMs < 1000) { - return; - } - console.log( - `[VoiceRuntime#${instanceId}][bridge] ${reason} ` + - `capture=${bridgeStats.captureEvents}ev/${bridgeStats.captureBytes}B ` + - `uplink=${bridgeStats.uplinkEvents}ev/${bridgeStats.uplinkRawBytes}B/${bridgeStats.uplinkBase64Chars}c ` + - `output=${bridgeStats.outputEvents}ev/${bridgeStats.outputBytes}B groups=${bridgeStats.outputGroups} ` + - `jsLagMaxMs=${bridgeStats.jsLagMaxMs} windowMs=${elapsedMs}`, - ); - bridgeStats.windowStartedAtMs = now; - bridgeStats.captureEvents = 0; - bridgeStats.captureBytes = 0; - bridgeStats.uplinkEvents = 0; - bridgeStats.uplinkRawBytes = 0; - bridgeStats.uplinkBase64Chars = 0; - bridgeStats.outputEvents = 0; - bridgeStats.outputBytes = 0; - bridgeStats.outputGroups = 0; - bridgeStats.jsLagMaxMs = 0; - } - function emit(): void { for (const listener of listeners) { listener(); @@ -354,17 +289,11 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { } function resetPlaybackState(): void { - const hadGroups = playback.groups.size; playback.generation += 1; playback.groups.clear(); playback.orderedGroupIds = []; playback.activeGroupId = null; playback.processing = false; - if (hadGroups > 0) { - console.log( - `[VoiceRuntime] resetPlaybackState: cleared ${hadGroups} groups, new gen=${playback.generation}`, - ); - } } function activateNextPlaybackGroup(): void { @@ -394,15 +323,9 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { playback.processing = true; const generation = playback.generation; - console.log( - `[VoiceRuntime] processPlaybackQueue start gen=${generation} activeGroup=${playback.activeGroupId}`, - ); try { while (playback.activeGroupId) { if (generation !== playback.generation) { - console.log( - `[VoiceRuntime] processPlaybackQueue abort: generation changed ${generation} -> ${playback.generation}`, - ); return; } @@ -415,9 +338,6 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { const nextChunk = group.chunks.get(group.nextChunkToPlay); if (!nextChunk) { if (group.finalChunkIndex !== null && group.nextChunkToPlay > group.finalChunkIndex) { - console.log( - `[VoiceRuntime] group=${group.groupId} complete, played=${group.started} chunks=${group.nextChunkToPlay}`, - ); playback.groups.delete(group.groupId); if (playback.orderedGroupIds[0] === group.groupId) { playback.orderedGroupIds.shift(); @@ -432,9 +352,6 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { activateNextPlaybackGroup(); continue; } - console.log( - `[VoiceRuntime] group=${group.groupId} waiting for chunk=${group.nextChunkToPlay} (finalChunkIndex=${group.finalChunkIndex})`, - ); return; } @@ -442,39 +359,21 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { if (group.shouldPlay && !group.started && group.isVoiceMode) { group.started = true; - console.log( - `[VoiceRuntime] group=${group.groupId} first play starting at chunk=${group.nextChunkToPlay}`, - ); api.onAssistantAudioStarted(serverId); } - const playStart = Date.now(); try { if (group.shouldPlay) { await deps.engine.play(nextChunk.source); - console.log( - `[VoiceRuntime] played chunk=${group.nextChunkToPlay} id=${nextChunk.id} took=${Date.now() - playStart}ms`, - ); - } else { - console.log( - `[VoiceRuntime] SKIPPED chunk=${group.nextChunkToPlay} id=${nextChunk.id} shouldPlay=false`, - ); } } catch (error) { if (generation !== playback.generation) { - console.log(`[VoiceRuntime] play error + generation changed, aborting`); return; } - console.error( - `[VoiceRuntime] play error chunk=${group.nextChunkToPlay} took=${Date.now() - playStart}ms:`, - error, - ); + console.error(`[VoiceRuntime] play error chunk=${group.nextChunkToPlay}:`, error); } if (generation !== playback.generation) { - console.log( - `[VoiceRuntime] post-play generation changed ${generation} -> ${playback.generation}`, - ); return; } @@ -491,9 +390,6 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { if (generation === playback.generation) { playback.processing = false; } - console.log( - `[VoiceRuntime] processPlaybackQueue exit gen=${generation} currentGen=${playback.generation}`, - ); } } @@ -606,12 +502,6 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { } const base64 = Buffer.from(chunk).toString("base64"); - bridgeStats.captureEvents += 1; - bridgeStats.captureBytes += chunk.byteLength; - bridgeStats.uplinkEvents += 1; - bridgeStats.uplinkRawBytes += chunk.byteLength; - bridgeStats.uplinkBase64Chars += base64.length; - flushBridgeStats("uplink"); void activeSession.adapter.sendVoiceAudioChunk(base64, PCM_MIME_TYPE).catch((error) => { console.error(`[VoiceRuntime#${instanceId}] Failed to send audio chunk:`, error); @@ -624,8 +514,6 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { state.turnInProgress = false; state.serverSpeechDetected = false; state.lastDisplayVolumePublishMs = 0; - state.lastNoServerSpeechLogMs = 0; - state.localAboveThresholdActive = false; uploader.reset(); resetCaptureTelemetry(); patchSnapshot({ ...INITIAL_SNAPSHOT }); @@ -746,11 +634,6 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { if (!state.snapshot.isVoiceMode || state.snapshot.isMuted) { return; } - if (bridgeStats.captureEvents === 0) { - console.log( - `[VoiceRuntime#${instanceId}] firstCapturePcm bytes=${chunk.byteLength} phase=${state.snapshot.phase} transportReady=${state.transportReady}`, - ); - } uploader.pushPcmChunk(chunk); }, @@ -767,30 +650,6 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { return; } - const isActive = level > REALTIME_VOICE_VAD_CONFIG.volumeThreshold; - if (isActive && !state.localAboveThresholdActive) { - state.localAboveThresholdActive = true; - console.log( - `[VoiceRuntime#${instanceId}] localSpeechActive level=${level.toFixed(3)} threshold=${REALTIME_VOICE_VAD_CONFIG.volumeThreshold.toFixed(3)} phase=${state.snapshot.phase} transportReady=${state.transportReady}`, - ); - } - if (!isActive && state.localAboveThresholdActive) { - state.localAboveThresholdActive = false; - console.log( - `[VoiceRuntime#${instanceId}] localSpeechInactive phase=${state.snapshot.phase} serverSpeaking=${state.serverSpeechDetected}`, - ); - } - if ( - isActive && - !state.serverSpeechDetected && - nowMs - state.lastNoServerSpeechLogMs >= 1500 - ) { - state.lastNoServerSpeechLogMs = nowMs; - console.log( - `[VoiceRuntime#${instanceId}] localSpeechWithoutServerSpeech level=${level.toFixed(3)} phase=${state.snapshot.phase} turnInProgress=${state.turnInProgress} transportReady=${state.transportReady}`, - ); - } - patchTelemetry((prev) => ({ ...prev, isSpeaking: state.serverSpeechDetected, @@ -805,33 +664,15 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { !state.snapshot.isVoiceMode || !payload.isVoiceMode ) { - console.log( - `[VoiceRuntime#${instanceId}] audio_output DROPPED: activeServer=${state.snapshot.activeServerId} serverId=${serverId} isVoiceMode=${state.snapshot.isVoiceMode} payloadVoice=${payload.isVoiceMode}`, - ); return; } const groupId = payload.groupId ?? payload.id; const chunkIndex = payload.chunkIndex ?? 0; const decoded = decodeAudioChunk(payload.audio); - bridgeStats.outputEvents += 1; - bridgeStats.outputBytes += decoded.byteLength; - bridgeStats.outputGroups += playback.groups.has(groupId) ? 0 : 1; - console.log( - `[VoiceRuntime#${instanceId}] audio_output groupId=${groupId} chunk=${chunkIndex} isLast=${payload.isLastChunk} ` + - `base64Chars=${payload.audio.length} decodedBytes=${decoded.byteLength} format=${payload.format} ` + - `head=${Array.from(decoded.slice(0, 12)) - .map((value) => value.toString(16).padStart(2, "0")) - .join(" ")}`, - ); - flushBridgeStats("audio_output"); let group = playback.groups.get(groupId); if (!group) { - const shouldPlay = api.shouldPlayVoiceAudio(serverId); - console.log( - `[VoiceRuntime] new group=${groupId} shouldPlay=${shouldPlay} phase=${state.snapshot.phase} isSpeaking=${state.telemetry.isSpeaking}`, - ); group = { groupId, isVoiceMode: payload.isVoiceMode, @@ -965,7 +806,6 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { async destroy() { await this.stopVoice().catch(() => undefined); - clearInterval(lagProbe); await deps.engine.destroy(); listeners.clear(); telemetryListeners.clear(); @@ -1054,9 +894,6 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { return; } - console.log( - `[VoiceRuntime#${instanceId}] onServerSpeechStateChanged isSpeaking=${isSpeaking} phase=${state.snapshot.phase} volume=${state.telemetry.volume}`, - ); state.serverSpeechDetected = isSpeaking; state.serverSpeechStartedAt = isSpeaking ? (state.serverSpeechStartedAt ?? Date.now()) : null; if (isSpeaking) {