zidane 5.6.0 → 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/dist/{agent-B26FuGew.d.ts → agent-Dtnvs5ee.d.ts} +2 -1
- package/dist/agent-Dtnvs5ee.d.ts.map +1 -0
- package/dist/chat.d.ts +112 -11
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +3 -3
- package/dist/{index-CE7z_11T.d.ts → index-DHeHe04L.d.ts} +2 -2
- package/dist/{index-CE7z_11T.d.ts.map → index-DHeHe04L.d.ts.map} +1 -1
- package/dist/{index-CROWxXo9.d.ts → index-DX8De0nl.d.ts} +2 -2
- package/dist/index-DX8De0nl.d.ts.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/{login-D5lQWoFx.js → login-BOj03nVe.js} +4 -3
- package/dist/login-BOj03nVe.js.map +1 -0
- package/dist/mcp.d.ts +1 -1
- package/dist/{presets-BDCthpyD.js → presets-CTSij3yV.js} +2 -2
- package/dist/{presets-BDCthpyD.js.map → presets-CTSij3yV.js.map} +1 -1
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/restate.d.ts +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/skills.d.ts +2 -2
- package/dist/{tools-Co3VYhgM.js → tools-CslsHpKb.js} +3 -2
- 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-CTTeQJzy.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 +851 -253
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-fhinWY4m.js → turn-operations-B8ySajUl.js} +572 -79
- package/dist/turn-operations-B8ySajUl.js.map +1 -0
- package/dist/types-oKPBdCmL.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/docs/TUI.md +1 -0
- package/package.json +2 -1
- package/dist/agent-B26FuGew.d.ts.map +0 -1
- package/dist/index-CROWxXo9.d.ts.map +0 -1
- package/dist/login-D5lQWoFx.js.map +0 -1
- package/dist/tools-Co3VYhgM.js.map +0 -1
- package/dist/transcript-anchors-CTTeQJzy.d.ts.map +0 -1
- package/dist/turn-operations-fhinWY4m.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,30 +1,37 @@
|
|
|
1
|
-
import { $
|
|
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-
|
|
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";
|
|
3
4
|
import { c as errorMessage } from "./errors-DdZXnyXE.js";
|
|
4
5
|
import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-ngMS0S6N.js";
|
|
5
|
-
import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-
|
|
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
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.
|
|
@@ -5848,7 +6417,61 @@ function renderProjectLabel(rowProject, currentProject, COLOR) {
|
|
|
5848
6417
|
/** Visible content lines: 1 minimum, 5 maximum (textarea scrolls past 5). */
|
|
5849
6418
|
const MIN_CONTENT_LINES = 1;
|
|
5850
6419
|
const MAX_CONTENT_LINES = 5;
|
|
5851
|
-
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
|
+
};
|
|
5852
6475
|
/**
|
|
5853
6476
|
* Stable empty-array reference — keeps `queuedMessages` default referentially
|
|
5854
6477
|
* stable across renders so memoized children don't bust their deps. Declared
|
|
@@ -5856,10 +6479,10 @@ const CWD_DISPLAY = compactPath(process.cwd());
|
|
|
5856
6479
|
* default-value reference at the prop destructure.
|
|
5857
6480
|
*/
|
|
5858
6481
|
const EMPTY_QUEUED_MESSAGES = [];
|
|
5859
|
-
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 }) {
|
|
5860
6483
|
const COLOR = useColors();
|
|
5861
6484
|
const titleText = session?.title ?? "untitled";
|
|
5862
|
-
const showSessionShortcut = !!session && !busy && !pending && !pendingInteraction;
|
|
6485
|
+
const showSessionShortcut = !!session && !busy && !pending && !pendingInteraction && settings.uiMode !== "minimal";
|
|
5863
6486
|
const userMessageCount = useMemo(() => events.filter((e) => e.kind === "user-prompt").length, [events]);
|
|
5864
6487
|
const { width: termWidth } = useTerminalDimensions();
|
|
5865
6488
|
const hasStatusIcon = busy || compacting;
|
|
@@ -5895,7 +6518,7 @@ function ChatScreen({ events, busy, compacting = false, queuedMessages = EMPTY_Q
|
|
|
5895
6518
|
const statsLen = segments.reduce((sum, s) => sum + s.text.length, 0);
|
|
5896
6519
|
const cwdBudget = Math.max(0, termWidth - 4 - titleText.length - iconReserve - OVERLAY_RESERVED - statsLen - 3);
|
|
5897
6520
|
if (cwdBudget >= 6) segments.unshift({
|
|
5898
|
-
text: compactPath(
|
|
6521
|
+
text: compactPath(cwd, cwdBudget),
|
|
5899
6522
|
color: COLOR.dim
|
|
5900
6523
|
}, {
|
|
5901
6524
|
text: " · ",
|
|
@@ -5903,6 +6526,7 @@ function ChatScreen({ events, busy, compacting = false, queuedMessages = EMPTY_Q
|
|
|
5903
6526
|
});
|
|
5904
6527
|
return segments;
|
|
5905
6528
|
}, [
|
|
6529
|
+
cwd,
|
|
5906
6530
|
session,
|
|
5907
6531
|
userMessageCount,
|
|
5908
6532
|
COLOR,
|
|
@@ -5921,8 +6545,8 @@ function ChatScreen({ events, busy, compacting = false, queuedMessages = EMPTY_Q
|
|
|
5921
6545
|
const modal = useModal();
|
|
5922
6546
|
useEffect(() => {
|
|
5923
6547
|
if (!fileEditPending) return;
|
|
5924
|
-
modal.
|
|
5925
|
-
return () => modal.
|
|
6548
|
+
modal.lock();
|
|
6549
|
+
return () => modal.unlock();
|
|
5926
6550
|
}, [fileEditPending, modal]);
|
|
5927
6551
|
return /* @__PURE__ */ jsxs("box", {
|
|
5928
6552
|
style: {
|
|
@@ -6281,44 +6905,16 @@ function QueuedMessagesBlock({ messages, selectionIndex, shortcuts }) {
|
|
|
6281
6905
|
function previewText(text) {
|
|
6282
6906
|
return text.replace(/\n+/g, " ↵ ");
|
|
6283
6907
|
}
|
|
6284
|
-
const KEY_GLYPHS = {
|
|
6285
|
-
up: "↑",
|
|
6286
|
-
down: "↓",
|
|
6287
|
-
left: "←",
|
|
6288
|
-
right: "→",
|
|
6289
|
-
return: "↵",
|
|
6290
|
-
enter: "↵",
|
|
6291
|
-
delete: "⌫",
|
|
6292
|
-
backspace: "⌫",
|
|
6293
|
-
escape: "esc",
|
|
6294
|
-
space: "␣",
|
|
6295
|
-
tab: "⇥"
|
|
6296
|
-
};
|
|
6297
|
-
/**
|
|
6298
|
-
* Render a binding spec (`"ctrl+return"`, `"backspace"`, `"delete"`) as a
|
|
6299
|
-
* compact display string the user recognizes at a glance. Substitutes
|
|
6300
|
-
* arrow / enter / backspace glyphs for their verbose names so the hint
|
|
6301
|
-
* row stays narrow; modifier names + plain keys pass through unchanged.
|
|
6302
|
-
*
|
|
6303
|
-
* Kept pure / local: every consumer in this file would otherwise embed
|
|
6304
|
-
* the same `replace` chain, and a future `keybindings.json` override
|
|
6305
|
-
* (e.g. `"backspace"` for drop) renders correctly without code edits.
|
|
6306
|
-
*/
|
|
6307
|
-
function formatBindingForDisplay(spec) {
|
|
6308
|
-
const segments = spec.toLowerCase().split("+");
|
|
6309
|
-
const key = segments.pop() ?? "";
|
|
6310
|
-
const modifiers = segments.join("+");
|
|
6311
|
-
const glyph = KEY_GLYPHS[key] ?? key;
|
|
6312
|
-
return modifiers ? `${modifiers}+${glyph}` : glyph;
|
|
6313
|
-
}
|
|
6314
6908
|
/** Stable empty providers reference — avoids `useCompletion` rerun on every render when no providers are wired. */
|
|
6315
6909
|
const EMPTY_PROVIDERS = [];
|
|
6316
6910
|
function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenChange, selectMode = null, triggerHints, busy = false, onEnterQueueFromEmpty }) {
|
|
6317
6911
|
const focused = useModalAwareFocus();
|
|
6318
6912
|
const COLOR = useColors();
|
|
6913
|
+
const SURFACE = useSurfaces();
|
|
6319
6914
|
const textareaRef = useRef(null);
|
|
6320
6915
|
/** Auto-grow: visible content rows the textarea currently occupies (clamped 1..5). */
|
|
6321
6916
|
const [contentLines, setContentLines] = useState(MIN_CONTENT_LINES);
|
|
6917
|
+
const [attachments, setAttachments] = useState([]);
|
|
6322
6918
|
/**
|
|
6323
6919
|
* Mirror of the textarea buffer + cursor, updated on every `onContentChange`.
|
|
6324
6920
|
* Drives `useCompletion` — the textarea stays uncontrolled (we don't push
|
|
@@ -6358,8 +6954,8 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
|
|
|
6358
6954
|
}, []);
|
|
6359
6955
|
const submit = useCallback(() => {
|
|
6360
6956
|
const value = textareaRef.current?.plainText ?? "";
|
|
6361
|
-
if (!value.trim()) return;
|
|
6362
|
-
onSubmit(value, completion.references);
|
|
6957
|
+
if (!value.trim() && attachments.length === 0) return;
|
|
6958
|
+
onSubmit(value, completion.references, attachments);
|
|
6363
6959
|
textareaRef.current?.clear();
|
|
6364
6960
|
historyRef.current = null;
|
|
6365
6961
|
setBufferState({
|
|
@@ -6367,7 +6963,12 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
|
|
|
6367
6963
|
cursor: 0
|
|
6368
6964
|
});
|
|
6369
6965
|
setContentLines(MIN_CONTENT_LINES);
|
|
6370
|
-
|
|
6966
|
+
setAttachments([]);
|
|
6967
|
+
}, [
|
|
6968
|
+
onSubmit,
|
|
6969
|
+
completion.references,
|
|
6970
|
+
attachments
|
|
6971
|
+
]);
|
|
6371
6972
|
const commitCompletion = useCallback(() => {
|
|
6372
6973
|
const result = completion.commit();
|
|
6373
6974
|
if (!result) return false;
|
|
@@ -6399,6 +7000,32 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
|
|
|
6399
7000
|
if (!ta) return;
|
|
6400
7001
|
const normalized = stripAnsiSequences(decodePasteBytes(event.bytes)).replace(/\r\n?/g, "\n");
|
|
6401
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
|
+
}
|
|
6402
7029
|
const parts = normalized.split("\n");
|
|
6403
7030
|
for (let i = 0; i < parts.length; i++) {
|
|
6404
7031
|
if (i > 0) ta.newLine();
|
|
@@ -6478,7 +7105,15 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
|
|
|
6478
7105
|
const buffer = textareaRef.current;
|
|
6479
7106
|
if (!buffer) return;
|
|
6480
7107
|
if (event.name === "up") {
|
|
6481
|
-
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
|
+
}
|
|
6482
7117
|
event.preventDefault();
|
|
6483
7118
|
if (buffer.plainText.length === 0 && onEnterQueueFromEmpty?.()) return;
|
|
6484
7119
|
cycleHistory(-1);
|
|
@@ -6492,7 +7127,8 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
|
|
|
6492
7127
|
completion,
|
|
6493
7128
|
commitCompletion,
|
|
6494
7129
|
cycleHistory,
|
|
6495
|
-
onEnterQueueFromEmpty
|
|
7130
|
+
onEnterQueueFromEmpty,
|
|
7131
|
+
syncBuffer
|
|
6496
7132
|
]);
|
|
6497
7133
|
const boxHeight = Math.min(MAX_CONTENT_LINES, contentLines) + 2;
|
|
6498
7134
|
return /* @__PURE__ */ jsxs("box", {
|
|
@@ -6505,36 +7141,71 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onPopupOpenCh
|
|
|
6505
7141
|
flexDirection: "column",
|
|
6506
7142
|
flexShrink: 0
|
|
6507
7143
|
},
|
|
6508
|
-
children: [
|
|
6509
|
-
|
|
6510
|
-
border: true,
|
|
6511
|
-
borderColor: selectMode ? COLOR.warn : COLOR.borderActive,
|
|
6512
|
-
paddingLeft: 1,
|
|
6513
|
-
paddingRight: 1,
|
|
6514
|
-
height: boxHeight,
|
|
6515
|
-
flexDirection: "column"
|
|
6516
|
-
},
|
|
6517
|
-
children: /* @__PURE__ */ jsx("textarea", {
|
|
6518
|
-
ref: textareaRef,
|
|
6519
|
-
focused: focused && !selectMode,
|
|
6520
|
-
keyBindings: TEXTAREA_BINDINGS,
|
|
6521
|
-
wrapMode: "word",
|
|
6522
|
-
placeholder: selectMode === "turn" ? "— turn-select mode — press ⎋ to resume typing —" : selectMode === "queue" ? "— queue-select mode — press ⎋ to resume typing —" : "Ask zidane…",
|
|
6523
|
-
syntaxStyle: chipStyle,
|
|
7144
|
+
children: [
|
|
7145
|
+
attachments.length > 0 && /* @__PURE__ */ jsx("box", {
|
|
6524
7146
|
style: {
|
|
6525
|
-
|
|
6526
|
-
|
|
7147
|
+
flexDirection: "row",
|
|
7148
|
+
flexWrap: "wrap",
|
|
7149
|
+
paddingLeft: 1,
|
|
7150
|
+
paddingRight: 1,
|
|
7151
|
+
paddingBottom: 0
|
|
7152
|
+
},
|
|
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"
|
|
6527
7185
|
},
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
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
|
|
6532
7207
|
})
|
|
6533
|
-
|
|
6534
|
-
selectMode,
|
|
6535
|
-
triggerHints,
|
|
6536
|
-
busy
|
|
6537
|
-
})]
|
|
7208
|
+
]
|
|
6538
7209
|
}), !selectMode && /* @__PURE__ */ jsx("box", {
|
|
6539
7210
|
style: {
|
|
6540
7211
|
position: "absolute",
|
|
@@ -6638,11 +7309,13 @@ const PROMPT_HINTS_SELECT_QUEUE = [{
|
|
|
6638
7309
|
*/
|
|
6639
7310
|
function PromptHints({ selectMode, triggerHints, busy = false }) {
|
|
6640
7311
|
const COLOR = useColors();
|
|
7312
|
+
const { settings } = useSettings();
|
|
6641
7313
|
const { width: termWidth } = useTerminalDimensions();
|
|
6642
|
-
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;
|
|
6643
7316
|
const hints = useMemo(() => {
|
|
6644
7317
|
const budget = Math.max(0, termWidth - 5);
|
|
6645
|
-
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;
|
|
6646
7319
|
return clipHintsToWidth(selectMode || !triggerHints || triggerHints.length === 0 || tooLongCombined ? primary : [...primary, ...triggerHints], budget);
|
|
6647
7320
|
}, [
|
|
6648
7321
|
selectMode,
|
|
@@ -8939,7 +9612,15 @@ function AppShell() {
|
|
|
8939
9612
|
useEffect(() => {
|
|
8940
9613
|
smoothStreamingRef.current = settings.smoothStreaming;
|
|
8941
9614
|
}, [settings.smoothStreaming]);
|
|
8942
|
-
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
|
+
}, []);
|
|
8943
9624
|
const safelistRef = useRef(null);
|
|
8944
9625
|
const readSafelist = useCallback(() => {
|
|
8945
9626
|
if (safelistRef.current === null) safelistRef.current = getSafelist(dataDir, projectDir);
|
|
@@ -8971,9 +9652,8 @@ function AppShell() {
|
|
|
8971
9652
|
const ensureSkillsCatalogRef = useRef(ensureSkillsCatalog);
|
|
8972
9653
|
ensureSkillsCatalogRef.current = ensureSkillsCatalog;
|
|
8973
9654
|
const formatFilePathForCwd = useMemo(() => {
|
|
8974
|
-
const cwd = process.cwd();
|
|
8975
9655
|
return (entry) => formatPathForCwd(entry.path, projectDir, cwd);
|
|
8976
|
-
}, [projectDir]);
|
|
9656
|
+
}, [projectDir, cwd]);
|
|
8977
9657
|
const completionProviders = useMemo(() => [createSkillsCompletionProvider({
|
|
8978
9658
|
getCatalog: () => skillsCatalogRef.current,
|
|
8979
9659
|
getEnabled: () => enabledSkillsRef.current,
|
|
@@ -9142,6 +9822,25 @@ function AppShell() {
|
|
|
9142
9822
|
queueSelectionIndexRef.current = queueSelectionIndex;
|
|
9143
9823
|
const agentRef = useRef(null);
|
|
9144
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]);
|
|
9145
9844
|
/**
|
|
9146
9845
|
* Live registry of in-flight tool calls — populated by `tool:before`
|
|
9147
9846
|
* (and `child:tool:before`), drained by `tool:after` / `tool:error` /
|
|
@@ -9287,7 +9986,7 @@ function AppShell() {
|
|
|
9287
9986
|
});
|
|
9288
9987
|
const allowInteraction = allowInteractionRef.current !== false;
|
|
9289
9988
|
const interactionTools = allowInteraction ? createInteractionTools({ requestInteraction: makeRequestInteraction(interactions) }) : {};
|
|
9290
|
-
const actualCwd =
|
|
9989
|
+
const actualCwd = cwdRef.current;
|
|
9291
9990
|
const envOpts = {
|
|
9292
9991
|
cwd: actualCwd,
|
|
9293
9992
|
...projectDir !== actualCwd ? { projectRoot: projectDir } : {},
|
|
@@ -9323,6 +10022,7 @@ function AppShell() {
|
|
|
9323
10022
|
...persistBehavior,
|
|
9324
10023
|
tasksDir
|
|
9325
10024
|
},
|
|
10025
|
+
execution: createProcessContext({ cwd: actualCwd }),
|
|
9326
10026
|
provider: descriptor.factory(),
|
|
9327
10027
|
session,
|
|
9328
10028
|
mcpConnector: (configs) => connectMcpServers(configs, void 0, agent.hooks, { buildAuthProvider: (cfg) => new McpOAuthProvider({
|
|
@@ -10147,7 +10847,7 @@ function AppShell() {
|
|
|
10147
10847
|
* busy-state lifecycle so the title spinner stays mounted continuously
|
|
10148
10848
|
* across back-to-back queued messages.
|
|
10149
10849
|
*/
|
|
10150
|
-
const runSingleMessage = useCallback(async (prompt, references) => {
|
|
10850
|
+
const runSingleMessage = useCallback(async (prompt, references, attachments) => {
|
|
10151
10851
|
const agent = agentRef.current;
|
|
10152
10852
|
const session = sessionRef.current;
|
|
10153
10853
|
if (!agent || !session || !picked) return;
|
|
@@ -10160,10 +10860,16 @@ function AppShell() {
|
|
|
10160
10860
|
end: r.end,
|
|
10161
10861
|
providerId: r.providerId
|
|
10162
10862
|
}));
|
|
10863
|
+
const attachmentMeta = attachments.map((a) => ({
|
|
10864
|
+
name: a.name,
|
|
10865
|
+
mediaType: a.mediaType,
|
|
10866
|
+
size: a.content.length
|
|
10867
|
+
}));
|
|
10163
10868
|
stream.appendImmediate({
|
|
10164
10869
|
kind: "user-prompt",
|
|
10165
10870
|
text: prompt,
|
|
10166
|
-
...refSpans.length > 0 ? { refs: refSpans } : {}
|
|
10871
|
+
...refSpans.length > 0 ? { refs: refSpans } : {},
|
|
10872
|
+
...attachmentMeta.length > 0 ? { attachments: attachmentMeta } : {}
|
|
10167
10873
|
});
|
|
10168
10874
|
if (autoCompactInFlightRef.current) await autoCompactInFlightRef.current.catch(() => {});
|
|
10169
10875
|
const newRefs = uniqueSkillNamesFromReferences(references);
|
|
@@ -10174,9 +10880,34 @@ function AppShell() {
|
|
|
10174
10880
|
debugLog(`activateSkill("${name}")`, err);
|
|
10175
10881
|
}
|
|
10176
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
|
+
})];
|
|
10177
10908
|
await agent.run({
|
|
10178
10909
|
model: picked.model,
|
|
10179
|
-
prompt,
|
|
10910
|
+
prompt: runPrompt,
|
|
10180
10911
|
...picked.effort ? { thinking: picked.effort } : {}
|
|
10181
10912
|
});
|
|
10182
10913
|
await session.save().catch((err) => debugLog("session.save failed", err));
|
|
@@ -10198,15 +10929,16 @@ function AppShell() {
|
|
|
10198
10929
|
stream.flushAndUpdate(finalizeStreamingMarkdown);
|
|
10199
10930
|
}
|
|
10200
10931
|
}, [picked, stream]);
|
|
10201
|
-
const onSubmitPrompt = useCallback((prompt, references) => {
|
|
10202
|
-
if (!prompt.trim()) return;
|
|
10932
|
+
const onSubmitPrompt = useCallback((prompt, references, attachments) => {
|
|
10933
|
+
if (!prompt.trim() && attachments.length === 0) return;
|
|
10203
10934
|
const agent = agentRef.current;
|
|
10204
10935
|
const session = sessionRef.current;
|
|
10205
10936
|
if (!agent || !session || !picked) return;
|
|
10206
10937
|
if (runningRef.current) {
|
|
10207
10938
|
messageQueueRef.current = [...messageQueueRef.current, {
|
|
10208
10939
|
prompt,
|
|
10209
|
-
references
|
|
10940
|
+
references,
|
|
10941
|
+
attachments
|
|
10210
10942
|
}];
|
|
10211
10943
|
setMessageQueue(messageQueueRef.current.slice());
|
|
10212
10944
|
return;
|
|
@@ -10215,7 +10947,7 @@ function AppShell() {
|
|
|
10215
10947
|
(async () => {
|
|
10216
10948
|
setBusy(true);
|
|
10217
10949
|
try {
|
|
10218
|
-
await runSingleMessage(prompt, references);
|
|
10950
|
+
await runSingleMessage(prompt, references, attachments);
|
|
10219
10951
|
while (messageQueueRef.current.length > 0) {
|
|
10220
10952
|
const next = messageQueueRef.current[0];
|
|
10221
10953
|
messageQueueRef.current = messageQueueRef.current.slice(1);
|
|
@@ -10226,7 +10958,7 @@ function AppShell() {
|
|
|
10226
10958
|
if (prev <= 0) return null;
|
|
10227
10959
|
return prev - 1;
|
|
10228
10960
|
});
|
|
10229
|
-
await runSingleMessage(next.prompt, next.references);
|
|
10961
|
+
await runSingleMessage(next.prompt, next.references, next.attachments);
|
|
10230
10962
|
}
|
|
10231
10963
|
} finally {
|
|
10232
10964
|
setBusy(false);
|
|
@@ -10852,6 +11584,15 @@ function AppShell() {
|
|
|
10852
11584
|
}));
|
|
10853
11585
|
return;
|
|
10854
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
|
+
}
|
|
10855
11596
|
if (matchesBinding(key, keybindings.enterSelectTurnMode) && screen === "chat" && !busy && !pendingApproval && !pendingInteraction) {
|
|
10856
11597
|
enterSelectMode();
|
|
10857
11598
|
return;
|
|
@@ -10883,6 +11624,16 @@ function AppShell() {
|
|
|
10883
11624
|
}));
|
|
10884
11625
|
return;
|
|
10885
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
|
+
}
|
|
10886
11637
|
if (key.name !== "escape") return;
|
|
10887
11638
|
if (busy || pendingApproval) return onAbort();
|
|
10888
11639
|
if (popupOpenRef.current) return;
|
|
@@ -10917,6 +11668,7 @@ function AppShell() {
|
|
|
10917
11668
|
pendingInteractionResumed: pendingInteractionIsResumed,
|
|
10918
11669
|
currentSession,
|
|
10919
11670
|
hasMultipleAgents,
|
|
11671
|
+
uiMode: settings.uiMode,
|
|
10920
11672
|
modelLabel: picked?.model ?? null,
|
|
10921
11673
|
modelColor: COLOR.model,
|
|
10922
11674
|
effortLabel: modelHasReasoning ? picked?.effort ?? "medium" : null,
|
|
@@ -10937,6 +11689,7 @@ function AppShell() {
|
|
|
10937
11689
|
pendingInteractionIsResumed,
|
|
10938
11690
|
currentSession,
|
|
10939
11691
|
hasMultipleAgents,
|
|
11692
|
+
settings.uiMode,
|
|
10940
11693
|
picked,
|
|
10941
11694
|
pickedAgent,
|
|
10942
11695
|
COLOR,
|
|
@@ -11016,6 +11769,7 @@ function AppShell() {
|
|
|
11016
11769
|
currentProjectRoot: projectDir
|
|
11017
11770
|
}),
|
|
11018
11771
|
screen === "chat" && /* @__PURE__ */ jsx(ChatScreen, {
|
|
11772
|
+
cwd,
|
|
11019
11773
|
events,
|
|
11020
11774
|
busy,
|
|
11021
11775
|
compacting,
|
|
@@ -11055,162 +11809,6 @@ function effortForModel(descriptor, modelId, remembered) {
|
|
|
11055
11809
|
if (!modelSupportsReasoning(descriptor, modelId)) return void 0;
|
|
11056
11810
|
return remembered?.[modelId] ?? "medium";
|
|
11057
11811
|
}
|
|
11058
|
-
/**
|
|
11059
|
-
* Build the footer's shortcut hints for the current screen. On the chat
|
|
11060
|
-
* screen the model id rides next to its `ctrl+m` shortcut and the agent
|
|
11061
|
-
* label rides next to `shift+tab`, each in its accent color — the bar
|
|
11062
|
-
* doubles as the status display without needing separate badges. When
|
|
11063
|
-
* the active model exposes reasoning, the `ctrl+m` hint grows a
|
|
11064
|
-
* secondary `/n` chord with the current effort label, surfacing the
|
|
11065
|
-
* effort picker as a discoverable, in-place affordance.
|
|
11066
|
-
*/
|
|
11067
|
-
function buildHints({ screen, busy, pending, pendingInteractionLive, pendingInteractionResumed, currentSession, hasMultipleAgents, modelLabel, modelColor, effortLabel, effortColor, effortKeyColor, agentLabel, agentColor, keybindings, inFlightToolCount, activeSkillCount, skillsChipColor, updateHint }) {
|
|
11068
|
-
if (pending) return [
|
|
11069
|
-
{
|
|
11070
|
-
key: "↑↓",
|
|
11071
|
-
label: "navigate"
|
|
11072
|
-
},
|
|
11073
|
-
{
|
|
11074
|
-
key: "↵",
|
|
11075
|
-
label: "select"
|
|
11076
|
-
},
|
|
11077
|
-
{
|
|
11078
|
-
key: "esc",
|
|
11079
|
-
label: "abort run"
|
|
11080
|
-
}
|
|
11081
|
-
];
|
|
11082
|
-
if (pendingInteractionLive) return [
|
|
11083
|
-
{
|
|
11084
|
-
key: "↑↓",
|
|
11085
|
-
label: "navigate"
|
|
11086
|
-
},
|
|
11087
|
-
{
|
|
11088
|
-
key: "↵",
|
|
11089
|
-
label: "select"
|
|
11090
|
-
},
|
|
11091
|
-
{
|
|
11092
|
-
key: "esc",
|
|
11093
|
-
label: "abort run"
|
|
11094
|
-
}
|
|
11095
|
-
];
|
|
11096
|
-
if (pendingInteractionResumed) return [
|
|
11097
|
-
{
|
|
11098
|
-
key: "↑↓",
|
|
11099
|
-
label: "navigate"
|
|
11100
|
-
},
|
|
11101
|
-
{
|
|
11102
|
-
key: "↵",
|
|
11103
|
-
label: "select"
|
|
11104
|
-
},
|
|
11105
|
-
{
|
|
11106
|
-
key: "esc",
|
|
11107
|
-
label: "leave for later"
|
|
11108
|
-
}
|
|
11109
|
-
];
|
|
11110
|
-
if (busy) {
|
|
11111
|
-
const baseBusyHints = [];
|
|
11112
|
-
if (inFlightToolCount > 0) baseBusyHints.push({
|
|
11113
|
-
key: keybindings.cancelToolCall,
|
|
11114
|
-
label: inFlightToolCount === 1 ? "cancel" : `cancel (${inFlightToolCount})`
|
|
11115
|
-
});
|
|
11116
|
-
baseBusyHints.push({
|
|
11117
|
-
key: "esc",
|
|
11118
|
-
label: "abort"
|
|
11119
|
-
});
|
|
11120
|
-
return baseBusyHints;
|
|
11121
|
-
}
|
|
11122
|
-
if (screen === "auth") return [
|
|
11123
|
-
{
|
|
11124
|
-
key: "↑↓",
|
|
11125
|
-
label: "navigate"
|
|
11126
|
-
},
|
|
11127
|
-
{
|
|
11128
|
-
key: "↵",
|
|
11129
|
-
label: "select"
|
|
11130
|
-
},
|
|
11131
|
-
{
|
|
11132
|
-
key: "esc",
|
|
11133
|
-
label: "exit"
|
|
11134
|
-
}
|
|
11135
|
-
];
|
|
11136
|
-
if (screen === "sessions") return [
|
|
11137
|
-
{
|
|
11138
|
-
key: "↑↓",
|
|
11139
|
-
label: "navigate"
|
|
11140
|
-
},
|
|
11141
|
-
{
|
|
11142
|
-
key: "↵",
|
|
11143
|
-
label: "open"
|
|
11144
|
-
},
|
|
11145
|
-
{
|
|
11146
|
-
key: keybindings.openSessionDetails,
|
|
11147
|
-
label: "session"
|
|
11148
|
-
},
|
|
11149
|
-
{
|
|
11150
|
-
key: keybindings.openSettings,
|
|
11151
|
-
label: "settings"
|
|
11152
|
-
},
|
|
11153
|
-
{
|
|
11154
|
-
key: "esc",
|
|
11155
|
-
label: currentSession ? "back" : "exit"
|
|
11156
|
-
}
|
|
11157
|
-
];
|
|
11158
|
-
const modelHint = modelLabel ? {
|
|
11159
|
-
key: keybindings.openModelPicker,
|
|
11160
|
-
label: modelLabel,
|
|
11161
|
-
labelColor: modelColor,
|
|
11162
|
-
...effortLabel ? { extra: {
|
|
11163
|
-
key: shortChord(keybindings.openEffortPicker),
|
|
11164
|
-
keyColor: effortKeyColor,
|
|
11165
|
-
label: effortLabel,
|
|
11166
|
-
labelColor: effortColor
|
|
11167
|
-
} } : {}
|
|
11168
|
-
} : null;
|
|
11169
|
-
const skillsChip = activeSkillCount > 0 ? {
|
|
11170
|
-
key: "✦",
|
|
11171
|
-
keyColor: skillsChipColor,
|
|
11172
|
-
label: activeSkillCount === 1 ? "1 skill" : `${activeSkillCount} skills`,
|
|
11173
|
-
labelColor: skillsChipColor
|
|
11174
|
-
} : null;
|
|
11175
|
-
const cancelTaskChip = inFlightToolCount > 0 ? {
|
|
11176
|
-
key: keybindings.cancelToolCall,
|
|
11177
|
-
label: inFlightToolCount === 1 ? "cancel task" : `cancel task (${inFlightToolCount})`
|
|
11178
|
-
} : null;
|
|
11179
|
-
return [
|
|
11180
|
-
...hasMultipleAgents ? [{
|
|
11181
|
-
key: keybindings.cycleAgent,
|
|
11182
|
-
label: agentLabel,
|
|
11183
|
-
labelColor: agentColor
|
|
11184
|
-
}] : [],
|
|
11185
|
-
...modelHint ? [modelHint] : [],
|
|
11186
|
-
...skillsChip ? [skillsChip] : [],
|
|
11187
|
-
...cancelTaskChip ? [cancelTaskChip] : [],
|
|
11188
|
-
...currentSession ? [{
|
|
11189
|
-
key: keybindings.openSessionDetails,
|
|
11190
|
-
label: "session"
|
|
11191
|
-
}] : [],
|
|
11192
|
-
{
|
|
11193
|
-
key: keybindings.openSettings,
|
|
11194
|
-
label: "settings"
|
|
11195
|
-
},
|
|
11196
|
-
{
|
|
11197
|
-
key: "esc",
|
|
11198
|
-
label: "sessions"
|
|
11199
|
-
},
|
|
11200
|
-
...updateHint ? [updateHint] : []
|
|
11201
|
-
];
|
|
11202
|
-
}
|
|
11203
|
-
/**
|
|
11204
|
-
* Shorten a binding spec for display as a "chord continuation" — the
|
|
11205
|
-
* `/n` after `ctrl+m model` in the chat footer. We only strip the
|
|
11206
|
-
* `ctrl+` prefix so user-customized chords (`alt+n`, `meta+shift+n`)
|
|
11207
|
-
* still render in full. Falls back to the verbatim spec when no
|
|
11208
|
-
* `ctrl+` prefix is found, which keeps the visual contract honest:
|
|
11209
|
-
* the rendered key always matches the bound trigger.
|
|
11210
|
-
*/
|
|
11211
|
-
function shortChord(spec) {
|
|
11212
|
-
return spec.startsWith("ctrl+") ? `/${spec.slice(5)}` : spec;
|
|
11213
|
-
}
|
|
11214
11812
|
//#endregion
|
|
11215
11813
|
//#region src/tui/tree-sitter.ts
|
|
11216
11814
|
/**
|