zidane 6.3.6 → 6.3.8

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 (74) hide show
  1. package/dist/{acp-Xkhrpx8r.js → acp-CAX3tOEY.js} +2 -2
  2. package/dist/{acp-Xkhrpx8r.js.map → acp-CAX3tOEY.js.map} +1 -1
  3. package/dist/acp-cli.js +2 -2
  4. package/dist/acp.d.ts +1 -1
  5. package/dist/acp.js +1 -1
  6. package/dist/{agent-xjMzFclr.js → agent-Emjeo5Ad.js} +2 -2
  7. package/dist/{agent-xjMzFclr.js.map → agent-Emjeo5Ad.js.map} +1 -1
  8. package/dist/agent.d.ts +1 -1
  9. package/dist/agent.js +1 -1
  10. package/dist/chat/pure.d.ts +3 -3
  11. package/dist/chat.d.ts +50 -6
  12. package/dist/chat.d.ts.map +1 -1
  13. package/dist/chat.js +3 -3
  14. package/dist/{context-transport-O-oQHwhP.js → context-transport-3b0qpM2m.js} +3 -3
  15. package/dist/{context-transport-O-oQHwhP.js.map → context-transport-3b0qpM2m.js.map} +1 -1
  16. package/dist/contexts/daytona.d.ts +1 -1
  17. package/dist/contexts/e2b.d.ts +1 -1
  18. package/dist/eval.d.ts +1 -1
  19. package/dist/extensions.d.ts +1 -1
  20. package/dist/extensions.js +1 -1
  21. package/dist/headless.d.ts +1 -1
  22. package/dist/headless.js +1 -1
  23. package/dist/{index-DTIe4e1R.d.ts → index-DWXLcGB_.d.ts} +13 -1
  24. package/dist/{index-DTIe4e1R.d.ts.map → index-DWXLcGB_.d.ts.map} +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.js +3 -3
  27. package/dist/{login-DZHMF7Gl.js → login-Cvx1nSVm.js} +35 -5
  28. package/dist/{login-DZHMF7Gl.js.map → login-Cvx1nSVm.js.map} +1 -1
  29. package/dist/{mcp-B5iXOceC.js → mcp-8bEHU4WB.js} +3 -3
  30. package/dist/{mcp-B5iXOceC.js.map → mcp-8bEHU4WB.js.map} +1 -1
  31. package/dist/mcp.d.ts +1 -1
  32. package/dist/mcp.js +1 -1
  33. package/dist/output/stream-json.d.ts +1 -1
  34. package/dist/output/terminal.d.ts +1 -1
  35. package/dist/presets.d.ts +1 -1
  36. package/dist/presets.js +1 -1
  37. package/dist/providers/anthropic.d.ts +1 -1
  38. package/dist/providers/arcee.d.ts +1 -1
  39. package/dist/providers/baseten.d.ts +1 -1
  40. package/dist/providers/cerebras.d.ts +1 -1
  41. package/dist/providers/local.d.ts +1 -1
  42. package/dist/providers/openai-compat.d.ts +1 -1
  43. package/dist/providers/openai.d.ts +1 -1
  44. package/dist/providers/openrouter.d.ts +1 -1
  45. package/dist/providers/xai.d.ts +1 -1
  46. package/dist/providers.d.ts +1 -1
  47. package/dist/restate.d.ts +1 -1
  48. package/dist/session/sqlite.d.ts +1 -1
  49. package/dist/session.d.ts +1 -1
  50. package/dist/skills.d.ts +1 -1
  51. package/dist/{stdio-BsB0QEE9.js → stdio-BKFpPNvX.js} +2 -2
  52. package/dist/{stdio-BsB0QEE9.js.map → stdio-BKFpPNvX.js.map} +1 -1
  53. package/dist/{stdio-loader-p7qCIzPS.js → stdio-loader-DFDpBwwI.js} +2 -2
  54. package/dist/{stdio-loader-p7qCIzPS.js.map → stdio-loader-DFDpBwwI.js.map} +1 -1
  55. package/dist/{tool-formatters-B_dGfYqT.d.ts → tool-formatters-Co5gl_7R.d.ts} +2 -2
  56. package/dist/{tool-formatters-B_dGfYqT.d.ts.map → tool-formatters-Co5gl_7R.d.ts.map} +1 -1
  57. package/dist/tools/fetch-url.d.ts +1 -1
  58. package/dist/tools/web-search.d.ts +1 -1
  59. package/dist/tools.d.ts +1 -1
  60. package/dist/tools.js +1 -1
  61. package/dist/{transcript-anchors-CqxIlrhv.d.ts → transcript-anchors-DBPJr5Ks.d.ts} +2 -2
  62. package/dist/{transcript-anchors-CqxIlrhv.d.ts.map → transcript-anchors-DBPJr5Ks.d.ts.map} +1 -1
  63. package/dist/{transcript-anchors-Bsmm4435.js → transcript-anchors-DhBTqTyc.js} +81 -7
  64. package/dist/{transcript-anchors-Bsmm4435.js.map → transcript-anchors-DhBTqTyc.js.map} +1 -1
  65. package/dist/tui.d.ts +3 -3
  66. package/dist/tui.d.ts.map +1 -1
  67. package/dist/tui.js +305 -105
  68. package/dist/tui.js.map +1 -1
  69. package/dist/{turn-operations-D7JhuW_X.d.ts → turn-operations-DokFalMC.d.ts} +3 -3
  70. package/dist/{turn-operations-D7JhuW_X.d.ts.map → turn-operations-DokFalMC.d.ts.map} +1 -1
  71. package/dist/types.d.ts +1 -1
  72. package/docs/CHAT.md +3 -1
  73. package/docs/TUI.md +6 -4
  74. package/package.json +1 -1
package/dist/tui.js CHANGED
@@ -1,5 +1,5 @@
1
- import { $ as InteractionsProvider, $t as countClaudeSessionFiles, A as runOAuthLogin, Bt as ConfigProvider, Cn as discoverAgentsMd, Cr as toolCallPreview, Ct as clampFps, D as formatPathForCwd, Dt as resolveChipColor, Er as usageByModel, F as refreshMcpToolsCatalog, Gt as chainPositionOf, H as indexOfServerRow, Ht as resolveConfig, It as createDiscoverySlot, J as useMcpAuthDispatch, Jt as providerKeySetChanged, Kt as chainsEqual, L as subscribeMcpToolsCache, Ln as TODO_STATUS_GLYPHS, Lt as DiscoveryProvider, N as loadMcpToolsCache, O as fetchOAuthRedirect, Ot as resolveTheme, P as mcpToolsCachePath, Q as ASK_USER_TOOL, Qt as useCompletion, Rt as useDiscovery, Sn as chatAutoCompactBehavior, St as SettingsProvider, T as suggestSafelistEntries, Tr as updateToolEventOutcomes, U as parentServerName, Ut as resolveStoragePaths, V as buildVisibleMcpRows, Vt as useConfig, W as createFileMcpCredentialStore, Wn as pruneTodosByRun, Wt as buildRunModel, X as getMcpAuthStatus, Y as useMcpAuthState, Yt as restoreChainFallbacks, Zt as toggleChainEntry, _ as useSafeModeQueue, _r as marginTopFor, _t as DEFAULT_SETTINGS, a as useSurfaces, an as buildMcpServers, ar as buildPlanSystem, at as pendingInteractionsFromTurns, b as getSafelist, br as stripSpawnTokensLine, bt as SETTINGS_INPUTS, c as useStreamBuffer, cr as createChatTurnsReadTool, ct as useInteractionsQueue, d as discoverProjectSkills, dn as bootTick, dt as hintsLength, en as hasClaudeCodeSetup, et as PRESENT_PLAN_TOOL, fn as buildUpdateHint, fr as deriveSessionTitle, ft as truncateTrailing, g as useSafeModeActions, gr as loadState, gt as useEnabledToggleSet, h as SafeModeProvider, hr as listSessionMeta, ht as listProjectFiles, i as useSelectStyle, in as upsertMcpServer, ir as buildBuildSystem, it as makeRequestInteraction, j as supportsOAuth, jn as accentColor, k as oauthUsesManualCodePaste, l as buildSkillsConfig, ln as tryOpenBrowser, lt as EMPTY_HINTS, m as writeSessionExport, mr as lastContextSizeFromTurns, n as ThemeProvider, nn as mcpsWritePath, nt as createInteractionTools, or as envSection, pn as useUpdateCheck, pr as eventsFromTurns, pt as buildHints, q as McpAuthProvider, qn as useActiveTodos, qt as filterChainFallbacks, r as useColors, rn as removeMcpServer, s as useTheme, sn as discoverProjectMcps, sr as chatReferenceTokensForPrompt, st as useInteractionsActions, t as computeTurnAnchors, tn as importFromClaudeCode, tt as buildResumedToolResultsTurn, u as defaultSkillScanPaths, ur as compactSummaryEvent, ut as clipHintsToWidth, vt as SETTINGS_CATEGORIES, wr as toolResultText, wt as useSettings, x as isOnSafelist, xr as sumRunCosts, xt as SETTINGS_TOGGLES, y as addToSafelist, yr as serverToolResultSummary, yt as SETTINGS_CHOICES, z as useMcpToolToggleMap, zt as useDiscoveryOptional } from "./transcript-anchors-Bsmm4435.js";
2
- import { $ as removeInstalledExtension, At as looksBinary, C as resolveTasksDir, E as selectFilesFromSession, Gt as KEYBINDING_KEY_COL_WIDTH, Jt as groupBindings, K as createExtensionPromptBridge, Kt as ensureKeybindingsFile, Mt as createModelSearchTool, O as compactConversation, Ot as tailTruncate, S as resolveSessionDir, Sn as effectiveContextWindow, V as summaryToTurn, Xt as matchesBinding, Y as installExtensionFromSpec, Yt as keybindingsPath, _ as rehomePersistedStubs, _n as resolveExtensionConfigValue, at as composeExtensionPreset, b as resolveMcpWarningsDir, c as applyCompactSummaryCutoff, et as updateInstalledExtension, ht as basic_default, ln as parseExtensionScanPathEnv, lt as partitionExtensionCatalog, m as cleanupPersistedSession, nt as discoverAndBuildExtensionRegistry, ot as disposeExtensionRegistry, pn as coerceExtensionConfigInput, q as ExtensionInstallError, qt as formatBindingForDisplay, rn as registerExtensionHostModules, sn as inlineLoadedExtension, st as filterExtensionRegistry, t as createAgent, tn as resolveExtensionKeybindings, tt as createExtensionHud, un as scanExtensionDirectories, ut as createExtensionHostRuntimeBridge, v as resolveLegacyPersistDir, vn as sanitizeExtensionConfigActionPatch, w as buildPostCompactAttachments, x as resolvePersistDir, xn as OUTPUT_RESERVE_TOKENS, xt as isSpawnTool, y as resolveLegacySessionDir } from "./agent-xjMzFclr.js";
1
+ import { $t as toggleChainEntry, A as redirectUriFromAuthUrl, At as resolveTheme, Bt as useDiscovery, Cr as sumRunCosts, Ct as SETTINGS_TOGGLES, D as formatPathForCwd, Dr as updateToolEventOutcomes, Er as toolResultText, Et as useSettings, F as loadMcpToolsCache, G as parentServerName, Gt as resolveStoragePaths, Ht as ConfigProvider, I as mcpToolsCachePath, Jt as chainsEqual, K as createFileMcpCredentialStore, Kn as pruneTodosByRun, Kt as buildRunModel, L as refreshMcpToolsCatalog, M as runOAuthLogin, N as supportsOAuth, Nn as accentColor, O as fetchOAuthRedirect, Or as usageByModel, Q as getMcpAuthStatus, Rt as createDiscoverySlot, Sr as stripSpawnTokensLine, St as SETTINGS_INPUTS, T as suggestSafelistEntries, Tn as discoverAgentsMd, Tr as toolCallPreview, Tt as clampFps, U as buildVisibleMcpRows, Ut as useConfig, V as useMcpToolToggleMap, Vt as useDiscoveryOptional, W as indexOfServerRow, Wt as resolveConfig, X as useMcpAuthDispatch, Xt as providerKeySetChanged, Y as McpAuthProvider, Yn as useActiveTodos, Yt as filterChainFallbacks, Z as useMcpAuthState, Zt as restoreChainFallbacks, _ as useSafeModeQueue, _r as listSessionMeta, _t as listProjectFiles, a as useSurfaces, an as removeMcpServer, b as getSafelist, bt as SETTINGS_CATEGORIES, c as useStreamBuffer, cr as envSection, d as discoverProjectSkills, dn as tryOpenBrowser, dt as EMPTY_HINTS, en as useCompletion, et as ASK_USER_TOOL, fr as compactSummaryEvent, ft as clipHintsToWidth, g as useSafeModeActions, gr as lastContextSizeFromTurns, h as SafeModeProvider, hn as useUpdateCheck, hr as eventsFromTurns, ht as buildHints, i as useSelectStyle, in as mcpsWritePath, it as createInteractionTools, j as oauthUsesManualCodePaste, kt as resolveChipColor, l as buildSkillsConfig, ln as discoverProjectMcps, lr as chatReferenceTokensForPrompt, lt as useInteractionsActions, m as writeSessionExport, mn as buildUpdateHint, mr as deriveSessionTitle, mt as truncateTrailing, n as ThemeProvider, nn as hasClaudeCodeSetup, nt as PRESENT_PLAN_TOOL, on as upsertMcpServer, or as buildBuildSystem, ot as makeRequestInteraction, pn as bootTick, pt as hintsLength, qt as chainPositionOf, r as useColors, rn as importFromClaudeCode, rt as buildResumedToolResultsTurn, s as useTheme, sn as buildMcpServers, sr as buildPlanSystem, st as pendingInteractionsFromTurns, t as computeTurnAnchors, tn as countClaudeSessionFiles, tt as InteractionsProvider, u as defaultSkillScanPaths, ur as createChatTurnsReadTool, ut as useInteractionsQueue, vr as loadState, vt as useEnabledToggleSet, wn as chatAutoCompactBehavior, wt as SettingsProvider, x as isOnSafelist, xr as serverToolResultSummary, xt as SETTINGS_CHOICES, y as addToSafelist, yr as marginTopFor, yt as DEFAULT_SETTINGS, z as subscribeMcpToolsCache, zn as TODO_STATUS_GLYPHS, zt as DiscoveryProvider } from "./transcript-anchors-DhBTqTyc.js";
2
+ import { $ as removeInstalledExtension, At as looksBinary, C as resolveTasksDir, E as selectFilesFromSession, Gt as KEYBINDING_KEY_COL_WIDTH, Jt as groupBindings, K as createExtensionPromptBridge, Kt as ensureKeybindingsFile, Mt as createModelSearchTool, O as compactConversation, Ot as tailTruncate, S as resolveSessionDir, Sn as effectiveContextWindow, V as summaryToTurn, Xt as matchesBinding, Y as installExtensionFromSpec, Yt as keybindingsPath, _ as rehomePersistedStubs, _n as resolveExtensionConfigValue, at as composeExtensionPreset, b as resolveMcpWarningsDir, c as applyCompactSummaryCutoff, et as updateInstalledExtension, ht as basic_default, ln as parseExtensionScanPathEnv, lt as partitionExtensionCatalog, m as cleanupPersistedSession, nt as discoverAndBuildExtensionRegistry, ot as disposeExtensionRegistry, pn as coerceExtensionConfigInput, q as ExtensionInstallError, qt as formatBindingForDisplay, rn as registerExtensionHostModules, sn as inlineLoadedExtension, st as filterExtensionRegistry, t as createAgent, tn as resolveExtensionKeybindings, tt as createExtensionHud, un as scanExtensionDirectories, ut as createExtensionHostRuntimeBridge, v as resolveLegacyPersistDir, vn as sanitizeExtensionConfigActionPatch, w as buildPostCompactAttachments, x as resolvePersistDir, xn as OUTPUT_RESERVE_TOKENS, xt as isSpawnTool, y as resolveLegacySessionDir } from "./agent-Emjeo5Ad.js";
3
3
  import { a as formatDuration, c as previewLine, i as fmtTokens, l as shortId, n as compactPath, o as formatTaskStatus, r as fmtCost, s as formatTaskSummary, t as ageString } from "./format-BF745pLL.js";
4
4
  import { n as appendCapped } from "./output-cap-C9zukcCj.js";
5
5
  import { i as killProcessGroup, n as SUPPORTS_PROCESS_GROUPS, r as createProcessContext } from "./contexts-DWVt7MvF.js";
@@ -7,7 +7,7 @@ import { l as errorMessage } from "./errors-DJUxZg9b.js";
7
7
  import { M as findGitRoot } from "./resolve-BPTA1RpX.js";
8
8
  import { a as createRingBufferSink, i as createLoggingHooks, l as logFileTimestamp, n as createFileSink, o as fanoutSink, r as createLogger, s as formatLogLine, t as consoleSink } from "./logger-DBX9uYOw.js";
9
9
  import { i as sniffImageMediaType } from "./media-sniff-UKijEaiQ.js";
10
- import { g as hasAuthorizationHeader, m as McpOAuthProvider, n as connectMcpServers, s as nonOwningMcpView } from "./mcp-B5iXOceC.js";
10
+ import { g as hasAuthorizationHeader, m as McpOAuthProvider, n as connectMcpServers, s as nonOwningMcpView } from "./mcp-8bEHU4WB.js";
11
11
  import { C as estimateRebilledCostUsd } from "./openai-compat-Bf9uMTMa.js";
12
12
  import { replaceDynamicSection } from "./prompt.js";
13
13
  import { a as getExtensionSessionState, c as setExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, t as createSession } from "./session-Bdk31kA5.js";
@@ -17,7 +17,7 @@ import { i as installCacheBreakLogger } from "./cache-telemetry-ehVdWsg2.js";
17
17
  import { a as generateStructuredData, c as shouldAutoGenerateTitle, f as commandBlockToEvent, g as parseCommandInvocation, h as executeCommand, n as patchInlineGhost, p as commandBlocksToContent, r as resolveInlineSuggestion, s as generateSessionTitle, t as createPathAutocompleteProvider } from "./inline-autocomplete-DC2cxprN.js";
18
18
  import { s as resolveCompletionSubmit } from "./completion-core-BRo86R_L.js";
19
19
  import { i as installSkillFromSpec, r as SkillInstallError } from "./skills-C7eIjWYm.js";
20
- import { a as ZIDANE_EXTENSION_SKILL_NAME, d as extensionScaffold, f as readSkillEditMarker, h as withZidaneSkillEditSkill, i as SKILL_EDIT_METADATA_KEY, l as editSessionMetadata, m as withZidaneExtensionSkill, o as ZIDANE_SKILL_EDIT_SKILL_NAME, t as loginMcpServer, u as extensionDirForCwd } from "./login-DZHMF7Gl.js";
20
+ import { a as ZIDANE_EXTENSION_SKILL_NAME, d as extensionScaffold, f as readSkillEditMarker, h as withZidaneSkillEditSkill, i as SKILL_EDIT_METADATA_KEY, l as editSessionMetadata, m as withZidaneExtensionSkill, o as ZIDANE_SKILL_EDIT_SKILL_NAME, t as loginMcpServer, u as extensionDirForCwd } from "./login-Cvx1nSVm.js";
21
21
  import { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning, w as renderDefaultCallbackPage } from "./auth-Dptb8U5J.js";
22
22
  import { createDockerContext } from "./contexts/docker.js";
23
23
  import { createTuiStore } from "./session/sqlite.js";
@@ -3475,7 +3475,7 @@ function UserPromptBlock({ text, refs, attachments, searchQuery }) {
3475
3475
  });
3476
3476
  }
3477
3477
  /** Click-feedback delay before `[copied]` reverts to `[copy]`, in ms. */
3478
- const COPY_FEEDBACK_MS = 1200;
3478
+ const COPY_FEEDBACK_MS$1 = 1200;
3479
3479
  /**
3480
3480
  * BoxRenderable subclass that hosts a `[lang] … [copy]` header above
3481
3481
  * an inner {@link CodeRenderable} body. Returned from
@@ -3583,7 +3583,7 @@ var CodeBlockWrapper = class extends BoxRenderable {
3583
3583
  if (this.button.isDestroyed) return;
3584
3584
  this.button.content = "[copy]";
3585
3585
  this.button.fg = this.bag.colors.warn;
3586
- }, COPY_FEEDBACK_MS);
3586
+ }, COPY_FEEDBACK_MS$1);
3587
3587
  };
3588
3588
  this.header.add(this.langLabel);
3589
3589
  this.header.add(this.spacer);
@@ -9419,11 +9419,12 @@ function OptionList({ items, initialCursor, onPick }) {
9419
9419
  * budget — default 14 fits a typical modal; pass a smaller value for
9420
9420
  * less-padded containers (e.g. 6 for the auth wizard panel).
9421
9421
  */
9422
- function OAuthUrlBlock({ url, fg, maxLineWidth, chromeWidth = 14 }) {
9422
+ function OAuthUrlBlock({ url, fg, bg, maxLineWidth, chromeWidth = 14 }) {
9423
9423
  const { width: termWidth } = useTerminalDimensions();
9424
9424
  return /* @__PURE__ */ jsx$1(Fragment, { children: chunkString(url, Math.max(20, Math.min(maxLineWidth, termWidth - chromeWidth))).map((line, i) => /* @__PURE__ */ jsx$1("text", {
9425
9425
  wrapMode: "none",
9426
9426
  fg,
9427
+ ...bg ? { bg } : {},
9427
9428
  children: /* @__PURE__ */ jsx$1("a", {
9428
9429
  href: url,
9429
9430
  children: line
@@ -9441,6 +9442,65 @@ function chunkString(s, n) {
9441
9442
  /** Keystroke shown next to the open-browser button. */
9442
9443
  const OPEN_BROWSER_KEY = "ctrl+b";
9443
9444
  /**
9445
+ * Keystroke shown next to the copy-URL button. `ctrl+y` is the global
9446
+ * `openKeybindings` chord, but it can't collide here: every surface that
9447
+ * mounts this block is either a modal (`app.tsx` bails on `modal.isOpen`
9448
+ * before dispatching global bindings) or the auth screen (excluded by
9449
+ * `screen !== 'auth'`). It's also absent from OpenTUI's input key table,
9450
+ * so the focused paste field ignores it.
9451
+ */
9452
+ const COPY_URL_KEY = "ctrl+y";
9453
+ /** How long a `copied` confirmation stays on screen. */
9454
+ const COPY_FEEDBACK_MS = 4e3;
9455
+ /**
9456
+ * Paste-input capacity. OpenTUI's `InputRenderable` defaults to 1000 chars
9457
+ * and silently drops the overflow — long enough to truncate a callback URL
9458
+ * carrying a fat `code` + `state` and make a correct paste look corrupt.
9459
+ */
9460
+ const PASTE_MAX_LENGTH = 4096;
9461
+ /** After this long without an authorization URL, spell out the escape route. */
9462
+ const SLOW_START_SECONDS = 8;
9463
+ /**
9464
+ * Seconds elapsed since `active` became true, ticking once a second.
9465
+ * Frozen (and reset) when inactive so the panel doesn't repaint after the
9466
+ * URL lands.
9467
+ */
9468
+ function useElapsedSeconds(active) {
9469
+ const [seconds, setSeconds] = useState(0);
9470
+ useEffect(() => {
9471
+ if (!active) {
9472
+ setSeconds(0);
9473
+ return;
9474
+ }
9475
+ const started = Date.now();
9476
+ const timer = setInterval(() => setSeconds(Math.floor((Date.now() - started) / 1e3)), 1e3);
9477
+ return () => clearInterval(timer);
9478
+ }, [active]);
9479
+ return seconds;
9480
+ }
9481
+ /**
9482
+ * Copy-to-clipboard with a self-clearing "copied" flag. Every copy
9483
+ * affordance in this module shares it so the timers are cleaned up on
9484
+ * unmount in exactly one place.
9485
+ */
9486
+ function useCopyFlash() {
9487
+ const [copied, setCopied] = useState(false);
9488
+ const timer = useRef(null);
9489
+ const copy = useCallback((text) => {
9490
+ if (!writeToClipboard(text)) return;
9491
+ setCopied(true);
9492
+ if (timer.current) clearTimeout(timer.current);
9493
+ timer.current = setTimeout(setCopied, COPY_FEEDBACK_MS, false);
9494
+ }, []);
9495
+ useEffect(() => () => {
9496
+ if (timer.current) clearTimeout(timer.current);
9497
+ }, []);
9498
+ return {
9499
+ copied,
9500
+ copy
9501
+ };
9502
+ }
9503
+ /**
9444
9504
  * Device-authorization affordance (RFC 8628): the user code the user must see
9445
9505
  * on the consent screen, plus the verification URI they type it into.
9446
9506
  *
@@ -9452,19 +9512,13 @@ const OPEN_BROWSER_KEY = "ctrl+b";
9452
9512
  */
9453
9513
  function OAuthDeviceCodeBlock({ userCode, verificationUri, expiresInSeconds }) {
9454
9514
  const COLOR = useColors();
9455
- const [copied, setCopied] = useState(false);
9456
- const copyTimer = useRef(null);
9515
+ const { copied, copy } = useCopyFlash();
9457
9516
  useKeyboard((key) => {
9458
9517
  if (key.name === "u" && key.ctrl) {
9459
9518
  key.preventDefault();
9460
- setCopied(writeToClipboard(userCode));
9461
- if (copyTimer.current) clearTimeout(copyTimer.current);
9462
- copyTimer.current = setTimeout(setCopied, 4e3, false);
9519
+ copy(userCode);
9463
9520
  }
9464
9521
  });
9465
- useEffect(() => () => {
9466
- if (copyTimer.current) clearTimeout(copyTimer.current);
9467
- }, []);
9468
9522
  return /* @__PURE__ */ jsxs("box", {
9469
9523
  style: { flexDirection: "column" },
9470
9524
  children: [
@@ -9489,23 +9543,29 @@ function OAuthDeviceCodeBlock({ userCode, verificationUri, expiresInSeconds }) {
9489
9543
  });
9490
9544
  }
9491
9545
  /**
9492
- * Unified affordance for surfacing an OAuth authorization URL in the TUI:
9546
+ * Unified affordance for surfacing an OAuth authorization URL in the TUI,
9547
+ * laid out as two numbered steps so the remote-session path reads as a
9548
+ * first-class option instead of a footnote:
9493
9549
  *
9494
- * 1. A prominent OSC 8 hyperlink button — "Click here to open auth URL in
9495
- * browser". Honored by every terminal that supports clickable links
9496
- * (iTerm2, Kitty, WezTerm, Ghostty, Alacritty, …). Lands on the user's
9497
- * LOCAL terminal even over SSH because OSC 8 is interpreted client-side.
9498
- * 2. The full URL rendered greyed below, chunked into per-row hyperlinks
9499
- * via {@link OAuthUrlBlock}. Backup channel for terminals without OSC 8,
9500
- * and for users who'd rather copy via drag-select than click.
9501
- * 3. Optional paste-back input. When `paste` is provided, an `<input>`
9502
- * renders below the URL so the user can paste the FULL redirect URL
9503
- * their browser ended up at after authorizing — useful when zidane runs
9504
- * over SSH and the browser-side redirect to loopback can't reach the
9505
- * remote callback server. The caller's `onSubmit` typically pipes the
9506
- * pasted value through {@link fetchOAuthRedirect} so the in-process
9507
- * server receives the request and the OAuth promise resolves through
9508
- * the same happy path a real browser would have taken.
9550
+ * **1 — get to the consent screen.** Two real buttons (mouse-clickable
9551
+ * AND keyed): {@link OPEN_BROWSER_KEY} opens the URL via the OS handler,
9552
+ * {@link COPY_URL_KEY} copies it to the clipboard (OSC 52 → lands on the
9553
+ * user's LOCAL clipboard over SSH). Below them the URL itself sits in a
9554
+ * framed panel with its own `[copy]` button, chunked into per-row OSC 8
9555
+ * hyperlinks by {@link OAuthUrlBlock} — the backup channel for terminals
9556
+ * without clickable links and for drag-select copying.
9557
+ *
9558
+ * **2 — hand the redirect back.** When `paste` is provided, an `<input>`
9559
+ * accepts whatever the browser (or consent page) produced. This is THE
9560
+ * path for `zidane` running over SSH: the browser sits on the user's
9561
+ * laptop and cannot reach the remote box's loopback callback server, so
9562
+ * they copy the failed `http://127.0.0.1:<port>/callback?code=…` out of
9563
+ * the URL bar and paste it here. Servers that render the code on the
9564
+ * page instead of redirecting have no failed URL to copy — paste the
9565
+ * code itself; `normalizeRedirectPaste` completes it against the
9566
+ * authorization URL's `redirect_uri` + `state`. We name that exact
9567
+ * address (parsed out of the authorization URL) so there's no guessing
9568
+ * about which string to copy.
9509
9569
  *
9510
9570
  * The component owns presentation only — keyboard focus, input ref, and
9511
9571
  * the submit handler stay with the caller so the affordance composes
@@ -9513,31 +9573,40 @@ function OAuthDeviceCodeBlock({ userCode, verificationUri, expiresInSeconds }) {
9513
9573
  */
9514
9574
  function OAuthAuthBlock({ authUrl, maxLineWidth, chromeWidth = 14, paste }) {
9515
9575
  const COLOR = useColors();
9576
+ const callbackUri = redirectUriFromAuthUrl(authUrl);
9577
+ const pasteRef = paste?.inputRef;
9578
+ const pasteFocused = paste?.focused ?? false;
9579
+ useEffect(() => {
9580
+ if (pasteFocused) pasteRef?.current?.focus();
9581
+ }, [pasteFocused, pasteRef]);
9516
9582
  return /* @__PURE__ */ jsxs("box", {
9517
9583
  style: {
9518
9584
  flexDirection: "column",
9519
9585
  gap: 1
9520
9586
  },
9521
9587
  children: [
9522
- /* @__PURE__ */ jsx$1(OpenBrowserButton, { authUrl }),
9523
9588
  /* @__PURE__ */ jsxs("box", {
9524
9589
  style: { flexDirection: "column" },
9525
- children: [/* @__PURE__ */ jsx$1("text", {
9526
- fg: COLOR.mute,
9527
- children: "or copy the URL manually:"
9528
- }), /* @__PURE__ */ jsx$1(OAuthUrlBlock, {
9529
- url: authUrl,
9530
- fg: COLOR.dim,
9531
- maxLineWidth,
9532
- chromeWidth
9533
- })]
9590
+ children: [/* @__PURE__ */ jsx$1(StepHeading, {
9591
+ step: paste ? "1" : void 0,
9592
+ text: "open this URL in your browser to authorize"
9593
+ }), /* @__PURE__ */ jsx$1(ActionButtons, { authUrl })]
9594
+ }),
9595
+ /* @__PURE__ */ jsx$1(AuthUrlPanel, {
9596
+ url: authUrl,
9597
+ maxLineWidth,
9598
+ chromeWidth
9534
9599
  }),
9535
9600
  paste && /* @__PURE__ */ jsxs("box", {
9536
9601
  style: { flexDirection: "column" },
9537
9602
  children: [
9603
+ /* @__PURE__ */ jsx$1(StepHeading, {
9604
+ step: "2",
9605
+ text: "no browser on this machine? (SSH, headless, firewalled)"
9606
+ }),
9538
9607
  /* @__PURE__ */ jsx$1("text", {
9539
- fg: COLOR.mute,
9540
- children: "or — if the browser couldn't reach this machine (SSH, firewall) — paste the URL it tried to redirect to:"
9608
+ fg: COLOR.dim,
9609
+ children: callbackUri ? `after you authorize, the browser lands on ${callbackUri} and fails — copy that whole address from its URL bar and paste it below.` : "after you authorize, copy the address the browser failed to reach (it starts with http://127.0.0.1) and paste it below."
9541
9610
  }),
9542
9611
  paste.hint && /* @__PURE__ */ jsx$1("text", {
9543
9612
  fg: toneColor(paste.hint.tone, COLOR),
@@ -9554,7 +9623,8 @@ function OAuthAuthBlock({ authUrl, maxLineWidth, chromeWidth = 14, paste }) {
9554
9623
  children: /* @__PURE__ */ jsx$1("input", {
9555
9624
  ref: paste.inputRef,
9556
9625
  focused: paste.focused,
9557
- placeholder: paste.placeholder ?? "paste redirect URL and press enter…",
9626
+ maxLength: PASTE_MAX_LENGTH,
9627
+ placeholder: paste.placeholder ?? "paste redirect URL (or just the code) and press enter…",
9558
9628
  onSubmit: paste.onSubmit,
9559
9629
  style: { flexGrow: 1 }
9560
9630
  })
@@ -9565,76 +9635,181 @@ function OAuthAuthBlock({ authUrl, maxLineWidth, chromeWidth = 14, paste }) {
9565
9635
  });
9566
9636
  }
9567
9637
  /**
9568
- * Real button: a bordered, brand-colored box hosting a labeled keystroke
9569
- * hint. Pressing `ctrl+b` (anywhere this block is mounted) calls
9570
- * {@link tryOpenBrowser} to launch the URL via the OS handler AND
9571
- * {@link writeToClipboard} so the URL also lands in the user's clipboard
9572
- * (OSC 52 → works over SSH, native helper → works locally). Both fire
9573
- * because either alone can fail silently — `open`/`xdg-open` are no-ops
9574
- * on a headless box, and OSC 52 is disabled on some terminals — and the
9575
- * combination covers both failure modes without prompting again.
9638
+ * Numbered step label. The numeral is dropped when the block has only one
9639
+ * step (no paste input) — "1" with no "2" reads like a truncated list.
9640
+ */
9641
+ function StepHeading({ step, text }) {
9642
+ const COLOR = useColors();
9643
+ return /* @__PURE__ */ jsxs("text", { children: [step !== void 0 && /* @__PURE__ */ jsx$1("span", {
9644
+ fg: COLOR.brand,
9645
+ attributes: 1,
9646
+ children: `${step} `
9647
+ }), /* @__PURE__ */ jsx$1("span", {
9648
+ fg: COLOR.mute,
9649
+ children: text
9650
+ })] });
9651
+ }
9652
+ /**
9653
+ * The two primary buttons, side by side: open-in-browser and copy-URL.
9654
+ *
9655
+ * Both are bordered boxes with a labeled keystroke, both are clickable
9656
+ * (`onMouseDown`), and both keys work regardless of which input owns focus
9657
+ * (`preventDefault` keeps the focused field from double-handling). Splitting
9658
+ * copy out of open matters over SSH: `open`/`xdg-open` on a headless remote
9659
+ * box does nothing, and the user's real intent is "give me the URL".
9576
9660
  *
9577
- * The visible label is also wrapped in an OSC 8 hyperlink so a mouse
9578
- * click in a supporting terminal (iTerm2, Kitty, WezTerm, …) reaches the
9579
- * same browser. The keybind is the authoritative path — it doesn't
9580
- * depend on terminal capability — and the hyperlink is the bonus.
9661
+ * The open button's label is additionally wrapped in an OSC 8 hyperlink so
9662
+ * a click in a terminal that supports links (iTerm2, Kitty, WezTerm, …)
9663
+ * reaches the browser on the user's LOCAL machine even over SSH.
9581
9664
  */
9582
- function OpenBrowserButton({ authUrl }) {
9665
+ function ActionButtons({ authUrl }) {
9583
9666
  const COLOR = useColors();
9584
- const [feedback, setFeedback] = useState(null);
9585
- const feedbackTimer = useRef(null);
9586
- const trigger = useCallback(() => {
9667
+ const [opened, setOpened] = useState(false);
9668
+ const openedTimer = useRef(null);
9669
+ const { copied, copy } = useCopyFlash();
9670
+ const open = useCallback(() => {
9587
9671
  tryOpenBrowser(authUrl);
9588
- const copied = writeToClipboard(authUrl);
9589
- setFeedback(copied ? "opened in browser · URL copied to clipboard" : "opened in browser");
9590
- if (feedbackTimer.current) clearTimeout(feedbackTimer.current);
9591
- feedbackTimer.current = setTimeout(setFeedback, 4e3, null);
9672
+ setOpened(true);
9673
+ if (openedTimer.current) clearTimeout(openedTimer.current);
9674
+ openedTimer.current = setTimeout(setOpened, COPY_FEEDBACK_MS, false);
9592
9675
  }, [authUrl]);
9676
+ useEffect(() => () => {
9677
+ if (openedTimer.current) clearTimeout(openedTimer.current);
9678
+ }, []);
9593
9679
  useKeyboard((key) => {
9594
- if (key.ctrl && key.name === "b") {
9680
+ if (!key.ctrl) return;
9681
+ if (key.name === "b") {
9682
+ key.preventDefault();
9683
+ open();
9684
+ return;
9685
+ }
9686
+ if (key.name === "y") {
9595
9687
  key.preventDefault();
9596
- trigger();
9688
+ copy(authUrl);
9597
9689
  }
9598
9690
  });
9599
- useEffect(() => () => {
9600
- if (feedbackTimer.current) clearTimeout(feedbackTimer.current);
9601
- }, []);
9602
9691
  return /* @__PURE__ */ jsxs("box", {
9603
9692
  style: { flexDirection: "column" },
9604
- children: [/* @__PURE__ */ jsx$1("box", {
9693
+ children: [/* @__PURE__ */ jsxs("box", {
9694
+ style: {
9695
+ flexDirection: "row",
9696
+ gap: 2,
9697
+ flexWrap: "wrap"
9698
+ },
9699
+ children: [/* @__PURE__ */ jsx$1(Button, {
9700
+ color: COLOR.brand,
9701
+ chord: OPEN_BROWSER_KEY,
9702
+ onPress: open,
9703
+ children: /* @__PURE__ */ jsx$1("a", {
9704
+ href: authUrl,
9705
+ fg: COLOR.brand,
9706
+ children: "↗ Open in browser"
9707
+ })
9708
+ }), /* @__PURE__ */ jsx$1(Button, {
9709
+ color: COLOR.warn,
9710
+ chord: COPY_URL_KEY,
9711
+ onPress: () => copy(authUrl),
9712
+ children: /* @__PURE__ */ jsx$1("span", {
9713
+ fg: COLOR.warn,
9714
+ children: "⧉ Copy URL"
9715
+ })
9716
+ })]
9717
+ }), (opened || copied) && /* @__PURE__ */ jsx$1("text", {
9718
+ fg: COLOR.accent,
9719
+ children: `✓ ${[opened ? "opened in browser" : null, copied ? "URL copied to clipboard" : null].filter(Boolean).join(" · ")}`
9720
+ })]
9721
+ });
9722
+ }
9723
+ /**
9724
+ * Bordered, clickable button hosting a label plus its keystroke hint.
9725
+ * The handler lives on the outer box: mouse events bubble from whatever
9726
+ * renderable was hit (label, padding, border) up through ancestors, so
9727
+ * the whole framed area is the target — not just the label text. Same
9728
+ * pattern as the settings-modal rows (`settings-modal.tsx:4006`).
9729
+ */
9730
+ function Button({ color, chord, onPress, children }) {
9731
+ const COLOR = useColors();
9732
+ return /* @__PURE__ */ jsx$1("box", {
9733
+ style: {
9734
+ border: true,
9735
+ borderColor: color,
9736
+ paddingLeft: 1,
9737
+ paddingRight: 1,
9738
+ alignSelf: "flex-start"
9739
+ },
9740
+ onMouseDown: (event) => {
9741
+ event.stopPropagation();
9742
+ event.preventDefault();
9743
+ onPress();
9744
+ },
9745
+ children: /* @__PURE__ */ jsxs("text", {
9746
+ wrapMode: "none",
9747
+ selectable: false,
9748
+ children: [
9749
+ children,
9750
+ /* @__PURE__ */ jsx$1("span", {
9751
+ fg: COLOR.mute,
9752
+ children: " "
9753
+ }),
9754
+ /* @__PURE__ */ jsx$1("span", {
9755
+ fg: COLOR.warn,
9756
+ children: chord
9757
+ })
9758
+ ]
9759
+ })
9760
+ });
9761
+ }
9762
+ /**
9763
+ * The authorization URL as a framed, copy-able panel — same idea as a
9764
+ * fenced code block in the transcript (`auth url` label left, clickable
9765
+ * `[copy]` right), so "here is a literal string you need to move
9766
+ * somewhere else" reads the same everywhere in the app.
9767
+ *
9768
+ * Framed rather than background-tinted because this block renders inside
9769
+ * containers that are already painted with the elevated modal surface; a
9770
+ * border is the only contrast that survives every host.
9771
+ */
9772
+ function AuthUrlPanel({ url, maxLineWidth, chromeWidth }) {
9773
+ const COLOR = useColors();
9774
+ const { copied, copy } = useCopyFlash();
9775
+ return /* @__PURE__ */ jsx$1("box", {
9776
+ style: { flexDirection: "column" },
9777
+ children: /* @__PURE__ */ jsxs("box", {
9605
9778
  style: {
9779
+ flexDirection: "column",
9606
9780
  border: true,
9607
- borderColor: COLOR.brand,
9781
+ borderColor: COLOR.border,
9608
9782
  paddingLeft: 1,
9609
- paddingRight: 1,
9610
- alignSelf: "flex-start"
9783
+ paddingRight: 1
9611
9784
  },
9612
- children: /* @__PURE__ */ jsxs("text", {
9613
- wrapMode: "none",
9785
+ children: [/* @__PURE__ */ jsxs("box", {
9786
+ style: { flexDirection: "row" },
9614
9787
  children: [
9615
- /* @__PURE__ */ jsx$1("a", {
9616
- href: authUrl,
9617
- fg: COLOR.brand,
9618
- children: "↗ Open auth URL in browser"
9619
- }),
9620
- /* @__PURE__ */ jsx$1("span", {
9788
+ /* @__PURE__ */ jsx$1("text", {
9621
9789
  fg: COLOR.mute,
9622
- children: " "
9790
+ children: "auth url"
9623
9791
  }),
9624
- /* @__PURE__ */ jsx$1("span", {
9625
- fg: COLOR.warn,
9626
- children: OPEN_BROWSER_KEY
9627
- }),
9628
- /* @__PURE__ */ jsx$1("span", {
9629
- fg: COLOR.mute,
9630
- children: " open · click also works"
9792
+ /* @__PURE__ */ jsx$1("box", { style: { flexGrow: 1 } }),
9793
+ /* @__PURE__ */ jsx$1("box", {
9794
+ onMouseDown: (event) => {
9795
+ event.stopPropagation();
9796
+ event.preventDefault();
9797
+ copy(url);
9798
+ },
9799
+ children: /* @__PURE__ */ jsx$1("text", {
9800
+ selectable: false,
9801
+ fg: copied ? COLOR.accent : COLOR.warn,
9802
+ children: copied ? "[copied]" : "[copy]"
9803
+ })
9631
9804
  })
9632
9805
  ]
9633
- })
9634
- }), feedback && /* @__PURE__ */ jsx$1("text", {
9635
- fg: COLOR.accent,
9636
- children: `✓ ${feedback}`
9637
- })]
9806
+ }), /* @__PURE__ */ jsx$1(OAuthUrlBlock, {
9807
+ url,
9808
+ fg: COLOR.dim,
9809
+ maxLineWidth: maxLineWidth - 4,
9810
+ chromeWidth: chromeWidth + 4
9811
+ })]
9812
+ })
9638
9813
  });
9639
9814
  }
9640
9815
  function toneColor(tone, COLOR) {
@@ -9653,7 +9828,9 @@ function toneColor(tone, COLOR) {
9653
9828
  * MCP SDK's loopback callback server resolves the OAuth promise
9654
9829
  * through the same code path a real browser-redirect would have
9655
9830
  * taken — works over SSH where the browser can't reach the remote
9656
- * callback server directly.
9831
+ * callback server directly. `authUrl` rides along so a partial paste
9832
+ * (bare code, query string) is completed against the callback URI
9833
+ * the authorization request was minted with.
9657
9834
  *
9658
9835
  * `inputFocused` is forwarded as the input's `focused` prop AND read by
9659
9836
  * the parent picker's `useKeyboard` (via a ref) to suppress single-key
@@ -9663,7 +9840,8 @@ function McpAuthorizingPanel({ serverName, authUrl, maxLineWidth, chromeWidth, i
9663
9840
  const COLOR = useColors();
9664
9841
  const inputRef = useRef(null);
9665
9842
  const [hint, setHint] = useState(null);
9666
- const status = hint && hint.tone !== "error" ? hint.text : authUrl === null ? "starting login flow…" : "complete the login in your browser — waiting for the callback…";
9843
+ const waited = useElapsedSeconds(authUrl === null);
9844
+ const status = hint && hint.tone !== "error" ? hint.text : authUrl === null ? `contacting ${serverName} for an authorization URL… (${waited}s)` : "complete the login in your browser — waiting for the callback…";
9667
9845
  const onSubmit = useCallback(() => {
9668
9846
  const value = inputRef.current?.value?.trim() ?? "";
9669
9847
  if (!value) return;
@@ -9673,7 +9851,7 @@ function McpAuthorizingPanel({ serverName, authUrl, maxLineWidth, chromeWidth, i
9673
9851
  });
9674
9852
  (async () => {
9675
9853
  try {
9676
- const result = await fetchOAuthRedirect(value);
9854
+ const result = await fetchOAuthRedirect(value, { ...authUrl ? { authUrl } : {} });
9677
9855
  if (result.status >= 200 && result.status < 300) setHint({
9678
9856
  text: "redirect accepted — finalizing…",
9679
9857
  tone: "accent"
@@ -9689,7 +9867,7 @@ function McpAuthorizingPanel({ serverName, authUrl, maxLineWidth, chromeWidth, i
9689
9867
  });
9690
9868
  }
9691
9869
  })();
9692
- }, []);
9870
+ }, [authUrl]);
9693
9871
  return /* @__PURE__ */ jsxs("box", {
9694
9872
  style: {
9695
9873
  flexDirection: "column",
@@ -9706,15 +9884,18 @@ function McpAuthorizingPanel({ serverName, authUrl, maxLineWidth, chromeWidth, i
9706
9884
  },
9707
9885
  children: [
9708
9886
  /* @__PURE__ */ jsx$1(Spinner, { label: status }),
9887
+ authUrl === null && waited >= SLOW_START_SECONDS && /* @__PURE__ */ jsx$1("text", {
9888
+ fg: COLOR.warn,
9889
+ children: `${serverName} is slow to respond — press esc to cancel and retry, or check the server URL in this list.`
9890
+ }),
9709
9891
  authUrl !== null && /* @__PURE__ */ jsx$1(OAuthAuthBlock, {
9710
9892
  authUrl,
9711
- maxLineWidth,
9893
+ maxLineWidth: maxLineWidth - 4,
9712
9894
  chromeWidth: (chromeWidth ?? 14) + 4,
9713
9895
  paste: {
9714
9896
  inputRef,
9715
9897
  focused: inputFocused,
9716
9898
  onSubmit,
9717
- placeholder: "paste redirect URL and press enter…",
9718
9899
  hint: hint?.tone === "error" ? hint : void 0
9719
9900
  }
9720
9901
  }),
@@ -10527,7 +10708,7 @@ function OAuthRunningStep({ descriptor, dataDir, onSuccess, onError }) {
10527
10708
  });
10528
10709
  (async () => {
10529
10710
  try {
10530
- const result = await fetchOAuthRedirect(value);
10711
+ const result = await fetchOAuthRedirect(value, { ...url ? { authUrl: url } : {} });
10531
10712
  if (result.status >= 200 && result.status < 300) {
10532
10713
  setPasteHint({
10533
10714
  text: "redirect accepted — exchanging code…",
@@ -10545,7 +10726,7 @@ function OAuthRunningStep({ descriptor, dataDir, onSuccess, onError }) {
10545
10726
  });
10546
10727
  }
10547
10728
  })();
10548
- }, []);
10729
+ }, [url]);
10549
10730
  return /* @__PURE__ */ jsxs(WizardPanel, {
10550
10731
  title: `configure ${descriptor.label} — OAuth`,
10551
10732
  children: [
@@ -21949,11 +22130,26 @@ function AppShell() {
21949
22130
  signal: ac.signal,
21950
22131
  renderCallbackPage: renderDefaultCallbackPage,
21951
22132
  hooks: agentRef.current?.hooks,
21952
- onAuthorizationUrl: (url) => tryOpenBrowser(url.toString())
22133
+ onAuthorizationUrl: (url) => {
22134
+ dispatchAuth({
22135
+ type: "auth-url",
22136
+ name,
22137
+ url: url.toString()
22138
+ });
22139
+ tryOpenBrowser(url.toString());
22140
+ }
22141
+ });
22142
+ dispatchAuth({
22143
+ type: "auth-success",
22144
+ name
21953
22145
  });
21954
22146
  rebuildActiveAgent().catch((err) => debugLog("mcp-login: rebuildActiveAgent failed", err));
21955
22147
  } catch (err) {
21956
- if (!agentRef.current) dispatchAuth({
22148
+ if (ac.signal.aborted) dispatchAuth({
22149
+ type: "login:cancel",
22150
+ name
22151
+ });
22152
+ else dispatchAuth({
21957
22153
  type: "auth-error",
21958
22154
  name,
21959
22155
  error: errorMessage(err)
@@ -21986,8 +22182,12 @@ function AppShell() {
21986
22182
  rebuildActiveAgent
21987
22183
  ]);
21988
22184
  const onCancelLoginMcp = useCallback((name) => {
22185
+ dispatchAuth({
22186
+ type: "login:cancel",
22187
+ name
22188
+ });
21989
22189
  mcpLoginAbortsRef.current.get(name)?.abort();
21990
- }, []);
22190
+ }, [dispatchAuth]);
21991
22191
  const onRefreshMcpTools = useCallback(async () => {
21992
22192
  const agent = agentRef.current;
21993
22193
  if (!agent) return;