zidane 5.1.13 → 5.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/{agent-skiQGYs2.d.ts → agent-a-mteIEP.d.ts} +19 -4
  2. package/dist/agent-a-mteIEP.d.ts.map +1 -0
  3. package/dist/chat.d.ts +172 -7
  4. package/dist/chat.d.ts.map +1 -1
  5. package/dist/chat.js +2 -2
  6. package/dist/{errors-D1lhd6mX.js → errors-COmsomd5.js} +13 -3
  7. package/dist/{errors-D1lhd6mX.js.map → errors-COmsomd5.js.map} +1 -1
  8. package/dist/{index-YM7SipFz.d.ts → index-CsdPEjlu.d.ts} +2 -2
  9. package/dist/{index-YM7SipFz.d.ts.map → index-CsdPEjlu.d.ts.map} +1 -1
  10. package/dist/{index-CjPh6CRE.d.ts → index-D5gCRi42.d.ts} +2 -2
  11. package/dist/{index-CjPh6CRE.d.ts.map → index-D5gCRi42.d.ts.map} +1 -1
  12. package/dist/index.d.ts +3 -3
  13. package/dist/index.js +10 -10
  14. package/dist/{interpolate-BI6ovwag.js → interpolate-BhmHKD6x.js} +3 -4
  15. package/dist/{interpolate-BI6ovwag.js.map → interpolate-BhmHKD6x.js.map} +1 -1
  16. package/dist/{login-Cc6Q-Fpu.js → login-DrnEZZVv.js} +4 -4
  17. package/dist/{login-Cc6Q-Fpu.js.map → login-DrnEZZVv.js.map} +1 -1
  18. package/dist/{mcp-CUt-N8zn.js → mcp-B1psg7jf.js} +4 -4
  19. package/dist/mcp-B1psg7jf.js.map +1 -0
  20. package/dist/mcp.d.ts +1 -1
  21. package/dist/mcp.js +1 -1
  22. package/dist/{messages-CIkO_aCH.js → messages-DsbMYNmt.js} +26 -34
  23. package/dist/messages-DsbMYNmt.js.map +1 -0
  24. package/dist/{presets-Ce79MK4J.js → presets-H8UYtz3b.js} +2 -2
  25. package/dist/{presets-Ce79MK4J.js.map → presets-H8UYtz3b.js.map} +1 -1
  26. package/dist/presets.d.ts +2 -2
  27. package/dist/presets.js +1 -1
  28. package/dist/{providers-CvriFHFU.js → providers-v1Rn2rqG.js} +40 -14
  29. package/dist/providers-v1Rn2rqG.js.map +1 -0
  30. package/dist/providers.d.ts +1 -1
  31. package/dist/providers.js +2 -2
  32. package/dist/session/sqlite.d.ts +1 -1
  33. package/dist/session/sqlite.d.ts.map +1 -1
  34. package/dist/session/sqlite.js +2 -2
  35. package/dist/session/sqlite.js.map +1 -1
  36. package/dist/{session-DtLD1Sl1.js → session-DOJgRXvF.js} +2 -2
  37. package/dist/{session-DtLD1Sl1.js.map → session-DOJgRXvF.js.map} +1 -1
  38. package/dist/session.d.ts +1 -1
  39. package/dist/session.js +2 -2
  40. package/dist/skills.d.ts +2 -2
  41. package/dist/skills.js +1 -1
  42. package/dist/{tools-BG2wMa3X.js → tools-Duptt9yy.js} +16 -20
  43. package/dist/tools-Duptt9yy.js.map +1 -0
  44. package/dist/tools.d.ts +2 -2
  45. package/dist/tools.js +1 -1
  46. package/dist/{tool-formatters-0aOMYbH-.d.ts → transcript-anchors-YFom211q.d.ts} +242 -97
  47. package/dist/transcript-anchors-YFom211q.d.ts.map +1 -0
  48. package/dist/tui.d.ts +55 -39
  49. package/dist/tui.d.ts.map +1 -1
  50. package/dist/tui.js +347 -326
  51. package/dist/tui.js.map +1 -1
  52. package/dist/{turn-operations-CDmQ2h-T.js → turn-operations-DNKpDGQi.js} +600 -158
  53. package/dist/turn-operations-DNKpDGQi.js.map +1 -0
  54. package/dist/{types-Bx_F8jet.js → types-IcokUOyC.js} +11 -4
  55. package/dist/{types-Bx_F8jet.js.map → types-IcokUOyC.js.map} +1 -1
  56. package/dist/types.d.ts +2 -2
  57. package/dist/types.js +2 -2
  58. package/package.json +1 -1
  59. package/dist/agent-skiQGYs2.d.ts.map +0 -1
  60. package/dist/mcp-CUt-N8zn.js.map +0 -1
  61. package/dist/messages-CIkO_aCH.js.map +0 -1
  62. package/dist/providers-CvriFHFU.js.map +0 -1
  63. package/dist/tool-formatters-0aOMYbH-.d.ts.map +0 -1
  64. package/dist/tools-BG2wMa3X.js.map +0 -1
  65. package/dist/turn-operations-CDmQ2h-T.js.map +0 -1
package/dist/tui.js CHANGED
@@ -1,18 +1,19 @@
1
- import { S as resolvePersistDir, b as cleanupPersistedSession, d as createAgent } from "./tools-BG2wMa3X.js";
2
- import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-CUt-N8zn.js";
3
- import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-Cc6Q-Fpu.js";
1
+ import { S as resolvePersistDir, b as cleanupPersistedSession, d as createAgent } from "./tools-Duptt9yy.js";
2
+ import { o as errorMessage } from "./errors-COmsomd5.js";
3
+ import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-B1psg7jf.js";
4
+ import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-DrnEZZVv.js";
4
5
  import { n as formatTokenUsage } from "./stats-DgOvY7wd.js";
5
- import { n as loadSession, t as createSession } from "./session-DtLD1Sl1.js";
6
+ import { n as loadSession, t as createSession } from "./session-DOJgRXvF.js";
6
7
  import { createTuiStore } from "./session/sqlite.js";
7
- import { $ as getMcpAuthStatus, $t as toolCallPreview, A as isOnSafelist, B as filterModelCatalog, Bn as setProviderCredential, Bt as eventsFromTurns, C as writeSessionExport, Cn as uniqueSkillNamesFromReferences, 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 getContextWindow, L as runOAuthLogin, Lt as resolveConfig, Mn as tryOpenBrowser, Nn as shouldAutoCompact, O as addToSafelist, Ot as resolveChipColor, P as suggestSafelistEntry, Pn as detectAuth, Q as useMcpAuthState, R as supportsOAuth, Sn as createSkillsCompletionProvider, St as SETTINGS_TOGGLES, T as useSafeModeActions, Tn as createFilesCompletionProvider, Tt as useSettings, Ut as isVisible, V as indexOfEntry, Vt as isEditErrorResult, W as discoverProjectMcps, Wt as lastContextSizeFromTurns, X as McpAuthProvider, Xn as modelSupportsReasoning, Xt as stripSpawnTokensLine, Yt as selectableTurnIds, Z as useMcpAuthDispatch, Zt as sumRunCosts, _ as useStreamBuffer, _t as shortId, a as TOOL_DISPLAY, at as createInteractionTools, b as discoverProjectSkills, br as buildPlanSystem, bt as DEFAULT_SETTINGS, c as ThemeProvider, ct as pendingInteractionsFromTurns, d as useSurfaces, dt as useInteractionsQueue, en as toolResultText, er as piIdOf, g as turnContextSize, gn as matchesBinding, gt as fmtTokens, h as finalizeStreamingMarkdownForOwner, ht as compactPath, i as turnAsText, it as buildResumedToolResultsTurn, jn as useCompletion, k as getSafelist, kt as resolveTheme, l as useColors, m as finalizeStreamingMarkdown, mn as ensureKeybindingsFile, mt as ageString, n as deleteTurnSafely, nn as buildContextualDiff, nt as InteractionsProvider, o as displayNameFor, on as extractEditPayload, or as accentColor, p as useTheme, pt as generateSessionTitle, q as createFileMcpCredentialStore, qt as marginTopFor, r as truncateTurnsAt, rn as buildUnifiedDiff, s as formatToolCall, sn as filetypeFromPath, st as makeRequestInteraction, tn as turnSelectionOwnership, u as useSelectStyle, un as findGitRoot, ut as useInteractionsActions, v as buildSkillsConfig, vt as listProjectFiles, w as SafeModeProvider, wt as clampFps, xt as SETTINGS_CHOICES, y as defaultSkillScanPaths, yr as buildBuildSystem, yt as useEnabledToggleSet, z as buildModelCatalog, zt as deriveSessionTitle } from "./turn-operations-CDmQ2h-T.js";
8
+ import { $ as useMcpAuthState, $n as setProviderCredential, $t as lastContextSizeFromTurns, A as getSafelist, At as useSettings, B as buildModelCatalog, Bn as useCompletion, Bt as createDiscoverySlot, Cn as ensureKeybindingsFile, Ct as listProjectFiles, D as useSafeModeQueue, Dt as SETTINGS_TOGGLES, E as useSafeModeActions, Et as SETTINGS_CHOICES, F as suggestSafelistEntry, G as discoverProjectMcps, Gn as bootTick, Gt as useConfig, H as indexOfEntry, Hn as buildLinearRamp, Ht as useDiscovery, J as createFileMcpCredentialStore, Jt as deriveSessionTitle, Kn as shouldAutoCompact, Kt as resolveConfig, L as splitPromptSegments, Mn as uniqueSkillNamesFromReferences, Mr as buildBuildSystem, Nr as buildPlanSystem, Nt as resolveChipColor, Ot as SettingsProvider, Pn as createFilesCompletionProvider, Pt as resolveTheme, Q as useMcpAuthDispatch, Qt as isVisible, R as runOAuthLogin, St as shortId, T as SafeModeProvider, Tn as matchesBinding, Tt as DEFAULT_SETTINGS, U as buildMcpServers, Un as tryOpenBrowser, Ut as useDiscoveryOptional, V as filterModelCatalog, Vn as blendHsl, Vt as DiscoveryProvider, Wt as ConfigProvider, Xt as isEditErrorResult, Yt as eventsFromTurns, Z as McpAuthProvider, Zt as isTurnHighlighted, _ as turnContextSize, a as computeTurnAnchors, an as stripSpawnTokensLine, b as defaultSkillScanPaths, bt as compactPath, c as formatToolCall, cn as toolCallPreview, d as useSelectStyle, dn as buildContextualDiff, en as listSessionMeta, et as getMcpAuthStatus, f as useSurfaces, fn as buildUnifiedDiff, ft as useInteractionsActions, g as finalizeStreamingMarkdownForOwner, gn as filetypeFromPath, gt as truncateTrailing, h as finalizeStreamingMarkdown, hn as extractEditPayload, ht as hintsLength, i as turnAsText, in as selectableTurnIds, it as InteractionsProvider, j as isOnSafelist, jn as createSkillsCompletionProvider, k as addToSafelist, kt as clampFps, l as ThemeProvider, ln as toolResultText, lr as modelSupportsReasoning, lt as makeRequestInteraction, m as useTheme, mt as clipHintsToWidth, n as deleteTurnSafely, nn as marginTopFor, nt as splitMarkdownCodeBlocks, o as TOOL_DISPLAY, on as sumRunCosts, ot as buildResumedToolResultsTurn, pr as piIdOf, pt as useInteractionsQueue, qn as detectAuth, r as truncateTurnsAt, s as displayNameFor, sr as getContextWindow, st as createInteractionTools, u as useColors, un as turnSelectionOwnership, ut as pendingInteractionsFromTurns, v as useStreamBuffer, vt as generateSessionTitle, w as writeSessionExport, wt as useEnabledToggleSet, x as discoverProjectSkills, xt as fmtTokens, y as buildSkillsConfig, yn as findGitRoot, yr as accentColor, yt as ageString, z as supportsOAuth } from "./turn-operations-DNKpDGQi.js";
8
9
  import { spawn } from "node:child_process";
9
10
  import { Buffer } from "node:buffer";
10
11
  import * as fs from "node:fs";
11
12
  import { homedir } from "node:os";
12
13
  import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
14
+ import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
13
15
  import { BoxRenderable, CodeRenderable, RGBA, SyntaxStyle, TextRenderable, addDefaultParsers, createCliRenderer, decodePasteBytes, defaultTextareaKeyBindings, getTreeSitterClient, stripAnsiSequences } from "@opentui/core";
14
16
  import { createRoot, useKeyboard, useRenderer, useSelectionHandler, useTerminalDimensions } from "@opentui/react";
15
- import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
16
17
  //#region src/tui/modal.tsx
17
18
  const ModalContext = createContext(null);
18
19
  function ModalRoot({ children }) {
@@ -318,95 +319,6 @@ function writeToClipboard(text) {
318
319
  return osc || helper;
319
320
  }
320
321
  //#endregion
321
- //#region src/tui/color-gradient.ts
322
- /** Parse `#rrggbb` (case-insensitive) into `[r, g, b]` 0–255 integers. */
323
- function parseHex(hex) {
324
- const h = hex.replace("#", "");
325
- return [
326
- Number.parseInt(h.slice(0, 2), 16),
327
- Number.parseInt(h.slice(2, 4), 16),
328
- Number.parseInt(h.slice(4, 6), 16)
329
- ];
330
- }
331
- /** Convert sRGB 0–255 → HSL 0–1. */
332
- function rgbToHsl(r, g, b) {
333
- r /= 255;
334
- g /= 255;
335
- b /= 255;
336
- const max = Math.max(r, g, b);
337
- const min = Math.min(r, g, b);
338
- const l = (max + min) / 2;
339
- if (max === min) return [
340
- 0,
341
- 0,
342
- l
343
- ];
344
- const d = max - min;
345
- const s = l > .5 ? d / (2 - max - min) : d / (max + min);
346
- let h;
347
- if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
348
- else if (max === g) h = (b - r) / d + 2;
349
- else h = (r - g) / d + 4;
350
- return [
351
- h / 6,
352
- s,
353
- l
354
- ];
355
- }
356
- /** Convert HSL 0–1 → sRGB 0–255. Standard piecewise formula. */
357
- function hslToRgb(h, s, l) {
358
- if (s === 0) return [
359
- l * 255,
360
- l * 255,
361
- l * 255
362
- ];
363
- const hue2rgb = (p, q, t) => {
364
- if (t < 0) t += 1;
365
- if (t > 1) t -= 1;
366
- if (t < 1 / 6) return p + (q - p) * 6 * t;
367
- if (t < 1 / 2) return q;
368
- if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
369
- return p;
370
- };
371
- const q = l < .5 ? l * (1 + s) : l + s - l * s;
372
- const p = 2 * l - q;
373
- return [
374
- hue2rgb(p, q, h + 1 / 3) * 255,
375
- hue2rgb(p, q, h) * 255,
376
- hue2rgb(p, q, h - 1 / 3) * 255
377
- ];
378
- }
379
- function toHex(rgb) {
380
- const pad = (v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, "0");
381
- return `#${pad(rgb[0])}${pad(rgb[1])}${pad(rgb[2])}`;
382
- }
383
- /**
384
- * Blend two hex colors in HSL space with shortest-path hue interpolation.
385
- * `t` ∈ [0, 1]; `t=0` returns `from`, `t=1` returns `to`.
386
- */
387
- function blendHsl(from, to, t) {
388
- const [r1, g1, b1] = parseHex(from);
389
- const [r2, g2, b2] = parseHex(to);
390
- const [h1, s1, l1] = rgbToHsl(r1, g1, b1);
391
- const [h2, s2, l2] = rgbToHsl(r2, g2, b2);
392
- let dh = h2 - h1;
393
- if (dh > .5) dh -= 1;
394
- else if (dh < -.5) dh += 1;
395
- return toHex(hslToRgb((h1 + dh * t + 1) % 1, s1 + (s2 - s1) * t, l1 + (l2 - l1) * t));
396
- }
397
- /**
398
- * Static gradient ramp of length `n` going from `from` (index 0) to
399
- * `to` (index n-1) in HSL space. For the cycling A→B→A→B ramp the
400
- * throbber uses, see `buildCycleRamp` in `crush-throbber.tsx`.
401
- */
402
- function buildLinearRamp(from, to, n) {
403
- if (n <= 0) return [];
404
- if (n === 1) return [blendHsl(from, to, .5)];
405
- const ramp = [];
406
- for (let i = 0; i < n; i++) ramp.push(blendHsl(from, to, i / (n - 1)));
407
- return ramp;
408
- }
409
- //#endregion
410
322
  //#region src/tui/crush-throbber.tsx
411
323
  /** @jsxImportSource @opentui/react */
412
324
  const CRUSH_RUNES = "0123456789abcdefABCDEF~!@#$£€%^&*()+=_";
@@ -947,67 +859,6 @@ function metaSegmentsLength(meta) {
947
859
  if (typeof meta === "string") return meta.length;
948
860
  return meta.reduce((sum, seg) => sum + seg.text.length, 0);
949
861
  }
950
- /**
951
- * Truncate `text` to at most `max` characters, replacing the trailing
952
- * overflow with `…`. Edge cases:
953
- * - `max <= 0` → empty string (no room to render at all).
954
- * - `max === 1` → just the ellipsis glyph.
955
- * - `text.length <= max` → unchanged.
956
- *
957
- * Trailing-style truncation matches the natural read order of titles:
958
- * the prefix carries enough signal to identify the surface.
959
- *
960
- * Exported for unit-tests; consumers should normally lean on
961
- * {@link TitleOverlay} instead of calling this directly.
962
- */
963
- function truncateTrailing(text, max) {
964
- if (max <= 0) return "";
965
- if (text.length <= max) return text;
966
- if (max === 1) return "…";
967
- return `${text.slice(0, max - 1)}…`;
968
- }
969
- /**
970
- * Plain-text width estimate for a list of {@link Hint}s rendered via
971
- * `renderHintSpans` — `<key> <label> · <key> <label> · …`. Exported so
972
- * the prompt-box overlay (in `screens.tsx`) can run the same responsive
973
- * math as the bottom-bar footer when deciding whether trigger hints
974
- * fit. Pure / total.
975
- */
976
- function hintsLength(hints) {
977
- if (hints.length === 0) return 0;
978
- return hints.reduce((sum, h, i) => sum + hintLength(h) + (i > 0 ? 3 : 0), 0);
979
- }
980
- /** Plain-text width of a single hint as rendered by {@link renderHintSpans}. */
981
- function hintLength(h) {
982
- return h.key.length + 1 + h.label.length + (h.extra ? h.extra.key.length + 1 + h.extra.label.length : 0);
983
- }
984
- /** Stable empty list so callers can compare by reference. */
985
- const EMPTY_HINTS = Object.freeze([]);
986
- /**
987
- * Return the longest prefix of `hints` whose rendered width via
988
- * {@link renderHintSpans} fits within `budget`. Used to degrade hint
989
- * rows gracefully at narrow terminal widths instead of letting OpenTUI
990
- * wrap an absolutely-positioned `<text>` mid-segment (which paints the
991
- * overflow over the prompt box's border and looks like garbled glyphs).
992
- *
993
- * Prefix-only (no reordering, no last-hint priority) so the survivors
994
- * keep their authored order — the user's muscle memory for "leftmost
995
- * hint = primary action" stays intact as the terminal shrinks.
996
- */
997
- function clipHintsToWidth(hints, budget) {
998
- if (budget <= 0 || hints.length === 0) return EMPTY_HINTS;
999
- const out = [];
1000
- let used = 0;
1001
- for (let i = 0; i < hints.length; i++) {
1002
- const h = hints[i];
1003
- const cost = (i > 0 ? 3 : 0) + hintLength(h);
1004
- if (used + cost > budget) break;
1005
- out.push(h);
1006
- used += cost;
1007
- }
1008
- if (out.length === 0) return EMPTY_HINTS;
1009
- return out.length === hints.length ? hints : out;
1010
- }
1011
862
  function contextIndicatorLength(context) {
1012
863
  const ratio = context.max > 0 ? context.used / context.max : 0;
1013
864
  const pct = Math.round(ratio * 100);
@@ -1135,41 +986,6 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false }) {
1135
986
  });
1136
987
  }
1137
988
  /**
1138
- * Per-item anchor ids for auto-scroll. Walks `items` in render order and,
1139
- * for each event, returns either:
1140
- * - `'turn-anchor-<turnId>'` — the first event of this turn (the
1141
- * scrollbox's target).
1142
- * - `undefined` — later event of an already-tagged turn (or a synthetic
1143
- * event with no `turnId`).
1144
- *
1145
- * `ids[i]` is a tuple per item: length 1 for plain events, length N for
1146
- * subagent runs (one entry per inner event). `idByTurn` is the inverse
1147
- * lookup used by the scroll effect. `lastTurnId` is the most-recently-
1148
- * rendered turn — the scroll effect special-cases it to snap to bottom.
1149
- *
1150
- * Exported so the anchor-tagging matrix can be unit-tested without rendering.
1151
- */
1152
- function computeTurnAnchors(items) {
1153
- const idByTurn = /* @__PURE__ */ new Map();
1154
- let lastTurnId;
1155
- const tag = (turnId) => {
1156
- if (!turnId) return void 0;
1157
- lastTurnId = turnId;
1158
- if (idByTurn.has(turnId)) return void 0;
1159
- const id = `turn-anchor-${turnId}`;
1160
- idByTurn.set(turnId, id);
1161
- return id;
1162
- };
1163
- const ids = [];
1164
- for (const item of items) if (item.kind === "event") ids.push([tag(item.event.turnId)]);
1165
- else ids.push(item.events.map((e) => tag(e.turnId)));
1166
- return {
1167
- idByTurn,
1168
- ids,
1169
- lastTurnId
1170
- };
1171
- }
1172
- /**
1173
989
  * Walk the visible-event list once and group consecutive child events
1174
990
  * (`depth > 0`) into runs so we can wrap each run in a single bordered
1175
991
  * subagent box.
@@ -1589,7 +1405,7 @@ var CodeBlockWrapper = class extends BoxRenderable {
1589
1405
  flexDirection: "column",
1590
1406
  flexShrink: 0,
1591
1407
  alignSelf: "stretch",
1592
- backgroundColor: surfaces.background
1408
+ backgroundColor: surfaces.modal
1593
1409
  });
1594
1410
  this.body = body;
1595
1411
  this.bag = options.bag;
@@ -1597,11 +1413,12 @@ var CodeBlockWrapper = class extends BoxRenderable {
1597
1413
  body.marginTop = 0;
1598
1414
  body.marginBottom = 0;
1599
1415
  body.drawUnstyledText = false;
1416
+ body.bg = surfaces.modal;
1600
1417
  this.header = new BoxRenderable(ctx, {
1601
1418
  flexDirection: "row",
1602
1419
  height: 1,
1603
1420
  alignSelf: "stretch",
1604
- backgroundColor: surfaces.background
1421
+ backgroundColor: surfaces.modal
1605
1422
  });
1606
1423
  this.langLabel = new TextRenderable(ctx, {
1607
1424
  content: options.lang && options.lang.length > 0 ? options.lang : "code",
@@ -1610,7 +1427,7 @@ var CodeBlockWrapper = class extends BoxRenderable {
1610
1427
  });
1611
1428
  this.spacer = new BoxRenderable(ctx, {
1612
1429
  flexGrow: 1,
1613
- backgroundColor: surfaces.background
1430
+ backgroundColor: surfaces.modal
1614
1431
  });
1615
1432
  this.button = new TextRenderable(ctx, {
1616
1433
  content: "[copy]",
@@ -1653,9 +1470,10 @@ var CodeBlockWrapper = class extends BoxRenderable {
1653
1470
  * `[copy]`/`[copied]` button colour (which depends on `this.copied`).
1654
1471
  */
1655
1472
  applyTheme(colors, surfaces) {
1656
- this.backgroundColor = surfaces.background;
1657
- this.header.backgroundColor = surfaces.background;
1658
- this.spacer.backgroundColor = surfaces.background;
1473
+ this.backgroundColor = surfaces.modal;
1474
+ this.header.backgroundColor = surfaces.modal;
1475
+ this.spacer.backgroundColor = surfaces.modal;
1476
+ this.body.bg = surfaces.modal;
1659
1477
  this.langLabel.fg = colors.mute;
1660
1478
  this.button.fg = this.copied ? colors.accent : colors.warn;
1661
1479
  }
@@ -1684,11 +1502,23 @@ var CodeBlockWrapper = class extends BoxRenderable {
1684
1502
  set fg(value) {
1685
1503
  this.body.fg = value;
1686
1504
  }
1505
+ /**
1506
+ * Always reports the live elevated-panel paint: see the setter doc.
1507
+ */
1687
1508
  get bg() {
1688
1509
  return this.body.bg;
1689
1510
  }
1690
- set bg(value) {
1691
- this.body.bg = value;
1511
+ /**
1512
+ * Pinned to {@link ThemeSurfaces.modal} the elevated code-panel
1513
+ * surface. The markdown's `applyCodeBlockRenderable` writes `bg` on
1514
+ * every delta from whatever surface the parent `<markdown>` carries
1515
+ * (the prose body paint). Forwarding that through would flip the
1516
+ * code panel back to the prose surface on every keystroke and erase
1517
+ * the visual lift. Theme switches are funnelled through
1518
+ * {@link applyTheme} instead, which rewrites `body.bg` directly.
1519
+ */
1520
+ set bg(_value) {
1521
+ this.body.bg = this.bag.surfaces.modal;
1692
1522
  }
1693
1523
  get conceal() {
1694
1524
  return this.body.conceal;
@@ -2007,6 +1837,156 @@ function ToolCallBlock({ event, display, dim }) {
2007
1837
  });
2008
1838
  }
2009
1839
  //#endregion
1840
+ //#region src/tui/discovery-shell.tsx
1841
+ /**
1842
+ * SWR throttles. `files` is short so a long-open `@` popover picks up
1843
+ * new files within seconds; `skills` is long because SKILL.md changes
1844
+ * are rare and the walk is cheap enough that we already eager-load it.
1845
+ */
1846
+ const FILES_REFRESH_THROTTLE_MS = 3e3;
1847
+ const SKILLS_REFRESH_THROTTLE_MS = 3e4;
1848
+ function debugLog$1(...args) {
1849
+ if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/tui] ${args.map(errorMessage).join(" ")}\n`);
1850
+ }
1851
+ function DiscoveryShell({ children }) {
1852
+ const config = useConfig();
1853
+ const dispatchAuth = useMcpAuthDispatch();
1854
+ const dispatchAuthRef = useRef(dispatchAuth);
1855
+ dispatchAuthRef.current = dispatchAuth;
1856
+ const [projectDir] = useState(() => findGitRoot(process.cwd()) ?? process.cwd());
1857
+ const dataDir = config.paths.userDir;
1858
+ const mcpCredentialStore = useMemo(() => createFileMcpCredentialStore(dataDir), [dataDir]);
1859
+ const [skillsCatalog, setSkillsCatalog] = useState([]);
1860
+ const [mcpsCatalog, setMcpsCatalog] = useState([]);
1861
+ const [mcpsErrors, setMcpsErrors] = useState([]);
1862
+ const [filesCatalog, setFilesCatalog] = useState([]);
1863
+ const filesSlotRef = useRef(null);
1864
+ const skillsSlotRef = useRef(null);
1865
+ useEffect(() => {
1866
+ filesSlotRef.current?.abort();
1867
+ skillsSlotRef.current?.abort();
1868
+ setFilesCatalog([]);
1869
+ setSkillsCatalog([]);
1870
+ const filesSlot = createDiscoverySlot({
1871
+ throttleMs: FILES_REFRESH_THROTTLE_MS,
1872
+ walk: (signal) => listProjectFiles({
1873
+ cwd: projectDir,
1874
+ signal
1875
+ }),
1876
+ onLoad: (items) => {
1877
+ if (filesSlotRef.current === filesSlot) setFilesCatalog(items);
1878
+ },
1879
+ onError: (err, phase) => {
1880
+ debugLog$1(`listProjectFiles ${phase} failed`, err);
1881
+ }
1882
+ });
1883
+ const skillsSlot = createDiscoverySlot({
1884
+ throttleMs: SKILLS_REFRESH_THROTTLE_MS,
1885
+ walk: () => discoverProjectSkills({
1886
+ cwd: projectDir,
1887
+ prefix: config.prefix
1888
+ }),
1889
+ onLoad: (items) => {
1890
+ if (skillsSlotRef.current === skillsSlot) setSkillsCatalog(items);
1891
+ },
1892
+ onError: (err, phase) => {
1893
+ debugLog$1(`discoverProjectSkills ${phase} failed`, err);
1894
+ }
1895
+ });
1896
+ filesSlotRef.current = filesSlot;
1897
+ skillsSlotRef.current = skillsSlot;
1898
+ skillsSlot.ensure().then((skills) => bootTick(`discovery:skills (${skills.length})`));
1899
+ try {
1900
+ const { servers, errors } = discoverProjectMcps({
1901
+ cwd: projectDir,
1902
+ prefix: config.prefix
1903
+ });
1904
+ setMcpsCatalog(servers);
1905
+ setMcpsErrors(errors);
1906
+ bootTick(`discovery:mcps (${servers.length} servers, ${errors.length} parse errors)`);
1907
+ for (const entry of servers) {
1908
+ if (entry.config.auth !== "oauth") continue;
1909
+ const hasTokens = !!mcpCredentialStore.load(entry.config.name)?.tokens;
1910
+ dispatchAuthRef.current(hasTokens ? {
1911
+ type: "auth-success",
1912
+ name: entry.config.name
1913
+ } : {
1914
+ type: "auth-required",
1915
+ name: entry.config.name,
1916
+ reason: "no-tokens"
1917
+ });
1918
+ }
1919
+ } catch (err) {
1920
+ debugLog$1("discoverProjectMcps failed", err);
1921
+ }
1922
+ return () => {
1923
+ filesSlot.abort();
1924
+ skillsSlot.abort();
1925
+ };
1926
+ }, [
1927
+ projectDir,
1928
+ config.prefix,
1929
+ mcpCredentialStore
1930
+ ]);
1931
+ const ensureFiles = useCallback(() => filesSlotRef.current?.ensure() ?? Promise.resolve([]), []);
1932
+ const ensureSkills = useCallback(() => skillsSlotRef.current?.ensure() ?? Promise.resolve([]), []);
1933
+ const refreshFiles = useCallback(() => filesSlotRef.current?.refresh() ?? Promise.resolve(), []);
1934
+ const refreshSkills = useCallback(() => skillsSlotRef.current?.refresh() ?? Promise.resolve(), []);
1935
+ const refreshMcps = useCallback(() => {
1936
+ try {
1937
+ const { servers, errors } = discoverProjectMcps({
1938
+ cwd: projectDir,
1939
+ prefix: config.prefix
1940
+ });
1941
+ setMcpsCatalog(servers);
1942
+ setMcpsErrors(errors);
1943
+ for (const entry of servers) {
1944
+ if (entry.config.auth !== "oauth") continue;
1945
+ const hasTokens = !!mcpCredentialStore.load(entry.config.name)?.tokens;
1946
+ dispatchAuthRef.current(hasTokens ? {
1947
+ type: "auth-success",
1948
+ name: entry.config.name
1949
+ } : {
1950
+ type: "auth-required",
1951
+ name: entry.config.name,
1952
+ reason: "no-tokens"
1953
+ });
1954
+ }
1955
+ } catch (err) {
1956
+ debugLog$1("refreshMcps failed", err);
1957
+ }
1958
+ return Promise.resolve();
1959
+ }, [
1960
+ projectDir,
1961
+ config.prefix,
1962
+ mcpCredentialStore
1963
+ ]);
1964
+ return /* @__PURE__ */ jsx(DiscoveryProvider, {
1965
+ value: useMemo(() => ({
1966
+ skillsCatalog,
1967
+ mcpsCatalog,
1968
+ mcpsErrors,
1969
+ filesCatalog,
1970
+ refreshSkills,
1971
+ refreshMcps,
1972
+ refreshFiles,
1973
+ ensureSkills,
1974
+ ensureFiles
1975
+ }), [
1976
+ skillsCatalog,
1977
+ mcpsCatalog,
1978
+ mcpsErrors,
1979
+ filesCatalog,
1980
+ refreshSkills,
1981
+ refreshMcps,
1982
+ refreshFiles,
1983
+ ensureSkills,
1984
+ ensureFiles
1985
+ ]),
1986
+ children
1987
+ });
1988
+ }
1989
+ //#endregion
2010
1990
  //#region src/tui/effort-picker.tsx
2011
1991
  const BASE_LEVELS = [
2012
1992
  {
@@ -2665,7 +2645,7 @@ function FileEditApprovalModal({ request, onDecide }) {
2665
2645
  return [fs.readFileSync(targetPath, "utf8"), null];
2666
2646
  } catch (e) {
2667
2647
  if (e.code === "ENOENT") return ["", null];
2668
- return ["", e.message];
2648
+ return ["", errorMessage(e)];
2669
2649
  }
2670
2650
  }, [targetPath]);
2671
2651
  const payload = useMemo(() => {
@@ -3679,7 +3659,7 @@ function SetupWizard({ registry, dataDir, onConfigured, onCancel }) {
3679
3659
  if (descriptor.envKey) process.env[descriptor.envKey] = trimmed;
3680
3660
  onConfigured();
3681
3661
  } catch (err) {
3682
- setError(err instanceof Error ? err.message : String(err));
3662
+ setError(errorMessage(err));
3683
3663
  }
3684
3664
  }, [dataDir, onConfigured]);
3685
3665
  const onOAuthError = useCallback((msg) => {
@@ -3935,7 +3915,7 @@ function OAuthRunningStep({ descriptor, dataDir, onSuccess, onError }) {
3935
3915
  onSuccess();
3936
3916
  } catch (err) {
3937
3917
  if (cancelled) return;
3938
- onError(err instanceof Error ? err.message : String(err));
3918
+ onError(errorMessage(err));
3939
3919
  }
3940
3920
  })();
3941
3921
  return () => {
@@ -5229,7 +5209,7 @@ function SessionDetailsModal({ session, title, isCurrent, actions, keybindings }
5229
5209
  setExportStatus("success");
5230
5210
  } catch (err) {
5231
5211
  if (!mountedRef.current) return;
5232
- setExportError(err instanceof Error ? err.message : String(err));
5212
+ setExportError(errorMessage(err));
5233
5213
  setExportStatus("failed");
5234
5214
  }
5235
5215
  };
@@ -5246,7 +5226,7 @@ function SessionDetailsModal({ session, title, isCurrent, actions, keybindings }
5246
5226
  setTitleStatus("idle");
5247
5227
  } catch (err) {
5248
5228
  if (!mountedRef.current || ac.signal.aborted) return;
5249
- setTitleError(err instanceof Error ? err.message : String(err));
5229
+ setTitleError(errorMessage(err));
5250
5230
  setTitleStatus("failed");
5251
5231
  } finally {
5252
5232
  if (generationAbortRef.current === ac) generationAbortRef.current = null;
@@ -5265,7 +5245,7 @@ function SessionDetailsModal({ session, title, isCurrent, actions, keybindings }
5265
5245
  setCompactStatus("success");
5266
5246
  } catch (err) {
5267
5247
  if (!mountedRef.current || ac.signal.aborted) return;
5268
- setCompactError(err instanceof Error ? err.message : String(err));
5248
+ setCompactError(errorMessage(err));
5269
5249
  setCompactStatus("failed");
5270
5250
  } finally {
5271
5251
  if (compactAbortRef.current === ac) compactAbortRef.current = null;
@@ -5829,13 +5809,17 @@ function anchorIdFor(index) {
5829
5809
  }
5830
5810
  const COL_TITLE = " ";
5831
5811
  const SPACER_CHECKBOX_WIDTH = " ";
5832
- function SettingsModal({ skillsCatalog = [], mcpsCatalog = [], mcpsErrors, actions } = {}) {
5812
+ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCatalogProp, mcpsErrors: mcpsErrorsProp, actions } = {}) {
5833
5813
  const COLOR = useColors();
5834
5814
  const SURFACE = useSurfaces();
5835
5815
  const { settings, toggle: toggleBoolean, setSetting } = useSettings();
5836
5816
  const authState = useMcpAuthState();
5837
5817
  const inputRef = useRef(null);
5838
5818
  const scrollboxRef = useRef(null);
5819
+ const discovery = useDiscoveryOptional();
5820
+ const skillsCatalog = discovery?.skillsCatalog ?? skillsCatalogProp ?? [];
5821
+ const mcpsCatalog = discovery?.mcpsCatalog ?? mcpsCatalogProp ?? [];
5822
+ const mcpsErrors = discovery?.mcpsErrors ?? mcpsErrorsProp;
5839
5823
  const skillsToggle = useEnabledToggleSet({
5840
5824
  catalog: skillsCatalog,
5841
5825
  keyOf: (s) => s.name,
@@ -5975,6 +5959,13 @@ function SettingsModal({ skillsCatalog = [], mcpsCatalog = [], mcpsErrors, actio
5975
5959
  }
5976
5960
  if (key.name === "escape" && focusedMcpStatus.kind === "authorizing") actions?.onCancelLoginMcp?.(focusedMcp.config.name);
5977
5961
  }
5962
+ if (key.ctrl && key.name === "r") {
5963
+ if (activeTab === "skills" && actions?.onRefreshSkills) {
5964
+ actions.onRefreshSkills();
5965
+ return;
5966
+ }
5967
+ if (activeTab === "mcps" && actions?.onRefreshMcps) actions.onRefreshMcps();
5968
+ }
5978
5969
  });
5979
5970
  return /* @__PURE__ */ jsxs(Modal, {
5980
5971
  title: "settings",
@@ -6067,7 +6058,9 @@ function SettingsModal({ skillsCatalog = [], mcpsCatalog = [], mcpsErrors, actio
6067
6058
  children: /* @__PURE__ */ jsx(Hints, {
6068
6059
  activeTab,
6069
6060
  focusedMcp,
6070
- focusedMcpStatus
6061
+ focusedMcpStatus,
6062
+ canRefreshSkills: !!actions?.onRefreshSkills,
6063
+ canRefreshMcps: !!actions?.onRefreshMcps
6071
6064
  })
6072
6065
  })
6073
6066
  ]
@@ -6522,11 +6515,12 @@ function renderMcpErrors(errors, home, warnColor) {
6522
6515
  }, err.path))
6523
6516
  });
6524
6517
  }
6525
- function Hints({ activeTab, focusedMcp, focusedMcpStatus }) {
6518
+ function Hints({ activeTab, focusedMcp, focusedMcpStatus, canRefreshSkills, canRefreshMcps }) {
6526
6519
  const COLOR = useColors();
6527
6520
  const showLogin = activeTab === "mcps" && !!focusedMcp && !!focusedMcpStatus && canLogin$1(focusedMcp, focusedMcpStatus);
6528
6521
  const showLogout = activeTab === "mcps" && !!focusedMcpStatus && canLogout$1(focusedMcpStatus);
6529
6522
  const showCancel = activeTab === "mcps" && focusedMcpStatus?.kind === "authorizing";
6523
+ const showRefresh = activeTab === "skills" && canRefreshSkills || activeTab === "mcps" && canRefreshMcps;
6530
6524
  return /* @__PURE__ */ jsxs("text", {
6531
6525
  fg: COLOR.mute,
6532
6526
  children: [
@@ -6561,6 +6555,14 @@ function Hints({ activeTab, focusedMcp, focusedMcpStatus }) {
6561
6555
  }),
6562
6556
  " logout"
6563
6557
  ] }),
6558
+ showRefresh && /* @__PURE__ */ jsxs("span", { children: [
6559
+ " · ",
6560
+ /* @__PURE__ */ jsx("span", {
6561
+ fg: COLOR.warn,
6562
+ children: "ctrl+R"
6563
+ }),
6564
+ " refresh"
6565
+ ] }),
6564
6566
  showCancel ? /* @__PURE__ */ jsxs("span", { children: [
6565
6567
  " · ",
6566
6568
  /* @__PURE__ */ jsx("span", {
@@ -6976,16 +6978,20 @@ function ThemedShell() {
6976
6978
  const { settings } = useSettings();
6977
6979
  return /* @__PURE__ */ jsx(ThemeProvider, {
6978
6980
  theme: useMemo(() => resolveTheme(settings.theme), [settings.theme]),
6979
- children: /* @__PURE__ */ jsx(MdStyleProvider, { children: /* @__PURE__ */ jsx(ChipStyleProvider, { children: /* @__PURE__ */ jsx(SafeModeProvider, { children: /* @__PURE__ */ jsx(InteractionsProvider, { children: /* @__PURE__ */ jsx(McpAuthProvider, { children: /* @__PURE__ */ jsx(ModalRoot, { children: /* @__PURE__ */ jsx(AppShell, {}) }) }) }) }) }) })
6981
+ children: /* @__PURE__ */ jsx(MdStyleProvider, { children: /* @__PURE__ */ jsx(ChipStyleProvider, { children: /* @__PURE__ */ jsx(SafeModeProvider, { children: /* @__PURE__ */ jsx(InteractionsProvider, { children: /* @__PURE__ */ jsx(McpAuthProvider, { children: /* @__PURE__ */ jsx(DiscoveryShell, { children: /* @__PURE__ */ jsx(ModalRoot, { children: /* @__PURE__ */ jsx(AppShell, {}) }) }) }) }) }) }) })
6980
6982
  });
6981
6983
  }
6982
6984
  function AppShell() {
6985
+ bootTick("AppShell:render-enter");
6983
6986
  const renderer = useRenderer();
6984
6987
  const modal = useModal();
6985
6988
  const config = useConfig();
6986
6989
  const { settings } = useSettings();
6987
6990
  const COLOR = useColors();
6988
6991
  const SURFACE = useSurfaces();
6992
+ useEffect(() => {
6993
+ bootTick("AppShell:first-effect (post-first-paint)");
6994
+ }, []);
6989
6995
  const queue = useSafeModeQueue();
6990
6996
  const { requestApproval, resolveHead, denyAll } = useSafeModeActions();
6991
6997
  const pendingApproval = queue[0] ?? null;
@@ -7045,70 +7051,11 @@ function AppShell() {
7045
7051
  safelistRef.current = null;
7046
7052
  }, [dataDir, projectDir]);
7047
7053
  const sessionSafelistRef = useRef(/* @__PURE__ */ new Set());
7048
- const [skillsCatalog, setSkillsCatalog] = useState([]);
7049
- const [mcpsCatalog, setMcpsCatalog] = useState([]);
7050
- const [mcpsErrors, setMcpsErrors] = useState([]);
7051
- const [filesCatalog, setFilesCatalog] = useState([]);
7054
+ const { skillsCatalog, mcpsCatalog, filesCatalog, ensureFiles: ensureFilesCatalog, ensureSkills: ensureSkillsCatalog, refreshSkills: onRefreshSkills, refreshMcps: onRefreshMcps } = useDiscovery();
7052
7055
  const mcpCredentialStore = useMemo(() => createFileMcpCredentialStore(dataDir), [dataDir]);
7053
7056
  const dispatchAuth = useMcpAuthDispatch();
7054
7057
  const dispatchAuthRef = useRef(dispatchAuth);
7055
7058
  dispatchAuthRef.current = dispatchAuth;
7056
- useEffect(() => {
7057
- const ac = new AbortController();
7058
- let cancelled = false;
7059
- (async () => {
7060
- try {
7061
- const skills = await discoverProjectSkills({
7062
- cwd: projectDir,
7063
- prefix: config.prefix
7064
- });
7065
- if (!cancelled) setSkillsCatalog(skills);
7066
- } catch (err) {
7067
- debugLog("discoverProjectSkills failed", err);
7068
- }
7069
- })();
7070
- (async () => {
7071
- try {
7072
- const files = await listProjectFiles({
7073
- cwd: projectDir,
7074
- signal: ac.signal
7075
- });
7076
- if (!cancelled) setFilesCatalog(files);
7077
- } catch (err) {
7078
- debugLog("listProjectFiles failed", err);
7079
- }
7080
- })();
7081
- try {
7082
- const { servers, errors } = discoverProjectMcps({
7083
- cwd: projectDir,
7084
- prefix: config.prefix
7085
- });
7086
- setMcpsCatalog(servers);
7087
- setMcpsErrors(errors);
7088
- for (const entry of servers) {
7089
- if (entry.config.auth !== "oauth") continue;
7090
- const hasTokens = !!mcpCredentialStore.load(entry.config.name)?.tokens;
7091
- dispatchAuthRef.current(hasTokens ? {
7092
- type: "auth-success",
7093
- name: entry.config.name
7094
- } : {
7095
- type: "auth-required",
7096
- name: entry.config.name,
7097
- reason: "no-tokens"
7098
- });
7099
- }
7100
- } catch (err) {
7101
- debugLog("discoverProjectMcps failed", err);
7102
- }
7103
- return () => {
7104
- cancelled = true;
7105
- ac.abort();
7106
- };
7107
- }, [
7108
- projectDir,
7109
- config.prefix,
7110
- mcpCredentialStore
7111
- ]);
7112
7059
  const skillsCatalogRef = useRef(skillsCatalog);
7113
7060
  skillsCatalogRef.current = skillsCatalog;
7114
7061
  const enabledSkillsRef = useRef(settings.enabledSkills);
@@ -7119,10 +7066,18 @@ function AppShell() {
7119
7066
  enabledMcpsRef.current = settings.enabledMcps;
7120
7067
  const filesCatalogRef = useRef(filesCatalog);
7121
7068
  filesCatalogRef.current = filesCatalog;
7069
+ const ensureFilesCatalogRef = useRef(ensureFilesCatalog);
7070
+ ensureFilesCatalogRef.current = ensureFilesCatalog;
7071
+ const ensureSkillsCatalogRef = useRef(ensureSkillsCatalog);
7072
+ ensureSkillsCatalogRef.current = ensureSkillsCatalog;
7122
7073
  const completionProviders = useMemo(() => [createSkillsCompletionProvider({
7123
7074
  getCatalog: () => skillsCatalogRef.current,
7124
- getEnabled: () => enabledSkillsRef.current
7125
- }), createFilesCompletionProvider({ getCatalog: () => filesCatalogRef.current })], []);
7075
+ getEnabled: () => enabledSkillsRef.current,
7076
+ ensureCatalog: () => ensureSkillsCatalogRef.current()
7077
+ }), createFilesCompletionProvider({
7078
+ getCatalog: () => filesCatalogRef.current,
7079
+ ensureCatalog: () => ensureFilesCatalogRef.current()
7080
+ })], [filesCatalog, skillsCatalog]);
7126
7081
  /**
7127
7082
  * Single source of truth for "should this call execute?". Returns true to
7128
7083
  * let the call through, false to refuse it. Handles three short-circuits:
@@ -7618,7 +7573,7 @@ function AppShell() {
7618
7573
  } catch (err) {
7619
7574
  stream.appendImmediate({
7620
7575
  kind: "error",
7621
- text: err instanceof Error ? err.message : String(err)
7576
+ text: errorMessage(err)
7622
7577
  });
7623
7578
  } finally {
7624
7579
  stream.flushAndUpdate(finalizeStreamingMarkdown);
@@ -7687,13 +7642,19 @@ function AppShell() {
7687
7642
  const sessionMatchesProject = settings.showAllProjects || data?.projectRoot != null && data.projectRoot === projectDir;
7688
7643
  if (data && sessionMatchesProject) {
7689
7644
  await activateSession(lastResumedSessionId, resumeProvider.key);
7645
+ bootTick(`resume:activate (sessionId=${lastResumedSessionId.slice(-8)})`);
7690
7646
  return;
7691
7647
  }
7692
7648
  }
7693
7649
  const list = await refreshSessions();
7694
7650
  if (cancelled) return;
7695
- if (list.length === 0) await activateSession(null, resumeProvider.key);
7696
- else setScreen("sessions");
7651
+ if (list.length === 0) {
7652
+ await activateSession(null, resumeProvider.key);
7653
+ bootTick("resume:fresh-session (empty list)");
7654
+ } else {
7655
+ setScreen("sessions");
7656
+ bootTick(`resume:sessions-list (${list.length})`);
7657
+ }
7697
7658
  })();
7698
7659
  return () => {
7699
7660
  cancelled = true;
@@ -8008,7 +7969,7 @@ function AppShell() {
8008
7969
  } catch (err) {
8009
7970
  stream.appendImmediate({
8010
7971
  kind: "error",
8011
- text: err instanceof Error ? err.message : String(err)
7972
+ text: errorMessage(err)
8012
7973
  });
8013
7974
  } finally {
8014
7975
  stream.flushAndUpdate(finalizeStreamingMarkdown);
@@ -8107,7 +8068,7 @@ function AppShell() {
8107
8068
  if (!agentRef.current) dispatchAuth({
8108
8069
  type: "auth-error",
8109
8070
  name,
8110
- error: err instanceof Error ? err.message : String(err)
8071
+ error: errorMessage(err)
8111
8072
  });
8112
8073
  } finally {
8113
8074
  mcpLoginAbortsRef.current.delete(name);
@@ -8434,7 +8395,7 @@ function AppShell() {
8434
8395
  if (abort.signal.aborted) return;
8435
8396
  stream.appendImmediate({
8436
8397
  kind: "error",
8437
- text: `Auto-compaction failed: ${err instanceof Error ? err.message : String(err)}`
8398
+ text: `Auto-compaction failed: ${errorMessage(err)}`
8438
8399
  });
8439
8400
  }).finally(() => {
8440
8401
  if (autoCompactInFlightRef.current === compactionPromise) {
@@ -8578,18 +8539,15 @@ function AppShell() {
8578
8539
  return;
8579
8540
  }
8580
8541
  if (matchesBinding(key, keybindings.openSettings) && screen !== "auth") {
8581
- modal.open(/* @__PURE__ */ jsx(SettingsModal, {
8582
- skillsCatalog,
8583
- mcpsCatalog,
8584
- mcpsErrors,
8585
- actions: {
8586
- onReauth,
8587
- onOpenKeybindings: onOpenKeybindingsFile,
8588
- onLoginMcp,
8589
- onLogoutMcp,
8590
- onCancelLoginMcp
8591
- }
8592
- }));
8542
+ modal.open(/* @__PURE__ */ jsx(SettingsModal, { actions: {
8543
+ onReauth,
8544
+ onOpenKeybindings: onOpenKeybindingsFile,
8545
+ onLoginMcp,
8546
+ onLogoutMcp,
8547
+ onCancelLoginMcp,
8548
+ onRefreshSkills,
8549
+ onRefreshMcps
8550
+ } }));
8593
8551
  return;
8594
8552
  }
8595
8553
  if (matchesBinding(key, keybindings.openSessionDetails)) {
@@ -8691,23 +8649,16 @@ function AppShell() {
8691
8649
  drop: keybindings.dropQueuedMessage
8692
8650
  }), [keybindings]);
8693
8651
  const promptTriggerHints = useMemo(() => {
8694
- const out = [];
8695
- if (filesCatalog.length > 0) out.push({
8652
+ return [{
8696
8653
  key: "@",
8697
8654
  label: "files",
8698
8655
  keyColor: resolveChipColor(SURFACE.chips, "files").bg
8699
- });
8700
- if (skillsCatalog.length > 0) out.push({
8656
+ }, {
8701
8657
  key: "/",
8702
8658
  label: "skills",
8703
8659
  keyColor: resolveChipColor(SURFACE.chips, "skills").bg
8704
- });
8705
- return out;
8706
- }, [
8707
- filesCatalog,
8708
- skillsCatalog,
8709
- SURFACE
8710
- ]);
8660
+ }];
8661
+ }, [SURFACE]);
8711
8662
  const contextUsage = useMemo(() => {
8712
8663
  if (screen !== "chat" || !picked) return null;
8713
8664
  const descriptor = providerRegistry[picked.provider.key];
@@ -9085,16 +9036,45 @@ function resolveLocalParsers() {
9085
9036
  return resolved;
9086
9037
  }
9087
9038
  let registered = false;
9039
+ let workerInitStarted = false;
9088
9040
  /**
9089
- * Register the extra Tree-sitter parsers + start the worker. Idempotent —
9090
- * subsequent calls are no-ops. Safe to invoke from `runTui()` and from
9091
- * composition hosts that mount `<App>` directly.
9041
+ * Synchronously append every URL-fetched + locally-vendored grammar to
9042
+ * OpenTUI's global parser registry. Cheap (just a couple of `Array.push`
9043
+ * calls into a module-level table) and idempotent — subsequent calls
9044
+ * are no-ops.
9045
+ *
9046
+ * Split out from {@link initTreeSitterWorker} so the boot path can land
9047
+ * the registry *synchronously* on its critical path (no awaiting
9048
+ * required) and defer the heavier worker spin-up below until after the
9049
+ * first frame is on screen. OpenTUI's `highlightOnce` self-initialises
9050
+ * the worker on first use anyway — registering parsers ahead of time is
9051
+ * the only piece that genuinely has to happen synchronously.
9092
9052
  */
9093
- async function setupTreeSitter() {
9053
+ function registerTreeSitterParsers() {
9094
9054
  if (registered) return;
9095
9055
  registered = true;
9096
9056
  addDefaultParsers([...EXTRA_PARSERS, ...resolveLocalParsers()]);
9097
- await getTreeSitterClient().initialize();
9057
+ }
9058
+ /**
9059
+ * Spin up OpenTUI's parser worker thread. Idempotent — concurrent
9060
+ * callers share a single in-flight promise; subsequent calls after
9061
+ * resolution are no-ops.
9062
+ *
9063
+ * Safe to fire-and-forget after the renderer mounts: the worker boot
9064
+ * is the heaviest step in tree-sitter setup (~40–100ms on most
9065
+ * machines) and nothing visible needs it until the first fenced code
9066
+ * block highlight, which is many frames away even on the fastest
9067
+ * sessions. If a `<markdown>` element happens to call
9068
+ * `highlightOnce()` before our explicit init completes, OpenTUI's
9069
+ * client guards that call with its own `if (!this.initialized) await
9070
+ * this.initialize()` — no race.
9071
+ */
9072
+ function initTreeSitterWorker() {
9073
+ if (!workerInitStarted) {
9074
+ workerInitStarted = true;
9075
+ registerTreeSitterParsers();
9076
+ }
9077
+ return getTreeSitterClient().initialize();
9098
9078
  }
9099
9079
  //#endregion
9100
9080
  //#region src/tui/mcps-settings.tsx
@@ -9125,7 +9105,7 @@ async function setupTreeSitter() {
9125
9105
  * Errors (`DiscoveryError[]`) surface in a warn-colored preamble so a
9126
9106
  * broken `mcps.json` is loud rather than invisible.
9127
9107
  */
9128
- function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin }) {
9108
+ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin, onRefresh }) {
9129
9109
  const COLOR = useColors();
9130
9110
  const home = homedir();
9131
9111
  const authState = useMcpAuthState();
@@ -9166,7 +9146,11 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin }
9166
9146
  onLogin(name);
9167
9147
  return;
9168
9148
  }
9169
- if (key.name === "o" && canLogout(status)) onLogout(name);
9149
+ if (key.name === "o" && canLogout(status)) {
9150
+ onLogout(name);
9151
+ return;
9152
+ }
9153
+ if (key.name === "r" && onRefresh) onRefresh();
9170
9154
  });
9171
9155
  if (catalog.length === 0) return /* @__PURE__ */ jsxs(Modal, {
9172
9156
  title: "mcp servers",
@@ -9227,10 +9211,17 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin }
9227
9211
  }),
9228
9212
  /* @__PURE__ */ jsxs("text", {
9229
9213
  fg: COLOR.mute,
9230
- children: [/* @__PURE__ */ jsx("span", {
9231
- fg: COLOR.warn,
9232
- children: "esc"
9233
- }), " close"]
9214
+ children: [
9215
+ onRefresh && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
9216
+ fg: COLOR.warn,
9217
+ children: "r"
9218
+ }), " refresh · "] }),
9219
+ /* @__PURE__ */ jsx("span", {
9220
+ fg: COLOR.warn,
9221
+ children: "esc"
9222
+ }),
9223
+ " close"
9224
+ ]
9234
9225
  })
9235
9226
  ]
9236
9227
  });
@@ -9272,7 +9263,7 @@ function McpsSettingsModal({ catalog, errors, onLogin, onLogout, onCancelLogin }
9272
9263
  })
9273
9264
  }),
9274
9265
  focusedEntry && focusedStatus && renderDetailPanel(focusedEntry, focusedStatus, COLOR),
9275
- renderActionHints(focusedEntry, focusedStatus, COLOR)
9266
+ renderActionHints(focusedEntry, focusedStatus, !!onRefresh, COLOR)
9276
9267
  ]
9277
9268
  });
9278
9269
  }
@@ -9383,7 +9374,7 @@ function renderDetailPanel(entry, status, COLOR) {
9383
9374
  });
9384
9375
  return null;
9385
9376
  }
9386
- function renderActionHints(entry, status, COLOR) {
9377
+ function renderActionHints(entry, status, showRefresh, COLOR) {
9387
9378
  const effectiveStatus = status ?? { kind: "idle" };
9388
9379
  const canL = entry ? canLogin(entry, effectiveStatus) : false;
9389
9380
  const canO = canLogout(effectiveStatus);
@@ -9417,6 +9408,14 @@ function renderActionHints(entry, status, COLOR) {
9417
9408
  }),
9418
9409
  " logout"
9419
9410
  ] }),
9411
+ showRefresh && /* @__PURE__ */ jsxs("span", { children: [
9412
+ " · ",
9413
+ /* @__PURE__ */ jsx("span", {
9414
+ fg: COLOR.warn,
9415
+ children: "r"
9416
+ }),
9417
+ " refresh"
9418
+ ] }),
9420
9419
  canCancel && /* @__PURE__ */ jsxs("span", { children: [
9421
9420
  " · ",
9422
9421
  /* @__PURE__ */ jsx("span", {
@@ -9467,7 +9466,7 @@ function displayPath(path, home) {
9467
9466
  * (chat layer) — a GUI shell can build its own toggle list against the
9468
9467
  * same hook without pulling OpenTUI.
9469
9468
  */
9470
- function ToggleListModal({ catalog, keyOf, settingKey, title, renderDetail, emptyState, preamble }) {
9469
+ function ToggleListModal({ catalog, keyOf, settingKey, title, renderDetail, emptyState, preamble, onRefresh }) {
9471
9470
  const COLOR = useColors();
9472
9471
  const { enabledSet, toggle } = useEnabledToggleSet({
9473
9472
  catalog,
@@ -9486,7 +9485,7 @@ function ToggleListModal({ catalog, keyOf, settingKey, title, renderDetail, empt
9486
9485
  else if (key.name === "return" || key.name === "space") {
9487
9486
  const entry = catalog[safeCursor];
9488
9487
  if (entry) toggle(keyOf(entry));
9489
- }
9488
+ } else if (key.ctrl && key.name === "r" && onRefresh) onRefresh();
9490
9489
  });
9491
9490
  if (catalog.length === 0) return /* @__PURE__ */ jsxs(Modal, {
9492
9491
  title,
@@ -9495,10 +9494,17 @@ function ToggleListModal({ catalog, keyOf, settingKey, title, renderDetail, empt
9495
9494
  emptyState,
9496
9495
  /* @__PURE__ */ jsxs("text", {
9497
9496
  fg: COLOR.mute,
9498
- children: [/* @__PURE__ */ jsx("span", {
9499
- fg: COLOR.warn,
9500
- children: "esc"
9501
- }), " close"]
9497
+ children: [
9498
+ onRefresh && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
9499
+ fg: COLOR.warn,
9500
+ children: "ctrl+R"
9501
+ }), " refresh · "] }),
9502
+ /* @__PURE__ */ jsx("span", {
9503
+ fg: COLOR.warn,
9504
+ children: "esc"
9505
+ }),
9506
+ " close"
9507
+ ]
9502
9508
  })
9503
9509
  ]
9504
9510
  });
@@ -9548,6 +9554,10 @@ function ToggleListModal({ catalog, keyOf, settingKey, title, renderDetail, empt
9548
9554
  children: "↵"
9549
9555
  }),
9550
9556
  " toggle · ",
9557
+ onRefresh && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
9558
+ fg: COLOR.warn,
9559
+ children: "ctrl+R"
9560
+ }), " refresh · "] }),
9551
9561
  /* @__PURE__ */ jsx("span", {
9552
9562
  fg: COLOR.warn,
9553
9563
  children: "esc"
@@ -9564,8 +9574,13 @@ function ToggleListModal({ catalog, keyOf, settingKey, title, renderDetail, empt
9564
9574
  * List + toggle modal for discovered skills. State machine + keyboard +
9565
9575
  * row geometry live in `<ToggleListModal>`; this file just supplies the
9566
9576
  * skill-specific column (description) and the empty-state hint.
9577
+ *
9578
+ * Pass `onRefresh` to expose `ctrl+R` (force re-scan) on the list. The
9579
+ * standalone modal is for embedders — the in-app flow goes through
9580
+ * `<SettingsModal>` which exposes the same affordance via its own
9581
+ * `SettingsActions.onRefreshSkills` plumbing.
9567
9582
  */
9568
- function SkillsSettingsModal({ catalog }) {
9583
+ function SkillsSettingsModal({ catalog, onRefresh }) {
9569
9584
  const COLOR = useColors();
9570
9585
  return /* @__PURE__ */ jsx(ToggleListModal, {
9571
9586
  catalog,
@@ -9573,6 +9588,7 @@ function SkillsSettingsModal({ catalog }) {
9573
9588
  settingKey: "enabledSkills",
9574
9589
  title: "skills",
9575
9590
  renderDetail: (skill) => skill.description,
9591
+ onRefresh,
9576
9592
  emptyState: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("text", {
9577
9593
  fg: COLOR.dim,
9578
9594
  children: "No skills discovered."
@@ -9662,14 +9678,14 @@ let runTuiInvoked = false;
9662
9678
  async function runTui(options = {}) {
9663
9679
  if (runTuiInvoked) throw new Error("runTui() can only be invoked once per process. Compose `<App config={resolveConfig(...)} />` against your own renderer if you need to run multiple TUIs in the same lifetime.");
9664
9680
  runTuiInvoked = true;
9665
- await setupTreeSitter().catch((err) => {
9666
- const cause = err instanceof Error ? err.message : String(err);
9667
- process.stderr.write(`[zidane/tui] tree-sitter setup failed: ${cause}\n`);
9668
- });
9681
+ bootTick("runTui:enter");
9682
+ registerTreeSitterParsers();
9683
+ bootTick("runTui:after-registerTreeSitterParsers");
9669
9684
  const config = resolveConfig({
9670
9685
  ...options,
9671
9686
  store: options.store ?? ((paths) => createTuiStore(paths.db))
9672
9687
  });
9688
+ bootTick("runTui:after-resolveConfig");
9673
9689
  let done = () => {};
9674
9690
  const exited = new Promise((resolve) => {
9675
9691
  done = resolve;
@@ -9684,17 +9700,22 @@ async function runTui(options = {}) {
9684
9700
  maxFps: bootFps,
9685
9701
  gatherStats
9686
9702
  });
9703
+ bootTick("runTui:after-createCliRenderer");
9687
9704
  createRoot(renderer).render(/* @__PURE__ */ jsx(App, { config }));
9705
+ bootTick("runTui:after-mount");
9706
+ initTreeSitterWorker().then(() => bootTick("runTui:after-treeSitterWorker"), (err) => {
9707
+ process.stderr.write(`[zidane/tui] tree-sitter setup failed: ${errorMessage(err)}\n`);
9708
+ });
9688
9709
  await exited;
9689
9710
  if (gatherStats) try {
9690
9711
  const s = renderer.getStats();
9691
9712
  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`);
9692
9713
  } catch (err) {
9693
- process.stderr.write(`[zidane/tui] render stats unavailable: ${err instanceof Error ? err.message : String(err)}\n`);
9714
+ process.stderr.write(`[zidane/tui] render stats unavailable: ${errorMessage(err)}\n`);
9694
9715
  }
9695
9716
  process.exit(0);
9696
9717
  }
9697
9718
  //#endregion
9698
- 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 };
9719
+ 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, clipHintsToWidth, computeTurnAnchors, displayNameFor, formatToolCall, hintsLength, isEditErrorResult, isTurnHighlighted, isVisible, marginTopFor, onInputSubmit, renderHintSpans, runTui, selectableTurnIds, splitMarkdownCodeBlocks, splitPromptSegments, truncateTrailing, turnSelectionOwnership, useMdStyle, useModal, useModalAwareFocus };
9699
9720
 
9700
9721
  //# sourceMappingURL=tui.js.map