zidane 5.6.14 → 5.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +3 -1
  2. package/dist/{agent-ClkpElCZ.d.ts → agent-BNS2nx_T.d.ts} +535 -15
  3. package/dist/agent-BNS2nx_T.d.ts.map +1 -0
  4. package/dist/chat/pure.d.ts +4 -0
  5. package/dist/chat/pure.js +3 -0
  6. package/dist/chat.d.ts +31 -661
  7. package/dist/chat.d.ts.map +1 -1
  8. package/dist/chat.js +5 -3
  9. package/dist/chat.js.map +1 -1
  10. package/dist/contexts/docker.d.ts +1 -1
  11. package/dist/contexts/docker.d.ts.map +1 -1
  12. package/dist/contexts/docker.js.map +1 -1
  13. package/dist/{contexts-BOtMvzli.js → contexts-BD2U_xpi.js} +2 -2
  14. package/dist/{contexts-BOtMvzli.js.map → contexts-BD2U_xpi.js.map} +1 -1
  15. package/dist/contexts.d.ts +3 -3
  16. package/dist/contexts.js +1 -1
  17. package/dist/edit-utils-DnfNoj16.js +574 -0
  18. package/dist/edit-utils-DnfNoj16.js.map +1 -0
  19. package/dist/{errors-DdZXnyXE.js → errors-CoQnKRf1.js} +32 -2
  20. package/dist/{errors-DdZXnyXE.js.map → errors-CoQnKRf1.js.map} +1 -1
  21. package/dist/fetch-url-CPxfiXDa.js +518 -0
  22. package/dist/fetch-url-CPxfiXDa.js.map +1 -0
  23. package/dist/image-sniff-B7uFSNO1.js +90 -0
  24. package/dist/image-sniff-B7uFSNO1.js.map +1 -0
  25. package/dist/{index-CbS75MD3.d.ts → index-CZOwAJIX.d.ts} +2 -2
  26. package/dist/index-CZOwAJIX.d.ts.map +1 -0
  27. package/dist/{index-CTDMMdIy.d.ts → index-Ck_AWt8P.d.ts} +3 -4
  28. package/dist/index-Ck_AWt8P.d.ts.map +1 -0
  29. package/dist/{index-v3Tzobqr.d.ts → index-KiS7w0dC.d.ts} +3 -3
  30. package/dist/index-KiS7w0dC.d.ts.map +1 -0
  31. package/dist/index.d.ts +6 -6
  32. package/dist/index.js +13 -12
  33. package/dist/index.js.map +1 -1
  34. package/dist/{interpolate-DM1UcKeQ.js → interpolate-TySiqKzc.js} +23 -23
  35. package/dist/{interpolate-DM1UcKeQ.js.map → interpolate-TySiqKzc.js.map} +1 -1
  36. package/dist/{login-7tHcckmX.js → login-BDeqENSe.js} +7 -58
  37. package/dist/login-BDeqENSe.js.map +1 -0
  38. package/dist/{mcp-DGeB7-3D.js → mcp-Kqzz-Rs_.js} +8 -6
  39. package/dist/mcp-Kqzz-Rs_.js.map +1 -0
  40. package/dist/mcp.d.ts +2 -2
  41. package/dist/mcp.js +1 -1
  42. package/dist/{messages-Dym8S_YH.js → messages-CvRQTdbR.js} +118 -39
  43. package/dist/messages-CvRQTdbR.js.map +1 -0
  44. package/dist/{presets-w9Px_aAm.js → presets-JuOnSI-i.js} +2 -2
  45. package/dist/{presets-w9Px_aAm.js.map → presets-JuOnSI-i.js.map} +1 -1
  46. package/dist/presets.d.ts +3 -3
  47. package/dist/presets.js +1 -1
  48. package/dist/{providers-beXyD9W9.js → providers-h4HJPbbv.js} +485 -31
  49. package/dist/providers-h4HJPbbv.js.map +1 -0
  50. package/dist/providers.d.ts +2 -2
  51. package/dist/providers.js +3 -3
  52. package/dist/restate.d.ts +1 -1
  53. package/dist/restate.d.ts.map +1 -1
  54. package/dist/restate.js.map +1 -1
  55. package/dist/session/sqlite.d.ts +1 -1
  56. package/dist/session/sqlite.d.ts.map +1 -1
  57. package/dist/session/sqlite.js +1 -1
  58. package/dist/session/sqlite.js.map +1 -1
  59. package/dist/{session-BRIsmBSY.js → session-BzLou2_-.js} +2 -2
  60. package/dist/{session-BRIsmBSY.js.map → session-BzLou2_-.js.map} +1 -1
  61. package/dist/session.d.ts +2 -2
  62. package/dist/session.js +2 -2
  63. package/dist/skills.d.ts +3 -3
  64. package/dist/skills.js +1 -1
  65. package/dist/skills.js.map +1 -1
  66. package/dist/{stats-Lc3zL3RM.js → stats-DAKBEKjc.js} +12 -2
  67. package/dist/stats-DAKBEKjc.js.map +1 -0
  68. package/dist/{stdio-loader-EVAF5KlU.js → stdio-loader-Ce68wUmM.js} +4 -4
  69. package/dist/stdio-loader-Ce68wUmM.js.map +1 -0
  70. package/dist/tool-formatters-CU-j3a3e.d.ts +1471 -0
  71. package/dist/tool-formatters-CU-j3a3e.d.ts.map +1 -0
  72. package/dist/tools/fetch-url.d.ts +70 -0
  73. package/dist/tools/fetch-url.d.ts.map +1 -0
  74. package/dist/tools/fetch-url.js +2 -0
  75. package/dist/tools/web-search.d.ts +7 -0
  76. package/dist/tools/web-search.d.ts.map +1 -0
  77. package/dist/tools/web-search.js +190 -0
  78. package/dist/tools/web-search.js.map +1 -0
  79. package/dist/{tools-DhrLrOEr.js → tools-BGtJK0vo.js} +1368 -421
  80. package/dist/tools-BGtJK0vo.js.map +1 -0
  81. package/dist/tools.d.ts +3 -3
  82. package/dist/tools.js +1 -1
  83. package/dist/{turn-operations-UAkOjO-u.js → transcript-anchors-BTSZAPVc.js} +147 -2713
  84. package/dist/transcript-anchors-BTSZAPVc.js.map +1 -0
  85. package/dist/{transcript-anchors-D0TR6djV.d.ts → transcript-anchors-DX90kXc4.d.ts} +13 -1299
  86. package/dist/transcript-anchors-DX90kXc4.d.ts.map +1 -0
  87. package/dist/tui.d.ts +58 -28
  88. package/dist/tui.d.ts.map +1 -1
  89. package/dist/tui.js +1349 -422
  90. package/dist/tui.js.map +1 -1
  91. package/dist/turn-operations-CCHfR9eC.js +1938 -0
  92. package/dist/turn-operations-CCHfR9eC.js.map +1 -0
  93. package/dist/turn-operations-DDIl4YVk.d.ts +658 -0
  94. package/dist/turn-operations-DDIl4YVk.d.ts.map +1 -0
  95. package/dist/{types-oKPBdCmL.js → types-BPw_i5vb.js} +1 -1
  96. package/dist/types-BPw_i5vb.js.map +1 -0
  97. package/dist/{types-KukEp-mi.d.ts → types-CEAMIUXw.d.ts} +1 -1
  98. package/dist/types-CEAMIUXw.d.ts.map +1 -0
  99. package/dist/types.d.ts +4 -4
  100. package/dist/types.js +3 -3
  101. package/docs/CHAT.md +53 -6
  102. package/docs/SKILL.md +3 -0
  103. package/docs/TUI.md +7 -0
  104. package/package.json +18 -2
  105. package/dist/agent-ClkpElCZ.d.ts.map +0 -1
  106. package/dist/index-CTDMMdIy.d.ts.map +0 -1
  107. package/dist/index-CbS75MD3.d.ts.map +0 -1
  108. package/dist/index-v3Tzobqr.d.ts.map +0 -1
  109. package/dist/login-7tHcckmX.js.map +0 -1
  110. package/dist/mcp-DGeB7-3D.js.map +0 -1
  111. package/dist/messages-Dym8S_YH.js.map +0 -1
  112. package/dist/providers-beXyD9W9.js.map +0 -1
  113. package/dist/stats-Lc3zL3RM.js.map +0 -1
  114. package/dist/stdio-loader-EVAF5KlU.js.map +0 -1
  115. package/dist/tools-DhrLrOEr.js.map +0 -1
  116. package/dist/transcript-anchors-D0TR6djV.d.ts.map +0 -1
  117. package/dist/turn-operations-UAkOjO-u.js.map +0 -1
  118. package/dist/types-KukEp-mi.d.ts.map +0 -1
  119. package/dist/types-oKPBdCmL.js.map +0 -1
package/dist/tui.js CHANGED
@@ -1,24 +1,27 @@
1
- import { A as getSafelist, Ai as useActiveTodos, An as turnSelectionOwnership, At as clipHintsToWidth, B as oauthUsesManualCodePaste, Bt as SETTINGS_CATEGORIES, Cr as tryOpenBrowser, D as useSafeModeQueue, Dr as useUpdateCheck, Dt as useInteractionsActions, E as useSafeModeActions, En as sumRunCosts, Er as buildUpdateHint, F as suggestSafelistEntry, Ft as buildHints, G as indexOfEntry, Gn as mergeApprovalAndBodyOutcomes, Gt as useSettings, H as supportsOAuth, Hi as buildPlanSystem, Ht as SETTINGS_TOGGLES, Ir as AUTO_COMPACT_MIN_GROWTH_FRACTION, J as discoverProjectMcps, Jn as rewriteMultiEditHeader, Jt as resolveChipColor, K as buildMcpServers, Kn as parseEditOutcomesFromResult, L as splitPromptSegments, Ln as extractEditPayload, Lr as shouldAutoCompact, Lt as listProjectFiles, Mt as truncateTrailing, Nn as buildContextualDiff, On as toolCallPreview, Ot as useInteractionsQueue, Pn as buildUnifiedDiff, Pt as generateSessionTitle, Q as loadMcpToolsCache, Qn as KEYBINDING_DEFS, Qr as getContextWindow, R as formatPathForCwd, Rn as filetypeFromPath, Rr as detectAuth, Rt as useEnabledToggleSet, Sn as marginTopFor, Sr as buildLinearRamp, St as createInteractionTools, T as SafeModeProvider, Tn as stripSpawnTokensLine, Tr as bootTick, Tt as pendingInteractionsFromTurns, U as buildModelCatalog, Ui as envSection, Un as buildEditOutcomesAnnotation, Ut as SettingsProvider, V as runOAuthLogin, Vi as buildBuildSystem, Vn as summarizeEditPayload, Vt as SETTINGS_CHOICES, W as filterModelCatalog, Wr as setProviderCredential, Wt as clampFps, Xn as summarizeOutcomes, Yn as stripEditOutcomesAnnotation, Yt as resolveTheme, _ as turnContextSize, _n as isTurnHighlighted, _t as splitMarkdownCodeBlocks, a as computeTurnAnchors, ai as discoverAgentsMd, an as useDiscovery, ar as matchesBinding, b as defaultSkillScanPaths, bi as TODO_STATUS_GLYPHS, bn as listSessionMeta, br as useCompletion, c as formatToolCall, cn as useConfig, ct as parentServerName, d as useSelectStyle, dr as createSkillsCompletionProvider, ei as modelSupportsReasoning, er as KEYBINDING_KEY_COL_WIDTH, et as refreshMcpToolsCatalog, f as useSurfaces, fn as EDIT_TOOL_NAMES, fr as uniqueSkillNamesFromReferences, ft as McpAuthProvider, g as finalizeStreamingMarkdownForOwner, gn as isEditErrorResult, h as finalizeStreamingMarkdown, hn as eventsFromTurns, ht as getMcpAuthStatus, i as turnAsText, ii as piIdOf, in as DiscoveryProvider, ir as keybindingsPath, it as useMcpToolToggleMap, j as isOnSafelist, jn as updateToolEventOutcomes, jt as hintsLength, k as addToSafelist, kn as toolResultText, kt as EMPTY_HINTS, l as ThemeProvider, ln as resolveConfig, lt as createFileMcpCredentialStore, m as useTheme, mi as accentColor, mn as deriveSessionTitle, mr as createFilesCompletionProvider, mt as useMcpAuthState, n as deleteTurnSafely, nr as formatBindingForDisplay, nt as subscribeMcpToolsCache, o as TOOL_DISPLAY, on as useDiscoveryOptional, ot as buildVisibleMcpRows, pt as useMcpAuthDispatch, qn as resolveApprovalForPayload, r as truncateTurnsAt, rn as createDiscoverySlot, rr as groupBindings, s as displayNameFor, si as findGitRoot, sn as ConfigProvider, st as indexOfServerRow, tr as ensureKeybindingsFile, u as useColors, v as useStreamBuffer, vn as isVisible, w as writeSessionExport, wn as selectableTurnIds, wt as makeRequestInteraction, x as discoverProjectSkills, xr as blendHsl, xt as buildResumedToolResultsTurn, y as buildSkillsConfig, yn as lastContextSizeFromTurns, yt as InteractionsProvider, z as fetchOAuthRedirect, zn as previewEditPayload, zt as DEFAULT_SETTINGS } from "./turn-operations-UAkOjO-u.js";
2
- import { A as resolvePersistDir, B as formatTaskStatus, H as previewLine, I as ageString, L as compactPath, O as cleanupPersistedSession, R as fmtTokens, U as shortId, V as formatTaskSummary, j as resolveTasksDir, p as createAgent, z as formatDuration } from "./tools-DhrLrOEr.js";
3
- import { n as createProcessContext } from "./contexts-BOtMvzli.js";
4
- import { c as errorMessage } from "./errors-DdZXnyXE.js";
5
- import { A as replaceDynamicSection } from "./messages-Dym8S_YH.js";
6
- import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-DGeB7-3D.js";
7
- import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-7tHcckmX.js";
8
- import { n as formatTokenUsage } from "./stats-Lc3zL3RM.js";
9
- import { n as loadSession, t as createSession } from "./session-BRIsmBSY.js";
1
+ import { $ as useMcpAuthDispatch, $t as deriveSessionTitle, A as runOAuthLogin, An as buildUpdateHint, At as clampFps, B as refreshMcpToolsCatalog, Bn as AUTO_COMPACT_MIN_GROWTH_FRACTION, Ct as listProjectFiles, D as formatPathForCwd, Dn as tryOpenBrowser, Dt as SETTINGS_CHOICES, En as useCompletion, Et as SETTINGS_CATEGORIES, Ft as resolveTheme, Gt as useDiscovery, H as subscribeMcpToolsCache, Hn as detectAuth, J as parentServerName, Jn as setProviderCredential, Jt as useConfig, K as buildVisibleMcpRows, Kt as useDiscoveryOptional, M as buildMcpServers, Mr as buildBuildSystem, Nr as buildPlanSystem, O as fetchOAuthRedirect, Ot as SETTINGS_TOGGLES, P as discoverProjectMcps, Pr as envSection, Pt as resolveChipColor, Q as McpAuthProvider, Qn as findGitRoot, R as loadMcpToolsCache, T as suggestSafelistEntry, Tt as DEFAULT_SETTINGS, Ut as createDiscoverySlot, Vn as shouldAutoCompact, W as useMcpToolToggleMap, Wt as DiscoveryProvider, Xn as discoverAgentsMd, Y as createFileMcpCredentialStore, Yt as resolveConfig, _ as useSafeModeQueue, _n as ensureKeybindingsFile, _t as hintsLength, a as useSurfaces, ar as accentColor, at as InteractionsProvider, b as getSafelist, bn as keybindingsPath, bt as generateSessionTitle, c as useStreamBuffer, cn as sumRunCosts, ct as createInteractionTools, d as discoverProjectSkills, dn as toolResultText, dr as TODO_STATUS_GLYPHS, dt as pendingInteractionsFromTurns, en as eventsFromTurns, et as useMcpAuthState, fn as updateToolEventOutcomes, g as useSafeModeActions, gn as KEYBINDING_KEY_COL_WIDTH, gt as clipHintsToWidth, h as SafeModeProvider, ht as EMPTY_HINTS, i as useSelectStyle, in as marginTopFor, j as supportsOAuth, jn as useUpdateCheck, jt as useSettings, k as oauthUsesManualCodePaste, kn as bootTick, kt as SettingsProvider, l as buildSkillsConfig, m as writeSessionExport, mn as KEYBINDING_DEFS, mt as useInteractionsQueue, n as ThemeProvider, nn as listSessionMeta, on as serverToolResultSummary, pt as useInteractionsActions, q as indexOfServerRow, qt as ConfigProvider, r as useColors, rt as splitMarkdownCodeBlocks, s as useTheme, sn as stripSpawnTokensLine, st as buildResumedToolResultsTurn, t as computeTurnAnchors, tn as lastContextSizeFromTurns, tt as getMcpAuthStatus, u as defaultSkillScanPaths, un as toolCallPreview, ut as makeRequestInteraction, vn as formatBindingForDisplay, vt as truncateTrailing, wt as useEnabledToggleSet, x as isOnSafelist, xn as matchesBinding, xr as useActiveTodos, xt as buildHints, y as addToSafelist, yn as groupBindings } from "./transcript-anchors-BTSZAPVc.js";
2
+ import { B as enabledModelOptions, E as cleanupPersistedSession, F as OUTPUT_RESERVE_TOKENS, G as modelSupportsReasoning, O as resolvePersistDir, V as getContextWindow, W as modelOptionsFor, X as restoreModelOptions, Y as piIdOf, d as createAgent, k as resolveTasksDir, z as effectiveContextWindow } from "./tools-BGtJK0vo.js";
3
+ import { a as compactPath, c as formatTaskStatus, d as shortId, i as ageString, l as formatTaskSummary, o as fmtTokens, s as formatDuration, u as previewLine } from "./edit-utils-DnfNoj16.js";
4
+ import { l as errorMessage } from "./errors-CoQnKRf1.js";
5
+ import { A as replaceDynamicSection } from "./messages-CvRQTdbR.js";
6
+ import { n as sniffImageMediaType } from "./image-sniff-B7uFSNO1.js";
7
+ import { n as createProcessContext } from "./contexts-BD2U_xpi.js";
8
+ import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-Kqzz-Rs_.js";
9
+ import { r as formatTokenUsage, t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
10
+ import { a as selectFilesFromSession, b as summaryToTurn, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-BDeqENSe.js";
11
+ import { n as loadSession, t as createSession } from "./session-BzLou2_-.js";
10
12
  import { createTuiStore } from "./session/sqlite.js";
13
+ import { B as summarizeOutcomes, C as buildContextualDiff, D as extractEditPayload, F as mergeApprovalAndBodyOutcomes, G as createFilesCompletionProvider, H as createSkillsCompletionProvider, I as parseEditOutcomesFromResult, L as resolveApprovalForPayload, N as buildEditOutcomesAnnotation, O as filetypeFromPath, Q as buildLinearRamp, R as rewriteMultiEditHeader, U as uniqueSkillNamesFromReferences, Z as blendHsl, _ as isEditErrorResult, a as TOOL_DISPLAY, b as selectableTurnIds, c as finalizeStreamingMarkdown, d as turnContextSize, f as splitPromptSegments, g as EDIT_TOOL_NAMES, h as indexOfEntry, i as turnAsText, j as summarizeEditPayload, k as previewEditPayload, l as finalizeStreamingMarkdownForOwner, m as filterModelCatalog, n as deleteTurnSafely, o as displayNameFor, p as buildModelCatalog, r as truncateTurnsAt, s as formatToolCall, v as isTurnHighlighted, w as buildUnifiedDiff, x as turnSelectionOwnership, y as isVisible, z as stripEditOutcomesAnnotation } from "./turn-operations-CCHfR9eC.js";
11
14
  import { basename, join, relative } from "node:path";
12
- import { homedir } from "node:os";
13
- import { spawn } from "node:child_process";
15
+ import { Buffer } from "node:buffer";
14
16
  import * as fs from "node:fs";
15
17
  import { readFileSync, readdirSync, statSync } from "node:fs";
16
- import { Buffer } from "node:buffer";
18
+ import { spawn } from "node:child_process";
19
+ import { homedir } from "node:os";
17
20
  import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
21
+ import { Fzf, byLengthAsc } from "fzf";
18
22
  import { BoxRenderable, CodeRenderable, RGBA, SyntaxStyle, TextRenderable, addDefaultParsers, createCliRenderer, decodePasteBytes, defaultTextareaKeyBindings, getTreeSitterClient, stripAnsiSequences } from "@opentui/core";
19
23
  import { createRoot, useKeyboard, useRenderer, useSelectionHandler, useTerminalDimensions } from "@opentui/react";
20
24
  import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
21
- import { Fzf, byLengthAsc } from "fzf";
22
25
  //#region src/tui/modal.tsx
23
26
  const ModalContext = createContext(null);
24
27
  function ModalRoot({ children }) {
@@ -2520,6 +2523,418 @@ function TodoInProgressList({ input, dim }) {
2520
2523
  });
2521
2524
  }
2522
2525
  //#endregion
2526
+ //#region src/tui/context-panel.tsx
2527
+ /**
2528
+ * Distinct categorical palette for the bar + swatches — kept in lock-step with
2529
+ * the GUI popover (`CATEGORY_COLORS` in `ChatboxControls.tsx`) so both surfaces
2530
+ * read identically. Fixed hues (not theme tokens) so the same category is the
2531
+ * same color across every theme and across both surfaces. Deferred/free fall
2532
+ * through to muted theme tones via {@link colorFor}.
2533
+ */
2534
+ const CATEGORY_HEX = {
2535
+ systemPrompt: "#4f9dff",
2536
+ rules: "#22d3ee",
2537
+ skills: "#a855f7",
2538
+ mcpInstructions: "#eab308",
2539
+ subagentDefs: "#ec4899",
2540
+ tools: "#22c55e",
2541
+ mcpTools: "#f59e0b",
2542
+ conversation: "#fb923c"
2543
+ };
2544
+ function colorFor(id, COLOR) {
2545
+ return CATEGORY_HEX[id] ?? COLOR.mute;
2546
+ }
2547
+ /** Percent label — `<1%` for tiny non-zero shares, empty at exactly 0. */
2548
+ function pctLabel(tokens, denom) {
2549
+ if (tokens <= 0 || denom <= 0) return "";
2550
+ const raw = tokens / denom * 100;
2551
+ return raw < 1 ? "<1%" : `${Math.round(raw)}%`;
2552
+ }
2553
+ function ContextPanelModal({ breakdown }) {
2554
+ const COLOR = useColors();
2555
+ const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set());
2556
+ const expandableIds = useMemo(() => breakdown.categories.filter((c) => c.items && c.items.length > 0).map((c) => c.id), [breakdown]);
2557
+ const [cursor, setCursor] = useState(0);
2558
+ const safeCursor = expandableIds.length === 0 ? 0 : Math.min(cursor, expandableIds.length - 1);
2559
+ useKeyboard((key) => {
2560
+ if (expandableIds.length === 0) return;
2561
+ if (key.name === "up") {
2562
+ setCursor((i) => ((i - 1) % expandableIds.length + expandableIds.length) % expandableIds.length);
2563
+ return;
2564
+ }
2565
+ if (key.name === "down") {
2566
+ setCursor((i) => (i + 1) % expandableIds.length);
2567
+ return;
2568
+ }
2569
+ if (key.name === "return" || key.name === "space") {
2570
+ const id = expandableIds[safeCursor];
2571
+ setExpanded((prev) => {
2572
+ const next = new Set(prev);
2573
+ if (next.has(id)) next.delete(id);
2574
+ else next.add(id);
2575
+ return next;
2576
+ });
2577
+ }
2578
+ });
2579
+ const denom = breakdown.effectiveWindow || breakdown.used || 1;
2580
+ const live = breakdown.categories.filter((c) => !c.deferred);
2581
+ const deferred = breakdown.categories.filter((c) => c.deferred);
2582
+ const pct = Math.round(breakdown.fraction * 100);
2583
+ const usedColor = pct >= 85 ? COLOR.error : pct >= 60 ? COLOR.warn : COLOR.brand;
2584
+ return /* @__PURE__ */ jsxs(Modal, {
2585
+ title: "context window",
2586
+ maxWidth: 76,
2587
+ children: [
2588
+ /* @__PURE__ */ jsxs("box", {
2589
+ style: {
2590
+ flexDirection: "column",
2591
+ flexShrink: 0
2592
+ },
2593
+ children: [
2594
+ /* @__PURE__ */ jsxs("box", {
2595
+ style: {
2596
+ flexDirection: "row",
2597
+ height: 1,
2598
+ flexShrink: 0
2599
+ },
2600
+ children: [
2601
+ /* @__PURE__ */ jsx("text", {
2602
+ wrapMode: "none",
2603
+ fg: COLOR.mute,
2604
+ children: breakdown.modelId
2605
+ }),
2606
+ /* @__PURE__ */ jsx("box", { style: { flexGrow: 1 } }),
2607
+ /* @__PURE__ */ jsxs("text", {
2608
+ wrapMode: "none",
2609
+ children: [
2610
+ /* @__PURE__ */ jsx("span", {
2611
+ fg: usedColor,
2612
+ children: fmtTokens(breakdown.used)
2613
+ }),
2614
+ /* @__PURE__ */ jsx("span", {
2615
+ fg: COLOR.mute,
2616
+ children: ` / ${fmtTokens(breakdown.effectiveWindow)} `
2617
+ }),
2618
+ /* @__PURE__ */ jsx("span", {
2619
+ fg: usedColor,
2620
+ children: `(${pct}%)`
2621
+ })
2622
+ ]
2623
+ })
2624
+ ]
2625
+ }),
2626
+ /* @__PURE__ */ jsx("box", {
2627
+ style: {
2628
+ height: 1,
2629
+ flexShrink: 0,
2630
+ marginTop: 1
2631
+ },
2632
+ children: /* @__PURE__ */ jsx(StackedBar, {
2633
+ breakdown,
2634
+ denom,
2635
+ COLOR
2636
+ })
2637
+ }),
2638
+ /* @__PURE__ */ jsx("box", { style: {
2639
+ height: 1,
2640
+ flexShrink: 0
2641
+ } }),
2642
+ live.map((cat) => /* @__PURE__ */ jsx(CategoryRows, {
2643
+ cat,
2644
+ denom,
2645
+ COLOR,
2646
+ isCursor: expandableIds[safeCursor] === cat.id,
2647
+ isExpanded: expanded.has(cat.id)
2648
+ }, cat.id)),
2649
+ deferred.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("box", { style: {
2650
+ height: 1,
2651
+ flexShrink: 0
2652
+ } }), deferred.map((cat) => /* @__PURE__ */ jsx(CategoryRows, {
2653
+ cat,
2654
+ denom,
2655
+ COLOR,
2656
+ isCursor: expandableIds[safeCursor] === cat.id,
2657
+ isExpanded: expanded.has(cat.id)
2658
+ }, cat.id))] }),
2659
+ breakdown.activeSkills && breakdown.activeSkills.length > 0 && /* @__PURE__ */ jsx(ActiveSkillsRow, {
2660
+ skills: breakdown.activeSkills,
2661
+ COLOR
2662
+ }),
2663
+ breakdown.usage && /* @__PURE__ */ jsx(UsageRows, {
2664
+ usage: breakdown.usage,
2665
+ COLOR
2666
+ })
2667
+ ]
2668
+ }),
2669
+ breakdown.hasEstimates && /* @__PURE__ */ jsxs("text", {
2670
+ fg: COLOR.dim,
2671
+ children: [/* @__PURE__ */ jsx("span", {
2672
+ fg: COLOR.mute,
2673
+ children: "~"
2674
+ }), " estimated — exact counts unavailable for this provider"]
2675
+ }),
2676
+ /* @__PURE__ */ jsx("text", {
2677
+ fg: COLOR.dim,
2678
+ children: expandableIds.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
2679
+ /* @__PURE__ */ jsx("span", {
2680
+ fg: COLOR.warn,
2681
+ children: "↑↓"
2682
+ }),
2683
+ " navigate · ",
2684
+ /* @__PURE__ */ jsx("span", {
2685
+ fg: COLOR.warn,
2686
+ children: "↵/space"
2687
+ }),
2688
+ " expand · ",
2689
+ /* @__PURE__ */ jsx("span", {
2690
+ fg: COLOR.warn,
2691
+ children: "esc"
2692
+ }),
2693
+ " close"
2694
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
2695
+ fg: COLOR.warn,
2696
+ children: "esc"
2697
+ }), " close"] })
2698
+ })
2699
+ ]
2700
+ });
2701
+ }
2702
+ /**
2703
+ * Single-row stacked bar that fills the panel width. Layout (left → right):
2704
+ * [ colored category segments ][ free space ][ autocompact buffer ]
2705
+ *
2706
+ * Sizing uses `flexGrow` weights (×1000 of the window fraction) so the bar
2707
+ * stretches to the full container width. Each non-zero colored segment also
2708
+ * carries `minWidth: 1` so tiny shares (system/tools at <1% of a 1M window)
2709
+ * stay visible instead of collapsing into one color. The autocompact buffer is
2710
+ * pinned at the far right — reserved tail headroom, consumed last.
2711
+ */
2712
+ function StackedBar({ breakdown, denom, COLOR }) {
2713
+ const live = breakdown.categories.filter((c) => !c.deferred && c.tokens > 0);
2714
+ const buffer = breakdown.categories.find((c) => c.id === "autocompactBuffer" && c.tokens > 0);
2715
+ const MIN_WEIGHT = 14;
2716
+ const w = (tokens) => Math.max(MIN_WEIGHT, Math.round(tokens / denom * 1e3));
2717
+ const colored = live.map((c) => ({
2718
+ id: c.id,
2719
+ weight: w(c.tokens)
2720
+ }));
2721
+ const coloredWeight = colored.reduce((a, s) => a + s.weight, 0);
2722
+ const bufferWeight = buffer ? Math.round(buffer.tokens / denom * 1e3) : 0;
2723
+ const freeWeight = Math.max(0, 1e3 - coloredWeight - bufferWeight);
2724
+ return /* @__PURE__ */ jsxs("box", {
2725
+ style: {
2726
+ flexDirection: "row",
2727
+ flexGrow: 1,
2728
+ height: 1
2729
+ },
2730
+ children: [
2731
+ colored.map((s, i) => /* @__PURE__ */ jsx("box", { style: {
2732
+ flexGrow: s.weight,
2733
+ flexBasis: 0,
2734
+ height: 1,
2735
+ backgroundColor: colorFor(s.id, COLOR)
2736
+ } }, `${s.id}-${i}`)),
2737
+ freeWeight > 0 && /* @__PURE__ */ jsx("box", { style: {
2738
+ flexGrow: freeWeight,
2739
+ flexBasis: 0,
2740
+ height: 1,
2741
+ backgroundColor: COLOR.mute
2742
+ } }),
2743
+ bufferWeight > 0 && /* @__PURE__ */ jsx("box", { style: {
2744
+ flexGrow: bufferWeight,
2745
+ flexBasis: 0,
2746
+ height: 1,
2747
+ backgroundColor: COLOR.dim
2748
+ } })
2749
+ ]
2750
+ });
2751
+ }
2752
+ /**
2753
+ * Currently-active skills. Informational — an activated skill's body lives in
2754
+ * the conversation (already counted there); this surfaces which are loaded.
2755
+ */
2756
+ function ActiveSkillsRow({ skills, COLOR }) {
2757
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2758
+ /* @__PURE__ */ jsx("box", { style: {
2759
+ height: 1,
2760
+ flexShrink: 0
2761
+ } }),
2762
+ /* @__PURE__ */ jsx("box", {
2763
+ style: {
2764
+ height: 1,
2765
+ flexShrink: 0
2766
+ },
2767
+ children: /* @__PURE__ */ jsx("text", {
2768
+ wrapMode: "none",
2769
+ fg: COLOR.mute,
2770
+ children: "active skills"
2771
+ })
2772
+ }),
2773
+ skills.map((name) => /* @__PURE__ */ jsx("box", {
2774
+ style: {
2775
+ flexDirection: "row",
2776
+ height: 1,
2777
+ flexShrink: 0
2778
+ },
2779
+ children: /* @__PURE__ */ jsxs("text", {
2780
+ wrapMode: "none",
2781
+ children: [/* @__PURE__ */ jsx("span", {
2782
+ fg: CATEGORY_HEX.skills,
2783
+ children: "✦ "
2784
+ }), /* @__PURE__ */ jsx("span", {
2785
+ fg: COLOR.dim,
2786
+ children: name
2787
+ })]
2788
+ })
2789
+ }, name))
2790
+ ] });
2791
+ }
2792
+ /**
2793
+ * Exact last-turn token split (cache read / write / fresh input / output).
2794
+ * Informational — sits below the categories, all values exact (no `~`).
2795
+ */
2796
+ function UsageRows({ usage, COLOR }) {
2797
+ const rows = [
2798
+ {
2799
+ label: "Cached (read)",
2800
+ tokens: usage.cacheRead
2801
+ },
2802
+ {
2803
+ label: "Cache write",
2804
+ tokens: usage.cacheCreation
2805
+ },
2806
+ {
2807
+ label: "Fresh input",
2808
+ tokens: usage.input
2809
+ },
2810
+ {
2811
+ label: "Output",
2812
+ tokens: usage.output
2813
+ }
2814
+ ].filter((r) => r.tokens > 0);
2815
+ if (rows.length === 0) return null;
2816
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2817
+ /* @__PURE__ */ jsx("box", { style: {
2818
+ height: 1,
2819
+ flexShrink: 0
2820
+ } }),
2821
+ /* @__PURE__ */ jsx("box", {
2822
+ style: {
2823
+ height: 1,
2824
+ flexShrink: 0
2825
+ },
2826
+ children: /* @__PURE__ */ jsx("text", {
2827
+ wrapMode: "none",
2828
+ fg: COLOR.mute,
2829
+ children: "last turn"
2830
+ })
2831
+ }),
2832
+ rows.map((r) => /* @__PURE__ */ jsxs("box", {
2833
+ style: {
2834
+ flexDirection: "row",
2835
+ height: 1,
2836
+ flexShrink: 0
2837
+ },
2838
+ children: [
2839
+ /* @__PURE__ */ jsx("text", {
2840
+ wrapMode: "none",
2841
+ fg: COLOR.mute,
2842
+ children: r.label
2843
+ }),
2844
+ /* @__PURE__ */ jsx("box", { style: { flexGrow: 1 } }),
2845
+ /* @__PURE__ */ jsx("text", {
2846
+ wrapMode: "none",
2847
+ fg: COLOR.dim,
2848
+ children: fmtTokens(r.tokens)
2849
+ })
2850
+ ]
2851
+ }, r.label))
2852
+ ] });
2853
+ }
2854
+ function CategoryRows({ cat, denom, COLOR, isCursor, isExpanded }) {
2855
+ const SURFACE = useSurfaces();
2856
+ const pct = pctLabel(cat.tokens, denom);
2857
+ const hasItems = !!cat.items && cat.items.length > 0;
2858
+ const labelColor = isCursor ? COLOR.brand : cat.deferred ? COLOR.mute : COLOR.dim;
2859
+ const marker = hasItems ? isExpanded ? " ▾" : " ▸" : "";
2860
+ return /* @__PURE__ */ jsxs("box", {
2861
+ style: {
2862
+ flexDirection: "column",
2863
+ flexShrink: 0
2864
+ },
2865
+ children: [/* @__PURE__ */ jsxs("box", {
2866
+ style: {
2867
+ flexDirection: "row",
2868
+ height: 1,
2869
+ flexShrink: 0,
2870
+ backgroundColor: isCursor && hasItems ? SURFACE.selection : void 0
2871
+ },
2872
+ children: [
2873
+ /* @__PURE__ */ jsxs("text", {
2874
+ wrapMode: "none",
2875
+ children: [
2876
+ /* @__PURE__ */ jsx("span", {
2877
+ fg: colorFor(cat.id, COLOR),
2878
+ children: "■ "
2879
+ }),
2880
+ /* @__PURE__ */ jsx("span", {
2881
+ fg: labelColor,
2882
+ children: cat.label
2883
+ }),
2884
+ marker ? /* @__PURE__ */ jsx("span", {
2885
+ fg: hasItems && isCursor ? COLOR.brand : COLOR.mute,
2886
+ children: marker
2887
+ }) : null
2888
+ ]
2889
+ }),
2890
+ /* @__PURE__ */ jsx("box", { style: { flexGrow: 1 } }),
2891
+ /* @__PURE__ */ jsxs("text", {
2892
+ wrapMode: "none",
2893
+ children: [
2894
+ cat.estimated && /* @__PURE__ */ jsx("span", {
2895
+ fg: COLOR.mute,
2896
+ children: "~"
2897
+ }),
2898
+ /* @__PURE__ */ jsx("span", {
2899
+ fg: cat.deferred ? COLOR.mute : COLOR.dim,
2900
+ children: fmtTokens(cat.tokens)
2901
+ }),
2902
+ pct ? /* @__PURE__ */ jsx("span", {
2903
+ fg: COLOR.mute,
2904
+ children: ` ${pct}`
2905
+ }) : null
2906
+ ]
2907
+ })
2908
+ ]
2909
+ }), isExpanded && hasItems && cat.items.map((item) => /* @__PURE__ */ jsxs("box", {
2910
+ style: {
2911
+ flexDirection: "row",
2912
+ height: 1,
2913
+ paddingLeft: 2,
2914
+ flexShrink: 0
2915
+ },
2916
+ children: [
2917
+ /* @__PURE__ */ jsx("text", {
2918
+ wrapMode: "none",
2919
+ fg: COLOR.mute,
2920
+ children: item.label
2921
+ }),
2922
+ /* @__PURE__ */ jsx("box", { style: { flexGrow: 1 } }),
2923
+ /* @__PURE__ */ jsxs("text", {
2924
+ wrapMode: "none",
2925
+ children: [item.estimated && /* @__PURE__ */ jsx("span", {
2926
+ fg: COLOR.mute,
2927
+ children: "~"
2928
+ }), /* @__PURE__ */ jsx("span", {
2929
+ fg: COLOR.mute,
2930
+ children: fmtTokens(item.tokens)
2931
+ })]
2932
+ })
2933
+ ]
2934
+ }, item.id))]
2935
+ });
2936
+ }
2937
+ //#endregion
2523
2938
  //#region src/tui/cwd-picker.tsx
2524
2939
  const VISIBLE_ROWS$1 = 12;
2525
2940
  const HOME = homedir();
@@ -2667,8 +3082,7 @@ function CwdPickerModal({ currentCwd, onPick }) {
2667
3082
  start: 0,
2668
3083
  slice: results
2669
3084
  };
2670
- const half = Math.floor(VISIBLE_ROWS$1 / 2);
2671
- let start = Math.max(0, safeIndex - half);
3085
+ let start = Math.max(0, safeIndex - Math.floor(VISIBLE_ROWS$1 / 2));
2672
3086
  if (start + VISIBLE_ROWS$1 > results.length) start = results.length - VISIBLE_ROWS$1;
2673
3087
  return {
2674
3088
  start,
@@ -3007,200 +3421,8 @@ function readToolsByServer(dataDir) {
3007
3421
  return out;
3008
3422
  }
3009
3423
  //#endregion
3010
- //#region src/tui/effort-picker.tsx
3011
- const BASE_LEVELS = [
3012
- {
3013
- id: "off",
3014
- description: "no reasoning — fastest, smallest output"
3015
- },
3016
- {
3017
- id: "minimal",
3018
- description: "tiny reasoning budget (gpt-5 family)"
3019
- },
3020
- {
3021
- id: "low",
3022
- description: "short reasoning pass"
3023
- },
3024
- {
3025
- id: "medium",
3026
- description: "balanced — sensible default"
3027
- },
3028
- {
3029
- id: "high",
3030
- description: "deep reasoning — slowest, longest"
3031
- }
3032
- ];
3033
- const ADAPTIVE_LEVEL = {
3034
- id: "adaptive",
3035
- description: "model decides per-turn (Anthropic)"
3036
- };
3037
- function EffortPickerModal({ current, supportsAdaptive, onPick }) {
3038
- const COLOR = useColors();
3039
- const SURFACE = useSurfaces();
3040
- const inputRef = useRef(null);
3041
- const [query, setQuery] = useState("");
3042
- const levels = useMemo(() => {
3043
- return (supportsAdaptive ? [...BASE_LEVELS, ADAPTIVE_LEVEL] : BASE_LEVELS).map((l) => ({
3044
- ...l,
3045
- searchCorpus: `${l.id} ${l.description}`.toLowerCase()
3046
- }));
3047
- }, [supportsAdaptive]);
3048
- const filtered = useMemo(() => {
3049
- const trimmed = query.trim().toLowerCase();
3050
- if (!trimmed) return levels;
3051
- const terms = trimmed.split(/\s+/);
3052
- return levels.filter((l) => terms.every((t) => l.searchCorpus.includes(t)));
3053
- }, [levels, query]);
3054
- const [selectedIdx, setSelectedIdx] = useState(() => {
3055
- const idx = levels.findIndex((l) => l.id === current);
3056
- if (idx >= 0) return idx;
3057
- const fallback = levels.findIndex((l) => l.id === "medium");
3058
- return fallback < 0 ? 0 : fallback;
3059
- });
3060
- const handleQueryChange = useCallback((next) => {
3061
- setQuery(next);
3062
- setSelectedIdx(0);
3063
- }, []);
3064
- const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1);
3065
- const commit = () => {
3066
- const row = filtered[safeIndex];
3067
- if (row) onPick(row.id);
3068
- };
3069
- useEffect(() => {
3070
- inputRef.current?.focus();
3071
- }, []);
3072
- useKeyboard((key) => {
3073
- if (key.name === "up") {
3074
- setSelectedIdx((i) => {
3075
- if (filtered.length === 0) return i;
3076
- return ((i - 1) % filtered.length + filtered.length) % filtered.length;
3077
- });
3078
- return;
3079
- }
3080
- if (key.name === "down") {
3081
- setSelectedIdx((i) => {
3082
- if (filtered.length === 0) return i;
3083
- return (i + 1) % filtered.length;
3084
- });
3085
- return;
3086
- }
3087
- if (key.name === "return") commit();
3088
- });
3089
- return /* @__PURE__ */ jsxs(Modal, {
3090
- title: "select reasoning effort",
3091
- maxWidth: 80,
3092
- children: [
3093
- /* @__PURE__ */ jsx("box", {
3094
- style: {
3095
- border: true,
3096
- borderColor: COLOR.borderActive,
3097
- paddingLeft: 1,
3098
- paddingRight: 1,
3099
- height: 3
3100
- },
3101
- children: /* @__PURE__ */ jsx("input", {
3102
- ref: inputRef,
3103
- focused: true,
3104
- placeholder: "search effort levels…",
3105
- onInput: handleQueryChange,
3106
- onSubmit: () => {},
3107
- style: { flexGrow: 1 }
3108
- })
3109
- }),
3110
- /* @__PURE__ */ jsx("box", {
3111
- style: {
3112
- flexDirection: "column",
3113
- flexShrink: 0
3114
- },
3115
- children: filtered.length === 0 ? /* @__PURE__ */ jsxs("text", {
3116
- fg: COLOR.dim,
3117
- children: [/* @__PURE__ */ jsx("span", {
3118
- fg: COLOR.mute,
3119
- children: "no levels match "
3120
- }), /* @__PURE__ */ jsx("span", {
3121
- fg: COLOR.warn,
3122
- children: query.trim()
3123
- })]
3124
- }) : filtered.map((level, i) => /* @__PURE__ */ jsx(EffortRow, {
3125
- level,
3126
- isCurrent: level.id === current,
3127
- isFocused: i === safeIndex,
3128
- highlightBg: SURFACE.selection
3129
- }, level.id))
3130
- }),
3131
- /* @__PURE__ */ jsxs("text", {
3132
- fg: COLOR.dim,
3133
- children: [
3134
- /* @__PURE__ */ jsx("span", {
3135
- fg: COLOR.warn,
3136
- children: "↑↓"
3137
- }),
3138
- " navigate · ",
3139
- /* @__PURE__ */ jsx("span", {
3140
- fg: COLOR.warn,
3141
- children: "↵"
3142
- }),
3143
- " select · ",
3144
- /* @__PURE__ */ jsx("span", {
3145
- fg: COLOR.warn,
3146
- children: "esc"
3147
- }),
3148
- " close · ",
3149
- /* @__PURE__ */ jsx("span", {
3150
- fg: COLOR.mute,
3151
- children: `${filtered.length} / ${levels.length} level${levels.length === 1 ? "" : "s"}`
3152
- })
3153
- ]
3154
- })
3155
- ]
3156
- });
3157
- }
3158
- /**
3159
- * Single row in the picker. Mirrors `ModelRow` in `model-picker.tsx`:
3160
- * `●` marker for the current pick, single-space middle-dot separators,
3161
- * focused row gets the `surfaces.selection` background lift.
3162
- */
3163
- function EffortRow({ level, isCurrent, isFocused, highlightBg }) {
3164
- const COLOR = useColors();
3165
- const marker = isCurrent ? "●" : " ";
3166
- return /* @__PURE__ */ jsx("box", {
3167
- style: {
3168
- height: 1,
3169
- paddingLeft: 1,
3170
- paddingRight: 1,
3171
- flexShrink: 0,
3172
- backgroundColor: isFocused ? highlightBg : void 0
3173
- },
3174
- children: /* @__PURE__ */ jsxs("text", {
3175
- wrapMode: "none",
3176
- children: [
3177
- /* @__PURE__ */ jsx("span", {
3178
- fg: isCurrent ? COLOR.brand : COLOR.mute,
3179
- children: marker
3180
- }),
3181
- /* @__PURE__ */ jsx("span", {
3182
- fg: COLOR.mute,
3183
- children: " "
3184
- }),
3185
- /* @__PURE__ */ jsx("span", {
3186
- fg: isFocused ? COLOR.brand : COLOR.dim,
3187
- children: level.id
3188
- }),
3189
- /* @__PURE__ */ jsx("span", {
3190
- fg: COLOR.mute,
3191
- children: " · "
3192
- }),
3193
- /* @__PURE__ */ jsx("span", {
3194
- fg: COLOR.mute,
3195
- children: level.description
3196
- })
3197
- ]
3198
- })
3199
- });
3200
- }
3201
- //#endregion
3202
- //#region src/tui/keybindings-modal.tsx
3203
- function KeybindingsModal({ bindings, filePath, onEditFile, onClose }) {
3424
+ //#region src/tui/keybindings-modal.tsx
3425
+ function KeybindingsModal({ bindings, filePath, onEditFile, onClose }) {
3204
3426
  const SURFACE = useSurfaces();
3205
3427
  const { height: termHeight } = useTerminalDimensions();
3206
3428
  const scrollRef = useRef(null);
@@ -3251,7 +3473,7 @@ function KeybindingsCatalog({ sections }) {
3251
3473
  flexShrink: 0,
3252
3474
  marginTop: sectionIdx === 0 ? 0 : 1
3253
3475
  },
3254
- children: [/* @__PURE__ */ jsx(SectionHeader, { label: section.group }), section.rows.map((row) => /* @__PURE__ */ jsx(BindingRow, {
3476
+ children: [/* @__PURE__ */ jsx(SectionHeader$1, { label: section.group }), section.rows.map((row) => /* @__PURE__ */ jsx(BindingRow, {
3255
3477
  def: row.def,
3256
3478
  spec: row.spec
3257
3479
  }, row.def.action))]
@@ -3308,7 +3530,7 @@ function KeybindingsEditFileButton({ filePath, highlightBg }) {
3308
3530
  })]
3309
3531
  });
3310
3532
  }
3311
- function SectionHeader({ label }) {
3533
+ function SectionHeader$1({ label }) {
3312
3534
  return /* @__PURE__ */ jsx("box", {
3313
3535
  style: {
3314
3536
  flexShrink: 0,
@@ -3324,57 +3546,285 @@ function SectionHeader({ label }) {
3324
3546
  })
3325
3547
  });
3326
3548
  }
3327
- function BindingRow({ def, spec }) {
3549
+ function BindingRow({ def, spec }) {
3550
+ const COLOR = useColors();
3551
+ const display = formatBindingForDisplay(spec);
3552
+ const keyText = (display || "—").padEnd(KEYBINDING_KEY_COL_WIDTH, " ");
3553
+ return /* @__PURE__ */ jsxs("box", {
3554
+ style: {
3555
+ flexDirection: "column",
3556
+ flexShrink: 0,
3557
+ paddingLeft: 3,
3558
+ paddingRight: 1
3559
+ },
3560
+ children: [/* @__PURE__ */ jsxs("text", {
3561
+ wrapMode: "none",
3562
+ children: [/* @__PURE__ */ jsx("span", {
3563
+ fg: display ? COLOR.warn : COLOR.mute,
3564
+ children: keyText
3565
+ }), /* @__PURE__ */ jsx("span", {
3566
+ fg: COLOR.dim,
3567
+ children: def.label
3568
+ })]
3569
+ }), /* @__PURE__ */ jsx("box", {
3570
+ style: {
3571
+ flexShrink: 0,
3572
+ paddingLeft: KEYBINDING_KEY_COL_WIDTH
3573
+ },
3574
+ children: /* @__PURE__ */ jsx("text", {
3575
+ wrapMode: "word",
3576
+ fg: COLOR.mute,
3577
+ children: def.description
3578
+ })
3579
+ })]
3580
+ });
3581
+ }
3582
+ function CountsBadge$1({ count }) {
3583
+ const COLOR = useColors();
3584
+ return /* @__PURE__ */ jsxs("text", {
3585
+ wrapMode: "none",
3586
+ children: [
3587
+ /* @__PURE__ */ jsx("span", {
3588
+ fg: COLOR.mute,
3589
+ children: " "
3590
+ }),
3591
+ /* @__PURE__ */ jsx("span", {
3592
+ fg: COLOR.accent,
3593
+ children: String(count)
3594
+ }),
3595
+ /* @__PURE__ */ jsx("span", {
3596
+ fg: COLOR.mute,
3597
+ children: ` action${count === 1 ? "" : "s"} `
3598
+ })
3599
+ ]
3600
+ });
3601
+ }
3602
+ //#endregion
3603
+ //#region src/tui/model-options-picker.tsx
3604
+ const BASE_LEVELS$1 = [
3605
+ {
3606
+ id: "off",
3607
+ description: "no reasoning — fastest, smallest output"
3608
+ },
3609
+ {
3610
+ id: "minimal",
3611
+ description: "tiny reasoning budget (gpt-5 family)"
3612
+ },
3613
+ {
3614
+ id: "low",
3615
+ description: "short reasoning pass"
3616
+ },
3617
+ {
3618
+ id: "medium",
3619
+ description: "balanced — sensible default"
3620
+ },
3621
+ {
3622
+ id: "high",
3623
+ description: "deep reasoning — slowest, longest"
3624
+ }
3625
+ ];
3626
+ const ADAPTIVE_LEVEL$1 = {
3627
+ id: "adaptive",
3628
+ description: "model decides per-turn (Anthropic)"
3629
+ };
3630
+ function ModelOptionsPickerModal({ supportsReasoning, supportsAdaptive, currentEffort, options, enabled, onPickEffort, onToggleOption }) {
3631
+ const COLOR = useColors();
3632
+ const SURFACE = useSurfaces();
3633
+ const [selectedIdx, setSelectedIdx] = useState(0);
3634
+ const [localEnabled, setLocalEnabled] = useState(() => ({ ...enabled }));
3635
+ const handleToggle = (optionId) => {
3636
+ setLocalEnabled((prev) => ({
3637
+ ...prev,
3638
+ [optionId]: !prev[optionId]
3639
+ }));
3640
+ onToggleOption(optionId);
3641
+ };
3642
+ const rows = useMemo(() => {
3643
+ const out = [];
3644
+ if (supportsReasoning) {
3645
+ const levels = supportsAdaptive ? [...BASE_LEVELS$1, ADAPTIVE_LEVEL$1] : BASE_LEVELS$1;
3646
+ for (const level of levels) out.push({
3647
+ kind: "effort",
3648
+ level
3649
+ });
3650
+ }
3651
+ for (const option of options) out.push({
3652
+ kind: "option",
3653
+ option
3654
+ });
3655
+ return out;
3656
+ }, [
3657
+ supportsReasoning,
3658
+ supportsAdaptive,
3659
+ options
3660
+ ]);
3661
+ const safeIndex = rows.length === 0 ? 0 : Math.min(selectedIdx, rows.length - 1);
3662
+ const firstOptionIdx = rows.findIndex((r) => r.kind === "option");
3663
+ useKeyboard((key) => {
3664
+ if (key.name === "up") {
3665
+ setSelectedIdx((i) => rows.length === 0 ? i : ((i - 1) % rows.length + rows.length) % rows.length);
3666
+ return;
3667
+ }
3668
+ if (key.name === "down") {
3669
+ setSelectedIdx((i) => rows.length === 0 ? i : (i + 1) % rows.length);
3670
+ return;
3671
+ }
3672
+ if (key.name === "return" || key.name === "space") {
3673
+ const row = rows[safeIndex];
3674
+ if (!row) return;
3675
+ if (row.kind === "effort") onPickEffort(row.level.id);
3676
+ else handleToggle(row.option.id);
3677
+ }
3678
+ });
3679
+ return /* @__PURE__ */ jsxs(Modal, {
3680
+ title: "model options",
3681
+ maxWidth: 80,
3682
+ children: [/* @__PURE__ */ jsxs("box", {
3683
+ style: {
3684
+ flexDirection: "column",
3685
+ flexShrink: 0
3686
+ },
3687
+ children: [supportsReasoning && /* @__PURE__ */ jsx(SectionHeader, {
3688
+ label: "reasoning effort",
3689
+ color: COLOR.mute
3690
+ }), rows.map((row, i) => {
3691
+ const isFocused = i === safeIndex;
3692
+ if (row.kind === "effort") return /* @__PURE__ */ jsx(EffortRow$1, {
3693
+ level: row.level,
3694
+ isCurrent: row.level.id === currentEffort,
3695
+ isFocused,
3696
+ highlightBg: SURFACE.selection
3697
+ }, `effort:${row.level.id}`);
3698
+ return /* @__PURE__ */ jsxs("box", {
3699
+ style: {
3700
+ flexDirection: "column",
3701
+ flexShrink: 0
3702
+ },
3703
+ children: [i === firstOptionIdx && /* @__PURE__ */ jsxs(Fragment, { children: [supportsReasoning && /* @__PURE__ */ jsx("box", { style: {
3704
+ height: 1,
3705
+ flexShrink: 0
3706
+ } }), /* @__PURE__ */ jsx(SectionHeader, {
3707
+ label: "features",
3708
+ color: COLOR.mute
3709
+ })] }), /* @__PURE__ */ jsx(OptionRow, {
3710
+ option: row.option,
3711
+ isEnabled: localEnabled[row.option.id] === true,
3712
+ isFocused,
3713
+ highlightBg: SURFACE.selection
3714
+ })]
3715
+ }, `option:${row.option.id}`);
3716
+ })]
3717
+ }), /* @__PURE__ */ jsxs("text", {
3718
+ fg: COLOR.dim,
3719
+ children: [
3720
+ /* @__PURE__ */ jsx("span", {
3721
+ fg: COLOR.warn,
3722
+ children: "↑↓"
3723
+ }),
3724
+ " navigate · ",
3725
+ /* @__PURE__ */ jsx("span", {
3726
+ fg: COLOR.warn,
3727
+ children: "↵/space"
3728
+ }),
3729
+ " select/toggle · ",
3730
+ /* @__PURE__ */ jsx("span", {
3731
+ fg: COLOR.warn,
3732
+ children: "esc"
3733
+ }),
3734
+ " close"
3735
+ ]
3736
+ })]
3737
+ });
3738
+ }
3739
+ function SectionHeader({ label, color }) {
3740
+ return /* @__PURE__ */ jsx("box", {
3741
+ style: {
3742
+ height: 1,
3743
+ paddingLeft: 1,
3744
+ flexShrink: 0
3745
+ },
3746
+ children: /* @__PURE__ */ jsx("text", {
3747
+ wrapMode: "none",
3748
+ children: /* @__PURE__ */ jsx("span", {
3749
+ fg: color,
3750
+ children: label
3751
+ })
3752
+ })
3753
+ });
3754
+ }
3755
+ function EffortRow$1({ level, isCurrent, isFocused, highlightBg }) {
3756
+ const COLOR = useColors();
3757
+ const marker = isCurrent ? "●" : " ";
3758
+ return /* @__PURE__ */ jsx("box", {
3759
+ style: {
3760
+ height: 1,
3761
+ paddingLeft: 1,
3762
+ paddingRight: 1,
3763
+ flexShrink: 0,
3764
+ backgroundColor: isFocused ? highlightBg : void 0
3765
+ },
3766
+ children: /* @__PURE__ */ jsxs("text", {
3767
+ wrapMode: "none",
3768
+ children: [
3769
+ /* @__PURE__ */ jsx("span", {
3770
+ fg: isCurrent ? COLOR.brand : COLOR.mute,
3771
+ children: marker
3772
+ }),
3773
+ /* @__PURE__ */ jsx("span", {
3774
+ fg: COLOR.mute,
3775
+ children: " "
3776
+ }),
3777
+ /* @__PURE__ */ jsx("span", {
3778
+ fg: isFocused ? COLOR.brand : COLOR.dim,
3779
+ children: level.id
3780
+ }),
3781
+ /* @__PURE__ */ jsx("span", {
3782
+ fg: COLOR.mute,
3783
+ children: " · "
3784
+ }),
3785
+ /* @__PURE__ */ jsx("span", {
3786
+ fg: COLOR.mute,
3787
+ children: level.description
3788
+ })
3789
+ ]
3790
+ })
3791
+ });
3792
+ }
3793
+ function OptionRow({ option, isEnabled, isFocused, highlightBg }) {
3328
3794
  const COLOR = useColors();
3329
- const display = formatBindingForDisplay(spec);
3330
- const keyText = (display || "").padEnd(KEYBINDING_KEY_COL_WIDTH, " ");
3331
- return /* @__PURE__ */ jsxs("box", {
3795
+ const marker = isEnabled ? "[x]" : "[ ]";
3796
+ return /* @__PURE__ */ jsx("box", {
3332
3797
  style: {
3333
- flexDirection: "column",
3798
+ height: 1,
3799
+ paddingLeft: 1,
3800
+ paddingRight: 1,
3334
3801
  flexShrink: 0,
3335
- paddingLeft: 3,
3336
- paddingRight: 1
3802
+ backgroundColor: isFocused ? highlightBg : void 0
3337
3803
  },
3338
- children: [/* @__PURE__ */ jsxs("text", {
3804
+ children: /* @__PURE__ */ jsxs("text", {
3339
3805
  wrapMode: "none",
3340
- children: [/* @__PURE__ */ jsx("span", {
3341
- fg: display ? COLOR.warn : COLOR.mute,
3342
- children: keyText
3343
- }), /* @__PURE__ */ jsx("span", {
3344
- fg: COLOR.dim,
3345
- children: def.label
3346
- })]
3347
- }), /* @__PURE__ */ jsx("box", {
3348
- style: {
3349
- flexShrink: 0,
3350
- paddingLeft: KEYBINDING_KEY_COL_WIDTH
3351
- },
3352
- children: /* @__PURE__ */ jsx("text", {
3353
- wrapMode: "word",
3354
- fg: COLOR.mute,
3355
- children: def.description
3356
- })
3357
- })]
3358
- });
3359
- }
3360
- function CountsBadge$1({ count }) {
3361
- const COLOR = useColors();
3362
- return /* @__PURE__ */ jsxs("text", {
3363
- wrapMode: "none",
3364
- children: [
3365
- /* @__PURE__ */ jsx("span", {
3366
- fg: COLOR.mute,
3367
- children: " "
3368
- }),
3369
- /* @__PURE__ */ jsx("span", {
3370
- fg: COLOR.accent,
3371
- children: String(count)
3372
- }),
3373
- /* @__PURE__ */ jsx("span", {
3374
- fg: COLOR.mute,
3375
- children: ` action${count === 1 ? "" : "s"} `
3376
- })
3377
- ]
3806
+ children: [
3807
+ /* @__PURE__ */ jsx("span", {
3808
+ fg: isEnabled ? COLOR.brand : COLOR.mute,
3809
+ children: marker
3810
+ }),
3811
+ /* @__PURE__ */ jsx("span", {
3812
+ fg: COLOR.mute,
3813
+ children: " "
3814
+ }),
3815
+ /* @__PURE__ */ jsx("span", {
3816
+ fg: isFocused ? COLOR.brand : COLOR.dim,
3817
+ children: option.label
3818
+ }),
3819
+ option.description ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
3820
+ fg: COLOR.mute,
3821
+ children: " · "
3822
+ }), /* @__PURE__ */ jsx("span", {
3823
+ fg: COLOR.mute,
3824
+ children: option.description
3825
+ })] }) : null
3826
+ ]
3827
+ })
3378
3828
  });
3379
3829
  }
3380
3830
  //#endregion
@@ -3442,8 +3892,7 @@ function ModelPickerModal({ providers, modelsFor, current, onPick }) {
3442
3892
  start: 0,
3443
3893
  slice: filtered
3444
3894
  };
3445
- const half = Math.floor(VISIBLE_ROWS / 2);
3446
- let start = Math.max(0, safeIndex - half);
3895
+ let start = Math.max(0, safeIndex - Math.floor(VISIBLE_ROWS / 2));
3447
3896
  if (start + VISIBLE_ROWS > filtered.length) start = filtered.length - VISIBLE_ROWS;
3448
3897
  return {
3449
3898
  start,
@@ -4525,23 +4974,25 @@ function snippet(s) {
4525
4974
  }
4526
4975
  //#endregion
4527
4976
  //#region src/tui/interaction-block.tsx
4528
- const COMMENT_TEXTAREA_BINDINGS = [
4529
- ...defaultTextareaKeyBindings.filter((b) => b.name !== "return" && !(b.name === "a" && b.ctrl && !b.shift && !b.meta)),
4530
- {
4531
- name: "a",
4532
- ctrl: true,
4533
- action: "select-all"
4534
- },
4535
- {
4536
- name: "return",
4537
- action: "submit"
4538
- },
4539
- {
4540
- name: "return",
4541
- shift: true,
4542
- action: "newline"
4543
- }
4544
- ];
4977
+ const COMMENT_TEXTAREA_BINDINGS = (() => {
4978
+ return [
4979
+ ...defaultTextareaKeyBindings.filter((b) => b.name !== "return" && !(b.name === "a" && b.ctrl && !b.shift && !b.meta)),
4980
+ {
4981
+ name: "a",
4982
+ ctrl: true,
4983
+ action: "select-all"
4984
+ },
4985
+ {
4986
+ name: "return",
4987
+ action: "submit"
4988
+ },
4989
+ {
4990
+ name: "return",
4991
+ shift: true,
4992
+ action: "newline"
4993
+ }
4994
+ ];
4995
+ })();
4545
4996
  /**
4546
4997
  * InteractionBlock — picker UI for `present_plan` and `ask_user` tool calls.
4547
4998
  *
@@ -5615,13 +6066,7 @@ const API_KEY_INPUT_BINDINGS = makeSubmitBindings(false);
5615
6066
  function findByKey(items, value) {
5616
6067
  return typeof value === "string" ? items.find((i) => i.key === value) : void 0;
5617
6068
  }
5618
- /**
5619
- * Sentinel value used by the picker's "+ add / re-configure" option. Lives
5620
- * outside the provider key namespace (key strings are at least one char, no
5621
- * leading `__`) so we can't collide with a real registry entry.
5622
- */
5623
- const WIZARD_OPTION_VALUE = "__wizard__";
5624
- function AuthScreen({ onPick }) {
6069
+ function AuthScreen({ onPick, initialConfigureKey }) {
5625
6070
  const config = useConfig();
5626
6071
  const { providers: registry } = config;
5627
6072
  const focused = useModalAwareFocus();
@@ -5632,30 +6077,48 @@ function AuthScreen({ onPick }) {
5632
6077
  useEffect(() => {
5633
6078
  refresh();
5634
6079
  }, [refresh]);
5635
- const [forceWizard, setForceWizard] = useState(false);
6080
+ const [configuring, setConfiguring] = useState(() => initialConfigureKey ? registry[initialConfigureKey] ?? null : null);
5636
6081
  const available = useMemo(() => providers.filter((p) => p.available), [providers]);
5637
- const onWizardDone = useCallback(() => {
5638
- setForceWizard(false);
5639
- refresh();
5640
- }, [refresh]);
5641
- if (available.length === 0 || forceWizard) {
5642
- const canCancel = forceWizard && available.length > 0;
5643
- return /* @__PURE__ */ jsx(SetupWizard, {
5644
- registry,
5645
- dataDir: config.paths.userDir,
5646
- onConfigured: onWizardDone,
5647
- onCancel: canCancel ? () => setForceWizard(false) : void 0
5648
- });
5649
- }
6082
+ const onWizardDone = useCallback((descriptorKey) => {
6083
+ setConfiguring(null);
6084
+ const detected = detectAuth(config.paths.userDir, registry);
6085
+ setProviders(detected);
6086
+ const configured = detected.find((p) => p.key === descriptorKey);
6087
+ if (configured?.available) onPick(configured);
6088
+ }, [
6089
+ config.paths.userDir,
6090
+ registry,
6091
+ onPick
6092
+ ]);
6093
+ const onSelectProvider = useCallback((p) => {
6094
+ if (p.available) {
6095
+ onPick(p);
6096
+ return;
6097
+ }
6098
+ const descriptor = registry[p.key];
6099
+ if (descriptor) setConfiguring(descriptor);
6100
+ }, [registry, onPick]);
6101
+ if (configuring) return /* @__PURE__ */ jsx(SetupWizard, {
6102
+ registry,
6103
+ dataDir: config.paths.userDir,
6104
+ initialDescriptor: configuring,
6105
+ onConfigured: onWizardDone,
6106
+ onCancel: available.length > 0 ? () => setConfiguring(null) : void 0
6107
+ });
6108
+ if (available.length === 0) return /* @__PURE__ */ jsx(SetupWizard, {
6109
+ registry,
6110
+ dataDir: config.paths.userDir,
6111
+ onConfigured: onWizardDone
6112
+ });
5650
6113
  const options = [...available.map((p) => ({
5651
6114
  name: p.label,
5652
6115
  description: p.methods.map((m) => m.detail).join(" · "),
5653
6116
  value: p.key
5654
- })), {
5655
- name: "+ add or re-configure a provider",
5656
- description: "launch the setup wizard",
5657
- value: WIZARD_OPTION_VALUE
5658
- }];
6117
+ })), ...providers.filter((p) => !p.available).map((p) => ({
6118
+ name: p.label,
6119
+ description: "not configured press ↵ to set up",
6120
+ value: p.key
6121
+ }))];
5659
6122
  return /* @__PURE__ */ jsxs("box", {
5660
6123
  style: {
5661
6124
  flexDirection: "column",
@@ -5676,57 +6139,114 @@ function AuthScreen({ onPick }) {
5676
6139
  wrapSelection: true,
5677
6140
  onSelect: (_idx, option) => {
5678
6141
  if (!option) return;
5679
- if (option.value === WIZARD_OPTION_VALUE) {
5680
- setForceWizard(true);
5681
- return;
5682
- }
5683
- const provider = findByKey(available, option.value);
5684
- if (provider) onPick(provider);
6142
+ const provider = findByKey(providers, option.value);
6143
+ if (provider) onSelectProvider(provider);
5685
6144
  },
5686
6145
  style: { flexGrow: 1 }
5687
6146
  })
5688
6147
  }), /* @__PURE__ */ jsx(TitleOverlay, { title: "pick a provider" })]
5689
6148
  });
5690
6149
  }
5691
- function SetupWizard({ registry, dataDir, onConfigured, onCancel }) {
5692
- const [step, setStep] = useState({ kind: "pick-provider" });
6150
+ /**
6151
+ * First credential step for a chosen provider. The method picker only earns
6152
+ * its place when there's a genuine choice — i.e. the provider supports OAuth
6153
+ * in addition to an API key. For API-key-only providers (e.g. `local`,
6154
+ * Cerebras), it's a single-option screen, so we skip it and go straight to the
6155
+ * customFields form / API-key input.
6156
+ */
6157
+ function firstCredentialStep(descriptor) {
6158
+ if (supportsOAuth(descriptor)) return {
6159
+ kind: "pick-method",
6160
+ descriptor
6161
+ };
6162
+ if (descriptor.customFields && descriptor.customFields.length > 0) return {
6163
+ kind: "enter-custom-fields",
6164
+ descriptor
6165
+ };
6166
+ return {
6167
+ kind: "enter-apikey",
6168
+ descriptor,
6169
+ customFields: {}
6170
+ };
6171
+ }
6172
+ function SetupWizard({ registry, dataDir, initialDescriptor, onConfigured, onCancel }) {
6173
+ const [step, setStep] = useState(initialDescriptor ? firstCredentialStep(initialDescriptor) : { kind: "pick-provider" });
5693
6174
  const [error, setError] = useState(null);
5694
6175
  const descriptors = useMemo(() => Object.values(registry), [registry]);
5695
6176
  const onPickProvider = useCallback((descriptor) => {
5696
6177
  setError(null);
5697
- setStep({
5698
- kind: "pick-method",
5699
- descriptor
5700
- });
6178
+ setStep(firstCredentialStep(descriptor));
5701
6179
  }, []);
5702
6180
  const onPickMethod = useCallback((descriptor, method) => {
5703
6181
  setError(null);
5704
- if (method === "apikey") setStep({
5705
- kind: "enter-apikey",
6182
+ if (method === "apikey") if (descriptor.customFields && descriptor.customFields.length > 0) setStep({
6183
+ kind: "enter-custom-fields",
5706
6184
  descriptor
5707
6185
  });
6186
+ else setStep({
6187
+ kind: "enter-apikey",
6188
+ descriptor,
6189
+ customFields: {}
6190
+ });
5708
6191
  else setStep({
5709
6192
  kind: "oauth-running",
5710
6193
  descriptor
5711
6194
  });
5712
6195
  }, []);
5713
- const onApiKeySubmit = useCallback((descriptor, value) => {
5714
- const trimmed = value.trim();
5715
- if (!trimmed) {
5716
- setError("API key cannot be empty.");
5717
- return;
5718
- }
6196
+ /**
6197
+ * Single write path for any wizard outcome that produces an apikey credential.
6198
+ * Centralizes credential persistence + env-var mirroring so the customFields,
6199
+ * API-key, and customFields-only flows can't drift out of sync.
6200
+ *
6201
+ * Env mirroring stays in lockstep with `applyApiKeyEnv`: anything we store
6202
+ * here is what that function would put back on the next launch, so the
6203
+ * in-process env reflects the on-disk truth.
6204
+ */
6205
+ const saveApiKeyCredential = useCallback((descriptor, apiKey, customFields) => {
5719
6206
  try {
5720
6207
  setProviderCredential(dataDir, descriptor, {
5721
6208
  kind: "apikey",
5722
- value: trimmed
6209
+ value: apiKey,
6210
+ ...Object.keys(customFields).length > 0 ? { customFields } : {}
5723
6211
  });
5724
- if (descriptor.envKey) process.env[descriptor.envKey] = trimmed;
5725
- onConfigured();
6212
+ if (descriptor.envKey && apiKey) process.env[descriptor.envKey] = apiKey;
6213
+ for (const field of descriptor.customFields ?? []) {
6214
+ const v = customFields[field.key];
6215
+ if (typeof v === "string" && v.length > 0) process.env[field.envVar] = v;
6216
+ }
6217
+ onConfigured(descriptor.key);
5726
6218
  } catch (err) {
5727
6219
  setError(errorMessage(err));
5728
6220
  }
5729
6221
  }, [dataDir, onConfigured]);
6222
+ const onCustomFieldsSubmit = useCallback((descriptor, values) => {
6223
+ const fields = descriptor.customFields ?? [];
6224
+ const missing = fields.find((f) => f.required && !values[f.key]?.trim());
6225
+ if (missing) {
6226
+ setError(`${missing.label} is required.`);
6227
+ return;
6228
+ }
6229
+ setError(null);
6230
+ const collected = {};
6231
+ for (const field of fields) {
6232
+ const v = values[field.key]?.trim();
6233
+ if (v) collected[field.key] = v;
6234
+ }
6235
+ if (descriptor.envKey) setStep({
6236
+ kind: "enter-apikey",
6237
+ descriptor,
6238
+ customFields: collected
6239
+ });
6240
+ else saveApiKeyCredential(descriptor, "", collected);
6241
+ }, [saveApiKeyCredential]);
6242
+ const onApiKeySubmit = useCallback((descriptor, customFields, value) => {
6243
+ const trimmed = value.trim();
6244
+ if (!trimmed) {
6245
+ setError("API key cannot be empty.");
6246
+ return;
6247
+ }
6248
+ saveApiKeyCredential(descriptor, trimmed, customFields);
6249
+ }, [saveApiKeyCredential]);
5730
6250
  const onOAuthError = useCallback((msg) => {
5731
6251
  setError(msg);
5732
6252
  setStep((prev) => prev.kind === "oauth-running" ? {
@@ -5734,6 +6254,12 @@ function SetupWizard({ registry, dataDir, onConfigured, onCancel }) {
5734
6254
  descriptor: prev.descriptor
5735
6255
  } : prev);
5736
6256
  }, []);
6257
+ const oauthDescriptorRef = useRef(null);
6258
+ oauthDescriptorRef.current = step.kind === "oauth-running" ? step.descriptor : oauthDescriptorRef.current;
6259
+ const onOAuthSuccess = useCallback(() => {
6260
+ const key = oauthDescriptorRef.current?.key;
6261
+ if (key) onConfigured(key);
6262
+ }, [onConfigured]);
5737
6263
  if (descriptors.length === 0) return /* @__PURE__ */ jsx(EmptyRegistryNotice, {});
5738
6264
  if (step.kind === "pick-provider") return /* @__PURE__ */ jsx(PickProviderStep, {
5739
6265
  descriptors,
@@ -5746,15 +6272,21 @@ function SetupWizard({ registry, dataDir, onConfigured, onCancel }) {
5746
6272
  error,
5747
6273
  onPick: onPickMethod
5748
6274
  });
6275
+ if (step.kind === "enter-custom-fields") return /* @__PURE__ */ jsx(EnterCustomFieldsStep, {
6276
+ descriptor: step.descriptor,
6277
+ error,
6278
+ onSubmit: onCustomFieldsSubmit
6279
+ });
5749
6280
  if (step.kind === "enter-apikey") return /* @__PURE__ */ jsx(EnterApiKeyStep, {
5750
6281
  descriptor: step.descriptor,
6282
+ customFields: step.customFields,
5751
6283
  error,
5752
6284
  onSubmit: onApiKeySubmit
5753
6285
  });
5754
6286
  return /* @__PURE__ */ jsx(OAuthRunningStep, {
5755
6287
  descriptor: step.descriptor,
5756
6288
  dataDir,
5757
- onSuccess: onConfigured,
6289
+ onSuccess: onOAuthSuccess,
5758
6290
  onError: onOAuthError
5759
6291
  });
5760
6292
  }
@@ -5802,6 +6334,15 @@ function WizardEscHint() {
5802
6334
  children: "esc to exit"
5803
6335
  });
5804
6336
  }
6337
+ /**
6338
+ * Defensive: older descriptor hints ended with a dangling "… Press" (the
6339
+ * one-field-at-a-time wizard appended " enter to continue"). The multi-field
6340
+ * form renders the hint standalone, so trim a trailing "Press" / "press"
6341
+ * (with optional period) to avoid a hanging word. No-op for clean hints.
6342
+ */
6343
+ function stripTrailingPressHint(hint) {
6344
+ return hint.replace(/\s*press\.?\s*$/i, "");
6345
+ }
5805
6346
  function EmptyRegistryNotice() {
5806
6347
  const COLOR = useColors();
5807
6348
  return /* @__PURE__ */ jsxs(WizardPanel, {
@@ -5881,9 +6422,23 @@ function PickMethodStep({ descriptor, error, onPick }) {
5881
6422
  const focused = useModalAwareFocus();
5882
6423
  const SELECT_THEME = useSelectStyle();
5883
6424
  const options = useMemo(() => {
6425
+ const hasCustomFields = (descriptor.customFields?.length ?? 0) > 0;
6426
+ const hasEnvKey = !!descriptor.envKey;
6427
+ let apikeyName;
6428
+ let apikeyDescription;
6429
+ if (!hasEnvKey && hasCustomFields) {
6430
+ apikeyName = "Configure";
6431
+ apikeyDescription = `configure ${descriptor.label} (no API key required)`;
6432
+ } else if (hasEnvKey && hasCustomFields) {
6433
+ apikeyName = "API key";
6434
+ apikeyDescription = `configure & paste your ${descriptor.label} API key`;
6435
+ } else {
6436
+ apikeyName = "API key";
6437
+ apikeyDescription = `paste your ${descriptor.label} API key`;
6438
+ }
5884
6439
  const items = [{
5885
- name: "API key",
5886
- description: `paste your ${descriptor.label} API key`,
6440
+ name: apikeyName,
6441
+ description: apikeyDescription,
5887
6442
  value: "apikey"
5888
6443
  }];
5889
6444
  if (supportsOAuth(descriptor)) {
@@ -5911,13 +6466,139 @@ function PickMethodStep({ descriptor, error, onPick }) {
5911
6466
  })]
5912
6467
  });
5913
6468
  }
5914
- function EnterApiKeyStep({ descriptor, error, onSubmit }) {
6469
+ /**
6470
+ * Multi-field form for {@link ProviderDescriptor.customFields}. All fields
6471
+ * render on a single page as a stack of inputs; the user moves between them
6472
+ * with ↑/↓ (or tab / shift-tab) and edits any value freely before submitting.
6473
+ * A trailing "save" row submits the whole form.
6474
+ *
6475
+ * Each `<input>` owns its own buffer (none are unmounted while navigating), so
6476
+ * returning to an earlier field shows what was already typed. Required fields
6477
+ * are validated on submit; the first missing one focuses + reports inline.
6478
+ */
6479
+ function EnterCustomFieldsStep({ descriptor, error, onSubmit }) {
6480
+ const screenFocused = useModalAwareFocus();
6481
+ const COLOR = useColors();
6482
+ const fields = useMemo(() => descriptor.customFields ?? [], [descriptor]);
6483
+ const inputRefs = useRef([]);
6484
+ const SAVE_ROW = fields.length;
6485
+ const [focusedRow, setFocusedRow] = useState(0);
6486
+ const collectValues = useCallback(() => {
6487
+ const values = {};
6488
+ fields.forEach((field, i) => {
6489
+ values[field.key] = inputRefs.current[i]?.value ?? "";
6490
+ });
6491
+ return values;
6492
+ }, [fields]);
6493
+ const submit = useCallback(() => {
6494
+ const values = collectValues();
6495
+ const missingIdx = fields.findIndex((f) => f.required && !values[f.key]?.trim());
6496
+ if (missingIdx >= 0) {
6497
+ setFocusedRow(missingIdx);
6498
+ onSubmit(descriptor, values);
6499
+ return;
6500
+ }
6501
+ onSubmit(descriptor, values);
6502
+ }, [
6503
+ descriptor,
6504
+ fields,
6505
+ collectValues,
6506
+ onSubmit
6507
+ ]);
6508
+ const move = useCallback((delta) => {
6509
+ setFocusedRow((prev) => {
6510
+ const next = prev + delta;
6511
+ if (next < 0) return 0;
6512
+ if (next > SAVE_ROW) return SAVE_ROW;
6513
+ return next;
6514
+ });
6515
+ }, [SAVE_ROW]);
6516
+ useKeyboard((key) => {
6517
+ if (!screenFocused) return;
6518
+ if (key.name === "up" || key.name === "tab" && key.shift) {
6519
+ move(-1);
6520
+ return;
6521
+ }
6522
+ if (key.name === "down" || key.name === "tab" && !key.shift) {
6523
+ move(1);
6524
+ return;
6525
+ }
6526
+ if (key.name === "return") if (focusedRow >= SAVE_ROW) submit();
6527
+ else move(1);
6528
+ });
6529
+ return /* @__PURE__ */ jsxs(WizardPanel, {
6530
+ title: `configure ${descriptor.label}`,
6531
+ error,
6532
+ children: [
6533
+ /* @__PURE__ */ jsxs("text", {
6534
+ fg: COLOR.dim,
6535
+ children: [
6536
+ /* @__PURE__ */ jsx("span", {
6537
+ fg: COLOR.model,
6538
+ children: "↑/↓"
6539
+ }),
6540
+ " to move between fields · ",
6541
+ /* @__PURE__ */ jsx("span", {
6542
+ fg: COLOR.model,
6543
+ children: "enter"
6544
+ }),
6545
+ " to advance / save · esc to exit"
6546
+ ]
6547
+ }),
6548
+ fields.map((field, i) => {
6549
+ const requiredTag = field.required ? " (required)" : " (optional)";
6550
+ const active = focusedRow === i;
6551
+ return /* @__PURE__ */ jsxs("box", {
6552
+ style: { flexDirection: "column" },
6553
+ children: [
6554
+ /* @__PURE__ */ jsx("text", {
6555
+ fg: active ? COLOR.model : COLOR.dim,
6556
+ children: `${field.label}${requiredTag}`
6557
+ }),
6558
+ field.hint && /* @__PURE__ */ jsx("text", {
6559
+ fg: COLOR.dim,
6560
+ children: stripTrailingPressHint(field.hint)
6561
+ }),
6562
+ /* @__PURE__ */ jsx("box", {
6563
+ style: {
6564
+ border: true,
6565
+ borderColor: active ? COLOR.borderActive : COLOR.border,
6566
+ paddingLeft: 1,
6567
+ paddingRight: 1,
6568
+ height: 3
6569
+ },
6570
+ children: /* @__PURE__ */ jsx("input", {
6571
+ ref: (r) => {
6572
+ inputRefs.current[i] = r;
6573
+ },
6574
+ focused: screenFocused && active,
6575
+ keyBindings: API_KEY_INPUT_BINDINGS,
6576
+ placeholder: field.placeholder ?? field.label,
6577
+ onSubmit: () => {},
6578
+ style: { flexGrow: 1 }
6579
+ })
6580
+ })
6581
+ ]
6582
+ }, field.key);
6583
+ }),
6584
+ /* @__PURE__ */ jsx("text", {
6585
+ fg: focusedRow === SAVE_ROW ? COLOR.brand : COLOR.dim,
6586
+ children: focusedRow === SAVE_ROW ? "▶ save" : " save"
6587
+ })
6588
+ ]
6589
+ });
6590
+ }
6591
+ function EnterApiKeyStep({ descriptor, customFields, error, onSubmit }) {
5915
6592
  const focused = useModalAwareFocus();
5916
6593
  const inputRef = useRef(null);
5917
6594
  const COLOR = useColors();
5918
6595
  const submit = useCallback(() => {
5919
- onSubmit(descriptor, inputRef.current?.value ?? "");
5920
- }, [descriptor, onSubmit]);
6596
+ onSubmit(descriptor, customFields, inputRef.current?.value ?? "");
6597
+ }, [
6598
+ descriptor,
6599
+ customFields,
6600
+ onSubmit
6601
+ ]);
5921
6602
  return /* @__PURE__ */ jsxs(WizardPanel, {
5922
6603
  title: `configure ${descriptor.label} — paste API key`,
5923
6604
  error,
@@ -6200,7 +6881,7 @@ function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreat
6200
6881
  const OVERLAY_RESERVED = 6;
6201
6882
  const TITLE_LEN = 8;
6202
6883
  const SEP_LEN = 3;
6203
- const allProjectsLen = showAllProjects ? 12 + SEP_LEN : 0;
6884
+ const allProjectsLen = showAllProjects ? 15 : 0;
6204
6885
  const countLen = countSegs.reduce((sum, s) => sum + s.text.length, 0);
6205
6886
  const cwdBudget = Math.max(0, termWidth - 4 - TITLE_LEN - OVERLAY_RESERVED) - countLen - SEP_LEN - allProjectsLen;
6206
6887
  if (cwdBudget < 6) return countSegs;
@@ -7033,7 +7714,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
7033
7714
  if (statSync(resolved).isFile()) {
7034
7715
  const buf = readFileSync(resolved);
7035
7716
  const ext = (resolved.split(".").pop() ?? "").toLowerCase();
7036
- const mediaType = IMAGE_MEDIA_TYPES[ext] ?? MIME_BY_EXT[ext] ?? "application/octet-stream";
7717
+ const mediaType = sniffImageMediaType(buf) ?? IMAGE_MEDIA_TYPES[ext] ?? MIME_BY_EXT[ext] ?? "application/octet-stream";
7037
7718
  setAttachments((prev) => [...prev, {
7038
7719
  name: basename(resolved),
7039
7720
  content: buf,
@@ -8173,7 +8854,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
8173
8854
  mcpToolsByServer
8174
8855
  ]);
8175
8856
  const filteredProviders = useMemo(() => (authentication?.providers ?? []).filter((p) => matchesQuery(providerCorpus(p), query)), [authentication?.providers, query]);
8176
- const authRowCount = filteredProviders.length + (authentication ? 1 : 0);
8857
+ const authRowCount = filteredProviders.length;
8177
8858
  const filteredSize = {
8178
8859
  ...Object.fromEntries(SETTINGS_CATEGORIES.map((c) => [c.id, filteredByCategory[c.id].length])),
8179
8860
  skills: filteredSkills.length,
@@ -8367,16 +9048,8 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
8367
9048
  return;
8368
9049
  }
8369
9050
  if (activeTab === "authentication") {
8370
- if (cursor === filteredProviders.length || !filteredProviders[cursor]) {
8371
- actions?.onReauth?.();
8372
- return;
8373
- }
8374
9051
  const provider = filteredProviders[cursor];
8375
- if (provider.available && actions?.onPickProvider) {
8376
- actions.onPickProvider(provider);
8377
- return;
8378
- }
8379
- actions?.onReauth?.();
9052
+ if (provider) actions?.onConfigureProvider?.(provider);
8380
9053
  return;
8381
9054
  }
8382
9055
  return;
@@ -8893,7 +9566,6 @@ function AuthenticationList({ view, providers, cursor, highlightBg, query }) {
8893
9566
  const COLOR = useColors();
8894
9567
  const home = homedir();
8895
9568
  const totalProviders = view.providers?.length ?? 0;
8896
- const addRowIndex = providers.length;
8897
9569
  return /* @__PURE__ */ jsxs("box", {
8898
9570
  style: { flexDirection: "column" },
8899
9571
  children: [
@@ -9012,44 +9684,10 @@ function AuthenticationList({ view, providers, cursor, highlightBg, query }) {
9012
9684
  children: ` ${p.methods.map((m) => `${m.source}: ${displayPath$1(m.detail, home)}`).join(" · ")}`
9013
9685
  })]
9014
9686
  }, p.key);
9015
- }),
9016
- /* @__PURE__ */ jsx(AuthAddRow, {
9017
- anchorIndex: addRowIndex,
9018
- focused: cursor === addRowIndex,
9019
- highlightBg
9020
9687
  })
9021
9688
  ]
9022
9689
  });
9023
9690
  }
9024
- function AuthAddRow({ anchorIndex, focused, highlightBg }) {
9025
- const COLOR = useColors();
9026
- const bg = focused ? highlightBg : void 0;
9027
- return /* @__PURE__ */ jsxs("box", {
9028
- id: anchorIdFor(anchorIndex),
9029
- style: {
9030
- flexDirection: "column",
9031
- flexShrink: 0,
9032
- marginTop: 1,
9033
- paddingLeft: 1,
9034
- paddingRight: 1,
9035
- backgroundColor: bg
9036
- },
9037
- children: [/* @__PURE__ */ jsxs("text", {
9038
- wrapMode: "none",
9039
- children: [/* @__PURE__ */ jsx("span", {
9040
- fg: focused ? COLOR.brand : COLOR.mute,
9041
- children: focused ? "▶ " : " "
9042
- }), /* @__PURE__ */ jsx("span", {
9043
- fg: focused ? COLOR.brand : COLOR.dim,
9044
- children: "+ add or re-configure a provider"
9045
- })]
9046
- }), /* @__PURE__ */ jsx("text", {
9047
- wrapMode: "none",
9048
- fg: COLOR.mute,
9049
- children: " launch the setup wizard"
9050
- })]
9051
- });
9052
- }
9053
9691
  function ToggleRow({ id, label, description, enabled, focused, bg }) {
9054
9692
  const COLOR = useColors();
9055
9693
  return /* @__PURE__ */ jsxs("box", {
@@ -9600,23 +10238,25 @@ const PREVIEW_CHAR_MAX = 8e3;
9600
10238
  * keeps a comfortable shape rather than stretching to the full height.
9601
10239
  */
9602
10240
  const MAX_MODAL_HEIGHT = 28;
9603
- const EDIT_TEXTAREA_BINDINGS = [
9604
- ...defaultTextareaKeyBindings.filter((b) => b.name !== "return" && !(b.name === "a" && b.ctrl && !b.shift && !b.meta)),
9605
- {
9606
- name: "a",
9607
- ctrl: true,
9608
- action: "select-all"
9609
- },
9610
- {
9611
- name: "return",
9612
- action: "submit"
9613
- },
9614
- {
9615
- name: "return",
9616
- shift: true,
9617
- action: "newline"
9618
- }
9619
- ];
10241
+ const EDIT_TEXTAREA_BINDINGS = (() => {
10242
+ return [
10243
+ ...defaultTextareaKeyBindings.filter((b) => b.name !== "return" && !(b.name === "a" && b.ctrl && !b.shift && !b.meta)),
10244
+ {
10245
+ name: "a",
10246
+ ctrl: true,
10247
+ action: "select-all"
10248
+ },
10249
+ {
10250
+ name: "return",
10251
+ action: "submit"
10252
+ },
10253
+ {
10254
+ name: "return",
10255
+ shift: true,
10256
+ action: "newline"
10257
+ }
10258
+ ];
10259
+ })();
9620
10260
  /**
9621
10261
  * Extract the editable text from a turn — joins all `text` blocks.
9622
10262
  * Non-text blocks (tool_call, tool_result, thinking, etc.) are structural
@@ -10304,6 +10944,7 @@ function AppShell() {
10304
10944
  if (!resumeProvider) return "auth";
10305
10945
  return lastResumedSessionId ? "chat" : "sessions";
10306
10946
  });
10947
+ const [authConfigureKey, setAuthConfigureKey] = useState(void 0);
10307
10948
  const [picked, setPicked] = useState(() => initialPicked);
10308
10949
  const pickedRef = useRef(picked);
10309
10950
  pickedRef.current = picked;
@@ -10523,13 +11164,12 @@ function AppShell() {
10523
11164
  const remembered = initialState.lastModelByProvider?.[provider.key];
10524
11165
  const model = modelId ?? remembered ?? descriptor.defaultModel ?? descriptor.factory().meta.defaultModel;
10525
11166
  const effort = effortForModel(descriptor, model, initialState.lastEffortByModel);
10526
- return effort ? {
11167
+ const opts = restoreModelOptions(descriptor, model, initialState.lastModelOptionsByModel);
11168
+ return {
10527
11169
  provider,
10528
11170
  model,
10529
- effort
10530
- } : {
10531
- provider,
10532
- model
11171
+ ...effort ? { effort } : {},
11172
+ ...opts ? { modelOptions: opts } : {}
10533
11173
  };
10534
11174
  }, [providerRegistry, initialState]);
10535
11175
  const cancelRunOnDenial = useCallback((reason) => {
@@ -10760,6 +11400,25 @@ function AppShell() {
10760
11400
  });
10761
11401
  agent.hooks.hook("stream:thinking", ({ delta, turnId }) => stream.queueStreamDelta("thinking", delta, { turnId }));
10762
11402
  agent.hooks.hook("stream:text", ({ delta, turnId }) => stream.queueStreamDelta("markdown", delta, { turnId }));
11403
+ agent.hooks.hook("stream:server_tool_use", ({ id, name, input, turnId }) => {
11404
+ stream.appendImmediate({
11405
+ kind: "tool",
11406
+ text: toolCallPreview(name, input),
11407
+ tool: name,
11408
+ input,
11409
+ callId: id,
11410
+ turnId
11411
+ });
11412
+ });
11413
+ agent.hooks.hook("stream:server_tool_result", ({ toolUseId, toolName, content, turnId }) => {
11414
+ stream.appendImmediate({
11415
+ kind: "tool-result",
11416
+ text: serverToolResultSummary(content),
11417
+ tool: toolName,
11418
+ callId: toolUseId,
11419
+ turnId
11420
+ });
11421
+ });
10763
11422
  agent.hooks.hook("tool:before", async ({ callId, name, input, turnId }) => {
10764
11423
  registerInFlightTool({
10765
11424
  callId,
@@ -10921,6 +11580,29 @@ function AppShell() {
10921
11580
  turnId
10922
11581
  });
10923
11582
  });
11583
+ agent.hooks.hook("child:stream:server_tool_use", ({ id, name, input, childId, depth, turnId }) => {
11584
+ stream.appendImmediate({
11585
+ kind: "tool",
11586
+ text: toolCallPreview(name, input),
11587
+ tool: name,
11588
+ input,
11589
+ callId: id,
11590
+ childId,
11591
+ depth,
11592
+ turnId
11593
+ });
11594
+ });
11595
+ agent.hooks.hook("child:stream:server_tool_result", ({ toolUseId, toolName, content, childId, depth, turnId }) => {
11596
+ stream.appendImmediate({
11597
+ kind: "tool-result",
11598
+ text: serverToolResultSummary(content),
11599
+ tool: toolName,
11600
+ callId: toolUseId,
11601
+ childId,
11602
+ depth,
11603
+ turnId
11604
+ });
11605
+ });
10924
11606
  agent.hooks.hook("child:tool:before", ({ callId, name, input, childId, depth, turnId, priorContent }) => {
10925
11607
  registerInFlightTool({
10926
11608
  callId,
@@ -11068,9 +11750,11 @@ function AppShell() {
11068
11750
  setEvents(eventsFromTurns(session.turns, session.runs));
11069
11751
  setBusy(true);
11070
11752
  try {
11753
+ const runModelOptions = enabledModelOptions(currentPicked.modelOptions);
11071
11754
  await agent.run({
11072
11755
  model: currentPicked.model,
11073
- ...currentPicked.effort ? { thinking: currentPicked.effort } : {}
11756
+ ...currentPicked.effort ? { thinking: currentPicked.effort } : {},
11757
+ ...Object.keys(runModelOptions).length > 0 ? { modelOptions: runModelOptions } : {}
11074
11758
  });
11075
11759
  await session.save().catch((err) => debugLog("resume-interaction: session.save failed", err));
11076
11760
  setCurrentSession((prev) => prev ? {
@@ -11193,6 +11877,7 @@ function AppShell() {
11193
11877
  const onPickProvider = useCallback(async (p) => {
11194
11878
  const next = makePicked(p);
11195
11879
  if (!next) return;
11880
+ setAuthConfigureKey(void 0);
11196
11881
  setPicked(next);
11197
11882
  stateStore.save({
11198
11883
  ...stateStore.load(),
@@ -11355,13 +12040,12 @@ function AppShell() {
11355
12040
  }
11356
12041
  });
11357
12042
  const nextEffort = descriptor ? effortForModel(descriptor, next.modelId, prior.lastEffortByModel) : void 0;
11358
- setPicked(nextEffort ? {
12043
+ const nextOptions = descriptor ? restoreModelOptions(descriptor, next.modelId, prior.lastModelOptionsByModel) : void 0;
12044
+ setPicked({
11359
12045
  provider: nextProvider,
11360
12046
  model: next.modelId,
11361
- effort: nextEffort
11362
- } : {
11363
- provider: nextProvider,
11364
- model: next.modelId
12047
+ ...nextEffort ? { effort: nextEffort } : {},
12048
+ ...nextOptions ? { modelOptions: nextOptions } : {}
11365
12049
  });
11366
12050
  modal.close();
11367
12051
  if (providerChanged && currentSession && !busy && !pendingApproval) await activateSession(currentSession.id, nextProvider.key);
@@ -11394,6 +12078,32 @@ function AppShell() {
11394
12078
  });
11395
12079
  modal.close();
11396
12080
  }, [modal, stateStore]);
12081
+ const onToggleModelOption = useCallback((optionId) => {
12082
+ setPicked((prev) => {
12083
+ if (!prev) return prev;
12084
+ const next = {
12085
+ ...prev.modelOptions ?? {},
12086
+ [optionId]: !prev.modelOptions?.[optionId]
12087
+ };
12088
+ const prior = stateStore.load();
12089
+ stateStore.save({
12090
+ ...prior,
12091
+ lastModelOptionsByModel: {
12092
+ ...prior.lastModelOptionsByModel,
12093
+ [prev.model]: next
12094
+ }
12095
+ });
12096
+ return {
12097
+ ...prev,
12098
+ modelOptions: next
12099
+ };
12100
+ });
12101
+ }, [stateStore]);
12102
+ const modelOptions = useMemo(() => {
12103
+ if (!picked) return [];
12104
+ const descriptor = providerRegistry[picked.provider.key];
12105
+ return descriptor ? modelOptionsFor(descriptor, picked.model) : [];
12106
+ }, [picked, providerRegistry]);
11397
12107
  const modelHasReasoning = useMemo(() => {
11398
12108
  if (!picked) return false;
11399
12109
  const descriptor = providerRegistry[picked.provider.key];
@@ -11497,10 +12207,12 @@ function AppShell() {
11497
12207
  name: att.name
11498
12208
  };
11499
12209
  })];
12210
+ const runModelOptions = enabledModelOptions(picked.modelOptions);
11500
12211
  await agent.run({
11501
12212
  model: picked.model,
11502
12213
  prompt: runPrompt,
11503
- ...picked.effort ? { thinking: picked.effort } : {}
12214
+ ...picked.effort ? { thinking: picked.effort } : {},
12215
+ ...Object.keys(runModelOptions).length > 0 ? { modelOptions: runModelOptions } : {}
11504
12216
  });
11505
12217
  await session.save().catch((err) => debugLog("session.save failed", err));
11506
12218
  setCurrentSession((prev) => prev ? {
@@ -11558,10 +12270,11 @@ function AppShell() {
11558
12270
  }
11559
12271
  })();
11560
12272
  }, [picked, runSingleMessage]);
11561
- const onReauth = useMemo(() => {
12273
+ const onConfigureProvider = useMemo(() => {
11562
12274
  if (busy || pendingApproval) return void 0;
11563
- return () => {
12275
+ return (provider) => {
11564
12276
  modal.close();
12277
+ setAuthConfigureKey(provider.key);
11565
12278
  setScreen("auth");
11566
12279
  };
11567
12280
  }, [
@@ -11954,7 +12667,7 @@ function AppShell() {
11954
12667
  restoredFiles: restoration.restoredFiles,
11955
12668
  restoredSkills: restoration.restoredSkills,
11956
12669
  model: result.model,
11957
- inputTokens: (result.usage.input ?? 0) + (result.usage.cacheRead ?? 0) + (result.usage.cacheCreation ?? 0),
12670
+ inputTokens: effectiveInputFromTurn(result.usage),
11958
12671
  outputTokens: result.usage.output ?? 0,
11959
12672
  effectiveTokens
11960
12673
  };
@@ -12147,10 +12860,6 @@ function AppShell() {
12147
12860
  }
12148
12861
  if (matchesBinding(key, keybindings.openSettings) && screen !== "auth") {
12149
12862
  const allProviders = detectAuth(config.paths.userDir, providerRegistry);
12150
- const onPickProviderFromSettings = (provider) => {
12151
- modal.close();
12152
- onPickProvider(provider);
12153
- };
12154
12863
  modal.open(/* @__PURE__ */ jsx(SettingsModal, {
12155
12864
  keybindings,
12156
12865
  keybindingsPath: keybindingsPath(config.paths.userDir),
@@ -12161,8 +12870,7 @@ function AppShell() {
12161
12870
  providers: allProviders
12162
12871
  },
12163
12872
  actions: {
12164
- onReauth,
12165
- onPickProvider: onPickProviderFromSettings,
12873
+ onConfigureProvider,
12166
12874
  onOpenKeybindings: onOpenKeybindingsFile,
12167
12875
  onLoginMcp,
12168
12876
  onLogoutMcp,
@@ -12196,12 +12904,16 @@ function AppShell() {
12196
12904
  }));
12197
12905
  return;
12198
12906
  }
12199
- if (matchesBinding(key, keybindings.openEffortPicker) && screen === "chat" && picked && !busy && modelHasReasoning) {
12907
+ if (matchesBinding(key, keybindings.openEffortPicker) && screen === "chat" && picked && !busy && (modelHasReasoning || modelOptions.length > 0)) {
12200
12908
  const descriptor = providerRegistry[picked.provider.key];
12201
- modal.open(/* @__PURE__ */ jsx(EffortPickerModal, {
12202
- current: picked.effort,
12909
+ modal.open(/* @__PURE__ */ jsx(ModelOptionsPickerModal, {
12910
+ supportsReasoning: modelHasReasoning,
12203
12911
  supportsAdaptive: !!descriptor && piIdOf(descriptor) === "anthropic",
12204
- onPick: onPickEffort
12912
+ currentEffort: picked.effort,
12913
+ options: modelOptions,
12914
+ enabled: picked.modelOptions,
12915
+ onPickEffort,
12916
+ onToggleOption: onToggleModelOption
12205
12917
  }));
12206
12918
  return;
12207
12919
  }
@@ -12212,6 +12924,21 @@ function AppShell() {
12212
12924
  }));
12213
12925
  return;
12214
12926
  }
12927
+ if (matchesBinding(key, keybindings.openContextPanel) && screen === "chat" && picked) {
12928
+ const agent = agentRef.current;
12929
+ if (!agent) return;
12930
+ const descriptor = providerRegistry[picked.provider.key];
12931
+ const effWindow = effectiveContextWindow(descriptor ? getContextWindow(descriptor, picked.model) : null) ?? void 0;
12932
+ agent.getContextBreakdown({
12933
+ model: picked.model,
12934
+ ...effWindow !== void 0 ? { effectiveWindow: effWindow } : {},
12935
+ autocompactBuffer: OUTPUT_RESERVE_TOKENS,
12936
+ ...settings.autoCompact && settings.autoCompactThreshold ? { compactThreshold: settings.autoCompactThreshold } : {}
12937
+ }).then((breakdown) => {
12938
+ if (breakdown && agentRef.current === agent) modal.open(/* @__PURE__ */ jsx(ContextPanelModal, { breakdown }));
12939
+ }).catch((err) => debugLog("getContextBreakdown failed", err));
12940
+ return;
12941
+ }
12215
12942
  if (matchesBinding(key, keybindings.openKeybindings) && screen !== "auth") {
12216
12943
  modal.open(/* @__PURE__ */ jsx(KeybindingsModal, {
12217
12944
  bindings: keybindings,
@@ -12353,6 +13080,10 @@ function AppShell() {
12353
13080
  modelColor: COLOR.model,
12354
13081
  effortLabel: modelHasReasoning ? picked?.effort ?? "medium" : null,
12355
13082
  effortColor: COLOR.warn,
13083
+ modelOptionsLabel: (() => {
13084
+ const on = modelOptions.filter((o) => picked?.modelOptions?.[o.id]);
13085
+ return on.length > 0 ? on.map((o) => o.id).join(", ") : null;
13086
+ })(),
12356
13087
  effortKeyColor: COLOR.warn,
12357
13088
  agentLabel: pickedAgent.label,
12358
13089
  agentColor: accentColor(pickedAgent.accent, COLOR),
@@ -12374,6 +13105,7 @@ function AppShell() {
12374
13105
  pickedAgent,
12375
13106
  COLOR,
12376
13107
  modelHasReasoning,
13108
+ modelOptions,
12377
13109
  keybindings,
12378
13110
  inFlightTools,
12379
13111
  backgroundTasks,
@@ -12438,7 +13170,10 @@ function AppShell() {
12438
13170
  paddingRight: 1
12439
13171
  },
12440
13172
  children: [
12441
- screen === "auth" && /* @__PURE__ */ jsx(AuthScreen, { onPick: onPickProvider }),
13173
+ screen === "auth" && /* @__PURE__ */ jsx(AuthScreen, {
13174
+ onPick: onPickProvider,
13175
+ initialConfigureKey: authConfigureKey
13176
+ }),
12442
13177
  screen === "sessions" && /* @__PURE__ */ jsx(SessionsScreen, {
12443
13178
  sessions,
12444
13179
  currentId: currentSession?.id ?? null,
@@ -12725,6 +13460,198 @@ function initTreeSitterWorker() {
12725
13460
  return getTreeSitterClient().initialize();
12726
13461
  }
12727
13462
  //#endregion
13463
+ //#region src/tui/effort-picker.tsx
13464
+ const BASE_LEVELS = [
13465
+ {
13466
+ id: "off",
13467
+ description: "no reasoning — fastest, smallest output"
13468
+ },
13469
+ {
13470
+ id: "minimal",
13471
+ description: "tiny reasoning budget (gpt-5 family)"
13472
+ },
13473
+ {
13474
+ id: "low",
13475
+ description: "short reasoning pass"
13476
+ },
13477
+ {
13478
+ id: "medium",
13479
+ description: "balanced — sensible default"
13480
+ },
13481
+ {
13482
+ id: "high",
13483
+ description: "deep reasoning — slowest, longest"
13484
+ }
13485
+ ];
13486
+ const ADAPTIVE_LEVEL = {
13487
+ id: "adaptive",
13488
+ description: "model decides per-turn (Anthropic)"
13489
+ };
13490
+ function EffortPickerModal({ current, supportsAdaptive, onPick }) {
13491
+ const COLOR = useColors();
13492
+ const SURFACE = useSurfaces();
13493
+ const inputRef = useRef(null);
13494
+ const [query, setQuery] = useState("");
13495
+ const levels = useMemo(() => {
13496
+ return (supportsAdaptive ? [...BASE_LEVELS, ADAPTIVE_LEVEL] : BASE_LEVELS).map((l) => ({
13497
+ ...l,
13498
+ searchCorpus: `${l.id} ${l.description}`.toLowerCase()
13499
+ }));
13500
+ }, [supportsAdaptive]);
13501
+ const filtered = useMemo(() => {
13502
+ const trimmed = query.trim().toLowerCase();
13503
+ if (!trimmed) return levels;
13504
+ const terms = trimmed.split(/\s+/);
13505
+ return levels.filter((l) => terms.every((t) => l.searchCorpus.includes(t)));
13506
+ }, [levels, query]);
13507
+ const [selectedIdx, setSelectedIdx] = useState(() => {
13508
+ const idx = levels.findIndex((l) => l.id === current);
13509
+ if (idx >= 0) return idx;
13510
+ const fallback = levels.findIndex((l) => l.id === "medium");
13511
+ return fallback < 0 ? 0 : fallback;
13512
+ });
13513
+ const handleQueryChange = useCallback((next) => {
13514
+ setQuery(next);
13515
+ setSelectedIdx(0);
13516
+ }, []);
13517
+ const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1);
13518
+ const commit = () => {
13519
+ const row = filtered[safeIndex];
13520
+ if (row) onPick(row.id);
13521
+ };
13522
+ useEffect(() => {
13523
+ inputRef.current?.focus();
13524
+ }, []);
13525
+ useKeyboard((key) => {
13526
+ if (key.name === "up") {
13527
+ setSelectedIdx((i) => {
13528
+ if (filtered.length === 0) return i;
13529
+ return ((i - 1) % filtered.length + filtered.length) % filtered.length;
13530
+ });
13531
+ return;
13532
+ }
13533
+ if (key.name === "down") {
13534
+ setSelectedIdx((i) => {
13535
+ if (filtered.length === 0) return i;
13536
+ return (i + 1) % filtered.length;
13537
+ });
13538
+ return;
13539
+ }
13540
+ if (key.name === "return") commit();
13541
+ });
13542
+ return /* @__PURE__ */ jsxs(Modal, {
13543
+ title: "select reasoning effort",
13544
+ maxWidth: 80,
13545
+ children: [
13546
+ /* @__PURE__ */ jsx("box", {
13547
+ style: {
13548
+ border: true,
13549
+ borderColor: COLOR.borderActive,
13550
+ paddingLeft: 1,
13551
+ paddingRight: 1,
13552
+ height: 3
13553
+ },
13554
+ children: /* @__PURE__ */ jsx("input", {
13555
+ ref: inputRef,
13556
+ focused: true,
13557
+ placeholder: "search effort levels…",
13558
+ onInput: handleQueryChange,
13559
+ onSubmit: () => {},
13560
+ style: { flexGrow: 1 }
13561
+ })
13562
+ }),
13563
+ /* @__PURE__ */ jsx("box", {
13564
+ style: {
13565
+ flexDirection: "column",
13566
+ flexShrink: 0
13567
+ },
13568
+ children: filtered.length === 0 ? /* @__PURE__ */ jsxs("text", {
13569
+ fg: COLOR.dim,
13570
+ children: [/* @__PURE__ */ jsx("span", {
13571
+ fg: COLOR.mute,
13572
+ children: "no levels match "
13573
+ }), /* @__PURE__ */ jsx("span", {
13574
+ fg: COLOR.warn,
13575
+ children: query.trim()
13576
+ })]
13577
+ }) : filtered.map((level, i) => /* @__PURE__ */ jsx(EffortRow, {
13578
+ level,
13579
+ isCurrent: level.id === current,
13580
+ isFocused: i === safeIndex,
13581
+ highlightBg: SURFACE.selection
13582
+ }, level.id))
13583
+ }),
13584
+ /* @__PURE__ */ jsxs("text", {
13585
+ fg: COLOR.dim,
13586
+ children: [
13587
+ /* @__PURE__ */ jsx("span", {
13588
+ fg: COLOR.warn,
13589
+ children: "↑↓"
13590
+ }),
13591
+ " navigate · ",
13592
+ /* @__PURE__ */ jsx("span", {
13593
+ fg: COLOR.warn,
13594
+ children: "↵"
13595
+ }),
13596
+ " select · ",
13597
+ /* @__PURE__ */ jsx("span", {
13598
+ fg: COLOR.warn,
13599
+ children: "esc"
13600
+ }),
13601
+ " close · ",
13602
+ /* @__PURE__ */ jsx("span", {
13603
+ fg: COLOR.mute,
13604
+ children: `${filtered.length} / ${levels.length} level${levels.length === 1 ? "" : "s"}`
13605
+ })
13606
+ ]
13607
+ })
13608
+ ]
13609
+ });
13610
+ }
13611
+ /**
13612
+ * Single row in the picker. Mirrors `ModelRow` in `model-picker.tsx`:
13613
+ * `●` marker for the current pick, single-space middle-dot separators,
13614
+ * focused row gets the `surfaces.selection` background lift.
13615
+ */
13616
+ function EffortRow({ level, isCurrent, isFocused, highlightBg }) {
13617
+ const COLOR = useColors();
13618
+ const marker = isCurrent ? "●" : " ";
13619
+ return /* @__PURE__ */ jsx("box", {
13620
+ style: {
13621
+ height: 1,
13622
+ paddingLeft: 1,
13623
+ paddingRight: 1,
13624
+ flexShrink: 0,
13625
+ backgroundColor: isFocused ? highlightBg : void 0
13626
+ },
13627
+ children: /* @__PURE__ */ jsxs("text", {
13628
+ wrapMode: "none",
13629
+ children: [
13630
+ /* @__PURE__ */ jsx("span", {
13631
+ fg: isCurrent ? COLOR.brand : COLOR.mute,
13632
+ children: marker
13633
+ }),
13634
+ /* @__PURE__ */ jsx("span", {
13635
+ fg: COLOR.mute,
13636
+ children: " "
13637
+ }),
13638
+ /* @__PURE__ */ jsx("span", {
13639
+ fg: isFocused ? COLOR.brand : COLOR.dim,
13640
+ children: level.id
13641
+ }),
13642
+ /* @__PURE__ */ jsx("span", {
13643
+ fg: COLOR.mute,
13644
+ children: " · "
13645
+ }),
13646
+ /* @__PURE__ */ jsx("span", {
13647
+ fg: COLOR.mute,
13648
+ children: level.description
13649
+ })
13650
+ ]
13651
+ })
13652
+ });
13653
+ }
13654
+ //#endregion
12728
13655
  //#region src/tui/mcps-settings.tsx
12729
13656
  /**
12730
13657
  * MCP server picker. Hierarchical: each server is one row, and an
@@ -13590,6 +14517,6 @@ async function runTui(options = {}) {
13590
14517
  process.exit(0);
13591
14518
  }
13592
14519
  //#endregion
13593
- export { AgentPickerModal, App, AuthScreen, ChatScreen, CompletionPopup, EffortPickerModal, Footer, InteractionBlock, McpsSettingsModal, Modal, ModalRoot, ModelPickerModal, SessionDetailsModal, SessionsScreen, SettingsModal, SkillsSettingsModal, Spinner, StatusSpinner, TOOL_DISPLAY, TitleOverlay, ToggleListModal, Transcript, TurnDetailsModal, accentColor, buildMdStyle, clipHintsToWidth, computeTurnAnchors, displayNameFor, formatToolCall, hintsLength, isEditErrorResult, isTurnHighlighted, isVisible, marginTopFor, onInputSubmit, renderHintSpans, runTui, selectableTurnIds, splitMarkdownCodeBlocks, splitPromptSegments, truncateTrailing, turnSelectionOwnership, useMdStyle, useModal, useModalAwareFocus };
14520
+ export { AgentPickerModal, App, AuthScreen, ChatScreen, CompletionPopup, EffortPickerModal, Footer, InteractionBlock, McpsSettingsModal, Modal, ModalRoot, ModelOptionsPickerModal, ModelPickerModal, SessionDetailsModal, SessionsScreen, SettingsModal, SkillsSettingsModal, Spinner, StatusSpinner, TOOL_DISPLAY, TitleOverlay, ToggleListModal, Transcript, TurnDetailsModal, accentColor, buildMdStyle, clipHintsToWidth, computeTurnAnchors, displayNameFor, formatToolCall, hintsLength, isEditErrorResult, isTurnHighlighted, isVisible, marginTopFor, onInputSubmit, renderHintSpans, runTui, selectableTurnIds, splitMarkdownCodeBlocks, splitPromptSegments, truncateTrailing, turnSelectionOwnership, useMdStyle, useModal, useModalAwareFocus };
13594
14521
 
13595
14522
  //# sourceMappingURL=tui.js.map