zidane 5.5.4 → 5.6.0

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 (69) hide show
  1. package/README.md +7 -1
  2. package/dist/{agent-CMAklak7.d.ts → agent-B26FuGew.d.ts} +90 -2
  3. package/dist/agent-B26FuGew.d.ts.map +1 -0
  4. package/dist/chat.d.ts +133 -22
  5. package/dist/chat.d.ts.map +1 -1
  6. package/dist/chat.js +3 -3
  7. package/dist/{errors-C5VSakmT.js → errors-DdZXnyXE.js} +38 -2
  8. package/dist/errors-DdZXnyXE.js.map +1 -0
  9. package/dist/{index-CF5QwBiz.d.ts → index-CE7z_11T.d.ts} +2 -2
  10. package/dist/{index-CF5QwBiz.d.ts.map → index-CE7z_11T.d.ts.map} +1 -1
  11. package/dist/{index-kroGomhj.d.ts → index-CROWxXo9.d.ts} +23 -2
  12. package/dist/index-CROWxXo9.d.ts.map +1 -0
  13. package/dist/index.d.ts +4 -4
  14. package/dist/index.js +10 -10
  15. package/dist/{interpolate-Cvjy8gpk.js → interpolate-j5V-wcAQ.js} +2 -2
  16. package/dist/{interpolate-Cvjy8gpk.js.map → interpolate-j5V-wcAQ.js.map} +1 -1
  17. package/dist/{login-B_kfoGMP.js → login-D5lQWoFx.js} +3 -3
  18. package/dist/{login-B_kfoGMP.js.map → login-D5lQWoFx.js.map} +1 -1
  19. package/dist/{mcp-BE43Viwi.js → mcp-ngMS0S6N.js} +2 -2
  20. package/dist/{mcp-BE43Viwi.js.map → mcp-ngMS0S6N.js.map} +1 -1
  21. package/dist/mcp.d.ts +1 -1
  22. package/dist/mcp.js +1 -1
  23. package/dist/{messages-BBWakTN6.js → messages-B5k4DAXy.js} +2 -2
  24. package/dist/{messages-BBWakTN6.js.map → messages-B5k4DAXy.js.map} +1 -1
  25. package/dist/{presets-BDvBZuYI.js → presets-BDCthpyD.js} +2 -2
  26. package/dist/{presets-BDvBZuYI.js.map → presets-BDCthpyD.js.map} +1 -1
  27. package/dist/presets.d.ts +2 -2
  28. package/dist/presets.js +1 -1
  29. package/dist/{providers-CsUyN_FJ.js → providers-CaJE2ToS.js} +3 -3
  30. package/dist/{providers-CsUyN_FJ.js.map → providers-CaJE2ToS.js.map} +1 -1
  31. package/dist/providers.d.ts +1 -1
  32. package/dist/providers.js +2 -2
  33. package/dist/restate.d.ts +1 -1
  34. package/dist/session/sqlite.d.ts +1 -1
  35. package/dist/session/sqlite.d.ts.map +1 -1
  36. package/dist/session/sqlite.js +226 -51
  37. package/dist/session/sqlite.js.map +1 -1
  38. package/dist/{session-DzfRacU_.js → session-BoEW_wCR.js} +2 -2
  39. package/dist/{session-DzfRacU_.js.map → session-BoEW_wCR.js.map} +1 -1
  40. package/dist/session.d.ts +1 -1
  41. package/dist/session.js +2 -2
  42. package/dist/skills.d.ts +2 -2
  43. package/dist/skills.js +1 -1
  44. package/dist/{tools-Bbd0Ivwn.js → tools-Co3VYhgM.js} +154 -15
  45. package/dist/tools-Co3VYhgM.js.map +1 -0
  46. package/dist/tools.d.ts +2 -2
  47. package/dist/tools.js +1 -1
  48. package/dist/{transcript-anchors-C79AszkC.d.ts → transcript-anchors-CTTeQJzy.d.ts} +12 -4
  49. package/dist/{transcript-anchors-C79AszkC.d.ts.map → transcript-anchors-CTTeQJzy.d.ts.map} +1 -1
  50. package/dist/tui.d.ts +2 -2
  51. package/dist/tui.d.ts.map +1 -1
  52. package/dist/tui.js +432 -83
  53. package/dist/tui.js.map +1 -1
  54. package/dist/{turn-operations-CGf7wWF0.js → turn-operations-fhinWY4m.js} +134 -18
  55. package/dist/turn-operations-fhinWY4m.js.map +1 -0
  56. package/dist/types-oKPBdCmL.js.map +1 -1
  57. package/dist/types.d.ts +3 -3
  58. package/dist/types.js +2 -2
  59. package/docs/ARCHITECTURE.md +5 -2
  60. package/docs/CHAT.md +10 -3
  61. package/docs/RESTATE.md +190 -0
  62. package/docs/SKILL.md +27 -2
  63. package/docs/TUI.md +3 -3
  64. package/package.json +1 -1
  65. package/dist/agent-CMAklak7.d.ts.map +0 -1
  66. package/dist/errors-C5VSakmT.js.map +0 -1
  67. package/dist/index-kroGomhj.d.ts.map +0 -1
  68. package/dist/tools-Bbd0Ivwn.js.map +0 -1
  69. package/dist/turn-operations-CGf7wWF0.js.map +0 -1
package/dist/tui.js CHANGED
@@ -1,10 +1,10 @@
1
- import { $ as useMcpAuthDispatch, $n as tryOpenBrowser, A as getSafelist, At as resolveChipColor, B as supportsOAuth, Br as accentColor, Bt as useDiscoveryOptional, Cn as mergeApprovalAndBodyOutcomes, Ct as SETTINGS_CHOICES, D as useSafeModeQueue, Dn as stripEditOutcomesAnnotation, Dr as getContextWindow, Dt as useSettings, E as useSafeModeActions, En as rewriteMultiEditHeader, Et as clampFps, F as suggestSafelistEntry, Fn as matchesBinding, H as filterModelCatalog, Hn as uniqueSkillNamesFromReferences, Ht as useConfig, Jt as isEditErrorResult, K as discoverProjectMcps, Kr as TODO_STATUS_GLYPHS, Kt as deriveSessionTitle, L as splitPromptSegments, Lt as createDiscoverySlot, Nn as ensureKeybindingsFile, Nr as piIdOf, On as summarizeOutcomes, Q as McpAuthProvider, Qn as buildLinearRamp, Qt as listSessionMeta, R as formatPathForCwd, Rt as DiscoveryProvider, St as DEFAULT_SETTINGS, T as SafeModeProvider, Tn as resolveApprovalForPayload, Tt as SettingsProvider, U as indexOfEntry, Ut as resolveConfig, V as buildModelCatalog, Vn as createSkillsCompletionProvider, Vt as ConfigProvider, W as buildMcpServers, Wn as createFilesCompletionProvider, Wt as EDIT_TOOL_NAMES, Xn as useCompletion, Xt as isVisible, Y as createFileMcpCredentialStore, Yt as isTurnHighlighted, Zn as blendHsl, Zt as lastContextSizeFromTurns, _ as turnContextSize, _n as previewEditPayload, _t as truncateTrailing, a as computeTurnAnchors, at as InteractionsProvider, b as defaultSkillScanPaths, bt as listProjectFiles, c as formatToolCall, cn as turnSelectionOwnership, ct as createInteractionTools, d as useSelectStyle, dn as buildContextualDiff, dt as pendingInteractionsFromTurns, en as marginTopFor, et as useMcpAuthState, f as useSurfaces, fn as buildUnifiedDiff, fr as shouldAutoCompact, g as finalizeStreamingMarkdownForOwner, gn as filetypeFromPath, gt as hintsLength, h as finalizeStreamingMarkdown, hn as extractEditPayload, ht as clipHintsToWidth, i as turnAsText, in as sumRunCosts, j as isOnSafelist, jt as resolveTheme, k as addToSafelist, kn as findGitRoot, kr as modelSupportsReasoning, l as ThemeProvider, ln as updateToolEventOutcomes, m as useTheme, mi as buildPlanSystem, mt as useInteractionsQueue, n as deleteTurnSafely, ni as useActiveTodos, nn as selectableTurnIds, nr as buildUpdateHint, o as TOOL_DISPLAY, on as toolCallPreview, pi as buildBuildSystem, pr as detectAuth, pt as useInteractionsActions, qt as eventsFromTurns, r as truncateTurnsAt, rn as stripSpawnTokensLine, rr as useUpdateCheck, rt as splitMarkdownCodeBlocks, s as displayNameFor, sn as toolResultText, st as buildResumedToolResultsTurn, tr as bootTick, tt as getMcpAuthStatus, u as useColors, ut as makeRequestInteraction, v as useStreamBuffer, w as writeSessionExport, wn as parseEditOutcomesFromResult, wt as SETTINGS_TOGGLES, x as discoverProjectSkills, xn as buildEditOutcomesAnnotation, xt as useEnabledToggleSet, y as buildSkillsConfig, yn as summarizeEditPayload, yr as setProviderCredential, yt as generateSessionTitle, z as runOAuthLogin, zt as useDiscovery } from "./turn-operations-CGf7wWF0.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-Bbd0Ivwn.js";
3
- import { s as errorMessage } from "./errors-C5VSakmT.js";
4
- import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-BE43Viwi.js";
5
- import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-B_kfoGMP.js";
1
+ import { $n as blendHsl, $t as lastContextSizeFromTurns, A as getSafelist, An as summarizeOutcomes, Ar as getContextWindow, B as oauthUsesManualCodePaste, Bt as DiscoveryProvider, Cn as buildEditOutcomesAnnotation, Ct as useEnabledToggleSet, D as useSafeModeQueue, Dn as resolveApprovalForPayload, Dt as SettingsProvider, E as useSafeModeActions, En as parseEditOutcomesFromResult, Et as SETTINGS_TOGGLES, F as suggestSafelistEntry, Fn as ensureKeybindingsFile, G as indexOfEntry, Gt as resolveConfig, H as supportsOAuth, Ht as useDiscoveryOptional, Ir as piIdOf, J as discoverProjectMcps, Jt as deriveSessionTitle, K as buildMcpServers, Kn as createFilesCompletionProvider, Kt as EDIT_TOOL_NAMES, L as splitPromptSegments, Ln as matchesBinding, Mr as modelSupportsReasoning, Mt as resolveChipColor, Nt as resolveTheme, On as rewriteMultiEditHeader, Ot as clampFps, Qn as useCompletion, Qt as isVisible, R as formatPathForCwd, Sr as setProviderCredential, St as listProjectFiles, T as SafeModeProvider, Tn as mergeApprovalAndBodyOutcomes, Tt as SETTINGS_CHOICES, U as buildModelCatalog, Un as createSkillsCompletionProvider, Ur as accentColor, Ut as ConfigProvider, V as runOAuthLogin, Vt as useDiscovery, W as filterModelCatalog, Wn as uniqueSkillNamesFromReferences, Wt as useConfig, Xt as isEditErrorResult, Yr as TODO_STATUS_GLYPHS, Yt as eventsFromTurns, Z as createFileMcpCredentialStore, Zt as isTurnHighlighted, _ as turnContextSize, _i as buildPlanSystem, _n as extractEditPayload, _t as clipHintsToWidth, a as computeTurnAnchors, ai as useActiveTodos, an as stripSpawnTokensLine, ar as useUpdateCheck, at as splitMarkdownCodeBlocks, b as defaultSkillScanPaths, c as formatToolCall, cn as toolCallPreview, d as useSelectStyle, dn as updateToolEventOutcomes, en as listSessionMeta, er as buildLinearRamp, et as McpAuthProvider, f as useSurfaces, ft as makeRequestInteraction, g as finalizeStreamingMarkdownForOwner, gi as buildBuildSystem, gr as detectAuth, gt as useInteractionsQueue, h as finalizeStreamingMarkdown, hr as shouldAutoCompact, ht as useInteractionsActions, i as turnAsText, in as selectableTurnIds, ir as buildUpdateHint, j as isOnSafelist, jn as findGitRoot, k as addToSafelist, kn as stripEditOutcomesAnnotation, kt as useSettings, l as ThemeProvider, ln as toolResultText, lt as buildResumedToolResultsTurn, m as useTheme, mn as buildUnifiedDiff, mr as AUTO_COMPACT_MIN_GROWTH_FRACTION, n as deleteTurnSafely, nn as marginTopFor, nt as useMcpAuthState, o as TOOL_DISPLAY, on as sumRunCosts, pn as buildContextualDiff, pt as pendingInteractionsFromTurns, r as truncateTurnsAt, rr as bootTick, rt as getMcpAuthStatus, s as displayNameFor, st as InteractionsProvider, tr as tryOpenBrowser, tt as useMcpAuthDispatch, u as useColors, un as turnSelectionOwnership, ut as createInteractionTools, v as useStreamBuffer, vn as filetypeFromPath, vt as hintsLength, w as writeSessionExport, wt as DEFAULT_SETTINGS, x as discoverProjectSkills, xn as summarizeEditPayload, xt as generateSessionTitle, y as buildSkillsConfig, yn as previewEditPayload, yt as truncateTrailing, z as fetchOAuthRedirect, zt as createDiscoverySlot } from "./turn-operations-fhinWY4m.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-Co3VYhgM.js";
3
+ import { c as errorMessage } from "./errors-DdZXnyXE.js";
4
+ import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-ngMS0S6N.js";
5
+ import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-D5lQWoFx.js";
6
6
  import { n as formatTokenUsage } from "./stats-Lc3zL3RM.js";
7
- import { n as loadSession, t as createSession } from "./session-DzfRacU_.js";
7
+ import { n as loadSession, t as createSession } from "./session-BoEW_wCR.js";
8
8
  import { createTuiStore } from "./session/sqlite.js";
9
9
  import { homedir } from "node:os";
10
10
  import { spawn } from "node:child_process";
@@ -4637,6 +4637,282 @@ function OptionList({ items, initialCursor, onPick }) {
4637
4637
  });
4638
4638
  }
4639
4639
  //#endregion
4640
+ //#region src/tui/oauth-url-block.tsx
4641
+ /** @jsxImportSource @opentui/react */
4642
+ /**
4643
+ * Long URL rendered as N single-row OSC 8 hyperlinks instead of one
4644
+ * wrapped hyperlink.
4645
+ *
4646
+ * Why split: OpenTUI packs a `linkId` per cell into the attribute
4647
+ * bitfield, but its renderer emits an OSC 8 open/close pair per visual
4648
+ * row without the spec's `id=` parameter. Terminals (notably iTerm2)
4649
+ * need a matching `id=` to stitch hyperlink fragments across rows —
4650
+ * without it, only one row of a wrapped link ends up clickable. We
4651
+ * pre-chunk the URL into rows that fit on one line and render each as
4652
+ * its own intact `<a href>` with `wrapMode="none"`, so the terminal
4653
+ * never sees a wrapped hyperlink and clicking any row opens the full
4654
+ * URL.
4655
+ *
4656
+ * Width: caller passes a hard cap (its container's content-area width).
4657
+ * We further clamp by the live terminal width minus `chromeWidth` so a
4658
+ * narrow terminal still chunks short enough to avoid forced wrap by
4659
+ * the layout engine. `chromeWidth` is the container's border + padding
4660
+ * budget — default 14 fits a typical modal; pass a smaller value for
4661
+ * less-padded containers (e.g. 6 for the auth wizard panel).
4662
+ */
4663
+ function OAuthUrlBlock({ url, fg, maxLineWidth, chromeWidth = 14 }) {
4664
+ const { width: termWidth } = useTerminalDimensions();
4665
+ return /* @__PURE__ */ jsx(Fragment, { children: chunkString(url, Math.max(20, Math.min(maxLineWidth, termWidth - chromeWidth))).map((line, i) => /* @__PURE__ */ jsx("text", {
4666
+ wrapMode: "none",
4667
+ fg,
4668
+ children: /* @__PURE__ */ jsx("a", {
4669
+ href: url,
4670
+ children: line
4671
+ })
4672
+ }, i)) });
4673
+ }
4674
+ function chunkString(s, n) {
4675
+ if (s.length <= n) return [s];
4676
+ const out = [];
4677
+ for (let i = 0; i < s.length; i += n) out.push(s.slice(i, i + n));
4678
+ return out;
4679
+ }
4680
+ //#endregion
4681
+ //#region src/tui/oauth-auth-block.tsx
4682
+ /** Keystroke shown next to the open-browser button. */
4683
+ const OPEN_BROWSER_KEY = "ctrl+b";
4684
+ /**
4685
+ * Unified affordance for surfacing an OAuth authorization URL in the TUI:
4686
+ *
4687
+ * 1. A prominent OSC 8 hyperlink button — "Click here to open auth URL in
4688
+ * browser". Honored by every terminal that supports clickable links
4689
+ * (iTerm2, Kitty, WezTerm, Ghostty, Alacritty, …). Lands on the user's
4690
+ * LOCAL terminal even over SSH because OSC 8 is interpreted client-side.
4691
+ * 2. The full URL rendered greyed below, chunked into per-row hyperlinks
4692
+ * via {@link OAuthUrlBlock}. Backup channel for terminals without OSC 8,
4693
+ * and for users who'd rather copy via drag-select than click.
4694
+ * 3. Optional paste-back input. When `paste` is provided, an `<input>`
4695
+ * renders below the URL so the user can paste the FULL redirect URL
4696
+ * their browser ended up at after authorizing — useful when zidane runs
4697
+ * over SSH and the browser-side redirect to loopback can't reach the
4698
+ * remote callback server. The caller's `onSubmit` typically pipes the
4699
+ * pasted value through {@link fetchOAuthRedirect} so the in-process
4700
+ * server receives the request and the OAuth promise resolves through
4701
+ * the same happy path a real browser would have taken.
4702
+ *
4703
+ * The component owns presentation only — keyboard focus, input ref, and
4704
+ * the submit handler stay with the caller so the affordance composes
4705
+ * cleanly with whatever picker / wizard / modal it lives in.
4706
+ */
4707
+ function OAuthAuthBlock({ authUrl, maxLineWidth, chromeWidth = 14, paste }) {
4708
+ const COLOR = useColors();
4709
+ return /* @__PURE__ */ jsxs("box", {
4710
+ style: {
4711
+ flexDirection: "column",
4712
+ gap: 1
4713
+ },
4714
+ children: [
4715
+ /* @__PURE__ */ jsx(OpenBrowserButton, { authUrl }),
4716
+ /* @__PURE__ */ jsxs("box", {
4717
+ style: { flexDirection: "column" },
4718
+ children: [/* @__PURE__ */ jsx("text", {
4719
+ fg: COLOR.mute,
4720
+ children: "or copy the URL manually:"
4721
+ }), /* @__PURE__ */ jsx(OAuthUrlBlock, {
4722
+ url: authUrl,
4723
+ fg: COLOR.dim,
4724
+ maxLineWidth,
4725
+ chromeWidth
4726
+ })]
4727
+ }),
4728
+ paste && /* @__PURE__ */ jsxs("box", {
4729
+ style: { flexDirection: "column" },
4730
+ children: [
4731
+ /* @__PURE__ */ jsx("text", {
4732
+ fg: COLOR.mute,
4733
+ children: "or — if the browser couldn't reach this machine (SSH, firewall) — paste the URL it tried to redirect to:"
4734
+ }),
4735
+ paste.hint && /* @__PURE__ */ jsx("text", {
4736
+ fg: toneColor(paste.hint.tone, COLOR),
4737
+ children: paste.hint.text
4738
+ }),
4739
+ /* @__PURE__ */ jsx("box", {
4740
+ style: {
4741
+ border: true,
4742
+ borderColor: paste.focused ? COLOR.borderActive : COLOR.border,
4743
+ paddingLeft: 1,
4744
+ paddingRight: 1,
4745
+ height: 3
4746
+ },
4747
+ children: /* @__PURE__ */ jsx("input", {
4748
+ ref: paste.inputRef,
4749
+ focused: paste.focused,
4750
+ placeholder: paste.placeholder ?? "paste redirect URL and press enter…",
4751
+ onSubmit: paste.onSubmit,
4752
+ style: { flexGrow: 1 }
4753
+ })
4754
+ })
4755
+ ]
4756
+ })
4757
+ ]
4758
+ });
4759
+ }
4760
+ /**
4761
+ * Real button: a bordered, brand-colored box hosting a labeled keystroke
4762
+ * hint. Pressing `ctrl+b` (anywhere this block is mounted) calls
4763
+ * {@link tryOpenBrowser} to launch the URL via the OS handler AND
4764
+ * {@link writeToClipboard} so the URL also lands in the user's clipboard
4765
+ * (OSC 52 → works over SSH, native helper → works locally). Both fire
4766
+ * because either alone can fail silently — `open`/`xdg-open` are no-ops
4767
+ * on a headless box, and OSC 52 is disabled on some terminals — and the
4768
+ * combination covers both failure modes without prompting again.
4769
+ *
4770
+ * The visible label is also wrapped in an OSC 8 hyperlink so a mouse
4771
+ * click in a supporting terminal (iTerm2, Kitty, WezTerm, …) reaches the
4772
+ * same browser. The keybind is the authoritative path — it doesn't
4773
+ * depend on terminal capability — and the hyperlink is the bonus.
4774
+ */
4775
+ function OpenBrowserButton({ authUrl }) {
4776
+ const COLOR = useColors();
4777
+ const [feedback, setFeedback] = useState(null);
4778
+ const feedbackTimer = useRef(null);
4779
+ const trigger = useCallback(() => {
4780
+ tryOpenBrowser(authUrl);
4781
+ setFeedback(writeToClipboard(authUrl) ? "opened in browser · URL copied to clipboard" : "opened in browser");
4782
+ if (feedbackTimer.current) clearTimeout(feedbackTimer.current);
4783
+ feedbackTimer.current = setTimeout(setFeedback, 4e3, null);
4784
+ }, [authUrl]);
4785
+ useKeyboard((key) => {
4786
+ if (key.ctrl && key.name === "b") {
4787
+ key.preventDefault();
4788
+ trigger();
4789
+ }
4790
+ });
4791
+ useEffect(() => () => {
4792
+ if (feedbackTimer.current) clearTimeout(feedbackTimer.current);
4793
+ }, []);
4794
+ return /* @__PURE__ */ jsxs("box", {
4795
+ style: { flexDirection: "column" },
4796
+ children: [/* @__PURE__ */ jsx("box", {
4797
+ style: {
4798
+ border: true,
4799
+ borderColor: COLOR.brand,
4800
+ paddingLeft: 1,
4801
+ paddingRight: 1,
4802
+ alignSelf: "flex-start"
4803
+ },
4804
+ children: /* @__PURE__ */ jsxs("text", {
4805
+ wrapMode: "none",
4806
+ children: [
4807
+ /* @__PURE__ */ jsx("a", {
4808
+ href: authUrl,
4809
+ fg: COLOR.brand,
4810
+ children: "↗ Open auth URL in browser"
4811
+ }),
4812
+ /* @__PURE__ */ jsx("span", {
4813
+ fg: COLOR.mute,
4814
+ children: " "
4815
+ }),
4816
+ /* @__PURE__ */ jsx("span", {
4817
+ fg: COLOR.warn,
4818
+ children: OPEN_BROWSER_KEY
4819
+ }),
4820
+ /* @__PURE__ */ jsx("span", {
4821
+ fg: COLOR.mute,
4822
+ children: " open · click also works"
4823
+ })
4824
+ ]
4825
+ })
4826
+ }), feedback && /* @__PURE__ */ jsx("text", {
4827
+ fg: COLOR.accent,
4828
+ children: `✓ ${feedback}`
4829
+ })]
4830
+ });
4831
+ }
4832
+ function toneColor(tone, COLOR) {
4833
+ switch (tone) {
4834
+ case "error": return COLOR.error;
4835
+ case "accent": return COLOR.accent;
4836
+ case "dim": return COLOR.dim;
4837
+ }
4838
+ }
4839
+ /**
4840
+ * Self-contained MCP authorizing panel:
4841
+ *
4842
+ * - Renders the {@link OAuthAuthBlock} for the URL + paste input.
4843
+ * - Owns the input ref, the submit handler, and the hint state.
4844
+ * - Auto-fetches the pasted URL via {@link fetchOAuthRedirect} so the
4845
+ * MCP SDK's loopback callback server resolves the OAuth promise
4846
+ * through the same code path a real browser-redirect would have
4847
+ * taken — works over SSH where the browser can't reach the remote
4848
+ * callback server directly.
4849
+ *
4850
+ * `inputFocused` is forwarded as the input's `focused` prop AND read by
4851
+ * the parent picker's `useKeyboard` (via a ref) to suppress single-key
4852
+ * shortcuts (`l` / `o` / `r`) while the user is typing into the input.
4853
+ */
4854
+ function McpAuthorizingPanel({ serverName, authUrl, maxLineWidth, chromeWidth, inputFocused }) {
4855
+ const COLOR = useColors();
4856
+ const inputRef = useRef(null);
4857
+ const [hint, setHint] = useState(null);
4858
+ const onSubmit = useCallback(() => {
4859
+ const value = inputRef.current?.value?.trim() ?? "";
4860
+ if (!value) return;
4861
+ setHint({
4862
+ text: "submitting redirect URL…",
4863
+ tone: "dim"
4864
+ });
4865
+ (async () => {
4866
+ try {
4867
+ const result = await fetchOAuthRedirect(value);
4868
+ if (result.status >= 200 && result.status < 300) setHint({
4869
+ text: "redirect accepted — finalizing…",
4870
+ tone: "accent"
4871
+ });
4872
+ else setHint({
4873
+ text: `callback server rejected the URL (${result.status}${result.message ? `: ${result.message}` : ""}). Cancel and retry.`,
4874
+ tone: "error"
4875
+ });
4876
+ } catch (err) {
4877
+ setHint({
4878
+ text: errorMessage(err),
4879
+ tone: "error"
4880
+ });
4881
+ }
4882
+ })();
4883
+ }, []);
4884
+ return /* @__PURE__ */ jsxs("box", {
4885
+ style: {
4886
+ flexDirection: "column",
4887
+ border: ["top"],
4888
+ borderColor: COLOR.border,
4889
+ paddingTop: 1
4890
+ },
4891
+ children: [
4892
+ /* @__PURE__ */ jsx("text", {
4893
+ fg: COLOR.brand,
4894
+ children: `Authorizing ${serverName}`
4895
+ }),
4896
+ /* @__PURE__ */ jsx("text", {
4897
+ fg: COLOR.dim,
4898
+ children: "Your browser should have opened — complete the login, then return here."
4899
+ }),
4900
+ /* @__PURE__ */ jsx(OAuthAuthBlock, {
4901
+ authUrl,
4902
+ maxLineWidth,
4903
+ chromeWidth,
4904
+ paste: {
4905
+ inputRef,
4906
+ focused: inputFocused,
4907
+ onSubmit,
4908
+ placeholder: "paste redirect URL and press enter…",
4909
+ hint: hint ?? void 0
4910
+ }
4911
+ })
4912
+ ]
4913
+ });
4914
+ }
4915
+ //#endregion
4640
4916
  //#region src/tui/todo-indicator.tsx
4641
4917
  /**
4642
4918
  * Layout budget when truncating the content. The bar never wraps —
@@ -5085,9 +5361,15 @@ function EnterApiKeyStep({ descriptor, error, onSubmit }) {
5085
5361
  });
5086
5362
  }
5087
5363
  function OAuthRunningStep({ descriptor, dataDir, onSuccess, onError }) {
5364
+ const usesManualPaste = oauthUsesManualCodePaste(descriptor);
5088
5365
  const [url, setUrl] = useState(null);
5089
- const [status, setStatus] = useState("starting browser…");
5090
- const COLOR = useColors();
5366
+ const [status, setStatus] = useState(usesManualPaste ? "opening browser…" : "starting browser…");
5367
+ const [pending, setPending] = useState(null);
5368
+ const [pasteHint, setPasteHint] = useState(null);
5369
+ const focused = useModalAwareFocus();
5370
+ const inputRef = useRef(null);
5371
+ const pendingRef = useRef(null);
5372
+ pendingRef.current = pending;
5091
5373
  useEffect(() => {
5092
5374
  const ac = new AbortController();
5093
5375
  let cancelled = false;
@@ -5097,8 +5379,22 @@ function OAuthRunningStep({ descriptor, dataDir, onSuccess, onError }) {
5097
5379
  onUrl: (loginUrl) => {
5098
5380
  if (cancelled) return;
5099
5381
  setUrl(loginUrl);
5100
- setStatus("waiting for browser callback…");
5382
+ setStatus(usesManualPaste ? "complete the login in your browser, then paste the code below" : "waiting for browser callback…");
5101
5383
  },
5384
+ onPrompt: (prompt) => new Promise((resolve, reject) => {
5385
+ if (cancelled) {
5386
+ reject(/* @__PURE__ */ new Error("OAuth flow cancelled"));
5387
+ return;
5388
+ }
5389
+ pendingRef.current?.reject(/* @__PURE__ */ new Error("superseded by a newer OAuth prompt"));
5390
+ setPending({
5391
+ message: prompt.message,
5392
+ placeholder: prompt.placeholder,
5393
+ allowEmpty: prompt.allowEmpty,
5394
+ resolve,
5395
+ reject
5396
+ });
5397
+ }),
5102
5398
  onProgress: (message) => {
5103
5399
  if (!cancelled) setStatus(message);
5104
5400
  },
@@ -5117,31 +5413,70 @@ function OAuthRunningStep({ descriptor, dataDir, onSuccess, onError }) {
5117
5413
  })();
5118
5414
  return () => {
5119
5415
  cancelled = true;
5416
+ pendingRef.current?.reject(/* @__PURE__ */ new Error("OAuth flow cancelled"));
5417
+ pendingRef.current = null;
5120
5418
  ac.abort();
5121
5419
  };
5122
5420
  }, [
5123
5421
  descriptor,
5124
5422
  dataDir,
5125
5423
  onSuccess,
5126
- onError
5424
+ onError,
5425
+ usesManualPaste
5127
5426
  ]);
5427
+ const submitInput = useCallback(() => {
5428
+ const value = inputRef.current?.value?.trim() ?? "";
5429
+ const current = pendingRef.current;
5430
+ if (current) {
5431
+ if (!value && !current.allowEmpty) return;
5432
+ pendingRef.current = null;
5433
+ setPending(null);
5434
+ setStatus("exchanging code…");
5435
+ current.resolve(value);
5436
+ return;
5437
+ }
5438
+ if (!value) return;
5439
+ setPasteHint({
5440
+ text: "submitting redirect URL…",
5441
+ tone: "dim"
5442
+ });
5443
+ (async () => {
5444
+ try {
5445
+ const result = await fetchOAuthRedirect(value);
5446
+ if (result.status >= 200 && result.status < 300) {
5447
+ setPasteHint({
5448
+ text: "redirect accepted — exchanging code…",
5449
+ tone: "accent"
5450
+ });
5451
+ setStatus("exchanging code…");
5452
+ } else setPasteHint({
5453
+ text: `callback server rejected the URL (${result.status}${result.message ? `: ${result.message}` : ""}). Try again or restart the flow.`,
5454
+ tone: "error"
5455
+ });
5456
+ } catch (err) {
5457
+ setPasteHint({
5458
+ text: errorMessage(err),
5459
+ tone: "error"
5460
+ });
5461
+ }
5462
+ })();
5463
+ }, []);
5128
5464
  return /* @__PURE__ */ jsxs(WizardPanel, {
5129
5465
  title: `configure ${descriptor.label} — OAuth`,
5130
5466
  children: [
5131
5467
  /* @__PURE__ */ jsx(WizardEscHint, {}),
5132
- /* @__PURE__ */ jsx(Spinner, { label: status }),
5133
- url && /* @__PURE__ */ jsxs("box", {
5134
- style: {
5135
- flexDirection: "column",
5136
- gap: 0
5137
- },
5138
- children: [/* @__PURE__ */ jsx("text", {
5139
- fg: COLOR.dim,
5140
- children: "If the browser didn't open, visit:"
5141
- }), /* @__PURE__ */ jsx("text", {
5142
- fg: COLOR.model,
5143
- children: url
5144
- })]
5468
+ pending ? /* @__PURE__ */ jsx(Spinner, { label: pending.message }) : /* @__PURE__ */ jsx(Spinner, { label: status }),
5469
+ url && /* @__PURE__ */ jsx(OAuthAuthBlock, {
5470
+ authUrl: url,
5471
+ maxLineWidth: 200,
5472
+ chromeWidth: 6,
5473
+ paste: {
5474
+ inputRef,
5475
+ focused,
5476
+ onSubmit: submitInput,
5477
+ placeholder: pending?.placeholder ?? "paste redirect URL (or code) and press enter…",
5478
+ hint: pasteHint ?? void 0
5479
+ }
5145
5480
  })
5146
5481
  ]
5147
5482
  });
@@ -7106,7 +7441,13 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
7106
7441
  ]);
7107
7442
  const focusedMcp = filteredMcps[cursor];
7108
7443
  const focusedMcpStatus = focusedMcp ? getMcpAuthStatus(authState, focusedMcp.config.name) : void 0;
7444
+ const oauthPasteActive = activeTab === "mcps" && focusedMcpStatus?.kind === "authorizing" && !!focusedMcpStatus.url;
7109
7445
  useKeyboard((key) => {
7446
+ if (key.name === "escape" && oauthPasteActive && focusedMcp) {
7447
+ actions?.onCancelLoginMcp?.(focusedMcp.config.name);
7448
+ return;
7449
+ }
7450
+ if (oauthPasteActive) return;
7110
7451
  if (!key.ctrl && !key.meta && !key.shift && (key.name === "left" || key.name === "right")) {
7111
7452
  key.preventDefault();
7112
7453
  const idx = TAB_ORDER.indexOf(activeTab);
@@ -7203,7 +7544,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
7203
7544
  },
7204
7545
  children: /* @__PURE__ */ jsx("input", {
7205
7546
  ref: inputRef,
7206
- focused: true,
7547
+ focused: !oauthPasteActive,
7207
7548
  placeholder: searchPlaceholder(activeTab),
7208
7549
  onInput: handleQueryChange,
7209
7550
  onSubmit: () => {},
@@ -7623,35 +7964,29 @@ function EmptyRow({ label }) {
7623
7964
  });
7624
7965
  }
7625
7966
  function renderMcpDetailPanel(entry, status, COLOR) {
7626
- if (status.kind === "authorizing") return /* @__PURE__ */ jsxs("box", {
7627
- style: {
7628
- flexDirection: "column",
7629
- border: ["top"],
7630
- borderColor: COLOR.border,
7631
- paddingTop: 1
7632
- },
7633
- children: [
7634
- /* @__PURE__ */ jsx("text", {
7967
+ if (status.kind === "authorizing") {
7968
+ if (!status.url) return /* @__PURE__ */ jsxs("box", {
7969
+ style: {
7970
+ flexDirection: "column",
7971
+ border: ["top"],
7972
+ borderColor: COLOR.border,
7973
+ paddingTop: 1
7974
+ },
7975
+ children: [/* @__PURE__ */ jsx("text", {
7635
7976
  fg: COLOR.brand,
7636
7977
  children: `Authorizing ${entry.config.name}`
7637
- }),
7638
- /* @__PURE__ */ jsx("text", {
7639
- fg: COLOR.dim,
7640
- children: "Your browser should have opened — complete the login, then return here."
7641
- }),
7642
- status.url && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("text", {
7643
- fg: COLOR.mute,
7644
- children: "If it didn't, click the URL below (or copy it):"
7645
7978
  }), /* @__PURE__ */ jsx("text", {
7646
- wrapMode: "char",
7647
- fg: COLOR.model,
7648
- children: /* @__PURE__ */ jsx("a", {
7649
- href: status.url,
7650
- children: status.url
7651
- })
7652
- })] })
7653
- ]
7654
- });
7979
+ fg: COLOR.dim,
7980
+ children: "Starting login flow…"
7981
+ })]
7982
+ });
7983
+ return /* @__PURE__ */ jsx(McpAuthorizingPanel, {
7984
+ serverName: entry.config.name,
7985
+ authUrl: status.url,
7986
+ maxLineWidth: 134,
7987
+ inputFocused: true
7988
+ });
7989
+ }
7655
7990
  if (status.kind === "error") return /* @__PURE__ */ jsxs("box", {
7656
7991
  style: {
7657
7992
  flexDirection: "column",
@@ -8591,6 +8926,15 @@ function AppShell() {
8591
8926
  useEffect(() => {
8592
8927
  autoCompactThresholdRef.current = settings.autoCompactThreshold;
8593
8928
  }, [settings.autoCompactThreshold]);
8929
+ /**
8930
+ * Post-compaction baseline for the hysteresis check in
8931
+ * {@link shouldAutoCompact}. Latched to the effective post-compact token
8932
+ * count whenever a compaction lands (see {@link onCompactSession}), reset
8933
+ * to `undefined` on session change so the first compaction in a fresh
8934
+ * session fires off the absolute threshold without any growth gating.
8935
+ * Pairs with {@link AUTO_COMPACT_MIN_GROWTH_FRACTION} in the predicate.
8936
+ */
8937
+ const lastCompactedInputTokensRef = useRef(void 0);
8594
8938
  const smoothStreamingRef = useRef(settings.smoothStreaming);
8595
8939
  useEffect(() => {
8596
8940
  smoothStreamingRef.current = settings.smoothStreaming;
@@ -9470,6 +9814,7 @@ function AppShell() {
9470
9814
  const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
9471
9815
  setLastInputTokens(replayedTokens);
9472
9816
  lastInputTokensRef.current = replayedTokens;
9817
+ lastCompactedInputTokensRef.current = void 0;
9473
9818
  setSessionCost(sumRunCosts(session.runs));
9474
9819
  setCurrentSession({
9475
9820
  id: session.id,
@@ -10048,6 +10393,7 @@ function AppShell() {
10048
10393
  const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
10049
10394
  setLastInputTokens(replayedTokens);
10050
10395
  lastInputTokensRef.current = replayedTokens;
10396
+ lastCompactedInputTokensRef.current = void 0;
10051
10397
  setSessionCost(sumRunCosts(session.runs));
10052
10398
  setCurrentSession((prev) => prev ? {
10053
10399
  ...prev,
@@ -10085,6 +10431,7 @@ function AppShell() {
10085
10431
  const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
10086
10432
  setLastInputTokens(replayedTokens);
10087
10433
  lastInputTokensRef.current = replayedTokens;
10434
+ lastCompactedInputTokensRef.current = void 0;
10088
10435
  setCurrentSession((prev) => prev ? {
10089
10436
  ...prev,
10090
10437
  updatedAt: Date.now()
@@ -10241,6 +10588,7 @@ function AppShell() {
10241
10588
  setEvents(eventsFromTurns(liveSession.turns, liveSession.runs));
10242
10589
  setLastInputTokens(effectiveTokens);
10243
10590
  lastInputTokensRef.current = effectiveTokens;
10591
+ lastCompactedInputTokensRef.current = effectiveTokens;
10244
10592
  }
10245
10593
  setCurrentSession((prev) => prev && prev.id === sessionId ? {
10246
10594
  ...prev,
@@ -10283,7 +10631,9 @@ function AppShell() {
10283
10631
  threshold: autoCompactThresholdRef.current,
10284
10632
  inputTokens: lastInputTokensRef.current,
10285
10633
  rawContextWindow: rawWindow,
10286
- alreadyCompacting: !!autoCompactInFlightRef.current
10634
+ alreadyCompacting: !!autoCompactInFlightRef.current,
10635
+ ...lastCompactedInputTokensRef.current !== void 0 ? { lastCompactedInputTokens: lastCompactedInputTokensRef.current } : {},
10636
+ minGrowthFraction: AUTO_COMPACT_MIN_GROWTH_FRACTION
10287
10637
  });
10288
10638
  if (decision.kind !== "fire") return;
10289
10639
  const pct = Math.round(decision.usedFraction * 100);
@@ -11104,7 +11454,18 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin,
11104
11454
  return ((prev + delta) % catalog.length + catalog.length) % catalog.length;
11105
11455
  }), [catalog.length]);
11106
11456
  const safeCursor = Math.min(cursor, Math.max(0, catalog.length - 1));
11457
+ const focusedEntry = catalog[safeCursor];
11458
+ const focusedStatus = focusedEntry ? getMcpAuthStatus(authState, focusedEntry.config.name) : void 0;
11459
+ const inputActive = focusedStatus?.kind === "authorizing" && !!focusedStatus.url;
11107
11460
  useKeyboard((key) => {
11461
+ if (key.name === "escape" && focusedEntry) {
11462
+ const name = focusedEntry.config.name;
11463
+ if (getMcpAuthStatus(authState, name).kind === "authorizing") {
11464
+ onCancelLogin(name);
11465
+ return;
11466
+ }
11467
+ }
11468
+ if (inputActive) return;
11108
11469
  if (key.name === "up" || key.name === "k" || key.ctrl && key.name === "p") {
11109
11470
  moveCursor(-1);
11110
11471
  return;
@@ -11118,10 +11479,6 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin,
11118
11479
  if (!entry) return;
11119
11480
  const name = entry.config.name;
11120
11481
  const status = getMcpAuthStatus(authState, name);
11121
- if (key.name === "escape" && status.kind === "authorizing") {
11122
- onCancelLogin(name);
11123
- return;
11124
- }
11125
11482
  if (key.name === "return" || key.name === "space") {
11126
11483
  toggle(name);
11127
11484
  return;
@@ -11209,8 +11566,6 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin,
11209
11566
  })
11210
11567
  ]
11211
11568
  });
11212
- const focusedEntry = catalog[safeCursor];
11213
- const focusedStatus = focusedEntry ? getMcpAuthStatus(authState, focusedEntry.config.name) : void 0;
11214
11569
  return /* @__PURE__ */ jsxs(Modal, {
11215
11570
  title: ` mcp servers · ${enabledSet.size} / ${catalog.length} enabled `,
11216
11571
  children: [
@@ -11300,35 +11655,29 @@ function renderInlineBadge(status, COLOR) {
11300
11655
  * itself to the content automatically).
11301
11656
  */
11302
11657
  function renderDetailPanel(entry, status, COLOR) {
11303
- if (status.kind === "authorizing") return /* @__PURE__ */ jsxs("box", {
11304
- style: {
11305
- flexDirection: "column",
11306
- border: ["top"],
11307
- borderColor: COLOR.border,
11308
- paddingTop: 1
11309
- },
11310
- children: [
11311
- /* @__PURE__ */ jsx("text", {
11658
+ if (status.kind === "authorizing") {
11659
+ if (!status.url) return /* @__PURE__ */ jsxs("box", {
11660
+ style: {
11661
+ flexDirection: "column",
11662
+ border: ["top"],
11663
+ borderColor: COLOR.border,
11664
+ paddingTop: 1
11665
+ },
11666
+ children: [/* @__PURE__ */ jsx("text", {
11312
11667
  fg: COLOR.brand,
11313
11668
  children: `Authorizing ${entry.config.name}`
11314
- }),
11315
- /* @__PURE__ */ jsx("text", {
11316
- fg: COLOR.dim,
11317
- children: "Your browser should have opened — complete the login, then return here."
11318
- }),
11319
- status.url && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("text", {
11320
- fg: COLOR.mute,
11321
- children: "If it didn't, click the URL below (or copy it):"
11322
11669
  }), /* @__PURE__ */ jsx("text", {
11323
- wrapMode: "char",
11324
- fg: COLOR.model,
11325
- children: /* @__PURE__ */ jsx("a", {
11326
- href: status.url,
11327
- children: status.url
11328
- })
11329
- })] })
11330
- ]
11331
- });
11670
+ fg: COLOR.dim,
11671
+ children: "Starting login flow…"
11672
+ })]
11673
+ });
11674
+ return /* @__PURE__ */ jsx(McpAuthorizingPanel, {
11675
+ serverName: entry.config.name,
11676
+ authUrl: status.url,
11677
+ maxLineWidth: 86,
11678
+ inputFocused: true
11679
+ });
11680
+ }
11332
11681
  if (status.kind === "error") return /* @__PURE__ */ jsxs("box", {
11333
11682
  style: {
11334
11683
  flexDirection: "column",