zidane 6.2.12 → 6.2.14
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.
- package/dist/{acp-C-auiVTk.js → acp-DxYjSCQW.js} +5 -5
- package/dist/{acp-C-auiVTk.js.map → acp-DxYjSCQW.js.map} +1 -1
- package/dist/acp-cli.js +6 -6
- package/dist/acp.js +1 -1
- package/dist/{agent-DhfdXgL2.js → agent-DoEx_WD3.js} +6 -5
- package/dist/{agent-DhfdXgL2.js.map → agent-DoEx_WD3.js.map} +1 -1
- package/dist/agent.js +2 -2
- package/dist/aliasing-lVFbm81n.js +217 -0
- package/dist/aliasing-lVFbm81n.js.map +1 -0
- package/dist/{anthropic-uXfsormU.js → anthropic-BYsc6qWj.js} +3 -3
- package/dist/{anthropic-uXfsormU.js.map → anthropic-BYsc6qWj.js.map} +1 -1
- package/dist/{auth-TZg3MVi2.js → auth-Bb479DFB.js} +4 -4
- package/dist/{auth-TZg3MVi2.js.map → auth-Bb479DFB.js.map} +1 -1
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat/pure.js +3 -2
- package/dist/chat.d.ts +3 -3
- package/dist/chat.js +6 -5
- package/dist/chat.js.map +1 -1
- package/dist/eval.js +1 -1
- package/dist/extensions.js +1 -1
- package/dist/headless.js +3 -3
- package/dist/history-search-C4bk1Wz_.js +486 -0
- package/dist/history-search-C4bk1Wz_.js.map +1 -0
- package/dist/history-search-worker.d.ts +1 -0
- package/dist/history-search-worker.js +86 -0
- package/dist/history-search-worker.js.map +1 -0
- package/dist/index-l0wehkWU.d.ts.map +1 -1
- package/dist/index.js +7 -7
- package/dist/{messages-BDpXLFoe.js → messages-CvHfYB7f.js} +3 -3
- package/dist/{messages-BDpXLFoe.js.map → messages-CvHfYB7f.js.map} +1 -1
- package/dist/{openai-compat-J_JZ7qsw.js → openai-compat-pHI0kBWH.js} +37 -8
- package/dist/openai-compat-pHI0kBWH.js.map +1 -0
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/arcee.js +1 -1
- package/dist/providers/baseten.js +1 -1
- package/dist/providers/cerebras.js +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.js +1 -1
- package/dist/providers/openai.js +27 -10
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.js +1 -1
- package/dist/providers/xai.js +1 -1
- package/dist/{providers-CclOzGxc.js → providers-D9Y2RndM.js} +3 -3
- package/dist/{providers-CclOzGxc.js.map → providers-D9Y2RndM.js.map} +1 -1
- package/dist/providers.js +4 -4
- package/dist/{read-state-Dq_TXUX1.js → read-state-rDmfeLmz.js} +3 -196
- package/dist/read-state-rDmfeLmz.js.map +1 -0
- package/dist/restate.js +2 -1
- package/dist/restate.js.map +1 -1
- package/dist/{session-CIXE-Bzp.js → session-DzrXKLw-.js} +2 -2
- package/dist/{session-CIXE-Bzp.js.map → session-DzrXKLw-.js.map} +1 -1
- package/dist/session.js +2 -2
- package/dist/{tool-formatters-BSwVkSX2.d.ts → tool-formatters-DdgS-xE2.d.ts} +90 -4
- package/dist/tool-formatters-DdgS-xE2.d.ts.map +1 -0
- package/dist/tools.js +2 -2
- package/dist/{transcript-anchors-BzcuKnq8.js → transcript-anchors-DE6D0dNo.js} +4 -4
- package/dist/transcript-anchors-DE6D0dNo.js.map +1 -0
- package/dist/transcript-anchors-DKjXpQ0h.d.ts.map +1 -1
- package/dist/tui.d.ts +52 -10
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +1344 -249
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-CXjRaltC.js → turn-operations-CokV32wf.js} +82 -11
- package/dist/turn-operations-CokV32wf.js.map +1 -0
- package/dist/{turn-operations-CjkgQKmR.d.ts → turn-operations-sE-Sli9H.d.ts} +2 -2
- package/dist/{turn-operations-CjkgQKmR.d.ts.map → turn-operations-sE-Sli9H.d.ts.map} +1 -1
- package/dist/{xai-D-wALLuZ.js → xai-BwhzoehX.js} +2 -2
- package/dist/{xai-D-wALLuZ.js.map → xai-BwhzoehX.js.map} +1 -1
- package/package.json +1 -1
- package/dist/hash-DJRzmcPI.js +0 -24
- package/dist/hash-DJRzmcPI.js.map +0 -1
- package/dist/openai-compat-J_JZ7qsw.js.map +0 -1
- package/dist/read-state-Dq_TXUX1.js.map +0 -1
- package/dist/tool-formatters-BSwVkSX2.d.ts.map +0 -1
- package/dist/transcript-anchors-BzcuKnq8.js.map +0 -1
- package/dist/turn-operations-CXjRaltC.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as useMcpAuthState, $n as buildPlanSystem, $t as restoreChainFallbacks, A as runOAuthLogin, Bt as createDiscoverySlot, Ct as SETTINGS_CHOICES, D as formatPathForCwd, Dt as clampFps, Et as SettingsProvider, G as buildVisibleMcpRows, Gt as useConfig, Ht as useDiscovery, J as createFileMcpCredentialStore, Jt as buildRunModel, K as indexOfServerRow, Kt as resolveConfig, L as loadMcpToolsCache, M as buildMcpServers, Mt as resolveTheme, O as fetchOAuthRedirect, Ot as useSettings, P as discoverProjectMcps, Q as useMcpAuthDispatch, Qn as buildBuildSystem, Qt as providerKeySetChanged, R as mcpToolsCachePath, St as SETTINGS_CATEGORIES, T as suggestSafelistEntries, Tn as accentColor, Tt as SETTINGS_TOGGLES, U as useMcpToolToggleMap, Ut as useDiscoveryOptional, V as subscribeMcpToolsCache, Vn as useActiveTodos, Vt as DiscoveryProvider, Wt as ConfigProvider, Xt as chainsEqual, Yt as chainPositionOf, Z as McpAuthProvider, Zt as filterChainFallbacks, _ as useSafeModeQueue, _n as discoverAgentsMd, _r as toolCallPreview, _t as buildHints, a as useSurfaces, an as bootTick, at as buildResumedToolResultsTurn, b as getSafelist, br as usageByModel, bt as useEnabledToggleSet, c as useStreamBuffer, cr as lastContextSizeFromTurns, ct as makeRequestInteraction, d as discoverProjectSkills, dr as marginTopFor, dt as useInteractionsActions, er as envSection, et as getMcpAuthStatus, ft as useInteractionsQueue, g as useSafeModeActions, gn as chatAutoCompactBehavior, gt as truncateTrailing, h as SafeModeProvider, hr as sumRunCosts, ht as hintsLength, i as useSelectStyle, ir as compactSummaryEvent, it as PRESENT_PLAN_TOOL, j as supportsOAuth, jn as TODO_STATUS_GLYPHS, jt as resolveChipColor, k as oauthUsesManualCodePaste, l as buildSkillsConfig, lr as listSessionMeta, lt as pendingInteractionsFromTurns, m as writeSessionExport, mr as stripSpawnTokensLine, mt as clipHintsToWidth, n as ThemeProvider, nn as useCompletion, nr as createChatTurnsReadTool, nt as ASK_USER_TOOL, on as buildUpdateHint, or as deriveSessionTitle, ot as createInteractionTools, pr as serverToolResultSummary, pt as EMPTY_HINTS, q as parentServerName, qt as resolveStoragePaths, r as useColors, rn as tryOpenBrowser, rt as InteractionsProvider, s as useTheme, sn as useUpdateCheck, sr as eventsFromTurns, t as computeTurnAnchors, tn as toggleChainEntry, tr as chatReferenceTokensForPrompt, u as defaultSkillScanPaths, ur as loadState, vr as toolResultText, wt as SETTINGS_INPUTS, x as isOnSafelist, xt as DEFAULT_SETTINGS, y as addToSafelist, yr as updateToolEventOutcomes, yt as listProjectFiles, z as refreshMcpToolsCatalog } from "./transcript-anchors-
|
|
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-
|
|
1
|
+
import { $ as useMcpAuthState, $n as buildPlanSystem, $t as restoreChainFallbacks, A as runOAuthLogin, Bt as createDiscoverySlot, Ct as SETTINGS_CHOICES, D as formatPathForCwd, Dt as clampFps, Et as SettingsProvider, G as buildVisibleMcpRows, Gt as useConfig, Ht as useDiscovery, J as createFileMcpCredentialStore, Jt as buildRunModel, K as indexOfServerRow, Kt as resolveConfig, L as loadMcpToolsCache, M as buildMcpServers, Mt as resolveTheme, O as fetchOAuthRedirect, Ot as useSettings, P as discoverProjectMcps, Q as useMcpAuthDispatch, Qn as buildBuildSystem, Qt as providerKeySetChanged, R as mcpToolsCachePath, St as SETTINGS_CATEGORIES, T as suggestSafelistEntries, Tn as accentColor, Tt as SETTINGS_TOGGLES, U as useMcpToolToggleMap, Ut as useDiscoveryOptional, V as subscribeMcpToolsCache, Vn as useActiveTodos, Vt as DiscoveryProvider, Wt as ConfigProvider, Xt as chainsEqual, Yt as chainPositionOf, Z as McpAuthProvider, Zt as filterChainFallbacks, _ as useSafeModeQueue, _n as discoverAgentsMd, _r as toolCallPreview, _t as buildHints, a as useSurfaces, an as bootTick, at as buildResumedToolResultsTurn, b as getSafelist, br as usageByModel, bt as useEnabledToggleSet, c as useStreamBuffer, cr as lastContextSizeFromTurns, ct as makeRequestInteraction, d as discoverProjectSkills, dr as marginTopFor, dt as useInteractionsActions, er as envSection, et as getMcpAuthStatus, ft as useInteractionsQueue, g as useSafeModeActions, gn as chatAutoCompactBehavior, gt as truncateTrailing, h as SafeModeProvider, hr as sumRunCosts, ht as hintsLength, i as useSelectStyle, ir as compactSummaryEvent, it as PRESENT_PLAN_TOOL, j as supportsOAuth, jn as TODO_STATUS_GLYPHS, jt as resolveChipColor, k as oauthUsesManualCodePaste, l as buildSkillsConfig, lr as listSessionMeta, lt as pendingInteractionsFromTurns, m as writeSessionExport, mr as stripSpawnTokensLine, mt as clipHintsToWidth, n as ThemeProvider, nn as useCompletion, nr as createChatTurnsReadTool, nt as ASK_USER_TOOL, on as buildUpdateHint, or as deriveSessionTitle, ot as createInteractionTools, pr as serverToolResultSummary, pt as EMPTY_HINTS, q as parentServerName, qt as resolveStoragePaths, r as useColors, rn as tryOpenBrowser, rt as InteractionsProvider, s as useTheme, sn as useUpdateCheck, sr as eventsFromTurns, t as computeTurnAnchors, tn as toggleChainEntry, tr as chatReferenceTokensForPrompt, u as defaultSkillScanPaths, ur as loadState, vr as toolResultText, wt as SETTINGS_INPUTS, x as isOnSafelist, xt as DEFAULT_SETTINGS, y as addToSafelist, yr as updateToolEventOutcomes, yt as listProjectFiles, z as refreshMcpToolsCatalog } from "./transcript-anchors-DE6D0dNo.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-DoEx_WD3.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
4
|
import { n as appendCapped } from "./output-cap-kBUHBlnq.js";
|
|
5
5
|
import { i as killProcessGroup, n as SUPPORTS_PROCESS_GROUPS, r as createProcessContext } from "./contexts-A4ep-iJs.js";
|
|
@@ -9,17 +9,18 @@ import { a as createRingBufferSink, i as createLoggingHooks, l as logFileTimesta
|
|
|
9
9
|
import { i as sniffImageMediaType } from "./media-sniff-Bn76JxAu.js";
|
|
10
10
|
import { n as flattenTurns, r as formatTokenUsage, t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
|
|
11
11
|
import { replaceDynamicSection } from "./prompt.js";
|
|
12
|
-
import { a as getExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, s as setExtensionSessionState, t as createSession } from "./session-
|
|
12
|
+
import { a as getExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, s as setExtensionSessionState, t as createSession } from "./session-DzrXKLw-.js";
|
|
13
13
|
import { o as withTimeout } from "./timeout-BDetbuN6.js";
|
|
14
14
|
import { n as connectMcpServers, u as McpOAuthProvider } from "./mcp-BCaN_Gyg.js";
|
|
15
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";
|
|
16
16
|
import { s as resolveCompletionSubmit } from "./completion-core-BRo86R_L.js";
|
|
17
17
|
import { i as installSkillFromSpec, r as SkillInstallError } from "./skills-DKWqoaYH.js";
|
|
18
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-Clf-R27F.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-
|
|
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-Bb479DFB.js";
|
|
20
20
|
import { createDockerContext } from "./contexts/docker.js";
|
|
21
21
|
import { createTuiStore } from "./session/sqlite.js";
|
|
22
|
-
import { $ as
|
|
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";
|
|
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";
|
|
23
24
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
24
25
|
import { Buffer } from "node:buffer";
|
|
25
26
|
import * as fs from "node:fs";
|
|
@@ -1505,15 +1506,16 @@ const BASH_RESULT_MAX_LINES = 10;
|
|
|
1505
1506
|
* same-turn events read as one continuous highlighted block instead of a
|
|
1506
1507
|
* striped list.
|
|
1507
1508
|
*/
|
|
1508
|
-
const EventLine = memo(({ event, previous, depthOffset = 0, selected = false, anchorId, hideChildLabel = false, settleCodeEpoch = 0, liveSpawn }) => {
|
|
1509
|
+
const EventLine = memo(({ event, previous, depthOffset = 0, selected = false, anchorId, hideChildLabel = false, settleCodeEpoch = 0, liveSpawn, searchQuery, searchTitleMatch = false }) => {
|
|
1509
1510
|
const SURFACE = useSurfaces();
|
|
1510
1511
|
const gap = marginTopFor(event, previous);
|
|
1512
|
+
const highlighted = selected;
|
|
1511
1513
|
return /* @__PURE__ */ jsx$1("box", {
|
|
1512
1514
|
id: anchorId,
|
|
1513
1515
|
style: {
|
|
1514
|
-
marginTop:
|
|
1515
|
-
paddingTop:
|
|
1516
|
-
backgroundColor:
|
|
1516
|
+
marginTop: 0,
|
|
1517
|
+
paddingTop: gap,
|
|
1518
|
+
backgroundColor: highlighted ? SURFACE.selection : SURFACE.background,
|
|
1517
1519
|
alignSelf: "stretch",
|
|
1518
1520
|
flexShrink: 0,
|
|
1519
1521
|
flexDirection: "column"
|
|
@@ -1523,9 +1525,11 @@ const EventLine = memo(({ event, previous, depthOffset = 0, selected = false, an
|
|
|
1523
1525
|
previous,
|
|
1524
1526
|
depthOffset,
|
|
1525
1527
|
hideChildLabel,
|
|
1526
|
-
selected,
|
|
1528
|
+
selected: highlighted,
|
|
1527
1529
|
settleCodeEpoch,
|
|
1528
|
-
liveSpawn
|
|
1530
|
+
liveSpawn,
|
|
1531
|
+
searchQuery,
|
|
1532
|
+
searchTitleMatch
|
|
1529
1533
|
})
|
|
1530
1534
|
});
|
|
1531
1535
|
});
|
|
@@ -1558,8 +1562,8 @@ function Footer({ hints, context, cost = null, status = null, compactionTokens =
|
|
|
1558
1562
|
const timer = setInterval(() => setCompactTick((t) => t + 1), 1e3);
|
|
1559
1563
|
return () => clearInterval(timer);
|
|
1560
1564
|
}, [status]);
|
|
1561
|
-
const
|
|
1562
|
-
const sW = status === "compacting" ? 2 +
|
|
1565
|
+
const statusLabel = status === "compacting" ? compactingLabel(compactionTokens, compactionStartedAt) : status === "searching" ? " Searching messages…" : "";
|
|
1566
|
+
const sW = status === "compacting" || status === "searching" ? 2 + statusLabel.length : status === "asking" ? 3 : status ? 2 : 0;
|
|
1563
1567
|
if (hW + sW + (rightW > 0 ? rightW + 1 : 0) <= inner) return /* @__PURE__ */ jsxs("box", {
|
|
1564
1568
|
style: {
|
|
1565
1569
|
flexDirection: "row",
|
|
@@ -1571,7 +1575,7 @@ function Footer({ hints, context, cost = null, status = null, compactionTokens =
|
|
|
1571
1575
|
/* @__PURE__ */ jsx$1(HintsText, { hints }),
|
|
1572
1576
|
/* @__PURE__ */ jsx$1(FooterStatusIcon, {
|
|
1573
1577
|
status,
|
|
1574
|
-
|
|
1578
|
+
label: statusLabel
|
|
1575
1579
|
}),
|
|
1576
1580
|
/* @__PURE__ */ jsx$1("box", { style: { flexGrow: 1 } }),
|
|
1577
1581
|
/* @__PURE__ */ jsx$1(RightStatus, {
|
|
@@ -1594,7 +1598,7 @@ function Footer({ hints, context, cost = null, status = null, compactionTokens =
|
|
|
1594
1598
|
},
|
|
1595
1599
|
children: [/* @__PURE__ */ jsx$1(HintsText, { hints: stackedHints }), /* @__PURE__ */ jsx$1(FooterStatusIcon, {
|
|
1596
1600
|
status,
|
|
1597
|
-
|
|
1601
|
+
label: statusLabel
|
|
1598
1602
|
})]
|
|
1599
1603
|
}), rightW > 0 && /* @__PURE__ */ jsxs("box", {
|
|
1600
1604
|
style: {
|
|
@@ -1625,16 +1629,16 @@ function RightStatus({ cost, context }) {
|
|
|
1625
1629
|
* compacting adds a short label because background compaction can otherwise
|
|
1626
1630
|
* look like an idle spinner.
|
|
1627
1631
|
*/
|
|
1628
|
-
function FooterStatusIcon({ status,
|
|
1632
|
+
function FooterStatusIcon({ status, label = "" }) {
|
|
1629
1633
|
const COLOR = useColors();
|
|
1630
1634
|
if (!status) return null;
|
|
1631
1635
|
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 });
|
|
1632
1636
|
return /* @__PURE__ */ jsxs("text", { children: [
|
|
1633
1637
|
/* @__PURE__ */ jsx$1("span", { children: " " }),
|
|
1634
1638
|
glyph,
|
|
1635
|
-
|
|
1639
|
+
label && /* @__PURE__ */ jsx$1("span", {
|
|
1636
1640
|
fg: COLOR.accent,
|
|
1637
|
-
children:
|
|
1641
|
+
children: label
|
|
1638
1642
|
})
|
|
1639
1643
|
] });
|
|
1640
1644
|
}
|
|
@@ -1924,9 +1928,8 @@ function StatusSpinner({ color }) {
|
|
|
1924
1928
|
children: useSpinnerFrame()
|
|
1925
1929
|
});
|
|
1926
1930
|
}
|
|
1927
|
-
function Transcript({ events, settings, selectedTurnId = null, busy = false }) {
|
|
1931
|
+
function Transcript({ events, settings, selectedTurnId = null, busy = false, searchQuery, searchPending = false, onLoadEarlier }) {
|
|
1928
1932
|
const COLOR = useColors();
|
|
1929
|
-
const items = useMemo(() => partitionTranscript(events, settings), [events, settings]);
|
|
1930
1933
|
const showThrobber = busy && settings.showThrobber;
|
|
1931
1934
|
const codeSettle = useRef({
|
|
1932
1935
|
wasBusy: false,
|
|
@@ -1949,8 +1952,48 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false }) {
|
|
|
1949
1952
|
codeSettle.current.wasBusy = busy;
|
|
1950
1953
|
const throbberLabel = events.length > 0 && events[events.length - 1].kind === "thinking" ? "Thinking" : void 0;
|
|
1951
1954
|
const ownership = useMemo(() => turnSelectionOwnership(events), [events]);
|
|
1955
|
+
const searchMatches = useMemo(() => {
|
|
1956
|
+
const direct = /* @__PURE__ */ new Set();
|
|
1957
|
+
const callIds = /* @__PURE__ */ new Set();
|
|
1958
|
+
const childIds = /* @__PURE__ */ new Set();
|
|
1959
|
+
if (!searchQuery) return {
|
|
1960
|
+
direct,
|
|
1961
|
+
callIds,
|
|
1962
|
+
childIds
|
|
1963
|
+
};
|
|
1964
|
+
const stack = [];
|
|
1965
|
+
for (const event of events) {
|
|
1966
|
+
if (event.kind === "spawn-start") stack.push(event);
|
|
1967
|
+
if (searchHighlightRanges(streamEventSearchText(event), searchQuery).length > 0) {
|
|
1968
|
+
direct.add(event);
|
|
1969
|
+
if (event.callId) callIds.add(event.callId);
|
|
1970
|
+
if ((event.depth ?? 0) > 0) {
|
|
1971
|
+
for (const parent of stack) if (parent.childId) childIds.add(parent.childId);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
if (event.kind === "spawn-end") {
|
|
1975
|
+
const index = stack.findLastIndex((parent) => parent.childId === event.childId);
|
|
1976
|
+
if (index >= 0) stack.splice(index);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
return {
|
|
1980
|
+
direct,
|
|
1981
|
+
callIds,
|
|
1982
|
+
childIds
|
|
1983
|
+
};
|
|
1984
|
+
}, [events, searchQuery]);
|
|
1985
|
+
const items = useMemo(() => partitionTranscript(events, settings, searchQuery ? (event) => searchMatches.direct.has(event) : void 0), [
|
|
1986
|
+
events,
|
|
1987
|
+
settings,
|
|
1988
|
+
searchQuery,
|
|
1989
|
+
searchMatches
|
|
1990
|
+
]);
|
|
1952
1991
|
const scrollboxRef = useRef(null);
|
|
1953
1992
|
const rowVisibility = useRef(createRowVisibilityStore());
|
|
1993
|
+
const loadEarlierRef = useRef(onLoadEarlier);
|
|
1994
|
+
loadEarlierRef.current = onLoadEarlier;
|
|
1995
|
+
const earlierLoad = useRef(null);
|
|
1996
|
+
const earlierLoadArmed = useRef(false);
|
|
1954
1997
|
useEffect(() => {
|
|
1955
1998
|
const timer = setInterval(() => {
|
|
1956
1999
|
const sb = scrollboxRef.current;
|
|
@@ -1959,9 +2002,45 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false }) {
|
|
|
1959
2002
|
top: sb.viewport.y,
|
|
1960
2003
|
height: sb.viewport.height
|
|
1961
2004
|
});
|
|
2005
|
+
const topThreshold = Math.max(4, Math.floor(sb.viewport.height / 4));
|
|
2006
|
+
if (sb.scrollTop > topThreshold) earlierLoadArmed.current = true;
|
|
2007
|
+
else if (earlierLoadArmed.current && !earlierLoad.current && loadEarlierRef.current && hiddenEarlierTranscriptCount(events) > 0) {
|
|
2008
|
+
earlierLoadArmed.current = false;
|
|
2009
|
+
const snapshot = {
|
|
2010
|
+
oldHeight: sb.scrollHeight,
|
|
2011
|
+
oldTop: sb.scrollTop
|
|
2012
|
+
};
|
|
2013
|
+
earlierLoad.current = snapshot;
|
|
2014
|
+
if (!loadEarlierRef.current()) {
|
|
2015
|
+
earlierLoad.current = null;
|
|
2016
|
+
earlierLoadArmed.current = true;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
1962
2019
|
}, 200);
|
|
1963
2020
|
return () => clearInterval(timer);
|
|
1964
|
-
}, []);
|
|
2021
|
+
}, [events]);
|
|
2022
|
+
useEffect(() => {
|
|
2023
|
+
const pending = earlierLoad.current;
|
|
2024
|
+
if (!pending) return;
|
|
2025
|
+
let cancelled = false;
|
|
2026
|
+
let attempts = 0;
|
|
2027
|
+
const restore = () => {
|
|
2028
|
+
if (cancelled) return;
|
|
2029
|
+
const sb = scrollboxRef.current;
|
|
2030
|
+
if (attempts < 5 && (!sb || sb.scrollHeight <= pending.oldHeight)) {
|
|
2031
|
+
attempts++;
|
|
2032
|
+
requestAnimationFrame(restore);
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
if (sb) sb.scrollTop = pending.oldTop + Math.max(0, sb.scrollHeight - pending.oldHeight);
|
|
2036
|
+
earlierLoad.current = null;
|
|
2037
|
+
};
|
|
2038
|
+
const handle = requestAnimationFrame(restore);
|
|
2039
|
+
return () => {
|
|
2040
|
+
cancelled = true;
|
|
2041
|
+
cancelAnimationFrame(handle);
|
|
2042
|
+
};
|
|
2043
|
+
}, [events]);
|
|
1965
2044
|
const modal = useModalState();
|
|
1966
2045
|
useKeyboard(useCallback((key) => {
|
|
1967
2046
|
if (modal?.isOpen) return;
|
|
@@ -1993,36 +2072,56 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false }) {
|
|
|
1993
2072
|
}, [items, events]);
|
|
1994
2073
|
useEffect(() => {
|
|
1995
2074
|
if (!selectedTurnId) return;
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
const
|
|
2075
|
+
let handle = 0;
|
|
2076
|
+
let attempts = 0;
|
|
2077
|
+
const align = () => {
|
|
2078
|
+
const scrollbox = scrollboxRef.current;
|
|
2079
|
+
if (!scrollbox) return;
|
|
2080
|
+
const id = anchors.idByTurn.get(selectedTurnId);
|
|
2081
|
+
if (searchQuery && id) {
|
|
2082
|
+
const child = scrollbox.content.findDescendantById(id);
|
|
2083
|
+
if (child && scrollbox.scrollHeight > scrollbox.viewport.height) {
|
|
2084
|
+
scrollbox.scrollTop = child.y - scrollbox.content.y;
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
if (attempts++ < 5) handle = requestAnimationFrame(align);
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
1999
2090
|
const ownsLast = anchors.lastTurnId !== void 0 && ownership.get(anchors.lastTurnId) === selectedTurnId;
|
|
2000
2091
|
if (selectedTurnId === anchors.lastTurnId || ownsLast) {
|
|
2001
2092
|
scrollbox.scrollTop = scrollbox.scrollHeight;
|
|
2002
2093
|
return;
|
|
2003
2094
|
}
|
|
2004
|
-
const id = anchors.idByTurn.get(selectedTurnId);
|
|
2005
2095
|
if (id) scrollbox.scrollChildIntoView(id);
|
|
2006
|
-
}
|
|
2096
|
+
};
|
|
2097
|
+
handle = requestAnimationFrame(align);
|
|
2007
2098
|
return () => cancelAnimationFrame(handle);
|
|
2008
2099
|
}, [
|
|
2009
2100
|
selectedTurnId,
|
|
2010
2101
|
anchors,
|
|
2011
|
-
ownership
|
|
2102
|
+
ownership,
|
|
2103
|
+
searchQuery
|
|
2012
2104
|
]);
|
|
2013
|
-
if (items.length === 0 && !showThrobber)
|
|
2105
|
+
if (items.length === 0 && !showThrobber) {
|
|
2106
|
+
if (searchQuery) return searchPending ? /* @__PURE__ */ jsx$1("box", { style: { flexGrow: 1 } }) : /* @__PURE__ */ jsx$1("text", {
|
|
2107
|
+
fg: COLOR.mute,
|
|
2108
|
+
children: `No messages match “${searchQuery.trim()}”`
|
|
2109
|
+
});
|
|
2110
|
+
return /* @__PURE__ */ jsx$1(EmptyState, {});
|
|
2111
|
+
}
|
|
2014
2112
|
return /* @__PURE__ */ jsx$1(RowVisibilityContext.Provider, {
|
|
2015
2113
|
value: rowVisibility.current.store,
|
|
2016
2114
|
children: /* @__PURE__ */ jsxs("scrollbox", {
|
|
2017
2115
|
ref: scrollboxRef,
|
|
2116
|
+
id: "transcript-scrollbox",
|
|
2018
2117
|
focusable: false,
|
|
2019
2118
|
style: {
|
|
2020
2119
|
flexGrow: 1,
|
|
2021
2120
|
paddingLeft: 1,
|
|
2022
2121
|
paddingRight: 1
|
|
2023
2122
|
},
|
|
2024
|
-
stickyScroll:
|
|
2025
|
-
stickyStart: "bottom",
|
|
2123
|
+
stickyScroll: !searchQuery,
|
|
2124
|
+
stickyStart: searchQuery ? void 0 : "bottom",
|
|
2026
2125
|
verticalScrollbarOptions: {
|
|
2027
2126
|
width: 1,
|
|
2028
2127
|
trackOptions: {
|
|
@@ -2039,7 +2138,9 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false }) {
|
|
|
2039
2138
|
selected: isTurnHighlighted(item.event, selectedTurnId, ownership),
|
|
2040
2139
|
anchorId: anchors.ids[i][0],
|
|
2041
2140
|
settleCodeEpoch: codeSettle.current.targets.has(item.event) ? codeSettle.current.epoch : 0,
|
|
2042
|
-
liveSpawn: live?.[0]
|
|
2141
|
+
liveSpawn: live?.[0],
|
|
2142
|
+
searchQuery,
|
|
2143
|
+
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)
|
|
2043
2144
|
}, i);
|
|
2044
2145
|
}
|
|
2045
2146
|
return /* @__PURE__ */ jsx$1(SubagentBlock, {
|
|
@@ -2049,7 +2150,9 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false }) {
|
|
|
2049
2150
|
anchorIds: anchors.ids[i],
|
|
2050
2151
|
codeSettleTargets: codeSettle.current.targets,
|
|
2051
2152
|
codeSettleEpoch: codeSettle.current.epoch,
|
|
2052
|
-
live: liveByItem.get(i)
|
|
2153
|
+
live: liveByItem.get(i),
|
|
2154
|
+
searchQuery,
|
|
2155
|
+
searchTitleMatches: searchMatches
|
|
2053
2156
|
}, i);
|
|
2054
2157
|
}), showThrobber && /* @__PURE__ */ jsx$1("box", {
|
|
2055
2158
|
style: { marginTop: items.length > 0 ? 1 : 0 },
|
|
@@ -2145,8 +2248,8 @@ function collapseCompletedSpawnStarts(events) {
|
|
|
2145
2248
|
}
|
|
2146
2249
|
return events.filter((_, i) => keep[i]);
|
|
2147
2250
|
}
|
|
2148
|
-
function partitionTranscript(events, settings) {
|
|
2149
|
-
const visible = collapseCompletedSpawnStarts(events).filter((
|
|
2251
|
+
function partitionTranscript(events, settings, searchReveal) {
|
|
2252
|
+
const visible = collapseCompletedSpawnStarts(events).filter((event) => isVisible(event, settings) || searchReveal !== void 0 && (event.depth ?? 0) === 0 && searchReveal(event));
|
|
2150
2253
|
if (visible.length === 0) return [];
|
|
2151
2254
|
if (settings.hideSubagentOutput) return visible.map((event, i) => ({
|
|
2152
2255
|
kind: "event",
|
|
@@ -2192,7 +2295,7 @@ function partitionTranscript(events, settings) {
|
|
|
2192
2295
|
* indented twice. Grandchildren (depth ≥ 2) still indent further, so
|
|
2193
2296
|
* nested subagents remain visually distinct.
|
|
2194
2297
|
*/
|
|
2195
|
-
function SubagentBlock({ events, previous, selectedTurnId = null, anchorIds, codeSettleTargets, codeSettleEpoch = 0, live }) {
|
|
2298
|
+
function SubagentBlock({ events, previous, selectedTurnId = null, anchorIds, codeSettleTargets, codeSettleEpoch = 0, live, searchQuery, searchTitleMatches }) {
|
|
2196
2299
|
const COLOR = useColors();
|
|
2197
2300
|
const childIds = useMemo(() => {
|
|
2198
2301
|
const set = /* @__PURE__ */ new Set();
|
|
@@ -2224,7 +2327,9 @@ function SubagentBlock({ events, previous, selectedTurnId = null, anchorIds, cod
|
|
|
2224
2327
|
anchorId: anchorIds?.[i],
|
|
2225
2328
|
hideChildLabel,
|
|
2226
2329
|
settleCodeEpoch: codeSettleTargets?.has(evt) ? codeSettleEpoch : 0,
|
|
2227
|
-
liveSpawn: evt.kind === "spawn-start" ? live?.find((spawn) => spawn.id === evt.childId) : void 0
|
|
2330
|
+
liveSpawn: evt.kind === "spawn-start" ? live?.find((spawn) => spawn.id === evt.childId) : void 0,
|
|
2331
|
+
searchQuery,
|
|
2332
|
+
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)
|
|
2228
2333
|
}, i))
|
|
2229
2334
|
});
|
|
2230
2335
|
}
|
|
@@ -2233,13 +2338,15 @@ function SubagentBlock({ events, previous, selectedTurnId = null, anchorIds, cod
|
|
|
2233
2338
|
* the left lane for the whole run; only the right tail changes:
|
|
2234
2339
|
* active `2 turns · 14s · ⠋ tool call`, done `4 turns`.
|
|
2235
2340
|
*/
|
|
2236
|
-
function SpawnLifecycleMarker({ row, dim, event, hideChildLabel, live }) {
|
|
2341
|
+
function SpawnLifecycleMarker({ row, dim, event, hideChildLabel, live, searchMatch = false }) {
|
|
2237
2342
|
const COLOR = useColors();
|
|
2343
|
+
const matchColor = resolveChipColor(useSurfaces().chips, "default");
|
|
2238
2344
|
const frame = useSpinnerFrame();
|
|
2239
2345
|
const terminal = event.kind === "spawn-end";
|
|
2240
2346
|
const failed = terminal && (event.text.startsWith("error") || event.text.startsWith("aborted"));
|
|
2241
2347
|
const turns = terminal ? event.spawn?.turns : live?.turns;
|
|
2242
2348
|
const turnLabel = `${turns ?? 0} turn${turns === 1 ? "" : "s"}`;
|
|
2349
|
+
const showTail = live !== void 0 || terminal && turns !== void 0;
|
|
2243
2350
|
const elapsed = live ? formatDuration(Math.max(0, Date.now() - live.startedAt)) : null;
|
|
2244
2351
|
return /* @__PURE__ */ jsxs("box", {
|
|
2245
2352
|
style: {
|
|
@@ -2258,7 +2365,8 @@ function SpawnLifecycleMarker({ row, dim, event, hideChildLabel, live }) {
|
|
|
2258
2365
|
children: terminal ? failed ? "✗ " : "🌳 " : "🌱 "
|
|
2259
2366
|
}),
|
|
2260
2367
|
!hideChildLabel && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
|
|
2261
|
-
fg: COLOR.brand,
|
|
2368
|
+
fg: searchMatch ? matchColor.fg : COLOR.brand,
|
|
2369
|
+
bg: searchMatch ? matchColor.bg : void 0,
|
|
2262
2370
|
children: event.childId ?? "child"
|
|
2263
2371
|
}), /* @__PURE__ */ jsx$1("span", {
|
|
2264
2372
|
fg: COLOR.mute,
|
|
@@ -2278,7 +2386,7 @@ function SpawnLifecycleMarker({ row, dim, event, hideChildLabel, live }) {
|
|
|
2278
2386
|
]
|
|
2279
2387
|
}),
|
|
2280
2388
|
/* @__PURE__ */ jsx$1("box", { style: { flexGrow: 1 } }),
|
|
2281
|
-
/* @__PURE__ */ jsxs("text", {
|
|
2389
|
+
showTail && /* @__PURE__ */ jsxs("text", {
|
|
2282
2390
|
fg: COLOR.mute,
|
|
2283
2391
|
wrapMode: "none",
|
|
2284
2392
|
style: { flexShrink: 0 },
|
|
@@ -2333,7 +2441,7 @@ function rowStyle(paddingLeft) {
|
|
|
2333
2441
|
alignSelf: "stretch"
|
|
2334
2442
|
};
|
|
2335
2443
|
}
|
|
2336
|
-
function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = false, selected = false, settleCodeEpoch = 0, liveSpawn }) {
|
|
2444
|
+
function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = false, selected = false, settleCodeEpoch = 0, liveSpawn, searchQuery, searchTitleMatch = false }) {
|
|
2337
2445
|
const COLOR = useColors();
|
|
2338
2446
|
const { settings } = useSettings();
|
|
2339
2447
|
const cleanText = stripTerminalControlChars(event.text);
|
|
@@ -2346,7 +2454,8 @@ function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = fals
|
|
|
2346
2454
|
case "user-prompt": return /* @__PURE__ */ jsx$1(UserPromptBlock, {
|
|
2347
2455
|
text: safeText,
|
|
2348
2456
|
refs: event.refs,
|
|
2349
|
-
attachments: event.attachments
|
|
2457
|
+
attachments: event.attachments,
|
|
2458
|
+
searchQuery
|
|
2350
2459
|
});
|
|
2351
2460
|
case "info": return /* @__PURE__ */ jsx$1("box", {
|
|
2352
2461
|
style: row,
|
|
@@ -2404,7 +2513,8 @@ function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = fals
|
|
|
2404
2513
|
children: [/* @__PURE__ */ jsx$1(ToolCallBlock, {
|
|
2405
2514
|
event,
|
|
2406
2515
|
display: settings.toolCallDisplay === "full" ? "full" : "formatted",
|
|
2407
|
-
dim: child
|
|
2516
|
+
dim: child,
|
|
2517
|
+
searchMatch: searchTitleMatch
|
|
2408
2518
|
}), event.tool === "todowrite" && /* @__PURE__ */ jsx$1(TodoInProgressList, {
|
|
2409
2519
|
input: event.input,
|
|
2410
2520
|
dim: child
|
|
@@ -2416,7 +2526,8 @@ function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = fals
|
|
|
2416
2526
|
renderDefault: () => /* @__PURE__ */ jsx$1(ToolResultBlock, {
|
|
2417
2527
|
text: event.text,
|
|
2418
2528
|
indent: row.paddingLeft,
|
|
2419
|
-
maxLines: isUserBashEvent(event) ? settings.showFullShellOutput ? Number.MAX_SAFE_INTEGER : BASH_RESULT_MAX_LINES : void 0
|
|
2529
|
+
maxLines: isUserBashEvent(event) ? settings.showFullShellOutput ? Number.MAX_SAFE_INTEGER : BASH_RESULT_MAX_LINES : void 0,
|
|
2530
|
+
searchQuery
|
|
2420
2531
|
})
|
|
2421
2532
|
});
|
|
2422
2533
|
case "custom": return /* @__PURE__ */ jsx$1("box", {
|
|
@@ -2461,13 +2572,15 @@ function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = fals
|
|
|
2461
2572
|
dim: COLOR.dim,
|
|
2462
2573
|
event,
|
|
2463
2574
|
hideChildLabel,
|
|
2464
|
-
live: liveSpawn
|
|
2575
|
+
live: liveSpawn,
|
|
2576
|
+
searchMatch: searchTitleMatch
|
|
2465
2577
|
});
|
|
2466
2578
|
case "spawn-end": return /* @__PURE__ */ jsx$1(SpawnLifecycleMarker, {
|
|
2467
2579
|
row,
|
|
2468
2580
|
dim: COLOR.dim,
|
|
2469
2581
|
event,
|
|
2470
|
-
hideChildLabel
|
|
2582
|
+
hideChildLabel,
|
|
2583
|
+
searchMatch: searchTitleMatch
|
|
2471
2584
|
});
|
|
2472
2585
|
case "compact-summary": return /* @__PURE__ */ jsx$1(CompactSummaryBlock, {
|
|
2473
2586
|
event,
|
|
@@ -2635,9 +2748,10 @@ function CompactSummaryBlock({ event, indent }) {
|
|
|
2635
2748
|
*/
|
|
2636
2749
|
/** Prompt chevron rendered ahead of every user-prompt block. */
|
|
2637
2750
|
const USER_PROMPT_PREFIX = "❯ ";
|
|
2638
|
-
function UserPromptBlock({ text, refs, attachments }) {
|
|
2751
|
+
function UserPromptBlock({ text, refs, attachments, searchQuery }) {
|
|
2639
2752
|
const COLOR = useColors();
|
|
2640
2753
|
const SURFACE = useSurfaces();
|
|
2754
|
+
const matchColor = resolveChipColor(SURFACE.chips, "default");
|
|
2641
2755
|
const boxStyle = {
|
|
2642
2756
|
border: true,
|
|
2643
2757
|
borderColor: COLOR.borderActive,
|
|
@@ -2670,20 +2784,30 @@ function UserPromptBlock({ text, refs, attachments }) {
|
|
|
2670
2784
|
})] }, `att-${idx}`);
|
|
2671
2785
|
})
|
|
2672
2786
|
}) : null;
|
|
2673
|
-
if (!refs || refs.length === 0)
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2787
|
+
if (!refs || refs.length === 0) {
|
|
2788
|
+
const ranges = searchQuery ? searchHighlightRanges(text, searchQuery) : [];
|
|
2789
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
2790
|
+
style: boxStyle,
|
|
2791
|
+
children: [
|
|
2792
|
+
text.length > 0 && /* @__PURE__ */ jsxs("text", {
|
|
2793
|
+
wrapMode: "word",
|
|
2794
|
+
children: [/* @__PURE__ */ jsx$1("span", {
|
|
2795
|
+
fg: COLOR.brand,
|
|
2796
|
+
children: USER_PROMPT_PREFIX
|
|
2797
|
+
}), highlightSegments(text, ranges).map((segment, i) => /* @__PURE__ */ jsx$1("span", {
|
|
2798
|
+
fg: segment.highlighted ? matchColor.fg : void 0,
|
|
2799
|
+
bg: segment.highlighted ? matchColor.bg : void 0,
|
|
2800
|
+
children: segment.text
|
|
2801
|
+
}, i))]
|
|
2802
|
+
}),
|
|
2803
|
+
!text.length && attachmentChips && /* @__PURE__ */ jsx$1("text", {
|
|
2804
|
+
fg: COLOR.brand,
|
|
2805
|
+
children: USER_PROMPT_PREFIX
|
|
2806
|
+
}),
|
|
2807
|
+
attachmentChips
|
|
2808
|
+
]
|
|
2809
|
+
});
|
|
2810
|
+
}
|
|
2687
2811
|
const segments = splitPromptSegments(text, refs);
|
|
2688
2812
|
return /* @__PURE__ */ jsxs("box", {
|
|
2689
2813
|
style: boxStyle,
|
|
@@ -2696,7 +2820,14 @@ function UserPromptBlock({ text, refs, attachments }) {
|
|
|
2696
2820
|
fg: COLOR.brand,
|
|
2697
2821
|
children: USER_PROMPT_PREFIX
|
|
2698
2822
|
}), segments.map((seg, i) => {
|
|
2699
|
-
if (seg.kind === "plain")
|
|
2823
|
+
if (seg.kind === "plain") {
|
|
2824
|
+
const ranges = searchQuery ? searchHighlightRanges(seg.text, searchQuery) : [];
|
|
2825
|
+
return /* @__PURE__ */ jsx$1("text", { children: highlightSegments(seg.text, ranges).map((part, j) => /* @__PURE__ */ jsx$1("span", {
|
|
2826
|
+
fg: part.highlighted ? matchColor.fg : void 0,
|
|
2827
|
+
bg: part.highlighted ? matchColor.bg : void 0,
|
|
2828
|
+
children: part.text
|
|
2829
|
+
}, j)) }, i);
|
|
2830
|
+
}
|
|
2700
2831
|
const chip = resolveChipColor(SURFACE.chips, seg.providerId);
|
|
2701
2832
|
return /* @__PURE__ */ jsx$1("text", {
|
|
2702
2833
|
fg: chip.fg,
|
|
@@ -3131,33 +3262,64 @@ function CommandDetailBlock({ label, text, indent, diff }) {
|
|
|
3131
3262
|
})]
|
|
3132
3263
|
});
|
|
3133
3264
|
}
|
|
3134
|
-
function ToolResultBlock({ text, indent, diff, maxLines = TOOL_RESULT_MAX_LINES }) {
|
|
3265
|
+
function ToolResultBlock({ text, indent, diff, maxLines = TOOL_RESULT_MAX_LINES, searchQuery }) {
|
|
3135
3266
|
const COLOR = useColors();
|
|
3136
|
-
const
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3267
|
+
const matchColor = resolveChipColor(useSurfaces().chips, "default");
|
|
3268
|
+
const { lines, start } = useMemo(() => {
|
|
3269
|
+
const rawLines = stripAnsiSequences(text).split("\n");
|
|
3270
|
+
let end = rawLines.length;
|
|
3271
|
+
while (end > 0 && rawLines[end - 1].trim() === "") end--;
|
|
3272
|
+
const trimmed = rawLines.slice(0, end);
|
|
3273
|
+
let windowStart = 0;
|
|
3274
|
+
if (searchQuery && trimmed.length > maxLines) {
|
|
3275
|
+
const matchIdx = trimmed.findIndex((line) => searchHighlightRanges(line, searchQuery).length > 0);
|
|
3276
|
+
if (matchIdx >= maxLines) windowStart = Math.min(Math.max(0, matchIdx - Math.floor(maxLines / 2)), trimmed.length - maxLines);
|
|
3277
|
+
}
|
|
3278
|
+
return {
|
|
3279
|
+
lines: trimmed,
|
|
3280
|
+
start: windowStart
|
|
3281
|
+
};
|
|
3282
|
+
}, [
|
|
3283
|
+
text,
|
|
3284
|
+
searchQuery,
|
|
3285
|
+
maxLines
|
|
3286
|
+
]);
|
|
3287
|
+
if (lines.length === 0) return null;
|
|
3288
|
+
const visible = lines.slice(start, start + maxLines);
|
|
3289
|
+
const omittedBefore = start;
|
|
3290
|
+
const omitted = Math.max(0, lines.length - start - maxLines);
|
|
3143
3291
|
return /* @__PURE__ */ jsxs("box", {
|
|
3144
3292
|
style: {
|
|
3145
3293
|
paddingLeft: indent,
|
|
3146
3294
|
flexDirection: "column"
|
|
3147
3295
|
},
|
|
3148
|
-
children: [
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
fg: COLOR.
|
|
3158
|
-
children: "
|
|
3159
|
-
|
|
3160
|
-
|
|
3296
|
+
children: [
|
|
3297
|
+
omittedBefore > 0 && /* @__PURE__ */ jsxs("text", {
|
|
3298
|
+
fg: COLOR.mute,
|
|
3299
|
+
children: [/* @__PURE__ */ jsx$1("span", {
|
|
3300
|
+
fg: COLOR.borderActive,
|
|
3301
|
+
children: "┃ "
|
|
3302
|
+
}), `… ${omittedBefore} earlier line${omittedBefore === 1 ? "" : "s"}`]
|
|
3303
|
+
}),
|
|
3304
|
+
visible.map((line, i) => /* @__PURE__ */ jsxs("text", {
|
|
3305
|
+
fg: COLOR.mute,
|
|
3306
|
+
children: [/* @__PURE__ */ jsx$1("span", {
|
|
3307
|
+
fg: COLOR.borderActive,
|
|
3308
|
+
children: "┃ "
|
|
3309
|
+
}), diff && line ? /* @__PURE__ */ jsx$1(DiffStatLine, { line }) : highlightSegments(line || " ", searchQuery ? searchHighlightRanges(line, searchQuery) : []).map((segment, j) => /* @__PURE__ */ jsx$1("span", {
|
|
3310
|
+
fg: segment.highlighted ? matchColor.fg : COLOR.mute,
|
|
3311
|
+
bg: segment.highlighted ? matchColor.bg : void 0,
|
|
3312
|
+
children: segment.text
|
|
3313
|
+
}, j))]
|
|
3314
|
+
}, i)),
|
|
3315
|
+
omitted > 0 && /* @__PURE__ */ jsxs("text", {
|
|
3316
|
+
fg: COLOR.mute,
|
|
3317
|
+
children: [/* @__PURE__ */ jsx$1("span", {
|
|
3318
|
+
fg: COLOR.borderActive,
|
|
3319
|
+
children: "┃ "
|
|
3320
|
+
}), `… ${omitted} more line${omitted === 1 ? "" : "s"}`]
|
|
3321
|
+
})
|
|
3322
|
+
]
|
|
3161
3323
|
});
|
|
3162
3324
|
}
|
|
3163
3325
|
function EditDiffBlock({ payload, dim }) {
|
|
@@ -3514,8 +3676,9 @@ function HunkBlock({ index, hunk, outcome, tool, path, filetype, mdStyle, COLOR,
|
|
|
3514
3676
|
});
|
|
3515
3677
|
}
|
|
3516
3678
|
const FULL_VIEW_MAX_INPUT_BYTES = 32 * 1024;
|
|
3517
|
-
function ToolCallBlock({ event, display, dim }) {
|
|
3679
|
+
function ToolCallBlock({ event, display, dim, searchMatch = false }) {
|
|
3518
3680
|
const COLOR = useColors();
|
|
3681
|
+
const matchColor = resolveChipColor(useSurfaces().chips, "default");
|
|
3519
3682
|
const mdStyle = useMdStyle();
|
|
3520
3683
|
const name = event.tool ?? "";
|
|
3521
3684
|
const verb = displayNameFor(name, event.input);
|
|
@@ -3543,7 +3706,8 @@ function ToolCallBlock({ event, display, dim }) {
|
|
|
3543
3706
|
children: "↳ "
|
|
3544
3707
|
}),
|
|
3545
3708
|
/* @__PURE__ */ jsx$1("span", {
|
|
3546
|
-
fg: dim ? COLOR.dim : COLOR.model,
|
|
3709
|
+
fg: searchMatch ? matchColor.fg : dim ? COLOR.dim : COLOR.model,
|
|
3710
|
+
bg: searchMatch ? matchColor.bg : void 0,
|
|
3547
3711
|
children: verb
|
|
3548
3712
|
}),
|
|
3549
3713
|
/* @__PURE__ */ jsx$1("span", {
|
|
@@ -3575,7 +3739,8 @@ function ToolCallBlock({ event, display, dim }) {
|
|
|
3575
3739
|
children: "↳ "
|
|
3576
3740
|
}),
|
|
3577
3741
|
/* @__PURE__ */ jsx$1("span", {
|
|
3578
|
-
fg: dim ? COLOR.dim : COLOR.model,
|
|
3742
|
+
fg: searchMatch ? matchColor.fg : dim ? COLOR.dim : COLOR.model,
|
|
3743
|
+
bg: searchMatch ? matchColor.bg : void 0,
|
|
3579
3744
|
children: verb
|
|
3580
3745
|
}),
|
|
3581
3746
|
line?.target && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
|
|
@@ -4945,6 +5110,255 @@ async function resolveExtensionModalNode(node) {
|
|
|
4945
5110
|
return loaded;
|
|
4946
5111
|
}
|
|
4947
5112
|
//#endregion
|
|
5113
|
+
//#region src/tui/history-search-worker-client.ts
|
|
5114
|
+
let nextClientId = 1;
|
|
5115
|
+
/** Mirror of the TUI's ZIDANE_DEBUG-gated logger — silent by default. */
|
|
5116
|
+
const debugFallback = process.env.ZIDANE_DEBUG ? (op, error) => console.error(`[zidane/tui] history search ${op} fell back to the main thread:`, error) : () => {};
|
|
5117
|
+
function workerUrl() {
|
|
5118
|
+
if (typeof Bun !== "undefined" && Bun.isStandaloneExecutable) return new URL("./history-search-worker.ts", import.meta.url);
|
|
5119
|
+
return new URL(import.meta.url.endsWith(".ts") ? "./history-search-worker.ts" : "./history-search-worker.js", import.meta.url);
|
|
5120
|
+
}
|
|
5121
|
+
var HistorySearchWorkerClient = class {
|
|
5122
|
+
createWorker;
|
|
5123
|
+
worker = null;
|
|
5124
|
+
constructor(createWorker = () => new Worker(workerUrl(), {
|
|
5125
|
+
type: "module",
|
|
5126
|
+
smol: true
|
|
5127
|
+
})) {
|
|
5128
|
+
this.createWorker = createWorker;
|
|
5129
|
+
}
|
|
5130
|
+
indexes = /* @__PURE__ */ new WeakMap();
|
|
5131
|
+
registrations = /* @__PURE__ */ new Map();
|
|
5132
|
+
rebuilds = /* @__PURE__ */ new Map();
|
|
5133
|
+
pending = /* @__PURE__ */ new Map();
|
|
5134
|
+
nextRequestId = 1;
|
|
5135
|
+
unavailable = false;
|
|
5136
|
+
prewarm() {
|
|
5137
|
+
if (this.unavailable) return;
|
|
5138
|
+
try {
|
|
5139
|
+
this.ensureWorker();
|
|
5140
|
+
} catch {}
|
|
5141
|
+
}
|
|
5142
|
+
async build(turns, signal) {
|
|
5143
|
+
signal?.throwIfAborted();
|
|
5144
|
+
const indexId = `search-index-${nextClientId++}`;
|
|
5145
|
+
if (this.unavailable) return {
|
|
5146
|
+
indexId,
|
|
5147
|
+
length: turns.length,
|
|
5148
|
+
turns
|
|
5149
|
+
};
|
|
5150
|
+
try {
|
|
5151
|
+
return {
|
|
5152
|
+
...await this.request({
|
|
5153
|
+
type: "build",
|
|
5154
|
+
indexId,
|
|
5155
|
+
turns
|
|
5156
|
+
}, signal),
|
|
5157
|
+
turns
|
|
5158
|
+
};
|
|
5159
|
+
} catch (error) {
|
|
5160
|
+
if (signal?.aborted) throw signal.reason;
|
|
5161
|
+
debugFallback("build", error);
|
|
5162
|
+
return {
|
|
5163
|
+
indexId,
|
|
5164
|
+
length: turns.length,
|
|
5165
|
+
turns
|
|
5166
|
+
};
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
async search(index, query, limit, signal) {
|
|
5170
|
+
if (this.unavailable) return searchSessionIndexAsync(await this.fallbackIndex(index, signal), query, {
|
|
5171
|
+
limit,
|
|
5172
|
+
signal
|
|
5173
|
+
});
|
|
5174
|
+
try {
|
|
5175
|
+
const indexId = "indexId" in index ? index.indexId : await this.register(index);
|
|
5176
|
+
return await this.request({
|
|
5177
|
+
type: "search",
|
|
5178
|
+
indexId,
|
|
5179
|
+
query,
|
|
5180
|
+
limit
|
|
5181
|
+
}, signal);
|
|
5182
|
+
} catch (error) {
|
|
5183
|
+
if (signal?.aborted) throw signal.reason;
|
|
5184
|
+
if ("indexId" in index && error instanceof Error && error.message.startsWith("Unknown search index:")) {
|
|
5185
|
+
await this.rebuild(index);
|
|
5186
|
+
return this.request({
|
|
5187
|
+
type: "search",
|
|
5188
|
+
indexId: index.indexId,
|
|
5189
|
+
query,
|
|
5190
|
+
limit
|
|
5191
|
+
}, signal);
|
|
5192
|
+
}
|
|
5193
|
+
debugFallback("search", error);
|
|
5194
|
+
return searchSessionIndexAsync(await this.fallbackIndex(index, signal), query, {
|
|
5195
|
+
limit,
|
|
5196
|
+
signal
|
|
5197
|
+
});
|
|
5198
|
+
}
|
|
5199
|
+
}
|
|
5200
|
+
async history(index, query, maxExcerpts, signal) {
|
|
5201
|
+
if (this.unavailable) return searchSessionHistoryIndexAsync(await this.fallbackIndex(index, signal), query, maxExcerpts, void 0, signal);
|
|
5202
|
+
try {
|
|
5203
|
+
const indexId = "indexId" in index ? index.indexId : await this.register(index);
|
|
5204
|
+
return await this.request({
|
|
5205
|
+
type: "history",
|
|
5206
|
+
indexId,
|
|
5207
|
+
query,
|
|
5208
|
+
maxExcerpts
|
|
5209
|
+
}, signal);
|
|
5210
|
+
} catch (error) {
|
|
5211
|
+
if (signal?.aborted) throw signal.reason;
|
|
5212
|
+
if ("indexId" in index && error instanceof Error && error.message.startsWith("Unknown search index:")) {
|
|
5213
|
+
await this.rebuild(index);
|
|
5214
|
+
return this.request({
|
|
5215
|
+
type: "history",
|
|
5216
|
+
indexId: index.indexId,
|
|
5217
|
+
query,
|
|
5218
|
+
maxExcerpts
|
|
5219
|
+
}, signal);
|
|
5220
|
+
}
|
|
5221
|
+
debugFallback("history", error);
|
|
5222
|
+
return searchSessionHistoryIndexAsync(await this.fallbackIndex(index, signal), query, maxExcerpts, void 0, signal);
|
|
5223
|
+
}
|
|
5224
|
+
}
|
|
5225
|
+
terminate() {
|
|
5226
|
+
this.worker?.terminate();
|
|
5227
|
+
this.worker = null;
|
|
5228
|
+
const error = /* @__PURE__ */ new Error("History search worker terminated");
|
|
5229
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
5230
|
+
this.pending.clear();
|
|
5231
|
+
this.registrations.clear();
|
|
5232
|
+
this.rebuilds.clear();
|
|
5233
|
+
}
|
|
5234
|
+
/** Re-register an evicted worker index; concurrent callers share one build. */
|
|
5235
|
+
rebuild(index) {
|
|
5236
|
+
const inFlight = this.rebuilds.get(index.indexId);
|
|
5237
|
+
if (inFlight) return inFlight;
|
|
5238
|
+
const rebuild = this.request({
|
|
5239
|
+
type: "build",
|
|
5240
|
+
indexId: index.indexId,
|
|
5241
|
+
turns: index.turns
|
|
5242
|
+
}).then(() => {}).finally(() => this.rebuilds.delete(index.indexId));
|
|
5243
|
+
this.rebuilds.set(index.indexId, rebuild);
|
|
5244
|
+
return rebuild;
|
|
5245
|
+
}
|
|
5246
|
+
fallbackIndex(index, signal) {
|
|
5247
|
+
if (!("indexId" in index)) return Promise.resolve(index);
|
|
5248
|
+
if (!index.fallback) index.fallback = buildSessionSearchIndexAsync(index.turns).catch((error) => {
|
|
5249
|
+
index.fallback = void 0;
|
|
5250
|
+
throw error;
|
|
5251
|
+
});
|
|
5252
|
+
signal?.throwIfAborted();
|
|
5253
|
+
return index.fallback;
|
|
5254
|
+
}
|
|
5255
|
+
ensureWorker() {
|
|
5256
|
+
if (this.worker) return this.worker;
|
|
5257
|
+
let worker;
|
|
5258
|
+
try {
|
|
5259
|
+
worker = this.createWorker();
|
|
5260
|
+
} catch (error) {
|
|
5261
|
+
this.disable(error);
|
|
5262
|
+
throw error;
|
|
5263
|
+
}
|
|
5264
|
+
worker.onmessage = (event) => this.onMessage(event.data);
|
|
5265
|
+
worker.onerror = (event) => this.disable(event.error ?? new Error(event.message));
|
|
5266
|
+
this.worker = worker;
|
|
5267
|
+
return worker;
|
|
5268
|
+
}
|
|
5269
|
+
register(index) {
|
|
5270
|
+
const known = this.indexes.get(index);
|
|
5271
|
+
if (known) {
|
|
5272
|
+
const pending = this.registrations.get(known);
|
|
5273
|
+
return pending ? pending.then(() => known) : Promise.resolve(known);
|
|
5274
|
+
}
|
|
5275
|
+
const indexId = `search-index-${nextClientId++}`;
|
|
5276
|
+
this.indexes.set(index, indexId);
|
|
5277
|
+
const registration = new Promise((resolve, reject) => {
|
|
5278
|
+
const worker = this.ensureWorker();
|
|
5279
|
+
let onRegistered;
|
|
5280
|
+
const onError = () => {
|
|
5281
|
+
worker.removeEventListener("message", onRegistered);
|
|
5282
|
+
this.registrations.delete(indexId);
|
|
5283
|
+
reject(/* @__PURE__ */ new Error("History search worker registration failed"));
|
|
5284
|
+
};
|
|
5285
|
+
onRegistered = (event) => {
|
|
5286
|
+
if (event.data.type !== "registered" || event.data.indexId !== indexId) return;
|
|
5287
|
+
worker.removeEventListener("message", onRegistered);
|
|
5288
|
+
worker.removeEventListener("error", onError);
|
|
5289
|
+
this.registrations.delete(indexId);
|
|
5290
|
+
resolve();
|
|
5291
|
+
};
|
|
5292
|
+
worker.addEventListener("message", onRegistered);
|
|
5293
|
+
worker.addEventListener("error", onError, { once: true });
|
|
5294
|
+
try {
|
|
5295
|
+
worker.postMessage({
|
|
5296
|
+
type: "register",
|
|
5297
|
+
indexId,
|
|
5298
|
+
index
|
|
5299
|
+
});
|
|
5300
|
+
} catch (error) {
|
|
5301
|
+
worker.removeEventListener("message", onRegistered);
|
|
5302
|
+
worker.removeEventListener("error", onError);
|
|
5303
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
5304
|
+
}
|
|
5305
|
+
});
|
|
5306
|
+
this.registrations.set(indexId, registration);
|
|
5307
|
+
return registration.then(() => indexId);
|
|
5308
|
+
}
|
|
5309
|
+
request(payload, signal) {
|
|
5310
|
+
signal?.throwIfAborted();
|
|
5311
|
+
const requestId = this.nextRequestId++;
|
|
5312
|
+
const worker = this.ensureWorker();
|
|
5313
|
+
return new Promise((resolve, reject) => {
|
|
5314
|
+
const abort = () => {
|
|
5315
|
+
worker.postMessage({
|
|
5316
|
+
type: "cancel",
|
|
5317
|
+
requestId
|
|
5318
|
+
});
|
|
5319
|
+
this.pending.delete(requestId);
|
|
5320
|
+
reject(signal?.reason ?? new DOMException("Aborted", "AbortError"));
|
|
5321
|
+
};
|
|
5322
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
5323
|
+
this.pending.set(requestId, {
|
|
5324
|
+
resolve: (value) => {
|
|
5325
|
+
signal?.removeEventListener("abort", abort);
|
|
5326
|
+
resolve(value);
|
|
5327
|
+
},
|
|
5328
|
+
reject: (error) => {
|
|
5329
|
+
signal?.removeEventListener("abort", abort);
|
|
5330
|
+
reject(error);
|
|
5331
|
+
}
|
|
5332
|
+
});
|
|
5333
|
+
try {
|
|
5334
|
+
worker.postMessage({
|
|
5335
|
+
...payload,
|
|
5336
|
+
requestId
|
|
5337
|
+
});
|
|
5338
|
+
} catch (error) {
|
|
5339
|
+
this.pending.delete(requestId);
|
|
5340
|
+
signal?.removeEventListener("abort", abort);
|
|
5341
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
5342
|
+
}
|
|
5343
|
+
});
|
|
5344
|
+
}
|
|
5345
|
+
onMessage(response) {
|
|
5346
|
+
if (response.requestId === void 0) return;
|
|
5347
|
+
const pending = this.pending.get(response.requestId);
|
|
5348
|
+
if (!pending) return;
|
|
5349
|
+
this.pending.delete(response.requestId);
|
|
5350
|
+
if (response.type === "error") pending.reject(new Error(response.message ?? "History search worker failed"));
|
|
5351
|
+
else pending.resolve(response.result);
|
|
5352
|
+
}
|
|
5353
|
+
disable(error) {
|
|
5354
|
+
this.unavailable = true;
|
|
5355
|
+
const reason = error instanceof Error ? error : new Error(String(error));
|
|
5356
|
+
for (const pending of this.pending.values()) pending.reject(reason);
|
|
5357
|
+
this.pending.clear();
|
|
5358
|
+
this.terminate();
|
|
5359
|
+
}
|
|
5360
|
+
};
|
|
5361
|
+
//#endregion
|
|
4948
5362
|
//#region src/tui/keybindings-modal.tsx
|
|
4949
5363
|
function KeybindingsModal({ bindings, extensionRows, filePath, onEditFile, onClose }) {
|
|
4950
5364
|
const SURFACE = useSurfaces();
|
|
@@ -5412,6 +5826,38 @@ function OptionRow({ option, isEnabled, isFocused, highlightBg }) {
|
|
|
5412
5826
|
*/
|
|
5413
5827
|
/** Visible rows in the windowed list. Keeps the modal a stable size on long catalogs. */
|
|
5414
5828
|
const VISIBLE_ROWS = 10;
|
|
5829
|
+
/**
|
|
5830
|
+
* Promote selected entries in SELECTION order, then append every unselected
|
|
5831
|
+
* row in its original catalog order. Pure/non-mutating and collision-safe:
|
|
5832
|
+
* duplicate/missing selection keys are ignored. Call only for an empty query
|
|
5833
|
+
* — search results must retain `filterModelCatalog`'s relevance order.
|
|
5834
|
+
*/
|
|
5835
|
+
function selectedModelsFirst(entries, selectedKeys, keyOf) {
|
|
5836
|
+
if (selectedKeys.length === 0) return [...entries];
|
|
5837
|
+
const byKey = new Map(entries.map((entry) => [keyOf(entry), entry]));
|
|
5838
|
+
const selected = [];
|
|
5839
|
+
const promoted = /* @__PURE__ */ new Set();
|
|
5840
|
+
for (const key of selectedKeys) {
|
|
5841
|
+
const entry = byKey.get(key);
|
|
5842
|
+
if (!entry || promoted.has(key)) continue;
|
|
5843
|
+
selected.push(entry);
|
|
5844
|
+
promoted.add(key);
|
|
5845
|
+
}
|
|
5846
|
+
return [...selected, ...entries.filter((entry) => !promoted.has(keyOf(entry)))];
|
|
5847
|
+
}
|
|
5848
|
+
function providerModelKey(providerKey, modelId) {
|
|
5849
|
+
return `${providerKey}\u0000${modelId}`;
|
|
5850
|
+
}
|
|
5851
|
+
/** Move one selected entry by one priority slot; boundary/missing = identity. */
|
|
5852
|
+
function reorderSelectedModel(selected, key, delta, keyOf) {
|
|
5853
|
+
const from = selected.findIndex((entry) => keyOf(entry) === key);
|
|
5854
|
+
const to = from + delta;
|
|
5855
|
+
if (from < 0 || to < 0 || to >= selected.length) return selected;
|
|
5856
|
+
const next = [...selected];
|
|
5857
|
+
const [entry] = next.splice(from, 1);
|
|
5858
|
+
next.splice(to, 0, entry);
|
|
5859
|
+
return next;
|
|
5860
|
+
}
|
|
5415
5861
|
function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onApplyChain }) {
|
|
5416
5862
|
const COLOR = useColors();
|
|
5417
5863
|
const SURFACE = useSurfaces();
|
|
@@ -5429,13 +5875,6 @@ function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onAppl
|
|
|
5429
5875
|
modelsFor,
|
|
5430
5876
|
current
|
|
5431
5877
|
]);
|
|
5432
|
-
const filtered = useMemo(() => filterModelCatalog(catalog, query), [catalog, query]);
|
|
5433
|
-
const [selectedIdx, setSelectedIdx] = useState(() => Math.max(0, indexOfEntry(catalog, current)));
|
|
5434
|
-
const handleQueryChange = useCallback((next) => {
|
|
5435
|
-
setQuery(next);
|
|
5436
|
-
setSelectedIdx(0);
|
|
5437
|
-
}, []);
|
|
5438
|
-
const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1);
|
|
5439
5878
|
const chainMode = onApplyChain !== void 0;
|
|
5440
5879
|
const [initialChain] = useState(() => chain && chain.length > 0 ? [...chain] : [{
|
|
5441
5880
|
provider: current.providerKey,
|
|
@@ -5443,6 +5882,21 @@ function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onAppl
|
|
|
5443
5882
|
}]);
|
|
5444
5883
|
const [chainState, setChainState] = useState(initialChain);
|
|
5445
5884
|
const modalApi = useModalState();
|
|
5885
|
+
const filtered = useMemo(() => {
|
|
5886
|
+
const matches = filterModelCatalog(catalog, query);
|
|
5887
|
+
if (query.trim()) return matches;
|
|
5888
|
+
return selectedModelsFirst(matches, chainState.map((pick) => providerModelKey(pick.provider, pick.model)), (row) => providerModelKey(row.providerKey, row.model.id));
|
|
5889
|
+
}, [
|
|
5890
|
+
catalog,
|
|
5891
|
+
query,
|
|
5892
|
+
chainState
|
|
5893
|
+
]);
|
|
5894
|
+
const [selectedIdx, setSelectedIdx] = useState(0);
|
|
5895
|
+
const handleQueryChange = useCallback((next) => {
|
|
5896
|
+
setQuery(next);
|
|
5897
|
+
setSelectedIdx(0);
|
|
5898
|
+
}, []);
|
|
5899
|
+
const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1);
|
|
5446
5900
|
const commit = () => {
|
|
5447
5901
|
const row = filtered[safeIndex];
|
|
5448
5902
|
if (!row) return;
|
|
@@ -5478,6 +5932,26 @@ function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onAppl
|
|
|
5478
5932
|
};
|
|
5479
5933
|
}, [filtered, safeIndex]);
|
|
5480
5934
|
useKeyboard((key) => {
|
|
5935
|
+
if (key.ctrl && key.shift && (key.name === "up" || key.name === "down")) {
|
|
5936
|
+
if (chainMode) {
|
|
5937
|
+
const row = filtered[safeIndex];
|
|
5938
|
+
if (row) {
|
|
5939
|
+
const keyOf = (entry) => providerModelKey(entry.provider, entry.model);
|
|
5940
|
+
const selectedKey = providerModelKey(row.providerKey, row.model.id);
|
|
5941
|
+
const delta = key.name === "up" ? -1 : 1;
|
|
5942
|
+
const reordered = reorderSelectedModel(chainState, selectedKey, delta, keyOf);
|
|
5943
|
+
if (reordered !== chainState) {
|
|
5944
|
+
setChainState([...reordered]);
|
|
5945
|
+
if (!query.trim()) {
|
|
5946
|
+
const served = new Set(catalog.map((entry) => providerModelKey(entry.providerKey, entry.model.id)));
|
|
5947
|
+
const next = reordered.filter((entry) => served.has(keyOf(entry))).findIndex((entry) => keyOf(entry) === selectedKey);
|
|
5948
|
+
if (next >= 0) setSelectedIdx(next);
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5952
|
+
}
|
|
5953
|
+
return;
|
|
5954
|
+
}
|
|
5481
5955
|
if (key.name === "up") {
|
|
5482
5956
|
setSelectedIdx((i) => {
|
|
5483
5957
|
if (filtered.length === 0) return i;
|
|
@@ -5553,6 +6027,10 @@ function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onAppl
|
|
|
5553
6027
|
children: "↵"
|
|
5554
6028
|
}),
|
|
5555
6029
|
chainMode ? " add/remove · " : " select · ",
|
|
6030
|
+
chainMode && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
|
|
6031
|
+
fg: COLOR.warn,
|
|
6032
|
+
children: "ctrl+shift+↑↓"
|
|
6033
|
+
}), " reorder · "] }),
|
|
5556
6034
|
/* @__PURE__ */ jsx$1("span", {
|
|
5557
6035
|
fg: COLOR.warn,
|
|
5558
6036
|
children: "esc"
|
|
@@ -5605,23 +6083,41 @@ function ModelSelectList({ catalog, multi = false, initial, onApply, onCancel, v
|
|
|
5605
6083
|
return true;
|
|
5606
6084
|
});
|
|
5607
6085
|
}, [catalog]);
|
|
5608
|
-
const
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
const idx = first ? deduped.findIndex((e) => e.model.id === first) : -1;
|
|
5612
|
-
return Math.max(0, idx);
|
|
6086
|
+
const [initialPicks] = useState(() => {
|
|
6087
|
+
const known = new Set(deduped.map((e) => e.model.id));
|
|
6088
|
+
return initial.filter((id) => known.has(id));
|
|
5613
6089
|
});
|
|
6090
|
+
const [picks, setPicks] = useState(initialPicks);
|
|
6091
|
+
const filtered = useMemo(() => {
|
|
6092
|
+
const matches = filterModelCatalog(deduped, query);
|
|
6093
|
+
return query.trim() ? matches : selectedModelsFirst(matches, picks, (row) => row.model.id);
|
|
6094
|
+
}, [
|
|
6095
|
+
deduped,
|
|
6096
|
+
query,
|
|
6097
|
+
picks
|
|
6098
|
+
]);
|
|
6099
|
+
const [selectedIdx, setSelectedIdx] = useState(0);
|
|
5614
6100
|
const handleQueryChange = useCallback((next) => {
|
|
5615
6101
|
setQuery(next);
|
|
5616
6102
|
setSelectedIdx(0);
|
|
5617
6103
|
}, []);
|
|
5618
6104
|
const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1);
|
|
5619
|
-
const [initialPicks] = useState(() => {
|
|
5620
|
-
const known = new Set(deduped.map((e) => e.model.id));
|
|
5621
|
-
return initial.filter((id) => known.has(id));
|
|
5622
|
-
});
|
|
5623
|
-
const [picks, setPicks] = useState(initialPicks);
|
|
5624
6105
|
useKeyboard((key) => {
|
|
6106
|
+
if (key.ctrl && key.shift && (key.name === "up" || key.name === "down")) {
|
|
6107
|
+
if (multi) {
|
|
6108
|
+
const row = filtered[safeIndex];
|
|
6109
|
+
if (row) {
|
|
6110
|
+
const delta = key.name === "up" ? -1 : 1;
|
|
6111
|
+
const from = picks.indexOf(row.model.id);
|
|
6112
|
+
const reordered = reorderSelectedModel(picks, row.model.id, delta, (id) => id);
|
|
6113
|
+
if (reordered !== picks) {
|
|
6114
|
+
setPicks(reordered);
|
|
6115
|
+
if (!query.trim()) setSelectedIdx(from + delta);
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
}
|
|
6119
|
+
return;
|
|
6120
|
+
}
|
|
5625
6121
|
if (key.name === "up" || key.name === "down") {
|
|
5626
6122
|
setSelectedIdx((i) => {
|
|
5627
6123
|
if (filtered.length === 0) return i;
|
|
@@ -5723,6 +6219,10 @@ function ModelSelectList({ catalog, multi = false, initial, onApply, onCancel, v
|
|
|
5723
6219
|
children: "↵"
|
|
5724
6220
|
}),
|
|
5725
6221
|
multi ? " add/remove · " : " select · ",
|
|
6222
|
+
multi && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
|
|
6223
|
+
fg: COLOR.warn,
|
|
6224
|
+
children: "ctrl+shift+↑↓"
|
|
6225
|
+
}), " reorder · "] }),
|
|
5726
6226
|
/* @__PURE__ */ jsx$1("span", {
|
|
5727
6227
|
fg: COLOR.warn,
|
|
5728
6228
|
children: "esc"
|
|
@@ -9293,21 +9793,27 @@ function isSessionRowId(rowId) {
|
|
|
9293
9793
|
}
|
|
9294
9794
|
/** Page-up / page-down jump size — half a typical visible window. */
|
|
9295
9795
|
const PAGE_JUMP = 6;
|
|
9296
|
-
function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreate, onFocusChange,
|
|
9796
|
+
function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreate, onFocusChange, contentMatches = /* @__PURE__ */ new Map(), contentSearching = false, contentSearchProgress = {
|
|
9797
|
+
done: 0,
|
|
9798
|
+
total: 0
|
|
9799
|
+
}, onSearchQueryChange, showAllProjects = false, onToggleScope, currentProjectRoot }) {
|
|
9297
9800
|
const focused = useModalAwareFocus();
|
|
9298
9801
|
const COLOR = useColors();
|
|
9299
9802
|
const inputRef = useRef(null);
|
|
9300
|
-
const
|
|
9301
|
-
const
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9803
|
+
const scrollboxRef = useRef(null);
|
|
9804
|
+
const [query, setQueryState] = useState("");
|
|
9805
|
+
const searchChangeRef = useRef(onSearchQueryChange);
|
|
9806
|
+
searchChangeRef.current = onSearchQueryChange;
|
|
9807
|
+
useEffect(() => () => searchChangeRef.current?.(""), []);
|
|
9808
|
+
const setQuery = useCallback((next) => {
|
|
9809
|
+
setQueryState(next);
|
|
9810
|
+
onSearchQueryChange?.(next);
|
|
9811
|
+
}, [onSearchQueryChange]);
|
|
9812
|
+
const filteredSessions = useMemo(() => filterSessionSearchCatalog(sessions, query, contentMatches), [
|
|
9813
|
+
sessions,
|
|
9814
|
+
query,
|
|
9815
|
+
contentMatches
|
|
9816
|
+
]);
|
|
9311
9817
|
const rows = useMemo(() => [{
|
|
9312
9818
|
kind: "new",
|
|
9313
9819
|
rowId: NEW_SESSION_ROW_ID,
|
|
@@ -9315,8 +9821,9 @@ function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreat
|
|
|
9315
9821
|
}, ...filteredSessions.map((meta) => ({
|
|
9316
9822
|
kind: "session",
|
|
9317
9823
|
rowId: meta.id,
|
|
9318
|
-
meta
|
|
9319
|
-
|
|
9824
|
+
meta,
|
|
9825
|
+
...query.trim() && contentMatches.get(meta.id) ? { contentMatch: contentMatches.get(meta.id) } : {}
|
|
9826
|
+
}))], [filteredSessions, contentMatches]);
|
|
9320
9827
|
useEffect(() => {
|
|
9321
9828
|
if (focused) inputRef.current?.focus();
|
|
9322
9829
|
}, [focused]);
|
|
@@ -9328,6 +9835,33 @@ function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreat
|
|
|
9328
9835
|
}
|
|
9329
9836
|
return rows.length > 1 ? 1 : 0;
|
|
9330
9837
|
}, [rows, focusedSessionId]);
|
|
9838
|
+
const lastScrolledCursorRef = useRef(-1);
|
|
9839
|
+
useEffect(() => {
|
|
9840
|
+
if (!focused) return;
|
|
9841
|
+
if (lastScrolledCursorRef.current === cursorIndex) return;
|
|
9842
|
+
const scrollbox = scrollboxRef.current;
|
|
9843
|
+
if (!scrollbox) return;
|
|
9844
|
+
let handle = 0;
|
|
9845
|
+
let attempts = 0;
|
|
9846
|
+
const align = () => {
|
|
9847
|
+
const child = scrollbox.content.findDescendantById(`session-row-${cursorIndex}`);
|
|
9848
|
+
if (child) {
|
|
9849
|
+
const childTop = child.y - scrollbox.content.y;
|
|
9850
|
+
const childBottom = childTop + child.height;
|
|
9851
|
+
if (childTop < scrollbox.scrollTop) scrollbox.scrollTop = childTop;
|
|
9852
|
+
else if (childBottom > scrollbox.scrollTop + scrollbox.viewport.height) scrollbox.scrollTop = childBottom - scrollbox.viewport.height;
|
|
9853
|
+
lastScrolledCursorRef.current = cursorIndex;
|
|
9854
|
+
return;
|
|
9855
|
+
}
|
|
9856
|
+
if (attempts++ < 5) handle = requestAnimationFrame(align);
|
|
9857
|
+
};
|
|
9858
|
+
handle = requestAnimationFrame(align);
|
|
9859
|
+
return () => cancelAnimationFrame(handle);
|
|
9860
|
+
}, [
|
|
9861
|
+
cursorIndex,
|
|
9862
|
+
focused,
|
|
9863
|
+
rows
|
|
9864
|
+
]);
|
|
9331
9865
|
useEffect(() => {
|
|
9332
9866
|
if (!onFocusChange) return;
|
|
9333
9867
|
if (focusedSessionId === "__new__") return;
|
|
@@ -9448,44 +9982,66 @@ function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreat
|
|
|
9448
9982
|
flexDirection: "column",
|
|
9449
9983
|
flexGrow: 1
|
|
9450
9984
|
},
|
|
9451
|
-
children: [
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9985
|
+
children: [
|
|
9986
|
+
/* @__PURE__ */ jsx$1("box", {
|
|
9987
|
+
style: {
|
|
9988
|
+
border: true,
|
|
9989
|
+
borderColor: COLOR.border,
|
|
9990
|
+
paddingLeft: 1,
|
|
9991
|
+
paddingRight: 1,
|
|
9992
|
+
height: 3,
|
|
9993
|
+
flexShrink: 0,
|
|
9994
|
+
marginBottom: 1
|
|
9995
|
+
},
|
|
9996
|
+
children: /* @__PURE__ */ jsx$1("input", {
|
|
9997
|
+
id: "session-search-input",
|
|
9998
|
+
ref: inputRef,
|
|
9999
|
+
focused,
|
|
10000
|
+
placeholder: "search sessions — title, project, or message content…",
|
|
10001
|
+
onInput: setQuery,
|
|
10002
|
+
onSubmit: () => {},
|
|
10003
|
+
style: { flexGrow: 1 }
|
|
10004
|
+
})
|
|
10005
|
+
}),
|
|
10006
|
+
/* @__PURE__ */ jsxs("scrollbox", {
|
|
10007
|
+
ref: scrollboxRef,
|
|
10008
|
+
id: "sessions-scrollbox",
|
|
10009
|
+
focusable: false,
|
|
10010
|
+
style: { flexGrow: 1 },
|
|
10011
|
+
children: [rows.map((row, idx) => /* @__PURE__ */ jsx$1(SessionRow, {
|
|
10012
|
+
anchorId: `session-row-${idx}`,
|
|
10013
|
+
row,
|
|
10014
|
+
separator: idx > 0,
|
|
10015
|
+
focused: idx === cursorIndex && focused,
|
|
10016
|
+
isCurrent: row.kind === "session" && row.rowId === currentId,
|
|
10017
|
+
showProject: showAllProjects,
|
|
10018
|
+
currentProjectRoot
|
|
10019
|
+
}, row.rowId)), filteredSessions.length === 0 && query.trim().length > 0 && /* @__PURE__ */ jsxs("text", {
|
|
10020
|
+
wrapMode: "none",
|
|
10021
|
+
children: [
|
|
10022
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
10023
|
+
fg: COLOR.mute,
|
|
10024
|
+
children: contentSearching ? " searching conversation history for " : " no sessions match "
|
|
10025
|
+
}),
|
|
10026
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
10027
|
+
fg: COLOR.warn,
|
|
10028
|
+
children: query.trim()
|
|
10029
|
+
}),
|
|
10030
|
+
contentSearching && /* @__PURE__ */ jsx$1("span", {
|
|
10031
|
+
fg: COLOR.mute,
|
|
10032
|
+
children: "…"
|
|
10033
|
+
})
|
|
10034
|
+
]
|
|
9486
10035
|
})]
|
|
9487
|
-
})
|
|
9488
|
-
|
|
10036
|
+
}),
|
|
10037
|
+
/* @__PURE__ */ jsx$1("box", {
|
|
10038
|
+
style: {
|
|
10039
|
+
height: 1,
|
|
10040
|
+
flexShrink: 0
|
|
10041
|
+
},
|
|
10042
|
+
children: contentSearching && /* @__PURE__ */ jsx$1(Spinner, { label: `searching conversation history ${contentSearchProgress.done}/${contentSearchProgress.total}` })
|
|
10043
|
+
})
|
|
10044
|
+
]
|
|
9489
10045
|
}), /* @__PURE__ */ jsx$1(TitleOverlay, {
|
|
9490
10046
|
title: "sessions",
|
|
9491
10047
|
meta: titleMeta
|
|
@@ -9501,18 +10057,25 @@ function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreat
|
|
|
9501
10057
|
* exactly the width of the focus + current markers above — so the first
|
|
9502
10058
|
* stat (the turn count) sits flush under the title's first letter.
|
|
9503
10059
|
*/
|
|
9504
|
-
function SessionRow({ row, focused, isCurrent, showProject = false, currentProjectRoot }) {
|
|
10060
|
+
function SessionRow({ row, anchorId, focused, isCurrent, separator = false, showProject = false, currentProjectRoot }) {
|
|
9505
10061
|
const COLOR = useColors();
|
|
10062
|
+
const SURFACE = useSurfaces();
|
|
9506
10063
|
const STATS_INDENT = " ";
|
|
9507
10064
|
const focusMark = focused ? "▶ " : " ";
|
|
9508
10065
|
const focusColor = focused ? COLOR.brand : COLOR.mute;
|
|
9509
10066
|
const titleColor = focused ? COLOR.brand : COLOR.dim;
|
|
10067
|
+
const rowStyle = {
|
|
10068
|
+
flexDirection: "column",
|
|
10069
|
+
flexShrink: 0,
|
|
10070
|
+
alignSelf: "stretch",
|
|
10071
|
+
...separator ? {
|
|
10072
|
+
border: ["top"],
|
|
10073
|
+
borderColor: COLOR.border
|
|
10074
|
+
} : {}
|
|
10075
|
+
};
|
|
9510
10076
|
if (row.kind === "new") return /* @__PURE__ */ jsxs("box", {
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
flexShrink: 0,
|
|
9514
|
-
alignSelf: "stretch"
|
|
9515
|
-
},
|
|
10077
|
+
id: anchorId,
|
|
10078
|
+
style: rowStyle,
|
|
9516
10079
|
children: [/* @__PURE__ */ jsxs("text", {
|
|
9517
10080
|
wrapMode: "none",
|
|
9518
10081
|
children: [
|
|
@@ -9544,11 +10107,8 @@ function SessionRow({ row, focused, isCurrent, showProject = false, currentProje
|
|
|
9544
10107
|
const currentMark = isCurrent ? "● " : " ";
|
|
9545
10108
|
const currentColor = isCurrent ? COLOR.accent : COLOR.mute;
|
|
9546
10109
|
return /* @__PURE__ */ jsxs("box", {
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
flexShrink: 0,
|
|
9550
|
-
alignSelf: "stretch"
|
|
9551
|
-
},
|
|
10110
|
+
id: anchorId,
|
|
10111
|
+
style: rowStyle,
|
|
9552
10112
|
children: [
|
|
9553
10113
|
/* @__PURE__ */ jsxs("text", {
|
|
9554
10114
|
wrapMode: "none",
|
|
@@ -9610,6 +10170,38 @@ function SessionRow({ row, focused, isCurrent, showProject = false, currentProje
|
|
|
9610
10170
|
fg: COLOR.mute,
|
|
9611
10171
|
children: STATS_INDENT
|
|
9612
10172
|
}), renderProjectLabel(meta.projectRoot, currentProjectRoot, COLOR)]
|
|
10173
|
+
}),
|
|
10174
|
+
row.contentMatch && /* @__PURE__ */ jsxs("box", {
|
|
10175
|
+
style: {
|
|
10176
|
+
flexDirection: "column",
|
|
10177
|
+
flexShrink: 0,
|
|
10178
|
+
marginTop: 1,
|
|
10179
|
+
marginLeft: 4,
|
|
10180
|
+
paddingLeft: 1,
|
|
10181
|
+
border: ["left"],
|
|
10182
|
+
borderColor: COLOR.border
|
|
10183
|
+
},
|
|
10184
|
+
children: [/* @__PURE__ */ jsxs("text", {
|
|
10185
|
+
wrapMode: "none",
|
|
10186
|
+
children: [/* @__PURE__ */ jsx$1("span", {
|
|
10187
|
+
fg: COLOR.brand,
|
|
10188
|
+
children: "matches"
|
|
10189
|
+
}), /* @__PURE__ */ jsx$1("span", {
|
|
10190
|
+
fg: COLOR.mute,
|
|
10191
|
+
children: ` · ${row.contentMatch.matchedTurnIds.length} message${row.contentMatch.matchedTurnIds.length === 1 ? "" : "s"}`
|
|
10192
|
+
})]
|
|
10193
|
+
}), row.contentMatch.excerpts.slice(0, 3).map((excerpt, i) => /* @__PURE__ */ jsxs("text", {
|
|
10194
|
+
fg: COLOR.dim,
|
|
10195
|
+
wrapMode: "none",
|
|
10196
|
+
children: [/* @__PURE__ */ jsx$1("span", {
|
|
10197
|
+
fg: COLOR.mute,
|
|
10198
|
+
children: `${excerpt.role === "user" ? "you" : excerpt.role} · `
|
|
10199
|
+
}), highlightSegments(excerpt.text, excerpt.highlighted).map((segment, j) => /* @__PURE__ */ jsx$1("span", {
|
|
10200
|
+
fg: segment.highlighted ? COLOR.warn : COLOR.dim,
|
|
10201
|
+
...segment.highlighted ? { bg: SURFACE.selection } : {},
|
|
10202
|
+
children: segment.text
|
|
10203
|
+
}, j))]
|
|
10204
|
+
}, `${excerpt.turnId}:${i}`))]
|
|
9613
10205
|
})
|
|
9614
10206
|
]
|
|
9615
10207
|
});
|
|
@@ -9707,7 +10299,10 @@ const MIME_BY_EXT = {
|
|
|
9707
10299
|
* default-value reference at the prop destructure.
|
|
9708
10300
|
*/
|
|
9709
10301
|
const EMPTY_QUEUED_MESSAGES = [];
|
|
9710
|
-
function
|
|
10302
|
+
function shouldRenderTranscriptSearch(searchMode, query) {
|
|
10303
|
+
return searchMode && query.trim().length > 0;
|
|
10304
|
+
}
|
|
10305
|
+
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 }) {
|
|
9711
10306
|
const COLOR = useColors();
|
|
9712
10307
|
const titleText = session?.title ?? "New session";
|
|
9713
10308
|
const sessionShortcutKey = formatBindingForDisplay(sessionDetailsBinding);
|
|
@@ -9765,6 +10360,8 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
|
|
|
9765
10360
|
hasStatusIcon
|
|
9766
10361
|
]);
|
|
9767
10362
|
const userPrompts = useMemo(() => promptHistoryFromEvents(events), [events]);
|
|
10363
|
+
const transcriptSearchActive = shouldRenderTranscriptSearch(transcriptSearchMode, transcriptSearchQuery);
|
|
10364
|
+
const displayedEvents = transcriptSearchActive ? transcriptSearchEvents : events;
|
|
9768
10365
|
const [completionPopupOpen, setCompletionPopupOpen] = useState(false);
|
|
9769
10366
|
const handlePopupOpenChange = useCallback((open) => {
|
|
9770
10367
|
setCompletionPopupOpen(open);
|
|
@@ -9792,10 +10389,13 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
|
|
|
9792
10389
|
visible: !fileEditPending
|
|
9793
10390
|
},
|
|
9794
10391
|
children: /* @__PURE__ */ jsx$1(Transcript, {
|
|
9795
|
-
events,
|
|
10392
|
+
events: displayedEvents,
|
|
9796
10393
|
settings,
|
|
9797
10394
|
selectedTurnId: selectedTurnId ?? null,
|
|
9798
|
-
|
|
10395
|
+
searchQuery: transcriptSearchActive ? transcriptSearchQuery : void 0,
|
|
10396
|
+
searchPending: transcriptSearchSearching,
|
|
10397
|
+
busy: !transcriptSearchActive && busy && !pending && !pendingInteraction,
|
|
10398
|
+
onLoadEarlier: transcriptSearchActive ? void 0 : onLoadEarlierTranscript
|
|
9799
10399
|
})
|
|
9800
10400
|
}),
|
|
9801
10401
|
/* @__PURE__ */ jsx$1(ExtensionStatusLine, {}),
|
|
@@ -9825,7 +10425,12 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
|
|
|
9825
10425
|
onCompletionTab,
|
|
9826
10426
|
enterSelectsCompletion,
|
|
9827
10427
|
onPopupOpenChange: handlePopupOpenChange,
|
|
9828
|
-
|
|
10428
|
+
searchMode: transcriptSearchMode,
|
|
10429
|
+
onSearchModeChange: onTranscriptSearchModeChange,
|
|
10430
|
+
onSearchQueryChange: onTranscriptSearchQueryChange,
|
|
10431
|
+
onSearchNavigate: onTranscriptSearchNavigate,
|
|
10432
|
+
onSearchOpen: onTranscriptSearchOpen,
|
|
10433
|
+
selectMode: queueSelectionIndex != null ? "queue" : null,
|
|
9829
10434
|
triggerHints: promptTriggerHints,
|
|
9830
10435
|
busy,
|
|
9831
10436
|
onEnterQueueFromEmpty: onEnterQueueFromEmptyPrompt,
|
|
@@ -10151,7 +10756,10 @@ const EMPTY_INLINE_PROVIDERS = [];
|
|
|
10151
10756
|
* ghost feels immediate on a pause.
|
|
10152
10757
|
*/
|
|
10153
10758
|
const INLINE_GHOST_DEBOUNCE_MS = 120;
|
|
10154
|
-
function
|
|
10759
|
+
function isTranscriptSearchExitKey(event, cursorOffset) {
|
|
10760
|
+
return event.name === "escape" || (event.name === "backspace" || event.name === "delete") && !event.ctrl && !event.meta && !event.shift && cursorOffset === 0;
|
|
10761
|
+
}
|
|
10762
|
+
function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocompleteProviders, onCompletionTab, enterSelectsCompletion, onPopupOpenChange, searchMode = false, onSearchModeChange, onSearchQueryChange, onSearchNavigate, onSearchOpen, selectMode = null, triggerHints, busy = false, onEnterQueueFromEmpty, attachImageBinding, editorBinding }) {
|
|
10155
10763
|
const focused = useModalAwareFocus();
|
|
10156
10764
|
const renderer = useRenderer();
|
|
10157
10765
|
const COLOR = useColors();
|
|
@@ -10216,7 +10824,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
10216
10824
|
* Once the user enters history (up at top), we snapshot the draft and cycle.
|
|
10217
10825
|
*/
|
|
10218
10826
|
const historyRef = useRef(null);
|
|
10219
|
-
const completion = useCompletion(bufferState, completionProviders ?? EMPTY_PROVIDERS);
|
|
10827
|
+
const completion = useCompletion(bufferState, searchMode ? EMPTY_PROVIDERS : completionProviders ?? EMPTY_PROVIDERS);
|
|
10220
10828
|
const popupOpen = completion.active != null && completion.items.length > 0;
|
|
10221
10829
|
useEffect(() => {
|
|
10222
10830
|
onPopupOpenChange?.(popupOpen);
|
|
@@ -10225,9 +10833,38 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
10225
10833
|
useChipHighlights(textareaRef, completion.references, chipStyle);
|
|
10226
10834
|
const [shellMode, setShellMode] = useState(false);
|
|
10227
10835
|
const shellModeRef = useRef(false);
|
|
10228
|
-
|
|
10229
|
-
|
|
10836
|
+
const searchModeRef = useRef(searchMode);
|
|
10837
|
+
const previousSearchModeRef = useRef(searchMode);
|
|
10838
|
+
const preSearchTextRef = useRef(null);
|
|
10839
|
+
/** Last text pushed into bufferState — distinguishes typed sentinels from bulk sets. */
|
|
10230
10840
|
const lastSyncedTextRef = useRef("");
|
|
10841
|
+
searchModeRef.current = searchMode;
|
|
10842
|
+
shellModeRef.current = shellMode;
|
|
10843
|
+
useEffect(() => {
|
|
10844
|
+
const textarea = textareaRef.current;
|
|
10845
|
+
if (!previousSearchModeRef.current && searchMode) {
|
|
10846
|
+
if (preSearchTextRef.current === null) {
|
|
10847
|
+
preSearchTextRef.current = textarea?.plainText ?? "";
|
|
10848
|
+
textarea?.clear();
|
|
10849
|
+
lastSyncedTextRef.current = "";
|
|
10850
|
+
setBufferState({
|
|
10851
|
+
text: "",
|
|
10852
|
+
cursor: 0
|
|
10853
|
+
});
|
|
10854
|
+
onSearchQueryChange?.("");
|
|
10855
|
+
}
|
|
10856
|
+
} else if (previousSearchModeRef.current && !searchMode) {
|
|
10857
|
+
const restored = preSearchTextRef.current ?? "";
|
|
10858
|
+
textarea?.setText(restored);
|
|
10859
|
+
lastSyncedTextRef.current = restored;
|
|
10860
|
+
setBufferState({
|
|
10861
|
+
text: restored,
|
|
10862
|
+
cursor: restored.length
|
|
10863
|
+
});
|
|
10864
|
+
preSearchTextRef.current = null;
|
|
10865
|
+
}
|
|
10866
|
+
previousSearchModeRef.current = searchMode;
|
|
10867
|
+
}, [searchMode, onSearchQueryChange]);
|
|
10231
10868
|
/**
|
|
10232
10869
|
* Flip shell mode with the ref mirror updated SYNCHRONOUSLY — callers
|
|
10233
10870
|
* that mutate the buffer and re-run `syncBuffer` in the same tick
|
|
@@ -10237,6 +10874,15 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
10237
10874
|
shellModeRef.current = on;
|
|
10238
10875
|
setShellMode(on);
|
|
10239
10876
|
}, []);
|
|
10877
|
+
const exitSearchMode = useCallback(() => {
|
|
10878
|
+
searchModeRef.current = false;
|
|
10879
|
+
onSearchQueryChange?.("");
|
|
10880
|
+
onSearchModeChange?.(false);
|
|
10881
|
+
}, [onSearchModeChange, onSearchQueryChange]);
|
|
10882
|
+
useKeyboard((key) => {
|
|
10883
|
+
if (!focused) return;
|
|
10884
|
+
if (searchModeRef.current && key.name === "escape") exitSearchMode();
|
|
10885
|
+
});
|
|
10240
10886
|
const [ghost, setGhost] = useState(null);
|
|
10241
10887
|
const ghostRef = useRef(null);
|
|
10242
10888
|
ghostRef.current = ghost;
|
|
@@ -10248,11 +10894,19 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
10248
10894
|
const syncBuffer = useCallback(() => {
|
|
10249
10895
|
const ta = textareaRef.current;
|
|
10250
10896
|
if (!ta) return;
|
|
10251
|
-
if (!shellModeRef.current && ta.plainText === "
|
|
10897
|
+
if (!searchModeRef.current && !shellModeRef.current && ta.plainText === "?" && lastSyncedTextRef.current === "") {
|
|
10898
|
+
ta.clear();
|
|
10899
|
+
preSearchTextRef.current = "";
|
|
10900
|
+
searchModeRef.current = true;
|
|
10901
|
+
onSearchModeChange?.(true);
|
|
10902
|
+
onSearchQueryChange?.("");
|
|
10903
|
+
}
|
|
10904
|
+
if (!searchModeRef.current && !shellModeRef.current && ta.plainText === "!" && lastSyncedTextRef.current === "") {
|
|
10252
10905
|
ta.clear();
|
|
10253
10906
|
setShellModeNow(true);
|
|
10254
10907
|
}
|
|
10255
10908
|
lastSyncedTextRef.current = ta.plainText;
|
|
10909
|
+
if (searchModeRef.current) onSearchQueryChange?.(ta.plainText);
|
|
10256
10910
|
setBufferState({
|
|
10257
10911
|
text: ta.plainText,
|
|
10258
10912
|
cursor: ta.cursorOffset
|
|
@@ -10265,7 +10919,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
10265
10919
|
const inlineEnabled = settings.inlineAutocomplete && inlineProviders.length > 0;
|
|
10266
10920
|
useEffect(() => {
|
|
10267
10921
|
const atWordBoundary = bufferState.cursor === bufferState.text.length || /\s/.test(bufferState.text[bufferState.cursor]);
|
|
10268
|
-
if (!inlineEnabled || popupOpen || selectMode !== null || shellMode || bufferState.text.length === 0 || !atWordBoundary) {
|
|
10922
|
+
if (!inlineEnabled || popupOpen || selectMode !== null || shellMode || searchMode || bufferState.text.length === 0 || !atWordBoundary) {
|
|
10269
10923
|
setGhost(null);
|
|
10270
10924
|
return;
|
|
10271
10925
|
}
|
|
@@ -10298,6 +10952,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
10298
10952
|
popupOpen,
|
|
10299
10953
|
selectMode,
|
|
10300
10954
|
shellMode,
|
|
10955
|
+
searchMode,
|
|
10301
10956
|
inlineProviders
|
|
10302
10957
|
]);
|
|
10303
10958
|
/**
|
|
@@ -10333,6 +10988,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
10333
10988
|
]);
|
|
10334
10989
|
const submit = useCallback(() => {
|
|
10335
10990
|
const value = textareaRef.current?.plainText ?? "";
|
|
10991
|
+
if (searchModeRef.current) return;
|
|
10336
10992
|
if (shellMode) {
|
|
10337
10993
|
if (!value.trim()) return;
|
|
10338
10994
|
onSubmit(`!${value}`, completion.references, attachments);
|
|
@@ -10500,6 +11156,24 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
10500
11156
|
openExternalEditor();
|
|
10501
11157
|
return;
|
|
10502
11158
|
}
|
|
11159
|
+
if (searchModeRef.current) {
|
|
11160
|
+
if (!event.ctrl && !event.meta && !event.shift && (event.name === "up" || event.name === "down")) {
|
|
11161
|
+
event.preventDefault();
|
|
11162
|
+
onSearchNavigate?.(event.name === "up" ? -1 : 1);
|
|
11163
|
+
return;
|
|
11164
|
+
}
|
|
11165
|
+
if (!event.ctrl && !event.meta && !event.shift && event.name === "return") {
|
|
11166
|
+
event.preventDefault();
|
|
11167
|
+
onSearchOpen?.();
|
|
11168
|
+
return;
|
|
11169
|
+
}
|
|
11170
|
+
if (isTranscriptSearchExitKey(event, textareaRef.current?.cursorOffset ?? 0)) {
|
|
11171
|
+
event.preventDefault();
|
|
11172
|
+
exitSearchMode();
|
|
11173
|
+
return;
|
|
11174
|
+
}
|
|
11175
|
+
return;
|
|
11176
|
+
}
|
|
10503
11177
|
if (event.name === "backspace" && !event.ctrl && !event.meta && !event.shift && shellModeRef.current && (textareaRef.current?.cursorOffset ?? 0) === 0) {
|
|
10504
11178
|
event.preventDefault();
|
|
10505
11179
|
setShellModeNow(false);
|
|
@@ -10610,7 +11284,10 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
10610
11284
|
editorBinding,
|
|
10611
11285
|
openExternalEditor,
|
|
10612
11286
|
attachments,
|
|
10613
|
-
showNotice
|
|
11287
|
+
showNotice,
|
|
11288
|
+
onSearchNavigate,
|
|
11289
|
+
onSearchOpen,
|
|
11290
|
+
exitSearchMode
|
|
10614
11291
|
]);
|
|
10615
11292
|
const ghostPosition = useMemo(() => {
|
|
10616
11293
|
if (!ghost) return null;
|
|
@@ -10691,7 +11368,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
10691
11368
|
children: [/* @__PURE__ */ jsxs("box", {
|
|
10692
11369
|
style: {
|
|
10693
11370
|
border: true,
|
|
10694
|
-
borderColor: selectMode ? COLOR.warn : shellMode ? COLOR.accent : COLOR.borderActive,
|
|
11371
|
+
borderColor: selectMode ? COLOR.warn : searchMode ? COLOR.warn : shellMode ? COLOR.accent : COLOR.borderActive,
|
|
10695
11372
|
paddingLeft: 1,
|
|
10696
11373
|
paddingRight: 1,
|
|
10697
11374
|
height: boxHeight,
|
|
@@ -10702,7 +11379,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
10702
11379
|
focused: focused && !selectMode,
|
|
10703
11380
|
keyBindings: TEXTAREA_BINDINGS,
|
|
10704
11381
|
wrapMode: "word",
|
|
10705
|
-
placeholder: selectMode === "
|
|
11382
|
+
placeholder: selectMode === "queue" ? "— queue-select mode — press ⎋ to resume typing —" : searchMode ? "Search this conversation…" : shellMode ? "Run a shell command…" : "Ask zidane…",
|
|
10706
11383
|
syntaxStyle: chipStyle,
|
|
10707
11384
|
style: {
|
|
10708
11385
|
flexGrow: 1,
|
|
@@ -10732,12 +11409,13 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
10732
11409
|
})]
|
|
10733
11410
|
}), /* @__PURE__ */ jsx$1(PromptHints, {
|
|
10734
11411
|
selectMode,
|
|
11412
|
+
searchMode,
|
|
10735
11413
|
triggerHints,
|
|
10736
11414
|
busy
|
|
10737
11415
|
})]
|
|
10738
11416
|
})
|
|
10739
11417
|
]
|
|
10740
|
-
}), !selectMode && /* @__PURE__ */ jsx$1("box", {
|
|
11418
|
+
}), !selectMode && !searchMode && /* @__PURE__ */ jsx$1("box", {
|
|
10741
11419
|
style: {
|
|
10742
11420
|
position: "absolute",
|
|
10743
11421
|
bottom: "100%",
|
|
@@ -10764,6 +11442,10 @@ const PROMPT_HINTS_NORMAL = [
|
|
|
10764
11442
|
key: "↑↓",
|
|
10765
11443
|
label: "history"
|
|
10766
11444
|
},
|
|
11445
|
+
{
|
|
11446
|
+
key: "?",
|
|
11447
|
+
label: "search"
|
|
11448
|
+
},
|
|
10767
11449
|
{
|
|
10768
11450
|
key: "ctrl+s",
|
|
10769
11451
|
label: "messages"
|
|
@@ -10792,8 +11474,8 @@ const PROMPT_HINTS_BUSY = [
|
|
|
10792
11474
|
label: "abort"
|
|
10793
11475
|
}
|
|
10794
11476
|
];
|
|
10795
|
-
/**
|
|
10796
|
-
const
|
|
11477
|
+
/** Conversation search is the message selector with an optional filter. */
|
|
11478
|
+
const PROMPT_HINTS_SEARCH = [
|
|
10797
11479
|
{
|
|
10798
11480
|
key: "↑↓",
|
|
10799
11481
|
label: "navigate"
|
|
@@ -10838,18 +11520,19 @@ const PROMPT_HINTS_SELECT_QUEUE = [{
|
|
|
10838
11520
|
* Swaps the hint set based on `selectMode` so the user sees only the
|
|
10839
11521
|
* shortcuts that actually do something in the active mode.
|
|
10840
11522
|
*/
|
|
10841
|
-
function PromptHints({ selectMode, triggerHints, busy = false }) {
|
|
11523
|
+
function PromptHints({ selectMode, searchMode = false, triggerHints, busy = false }) {
|
|
10842
11524
|
const COLOR = useColors();
|
|
10843
11525
|
const { settings } = useSettings();
|
|
10844
11526
|
const { width: termWidth } = useTerminalDimensions();
|
|
10845
|
-
const minimalResting = !selectMode && !busy && settings.uiMode === "minimal";
|
|
10846
|
-
const primary =
|
|
11527
|
+
const minimalResting = !selectMode && !searchMode && !busy && settings.uiMode === "minimal";
|
|
11528
|
+
const primary = searchMode ? PROMPT_HINTS_SEARCH : selectMode === "queue" ? PROMPT_HINTS_SELECT_QUEUE : busy ? PROMPT_HINTS_BUSY : minimalResting ? EMPTY_HINTS : PROMPT_HINTS_NORMAL;
|
|
10847
11529
|
const hints = useMemo(() => {
|
|
10848
11530
|
const budget = Math.max(0, termWidth - 5);
|
|
10849
11531
|
const tooLongCombined = !!triggerHints && triggerHints.length > 0 && !selectMode && primary.length > 0 && hintsLength(primary) + 3 + hintsLength(triggerHints) > budget;
|
|
10850
11532
|
return clipHintsToWidth(selectMode || !triggerHints || triggerHints.length === 0 || tooLongCombined ? primary : [...primary, ...triggerHints], budget);
|
|
10851
11533
|
}, [
|
|
10852
11534
|
selectMode,
|
|
11535
|
+
searchMode,
|
|
10853
11536
|
primary,
|
|
10854
11537
|
triggerHints,
|
|
10855
11538
|
termWidth
|
|
@@ -15495,7 +16178,8 @@ const PREVIEW_CHAR_MAX = 8e3;
|
|
|
15495
16178
|
* the Modal's own clamp; this is the cap on wide terminals so the modal
|
|
15496
16179
|
* keeps a comfortable shape rather than stretching to the full height.
|
|
15497
16180
|
*/
|
|
15498
|
-
const MAX_MODAL_HEIGHT =
|
|
16181
|
+
const MAX_MODAL_HEIGHT = 46;
|
|
16182
|
+
const MAX_MODAL_WIDTH = 148;
|
|
15499
16183
|
const EDIT_TEXTAREA_BINDINGS = (() => {
|
|
15500
16184
|
return [
|
|
15501
16185
|
...defaultTextareaKeyBindings.filter((b) => b.name !== "return" && !(b.name === "a" && b.ctrl && !b.shift && !b.meta)),
|
|
@@ -15523,12 +16207,18 @@ const EDIT_TEXTAREA_BINDINGS = (() => {
|
|
|
15523
16207
|
function editableText(turn) {
|
|
15524
16208
|
return turn.content.filter((b) => b.type === "text").map((b) => b.text).join("\n\n");
|
|
15525
16209
|
}
|
|
15526
|
-
function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
16210
|
+
function TurnDetailsModal({ turn, toolResultTurns = [], searchQuery, index, total, actions, keybindings }) {
|
|
15527
16211
|
const COLOR = useColors();
|
|
16212
|
+
const SURFACE = useSurfaces();
|
|
15528
16213
|
const modal = useModal();
|
|
15529
|
-
const fullText =
|
|
15530
|
-
|
|
15531
|
-
|
|
16214
|
+
const { fullText, previewEntries, summary } = useMemo(() => {
|
|
16215
|
+
const previewTurns = [turn, ...toolResultTurns];
|
|
16216
|
+
return {
|
|
16217
|
+
fullText: previewTurns.map(turnAsText).filter(Boolean).join("\n\n"),
|
|
16218
|
+
previewEntries: buildPreviewEntries(previewTurns, PREVIEW_CHAR_MAX),
|
|
16219
|
+
summary: blockSummary(previewTurns)
|
|
16220
|
+
};
|
|
16221
|
+
}, [turn, toolResultTurns]);
|
|
15532
16222
|
const bottomTitle = `${index - 1} before · ${total - index} after`;
|
|
15533
16223
|
const [pending, setPending] = useState(null);
|
|
15534
16224
|
const [copyStatus, setCopyStatus] = useState("idle");
|
|
@@ -15594,6 +16284,10 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
15594
16284
|
title: editing ? `edit turn ${index} / ${total} · ${turn.role}` : `turn ${index} / ${total} · ${turn.role}`,
|
|
15595
16285
|
bottomTitle: editing ? void 0 : bottomTitle,
|
|
15596
16286
|
maxHeight: MAX_MODAL_HEIGHT,
|
|
16287
|
+
maxWidth: MAX_MODAL_WIDTH,
|
|
16288
|
+
minWidth: 64,
|
|
16289
|
+
horizontalMargin: 2,
|
|
16290
|
+
verticalMargin: 1,
|
|
15597
16291
|
disableEscape: editing || pending !== null,
|
|
15598
16292
|
children: [
|
|
15599
16293
|
!editing && /* @__PURE__ */ jsxs("text", {
|
|
@@ -15673,7 +16367,8 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
15673
16367
|
title: " preview ",
|
|
15674
16368
|
style: {
|
|
15675
16369
|
border: true,
|
|
15676
|
-
borderColor: COLOR.
|
|
16370
|
+
borderColor: COLOR.borderActive,
|
|
16371
|
+
backgroundColor: SURFACE.background,
|
|
15677
16372
|
paddingLeft: 1,
|
|
15678
16373
|
paddingRight: 1,
|
|
15679
16374
|
flexDirection: "column",
|
|
@@ -15681,17 +16376,38 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
15681
16376
|
flexShrink: 1,
|
|
15682
16377
|
minHeight: 5
|
|
15683
16378
|
},
|
|
15684
|
-
children:
|
|
16379
|
+
children: previewEntries.length > 0 ? /* @__PURE__ */ jsx$1("scrollbox", {
|
|
15685
16380
|
focusable: false,
|
|
15686
|
-
style: {
|
|
16381
|
+
style: {
|
|
16382
|
+
flexGrow: 1,
|
|
16383
|
+
rootOptions: { backgroundColor: SURFACE.background },
|
|
16384
|
+
contentOptions: { flexDirection: "column" }
|
|
16385
|
+
},
|
|
15687
16386
|
stickyScroll: false,
|
|
15688
|
-
children: /* @__PURE__ */
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
16387
|
+
children: previewEntries.map((entry, i) => /* @__PURE__ */ jsxs("box", {
|
|
16388
|
+
style: {
|
|
16389
|
+
flexDirection: "column",
|
|
16390
|
+
marginBottom: i === previewEntries.length - 1 ? 0 : 1
|
|
16391
|
+
},
|
|
16392
|
+
children: [/* @__PURE__ */ jsx$1("text", {
|
|
16393
|
+
fg: entry.error ? COLOR.error : entry.kind === "tool-result" ? COLOR.accent : entry.kind === "tool-call" ? COLOR.warn : COLOR.model,
|
|
16394
|
+
children: /* @__PURE__ */ jsx$1(HighlightedPreviewText, {
|
|
16395
|
+
text: entry.label,
|
|
16396
|
+
query: searchQuery,
|
|
16397
|
+
foreground: entry.error ? COLOR.error : entry.kind === "tool-result" ? COLOR.accent : entry.kind === "tool-call" ? COLOR.warn : COLOR.model
|
|
16398
|
+
})
|
|
16399
|
+
}), entry.text && /* @__PURE__ */ jsx$1("text", {
|
|
16400
|
+
fg: entry.error ? COLOR.error : COLOR.dim,
|
|
16401
|
+
children: /* @__PURE__ */ jsx$1(HighlightedPreviewText, {
|
|
16402
|
+
text: entry.text,
|
|
16403
|
+
query: searchQuery,
|
|
16404
|
+
foreground: entry.error ? COLOR.error : COLOR.dim
|
|
16405
|
+
})
|
|
16406
|
+
})]
|
|
16407
|
+
}, `${entry.kind}:${i}`))
|
|
15692
16408
|
}) : /* @__PURE__ */ jsx$1("text", {
|
|
15693
16409
|
fg: COLOR.mute,
|
|
15694
|
-
children: "— no
|
|
16410
|
+
children: "— no previewable content —"
|
|
15695
16411
|
})
|
|
15696
16412
|
}),
|
|
15697
16413
|
editing ? /* @__PURE__ */ jsxs("text", {
|
|
@@ -15851,14 +16567,97 @@ function ActionRow({ pending, copyStatus, canCopy, canEdit, forkKey, deleteKey,
|
|
|
15851
16567
|
]
|
|
15852
16568
|
});
|
|
15853
16569
|
}
|
|
15854
|
-
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
|
|
15858
|
-
|
|
15859
|
-
|
|
16570
|
+
function buildPreviewEntries(turns, limit) {
|
|
16571
|
+
const entries = [];
|
|
16572
|
+
let remaining = limit;
|
|
16573
|
+
let skipped = 0;
|
|
16574
|
+
const add = (entry) => {
|
|
16575
|
+
if (remaining <= 0) {
|
|
16576
|
+
skipped++;
|
|
16577
|
+
return;
|
|
16578
|
+
}
|
|
16579
|
+
const text = entry.text.length > remaining ? `${entry.text.slice(0, remaining)}\n… (${entry.text.length - remaining} more chars)` : entry.text;
|
|
16580
|
+
entries.push({
|
|
16581
|
+
...entry,
|
|
16582
|
+
text
|
|
16583
|
+
});
|
|
16584
|
+
remaining -= Math.min(entry.text.length, remaining);
|
|
16585
|
+
};
|
|
16586
|
+
for (const turn of turns) for (const block of turn.content) if (block.type === "text") add({
|
|
16587
|
+
kind: "text",
|
|
16588
|
+
label: turn.role === "user" ? "you" : turn.role,
|
|
16589
|
+
text: block.text
|
|
16590
|
+
});
|
|
16591
|
+
else if (block.type === "thinking") add({
|
|
16592
|
+
kind: "thinking",
|
|
16593
|
+
label: "thinking",
|
|
16594
|
+
text: block.text
|
|
16595
|
+
});
|
|
16596
|
+
else if (block.type === "tool_call") add({
|
|
16597
|
+
kind: "tool-call",
|
|
16598
|
+
label: `tool call · ${block.name}`,
|
|
16599
|
+
text: stringifyPreview(block.input)
|
|
16600
|
+
});
|
|
16601
|
+
else if (block.type === "tool_result") add({
|
|
16602
|
+
kind: "tool-result",
|
|
16603
|
+
label: block.isError ? "tool result · error" : "tool result",
|
|
16604
|
+
text: toolResultPreview(block.output),
|
|
16605
|
+
error: block.isError
|
|
16606
|
+
});
|
|
16607
|
+
else if (block.type === "server_tool_use") add({
|
|
16608
|
+
kind: "tool-call",
|
|
16609
|
+
label: `server tool call · ${block.name}`,
|
|
16610
|
+
text: stringifyPreview(block.input)
|
|
16611
|
+
});
|
|
16612
|
+
else if (block.type === "server_tool_result") add({
|
|
16613
|
+
kind: "tool-result",
|
|
16614
|
+
label: `server tool result · ${block.toolName}`,
|
|
16615
|
+
text: stringifyPreview(block.content)
|
|
16616
|
+
});
|
|
16617
|
+
else if (block.type === "compact-summary") add({
|
|
16618
|
+
kind: "summary",
|
|
16619
|
+
label: `compaction summary · ${block.replacesTurnIds.length} turns`,
|
|
16620
|
+
text: block.summary
|
|
16621
|
+
});
|
|
16622
|
+
else if (block.type === "image" || block.type === "audio" || block.type === "video" || block.type === "document") add({
|
|
16623
|
+
kind: "attachment",
|
|
16624
|
+
label: `${block.type} attachment`,
|
|
16625
|
+
text: block.name ?? block.mediaType
|
|
16626
|
+
});
|
|
16627
|
+
else skipped++;
|
|
16628
|
+
if (skipped > 0) entries.push({
|
|
16629
|
+
kind: "summary",
|
|
16630
|
+
label: `… ${skipped} more block${skipped === 1 ? "" : "s"} not shown`,
|
|
16631
|
+
text: ""
|
|
16632
|
+
});
|
|
16633
|
+
return entries;
|
|
16634
|
+
}
|
|
16635
|
+
function HighlightedPreviewText({ text, query, foreground }) {
|
|
16636
|
+
const matchColor = resolveChipColor(useSurfaces().chips, "default");
|
|
16637
|
+
return /* @__PURE__ */ jsx$1(Fragment, { children: highlightSegments(text, query ? searchHighlightRanges(text, query) : []).map((segment, i) => /* @__PURE__ */ jsx$1("span", {
|
|
16638
|
+
fg: segment.highlighted ? matchColor.fg : foreground,
|
|
16639
|
+
bg: segment.highlighted ? matchColor.bg : void 0,
|
|
16640
|
+
children: segment.text
|
|
16641
|
+
}, i)) });
|
|
16642
|
+
}
|
|
16643
|
+
function toolResultPreview(output) {
|
|
16644
|
+
if (typeof output === "string") return output;
|
|
16645
|
+
return output.map((part) => {
|
|
16646
|
+
if (part.type === "text") return part.text;
|
|
16647
|
+
const name = "name" in part && part.name ? `: ${part.name}` : "";
|
|
16648
|
+
return `[${part.type} attachment${name}]`;
|
|
16649
|
+
}).join("\n");
|
|
16650
|
+
}
|
|
16651
|
+
function stringifyPreview(value) {
|
|
16652
|
+
try {
|
|
16653
|
+
return JSON.stringify(value, null, 2);
|
|
16654
|
+
} catch {
|
|
16655
|
+
return String(value);
|
|
16656
|
+
}
|
|
16657
|
+
}
|
|
16658
|
+
function blockSummary(turns) {
|
|
15860
16659
|
const counts = {};
|
|
15861
|
-
for (const block of turn.content) counts[block.type] = (counts[block.type] ?? 0) + 1;
|
|
16660
|
+
for (const turn of turns) for (const block of turn.content) counts[block.type] = (counts[block.type] ?? 0) + 1;
|
|
15862
16661
|
const parts = [];
|
|
15863
16662
|
for (const [type, n] of Object.entries(counts)) parts.push(`${n} ${type}`);
|
|
15864
16663
|
return parts.length === 0 ? "(empty)" : parts.join(" · ");
|
|
@@ -15914,6 +16713,8 @@ function lastLines(text, n) {
|
|
|
15914
16713
|
* and lstats — cheap enough that latency, not cost, picked the number.
|
|
15915
16714
|
*/
|
|
15916
16715
|
const EXTENSION_WATCH_POLL_MS = 3e3;
|
|
16716
|
+
/** LRU bound for cross-session search indexes (full lowercased text per session). */
|
|
16717
|
+
const SESSION_SEARCH_INDEX_CACHE_MAX = 64;
|
|
15917
16718
|
/**
|
|
15918
16719
|
* Surface failures that are normally silenced (teardown / save) when the
|
|
15919
16720
|
* `ZIDANE_DEBUG` env var is set. Logging via `console.error` would otherwise
|
|
@@ -16513,6 +17314,19 @@ function AppShell() {
|
|
|
16513
17314
|
const pickedRef = useRef(picked);
|
|
16514
17315
|
pickedRef.current = picked;
|
|
16515
17316
|
const [sessions, setSessions] = useState([]);
|
|
17317
|
+
const [sessionContentMatches, setSessionContentMatches] = useState(/* @__PURE__ */ new Map());
|
|
17318
|
+
const [sessionContentSearching, setSessionContentSearching] = useState(false);
|
|
17319
|
+
const [sessionContentSearchProgress, setSessionContentSearchProgress] = useState({
|
|
17320
|
+
done: 0,
|
|
17321
|
+
total: 0
|
|
17322
|
+
});
|
|
17323
|
+
const sessionSearchTimerRef = useRef(null);
|
|
17324
|
+
const sessionSearchQueryRef = useRef("");
|
|
17325
|
+
const sessionSearchGenerationRef = useRef(0);
|
|
17326
|
+
const sessionSearchAbortRef = useRef(null);
|
|
17327
|
+
const historySearchWorkerRef = useRef(null);
|
|
17328
|
+
const sessionSearchCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
17329
|
+
const liveSessionSearchIndexRef = useRef(null);
|
|
16516
17330
|
const [currentSession, setCurrentSession] = useState(null);
|
|
16517
17331
|
const [chatReferenceScope, setChatReferenceScope] = useState("project");
|
|
16518
17332
|
const [projectChatCatalog, setProjectChatCatalog] = useState([]);
|
|
@@ -16522,6 +17336,16 @@ function AppShell() {
|
|
|
16522
17336
|
const allChatCatalogRef = useRef(allChatCatalog);
|
|
16523
17337
|
allChatCatalogRef.current = allChatCatalog;
|
|
16524
17338
|
const [events, setEvents] = useState([]);
|
|
17339
|
+
const eventsRef = useRef(events);
|
|
17340
|
+
eventsRef.current = events;
|
|
17341
|
+
/**
|
|
17342
|
+
* True while the user is browsing revealed history (`onLoadEarlierTranscript`)
|
|
17343
|
+
* and no new run has started. Suspends the stream buffer's display cap so a
|
|
17344
|
+
* trailing smooth-mode drain (typewriter backlog outliving `runningRef`)
|
|
17345
|
+
* can't re-trim the page that was just loaded. Cleared wherever a run
|
|
17346
|
+
* starts and on session switch (`stream.reset()`), where the cap resumes.
|
|
17347
|
+
*/
|
|
17348
|
+
const transcriptRevealHoldRef = useRef(false);
|
|
16525
17349
|
const [busy, setBusy] = useState(false);
|
|
16526
17350
|
/**
|
|
16527
17351
|
* The provider+model the run actually advanced to after a `model:fallback`
|
|
@@ -16765,12 +17589,17 @@ function AppShell() {
|
|
|
16765
17589
|
*/
|
|
16766
17590
|
const lastInputTokensRef = useRef(0);
|
|
16767
17591
|
/**
|
|
16768
|
-
* Active turn
|
|
16769
|
-
*
|
|
16770
|
-
* has no highlight. When set, the prompt textarea is unfocused so up/down
|
|
16771
|
-
* navigate the turn list, ↵ opens the details modal, ⎋ exits the mode.
|
|
17592
|
+
* Active turn in conversation-search mode. The prompt remains focused for
|
|
17593
|
+
* filtering while plain arrows navigate and Enter opens turn details.
|
|
16772
17594
|
*/
|
|
16773
17595
|
const [selectedTurnId, setSelectedTurnId] = useState(null);
|
|
17596
|
+
const [transcriptSearchMode, setTranscriptSearchMode] = useState(false);
|
|
17597
|
+
const [transcriptSearchQuery, setTranscriptSearchQuery] = useState("");
|
|
17598
|
+
const [transcriptSearchResults, setTranscriptSearchResults] = useState([]);
|
|
17599
|
+
const [transcriptSearchSearching, setTranscriptSearchSearching] = useState(false);
|
|
17600
|
+
const transcriptSearchGenerationRef = useRef(0);
|
|
17601
|
+
const transcriptSearchAbortRef = useRef(null);
|
|
17602
|
+
const transcriptSearchIndexRef = useRef(null);
|
|
16774
17603
|
/**
|
|
16775
17604
|
* Highlighted entry in the type-ahead queue box. `null` means the user
|
|
16776
17605
|
* is in normal mode (textarea focused, queue purely informational);
|
|
@@ -17042,7 +17871,10 @@ function AppShell() {
|
|
|
17042
17871
|
* writes a title to a torn-down session.
|
|
17043
17872
|
*/
|
|
17044
17873
|
const autoTitleAbortRef = useRef(null);
|
|
17045
|
-
const stream = useStreamBuffer(setEvents, {
|
|
17874
|
+
const stream = useStreamBuffer(setEvents, {
|
|
17875
|
+
getSmooth: useCallback(() => smoothStreamingRef.current, []),
|
|
17876
|
+
maxTranscriptEvents: transcriptRevealHoldRef.current ? null : void 0
|
|
17877
|
+
});
|
|
17046
17878
|
streamRef.current = stream;
|
|
17047
17879
|
const makePicked = useCallback((provider, modelId) => {
|
|
17048
17880
|
const descriptor = providerRegistry[provider.key];
|
|
@@ -17801,6 +18633,113 @@ function AppShell() {
|
|
|
17801
18633
|
const toggleSessionsScope = useCallback(() => {
|
|
17802
18634
|
setSessionsScopeAll(!sessionsShowAll);
|
|
17803
18635
|
}, [sessionsShowAll]);
|
|
18636
|
+
/** Debounced, cached full-history search for the conversations list. */
|
|
18637
|
+
const onSessionSearchQueryChange = useCallback((raw) => {
|
|
18638
|
+
sessionSearchQueryRef.current = raw;
|
|
18639
|
+
const query = raw.trim();
|
|
18640
|
+
const generation = ++sessionSearchGenerationRef.current;
|
|
18641
|
+
sessionSearchAbortRef.current?.abort();
|
|
18642
|
+
const abortController = new AbortController();
|
|
18643
|
+
sessionSearchAbortRef.current = abortController;
|
|
18644
|
+
if (sessionSearchTimerRef.current) clearTimeout(sessionSearchTimerRef.current);
|
|
18645
|
+
if (!query) {
|
|
18646
|
+
setSessionContentMatches(/* @__PURE__ */ new Map());
|
|
18647
|
+
setSessionContentSearching(false);
|
|
18648
|
+
setSessionContentSearchProgress({
|
|
18649
|
+
done: 0,
|
|
18650
|
+
total: 0
|
|
18651
|
+
});
|
|
18652
|
+
return;
|
|
18653
|
+
}
|
|
18654
|
+
setSessionContentMatches(/* @__PURE__ */ new Map());
|
|
18655
|
+
setSessionContentSearching(true);
|
|
18656
|
+
setSessionContentSearchProgress({
|
|
18657
|
+
done: 0,
|
|
18658
|
+
total: sessions.length
|
|
18659
|
+
});
|
|
18660
|
+
sessionSearchTimerRef.current = setTimeout(() => {
|
|
18661
|
+
(async () => {
|
|
18662
|
+
const matches = /* @__PURE__ */ new Map();
|
|
18663
|
+
let cursor = 0;
|
|
18664
|
+
let done = 0;
|
|
18665
|
+
const worker = async () => {
|
|
18666
|
+
while (cursor < sessions.length && generation === sessionSearchGenerationRef.current) {
|
|
18667
|
+
const meta = sessions[cursor++];
|
|
18668
|
+
try {
|
|
18669
|
+
let index;
|
|
18670
|
+
const live = sessionRef.current;
|
|
18671
|
+
const cached = sessionSearchCacheRef.current.get(meta.id);
|
|
18672
|
+
if (live?.id === meta.id) {
|
|
18673
|
+
const cachedLive = liveSessionSearchIndexRef.current;
|
|
18674
|
+
if (cachedLive?.sessionId === meta.id && cachedLive.updatedAt === meta.updatedAt && cachedLive.turnCount === live.turns.length) index = cachedLive.index;
|
|
18675
|
+
else {
|
|
18676
|
+
index = await (historySearchWorkerRef.current ??= new HistorySearchWorkerClient()).build(live.turns, abortController.signal);
|
|
18677
|
+
liveSessionSearchIndexRef.current = {
|
|
18678
|
+
sessionId: meta.id,
|
|
18679
|
+
updatedAt: meta.updatedAt,
|
|
18680
|
+
turnCount: live.turns.length,
|
|
18681
|
+
index
|
|
18682
|
+
};
|
|
18683
|
+
}
|
|
18684
|
+
} else if (cached?.updatedAt === meta.updatedAt) {
|
|
18685
|
+
index = cached.index;
|
|
18686
|
+
sessionSearchCacheRef.current.delete(meta.id);
|
|
18687
|
+
sessionSearchCacheRef.current.set(meta.id, cached);
|
|
18688
|
+
} else {
|
|
18689
|
+
const turns = await store.getTurns(meta.id);
|
|
18690
|
+
index = await (historySearchWorkerRef.current ??= new HistorySearchWorkerClient()).build(turns, abortController.signal);
|
|
18691
|
+
const cache = sessionSearchCacheRef.current;
|
|
18692
|
+
cache.set(meta.id, {
|
|
18693
|
+
updatedAt: meta.updatedAt,
|
|
18694
|
+
index
|
|
18695
|
+
});
|
|
18696
|
+
while (cache.size > SESSION_SEARCH_INDEX_CACHE_MAX) {
|
|
18697
|
+
const oldest = cache.keys().next().value;
|
|
18698
|
+
if (oldest === void 0) break;
|
|
18699
|
+
cache.delete(oldest);
|
|
18700
|
+
}
|
|
18701
|
+
}
|
|
18702
|
+
if (generation !== sessionSearchGenerationRef.current) return;
|
|
18703
|
+
const match = await (historySearchWorkerRef.current ??= new HistorySearchWorkerClient()).history(index, query, 3, abortController.signal);
|
|
18704
|
+
if (match) matches.set(meta.id, match);
|
|
18705
|
+
} catch (error) {
|
|
18706
|
+
if (abortController.signal.aborted) throw abortController.signal.reason instanceof DOMException ? abortController.signal.reason : error;
|
|
18707
|
+
debugLog("session content search skipped a session", error);
|
|
18708
|
+
}
|
|
18709
|
+
done++;
|
|
18710
|
+
if (done === sessions.length || done % 4 === 0) setSessionContentSearchProgress({
|
|
18711
|
+
done,
|
|
18712
|
+
total: sessions.length
|
|
18713
|
+
});
|
|
18714
|
+
}
|
|
18715
|
+
};
|
|
18716
|
+
try {
|
|
18717
|
+
await Promise.all(Array.from({ length: Math.min(6, sessions.length) }, worker));
|
|
18718
|
+
} catch (error) {
|
|
18719
|
+
if (!(error instanceof DOMException && error.name === "AbortError")) throw error;
|
|
18720
|
+
return;
|
|
18721
|
+
}
|
|
18722
|
+
if (generation !== sessionSearchGenerationRef.current) return;
|
|
18723
|
+
setSessionContentMatches(matches);
|
|
18724
|
+
setSessionContentSearchProgress({
|
|
18725
|
+
done: sessions.length,
|
|
18726
|
+
total: sessions.length
|
|
18727
|
+
});
|
|
18728
|
+
setSessionContentSearching(false);
|
|
18729
|
+
})().catch((error) => {
|
|
18730
|
+
debugLog("session history search failed", error);
|
|
18731
|
+
if (generation === sessionSearchGenerationRef.current) setSessionContentSearching(false);
|
|
18732
|
+
});
|
|
18733
|
+
}, 120);
|
|
18734
|
+
}, [sessions, store]);
|
|
18735
|
+
useEffect(() => {
|
|
18736
|
+
if (sessionSearchQueryRef.current.trim()) onSessionSearchQueryChange(sessionSearchQueryRef.current);
|
|
18737
|
+
}, [sessions, onSessionSearchQueryChange]);
|
|
18738
|
+
useEffect(() => () => {
|
|
18739
|
+
if (sessionSearchTimerRef.current) clearTimeout(sessionSearchTimerRef.current);
|
|
18740
|
+
sessionSearchAbortRef.current?.abort();
|
|
18741
|
+
historySearchWorkerRef.current?.terminate();
|
|
18742
|
+
}, []);
|
|
17804
18743
|
const refreshSessions = useCallback(async () => {
|
|
17805
18744
|
const list = await listSessionMeta(store, sessionsShowAll ? void 0 : { projectRoot: projectDir });
|
|
17806
18745
|
setSessions(list);
|
|
@@ -17845,6 +18784,7 @@ function AppShell() {
|
|
|
17845
18784
|
debugLog("teardown: auto-title abort failed", err);
|
|
17846
18785
|
}
|
|
17847
18786
|
autoTitleAbortRef.current = null;
|
|
18787
|
+
transcriptRevealHoldRef.current = false;
|
|
17848
18788
|
stream.reset();
|
|
17849
18789
|
await agentRef.current?.destroy().catch((err) => debugLog("agent.destroy failed", err));
|
|
17850
18790
|
agentRef.current = null;
|
|
@@ -17908,6 +18848,7 @@ function AppShell() {
|
|
|
17908
18848
|
await session.appendTurns([turn]);
|
|
17909
18849
|
setEvents(capTranscriptEvents(eventsFromTurns(session.turns, session.runs)));
|
|
17910
18850
|
runningRef.current = true;
|
|
18851
|
+
transcriptRevealHoldRef.current = false;
|
|
17911
18852
|
setBusy(true);
|
|
17912
18853
|
setActiveFallback(null);
|
|
17913
18854
|
try {
|
|
@@ -18309,6 +19250,25 @@ function AppShell() {
|
|
|
18309
19250
|
const onPopupOpenChange = useCallback((open) => {
|
|
18310
19251
|
popupOpenRef.current = open;
|
|
18311
19252
|
}, []);
|
|
19253
|
+
/**
|
|
19254
|
+
* Reveal one persisted-history page when Transcript reaches its top marker.
|
|
19255
|
+
* No full-history array is retained: materialize from Session on demand,
|
|
19256
|
+
* slice, then let the temporary array go. A running stream deliberately
|
|
19257
|
+
* declines — its first next append would re-cap anyway, and changing the
|
|
19258
|
+
* head while buckets are draining makes scroll preservation misleading.
|
|
19259
|
+
*/
|
|
19260
|
+
const onLoadEarlierTranscript = useCallback(() => {
|
|
19261
|
+
if (runningRef.current) return false;
|
|
19262
|
+
const session = sessionRef.current;
|
|
19263
|
+
const displayed = eventsRef.current;
|
|
19264
|
+
if (!session || hiddenEarlierTranscriptCount(displayed) <= 0) return false;
|
|
19265
|
+
const next = revealEarlierTranscriptEvents(eventsFromTurns(session.turns, session.runs), displayed);
|
|
19266
|
+
if (next === displayed) return false;
|
|
19267
|
+
transcriptRevealHoldRef.current = true;
|
|
19268
|
+
eventsRef.current = next;
|
|
19269
|
+
setEvents(next);
|
|
19270
|
+
return true;
|
|
19271
|
+
}, []);
|
|
18312
19272
|
const onAbort = useCallback(() => {
|
|
18313
19273
|
if (popupOpenRef.current) return;
|
|
18314
19274
|
commandAbortRef.current?.abort();
|
|
@@ -19248,6 +20208,7 @@ function AppShell() {
|
|
|
19248
20208
|
if (!agent || !session || !picked) return;
|
|
19249
20209
|
const wasRunning = runningRef.current;
|
|
19250
20210
|
if (!wasRunning) runningRef.current = true;
|
|
20211
|
+
transcriptRevealHoldRef.current = false;
|
|
19251
20212
|
suppressWakeRef.current = false;
|
|
19252
20213
|
(async () => {
|
|
19253
20214
|
let effectivePrompt = prompt;
|
|
@@ -19398,6 +20359,7 @@ function AppShell() {
|
|
|
19398
20359
|
if (suppressWakeRef.current) return;
|
|
19399
20360
|
if (!agent.hasPendingTaskNotifications) return;
|
|
19400
20361
|
runningRef.current = true;
|
|
20362
|
+
transcriptRevealHoldRef.current = false;
|
|
19401
20363
|
(async () => {
|
|
19402
20364
|
setBusy(true);
|
|
19403
20365
|
try {
|
|
@@ -19606,25 +20568,143 @@ function AppShell() {
|
|
|
19606
20568
|
}, [modal, config.paths.userDir]);
|
|
19607
20569
|
const hasMultipleAgents = useMemo(() => Object.keys(liveAgentRegistry).length > 1, [liveAgentRegistry]);
|
|
19608
20570
|
const turnIds = useMemo(() => selectableTurnIds(events, settings), [events, settings]);
|
|
20571
|
+
useEffect(() => {
|
|
20572
|
+
const query = transcriptSearchQuery.trim();
|
|
20573
|
+
const generation = ++transcriptSearchGenerationRef.current;
|
|
20574
|
+
transcriptSearchAbortRef.current?.abort();
|
|
20575
|
+
if (!transcriptSearchMode || !query) {
|
|
20576
|
+
setTranscriptSearchResults([]);
|
|
20577
|
+
setTranscriptSearchSearching(false);
|
|
20578
|
+
return;
|
|
20579
|
+
}
|
|
20580
|
+
const session = sessionRef.current;
|
|
20581
|
+
if (!session) {
|
|
20582
|
+
setTranscriptSearchResults([]);
|
|
20583
|
+
setTranscriptSearchSearching(false);
|
|
20584
|
+
return;
|
|
20585
|
+
}
|
|
20586
|
+
const turns = session.turns;
|
|
20587
|
+
const abortController = new AbortController();
|
|
20588
|
+
transcriptSearchAbortRef.current = abortController;
|
|
20589
|
+
setTranscriptSearchSearching(true);
|
|
20590
|
+
(async () => {
|
|
20591
|
+
const cached = transcriptSearchIndexRef.current;
|
|
20592
|
+
const index = cached?.sessionId === session.id && cached.events === events ? cached.index : await (historySearchWorkerRef.current ??= new HistorySearchWorkerClient()).build(turns, abortController.signal);
|
|
20593
|
+
transcriptSearchIndexRef.current = {
|
|
20594
|
+
sessionId: session.id,
|
|
20595
|
+
events,
|
|
20596
|
+
index
|
|
20597
|
+
};
|
|
20598
|
+
const matches = await (historySearchWorkerRef.current ??= new HistorySearchWorkerClient()).search(index, query, turns.length, abortController.signal);
|
|
20599
|
+
if (generation !== transcriptSearchGenerationRef.current) return;
|
|
20600
|
+
const order = new Map(turns.map((turn, i) => [turn.id, i]));
|
|
20601
|
+
matches.sort((a, b) => (order.get(a.turn.id) ?? 0) - (order.get(b.turn.id) ?? 0));
|
|
20602
|
+
setTranscriptSearchResults(matches);
|
|
20603
|
+
setTranscriptSearchSearching(false);
|
|
20604
|
+
})().catch((error) => {
|
|
20605
|
+
if (!(error instanceof DOMException && error.name === "AbortError")) {
|
|
20606
|
+
debugLog("conversation search failed", error);
|
|
20607
|
+
if (generation === transcriptSearchGenerationRef.current) setTranscriptSearchSearching(false);
|
|
20608
|
+
}
|
|
20609
|
+
});
|
|
20610
|
+
return () => abortController.abort();
|
|
20611
|
+
}, [
|
|
20612
|
+
transcriptSearchMode,
|
|
20613
|
+
transcriptSearchQuery,
|
|
20614
|
+
events
|
|
20615
|
+
]);
|
|
20616
|
+
const transcriptSearchOwnerIds = useMemo(() => {
|
|
20617
|
+
const owners = /* @__PURE__ */ new Map();
|
|
20618
|
+
if (!transcriptSearchQuery.trim() || transcriptSearchResults.length === 0) return owners;
|
|
20619
|
+
const session = sessionRef.current;
|
|
20620
|
+
if (!session) return owners;
|
|
20621
|
+
const callOwner = /* @__PURE__ */ new Map();
|
|
20622
|
+
for (const turn of session.turns) for (const block of turn.content) if (block.type === "tool_call") callOwner.set(block.id, turn.id);
|
|
20623
|
+
for (const result of transcriptSearchResults) {
|
|
20624
|
+
const resultOwners = [];
|
|
20625
|
+
for (const block of result.turn.content) {
|
|
20626
|
+
if (block.type !== "tool_result") continue;
|
|
20627
|
+
const owner = callOwner.get(block.callId);
|
|
20628
|
+
if (owner && owner !== result.turn.id && !resultOwners.includes(owner)) resultOwners.push(owner);
|
|
20629
|
+
}
|
|
20630
|
+
if (resultOwners.length > 0) owners.set(result.turn.id, resultOwners);
|
|
20631
|
+
}
|
|
20632
|
+
return owners;
|
|
20633
|
+
}, [
|
|
20634
|
+
transcriptSearchQuery,
|
|
20635
|
+
transcriptSearchResults,
|
|
20636
|
+
events
|
|
20637
|
+
]);
|
|
20638
|
+
const transcriptSearchEvents = useMemo(() => {
|
|
20639
|
+
if (!transcriptSearchMode || !transcriptSearchQuery.trim()) return [];
|
|
20640
|
+
const session = sessionRef.current;
|
|
20641
|
+
if (!session) return [];
|
|
20642
|
+
const shown = transcriptSearchResults.slice(-200);
|
|
20643
|
+
const contextIds = new Set(shown.map((result) => result.turn.id));
|
|
20644
|
+
for (const result of shown) for (const owner of transcriptSearchOwnerIds.get(result.turn.id) ?? []) contextIds.add(owner);
|
|
20645
|
+
const searchEvents = eventsFromTurns(session.turns.filter((turn) => contextIds.has(turn.id)), session.runs);
|
|
20646
|
+
const hidden = transcriptSearchResults.length - shown.length;
|
|
20647
|
+
if (hidden > 0) searchEvents.unshift({
|
|
20648
|
+
kind: "info",
|
|
20649
|
+
text: `… ${hidden} earlier matched message${hidden === 1 ? "" : "s"} not shown — refine the query to narrow results.`
|
|
20650
|
+
});
|
|
20651
|
+
return searchEvents;
|
|
20652
|
+
}, [
|
|
20653
|
+
transcriptSearchMode,
|
|
20654
|
+
transcriptSearchQuery,
|
|
20655
|
+
transcriptSearchResults,
|
|
20656
|
+
transcriptSearchOwnerIds,
|
|
20657
|
+
events
|
|
20658
|
+
]);
|
|
20659
|
+
const transcriptSearchTurnIds = useMemo(() => {
|
|
20660
|
+
if (!transcriptSearchQuery.trim()) return turnIds;
|
|
20661
|
+
return selectableTurnIds(transcriptSearchEvents, settings, (event) => isVisibleDuringSearch(event, settings, transcriptSearchQuery));
|
|
20662
|
+
}, [
|
|
20663
|
+
transcriptSearchQuery,
|
|
20664
|
+
transcriptSearchEvents,
|
|
20665
|
+
turnIds,
|
|
20666
|
+
settings
|
|
20667
|
+
]);
|
|
19609
20668
|
/** Drop the selection if its turn disappeared (session swap, history reset). */
|
|
19610
20669
|
useEffect(() => {
|
|
20670
|
+
if (transcriptSearchMode) return;
|
|
19611
20671
|
if (selectedTurnId && !turnIds.includes(selectedTurnId)) setSelectedTurnId(null);
|
|
19612
|
-
}, [
|
|
19613
|
-
|
|
19614
|
-
|
|
19615
|
-
|
|
19616
|
-
|
|
20672
|
+
}, [
|
|
20673
|
+
selectedTurnId,
|
|
20674
|
+
turnIds,
|
|
20675
|
+
transcriptSearchMode
|
|
20676
|
+
]);
|
|
20677
|
+
useEffect(() => {
|
|
20678
|
+
if (!transcriptSearchMode) return;
|
|
20679
|
+
if (transcriptSearchTurnIds.length === 0) {
|
|
20680
|
+
setSelectedTurnId(null);
|
|
20681
|
+
return;
|
|
20682
|
+
}
|
|
20683
|
+
if (!selectedTurnId || !transcriptSearchTurnIds.includes(selectedTurnId)) setSelectedTurnId(transcriptSearchTurnIds[transcriptSearchTurnIds.length - 1] ?? null);
|
|
20684
|
+
}, [
|
|
20685
|
+
transcriptSearchMode,
|
|
20686
|
+
transcriptSearchTurnIds,
|
|
20687
|
+
selectedTurnId
|
|
20688
|
+
]);
|
|
20689
|
+
const onTranscriptSearchModeChange = useCallback((enabled) => {
|
|
20690
|
+
setTranscriptSearchMode(enabled);
|
|
20691
|
+
setTranscriptSearchQuery("");
|
|
20692
|
+
if (enabled) (historySearchWorkerRef.current ??= new HistorySearchWorkerClient()).prewarm();
|
|
20693
|
+
if (!enabled) setSelectedTurnId(null);
|
|
20694
|
+
else if (turnIds.length > 0) setSelectedTurnId(turnIds[turnIds.length - 1] ?? null);
|
|
19617
20695
|
}, [turnIds]);
|
|
19618
|
-
const cycleSelectedTurn = useCallback((direction) => {
|
|
20696
|
+
const cycleSelectedTurn = useCallback((direction, candidates = turnIds) => {
|
|
19619
20697
|
setSelectedTurnId((prev) => {
|
|
19620
|
-
if (!prev ||
|
|
19621
|
-
const idx =
|
|
19622
|
-
if (idx === -1) return
|
|
19623
|
-
const len =
|
|
19624
|
-
|
|
19625
|
-
return turnIds[nextIdx];
|
|
20698
|
+
if (!prev || candidates.length === 0) return prev;
|
|
20699
|
+
const idx = candidates.indexOf(prev);
|
|
20700
|
+
if (idx === -1) return candidates[candidates.length - 1];
|
|
20701
|
+
const len = candidates.length;
|
|
20702
|
+
return candidates[((idx + direction) % len + len) % len];
|
|
19626
20703
|
});
|
|
19627
20704
|
}, [turnIds]);
|
|
20705
|
+
const onTranscriptSearchNavigate = useCallback((direction) => {
|
|
20706
|
+
cycleSelectedTurn(direction, transcriptSearchTurnIds);
|
|
20707
|
+
}, [cycleSelectedTurn, transcriptSearchTurnIds]);
|
|
19628
20708
|
const onForkTurn = useCallback(async (turnId) => {
|
|
19629
20709
|
const source = sessionRef.current;
|
|
19630
20710
|
if (!source || !picked) return;
|
|
@@ -19664,7 +20744,10 @@ function AppShell() {
|
|
|
19664
20744
|
debugLog("delete: save failed", err);
|
|
19665
20745
|
return;
|
|
19666
20746
|
}
|
|
19667
|
-
|
|
20747
|
+
const nextEvents = capTranscriptEvents(eventsFromTurns(session.turns, session.runs));
|
|
20748
|
+
const nextIds = selectableTurnIds(nextEvents, settings);
|
|
20749
|
+
setEvents(nextEvents);
|
|
20750
|
+
setSelectedTurnId((prev) => prev ? selectionAfterTurnRemoval(turnIds, prev, nextIds) : prev);
|
|
19668
20751
|
const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
|
|
19669
20752
|
setLastInputTokens(replayedTokens);
|
|
19670
20753
|
lastInputTokensRef.current = replayedTokens;
|
|
@@ -19675,11 +20758,7 @@ function AppShell() {
|
|
|
19675
20758
|
userMessageCount: session.turns.reduce((n, t) => t.role === "user" ? n + 1 : n, 0),
|
|
19676
20759
|
updatedAt: Date.now()
|
|
19677
20760
|
} : prev);
|
|
19678
|
-
|
|
19679
|
-
if (!prev) return prev;
|
|
19680
|
-
return nextTurns.some((t) => t.id === prev) ? prev : null;
|
|
19681
|
-
});
|
|
19682
|
-
}, []);
|
|
20761
|
+
}, [settings, turnIds]);
|
|
19683
20762
|
const onEditTurn = useCallback(async (turnId, newText) => {
|
|
19684
20763
|
const session = sessionRef.current;
|
|
19685
20764
|
if (!session) return;
|
|
@@ -19701,7 +20780,10 @@ function AppShell() {
|
|
|
19701
20780
|
debugLog("edit: save failed", err);
|
|
19702
20781
|
return;
|
|
19703
20782
|
}
|
|
19704
|
-
|
|
20783
|
+
const rebuilt = eventsFromTurns(session.turns, session.runs);
|
|
20784
|
+
const patched = replaceTranscriptTurnEvents(eventsRef.current, rebuilt, turnId);
|
|
20785
|
+
setEvents(patched ?? capTranscriptEvents(rebuilt));
|
|
20786
|
+
setSelectedTurnId(turnId);
|
|
19705
20787
|
const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
|
|
19706
20788
|
setLastInputTokens(replayedTokens);
|
|
19707
20789
|
lastInputTokensRef.current = replayedTokens;
|
|
@@ -19999,11 +21081,23 @@ function AppShell() {
|
|
|
19999
21081
|
if (!session) return;
|
|
20000
21082
|
const turn = session.turns.find((t) => t.id === id);
|
|
20001
21083
|
if (!turn) return;
|
|
20002
|
-
const
|
|
21084
|
+
const candidates = transcriptSearchMode ? transcriptSearchTurnIds : turnIds;
|
|
21085
|
+
const index = candidates.indexOf(id) + 1;
|
|
21086
|
+
const callIds = new Set(turn.content.filter((block) => block.type === "tool_call").map((block) => block.id));
|
|
21087
|
+
const toolResultTurns = callIds.size === 0 ? [] : session.turns.flatMap((candidate) => {
|
|
21088
|
+
if (candidate.id === turn.id) return [];
|
|
21089
|
+
const content = candidate.content.filter((block) => block.type === "tool_result" && callIds.has(block.callId));
|
|
21090
|
+
return content.length > 0 ? [{
|
|
21091
|
+
...candidate,
|
|
21092
|
+
content
|
|
21093
|
+
}] : [];
|
|
21094
|
+
});
|
|
20003
21095
|
modal.open(/* @__PURE__ */ jsx$1(TurnDetailsModal, {
|
|
20004
21096
|
turn,
|
|
21097
|
+
toolResultTurns,
|
|
21098
|
+
searchQuery: transcriptSearchMode ? transcriptSearchQuery : void 0,
|
|
20005
21099
|
index,
|
|
20006
|
-
total:
|
|
21100
|
+
total: candidates.length,
|
|
20007
21101
|
actions: {
|
|
20008
21102
|
onFork: onForkTurn,
|
|
20009
21103
|
onDelete: onDeleteTurn,
|
|
@@ -20015,6 +21109,9 @@ function AppShell() {
|
|
|
20015
21109
|
modal,
|
|
20016
21110
|
selectedTurnId,
|
|
20017
21111
|
turnIds,
|
|
21112
|
+
transcriptSearchMode,
|
|
21113
|
+
transcriptSearchQuery,
|
|
21114
|
+
transcriptSearchTurnIds,
|
|
20018
21115
|
onForkTurn,
|
|
20019
21116
|
onDeleteTurn,
|
|
20020
21117
|
onEditTurn,
|
|
@@ -20022,23 +21119,8 @@ function AppShell() {
|
|
|
20022
21119
|
]);
|
|
20023
21120
|
useKeyboard((key) => {
|
|
20024
21121
|
if (modal.isOpen) return;
|
|
20025
|
-
if (
|
|
20026
|
-
if (key.name === "
|
|
20027
|
-
cycleSelectedTurn(-1);
|
|
20028
|
-
return;
|
|
20029
|
-
}
|
|
20030
|
-
if (key.name === "down") {
|
|
20031
|
-
cycleSelectedTurn(1);
|
|
20032
|
-
return;
|
|
20033
|
-
}
|
|
20034
|
-
if (key.name === "return") {
|
|
20035
|
-
openSelectedTurn();
|
|
20036
|
-
return;
|
|
20037
|
-
}
|
|
20038
|
-
if (key.name === "escape") {
|
|
20039
|
-
setSelectedTurnId(null);
|
|
20040
|
-
return;
|
|
20041
|
-
}
|
|
21122
|
+
if (transcriptSearchMode && screen === "chat") {
|
|
21123
|
+
if (key.name === "escape") onTranscriptSearchModeChange(false);
|
|
20042
21124
|
return;
|
|
20043
21125
|
}
|
|
20044
21126
|
if (queueSelectionIndex != null && screen === "chat") {
|
|
@@ -20203,8 +21285,8 @@ function AppShell() {
|
|
|
20203
21285
|
}));
|
|
20204
21286
|
return;
|
|
20205
21287
|
}
|
|
20206
|
-
if (matchesBinding(key, keybindings.enterSelectTurnMode) && screen === "chat" && !busy && !pendingApproval && !pendingInteraction) {
|
|
20207
|
-
|
|
21288
|
+
if (matchesBinding(key, keybindings.enterSelectTurnMode) && screen === "chat" && turnIds.length > 0 && !busy && !pendingApproval && !pendingInteraction) {
|
|
21289
|
+
onTranscriptSearchModeChange(true);
|
|
20208
21290
|
return;
|
|
20209
21291
|
}
|
|
20210
21292
|
if (matchesBinding(key, keybindings.cycleAgent) && screen === "chat" && hasMultipleAgents && !busy && !pendingApproval && !pendingInteraction) {
|
|
@@ -20495,6 +21577,10 @@ function AppShell() {
|
|
|
20495
21577
|
onPick: onSwitchSession,
|
|
20496
21578
|
onCreate: onCreateSession,
|
|
20497
21579
|
onFocusChange: setFocusedSessionId,
|
|
21580
|
+
contentMatches: sessionContentMatches,
|
|
21581
|
+
contentSearching: sessionContentSearching,
|
|
21582
|
+
contentSearchProgress: sessionContentSearchProgress,
|
|
21583
|
+
onSearchQueryChange: onSessionSearchQueryChange,
|
|
20498
21584
|
showAllProjects: sessionsShowAll,
|
|
20499
21585
|
onToggleScope: toggleSessionsScope,
|
|
20500
21586
|
currentProjectRoot: projectDir
|
|
@@ -20526,6 +21612,15 @@ function AppShell() {
|
|
|
20526
21612
|
enterSelectsCompletion: (providerId) => providerId === "chats",
|
|
20527
21613
|
onPopupOpenChange,
|
|
20528
21614
|
selectedTurnId,
|
|
21615
|
+
transcriptSearchMode,
|
|
21616
|
+
transcriptSearchQuery,
|
|
21617
|
+
transcriptSearchEvents,
|
|
21618
|
+
transcriptSearchSearching,
|
|
21619
|
+
onTranscriptSearchModeChange,
|
|
21620
|
+
onTranscriptSearchQueryChange: setTranscriptSearchQuery,
|
|
21621
|
+
onTranscriptSearchNavigate,
|
|
21622
|
+
onTranscriptSearchOpen: openSelectedTurn,
|
|
21623
|
+
onLoadEarlierTranscript,
|
|
20529
21624
|
promptTriggerHints,
|
|
20530
21625
|
attachImageBinding: keybindings.attachClipboardImage,
|
|
20531
21626
|
editorBinding: settings.externalEditor ? keybindings.openExternalEditor : void 0,
|
|
@@ -20537,7 +21632,7 @@ function AppShell() {
|
|
|
20537
21632
|
hints,
|
|
20538
21633
|
context: contextUsage,
|
|
20539
21634
|
cost: footerCost,
|
|
20540
|
-
status: pendingInteraction?.kind === "question" ? "asking" : compacting ? "compacting" : busy ? "busy" : null,
|
|
21635
|
+
status: transcriptSearchMode && transcriptSearchSearching ? "searching" : pendingInteraction?.kind === "question" ? "asking" : compacting ? "compacting" : busy ? "busy" : null,
|
|
20541
21636
|
compactionTokens,
|
|
20542
21637
|
compactionStartedAt
|
|
20543
21638
|
}),
|