diff --git a/packages/coding-agent/src/core/session-manager.ts b/packages/coding-agent/src/core/session-manager.ts index ef319371..5d6b6c90 100644 --- a/packages/coding-agent/src/core/session-manager.ts +++ b/packages/coding-agent/src/core/session-manager.ts @@ -789,7 +789,7 @@ export class SessionManager { // Build tree for (const entry of entries) { const node = nodeMap.get(entry.id)!; - if (entry.parentId === null) { + if (entry.parentId === null || entry.parentId === entry.id) { roots.push(node); } else { const parent = nodeMap.get(entry.parentId); @@ -803,11 +803,13 @@ export class SessionManager { } // Sort children by timestamp (oldest first, newest at bottom) - const sortChildren = (node: SessionTreeNode): void => { + // Use iterative approach to avoid stack overflow on deep trees + const stack: SessionTreeNode[] = [...roots]; + while (stack.length > 0) { + const node = stack.pop()!; node.children.sort((a, b) => new Date(a.entry.timestamp).getTime() - new Date(b.entry.timestamp).getTime()); - node.children.forEach(sortChildren); - }; - roots.forEach(sortChildren); + stack.push(...node.children); + } return roots; } diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 70185bc0..b7b4853f 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -621,6 +621,9 @@ export class InteractiveMode { this.editor.onShiftTab = () => this.cycleThinkingLevel(); this.editor.onCtrlP = () => this.cycleModel("forward"); this.editor.onShiftCtrlP = () => this.cycleModel("backward"); + + // Global debug handler on TUI (works regardless of focus) + this.ui.onDebug = () => this.handleDebugCommand(); this.editor.onCtrlL = () => this.showModelSelector(); this.editor.onCtrlO = () => this.toggleToolOutputExpansion(); this.editor.onCtrlT = () => this.toggleThinkingBlockVisibility(); @@ -1645,7 +1648,7 @@ export class InteractiveMode { this.ui.requestRender(); }, ); - return { component: selector, focus: selector.getTreeList() }; + return { component: selector, focus: selector }; }); } diff --git a/packages/tui/src/index.ts b/packages/tui/src/index.ts index 71f0eca3..2c741eb8 100644 --- a/packages/tui/src/index.ts +++ b/packages/tui/src/index.ts @@ -50,6 +50,7 @@ export { isEnter, isEscape, isHome, + isShiftCtrlD, isShiftCtrlP, isShiftEnter, isShiftTab, diff --git a/packages/tui/src/keys.ts b/packages/tui/src/keys.ts index edd2c06d..12965fec 100644 --- a/packages/tui/src/keys.ts +++ b/packages/tui/src/keys.ts @@ -320,6 +320,14 @@ export function isShiftCtrlP(data: string): boolean { return matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.shift + MODIFIERS.ctrl); } +/** + * Check if input matches Shift+Ctrl+D (Kitty protocol only, for debug). + * Ignores lock key bits. + */ +export function isShiftCtrlD(data: string): boolean { + return matchesKittySequence(data, CODEPOINTS.d, MODIFIERS.shift + MODIFIERS.ctrl); +} + /** * Check if input matches Ctrl+T (raw byte or Kitty protocol). * Ignores lock key bits. diff --git a/packages/tui/src/tui.ts b/packages/tui/src/tui.ts index 1baf5e4d..583f099e 100644 --- a/packages/tui/src/tui.ts +++ b/packages/tui/src/tui.ts @@ -5,6 +5,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; +import { isShiftCtrlD } from "./keys.js"; import type { Terminal } from "./terminal.js"; import { getCapabilities, setCellDimensions } from "./terminal-image.js"; import { visibleWidth } from "./utils.js"; @@ -78,6 +79,9 @@ export class TUI extends Container { private previousLines: string[] = []; private previousWidth = 0; private focusedComponent: Component | null = null; + + /** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */ + public onDebug?: () => void; private renderRequested = false; private cursorRow = 0; // Track where cursor is (0-indexed, relative to our first line) private inputBuffer = ""; // Buffer for parsing terminal responses @@ -141,6 +145,12 @@ export class TUI extends Container { data = filtered; } + // Global debug key handler (Shift+Ctrl+D) + if (isShiftCtrlD(data) && this.onDebug) { + this.onDebug(); + return; + } + // Pass input to focused component (including Ctrl+C) // The focused component can decide how to handle Ctrl+C if (this.focusedComponent?.handleInput) {