zidane 5.1.0 → 5.1.1
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/chat.d.ts +3 -3
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +2 -2
- package/dist/index.js +1 -1
- package/dist/{login-BiuHyuEh.js → login-B7b7NNJQ.js} +2 -1
- package/dist/login-B7b7NNJQ.js.map +1 -0
- package/dist/{theme-CcGLMJrn.d.ts → tool-formatters-D7cN3T_W.d.ts} +203 -14
- package/dist/tool-formatters-D7cN3T_W.d.ts.map +1 -0
- package/dist/tui.d.ts +9 -33
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +69 -369
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-BzOIM6Of.js → turn-operations-lsUMITng.js} +497 -29
- package/dist/turn-operations-lsUMITng.js.map +1 -0
- package/package.json +1 -1
- package/dist/login-BiuHyuEh.js.map +0 -1
- package/dist/theme-CcGLMJrn.d.ts.map +0 -1
- package/dist/turn-operations-BzOIM6Of.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { S as resolvePersistDir, b as cleanupPersistedSession, d as createAgent } from "./tools-d1yeA6xK.js";
|
|
2
2
|
import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-BgwK6ySj.js";
|
|
3
|
-
import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-
|
|
3
|
+
import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-B7b7NNJQ.js";
|
|
4
4
|
import { n as formatTokenUsage } from "./stats-DvCtBRwK.js";
|
|
5
5
|
import { n as loadSession, t as createSession } from "./session-pS4Vt4dl.js";
|
|
6
6
|
import { createTuiStore } from "./session/sqlite.js";
|
|
7
|
-
import { $ as
|
|
7
|
+
import { $ as getMcpAuthStatus, $t as toolResultText, A as isOnSafelist, An as tryOpenBrowser, B as filterModelCatalog, Bt as eventsFromTurns, C as writeSessionExport, Cn as createFilesCompletionProvider, Ct as SettingsProvider, E as useSafeModeQueue, Ft as ConfigProvider, Gt as listSessionMeta, H as buildMcpServers, Ht as isTurnHighlighted, I as splitPromptSegments, It as useConfig, Jn as modelSupportsReasoning, Kn as getContextWindow, L as runOAuthLogin, Lt as resolveConfig, Mn as detectAuth, O as addToSafelist, Ot as resolveChipColor, P as suggestSafelistEntry, Q as useMcpAuthState, Qn as piIdOf, Qt as toolCallPreview, R as supportsOAuth, Rn as setProviderCredential, St as SETTINGS_TOGGLES, T as useSafeModeActions, Tt as useSettings, Ut as isVisible, V as indexOfEntry, Vt as isEditErrorResult, W as discoverProjectMcps, Wt as lastContextSizeFromTurns, X as McpAuthProvider, Xt as stripSpawnTokensLine, Yt as selectableTurnIds, Z as useMcpAuthDispatch, _ as useStreamBuffer, _t as shortId, a as TOOL_DISPLAY, an as filetypeFromPath, at as createInteractionTools, b as discoverProjectSkills, bn as createSkillsCompletionProvider, bt as DEFAULT_SETTINGS, c as ThemeProvider, cn as findGitRoot, ct as pendingInteractionsFromTurns, d as useSurfaces, dt as useInteractionsQueue, en as turnSelectionOwnership, fn as ensureKeybindingsFile, g as turnContextSize, gr as buildPlanSystem, gt as fmtTokens, h as finalizeStreamingMarkdownForOwner, hr as buildBuildSystem, ht as compactPath, i as turnAsText, in as extractEditPayload, ir as accentColor, it as buildResumedToolResultsTurn, jn as shouldAutoCompact, k as getSafelist, kn as useCompletion, kt as resolveTheme, l as useColors, m as finalizeStreamingMarkdown, mn as matchesBinding, mt as ageString, n as deleteTurnSafely, nt as InteractionsProvider, o as displayNameFor, p as useTheme, pt as generateSessionTitle, q as createFileMcpCredentialStore, qt as marginTopFor, r as truncateTurnsAt, s as formatToolCall, st as makeRequestInteraction, tn as buildUnifiedDiff, u as useSelectStyle, ut as useInteractionsActions, v as buildSkillsConfig, vt as listProjectFiles, w as SafeModeProvider, wt as clampFps, xn as uniqueSkillNamesFromReferences, xt as SETTINGS_CHOICES, y as defaultSkillScanPaths, yt as useEnabledToggleSet, z as buildModelCatalog, zt as deriveSessionTitle } from "./turn-operations-lsUMITng.js";
|
|
8
8
|
import { Buffer } from "node:buffer";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
-
import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
|
|
12
11
|
import { RGBA, SyntaxStyle, addDefaultParsers, createCliRenderer, defaultTextareaKeyBindings, getTreeSitterClient } from "@opentui/core";
|
|
13
12
|
import { createRoot, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/react";
|
|
13
|
+
import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
|
|
14
14
|
//#region src/tui/modal.tsx
|
|
15
15
|
const ModalContext = createContext(null);
|
|
16
16
|
function ModalRoot({ children }) {
|
|
@@ -204,19 +204,6 @@ function EmptyState$1() {
|
|
|
204
204
|
})]
|
|
205
205
|
});
|
|
206
206
|
}
|
|
207
|
-
/**
|
|
208
|
-
* Resolve a profile's `accent` token to a concrete theme color via the
|
|
209
|
-
* caller's color palette. Exposed for the Footer badge so all surfaces
|
|
210
|
-
* stay in sync with the picker's row tinting.
|
|
211
|
-
*/
|
|
212
|
-
function accentColor(accent, COLOR) {
|
|
213
|
-
switch (accent) {
|
|
214
|
-
case "brand": return COLOR.brand;
|
|
215
|
-
case "warn": return COLOR.warn;
|
|
216
|
-
case "model": return COLOR.model;
|
|
217
|
-
default: return COLOR.accent;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
207
|
//#endregion
|
|
221
208
|
//#region src/tui/theme.ts
|
|
222
209
|
/**
|
|
@@ -360,250 +347,6 @@ function useChipHighlights(textareaRef, references, chipStyle) {
|
|
|
360
347
|
]);
|
|
361
348
|
}
|
|
362
349
|
//#endregion
|
|
363
|
-
//#region src/tui/tool-formatters.ts
|
|
364
|
-
const TOOL_DISPLAY = {
|
|
365
|
-
read_file: {
|
|
366
|
-
displayName: "Read",
|
|
367
|
-
format: (input) => {
|
|
368
|
-
const path = stringField(input, "path");
|
|
369
|
-
if (!path) return null;
|
|
370
|
-
const meta = [];
|
|
371
|
-
const offset = numberField(input, "offset");
|
|
372
|
-
const limit = numberField(input, "limit");
|
|
373
|
-
if (offset !== void 0 && limit !== void 0 && limit > 0) meta.push(`L${offset}–${offset + limit - 1}`);
|
|
374
|
-
else if (offset !== void 0) meta.push(`from L${offset}`);
|
|
375
|
-
else if (limit !== void 0 && limit > 0) meta.push(`${limit} lines`);
|
|
376
|
-
return {
|
|
377
|
-
target: path,
|
|
378
|
-
meta
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
},
|
|
382
|
-
list_files: {
|
|
383
|
-
displayName: "List",
|
|
384
|
-
format: (input) => {
|
|
385
|
-
return { target: stringField(input, "path") ?? "." };
|
|
386
|
-
}
|
|
387
|
-
},
|
|
388
|
-
glob: {
|
|
389
|
-
displayName: "Glob",
|
|
390
|
-
format: (input) => {
|
|
391
|
-
const pattern = stringField(input, "pattern");
|
|
392
|
-
if (!pattern) return null;
|
|
393
|
-
const meta = [];
|
|
394
|
-
const limit = numberField(input, "limit");
|
|
395
|
-
if (limit !== void 0) meta.push(`limit ${limit}`);
|
|
396
|
-
return {
|
|
397
|
-
target: pattern,
|
|
398
|
-
meta
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
},
|
|
402
|
-
grep: {
|
|
403
|
-
displayName: "Grep",
|
|
404
|
-
format: (input) => {
|
|
405
|
-
const pattern = stringField(input, "pattern");
|
|
406
|
-
if (!pattern) return null;
|
|
407
|
-
const target = `/${pattern}/`;
|
|
408
|
-
const meta = [];
|
|
409
|
-
const path = stringField(input, "path");
|
|
410
|
-
if (path && path !== ".") meta.push(`in ${path}`);
|
|
411
|
-
const glob = stringField(input, "glob");
|
|
412
|
-
if (glob) meta.push(glob);
|
|
413
|
-
const type = stringField(input, "type");
|
|
414
|
-
if (type) meta.push(`type:${type}`);
|
|
415
|
-
if (input["-i"] === true) meta.push("case-insensitive");
|
|
416
|
-
const mode = stringField(input, "output_mode");
|
|
417
|
-
if (mode && mode !== "files_with_matches") meta.push(mode);
|
|
418
|
-
return {
|
|
419
|
-
target,
|
|
420
|
-
meta
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
},
|
|
424
|
-
shell: {
|
|
425
|
-
displayName: "Shell",
|
|
426
|
-
format: (input) => {
|
|
427
|
-
const command = stringField(input, "command");
|
|
428
|
-
if (!command) return null;
|
|
429
|
-
return { target: truncate(command.trim(), 200) };
|
|
430
|
-
}
|
|
431
|
-
},
|
|
432
|
-
edit: {
|
|
433
|
-
displayName: "Edit",
|
|
434
|
-
format: (input) => {
|
|
435
|
-
const path = stringField(input, "path");
|
|
436
|
-
if (!path) return null;
|
|
437
|
-
return {
|
|
438
|
-
target: path,
|
|
439
|
-
meta: input.replace_all === true ? ["replace all"] : []
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
},
|
|
443
|
-
multi_edit: {
|
|
444
|
-
displayName: "Multi-edit",
|
|
445
|
-
format: (input) => {
|
|
446
|
-
const path = stringField(input, "path");
|
|
447
|
-
if (!path) return null;
|
|
448
|
-
const edits = Array.isArray(input.edits) ? input.edits.length : 0;
|
|
449
|
-
return {
|
|
450
|
-
target: path,
|
|
451
|
-
meta: edits > 0 ? [`${edits} hunk${edits === 1 ? "" : "s"}`] : []
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
write_file: {
|
|
456
|
-
displayName: "Write",
|
|
457
|
-
format: (input) => {
|
|
458
|
-
const path = stringField(input, "path");
|
|
459
|
-
if (!path) return null;
|
|
460
|
-
const content = stringField(input, "content");
|
|
461
|
-
const meta = [];
|
|
462
|
-
if (content !== void 0) {
|
|
463
|
-
const bytes = byteLengthUtf8(content);
|
|
464
|
-
meta.push(`${formatBytes(bytes)}`);
|
|
465
|
-
}
|
|
466
|
-
return {
|
|
467
|
-
target: path,
|
|
468
|
-
meta
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
},
|
|
472
|
-
spawn: {
|
|
473
|
-
displayName: "Spawn",
|
|
474
|
-
format: (input) => {
|
|
475
|
-
const task = stringField(input, "task");
|
|
476
|
-
if (!task) return null;
|
|
477
|
-
return { target: truncate(task.replace(/\s+/g, " ").trim(), 120) };
|
|
478
|
-
}
|
|
479
|
-
},
|
|
480
|
-
tool_search: {
|
|
481
|
-
displayName: "Search tools",
|
|
482
|
-
format: (input) => {
|
|
483
|
-
const query = stringField(input, "query");
|
|
484
|
-
const names = Array.isArray(input.names) ? input.names.length : 0;
|
|
485
|
-
if (query) return { target: `“${query}”` };
|
|
486
|
-
if (names > 0) return { target: `${names} tool${names === 1 ? "" : "s"}` };
|
|
487
|
-
return null;
|
|
488
|
-
}
|
|
489
|
-
},
|
|
490
|
-
skills_use: {
|
|
491
|
-
displayName: "Activate skill",
|
|
492
|
-
format: (input) => {
|
|
493
|
-
const name = stringField(input, "name");
|
|
494
|
-
if (!name) return null;
|
|
495
|
-
return { target: name };
|
|
496
|
-
}
|
|
497
|
-
},
|
|
498
|
-
skills_read: {
|
|
499
|
-
displayName: "Read skill",
|
|
500
|
-
format: (input) => {
|
|
501
|
-
const name = stringField(input, "name");
|
|
502
|
-
const path = stringField(input, "path");
|
|
503
|
-
if (!name) return null;
|
|
504
|
-
return { target: path ? `${name}/${path}` : name };
|
|
505
|
-
}
|
|
506
|
-
},
|
|
507
|
-
skills_run_script: {
|
|
508
|
-
displayName: "Run script",
|
|
509
|
-
format: (input) => {
|
|
510
|
-
const name = stringField(input, "name");
|
|
511
|
-
const script = stringField(input, "script");
|
|
512
|
-
if (!name || !script) return null;
|
|
513
|
-
const meta = [`skill ${name}`];
|
|
514
|
-
const args = Array.isArray(input.args) ? input.args : null;
|
|
515
|
-
if (args && args.length > 0) meta.push(truncate(args.map(String).join(" "), 80));
|
|
516
|
-
return {
|
|
517
|
-
target: script,
|
|
518
|
-
meta
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
},
|
|
522
|
-
ask_user: {
|
|
523
|
-
displayName: "Ask user",
|
|
524
|
-
format: (input) => {
|
|
525
|
-
const questions = Array.isArray(input.questions) ? input.questions.length : 0;
|
|
526
|
-
if (questions === 0) return null;
|
|
527
|
-
return { target: `${questions} question${questions === 1 ? "" : "s"}` };
|
|
528
|
-
}
|
|
529
|
-
},
|
|
530
|
-
present_plan: {
|
|
531
|
-
displayName: "Present plan",
|
|
532
|
-
format: (input) => {
|
|
533
|
-
const title = stringField(input, "title");
|
|
534
|
-
if (!title) return null;
|
|
535
|
-
return { target: title };
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
/**
|
|
540
|
-
* Resolve the display verb for a tool. Native tools use their curated
|
|
541
|
-
* entry from {@link TOOL_DISPLAY}; everything else gets a Title-Case
|
|
542
|
-
* version of the raw name (`my_host_tool` → `My Host Tool`) so an MCP /
|
|
543
|
-
* host tool still reads cleanly in the transcript.
|
|
544
|
-
*
|
|
545
|
-
* MCP convention: every tool surfaced by `mcp/connectMcpServers` is
|
|
546
|
-
* namespaced as `mcp_<server>_<tool>` (see `src/mcp/index.ts`). The
|
|
547
|
-
* `mcp_` prefix is plumbing — strip it before title-casing so the
|
|
548
|
-
* label reads as `Github Create Issue` instead of `Mcp Github Create
|
|
549
|
-
* Issue`. The server name becomes the leading words, which doubles as
|
|
550
|
-
* a free visual grouping affordance ("everything starting with
|
|
551
|
-
* `Github` came from the github MCP server").
|
|
552
|
-
*/
|
|
553
|
-
function displayNameFor(name) {
|
|
554
|
-
const entry = TOOL_DISPLAY[name];
|
|
555
|
-
if (entry) return entry.displayName;
|
|
556
|
-
return titleCase(name.startsWith("mcp_") ? name.slice(4) : name);
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Run a tool's curated formatter and return the result, or `null` when
|
|
560
|
-
* no formatter is registered / the input shape doesn't match. Renderer
|
|
561
|
-
* decides what to do with `null` — typically: show `↳ <displayName>`
|
|
562
|
-
* with no target / meta tail.
|
|
563
|
-
*/
|
|
564
|
-
function formatToolCall(name, input) {
|
|
565
|
-
const entry = TOOL_DISPLAY[name];
|
|
566
|
-
if (!entry) return null;
|
|
567
|
-
try {
|
|
568
|
-
return entry.format(input);
|
|
569
|
-
} catch {
|
|
570
|
-
return null;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
function stringField(input, key) {
|
|
574
|
-
const v = input[key];
|
|
575
|
-
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
576
|
-
}
|
|
577
|
-
function numberField(input, key) {
|
|
578
|
-
const v = input[key];
|
|
579
|
-
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
580
|
-
}
|
|
581
|
-
/** `snake_case` / `kebab-case` / lowercase → `Title Case`. */
|
|
582
|
-
function titleCase(s) {
|
|
583
|
-
return s.split(/[-_\s]+/).filter(Boolean).map((w) => w[0]?.toUpperCase() + w.slice(1).toLowerCase()).join(" ");
|
|
584
|
-
}
|
|
585
|
-
function truncate(s, max) {
|
|
586
|
-
return s.length <= max ? s : `${s.slice(0, max - 1)}…`;
|
|
587
|
-
}
|
|
588
|
-
function byteLengthUtf8(s) {
|
|
589
|
-
let bytes = 0;
|
|
590
|
-
for (let i = 0; i < s.length; i++) {
|
|
591
|
-
const code = s.charCodeAt(i);
|
|
592
|
-
if (code < 128) bytes += 1;
|
|
593
|
-
else if (code < 2048) bytes += 2;
|
|
594
|
-
else if (code >= 55296 && code <= 56319) {
|
|
595
|
-
bytes += 4;
|
|
596
|
-
i++;
|
|
597
|
-
} else bytes += 3;
|
|
598
|
-
}
|
|
599
|
-
return bytes;
|
|
600
|
-
}
|
|
601
|
-
function formatBytes(bytes) {
|
|
602
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
603
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
604
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
605
|
-
}
|
|
606
|
-
//#endregion
|
|
607
350
|
//#region src/tui/components.tsx
|
|
608
351
|
/**
|
|
609
352
|
* Memoized so a flush that mutates only the trailing event doesn't force the
|
|
@@ -968,15 +711,29 @@ function StatusSpinner({ color }) {
|
|
|
968
711
|
});
|
|
969
712
|
}
|
|
970
713
|
/**
|
|
971
|
-
*
|
|
972
|
-
*
|
|
973
|
-
*
|
|
974
|
-
*
|
|
975
|
-
*
|
|
714
|
+
* Scrollbar thumb sizing floors. Two-layered so the thumb stays useful
|
|
715
|
+
* across both tiny and tall terminal heights:
|
|
716
|
+
*
|
|
717
|
+
* - {@link MIN_THUMB_RATIO} — soft floor as a fraction of the track.
|
|
718
|
+
* `0.2` means "always at least 20% of the visible scroll area".
|
|
719
|
+
* This is what keeps a medium-length transcript from collapsing
|
|
720
|
+
* to a 2-cell pill the moment content modestly exceeds the
|
|
721
|
+
* viewport — only a genuinely long conversation (where the
|
|
722
|
+
* natural ratio is < 20%) ever hits this floor.
|
|
723
|
+
* - {@link MIN_THUMB_HALF_BLOCKS} — hard floor in half-block units
|
|
724
|
+
* (OpenTUI renders the thumb at 2 half-blocks per character cell;
|
|
725
|
+
* `4` = 2 character cells). Catches the degenerate case of a tiny
|
|
726
|
+
* viewport where 20% rounds to zero, keeping the thumb grabbable
|
|
727
|
+
* even on a 5-row chat pane.
|
|
728
|
+
*
|
|
729
|
+
* Effective floor: `max(MIN_THUMB_HALF_BLOCKS, floor(trackSize * MIN_THUMB_RATIO))`.
|
|
976
730
|
*/
|
|
977
|
-
const MIN_THUMB_HALF_BLOCKS =
|
|
731
|
+
const MIN_THUMB_HALF_BLOCKS = 4;
|
|
732
|
+
const MIN_THUMB_RATIO = .2;
|
|
978
733
|
function Transcript({ events, settings, selectedTurnId = null }) {
|
|
734
|
+
const COLOR = useColors();
|
|
979
735
|
const items = useMemo(() => partitionTranscript(events, settings), [events, settings]);
|
|
736
|
+
const ownership = useMemo(() => turnSelectionOwnership(events), [events]);
|
|
980
737
|
const scrollboxRef = useRef(null);
|
|
981
738
|
useEffect(() => {
|
|
982
739
|
const scrollbox = scrollboxRef.current;
|
|
@@ -987,7 +744,8 @@ function Transcript({ events, settings, selectedTurnId = null }) {
|
|
|
987
744
|
slider.getVirtualThumbSize = function() {
|
|
988
745
|
const upstream = original();
|
|
989
746
|
const virtualTrackSize = slider.height * 2;
|
|
990
|
-
|
|
747
|
+
const softFloor = Math.floor(virtualTrackSize * MIN_THUMB_RATIO);
|
|
748
|
+
return Math.min(virtualTrackSize, Math.max(Math.max(MIN_THUMB_HALF_BLOCKS, softFloor), upstream));
|
|
991
749
|
};
|
|
992
750
|
return () => {
|
|
993
751
|
slider.getVirtualThumbSize = original;
|
|
@@ -999,7 +757,8 @@ function Transcript({ events, settings, selectedTurnId = null }) {
|
|
|
999
757
|
const scrollbox = scrollboxRef.current;
|
|
1000
758
|
if (!scrollbox) return;
|
|
1001
759
|
const handle = requestAnimationFrame(() => {
|
|
1002
|
-
|
|
760
|
+
const ownsLast = anchors.lastTurnId !== void 0 && ownership.get(anchors.lastTurnId) === selectedTurnId;
|
|
761
|
+
if (selectedTurnId === anchors.lastTurnId || ownsLast) {
|
|
1003
762
|
scrollbox.scrollTop = scrollbox.scrollHeight;
|
|
1004
763
|
return;
|
|
1005
764
|
}
|
|
@@ -1007,7 +766,11 @@ function Transcript({ events, settings, selectedTurnId = null }) {
|
|
|
1007
766
|
if (id) scrollbox.scrollChildIntoView(id);
|
|
1008
767
|
});
|
|
1009
768
|
return () => cancelAnimationFrame(handle);
|
|
1010
|
-
}, [
|
|
769
|
+
}, [
|
|
770
|
+
selectedTurnId,
|
|
771
|
+
anchors,
|
|
772
|
+
ownership
|
|
773
|
+
]);
|
|
1011
774
|
if (items.length === 0) return /* @__PURE__ */ jsx(EmptyState, {});
|
|
1012
775
|
return /* @__PURE__ */ jsx("scrollbox", {
|
|
1013
776
|
ref: scrollboxRef,
|
|
@@ -1019,10 +782,17 @@ function Transcript({ events, settings, selectedTurnId = null }) {
|
|
|
1019
782
|
},
|
|
1020
783
|
stickyScroll: true,
|
|
1021
784
|
stickyStart: "bottom",
|
|
785
|
+
verticalScrollbarOptions: {
|
|
786
|
+
width: 1,
|
|
787
|
+
trackOptions: {
|
|
788
|
+
backgroundColor: "transparent",
|
|
789
|
+
foregroundColor: COLOR.mute
|
|
790
|
+
}
|
|
791
|
+
},
|
|
1022
792
|
children: items.map((item, i) => item.kind === "event" ? /* @__PURE__ */ jsx(EventLine, {
|
|
1023
793
|
event: item.event,
|
|
1024
794
|
previous: item.previous,
|
|
1025
|
-
selected:
|
|
795
|
+
selected: isTurnHighlighted(item.event, selectedTurnId, ownership),
|
|
1026
796
|
anchorId: anchors.ids[i][0]
|
|
1027
797
|
}, i) : /* @__PURE__ */ jsx(SubagentBlock, {
|
|
1028
798
|
events: item.events,
|
|
@@ -1068,51 +838,6 @@ function computeTurnAnchors(items) {
|
|
|
1068
838
|
};
|
|
1069
839
|
}
|
|
1070
840
|
/**
|
|
1071
|
-
* Per-event visibility — filters honor user toggles and the
|
|
1072
|
-
* `hideSubagentOutput` setting. When subagent output is hidden:
|
|
1073
|
-
* - Child-agent events are filtered down to the `spawn-start` /
|
|
1074
|
-
* `spawn-end` markers so the user still sees "🌱 working… 🌳 done".
|
|
1075
|
-
* - The parent's `tool-result` for `spawn` is hidden too. Its body
|
|
1076
|
-
* duplicates `spawn-end`'s stats line *and* the parent's next markdown
|
|
1077
|
-
* turn ("Here's what the sub-agent found: …"). Showing it again
|
|
1078
|
-
* produced an extra `┃ [sub-agent child-1] Completed …` block that
|
|
1079
|
-
* the user just wanted gone.
|
|
1080
|
-
*
|
|
1081
|
-
* Exported so the visibility matrix can be unit-tested without rendering.
|
|
1082
|
-
*/
|
|
1083
|
-
/** Tools whose `tool-result` event is suppressed when `showEditDiffs` is on. */
|
|
1084
|
-
const EDIT_TOOL_NAMES = new Set([
|
|
1085
|
-
"edit",
|
|
1086
|
-
"multi_edit",
|
|
1087
|
-
"write_file"
|
|
1088
|
-
]);
|
|
1089
|
-
function isVisible(event, settings) {
|
|
1090
|
-
if (settings.hideSubagentOutput) {
|
|
1091
|
-
if (isChild(event)) return event.kind === "spawn-start" || event.kind === "spawn-end";
|
|
1092
|
-
if (event.kind === "tool-result" && event.tool === "spawn") return false;
|
|
1093
|
-
}
|
|
1094
|
-
if (settings.showEditDiffs && event.kind === "tool-result" && event.tool && EDIT_TOOL_NAMES.has(event.tool) && !isEditErrorResult(event.text)) return false;
|
|
1095
|
-
switch (event.kind) {
|
|
1096
|
-
case "thinking": return settings.showThinking;
|
|
1097
|
-
case "tool": return settings.toolCallDisplay !== "hidden";
|
|
1098
|
-
case "tool-result": return settings.showToolResults;
|
|
1099
|
-
default: return true;
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
/**
|
|
1103
|
-
* Recognize a tool-result body as a failure. The three edit tools all
|
|
1104
|
-
* return short, deterministic error prefixes on the failure path:
|
|
1105
|
-
* - `edit` → "Edit error: …"
|
|
1106
|
-
* - `multi_edit` → "multi_edit error: …"
|
|
1107
|
-
* - `write_file` → succeeds on permission errors only via exception →
|
|
1108
|
-
* the loop wraps as "Tool failed: …" so we match that prefix too.
|
|
1109
|
-
*
|
|
1110
|
-
* Exported for unit-testability of the visibility matrix.
|
|
1111
|
-
*/
|
|
1112
|
-
function isEditErrorResult(text) {
|
|
1113
|
-
return text.startsWith("Edit error:") || text.startsWith("multi_edit error:") || text.startsWith("Tool failed:");
|
|
1114
|
-
}
|
|
1115
|
-
/**
|
|
1116
841
|
* Walk the visible-event list once and group consecutive child events
|
|
1117
842
|
* (`depth > 0`) into runs so we can wrap each run in a single bordered
|
|
1118
843
|
* subagent box.
|
|
@@ -1238,55 +963,6 @@ function rowStyle(paddingLeft) {
|
|
|
1238
963
|
alignSelf: "stretch"
|
|
1239
964
|
};
|
|
1240
965
|
}
|
|
1241
|
-
/**
|
|
1242
|
-
* Default top-margin per kind. Spacing intent:
|
|
1243
|
-
* - `info` / `markdown` / `tool` / `error` / `spawn-start` open a new block
|
|
1244
|
-
* so they each get one row of breathing room above.
|
|
1245
|
-
* - `thinking` / `tool-result` / `spawn-end` continue the previous block
|
|
1246
|
-
* and stay flush.
|
|
1247
|
-
*
|
|
1248
|
-
* Context-aware overrides live in `marginTopFor` — e.g. consecutive tool
|
|
1249
|
-
* round-trips collapse to a tight list regardless of whether outputs are shown.
|
|
1250
|
-
*/
|
|
1251
|
-
const MARGIN_TOP = {
|
|
1252
|
-
"separator": 0,
|
|
1253
|
-
"user-prompt": 1,
|
|
1254
|
-
"info": 1,
|
|
1255
|
-
"thinking": 0,
|
|
1256
|
-
"tool": 1,
|
|
1257
|
-
"tool-result": 0,
|
|
1258
|
-
"error": 1,
|
|
1259
|
-
"markdown": 1,
|
|
1260
|
-
"spawn-start": 1,
|
|
1261
|
-
"spawn-end": 0,
|
|
1262
|
-
"compact-summary": 1
|
|
1263
|
-
};
|
|
1264
|
-
const TOOL_KINDS = new Set(["tool", "tool-result"]);
|
|
1265
|
-
/**
|
|
1266
|
-
* Resolve the top margin for an event given the one rendered just before it.
|
|
1267
|
-
*
|
|
1268
|
-
* Context-aware rules:
|
|
1269
|
-
*
|
|
1270
|
-
* - A `tool` / `tool-result` event right after another `tool` / `tool-result`
|
|
1271
|
-
* collapses to a tight list — call→result pairs and back-to-back calls
|
|
1272
|
-
* read as one logical block.
|
|
1273
|
-
* - A parent-level event (`depth === 0`) right after a subagent event
|
|
1274
|
-
* (`depth > 0`) collapses too. The subagent's `🌳` end marker (and, in
|
|
1275
|
-
* show mode, the subagent box's bottom border) already provides the
|
|
1276
|
-
* separation; adding the event's default `marginTop` on top would
|
|
1277
|
-
* produce the visible "line jump" between a subagent's outcome and the
|
|
1278
|
-
* parent's follow-up. Either form of marker is enough — we don't want
|
|
1279
|
-
* both.
|
|
1280
|
-
*
|
|
1281
|
-
* Exported so the spacing matrix can be unit-tested without rendering.
|
|
1282
|
-
*/
|
|
1283
|
-
function marginTopFor(event, previous) {
|
|
1284
|
-
if (TOOL_KINDS.has(event.kind) && previous && TOOL_KINDS.has(previous.kind)) return 0;
|
|
1285
|
-
const eventDepth = event.depth ?? 0;
|
|
1286
|
-
const previousDepth = previous?.depth ?? 0;
|
|
1287
|
-
if (eventDepth === 0 && previousDepth > 0) return 0;
|
|
1288
|
-
return MARGIN_TOP[event.kind] ?? 0;
|
|
1289
|
-
}
|
|
1290
966
|
function EventLineImpl({ event, depthOffset = 0 }) {
|
|
1291
967
|
const COLOR = useColors();
|
|
1292
968
|
const { settings } = useSettings();
|
|
@@ -6487,6 +6163,11 @@ function AppShell() {
|
|
|
6487
6163
|
useEffect(() => {
|
|
6488
6164
|
safeModeEnabledRef.current = settings.safeMode;
|
|
6489
6165
|
}, [settings.safeMode]);
|
|
6166
|
+
useEffect(() => {
|
|
6167
|
+
const fps = clampFps(settings.targetFps);
|
|
6168
|
+
if (renderer.targetFps !== fps) renderer.targetFps = fps;
|
|
6169
|
+
if (renderer.maxFps !== fps) renderer.maxFps = fps;
|
|
6170
|
+
}, [renderer, settings.targetFps]);
|
|
6490
6171
|
const persistToolResultsRef = useRef(settings.persistToolResults);
|
|
6491
6172
|
useEffect(() => {
|
|
6492
6173
|
persistToolResultsRef.current = settings.persistToolResults;
|
|
@@ -7586,7 +7267,7 @@ function AppShell() {
|
|
|
7586
7267
|
})();
|
|
7587
7268
|
}, [modal, config.paths.userDir]);
|
|
7588
7269
|
const hasMultipleAgents = useMemo(() => Object.keys(agentRegistry).length > 1, [agentRegistry]);
|
|
7589
|
-
const turnIds = useMemo(() => selectableTurnIds(events), [events]);
|
|
7270
|
+
const turnIds = useMemo(() => selectableTurnIds(events, settings), [events, settings]);
|
|
7590
7271
|
/** Drop the selection if its turn disappeared (session swap, history reset). */
|
|
7591
7272
|
useEffect(() => {
|
|
7592
7273
|
if (selectedTurnId && !turnIds.includes(selectedTurnId)) setSelectedTurnId(null);
|
|
@@ -8992,6 +8673,13 @@ let runTuiInvoked = false;
|
|
|
8992
8673
|
* Env-var overrides (handy when launching from CI or restricted shells):
|
|
8993
8674
|
* - `ZIDANE_STORAGE_DIR` — sets `storageDir`
|
|
8994
8675
|
* - `ZIDANE_PREFIX` — sets `prefix`
|
|
8676
|
+
* - `ZIDANE_DEBUG` — enables `gatherStats` on the renderer and dumps the
|
|
8677
|
+
* final fps / frametime distribution to stderr on exit. Useful when
|
|
8678
|
+
* tuning the renderer fps setting or comparing terminal emulators.
|
|
8679
|
+
*
|
|
8680
|
+
* Renderer fps lives in Settings (`targetFps`, cycle `30 / 60 / 120`);
|
|
8681
|
+
* the on-disk value seeds the renderer at boot and `AppShell` mirrors
|
|
8682
|
+
* subsequent flips onto the live renderer.
|
|
8995
8683
|
*
|
|
8996
8684
|
* Hosts building on top of `zidane/tui` typically want their own env vars
|
|
8997
8685
|
* (e.g. `MYAPP_STORAGE_DIR`); read them in your launch script and forward
|
|
@@ -9025,15 +8713,27 @@ async function runTui(options = {}) {
|
|
|
9025
8713
|
const exited = new Promise((resolve) => {
|
|
9026
8714
|
done = resolve;
|
|
9027
8715
|
});
|
|
9028
|
-
|
|
8716
|
+
const bootFps = clampFps(config.initialSettings.targetFps ?? DEFAULT_SETTINGS.targetFps);
|
|
8717
|
+
const gatherStats = !!process.env.ZIDANE_DEBUG;
|
|
8718
|
+
const renderer = await createCliRenderer({
|
|
9029
8719
|
exitOnCtrlC: true,
|
|
9030
8720
|
onDestroy: () => done(),
|
|
9031
|
-
debounceDelay: 0
|
|
9032
|
-
|
|
8721
|
+
debounceDelay: 0,
|
|
8722
|
+
targetFps: bootFps,
|
|
8723
|
+
maxFps: bootFps,
|
|
8724
|
+
gatherStats
|
|
8725
|
+
});
|
|
8726
|
+
createRoot(renderer).render(/* @__PURE__ */ jsx(App, { config }));
|
|
9033
8727
|
await exited;
|
|
8728
|
+
if (gatherStats) try {
|
|
8729
|
+
const s = renderer.getStats();
|
|
8730
|
+
process.stderr.write(`[zidane/tui] render stats: fps=${s.fps.toFixed(1)} avgFrame=${s.averageFrameTime.toFixed(2)}ms min=${s.minFrameTime.toFixed(2)}ms max=${s.maxFrameTime.toFixed(2)}ms frames=${s.frameCount}\n`);
|
|
8731
|
+
} catch (err) {
|
|
8732
|
+
process.stderr.write(`[zidane/tui] render stats unavailable: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
8733
|
+
}
|
|
9034
8734
|
process.exit(0);
|
|
9035
8735
|
}
|
|
9036
8736
|
//#endregion
|
|
9037
|
-
export { AgentPickerModal, App, AuthScreen, ChatScreen, CompletionPopup, EffortPickerModal, Footer, InteractionBlock, McpsSettingsModal, Modal, ModalRoot, ModelPickerModal, SessionDetailsModal, SessionsScreen, SettingsModal, SkillsSettingsModal, Spinner, StatusSpinner, TitleOverlay, ToggleListModal, Transcript, TurnDetailsModal, accentColor, buildMdStyle, hintsLength, isVisible, marginTopFor, onInputSubmit, renderHintSpans, runTui, splitPromptSegments, useMdStyle, useModal, useModalAwareFocus };
|
|
8737
|
+
export { AgentPickerModal, App, AuthScreen, ChatScreen, CompletionPopup, EffortPickerModal, Footer, InteractionBlock, McpsSettingsModal, Modal, ModalRoot, ModelPickerModal, SessionDetailsModal, SessionsScreen, SettingsModal, SkillsSettingsModal, Spinner, StatusSpinner, TOOL_DISPLAY, TitleOverlay, ToggleListModal, Transcript, TurnDetailsModal, accentColor, buildMdStyle, displayNameFor, formatToolCall, hintsLength, isEditErrorResult, isTurnHighlighted, isVisible, marginTopFor, onInputSubmit, renderHintSpans, runTui, selectableTurnIds, splitPromptSegments, turnSelectionOwnership, useMdStyle, useModal, useModalAwareFocus };
|
|
9038
8738
|
|
|
9039
8739
|
//# sourceMappingURL=tui.js.map
|