zidane 6.2.23 → 6.2.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/{acp-B7wH6LhE.js → acp-CNZqD1Fw.js} +5 -5
  2. package/dist/{acp-B7wH6LhE.js.map → acp-CNZqD1Fw.js.map} +1 -1
  3. package/dist/acp-cli.js +4 -4
  4. package/dist/acp.d.ts +1 -1
  5. package/dist/acp.js +1 -1
  6. package/dist/{agent-BSIMsEtD.js → agent--VPMZ0nT.js} +324 -39
  7. package/dist/agent--VPMZ0nT.js.map +1 -0
  8. package/dist/agent.d.ts +1 -1
  9. package/dist/agent.js +2 -2
  10. package/dist/{auth-CpdQebUy.js → auth-DIT_DMwa.js} +2 -2
  11. package/dist/{auth-CpdQebUy.js.map → auth-DIT_DMwa.js.map} +1 -1
  12. package/dist/chat/pure.d.ts +3 -3
  13. package/dist/chat/pure.js +1 -1
  14. package/dist/chat.d.ts +4 -4
  15. package/dist/chat.d.ts.map +1 -1
  16. package/dist/chat.js +6 -6
  17. package/dist/contexts/daytona.d.ts +1 -1
  18. package/dist/contexts/daytona.d.ts.map +1 -1
  19. package/dist/contexts/daytona.js +9 -2
  20. package/dist/contexts/daytona.js.map +1 -1
  21. package/dist/contexts/docker.js +1 -1
  22. package/dist/contexts/e2b.d.ts +1 -1
  23. package/dist/contexts/e2b.d.ts.map +1 -1
  24. package/dist/contexts/e2b.js +3 -4
  25. package/dist/contexts/e2b.js.map +1 -1
  26. package/dist/{contexts-A4ep-iJs.js → contexts-0rmrBhTK.js} +8 -5
  27. package/dist/contexts-0rmrBhTK.js.map +1 -0
  28. package/dist/contexts.js +1 -1
  29. package/dist/eval.d.ts +1 -1
  30. package/dist/eval.js +2 -2
  31. package/dist/extensions.d.ts +1 -1
  32. package/dist/extensions.js +1 -1
  33. package/dist/{output-cap-kBUHBlnq.js → glob-DluQFSw1.js} +2 -50
  34. package/dist/glob-DluQFSw1.js.map +1 -0
  35. package/dist/headless.d.ts +1 -1
  36. package/dist/headless.js +4 -4
  37. package/dist/history-search-worker.js +6 -1
  38. package/dist/history-search-worker.js.map +1 -1
  39. package/dist/{index-Bv9qhj4n.d.ts → index-AVZ6pYpi.d.ts} +254 -97
  40. package/dist/index-AVZ6pYpi.d.ts.map +1 -0
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.js +12 -9
  43. package/dist/index.js.map +1 -1
  44. package/dist/{inline-autocomplete-BAwBcZtD.js → inline-autocomplete-CbcdZpfC.js} +2 -1
  45. package/dist/inline-autocomplete-CbcdZpfC.js.map +1 -0
  46. package/dist/{login-BkZ_d5sT.js → login-afiadwf0.js} +35 -19
  47. package/dist/login-afiadwf0.js.map +1 -0
  48. package/dist/mcp.d.ts +1 -1
  49. package/dist/output/stream-json.d.ts +1 -1
  50. package/dist/output/terminal.d.ts +1 -1
  51. package/dist/output-cap-DM1HrgIp.js +100 -0
  52. package/dist/output-cap-DM1HrgIp.js.map +1 -0
  53. package/dist/presets.d.ts +1 -1
  54. package/dist/presets.js +1 -1
  55. package/dist/providers/anthropic.d.ts +1 -1
  56. package/dist/providers/arcee.d.ts +1 -1
  57. package/dist/providers/baseten.d.ts +1 -1
  58. package/dist/providers/cerebras.d.ts +1 -1
  59. package/dist/providers/local.d.ts +1 -1
  60. package/dist/providers/openai-compat.d.ts +1 -1
  61. package/dist/providers/openai.d.ts +1 -1
  62. package/dist/providers/openrouter.d.ts +1 -1
  63. package/dist/providers/xai.d.ts +1 -1
  64. package/dist/providers.d.ts +1 -1
  65. package/dist/{resolve-x5mlFtLX.js → resolve-BPTA1RpX.js} +23 -3
  66. package/dist/resolve-BPTA1RpX.js.map +1 -0
  67. package/dist/restate.d.ts +1 -1
  68. package/dist/{run-summary-nD3IQ4ZQ.js → run-summary-BU_NgwUT.js} +17 -1
  69. package/dist/run-summary-BU_NgwUT.js.map +1 -0
  70. package/dist/session/sqlite.d.ts +1 -1
  71. package/dist/{session-Dbvz1ZUE.js → session-jLsc_kMH.js} +22 -3
  72. package/dist/session-jLsc_kMH.js.map +1 -0
  73. package/dist/session.d.ts +2 -2
  74. package/dist/session.js +2 -2
  75. package/dist/{skills-C1vGKbqv.js → skills-C7eIjWYm.js} +2 -2
  76. package/dist/{skills-C1vGKbqv.js.map → skills-C7eIjWYm.js.map} +1 -1
  77. package/dist/skills.d.ts +1 -1
  78. package/dist/skills.js +2 -2
  79. package/dist/{tool-formatters-D9P6Znip.d.ts → tool-formatters-Dual3Hdl.d.ts} +2 -2
  80. package/dist/{tool-formatters-D9P6Znip.d.ts.map → tool-formatters-Dual3Hdl.d.ts.map} +1 -1
  81. package/dist/tools/fetch-url.d.ts +1 -1
  82. package/dist/tools/web-search.d.ts +1 -1
  83. package/dist/tools.d.ts +1 -1
  84. package/dist/tools.js +1 -1
  85. package/dist/{transcript-anchors-Ci5888xS.d.ts → transcript-anchors-CG85bieG.d.ts} +2 -2
  86. package/dist/transcript-anchors-CG85bieG.d.ts.map +1 -0
  87. package/dist/{transcript-anchors-CqzIr74w.js → transcript-anchors-CnmxZ8QR.js} +96 -16
  88. package/dist/transcript-anchors-CnmxZ8QR.js.map +1 -0
  89. package/dist/tui.d.ts +55 -8
  90. package/dist/tui.d.ts.map +1 -1
  91. package/dist/tui.js +1845 -1145
  92. package/dist/tui.js.map +1 -1
  93. package/dist/{turn-operations-CokV32wf.js → turn-operations-4tnvKwfJ.js} +82 -14
  94. package/dist/turn-operations-4tnvKwfJ.js.map +1 -0
  95. package/dist/{turn-operations-C3y6A82G.d.ts → turn-operations-BUYaSCL3.d.ts} +3 -3
  96. package/dist/{turn-operations-C3y6A82G.d.ts.map → turn-operations-BUYaSCL3.d.ts.map} +1 -1
  97. package/dist/types-BIarq1qE.js.map +1 -1
  98. package/dist/types.d.ts +1 -1
  99. package/package.json +2 -1
  100. package/dist/agent-BSIMsEtD.js.map +0 -1
  101. package/dist/contexts-A4ep-iJs.js.map +0 -1
  102. package/dist/index-Bv9qhj4n.d.ts.map +0 -1
  103. package/dist/inline-autocomplete-BAwBcZtD.js.map +0 -1
  104. package/dist/login-BkZ_d5sT.js.map +0 -1
  105. package/dist/output-cap-kBUHBlnq.js.map +0 -1
  106. package/dist/resolve-x5mlFtLX.js.map +0 -1
  107. package/dist/run-summary-nD3IQ4ZQ.js.map +0 -1
  108. package/dist/session-Dbvz1ZUE.js.map +0 -1
  109. package/dist/transcript-anchors-Ci5888xS.d.ts.map +0 -1
  110. package/dist/transcript-anchors-CqzIr74w.js.map +0 -1
  111. package/dist/turn-operations-CokV32wf.js.map +0 -1
package/dist/tui.js CHANGED
@@ -1,25 +1,25 @@
1
- import { $ as McpAuthProvider, $t as filterChainFallbacks, A as runOAuthLogin, At as useSettings, B as mcpToolsCachePath, Ct as DEFAULT_SETTINGS, D as formatPathForCwd, Dn as accentColor, Dt as SETTINGS_TOGGLES, Et as SETTINGS_INPUTS, F as parseJsonTolerant, G as useMcpToolToggleMap, Gt as useDiscoveryOptional, Ht as createDiscoverySlot, I as parseMcpsFile, J as indexOfServerRow, Jt as resolveConfig, Kt as ConfigProvider, L as parseClaudeState, M as buildMcpServers, N as defaultMcpsConfigPaths, Nn as TODO_STATUS_GLYPHS, Nt as resolveChipColor, O as fetchOAuthRedirect, Ot as SettingsProvider, P as discoverProjectMcps, Pt as resolveTheme, Qt as chainsEqual, Sr as usageByModel, St as useEnabledToggleSet, T as suggestSafelistEntries, Tt as SETTINGS_CHOICES, U as subscribeMcpToolsCache, Un as useActiveTodos, Ut as DiscoveryProvider, V as refreshMcpToolsCatalog, Wt as useDiscovery, X as createFileMcpCredentialStore, Xt as buildRunModel, Y as parentServerName, Yt as resolveStoragePaths, Zt as chainPositionOf, _ as useSafeModeQueue, _r as sumRunCosts, _t as hintsLength, a as useSurfaces, an as tryOpenBrowser, at as InteractionsProvider, b as getSafelist, br as toolResultText, c as useStreamBuffer, cn as buildUpdateHint, cr as deriveSessionTitle, ct as createInteractionTools, d as discoverProjectSkills, dr as listSessionMeta, dt as pendingInteractionsFromTurns, en as providerKeySetChanged, er as buildBuildSystem, et as useMcpAuthDispatch, fr as loadState, g as useSafeModeActions, gr as stripSpawnTokensLine, gt as clipHintsToWidth, h as SafeModeProvider, hr as serverToolResultSummary, ht as EMPTY_HINTS, i as useSelectStyle, in as useCompletion, ir as createChatTurnsReadTool, it as ASK_USER_TOOL, j as supportsOAuth, k as oauthUsesManualCodePaste, kt as clampFps, l as buildSkillsConfig, ln as useUpdateCheck, lr as eventsFromTurns, m as writeSessionExport, mt as useInteractionsQueue, n as ThemeProvider, nr as envSection, nt as getMcpAuthStatus, or as compactSummaryEvent, ot as PRESENT_PLAN_TOOL, pr as marginTopFor, pt as useInteractionsActions, q as buildVisibleMcpRows, qt as useConfig, r as useColors, rn as toggleChainEntry, rr as chatReferenceTokensForPrompt, s as useTheme, sn as bootTick, st as buildResumedToolResultsTurn, t as computeTurnAnchors, tn as restoreChainFallbacks, tr as buildPlanSystem, tt as useMcpAuthState, u as defaultSkillScanPaths, ur as lastContextSizeFromTurns, ut as makeRequestInteraction, vn as chatAutoCompactBehavior, vt as truncateTrailing, wt as SETTINGS_CATEGORIES, x as isOnSafelist, xr as updateToolEventOutcomes, xt as listProjectFiles, y as addToSafelist, yn as discoverAgentsMd, yr as toolCallPreview, yt as buildHints, z as loadMcpToolsCache } from "./transcript-anchors-CqzIr74w.js";
2
- import { $ as composeExtensionPreset, B as ExtensionInstallError, Bt as ensureKeybindingsFile, Et as createModelSearchTool, H as installExtensionFromSpec, Ht as groupBindings, J as createExtensionHud, K as removeInstalledExtension, P as summaryToTurn, St as tailTruncate, Ut as keybindingsPath, Vt as formatBindingForDisplay, Wt as matchesBinding, X as discoverAndBuildExtensionRegistry, Y as registerExtensionHostModules, Yt as resolveExtensionKeybindings, _ as buildPostCompactAttachments, c as applyCompactSummaryCutoff, dn as sanitizeExtensionConfigActionPatch, en as inlineLoadedExtension, et as disposeExtensionRegistry, f as cleanupPersistedSession, g as resolveTasksDir, h as resolvePersistDir, hn as effectiveContextWindow, ht as isSpawnTool, it as createExtensionHostRuntimeBridge, lt as basic_default, m as resolveMcpWarningsDir, mn as OUTPUT_RESERVE_TOKENS, nn as parseExtensionScanPathEnv, on as coerceExtensionConfigInput, q as updateInstalledExtension, rn as scanExtensionDirectories, rt as partitionExtensionCatalog, t as createAgent, tt as filterExtensionRegistry, un as resolveExtensionConfigValue, wt as looksBinary, x as compactConversation, y as selectFilesFromSession, z as createExtensionPromptBridge, zt as KEYBINDING_KEY_COL_WIDTH } from "./agent-BSIMsEtD.js";
1
+ import { $ as McpAuthProvider, $t as filterChainFallbacks, A as runOAuthLogin, At as useSettings, B as mcpToolsCachePath, Bn as pruneTodosByRun, Ct as DEFAULT_SETTINGS, D as formatPathForCwd, Dn as accentColor, Dt as SETTINGS_TOGGLES, Et as SETTINGS_INPUTS, F as parseJsonTolerant, G as useMcpToolToggleMap, Gt as useDiscoveryOptional, Ht as createDiscoverySlot, I as parseMcpsFile, J as indexOfServerRow, Jt as resolveConfig, Kt as ConfigProvider, L as parseClaudeState, M as buildMcpServers, N as defaultMcpsConfigPaths, Nn as TODO_STATUS_GLYPHS, Nt as resolveChipColor, O as fetchOAuthRedirect, Ot as SettingsProvider, P as discoverProjectMcps, Pt as resolveTheme, Qt as chainsEqual, Sr as usageByModel, St as useEnabledToggleSet, T as suggestSafelistEntries, Tt as SETTINGS_CHOICES, U as subscribeMcpToolsCache, Un as useActiveTodos, Ut as DiscoveryProvider, V as refreshMcpToolsCatalog, Wt as useDiscovery, X as createFileMcpCredentialStore, Xt as buildRunModel, Y as parentServerName, Yt as resolveStoragePaths, Zt as chainPositionOf, _ as useSafeModeQueue, _r as sumRunCosts, _t as hintsLength, a as useSurfaces, an as tryOpenBrowser, at as InteractionsProvider, b as getSafelist, br as toolResultText, c as useStreamBuffer, cn as buildUpdateHint, cr as deriveSessionTitle, ct as createInteractionTools, d as discoverProjectSkills, dr as listSessionMeta, dt as pendingInteractionsFromTurns, en as providerKeySetChanged, er as buildBuildSystem, et as useMcpAuthDispatch, fr as loadState, g as useSafeModeActions, gr as stripSpawnTokensLine, gt as clipHintsToWidth, h as SafeModeProvider, hr as serverToolResultSummary, ht as EMPTY_HINTS, i as useSelectStyle, in as useCompletion, ir as createChatTurnsReadTool, it as ASK_USER_TOOL, j as supportsOAuth, k as oauthUsesManualCodePaste, kt as clampFps, l as buildSkillsConfig, ln as useUpdateCheck, lr as eventsFromTurns, m as writeSessionExport, mt as useInteractionsQueue, n as ThemeProvider, nr as envSection, nt as getMcpAuthStatus, or as compactSummaryEvent, ot as PRESENT_PLAN_TOOL, pr as marginTopFor, pt as useInteractionsActions, q as buildVisibleMcpRows, qt as useConfig, r as useColors, rn as toggleChainEntry, rr as chatReferenceTokensForPrompt, s as useTheme, sn as bootTick, st as buildResumedToolResultsTurn, t as computeTurnAnchors, tn as restoreChainFallbacks, tr as buildPlanSystem, tt as useMcpAuthState, u as defaultSkillScanPaths, ur as lastContextSizeFromTurns, ut as makeRequestInteraction, vn as chatAutoCompactBehavior, vt as truncateTrailing, wt as SETTINGS_CATEGORIES, x as isOnSafelist, xr as updateToolEventOutcomes, xt as listProjectFiles, y as addToSafelist, yn as discoverAgentsMd, yr as toolCallPreview, yt as buildHints, z as loadMcpToolsCache } from "./transcript-anchors-CnmxZ8QR.js";
2
+ import { B as createExtensionPromptBridge, Bt as KEYBINDING_KEY_COL_WIDTH, Ct as tailTruncate, Dt as createModelSearchTool, F as summaryToTurn, Gt as matchesBinding, Ht as formatBindingForDisplay, J as updateInstalledExtension, S as compactConversation, Tt as looksBinary, U as installExtensionFromSpec, Ut as groupBindings, V as ExtensionInstallError, Vt as ensureKeybindingsFile, Wt as keybindingsPath, X as registerExtensionHostModules, Xt as resolveExtensionKeybindings, Y as createExtensionHud, Z as discoverAndBuildExtensionRegistry, _ as resolveTasksDir, at as createExtensionHostRuntimeBridge, b as selectFilesFromSession, c as applyCompactSummaryCutoff, dn as resolveExtensionConfigValue, et as composeExtensionPreset, f as cleanupPersistedSession, fn as sanitizeExtensionConfigActionPatch, g as resolvePersistDir, gn as effectiveContextWindow, gt as isSpawnTool, h as resolveMcpWarningsDir, hn as OUTPUT_RESERVE_TOKENS, in as scanExtensionDirectories, it as partitionExtensionCatalog, m as rehomePersistedStubs, nt as filterExtensionRegistry, q as removeInstalledExtension, rn as parseExtensionScanPathEnv, sn as coerceExtensionConfigInput, t as createAgent, tn as inlineLoadedExtension, tt as disposeExtensionRegistry, ut as basic_default, v as buildPostCompactAttachments } from "./agent--VPMZ0nT.js";
3
3
  import { a as formatDuration, c as previewLine, i as fmtTokens, l as shortId, n as compactPath, o as formatTaskStatus, r as fmtCost, s as formatTaskSummary, t as ageString } from "./format-BF745pLL.js";
4
- import { n as appendCapped } from "./output-cap-kBUHBlnq.js";
5
- import { i as killProcessGroup, n as SUPPORTS_PROCESS_GROUPS, r as createProcessContext } from "./contexts-A4ep-iJs.js";
4
+ import { n as appendCapped } from "./output-cap-DM1HrgIp.js";
5
+ import { i as killProcessGroup, n as SUPPORTS_PROCESS_GROUPS, r as createProcessContext } from "./contexts-0rmrBhTK.js";
6
6
  import { l as errorMessage } from "./errors-DJUxZg9b.js";
7
- import { M as findGitRoot, j as projectUserPaths } from "./resolve-x5mlFtLX.js";
7
+ import { M as findGitRoot, j as projectUserPaths } from "./resolve-BPTA1RpX.js";
8
8
  import { a as createRingBufferSink, i as createLoggingHooks, l as logFileTimestamp, n as createFileSink, o as fanoutSink, r as createLogger, s as formatLogLine, t as consoleSink } from "./logger-DBX9uYOw.js";
9
9
  import { i as sniffImageMediaType } from "./media-sniff-Bn76JxAu.js";
10
10
  import { d as McpOAuthProvider, i as nonOwningMcpView, n as connectMcpServers, p as hasAuthorizationHeader } from "./mcp-H6bqyM94.js";
11
- import { n as flattenTurns, r as formatTokenUsage, t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
12
11
  import { replaceDynamicSection } from "./prompt.js";
13
- import { a as getExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, s as setExtensionSessionState, t as createSession } from "./session-Dbvz1ZUE.js";
12
+ import { a as getExtensionSessionState, c as setExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, t as createSession } from "./session-jLsc_kMH.js";
13
+ import { n as flattenTurns, r as formatTokenUsage, t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
14
14
  import { o as withTimeout } from "./timeout-BDetbuN6.js";
15
- import { a as generateStructuredData, c as shouldAutoGenerateTitle, f as commandBlockToEvent, g as parseCommandInvocation, h as executeCommand, n as patchInlineGhost, p as commandBlocksToContent, r as resolveInlineSuggestion, s as generateSessionTitle, t as createPathAutocompleteProvider } from "./inline-autocomplete-BAwBcZtD.js";
15
+ import { a as generateStructuredData, c as shouldAutoGenerateTitle, f as commandBlockToEvent, g as parseCommandInvocation, h as executeCommand, n as patchInlineGhost, p as commandBlocksToContent, r as resolveInlineSuggestion, s as generateSessionTitle, t as createPathAutocompleteProvider } from "./inline-autocomplete-CbcdZpfC.js";
16
16
  import { s as resolveCompletionSubmit } from "./completion-core-BRo86R_L.js";
17
- import { i as installSkillFromSpec, r as SkillInstallError } from "./skills-C1vGKbqv.js";
18
- import { i as ZIDANE_EXTENSION_SKILL_NAME, l as withZidaneExtensionSkill, o as extensionDirForCwd, r as EXTENSION_EDIT_METADATA_KEY, s as extensionScaffold, t as loginMcpServer } from "./login-BkZ_d5sT.js";
19
- import { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning } from "./auth-CpdQebUy.js";
17
+ import { i as installSkillFromSpec, r as SkillInstallError } from "./skills-C7eIjWYm.js";
18
+ import { i as ZIDANE_EXTENSION_SKILL_NAME, l as withZidaneExtensionSkill, o as extensionDirForCwd, r as EXTENSION_EDIT_METADATA_KEY, s as extensionScaffold, t as loginMcpServer } from "./login-afiadwf0.js";
19
+ import { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning, w as renderDefaultCallbackPage } from "./auth-DIT_DMwa.js";
20
20
  import { createDockerContext } from "./contexts/docker.js";
21
21
  import { createTuiStore } from "./session/sqlite.js";
22
- import { $ as extractEditPayload, B as isVisible, C as chunkMarkdownForRender, E as stripHtmlCommentsOutsideCode, F as BASH_CALL_ID_PREFIX, G as streamEventSearchText, H as promptHistoryFromEvents, I as EDIT_TOOL_NAMES, J as buildContextualDiff, K as turnSelectionOwnership, L as isEditErrorResult, N as blendHsl, O as createSkillsCompletionProvider, P as buildLinearRamp, R as isTurnHighlighted, T as splitMarkdownCodeBlocks, U as selectableTurnIds, V as isVisibleDuringSearch, W as selectionAfterTurnRemoval, X as clipDiffForRender, Y as buildUnifiedDiff, _ as turnContextSize, a as TOOL_DISPLAY, at as buildEditOutcomesAnnotation, b as filterModelCatalog, ct as parseEditOutcomesFromResult, d as finalizeStreamingMarkdown, dt as stripEditOutcomesAnnotation, et as filetypeFromPath, f as finalizeStreamingMarkdownForOwner, ft as summarizeOutcomes, g as revealEarlierTranscriptEvents, h as replaceTranscriptTurnEvents, ht as createChatsCompletionProvider, i as turnAsText, j as createFilesCompletionProvider, k as uniqueSkillNamesFromReferences, lt as resolveApprovalForPayload, n as deleteTurnSafely, o as displayNameFor, p as hiddenEarlierTranscriptCount, r as truncateTurnsAt, rt as summarizeEditPayload, s as formatToolCall, st as mergeApprovalAndBodyOutcomes, tt as previewEditPayload, u as capTranscriptEvents, ut as rewriteMultiEditHeader, v as splitPromptSegments, w as normalizeMarkdownCodeFences, y as buildModelCatalog, z as isUserBashEvent } from "./turn-operations-CokV32wf.js";
22
+ import { $ as extractEditPayload, B as isVisible, C as chunkMarkdownForRender, E as stripHtmlCommentsOutsideCode, F as BASH_CALL_ID_PREFIX, G as streamEventSearchText, H as promptHistoryFromEvents, I as EDIT_TOOL_NAMES, J as buildContextualDiff, K as turnSelectionOwnership, L as isEditErrorResult, N as blendHsl, O as createSkillsCompletionProvider, P as buildLinearRamp, R as isTurnHighlighted, T as splitMarkdownCodeBlocks, U as selectableTurnIds, V as isVisibleDuringSearch, W as selectionAfterTurnRemoval, X as clipDiffForRender, Y as buildUnifiedDiff, _ as turnContextSize, a as TOOL_DISPLAY, at as buildEditOutcomesAnnotation, b as filterModelCatalog, ct as parseEditOutcomesFromResult, d as finalizeStreamingMarkdown, dt as stripEditOutcomesAnnotation, et as filetypeFromPath, f as finalizeStreamingMarkdownForOwner, ft as summarizeOutcomes, g as revealEarlierTranscriptEvents, h as replaceTranscriptTurnEvents, ht as createChatsCompletionProvider, i as turnAsText, j as createFilesCompletionProvider, k as uniqueSkillNamesFromReferences, lt as resolveApprovalForPayload, n as deleteTurnSafely, o as displayNameFor, p as hiddenEarlierTranscriptCount, r as truncateTurnsAt, rt as summarizeEditPayload, s as formatToolCall, st as mergeApprovalAndBodyOutcomes, tt as previewEditPayload, u as capTranscriptEvents, ut as rewriteMultiEditHeader, v as splitPromptSegments, w as normalizeMarkdownCodeFences, y as buildModelCatalog, z as isUserBashEvent } from "./turn-operations-4tnvKwfJ.js";
23
23
  import { a as searchHighlightRanges, c as searchSessionHistoryIndexAsync, i as highlightSegments, n as buildSessionSearchIndexAsync, r as filterSessionSearchCatalog, u as searchSessionIndexAsync } from "./history-search-C4bk1Wz_.js";
24
24
  import { basename, dirname, join, relative, resolve } from "node:path";
25
25
  import { Buffer } from "node:buffer";
@@ -198,33 +198,70 @@ function claudeProjectSlug(path) {
198
198
  * `projectRoot` is derived.
199
199
  */
200
200
  function listClaudeSessionFiles(opts = {}) {
201
+ const out = [];
202
+ walkClaudeSessionFiles(opts, opts.allProjects ? (file) => out.push(file) : (file, scoped) => {
203
+ if (scoped()) out.push(file);
204
+ });
205
+ return out;
206
+ }
207
+ /**
208
+ * Detection-preview counts — scoped and all-projects figures from ONE
209
+ * directory walk (the naive two-listing approach reads every project dir
210
+ * twice). The scoped classification (with its per-file cwd peek for
211
+ * candidate dirs) runs at most once per file via the lazy `scoped()`
212
+ * thunk.
213
+ */
214
+ function countClaudeSessionFiles(opts = {}) {
215
+ let project = 0;
216
+ let all = 0;
217
+ walkClaudeSessionFiles({
218
+ ...opts,
219
+ allProjects: false
220
+ }, (_file, scoped) => {
221
+ all++;
222
+ if (scoped()) project++;
223
+ }, true);
224
+ return {
225
+ project,
226
+ all
227
+ };
228
+ }
229
+ /**
230
+ * Shared walk under `~/.claude/projects`. `visit` receives every
231
+ * transcript of every CANDIDATE dir (scoped mode) or every dir
232
+ * (`allProjects` / `visitAllDirs`), plus a lazy `scoped()` thunk that
233
+ * answers "does this file belong to the current project?" — lazy because
234
+ * the answer can cost a `transcriptProjectRoot` peek (up to the whole
235
+ * file), which pure all-projects listings never need.
236
+ */
237
+ function walkClaudeSessionFiles(opts, visit, visitAllDirs = false) {
201
238
  const { projectRoot, home } = roots(opts);
202
239
  const projectsDir = resolve(home, ".claude/projects");
203
- if (!existsSync(projectsDir)) return [];
240
+ if (!existsSync(projectsDir)) return;
204
241
  const slugs = [claudeProjectSlug(projectRoot), claudeProjectSlug(realpathOr(projectRoot))];
205
242
  const exact = new Set(slugs);
206
243
  const prefixes = [...new Set(slugs.map((s) => `${s}-`))];
207
244
  const wantedRoots = /* @__PURE__ */ new Set([projectRoot, realpathOr(projectRoot)]);
208
- const out = [];
209
245
  for (const entry of readdirSync(projectsDir, { withFileTypes: true })) {
210
246
  if (!entry.isDirectory()) continue;
211
247
  const exactMatch = exact.has(entry.name);
212
- if (!opts.allProjects && !exactMatch && !prefixes.some((p) => entry.name.startsWith(p))) continue;
248
+ const candidate = exactMatch || prefixes.some((p) => entry.name.startsWith(p));
249
+ if (!opts.allProjects && !visitAllDirs && !candidate) continue;
213
250
  const projectDir = join(projectsDir, entry.name);
214
251
  for (const file of readdirSync(projectDir)) {
215
252
  if (!file.endsWith(".jsonl")) continue;
216
253
  const path = join(projectDir, file);
217
- if (!opts.allProjects) {
254
+ const scoped = () => {
255
+ if (!candidate) return false;
218
256
  const root = transcriptProjectRoot(path);
219
- if (root === null ? !exactMatch : !(wantedRoots.has(root) || wantedRoots.has(realpathOr(root)))) continue;
220
- }
221
- out.push({
257
+ return root === null ? exactMatch : wantedRoots.has(root) || wantedRoots.has(realpathOr(root));
258
+ };
259
+ visit({
222
260
  path,
223
261
  projectDir
224
- });
262
+ }, scoped);
225
263
  }
226
264
  }
227
- return out;
228
265
  }
229
266
  /**
230
267
  * Peek a transcript's project root: scan the first chunk for the first
@@ -363,7 +400,7 @@ function convertClaudeSession(jsonl, opts = {}) {
363
400
  });
364
401
  }
365
402
  if (turns.length === 0) return null;
366
- const id = claudeSessionId ?? opts.fallbackId ?? crypto.randomUUID();
403
+ const id = opts.fallbackId ?? claudeSessionId ?? crypto.randomUUID();
367
404
  const projectRoot = cwd ? findGitRoot(cwd) ?? cwd : void 0;
368
405
  return {
369
406
  session: {
@@ -375,7 +412,8 @@ function convertClaudeSession(jsonl, opts = {}) {
375
412
  metadata: {
376
413
  ...title ? { title } : {},
377
414
  importedFrom: "claude-code",
378
- claudeSessionId: id
415
+ claudeSessionId: id,
416
+ ...claudeSessionId && claudeSessionId !== id ? { claudeLineageId: claudeSessionId } : {}
379
417
  },
380
418
  createdAt: turns[0].createdAt,
381
419
  updatedAt: turns[turns.length - 1].createdAt
@@ -483,13 +521,14 @@ async function importClaudeSessions(store, opts = {}) {
483
521
  failed: []
484
522
  };
485
523
  for (const file of listClaudeSessionFiles(opts)) try {
486
- const converted = convertClaudeSession(readFileSync(file.path, "utf8"), { fallbackId: basename(file.path, ".jsonl") });
487
- if (!converted) {
488
- result.empty++;
524
+ const fallbackId = basename(file.path, ".jsonl");
525
+ if (await store.load(fallbackId)) {
526
+ result.skipped++;
489
527
  continue;
490
528
  }
491
- if (await store.load(converted.session.id)) {
492
- result.skipped++;
529
+ const converted = convertClaudeSession(readFileSync(file.path, "utf8"), { fallbackId });
530
+ if (!converted) {
531
+ result.empty++;
493
532
  continue;
494
533
  }
495
534
  await store.save(converted.session);
@@ -519,7 +558,8 @@ function importClaudeMcps(opts = {}) {
519
558
  const result = {
520
559
  copied: [],
521
560
  skipped: [],
522
- failed: []
561
+ failed: [],
562
+ duplicates: []
523
563
  };
524
564
  const nativeNames = /* @__PURE__ */ new Set();
525
565
  for (const candidate of defaultMcpsConfigPaths({
@@ -562,7 +602,10 @@ function importClaudeMcps(opts = {}) {
562
602
  }
563
603
  const seen = /* @__PURE__ */ new Set();
564
604
  for (const { servers, scope } of sources) for (const server of servers) {
565
- if (seen.has(server.name)) continue;
605
+ if (seen.has(server.name)) {
606
+ result.duplicates.push(server.name);
607
+ continue;
608
+ }
566
609
  seen.add(server.name);
567
610
  if (nativeNames.has(server.name)) {
568
611
  result.skipped.push(server.name);
@@ -679,13 +722,28 @@ async function importFromClaudeCode(store, opts = {}) {
679
722
  if (opts.skills !== false) summary.skills = importClaudeSkills(opts);
680
723
  return summary;
681
724
  }
725
+ /**
726
+ * Per-catalog fzf instance, keyed on catalog reference identity —
727
+ * `suggest()` runs per keystroke and `new Fzf(...)` walks the whole
728
+ * command list to build its match structures. The getter returns a
729
+ * stable array until a registry rebuild swaps it.
730
+ */
731
+ const fzfByCatalog = /* @__PURE__ */ new WeakMap();
732
+ function fzfFor(catalog) {
733
+ let fzf = fzfByCatalog.get(catalog);
734
+ if (!fzf) {
735
+ fzf = new Fzf(catalog, {
736
+ selector: (c) => c.name,
737
+ casing: "case-insensitive"
738
+ });
739
+ fzfByCatalog.set(catalog, fzf);
740
+ }
741
+ return fzf;
742
+ }
682
743
  /** Filter + rank commands against a query (name-priority, then description, then fuzzy on name). */
683
744
  function scoreCommands(catalog, query) {
684
745
  const q = query.trim().toLowerCase();
685
- const fuzzyHits = q.length > 0 ? new Set(new Fzf([...catalog], {
686
- selector: (c) => c.name,
687
- casing: "case-insensitive"
688
- }).find(q).map((r) => r.item)) : null;
746
+ const fuzzyHits = q.length > 0 ? new Set(fzfFor(catalog).find(q).map((r) => r.item)) : null;
689
747
  const scored = [];
690
748
  for (const command of catalog) {
691
749
  const name = command.name.toLowerCase();
@@ -805,12 +863,252 @@ function attachMcpWarnLog(hooks, opts) {
805
863
  } };
806
864
  }
807
865
  //#endregion
866
+ //#region src/tui/clickable.tsx
867
+ /** Glyph / word aliases → OpenTUI key names (`parse.keypress` conventions). */
868
+ const KEY_NAME_ALIASES = {
869
+ "esc": "escape",
870
+ "↵": "return",
871
+ "enter": "return",
872
+ "tab": "tab",
873
+ "space": "space",
874
+ "⌫": "backspace",
875
+ "backspace": "backspace",
876
+ "pgup": "pageup",
877
+ "pgdn": "pagedown",
878
+ "↑": "up",
879
+ "↓": "down",
880
+ "←": "left",
881
+ "→": "right",
882
+ "↑↓": "down",
883
+ "←→": "right",
884
+ "del": "delete"
885
+ };
886
+ /** Decorative pseudo-keys that must never dispatch (chips, markers). */
887
+ const NON_KEYS = /* @__PURE__ */ new Set([
888
+ "✦",
889
+ "·",
890
+ "…"
891
+ ]);
892
+ /** Mac-style modifier glyphs → word modifiers (`⌘k`, `⌘+k`, `⇧↵`, …). */
893
+ const GLYPH_MODIFIERS = {
894
+ "⌘": "meta",
895
+ "⌃": "ctrl",
896
+ "⌥": "option",
897
+ "⇧": "shift"
898
+ };
899
+ /**
900
+ * Parse a hint key spec (`ctrl+g`, `shift+↵`, `esc`, `a`, `↑↓`) into a
901
+ * dispatchable {@link ParsedKey}, or `null` when the spec is decorative
902
+ * or ambiguous (the hint then renders non-clickable). Modifier segments
903
+ * are order-insensitive; the final segment is the key itself.
904
+ */
905
+ function parseHintKeySpec(spec) {
906
+ const trimmed = spec.trim().toLowerCase();
907
+ if (!trimmed || NON_KEYS.has(trimmed)) return null;
908
+ let first = trimmed.split("/")[0] ?? "";
909
+ first = first.replace(/[⌘⌃⌥⇧]/g, (glyph) => `${GLYPH_MODIFIERS[glyph]}+`).replace(/\+\+/g, "+");
910
+ if (first.startsWith("^") && first.length > 1) first = `ctrl+${first.slice(1)}`;
911
+ const segments = first.split("+").map((s) => s.trim()).filter(Boolean);
912
+ if (segments.length === 0) return null;
913
+ const keyPart = segments[segments.length - 1] ?? "";
914
+ const modifiers = segments.slice(0, -1);
915
+ const mods = {
916
+ ctrl: false,
917
+ meta: false,
918
+ shift: false,
919
+ option: false
920
+ };
921
+ for (const mod of modifiers) if (mod === "ctrl" || mod === "control") mods.ctrl = true;
922
+ else if (mod === "shift") mods.shift = true;
923
+ else if (mod === "alt" || mod === "option") mods.option = true;
924
+ else if (mod === "meta" || mod === "cmd" || mod === "super") mods.meta = true;
925
+ else return null;
926
+ const name = KEY_NAME_ALIASES[keyPart] ?? (/^f(?:[1-9]|1[0-2])$/.test(keyPart) ? keyPart : null) ?? (keyPart.length === 1 ? keyPart : null);
927
+ if (!name) return null;
928
+ const sequence = keySequence(name, mods);
929
+ return {
930
+ name,
931
+ ...mods,
932
+ sequence,
933
+ number: /^\d$/.test(name),
934
+ raw: sequence,
935
+ eventType: "press",
936
+ source: "raw"
937
+ };
938
+ }
939
+ /** Byte sequences real terminals send for the named specials we synthesize. */
940
+ const NAMED_KEY_SEQUENCES = {
941
+ return: "\r",
942
+ tab: " ",
943
+ escape: "\x1B",
944
+ space: " ",
945
+ backspace: "",
946
+ delete: "\x1B[3~",
947
+ up: "\x1B[A",
948
+ down: "\x1B[B",
949
+ right: "\x1B[C",
950
+ left: "\x1B[D",
951
+ pageup: "\x1B[5~",
952
+ pagedown: "\x1B[6~"
953
+ };
954
+ /**
955
+ * The `sequence`/`raw` a REAL keypress would carry — without it the
956
+ * synthetic event is second-class: focused inputs insert text from
957
+ * `key.sequence` (so a clicked `@ files` hint must actually type `@`),
958
+ * and some handlers match on `sequence` rather than `name`. Modifier
959
+ * chords return `''`: their handlers dispatch on `name` + flags, and
960
+ * fabricating control bytes would risk double interpretation.
961
+ */
962
+ function keySequence(name, mods) {
963
+ if (mods.ctrl || mods.meta || mods.option) return "";
964
+ if (mods.shift) return name === "tab" ? "\x1B[Z" : "";
965
+ if (name.length === 1) return name;
966
+ return NAMED_KEY_SEQUENCES[name] ?? "";
967
+ }
968
+ /**
969
+ * Dispatch a hint key spec through the renderer's key pipeline. Returns
970
+ * `false` for unparseable specs (no-op). Exposed for non-hint surfaces
971
+ * that want the same click==keypress contract.
972
+ *
973
+ * CONTRACT NOTE: `processParsedKey` wraps the parsed key in a real
974
+ * `KeyEvent`, so handlers receive the same object type as terminal
975
+ * input — but synthesized modifier chords carry an empty
976
+ * `sequence`/`raw` (fabricating control bytes would risk double
977
+ * interpretation by byte-level consumers). Every handler in this app
978
+ * discriminates on `name` + modifier flags, which is the contract this
979
+ * dispatch guarantees; a future handler matching on non-empty
980
+ * `sequence` for chords would see synthesized clicks differ from real
981
+ * keystrokes.
982
+ */
983
+ function dispatchHintKey(renderer, spec) {
984
+ const parsed = parseHintKeySpec(spec);
985
+ if (!parsed) return false;
986
+ renderer.keyInput.processParsedKey(parsed);
987
+ return true;
988
+ }
989
+ /**
990
+ * True when a row click landed on the row's `[✓] ` checkbox cell — the
991
+ * columns `[startCol, startCol + width)` of the FIRST line of the target
992
+ * renderable. Rows that must stay single `<text>` renderables (the MCP
993
+ * list documents an OpenTUI layout regression forbidding box wrappers)
994
+ * can't make the checkbox its own renderable, so zone-splitting works by
995
+ * coordinate instead: `event.x/y` are absolute, the target renderable
996
+ * knows its own absolute origin, and the difference is the glyph column.
997
+ * The `y` check pins line 0 so wrapped continuation lines can't false-
998
+ * positive into the checkbox range. Degrades to `false` (row semantics)
999
+ * when the target's geometry is unavailable.
1000
+ */
1001
+ function inCheckboxZone(event, startCol, width = 4) {
1002
+ const target = event.target;
1003
+ if (!target || typeof target.x !== "number" || typeof target.y !== "number") return false;
1004
+ if (event.y - target.y !== 0) return false;
1005
+ const col = event.x - target.x;
1006
+ return col >= startCol && col < startCol + width;
1007
+ }
1008
+ /** Spans for one hint's text content — shared by hover and rest states. */
1009
+ function hintSpans(hint, COLOR, hovered) {
1010
+ const keyColor = hovered ? COLOR.accent : hint.keyColor ?? COLOR.warn;
1011
+ const labelColor = hovered ? COLOR.accent : hint.labelColor ?? COLOR.dim;
1012
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1013
+ hint.key !== "" && /* @__PURE__ */ jsx$1("span", {
1014
+ fg: keyColor,
1015
+ children: hint.key
1016
+ }),
1017
+ hint.extra && /* @__PURE__ */ jsx$1("span", {
1018
+ fg: hovered ? COLOR.accent : hint.extra.keyColor ?? COLOR.mute,
1019
+ children: hint.extra.key
1020
+ }),
1021
+ /* @__PURE__ */ jsx$1("span", {
1022
+ fg: labelColor,
1023
+ children: hint.key === "" ? hint.label : ` ${hint.label}`
1024
+ }),
1025
+ hint.extra && /* @__PURE__ */ jsx$1("span", {
1026
+ fg: hovered ? COLOR.accent : hint.extra.labelColor ?? COLOR.dim,
1027
+ children: ` ${hint.extra.label}`
1028
+ })
1029
+ ] });
1030
+ }
1031
+ /**
1032
+ * One hint as its own `<text>` renderable — clickable when its key spec
1033
+ * parses, hover-highlighted in the theme accent. Non-parseable hints
1034
+ * render identically but inert, so decorative chips cost nothing.
1035
+ */
1036
+ function ClickableHint({ hint }) {
1037
+ const COLOR = useColors();
1038
+ const renderer = useRenderer();
1039
+ const [hovered, setHovered] = useState(false);
1040
+ if (!(parseHintKeySpec(hint.key) !== null)) return /* @__PURE__ */ jsx$1("text", {
1041
+ selectable: false,
1042
+ children: hintSpans(hint, COLOR, false)
1043
+ });
1044
+ return /* @__PURE__ */ jsx$1("text", {
1045
+ selectable: false,
1046
+ onMouseDown: (event) => {
1047
+ event.stopPropagation();
1048
+ event.preventDefault();
1049
+ dispatchHintKey(renderer, hint.key);
1050
+ },
1051
+ onMouseOver: () => setHovered(true),
1052
+ onMouseOut: () => setHovered(false),
1053
+ children: hintSpans(hint, COLOR, hovered)
1054
+ });
1055
+ }
1056
+ /**
1057
+ * Modal-footer flavor of {@link HintRow}: same clickable hints, but with
1058
+ * the modal convention's mute labels (footers historically rendered as
1059
+ * `<text fg={mute}>` prose) and falsy-entry filtering so call sites can
1060
+ * express conditional hints inline:
1061
+ *
1062
+ * ```tsx
1063
+ * <ModalHints hints={[
1064
+ * { key: '↑↓', label: 'navigate' },
1065
+ * onRefresh && { key: 'ctrl+r', label: 'refresh' },
1066
+ * { key: 'esc', label: 'close' },
1067
+ * ]}
1068
+ * />
1069
+ * ```
1070
+ *
1071
+ * Per-hint `keyColor`/`labelColor` overrides still win over the defaults.
1072
+ */
1073
+ function ModalHints({ hints }) {
1074
+ const COLOR = useColors();
1075
+ return /* @__PURE__ */ jsx$1(HintRow, { hints: hints.filter((h) => Boolean(h)).map((h) => ({
1076
+ labelColor: COLOR.mute,
1077
+ ...h
1078
+ })) });
1079
+ }
1080
+ /**
1081
+ * A row of clickable hints with ` · ` separators — the renderable-per-hint
1082
+ * replacement for the span-based `renderHintSpans`. Text content is
1083
+ * byte-identical to the span renderer, so existing `hintsLength` width
1084
+ * math keeps holding.
1085
+ */
1086
+ function HintRow({ hints }) {
1087
+ const COLOR = useColors();
1088
+ return /* @__PURE__ */ jsx$1("box", {
1089
+ style: {
1090
+ flexDirection: "row",
1091
+ flexWrap: "wrap"
1092
+ },
1093
+ children: hints.map((hint, i) => /* @__PURE__ */ jsxs("box", {
1094
+ style: { flexDirection: "row" },
1095
+ children: [i > 0 && /* @__PURE__ */ jsx$1("text", {
1096
+ selectable: false,
1097
+ fg: COLOR.mute,
1098
+ children: " · "
1099
+ }), /* @__PURE__ */ jsx$1(ClickableHint, { hint })]
1100
+ }, `${hint.key}\u0000${hint.label}\u0000${i}`))
1101
+ });
1102
+ }
1103
+ //#endregion
808
1104
  //#region src/tui/modal.tsx
809
1105
  const ModalContext = createContext(null);
810
1106
  function ModalRoot({ children }) {
811
1107
  const [active, setActive] = useState(null);
812
1108
  const [lockCount, setLockCount] = useState(0);
813
1109
  const [escLockCount, setEscLockCount] = useState(0);
1110
+ const backdropRef = useRef(null);
1111
+ const renderer = useRenderer();
814
1112
  const api = useMemo(() => ({
815
1113
  open: (node) => setActive(node),
816
1114
  close: () => setActive(null),
@@ -837,6 +1135,7 @@ function ModalRoot({ children }) {
837
1135
  },
838
1136
  children
839
1137
  }), active && /* @__PURE__ */ jsx$1("box", {
1138
+ ref: backdropRef,
840
1139
  style: {
841
1140
  position: "absolute",
842
1141
  top: 0,
@@ -847,6 +1146,12 @@ function ModalRoot({ children }) {
847
1146
  justifyContent: "center",
848
1147
  zIndex: 100
849
1148
  },
1149
+ onMouseDown: (event) => {
1150
+ if (event.target !== backdropRef.current) return;
1151
+ event.stopPropagation();
1152
+ event.preventDefault();
1153
+ dispatchHintKey(renderer, "esc");
1154
+ },
850
1155
  children: active
851
1156
  })]
852
1157
  });
@@ -990,26 +1295,20 @@ function AgentPickerModal({ agents, currentAgentId, onPick }) {
990
1295
  if (option) onPick(option.value);
991
1296
  }
992
1297
  }),
993
- /* @__PURE__ */ jsxs("text", {
994
- fg: COLOR.mute,
995
- children: [
996
- /* @__PURE__ */ jsx$1("span", {
997
- fg: COLOR.warn,
998
- children: "↑↓"
999
- }),
1000
- " navigate · ",
1001
- /* @__PURE__ */ jsx$1("span", {
1002
- fg: COLOR.warn,
1003
- children: "↵"
1004
- }),
1005
- " select · ",
1006
- /* @__PURE__ */ jsx$1("span", {
1007
- fg: COLOR.warn,
1008
- children: "esc"
1009
- }),
1010
- " close"
1011
- ]
1012
- })
1298
+ /* @__PURE__ */ jsx$1(ModalHints, { hints: [
1299
+ {
1300
+ key: "↑↓",
1301
+ label: "navigate"
1302
+ },
1303
+ {
1304
+ key: "↵",
1305
+ label: "select"
1306
+ },
1307
+ {
1308
+ key: "esc",
1309
+ label: "close"
1310
+ }
1311
+ ] })
1013
1312
  ]
1014
1313
  });
1015
1314
  }
@@ -1217,31 +1516,28 @@ function CancelToolModal({ inFlight, onCancel, onCancelAll, onClose }) {
1217
1516
  callIdColWidth,
1218
1517
  childColWidth
1219
1518
  }, row.callId))
1220
- }), /* @__PURE__ */ jsxs("text", {
1221
- fg: COLOR.dim,
1222
- children: [
1223
- /* @__PURE__ */ jsx$1("span", {
1224
- fg: COLOR.warn,
1225
- children: "↑↓"
1226
- }),
1227
- " navigate · ",
1228
- /* @__PURE__ */ jsx$1("span", {
1229
- fg: COLOR.warn,
1230
- children: "↵"
1231
- }),
1232
- " cancel selected · ",
1233
- /* @__PURE__ */ jsx$1("span", {
1234
- fg: COLOR.warn,
1235
- children: "a"
1236
- }),
1237
- " cancel all · ",
1238
- /* @__PURE__ */ jsx$1("span", {
1239
- fg: COLOR.warn,
1240
- children: "esc"
1241
- }),
1242
- " close"
1243
- ]
1244
- })]
1519
+ }), /* @__PURE__ */ jsx$1(ModalHints, { hints: [
1520
+ {
1521
+ key: "↑↓",
1522
+ label: "navigate",
1523
+ labelColor: COLOR.dim
1524
+ },
1525
+ {
1526
+ key: "↵",
1527
+ label: "cancel selected",
1528
+ labelColor: COLOR.dim
1529
+ },
1530
+ {
1531
+ key: "a",
1532
+ label: "cancel all",
1533
+ labelColor: COLOR.dim
1534
+ },
1535
+ {
1536
+ key: "esc",
1537
+ label: "close",
1538
+ labelColor: COLOR.dim
1539
+ }
1540
+ ] })]
1245
1541
  });
1246
1542
  }
1247
1543
  function CancelToolRow({ row, isFocused, highlightBg, elapsedColWidth, callIdColWidth, childColWidth }) {
@@ -1354,13 +1650,7 @@ function ClaudeImportModal({ store, cwd, prefix, onDone }) {
1354
1650
  const [summary, setSummary] = useState(null);
1355
1651
  const [error, setError] = useState(null);
1356
1652
  const runningRef = useRef(false);
1357
- const counts = useMemo(() => ({
1358
- project: listClaudeSessionFiles({ cwd }).length,
1359
- all: listClaudeSessionFiles({
1360
- cwd,
1361
- allProjects: true
1362
- }).length
1363
- }), [cwd]);
1653
+ const counts = useMemo(() => countClaudeSessionFiles({ cwd }), [cwd]);
1364
1654
  const rows = [
1365
1655
  {
1366
1656
  key: "sessions",
@@ -1415,18 +1705,19 @@ function ClaudeImportModal({ store, cwd, prefix, onDone }) {
1415
1705
  await onDone?.(result);
1416
1706
  } catch {}
1417
1707
  };
1708
+ /** ↵/space/click on a row: the Import row runs, every other row toggles. */
1709
+ const activateRow = (row) => {
1710
+ if (row.key === "run") runImport();
1711
+ else setOptions((prev) => ({
1712
+ ...prev,
1713
+ [row.key]: !prev[row.key]
1714
+ }));
1715
+ };
1418
1716
  useKeyboard((key) => {
1419
1717
  if (phase !== "pick") return;
1420
1718
  if (key.name === "up" || key.ctrl && key.name === "p") setCursor((c) => (c - 1 + rows.length) % rows.length);
1421
1719
  else if (key.name === "down" || key.ctrl && key.name === "n") setCursor((c) => (c + 1) % rows.length);
1422
- else if (key.name === "return" || key.name === "space") {
1423
- const row = rows[cursor];
1424
- if (row.key === "run") runImport();
1425
- else setOptions((prev) => ({
1426
- ...prev,
1427
- [row.key]: !prev[row.key]
1428
- }));
1429
- }
1720
+ else if (key.name === "return" || key.name === "space") activateRow(rows[cursor]);
1430
1721
  });
1431
1722
  if (phase === "running") return /* @__PURE__ */ jsx$1(Modal, {
1432
1723
  title: "Import from Claude Code",
@@ -1453,26 +1744,35 @@ function ClaudeImportModal({ store, cwd, prefix, onDone }) {
1453
1744
  summary.sessions.failed.length > 0 ? `, ${summary.sessions.failed.length} failed` : ""
1454
1745
  ]
1455
1746
  }),
1747
+ summary?.sessions && failLine(summary.sessions.failed.map((f) => f.error)) !== null && /* @__PURE__ */ jsx$1("text", {
1748
+ fg: COLOR.error,
1749
+ children: failLine(summary.sessions.failed.map((f) => f.error))
1750
+ }),
1456
1751
  summary?.mcps && /* @__PURE__ */ jsx$1("text", {
1457
1752
  fg: COLOR.dim,
1458
1753
  children: copyLine("mcp servers", summary.mcps)
1459
1754
  }),
1755
+ summary?.mcps && failLine(summary.mcps.failed.map((f) => `${f.name}: ${f.error}`)) !== null && /* @__PURE__ */ jsx$1("text", {
1756
+ fg: COLOR.error,
1757
+ children: failLine(summary.mcps.failed.map((f) => `${f.name}: ${f.error}`))
1758
+ }),
1460
1759
  summary?.skills && /* @__PURE__ */ jsx$1("text", {
1461
1760
  fg: COLOR.dim,
1462
1761
  children: copyLine("skills", summary.skills)
1463
1762
  }),
1763
+ summary?.skills && failLine(summary.skills.failed.map((f) => `${f.name}: ${f.error}`)) !== null && /* @__PURE__ */ jsx$1("text", {
1764
+ fg: COLOR.error,
1765
+ children: failLine(summary.skills.failed.map((f) => `${f.name}: ${f.error}`))
1766
+ }),
1464
1767
  summary?.notes.map((note) => /* @__PURE__ */ jsx$1("text", {
1465
1768
  fg: COLOR.mute,
1466
1769
  children: `· ${note}`
1467
1770
  }, note))
1468
1771
  ]
1469
- }), /* @__PURE__ */ jsxs("text", {
1470
- fg: COLOR.mute,
1471
- children: [/* @__PURE__ */ jsx$1("span", {
1472
- fg: COLOR.warn,
1473
- children: "esc"
1474
- }), " close"]
1475
- })]
1772
+ }), /* @__PURE__ */ jsx$1(ModalHints, { hints: [{
1773
+ key: "esc",
1774
+ label: "close"
1775
+ }] })]
1476
1776
  });
1477
1777
  const enabledFor = (key) => key === "run" ? void 0 : options[key];
1478
1778
  return /* @__PURE__ */ jsxs(Modal, {
@@ -1484,6 +1784,14 @@ function ClaudeImportModal({ store, cwd, prefix, onDone }) {
1484
1784
  const enabled = enabledFor(row.key);
1485
1785
  return /* @__PURE__ */ jsxs("text", {
1486
1786
  fg: focused ? COLOR.brand : COLOR.dim,
1787
+ selectable: false,
1788
+ onMouseOver: () => setCursor(i),
1789
+ onMouseDown: (event) => {
1790
+ event.stopPropagation();
1791
+ event.preventDefault();
1792
+ setCursor(i);
1793
+ activateRow(row);
1794
+ },
1487
1795
  children: [
1488
1796
  /* @__PURE__ */ jsx$1("span", {
1489
1797
  fg: focused ? COLOR.brand : COLOR.mute,
@@ -1504,32 +1812,32 @@ function ClaudeImportModal({ store, cwd, prefix, onDone }) {
1504
1812
  ]
1505
1813
  }, row.key);
1506
1814
  })
1507
- }), /* @__PURE__ */ jsxs("text", {
1508
- fg: COLOR.mute,
1509
- children: [
1510
- /* @__PURE__ */ jsx$1("span", {
1511
- fg: COLOR.warn,
1512
- children: "↑↓"
1513
- }),
1514
- " navigate · ",
1515
- /* @__PURE__ */ jsx$1("span", {
1516
- fg: COLOR.warn,
1517
- children: "↵"
1518
- }),
1519
- " toggle / run · ",
1520
- /* @__PURE__ */ jsx$1("span", {
1521
- fg: COLOR.warn,
1522
- children: "esc"
1523
- }),
1524
- " close"
1525
- ]
1526
- })]
1815
+ }), /* @__PURE__ */ jsx$1(ModalHints, { hints: [
1816
+ {
1817
+ key: "↑↓",
1818
+ label: "navigate"
1819
+ },
1820
+ {
1821
+ key: "↵",
1822
+ label: "toggle / run"
1823
+ },
1824
+ {
1825
+ key: "esc",
1826
+ label: "close"
1827
+ }
1828
+ ] })]
1527
1829
  });
1528
1830
  }
1831
+ /** First failure reason (+count of the rest), or null when nothing failed. */
1832
+ function failLine(errors) {
1833
+ if (errors.length === 0) return null;
1834
+ return errors.length > 1 ? ` ⚠ ${errors[0]} (+${errors.length - 1} more)` : ` ⚠ ${errors[0]}`;
1835
+ }
1529
1836
  function copyLine(label, result) {
1530
1837
  const parts = [];
1531
1838
  parts.push(result.copied.length > 0 ? `${result.copied.length} copied (${result.copied.join(", ")})` : "none copied");
1532
1839
  if (result.skipped.length > 0) parts.push(`${result.skipped.length} skipped as native (${result.skipped.join(", ")})`);
1840
+ if (result.duplicates && result.duplicates.length > 0) parts.push(`${result.duplicates.length} duplicate source entr${result.duplicates.length === 1 ? "y" : "ies"} dropped (${result.duplicates.join(", ")})`);
1533
1841
  if (result.failed.length > 0) parts.push(`${result.failed.length} failed`);
1534
1842
  return `${label}: ${parts.join(" · ")}`;
1535
1843
  }
@@ -2311,7 +2619,7 @@ function onInputSubmit(handler) {
2311
2619
  * Width tiering is driven by plain-text length estimates — close enough
2312
2620
  * since the segments are ASCII-heavy.
2313
2621
  */
2314
- function Footer({ hints, context, cost = null, status = null, compactionTokens = null, compactionStartedAt = null }) {
2622
+ function Footer({ hints, context, cost = null, status = null, compactionTokens = null, compactionStartedAt = null, retry = null, contextKey = null }) {
2315
2623
  const { width } = useTerminalDimensions();
2316
2624
  const showCost = typeof cost === "number" && cost > 0;
2317
2625
  const inner = Math.max(0, width - 2);
@@ -2325,8 +2633,8 @@ function Footer({ hints, context, cost = null, status = null, compactionTokens =
2325
2633
  const timer = setInterval(() => setCompactTick((t) => t + 1), 1e3);
2326
2634
  return () => clearInterval(timer);
2327
2635
  }, [status]);
2328
- const statusLabel = status === "compacting" ? compactingLabel(compactionTokens, compactionStartedAt) : status === "searching" ? " Searching messages…" : "";
2329
- const sW = status === "compacting" || status === "searching" ? 2 + statusLabel.length : status === "asking" ? 3 : status ? 2 : 0;
2636
+ const statusLabel = status === "compacting" ? compactingLabel(compactionTokens, compactionStartedAt) : status === "searching" ? " Searching messages…" : status === "retrying" ? retryingLabel(retry) : "";
2637
+ const sW = status === "compacting" || status === "searching" || status === "retrying" ? 2 + statusLabel.length : status === "asking" ? 3 : status ? 2 : 0;
2330
2638
  if (hW + sW + (rightW > 0 ? rightW + 1 : 0) <= inner) return /* @__PURE__ */ jsxs("box", {
2331
2639
  style: {
2332
2640
  flexDirection: "row",
@@ -2343,7 +2651,8 @@ function Footer({ hints, context, cost = null, status = null, compactionTokens =
2343
2651
  /* @__PURE__ */ jsx$1("box", { style: { flexGrow: 1 } }),
2344
2652
  /* @__PURE__ */ jsx$1(RightStatus, {
2345
2653
  cost: showCost ? cost : null,
2346
- context
2654
+ context,
2655
+ contextKey: contextKey ?? null
2347
2656
  })
2348
2657
  ]
2349
2658
  });
@@ -2370,22 +2679,43 @@ function Footer({ hints, context, cost = null, status = null, compactionTokens =
2370
2679
  },
2371
2680
  children: [/* @__PURE__ */ jsx$1("box", { style: { flexGrow: 1 } }), /* @__PURE__ */ jsx$1(RightStatus, {
2372
2681
  cost: showCost ? cost : null,
2373
- context
2682
+ context,
2683
+ contextKey: contextKey ?? null
2374
2684
  })]
2375
2685
  })]
2376
2686
  });
2377
2687
  }
2378
- function RightStatus({ cost, context }) {
2688
+ function RightStatus({ cost, context, contextKey }) {
2379
2689
  const COLOR = useColors();
2690
+ const renderer = useRenderer();
2691
+ const [hovered, setHovered] = useState(false);
2380
2692
  if (cost == null && !context) return null;
2381
- return /* @__PURE__ */ jsxs("text", { children: [
2382
- cost != null && /* @__PURE__ */ jsx$1(CostIndicator, { cost }),
2693
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
2694
+ cost != null && /* @__PURE__ */ jsx$1(CostIndicator, {
2695
+ cost,
2696
+ accent: hovered
2697
+ }),
2383
2698
  cost != null && context && /* @__PURE__ */ jsx$1("span", {
2384
2699
  fg: COLOR.mute,
2385
2700
  children: " · "
2386
2701
  }),
2387
- context && /* @__PURE__ */ jsx$1(ContextIndicator, { context })
2702
+ context && /* @__PURE__ */ jsx$1(ContextIndicator, {
2703
+ context,
2704
+ accent: hovered
2705
+ })
2388
2706
  ] });
2707
+ if (!(contextKey !== null && parseHintKeySpec(contextKey) !== null)) return /* @__PURE__ */ jsx$1("text", { children: body });
2708
+ return /* @__PURE__ */ jsx$1("text", {
2709
+ selectable: false,
2710
+ onMouseDown: (event) => {
2711
+ event.stopPropagation();
2712
+ event.preventDefault();
2713
+ dispatchHintKey(renderer, contextKey);
2714
+ },
2715
+ onMouseOver: () => setHovered(true),
2716
+ onMouseOut: () => setHovered(false),
2717
+ children: body
2718
+ });
2389
2719
  }
2390
2720
  /**
2391
2721
  * Right-edge status in the Footer's left section. Busy stays glyph-only;
@@ -2395,17 +2725,25 @@ function RightStatus({ cost, context }) {
2395
2725
  function FooterStatusIcon({ status, label = "" }) {
2396
2726
  const COLOR = useColors();
2397
2727
  if (!status) return null;
2398
- const glyph = status === "asking" ? /* @__PURE__ */ jsx$1("span", { children: "❓" }) : status === "busy" ? /* @__PURE__ */ jsx$1(StatusSpinner, { color: COLOR.warn }) : /* @__PURE__ */ jsx$1(StatusSpinner, { color: COLOR.accent });
2728
+ const glyph = status === "asking" ? /* @__PURE__ */ jsx$1("span", { children: "❓" }) : status === "busy" || status === "retrying" ? /* @__PURE__ */ jsx$1(StatusSpinner, { color: COLOR.warn }) : /* @__PURE__ */ jsx$1(StatusSpinner, { color: COLOR.accent });
2399
2729
  return /* @__PURE__ */ jsxs("text", { children: [
2400
2730
  /* @__PURE__ */ jsx$1("span", { children: " " }),
2401
2731
  glyph,
2402
2732
  label && /* @__PURE__ */ jsx$1("span", {
2403
- fg: COLOR.accent,
2733
+ fg: status === "retrying" ? COLOR.warn : COLOR.accent,
2404
2734
  children: label
2405
2735
  })
2406
2736
  ] });
2407
2737
  }
2408
2738
  /**
2739
+ * Footer label for the retrying state — ` Retrying (n/max)...` where `n`
2740
+ * is the attempt about to be issued. Plain string for the same width-math
2741
+ * reason as {@link compactingLabel}.
2742
+ */
2743
+ function retryingLabel(retry) {
2744
+ return retry ? ` Retrying (${retry.attempt}/${retry.max})...` : " Retrying...";
2745
+ }
2746
+ /**
2409
2747
  * Footer label for the compacting state — ` Compacting...` plus liveness:
2410
2748
  * a `~2.1k` output-token estimate once the summary streams, and an elapsed
2411
2749
  * suffix while it doesn't (prompt ingestion is minutes of silence on
@@ -2419,11 +2757,7 @@ function compactingLabel(estTokens, startedAt) {
2419
2757
  return typeof estTokens === "number" && estTokens > 0 ? `${base} ~${fmtTokens(estTokens)}${elapsed}` : `${base}${elapsed}`;
2420
2758
  }
2421
2759
  function HintsText({ hints }) {
2422
- const COLOR = useColors();
2423
- return /* @__PURE__ */ jsx$1("text", {
2424
- fg: COLOR.dim,
2425
- children: renderHintSpans(hints, COLOR)
2426
- });
2760
+ return /* @__PURE__ */ jsx$1(HintRow, { hints });
2427
2761
  }
2428
2762
  /**
2429
2763
  * Pure renderer for a list of {@link Hint}s as colored spans —
@@ -2463,14 +2797,15 @@ function renderHintSpans(hints, COLOR) {
2463
2797
  })
2464
2798
  ] }, i));
2465
2799
  }
2466
- function ContextIndicator({ context }) {
2800
+ function ContextIndicator({ context, accent = false }) {
2467
2801
  const COLOR = useColors();
2468
2802
  const ratio = context.max > 0 ? context.used / context.max : 0;
2469
2803
  const pct = Math.round(ratio * 100);
2470
- const color = ratio >= .85 ? COLOR.error : ratio >= .6 ? COLOR.warn : COLOR.dim;
2804
+ const color = accent ? COLOR.accent : ratio >= .85 ? COLOR.error : ratio >= .6 ? COLOR.warn : COLOR.dim;
2805
+ const muted = accent ? COLOR.accent : COLOR.mute;
2471
2806
  return /* @__PURE__ */ jsxs("span", { children: [
2472
2807
  /* @__PURE__ */ jsx$1("span", {
2473
- fg: COLOR.mute,
2808
+ fg: muted,
2474
2809
  children: "ctx "
2475
2810
  }),
2476
2811
  /* @__PURE__ */ jsx$1("span", {
@@ -2478,7 +2813,7 @@ function ContextIndicator({ context }) {
2478
2813
  children: fmtTokens(context.used)
2479
2814
  }),
2480
2815
  /* @__PURE__ */ jsx$1("span", {
2481
- fg: COLOR.mute,
2816
+ fg: muted,
2482
2817
  children: ` / ${fmtTokens(context.max)} `
2483
2818
  }),
2484
2819
  /* @__PURE__ */ jsx$1("span", {
@@ -2487,11 +2822,11 @@ function ContextIndicator({ context }) {
2487
2822
  })
2488
2823
  ] });
2489
2824
  }
2490
- function CostIndicator({ cost }) {
2825
+ function CostIndicator({ cost, accent = false }) {
2491
2826
  const COLOR = useColors();
2492
- const fg = COLOR.money ?? COLOR.warn;
2827
+ const fg = accent ? COLOR.accent : COLOR.money ?? COLOR.warn;
2493
2828
  return /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx$1("span", {
2494
- fg: COLOR.mute,
2829
+ fg: accent ? COLOR.accent : COLOR.mute,
2495
2830
  children: "$"
2496
2831
  }), /* @__PURE__ */ jsx$1("span", {
2497
2832
  fg,
@@ -2560,8 +2895,11 @@ function renderGradientText(text, from, to) {
2560
2895
  * </box>
2561
2896
  * ```
2562
2897
  */
2563
- function TitleOverlay({ title, meta = null, titleColor, parentWidth, statusIcon = null, statusIconCells = 1 }) {
2898
+ function TitleOverlay({ title, meta = null, titleColor, parentWidth, statusIcon = null, statusIconCells = 1, clickKey = null }) {
2564
2899
  const COLOR = useColors();
2900
+ const renderer = useRenderer();
2901
+ const [titleHovered, setTitleHovered] = useState(false);
2902
+ const clickable = clickKey !== null && parseHintKeySpec(clickKey) !== null;
2565
2903
  const { width: termWidth } = useTerminalDimensions();
2566
2904
  const useGradient = titleColor === void 0;
2567
2905
  const gradientFrom = COLOR.throbber?.to ?? COLOR.accent;
@@ -2574,32 +2912,51 @@ function TitleOverlay({ title, meta = null, titleColor, parentWidth, statusIcon
2574
2912
  const showMeta = meta != null && metaLen > 0 && title.length + iconReserve + TITLE_OVERLAY_WRAP + TITLE_OVERLAY_GAP + metaLen + TITLE_OVERLAY_META_WRAP <= inner;
2575
2913
  const titleBudget = (showMeta ? inner - (metaLen + TITLE_OVERLAY_META_WRAP) - TITLE_OVERLAY_GAP - TITLE_OVERLAY_WRAP : inner - TITLE_OVERLAY_WRAP) - iconReserve;
2576
2914
  const visibleTitle = titleBudget <= 0 ? "" : truncateTrailing(title, titleBudget);
2577
- return /* @__PURE__ */ jsxs(Fragment, { children: [(visibleTitle || statusIcon) && /* @__PURE__ */ jsxs("text", {
2915
+ const titleBody = /* @__PURE__ */ jsxs(Fragment, { children: [
2916
+ /* @__PURE__ */ jsx$1("span", {
2917
+ fg: COLOR.mute,
2918
+ children: " "
2919
+ }),
2920
+ titleHovered ? /* @__PURE__ */ jsx$1("span", {
2921
+ fg: COLOR.accent,
2922
+ children: visibleTitle
2923
+ }) : useGradient && visibleTitle.length > 0 ? renderGradientText(visibleTitle, gradientFrom, gradientTo) : /* @__PURE__ */ jsx$1("span", {
2924
+ fg,
2925
+ children: visibleTitle
2926
+ }),
2927
+ statusIcon && /* @__PURE__ */ jsx$1("span", {
2928
+ fg: COLOR.mute,
2929
+ children: " "
2930
+ }),
2931
+ statusIcon,
2932
+ /* @__PURE__ */ jsx$1("span", {
2933
+ fg: COLOR.mute,
2934
+ children: " "
2935
+ })
2936
+ ] });
2937
+ return /* @__PURE__ */ jsxs(Fragment, { children: [(visibleTitle || statusIcon) && (clickable ? /* @__PURE__ */ jsx$1("text", {
2578
2938
  style: {
2579
2939
  position: "absolute",
2580
2940
  top: 0,
2581
2941
  left: 1
2582
2942
  },
2583
- children: [
2584
- /* @__PURE__ */ jsx$1("span", {
2585
- fg: COLOR.mute,
2586
- children: " "
2587
- }),
2588
- useGradient && visibleTitle.length > 0 ? renderGradientText(visibleTitle, gradientFrom, gradientTo) : /* @__PURE__ */ jsx$1("span", {
2589
- fg,
2590
- children: visibleTitle
2591
- }),
2592
- statusIcon && /* @__PURE__ */ jsx$1("span", {
2593
- fg: COLOR.mute,
2594
- children: " "
2595
- }),
2596
- statusIcon,
2597
- /* @__PURE__ */ jsx$1("span", {
2598
- fg: COLOR.mute,
2599
- children: " "
2600
- })
2601
- ]
2602
- }), showMeta && meta && /* @__PURE__ */ jsxs("text", {
2943
+ selectable: false,
2944
+ onMouseDown: (event) => {
2945
+ event.stopPropagation();
2946
+ event.preventDefault();
2947
+ dispatchHintKey(renderer, clickKey);
2948
+ },
2949
+ onMouseOver: () => setTitleHovered(true),
2950
+ onMouseOut: () => setTitleHovered(false),
2951
+ children: titleBody
2952
+ }) : /* @__PURE__ */ jsx$1("text", {
2953
+ style: {
2954
+ position: "absolute",
2955
+ top: 0,
2956
+ left: 1
2957
+ },
2958
+ children: titleBody
2959
+ })), showMeta && meta && /* @__PURE__ */ jsxs("text", {
2603
2960
  style: {
2604
2961
  position: "absolute",
2605
2962
  top: 0,
@@ -2691,14 +3048,79 @@ function StatusSpinner({ color }) {
2691
3048
  children: useSpinnerFrame()
2692
3049
  });
2693
3050
  }
2694
- function Transcript({ events, settings, selectedTurnId = null, busy = false, searchQuery, searchPending = false, onLoadEarlier }) {
3051
+ /**
3052
+ * Bordered container for one run of subagent events. The box's border +
3053
+ * left padding give the visual "this is a subagent" affordance, so events
3054
+ * inside render with `depthOffset: 1` — a direct child of the parent
3055
+ * (depth 1) sits flush against the box's inner padding rather than being
3056
+ * indented twice. Grandchildren (depth ≥ 2) still indent further, so
3057
+ * nested subagents remain visually distinct.
3058
+ *
3059
+ * Memoized like {@link EventLine} so completed historical runs don't
3060
+ * re-execute per drain tick — but with a custom comparator:
3061
+ * `partitionTranscript` / `computeTurnAnchors` rebuild the per-run
3062
+ * `events` / `anchorIds` arrays on every transcript change, so the default
3063
+ * shallow compare would never bail while streaming. The inner event
3064
+ * references (and the deterministic anchor-id strings) ARE stable until
3065
+ * their content changes, so an element-wise compare recovers the bail-out.
3066
+ */
3067
+ const SubagentBlock = memo(({ events, previous, selectedTurnId = null, anchorIds, codeSettleTargets, codeSettleEpoch = 0, live, searchQuery, searchTitleMatches }) => {
2695
3068
  const COLOR = useColors();
2696
- const showThrobber = busy && settings.showThrobber;
2697
- const codeSettle = useRef({
2698
- wasBusy: false,
2699
- epoch: 0,
2700
- activeTargets: /* @__PURE__ */ new Set(),
2701
- targets: /* @__PURE__ */ new Set()
3069
+ const childIds = useMemo(() => {
3070
+ const set = /* @__PURE__ */ new Set();
3071
+ for (const e of events) if (e.childId) set.add(e.childId);
3072
+ return Array.from(set);
3073
+ }, [events]);
3074
+ const title = childIds.length === 0 ? " subagent " : childIds.length === 1 ? ` ${childIds[0]} ` : ` subagents · ${childIds.join(", ")} `;
3075
+ const marginTop = previous ? 1 : 0;
3076
+ const hideChildLabel = childIds.length === 1;
3077
+ return /* @__PURE__ */ jsx$1("box", {
3078
+ title,
3079
+ style: {
3080
+ border: true,
3081
+ borderColor: COLOR.brand,
3082
+ paddingLeft: 1,
3083
+ paddingRight: 1,
3084
+ paddingTop: 0,
3085
+ paddingBottom: 0,
3086
+ marginTop,
3087
+ flexDirection: "column",
3088
+ flexShrink: 0,
3089
+ alignSelf: "stretch"
3090
+ },
3091
+ children: events.map((evt, i) => /* @__PURE__ */ jsx$1(EventLine, {
3092
+ event: evt,
3093
+ previous: events[i - 1],
3094
+ depthOffset: 1,
3095
+ selected: selectedTurnId !== null && evt.turnId === selectedTurnId,
3096
+ anchorId: anchorIds?.[i],
3097
+ hideChildLabel,
3098
+ settleCodeEpoch: codeSettleTargets?.has(evt) ? codeSettleEpoch : 0,
3099
+ liveSpawn: evt.kind === "spawn-start" ? live?.find((spawn) => spawn.id === evt.childId) : void 0,
3100
+ searchQuery,
3101
+ searchTitleMatch: evt.kind === "tool" && evt.callId !== void 0 && searchTitleMatches?.callIds.has(evt.callId) || (evt.kind === "spawn-start" || evt.kind === "spawn-end") && evt.childId !== void 0 && searchTitleMatches?.childIds.has(evt.childId)
3102
+ }, i))
3103
+ });
3104
+ }, (prev, next) => sameElements(prev.events, next.events) && sameElements(prev.anchorIds, next.anchorIds) && prev.previous === next.previous && prev.selectedTurnId === next.selectedTurnId && prev.codeSettleTargets === next.codeSettleTargets && prev.codeSettleEpoch === next.codeSettleEpoch && prev.live === next.live && prev.searchQuery === next.searchQuery && prev.searchTitleMatches === next.searchTitleMatches);
3105
+ /** Element-wise `===` over possibly-rebuilt arrays — see {@link SubagentBlock}. */
3106
+ function sameElements(a, b) {
3107
+ if (a === b) return true;
3108
+ if (!a || !b || a.length !== b.length) return false;
3109
+ return a.every((value, i) => value === b[i]);
3110
+ }
3111
+ const NO_SEARCH_MATCHES = {
3112
+ direct: /* @__PURE__ */ new Set(),
3113
+ callIds: /* @__PURE__ */ new Set(),
3114
+ childIds: /* @__PURE__ */ new Set()
3115
+ };
3116
+ function Transcript({ events, settings, selectedTurnId = null, busy = false, searchQuery, searchPending = false, onLoadEarlier, onSelectTurn, onOpenTurn }) {
3117
+ const COLOR = useColors();
3118
+ const showThrobber = busy && settings.showThrobber;
3119
+ const codeSettle = useRef({
3120
+ wasBusy: false,
3121
+ epoch: 0,
3122
+ activeTargets: /* @__PURE__ */ new Set(),
3123
+ targets: /* @__PURE__ */ new Set()
2702
3124
  });
2703
3125
  const lastUserPromptIndex = events.findLastIndex((event) => event.kind === "user-prompt");
2704
3126
  if (busy) {
@@ -2716,14 +3138,10 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false, sea
2716
3138
  const throbberLabel = events.length > 0 && events[events.length - 1].kind === "thinking" ? "Thinking" : void 0;
2717
3139
  const ownership = useMemo(() => turnSelectionOwnership(events), [events]);
2718
3140
  const searchMatches = useMemo(() => {
3141
+ if (!searchQuery) return NO_SEARCH_MATCHES;
2719
3142
  const direct = /* @__PURE__ */ new Set();
2720
3143
  const callIds = /* @__PURE__ */ new Set();
2721
3144
  const childIds = /* @__PURE__ */ new Set();
2722
- if (!searchQuery) return {
2723
- direct,
2724
- callIds,
2725
- childIds
2726
- };
2727
3145
  const stack = [];
2728
3146
  for (const event of events) {
2729
3147
  if (event.kind === "spawn-start") stack.push(event);
@@ -2752,9 +3170,13 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false, sea
2752
3170
  searchMatches
2753
3171
  ]);
2754
3172
  const scrollboxRef = useRef(null);
3173
+ const mouseSelectionRef = useRef(false);
3174
+ const pressedTurnRef = useRef(null);
2755
3175
  const rowVisibility = useRef(createRowVisibilityStore());
2756
3176
  const loadEarlierRef = useRef(onLoadEarlier);
2757
3177
  loadEarlierRef.current = onLoadEarlier;
3178
+ const eventsRef = useRef(events);
3179
+ eventsRef.current = events;
2758
3180
  const earlierLoad = useRef(null);
2759
3181
  const earlierLoadArmed = useRef(false);
2760
3182
  useEffect(() => {
@@ -2767,7 +3189,7 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false, sea
2767
3189
  });
2768
3190
  const topThreshold = Math.max(4, Math.floor(sb.viewport.height / 4));
2769
3191
  if (sb.scrollTop > topThreshold) earlierLoadArmed.current = true;
2770
- else if (earlierLoadArmed.current && !earlierLoad.current && loadEarlierRef.current && hiddenEarlierTranscriptCount(events) > 0) {
3192
+ else if (earlierLoadArmed.current && !earlierLoad.current && loadEarlierRef.current && hiddenEarlierTranscriptCount(eventsRef.current) > 0) {
2771
3193
  earlierLoadArmed.current = false;
2772
3194
  const snapshot = {
2773
3195
  oldHeight: sb.scrollHeight,
@@ -2781,7 +3203,7 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false, sea
2781
3203
  }
2782
3204
  }, 200);
2783
3205
  return () => clearInterval(timer);
2784
- }, [events]);
3206
+ }, []);
2785
3207
  useEffect(() => {
2786
3208
  const pending = earlierLoad.current;
2787
3209
  if (!pending) return;
@@ -2835,6 +3257,10 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false, sea
2835
3257
  }, [items, events]);
2836
3258
  useEffect(() => {
2837
3259
  if (!selectedTurnId) return;
3260
+ if (mouseSelectionRef.current) {
3261
+ mouseSelectionRef.current = false;
3262
+ return;
3263
+ }
2838
3264
  let handle = 0;
2839
3265
  let attempts = 0;
2840
3266
  const align = () => {
@@ -2895,7 +3321,7 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false, sea
2895
3321
  children: [items.map((item, i) => {
2896
3322
  if (item.kind === "event") {
2897
3323
  const live = liveByItem.get(i);
2898
- return /* @__PURE__ */ jsx$1(EventLine, {
3324
+ const line = /* @__PURE__ */ jsx$1(EventLine, {
2899
3325
  event: item.event,
2900
3326
  previous: item.previous,
2901
3327
  selected: isTurnHighlighted(item.event, selectedTurnId, ownership),
@@ -2904,6 +3330,33 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false, sea
2904
3330
  liveSpawn: live?.[0],
2905
3331
  searchQuery,
2906
3332
  searchTitleMatch: item.event.kind === "tool" && item.event.callId !== void 0 && searchMatches.callIds.has(item.event.callId) || (item.event.kind === "spawn-start" || item.event.kind === "spawn-end") && item.event.childId !== void 0 && searchMatches.childIds.has(item.event.childId)
3333
+ });
3334
+ const selectActive = selectedTurnId !== null && (onSelectTurn !== void 0 || onOpenTurn !== void 0);
3335
+ const targetTurn = item.event.turnId && !item.event.childId ? ownership.get(item.event.turnId) ?? item.event.turnId : null;
3336
+ const active = selectActive && targetTurn !== null;
3337
+ return /* @__PURE__ */ jsx$1("box", {
3338
+ style: { flexDirection: "column" },
3339
+ onMouseOver: active ? () => {
3340
+ if (targetTurn === selectedTurnId) return;
3341
+ if (onSelectTurn?.(targetTurn) !== false) mouseSelectionRef.current = true;
3342
+ } : void 0,
3343
+ onMouseDown: active && onOpenTurn ? (event) => {
3344
+ pressedTurnRef.current = event.button === 0 ? {
3345
+ turnId: targetTurn,
3346
+ x: event.x,
3347
+ y: event.y
3348
+ } : null;
3349
+ } : void 0,
3350
+ onMouseUp: active && onOpenTurn ? (event) => {
3351
+ const pressed = pressedTurnRef.current;
3352
+ pressedTurnRef.current = null;
3353
+ if (event.button !== 0 || pressed === null || pressed.turnId !== targetTurn) return;
3354
+ if (pressed.x !== event.x || pressed.y !== event.y) return;
3355
+ event.stopPropagation();
3356
+ event.preventDefault();
3357
+ if (onOpenTurn(targetTurn) !== false && targetTurn !== selectedTurnId) mouseSelectionRef.current = true;
3358
+ } : void 0,
3359
+ children: line
2907
3360
  }, i);
2908
3361
  }
2909
3362
  return /* @__PURE__ */ jsx$1(SubagentBlock, {
@@ -3051,52 +3504,6 @@ function partitionTranscript(events, settings, searchReveal) {
3051
3504
  return items;
3052
3505
  }
3053
3506
  /**
3054
- * Bordered container for one run of subagent events. The box's border +
3055
- * left padding give the visual "this is a subagent" affordance, so events
3056
- * inside render with `depthOffset: 1` — a direct child of the parent
3057
- * (depth 1) sits flush against the box's inner padding rather than being
3058
- * indented twice. Grandchildren (depth ≥ 2) still indent further, so
3059
- * nested subagents remain visually distinct.
3060
- */
3061
- function SubagentBlock({ events, previous, selectedTurnId = null, anchorIds, codeSettleTargets, codeSettleEpoch = 0, live, searchQuery, searchTitleMatches }) {
3062
- const COLOR = useColors();
3063
- const childIds = useMemo(() => {
3064
- const set = /* @__PURE__ */ new Set();
3065
- for (const e of events) if (e.childId) set.add(e.childId);
3066
- return Array.from(set);
3067
- }, [events]);
3068
- const title = childIds.length === 0 ? " subagent " : childIds.length === 1 ? ` ${childIds[0]} ` : ` subagents · ${childIds.join(", ")} `;
3069
- const marginTop = previous ? 1 : 0;
3070
- const hideChildLabel = childIds.length === 1;
3071
- return /* @__PURE__ */ jsx$1("box", {
3072
- title,
3073
- style: {
3074
- border: true,
3075
- borderColor: COLOR.brand,
3076
- paddingLeft: 1,
3077
- paddingRight: 1,
3078
- paddingTop: 0,
3079
- paddingBottom: 0,
3080
- marginTop,
3081
- flexDirection: "column",
3082
- flexShrink: 0,
3083
- alignSelf: "stretch"
3084
- },
3085
- children: events.map((evt, i) => /* @__PURE__ */ jsx$1(EventLine, {
3086
- event: evt,
3087
- previous: events[i - 1],
3088
- depthOffset: 1,
3089
- selected: selectedTurnId !== null && evt.turnId === selectedTurnId,
3090
- anchorId: anchorIds?.[i],
3091
- hideChildLabel,
3092
- settleCodeEpoch: codeSettleTargets?.has(evt) ? codeSettleEpoch : 0,
3093
- liveSpawn: evt.kind === "spawn-start" ? live?.find((spawn) => spawn.id === evt.childId) : void 0,
3094
- searchQuery,
3095
- searchTitleMatch: evt.kind === "tool" && evt.callId !== void 0 && searchTitleMatches?.callIds.has(evt.callId) || (evt.kind === "spawn-start" || evt.kind === "spawn-end") && evt.childId !== void 0 && searchTitleMatches?.childIds.has(evt.childId)
3096
- }, i))
3097
- });
3098
- }
3099
- /**
3100
3507
  * Stable one-row subagent lifecycle layout. Identity/model/task stay in
3101
3508
  * the left lane for the whole run; only the right tail changes:
3102
3509
  * active `2 turns · 14s · ⠋ tool call`, done `4 turns`.
@@ -4716,29 +5123,23 @@ function ContextPanelModal({ breakdown, modelUsage }) {
4716
5123
  children: "~"
4717
5124
  }), " estimated — exact counts unavailable for this provider"]
4718
5125
  }),
4719
- /* @__PURE__ */ jsx$1("text", {
4720
- fg: COLOR.dim,
4721
- children: expandableIds.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
4722
- /* @__PURE__ */ jsx$1("span", {
4723
- fg: COLOR.warn,
4724
- children: "↑↓"
4725
- }),
4726
- " navigate · ",
4727
- /* @__PURE__ */ jsx$1("span", {
4728
- fg: COLOR.warn,
4729
- children: "↵/space"
4730
- }),
4731
- " expand · ",
4732
- /* @__PURE__ */ jsx$1("span", {
4733
- fg: COLOR.warn,
4734
- children: "esc"
4735
- }),
4736
- " close"
4737
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
4738
- fg: COLOR.warn,
4739
- children: "esc"
4740
- }), " close"] })
4741
- })
5126
+ /* @__PURE__ */ jsx$1(ModalHints, { hints: [
5127
+ expandableIds.length > 0 && {
5128
+ key: "↑↓",
5129
+ label: "navigate",
5130
+ labelColor: COLOR.dim
5131
+ },
5132
+ expandableIds.length > 0 && {
5133
+ key: "↵/space",
5134
+ label: "expand",
5135
+ labelColor: COLOR.dim
5136
+ },
5137
+ {
5138
+ key: "esc",
5139
+ label: "close",
5140
+ labelColor: COLOR.dim
5141
+ }
5142
+ ] })
4742
5143
  ]
4743
5144
  });
4744
5145
  }
@@ -5247,26 +5648,23 @@ function CreateExtensionModal({ userRoot, projectRoot, loadProjectExtensions, on
5247
5648
  fg: COLOR.error,
5248
5649
  children: error
5249
5650
  }),
5250
- /* @__PURE__ */ jsxs("text", {
5251
- fg: COLOR.dim,
5252
- children: [
5253
- /* @__PURE__ */ jsx$1("span", {
5254
- fg: COLOR.warn,
5255
- children: "tab"
5256
- }),
5257
- " scope · ",
5258
- /* @__PURE__ */ jsx$1("span", {
5259
- fg: COLOR.warn,
5260
- children: "↵"
5261
- }),
5262
- " create · ",
5263
- /* @__PURE__ */ jsx$1("span", {
5264
- fg: COLOR.warn,
5265
- children: "esc"
5266
- }),
5267
- " cancel"
5268
- ]
5269
- })
5651
+ /* @__PURE__ */ jsx$1(ModalHints, { hints: [
5652
+ {
5653
+ key: "tab",
5654
+ label: "scope",
5655
+ labelColor: COLOR.dim
5656
+ },
5657
+ {
5658
+ key: "↵",
5659
+ label: "create",
5660
+ labelColor: COLOR.dim
5661
+ },
5662
+ {
5663
+ key: "esc",
5664
+ label: "cancel",
5665
+ labelColor: COLOR.dim
5666
+ }
5667
+ ] })
5270
5668
  ]
5271
5669
  });
5272
5670
  }
@@ -5575,40 +5973,37 @@ function CwdPickerModal({ currentCwd, onPick }) {
5575
5973
  }, result.item.path);
5576
5974
  })
5577
5975
  }),
5578
- /* @__PURE__ */ jsxs("text", {
5579
- fg: COLOR.dim,
5580
- children: [
5581
- /* @__PURE__ */ jsx$1("span", {
5582
- fg: COLOR.warn,
5583
- children: "↑↓"
5584
- }),
5585
- " navigate · ",
5586
- /* @__PURE__ */ jsx$1("span", {
5587
- fg: COLOR.warn,
5588
- children: "tab"
5589
- }),
5590
- " enter dir · ",
5591
- /* @__PURE__ */ jsx$1("span", {
5592
- fg: COLOR.warn,
5593
- children: "←"
5594
- }),
5595
- " parent · ",
5596
- /* @__PURE__ */ jsx$1("span", {
5597
- fg: COLOR.warn,
5598
- children: "↵"
5599
- }),
5600
- " select · ",
5601
- /* @__PURE__ */ jsx$1("span", {
5602
- fg: COLOR.warn,
5603
- children: "esc"
5604
- }),
5605
- " close · ",
5606
- /* @__PURE__ */ jsx$1("span", {
5607
- fg: COLOR.mute,
5608
- children: `${results.length} match${results.length === 1 ? "" : "es"}`
5609
- })
5610
- ]
5611
- })
5976
+ /* @__PURE__ */ jsx$1(ModalHints, { hints: [
5977
+ {
5978
+ key: "↑↓",
5979
+ label: "navigate",
5980
+ labelColor: COLOR.dim
5981
+ },
5982
+ {
5983
+ key: "tab",
5984
+ label: "enter dir",
5985
+ labelColor: COLOR.dim
5986
+ },
5987
+ {
5988
+ key: "←",
5989
+ label: "parent",
5990
+ labelColor: COLOR.dim
5991
+ },
5992
+ {
5993
+ key: "↵",
5994
+ label: "select",
5995
+ labelColor: COLOR.dim
5996
+ },
5997
+ {
5998
+ key: "esc",
5999
+ label: "close",
6000
+ labelColor: COLOR.dim
6001
+ },
6002
+ {
6003
+ key: "",
6004
+ label: `${results.length} match${results.length === 1 ? "" : "es"}`
6005
+ }
6006
+ ] })
5612
6007
  ]
5613
6008
  });
5614
6009
  }
@@ -5895,6 +6290,21 @@ async function resolveExtensionModalNode(node) {
5895
6290
  let nextClientId = 1;
5896
6291
  /** Mirror of the TUI's ZIDANE_DEBUG-gated logger — silent by default. */
5897
6292
  const debugFallback = process.env.ZIDANE_DEBUG ? (op, error) => console.error(`[zidane/tui] history search ${op} fell back to the main thread:`, error) : () => {};
6293
+ /**
6294
+ * Rough searchable-size estimate for degraded builds — string block
6295
+ * payloads only, mirroring what index extraction keeps (attachments /
6296
+ * base64 are stripped there). Cheap enough to run inline; only feeds the
6297
+ * byte budget, so precision doesn't matter.
6298
+ */
6299
+ function estimateTurnsBytes(turns) {
6300
+ let total = 0;
6301
+ for (const turn of turns) for (const block of turn.content) {
6302
+ const b = block;
6303
+ if (typeof b.text === "string") total += b.text.length;
6304
+ if (typeof b.output === "string") total += b.output.length;
6305
+ }
6306
+ return total;
6307
+ }
5898
6308
  function workerUrl() {
5899
6309
  if (typeof Bun !== "undefined" && Bun.isStandaloneExecutable) return new URL("./history-search-worker.ts", import.meta.url);
5900
6310
  return new URL(import.meta.url.endsWith(".ts") ? "./history-search-worker.ts" : "./history-search-worker.js", import.meta.url);
@@ -5909,6 +6319,12 @@ var HistorySearchWorkerClient = class {
5909
6319
  this.createWorker = createWorker;
5910
6320
  }
5911
6321
  indexes = /* @__PURE__ */ new WeakMap();
6322
+ /**
6323
+ * Handles whose index lives in the worker (a successful `build`). Only
6324
+ * these are safe to `slim()` — a degraded handle's turns are the sole
6325
+ * source for its main-thread fallback index.
6326
+ */
6327
+ workerBuilt = /* @__PURE__ */ new WeakSet();
5912
6328
  registrations = /* @__PURE__ */ new Map();
5913
6329
  rebuilds = /* @__PURE__ */ new Map();
5914
6330
  pending = /* @__PURE__ */ new Map();
@@ -5926,10 +6342,11 @@ var HistorySearchWorkerClient = class {
5926
6342
  if (this.unavailable) return {
5927
6343
  indexId,
5928
6344
  length: turns.length,
6345
+ bytes: estimateTurnsBytes(turns),
5929
6346
  turns
5930
6347
  };
5931
6348
  try {
5932
- return {
6349
+ const handle = {
5933
6350
  ...await this.request({
5934
6351
  type: "build",
5935
6352
  indexId,
@@ -5937,16 +6354,47 @@ var HistorySearchWorkerClient = class {
5937
6354
  }, signal),
5938
6355
  turns
5939
6356
  };
6357
+ this.workerBuilt.add(handle);
6358
+ return handle;
5940
6359
  } catch (error) {
5941
6360
  if (signal?.aborted) throw signal.reason;
5942
6361
  debugFallback("build", error);
5943
6362
  return {
5944
6363
  indexId,
5945
6364
  length: turns.length,
6365
+ bytes: estimateTurnsBytes(turns),
5946
6366
  turns
5947
6367
  };
5948
6368
  }
5949
6369
  }
6370
+ /**
6371
+ * Drop the handle's main-thread turn retention once the worker owns the
6372
+ * index. Callers whose turns are alive anyway (live session, transcript
6373
+ * search) skip this; the cross-session cache calls it so 64 cached
6374
+ * handles don't pin 64 full sessions on the main thread. A slimmed
6375
+ * handle can't rebuild after a worker-side eviction — the caller is
6376
+ * expected to self-heal by rebuilding from the store on failure.
6377
+ */
6378
+ slim(index) {
6379
+ if (!this.workerBuilt.has(index)) return;
6380
+ index.turns = void 0;
6381
+ }
6382
+ /**
6383
+ * Main-thread eviction notice: free the handle's retained data AND the
6384
+ * worker's cached index. Without this the worker kept its copy until
6385
+ * its own LRU happened to cycle it out. Fire-and-forget by design.
6386
+ */
6387
+ release(index) {
6388
+ index.turns = void 0;
6389
+ index.fallback = void 0;
6390
+ if (!this.worker) return;
6391
+ try {
6392
+ this.worker.postMessage({
6393
+ type: "release",
6394
+ indexId: index.indexId
6395
+ });
6396
+ } catch {}
6397
+ }
5950
6398
  async search(index, query, limit, signal) {
5951
6399
  if (this.unavailable) return searchSessionIndexAsync(await this.fallbackIndex(index, signal), query, {
5952
6400
  limit,
@@ -6016,6 +6464,7 @@ var HistorySearchWorkerClient = class {
6016
6464
  rebuild(index) {
6017
6465
  const inFlight = this.rebuilds.get(index.indexId);
6018
6466
  if (inFlight) return inFlight;
6467
+ if (!index.turns) return Promise.reject(/* @__PURE__ */ new Error(`History search index released: ${index.indexId}`));
6019
6468
  const rebuild = this.request({
6020
6469
  type: "build",
6021
6470
  indexId: index.indexId,
@@ -6026,10 +6475,13 @@ var HistorySearchWorkerClient = class {
6026
6475
  }
6027
6476
  fallbackIndex(index, signal) {
6028
6477
  if (!("indexId" in index)) return Promise.resolve(index);
6029
- if (!index.fallback) index.fallback = buildSessionSearchIndexAsync(index.turns).catch((error) => {
6030
- index.fallback = void 0;
6031
- throw error;
6032
- });
6478
+ if (!index.fallback) {
6479
+ if (!index.turns) return Promise.reject(/* @__PURE__ */ new Error(`History search index released: ${index.indexId}`));
6480
+ index.fallback = buildSessionSearchIndexAsync(index.turns).catch((error) => {
6481
+ index.fallback = void 0;
6482
+ throw error;
6483
+ });
6484
+ }
6033
6485
  signal?.throwIfAborted();
6034
6486
  return index.fallback;
6035
6487
  }
@@ -6085,6 +6537,10 @@ var HistorySearchWorkerClient = class {
6085
6537
  }
6086
6538
  });
6087
6539
  this.registrations.set(indexId, registration);
6540
+ registration.catch(() => {
6541
+ this.registrations.delete(indexId);
6542
+ this.indexes.delete(index);
6543
+ });
6088
6544
  return registration.then(() => indexId);
6089
6545
  }
6090
6546
  request(payload, signal) {
@@ -6455,26 +6911,23 @@ function ModelOptionsPickerModal({ supportsReasoning, supportsAdaptive, currentE
6455
6911
  })]
6456
6912
  }, `option:${row.option.id}`);
6457
6913
  })]
6458
- }), /* @__PURE__ */ jsxs("text", {
6459
- fg: COLOR.dim,
6460
- children: [
6461
- /* @__PURE__ */ jsx$1("span", {
6462
- fg: COLOR.warn,
6463
- children: "↑↓"
6464
- }),
6465
- " navigate · ",
6466
- /* @__PURE__ */ jsx$1("span", {
6467
- fg: COLOR.warn,
6468
- children: "↵/space"
6469
- }),
6470
- " select/toggle · ",
6471
- /* @__PURE__ */ jsx$1("span", {
6472
- fg: COLOR.warn,
6473
- children: "esc"
6474
- }),
6475
- " close"
6476
- ]
6477
- })]
6914
+ }), /* @__PURE__ */ jsx$1(ModalHints, { hints: [
6915
+ {
6916
+ key: "↑↓",
6917
+ label: "navigate",
6918
+ labelColor: COLOR.dim
6919
+ },
6920
+ {
6921
+ key: "↵/space",
6922
+ label: "select/toggle",
6923
+ labelColor: COLOR.dim
6924
+ },
6925
+ {
6926
+ key: "esc",
6927
+ label: "close",
6928
+ labelColor: COLOR.dim
6929
+ }
6930
+ ] })]
6478
6931
  });
6479
6932
  }
6480
6933
  function SectionHeader({ label, color }) {
@@ -6795,38 +7248,37 @@ function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onAppl
6795
7248
  chainPosition: chainMode ? chainPositionOf(chainState, entry.providerKey, entry.model.id) : null
6796
7249
  }, `${entry.providerKey}:${entry.model.id}`))
6797
7250
  }),
6798
- /* @__PURE__ */ jsxs("text", {
6799
- fg: COLOR.dim,
6800
- children: [
6801
- /* @__PURE__ */ jsx$1("span", {
6802
- fg: COLOR.warn,
6803
- children: "↑↓"
6804
- }),
6805
- " navigate · ",
6806
- /* @__PURE__ */ jsx$1("span", {
6807
- fg: COLOR.warn,
6808
- children: "↵"
6809
- }),
6810
- chainMode ? " add/remove · " : " select · ",
6811
- chainMode && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
6812
- fg: COLOR.warn,
6813
- children: "ctrl+shift+↑↓"
6814
- }), " reorder · "] }),
6815
- /* @__PURE__ */ jsx$1("span", {
6816
- fg: COLOR.warn,
6817
- children: "esc"
6818
- }),
6819
- chainMode ? " apply · " : " close · ",
6820
- chainMode && /* @__PURE__ */ jsx$1("span", {
6821
- fg: COLOR.brand,
6822
- children: `${chainState.length} selected · `
6823
- }),
6824
- /* @__PURE__ */ jsx$1("span", {
6825
- fg: COLOR.mute,
6826
- children: `${filtered.length} / ${catalog.length} model${catalog.length === 1 ? "" : "s"}`
6827
- })
6828
- ]
6829
- })
7251
+ /* @__PURE__ */ jsx$1(ModalHints, { hints: [
7252
+ {
7253
+ key: "↑↓",
7254
+ label: "navigate",
7255
+ labelColor: COLOR.dim
7256
+ },
7257
+ {
7258
+ key: "↵",
7259
+ label: chainMode ? "add/remove" : "select",
7260
+ labelColor: COLOR.dim
7261
+ },
7262
+ chainMode && {
7263
+ key: "ctrl+shift+↑↓",
7264
+ label: "reorder",
7265
+ labelColor: COLOR.dim
7266
+ },
7267
+ {
7268
+ key: "esc",
7269
+ label: chainMode ? "apply" : "close",
7270
+ labelColor: COLOR.dim
7271
+ },
7272
+ chainMode && {
7273
+ key: "",
7274
+ label: `${chainState.length} selected`,
7275
+ labelColor: COLOR.brand
7276
+ },
7277
+ {
7278
+ key: "",
7279
+ label: `${filtered.length} / ${catalog.length} model${catalog.length === 1 ? "" : "s"}`
7280
+ }
7281
+ ] })
6830
7282
  ]
6831
7283
  });
6832
7284
  }
@@ -6987,38 +7439,37 @@ function ModelSelectList({ catalog, multi = false, initial, onApply, onCancel, v
6987
7439
  chainPosition: multi ? picks.includes(entry.model.id) ? picks.indexOf(entry.model.id) + 1 : null : null
6988
7440
  }, `${entry.providerKey}:${entry.model.id}`))
6989
7441
  }),
6990
- /* @__PURE__ */ jsxs("text", {
6991
- fg: COLOR.dim,
6992
- children: [
6993
- /* @__PURE__ */ jsx$1("span", {
6994
- fg: COLOR.warn,
6995
- children: "↑↓"
6996
- }),
6997
- " navigate · ",
6998
- /* @__PURE__ */ jsx$1("span", {
6999
- fg: COLOR.warn,
7000
- children: "↵"
7001
- }),
7002
- multi ? " add/remove · " : " select · ",
7003
- multi && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
7004
- fg: COLOR.warn,
7005
- children: "ctrl+shift+↑↓"
7006
- }), " reorder · "] }),
7007
- /* @__PURE__ */ jsx$1("span", {
7008
- fg: COLOR.warn,
7009
- children: "esc"
7010
- }),
7011
- multi ? " apply · " : " cancel · ",
7012
- multi && /* @__PURE__ */ jsx$1("span", {
7013
- fg: COLOR.brand,
7014
- children: `${picks.length} selected · `
7015
- }),
7016
- /* @__PURE__ */ jsx$1("span", {
7017
- fg: COLOR.mute,
7018
- children: `${filtered.length} / ${deduped.length} model${deduped.length === 1 ? "" : "s"}`
7019
- })
7020
- ]
7021
- })
7442
+ /* @__PURE__ */ jsx$1(ModalHints, { hints: [
7443
+ {
7444
+ key: "↑↓",
7445
+ label: "navigate",
7446
+ labelColor: COLOR.dim
7447
+ },
7448
+ {
7449
+ key: "↵",
7450
+ label: multi ? "add/remove" : "select",
7451
+ labelColor: COLOR.dim
7452
+ },
7453
+ multi && {
7454
+ key: "ctrl+shift+↑↓",
7455
+ label: "reorder",
7456
+ labelColor: COLOR.dim
7457
+ },
7458
+ {
7459
+ key: "esc",
7460
+ label: multi ? "apply" : "cancel",
7461
+ labelColor: COLOR.dim
7462
+ },
7463
+ multi && {
7464
+ key: "",
7465
+ label: `${picks.length} selected`,
7466
+ labelColor: COLOR.brand
7467
+ },
7468
+ {
7469
+ key: "",
7470
+ label: `${filtered.length} / ${deduped.length} model${deduped.length === 1 ? "" : "s"}`
7471
+ }
7472
+ ] })
7022
7473
  ]
7023
7474
  });
7024
7475
  }
@@ -7565,33 +8016,27 @@ function CompletionPopup({ state, visibleRows = 6 }) {
7565
8016
  })
7566
8017
  }, item.id);
7567
8018
  })
7568
- }), /* @__PURE__ */ jsxs("text", {
7569
- fg: COLOR.mute,
7570
- children: [
7571
- /* @__PURE__ */ jsx$1("span", {
7572
- fg: COLOR.warn,
7573
- children: "↑↓"
7574
- }),
7575
- " navigate · ",
7576
- /* @__PURE__ */ jsx$1("span", {
7577
- fg: COLOR.warn,
7578
- children: enterSelects ? "↵" : "tab"
7579
- }),
7580
- " select · ",
7581
- enterSelects ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
7582
- fg: COLOR.warn,
7583
- children: "tab"
7584
- }), " scope · "] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
7585
- fg: COLOR.warn,
7586
- children: "↵"
7587
- }), " submit · "] }),
7588
- /* @__PURE__ */ jsx$1("span", {
7589
- fg: COLOR.warn,
7590
- children: "esc"
7591
- }),
7592
- " close"
7593
- ]
7594
- })] });
8019
+ }), /* @__PURE__ */ jsx$1(ModalHints, { hints: [
8020
+ {
8021
+ key: "↑↓",
8022
+ label: "navigate"
8023
+ },
8024
+ {
8025
+ key: enterSelects ? "↵" : "tab",
8026
+ label: "select"
8027
+ },
8028
+ enterSelects ? {
8029
+ key: "tab",
8030
+ label: "scope"
8031
+ } : {
8032
+ key: "↵",
8033
+ label: "submit"
8034
+ },
8035
+ {
8036
+ key: "esc",
8037
+ label: "close"
8038
+ }
8039
+ ] })] });
7595
8040
  }
7596
8041
  return /* @__PURE__ */ jsxs("box", {
7597
8042
  style: {
@@ -7851,7 +8296,7 @@ const BULK_ACTIONS = [
7851
8296
  destructive: true
7852
8297
  }
7853
8298
  ];
7854
- function ActionBar({ actions, selected, customLabel }) {
8299
+ function ActionBar({ actions, selected, customLabel, onActivate, onHover }) {
7855
8300
  const COLOR = useColors();
7856
8301
  const SURFACE = useSurfaces();
7857
8302
  return /* @__PURE__ */ jsx$1("box", {
@@ -7869,6 +8314,12 @@ function ActionBar({ actions, selected, customLabel }) {
7869
8314
  marginRight: 2,
7870
8315
  flexShrink: 0
7871
8316
  },
8317
+ onMouseDown: (event) => {
8318
+ event.stopPropagation();
8319
+ event.preventDefault();
8320
+ onActivate?.(action, i);
8321
+ },
8322
+ onMouseOver: () => onHover?.(i),
7872
8323
  children: isSelected ? /* @__PURE__ */ jsx$1("text", {
7873
8324
  bg: tint,
7874
8325
  fg: SURFACE.background,
@@ -8030,7 +8481,9 @@ function SingleEditApprovalModal({ request, payload, priorContent, priorError, t
8030
8481
  })]
8031
8482
  }), /* @__PURE__ */ jsx$1(ActionBar, {
8032
8483
  actions: BULK_ACTIONS,
8033
- selected
8484
+ selected,
8485
+ onActivate: (action) => decide(action.decision),
8486
+ onHover: setSelected
8034
8487
  })]
8035
8488
  }), /* @__PURE__ */ jsxs("text", {
8036
8489
  style: {
@@ -8355,6 +8808,10 @@ function MultiEditApprovalModal({ request, payload, priorContent, priorError, ta
8355
8808
  customLabel: {
8356
8809
  idx: 0,
8357
8810
  label: applyLabel
8811
+ },
8812
+ onActivate: (action) => decide(resolveDecision(action.decision)),
8813
+ onHover: (i) => {
8814
+ if (zone === "actions") setActionIdx(i);
8358
8815
  }
8359
8816
  })
8360
8817
  ]
@@ -9341,6 +9798,19 @@ function MultiSelectQuestionInput({ question, initialChoiceIds, onSubmit }) {
9341
9798
  const isPicked = picked.has(choice.id);
9342
9799
  return /* @__PURE__ */ jsxs("text", {
9343
9800
  wrapMode: "word",
9801
+ onMouseDown: (event) => {
9802
+ event.stopPropagation();
9803
+ event.preventDefault();
9804
+ setCursor(i);
9805
+ if (!inCheckboxZone(event, 5)) return;
9806
+ setPicked((prev) => {
9807
+ const next = new Set(prev);
9808
+ if (next.has(choice.id)) next.delete(choice.id);
9809
+ else next.add(choice.id);
9810
+ return next;
9811
+ });
9812
+ },
9813
+ onMouseOver: () => setCursor(i),
9344
9814
  children: [
9345
9815
  /* @__PURE__ */ jsx$1("span", {
9346
9816
  fg: COLOR.mute,
@@ -9364,25 +9834,28 @@ function MultiSelectQuestionInput({ question, initialChoiceIds, onSubmit }) {
9364
9834
  })
9365
9835
  ]
9366
9836
  }, choice.id);
9367
- }), /* @__PURE__ */ jsxs("text", {
9368
- fg: COLOR.mute,
9369
- children: [
9370
- " ",
9371
- /* @__PURE__ */ jsx$1("span", {
9372
- fg: COLOR.warn,
9373
- children: "space"
9374
- }),
9375
- " toggle · ",
9376
- /* @__PURE__ */ jsx$1("span", {
9377
- fg: COLOR.warn,
9378
- children: "↵"
9379
- }),
9380
- ` submit${required ? " · " : ""}`,
9381
- required && /* @__PURE__ */ jsx$1("span", {
9382
- fg: COLOR.warn,
9383
- children: "required"
9384
- })
9385
- ]
9837
+ }), /* @__PURE__ */ jsxs("box", {
9838
+ style: { flexDirection: "row" },
9839
+ children: [/* @__PURE__ */ jsx$1("text", {
9840
+ fg: COLOR.mute,
9841
+ children: " "
9842
+ }), /* @__PURE__ */ jsx$1(HintRow, { hints: [
9843
+ {
9844
+ key: "space",
9845
+ label: "toggle",
9846
+ labelColor: COLOR.mute
9847
+ },
9848
+ {
9849
+ key: "↵",
9850
+ label: "submit",
9851
+ labelColor: COLOR.mute
9852
+ },
9853
+ ...required ? [{
9854
+ key: "",
9855
+ label: "required",
9856
+ labelColor: COLOR.warn
9857
+ }] : []
9858
+ ] })]
9386
9859
  })]
9387
9860
  });
9388
9861
  }
@@ -9407,7 +9880,9 @@ function OptionList({ items, initialCursor, onPick }) {
9407
9880
  if (typeof initialCursor !== "number" || items.length === 0) return 0;
9408
9881
  return Math.max(0, Math.min(items.length - 1, initialCursor));
9409
9882
  });
9883
+ const [clickArmed, setClickArmed] = useState(false);
9410
9884
  const moveCursor = useCallback((delta) => {
9885
+ setClickArmed(true);
9411
9886
  setCursor((c) => {
9412
9887
  if (items.length === 0) return c;
9413
9888
  return ((c + delta) % items.length + items.length) % items.length;
@@ -9438,6 +9913,16 @@ function OptionList({ items, initialCursor, onPick }) {
9438
9913
  const isCursor = i === cursor;
9439
9914
  return /* @__PURE__ */ jsxs("text", {
9440
9915
  wrapMode: "word",
9916
+ onMouseDown: (event) => {
9917
+ event.stopPropagation();
9918
+ event.preventDefault();
9919
+ if (isCursor && clickArmed) {
9920
+ onPick(item.id);
9921
+ return;
9922
+ }
9923
+ setCursor(i);
9924
+ setClickArmed(true);
9925
+ },
9441
9926
  children: [
9442
9927
  /* @__PURE__ */ jsx$1("span", {
9443
9928
  fg: COLOR.mute,
@@ -9676,10 +10161,11 @@ function toneColor(tone, COLOR) {
9676
10161
  * the parent picker's `useKeyboard` (via a ref) to suppress single-key
9677
10162
  * shortcuts (`l` / `o` / `r`) while the user is typing into the input.
9678
10163
  */
9679
- function McpAuthorizingPanel({ serverName, authUrl, maxLineWidth, chromeWidth, inputFocused }) {
10164
+ function McpAuthorizingPanel({ serverName, authUrl, maxLineWidth, chromeWidth, inputFocused, fill = false }) {
9680
10165
  const COLOR = useColors();
9681
10166
  const inputRef = useRef(null);
9682
10167
  const [hint, setHint] = useState(null);
10168
+ const status = hint && hint.tone !== "error" ? hint.text : authUrl === null ? "starting login flow…" : "complete the login in your browser — waiting for the callback…";
9683
10169
  const onSubmit = useCallback(() => {
9684
10170
  const value = inputRef.current?.value?.trim() ?? "";
9685
10171
  if (!value) return;
@@ -9709,32 +10195,37 @@ function McpAuthorizingPanel({ serverName, authUrl, maxLineWidth, chromeWidth, i
9709
10195
  return /* @__PURE__ */ jsxs("box", {
9710
10196
  style: {
9711
10197
  flexDirection: "column",
9712
- border: ["top"],
9713
- borderColor: COLOR.border,
9714
- paddingTop: 1
10198
+ ...fill ? { flexGrow: 1 } : { flexShrink: 0 }
9715
10199
  },
9716
- children: [
9717
- /* @__PURE__ */ jsx$1("text", {
9718
- fg: COLOR.brand,
9719
- children: `Authorizing ${serverName}`
9720
- }),
9721
- /* @__PURE__ */ jsx$1("text", {
9722
- fg: COLOR.dim,
9723
- children: "Your browser should have opened — complete the login, then return here."
9724
- }),
9725
- /* @__PURE__ */ jsx$1(OAuthAuthBlock, {
9726
- authUrl,
9727
- maxLineWidth,
9728
- chromeWidth,
9729
- paste: {
9730
- inputRef,
9731
- focused: inputFocused,
9732
- onSubmit,
9733
- placeholder: "paste redirect URL and press enter…",
9734
- hint: hint ?? void 0
9735
- }
9736
- })
9737
- ]
10200
+ children: [/* @__PURE__ */ jsxs("box", {
10201
+ style: {
10202
+ flexDirection: "column",
10203
+ border: true,
10204
+ borderColor: COLOR.border,
10205
+ padding: 1,
10206
+ gap: 1,
10207
+ ...fill ? { flexGrow: 1 } : {}
10208
+ },
10209
+ children: [
10210
+ /* @__PURE__ */ jsx$1(Spinner, { label: status }),
10211
+ authUrl !== null && /* @__PURE__ */ jsx$1(OAuthAuthBlock, {
10212
+ authUrl,
10213
+ maxLineWidth,
10214
+ chromeWidth: (chromeWidth ?? 14) + 4,
10215
+ paste: {
10216
+ inputRef,
10217
+ focused: inputFocused,
10218
+ onSubmit,
10219
+ placeholder: "paste redirect URL and press enter…",
10220
+ hint: hint?.tone === "error" ? hint : void 0
10221
+ }
10222
+ }),
10223
+ /* @__PURE__ */ jsx$1("text", {
10224
+ fg: COLOR.dim,
10225
+ children: "esc cancel — the server stays signed out until you retry"
10226
+ })
10227
+ ]
10228
+ }), /* @__PURE__ */ jsx$1(TitleOverlay, { title: `authorizing ${serverName} — OAuth` })]
9738
10229
  });
9739
10230
  }
9740
10231
  //#endregion
@@ -10796,7 +11287,13 @@ function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreat
10796
11287
  focused: idx === cursorIndex && focused,
10797
11288
  isCurrent: row.kind === "session" && row.rowId === currentId,
10798
11289
  showProject: showAllProjects,
10799
- currentProjectRoot
11290
+ currentProjectRoot,
11291
+ onHover: focused ? () => moveCursor(idx) : void 0,
11292
+ onClick: focused ? () => {
11293
+ moveCursor(idx);
11294
+ if (row.kind === "new") onCreate();
11295
+ else onPick(row.rowId);
11296
+ } : void 0
10800
11297
  }, row.rowId)), filteredSessions.length === 0 && query.trim().length > 0 && /* @__PURE__ */ jsxs("text", {
10801
11298
  wrapMode: "none",
10802
11299
  children: [
@@ -10838,7 +11335,7 @@ function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreat
10838
11335
  * exactly the width of the focus + current markers above — so the first
10839
11336
  * stat (the turn count) sits flush under the title's first letter.
10840
11337
  */
10841
- function SessionRow({ row, anchorId, focused, isCurrent, separator = false, showProject = false, currentProjectRoot }) {
11338
+ function SessionRow({ row, anchorId, focused, isCurrent, separator = false, showProject = false, currentProjectRoot, onClick, onHover }) {
10842
11339
  const COLOR = useColors();
10843
11340
  const SURFACE = useSurfaces();
10844
11341
  const STATS_INDENT = " ";
@@ -10854,9 +11351,18 @@ function SessionRow({ row, anchorId, focused, isCurrent, separator = false, show
10854
11351
  borderColor: COLOR.border
10855
11352
  } : {}
10856
11353
  };
11354
+ const mouseProps = {
11355
+ ...onClick ? { onMouseDown: (event) => {
11356
+ event.stopPropagation();
11357
+ event.preventDefault();
11358
+ onClick();
11359
+ } } : {},
11360
+ ...onHover ? { onMouseOver: () => onHover() } : {}
11361
+ };
10857
11362
  if (row.kind === "new") return /* @__PURE__ */ jsxs("box", {
10858
11363
  id: anchorId,
10859
11364
  style: rowStyle,
11365
+ ...mouseProps,
10860
11366
  children: [/* @__PURE__ */ jsxs("text", {
10861
11367
  wrapMode: "none",
10862
11368
  children: [
@@ -10890,6 +11396,7 @@ function SessionRow({ row, anchorId, focused, isCurrent, separator = false, show
10890
11396
  return /* @__PURE__ */ jsxs("box", {
10891
11397
  id: anchorId,
10892
11398
  style: rowStyle,
11399
+ ...mouseProps,
10893
11400
  children: [
10894
11401
  /* @__PURE__ */ jsxs("text", {
10895
11402
  wrapMode: "none",
@@ -11083,7 +11590,7 @@ const EMPTY_QUEUED_MESSAGES = [];
11083
11590
  function shouldRenderTranscriptSearch(searchMode, query) {
11084
11591
  return searchMode && query.trim().length > 0;
11085
11592
  }
11086
- function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EMPTY_QUEUED_MESSAGES, queueSelectionIndex = null, queueShortcuts, onEnterQueueFromEmptyPrompt, settings, onSubmit, session, pending, onApproval, pendingInteraction, onInteraction, completionProviders, inlineAutocompleteProviders, onCompletionTab, enterSelectsCompletion, onPopupOpenChange, selectedTurnId, transcriptSearchMode = false, transcriptSearchQuery = "", transcriptSearchEvents = [], transcriptSearchSearching = false, onTranscriptSearchModeChange, onTranscriptSearchQueryChange, onTranscriptSearchNavigate, onTranscriptSearchOpen, onLoadEarlierTranscript, promptTriggerHints, attachImageBinding, editorBinding, sessionDetailsBinding, liveSession = null }) {
11593
+ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EMPTY_QUEUED_MESSAGES, queueSelectionIndex = null, queueShortcuts, onEnterQueueFromEmptyPrompt, settings, onSubmit, session, pending, onApproval, pendingInteraction, onInteraction, completionProviders, inlineAutocompleteProviders, onCompletionTab, enterSelectsCompletion, onPopupOpenChange, selectedTurnId, onSelectTurn, onOpenTurn, transcriptSearchMode = false, transcriptSearchQuery = "", transcriptSearchEvents = [], transcriptSearchSearching = false, onTranscriptSearchModeChange, onTranscriptSearchQueryChange, onTranscriptSearchNavigate, onTranscriptSearchOpen, onLoadEarlierTranscript, promptTriggerHints, attachImageBinding, editorBinding, sessionDetailsBinding, liveSession = null }) {
11087
11594
  const COLOR = useColors();
11088
11595
  const titleText = session?.title ?? "New session";
11089
11596
  const sessionShortcutKey = formatBindingForDisplay(sessionDetailsBinding);
@@ -11173,6 +11680,8 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
11173
11680
  events: displayedEvents,
11174
11681
  settings,
11175
11682
  selectedTurnId: selectedTurnId ?? null,
11683
+ onSelectTurn,
11684
+ onOpenTurn,
11176
11685
  searchQuery: transcriptSearchActive ? transcriptSearchQuery : void 0,
11177
11686
  searchPending: transcriptSearchSearching,
11178
11687
  busy: !transcriptSearchActive && busy && !pending && !pendingInteraction,
@@ -11221,7 +11730,8 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
11221
11730
  ] }),
11222
11731
  /* @__PURE__ */ jsx$1(TitleOverlay, {
11223
11732
  title: titleText,
11224
- meta: metaSegments
11733
+ meta: metaSegments,
11734
+ clickKey: sessionDetailsBinding ?? null
11225
11735
  })
11226
11736
  ]
11227
11737
  });
@@ -12302,7 +12812,6 @@ const PROMPT_HINTS_SELECT_QUEUE = [{
12302
12812
  * shortcuts that actually do something in the active mode.
12303
12813
  */
12304
12814
  function PromptHints({ selectMode, searchMode = false, triggerHints, busy = false }) {
12305
- const COLOR = useColors();
12306
12815
  const { settings } = useSettings();
12307
12816
  const { width: termWidth } = useTerminalDimensions();
12308
12817
  const minimalResting = !selectMode && !searchMode && !busy && settings.uiMode === "minimal";
@@ -12319,21 +12828,21 @@ function PromptHints({ selectMode, searchMode = false, triggerHints, busy = fals
12319
12828
  termWidth
12320
12829
  ]);
12321
12830
  if (hints.length === 0) return null;
12322
- return /* @__PURE__ */ jsxs("text", {
12831
+ return /* @__PURE__ */ jsxs("box", {
12323
12832
  style: {
12324
12833
  position: "absolute",
12325
12834
  top: 0,
12326
- right: 1
12835
+ right: 1,
12836
+ flexDirection: "row"
12327
12837
  },
12328
- fg: COLOR.dim,
12329
12838
  children: [
12330
- /* @__PURE__ */ jsx$1("span", {
12331
- fg: COLOR.mute,
12839
+ /* @__PURE__ */ jsx$1("text", {
12840
+ selectable: false,
12332
12841
  children: " "
12333
12842
  }),
12334
- renderHintSpans(hints, COLOR),
12335
- /* @__PURE__ */ jsx$1("span", {
12336
- fg: COLOR.mute,
12843
+ /* @__PURE__ */ jsx$1(HintRow, { hints }),
12844
+ /* @__PURE__ */ jsx$1("text", {
12845
+ selectable: false,
12337
12846
  children: " "
12338
12847
  })
12339
12848
  ]
@@ -12896,81 +13405,64 @@ function ActionRow$2({ pending, copyStatus, titleStatus, titleError, hasGenerate
12896
13405
  })]
12897
13406
  });
12898
13407
  }
12899
- if (copyStatus === "copied") return /* @__PURE__ */ jsxs("text", {
12900
- fg: COLOR.dim,
12901
- children: [
12902
- /* @__PURE__ */ jsx$1("span", {
12903
- fg: COLOR.accent,
12904
- children: "✓ session id copied"
12905
- }),
12906
- " · ",
12907
- /* @__PURE__ */ jsx$1("span", {
12908
- fg: COLOR.warn,
12909
- children: keys.delete
12910
- }),
12911
- " delete · ",
12912
- /* @__PURE__ */ jsx$1("span", {
12913
- fg: COLOR.warn,
12914
- children: "esc"
12915
- }),
12916
- " close"
12917
- ]
12918
- });
12919
- if (copyStatus === "failed") return /* @__PURE__ */ jsxs("text", {
12920
- fg: COLOR.dim,
12921
- children: [
12922
- /* @__PURE__ */ jsx$1("span", {
12923
- fg: COLOR.error,
12924
- children: "copy failed (terminal may not support OSC 52)"
12925
- }),
12926
- " · ",
12927
- /* @__PURE__ */ jsx$1("span", {
12928
- fg: COLOR.warn,
12929
- children: "esc"
12930
- }),
12931
- " close"
12932
- ]
12933
- });
12934
- return /* @__PURE__ */ jsxs("text", {
12935
- fg: COLOR.dim,
12936
- children: [
12937
- hasGenerate && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
12938
- fg: COLOR.warn,
12939
- children: keys.generate
12940
- }), " title · "] }),
12941
- hasExport && /* @__PURE__ */ jsxs(Fragment, { children: [
12942
- /* @__PURE__ */ jsx$1("span", {
12943
- fg: COLOR.warn,
12944
- children: keys.markdown
12945
- }),
12946
- "/",
12947
- /* @__PURE__ */ jsx$1("span", {
12948
- fg: COLOR.warn,
12949
- children: keys.json
12950
- }),
12951
- " export · "
12952
- ] }),
12953
- hasCompact && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
12954
- fg: COLOR.warn,
12955
- children: keys.compact
12956
- }), " compact · "] }),
12957
- /* @__PURE__ */ jsx$1("span", {
12958
- fg: COLOR.warn,
12959
- children: keys.delete
12960
- }),
12961
- " delete · ",
12962
- /* @__PURE__ */ jsx$1("span", {
12963
- fg: COLOR.warn,
12964
- children: keys.copy
12965
- }),
12966
- " copy · ",
12967
- /* @__PURE__ */ jsx$1("span", {
12968
- fg: COLOR.warn,
12969
- children: "esc"
12970
- }),
12971
- " close"
12972
- ]
12973
- });
13408
+ if (copyStatus === "copied") return /* @__PURE__ */ jsx$1(ModalHints, { hints: [
13409
+ {
13410
+ key: "",
13411
+ label: "✓ session id copied",
13412
+ labelColor: COLOR.accent
13413
+ },
13414
+ {
13415
+ key: keys.delete,
13416
+ label: "delete",
13417
+ labelColor: COLOR.dim
13418
+ },
13419
+ {
13420
+ key: "esc",
13421
+ label: "close",
13422
+ labelColor: COLOR.dim
13423
+ }
13424
+ ] });
13425
+ if (copyStatus === "failed") return /* @__PURE__ */ jsx$1(ModalHints, { hints: [{
13426
+ key: "",
13427
+ label: "copy failed (terminal may not support OSC 52)",
13428
+ labelColor: COLOR.error
13429
+ }, {
13430
+ key: "esc",
13431
+ label: "close",
13432
+ labelColor: COLOR.dim
13433
+ }] });
13434
+ return /* @__PURE__ */ jsx$1(ModalHints, { hints: [
13435
+ hasGenerate && {
13436
+ key: keys.generate,
13437
+ label: "title",
13438
+ labelColor: COLOR.dim
13439
+ },
13440
+ hasExport && {
13441
+ key: `${keys.markdown}/${keys.json}`,
13442
+ label: "export",
13443
+ labelColor: COLOR.dim
13444
+ },
13445
+ hasCompact && {
13446
+ key: keys.compact,
13447
+ label: "compact",
13448
+ labelColor: COLOR.dim
13449
+ },
13450
+ {
13451
+ key: keys.delete,
13452
+ label: "delete",
13453
+ labelColor: COLOR.dim
13454
+ },
13455
+ {
13456
+ key: keys.copy,
13457
+ label: "copy",
13458
+ labelColor: COLOR.dim
13459
+ },
13460
+ {
13461
+ key: "esc",
13462
+ label: "close",
13463
+ labelColor: COLOR.dim
13464
+ }
13465
+ ] });
12974
13466
  }
12975
13467
  /**
12976
13468
  * Sum token + cost figures across every {@link SessionRun}. Preference
@@ -14434,8 +14926,8 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14434
14926
  [activeTab]: 0
14435
14927
  }));
14436
14928
  }, [activeTab]);
14437
- const expandFocusedMcpRow = useCallback(() => {
14438
- const row = mcpVisibleRows[cursor];
14929
+ const expandMcpRowAt = useCallback((index) => {
14930
+ const row = mcpVisibleRows[index];
14439
14931
  if (!row || row.kind !== "server") return;
14440
14932
  const name = row.entry.config.name;
14441
14933
  if ((mcpToolsByServer[name]?.length ?? 0) === 0) fireMcpToolsRefresh();
@@ -14447,12 +14939,11 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14447
14939
  });
14448
14940
  }, [
14449
14941
  mcpVisibleRows,
14450
- cursor,
14451
14942
  mcpToolsByServer,
14452
14943
  fireMcpToolsRefresh
14453
14944
  ]);
14454
- const collapseFocusedMcpRow = useCallback(() => {
14455
- const row = mcpVisibleRows[cursor];
14945
+ const collapseMcpRowAt = useCallback((index) => {
14946
+ const row = mcpVisibleRows[index];
14456
14947
  if (!row) return;
14457
14948
  const parentName = parentServerName(row);
14458
14949
  if (row.kind !== "server") {
@@ -14468,7 +14959,18 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14468
14959
  next.delete(parentName);
14469
14960
  return next;
14470
14961
  });
14471
- }, [mcpVisibleRows, cursor]);
14962
+ }, [mcpVisibleRows]);
14963
+ const toggleExpandMcpRowAt = useCallback((index) => {
14964
+ const row = mcpVisibleRows[index];
14965
+ if (!row) return;
14966
+ if (expandedMcps.has(parentServerName(row))) collapseMcpRowAt(index);
14967
+ else expandMcpRowAt(index);
14968
+ }, [
14969
+ mcpVisibleRows,
14970
+ expandedMcps,
14971
+ collapseMcpRowAt,
14972
+ expandMcpRowAt
14973
+ ]);
14472
14974
  /**
14473
14975
  * Single `tab` press toggles between expanded / collapsed based on
14474
14976
  * the focused row's current state. The split helpers above remain
@@ -14476,21 +14978,9 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14476
14978
  * users expect — expand-only / collapse-only). Toggle is the
14477
14979
  * default that the footer hint advertises.
14478
14980
  */
14479
- const toggleExpandFocusedMcpRow = useCallback(() => {
14480
- const row = mcpVisibleRows[cursor];
14481
- if (!row) return;
14482
- const parentName = parentServerName(row);
14483
- if (expandedMcps.has(parentName)) collapseFocusedMcpRow();
14484
- else expandFocusedMcpRow();
14485
- }, [
14486
- mcpVisibleRows,
14487
- cursor,
14488
- expandedMcps,
14489
- collapseFocusedMcpRow,
14490
- expandFocusedMcpRow
14491
- ]);
14492
- const toggleExpandFocusedExtensionRow = useCallback(() => {
14493
- const row = extensionVisibleRows[cursor];
14981
+ const toggleExpandFocusedMcpRow = useCallback(() => toggleExpandMcpRowAt(cursor), [toggleExpandMcpRowAt, cursor]);
14982
+ const toggleExpandExtensionRowAt = useCallback((index) => {
14983
+ const row = extensionVisibleRows[index];
14494
14984
  const parentName = parentExtensionName(row);
14495
14985
  if (!parentName) return;
14496
14986
  setExpandedExtensions((prev) => {
@@ -14507,7 +14997,8 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14507
14997
  } else next.add(parentName);
14508
14998
  return next;
14509
14999
  });
14510
- }, [extensionVisibleRows, cursor]);
15000
+ }, [extensionVisibleRows]);
15001
+ const toggleExpandFocusedExtensionRow = useCallback(() => toggleExpandExtensionRowAt(cursor), [toggleExpandExtensionRowAt, cursor]);
14511
15002
  const mcpRowCount = mcpVisibleRows.length;
14512
15003
  const extensionRowCount = extensionVisibleRows.length;
14513
15004
  useEffect(() => {
@@ -14590,13 +15081,163 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14590
15081
  useEffect(() => {
14591
15082
  setMcpRemoveArmed(null);
14592
15083
  }, [focusedMcp?.config.name, activeTab]);
14593
- const oauthPasteActive = activeTab === "mcps" && focusedMcpStatus?.kind === "authorizing" && !!focusedMcpStatus.url;
15084
+ const authorizingMcp = activeTab === "mcps" ? mcpsCatalog.map((entry) => ({
15085
+ entry,
15086
+ status: getMcpAuthStatus(authState, entry.config.name)
15087
+ })).find(({ status }) => status.kind === "authorizing") : void 0;
15088
+ const oauthPasteActive = !!authorizingMcp && authorizingMcp.status.kind === "authorizing" && !!authorizingMcp.status.url;
14594
15089
  useEffect(() => {
14595
- if (!oauthPasteActive && !editingInputKey) inputRef.current?.focus();
14596
- }, [oauthPasteActive, editingInputKey]);
15090
+ if (!oauthPasteActive && !editingInputKey && !authorizingMcp) inputRef.current?.focus();
15091
+ }, [
15092
+ oauthPasteActive,
15093
+ editingInputKey,
15094
+ authorizingMcp
15095
+ ]);
14597
15096
  useEffect(() => {
14598
15097
  if (editingInputKey) editInputRef.current?.focus();
14599
15098
  }, [editingInputKey]);
15099
+ /**
15100
+ * Activate the row at `index` on the ACTIVE tab — the ↵ semantics
15101
+ * (toggle / cycle / open editor / run action), extracted so row
15102
+ * CLICKS can activate the clicked row directly. Cursor-based
15103
+ * callers (the keyboard handler) pass `cursor`.
15104
+ */
15105
+ const activateRowAt = useCallback((index) => {
15106
+ if (isCategoryTab(activeTab)) {
15107
+ const it = filteredByCategory[activeTab][index];
15108
+ if (!it) return;
15109
+ if (it.kind === "toggle") toggleBoolean(it.key);
15110
+ else if (it.kind === "choice") {
15111
+ const current = settings[it.key];
15112
+ const idx = it.options.findIndex((o) => o.value === current);
15113
+ const next = it.options[(idx + 1) % it.options.length];
15114
+ if (next) setSetting(it.key, next.value);
15115
+ } else if (it.kind === "input") setEditingInputKey(it.key);
15116
+ else it.onPick();
15117
+ return;
15118
+ }
15119
+ if (activeTab === "skills") {
15120
+ const s = filteredSkills[index];
15121
+ if (s) skillsToggle.toggle(s.name);
15122
+ return;
15123
+ }
15124
+ if (activeTab === "extensions") {
15125
+ const row = extensionVisibleRows[index];
15126
+ if (!row) return;
15127
+ if (row.kind === "extension") extensionsToggle.toggle(row.ext.name);
15128
+ else if (row.kind === "config") activateConfigRow(row.extName, row.item);
15129
+ return;
15130
+ }
15131
+ if (activeTab === "mcps") {
15132
+ const row = mcpVisibleRows[index];
15133
+ if (!row) return;
15134
+ if (row.kind === "server") {
15135
+ const name = row.entry.config.name;
15136
+ const enabling = !mcpsToggle.enabledSet.has(name);
15137
+ mcpsToggle.toggle(name);
15138
+ if (enabling && (mcpToolsByServer[name]?.length ?? 0) === 0) fireMcpToolsRefresh();
15139
+ } else if (row.kind === "tool") mcpToolToggleMap[row.serverName]?.toggle(row.tool.name);
15140
+ return;
15141
+ }
15142
+ if (activeTab === "keybindings") {
15143
+ actions?.onOpenKeybindings?.();
15144
+ return;
15145
+ }
15146
+ if (activeTab === "authentication") {
15147
+ const provider = filteredProviders[index];
15148
+ if (provider) actions?.onConfigureProvider?.(provider);
15149
+ }
15150
+ }, [
15151
+ activeTab,
15152
+ filteredByCategory,
15153
+ settings,
15154
+ setSetting,
15155
+ toggleBoolean,
15156
+ filteredSkills,
15157
+ skillsToggle,
15158
+ extensionVisibleRows,
15159
+ extensionsToggle,
15160
+ activateConfigRow,
15161
+ mcpVisibleRows,
15162
+ mcpsToggle,
15163
+ mcpToolsByServer,
15164
+ fireMcpToolsRefresh,
15165
+ mcpToolToggleMap,
15166
+ actions,
15167
+ filteredProviders
15168
+ ]);
15169
+ /**
15170
+ * True while an inline editor or overlay dialog owns the modal's input
15171
+ * — the same states the keyboard handler early-returns on. Mouse paths
15172
+ * (`clickRowAt`, tab-strip clicks) must honor it too: a click on a row
15173
+ * or tab underneath an open dialog would otherwise toggle settings or
15174
+ * switch tabs behind the user's back. Also drives the lists' `focused`
15175
+ * prop, so "list looks focused" and "list accepts input" cannot drift.
15176
+ */
15177
+ const modalInputCaptured = oauthPasteActive || configEditActive || editingInputKey !== null || extInstallActive || skillInstallActive || skillImportAllActive || mcpAddActive;
15178
+ /**
15179
+ * Row CLICK dispatch: focus the clicked row, then act on it. Expandable
15180
+ * headers (MCP servers, extensions with config) expand/collapse — the
15181
+ * disclosure is the row's primary affordance; everything else gets the
15182
+ * ↵ semantics (toggle / cycle / edit / action). Rows stay single
15183
+ * renderables (the MCP list documents an OpenTUI layout regression that
15184
+ * forbids box wrappers there), so the whole row is one click target —
15185
+ * the enable checkbox on expandable headers remains ↵.
15186
+ */
15187
+ /**
15188
+ * Row CLICK dispatch, by ZONE:
15189
+ *
15190
+ * - `checkbox` — the click landed on the row's `[✓] ` cell (rows
15191
+ * compute this by glyph column — see `inCheckboxZone`): flip the
15192
+ * toggle (↵ semantics). The checkbox is a SOLO target, including
15193
+ * the enable checkbox on MCP server / extension header rows.
15194
+ * - `row` — anywhere else: expandable headers expand/collapse;
15195
+ * checkbox-bearing rows only FOCUS (a body click must never flip
15196
+ * state the user didn't aim at); rows whose whole body is one
15197
+ * action (choices, inputs, actions, auth) activate.
15198
+ */
15199
+ const clickRowAt = useCallback((index, zone = "row") => {
15200
+ if (modalInputCaptured) return;
15201
+ setCursorByTab((prev) => ({
15202
+ ...prev,
15203
+ [activeTab]: index
15204
+ }));
15205
+ if (zone === "checkbox") {
15206
+ activateRowAt(index);
15207
+ return;
15208
+ }
15209
+ if (activeTab === "mcps") {
15210
+ if (mcpVisibleRows[index]?.kind === "server") toggleExpandMcpRowAt(index);
15211
+ return;
15212
+ }
15213
+ if (activeTab === "extensions") {
15214
+ const row = extensionVisibleRows[index];
15215
+ if (row?.kind === "extension") {
15216
+ if (row.hasConfig) toggleExpandExtensionRowAt(index);
15217
+ return;
15218
+ }
15219
+ if (row?.kind === "config" && row.item.type === "boolean") return;
15220
+ activateRowAt(index);
15221
+ return;
15222
+ }
15223
+ if (activeTab === "skills") return;
15224
+ if (isCategoryTab(activeTab) && filteredByCategory[activeTab][index]?.kind === "toggle") return;
15225
+ activateRowAt(index);
15226
+ }, [
15227
+ activeTab,
15228
+ modalInputCaptured,
15229
+ mcpVisibleRows,
15230
+ extensionVisibleRows,
15231
+ filteredByCategory,
15232
+ toggleExpandMcpRowAt,
15233
+ toggleExpandExtensionRowAt,
15234
+ activateRowAt
15235
+ ]);
15236
+ /** Tab-strip clicks — same input-capture guard as row clicks. */
15237
+ const clickTab = useCallback((id) => {
15238
+ if (modalInputCaptured) return;
15239
+ setActiveTab(id);
15240
+ }, [modalInputCaptured]);
14600
15241
  useKeyboard((key) => {
14601
15242
  if (configEditActive) {
14602
15243
  if (key.name === "escape" && configEdit?.kind !== "model") setConfigEdit(null);
@@ -14604,8 +15245,8 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14604
15245
  }
14605
15246
  if (extInstallActive || skillInstallActive || skillImportAllActive) return;
14606
15247
  if (mcpAddActive) return;
14607
- if (key.name === "escape" && oauthPasteActive && focusedMcp) {
14608
- actions?.onCancelLoginMcp?.(focusedMcp.config.name);
15248
+ if (key.name === "escape" && oauthPasteActive && authorizingMcp) {
15249
+ actions?.onCancelLoginMcp?.(authorizingMcp.entry.config.name);
14609
15250
  return;
14610
15251
  }
14611
15252
  if (oauthPasteActive) return;
@@ -14659,51 +15300,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14659
15300
  return;
14660
15301
  }
14661
15302
  if (key.name === "return") {
14662
- if (isCategoryTab(activeTab)) {
14663
- const it = filteredByCategory[activeTab][cursor];
14664
- if (!it) return;
14665
- if (it.kind === "toggle") toggleBoolean(it.key);
14666
- else if (it.kind === "choice") {
14667
- const current = settings[it.key];
14668
- const idx = it.options.findIndex((o) => o.value === current);
14669
- const next = it.options[(idx + 1) % it.options.length];
14670
- if (next) setSetting(it.key, next.value);
14671
- } else if (it.kind === "input") setEditingInputKey(it.key);
14672
- else it.onPick();
14673
- return;
14674
- }
14675
- if (activeTab === "skills") {
14676
- const s = filteredSkills[cursor];
14677
- if (s) skillsToggle.toggle(s.name);
14678
- return;
14679
- }
14680
- if (activeTab === "extensions") {
14681
- const row = extensionVisibleRows[cursor];
14682
- if (!row) return;
14683
- if (row.kind === "extension") extensionsToggle.toggle(row.ext.name);
14684
- else if (row.kind === "config") activateConfigRow(row.extName, row.item);
14685
- return;
14686
- }
14687
- if (activeTab === "mcps") {
14688
- const row = mcpVisibleRows[cursor];
14689
- if (!row) return;
14690
- if (row.kind === "server") {
14691
- const name = row.entry.config.name;
14692
- const enabling = !mcpsToggle.enabledSet.has(name);
14693
- mcpsToggle.toggle(name);
14694
- if (enabling && (mcpToolsByServer[name]?.length ?? 0) === 0) fireMcpToolsRefresh();
14695
- } else if (row.kind === "tool") mcpToolToggleMap[row.serverName]?.toggle(row.tool.name);
14696
- return;
14697
- }
14698
- if (activeTab === "keybindings") {
14699
- actions?.onOpenKeybindings?.();
14700
- return;
14701
- }
14702
- if (activeTab === "authentication") {
14703
- const provider = filteredProviders[cursor];
14704
- if (provider) actions?.onConfigureProvider?.(provider);
14705
- return;
14706
- }
15303
+ activateRowAt(cursor);
14707
15304
  return;
14708
15305
  }
14709
15306
  if (activeTab === "extensions") {
@@ -14787,7 +15384,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14787
15384
  actions?.onLogoutMcp?.(focusedMcp.config.name);
14788
15385
  return;
14789
15386
  }
14790
- if (key.name === "escape" && focusedMcpStatus.kind === "authorizing") actions?.onCancelLoginMcp?.(focusedMcp.config.name);
15387
+ if (key.name === "escape" && authorizingMcp) actions?.onCancelLoginMcp?.(authorizingMcp.entry.config.name);
14791
15388
  }
14792
15389
  if (key.ctrl && !key.shift && key.name === "l" && activeTab === "logs") {
14793
15390
  key.preventDefault();
@@ -14908,10 +15505,11 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14908
15505
  children: /* @__PURE__ */ jsx$1(TabStrip, {
14909
15506
  order: tabOrder,
14910
15507
  active: activeTab,
14911
- counts: tabCounts
15508
+ counts: tabCounts,
15509
+ onSelect: clickTab
14912
15510
  })
14913
15511
  }),
14914
- /* @__PURE__ */ jsx$1("box", {
15512
+ !authorizingMcp && /* @__PURE__ */ jsx$1("box", {
14915
15513
  style: {
14916
15514
  border: true,
14917
15515
  borderColor: COLOR.borderActive,
@@ -14922,14 +15520,26 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14922
15520
  },
14923
15521
  children: /* @__PURE__ */ jsx$1("input", {
14924
15522
  ref: inputRef,
14925
- focused: !oauthPasteActive && !configEditActive && !editingInputKey && !extInstallActive && !skillInstallActive && !skillImportAllActive && !mcpAddActive,
15523
+ focused: !modalInputCaptured,
14926
15524
  placeholder: searchPlaceholder(activeTab),
14927
15525
  onInput: handleQueryChange,
14928
15526
  onSubmit: () => {},
14929
15527
  style: { flexGrow: 1 }
14930
15528
  })
14931
15529
  }),
14932
- /* @__PURE__ */ jsxs("scrollbox", {
15530
+ authorizingMcp ? /* @__PURE__ */ jsx$1("box", {
15531
+ style: {
15532
+ flexGrow: 1,
15533
+ flexDirection: "column"
15534
+ },
15535
+ children: /* @__PURE__ */ jsx$1(McpAuthorizingPanel, {
15536
+ serverName: authorizingMcp.entry.config.name,
15537
+ authUrl: authorizingMcp.status.kind === "authorizing" ? authorizingMcp.status.url ?? null : null,
15538
+ maxLineWidth: 154,
15539
+ inputFocused: true,
15540
+ fill: true
15541
+ })
15542
+ }) : /* @__PURE__ */ jsxs("scrollbox", {
14933
15543
  ref: scrollboxRef,
14934
15544
  focusable: false,
14935
15545
  style: {
@@ -14941,6 +15551,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14941
15551
  viewportCulling: false,
14942
15552
  children: [
14943
15553
  isCategoryTab(activeTab) && /* @__PURE__ */ jsx$1(GeneralList, {
15554
+ onRowClick: clickRowAt,
14944
15555
  items: filteredByCategory[activeTab],
14945
15556
  cursor,
14946
15557
  highlightBg: SURFACE.selection,
@@ -14949,6 +15560,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14949
15560
  editingKey: editingInputKey
14950
15561
  }),
14951
15562
  activeTab === "skills" && /* @__PURE__ */ jsx$1(SkillsList, {
15563
+ onRowClick: clickRowAt,
14952
15564
  items: filteredSkills,
14953
15565
  enabledSet: skillsToggle.enabledSet,
14954
15566
  totalCount: skillsCatalog.length,
@@ -14958,6 +15570,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14958
15570
  contentCols
14959
15571
  }),
14960
15572
  activeTab === "extensions" && /* @__PURE__ */ jsx$1(ExtensionsList, {
15573
+ onRowClick: clickRowAt,
14961
15574
  rows: extensionVisibleRows,
14962
15575
  enabledSet: extensionsToggle.enabledSet,
14963
15576
  extensionConfig: settings.extensionConfig,
@@ -14977,6 +15590,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14977
15590
  actionProbes
14978
15591
  }),
14979
15592
  activeTab === "mcps" && /* @__PURE__ */ jsx$1(McpsList, {
15593
+ onRowClick: clickRowAt,
14980
15594
  rows: mcpVisibleRows,
14981
15595
  filteredCount: filteredMcps.length,
14982
15596
  enabledSet: mcpsToggle.enabledSet,
@@ -15020,7 +15634,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15020
15634
  highlightBg: SURFACE.selection
15021
15635
  })
15022
15636
  }),
15023
- activeTab === "mcps" && focusedMcp && focusedMcpStatus && /* @__PURE__ */ jsx$1("box", {
15637
+ activeTab === "mcps" && !authorizingMcp && focusedMcp && focusedMcpStatus && /* @__PURE__ */ jsx$1("box", {
15024
15638
  style: { flexShrink: 0 },
15025
15639
  children: renderMcpDetailPanel(focusedMcp, focusedMcpStatus, COLOR)
15026
15640
  }),
@@ -15090,27 +15704,27 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15090
15704
  style: { flexGrow: 1 }
15091
15705
  })
15092
15706
  }),
15093
- /* @__PURE__ */ jsxs("text", {
15094
- fg: COLOR.mute,
15095
- children: [
15096
- /* @__PURE__ */ jsx$1("span", {
15097
- fg: COLOR.warn,
15098
- children: "↵"
15099
- }),
15100
- " save · ",
15101
- /* @__PURE__ */ jsx$1("span", {
15102
- fg: COLOR.warn,
15103
- children: "esc"
15104
- }),
15105
- ` cancel · empty = ${editingInputItem.placeholder}`
15106
- ]
15107
- })
15707
+ /* @__PURE__ */ jsx$1(ModalHints, { hints: [
15708
+ {
15709
+ key: "↵",
15710
+ label: "save"
15711
+ },
15712
+ {
15713
+ key: "esc",
15714
+ label: "cancel"
15715
+ },
15716
+ {
15717
+ key: "",
15718
+ label: `empty = ${editingInputItem.placeholder}`
15719
+ }
15720
+ ] })
15108
15721
  ]
15109
15722
  }),
15110
15723
  /* @__PURE__ */ jsx$1("box", {
15111
15724
  style: { flexShrink: 0 },
15112
15725
  children: /* @__PURE__ */ jsx$1(Hints, {
15113
15726
  activeTab,
15727
+ authorizing: !!authorizingMcp,
15114
15728
  focusedMcp,
15115
15729
  focusedMcpRow,
15116
15730
  focusedMcpStatus,
@@ -15183,9 +15797,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15183
15797
  ]
15184
15798
  });
15185
15799
  }
15186
- function TabStrip({ order, active, counts }) {
15800
+ function TabStrip({ order, active, counts, onSelect }) {
15187
15801
  const COLOR = useColors();
15188
15802
  const SURFACE = useSurfaces();
15803
+ const [hoveredTab, setHoveredTab] = useState(null);
15189
15804
  return /* @__PURE__ */ jsx$1("box", {
15190
15805
  style: {
15191
15806
  flexDirection: "row",
@@ -15193,11 +15808,19 @@ function TabStrip({ order, active, counts }) {
15193
15808
  },
15194
15809
  children: order.map((id, i) => {
15195
15810
  const isActive = id === active;
15811
+ const hovered = hoveredTab === id && !isActive;
15196
15812
  const label = TAB_LABELS[id];
15197
15813
  const tabCounts = counts[id];
15198
15814
  const showBadge = !!tabCounts;
15199
15815
  const badge = tabCounts ? tabCounts.current !== tabCounts.total ? `${tabCounts.current}/${tabCounts.total}` : `${tabCounts.total}` : "";
15200
15816
  return /* @__PURE__ */ jsx$1("box", {
15817
+ onMouseDown: (event) => {
15818
+ event.stopPropagation();
15819
+ event.preventDefault();
15820
+ onSelect(id);
15821
+ },
15822
+ onMouseOver: () => setHoveredTab(id),
15823
+ onMouseOut: () => setHoveredTab((current) => current === id ? null : current),
15201
15824
  style: {
15202
15825
  paddingLeft: 1,
15203
15826
  paddingRight: 1,
@@ -15207,10 +15830,10 @@ function TabStrip({ order, active, counts }) {
15207
15830
  children: /* @__PURE__ */ jsxs("text", {
15208
15831
  wrapMode: "none",
15209
15832
  children: [/* @__PURE__ */ jsx$1("span", {
15210
- fg: isActive ? COLOR.brand : COLOR.dim,
15833
+ fg: isActive ? COLOR.brand : hovered ? COLOR.accent : COLOR.dim,
15211
15834
  children: label
15212
15835
  }), showBadge && /* @__PURE__ */ jsx$1("span", {
15213
- fg: isActive ? COLOR.brand : COLOR.mute,
15836
+ fg: isActive ? COLOR.brand : hovered ? COLOR.accent : COLOR.mute,
15214
15837
  children: ` ${badge}`
15215
15838
  })]
15216
15839
  })
@@ -15218,7 +15841,7 @@ function TabStrip({ order, active, counts }) {
15218
15841
  })
15219
15842
  });
15220
15843
  }
15221
- function GeneralList({ items, cursor, highlightBg, query, contentCols, editingKey }) {
15844
+ function GeneralList({ items, cursor, highlightBg, query, contentCols, editingKey, onRowClick }) {
15222
15845
  const { settings } = useSettings();
15223
15846
  if (items.length === 0) return /* @__PURE__ */ jsx$1(EmptyRow, { label: query ? `no settings match "${query}"` : "no settings" });
15224
15847
  const descCols = Math.max(0, contentCols - 1 - 1 - 6);
@@ -15229,6 +15852,7 @@ function GeneralList({ items, cursor, highlightBg, query, contentCols, editingKe
15229
15852
  const bg = focused ? highlightBg : void 0;
15230
15853
  const id = anchorIdFor(i);
15231
15854
  if (item.kind === "toggle") return /* @__PURE__ */ jsx$1(ToggleRow, {
15855
+ onClick: (zone) => onRowClick?.(i, zone),
15232
15856
  id,
15233
15857
  label: item.label,
15234
15858
  description: truncateToCols(item.description, descCols),
@@ -15240,6 +15864,7 @@ function GeneralList({ items, cursor, highlightBg, query, contentCols, editingKe
15240
15864
  const current = settings[item.key];
15241
15865
  const opt = item.options.find((o) => o.value === current);
15242
15866
  return /* @__PURE__ */ jsx$1(ChoiceRow, {
15867
+ onClick: () => onRowClick?.(i),
15243
15868
  id,
15244
15869
  label: item.label,
15245
15870
  description: truncateToCols(item.description, descCols),
@@ -15250,6 +15875,7 @@ function GeneralList({ items, cursor, highlightBg, query, contentCols, editingKe
15250
15875
  }, item.key);
15251
15876
  }
15252
15877
  if (item.kind === "input") return /* @__PURE__ */ jsx$1(InputRow, {
15878
+ onClick: () => onRowClick?.(i),
15253
15879
  id,
15254
15880
  label: item.label,
15255
15881
  description: truncateToCols(item.description, descCols),
@@ -15270,7 +15896,7 @@ function GeneralList({ items, cursor, highlightBg, query, contentCols, editingKe
15270
15896
  });
15271
15897
  }
15272
15898
  const EXTENSION_CONFIG_INDENT = 3;
15273
- function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCount, cursor, highlightBg, query, contentCols, editing, inputRef, onCommitEdit, modelCatalog, onApplyModelEdit, onCancelEdit, actionStatus, actionProbes }) {
15899
+ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCount, cursor, highlightBg, query, contentCols, editing, inputRef, onCommitEdit, modelCatalog, onApplyModelEdit, onCancelEdit, actionStatus, actionProbes, onRowClick }) {
15274
15900
  const COLOR = useColors();
15275
15901
  const home = homedir();
15276
15902
  if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
@@ -15420,6 +16046,7 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
15420
16046
  if (item.type === "action") {
15421
16047
  const status = actionStatus && actionStatus.extName === group.ext.name && actionStatus.key === item.key ? actionStatus : null;
15422
16048
  childEls.push(/* @__PURE__ */ jsx$1(ConfigActionRow, {
16049
+ onClick: () => onRowClick?.(index),
15423
16050
  id: anchorIdFor(index),
15424
16051
  indent: EXTENSION_CONFIG_INDENT,
15425
16052
  label: item.label,
@@ -15430,6 +16057,7 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
15430
16057
  bg: childBg
15431
16058
  }, item.key));
15432
16059
  } else if (item.type === "boolean") childEls.push(/* @__PURE__ */ jsx$1(ToggleRow, {
16060
+ onClick: (zone) => onRowClick?.(index, zone),
15433
16061
  id: anchorIdFor(index),
15434
16062
  indent: EXTENSION_CONFIG_INDENT,
15435
16063
  label: item.label,
@@ -15441,6 +16069,7 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
15441
16069
  else if (item.type === "enum") {
15442
16070
  const opt = item.options.find((o) => o.value === value);
15443
16071
  childEls.push(/* @__PURE__ */ jsx$1(ChoiceRow, {
16072
+ onClick: () => onRowClick?.(index),
15444
16073
  id: anchorIdFor(index),
15445
16074
  indent: EXTENSION_CONFIG_INDENT,
15446
16075
  label: item.label,
@@ -15489,6 +16118,11 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
15489
16118
  id: anchorIdFor(group.index),
15490
16119
  wrapMode: "none",
15491
16120
  bg,
16121
+ onMouseDown: onRowClick && ((event) => {
16122
+ event.stopPropagation();
16123
+ event.preventDefault();
16124
+ onRowClick(group.index, inCheckboxZone(event, 4) ? "checkbox" : "row");
16125
+ }),
15492
16126
  children: [
15493
16127
  /* @__PURE__ */ jsx$1("span", {
15494
16128
  fg: focused ? COLOR.brand : COLOR.mute,
@@ -15568,11 +16202,16 @@ function CreateExtensionButton() {
15568
16202
  })]
15569
16203
  });
15570
16204
  }
15571
- function ConfigActionRow({ id, label, description, status, probe, focused, bg, indent = 0 }) {
16205
+ function ConfigActionRow({ id, label, description, status, probe, focused, bg, indent = 0, onClick }) {
15572
16206
  const COLOR = useColors();
15573
16207
  const statusText = (status?.state === "busy" ? "running…" : status?.text) ?? probe?.message;
15574
16208
  const statusColor = status ? status.state === "error" ? COLOR.error : status.state === "busy" ? COLOR.mute : COLOR.accent : probe ? probe.state === "error" ? COLOR.error : probe.state === "off" ? COLOR.mute : COLOR.accent : COLOR.mute;
15575
16209
  return /* @__PURE__ */ jsxs("box", {
16210
+ onMouseDown: onClick && ((event) => {
16211
+ event.stopPropagation();
16212
+ event.preventDefault();
16213
+ onClick();
16214
+ }),
15576
16215
  id,
15577
16216
  style: {
15578
16217
  flexDirection: "column",
@@ -15745,7 +16384,7 @@ function ConfigModelPickerRow({ id, label, description, catalog, multi, initial,
15745
16384
  ]
15746
16385
  });
15747
16386
  }
15748
- function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query, contentCols }) {
16387
+ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query, contentCols, onRowClick }) {
15749
16388
  const COLOR = useColors();
15750
16389
  if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
15751
16390
  style: { flexDirection: "column" },
@@ -15790,6 +16429,11 @@ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query,
15790
16429
  const description = truncateToCols(entry.description ?? "", descCols);
15791
16430
  const origin = skillOriginLabel(entry);
15792
16431
  return /* @__PURE__ */ jsxs("box", {
16432
+ onMouseDown: onRowClick && ((event) => {
16433
+ event.stopPropagation();
16434
+ event.preventDefault();
16435
+ onRowClick(i, "row");
16436
+ }),
15793
16437
  id: anchorIdFor(i),
15794
16438
  style: {
15795
16439
  flexDirection: "column",
@@ -15800,6 +16444,11 @@ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query,
15800
16444
  },
15801
16445
  children: [/* @__PURE__ */ jsxs("text", {
15802
16446
  wrapMode: "none",
16447
+ onMouseDown: onRowClick && ((event) => {
16448
+ event.stopPropagation();
16449
+ event.preventDefault();
16450
+ onRowClick(i, inCheckboxZone(event, 2) ? "checkbox" : "row");
16451
+ }),
15803
16452
  children: [
15804
16453
  /* @__PURE__ */ jsx$1("span", {
15805
16454
  fg: focused ? COLOR.brand : COLOR.mute,
@@ -15827,7 +16476,7 @@ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query,
15827
16476
  })
15828
16477
  });
15829
16478
  }
15830
- function McpsList({ rows, filteredCount, enabledSet, toolToggleMap, toolsByServer, toolsRefreshing, expanded, totalCount, cursor, highlightBg, query, errors, authState, contentCols }) {
16479
+ function McpsList({ rows, filteredCount, enabledSet, toolToggleMap, toolsByServer, toolsRefreshing, expanded, totalCount, cursor, highlightBg, query, errors, authState, contentCols, onRowClick }) {
15831
16480
  const COLOR = useColors();
15832
16481
  const home = homedir();
15833
16482
  if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
@@ -15897,6 +16546,7 @@ function McpsList({ rows, filteredCount, enabledSet, toolToggleMap, toolsByServe
15897
16546
  children: [renderMcpErrors(errors, home, COLOR.warn, COLOR.dim), groups.map((group) => /* @__PURE__ */ jsxs("box", {
15898
16547
  style: { flexDirection: "column" },
15899
16548
  children: [renderServerListRow({
16549
+ onClick: onRowClick ? (zone) => onRowClick(group.serverIndex, zone) : void 0,
15900
16550
  entry: group.server.entry,
15901
16551
  focused: group.serverIndex === cursor,
15902
16552
  bg: group.serverIndex === cursor ? highlightBg : void 0,
@@ -15912,6 +16562,7 @@ function McpsList({ rows, filteredCount, enabledSet, toolToggleMap, toolsByServe
15912
16562
  const focused = index === cursor;
15913
16563
  const bg = focused ? highlightBg : void 0;
15914
16564
  if (row.kind === "tool") return renderToolListRow({
16565
+ onClick: onRowClick ? (zone) => onRowClick(index, zone) : void 0,
15915
16566
  serverName: row.serverName,
15916
16567
  tool: row.tool,
15917
16568
  focused,
@@ -15933,7 +16584,7 @@ function McpsList({ rows, filteredCount, enabledSet, toolToggleMap, toolsByServe
15933
16584
  }, `group:${group.server.entry.config.name}`))]
15934
16585
  });
15935
16586
  }
15936
- function renderServerListRow({ entry, focused, bg, enabled, expanded, toolEnabledSet, toolTotal, anchorId, status, COLOR, contentCols }) {
16587
+ function renderServerListRow({ onClick, entry, focused, bg, enabled, expanded, toolEnabledSet, toolTotal, anchorId, status, COLOR, contentCols }) {
15937
16588
  const name = entry.config.name;
15938
16589
  const chevron = expanded ? "▾" : "▸";
15939
16590
  const toolCountChip = toolEnabledSet && typeof toolTotal === "number" ? ` (${toolEnabledSet.size}/${toolTotal})` : "";
@@ -15944,6 +16595,11 @@ function renderServerListRow({ entry, focused, bg, enabled, expanded, toolEnable
15944
16595
  id: anchorId,
15945
16596
  wrapMode: "none",
15946
16597
  bg,
16598
+ onMouseDown: onClick && ((event) => {
16599
+ event.stopPropagation();
16600
+ event.preventDefault();
16601
+ onClick(inCheckboxZone(event, 4) ? "checkbox" : "row");
16602
+ }),
15947
16603
  children: [
15948
16604
  /* @__PURE__ */ jsx$1("span", {
15949
16605
  fg: focused ? COLOR.brand : COLOR.mute,
@@ -15975,13 +16631,18 @@ function renderServerListRow({ entry, focused, bg, enabled, expanded, toolEnable
15975
16631
  }
15976
16632
  const TOOL_ROW_INDENT$1 = " ";
15977
16633
  const TOOL_ROW_FOCUS_INDENT$1 = " ▶ ";
15978
- function renderToolListRow({ serverName, tool, focused, bg, enabled, anchorId, COLOR, contentCols }) {
16634
+ function renderToolListRow({ onClick, serverName, tool, focused, bg, enabled, anchorId, COLOR, contentCols }) {
15979
16635
  const descBudget = Math.max(0, contentCols - 6 - 4 - tool.name.length - 2);
15980
16636
  const description = truncateToCols(tool.description ?? "", descBudget);
15981
16637
  return /* @__PURE__ */ jsxs("text", {
15982
16638
  id: anchorId,
15983
16639
  wrapMode: "none",
15984
16640
  bg,
16641
+ onMouseDown: onClick && ((event) => {
16642
+ event.stopPropagation();
16643
+ event.preventDefault();
16644
+ onClick(inCheckboxZone(event, 6) ? "checkbox" : "row");
16645
+ }),
15985
16646
  children: [
15986
16647
  /* @__PURE__ */ jsx$1("span", {
15987
16648
  fg: focused ? COLOR.brand : COLOR.mute,
@@ -16204,9 +16865,14 @@ function AuthenticationList({ view, providers, cursor, highlightBg, query }) {
16204
16865
  ]
16205
16866
  });
16206
16867
  }
16207
- function ToggleRow({ id, label, description, enabled, focused, bg, indent = 0 }) {
16868
+ function ToggleRow({ id, label, description, enabled, focused, bg, indent = 0, onClick }) {
16208
16869
  const COLOR = useColors();
16209
16870
  return /* @__PURE__ */ jsxs("box", {
16871
+ onMouseDown: onClick && ((event) => {
16872
+ event.stopPropagation();
16873
+ event.preventDefault();
16874
+ onClick("row");
16875
+ }),
16210
16876
  id,
16211
16877
  style: {
16212
16878
  flexDirection: "column",
@@ -16217,6 +16883,11 @@ function ToggleRow({ id, label, description, enabled, focused, bg, indent = 0 })
16217
16883
  },
16218
16884
  children: [/* @__PURE__ */ jsxs("text", {
16219
16885
  wrapMode: "none",
16886
+ onMouseDown: onClick && ((event) => {
16887
+ event.stopPropagation();
16888
+ event.preventDefault();
16889
+ onClick(inCheckboxZone(event, 2) ? "checkbox" : "row");
16890
+ }),
16220
16891
  children: [
16221
16892
  /* @__PURE__ */ jsx$1("span", {
16222
16893
  fg: focused ? COLOR.brand : COLOR.mute,
@@ -16238,9 +16909,14 @@ function ToggleRow({ id, label, description, enabled, focused, bg, indent = 0 })
16238
16909
  })]
16239
16910
  });
16240
16911
  }
16241
- function ChoiceRow({ id, label, description, value, cyclable, focused, bg, indent = 0 }) {
16912
+ function ChoiceRow({ id, label, description, value, cyclable, focused, bg, indent = 0, onClick }) {
16242
16913
  const COLOR = useColors();
16243
16914
  return /* @__PURE__ */ jsxs("box", {
16915
+ onMouseDown: onClick && ((event) => {
16916
+ event.stopPropagation();
16917
+ event.preventDefault();
16918
+ onClick();
16919
+ }),
16244
16920
  id,
16245
16921
  style: {
16246
16922
  flexDirection: "column",
@@ -16284,9 +16960,14 @@ function ChoiceRow({ id, label, description, value, cyclable, focused, bg, inden
16284
16960
  })]
16285
16961
  });
16286
16962
  }
16287
- function ActionRow$1({ id, label, description, focused, bg }) {
16963
+ function ActionRow$1({ id, label, description, focused, bg, onClick }) {
16288
16964
  const COLOR = useColors();
16289
16965
  return /* @__PURE__ */ jsxs("box", {
16966
+ onMouseDown: onClick && ((event) => {
16967
+ event.stopPropagation();
16968
+ event.preventDefault();
16969
+ onClick();
16970
+ }),
16290
16971
  id,
16291
16972
  style: {
16292
16973
  flexDirection: "column",
@@ -16322,10 +17003,15 @@ function ActionRow$1({ id, label, description, focused, bg }) {
16322
17003
  })]
16323
17004
  });
16324
17005
  }
16325
- function InputRow({ id, label, description, value, placeholder, editing, focused, bg }) {
17006
+ function InputRow({ id, label, description, value, placeholder, editing, focused, bg, onClick }) {
16326
17007
  const COLOR = useColors();
16327
17008
  const hasValue = value.length > 0;
16328
17009
  return /* @__PURE__ */ jsxs("box", {
17010
+ onMouseDown: onClick && ((event) => {
17011
+ event.stopPropagation();
17012
+ event.preventDefault();
17013
+ onClick();
17014
+ }),
16329
17015
  id,
16330
17016
  style: {
16331
17017
  flexDirection: "column",
@@ -16379,29 +17065,7 @@ function EmptyRow({ label }) {
16379
17065
  });
16380
17066
  }
16381
17067
  function renderMcpDetailPanel(entry, status, COLOR) {
16382
- if (status.kind === "authorizing") {
16383
- if (!status.url) return /* @__PURE__ */ jsxs("box", {
16384
- style: {
16385
- flexDirection: "column",
16386
- border: ["top"],
16387
- borderColor: COLOR.border,
16388
- paddingTop: 1
16389
- },
16390
- children: [/* @__PURE__ */ jsx$1("text", {
16391
- fg: COLOR.brand,
16392
- children: `Authorizing ${entry.config.name}`
16393
- }), /* @__PURE__ */ jsx$1("text", {
16394
- fg: COLOR.dim,
16395
- children: "Starting login flow…"
16396
- })]
16397
- });
16398
- return /* @__PURE__ */ jsx$1(McpAuthorizingPanel, {
16399
- serverName: entry.config.name,
16400
- authUrl: status.url,
16401
- maxLineWidth: 154,
16402
- inputFocused: true
16403
- });
16404
- }
17068
+ if (status.kind === "authorizing") return null;
16405
17069
  if (status.kind === "error") return /* @__PURE__ */ jsxs("box", {
16406
17070
  style: {
16407
17071
  flexDirection: "column",
@@ -16501,162 +17165,96 @@ function renderMcpErrors(errors, home, warnColor, dimColor) {
16501
17165
  }, err.path))
16502
17166
  });
16503
17167
  }
16504
- function Hints({ activeTab, focusedMcp, focusedMcpRow, focusedMcpStatus, canRefreshSkills, canRefreshMcps, canEditExtension, canCreateExtension, canUpdateExtension, canRemoveExtension, canInstallExtension, canInstallSkill, canAddMcp, canRemoveSkill, canRemoveMcp, focusedExtensionExpandable }) {
17168
+ function Hints({ activeTab, authorizing = false, focusedMcp, focusedMcpRow, focusedMcpStatus, canRefreshSkills, canRefreshMcps, canEditExtension, canCreateExtension, canUpdateExtension, canRemoveExtension, canInstallExtension, canInstallSkill, canAddMcp, canRemoveSkill, canRemoveMcp, focusedExtensionExpandable }) {
16505
17169
  const COLOR = useColors();
16506
17170
  const onServerRow = focusedMcpRow?.kind === "server";
16507
17171
  const showLogin = activeTab === "mcps" && onServerRow && !!focusedMcp && !!focusedMcpStatus && canLogin$1(focusedMcp, focusedMcpStatus);
16508
17172
  const showLogout = activeTab === "mcps" && onServerRow && !!focusedMcpStatus && canLogout$1(focusedMcpStatus);
16509
- const showCancel = activeTab === "mcps" && focusedMcpStatus?.kind === "authorizing";
17173
+ const showCancel = activeTab === "mcps" && authorizing;
16510
17174
  const showRefresh = activeTab === "skills" && canRefreshSkills || activeTab === "mcps" && canRefreshMcps;
16511
- return /* @__PURE__ */ jsxs("text", {
16512
- fg: COLOR.mute,
16513
- children: [
16514
- /* @__PURE__ */ jsx$1("span", {
16515
- fg: COLOR.warn,
16516
- children: "←→"
16517
- }),
16518
- " tabs · ",
16519
- /* @__PURE__ */ jsx$1("span", {
16520
- fg: COLOR.warn,
16521
- children: "↑↓"
16522
- }),
16523
- activeTab === "keybindings" || activeTab === "logs" ? " scroll · " : " navigate · ",
16524
- activeTab === "logs" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
16525
- fg: COLOR.warn,
16526
- children: "ctrl+l"
16527
- }), " level · type to filter"] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
16528
- fg: COLOR.warn,
16529
- children: "↵"
16530
- }), isCategoryTab(activeTab) ? " toggle/cycle/select" : activeTab === "keybindings" ? " edit file" : activeTab === "authentication" ? " switch / re-authenticate" : " toggle"] }),
16531
- activeTab === "mcps" && focusedMcpRow && /* @__PURE__ */ jsxs("span", { children: [
16532
- " · ",
16533
- /* @__PURE__ */ jsx$1("span", {
16534
- fg: COLOR.warn,
16535
- children: "tab"
16536
- }),
16537
- focusedMcpRow.kind === "server" ? " expand/collapse" : " collapse"
16538
- ] }),
16539
- activeTab === "extensions" && focusedExtensionExpandable && /* @__PURE__ */ jsxs("span", { children: [
16540
- " · ",
16541
- /* @__PURE__ */ jsx$1("span", {
16542
- fg: COLOR.warn,
16543
- children: "tab"
16544
- }),
16545
- " settings"
16546
- ] }),
16547
- activeTab === "extensions" && canEditExtension && /* @__PURE__ */ jsxs("span", { children: [
16548
- " · ",
16549
- /* @__PURE__ */ jsx$1("span", {
16550
- fg: COLOR.warn,
16551
- children: "ctrl+e"
16552
- }),
16553
- " edit"
16554
- ] }),
16555
- activeTab === "extensions" && canCreateExtension && /* @__PURE__ */ jsxs("span", { children: [
16556
- " · ",
16557
- /* @__PURE__ */ jsx$1("span", {
16558
- fg: COLOR.warn,
16559
- children: "ctrl+a"
16560
- }),
16561
- " create"
16562
- ] }),
16563
- activeTab === "extensions" && canUpdateExtension && /* @__PURE__ */ jsxs("span", { children: [
16564
- " · ",
16565
- /* @__PURE__ */ jsx$1("span", {
16566
- fg: COLOR.warn,
16567
- children: "ctrl+u"
16568
- }),
16569
- " update"
16570
- ] }),
16571
- activeTab === "extensions" && canRemoveExtension && /* @__PURE__ */ jsxs("span", { children: [
16572
- " · ",
16573
- /* @__PURE__ */ jsx$1("span", {
16574
- fg: COLOR.warn,
16575
- children: "ctrl+x"
16576
- }),
16577
- " remove"
16578
- ] }),
16579
- activeTab === "extensions" && canInstallExtension && /* @__PURE__ */ jsxs("span", { children: [
16580
- " · ",
16581
- /* @__PURE__ */ jsx$1("span", {
16582
- fg: COLOR.warn,
16583
- children: "ctrl+g"
16584
- }),
16585
- " install"
16586
- ] }),
16587
- activeTab === "skills" && canInstallSkill && /* @__PURE__ */ jsxs("span", { children: [
16588
- " · ",
16589
- /* @__PURE__ */ jsx$1("span", {
16590
- fg: COLOR.warn,
16591
- children: "ctrl+g"
16592
- }),
16593
- " install"
16594
- ] }),
16595
- activeTab === "skills" && canRemoveSkill && /* @__PURE__ */ jsxs("span", { children: [
16596
- " · ",
16597
- /* @__PURE__ */ jsx$1("span", {
16598
- fg: COLOR.warn,
16599
- children: "ctrl+x"
16600
- }),
16601
- " delete"
16602
- ] }),
16603
- activeTab === "mcps" && canAddMcp && /* @__PURE__ */ jsxs("span", { children: [
16604
- " · ",
16605
- /* @__PURE__ */ jsx$1("span", {
16606
- fg: COLOR.warn,
16607
- children: "ctrl+g"
16608
- }),
16609
- " add server"
16610
- ] }),
16611
- activeTab === "mcps" && canRemoveMcp && onServerRow && /* @__PURE__ */ jsxs("span", { children: [
16612
- " · ",
16613
- /* @__PURE__ */ jsx$1("span", {
16614
- fg: COLOR.warn,
16615
- children: "ctrl+x"
16616
- }),
16617
- " remove"
16618
- ] }),
16619
- showLogin && /* @__PURE__ */ jsxs("span", { children: [
16620
- " · ",
16621
- /* @__PURE__ */ jsx$1("span", {
16622
- fg: COLOR.warn,
16623
- children: "ctrl+L"
16624
- }),
16625
- " login"
16626
- ] }),
16627
- showLogout && /* @__PURE__ */ jsxs("span", { children: [
16628
- " · ",
16629
- /* @__PURE__ */ jsx$1("span", {
16630
- fg: COLOR.warn,
16631
- children: "ctrl+O"
16632
- }),
16633
- " logout"
16634
- ] }),
16635
- showRefresh && /* @__PURE__ */ jsxs("span", { children: [
16636
- " · ",
16637
- /* @__PURE__ */ jsx$1("span", {
16638
- fg: COLOR.warn,
16639
- children: "ctrl+r"
16640
- }),
16641
- " refresh"
16642
- ] }),
16643
- showCancel ? /* @__PURE__ */ jsxs("span", { children: [
16644
- " · ",
16645
- /* @__PURE__ */ jsx$1("span", {
16646
- fg: COLOR.warn,
16647
- children: "esc"
16648
- }),
16649
- " cancel"
16650
- ] }) : /* @__PURE__ */ jsxs("span", { children: [
16651
- " · ",
16652
- /* @__PURE__ */ jsx$1("span", {
16653
- fg: COLOR.warn,
16654
- children: "esc"
16655
- }),
16656
- " close"
16657
- ] })
16658
- ]
16659
- });
17175
+ return /* @__PURE__ */ jsx$1(HintRow, { hints: [
17176
+ {
17177
+ key: "←→",
17178
+ label: "tabs"
17179
+ },
17180
+ {
17181
+ key: "↑↓",
17182
+ label: activeTab === "keybindings" || activeTab === "logs" ? "scroll" : "navigate"
17183
+ },
17184
+ ...activeTab === "logs" ? [{
17185
+ key: "ctrl+l",
17186
+ label: "level"
17187
+ }, {
17188
+ key: "",
17189
+ label: "type to filter"
17190
+ }] : [{
17191
+ key: "↵",
17192
+ label: isCategoryTab(activeTab) ? "toggle/cycle/select" : activeTab === "keybindings" ? "edit file" : activeTab === "authentication" ? "switch / re-authenticate" : "toggle"
17193
+ }],
17194
+ ...activeTab === "mcps" && focusedMcpRow ? [{
17195
+ key: "tab",
17196
+ label: focusedMcpRow.kind === "server" ? "expand/collapse" : "collapse"
17197
+ }] : [],
17198
+ ...activeTab === "extensions" && focusedExtensionExpandable ? [{
17199
+ key: "tab",
17200
+ label: "settings"
17201
+ }] : [],
17202
+ ...activeTab === "extensions" && canEditExtension ? [{
17203
+ key: "ctrl+e",
17204
+ label: "edit"
17205
+ }] : [],
17206
+ ...activeTab === "extensions" && canCreateExtension ? [{
17207
+ key: "ctrl+a",
17208
+ label: "create"
17209
+ }] : [],
17210
+ ...activeTab === "extensions" && canUpdateExtension ? [{
17211
+ key: "ctrl+u",
17212
+ label: "update"
17213
+ }] : [],
17214
+ ...activeTab === "extensions" && canRemoveExtension ? [{
17215
+ key: "ctrl+x",
17216
+ label: "remove"
17217
+ }] : [],
17218
+ ...activeTab === "extensions" && canInstallExtension ? [{
17219
+ key: "ctrl+g",
17220
+ label: "install"
17221
+ }] : [],
17222
+ ...activeTab === "skills" && canInstallSkill ? [{
17223
+ key: "ctrl+g",
17224
+ label: "install"
17225
+ }] : [],
17226
+ ...activeTab === "skills" && canRemoveSkill ? [{
17227
+ key: "ctrl+x",
17228
+ label: "delete"
17229
+ }] : [],
17230
+ ...activeTab === "mcps" && canAddMcp ? [{
17231
+ key: "ctrl+g",
17232
+ label: "add server"
17233
+ }] : [],
17234
+ ...activeTab === "mcps" && canRemoveMcp && onServerRow ? [{
17235
+ key: "ctrl+x",
17236
+ label: "remove"
17237
+ }] : [],
17238
+ ...showLogin ? [{
17239
+ key: "ctrl+L",
17240
+ label: "login"
17241
+ }] : [],
17242
+ ...showLogout ? [{
17243
+ key: "ctrl+O",
17244
+ label: "logout"
17245
+ }] : [],
17246
+ ...showRefresh ? [{
17247
+ key: "ctrl+r",
17248
+ label: "refresh"
17249
+ }] : [],
17250
+ {
17251
+ key: "esc",
17252
+ label: showCancel ? "cancel" : "close"
17253
+ }
17254
+ ].map((h) => ({
17255
+ ...h,
17256
+ labelColor: COLOR.mute
17257
+ })) });
16660
17258
  }
16661
17259
  function generalCorpus(item) {
16662
17260
  const parts = [item.label, item.description];
@@ -16796,7 +17394,10 @@ function composeTerminalTitle(opts) {
16796
17394
  * bring it back later. No-op when `out` isn't a TTY.
16797
17395
  */
16798
17396
  function saveTerminalTitle(out = process.stdout) {
16799
- if (out.isTTY) out.write(TITLE_STACK_PUSH);
17397
+ if (!out.isTTY) return;
17398
+ try {
17399
+ out.write(TITLE_STACK_PUSH);
17400
+ } catch {}
16800
17401
  }
16801
17402
  /**
16802
17403
  * Clear our title and pop the previously-saved one. No-op when `out`
@@ -16804,7 +17405,10 @@ function saveTerminalTitle(out = process.stdout) {
16804
17405
  * otherwise the clear would wipe a title zidane never set.
16805
17406
  */
16806
17407
  function restoreTerminalTitle(out = process.stdout) {
16807
- if (out.isTTY) out.write(TITLE_STACK_RESTORE);
17408
+ if (!out.isTTY) return;
17409
+ try {
17410
+ out.write(TITLE_STACK_RESTORE);
17411
+ } catch {}
16808
17412
  }
16809
17413
  //#endregion
16810
17414
  //#region src/tui/todos-modal.tsx
@@ -17300,26 +17904,23 @@ function TurnDetailsModal({ turn, toolResultTurns = [], searchQuery, index, tota
17300
17904
  children: "— no previewable content —"
17301
17905
  })
17302
17906
  }),
17303
- editing ? /* @__PURE__ */ jsxs("text", {
17304
- fg: COLOR.dim,
17305
- children: [
17306
- /* @__PURE__ */ jsx$1("span", {
17307
- fg: COLOR.warn,
17308
- children: "↵"
17309
- }),
17310
- " save · ",
17311
- /* @__PURE__ */ jsx$1("span", {
17312
- fg: COLOR.warn,
17313
- children: "shift+↵"
17314
- }),
17315
- " newline · ",
17316
- /* @__PURE__ */ jsx$1("span", {
17317
- fg: COLOR.warn,
17318
- children: "esc"
17319
- }),
17320
- " cancel"
17321
- ]
17322
- }) : /* @__PURE__ */ jsx$1(ActionRow, {
17907
+ editing ? /* @__PURE__ */ jsx$1(ModalHints, { hints: [
17908
+ {
17909
+ key: "↵",
17910
+ label: "save",
17911
+ labelColor: COLOR.dim
17912
+ },
17913
+ {
17914
+ key: "shift+↵",
17915
+ label: "newline",
17916
+ labelColor: COLOR.dim
17917
+ },
17918
+ {
17919
+ key: "esc",
17920
+ label: "cancel",
17921
+ labelColor: COLOR.dim
17922
+ }
17923
+ ] }) : /* @__PURE__ */ jsx$1(ActionRow, {
17323
17924
  pending,
17324
17925
  copyStatus,
17325
17926
  canCopy: fullText.length > 0,
@@ -17381,81 +17982,72 @@ function ActionRow({ pending, copyStatus, canCopy, canEdit, forkKey, deleteKey,
17381
17982
  " cancel"
17382
17983
  ]
17383
17984
  });
17384
- if (copyStatus === "copied") return /* @__PURE__ */ jsxs("text", {
17385
- fg: COLOR.dim,
17386
- children: [
17387
- /* @__PURE__ */ jsx$1("span", {
17388
- fg: COLOR.accent,
17389
- children: "✓ copied"
17390
- }),
17391
- " · ",
17392
- /* @__PURE__ */ jsx$1("span", {
17393
- fg: COLOR.warn,
17394
- children: forkKey
17395
- }),
17396
- " fork · ",
17397
- /* @__PURE__ */ jsx$1("span", {
17398
- fg: COLOR.warn,
17399
- children: deleteKey
17400
- }),
17401
- " delete · ",
17402
- /* @__PURE__ */ jsx$1("span", {
17403
- fg: canEdit ? COLOR.warn : COLOR.mute,
17404
- children: editKey
17405
- }),
17406
- canEdit ? " edit · " : " (no text) · ",
17407
- /* @__PURE__ */ jsx$1("span", {
17408
- fg: COLOR.warn,
17409
- children: "esc"
17410
- }),
17411
- " close"
17412
- ]
17413
- });
17414
- if (copyStatus === "failed") return /* @__PURE__ */ jsxs("text", {
17415
- fg: COLOR.dim,
17416
- children: [
17417
- /* @__PURE__ */ jsx$1("span", {
17418
- fg: COLOR.error,
17419
- children: "copy failed (terminal may not support OSC 52)"
17420
- }),
17421
- " · ",
17422
- /* @__PURE__ */ jsx$1("span", {
17423
- fg: COLOR.warn,
17424
- children: "esc"
17425
- }),
17426
- " close"
17427
- ]
17428
- });
17429
- return /* @__PURE__ */ jsxs("text", {
17430
- fg: COLOR.dim,
17431
- children: [
17432
- /* @__PURE__ */ jsx$1("span", {
17433
- fg: COLOR.warn,
17434
- children: forkKey
17435
- }),
17436
- " fork · ",
17437
- /* @__PURE__ */ jsx$1("span", {
17438
- fg: COLOR.warn,
17439
- children: deleteKey
17440
- }),
17441
- " delete · ",
17442
- /* @__PURE__ */ jsx$1("span", {
17443
- fg: canCopy ? COLOR.warn : COLOR.mute,
17444
- children: copyKey
17445
- }),
17446
- canCopy ? " copy · " : " (nothing to copy) · ",
17447
- /* @__PURE__ */ jsx$1("span", {
17448
- fg: canEdit ? COLOR.warn : COLOR.mute,
17449
- children: editKey
17450
- }),
17451
- canEdit ? " edit · " : " (no text) · ",
17452
- /* @__PURE__ */ jsx$1("span", {
17453
- fg: COLOR.warn,
17454
- children: "esc"
17455
- }),
17456
- " close"
17457
- ]
17458
- });
17985
+ if (copyStatus === "copied") return /* @__PURE__ */ jsx$1(ModalHints, { hints: [
17986
+ {
17987
+ key: "",
17988
+ label: "✓ copied",
17989
+ labelColor: COLOR.accent
17990
+ },
17991
+ {
17992
+ key: forkKey,
17993
+ label: "fork",
17994
+ labelColor: COLOR.dim
17995
+ },
17996
+ {
17997
+ key: deleteKey,
17998
+ label: "delete",
17999
+ labelColor: COLOR.dim
18000
+ },
18001
+ {
18002
+ key: editKey,
18003
+ label: canEdit ? "edit" : "(no text)",
18004
+ keyColor: canEdit ? COLOR.warn : COLOR.mute,
18005
+ labelColor: COLOR.dim
18006
+ },
18007
+ {
18008
+ key: "esc",
18009
+ label: "close",
18010
+ labelColor: COLOR.dim
18011
+ }
18012
+ ] });
18013
+ if (copyStatus === "failed") return /* @__PURE__ */ jsx$1(ModalHints, { hints: [{
18014
+ key: "",
18015
+ label: "copy failed (terminal may not support OSC 52)",
18016
+ labelColor: COLOR.error
18017
+ }, {
18018
+ key: "esc",
18019
+ label: "close",
18020
+ labelColor: COLOR.dim
18021
+ }] });
18022
+ return /* @__PURE__ */ jsx$1(ModalHints, { hints: [
18023
+ {
18024
+ key: forkKey,
18025
+ label: "fork",
18026
+ labelColor: COLOR.dim
18027
+ },
18028
+ {
18029
+ key: deleteKey,
18030
+ label: "delete",
18031
+ labelColor: COLOR.dim
18032
+ },
18033
+ {
18034
+ key: copyKey,
18035
+ label: canCopy ? "copy" : "(nothing to copy)",
18036
+ keyColor: canCopy ? COLOR.warn : COLOR.mute,
18037
+ labelColor: COLOR.dim
18038
+ },
18039
+ {
18040
+ key: editKey,
18041
+ label: canEdit ? "edit" : "(no text)",
18042
+ keyColor: canEdit ? COLOR.warn : COLOR.mute,
18043
+ labelColor: COLOR.dim
18044
+ },
18045
+ {
18046
+ key: "esc",
18047
+ label: "close",
18048
+ labelColor: COLOR.dim
18049
+ }
18050
+ ] });
17459
18051
  }
17460
18052
  function buildPreviewEntries(turns, limit) {
17461
18053
  const entries = [];
@@ -17592,10 +18184,20 @@ const BASH_PAINT_INTERVAL_MS = 100;
17592
18184
  const BASH_RESULT_MAX_BYTES = 32 * 1024;
17593
18185
  /** Rolling-tail window while output streams — matches the renderer's bash display cap. */
17594
18186
  const BASH_STREAM_TAIL_LINES = 10;
17595
- /** Last `n` lines of `text` (terminal-style rolling tail for live bash output). */
18187
+ /**
18188
+ * Last `n` lines of `text` (terminal-style rolling tail for live bash
18189
+ * output). Backward newline scan, not `split` — this runs per output
18190
+ * chunk against the full accumulation (up to 1MB), and re-splitting the
18191
+ * whole snapshot every chunk is O(n²) in total streamed bytes.
18192
+ */
17596
18193
  function lastLines(text, n) {
17597
- const lines = text.split("\n");
17598
- return lines.length <= n ? text : lines.slice(lines.length - n).join("\n");
18194
+ let idx = text.length;
18195
+ for (let i = 0; i < n; i++) {
18196
+ if (idx === 0) return text;
18197
+ idx = text.lastIndexOf("\n", idx - 1);
18198
+ if (idx === -1) return text;
18199
+ }
18200
+ return text.slice(idx + 1);
17599
18201
  }
17600
18202
  /**
17601
18203
  * Poll cadence for out-of-band extension install detection (see the
@@ -17606,6 +18208,14 @@ const EXTENSION_WATCH_POLL_MS = 3e3;
17606
18208
  /** LRU bound for cross-session search indexes (full lowercased text per session). */
17607
18209
  const SESSION_SEARCH_INDEX_CACHE_MAX = 64;
17608
18210
  /**
18211
+ * Byte budget for the same cache, using each handle's indexed-text size
18212
+ * (reported by the worker build). The count cap alone lets 64 huge
18213
+ * sessions pin hundreds of MB (index copy in the worker, plus any
18214
+ * main-thread fallback indexes); the budget evicts LRU-first once the
18215
+ * summed estimate crosses it, keeping worst-case retention predictable.
18216
+ */
18217
+ const SESSION_SEARCH_INDEX_CACHE_MAX_BYTES = 64 * 1024 * 1024;
18218
+ /**
17609
18219
  * Surface failures that are normally silenced (teardown / save) when the
17610
18220
  * `ZIDANE_DEBUG` env var is set. Logging via `console.error` would otherwise
17611
18221
  * trigger OpenTUI's error console overlay and clutter the UI for end users.
@@ -17847,6 +18457,9 @@ function AppShell() {
17847
18457
  if (renderer.targetFps !== fps) renderer.targetFps = fps;
17848
18458
  if (renderer.maxFps !== fps) renderer.maxFps = fps;
17849
18459
  }, [renderer, settings.targetFps]);
18460
+ useEffect(() => {
18461
+ if (renderer.useMouse !== settings.mouseInteractions) renderer.useMouse = settings.mouseInteractions;
18462
+ }, [renderer, settings.mouseInteractions]);
17850
18463
  const lastCopiedRef = useRef("");
17851
18464
  useSelectionHandler((selection) => {
17852
18465
  if (selection.isDragging) return;
@@ -18258,6 +18871,22 @@ function AppShell() {
18258
18871
  */
18259
18872
  const [compacting, setCompacting] = useState(false);
18260
18873
  /**
18874
+ * Live provider-retry indicator — set by `stream:retry` (and the bubbled
18875
+ * `child:stream:retry`, since a stalled subagent stalls the pipeline the
18876
+ * user is watching), cleared when the SAME source streams output again
18877
+ * or the run ends. Keyed by source (`'parent'` / childId) so a parallel
18878
+ * batch can't hide one child's still-active backoff behind a sibling's
18879
+ * output — the motivating /review case runs many children at once.
18880
+ * Drives the footer's `Retrying (n/max)` status; last retry wins the
18881
+ * single slot.
18882
+ */
18883
+ const [retryInfo, setRetryInfo] = useState(null);
18884
+ /** Source-scoped functional clear — bails re-render when nothing matches (hot path: every stream delta). */
18885
+ const clearRetryInfo = useCallback((source) => setRetryInfo((prev) => prev !== null && prev.source === source ? null : prev), []);
18886
+ useEffect(() => {
18887
+ if (!busy) setRetryInfo((prev) => prev === null ? prev : null);
18888
+ }, [busy]);
18889
+ /**
18261
18890
  * Rough token estimate of the in-flight compaction summary, painted
18262
18891
  * next to the footer's "Compacting…" label (`null` = no live estimate
18263
18892
  * yet). Fed by the loop's throttled `compact:progress` hook and the
@@ -18577,9 +19206,17 @@ function AppShell() {
18577
19206
  store,
18578
19207
  getAllowedTokens: () => chatReferenceTokensRef.current
18579
19208
  }), [store]);
19209
+ const visibleChatCatalogCache = useRef(/* @__PURE__ */ new WeakMap());
18580
19210
  const visibleChatCatalog = useCallback((catalog) => {
18581
- const currentId = currentSessionIdRef.current;
18582
- return currentId ? catalog.filter((entry) => entry.id !== currentId) : [...catalog];
19211
+ const currentId = currentSessionIdRef.current ?? null;
19212
+ const hit = visibleChatCatalogCache.current.get(catalog);
19213
+ if (hit && hit.currentId === currentId) return hit.result;
19214
+ const result = currentId ? catalog.filter((entry) => entry.id !== currentId) : [...catalog];
19215
+ visibleChatCatalogCache.current.set(catalog, {
19216
+ currentId,
19217
+ result
19218
+ });
19219
+ return result;
18583
19220
  }, []);
18584
19221
  const ensureChatCatalog = useCallback(async () => {
18585
19222
  const [projectList, allList] = await Promise.all([listSessionMeta(store, { projectRoot: projectDir }), listSessionMeta(store)]);
@@ -19181,8 +19818,26 @@ function AppShell() {
19181
19818
  });
19182
19819
  if (pendingAutoLoginMcpRef.current === name) pendingAutoLoginMcpRef.current = null;
19183
19820
  });
19184
- agent.hooks.hook("stream:thinking", ({ delta, turnId }) => stream.queueStreamDelta("thinking", delta, { turnId }));
19185
- agent.hooks.hook("stream:text", ({ delta, turnId }) => stream.queueStreamDelta("markdown", delta, { turnId }));
19821
+ agent.hooks.hook("stream:thinking", ({ delta, turnId }) => {
19822
+ clearRetryInfo("parent");
19823
+ stream.queueStreamDelta("thinking", delta, { turnId });
19824
+ });
19825
+ agent.hooks.hook("stream:text", ({ delta, turnId }) => {
19826
+ clearRetryInfo("parent");
19827
+ stream.queueStreamDelta("markdown", delta, { turnId });
19828
+ });
19829
+ agent.hooks.hook("stream:retry", ({ nextAttempt, maxAttempts }) => setRetryInfo({
19830
+ attempt: nextAttempt,
19831
+ max: maxAttempts,
19832
+ source: "parent"
19833
+ }));
19834
+ agent.hooks.hook("child:stream:retry", ({ nextAttempt, maxAttempts, childId }) => setRetryInfo({
19835
+ attempt: nextAttempt,
19836
+ max: maxAttempts,
19837
+ source: childId
19838
+ }));
19839
+ agent.hooks.hook("stream:error", () => clearRetryInfo("parent"));
19840
+ agent.hooks.hook("child:stream:error", ({ childId }) => clearRetryInfo(childId));
19186
19841
  agent.hooks.hook("stream:server_tool_use", ({ id, name, input, turnId }) => {
19187
19842
  stream.appendImmediate({
19188
19843
  kind: "tool",
@@ -19432,6 +20087,7 @@ function AppShell() {
19432
20087
  });
19433
20088
  });
19434
20089
  agent.hooks.hook("child:stream:thinking", ({ delta, childId, depth, turnId }) => {
20090
+ clearRetryInfo(childId);
19435
20091
  stream.queueStreamDelta("thinking", delta, {
19436
20092
  childId,
19437
20093
  depth,
@@ -19439,6 +20095,7 @@ function AppShell() {
19439
20095
  });
19440
20096
  });
19441
20097
  agent.hooks.hook("child:stream:text", ({ delta, childId, depth, turnId }) => {
20098
+ clearRetryInfo(childId);
19442
20099
  stream.queueStreamDelta("markdown", delta, {
19443
20100
  childId,
19444
20101
  depth,
@@ -19512,6 +20169,7 @@ function AppShell() {
19512
20169
  });
19513
20170
  agent.hooks.hook("agent:done", () => {
19514
20171
  pendingAnnotationsRef.current.clear();
20172
+ pruneTodosByRun(session);
19515
20173
  });
19516
20174
  subscribeMcpToolsCache(agent.hooks, { dataDir });
19517
20175
  return agent;
@@ -19578,7 +20236,9 @@ function AppShell() {
19578
20236
  const cachedLive = liveSessionSearchIndexRef.current;
19579
20237
  if (cachedLive?.sessionId === meta.id && cachedLive.updatedAt === meta.updatedAt && cachedLive.turnCount === live.turns.length) index = cachedLive.index;
19580
20238
  else {
20239
+ const staleLive = liveSessionSearchIndexRef.current;
19581
20240
  index = await (historySearchWorkerRef.current ??= new HistorySearchWorkerClient()).build(live.turns, abortController.signal);
20241
+ if (staleLive) historySearchWorkerRef.current?.release(staleLive.index);
19582
20242
  liveSessionSearchIndexRef.current = {
19583
20243
  sessionId: meta.id,
19584
20244
  updatedAt: meta.updatedAt,
@@ -19593,15 +20253,25 @@ function AppShell() {
19593
20253
  } else {
19594
20254
  const turns = await store.getTurns(meta.id);
19595
20255
  index = await (historySearchWorkerRef.current ??= new HistorySearchWorkerClient()).build(turns, abortController.signal);
20256
+ historySearchWorkerRef.current?.slim(index);
19596
20257
  const cache = sessionSearchCacheRef.current;
20258
+ const stale = cache.get(meta.id);
20259
+ if (stale) historySearchWorkerRef.current?.release(stale.index);
19597
20260
  cache.set(meta.id, {
19598
20261
  updatedAt: meta.updatedAt,
19599
20262
  index
19600
20263
  });
19601
- while (cache.size > SESSION_SEARCH_INDEX_CACHE_MAX) {
20264
+ let totalBytes = 0;
20265
+ for (const entry of cache.values()) totalBytes += entry.index.bytes;
20266
+ while (cache.size > 1 && (cache.size > SESSION_SEARCH_INDEX_CACHE_MAX || totalBytes > SESSION_SEARCH_INDEX_CACHE_MAX_BYTES)) {
19602
20267
  const oldest = cache.keys().next().value;
19603
20268
  if (oldest === void 0) break;
20269
+ const evicted = cache.get(oldest);
19604
20270
  cache.delete(oldest);
20271
+ if (evicted) {
20272
+ totalBytes -= evicted.index.bytes;
20273
+ historySearchWorkerRef.current?.release(evicted.index);
20274
+ }
19605
20275
  }
19606
20276
  }
19607
20277
  if (generation !== sessionSearchGenerationRef.current) return;
@@ -19609,7 +20279,24 @@ function AppShell() {
19609
20279
  if (match) matches.set(meta.id, match);
19610
20280
  } catch (error) {
19611
20281
  if (abortController.signal.aborted) throw abortController.signal.reason instanceof DOMException ? abortController.signal.reason : error;
19612
- debugLog("session content search skipped a session", error);
20282
+ const broken = sessionSearchCacheRef.current.get(meta.id);
20283
+ if (broken) {
20284
+ sessionSearchCacheRef.current.delete(meta.id);
20285
+ historySearchWorkerRef.current?.release(broken.index);
20286
+ try {
20287
+ const turns = await store.getTurns(meta.id);
20288
+ const retry = await (historySearchWorkerRef.current ??= new HistorySearchWorkerClient()).build(turns, abortController.signal);
20289
+ try {
20290
+ const match = await historySearchWorkerRef.current.history(retry, query, 3, abortController.signal);
20291
+ if (match) matches.set(meta.id, match);
20292
+ } finally {
20293
+ historySearchWorkerRef.current?.release(retry);
20294
+ }
20295
+ } catch (retryError) {
20296
+ if (abortController.signal.aborted) throw abortController.signal.reason instanceof DOMException ? abortController.signal.reason : retryError;
20297
+ debugLog("session content search retry failed", retryError);
20298
+ }
20299
+ } else debugLog("session content search skipped a session", error);
19613
20300
  }
19614
20301
  done++;
19615
20302
  if (done === sessions.length || done % 4 === 0) setSessionContentSearchProgress({
@@ -20759,6 +21446,7 @@ function AppShell() {
20759
21446
  const ctx = {
20760
21447
  args: invocation.args,
20761
21448
  argv: invocation.argv,
21449
+ invokedAs: invocation.invokedAs,
20762
21450
  ...attachments.length > 0 ? { attachments: attachments.map((a) => ({
20763
21451
  name: a.name,
20764
21452
  mediaType: a.mediaType,
@@ -21508,6 +22196,10 @@ function AppShell() {
21508
22196
  const onLoginMcp = useCallback(async (name) => {
21509
22197
  const entry = mcpsCatalogRef.current.find((d) => d.config.name === name);
21510
22198
  if (!entry) return;
22199
+ for (const [other, ctrl] of mcpLoginAbortsRef.current) if (other !== name && !ctrl.signal.aborted) {
22200
+ debugLog(`mcp-login: refusing concurrent login for "${name}"`, `"${other}" is still authorizing`);
22201
+ return;
22202
+ }
21511
22203
  mcpLoginAbortsRef.current.get(name)?.abort();
21512
22204
  const ac = new AbortController();
21513
22205
  mcpLoginAbortsRef.current.set(name, ac);
@@ -21519,6 +22211,7 @@ function AppShell() {
21519
22211
  await loginMcpServer(entry.config, {
21520
22212
  store: mcpCredentialStore,
21521
22213
  signal: ac.signal,
22214
+ renderCallbackPage: renderDefaultCallbackPage,
21522
22215
  hooks: agentRef.current?.hooks,
21523
22216
  onAuthorizationUrl: (url) => tryOpenBrowser(url.toString())
21524
22217
  });
@@ -21539,9 +22232,10 @@ function AppShell() {
21539
22232
  ]);
21540
22233
  onLoginMcpRef.current = onLoginMcp;
21541
22234
  const onLogoutMcp = useCallback((name) => {
22235
+ const hadTokens = !!mcpCredentialStore.load(name)?.tokens;
21542
22236
  mcpCredentialStore.delete(name);
21543
22237
  const entry = mcpsCatalogRef.current.find((d) => d.config.name === name);
21544
- dispatchAuth(entry?.config.auth === "oauth" ? {
22238
+ dispatchAuth(entry?.config.auth === "oauth" || hadTokens ? {
21545
22239
  type: "auth-required",
21546
22240
  name,
21547
22241
  reason: "no-tokens"
@@ -21610,6 +22304,7 @@ function AppShell() {
21610
22304
  (async () => {
21611
22305
  const cached = transcriptSearchIndexRef.current;
21612
22306
  const index = cached?.sessionId === session.id && cached.events === events ? cached.index : await (historySearchWorkerRef.current ??= new HistorySearchWorkerClient()).build(turns, abortController.signal);
22307
+ if (cached && cached.index !== index) historySearchWorkerRef.current?.release(cached.index);
21613
22308
  transcriptSearchIndexRef.current = {
21614
22309
  sessionId: session.id,
21615
22310
  events,
@@ -21737,7 +22432,14 @@ function AppShell() {
21737
22432
  store,
21738
22433
  ...source.projectRoot ? { projectRoot: source.projectRoot } : {}
21739
22434
  });
21740
- fork.setTurns(slice);
22435
+ const rehomed = await rehomePersistedStubs(slice, resolvePersistDir({
22436
+ userDir: cacheDir,
22437
+ sessionId: source.id
22438
+ }), resolvePersistDir({
22439
+ userDir: cacheDir,
22440
+ sessionId: fork.id
22441
+ }));
22442
+ fork.setTurns(rehomed);
21741
22443
  fork.setRuns(inheritedRuns);
21742
22444
  try {
21743
22445
  await fork.save();
@@ -21750,7 +22452,8 @@ function AppShell() {
21750
22452
  }, [
21751
22453
  picked,
21752
22454
  store,
21753
- activateSession
22455
+ activateSession,
22456
+ cacheDir
21754
22457
  ]);
21755
22458
  const onDeleteTurn = useCallback(async (turnId) => {
21756
22459
  const session = sessionRef.current;
@@ -22094,9 +22797,7 @@ function AppShell() {
22094
22797
  onExportSession,
22095
22798
  keybindings
22096
22799
  ]);
22097
- const openSelectedTurn = useCallback(() => {
22098
- const id = selectedTurnId;
22099
- if (!id) return;
22800
+ const openTurnDetails = useCallback((id) => {
22100
22801
  const session = sessionRef.current;
22101
22802
  if (!session) return;
22102
22803
  const turn = session.turns.find((t) => t.id === id);
@@ -22127,7 +22828,6 @@ function AppShell() {
22127
22828
  }));
22128
22829
  }, [
22129
22830
  modal,
22130
- selectedTurnId,
22131
22831
  turnIds,
22132
22832
  transcriptSearchMode,
22133
22833
  transcriptSearchQuery,
@@ -22137,7 +22837,54 @@ function AppShell() {
22137
22837
  onEditTurn,
22138
22838
  keybindings
22139
22839
  ]);
22840
+ const openSelectedTurn = useCallback(() => {
22841
+ if (selectedTurnId) openTurnDetails(selectedTurnId);
22842
+ }, [selectedTurnId, openTurnDetails]);
22843
+ const onTranscriptSelectTurn = useCallback((id) => {
22844
+ if (!(transcriptSearchMode ? transcriptSearchTurnIds : turnIds).includes(id)) return false;
22845
+ setSelectedTurnId(id);
22846
+ return true;
22847
+ }, [
22848
+ transcriptSearchMode,
22849
+ transcriptSearchTurnIds,
22850
+ turnIds
22851
+ ]);
22852
+ const onTranscriptOpenTurn = useCallback((id) => {
22853
+ if (!(transcriptSearchMode ? transcriptSearchTurnIds : turnIds).includes(id)) return false;
22854
+ setSelectedTurnId(id);
22855
+ openTurnDetails(id);
22856
+ return true;
22857
+ }, [
22858
+ transcriptSearchMode,
22859
+ transcriptSearchTurnIds,
22860
+ turnIds,
22861
+ openTurnDetails
22862
+ ]);
22140
22863
  useKeyboard((key) => {
22864
+ if (showQuitConfirmRef.current) {
22865
+ if (key.name === "escape") {
22866
+ setShowQuitConfirm(false);
22867
+ setQuitConfirmSelected("cancel");
22868
+ return;
22869
+ }
22870
+ if (key.name === "c" && key.ctrl && !key.shift) {
22871
+ renderer.destroy();
22872
+ return;
22873
+ }
22874
+ if (key.name === "return") {
22875
+ if (quitConfirmSelectedRef.current === "quit") renderer.destroy();
22876
+ else {
22877
+ setShowQuitConfirm(false);
22878
+ setQuitConfirmSelected("cancel");
22879
+ }
22880
+ return;
22881
+ }
22882
+ if (key.name === "tab" || key.name === "left" || key.name === "right") {
22883
+ setQuitConfirmSelected((prev) => prev === "cancel" ? "quit" : "cancel");
22884
+ return;
22885
+ }
22886
+ return;
22887
+ }
22141
22888
  if (modal.isOpen) return;
22142
22889
  if (transcriptSearchMode && screen === "chat") {
22143
22890
  if (key.name === "escape") onTranscriptSearchModeChange(false);
@@ -22349,7 +23096,7 @@ function AppShell() {
22349
23096
  }));
22350
23097
  return;
22351
23098
  }
22352
- if (key.ctrl && !key.shift && !key.meta && (key.name === "c" || key.name === "x") && !showQuitConfirmRef.current) {
23099
+ if (key.ctrl && !key.shift && !key.meta && (key.name === "c" || key.name === "x")) {
22353
23100
  const editor = renderer.currentFocusedEditor;
22354
23101
  if (editor?.hasSelection()) {
22355
23102
  const text = editor.getSelectedText();
@@ -22358,30 +23105,6 @@ function AppShell() {
22358
23105
  return;
22359
23106
  }
22360
23107
  }
22361
- if (showQuitConfirmRef.current) {
22362
- if (key.name === "escape") {
22363
- setShowQuitConfirm(false);
22364
- setQuitConfirmSelected("cancel");
22365
- return;
22366
- }
22367
- if (key.name === "c" && key.ctrl && !key.shift) {
22368
- renderer.destroy();
22369
- return;
22370
- }
22371
- if (key.name === "return") {
22372
- if (quitConfirmSelectedRef.current === "quit") renderer.destroy();
22373
- else {
22374
- setShowQuitConfirm(false);
22375
- setQuitConfirmSelected("cancel");
22376
- }
22377
- return;
22378
- }
22379
- if (key.name === "tab" || key.name === "left" || key.name === "right") {
22380
- setQuitConfirmSelected((prev) => prev === "cancel" ? "quit" : "cancel");
22381
- return;
22382
- }
22383
- return;
22384
- }
22385
23108
  if (key.name === "c" && key.ctrl && !key.shift) {
22386
23109
  const mode = settingsRef.current.ctrlCBehavior;
22387
23110
  if (mode === "quit") {
@@ -22635,6 +23358,8 @@ function AppShell() {
22635
23358
  enterSelectsCompletion: (providerId) => providerId === "chats",
22636
23359
  onPopupOpenChange,
22637
23360
  selectedTurnId,
23361
+ onSelectTurn: onTranscriptSelectTurn,
23362
+ onOpenTurn: onTranscriptOpenTurn,
22638
23363
  transcriptSearchMode,
22639
23364
  transcriptSearchQuery,
22640
23365
  transcriptSearchEvents,
@@ -22655,17 +23380,19 @@ function AppShell() {
22655
23380
  hints,
22656
23381
  context: contextUsage,
22657
23382
  cost: footerCost,
22658
- status: transcriptSearchMode && transcriptSearchSearching ? "searching" : pendingInteraction?.kind === "question" ? "asking" : compacting ? "compacting" : busy ? "busy" : null,
23383
+ status: transcriptSearchMode && transcriptSearchSearching ? "searching" : pendingInteraction?.kind === "question" ? "asking" : compacting ? "compacting" : retryInfo && busy ? "retrying" : busy ? "busy" : null,
22659
23384
  compactionTokens,
22660
- compactionStartedAt
23385
+ compactionStartedAt,
23386
+ retry: retryInfo,
23387
+ contextKey: keybindings.openContextPanel
22661
23388
  }),
22662
23389
  showQuitConfirm && /* @__PURE__ */ jsxs("box", {
22663
23390
  position: "absolute",
22664
23391
  top: "50%",
22665
23392
  left: "50%",
22666
23393
  marginTop: -3,
22667
- marginLeft: -18,
22668
- width: 36,
23394
+ marginLeft: -21,
23395
+ width: 42,
22669
23396
  height: 7,
22670
23397
  borderStyle: "rounded",
22671
23398
  borderColor: COLOR.warn,
@@ -22674,23 +23401,52 @@ function AppShell() {
22674
23401
  alignItems: "center",
22675
23402
  justifyContent: "center",
22676
23403
  gap: 1,
22677
- children: [/* @__PURE__ */ jsx$1("text", { children: "Quit zidane?" }), /* @__PURE__ */ jsxs("box", {
22678
- flexDirection: "row",
22679
- gap: 2,
22680
- children: [/* @__PURE__ */ jsx$1("box", {
22681
- backgroundColor: quitConfirmSelected === "cancel" ? COLOR.accent : void 0,
22682
- children: /* @__PURE__ */ jsx$1("text", {
22683
- fg: quitConfirmSelected === "cancel" ? SURFACE.modal : COLOR.dim,
22684
- children: " Cancel "
22685
- })
22686
- }), /* @__PURE__ */ jsx$1("box", {
22687
- backgroundColor: quitConfirmSelected === "quit" ? COLOR.warn : void 0,
22688
- children: /* @__PURE__ */ jsx$1("text", {
22689
- fg: quitConfirmSelected === "quit" ? SURFACE.modal : COLOR.dim,
22690
- children: " Quit "
22691
- })
22692
- })]
22693
- })]
23404
+ children: [
23405
+ /* @__PURE__ */ jsx$1("text", { children: "Quit zidane?" }),
23406
+ /* @__PURE__ */ jsxs("box", {
23407
+ flexDirection: "row",
23408
+ gap: 2,
23409
+ children: [/* @__PURE__ */ jsx$1("box", {
23410
+ backgroundColor: quitConfirmSelected === "cancel" ? COLOR.accent : void 0,
23411
+ onMouseDown: (event) => {
23412
+ event.stopPropagation();
23413
+ event.preventDefault();
23414
+ if (quitConfirmSelectedRef.current === "cancel") setShowQuitConfirm(false);
23415
+ else setQuitConfirmSelected("cancel");
23416
+ },
23417
+ children: /* @__PURE__ */ jsx$1("text", {
23418
+ fg: quitConfirmSelected === "cancel" ? SURFACE.modal : COLOR.dim,
23419
+ children: " Cancel "
23420
+ })
23421
+ }), /* @__PURE__ */ jsx$1("box", {
23422
+ backgroundColor: quitConfirmSelected === "quit" ? COLOR.warn : void 0,
23423
+ onMouseDown: (event) => {
23424
+ event.stopPropagation();
23425
+ event.preventDefault();
23426
+ if (quitConfirmSelectedRef.current === "quit") renderer.destroy();
23427
+ else setQuitConfirmSelected("quit");
23428
+ },
23429
+ children: /* @__PURE__ */ jsx$1("text", {
23430
+ fg: quitConfirmSelected === "quit" ? SURFACE.modal : COLOR.dim,
23431
+ children: " Quit "
23432
+ })
23433
+ })]
23434
+ }),
23435
+ /* @__PURE__ */ jsx$1(ModalHints, { hints: [
23436
+ {
23437
+ key: "tab",
23438
+ label: "switch"
23439
+ },
23440
+ {
23441
+ key: "↵",
23442
+ label: "confirm"
23443
+ },
23444
+ {
23445
+ key: "esc",
23446
+ label: "cancel"
23447
+ }
23448
+ ] })
23449
+ ]
22694
23450
  })
22695
23451
  ]
22696
23452
  });
@@ -23107,30 +23863,27 @@ function EffortPickerModal({ current, supportsAdaptive, onPick }) {
23107
23863
  highlightBg: SURFACE.selection
23108
23864
  }, level.id))
23109
23865
  }),
23110
- /* @__PURE__ */ jsxs("text", {
23111
- fg: COLOR.dim,
23112
- children: [
23113
- /* @__PURE__ */ jsx$1("span", {
23114
- fg: COLOR.warn,
23115
- children: "↑↓"
23116
- }),
23117
- " navigate · ",
23118
- /* @__PURE__ */ jsx$1("span", {
23119
- fg: COLOR.warn,
23120
- children: "↵"
23121
- }),
23122
- " select · ",
23123
- /* @__PURE__ */ jsx$1("span", {
23124
- fg: COLOR.warn,
23125
- children: "esc"
23126
- }),
23127
- " close · ",
23128
- /* @__PURE__ */ jsx$1("span", {
23129
- fg: COLOR.mute,
23130
- children: `${filtered.length} / ${levels.length} level${levels.length === 1 ? "" : "s"}`
23131
- })
23132
- ]
23133
- })
23866
+ /* @__PURE__ */ jsx$1(ModalHints, { hints: [
23867
+ {
23868
+ key: "↑↓",
23869
+ label: "navigate",
23870
+ labelColor: COLOR.dim
23871
+ },
23872
+ {
23873
+ key: "↵",
23874
+ label: "select",
23875
+ labelColor: COLOR.dim
23876
+ },
23877
+ {
23878
+ key: "esc",
23879
+ label: "close",
23880
+ labelColor: COLOR.dim
23881
+ },
23882
+ {
23883
+ key: "",
23884
+ label: `${filtered.length} / ${levels.length} level${levels.length === 1 ? "" : "s"}`
23885
+ }
23886
+ ] })
23134
23887
  ]
23135
23888
  });
23136
23889
  }
@@ -23221,20 +23974,13 @@ function ToggleListModal({ catalog, keyOf, settingKey, gcKeys, title, renderDeta
23221
23974
  children: [
23222
23975
  preamble,
23223
23976
  emptyState,
23224
- /* @__PURE__ */ jsxs("text", {
23225
- fg: COLOR.mute,
23226
- children: [
23227
- onRefresh && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx$1("span", {
23228
- fg: COLOR.warn,
23229
- children: "ctrl+r"
23230
- }), " refresh · "] }),
23231
- /* @__PURE__ */ jsx$1("span", {
23232
- fg: COLOR.warn,
23233
- children: "esc"
23234
- }),
23235
- " close"
23236
- ]
23237
- })
23977
+ /* @__PURE__ */ jsx$1(ModalHints, { hints: [onRefresh && {
23978
+ key: "ctrl+r",
23979
+ label: "refresh"
23980
+ }, {
23981
+ key: "esc",
23982
+ label: "close"
23983
+ }] })
23238
23984
  ]
23239
23985
  });
23240
23986
  return /* @__PURE__ */ jsxs(Modal, {
@@ -23270,30 +24016,24 @@ function ToggleListModal({ catalog, keyOf, settingKey, gcKeys, title, renderDeta
23270
24016
  }, name);
23271
24017
  })
23272
24018
  }),
23273
- /* @__PURE__ */ jsxs("text", {
23274
- fg: COLOR.mute,
23275
- children: [
23276
- /* @__PURE__ */ jsx$1("span", {
23277
- fg: COLOR.warn,
23278
- children: "↑↓"
23279
- }),
23280
- " navigate · ",
23281
- /* @__PURE__ */ jsx$1("span", {
23282
- fg: COLOR.warn,
23283
- children: "↵"
23284
- }),
23285
- " toggle · ",
23286
- onRefresh && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx$1("span", {
23287
- fg: COLOR.warn,
23288
- children: "ctrl+r"
23289
- }), " refresh · "] }),
23290
- /* @__PURE__ */ jsx$1("span", {
23291
- fg: COLOR.warn,
23292
- children: "esc"
23293
- }),
23294
- " close"
23295
- ]
23296
- })
24019
+ /* @__PURE__ */ jsx$1(ModalHints, { hints: [
24020
+ {
24021
+ key: "↑↓",
24022
+ label: "navigate"
24023
+ },
24024
+ {
24025
+ key: "↵",
24026
+ label: "toggle"
24027
+ },
24028
+ onRefresh && {
24029
+ key: "ctrl+r",
24030
+ label: "refresh"
24031
+ },
24032
+ {
24033
+ key: "esc",
24034
+ label: "close"
24035
+ }
24036
+ ] })
23297
24037
  ]
23298
24038
  });
23299
24039
  }
@@ -23603,20 +24343,13 @@ function McpsSettingsModal({ catalog, errors, toolsByServer, onLogin, onLogout,
23603
24343
  children: " { \"mcpServers\": { ... } } "
23604
24344
  }), "wrapper from Claude Code / Cursor also works."]
23605
24345
  }),
23606
- /* @__PURE__ */ jsxs("text", {
23607
- fg: COLOR.mute,
23608
- children: [
23609
- onRefresh && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx$1("span", {
23610
- fg: COLOR.warn,
23611
- children: "r"
23612
- }), " refresh · "] }),
23613
- /* @__PURE__ */ jsx$1("span", {
23614
- fg: COLOR.warn,
23615
- children: "esc"
23616
- }),
23617
- " close"
23618
- ]
23619
- })
24346
+ /* @__PURE__ */ jsx$1(ModalHints, { hints: [onRefresh && {
24347
+ key: "r",
24348
+ label: "refresh"
24349
+ }, {
24350
+ key: "esc",
24351
+ label: "close"
24352
+ }] })
23620
24353
  ]
23621
24354
  });
23622
24355
  const groups = [];
@@ -23671,8 +24404,7 @@ function McpsSettingsModal({ catalog, errors, toolsByServer, onLogin, onLogout,
23671
24404
  focusedEntry,
23672
24405
  focusedStatus,
23673
24406
  expanded,
23674
- showRefresh: !!onRefresh,
23675
- COLOR
24407
+ showRefresh: !!onRefresh
23676
24408
  })
23677
24409
  ]
23678
24410
  });
@@ -23903,7 +24635,7 @@ function renderDetailPanel(entry, status, COLOR) {
23903
24635
  });
23904
24636
  return null;
23905
24637
  }
23906
- function renderActionHints({ focusedRow, focusedEntry, focusedStatus, expanded, showRefresh, COLOR }) {
24638
+ function renderActionHints({ focusedRow, focusedEntry, focusedStatus, expanded, showRefresh }) {
23907
24639
  const effectiveStatus = focusedStatus ?? { kind: "idle" };
23908
24640
  const onServerRow = focusedRow?.kind === "server";
23909
24641
  const canL = onServerRow && focusedEntry ? canLogin(focusedEntry, effectiveStatus) : false;
@@ -23911,77 +24643,44 @@ function renderActionHints({ focusedRow, focusedEntry, focusedStatus, expanded,
23911
24643
  const canCancel = effectiveStatus.kind === "authorizing";
23912
24644
  const canExpand = onServerRow && focusedEntry ? !expanded.has(focusedEntry.config.name) : false;
23913
24645
  const canCollapse = focusedRow?.kind === "tool" || (onServerRow && focusedEntry ? expanded.has(focusedEntry.config.name) : false);
23914
- return /* @__PURE__ */ jsxs("text", {
23915
- fg: COLOR.mute,
23916
- children: [
23917
- /* @__PURE__ */ jsx$1("span", {
23918
- fg: COLOR.warn,
23919
- children: "↑↓"
23920
- }),
23921
- " navigate · ",
23922
- /* @__PURE__ */ jsx$1("span", {
23923
- fg: COLOR.warn,
23924
- children: "↵"
23925
- }),
23926
- " toggle",
23927
- canExpand && /* @__PURE__ */ jsxs("span", { children: [
23928
- " · ",
23929
- /* @__PURE__ */ jsx$1("span", {
23930
- fg: COLOR.warn,
23931
- children: "→"
23932
- }),
23933
- " expand"
23934
- ] }),
23935
- canCollapse && /* @__PURE__ */ jsxs("span", { children: [
23936
- " · ",
23937
- /* @__PURE__ */ jsx$1("span", {
23938
- fg: COLOR.warn,
23939
- children: "←"
23940
- }),
23941
- " collapse"
23942
- ] }),
23943
- canL && /* @__PURE__ */ jsxs("span", { children: [
23944
- " · ",
23945
- /* @__PURE__ */ jsx$1("span", {
23946
- fg: COLOR.warn,
23947
- children: "l"
23948
- }),
23949
- " login"
23950
- ] }),
23951
- canO && /* @__PURE__ */ jsxs("span", { children: [
23952
- " · ",
23953
- /* @__PURE__ */ jsx$1("span", {
23954
- fg: COLOR.warn,
23955
- children: "o"
23956
- }),
23957
- " logout"
23958
- ] }),
23959
- showRefresh && /* @__PURE__ */ jsxs("span", { children: [
23960
- " · ",
23961
- /* @__PURE__ */ jsx$1("span", {
23962
- fg: COLOR.warn,
23963
- children: "r"
23964
- }),
23965
- " refresh"
23966
- ] }),
23967
- canCancel && /* @__PURE__ */ jsxs("span", { children: [
23968
- " · ",
23969
- /* @__PURE__ */ jsx$1("span", {
23970
- fg: COLOR.warn,
23971
- children: "esc"
23972
- }),
23973
- " cancel"
23974
- ] }),
23975
- !canCancel && /* @__PURE__ */ jsxs("span", { children: [
23976
- " · ",
23977
- /* @__PURE__ */ jsx$1("span", {
23978
- fg: COLOR.warn,
23979
- children: "esc"
23980
- }),
23981
- " close"
23982
- ] })
23983
- ]
23984
- });
24646
+ return /* @__PURE__ */ jsx$1(ModalHints, { hints: [
24647
+ {
24648
+ key: "↑↓",
24649
+ label: "navigate"
24650
+ },
24651
+ {
24652
+ key: "↵",
24653
+ label: "toggle"
24654
+ },
24655
+ canExpand && {
24656
+ key: "→",
24657
+ label: "expand"
24658
+ },
24659
+ canCollapse && {
24660
+ key: "←",
24661
+ label: "collapse"
24662
+ },
24663
+ canL && {
24664
+ key: "l",
24665
+ label: "login"
24666
+ },
24667
+ canO && {
24668
+ key: "o",
24669
+ label: "logout"
24670
+ },
24671
+ showRefresh && {
24672
+ key: "r",
24673
+ label: "refresh"
24674
+ },
24675
+ canCancel && {
24676
+ key: "esc",
24677
+ label: "cancel"
24678
+ },
24679
+ !canCancel && {
24680
+ key: "esc",
24681
+ label: "close"
24682
+ }
24683
+ ] });
23985
24684
  }
23986
24685
  function renderErrors(errors, home, warnColor) {
23987
24686
  if (!errors || errors.length === 0) return null;
@@ -24206,6 +24905,7 @@ async function runTui(options = {}) {
24206
24905
  debounceDelay: 0,
24207
24906
  targetFps: bootFps,
24208
24907
  maxFps: bootFps,
24908
+ useMouse: config.initialSettings.mouseInteractions ?? DEFAULT_SETTINGS.mouseInteractions,
24209
24909
  gatherStats
24210
24910
  });
24211
24911
  bootTick("runTui:after-createCliRenderer");