zidane 5.5.5 → 5.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/{agent-CMAklak7.d.ts → agent-Dtnvs5ee.d.ts} +91 -2
- package/dist/agent-Dtnvs5ee.d.ts.map +1 -0
- package/dist/chat.d.ts +204 -15
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +3 -3
- package/dist/{errors-C5VSakmT.js → errors-DdZXnyXE.js} +38 -2
- package/dist/errors-DdZXnyXE.js.map +1 -0
- package/dist/{index-CF5QwBiz.d.ts → index-DHeHe04L.d.ts} +2 -2
- package/dist/{index-CF5QwBiz.d.ts.map → index-DHeHe04L.d.ts.map} +1 -1
- package/dist/{index-kroGomhj.d.ts → index-DX8De0nl.d.ts} +23 -2
- package/dist/index-DX8De0nl.d.ts.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +10 -10
- package/dist/{interpolate-Cvjy8gpk.js → interpolate-j5V-wcAQ.js} +2 -2
- package/dist/{interpolate-Cvjy8gpk.js.map → interpolate-j5V-wcAQ.js.map} +1 -1
- package/dist/{login-B_kfoGMP.js → login-BOj03nVe.js} +5 -4
- package/dist/login-BOj03nVe.js.map +1 -0
- package/dist/{mcp-BE43Viwi.js → mcp-ngMS0S6N.js} +2 -2
- package/dist/{mcp-BE43Viwi.js.map → mcp-ngMS0S6N.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/{messages-BBWakTN6.js → messages-B5k4DAXy.js} +2 -2
- package/dist/{messages-BBWakTN6.js.map → messages-B5k4DAXy.js.map} +1 -1
- package/dist/{presets-BDvBZuYI.js → presets-CTSij3yV.js} +2 -2
- package/dist/{presets-BDvBZuYI.js.map → presets-CTSij3yV.js.map} +1 -1
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +1 -1
- package/dist/{providers-CsUyN_FJ.js → providers-CaJE2ToS.js} +3 -3
- package/dist/{providers-CsUyN_FJ.js.map → providers-CaJE2ToS.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +2 -2
- package/dist/restate.d.ts +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session/sqlite.d.ts.map +1 -1
- package/dist/session/sqlite.js +226 -51
- package/dist/session/sqlite.js.map +1 -1
- package/dist/{session-DzfRacU_.js → session-BoEW_wCR.js} +2 -2
- package/dist/{session-DzfRacU_.js.map → session-BoEW_wCR.js.map} +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/skills.d.ts +2 -2
- package/dist/skills.js +1 -1
- package/dist/{tools-Bbd0Ivwn.js → tools-CslsHpKb.js} +156 -16
- package/dist/tools-CslsHpKb.js.map +1 -0
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-C7CtKPPo.d.ts → transcript-anchors-CwoKNW6Y.d.ts} +74 -5
- package/dist/transcript-anchors-CwoKNW6Y.d.ts.map +1 -0
- package/dist/tui.d.ts +24 -5
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +1280 -333
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-rYyU2Qyq.js → turn-operations-B8ySajUl.js} +687 -86
- package/dist/turn-operations-B8ySajUl.js.map +1 -0
- package/dist/types-oKPBdCmL.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.js +2 -2
- package/docs/ARCHITECTURE.md +5 -2
- package/docs/CHAT.md +10 -3
- package/docs/RESTATE.md +190 -0
- package/docs/SKILL.md +27 -2
- package/docs/TUI.md +4 -3
- package/package.json +2 -1
- package/dist/agent-CMAklak7.d.ts.map +0 -1
- package/dist/errors-C5VSakmT.js.map +0 -1
- package/dist/index-kroGomhj.d.ts.map +0 -1
- package/dist/login-B_kfoGMP.js.map +0 -1
- package/dist/tools-Bbd0Ivwn.js.map +0 -1
- package/dist/transcript-anchors-C7CtKPPo.d.ts.map +0 -1
- package/dist/turn-operations-rYyU2Qyq.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,30 +1,37 @@
|
|
|
1
|
-
import { $ as
|
|
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-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { $r as TODO_STATUS_GLYPHS, $t as isEditErrorResult, A as getSafelist, An as resolveApprovalForPayload, At as SettingsProvider, B as oauthUsesManualCodePaste, Bn as keybindingsPath, Br as piIdOf, Ct as buildHints, D as useSafeModeQueue, Dt as DEFAULT_SETTINGS, E as useSafeModeActions, En as buildEditOutcomesAnnotation, Er as setProviderCredential, Et as useEnabledToggleSet, F as suggestSafelistEntry, Ft as resolveChipColor, G as indexOfEntry, Gt as useDiscoveryOptional, H as supportsOAuth, Ht as createDiscoverySlot, In as KEYBINDING_DEFS, Ir as modelSupportsReasoning, It as resolveTheme, J as discoverProjectMcps, Jn as uniqueSkillNamesFromReferences, Jt as resolveConfig, K as buildMcpServers, Kt as ConfigProvider, L as splitPromptSegments, Mn as stripEditOutcomesAnnotation, Mt as useSettings, Nn as summarizeOutcomes, On as mergeApprovalAndBodyOutcomes, Ot as SETTINGS_CHOICES, Pn as findGitRoot, Pr as getContextWindow, Qt as eventsFromTurns, R as formatPathForCwd, Rn as ensureKeybindingsFile, Si as envSection, Sn as previewEditPayload, St as generateSessionTitle, T as SafeModeProvider, Tt as listProjectFiles, U as buildModelCatalog, Ut as DiscoveryProvider, V as runOAuthLogin, Vn as matchesBinding, W as filterModelCatalog, Wt as useDiscovery, Xn as createFilesCompletionProvider, Yt as EDIT_TOOL_NAMES, Z as createFileMcpCredentialStore, Zt as deriveSessionTitle, _ as turnContextSize, _n as buildUnifiedDiff, _t as EMPTY_HINTS, a as computeTurnAnchors, an as marginTopFor, ar as tryOpenBrowser, at as splitMarkdownCodeBlocks, b as defaultSkillScanPaths, bi as buildBuildSystem, bn as extractEditPayload, br as detectAuth, bt as truncateTrailing, c as formatToolCall, cn as stripSpawnTokensLine, cr as buildUpdateHint, d as useSelectStyle, dn as toolCallPreview, en as isTurnHighlighted, et as McpAuthProvider, f as useSurfaces, fn as toolResultText, ft as makeRequestInteraction, g as finalizeStreamingMarkdownForOwner, gn as buildContextualDiff, gt as useInteractionsQueue, h as finalizeStreamingMarkdown, ht as useInteractionsActions, i as turnAsText, ir as buildLinearRamp, j as isOnSafelist, jn as rewriteMultiEditHeader, jt as clampFps, k as addToSafelist, kn as parseEditOutcomesFromResult, kt as SETTINGS_TOGGLES, l as ThemeProvider, li as useActiveTodos, ln as sumRunCosts, lr as useUpdateCheck, lt as buildResumedToolResultsTurn, m as useTheme, mn as updateToolEventOutcomes, n as deleteTurnSafely, nn as lastContextSizeFromTurns, nr as useCompletion, nt as useMcpAuthState, o as TOOL_DISPLAY, pn as turnSelectionOwnership, pt as pendingInteractionsFromTurns, qn as createSkillsCompletionProvider, qr as accentColor, qt as useConfig, r as truncateTurnsAt, rn as listSessionMeta, rr as blendHsl, rt as getMcpAuthStatus, s as displayNameFor, sn as selectableTurnIds, sr as bootTick, st as InteractionsProvider, tn as isVisible, tt as useMcpAuthDispatch, u as useColors, ut as createInteractionTools, v as useStreamBuffer, vr as AUTO_COMPACT_MIN_GROWTH_FRACTION, vt as clipHintsToWidth, w as writeSessionExport, wn as summarizeEditPayload, x as discoverProjectSkills, xi as buildPlanSystem, xn as filetypeFromPath, y as buildSkillsConfig, yr as shouldAutoCompact, yt as hintsLength, z as fetchOAuthRedirect, zn as formatBindingForDisplay } from "./turn-operations-B8ySajUl.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-CslsHpKb.js";
|
|
3
|
+
import { n as createProcessContext } from "./contexts-BOtMvzli.js";
|
|
4
|
+
import { c as errorMessage } from "./errors-DdZXnyXE.js";
|
|
5
|
+
import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-ngMS0S6N.js";
|
|
6
|
+
import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-BOj03nVe.js";
|
|
6
7
|
import { n as formatTokenUsage } from "./stats-Lc3zL3RM.js";
|
|
7
|
-
import { n as loadSession, t as createSession } from "./session-
|
|
8
|
+
import { n as loadSession, t as createSession } from "./session-BoEW_wCR.js";
|
|
8
9
|
import { createTuiStore } from "./session/sqlite.js";
|
|
10
|
+
import { basename, join, relative } from "node:path";
|
|
9
11
|
import { homedir } from "node:os";
|
|
10
12
|
import { spawn } from "node:child_process";
|
|
11
13
|
import * as fs from "node:fs";
|
|
14
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
12
15
|
import { Buffer } from "node:buffer";
|
|
13
16
|
import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
14
17
|
import { BoxRenderable, CodeRenderable, RGBA, SyntaxStyle, TextRenderable, addDefaultParsers, createCliRenderer, decodePasteBytes, defaultTextareaKeyBindings, getTreeSitterClient, stripAnsiSequences } from "@opentui/core";
|
|
15
18
|
import { createRoot, useKeyboard, useRenderer, useSelectionHandler, useTerminalDimensions } from "@opentui/react";
|
|
16
19
|
import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
|
|
20
|
+
import { Fzf, byLengthAsc } from "fzf";
|
|
17
21
|
//#region src/tui/modal.tsx
|
|
18
22
|
const ModalContext = createContext(null);
|
|
19
23
|
function ModalRoot({ children }) {
|
|
20
24
|
const [active, setActive] = useState(null);
|
|
25
|
+
const [lockCount, setLockCount] = useState(0);
|
|
21
26
|
const api = useMemo(() => ({
|
|
22
27
|
open: (node) => setActive(node),
|
|
23
28
|
close: () => setActive(null),
|
|
29
|
+
lock: () => setLockCount((c) => c + 1),
|
|
30
|
+
unlock: () => setLockCount((c) => Math.max(0, c - 1)),
|
|
24
31
|
get isOpen() {
|
|
25
|
-
return active !== null;
|
|
32
|
+
return active !== null || lockCount > 0;
|
|
26
33
|
}
|
|
27
|
-
}), [active]);
|
|
34
|
+
}), [active, lockCount]);
|
|
28
35
|
return /* @__PURE__ */ jsxs(ModalContext.Provider, {
|
|
29
36
|
value: api,
|
|
30
37
|
children: [/* @__PURE__ */ jsx("box", {
|
|
@@ -1341,7 +1348,8 @@ function EventLineImpl({ event, depthOffset = 0, hideChildLabel = false, selecte
|
|
|
1341
1348
|
case "separator": return /* @__PURE__ */ jsx("text", { children: " " });
|
|
1342
1349
|
case "user-prompt": return /* @__PURE__ */ jsx(UserPromptBlock, {
|
|
1343
1350
|
text: safeText,
|
|
1344
|
-
refs: event.refs
|
|
1351
|
+
refs: event.refs,
|
|
1352
|
+
attachments: event.attachments
|
|
1345
1353
|
});
|
|
1346
1354
|
case "info": return /* @__PURE__ */ jsx("box", {
|
|
1347
1355
|
style: row,
|
|
@@ -1608,7 +1616,7 @@ function CompactSummaryBlock({ event, indent }) {
|
|
|
1608
1616
|
*/
|
|
1609
1617
|
/** Prompt chevron rendered ahead of every user-prompt block. */
|
|
1610
1618
|
const USER_PROMPT_PREFIX = "❯ ";
|
|
1611
|
-
function UserPromptBlock({ text, refs }) {
|
|
1619
|
+
function UserPromptBlock({ text, refs, attachments }) {
|
|
1612
1620
|
const COLOR = useColors();
|
|
1613
1621
|
const SURFACE = useSurfaces();
|
|
1614
1622
|
const boxStyle = {
|
|
@@ -1617,17 +1625,54 @@ function UserPromptBlock({ text, refs }) {
|
|
|
1617
1625
|
paddingLeft: 1,
|
|
1618
1626
|
paddingRight: 1
|
|
1619
1627
|
};
|
|
1620
|
-
|
|
1628
|
+
const attachmentChips = attachments && attachments.length > 0 ? /* @__PURE__ */ jsx("box", {
|
|
1629
|
+
style: {
|
|
1630
|
+
flexDirection: "row",
|
|
1631
|
+
flexWrap: "wrap",
|
|
1632
|
+
paddingTop: text.length > 0 ? 0 : 0
|
|
1633
|
+
},
|
|
1634
|
+
children: attachments.map((att, idx) => {
|
|
1635
|
+
const sz = att.size;
|
|
1636
|
+
const label = sz < 1024 ? `${sz}B` : sz < 1024 * 1024 ? `${(sz / 1024).toFixed(1)}KB` : `${(sz / (1024 * 1024)).toFixed(1)}MB`;
|
|
1637
|
+
const icon = att.mediaType.startsWith("image/") ? "🖼" : "📎";
|
|
1638
|
+
const chipColor = resolveChipColor(SURFACE.chips, "file");
|
|
1639
|
+
return /* @__PURE__ */ jsxs("text", { children: [
|
|
1640
|
+
idx > 0 ? " " : "",
|
|
1641
|
+
icon,
|
|
1642
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1643
|
+
fg: chipColor.fg,
|
|
1644
|
+
bg: chipColor.bg,
|
|
1645
|
+
children: [
|
|
1646
|
+
" ",
|
|
1647
|
+
att.name,
|
|
1648
|
+
" ",
|
|
1649
|
+
"(",
|
|
1650
|
+
label,
|
|
1651
|
+
")",
|
|
1652
|
+
" "
|
|
1653
|
+
]
|
|
1654
|
+
})
|
|
1655
|
+
] }, `att-${idx}`);
|
|
1656
|
+
})
|
|
1657
|
+
}) : null;
|
|
1658
|
+
if (!refs || refs.length === 0) return /* @__PURE__ */ jsxs("box", {
|
|
1621
1659
|
style: boxStyle,
|
|
1622
|
-
children:
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1660
|
+
children: [
|
|
1661
|
+
text.length > 0 && /* @__PURE__ */ jsxs("text", { children: [/* @__PURE__ */ jsx("span", {
|
|
1662
|
+
fg: COLOR.brand,
|
|
1663
|
+
children: USER_PROMPT_PREFIX
|
|
1664
|
+
}), text] }),
|
|
1665
|
+
!text.length && attachmentChips && /* @__PURE__ */ jsx("text", {
|
|
1666
|
+
fg: COLOR.brand,
|
|
1667
|
+
children: USER_PROMPT_PREFIX
|
|
1668
|
+
}),
|
|
1669
|
+
attachmentChips
|
|
1670
|
+
]
|
|
1626
1671
|
});
|
|
1627
1672
|
const segments = splitPromptSegments(text, refs);
|
|
1628
|
-
return /* @__PURE__ */
|
|
1673
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
1629
1674
|
style: boxStyle,
|
|
1630
|
-
children: /* @__PURE__ */ jsxs("box", {
|
|
1675
|
+
children: [/* @__PURE__ */ jsxs("box", {
|
|
1631
1676
|
style: {
|
|
1632
1677
|
flexDirection: "row",
|
|
1633
1678
|
flexWrap: "wrap"
|
|
@@ -1644,7 +1689,7 @@ function UserPromptBlock({ text, refs }) {
|
|
|
1644
1689
|
children: seg.text
|
|
1645
1690
|
}, i);
|
|
1646
1691
|
})]
|
|
1647
|
-
})
|
|
1692
|
+
}), attachmentChips]
|
|
1648
1693
|
});
|
|
1649
1694
|
}
|
|
1650
1695
|
/** Click-feedback delay before `[copied]` reverts to `[copy]`, in ms. */
|
|
@@ -2444,6 +2489,316 @@ function TodoInProgressList({ input, dim }) {
|
|
|
2444
2489
|
});
|
|
2445
2490
|
}
|
|
2446
2491
|
//#endregion
|
|
2492
|
+
//#region src/tui/cwd-picker.tsx
|
|
2493
|
+
const VISIBLE_ROWS$1 = 12;
|
|
2494
|
+
const HOME = homedir();
|
|
2495
|
+
const MAX_ENTRIES = 5e4;
|
|
2496
|
+
const MAX_DEPTH = 5;
|
|
2497
|
+
const SKIP_DIRS = new Set([
|
|
2498
|
+
"node_modules",
|
|
2499
|
+
".git",
|
|
2500
|
+
".hg",
|
|
2501
|
+
".svn",
|
|
2502
|
+
"__pycache__",
|
|
2503
|
+
".cache",
|
|
2504
|
+
".npm",
|
|
2505
|
+
".yarn",
|
|
2506
|
+
"dist",
|
|
2507
|
+
"build",
|
|
2508
|
+
".next",
|
|
2509
|
+
".nuxt",
|
|
2510
|
+
"coverage",
|
|
2511
|
+
".venv",
|
|
2512
|
+
"venv",
|
|
2513
|
+
".tox",
|
|
2514
|
+
"vendor",
|
|
2515
|
+
"target",
|
|
2516
|
+
".gradle",
|
|
2517
|
+
".idea",
|
|
2518
|
+
".vscode"
|
|
2519
|
+
]);
|
|
2520
|
+
function walkDirs(base) {
|
|
2521
|
+
const results = [];
|
|
2522
|
+
const queue = [{
|
|
2523
|
+
dir: base,
|
|
2524
|
+
depth: 0
|
|
2525
|
+
}];
|
|
2526
|
+
while (queue.length > 0 && results.length < MAX_ENTRIES) {
|
|
2527
|
+
const { dir, depth } = queue.shift();
|
|
2528
|
+
let entries;
|
|
2529
|
+
try {
|
|
2530
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
2531
|
+
} catch {
|
|
2532
|
+
continue;
|
|
2533
|
+
}
|
|
2534
|
+
for (const e of entries) {
|
|
2535
|
+
if (results.length >= MAX_ENTRIES) break;
|
|
2536
|
+
if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
|
|
2537
|
+
try {
|
|
2538
|
+
const full = join(dir, e.name);
|
|
2539
|
+
if (e.isDirectory() || e.isSymbolicLink() && statSync(full).isDirectory()) {
|
|
2540
|
+
results.push({
|
|
2541
|
+
rel: relative(base, full),
|
|
2542
|
+
path: full
|
|
2543
|
+
});
|
|
2544
|
+
if (depth + 1 < MAX_DEPTH) queue.push({
|
|
2545
|
+
dir: full,
|
|
2546
|
+
depth: depth + 1
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
} catch {}
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
return results;
|
|
2553
|
+
}
|
|
2554
|
+
function compactHome(p) {
|
|
2555
|
+
return p === HOME ? "~" : p.startsWith(`${HOME}/`) ? `~${p.slice(HOME.length)}` : p;
|
|
2556
|
+
}
|
|
2557
|
+
function highlightName(name, positions, hlColor, dimColor) {
|
|
2558
|
+
const spans = [];
|
|
2559
|
+
let run = "";
|
|
2560
|
+
let runHL = false;
|
|
2561
|
+
for (let i = 0; i < name.length; i++) {
|
|
2562
|
+
const hl = positions.has(i);
|
|
2563
|
+
if (hl !== runHL && run) {
|
|
2564
|
+
spans.push({
|
|
2565
|
+
text: run,
|
|
2566
|
+
color: runHL ? hlColor : dimColor
|
|
2567
|
+
});
|
|
2568
|
+
run = "";
|
|
2569
|
+
}
|
|
2570
|
+
run += name[i];
|
|
2571
|
+
runHL = hl;
|
|
2572
|
+
}
|
|
2573
|
+
if (run) spans.push({
|
|
2574
|
+
text: run,
|
|
2575
|
+
color: runHL ? hlColor : dimColor
|
|
2576
|
+
});
|
|
2577
|
+
return spans;
|
|
2578
|
+
}
|
|
2579
|
+
function CwdPickerModal({ currentCwd, onPick }) {
|
|
2580
|
+
const COLOR = useColors();
|
|
2581
|
+
const SURFACE = useSurfaces();
|
|
2582
|
+
const inputRef = useRef(null);
|
|
2583
|
+
const [query, setQuery] = useState("");
|
|
2584
|
+
const [browsePath, setBrowsePath] = useState(HOME);
|
|
2585
|
+
const [selectedIdx, setSelectedIdx] = useState(0);
|
|
2586
|
+
useEffect(() => {
|
|
2587
|
+
inputRef.current?.focus();
|
|
2588
|
+
}, []);
|
|
2589
|
+
const dirs = useMemo(() => walkDirs(browsePath), [browsePath]);
|
|
2590
|
+
const fzf = useMemo(() => new Fzf(dirs, {
|
|
2591
|
+
selector: (d) => d.rel,
|
|
2592
|
+
tiebreakers: [byLengthAsc],
|
|
2593
|
+
limit: 200
|
|
2594
|
+
}), [dirs]);
|
|
2595
|
+
const results = useMemo(() => {
|
|
2596
|
+
if (!query) return dirs.slice(0, 200).map((item) => ({
|
|
2597
|
+
item,
|
|
2598
|
+
start: 0,
|
|
2599
|
+
end: 0,
|
|
2600
|
+
score: 0,
|
|
2601
|
+
positions: /* @__PURE__ */ new Set()
|
|
2602
|
+
}));
|
|
2603
|
+
return fzf.find(query);
|
|
2604
|
+
}, [
|
|
2605
|
+
fzf,
|
|
2606
|
+
dirs,
|
|
2607
|
+
query
|
|
2608
|
+
]);
|
|
2609
|
+
const safeIndex = results.length === 0 ? 0 : Math.min(selectedIdx, results.length - 1);
|
|
2610
|
+
const handleQueryChange = useCallback((next) => {
|
|
2611
|
+
setQuery(next);
|
|
2612
|
+
setSelectedIdx(0);
|
|
2613
|
+
}, []);
|
|
2614
|
+
const commit = () => {
|
|
2615
|
+
const row = results[safeIndex];
|
|
2616
|
+
if (row) onPick(row.item.path);
|
|
2617
|
+
};
|
|
2618
|
+
const drillDown = () => {
|
|
2619
|
+
const row = results[safeIndex];
|
|
2620
|
+
if (row) {
|
|
2621
|
+
setBrowsePath(row.item.path);
|
|
2622
|
+
setQuery("");
|
|
2623
|
+
setSelectedIdx(0);
|
|
2624
|
+
}
|
|
2625
|
+
};
|
|
2626
|
+
const goUp = () => {
|
|
2627
|
+
const parent = join(browsePath, "..");
|
|
2628
|
+
if (parent !== browsePath) {
|
|
2629
|
+
setBrowsePath(parent);
|
|
2630
|
+
setQuery("");
|
|
2631
|
+
setSelectedIdx(0);
|
|
2632
|
+
}
|
|
2633
|
+
};
|
|
2634
|
+
const viewport = useMemo(() => {
|
|
2635
|
+
if (results.length <= VISIBLE_ROWS$1) return {
|
|
2636
|
+
start: 0,
|
|
2637
|
+
slice: results
|
|
2638
|
+
};
|
|
2639
|
+
const half = Math.floor(VISIBLE_ROWS$1 / 2);
|
|
2640
|
+
let start = Math.max(0, safeIndex - half);
|
|
2641
|
+
if (start + VISIBLE_ROWS$1 > results.length) start = results.length - VISIBLE_ROWS$1;
|
|
2642
|
+
return {
|
|
2643
|
+
start,
|
|
2644
|
+
slice: results.slice(start, start + VISIBLE_ROWS$1)
|
|
2645
|
+
};
|
|
2646
|
+
}, [results, safeIndex]);
|
|
2647
|
+
useKeyboard((key) => {
|
|
2648
|
+
if (key.name === "up") {
|
|
2649
|
+
setSelectedIdx((i) => results.length === 0 ? i : ((i - 1) % results.length + results.length) % results.length);
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
if (key.name === "down") {
|
|
2653
|
+
setSelectedIdx((i) => results.length === 0 ? i : (i + 1) % results.length);
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
if (key.name === "tab") {
|
|
2657
|
+
drillDown();
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
if (key.name === "left" && !query) {
|
|
2661
|
+
goUp();
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
if (key.name === "right" && !query) {
|
|
2665
|
+
drillDown();
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
if (key.name === "return") commit();
|
|
2669
|
+
});
|
|
2670
|
+
return /* @__PURE__ */ jsxs(Modal, {
|
|
2671
|
+
title: "change directory",
|
|
2672
|
+
maxWidth: 80,
|
|
2673
|
+
children: [
|
|
2674
|
+
/* @__PURE__ */ jsxs("text", {
|
|
2675
|
+
fg: COLOR.dim,
|
|
2676
|
+
children: [
|
|
2677
|
+
/* @__PURE__ */ jsx("span", {
|
|
2678
|
+
fg: COLOR.mute,
|
|
2679
|
+
children: "browsing "
|
|
2680
|
+
}),
|
|
2681
|
+
/* @__PURE__ */ jsx("span", {
|
|
2682
|
+
fg: COLOR.brand,
|
|
2683
|
+
children: compactHome(browsePath)
|
|
2684
|
+
}),
|
|
2685
|
+
/* @__PURE__ */ jsx("span", {
|
|
2686
|
+
fg: COLOR.mute,
|
|
2687
|
+
children: ` · ${dirs.length} dirs`
|
|
2688
|
+
})
|
|
2689
|
+
]
|
|
2690
|
+
}),
|
|
2691
|
+
/* @__PURE__ */ jsx("box", {
|
|
2692
|
+
style: {
|
|
2693
|
+
border: true,
|
|
2694
|
+
borderColor: COLOR.borderActive,
|
|
2695
|
+
paddingLeft: 1,
|
|
2696
|
+
paddingRight: 1,
|
|
2697
|
+
height: 3
|
|
2698
|
+
},
|
|
2699
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
2700
|
+
ref: inputRef,
|
|
2701
|
+
focused: true,
|
|
2702
|
+
placeholder: "fuzzy search directories…",
|
|
2703
|
+
onInput: handleQueryChange,
|
|
2704
|
+
onSubmit: () => {},
|
|
2705
|
+
style: { flexGrow: 1 }
|
|
2706
|
+
})
|
|
2707
|
+
}),
|
|
2708
|
+
/* @__PURE__ */ jsx("box", {
|
|
2709
|
+
style: {
|
|
2710
|
+
flexDirection: "column",
|
|
2711
|
+
height: VISIBLE_ROWS$1,
|
|
2712
|
+
flexShrink: 0
|
|
2713
|
+
},
|
|
2714
|
+
children: results.length === 0 ? /* @__PURE__ */ jsxs("text", {
|
|
2715
|
+
fg: COLOR.dim,
|
|
2716
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2717
|
+
fg: COLOR.mute,
|
|
2718
|
+
children: "no directories match "
|
|
2719
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2720
|
+
fg: COLOR.warn,
|
|
2721
|
+
children: query.trim()
|
|
2722
|
+
})]
|
|
2723
|
+
}) : viewport.slice.map((result, i) => {
|
|
2724
|
+
const focused = viewport.start + i === safeIndex;
|
|
2725
|
+
const current = result.item.path === currentCwd;
|
|
2726
|
+
const marker = current ? "●" : " ";
|
|
2727
|
+
const nameColor = focused ? COLOR.brand : COLOR.dim;
|
|
2728
|
+
const spans = query ? highlightName(result.item.rel, result.positions, COLOR.warn, nameColor) : [{
|
|
2729
|
+
text: result.item.rel,
|
|
2730
|
+
color: nameColor
|
|
2731
|
+
}];
|
|
2732
|
+
return /* @__PURE__ */ jsx("box", {
|
|
2733
|
+
style: {
|
|
2734
|
+
height: 1,
|
|
2735
|
+
paddingLeft: 1,
|
|
2736
|
+
paddingRight: 1,
|
|
2737
|
+
flexShrink: 0,
|
|
2738
|
+
backgroundColor: focused ? SURFACE.selection : void 0
|
|
2739
|
+
},
|
|
2740
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
2741
|
+
wrapMode: "none",
|
|
2742
|
+
children: [
|
|
2743
|
+
/* @__PURE__ */ jsx("span", {
|
|
2744
|
+
fg: current ? COLOR.brand : COLOR.mute,
|
|
2745
|
+
children: marker
|
|
2746
|
+
}),
|
|
2747
|
+
/* @__PURE__ */ jsx("span", {
|
|
2748
|
+
fg: COLOR.mute,
|
|
2749
|
+
children: " "
|
|
2750
|
+
}),
|
|
2751
|
+
spans.map((s, si) => /* @__PURE__ */ jsx("span", {
|
|
2752
|
+
fg: s.color,
|
|
2753
|
+
children: s.text
|
|
2754
|
+
}, si)),
|
|
2755
|
+
/* @__PURE__ */ jsx("span", {
|
|
2756
|
+
fg: COLOR.mute,
|
|
2757
|
+
children: "/"
|
|
2758
|
+
})
|
|
2759
|
+
]
|
|
2760
|
+
})
|
|
2761
|
+
}, result.item.path);
|
|
2762
|
+
})
|
|
2763
|
+
}),
|
|
2764
|
+
/* @__PURE__ */ jsxs("text", {
|
|
2765
|
+
fg: COLOR.dim,
|
|
2766
|
+
children: [
|
|
2767
|
+
/* @__PURE__ */ jsx("span", {
|
|
2768
|
+
fg: COLOR.warn,
|
|
2769
|
+
children: "↑↓"
|
|
2770
|
+
}),
|
|
2771
|
+
" navigate · ",
|
|
2772
|
+
/* @__PURE__ */ jsx("span", {
|
|
2773
|
+
fg: COLOR.warn,
|
|
2774
|
+
children: "tab"
|
|
2775
|
+
}),
|
|
2776
|
+
" enter dir · ",
|
|
2777
|
+
/* @__PURE__ */ jsx("span", {
|
|
2778
|
+
fg: COLOR.warn,
|
|
2779
|
+
children: "←"
|
|
2780
|
+
}),
|
|
2781
|
+
" parent · ",
|
|
2782
|
+
/* @__PURE__ */ jsx("span", {
|
|
2783
|
+
fg: COLOR.warn,
|
|
2784
|
+
children: "↵"
|
|
2785
|
+
}),
|
|
2786
|
+
" select · ",
|
|
2787
|
+
/* @__PURE__ */ jsx("span", {
|
|
2788
|
+
fg: COLOR.warn,
|
|
2789
|
+
children: "esc"
|
|
2790
|
+
}),
|
|
2791
|
+
" close · ",
|
|
2792
|
+
/* @__PURE__ */ jsx("span", {
|
|
2793
|
+
fg: COLOR.mute,
|
|
2794
|
+
children: `${results.length} match${results.length === 1 ? "" : "es"}`
|
|
2795
|
+
})
|
|
2796
|
+
]
|
|
2797
|
+
})
|
|
2798
|
+
]
|
|
2799
|
+
});
|
|
2800
|
+
}
|
|
2801
|
+
//#endregion
|
|
2447
2802
|
//#region src/tui/discovery-shell.tsx
|
|
2448
2803
|
/**
|
|
2449
2804
|
* SWR throttles. `files` is short so a long-open `@` popover picks up
|
|
@@ -2786,6 +3141,220 @@ function EffortRow({ level, isCurrent, isFocused, highlightBg }) {
|
|
|
2786
3141
|
});
|
|
2787
3142
|
}
|
|
2788
3143
|
//#endregion
|
|
3144
|
+
//#region src/tui/keybindings-modal.tsx
|
|
3145
|
+
/**
|
|
3146
|
+
* Fixed column width for the rendered key — derived once from
|
|
3147
|
+
* {@link KEYBINDING_DEFS} so adding an action with a wider default spec
|
|
3148
|
+
* (`ctrl+shift+x`, etc.) automatically grows the column instead of
|
|
3149
|
+
* truncating the label. Falls back to a sensible minimum so empty /
|
|
3150
|
+
* one-glyph defaults don't collapse the layout.
|
|
3151
|
+
*/
|
|
3152
|
+
const KEY_COL_WIDTH = (() => {
|
|
3153
|
+
let max = 8;
|
|
3154
|
+
for (const def of KEYBINDING_DEFS) {
|
|
3155
|
+
const width = formatBindingForDisplay(def.default).length;
|
|
3156
|
+
if (width > max) max = width;
|
|
3157
|
+
}
|
|
3158
|
+
return max + 2;
|
|
3159
|
+
})();
|
|
3160
|
+
function KeybindingsModal({ bindings, filePath, onEditFile, onClose }) {
|
|
3161
|
+
const COLOR = useColors();
|
|
3162
|
+
const SURFACE = useSurfaces();
|
|
3163
|
+
const { height: termHeight } = useTerminalDimensions();
|
|
3164
|
+
const scrollRef = useRef(null);
|
|
3165
|
+
const sections = useMemo(() => groupBindings(bindings), [bindings]);
|
|
3166
|
+
useKeyboard((key) => {
|
|
3167
|
+
if (key.name === "return") onEditFile();
|
|
3168
|
+
});
|
|
3169
|
+
const idealHeight = Math.floor((termHeight - 4) * .7);
|
|
3170
|
+
const maxHeight = Math.max(18, Math.min(40, idealHeight));
|
|
3171
|
+
const totalCount = KEYBINDING_DEFS.length;
|
|
3172
|
+
return /* @__PURE__ */ jsxs(Modal, {
|
|
3173
|
+
title: "keybindings",
|
|
3174
|
+
bottomTitle: "↵ edit file · esc close",
|
|
3175
|
+
rightTitle: /* @__PURE__ */ jsx(CountsBadge$1, { count: totalCount }),
|
|
3176
|
+
maxWidth: 104,
|
|
3177
|
+
minWidth: 64,
|
|
3178
|
+
maxHeight,
|
|
3179
|
+
onClose,
|
|
3180
|
+
children: [/* @__PURE__ */ jsx("box", {
|
|
3181
|
+
style: {
|
|
3182
|
+
flexDirection: "column",
|
|
3183
|
+
flexGrow: 1,
|
|
3184
|
+
flexShrink: 1,
|
|
3185
|
+
overflow: "hidden"
|
|
3186
|
+
},
|
|
3187
|
+
children: /* @__PURE__ */ jsx("scrollbox", {
|
|
3188
|
+
ref: scrollRef,
|
|
3189
|
+
focusable: false,
|
|
3190
|
+
stickyScroll: false,
|
|
3191
|
+
style: {
|
|
3192
|
+
flexGrow: 1,
|
|
3193
|
+
flexShrink: 1
|
|
3194
|
+
},
|
|
3195
|
+
children: sections.map((section, sectionIdx) => /* @__PURE__ */ jsxs("box", {
|
|
3196
|
+
style: {
|
|
3197
|
+
flexDirection: "column",
|
|
3198
|
+
flexShrink: 0,
|
|
3199
|
+
marginTop: sectionIdx === 0 ? 0 : 1
|
|
3200
|
+
},
|
|
3201
|
+
children: [/* @__PURE__ */ jsx(SectionHeader, { label: section.group }), section.rows.map((row) => /* @__PURE__ */ jsx(BindingRow, {
|
|
3202
|
+
def: row.def,
|
|
3203
|
+
spec: row.spec
|
|
3204
|
+
}, row.def.action))]
|
|
3205
|
+
}, section.group))
|
|
3206
|
+
})
|
|
3207
|
+
}), /* @__PURE__ */ jsx(EditFileButton, {
|
|
3208
|
+
filePath,
|
|
3209
|
+
highlightBg: SURFACE.selection,
|
|
3210
|
+
brand: COLOR.brand,
|
|
3211
|
+
mute: COLOR.mute,
|
|
3212
|
+
dim: COLOR.dim
|
|
3213
|
+
})]
|
|
3214
|
+
});
|
|
3215
|
+
}
|
|
3216
|
+
function SectionHeader({ label }) {
|
|
3217
|
+
return /* @__PURE__ */ jsx("box", {
|
|
3218
|
+
style: {
|
|
3219
|
+
flexShrink: 0,
|
|
3220
|
+
paddingLeft: 1,
|
|
3221
|
+
paddingRight: 1
|
|
3222
|
+
},
|
|
3223
|
+
children: /* @__PURE__ */ jsx("text", {
|
|
3224
|
+
wrapMode: "none",
|
|
3225
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3226
|
+
fg: useColors().brand,
|
|
3227
|
+
children: label
|
|
3228
|
+
})
|
|
3229
|
+
})
|
|
3230
|
+
});
|
|
3231
|
+
}
|
|
3232
|
+
function BindingRow({ def, spec }) {
|
|
3233
|
+
const COLOR = useColors();
|
|
3234
|
+
const display = formatBindingForDisplay(spec);
|
|
3235
|
+
const keyText = (display || "—").padEnd(KEY_COL_WIDTH, " ");
|
|
3236
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
3237
|
+
style: {
|
|
3238
|
+
flexDirection: "column",
|
|
3239
|
+
flexShrink: 0,
|
|
3240
|
+
paddingLeft: 3,
|
|
3241
|
+
paddingRight: 1
|
|
3242
|
+
},
|
|
3243
|
+
children: [/* @__PURE__ */ jsxs("text", {
|
|
3244
|
+
wrapMode: "none",
|
|
3245
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
3246
|
+
fg: display ? COLOR.warn : COLOR.mute,
|
|
3247
|
+
children: keyText
|
|
3248
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
3249
|
+
fg: COLOR.dim,
|
|
3250
|
+
children: def.label
|
|
3251
|
+
})]
|
|
3252
|
+
}), /* @__PURE__ */ jsx("box", {
|
|
3253
|
+
style: {
|
|
3254
|
+
flexShrink: 0,
|
|
3255
|
+
paddingLeft: KEY_COL_WIDTH
|
|
3256
|
+
},
|
|
3257
|
+
children: /* @__PURE__ */ jsx("text", {
|
|
3258
|
+
wrapMode: "word",
|
|
3259
|
+
fg: COLOR.mute,
|
|
3260
|
+
children: def.description
|
|
3261
|
+
})
|
|
3262
|
+
})]
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3265
|
+
function EditFileButton({ filePath, highlightBg, brand, mute, dim }) {
|
|
3266
|
+
const display = filePath ? compactPath(filePath) : "~/.zidane/keybindings.json";
|
|
3267
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
3268
|
+
style: {
|
|
3269
|
+
flexShrink: 0,
|
|
3270
|
+
flexDirection: "column",
|
|
3271
|
+
paddingLeft: 1,
|
|
3272
|
+
paddingRight: 1,
|
|
3273
|
+
backgroundColor: highlightBg
|
|
3274
|
+
},
|
|
3275
|
+
children: [/* @__PURE__ */ jsxs("text", {
|
|
3276
|
+
wrapMode: "none",
|
|
3277
|
+
children: [
|
|
3278
|
+
/* @__PURE__ */ jsx("span", {
|
|
3279
|
+
fg: brand,
|
|
3280
|
+
children: "▶ "
|
|
3281
|
+
}),
|
|
3282
|
+
/* @__PURE__ */ jsx("span", {
|
|
3283
|
+
fg: brand,
|
|
3284
|
+
children: "Edit keybindings file"
|
|
3285
|
+
}),
|
|
3286
|
+
/* @__PURE__ */ jsx("span", {
|
|
3287
|
+
fg: mute,
|
|
3288
|
+
children: " "
|
|
3289
|
+
}),
|
|
3290
|
+
/* @__PURE__ */ jsx("span", {
|
|
3291
|
+
fg: brand,
|
|
3292
|
+
children: "›"
|
|
3293
|
+
})
|
|
3294
|
+
]
|
|
3295
|
+
}), /* @__PURE__ */ jsxs("text", {
|
|
3296
|
+
wrapMode: "none",
|
|
3297
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
3298
|
+
fg: mute,
|
|
3299
|
+
children: " "
|
|
3300
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
3301
|
+
fg: dim,
|
|
3302
|
+
children: `${display} — restart to apply changes`
|
|
3303
|
+
})]
|
|
3304
|
+
})]
|
|
3305
|
+
});
|
|
3306
|
+
}
|
|
3307
|
+
function CountsBadge$1({ count }) {
|
|
3308
|
+
const COLOR = useColors();
|
|
3309
|
+
return /* @__PURE__ */ jsxs("text", {
|
|
3310
|
+
wrapMode: "none",
|
|
3311
|
+
children: [
|
|
3312
|
+
/* @__PURE__ */ jsx("span", {
|
|
3313
|
+
fg: COLOR.mute,
|
|
3314
|
+
children: " "
|
|
3315
|
+
}),
|
|
3316
|
+
/* @__PURE__ */ jsx("span", {
|
|
3317
|
+
fg: COLOR.accent,
|
|
3318
|
+
children: String(count)
|
|
3319
|
+
}),
|
|
3320
|
+
/* @__PURE__ */ jsx("span", {
|
|
3321
|
+
fg: COLOR.mute,
|
|
3322
|
+
children: ` action${count === 1 ? "" : "s"} `
|
|
3323
|
+
})
|
|
3324
|
+
]
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3327
|
+
/**
|
|
3328
|
+
* Walk `KEYBINDING_DEFS` in order and bucket rows into contiguous
|
|
3329
|
+
* sections by `group`. Preserves catalog order — if two actions in the
|
|
3330
|
+
* same group are split by an entry from another group, they'd render
|
|
3331
|
+
* as two separate sections with the same header (catalog order wins
|
|
3332
|
+
* over "merge same-group entries" so the on-screen story matches the
|
|
3333
|
+
* on-disk file).
|
|
3334
|
+
*/
|
|
3335
|
+
function groupBindings(bindings) {
|
|
3336
|
+
const sections = [];
|
|
3337
|
+
for (const def of KEYBINDING_DEFS) {
|
|
3338
|
+
const last = sections[sections.length - 1];
|
|
3339
|
+
const spec = bindings[def.action] ?? "";
|
|
3340
|
+
if (last && last.group === def.group) {
|
|
3341
|
+
last.rows.push({
|
|
3342
|
+
def,
|
|
3343
|
+
spec
|
|
3344
|
+
});
|
|
3345
|
+
continue;
|
|
3346
|
+
}
|
|
3347
|
+
sections.push({
|
|
3348
|
+
group: def.group,
|
|
3349
|
+
rows: [{
|
|
3350
|
+
def,
|
|
3351
|
+
spec
|
|
3352
|
+
}]
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
3355
|
+
return sections;
|
|
3356
|
+
}
|
|
3357
|
+
//#endregion
|
|
2789
3358
|
//#region src/tui/model-picker.tsx
|
|
2790
3359
|
/**
|
|
2791
3360
|
* Cross-provider, searchable model picker.
|
|
@@ -4637,6 +5206,282 @@ function OptionList({ items, initialCursor, onPick }) {
|
|
|
4637
5206
|
});
|
|
4638
5207
|
}
|
|
4639
5208
|
//#endregion
|
|
5209
|
+
//#region src/tui/oauth-url-block.tsx
|
|
5210
|
+
/** @jsxImportSource @opentui/react */
|
|
5211
|
+
/**
|
|
5212
|
+
* Long URL rendered as N single-row OSC 8 hyperlinks instead of one
|
|
5213
|
+
* wrapped hyperlink.
|
|
5214
|
+
*
|
|
5215
|
+
* Why split: OpenTUI packs a `linkId` per cell into the attribute
|
|
5216
|
+
* bitfield, but its renderer emits an OSC 8 open/close pair per visual
|
|
5217
|
+
* row without the spec's `id=` parameter. Terminals (notably iTerm2)
|
|
5218
|
+
* need a matching `id=` to stitch hyperlink fragments across rows —
|
|
5219
|
+
* without it, only one row of a wrapped link ends up clickable. We
|
|
5220
|
+
* pre-chunk the URL into rows that fit on one line and render each as
|
|
5221
|
+
* its own intact `<a href>` with `wrapMode="none"`, so the terminal
|
|
5222
|
+
* never sees a wrapped hyperlink and clicking any row opens the full
|
|
5223
|
+
* URL.
|
|
5224
|
+
*
|
|
5225
|
+
* Width: caller passes a hard cap (its container's content-area width).
|
|
5226
|
+
* We further clamp by the live terminal width minus `chromeWidth` so a
|
|
5227
|
+
* narrow terminal still chunks short enough to avoid forced wrap by
|
|
5228
|
+
* the layout engine. `chromeWidth` is the container's border + padding
|
|
5229
|
+
* budget — default 14 fits a typical modal; pass a smaller value for
|
|
5230
|
+
* less-padded containers (e.g. 6 for the auth wizard panel).
|
|
5231
|
+
*/
|
|
5232
|
+
function OAuthUrlBlock({ url, fg, maxLineWidth, chromeWidth = 14 }) {
|
|
5233
|
+
const { width: termWidth } = useTerminalDimensions();
|
|
5234
|
+
return /* @__PURE__ */ jsx(Fragment, { children: chunkString(url, Math.max(20, Math.min(maxLineWidth, termWidth - chromeWidth))).map((line, i) => /* @__PURE__ */ jsx("text", {
|
|
5235
|
+
wrapMode: "none",
|
|
5236
|
+
fg,
|
|
5237
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
5238
|
+
href: url,
|
|
5239
|
+
children: line
|
|
5240
|
+
})
|
|
5241
|
+
}, i)) });
|
|
5242
|
+
}
|
|
5243
|
+
function chunkString(s, n) {
|
|
5244
|
+
if (s.length <= n) return [s];
|
|
5245
|
+
const out = [];
|
|
5246
|
+
for (let i = 0; i < s.length; i += n) out.push(s.slice(i, i + n));
|
|
5247
|
+
return out;
|
|
5248
|
+
}
|
|
5249
|
+
//#endregion
|
|
5250
|
+
//#region src/tui/oauth-auth-block.tsx
|
|
5251
|
+
/** Keystroke shown next to the open-browser button. */
|
|
5252
|
+
const OPEN_BROWSER_KEY = "ctrl+b";
|
|
5253
|
+
/**
|
|
5254
|
+
* Unified affordance for surfacing an OAuth authorization URL in the TUI:
|
|
5255
|
+
*
|
|
5256
|
+
* 1. A prominent OSC 8 hyperlink button — "Click here to open auth URL in
|
|
5257
|
+
* browser". Honored by every terminal that supports clickable links
|
|
5258
|
+
* (iTerm2, Kitty, WezTerm, Ghostty, Alacritty, …). Lands on the user's
|
|
5259
|
+
* LOCAL terminal even over SSH because OSC 8 is interpreted client-side.
|
|
5260
|
+
* 2. The full URL rendered greyed below, chunked into per-row hyperlinks
|
|
5261
|
+
* via {@link OAuthUrlBlock}. Backup channel for terminals without OSC 8,
|
|
5262
|
+
* and for users who'd rather copy via drag-select than click.
|
|
5263
|
+
* 3. Optional paste-back input. When `paste` is provided, an `<input>`
|
|
5264
|
+
* renders below the URL so the user can paste the FULL redirect URL
|
|
5265
|
+
* their browser ended up at after authorizing — useful when zidane runs
|
|
5266
|
+
* over SSH and the browser-side redirect to loopback can't reach the
|
|
5267
|
+
* remote callback server. The caller's `onSubmit` typically pipes the
|
|
5268
|
+
* pasted value through {@link fetchOAuthRedirect} so the in-process
|
|
5269
|
+
* server receives the request and the OAuth promise resolves through
|
|
5270
|
+
* the same happy path a real browser would have taken.
|
|
5271
|
+
*
|
|
5272
|
+
* The component owns presentation only — keyboard focus, input ref, and
|
|
5273
|
+
* the submit handler stay with the caller so the affordance composes
|
|
5274
|
+
* cleanly with whatever picker / wizard / modal it lives in.
|
|
5275
|
+
*/
|
|
5276
|
+
function OAuthAuthBlock({ authUrl, maxLineWidth, chromeWidth = 14, paste }) {
|
|
5277
|
+
const COLOR = useColors();
|
|
5278
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
5279
|
+
style: {
|
|
5280
|
+
flexDirection: "column",
|
|
5281
|
+
gap: 1
|
|
5282
|
+
},
|
|
5283
|
+
children: [
|
|
5284
|
+
/* @__PURE__ */ jsx(OpenBrowserButton, { authUrl }),
|
|
5285
|
+
/* @__PURE__ */ jsxs("box", {
|
|
5286
|
+
style: { flexDirection: "column" },
|
|
5287
|
+
children: [/* @__PURE__ */ jsx("text", {
|
|
5288
|
+
fg: COLOR.mute,
|
|
5289
|
+
children: "or copy the URL manually:"
|
|
5290
|
+
}), /* @__PURE__ */ jsx(OAuthUrlBlock, {
|
|
5291
|
+
url: authUrl,
|
|
5292
|
+
fg: COLOR.dim,
|
|
5293
|
+
maxLineWidth,
|
|
5294
|
+
chromeWidth
|
|
5295
|
+
})]
|
|
5296
|
+
}),
|
|
5297
|
+
paste && /* @__PURE__ */ jsxs("box", {
|
|
5298
|
+
style: { flexDirection: "column" },
|
|
5299
|
+
children: [
|
|
5300
|
+
/* @__PURE__ */ jsx("text", {
|
|
5301
|
+
fg: COLOR.mute,
|
|
5302
|
+
children: "or — if the browser couldn't reach this machine (SSH, firewall) — paste the URL it tried to redirect to:"
|
|
5303
|
+
}),
|
|
5304
|
+
paste.hint && /* @__PURE__ */ jsx("text", {
|
|
5305
|
+
fg: toneColor(paste.hint.tone, COLOR),
|
|
5306
|
+
children: paste.hint.text
|
|
5307
|
+
}),
|
|
5308
|
+
/* @__PURE__ */ jsx("box", {
|
|
5309
|
+
style: {
|
|
5310
|
+
border: true,
|
|
5311
|
+
borderColor: paste.focused ? COLOR.borderActive : COLOR.border,
|
|
5312
|
+
paddingLeft: 1,
|
|
5313
|
+
paddingRight: 1,
|
|
5314
|
+
height: 3
|
|
5315
|
+
},
|
|
5316
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
5317
|
+
ref: paste.inputRef,
|
|
5318
|
+
focused: paste.focused,
|
|
5319
|
+
placeholder: paste.placeholder ?? "paste redirect URL and press enter…",
|
|
5320
|
+
onSubmit: paste.onSubmit,
|
|
5321
|
+
style: { flexGrow: 1 }
|
|
5322
|
+
})
|
|
5323
|
+
})
|
|
5324
|
+
]
|
|
5325
|
+
})
|
|
5326
|
+
]
|
|
5327
|
+
});
|
|
5328
|
+
}
|
|
5329
|
+
/**
|
|
5330
|
+
* Real button: a bordered, brand-colored box hosting a labeled keystroke
|
|
5331
|
+
* hint. Pressing `ctrl+b` (anywhere this block is mounted) calls
|
|
5332
|
+
* {@link tryOpenBrowser} to launch the URL via the OS handler AND
|
|
5333
|
+
* {@link writeToClipboard} so the URL also lands in the user's clipboard
|
|
5334
|
+
* (OSC 52 → works over SSH, native helper → works locally). Both fire
|
|
5335
|
+
* because either alone can fail silently — `open`/`xdg-open` are no-ops
|
|
5336
|
+
* on a headless box, and OSC 52 is disabled on some terminals — and the
|
|
5337
|
+
* combination covers both failure modes without prompting again.
|
|
5338
|
+
*
|
|
5339
|
+
* The visible label is also wrapped in an OSC 8 hyperlink so a mouse
|
|
5340
|
+
* click in a supporting terminal (iTerm2, Kitty, WezTerm, …) reaches the
|
|
5341
|
+
* same browser. The keybind is the authoritative path — it doesn't
|
|
5342
|
+
* depend on terminal capability — and the hyperlink is the bonus.
|
|
5343
|
+
*/
|
|
5344
|
+
function OpenBrowserButton({ authUrl }) {
|
|
5345
|
+
const COLOR = useColors();
|
|
5346
|
+
const [feedback, setFeedback] = useState(null);
|
|
5347
|
+
const feedbackTimer = useRef(null);
|
|
5348
|
+
const trigger = useCallback(() => {
|
|
5349
|
+
tryOpenBrowser(authUrl);
|
|
5350
|
+
setFeedback(writeToClipboard(authUrl) ? "opened in browser · URL copied to clipboard" : "opened in browser");
|
|
5351
|
+
if (feedbackTimer.current) clearTimeout(feedbackTimer.current);
|
|
5352
|
+
feedbackTimer.current = setTimeout(setFeedback, 4e3, null);
|
|
5353
|
+
}, [authUrl]);
|
|
5354
|
+
useKeyboard((key) => {
|
|
5355
|
+
if (key.ctrl && key.name === "b") {
|
|
5356
|
+
key.preventDefault();
|
|
5357
|
+
trigger();
|
|
5358
|
+
}
|
|
5359
|
+
});
|
|
5360
|
+
useEffect(() => () => {
|
|
5361
|
+
if (feedbackTimer.current) clearTimeout(feedbackTimer.current);
|
|
5362
|
+
}, []);
|
|
5363
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
5364
|
+
style: { flexDirection: "column" },
|
|
5365
|
+
children: [/* @__PURE__ */ jsx("box", {
|
|
5366
|
+
style: {
|
|
5367
|
+
border: true,
|
|
5368
|
+
borderColor: COLOR.brand,
|
|
5369
|
+
paddingLeft: 1,
|
|
5370
|
+
paddingRight: 1,
|
|
5371
|
+
alignSelf: "flex-start"
|
|
5372
|
+
},
|
|
5373
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
5374
|
+
wrapMode: "none",
|
|
5375
|
+
children: [
|
|
5376
|
+
/* @__PURE__ */ jsx("a", {
|
|
5377
|
+
href: authUrl,
|
|
5378
|
+
fg: COLOR.brand,
|
|
5379
|
+
children: "↗ Open auth URL in browser"
|
|
5380
|
+
}),
|
|
5381
|
+
/* @__PURE__ */ jsx("span", {
|
|
5382
|
+
fg: COLOR.mute,
|
|
5383
|
+
children: " "
|
|
5384
|
+
}),
|
|
5385
|
+
/* @__PURE__ */ jsx("span", {
|
|
5386
|
+
fg: COLOR.warn,
|
|
5387
|
+
children: OPEN_BROWSER_KEY
|
|
5388
|
+
}),
|
|
5389
|
+
/* @__PURE__ */ jsx("span", {
|
|
5390
|
+
fg: COLOR.mute,
|
|
5391
|
+
children: " open · click also works"
|
|
5392
|
+
})
|
|
5393
|
+
]
|
|
5394
|
+
})
|
|
5395
|
+
}), feedback && /* @__PURE__ */ jsx("text", {
|
|
5396
|
+
fg: COLOR.accent,
|
|
5397
|
+
children: `✓ ${feedback}`
|
|
5398
|
+
})]
|
|
5399
|
+
});
|
|
5400
|
+
}
|
|
5401
|
+
function toneColor(tone, COLOR) {
|
|
5402
|
+
switch (tone) {
|
|
5403
|
+
case "error": return COLOR.error;
|
|
5404
|
+
case "accent": return COLOR.accent;
|
|
5405
|
+
case "dim": return COLOR.dim;
|
|
5406
|
+
}
|
|
5407
|
+
}
|
|
5408
|
+
/**
|
|
5409
|
+
* Self-contained MCP authorizing panel:
|
|
5410
|
+
*
|
|
5411
|
+
* - Renders the {@link OAuthAuthBlock} for the URL + paste input.
|
|
5412
|
+
* - Owns the input ref, the submit handler, and the hint state.
|
|
5413
|
+
* - Auto-fetches the pasted URL via {@link fetchOAuthRedirect} so the
|
|
5414
|
+
* MCP SDK's loopback callback server resolves the OAuth promise
|
|
5415
|
+
* through the same code path a real browser-redirect would have
|
|
5416
|
+
* taken — works over SSH where the browser can't reach the remote
|
|
5417
|
+
* callback server directly.
|
|
5418
|
+
*
|
|
5419
|
+
* `inputFocused` is forwarded as the input's `focused` prop AND read by
|
|
5420
|
+
* the parent picker's `useKeyboard` (via a ref) to suppress single-key
|
|
5421
|
+
* shortcuts (`l` / `o` / `r`) while the user is typing into the input.
|
|
5422
|
+
*/
|
|
5423
|
+
function McpAuthorizingPanel({ serverName, authUrl, maxLineWidth, chromeWidth, inputFocused }) {
|
|
5424
|
+
const COLOR = useColors();
|
|
5425
|
+
const inputRef = useRef(null);
|
|
5426
|
+
const [hint, setHint] = useState(null);
|
|
5427
|
+
const onSubmit = useCallback(() => {
|
|
5428
|
+
const value = inputRef.current?.value?.trim() ?? "";
|
|
5429
|
+
if (!value) return;
|
|
5430
|
+
setHint({
|
|
5431
|
+
text: "submitting redirect URL…",
|
|
5432
|
+
tone: "dim"
|
|
5433
|
+
});
|
|
5434
|
+
(async () => {
|
|
5435
|
+
try {
|
|
5436
|
+
const result = await fetchOAuthRedirect(value);
|
|
5437
|
+
if (result.status >= 200 && result.status < 300) setHint({
|
|
5438
|
+
text: "redirect accepted — finalizing…",
|
|
5439
|
+
tone: "accent"
|
|
5440
|
+
});
|
|
5441
|
+
else setHint({
|
|
5442
|
+
text: `callback server rejected the URL (${result.status}${result.message ? `: ${result.message}` : ""}). Cancel and retry.`,
|
|
5443
|
+
tone: "error"
|
|
5444
|
+
});
|
|
5445
|
+
} catch (err) {
|
|
5446
|
+
setHint({
|
|
5447
|
+
text: errorMessage(err),
|
|
5448
|
+
tone: "error"
|
|
5449
|
+
});
|
|
5450
|
+
}
|
|
5451
|
+
})();
|
|
5452
|
+
}, []);
|
|
5453
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
5454
|
+
style: {
|
|
5455
|
+
flexDirection: "column",
|
|
5456
|
+
border: ["top"],
|
|
5457
|
+
borderColor: COLOR.border,
|
|
5458
|
+
paddingTop: 1
|
|
5459
|
+
},
|
|
5460
|
+
children: [
|
|
5461
|
+
/* @__PURE__ */ jsx("text", {
|
|
5462
|
+
fg: COLOR.brand,
|
|
5463
|
+
children: `Authorizing ${serverName}`
|
|
5464
|
+
}),
|
|
5465
|
+
/* @__PURE__ */ jsx("text", {
|
|
5466
|
+
fg: COLOR.dim,
|
|
5467
|
+
children: "Your browser should have opened — complete the login, then return here."
|
|
5468
|
+
}),
|
|
5469
|
+
/* @__PURE__ */ jsx(OAuthAuthBlock, {
|
|
5470
|
+
authUrl,
|
|
5471
|
+
maxLineWidth,
|
|
5472
|
+
chromeWidth,
|
|
5473
|
+
paste: {
|
|
5474
|
+
inputRef,
|
|
5475
|
+
focused: inputFocused,
|
|
5476
|
+
onSubmit,
|
|
5477
|
+
placeholder: "paste redirect URL and press enter…",
|
|
5478
|
+
hint: hint ?? void 0
|
|
5479
|
+
}
|
|
5480
|
+
})
|
|
5481
|
+
]
|
|
5482
|
+
});
|
|
5483
|
+
}
|
|
5484
|
+
//#endregion
|
|
4640
5485
|
//#region src/tui/todo-indicator.tsx
|
|
4641
5486
|
/**
|
|
4642
5487
|
* Layout budget when truncating the content. The bar never wraps —
|
|
@@ -5085,9 +5930,15 @@ function EnterApiKeyStep({ descriptor, error, onSubmit }) {
|
|
|
5085
5930
|
});
|
|
5086
5931
|
}
|
|
5087
5932
|
function OAuthRunningStep({ descriptor, dataDir, onSuccess, onError }) {
|
|
5933
|
+
const usesManualPaste = oauthUsesManualCodePaste(descriptor);
|
|
5088
5934
|
const [url, setUrl] = useState(null);
|
|
5089
|
-
const [status, setStatus] = useState("starting browser…");
|
|
5090
|
-
const
|
|
5935
|
+
const [status, setStatus] = useState(usesManualPaste ? "opening browser…" : "starting browser…");
|
|
5936
|
+
const [pending, setPending] = useState(null);
|
|
5937
|
+
const [pasteHint, setPasteHint] = useState(null);
|
|
5938
|
+
const focused = useModalAwareFocus();
|
|
5939
|
+
const inputRef = useRef(null);
|
|
5940
|
+
const pendingRef = useRef(null);
|
|
5941
|
+
pendingRef.current = pending;
|
|
5091
5942
|
useEffect(() => {
|
|
5092
5943
|
const ac = new AbortController();
|
|
5093
5944
|
let cancelled = false;
|
|
@@ -5097,8 +5948,22 @@ function OAuthRunningStep({ descriptor, dataDir, onSuccess, onError }) {
|
|
|
5097
5948
|
onUrl: (loginUrl) => {
|
|
5098
5949
|
if (cancelled) return;
|
|
5099
5950
|
setUrl(loginUrl);
|
|
5100
|
-
setStatus("waiting for browser callback…");
|
|
5951
|
+
setStatus(usesManualPaste ? "complete the login in your browser, then paste the code below" : "waiting for browser callback…");
|
|
5101
5952
|
},
|
|
5953
|
+
onPrompt: (prompt) => new Promise((resolve, reject) => {
|
|
5954
|
+
if (cancelled) {
|
|
5955
|
+
reject(/* @__PURE__ */ new Error("OAuth flow cancelled"));
|
|
5956
|
+
return;
|
|
5957
|
+
}
|
|
5958
|
+
pendingRef.current?.reject(/* @__PURE__ */ new Error("superseded by a newer OAuth prompt"));
|
|
5959
|
+
setPending({
|
|
5960
|
+
message: prompt.message,
|
|
5961
|
+
placeholder: prompt.placeholder,
|
|
5962
|
+
allowEmpty: prompt.allowEmpty,
|
|
5963
|
+
resolve,
|
|
5964
|
+
reject
|
|
5965
|
+
});
|
|
5966
|
+
}),
|
|
5102
5967
|
onProgress: (message) => {
|
|
5103
5968
|
if (!cancelled) setStatus(message);
|
|
5104
5969
|
},
|
|
@@ -5117,31 +5982,70 @@ function OAuthRunningStep({ descriptor, dataDir, onSuccess, onError }) {
|
|
|
5117
5982
|
})();
|
|
5118
5983
|
return () => {
|
|
5119
5984
|
cancelled = true;
|
|
5985
|
+
pendingRef.current?.reject(/* @__PURE__ */ new Error("OAuth flow cancelled"));
|
|
5986
|
+
pendingRef.current = null;
|
|
5120
5987
|
ac.abort();
|
|
5121
5988
|
};
|
|
5122
5989
|
}, [
|
|
5123
5990
|
descriptor,
|
|
5124
5991
|
dataDir,
|
|
5125
5992
|
onSuccess,
|
|
5126
|
-
onError
|
|
5993
|
+
onError,
|
|
5994
|
+
usesManualPaste
|
|
5127
5995
|
]);
|
|
5996
|
+
const submitInput = useCallback(() => {
|
|
5997
|
+
const value = inputRef.current?.value?.trim() ?? "";
|
|
5998
|
+
const current = pendingRef.current;
|
|
5999
|
+
if (current) {
|
|
6000
|
+
if (!value && !current.allowEmpty) return;
|
|
6001
|
+
pendingRef.current = null;
|
|
6002
|
+
setPending(null);
|
|
6003
|
+
setStatus("exchanging code…");
|
|
6004
|
+
current.resolve(value);
|
|
6005
|
+
return;
|
|
6006
|
+
}
|
|
6007
|
+
if (!value) return;
|
|
6008
|
+
setPasteHint({
|
|
6009
|
+
text: "submitting redirect URL…",
|
|
6010
|
+
tone: "dim"
|
|
6011
|
+
});
|
|
6012
|
+
(async () => {
|
|
6013
|
+
try {
|
|
6014
|
+
const result = await fetchOAuthRedirect(value);
|
|
6015
|
+
if (result.status >= 200 && result.status < 300) {
|
|
6016
|
+
setPasteHint({
|
|
6017
|
+
text: "redirect accepted — exchanging code…",
|
|
6018
|
+
tone: "accent"
|
|
6019
|
+
});
|
|
6020
|
+
setStatus("exchanging code…");
|
|
6021
|
+
} else setPasteHint({
|
|
6022
|
+
text: `callback server rejected the URL (${result.status}${result.message ? `: ${result.message}` : ""}). Try again or restart the flow.`,
|
|
6023
|
+
tone: "error"
|
|
6024
|
+
});
|
|
6025
|
+
} catch (err) {
|
|
6026
|
+
setPasteHint({
|
|
6027
|
+
text: errorMessage(err),
|
|
6028
|
+
tone: "error"
|
|
6029
|
+
});
|
|
6030
|
+
}
|
|
6031
|
+
})();
|
|
6032
|
+
}, []);
|
|
5128
6033
|
return /* @__PURE__ */ jsxs(WizardPanel, {
|
|
5129
6034
|
title: `configure ${descriptor.label} — OAuth`,
|
|
5130
6035
|
children: [
|
|
5131
6036
|
/* @__PURE__ */ jsx(WizardEscHint, {}),
|
|
5132
|
-
/* @__PURE__ */ jsx(Spinner, { label: status }),
|
|
5133
|
-
url && /* @__PURE__ */
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
})]
|
|
6037
|
+
pending ? /* @__PURE__ */ jsx(Spinner, { label: pending.message }) : /* @__PURE__ */ jsx(Spinner, { label: status }),
|
|
6038
|
+
url && /* @__PURE__ */ jsx(OAuthAuthBlock, {
|
|
6039
|
+
authUrl: url,
|
|
6040
|
+
maxLineWidth: 200,
|
|
6041
|
+
chromeWidth: 6,
|
|
6042
|
+
paste: {
|
|
6043
|
+
inputRef,
|
|
6044
|
+
focused,
|
|
6045
|
+
onSubmit: submitInput,
|
|
6046
|
+
placeholder: pending?.placeholder ?? "paste redirect URL (or code) and press enter…",
|
|
6047
|
+
hint: pasteHint ?? void 0
|
|
6048
|
+
}
|
|
5145
6049
|
})
|
|
5146
6050
|
]
|
|
5147
6051
|
});
|
|
@@ -5513,7 +6417,61 @@ function renderProjectLabel(rowProject, currentProject, COLOR) {
|
|
|
5513
6417
|
/** Visible content lines: 1 minimum, 5 maximum (textarea scrolls past 5). */
|
|
5514
6418
|
const MIN_CONTENT_LINES = 1;
|
|
5515
6419
|
const MAX_CONTENT_LINES = 5;
|
|
5516
|
-
const
|
|
6420
|
+
const IMAGE_MEDIA_TYPES = {
|
|
6421
|
+
png: "image/png",
|
|
6422
|
+
jpg: "image/jpeg",
|
|
6423
|
+
jpeg: "image/jpeg",
|
|
6424
|
+
gif: "image/gif",
|
|
6425
|
+
webp: "image/webp",
|
|
6426
|
+
svg: "image/svg+xml",
|
|
6427
|
+
bmp: "image/bmp"
|
|
6428
|
+
};
|
|
6429
|
+
const MIME_BY_EXT = {
|
|
6430
|
+
txt: "text/plain",
|
|
6431
|
+
md: "text/markdown",
|
|
6432
|
+
json: "application/json",
|
|
6433
|
+
yaml: "text/yaml",
|
|
6434
|
+
yml: "text/yaml",
|
|
6435
|
+
toml: "text/plain",
|
|
6436
|
+
xml: "application/xml",
|
|
6437
|
+
html: "text/html",
|
|
6438
|
+
htm: "text/html",
|
|
6439
|
+
css: "text/css",
|
|
6440
|
+
csv: "text/csv",
|
|
6441
|
+
tsv: "text/tab-separated-values",
|
|
6442
|
+
js: "text/javascript",
|
|
6443
|
+
mjs: "text/javascript",
|
|
6444
|
+
cjs: "text/javascript",
|
|
6445
|
+
ts: "text/typescript",
|
|
6446
|
+
mts: "text/typescript",
|
|
6447
|
+
cts: "text/typescript",
|
|
6448
|
+
tsx: "text/typescript",
|
|
6449
|
+
jsx: "text/javascript",
|
|
6450
|
+
py: "text/x-python",
|
|
6451
|
+
rb: "text/x-ruby",
|
|
6452
|
+
rs: "text/x-rust",
|
|
6453
|
+
go: "text/x-go",
|
|
6454
|
+
java: "text/x-java",
|
|
6455
|
+
c: "text/x-c",
|
|
6456
|
+
h: "text/x-c",
|
|
6457
|
+
cpp: "text/x-c++",
|
|
6458
|
+
hpp: "text/x-c++",
|
|
6459
|
+
sh: "text/x-shellscript",
|
|
6460
|
+
bash: "text/x-shellscript",
|
|
6461
|
+
zsh: "text/x-shellscript",
|
|
6462
|
+
fish: "text/x-shellscript",
|
|
6463
|
+
sql: "text/x-sql",
|
|
6464
|
+
graphql: "text/x-graphql",
|
|
6465
|
+
pdf: "application/pdf",
|
|
6466
|
+
zip: "application/zip",
|
|
6467
|
+
tar: "application/x-tar",
|
|
6468
|
+
gz: "application/gzip",
|
|
6469
|
+
log: "text/plain",
|
|
6470
|
+
env: "text/plain",
|
|
6471
|
+
cfg: "text/plain",
|
|
6472
|
+
ini: "text/plain",
|
|
6473
|
+
conf: "text/plain"
|
|
6474
|
+
};
|
|
5517
6475
|
/**
|
|
5518
6476
|
* Stable empty-array reference — keeps `queuedMessages` default referentially
|
|
5519
6477
|
* stable across renders so memoized children don't bust their deps. Declared
|
|
@@ -5521,10 +6479,10 @@ const CWD_DISPLAY = compactPath(process.cwd());
|
|
|
5521
6479
|
* default-value reference at the prop destructure.
|
|
5522
6480
|
*/
|
|
5523
6481
|
const EMPTY_QUEUED_MESSAGES = [];
|
|
5524
|
-
function ChatScreen({ events, busy, compacting = false, queuedMessages = EMPTY_QUEUED_MESSAGES, queueSelectionIndex = null, queueShortcuts, onEnterQueueFromEmptyPrompt, settings, onSubmit, session, pending, onApproval, pendingInteraction, onInteraction, completionProviders, onPopupOpenChange, selectedTurnId, promptTriggerHints, liveSession = null }) {
|
|
6482
|
+
function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EMPTY_QUEUED_MESSAGES, queueSelectionIndex = null, queueShortcuts, onEnterQueueFromEmptyPrompt, settings, onSubmit, session, pending, onApproval, pendingInteraction, onInteraction, completionProviders, onPopupOpenChange, selectedTurnId, promptTriggerHints, liveSession = null }) {
|
|
5525
6483
|
const COLOR = useColors();
|
|
5526
6484
|
const titleText = session?.title ?? "untitled";
|
|
5527
|
-
const showSessionShortcut = !!session && !busy && !pending && !pendingInteraction;
|
|
6485
|
+
const showSessionShortcut = !!session && !busy && !pending && !pendingInteraction && settings.uiMode !== "minimal";
|
|
5528
6486
|
const userMessageCount = useMemo(() => events.filter((e) => e.kind === "user-prompt").length, [events]);
|
|
5529
6487
|
const { width: termWidth } = useTerminalDimensions();
|
|
5530
6488
|
const hasStatusIcon = busy || compacting;
|
|
@@ -5560,7 +6518,7 @@ function ChatScreen({ events, busy, compacting = false, queuedMessages = EMPTY_Q
|
|
|
5560
6518
|
const statsLen = segments.reduce((sum, s) => sum + s.text.length, 0);
|
|
5561
6519
|
const cwdBudget = Math.max(0, termWidth - 4 - titleText.length - iconReserve - OVERLAY_RESERVED - statsLen - 3);
|
|
5562
6520
|
if (cwdBudget >= 6) segments.unshift({
|
|
5563
|
-
text: compactPath(
|
|
6521
|
+
text: compactPath(cwd, cwdBudget),
|
|
5564
6522
|
color: COLOR.dim
|
|
5565
6523
|
}, {
|
|
5566
6524
|
text: " · ",
|
|
@@ -5568,6 +6526,7 @@ function ChatScreen({ events, busy, compacting = false, queuedMessages = EMPTY_Q
|
|
|
5568
6526
|
});
|
|
5569
6527
|
return segments;
|
|
5570
6528
|
}, [
|
|
6529
|
+
cwd,
|
|
5571
6530
|
session,
|
|
5572
6531
|
userMessageCount,
|
|
5573
6532
|
COLOR,
|
|
@@ -5586,8 +6545,8 @@ function ChatScreen({ events, busy, compacting = false, queuedMessages = EMPTY_Q
|
|
|
5586
6545
|
const modal = useModal();
|
|
5587
6546
|
useEffect(() => {
|
|
5588
6547
|
if (!fileEditPending) return;
|
|
5589
|
-
modal.
|
|
5590
|
-
return () => modal.
|
|
6548
|
+
modal.lock();
|
|
6549
|
+
return () => modal.unlock();
|
|
5591
6550
|
}, [fileEditPending, modal]);
|
|
5592
6551
|
return /* @__PURE__ */ jsxs("box", {
|
|
5593
6552
|
style: {
|
|
@@ -5946,44 +6905,16 @@ function QueuedMessagesBlock({ messages, selectionIndex, shortcuts }) {
|
|
|
5946
6905
|
function previewText(text) {
|
|
5947
6906
|
return text.replace(/\n+/g, " ↵ ");
|
|
5948
6907
|
}
|
|
5949
|
-
const KEY_GLYPHS = {
|
|
5950
|
-
up: "↑",
|
|
5951
|
-
down: "↓",
|
|
5952
|
-
left: "←",
|
|
5953
|
-
right: "→",
|
|
5954
|
-
return: "↵",
|
|
5955
|
-
enter: "↵",
|
|
5956
|
-
delete: "⌫",
|
|
5957
|
-
backspace: "⌫",
|
|
5958
|
-
escape: "esc",
|
|
5959
|
-
space: "␣",
|
|
5960
|
-
tab: "⇥"
|
|
5961
|
-
};
|
|
5962
|
-
/**
|
|
5963
|
-
* Render a binding spec (`"ctrl+return"`, `"backspace"`, `"delete"`) as a
|
|
5964
|
-
* compact display string the user recognizes at a glance. Substitutes
|
|
5965
|
-
* arrow / enter / backspace glyphs for their verbose names so the hint
|
|
5966
|
-
* row stays narrow; modifier names + plain keys pass through unchanged.
|
|
5967
|
-
*
|
|
5968
|
-
* Kept pure / local: every consumer in this file would otherwise embed
|
|
5969
|
-
* the same `replace` chain, and a future `keybindings.json` override
|
|
5970
|
-
* (e.g. `"backspace"` for drop) renders correctly without code edits.
|
|
5971
|
-
*/
|
|
5972
|
-
function formatBindingForDisplay(spec) {
|
|
5973
|
-
const segments = spec.toLowerCase().split("+");
|
|
5974
|
-
const key = segments.pop() ?? "";
|
|
5975
|
-
const modifiers = segments.join("+");
|
|
5976
|
-
const glyph = KEY_GLYPHS[key] ?? key;
|
|
5977
|
-
return modifiers ? `${modifiers}+${glyph}` : glyph;
|
|
5978
|
-
}
|
|
5979
6908
|
/** Stable empty providers reference — avoids `useCompletion` rerun on every render when no providers are wired. */
|
|
5980
6909
|
const EMPTY_PROVIDERS = [];
|
|
5981
6910
|
function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenChange, selectMode = null, triggerHints, busy = false, onEnterQueueFromEmpty }) {
|
|
5982
6911
|
const focused = useModalAwareFocus();
|
|
5983
6912
|
const COLOR = useColors();
|
|
6913
|
+
const SURFACE = useSurfaces();
|
|
5984
6914
|
const textareaRef = useRef(null);
|
|
5985
6915
|
/** Auto-grow: visible content rows the textarea currently occupies (clamped 1..5). */
|
|
5986
6916
|
const [contentLines, setContentLines] = useState(MIN_CONTENT_LINES);
|
|
6917
|
+
const [attachments, setAttachments] = useState([]);
|
|
5987
6918
|
/**
|
|
5988
6919
|
* Mirror of the textarea buffer + cursor, updated on every `onContentChange`.
|
|
5989
6920
|
* Drives `useCompletion` — the textarea stays uncontrolled (we don't push
|
|
@@ -6023,8 +6954,8 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
|
|
|
6023
6954
|
}, []);
|
|
6024
6955
|
const submit = useCallback(() => {
|
|
6025
6956
|
const value = textareaRef.current?.plainText ?? "";
|
|
6026
|
-
if (!value.trim()) return;
|
|
6027
|
-
onSubmit(value, completion.references);
|
|
6957
|
+
if (!value.trim() && attachments.length === 0) return;
|
|
6958
|
+
onSubmit(value, completion.references, attachments);
|
|
6028
6959
|
textareaRef.current?.clear();
|
|
6029
6960
|
historyRef.current = null;
|
|
6030
6961
|
setBufferState({
|
|
@@ -6032,7 +6963,12 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
|
|
|
6032
6963
|
cursor: 0
|
|
6033
6964
|
});
|
|
6034
6965
|
setContentLines(MIN_CONTENT_LINES);
|
|
6035
|
-
|
|
6966
|
+
setAttachments([]);
|
|
6967
|
+
}, [
|
|
6968
|
+
onSubmit,
|
|
6969
|
+
completion.references,
|
|
6970
|
+
attachments
|
|
6971
|
+
]);
|
|
6036
6972
|
const commitCompletion = useCallback(() => {
|
|
6037
6973
|
const result = completion.commit();
|
|
6038
6974
|
if (!result) return false;
|
|
@@ -6064,6 +7000,32 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
|
|
|
6064
7000
|
if (!ta) return;
|
|
6065
7001
|
const normalized = stripAnsiSequences(decodePasteBytes(event.bytes)).replace(/\r\n?/g, "\n");
|
|
6066
7002
|
if (normalized.length === 0) return;
|
|
7003
|
+
if (!normalized.includes("\n")) {
|
|
7004
|
+
const trimmed = normalized.trim().replace(/^["']|["']$/g, "");
|
|
7005
|
+
let resolved = trimmed.startsWith("file://") ? decodeURIComponent(trimmed.slice(7)) : trimmed;
|
|
7006
|
+
if (resolved.startsWith("~/")) resolved = (process.env.HOME ?? "") + resolved.slice(1);
|
|
7007
|
+
if (resolved.length > 0 && (resolved.startsWith("/") || resolved.startsWith("."))) try {
|
|
7008
|
+
if (statSync(resolved).isFile()) {
|
|
7009
|
+
const buf = readFileSync(resolved);
|
|
7010
|
+
const ext = (resolved.split(".").pop() ?? "").toLowerCase();
|
|
7011
|
+
const mediaType = IMAGE_MEDIA_TYPES[ext] ?? MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
7012
|
+
setAttachments((prev) => [...prev, {
|
|
7013
|
+
name: basename(resolved),
|
|
7014
|
+
content: buf,
|
|
7015
|
+
mediaType
|
|
7016
|
+
}]);
|
|
7017
|
+
return;
|
|
7018
|
+
}
|
|
7019
|
+
} catch {}
|
|
7020
|
+
}
|
|
7021
|
+
if (normalized.includes("\n")) {
|
|
7022
|
+
setAttachments((prev) => [...prev, {
|
|
7023
|
+
name: "paste.txt",
|
|
7024
|
+
content: Buffer.from(normalized, "utf-8"),
|
|
7025
|
+
mediaType: "text/plain"
|
|
7026
|
+
}]);
|
|
7027
|
+
return;
|
|
7028
|
+
}
|
|
6067
7029
|
const parts = normalized.split("\n");
|
|
6068
7030
|
for (let i = 0; i < parts.length; i++) {
|
|
6069
7031
|
if (i > 0) ta.newLine();
|
|
@@ -6143,7 +7105,15 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
|
|
|
6143
7105
|
const buffer = textareaRef.current;
|
|
6144
7106
|
if (!buffer) return;
|
|
6145
7107
|
if (event.name === "up") {
|
|
6146
|
-
if (buffer.cursorOffset !== 0)
|
|
7108
|
+
if (buffer.cursorOffset !== 0) {
|
|
7109
|
+
const { row } = buffer.editorView.getCursor();
|
|
7110
|
+
if (row === 0) {
|
|
7111
|
+
event.preventDefault();
|
|
7112
|
+
buffer.cursorOffset = 0;
|
|
7113
|
+
syncBuffer();
|
|
7114
|
+
}
|
|
7115
|
+
return;
|
|
7116
|
+
}
|
|
6147
7117
|
event.preventDefault();
|
|
6148
7118
|
if (buffer.plainText.length === 0 && onEnterQueueFromEmpty?.()) return;
|
|
6149
7119
|
cycleHistory(-1);
|
|
@@ -6157,7 +7127,8 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
|
|
|
6157
7127
|
completion,
|
|
6158
7128
|
commitCompletion,
|
|
6159
7129
|
cycleHistory,
|
|
6160
|
-
onEnterQueueFromEmpty
|
|
7130
|
+
onEnterQueueFromEmpty,
|
|
7131
|
+
syncBuffer
|
|
6161
7132
|
]);
|
|
6162
7133
|
const boxHeight = Math.min(MAX_CONTENT_LINES, contentLines) + 2;
|
|
6163
7134
|
return /* @__PURE__ */ jsxs("box", {
|
|
@@ -6170,36 +7141,71 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
|
|
|
6170
7141
|
flexDirection: "column",
|
|
6171
7142
|
flexShrink: 0
|
|
6172
7143
|
},
|
|
6173
|
-
children: [
|
|
6174
|
-
|
|
6175
|
-
border: true,
|
|
6176
|
-
borderColor: selectMode ? COLOR.warn : COLOR.borderActive,
|
|
6177
|
-
paddingLeft: 1,
|
|
6178
|
-
paddingRight: 1,
|
|
6179
|
-
height: boxHeight,
|
|
6180
|
-
flexDirection: "column"
|
|
6181
|
-
},
|
|
6182
|
-
children: /* @__PURE__ */ jsx("textarea", {
|
|
6183
|
-
ref: textareaRef,
|
|
6184
|
-
focused: focused && !selectMode,
|
|
6185
|
-
keyBindings: TEXTAREA_BINDINGS,
|
|
6186
|
-
wrapMode: "word",
|
|
6187
|
-
placeholder: selectMode === "turn" ? "— turn-select mode — press ⎋ to resume typing —" : selectMode === "queue" ? "— queue-select mode — press ⎋ to resume typing —" : "Ask zidane…",
|
|
6188
|
-
syntaxStyle: chipStyle,
|
|
7144
|
+
children: [
|
|
7145
|
+
attachments.length > 0 && /* @__PURE__ */ jsx("box", {
|
|
6189
7146
|
style: {
|
|
6190
|
-
|
|
6191
|
-
|
|
7147
|
+
flexDirection: "row",
|
|
7148
|
+
flexWrap: "wrap",
|
|
7149
|
+
paddingLeft: 1,
|
|
7150
|
+
paddingRight: 1,
|
|
7151
|
+
paddingBottom: 0
|
|
6192
7152
|
},
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
7153
|
+
children: attachments.map((att, idx) => {
|
|
7154
|
+
const sz = att.content.length;
|
|
7155
|
+
const label = sz < 1024 ? `${sz}B` : sz < 1024 * 1024 ? `${(sz / 1024).toFixed(1)}KB` : `${(sz / (1024 * 1024)).toFixed(1)}MB`;
|
|
7156
|
+
const icon = att.mediaType.startsWith("image/") ? "🖼" : "📎";
|
|
7157
|
+
const chipColor = resolveChipColor(SURFACE.chips, "file");
|
|
7158
|
+
return /* @__PURE__ */ jsxs("text", { children: [
|
|
7159
|
+
idx > 0 ? " " : "",
|
|
7160
|
+
icon,
|
|
7161
|
+
/* @__PURE__ */ jsxs("span", {
|
|
7162
|
+
fg: chipColor.fg,
|
|
7163
|
+
bg: chipColor.bg,
|
|
7164
|
+
children: [
|
|
7165
|
+
" ",
|
|
7166
|
+
att.name,
|
|
7167
|
+
" ",
|
|
7168
|
+
"(",
|
|
7169
|
+
label,
|
|
7170
|
+
")",
|
|
7171
|
+
" "
|
|
7172
|
+
]
|
|
7173
|
+
})
|
|
7174
|
+
] }, `${att.name}-${idx}`);
|
|
7175
|
+
})
|
|
7176
|
+
}),
|
|
7177
|
+
/* @__PURE__ */ jsx("box", {
|
|
7178
|
+
style: {
|
|
7179
|
+
border: true,
|
|
7180
|
+
borderColor: selectMode ? COLOR.warn : COLOR.borderActive,
|
|
7181
|
+
paddingLeft: 1,
|
|
7182
|
+
paddingRight: 1,
|
|
7183
|
+
height: boxHeight,
|
|
7184
|
+
flexDirection: "column"
|
|
7185
|
+
},
|
|
7186
|
+
children: /* @__PURE__ */ jsx("textarea", {
|
|
7187
|
+
ref: textareaRef,
|
|
7188
|
+
focused: focused && !selectMode,
|
|
7189
|
+
keyBindings: TEXTAREA_BINDINGS,
|
|
7190
|
+
wrapMode: "word",
|
|
7191
|
+
placeholder: selectMode === "turn" ? "— turn-select mode — press ⎋ to resume typing —" : selectMode === "queue" ? "— queue-select mode — press ⎋ to resume typing —" : "Ask zidane…",
|
|
7192
|
+
syntaxStyle: chipStyle,
|
|
7193
|
+
style: {
|
|
7194
|
+
flexGrow: 1,
|
|
7195
|
+
height: "100%"
|
|
7196
|
+
},
|
|
7197
|
+
onSubmit: submit,
|
|
7198
|
+
onContentChange: syncBuffer,
|
|
7199
|
+
onKeyDown,
|
|
7200
|
+
onPaste
|
|
7201
|
+
})
|
|
7202
|
+
}),
|
|
7203
|
+
/* @__PURE__ */ jsx(PromptHints, {
|
|
7204
|
+
selectMode,
|
|
7205
|
+
triggerHints,
|
|
7206
|
+
busy
|
|
6197
7207
|
})
|
|
6198
|
-
|
|
6199
|
-
selectMode,
|
|
6200
|
-
triggerHints,
|
|
6201
|
-
busy
|
|
6202
|
-
})]
|
|
7208
|
+
]
|
|
6203
7209
|
}), !selectMode && /* @__PURE__ */ jsx("box", {
|
|
6204
7210
|
style: {
|
|
6205
7211
|
position: "absolute",
|
|
@@ -6303,11 +7309,13 @@ const PROMPT_HINTS_SELECT_QUEUE = [{
|
|
|
6303
7309
|
*/
|
|
6304
7310
|
function PromptHints({ selectMode, triggerHints, busy = false }) {
|
|
6305
7311
|
const COLOR = useColors();
|
|
7312
|
+
const { settings } = useSettings();
|
|
6306
7313
|
const { width: termWidth } = useTerminalDimensions();
|
|
6307
|
-
const
|
|
7314
|
+
const minimalResting = !selectMode && !busy && settings.uiMode === "minimal";
|
|
7315
|
+
const primary = selectMode === "turn" ? PROMPT_HINTS_SELECT_TURN : selectMode === "queue" ? PROMPT_HINTS_SELECT_QUEUE : busy ? PROMPT_HINTS_BUSY : minimalResting ? EMPTY_HINTS : PROMPT_HINTS_NORMAL;
|
|
6308
7316
|
const hints = useMemo(() => {
|
|
6309
7317
|
const budget = Math.max(0, termWidth - 5);
|
|
6310
|
-
const tooLongCombined = !!triggerHints && triggerHints.length > 0 && !selectMode && hintsLength(primary) + 3 + hintsLength(triggerHints) > budget;
|
|
7318
|
+
const tooLongCombined = !!triggerHints && triggerHints.length > 0 && !selectMode && primary.length > 0 && hintsLength(primary) + 3 + hintsLength(triggerHints) > budget;
|
|
6311
7319
|
return clipHintsToWidth(selectMode || !triggerHints || triggerHints.length === 0 || tooLongCombined ? primary : [...primary, ...triggerHints], budget);
|
|
6312
7320
|
}, [
|
|
6313
7321
|
selectMode,
|
|
@@ -7106,7 +8114,13 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
7106
8114
|
]);
|
|
7107
8115
|
const focusedMcp = filteredMcps[cursor];
|
|
7108
8116
|
const focusedMcpStatus = focusedMcp ? getMcpAuthStatus(authState, focusedMcp.config.name) : void 0;
|
|
8117
|
+
const oauthPasteActive = activeTab === "mcps" && focusedMcpStatus?.kind === "authorizing" && !!focusedMcpStatus.url;
|
|
7109
8118
|
useKeyboard((key) => {
|
|
8119
|
+
if (key.name === "escape" && oauthPasteActive && focusedMcp) {
|
|
8120
|
+
actions?.onCancelLoginMcp?.(focusedMcp.config.name);
|
|
8121
|
+
return;
|
|
8122
|
+
}
|
|
8123
|
+
if (oauthPasteActive) return;
|
|
7110
8124
|
if (!key.ctrl && !key.meta && !key.shift && (key.name === "left" || key.name === "right")) {
|
|
7111
8125
|
key.preventDefault();
|
|
7112
8126
|
const idx = TAB_ORDER.indexOf(activeTab);
|
|
@@ -7203,7 +8217,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
7203
8217
|
},
|
|
7204
8218
|
children: /* @__PURE__ */ jsx("input", {
|
|
7205
8219
|
ref: inputRef,
|
|
7206
|
-
focused:
|
|
8220
|
+
focused: !oauthPasteActive,
|
|
7207
8221
|
placeholder: searchPlaceholder(activeTab),
|
|
7208
8222
|
onInput: handleQueryChange,
|
|
7209
8223
|
onSubmit: () => {},
|
|
@@ -7623,35 +8637,29 @@ function EmptyRow({ label }) {
|
|
|
7623
8637
|
});
|
|
7624
8638
|
}
|
|
7625
8639
|
function renderMcpDetailPanel(entry, status, COLOR) {
|
|
7626
|
-
if (status.kind === "authorizing")
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
/* @__PURE__ */ jsx("text", {
|
|
8640
|
+
if (status.kind === "authorizing") {
|
|
8641
|
+
if (!status.url) return /* @__PURE__ */ jsxs("box", {
|
|
8642
|
+
style: {
|
|
8643
|
+
flexDirection: "column",
|
|
8644
|
+
border: ["top"],
|
|
8645
|
+
borderColor: COLOR.border,
|
|
8646
|
+
paddingTop: 1
|
|
8647
|
+
},
|
|
8648
|
+
children: [/* @__PURE__ */ jsx("text", {
|
|
7635
8649
|
fg: COLOR.brand,
|
|
7636
8650
|
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
8651
|
}), /* @__PURE__ */ jsx("text", {
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
8652
|
+
fg: COLOR.dim,
|
|
8653
|
+
children: "Starting login flow…"
|
|
8654
|
+
})]
|
|
8655
|
+
});
|
|
8656
|
+
return /* @__PURE__ */ jsx(McpAuthorizingPanel, {
|
|
8657
|
+
serverName: entry.config.name,
|
|
8658
|
+
authUrl: status.url,
|
|
8659
|
+
maxLineWidth: 134,
|
|
8660
|
+
inputFocused: true
|
|
8661
|
+
});
|
|
8662
|
+
}
|
|
7655
8663
|
if (status.kind === "error") return /* @__PURE__ */ jsxs("box", {
|
|
7656
8664
|
style: {
|
|
7657
8665
|
flexDirection: "column",
|
|
@@ -8591,11 +9599,28 @@ function AppShell() {
|
|
|
8591
9599
|
useEffect(() => {
|
|
8592
9600
|
autoCompactThresholdRef.current = settings.autoCompactThreshold;
|
|
8593
9601
|
}, [settings.autoCompactThreshold]);
|
|
9602
|
+
/**
|
|
9603
|
+
* Post-compaction baseline for the hysteresis check in
|
|
9604
|
+
* {@link shouldAutoCompact}. Latched to the effective post-compact token
|
|
9605
|
+
* count whenever a compaction lands (see {@link onCompactSession}), reset
|
|
9606
|
+
* to `undefined` on session change so the first compaction in a fresh
|
|
9607
|
+
* session fires off the absolute threshold without any growth gating.
|
|
9608
|
+
* Pairs with {@link AUTO_COMPACT_MIN_GROWTH_FRACTION} in the predicate.
|
|
9609
|
+
*/
|
|
9610
|
+
const lastCompactedInputTokensRef = useRef(void 0);
|
|
8594
9611
|
const smoothStreamingRef = useRef(settings.smoothStreaming);
|
|
8595
9612
|
useEffect(() => {
|
|
8596
9613
|
smoothStreamingRef.current = settings.smoothStreaming;
|
|
8597
9614
|
}, [settings.smoothStreaming]);
|
|
8598
|
-
const [projectDir] = useState(() => findGitRoot(process.cwd()) ?? process.cwd());
|
|
9615
|
+
const [projectDir, setProjectDir] = useState(() => findGitRoot(process.cwd()) ?? process.cwd());
|
|
9616
|
+
const [cwd, setCwdRaw] = useState(process.cwd);
|
|
9617
|
+
const cwdRef = useRef(cwd);
|
|
9618
|
+
cwdRef.current = cwd;
|
|
9619
|
+
const setCwd = useCallback((next) => {
|
|
9620
|
+
process.chdir(next);
|
|
9621
|
+
setCwdRaw(next);
|
|
9622
|
+
setProjectDir(findGitRoot(next) ?? next);
|
|
9623
|
+
}, []);
|
|
8599
9624
|
const safelistRef = useRef(null);
|
|
8600
9625
|
const readSafelist = useCallback(() => {
|
|
8601
9626
|
if (safelistRef.current === null) safelistRef.current = getSafelist(dataDir, projectDir);
|
|
@@ -8627,9 +9652,8 @@ function AppShell() {
|
|
|
8627
9652
|
const ensureSkillsCatalogRef = useRef(ensureSkillsCatalog);
|
|
8628
9653
|
ensureSkillsCatalogRef.current = ensureSkillsCatalog;
|
|
8629
9654
|
const formatFilePathForCwd = useMemo(() => {
|
|
8630
|
-
const cwd = process.cwd();
|
|
8631
9655
|
return (entry) => formatPathForCwd(entry.path, projectDir, cwd);
|
|
8632
|
-
}, [projectDir]);
|
|
9656
|
+
}, [projectDir, cwd]);
|
|
8633
9657
|
const completionProviders = useMemo(() => [createSkillsCompletionProvider({
|
|
8634
9658
|
getCatalog: () => skillsCatalogRef.current,
|
|
8635
9659
|
getEnabled: () => enabledSkillsRef.current,
|
|
@@ -8798,6 +9822,25 @@ function AppShell() {
|
|
|
8798
9822
|
queueSelectionIndexRef.current = queueSelectionIndex;
|
|
8799
9823
|
const agentRef = useRef(null);
|
|
8800
9824
|
const sessionRef = useRef(null);
|
|
9825
|
+
useEffect(() => {
|
|
9826
|
+
const handle = agentRef.current?.handle;
|
|
9827
|
+
if (handle) handle.cwd = cwd;
|
|
9828
|
+
}, [cwd]);
|
|
9829
|
+
useEffect(() => {
|
|
9830
|
+
const agent = agentRef.current;
|
|
9831
|
+
if (!agent) return;
|
|
9832
|
+
const profile = pickedAgentRef.current;
|
|
9833
|
+
if (profile.id !== "build" && profile.id !== "plan") return;
|
|
9834
|
+
return agent.hooks.hook("system:transform", (ctx) => {
|
|
9835
|
+
const allowInteraction = allowInteractionRef.current !== false;
|
|
9836
|
+
const freshEnv = envSection({
|
|
9837
|
+
cwd,
|
|
9838
|
+
...projectDir !== cwd ? { projectRoot: projectDir } : {},
|
|
9839
|
+
allowInteraction
|
|
9840
|
+
});
|
|
9841
|
+
ctx.system = ctx.system.replace(/<env>[\s\S]*?<\/env>/, freshEnv);
|
|
9842
|
+
});
|
|
9843
|
+
}, [cwd, projectDir]);
|
|
8801
9844
|
/**
|
|
8802
9845
|
* Live registry of in-flight tool calls — populated by `tool:before`
|
|
8803
9846
|
* (and `child:tool:before`), drained by `tool:after` / `tool:error` /
|
|
@@ -8943,7 +9986,7 @@ function AppShell() {
|
|
|
8943
9986
|
});
|
|
8944
9987
|
const allowInteraction = allowInteractionRef.current !== false;
|
|
8945
9988
|
const interactionTools = allowInteraction ? createInteractionTools({ requestInteraction: makeRequestInteraction(interactions) }) : {};
|
|
8946
|
-
const actualCwd =
|
|
9989
|
+
const actualCwd = cwdRef.current;
|
|
8947
9990
|
const envOpts = {
|
|
8948
9991
|
cwd: actualCwd,
|
|
8949
9992
|
...projectDir !== actualCwd ? { projectRoot: projectDir } : {},
|
|
@@ -8979,6 +10022,7 @@ function AppShell() {
|
|
|
8979
10022
|
...persistBehavior,
|
|
8980
10023
|
tasksDir
|
|
8981
10024
|
},
|
|
10025
|
+
execution: createProcessContext({ cwd: actualCwd }),
|
|
8982
10026
|
provider: descriptor.factory(),
|
|
8983
10027
|
session,
|
|
8984
10028
|
mcpConnector: (configs) => connectMcpServers(configs, void 0, agent.hooks, { buildAuthProvider: (cfg) => new McpOAuthProvider({
|
|
@@ -9470,6 +10514,7 @@ function AppShell() {
|
|
|
9470
10514
|
const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
|
|
9471
10515
|
setLastInputTokens(replayedTokens);
|
|
9472
10516
|
lastInputTokensRef.current = replayedTokens;
|
|
10517
|
+
lastCompactedInputTokensRef.current = void 0;
|
|
9473
10518
|
setSessionCost(sumRunCosts(session.runs));
|
|
9474
10519
|
setCurrentSession({
|
|
9475
10520
|
id: session.id,
|
|
@@ -9802,7 +10847,7 @@ function AppShell() {
|
|
|
9802
10847
|
* busy-state lifecycle so the title spinner stays mounted continuously
|
|
9803
10848
|
* across back-to-back queued messages.
|
|
9804
10849
|
*/
|
|
9805
|
-
const runSingleMessage = useCallback(async (prompt, references) => {
|
|
10850
|
+
const runSingleMessage = useCallback(async (prompt, references, attachments) => {
|
|
9806
10851
|
const agent = agentRef.current;
|
|
9807
10852
|
const session = sessionRef.current;
|
|
9808
10853
|
if (!agent || !session || !picked) return;
|
|
@@ -9815,10 +10860,16 @@ function AppShell() {
|
|
|
9815
10860
|
end: r.end,
|
|
9816
10861
|
providerId: r.providerId
|
|
9817
10862
|
}));
|
|
10863
|
+
const attachmentMeta = attachments.map((a) => ({
|
|
10864
|
+
name: a.name,
|
|
10865
|
+
mediaType: a.mediaType,
|
|
10866
|
+
size: a.content.length
|
|
10867
|
+
}));
|
|
9818
10868
|
stream.appendImmediate({
|
|
9819
10869
|
kind: "user-prompt",
|
|
9820
10870
|
text: prompt,
|
|
9821
|
-
...refSpans.length > 0 ? { refs: refSpans } : {}
|
|
10871
|
+
...refSpans.length > 0 ? { refs: refSpans } : {},
|
|
10872
|
+
...attachmentMeta.length > 0 ? { attachments: attachmentMeta } : {}
|
|
9822
10873
|
});
|
|
9823
10874
|
if (autoCompactInFlightRef.current) await autoCompactInFlightRef.current.catch(() => {});
|
|
9824
10875
|
const newRefs = uniqueSkillNamesFromReferences(references);
|
|
@@ -9829,9 +10880,34 @@ function AppShell() {
|
|
|
9829
10880
|
debugLog(`activateSkill("${name}")`, err);
|
|
9830
10881
|
}
|
|
9831
10882
|
try {
|
|
10883
|
+
const runPrompt = attachments.length === 0 ? prompt : [...prompt.length > 0 ? [{
|
|
10884
|
+
type: "text",
|
|
10885
|
+
text: prompt
|
|
10886
|
+
}] : [], ...attachments.map((att) => {
|
|
10887
|
+
if (att.mediaType.startsWith("image/")) return {
|
|
10888
|
+
type: "image",
|
|
10889
|
+
mediaType: att.mediaType,
|
|
10890
|
+
data: att.content.toString("base64"),
|
|
10891
|
+
name: att.name
|
|
10892
|
+
};
|
|
10893
|
+
if (att.mediaType.startsWith("text/")) return {
|
|
10894
|
+
type: "document",
|
|
10895
|
+
mediaType: att.mediaType,
|
|
10896
|
+
data: att.content.toString("utf-8"),
|
|
10897
|
+
encoding: "text",
|
|
10898
|
+
name: att.name
|
|
10899
|
+
};
|
|
10900
|
+
return {
|
|
10901
|
+
type: "document",
|
|
10902
|
+
mediaType: att.mediaType,
|
|
10903
|
+
data: att.content.toString("base64"),
|
|
10904
|
+
encoding: "base64",
|
|
10905
|
+
name: att.name
|
|
10906
|
+
};
|
|
10907
|
+
})];
|
|
9832
10908
|
await agent.run({
|
|
9833
10909
|
model: picked.model,
|
|
9834
|
-
prompt,
|
|
10910
|
+
prompt: runPrompt,
|
|
9835
10911
|
...picked.effort ? { thinking: picked.effort } : {}
|
|
9836
10912
|
});
|
|
9837
10913
|
await session.save().catch((err) => debugLog("session.save failed", err));
|
|
@@ -9853,15 +10929,16 @@ function AppShell() {
|
|
|
9853
10929
|
stream.flushAndUpdate(finalizeStreamingMarkdown);
|
|
9854
10930
|
}
|
|
9855
10931
|
}, [picked, stream]);
|
|
9856
|
-
const onSubmitPrompt = useCallback((prompt, references) => {
|
|
9857
|
-
if (!prompt.trim()) return;
|
|
10932
|
+
const onSubmitPrompt = useCallback((prompt, references, attachments) => {
|
|
10933
|
+
if (!prompt.trim() && attachments.length === 0) return;
|
|
9858
10934
|
const agent = agentRef.current;
|
|
9859
10935
|
const session = sessionRef.current;
|
|
9860
10936
|
if (!agent || !session || !picked) return;
|
|
9861
10937
|
if (runningRef.current) {
|
|
9862
10938
|
messageQueueRef.current = [...messageQueueRef.current, {
|
|
9863
10939
|
prompt,
|
|
9864
|
-
references
|
|
10940
|
+
references,
|
|
10941
|
+
attachments
|
|
9865
10942
|
}];
|
|
9866
10943
|
setMessageQueue(messageQueueRef.current.slice());
|
|
9867
10944
|
return;
|
|
@@ -9870,7 +10947,7 @@ function AppShell() {
|
|
|
9870
10947
|
(async () => {
|
|
9871
10948
|
setBusy(true);
|
|
9872
10949
|
try {
|
|
9873
|
-
await runSingleMessage(prompt, references);
|
|
10950
|
+
await runSingleMessage(prompt, references, attachments);
|
|
9874
10951
|
while (messageQueueRef.current.length > 0) {
|
|
9875
10952
|
const next = messageQueueRef.current[0];
|
|
9876
10953
|
messageQueueRef.current = messageQueueRef.current.slice(1);
|
|
@@ -9881,7 +10958,7 @@ function AppShell() {
|
|
|
9881
10958
|
if (prev <= 0) return null;
|
|
9882
10959
|
return prev - 1;
|
|
9883
10960
|
});
|
|
9884
|
-
await runSingleMessage(next.prompt, next.references);
|
|
10961
|
+
await runSingleMessage(next.prompt, next.references, next.attachments);
|
|
9885
10962
|
}
|
|
9886
10963
|
} finally {
|
|
9887
10964
|
setBusy(false);
|
|
@@ -10048,6 +11125,7 @@ function AppShell() {
|
|
|
10048
11125
|
const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
|
|
10049
11126
|
setLastInputTokens(replayedTokens);
|
|
10050
11127
|
lastInputTokensRef.current = replayedTokens;
|
|
11128
|
+
lastCompactedInputTokensRef.current = void 0;
|
|
10051
11129
|
setSessionCost(sumRunCosts(session.runs));
|
|
10052
11130
|
setCurrentSession((prev) => prev ? {
|
|
10053
11131
|
...prev,
|
|
@@ -10085,6 +11163,7 @@ function AppShell() {
|
|
|
10085
11163
|
const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
|
|
10086
11164
|
setLastInputTokens(replayedTokens);
|
|
10087
11165
|
lastInputTokensRef.current = replayedTokens;
|
|
11166
|
+
lastCompactedInputTokensRef.current = void 0;
|
|
10088
11167
|
setCurrentSession((prev) => prev ? {
|
|
10089
11168
|
...prev,
|
|
10090
11169
|
updatedAt: Date.now()
|
|
@@ -10241,6 +11320,7 @@ function AppShell() {
|
|
|
10241
11320
|
setEvents(eventsFromTurns(liveSession.turns, liveSession.runs));
|
|
10242
11321
|
setLastInputTokens(effectiveTokens);
|
|
10243
11322
|
lastInputTokensRef.current = effectiveTokens;
|
|
11323
|
+
lastCompactedInputTokensRef.current = effectiveTokens;
|
|
10244
11324
|
}
|
|
10245
11325
|
setCurrentSession((prev) => prev && prev.id === sessionId ? {
|
|
10246
11326
|
...prev,
|
|
@@ -10283,7 +11363,9 @@ function AppShell() {
|
|
|
10283
11363
|
threshold: autoCompactThresholdRef.current,
|
|
10284
11364
|
inputTokens: lastInputTokensRef.current,
|
|
10285
11365
|
rawContextWindow: rawWindow,
|
|
10286
|
-
alreadyCompacting: !!autoCompactInFlightRef.current
|
|
11366
|
+
alreadyCompacting: !!autoCompactInFlightRef.current,
|
|
11367
|
+
...lastCompactedInputTokensRef.current !== void 0 ? { lastCompactedInputTokens: lastCompactedInputTokensRef.current } : {},
|
|
11368
|
+
minGrowthFraction: AUTO_COMPACT_MIN_GROWTH_FRACTION
|
|
10287
11369
|
});
|
|
10288
11370
|
if (decision.kind !== "fire") return;
|
|
10289
11371
|
const pct = Math.round(decision.usedFraction * 100);
|
|
@@ -10502,6 +11584,15 @@ function AppShell() {
|
|
|
10502
11584
|
}));
|
|
10503
11585
|
return;
|
|
10504
11586
|
}
|
|
11587
|
+
if (matchesBinding(key, keybindings.openKeybindings) && screen !== "auth") {
|
|
11588
|
+
modal.open(/* @__PURE__ */ jsx(KeybindingsModal, {
|
|
11589
|
+
bindings: keybindings,
|
|
11590
|
+
filePath: keybindingsPath(config.paths.userDir),
|
|
11591
|
+
onEditFile: onOpenKeybindingsFile,
|
|
11592
|
+
onClose: () => modal.close()
|
|
11593
|
+
}));
|
|
11594
|
+
return;
|
|
11595
|
+
}
|
|
10505
11596
|
if (matchesBinding(key, keybindings.enterSelectTurnMode) && screen === "chat" && !busy && !pendingApproval && !pendingInteraction) {
|
|
10506
11597
|
enterSelectMode();
|
|
10507
11598
|
return;
|
|
@@ -10533,6 +11624,16 @@ function AppShell() {
|
|
|
10533
11624
|
}));
|
|
10534
11625
|
return;
|
|
10535
11626
|
}
|
|
11627
|
+
if (matchesBinding(key, keybindings.changeCwd) && screen !== "auth") {
|
|
11628
|
+
modal.open(/* @__PURE__ */ jsx(CwdPickerModal, {
|
|
11629
|
+
currentCwd: cwdRef.current,
|
|
11630
|
+
onPick: (dir) => {
|
|
11631
|
+
setCwd(dir);
|
|
11632
|
+
modal.close();
|
|
11633
|
+
}
|
|
11634
|
+
}));
|
|
11635
|
+
return;
|
|
11636
|
+
}
|
|
10536
11637
|
if (key.name !== "escape") return;
|
|
10537
11638
|
if (busy || pendingApproval) return onAbort();
|
|
10538
11639
|
if (popupOpenRef.current) return;
|
|
@@ -10567,6 +11668,7 @@ function AppShell() {
|
|
|
10567
11668
|
pendingInteractionResumed: pendingInteractionIsResumed,
|
|
10568
11669
|
currentSession,
|
|
10569
11670
|
hasMultipleAgents,
|
|
11671
|
+
uiMode: settings.uiMode,
|
|
10570
11672
|
modelLabel: picked?.model ?? null,
|
|
10571
11673
|
modelColor: COLOR.model,
|
|
10572
11674
|
effortLabel: modelHasReasoning ? picked?.effort ?? "medium" : null,
|
|
@@ -10587,6 +11689,7 @@ function AppShell() {
|
|
|
10587
11689
|
pendingInteractionIsResumed,
|
|
10588
11690
|
currentSession,
|
|
10589
11691
|
hasMultipleAgents,
|
|
11692
|
+
settings.uiMode,
|
|
10590
11693
|
picked,
|
|
10591
11694
|
pickedAgent,
|
|
10592
11695
|
COLOR,
|
|
@@ -10666,6 +11769,7 @@ function AppShell() {
|
|
|
10666
11769
|
currentProjectRoot: projectDir
|
|
10667
11770
|
}),
|
|
10668
11771
|
screen === "chat" && /* @__PURE__ */ jsx(ChatScreen, {
|
|
11772
|
+
cwd,
|
|
10669
11773
|
events,
|
|
10670
11774
|
busy,
|
|
10671
11775
|
compacting,
|
|
@@ -10705,162 +11809,6 @@ function effortForModel(descriptor, modelId, remembered) {
|
|
|
10705
11809
|
if (!modelSupportsReasoning(descriptor, modelId)) return void 0;
|
|
10706
11810
|
return remembered?.[modelId] ?? "medium";
|
|
10707
11811
|
}
|
|
10708
|
-
/**
|
|
10709
|
-
* Build the footer's shortcut hints for the current screen. On the chat
|
|
10710
|
-
* screen the model id rides next to its `ctrl+m` shortcut and the agent
|
|
10711
|
-
* label rides next to `shift+tab`, each in its accent color — the bar
|
|
10712
|
-
* doubles as the status display without needing separate badges. When
|
|
10713
|
-
* the active model exposes reasoning, the `ctrl+m` hint grows a
|
|
10714
|
-
* secondary `/n` chord with the current effort label, surfacing the
|
|
10715
|
-
* effort picker as a discoverable, in-place affordance.
|
|
10716
|
-
*/
|
|
10717
|
-
function buildHints({ screen, busy, pending, pendingInteractionLive, pendingInteractionResumed, currentSession, hasMultipleAgents, modelLabel, modelColor, effortLabel, effortColor, effortKeyColor, agentLabel, agentColor, keybindings, inFlightToolCount, activeSkillCount, skillsChipColor, updateHint }) {
|
|
10718
|
-
if (pending) return [
|
|
10719
|
-
{
|
|
10720
|
-
key: "↑↓",
|
|
10721
|
-
label: "navigate"
|
|
10722
|
-
},
|
|
10723
|
-
{
|
|
10724
|
-
key: "↵",
|
|
10725
|
-
label: "select"
|
|
10726
|
-
},
|
|
10727
|
-
{
|
|
10728
|
-
key: "esc",
|
|
10729
|
-
label: "abort run"
|
|
10730
|
-
}
|
|
10731
|
-
];
|
|
10732
|
-
if (pendingInteractionLive) return [
|
|
10733
|
-
{
|
|
10734
|
-
key: "↑↓",
|
|
10735
|
-
label: "navigate"
|
|
10736
|
-
},
|
|
10737
|
-
{
|
|
10738
|
-
key: "↵",
|
|
10739
|
-
label: "select"
|
|
10740
|
-
},
|
|
10741
|
-
{
|
|
10742
|
-
key: "esc",
|
|
10743
|
-
label: "abort run"
|
|
10744
|
-
}
|
|
10745
|
-
];
|
|
10746
|
-
if (pendingInteractionResumed) return [
|
|
10747
|
-
{
|
|
10748
|
-
key: "↑↓",
|
|
10749
|
-
label: "navigate"
|
|
10750
|
-
},
|
|
10751
|
-
{
|
|
10752
|
-
key: "↵",
|
|
10753
|
-
label: "select"
|
|
10754
|
-
},
|
|
10755
|
-
{
|
|
10756
|
-
key: "esc",
|
|
10757
|
-
label: "leave for later"
|
|
10758
|
-
}
|
|
10759
|
-
];
|
|
10760
|
-
if (busy) {
|
|
10761
|
-
const baseBusyHints = [];
|
|
10762
|
-
if (inFlightToolCount > 0) baseBusyHints.push({
|
|
10763
|
-
key: keybindings.cancelToolCall,
|
|
10764
|
-
label: inFlightToolCount === 1 ? "cancel" : `cancel (${inFlightToolCount})`
|
|
10765
|
-
});
|
|
10766
|
-
baseBusyHints.push({
|
|
10767
|
-
key: "esc",
|
|
10768
|
-
label: "abort"
|
|
10769
|
-
});
|
|
10770
|
-
return baseBusyHints;
|
|
10771
|
-
}
|
|
10772
|
-
if (screen === "auth") return [
|
|
10773
|
-
{
|
|
10774
|
-
key: "↑↓",
|
|
10775
|
-
label: "navigate"
|
|
10776
|
-
},
|
|
10777
|
-
{
|
|
10778
|
-
key: "↵",
|
|
10779
|
-
label: "select"
|
|
10780
|
-
},
|
|
10781
|
-
{
|
|
10782
|
-
key: "esc",
|
|
10783
|
-
label: "exit"
|
|
10784
|
-
}
|
|
10785
|
-
];
|
|
10786
|
-
if (screen === "sessions") return [
|
|
10787
|
-
{
|
|
10788
|
-
key: "↑↓",
|
|
10789
|
-
label: "navigate"
|
|
10790
|
-
},
|
|
10791
|
-
{
|
|
10792
|
-
key: "↵",
|
|
10793
|
-
label: "open"
|
|
10794
|
-
},
|
|
10795
|
-
{
|
|
10796
|
-
key: keybindings.openSessionDetails,
|
|
10797
|
-
label: "session"
|
|
10798
|
-
},
|
|
10799
|
-
{
|
|
10800
|
-
key: keybindings.openSettings,
|
|
10801
|
-
label: "settings"
|
|
10802
|
-
},
|
|
10803
|
-
{
|
|
10804
|
-
key: "esc",
|
|
10805
|
-
label: currentSession ? "back" : "exit"
|
|
10806
|
-
}
|
|
10807
|
-
];
|
|
10808
|
-
const modelHint = modelLabel ? {
|
|
10809
|
-
key: keybindings.openModelPicker,
|
|
10810
|
-
label: modelLabel,
|
|
10811
|
-
labelColor: modelColor,
|
|
10812
|
-
...effortLabel ? { extra: {
|
|
10813
|
-
key: shortChord(keybindings.openEffortPicker),
|
|
10814
|
-
keyColor: effortKeyColor,
|
|
10815
|
-
label: effortLabel,
|
|
10816
|
-
labelColor: effortColor
|
|
10817
|
-
} } : {}
|
|
10818
|
-
} : null;
|
|
10819
|
-
const skillsChip = activeSkillCount > 0 ? {
|
|
10820
|
-
key: "✦",
|
|
10821
|
-
keyColor: skillsChipColor,
|
|
10822
|
-
label: activeSkillCount === 1 ? "1 skill" : `${activeSkillCount} skills`,
|
|
10823
|
-
labelColor: skillsChipColor
|
|
10824
|
-
} : null;
|
|
10825
|
-
const cancelTaskChip = inFlightToolCount > 0 ? {
|
|
10826
|
-
key: keybindings.cancelToolCall,
|
|
10827
|
-
label: inFlightToolCount === 1 ? "cancel task" : `cancel task (${inFlightToolCount})`
|
|
10828
|
-
} : null;
|
|
10829
|
-
return [
|
|
10830
|
-
...hasMultipleAgents ? [{
|
|
10831
|
-
key: keybindings.cycleAgent,
|
|
10832
|
-
label: agentLabel,
|
|
10833
|
-
labelColor: agentColor
|
|
10834
|
-
}] : [],
|
|
10835
|
-
...modelHint ? [modelHint] : [],
|
|
10836
|
-
...skillsChip ? [skillsChip] : [],
|
|
10837
|
-
...cancelTaskChip ? [cancelTaskChip] : [],
|
|
10838
|
-
...currentSession ? [{
|
|
10839
|
-
key: keybindings.openSessionDetails,
|
|
10840
|
-
label: "session"
|
|
10841
|
-
}] : [],
|
|
10842
|
-
{
|
|
10843
|
-
key: keybindings.openSettings,
|
|
10844
|
-
label: "settings"
|
|
10845
|
-
},
|
|
10846
|
-
{
|
|
10847
|
-
key: "esc",
|
|
10848
|
-
label: "sessions"
|
|
10849
|
-
},
|
|
10850
|
-
...updateHint ? [updateHint] : []
|
|
10851
|
-
];
|
|
10852
|
-
}
|
|
10853
|
-
/**
|
|
10854
|
-
* Shorten a binding spec for display as a "chord continuation" — the
|
|
10855
|
-
* `/n` after `ctrl+m model` in the chat footer. We only strip the
|
|
10856
|
-
* `ctrl+` prefix so user-customized chords (`alt+n`, `meta+shift+n`)
|
|
10857
|
-
* still render in full. Falls back to the verbatim spec when no
|
|
10858
|
-
* `ctrl+` prefix is found, which keeps the visual contract honest:
|
|
10859
|
-
* the rendered key always matches the bound trigger.
|
|
10860
|
-
*/
|
|
10861
|
-
function shortChord(spec) {
|
|
10862
|
-
return spec.startsWith("ctrl+") ? `/${spec.slice(5)}` : spec;
|
|
10863
|
-
}
|
|
10864
11812
|
//#endregion
|
|
10865
11813
|
//#region src/tui/tree-sitter.ts
|
|
10866
11814
|
/**
|
|
@@ -11104,7 +12052,18 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin,
|
|
|
11104
12052
|
return ((prev + delta) % catalog.length + catalog.length) % catalog.length;
|
|
11105
12053
|
}), [catalog.length]);
|
|
11106
12054
|
const safeCursor = Math.min(cursor, Math.max(0, catalog.length - 1));
|
|
12055
|
+
const focusedEntry = catalog[safeCursor];
|
|
12056
|
+
const focusedStatus = focusedEntry ? getMcpAuthStatus(authState, focusedEntry.config.name) : void 0;
|
|
12057
|
+
const inputActive = focusedStatus?.kind === "authorizing" && !!focusedStatus.url;
|
|
11107
12058
|
useKeyboard((key) => {
|
|
12059
|
+
if (key.name === "escape" && focusedEntry) {
|
|
12060
|
+
const name = focusedEntry.config.name;
|
|
12061
|
+
if (getMcpAuthStatus(authState, name).kind === "authorizing") {
|
|
12062
|
+
onCancelLogin(name);
|
|
12063
|
+
return;
|
|
12064
|
+
}
|
|
12065
|
+
}
|
|
12066
|
+
if (inputActive) return;
|
|
11108
12067
|
if (key.name === "up" || key.name === "k" || key.ctrl && key.name === "p") {
|
|
11109
12068
|
moveCursor(-1);
|
|
11110
12069
|
return;
|
|
@@ -11118,10 +12077,6 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin,
|
|
|
11118
12077
|
if (!entry) return;
|
|
11119
12078
|
const name = entry.config.name;
|
|
11120
12079
|
const status = getMcpAuthStatus(authState, name);
|
|
11121
|
-
if (key.name === "escape" && status.kind === "authorizing") {
|
|
11122
|
-
onCancelLogin(name);
|
|
11123
|
-
return;
|
|
11124
|
-
}
|
|
11125
12080
|
if (key.name === "return" || key.name === "space") {
|
|
11126
12081
|
toggle(name);
|
|
11127
12082
|
return;
|
|
@@ -11209,8 +12164,6 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin,
|
|
|
11209
12164
|
})
|
|
11210
12165
|
]
|
|
11211
12166
|
});
|
|
11212
|
-
const focusedEntry = catalog[safeCursor];
|
|
11213
|
-
const focusedStatus = focusedEntry ? getMcpAuthStatus(authState, focusedEntry.config.name) : void 0;
|
|
11214
12167
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
11215
12168
|
title: ` mcp servers · ${enabledSet.size} / ${catalog.length} enabled `,
|
|
11216
12169
|
children: [
|
|
@@ -11300,35 +12253,29 @@ function renderInlineBadge(status, COLOR) {
|
|
|
11300
12253
|
* itself to the content automatically).
|
|
11301
12254
|
*/
|
|
11302
12255
|
function renderDetailPanel(entry, status, COLOR) {
|
|
11303
|
-
if (status.kind === "authorizing")
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
/* @__PURE__ */ jsx("text", {
|
|
12256
|
+
if (status.kind === "authorizing") {
|
|
12257
|
+
if (!status.url) return /* @__PURE__ */ jsxs("box", {
|
|
12258
|
+
style: {
|
|
12259
|
+
flexDirection: "column",
|
|
12260
|
+
border: ["top"],
|
|
12261
|
+
borderColor: COLOR.border,
|
|
12262
|
+
paddingTop: 1
|
|
12263
|
+
},
|
|
12264
|
+
children: [/* @__PURE__ */ jsx("text", {
|
|
11312
12265
|
fg: COLOR.brand,
|
|
11313
12266
|
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
12267
|
}), /* @__PURE__ */ jsx("text", {
|
|
11323
|
-
|
|
11324
|
-
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
12268
|
+
fg: COLOR.dim,
|
|
12269
|
+
children: "Starting login flow…"
|
|
12270
|
+
})]
|
|
12271
|
+
});
|
|
12272
|
+
return /* @__PURE__ */ jsx(McpAuthorizingPanel, {
|
|
12273
|
+
serverName: entry.config.name,
|
|
12274
|
+
authUrl: status.url,
|
|
12275
|
+
maxLineWidth: 86,
|
|
12276
|
+
inputFocused: true
|
|
12277
|
+
});
|
|
12278
|
+
}
|
|
11332
12279
|
if (status.kind === "error") return /* @__PURE__ */ jsxs("box", {
|
|
11333
12280
|
style: {
|
|
11334
12281
|
flexDirection: "column",
|