mastracode 0.45.0 → 0.46.0-alpha.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.
Files changed (69) hide show
  1. package/dist/cli.cjs +7 -2
  2. package/dist/cli.cjs.map +1 -1
  3. package/dist/cli.js +7 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/docs/SKILL.md +1 -1
  6. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  7. package/dist/tui/commands/think.d.ts.map +1 -1
  8. package/dist/tui/components/account-switch-notice.d.ts +1 -1
  9. package/dist/tui/components/ask-question-inline.d.ts +15 -0
  10. package/dist/tui/components/ask-question-inline.d.ts.map +1 -1
  11. package/dist/tui/components/banner.d.ts +32 -5
  12. package/dist/tui/components/banner.d.ts.map +1 -1
  13. package/dist/tui/components/custom-editor.d.ts +0 -4
  14. package/dist/tui/components/custom-editor.d.ts.map +1 -1
  15. package/dist/tui/components/error-display.d.ts.map +1 -1
  16. package/dist/tui/components/idle-counter.d.ts +5 -1
  17. package/dist/tui/components/idle-counter.d.ts.map +1 -1
  18. package/dist/tui/components/judge-display.d.ts +1 -0
  19. package/dist/tui/components/judge-display.d.ts.map +1 -1
  20. package/dist/tui/components/notification.d.ts.map +1 -1
  21. package/dist/tui/components/om-output.d.ts +2 -2
  22. package/dist/tui/components/om-output.d.ts.map +1 -1
  23. package/dist/tui/components/plan-approval-inline.d.ts +2 -0
  24. package/dist/tui/components/plan-approval-inline.d.ts.map +1 -1
  25. package/dist/tui/components/shell-output.d.ts +1 -1
  26. package/dist/tui/components/shell-output.d.ts.map +1 -1
  27. package/dist/tui/components/slash-command.d.ts +2 -2
  28. package/dist/tui/components/slash-command.d.ts.map +1 -1
  29. package/dist/tui/components/subagent-execution.d.ts.map +1 -1
  30. package/dist/tui/components/surface.d.ts +30 -0
  31. package/dist/tui/components/surface.d.ts.map +1 -0
  32. package/dist/tui/components/system-reminder.d.ts.map +1 -1
  33. package/dist/tui/components/task-progress.d.ts.map +1 -1
  34. package/dist/tui/components/thinking-settings.d.ts.map +1 -1
  35. package/dist/tui/components/tool-approval-dialog.d.ts +36 -15
  36. package/dist/tui/components/tool-approval-dialog.d.ts.map +1 -1
  37. package/dist/tui/components/tool-execution-enhanced.d.ts +16 -4
  38. package/dist/tui/components/tool-execution-enhanced.d.ts.map +1 -1
  39. package/dist/tui/components/tool-execution-interface.d.ts +5 -0
  40. package/dist/tui/components/tool-execution-interface.d.ts.map +1 -1
  41. package/dist/tui/components/user-message.d.ts +3 -2
  42. package/dist/tui/components/user-message.d.ts.map +1 -1
  43. package/dist/tui/components/wrapping-select-list.d.ts +13 -1
  44. package/dist/tui/components/wrapping-select-list.d.ts.map +1 -1
  45. package/dist/tui/handlers/agent-lifecycle.d.ts +1 -0
  46. package/dist/tui/handlers/agent-lifecycle.d.ts.map +1 -1
  47. package/dist/tui/handlers/message.d.ts.map +1 -1
  48. package/dist/tui/handlers/tool.d.ts.map +1 -1
  49. package/dist/tui/mastra-tui.d.ts.map +1 -1
  50. package/dist/tui/onboarding-inline.d.ts +6 -0
  51. package/dist/tui/onboarding-inline.d.ts.map +1 -1
  52. package/dist/tui/render-messages.d.ts +1 -1
  53. package/dist/tui/render-messages.d.ts.map +1 -1
  54. package/dist/tui/setup.d.ts.map +1 -1
  55. package/dist/tui/state.d.ts +9 -13
  56. package/dist/tui/state.d.ts.map +1 -1
  57. package/dist/tui/status-line.d.ts +8 -2
  58. package/dist/tui/status-line.d.ts.map +1 -1
  59. package/dist/tui/theme.d.ts +19 -1
  60. package/dist/tui/theme.d.ts.map +1 -1
  61. package/dist/{tui-CDdSkr_Q.js → tui-5IfsALo2.js} +1338 -1137
  62. package/dist/tui-5IfsALo2.js.map +1 -0
  63. package/dist/{tui-BsLtXA9y.cjs → tui-jrSC6Hge.cjs} +1337 -1136
  64. package/dist/tui-jrSC6Hge.cjs.map +1 -0
  65. package/dist/tui.cjs +1 -1
  66. package/dist/tui.js +1 -1
  67. package/package.json +12 -12
  68. package/dist/tui-BsLtXA9y.cjs.map +0 -1
  69. package/dist/tui-CDdSkr_Q.js.map +0 -1
@@ -23,7 +23,8 @@ import { DEFAULT_STT_MODEL, defaultModelForProvider, resolveSTTModel, sttModelsF
23
23
  import { Readable } from "stream";
24
24
  import { DeepgramVoice } from "@mastra/voice-deepgram";
25
25
  import { OpenAIVoice } from "@mastra/voice-openai";
26
- import { THINK_COMMAND_DESCRIPTOR, getAvailableThinkingLevelsForModel, isThinkingLevelSetting, parseThinkCommand } from "@mastra/code-sdk/thinking";
26
+ import { THINK_COMMAND_DESCRIPTOR, isThinkingLevelSetting, parseThinkCommand } from "@mastra/code-sdk/thinking";
27
+ import { getCatalogThinkingLevels, runCatalogThinkingLevel } from "@mastra/code-sdk/thinking-catalog";
27
28
  import { VALID_EVENTS } from "@mastra/code-sdk/hooks/index";
28
29
  import { openUrlInBrowser } from "@mastra/code-sdk/utils/open-url";
29
30
  import { parseError } from "@mastra/code-sdk/utils/errors";
@@ -138,6 +139,7 @@ const textThemeKeys = [
138
139
  "muted",
139
140
  "dim",
140
141
  "text",
142
+ "secondary",
141
143
  "thinkingText",
142
144
  "userMessageText",
143
145
  "toolTitle",
@@ -171,6 +173,60 @@ const mastra = new Proxy({}, { get(_target, prop) {
171
173
  const surface = getSurface();
172
174
  if (prop in surface) return surface[prop];
173
175
  } });
176
+ /**
177
+ * A surface shade derived from the terminal's own background (detected via OSC 11), so panels match the
178
+ * user's theme instead of a fixed grey. Same idea as opencode's "system" theme: each step moves the
179
+ * background's luminance 1/30 of the way toward white (dark) or black (light) while keeping its hue.
180
+ * Step 1 = tool output panels, step 2 = the prompt and sent messages.
181
+ */
182
+ function surfaceShade(step) {
183
+ const bg = getContrastBg();
184
+ const [r, g, b] = [
185
+ 1,
186
+ 3,
187
+ 5
188
+ ].map((i) => parseInt(bg.slice(i, i + 2), 16));
189
+ const lum = .299 * r + .587 * g + .114 * b;
190
+ const f = step / 12 * .4;
191
+ let out;
192
+ if (currentThemeMode === "light") out = [
193
+ r,
194
+ g,
195
+ b
196
+ ].map((c) => c * (1 - f));
197
+ else if (lum < 10) {
198
+ const lift = Math.min(255, step * 18);
199
+ out = [
200
+ lift,
201
+ lift,
202
+ lift
203
+ ];
204
+ } else {
205
+ const ratio = (lum + (255 - lum) * f) / lum;
206
+ out = [
207
+ r,
208
+ g,
209
+ b
210
+ ].map((c) => Math.min(255, c * ratio));
211
+ }
212
+ return `#${out.map((c) => Math.floor(c).toString(16).padStart(2, "0")).join("")}`;
213
+ }
214
+ /**
215
+ * Mode colors (build / plan / fast) as displayed. On dark backgrounds the deep brand colors are lifted
216
+ * toward white the same way the accent is (brand green maps exactly to the accent), so mode-colored text
217
+ * never reads darker than the accent. On light backgrounds brand green maps to the light accent and the
218
+ * others are darkened just enough to read.
219
+ */
220
+ function displayModeColor(hex) {
221
+ const isBrandGreen = hex.toLowerCase() === mastraBrand.green.toLowerCase();
222
+ if (currentThemeMode === "light") return isBrandGreen ? lightTheme.accent : ensureContrast(hex, getContrastBg());
223
+ if (isBrandGreen) return darkTheme.accent;
224
+ const mix = (i) => {
225
+ const c = parseInt(hex.slice(i, i + 2), 16);
226
+ return Math.round(c + (255 - c) * .35).toString(16).padStart(2, "0");
227
+ };
228
+ return `#${mix(1)}${mix(3)}${mix(5)}`;
229
+ }
174
230
  /** Tint a hex color by a brightness factor (0–1). e.g. tintHex("#ff8800", 0.15) → near-black orange */
175
231
  function tintHex(hex, factor) {
176
232
  const r = Math.floor(parseInt(hex.slice(1, 3), 16) * factor);
@@ -179,16 +235,17 @@ function tintHex(hex, factor) {
179
235
  return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
180
236
  }
181
237
  const darkTheme = {
182
- accent: "#16c858",
238
+ accent: "#62f69d",
183
239
  border: "#3f3f46",
184
- borderAccent: "#16c858",
240
+ borderAccent: "#62f69d",
185
241
  borderMuted: "#27272a",
186
- success: "#22c55e",
242
+ success: "#62f69d",
187
243
  error: "#ef4444",
188
244
  warning: "#f59e0b",
189
245
  muted: "#8c8c94",
190
246
  dim: "#84848c",
191
247
  text: "#fafafa",
248
+ secondary: "#d9d9dc",
192
249
  thinkingText: "#a1a1aa",
193
250
  userMessageBg: "#0f172a",
194
251
  userMessageText: "#fafafa",
@@ -199,8 +256,8 @@ const darkTheme = {
199
256
  toolBorderPending: "#52525b",
200
257
  toolBorderSuccess: "#52525b",
201
258
  toolBorderError: "#ef4444",
202
- toolTitle: "#fb923c",
203
- toolArgs: "#ffe4c4",
259
+ toolTitle: "#d9d9dc",
260
+ toolArgs: "#d9d9dc",
204
261
  toolOutput: "#d4d4d8",
205
262
  textHighlight: "#c084fc",
206
263
  errorBg: "#291415",
@@ -215,12 +272,13 @@ const lightTheme = {
215
272
  border: "#d4d4d8",
216
273
  borderAccent: "#0d8020",
217
274
  borderMuted: "#e4e4e7",
218
- success: "#15803d",
275
+ success: "#0d8020",
219
276
  error: "#dc2626",
220
277
  warning: "#d97706",
221
278
  muted: "#595961",
222
279
  dim: "#67676f",
223
280
  text: "#18181b",
281
+ secondary: "#38383e",
224
282
  thinkingText: "#595961",
225
283
  userMessageBg: "#f0fdf4",
226
284
  userMessageText: "#18181b",
@@ -231,8 +289,8 @@ const lightTheme = {
231
289
  toolBorderPending: "#a1a1aa",
232
290
  toolBorderSuccess: "#a1a1aa",
233
291
  toolBorderError: "#dc2626",
234
- toolTitle: "#c2410c",
235
- toolArgs: "#92400e",
292
+ toolTitle: "#38383e",
293
+ toolArgs: "#38383e",
236
294
  toolOutput: "#3f3f46",
237
295
  textHighlight: "#7c3aed",
238
296
  errorBg: "#fef2f2",
@@ -3191,12 +3249,18 @@ var WrappingSelectList = class {
3191
3249
  onSelectionChange;
3192
3250
  /** Called when the user confirms a multi-select list with Enter; receives all checked items in display order. */
3193
3251
  onConfirmMulti;
3194
- constructor(items, maxVisible, theme, multiSelect = false) {
3252
+ /**
3253
+ * `card` style (inline prompts): an accent ❯ cursor and a bold label instead of a highlighted row,
3254
+ * and ●/○ instead of [x]/[ ] in multi-select mode.
3255
+ */
3256
+ cardStyle;
3257
+ constructor(items, maxVisible, theme, multiSelect = false, cardStyle) {
3195
3258
  this.items = items;
3196
3259
  this.filteredItems = items;
3197
3260
  this.maxVisible = maxVisible;
3198
3261
  this.theme = theme;
3199
3262
  this.multiSelect = multiSelect;
3263
+ this.cardStyle = cardStyle;
3200
3264
  }
3201
3265
  setFilter(filter) {
3202
3266
  this.filteredItems = this.items.filter((item) => item.value.toLowerCase().startsWith(filter.toLowerCase()));
@@ -3253,6 +3317,14 @@ var WrappingSelectList = class {
3253
3317
  const checkboxWidth = this.multiSelect ? CHECKBOX_WIDTH : 0;
3254
3318
  const wrapped = wrapTextWithAnsi(labelText, Math.max(1, width - PREFIX_WIDTH - checkboxWidth));
3255
3319
  const checkbox = this.multiSelect ? this.checkedValues.has(item.value) ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED : "";
3320
+ if (this.cardStyle) {
3321
+ const style = this.cardStyle;
3322
+ const box = this.multiSelect ? this.checkedValues.has(item.value) ? style.checked : style.unchecked : "";
3323
+ return (wrapped.length === 0 ? [""] : wrapped).map((chunk, index) => {
3324
+ if (index > 0) return `${" ".repeat(PREFIX_WIDTH + (this.multiSelect ? CHECKBOX_WIDTH : 0))}${chunk}`;
3325
+ return `${isSelected ? style.cursor : UNSELECTED_PREFIX}${box}${isSelected ? style.selectedLabel(chunk) : chunk}`;
3326
+ });
3327
+ }
3256
3328
  if (wrapped.length === 0) {
3257
3329
  const rendered = `${isSelected ? SELECTED_PREFIX : UNSELECTED_PREFIX}${checkbox}`;
3258
3330
  return [isSelected ? this.theme.selectedText(rendered) : rendered];
@@ -3833,11 +3905,11 @@ const BASE_THINKING_LEVELS = [
3833
3905
  id: "max",
3834
3906
  label: "Max",
3835
3907
  providerValue: "max",
3836
- description: "Unbounded reasoning (Anthropic, GPT-5.6+)"
3908
+ description: "Unbounded reasoning"
3837
3909
  }
3838
3910
  ];
3839
3911
  function getThinkingLevelsForModel(modelId) {
3840
- const availableLevels = getAvailableThinkingLevelsForModel(modelId);
3912
+ const availableLevels = getCatalogThinkingLevels(modelId);
3841
3913
  const levels = BASE_THINKING_LEVELS.filter((level) => availableLevels.some((available) => available === level.id));
3842
3914
  if (!modelId.startsWith("openai/")) return [...levels];
3843
3915
  return levels.map((level) => ({
@@ -3894,6 +3966,7 @@ async function handleThinkCommand(ctx, args = []) {
3894
3966
  const modelId = ctx.state.session.model.get() ?? "";
3895
3967
  const thinkingLevels = getThinkingLevelsForModel(modelId);
3896
3968
  const override = getSessionOverride(ctx);
3969
+ const runningOverride = override && runCatalogThinkingLevel(modelId, override);
3897
3970
  const configuredDefault = getConfiguredDefault(ctx);
3898
3971
  const rawArguments = args.join(" ");
3899
3972
  if (rawArguments) {
@@ -3923,7 +3996,7 @@ async function handleThinkCommand(ctx, args = []) {
3923
3996
  label: ` Default ${theme.fg("dim", defaultDescription)}${override === void 0 ? theme.fg("dim", " (current)") : ""}`
3924
3997
  }, ...thinkingLevels.map((l) => ({
3925
3998
  value: l.id,
3926
- label: ` ${l.label} ${theme.fg("dim", l.description)}${l.id === override ? theme.fg("dim", " (current)") : ""}`
3999
+ label: ` ${l.label} ${theme.fg("dim", l.description)}${l.id === runningOverride ? theme.fg("dim", " (current)") : ""}`
3927
4000
  }))];
3928
4001
  const modelNote = getModelNote(ctx);
3929
4002
  return new Promise((resolve) => {
@@ -3947,7 +4020,7 @@ async function handleThinkCommand(ctx, args = []) {
3947
4020
  } else if (isThinkingLevelSetting(selectedValue)) {
3948
4021
  await ctx.state.session.state.set({ thinkingLevel: selectedValue });
3949
4022
  const selectedLabel = getThinkingLevelForModel(modelId, selectedValue).label;
3950
- ctx.showInfo(`Thinking → ${selectedValue === override ? `${selectedLabel} (unchanged)` : `${selectedLabel} (session override)`}`);
4023
+ ctx.showInfo(`Thinking → ${selectedValue === runningOverride ? `${selectedLabel} (unchanged)` : `${selectedLabel} (session override)`}`);
3951
4024
  }
3952
4025
  } catch {} finally {
3953
4026
  resolve();
@@ -3960,7 +4033,7 @@ async function handleThinkCommand(ctx, args = []) {
3960
4033
  container.addChild(selectList);
3961
4034
  container.addChild(new Spacer(1));
3962
4035
  container.addChild(new Text(theme.fg("dim", "↑↓ navigate · Enter select · Esc cancel"), 0, 0));
3963
- const currentIdx = override === void 0 ? 0 : thinkingLevels.findIndex((l) => l.id === override) + 1;
4036
+ const currentIdx = override === void 0 ? 0 : thinkingLevels.findIndex((l) => l.id === runningOverride) + 1;
3964
4037
  if (currentIdx > 0) selectList.setSelectedIndex(currentIdx);
3965
4038
  const modal = container;
3966
4039
  modal.handleInput = (data) => selectList.handleInput(data);
@@ -5358,6 +5431,227 @@ async function handleModeCommand(ctx, args) {
5358
5431
  }
5359
5432
  }
5360
5433
  //#endregion
5434
+ //#region src/tui/components/ansi.ts
5435
+ /**
5436
+ * Shared ANSI text-handling helpers for TUI components.
5437
+ */
5438
+ const ANSI_CLOSERS = "\x1B]8;;\x07\x1B[0m";
5439
+ const ELLIPSIS = "…";
5440
+ const ELLIPSIS_WIDTH = visibleWidth(ELLIPSIS);
5441
+ const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
5442
+ function fitVisibleText(text, maxWidth) {
5443
+ if (maxWidth <= 0) return {
5444
+ text: "",
5445
+ width: 0,
5446
+ truncated: text.length > 0
5447
+ };
5448
+ const targetWidth = Math.max(0, maxWidth - ELLIPSIS_WIDTH);
5449
+ let width = 0;
5450
+ let result = "";
5451
+ let resultWidth = 0;
5452
+ for (const { segment: char } of graphemeSegmenter.segment(text)) {
5453
+ const charWidth = visibleWidth(char);
5454
+ if (width + charWidth > maxWidth) return {
5455
+ text: `${result}${ELLIPSIS}`,
5456
+ width: resultWidth + ELLIPSIS_WIDTH,
5457
+ truncated: true
5458
+ };
5459
+ if (width + charWidth <= targetWidth) {
5460
+ result += char;
5461
+ resultWidth += charWidth;
5462
+ }
5463
+ width += charWidth;
5464
+ }
5465
+ return {
5466
+ text,
5467
+ width,
5468
+ truncated: false
5469
+ };
5470
+ }
5471
+ /** Truncate a string with ANSI codes to a visible width.
5472
+ * Handles both SGR sequences (\x1b[...m) and OSC 8 hyperlinks (\x1b]8;...;\x07).
5473
+ */
5474
+ function truncateAnsi(str, maxWidth) {
5475
+ if (maxWidth <= 0) return "";
5476
+ const ansiRegex = /\x1b\[[0-9;]{0,32}m|\x1b\]8;[^\x07\x1b]{0,8192}\x07/g;
5477
+ let visibleLength = 0;
5478
+ let result = "";
5479
+ let lastIndex = 0;
5480
+ let match;
5481
+ while ((match = ansiRegex.exec(str)) !== null) {
5482
+ const fitted = fitVisibleText(str.slice(lastIndex, match.index), maxWidth - visibleLength);
5483
+ if (fitted.truncated) {
5484
+ result += fitted.text;
5485
+ result += ANSI_CLOSERS;
5486
+ return result;
5487
+ }
5488
+ result += fitted.text;
5489
+ visibleLength += fitted.width;
5490
+ result += match[0];
5491
+ lastIndex = match.index + match[0].length;
5492
+ }
5493
+ const fitted = fitVisibleText(str.slice(lastIndex), maxWidth - visibleLength);
5494
+ result += fitted.text;
5495
+ if (fitted.truncated) result += ANSI_CLOSERS;
5496
+ return result;
5497
+ }
5498
+ //#endregion
5499
+ //#region src/tui/components/surface.ts
5500
+ /**
5501
+ * Shared drawing primitives for the borderless TUI design: half-block panels (prompt, sent messages,
5502
+ * tool output), the colored left bar for prompts that wait on the user, and soft keycaps for key hints.
5503
+ */
5504
+ /** The xterm-256 gray ramp (232-255) plus black (16) and white (231). */
5505
+ const GRAYS_256 = [
5506
+ [16, 0],
5507
+ ...Array.from({ length: 24 }, (_, i) => [232 + i, 8 + i * 10]),
5508
+ [231, 255]
5509
+ ];
5510
+ /**
5511
+ * Nearest xterm-256 gray. Panel shades are a step off the background, and the nearest color of the 6×6×6 cube
5512
+ * is often a saturated one (peach on a cream background, teal on Solarized), so panels stay neutral instead.
5513
+ */
5514
+ function nearestGray256(rgb) {
5515
+ const luma = .299 * rgb[0] + .587 * rgb[1] + .114 * rgb[2];
5516
+ let best = GRAYS_256[0];
5517
+ for (const gray of GRAYS_256) if (Math.abs(gray[1] - luma) < Math.abs(best[1] - luma)) best = gray;
5518
+ return best[0];
5519
+ }
5520
+ /** SGR opening a panel color at the terminal's color depth, so panel bodies and their half-block edges match. */
5521
+ function surfaceOpen(hex, layer) {
5522
+ const rgb = [
5523
+ 1,
5524
+ 3,
5525
+ 5
5526
+ ].map((i) => parseInt(hex.slice(i, i + 2), 16));
5527
+ if (chalk.level === 0) return "";
5528
+ if (chalk.level === 2) return `\x1b[${layer};5;${nearestGray256(rgb)}m`;
5529
+ return `\x1b[${layer};2;${rgb.join(";")}m`;
5530
+ }
5531
+ const bgOpen = (hex) => surfaceOpen(hex, 48);
5532
+ /** Fill a line with `bg` across `width` columns, re-applying it after any reset inside the line. */
5533
+ function fillBg(line, width, bg) {
5534
+ const open = bgOpen(bg);
5535
+ const pad = Math.max(0, width - visibleWidth(line));
5536
+ return open + line.replace(/\x1b\[(?:0|49)?m/g, (m) => m + open) + " ".repeat(pad) + "\x1B[49m";
5537
+ }
5538
+ /**
5539
+ * Rows on a solid background framed by half blocks (▄ above, ▀ below), so the panel reads as starting and
5540
+ * ending mid-row instead of using a border.
5541
+ */
5542
+ function halfBlockPanel(rows, width, bg) {
5543
+ return [
5544
+ panelEdge("▄", width, bg),
5545
+ ...rows.map((r) => fillBg(r, width, bg)),
5546
+ panelEdge("▀", width, bg)
5547
+ ];
5548
+ }
5549
+ /** A panel's ▄ or ▀ edge in the panel color. Without colors there's no panel background, so it's blank. */
5550
+ function panelEdge(ch, width, bg) {
5551
+ const open = surfaceOpen(bg, 38);
5552
+ const n = Math.max(0, width);
5553
+ return open ? `${open}${ch.repeat(n)}\x1b[39m` : " ".repeat(n);
5554
+ }
5555
+ const ESCAPE_TOKEN_RE = /(\x1b(?:\[[0-?]*[ -/]*[@-~]|[\]_][^\x07\x1b]*(?:\x07|\x1b\\)))/;
5556
+ const RESET_RE = /^\x1b\[(?:0|49)?m$/;
5557
+ const graphemes = new Intl.Segmenter();
5558
+ /** Higher = the shade stays strong farther right before fading. 1 is a straight linear fade. */
5559
+ const FADE_CURVE = 3;
5560
+ function fadeShades(width, step) {
5561
+ const last = Math.max(1, width - 1);
5562
+ return Array.from({ length: Math.max(0, width) }, (_, col) => surfaceShade(step * (1 - (col / last) ** FADE_CURVE)));
5563
+ }
5564
+ function fadeRow(line, shades) {
5565
+ let out = "";
5566
+ let col = 0;
5567
+ let current = "";
5568
+ const cell = (text) => {
5569
+ const hex = shades[Math.min(col, shades.length - 1)] ?? "";
5570
+ if (hex !== current) {
5571
+ current = hex;
5572
+ out += bgOpen(hex);
5573
+ }
5574
+ out += text;
5575
+ col += visibleWidth(text);
5576
+ };
5577
+ for (const part of line.split(ESCAPE_TOKEN_RE)) {
5578
+ if (!part) continue;
5579
+ if (part.startsWith("\x1B")) {
5580
+ out += part;
5581
+ if (RESET_RE.test(part) && current) out += bgOpen(current);
5582
+ continue;
5583
+ }
5584
+ for (const { segment } of graphemes.segment(part)) cell(segment);
5585
+ }
5586
+ while (col < shades.length) cell(" ");
5587
+ return `${out}\x1b[49m`;
5588
+ }
5589
+ function fadeEdge(ch, shades) {
5590
+ if (chalk.level === 0) return " ".repeat(shades.length);
5591
+ let out = "";
5592
+ let current = "";
5593
+ for (const hex of shades) {
5594
+ if (hex !== current) {
5595
+ current = hex;
5596
+ out += surfaceOpen(hex, 38);
5597
+ }
5598
+ out += ch;
5599
+ }
5600
+ return `${out}\x1b[39m`;
5601
+ }
5602
+ /** Like halfBlockPanel, but the shade fades out toward the right edge. */
5603
+ function fadePanel(rows, width, step) {
5604
+ if (chalk.level === 2) return halfBlockPanel(rows, width, surfaceShade(step));
5605
+ const shades = fadeShades(width, step);
5606
+ return [
5607
+ fadeEdge("▄", shades),
5608
+ ...rows.map((r) => fadeRow(r, shades)),
5609
+ fadeEdge("▀", shades)
5610
+ ];
5611
+ }
5612
+ /** Background of the prompt and sent messages. */
5613
+ const promptSurface = () => surfaceShade(2);
5614
+ /** Tool blocks with output: the title band is a step lighter than the output panel under it. */
5615
+ const toolTitleSurface = () => surfaceShade(2);
5616
+ const toolOutputSurface = () => surfaceShade(1);
5617
+ /** Status dot in front of a tool-style row: grey while running, green when done, red on failure. */
5618
+ function statusDot(status) {
5619
+ return status === "running" ? theme.fg("muted", "•") : theme.fg(status === "error" ? "error" : "success", "•");
5620
+ }
5621
+ /**
5622
+ * Tool-style block: a "• title" row (further title rows indented under it). With output, the title rows sit
5623
+ * on a lighter band directly above the output panel, the output lined up under the title text. Without output
5624
+ * it's just the title rows, so runs of quiet calls stay one row each.
5625
+ */
5626
+ function toolBlock(dot, title, output, width) {
5627
+ const [first = "", ...rest] = Array.isArray(title) ? title : [title];
5628
+ const titleRows = [`${dot} ${first}`, ...rest.map((line) => ` ${line}`)];
5629
+ if (output.length === 0) return titleRows;
5630
+ const contentWidth = Math.max(1, width - 3);
5631
+ const titleBg = toolTitleSurface();
5632
+ const outputBg = toolOutputSurface();
5633
+ return [
5634
+ panelEdge("▄", width, titleBg),
5635
+ ...titleRows.map((line) => fillBg(line, width, titleBg)),
5636
+ ...output.map((line) => fillBg(` ${truncateAnsi(line, contentWidth)}`, width, outputBg)),
5637
+ panelEdge("▀", width, outputBg)
5638
+ ];
5639
+ }
5640
+ /** Left-bar card for inline prompts: accent = waiting on you, warning = approval, border = answered. */
5641
+ function card(color, lines) {
5642
+ const bar = chalk.hex(color)("▎");
5643
+ return lines.map((l) => l === "" ? bar : `${bar} ${l}`);
5644
+ }
5645
+ /** Soft keycap: key text between the description grey and full white, on a chip one shade up. */
5646
+ function keycap(key) {
5647
+ const fg = theme.getTheme().secondary;
5648
+ return fillBg(chalk.hex(fg)(` ${key} `), visibleWidth(key) + 2, surfaceShade(2));
5649
+ }
5650
+ /** "key description" hint, e.g. keyHint('/help', 'info & shortcuts'). */
5651
+ function keyHint(key, description) {
5652
+ return `${keycap(key)} ${theme.fg("muted", description)}`;
5653
+ }
5654
+ //#endregion
5361
5655
  //#region src/tui/components/width-aware-container.ts
5362
5656
  /** Rebuilds width-derived children only when state or the received render width changes. */
5363
5657
  var WidthAwareContainer = class extends Container {
@@ -5379,12 +5673,10 @@ var WidthAwareContainer = class extends Container {
5379
5673
  //#endregion
5380
5674
  //#region src/tui/components/slash-command.ts
5381
5675
  /**
5382
- * SlashCommandComponent - renders a bordered box for slash command messages
5383
- * showing the command name as a heading and truncated content that can be
5676
+ * SlashCommandComponent - renders a "• skill /name" or "• command /name" block for slash command messages
5677
+ * showing the command name as a heading; the content stays hidden until
5384
5678
  * expanded with ctrl+e. The full content is still sent to the assistant.
5385
5679
  */
5386
- const MAX_COLLAPSED_LINES$1 = 3;
5387
- const getBorderColor = () => mastra.green;
5388
5680
  var SlashCommandComponent = class extends WidthAwareContainer {
5389
5681
  commandName;
5390
5682
  contentLines;
@@ -5405,14 +5697,17 @@ var SlashCommandComponent = class extends WidthAwareContainer {
5405
5697
  }
5406
5698
  rebuildForWidth(termWidth) {
5407
5699
  this.clear();
5408
- const border = (char) => chalk.bold.hex(getBorderColor())(char);
5409
- const maxLineWidth = Math.max(1, termWidth - 6 - 0);
5410
- const heading = chalk.hex(mastra.specialGray)(`/${this.commandName}`);
5411
- if (this.contentLines.length === 0) {
5412
- this.addChild(new Text(`${border("╰──")} ${heading}`, 0, 0));
5700
+ const width = Math.max(1, termWidth - 0);
5701
+ const maxLineWidth = Math.max(1, width - 3);
5702
+ const dot = statusDot("done");
5703
+ const isSkill = this.commandName.startsWith("skill/");
5704
+ const name = isSkill ? this.commandName.slice(6) : this.commandName;
5705
+ const heading = `${theme.bold(theme.fg("success", isSkill ? "skill" : "command"))} ${theme.fg("muted", `/${name}`)}`;
5706
+ const block = (output) => this.addChild(new Text(toolBlock(dot, heading, output, width).join("\n"), 0, 0));
5707
+ if (this.contentLines.length === 0 || !this.expanded) {
5708
+ block([]);
5413
5709
  return;
5414
5710
  }
5415
- this.addChild(new Text(`${border("╭──")}`, 0, 0));
5416
5711
  const wrappedLines = [];
5417
5712
  for (const line of this.contentLines) if (line.length > maxLineWidth) {
5418
5713
  let remaining = line;
@@ -5424,14 +5719,7 @@ var SlashCommandComponent = class extends WidthAwareContainer {
5424
5719
  }
5425
5720
  if (remaining) wrappedLines.push(remaining);
5426
5721
  } else wrappedLines.push(line);
5427
- const truncated = !this.expanded && wrappedLines.length > 4;
5428
- const contentText = (truncated ? wrappedLines.slice(0, MAX_COLLAPSED_LINES$1) : wrappedLines).map((line) => `${border("│")} ${chalk.hex(mastra.mainGray)(line.length > maxLineWidth ? line.slice(0, maxLineWidth - 1) + "…" : line)}`).join("\n");
5429
- this.addChild(new Text(contentText, 0, 0));
5430
- if (truncated) {
5431
- const moreText = chalk.hex(mastra.darkGray)(`... ${wrappedLines.length - MAX_COLLAPSED_LINES$1} more lines (ctrl+e to expand)`);
5432
- this.addChild(new Text(`${border("│")} ${moreText}`, 0, 0));
5433
- }
5434
- this.addChild(new Text(`${border("╰──")} ${heading}`, 0, 0));
5722
+ block(wrappedLines.map((line) => chalk.hex(mastra.mainGray)(line.length > maxLineWidth ? line.slice(0, maxLineWidth - 1) + "…" : line)));
5435
5723
  }
5436
5724
  getChatSpacingKind() {
5437
5725
  return "other";
@@ -5441,7 +5729,7 @@ var SlashCommandComponent = class extends WidthAwareContainer {
5441
5729
  //#region src/tui/components/account-switch-notice.ts
5442
5730
  /**
5443
5731
  * One-line transcript notice for persisted `data-mastracode-account-switch`
5444
- * parts: account rotations, pool exhaustion, and starting-on-account notices.
5732
+ * parts: account rotations and pool exhaustion.
5445
5733
  * Modeled on OMMarkerComponent (single themed Text row). The copy comes from
5446
5734
  * the SDK's shared formatter so live `info`-event lines and history-reloaded
5447
5735
  * part lines read identically.
@@ -5510,6 +5798,8 @@ var AskQuestionBorderedBox = class {
5510
5798
  answerIsNegative = false;
5511
5799
  /** True when created during streaming, before activate() is called */
5512
5800
  streaming = false;
5801
+ /** Rows the card last took while waiting for an answer; the settled card keeps this height. */
5802
+ liveHeight = 0;
5513
5803
  constructor(questionLines, hintText, items, selectList, input, streaming) {
5514
5804
  this.questionLines = questionLines;
5515
5805
  this.hintText = hintText;
@@ -5558,99 +5848,95 @@ var AskQuestionBorderedBox = class {
5558
5848
  if (this.answered) {
5559
5849
  if (this.cachedLines && this.cachedWidth === width && this.cachedThemeGeneration === getThemeGeneration()) return this.cachedLines;
5560
5850
  const result = this.renderSafe(width);
5851
+ while (result.length < this.liveHeight) result.push("");
5561
5852
  this.cachedLines = result;
5562
5853
  this.cachedWidth = width;
5563
5854
  this.cachedThemeGeneration = getThemeGeneration();
5564
5855
  return result;
5565
5856
  }
5566
- return this.renderSafe(width);
5857
+ const result = this.renderSafe(width);
5858
+ this.liveHeight = result.length;
5859
+ return result;
5567
5860
  }
5568
5861
  renderSafe(width) {
5569
5862
  try {
5570
5863
  return this._render(width);
5571
5864
  } catch {
5572
- return [
5573
- "" + theme.fg("dim", "╭──── Question ────╮"),
5574
- "" + theme.fg("dim", "│ (render error) │"),
5575
- "" + theme.fg("dim", "╰──────────────────╯")
5576
- ];
5865
+ return card(theme.getTheme().dim, [theme.fg("dim", "(render error)")]);
5577
5866
  }
5578
5867
  }
5868
+ /**
5869
+ * Borderless card with a colored left bar (▎):
5870
+ * - waiting for an answer: accent bar, bold question, ❯ options, hint
5871
+ * - answered: muted question with the options frozen (✓ on the chosen ones), or "✓ answer" for
5872
+ * free text; a card answered live keeps the height it had while waiting (see render)
5873
+ */
5579
5874
  _render(width) {
5580
- const border = (s) => theme.fg("dim", s);
5581
- const innerWidth = Math.max(1, width - 0 - 4);
5582
- const boxWidth = innerWidth + 4;
5583
- const lines = [];
5584
- lines.push("" + border(`╭${"─".repeat(boxWidth - 2)}╮`));
5585
- const addLine = (content, contentVisWidth) => {
5586
- let finalContent = content;
5587
- let finalWidth = contentVisWidth;
5588
- if (finalWidth > innerWidth) {
5589
- finalContent = truncateToWidth(content, innerWidth);
5590
- finalWidth = visibleWidth(finalContent);
5591
- }
5592
- const pad = Math.max(0, innerWidth - finalWidth);
5593
- lines.push("" + border("│") + " " + finalContent + " ".repeat(pad) + " " + border("│"));
5594
- };
5595
- const header = theme.bold(theme.fg("accent", "Question"));
5596
- addLine(header, visibleWidth(header));
5597
- for (const qLine of this.questionLines) {
5598
- const wrapped = wrapTextWithAnsi(qLine, innerWidth);
5599
- for (const wLine of wrapped) addLine(theme.fg("text", wLine), visibleWidth(wLine));
5600
- }
5601
- addLine("", 0);
5602
- const continuationPrefix = " ";
5603
- const addWrappedOptionLine = (prefix, label, style) => {
5604
- const prefixVis = visibleWidth(prefix);
5605
- wrapTextWithAnsi(label, Math.max(1, innerWidth - prefixVis)).forEach((line, index) => {
5606
- const linePrefix = index === 0 ? prefix : continuationPrefix;
5607
- const content = `${linePrefix}${style(line)}`;
5608
- addLine(content, visibleWidth(linePrefix) + visibleWidth(line));
5609
- });
5610
- };
5611
- if (this.streaming) {
5612
- const dim = (s) => theme.fg("dim", s);
5613
- for (const item of this.items) addWrappedOptionLine(continuationPrefix, item.label, dim);
5614
- const waiting = theme.fg("dim", "…");
5615
- addLine(waiting, visibleWidth(waiting));
5616
- } else if (this.answered && this.items.length > 0) {
5617
- const dim = (s) => theme.fg("dim", s);
5618
- if (this.cancelled) {
5619
- for (const item of this.items) addWrappedOptionLine(continuationPrefix, item.label, dim);
5620
- const cancelLine = `${theme.fg("error", "✗")} ${theme.fg("dim", "(cancelled)")}`;
5621
- addLine(cancelLine, visibleWidth(cancelLine));
5622
- } else {
5623
- const text = (s) => theme.fg("text", s);
5624
- for (const item of this.items) if (this.selectedValues ? this.selectedValues.includes(item.label) : item.label === this.selectedValue) addWrappedOptionLine(`${this.answerIsNegative ? theme.fg("error", "✗") : theme.fg("success", "✓")} `, item.label, text);
5625
- else addWrappedOptionLine(continuationPrefix, item.label, dim);
5626
- }
5627
- addLine("", 0);
5628
- } else if (this.answered && this.selectedValue != null) {
5629
- const iconPrefix = `${this.answerIsNegative ? theme.fg("error", "✗") : theme.fg("success", "✓")} `;
5630
- const continuationPrefix = " ";
5631
- wrapTextWithAnsi(this.selectedValue, Math.max(1, innerWidth - visibleWidth(iconPrefix))).forEach((line, index) => {
5632
- const prefix = index === 0 ? iconPrefix : continuationPrefix;
5633
- const content = `${prefix}${theme.fg("text", line)}`;
5634
- addLine(content, visibleWidth(prefix) + visibleWidth(line));
5635
- });
5636
- } else if (this.answered && this.cancelled) {
5637
- const cancelLine = `${theme.fg("error", "✗")} ${theme.fg("dim", "(cancelled)")}`;
5638
- addLine(cancelLine, visibleWidth(cancelLine));
5639
- } else {
5640
- if (this.selectList) {
5641
- const selectLines = this.selectList.render(innerWidth);
5642
- for (const sLine of selectLines) addLine(sLine, visibleWidth(sLine));
5643
- } else if (this.input) {
5644
- const inputLines = this.input.render(innerWidth);
5645
- for (const iLine of inputLines) addLine(iLine, visibleWidth(iLine));
5646
- }
5647
- const hint = theme.fg("dim", this.hintText);
5648
- addLine(hint, visibleWidth(hint));
5875
+ const t = theme.getTheme();
5876
+ const innerWidth = Math.max(1, width - 0 - 2);
5877
+ const wrap = (text, style) => wrapTextWithAnsi(text, innerWidth).map((line) => style(line));
5878
+ const question = (style) => this.questionLines.flatMap((line) => line ? wrap(line, style) : [""]);
5879
+ const out = (color, lines) => card(color, lines.map((line) => truncateToWidth(line, innerWidth))).map((line) => "" + line);
5880
+ const icon = this.answerIsNegative ? theme.fg("error", "✗") : theme.fg("success", "✓");
5881
+ if (this.streaming) return out(t.dim, [
5882
+ ...question((s) => theme.bold(theme.fg("text", s))),
5883
+ "",
5884
+ ...this.items.flatMap((item) => wrap(item.label, (s) => theme.fg("dim", ` ${s}`))),
5885
+ theme.fg("dim", "…")
5886
+ ]);
5887
+ if (this.answered) {
5888
+ const asked = question((s) => theme.fg("muted", s));
5889
+ const cancelledLine = `${theme.fg("error", "✗")} ${theme.fg("dim", "(cancelled)")}`;
5890
+ if (this.items.length > 0) {
5891
+ const chosen = new Set(this.selectedValues ?? (this.selectedValue != null ? [this.selectedValue] : []));
5892
+ const optionLines = this.items.flatMap((item) => {
5893
+ const picked = !this.cancelled && chosen.has(item.label);
5894
+ return wrapTextWithAnsi(item.label, Math.max(1, innerWidth - 2)).map((line, j) => `${picked && j === 0 ? icon : " "} ${theme.fg(picked ? "secondary" : "dim", line)}`);
5895
+ });
5896
+ return out(t.border, [
5897
+ ...asked,
5898
+ "",
5899
+ ...optionLines,
5900
+ ...this.cancelled ? [cancelledLine] : []
5901
+ ]);
5902
+ }
5903
+ if (this.cancelled) return out(t.border, [
5904
+ ...asked,
5905
+ "",
5906
+ cancelledLine
5907
+ ]);
5908
+ const answers = this.selectedValues ?? (this.selectedValue != null ? [this.selectedValue] : []);
5909
+ if (answers.length === 0) return out(t.border, asked);
5910
+ const answerLines = answers.flatMap((answer, i) => wrapTextWithAnsi(answer, Math.max(1, innerWidth - 2)).map((line, j) => `${j === 0 ? icon : " "} ${theme.fg("secondary", line)}`));
5911
+ return out(t.border, [
5912
+ ...asked,
5913
+ "",
5914
+ ...answerLines
5915
+ ]);
5649
5916
  }
5650
- lines.push("" + border(`╰${"─".repeat(boxWidth - 2)}╯`));
5651
- return lines;
5917
+ const body = this.selectList ? this.selectList.render(innerWidth) : this.input ? this.input.render(innerWidth) : [];
5918
+ return out(t.accent, [
5919
+ ...question((s) => theme.bold(theme.fg("text", s))),
5920
+ "",
5921
+ ...body,
5922
+ "",
5923
+ theme.fg("dim", this.hintText)
5924
+ ]);
5652
5925
  }
5653
5926
  };
5927
+ /** Option list styling shared by inline prompts: accent ❯ cursor, bold selected label, ●/○ toggles. */
5928
+ const INLINE_CARD_LIST_STYLE = {
5929
+ get cursor() {
5930
+ return theme.fg("accent", "❯ ");
5931
+ },
5932
+ get checked() {
5933
+ return theme.fg("accent", "●") + " ";
5934
+ },
5935
+ get unchecked() {
5936
+ return theme.fg("dim", "○") + " ";
5937
+ },
5938
+ selectedLabel: (label) => theme.bold(label)
5939
+ };
5654
5940
  var AskQuestionInlineComponent = class AskQuestionInlineComponent extends Container {
5655
5941
  borderedBox;
5656
5942
  selectList;
@@ -5789,7 +6075,7 @@ var AskQuestionInlineComponent = class AskQuestionInlineComponent extends Contai
5789
6075
  value: AskQuestionInlineComponent.CUSTOM_RESPONSE_VALUE,
5790
6076
  label: theme.fg("dim", "✎ Custom response...")
5791
6077
  });
5792
- this.selectList = new WrappingSelectList(items, Math.min(items.length, 8), getSelectListTheme(), this.multiSelect);
6078
+ this.selectList = new WrappingSelectList(items, Math.min(items.length, 8), getSelectListTheme(), this.multiSelect, INLINE_CARD_LIST_STYLE);
5793
6079
  if (this.multiSelect) this.selectList.onConfirmMulti = (selected) => {
5794
6080
  this.handleMultiAnswer(selected.map((item) => item.value));
5795
6081
  };
@@ -5945,16 +6231,13 @@ var JudgeDisplayComponent = class extends WidthAwareContainer {
5945
6231
  this.clear();
5946
6232
  const border = (char) => chalk.hex(JUDGE_COLOR)(char);
5947
6233
  const title = chalk.hex(JUDGE_COLOR).bold("Goal");
5948
- const innerWidth = Math.max(20, termWidth - 0 - 4);
5949
- const horizontal = "─".repeat(innerWidth + 1);
5950
- this.addChild(new Text(`${border("╭")}${border(horizontal)}${border("╮")}`, 0, 0));
6234
+ const innerWidth = Math.max(20, termWidth - 0 - 2);
5951
6235
  this.addChild(new Text(this.renderRow(this.renderHeader(title), innerWidth, border), 0, 0));
5952
6236
  if (!this.result && this.activity.length === 0 && !this.streamingReason) this.addChild(new Text(this.renderRow(chalk.dim("evaluating…"), innerWidth, border), 0, 0));
5953
6237
  for (const line of this.activity) this.addChild(new Text(this.renderRow(this.renderActivityLine(line), innerWidth, border), 0, 0));
5954
6238
  if (this.activity.length > 0 && (this.result || this.streamingReason)) this.addChild(new Text(this.renderRow("", innerWidth, border), 0, 0));
5955
6239
  const reason = stripControlChars(this.result?.reason ?? this.streamingReason);
5956
6240
  if (reason) for (const line of this.wrapLine(reason, innerWidth)) this.addChild(new Text(this.renderRow(chalk.dim(line), innerWidth, border), 0, 0));
5957
- this.addChild(new Text(`${border("╰")}${border(horizontal)}${border("╯")}`, 0, 0));
5958
6241
  }
5959
6242
  renderActivityLine(line) {
5960
6243
  const toolName = getActivityToolName(line);
@@ -5969,9 +6252,9 @@ var JudgeDisplayComponent = class extends WidthAwareContainer {
5969
6252
  const turnInfo = this.maxTurns > 0 ? chalk.hex(MUTED_COLOR)(`(${this.turnsUsed}/${this.maxTurns})`) : "";
5970
6253
  return `${title} ${decisionIcon} ${decisionText}${turnInfo ? ` ${turnInfo}` : ""}`;
5971
6254
  }
6255
+ /** A row of the left-bar card (`width` is the room for text after the bar). */
5972
6256
  renderRow(text, width, border) {
5973
- const content = this.padLine(text, width);
5974
- return `${border("│")} ${content}${border("│")}`;
6257
+ return text ? `${border("▎")} ${this.padLine(text, width)}` : border("▎");
5975
6258
  }
5976
6259
  wrapLine(text, width) {
5977
6260
  const lines = [];
@@ -6047,9 +6330,6 @@ function priorityColor(priority) {
6047
6330
  }
6048
6331
  const MAX_NOTIFICATION_CONTENT_WIDTH = 100;
6049
6332
  const MIN_NOTIFICATION_CONTENT_WIDTH = 24;
6050
- function padLine$1(value, width) {
6051
- return value + " ".repeat(Math.max(0, width - visibleWidth(value)));
6052
- }
6053
6333
  function splitLongWord(word, maxWidth) {
6054
6334
  const segments = [];
6055
6335
  let current = "";
@@ -6131,7 +6411,7 @@ var NotificationComponent = class extends WidthAwareContainer {
6131
6411
  options.status
6132
6412
  ].filter(Boolean).join(" · ");
6133
6413
  const message = options.message.trim();
6134
- const maxContentWidth = Math.max(MIN_NOTIFICATION_CONTENT_WIDTH, Math.min(MAX_NOTIFICATION_CONTENT_WIDTH, width - 0 - 4));
6414
+ const maxContentWidth = Math.max(MIN_NOTIFICATION_CONTENT_WIDTH, Math.min(MAX_NOTIFICATION_CONTENT_WIDTH, width - 0 - 2));
6135
6415
  const titleLines = wrapText(titleText, maxContentWidth);
6136
6416
  const detailLines = details ? wrapText(details, maxContentWidth) : [];
6137
6417
  const messageLines = message ? this.limitMessageLines(wrapText(message, maxContentWidth), quiet, maxContentWidth) : [];
@@ -6140,22 +6420,13 @@ var NotificationComponent = class extends WidthAwareContainer {
6140
6420
  options.backgroundCompletion.argsSummary ? `invocation · ${options.backgroundCompletion.argsSummary}` : void 0,
6141
6421
  options.backgroundCompletion.errorSummary ? `failure · ${options.backgroundCompletion.errorSummary}` : void 0
6142
6422
  ].filter((line) => Boolean(line)).flatMap((line) => wrapText(line, maxContentWidth)) : [];
6143
- const allLines = [
6144
- ...titleLines,
6145
- ...detailLines,
6146
- ...messageLines,
6147
- ...backgroundDetailLines
6423
+ const lines = [
6424
+ ...titleLines.map((line) => chalk.hex(priorityColor(options.priority)).bold(line)),
6425
+ ...detailLines.map((line) => theme.fg("dim", line)),
6426
+ ...messageLines.map((line) => theme.fg("thinkingText", line)),
6427
+ ...backgroundDetailLines.map((line) => theme.fg("dim", line))
6148
6428
  ];
6149
- const contentWidth = Math.max(...allLines.map((line) => visibleWidth(line)), 1);
6150
- const borderColor = chalk.hex(mastra.blue);
6151
- const top = `╭${"─".repeat(contentWidth + 2)}╮`;
6152
- const bottom = `╰${"─".repeat(contentWidth + 2)}╯`;
6153
- this.addChild(new Text(borderColor(top), 0, 0));
6154
- for (const line of titleLines) this.addChild(new Text(`${borderColor("│")} ${chalk.hex(priorityColor(options.priority)).bold(padLine$1(line, contentWidth))} ${borderColor("│")}`, 0, 0));
6155
- for (const line of detailLines) this.addChild(new Text(`${borderColor("│")} ${theme.fg("dim", padLine$1(line, contentWidth))} ${borderColor("│")}`, 0, 0));
6156
- for (const line of messageLines) this.addChild(new Text(`${borderColor("│")} ${padLine$1(line, contentWidth)} ${borderColor("│")}`, 0, 0));
6157
- for (const line of backgroundDetailLines) this.addChild(new Text(`${borderColor("│")} ${theme.fg("dim", padLine$1(line, contentWidth))} ${borderColor("│")}`, 0, 0));
6158
- this.addChild(new Text(borderColor(bottom), 0, 0));
6429
+ this.addChild(new Text(card(mastra.blue, lines).join("\n"), 0, 0));
6159
6430
  }
6160
6431
  limitMessageLines(lines, quiet, maxWidth) {
6161
6432
  if (!quiet || lines.length <= this.quietPreviewLineLimit) return lines;
@@ -6267,10 +6538,10 @@ function formatMarker(data) {
6267
6538
  //#endregion
6268
6539
  //#region src/tui/components/om-output.ts
6269
6540
  /**
6270
- * TUI component for rendering OM observation/reflection output in a bordered box.
6541
+ * TUI component for rendering OM observation/reflection output as a tool-style block.
6271
6542
  * Uses observer (amber) color for observations and reflector (red) color for reflections.
6272
6543
  * Collapsed to COLLAPSED_LINES by default, expandable with ctrl+e.
6273
- * Includes marker info (emoji, compression stats) in the footer.
6544
+ * The title row carries the compression stats; the observations sit on a panel below.
6274
6545
  */
6275
6546
  const getObserverColor$1 = () => mastra.orange;
6276
6547
  const getReflectorColor$1 = () => mastra.red;
@@ -6280,33 +6551,6 @@ function formatTokens$1(tokens) {
6280
6551
  const k = tokens / 1e3;
6281
6552
  return k % 1 === 0 ? `${k}k` : `${k.toFixed(1)}k`;
6282
6553
  }
6283
- /** Truncate a string with ANSI codes to a visible width */
6284
- function truncateAnsi$1(str, maxWidth) {
6285
- const ansiRegex = /\x1b\[[0-9;]{0,32}m/g;
6286
- let visibleLength = 0;
6287
- let result = "";
6288
- let lastIndex = 0;
6289
- let match;
6290
- while ((match = ansiRegex.exec(str)) !== null) {
6291
- const textBefore = str.slice(lastIndex, match.index);
6292
- for (const char of textBefore) {
6293
- if (visibleLength >= maxWidth) break;
6294
- result += char;
6295
- visibleLength++;
6296
- }
6297
- if (visibleLength >= maxWidth) break;
6298
- result += match[0];
6299
- lastIndex = match.index + match[0].length;
6300
- }
6301
- const remaining = str.slice(lastIndex);
6302
- for (const char of remaining) {
6303
- if (visibleLength >= maxWidth) break;
6304
- result += char;
6305
- visibleLength++;
6306
- }
6307
- if (visibleLength >= maxWidth) result += "\x1B[0m";
6308
- return result;
6309
- }
6310
6554
  /**
6311
6555
  * Soft-wrap an array of lines to fit within maxWidth, preserving words where possible.
6312
6556
  * Returns groups where each group corresponds to one original line split into wrapped segments.
@@ -6359,14 +6603,13 @@ var OMOutputComponent = class extends WidthAwareContainer {
6359
6603
  rebuildForWidth(termWidth) {
6360
6604
  this.clear();
6361
6605
  const color = this.data.type === "reflection" ? getReflectorColor$1() : getObserverColor$1();
6362
- const border = (char) => chalk.bold.hex(color)(char);
6363
- const maxLineWidth = termWidth - 6 - 0;
6606
+ const width = Math.max(1, termWidth - 0);
6607
+ const maxLineWidth = width - 4;
6364
6608
  const originalLines = this.data.observations.split("\n");
6365
6609
  const { groups, flat: wrappedLines } = softWrapLines(originalLines, maxLineWidth);
6366
6610
  const originalLineCount = originalLines.length;
6367
6611
  const wrappedLineCount = wrappedLines.length;
6368
6612
  const footerText = this.buildFooterText(color);
6369
- this.addChild(new Text(border("╭──"), 0, 0));
6370
6613
  let truncated = false;
6371
6614
  const borderedLines = [];
6372
6615
  if (!this.expanded && wrappedLineCount > 11) {
@@ -6388,40 +6631,31 @@ var OMOutputComponent = class extends WidthAwareContainer {
6388
6631
  }
6389
6632
  truncated = tailGroupStart - headGroupCount > 0;
6390
6633
  if (truncated) {
6391
- for (const line of headLines) borderedLines.push(border("│") + " " + chalk.hex(mastra.specialGray)(line));
6392
- borderedLines.push(border("│") + " " + chalk.hex(mastra.mainGray)(`... ${originalLineCount} lines total (ctrl+e to expand)`));
6393
- for (const line of tailLines) borderedLines.push(border("│") + " " + chalk.hex(mastra.specialGray)(line));
6394
- } else for (const line of wrappedLines) borderedLines.push(border("│") + " " + chalk.hex(mastra.specialGray)(line));
6395
- } else for (const line of wrappedLines) borderedLines.push(border("│") + " " + chalk.hex(mastra.specialGray)(line));
6396
- const displayOutput = borderedLines.join("\n");
6397
- if (displayOutput.trim()) this.addChild(new Text(displayOutput, 0, 0));
6398
- if (this.data.currentTask && (this.expanded || !truncated)) {
6399
- const taskLine = border("│") + " " + chalk.hex(color).bold("Current task: ") + chalk.hex(mastra.specialGray)(this.data.currentTask);
6400
- this.addChild(new Text(truncateAnsi$1(taskLine, termWidth - 2 - 0), 0, 0));
6401
- }
6402
- if (this.data.suggestedResponse && (this.expanded || !truncated)) {
6403
- const sugLine = border("│") + " " + chalk.hex(color).bold("Suggested response: ") + chalk.hex(mastra.specialGray)(this.data.suggestedResponse);
6404
- this.addChild(new Text(truncateAnsi$1(sugLine, termWidth - 2 - 0), 0, 0));
6405
- }
6406
- this.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
6634
+ for (const line of headLines) borderedLines.push(chalk.hex(mastra.specialGray)(line));
6635
+ borderedLines.push(chalk.hex(mastra.mainGray)(`... ${originalLineCount} lines total (ctrl+e to expand)`));
6636
+ for (const line of tailLines) borderedLines.push(chalk.hex(mastra.specialGray)(line));
6637
+ } else for (const line of wrappedLines) borderedLines.push(chalk.hex(mastra.specialGray)(line));
6638
+ } else for (const line of wrappedLines) borderedLines.push(chalk.hex(mastra.specialGray)(line));
6639
+ if (this.data.currentTask && (this.expanded || !truncated)) borderedLines.push(chalk.hex(color).bold("Current task: ") + chalk.hex(mastra.specialGray)(this.data.currentTask));
6640
+ if (this.data.suggestedResponse && (this.expanded || !truncated)) borderedLines.push(chalk.hex(color).bold("Suggested response: ") + chalk.hex(mastra.specialGray)(this.data.suggestedResponse));
6641
+ const output = borderedLines.some((line) => line.trim()) ? borderedLines : [];
6642
+ this.addChild(new Text(toolBlock(chalk.hex(color)("•"), footerText, output, width).join("\n"), 0, 0));
6407
6643
  }
6408
6644
  buildFooterText(color) {
6409
- const isReflection = this.data.type === "reflection";
6410
- const emoji = "🧠";
6411
- if (isReflection) {
6645
+ if (this.data.type === "reflection") {
6412
6646
  const observed = formatTokens$1(this.data.tokensObserved ?? 0);
6413
6647
  const compressed = formatTokens$1(this.data.compressedTokens ?? this.data.observationTokens ?? 0);
6414
6648
  const ratio = (this.data.tokensObserved ?? 0) > 0 && (this.data.compressedTokens ?? this.data.observationTokens ?? 0) > 0 ? `${Math.round((this.data.tokensObserved ?? 0) / (this.data.compressedTokens ?? this.data.observationTokens ?? 1))}x` : "";
6415
6649
  const durationStr = this.data.durationMs ? ` in ${(this.data.durationMs / 1e3).toFixed(1)}s` : "";
6416
6650
  const ratioStr = ratio ? ` (${ratio} compression)` : "";
6417
- return `${emoji} ${chalk.hex(color)(`Reflected: ${observed} → ${compressed} tokens${ratioStr}${durationStr}`)} ${chalk.hex(mastra.green)("✓")}`;
6651
+ return `${chalk.hex(color)(`Reflected: ${observed} → ${compressed} tokens${ratioStr}${durationStr}`)}`;
6418
6652
  } else {
6419
6653
  const observed = formatTokens$1(this.data.tokensObserved ?? 0);
6420
6654
  const compressed = formatTokens$1(this.data.observationTokens ?? 0);
6421
6655
  const ratio = (this.data.tokensObserved ?? 0) > 0 && (this.data.observationTokens ?? 0) > 0 ? `${Math.round((this.data.tokensObserved ?? 0) / (this.data.observationTokens ?? 1))}x` : "";
6422
6656
  const durationStr = this.data.durationMs ? ` in ${(this.data.durationMs / 1e3).toFixed(1)}s` : "";
6423
6657
  const ratioStr = ratio ? ` (${ratio} compression)` : "";
6424
- return `${emoji} ${chalk.hex(color)(`Observed: ${observed} → ${compressed} tokens${ratioStr}${durationStr}`)} ${chalk.hex(mastra.green)("✓")}`;
6658
+ return `${chalk.hex(color)(`Observed: ${observed} → ${compressed} tokens${ratioStr}${durationStr}`)}`;
6425
6659
  }
6426
6660
  }
6427
6661
  getChatSpacingKind() {
@@ -6454,26 +6688,11 @@ var PlanContentBox = class {
6454
6688
  }
6455
6689
  render(width) {
6456
6690
  if (this.cachedLines && this.cachedWidth === width && this.cachedThemeGeneration === getThemeGeneration()) return this.cachedLines;
6457
- const availableWidth = Math.max(24, width - 0);
6458
- const innerWidth = Math.max(20, availableWidth - 4);
6691
+ const innerWidth = Math.max(20, width - 0);
6459
6692
  const rendered = new Markdown(this.plan, 0, 0, getMarkdownTheme(), { color: (text) => theme.fg("text", text) }).render(innerWidth).flatMap((line) => line.length > 0 ? [line] : [""]);
6460
- const border = (text) => chalk.hex(mastra.purple)(text);
6461
- const top = `${border("╭")}${border("─".repeat(innerWidth + 2))}${border("╮")}`;
6462
- const bottom = `${border("╰")}${border("─".repeat(innerWidth + 2))}${border("╯")}`;
6463
6693
  const body = [];
6464
- for (const line of rendered) {
6465
- const wrapped = wrapTextWithAnsi(line, innerWidth);
6466
- for (const chunk of wrapped) {
6467
- const chunkVis = visibleWidth(chunk);
6468
- const padding = " ".repeat(Math.max(0, innerWidth - chunkVis));
6469
- body.push(`${border("│")} ${chunk}${padding} ${border("│")}`);
6470
- }
6471
- }
6472
- this.cachedLines = [
6473
- top,
6474
- ...body,
6475
- bottom
6476
- ];
6694
+ for (const line of rendered) body.push(...wrapTextWithAnsi(line, innerWidth).map((chunk) => chunk.replace(/\s+$/, "")));
6695
+ this.cachedLines = body;
6477
6696
  this.cachedWidth = width;
6478
6697
  this.cachedThemeGeneration = getThemeGeneration();
6479
6698
  return this.cachedLines;
@@ -6498,11 +6717,7 @@ var PlanDiffBox = class {
6498
6717
  }
6499
6718
  render(width) {
6500
6719
  if (this.cachedLines && this.cachedWidth === width && this.cachedThemeGeneration === getThemeGeneration()) return this.cachedLines;
6501
- const availableWidth = Math.max(24, width - 0);
6502
- const innerWidth = Math.max(20, availableWidth - 4);
6503
- const border = (text) => chalk.hex(mastra.purple)(text);
6504
- const top = `${border("╭")}${border("─".repeat(innerWidth + 2))}${border("╮")}`;
6505
- const bottom = `${border("╰")}${border("─".repeat(innerWidth + 2))}${border("╯")}`;
6720
+ const innerWidth = Math.max(20, width - 0);
6506
6721
  const removedColor = chalk.hex(mastra.red);
6507
6722
  const addedColor = chalk.hex(theme.getTheme().success);
6508
6723
  const body = [];
@@ -6513,23 +6728,25 @@ var PlanDiffBox = class {
6513
6728
  const wrappedChunks = wrapTextWithAnsi(entry.text, textWidth);
6514
6729
  for (let ci = 0; ci < wrappedChunks.length; ci++) {
6515
6730
  const content = colorFn(`${ci === 0 ? prefix : " "}${wrappedChunks[ci]}`);
6516
- const contentVis = visibleWidth(content);
6517
- const padding = " ".repeat(Math.max(0, innerWidth - contentVis));
6518
- body.push(`${border("│")} ${content}${padding} ${border("│")}`);
6731
+ body.push(content);
6519
6732
  }
6520
6733
  }
6521
- this.cachedLines = [
6522
- top,
6523
- ...body,
6524
- bottom
6525
- ];
6734
+ this.cachedLines = body;
6526
6735
  this.cachedWidth = width;
6527
6736
  this.cachedThemeGeneration = getThemeGeneration();
6528
6737
  return this.cachedLines;
6529
6738
  }
6530
6739
  };
6740
+ /** Plan prompts sit inside a left bar in the plan-mode color, like the other inline prompts. */
6741
+ function withPlanBar(lines) {
6742
+ const bar = chalk.hex(displayModeColor(mastra.purple))("▎");
6743
+ return lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "").trim() === "" ? bar : `${bar} ${line}`);
6744
+ }
6531
6745
  var PlanApprovalInlineComponent = class PlanApprovalInlineComponent extends Container {
6532
6746
  ui;
6747
+ render(width) {
6748
+ return withPlanBar(super.render(Math.max(1, width - 2)));
6749
+ }
6533
6750
  contentBox;
6534
6751
  selectList;
6535
6752
  onApprove;
@@ -6607,15 +6824,15 @@ var PlanApprovalInlineComponent = class PlanApprovalInlineComponent extends Cont
6607
6824
  const items = [
6608
6825
  {
6609
6826
  value: "approve",
6610
- label: ` ${theme.fg("success", "Approve")} ${theme.fg("dim", "— switch to Build mode and implement")}`
6827
+ label: `${theme.fg("text", "Approve")} ${theme.fg("dim", "switch to Build mode and implement")}`
6611
6828
  },
6612
6829
  {
6613
6830
  value: "goal",
6614
- label: ` ${theme.fg("success", "Use as /goal")} ${theme.fg("dim", "— switch to Build mode and pursue this plan")}`
6831
+ label: `${theme.fg("text", "Use as /goal")} ${theme.fg("dim", "switch to Build mode and pursue this plan")}`
6615
6832
  },
6616
6833
  {
6617
6834
  value: "changes",
6618
- label: ` ${theme.fg("warning", "Request changes")} ${theme.fg("dim", "— reject and provide feedback via chat")}`
6835
+ label: `${theme.fg("text", "Request changes")} ${theme.fg("dim", "reject and give feedback in chat")}`
6619
6836
  }
6620
6837
  ];
6621
6838
  this.selectList = new SelectList(items, items.length, getSelectListTheme());
@@ -6627,7 +6844,7 @@ var PlanApprovalInlineComponent = class PlanApprovalInlineComponent extends Cont
6627
6844
  };
6628
6845
  this.contentBox.addChild(this.selectList);
6629
6846
  this.contentBox.addChild(new Spacer(1));
6630
- this.contentBox.addChild(new Text(theme.fg("dim", "Up/Down navigate Enter select Esc reject"), 0, 0));
6847
+ this.contentBox.addChild(new Text(theme.fg("dim", "↑↓ select · Enter confirm · Esc reject"), 0, 0));
6631
6848
  }
6632
6849
  renderStreaming() {
6633
6850
  this.contentBox.clear();
@@ -6637,7 +6854,8 @@ var PlanApprovalInlineComponent = class PlanApprovalInlineComponent extends Cont
6637
6854
  this.contentBox.addChild(new Text(theme.fg("dim", "Submitting plan…"), 0, 0));
6638
6855
  }
6639
6856
  renderPlanHeader(prefix = "") {
6640
- this.contentBox.addChild(new Text(`${prefix}${theme.bold(theme.fg("accent", `Plan: ${this.planTitle}`))}`, 0, 0));
6857
+ const plan = chalk.bold.hex(displayModeColor(mastra.purple));
6858
+ this.contentBox.addChild(new Text(`${prefix}${plan("Plan:")} ${theme.bold(theme.fg("text", this.planTitle))}`, 0, 0));
6641
6859
  if (this.planFilename) this.contentBox.addChild(new Text(theme.fg("dim", this.planFilename), 0, 0));
6642
6860
  this.contentBox.addChild(new Spacer(1));
6643
6861
  }
@@ -6695,6 +6913,9 @@ var PlanApprovalInlineComponent = class PlanApprovalInlineComponent extends Cont
6695
6913
  }
6696
6914
  };
6697
6915
  var PlanResultComponent = class extends Container {
6916
+ render(width) {
6917
+ return withPlanBar(super.render(Math.max(1, width - 2)));
6918
+ }
6698
6919
  getChatSpacingKind() {
6699
6920
  return "plan";
6700
6921
  }
@@ -6704,7 +6925,7 @@ var PlanResultComponent = class extends Container {
6704
6925
  this.addChild(contentBox);
6705
6926
  const icon = options.isApproved ? theme.fg("success", "✓") : theme.fg("error", "✗");
6706
6927
  const status = options.isApproved ? "Approved" : options.feedback ? "Changes requested" : "Rejected";
6707
- contentBox.addChild(new Text(theme.bold(theme.fg("accent", `Plan: ${options.title}`)), 0, 0));
6928
+ contentBox.addChild(new Text(`${chalk.bold.hex(displayModeColor(mastra.purple))("Plan:")} ${theme.bold(theme.fg("text", options.title))}`, 0, 0));
6708
6929
  if (options.planFilename) contentBox.addChild(new Text(theme.fg("dim", options.planFilename), 0, 0));
6709
6930
  contentBox.addChild(new Spacer(1));
6710
6931
  contentBox.addChild(new PlanContentBox(options.plan));
@@ -6962,21 +7183,25 @@ var SubagentExecutionComponent = class extends WidthAwareContainer {
6962
7183
  }
6963
7184
  rebuildForWidth(termWidth) {
6964
7185
  this.clear();
6965
- const border = (char) => theme.bold(colorText(this.colors.border, char, (text) => theme.fg("accent", text)));
6966
- const maxLineWidth = Math.max(1, termWidth - 6 - 0);
7186
+ const width = Math.max(1, termWidth - 0);
7187
+ const maxLineWidth = Math.max(1, width - 3);
6967
7188
  const typeLabelText = this.forked ? "fork" : this.agentType;
6968
7189
  const typeLabel = theme.bold(colorText(this.colors.agentType, typeLabelText, (text) => theme.fg("accent", text)));
6969
7190
  const modelLabel = this.modelId ? theme.fg("muted", ` ${this.modelId}`) : "";
6970
- const statusIcon = this.backgroundTaskId ? this.done ? this.cancelled ? theme.fg("muted", ` ■ background · ${this.backgroundTaskId}`) : this.isError ? theme.fg("error", ` ✗ background · ${this.backgroundTaskId}`) : theme.fg("success", ` ✓ background · ${this.backgroundTaskId}`) : theme.fg("warning", ` ◌ background · ${this.backgroundTaskId}`) : this.done ? this.isError ? colorText(this.colors.icon, ` ${this.icons.error}`, (text) => theme.fg("error", text)) : colorText(this.colors.icon, ` ${this.icons.success}`, (text) => theme.fg("success", text)) : colorText(this.colors.icon, ` ${this.icons.running}`, (text) => theme.fg("muted", text));
7191
+ const statusIcon = this.backgroundTaskId ? this.done ? this.cancelled ? theme.fg("muted", ` ■ background · ${this.backgroundTaskId}`) : this.isError ? theme.fg("error", ` ✗ background · ${this.backgroundTaskId}`) : theme.fg("success", ` ✓ background · ${this.backgroundTaskId}`) : theme.fg("warning", ` ◌ background · ${this.backgroundTaskId}`) : this.done && this.isError ? colorText(this.colors.icon, ` ${this.icons.error}`, (text) => theme.fg("error", text)) : "";
7192
+ const dot = statusDot(this.done ? this.isError ? "error" : "done" : "running");
7193
+ const output = [];
7194
+ const emit = () => {
7195
+ this.addChild(new Text(toolBlock(dot, footerText, output, width).join("\n"), 0, 0));
7196
+ this.invalidate();
7197
+ this.ui.requestRender();
7198
+ };
6971
7199
  const durationStr = this.done ? theme.fg("muted", ` ${formatDuration$1(this.durationMs)}`) : "";
6972
7200
  const footerText = `${theme.bold(colorText(this.colors.label, this.label, (text) => theme.fg("toolTitle", text)))} ${typeLabel}${modelLabel}${durationStr}${statusIcon}`;
6973
7201
  if ((this.collapseOnComplete || this.backgroundTaskId) && this.done && !this.expanded) {
6974
- this.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
6975
- this.invalidate();
6976
- this.ui.requestRender();
7202
+ emit();
6977
7203
  return;
6978
7204
  }
6979
- this.addChild(new Text(border("╭──"), 0, 0));
6980
7205
  const taskLines = this.task.split("\n");
6981
7206
  const wrappedTaskLines = [];
6982
7207
  for (const line of taskLines) if (line.length > maxLineWidth) {
@@ -6991,14 +7216,11 @@ var SubagentExecutionComponent = class extends WidthAwareContainer {
6991
7216
  } else wrappedTaskLines.push(line);
6992
7217
  const maxTaskLines = 5;
6993
7218
  const taskTruncated = !this.expanded && wrappedTaskLines.length > 6;
6994
- const taskContent = (taskTruncated ? wrappedTaskLines.slice(0, maxTaskLines) : wrappedTaskLines).map((line) => `${border("│")} ${line}`).join("\n");
6995
- this.addChild(new Text(taskContent, 0, 0));
6996
- if (taskTruncated) {
6997
- const moreText = theme.fg("muted", `... ${wrappedTaskLines.length - maxTaskLines} more lines (ctrl+e to expand)`);
6998
- this.addChild(new Text(`${border("│")} ${moreText}`, 0, 0));
6999
- }
7219
+ const displayTaskLines = taskTruncated ? wrappedTaskLines.slice(0, maxTaskLines) : wrappedTaskLines;
7220
+ output.push(...displayTaskLines);
7221
+ if (taskTruncated) output.push(theme.fg("muted", `... ${wrappedTaskLines.length - maxTaskLines} more lines (ctrl+e to expand)`));
7000
7222
  if (this.activity.length > 0) {
7001
- this.addChild(new Text(`${border("│")} ${theme.fg("muted", "───")}`, 0, 0));
7223
+ output.push(theme.fg("muted", "───"));
7002
7224
  const activityLines = this.activity.flatMap((item) => formatActivityLine(item, maxLineWidth, this.icons, this.colors.icon));
7003
7225
  const cap = this.done ? this.collapsedLines : this.maxActivityLines;
7004
7226
  let displayLines = activityLines;
@@ -7010,22 +7232,15 @@ var SubagentExecutionComponent = class extends WidthAwareContainer {
7010
7232
  }
7011
7233
  if (hiddenCount > 0) {
7012
7234
  const hiddenText = theme.fg("muted", ` ... ${hiddenCount} more above${this.done ? " (ctrl+e to expand)" : ""}`);
7013
- this.addChild(new Text(`${border("│")} ${hiddenText}`, 0, 0));
7235
+ output.push(hiddenText);
7014
7236
  }
7015
- const activityContent = displayLines.map((line) => `${border("│")} ${line}`).join("\n");
7016
- this.addChild(new Text(activityContent, 0, 0));
7237
+ output.push(...displayLines);
7017
7238
  }
7018
7239
  if (this.done && this.finalResult && this.expanded) {
7019
- this.addChild(new Text(`${border("│")} ${theme.fg("muted", "───")}`, 0, 0));
7020
- const resultContent = this.finalResult.split("\n").map((line) => {
7021
- const truncatedLine = line.length > maxLineWidth ? line.slice(0, maxLineWidth - 1) + "…" : line;
7022
- return `${border("│")} ${theme.fg("muted", truncatedLine)}`;
7023
- }).join("\n");
7024
- if (resultContent.trim()) this.addChild(new Text(resultContent, 0, 0));
7025
- }
7026
- this.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
7027
- this.invalidate();
7028
- this.ui.requestRender();
7240
+ output.push(theme.fg("muted", "───"));
7241
+ for (const line of this.finalResult.split("\n")) output.push(theme.fg("muted", line.length > maxLineWidth ? line.slice(0, maxLineWidth - 1) + "…" : line));
7242
+ }
7243
+ emit();
7029
7244
  }
7030
7245
  };
7031
7246
  function clampPositiveInt(value, fallback) {
@@ -7217,13 +7432,11 @@ var SystemReminderComponent = class extends WidthAwareContainer {
7217
7432
  const metadataColor = (text) => theme.fg("dim", text);
7218
7433
  const bodyColor = (text) => theme.fg("text", text);
7219
7434
  const hintColor = (text) => theme.fg("dim", text);
7220
- const innerWidth = Math.max(20, termWidth - 0 - 4);
7221
- const horizontal = "─".repeat(innerWidth + 1);
7435
+ const innerWidth = Math.max(20, termWidth - 0 - 2);
7222
7436
  const metadataLines = [this.path ? formatReminderPath(this.path) : void 0].filter((line) => Boolean(line));
7223
7437
  const wrappedMessageLines = wrapLines(this.messageLines, innerWidth);
7224
7438
  const shouldCollapse = wrappedMessageLines.length > MAX_COLLAPSED_LINES;
7225
7439
  const visibleMessageLines = shouldCollapse && !this.expanded ? wrappedMessageLines.slice(0, MAX_COLLAPSED_LINES) : wrappedMessageLines;
7226
- this.addChild(new Text(`${border("╭")}${border(horizontal)}${border("╮")}`, 0, 0));
7227
7440
  this.addChild(new Text(renderRow(title, innerWidth, border), 0, 0));
7228
7441
  for (const line of metadataLines) this.addChild(new Text(renderRow(metadataColor(line), innerWidth, border), 0, 0));
7229
7442
  if (metadataLines.length > 0 && visibleMessageLines.length > 0) this.addChild(new Text(renderRow("", innerWidth, border), 0, 0));
@@ -7232,13 +7445,11 @@ var SystemReminderComponent = class extends WidthAwareContainer {
7232
7445
  const hint = hintColor(`... ${wrappedMessageLines.length - visibleMessageLines.length} more lines (ctrl+e to expand)`);
7233
7446
  this.addChild(new Text(renderRow(hint, innerWidth, border), 0, 0));
7234
7447
  }
7235
- this.addChild(new Text(`${border("╰")}${border(horizontal)}${border("╯")}`, 0, 0));
7236
7448
  }
7237
7449
  };
7450
+ /** A row of the left-bar card (`width` is the room for text after the bar). */
7238
7451
  function renderRow(text, width, border) {
7239
- const content = padLine(text, width);
7240
- const rightPadding = hasWideGlyph(stripAnsi(text)) ? " " : "";
7241
- return `${border("│")} ${content}${rightPadding}${border("│")}`;
7452
+ return text ? `${border("▎")} ${padLine(text, width)}` : border("▎");
7242
7453
  }
7243
7454
  function splitMessageLines(message) {
7244
7455
  return message.split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0);
@@ -7270,9 +7481,6 @@ function formatReminderPath(path) {
7270
7481
  const cwdPrefix = `${cwd}/`;
7271
7482
  return path.startsWith(cwdPrefix) ? path.slice(cwdPrefix.length) : path;
7272
7483
  }
7273
- function hasWideGlyph(text) {
7274
- return [...text].some((char) => /[\p{Extended_Pictographic}\u1100-\u115F\u2329\u232A\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE6F\uFF00-\uFF60\uFFE0-\uFFE6]/u.test(char));
7275
- }
7276
7484
  function wrapLines(lines, maxLineWidth) {
7277
7485
  if (lines.length === 0) return [""];
7278
7486
  const wrappedLines = [];
@@ -7303,71 +7511,6 @@ function truncateLine(text, width) {
7303
7511
  return plain.length <= width ? text : plain.slice(0, Math.max(0, width - 1)) + "…";
7304
7512
  }
7305
7513
  //#endregion
7306
- //#region src/tui/components/ansi.ts
7307
- /**
7308
- * Shared ANSI text-handling helpers for TUI components.
7309
- */
7310
- const ANSI_CLOSERS = "\x1B]8;;\x07\x1B[0m";
7311
- const ELLIPSIS = "…";
7312
- const ELLIPSIS_WIDTH = visibleWidth(ELLIPSIS);
7313
- const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
7314
- function fitVisibleText(text, maxWidth) {
7315
- if (maxWidth <= 0) return {
7316
- text: "",
7317
- width: 0,
7318
- truncated: text.length > 0
7319
- };
7320
- const targetWidth = Math.max(0, maxWidth - ELLIPSIS_WIDTH);
7321
- let width = 0;
7322
- let result = "";
7323
- let resultWidth = 0;
7324
- for (const { segment: char } of graphemeSegmenter.segment(text)) {
7325
- const charWidth = visibleWidth(char);
7326
- if (width + charWidth > maxWidth) return {
7327
- text: `${result}${ELLIPSIS}`,
7328
- width: resultWidth + ELLIPSIS_WIDTH,
7329
- truncated: true
7330
- };
7331
- if (width + charWidth <= targetWidth) {
7332
- result += char;
7333
- resultWidth += charWidth;
7334
- }
7335
- width += charWidth;
7336
- }
7337
- return {
7338
- text,
7339
- width,
7340
- truncated: false
7341
- };
7342
- }
7343
- /** Truncate a string with ANSI codes to a visible width.
7344
- * Handles both SGR sequences (\x1b[...m) and OSC 8 hyperlinks (\x1b]8;...;\x07).
7345
- */
7346
- function truncateAnsi(str, maxWidth) {
7347
- if (maxWidth <= 0) return "";
7348
- const ansiRegex = /\x1b\[[0-9;]{0,32}m|\x1b\]8;[^\x07\x1b]{0,8192}\x07/g;
7349
- let visibleLength = 0;
7350
- let result = "";
7351
- let lastIndex = 0;
7352
- let match;
7353
- while ((match = ansiRegex.exec(str)) !== null) {
7354
- const fitted = fitVisibleText(str.slice(lastIndex, match.index), maxWidth - visibleLength);
7355
- if (fitted.truncated) {
7356
- result += fitted.text;
7357
- result += ANSI_CLOSERS;
7358
- return result;
7359
- }
7360
- result += fitted.text;
7361
- visibleLength += fitted.width;
7362
- result += match[0];
7363
- lastIndex = match.index + match[0].length;
7364
- }
7365
- const fitted = fitVisibleText(str.slice(lastIndex), maxWidth - visibleLength);
7366
- result += fitted.text;
7367
- if (fitted.truncated) result += ANSI_CLOSERS;
7368
- return result;
7369
- }
7370
- //#endregion
7371
7514
  //#region src/tui/components/task-progress.ts
7372
7515
  /**
7373
7516
  * Task progress component for the TUI.
@@ -7410,10 +7553,7 @@ var TaskProgressComponent = class extends WidthAwareContainer {
7410
7553
  this.clear();
7411
7554
  const completed = this.tasks.filter((t) => t.status === "completed").length;
7412
7555
  const total = this.tasks.length;
7413
- if (!(total > 0 && completed !== total)) {
7414
- this.addChild(new Spacer(1));
7415
- return;
7416
- }
7556
+ if (!(total > 0 && completed !== total)) return;
7417
7557
  this.addChild(new Spacer(1));
7418
7558
  if (this.quietMode) {
7419
7559
  for (const line of this.formatQuietTaskLines(completed, total, width)) this.addChild(new Text(line, 0, 0));
@@ -7565,12 +7705,6 @@ var CollapsibleComponent = class extends Container {
7565
7705
  * Enhanced error display component with collapsible stack traces,
7566
7706
  * syntax highlighting, and smart summarization.
7567
7707
  */
7568
- /** A single-width box-drawing border sized to `width`, truncating from the right when the corners don't fit. */
7569
- function fitBorder(width, left, right, fill = "─") {
7570
- const inner = Math.max(0, width - left.length - right.length);
7571
- const line = left + fill.repeat(inner) + right;
7572
- return line.length <= width ? line : line.slice(0, Math.max(0, width));
7573
- }
7574
7708
  /**
7575
7709
  * Parse error information from various error formats
7576
7710
  */
@@ -7660,38 +7794,21 @@ var ErrorDisplayComponent = class extends WidthAwareContainer {
7660
7794
  rebuildForWidth(width) {
7661
7795
  this.clear();
7662
7796
  const info = parseErrorInfo(this.error);
7663
- const box = new Box(0, 0, (text) => text);
7664
- this.addChild(box);
7665
- const borderWidth = Math.max(0, width - 0);
7666
- const borderTop = new Text(theme.fg("error", fitBorder(borderWidth, "╭─ Error ", "╮")), 0, 0);
7667
- box.addChild(borderTop);
7668
- const errorContainer = new Container();
7669
- const errorBg = (text) => theme.bg("errorBg", text);
7670
- if (info.name && info.name !== "Error") {
7671
- const typeLine = new Container();
7672
- typeLine.addChild(new Text("│ ", 0, 0));
7673
- typeLine.addChild(new Text(errorBg(` ${theme.bold(theme.fg("error", info.name))} `), 0, 0));
7674
- errorContainer.addChild(typeLine);
7675
- }
7676
- const msgLine = new Container();
7677
- msgLine.addChild(new Text("│ ", 0, 0));
7678
- msgLine.addChild(new Text(theme.bold(info.message), 0, 0));
7679
- errorContainer.addChild(msgLine);
7797
+ const room = Math.max(10, width - 0 - 2);
7798
+ const lines = [theme.bold(theme.fg("error", info.name && info.name !== "Error" ? info.name : "Error")), ...wrapTextWithAnsi(theme.fg("text", info.message), room)];
7680
7799
  if (info.file && info.line) {
7681
7800
  const location = `${info.file}:${info.line}${info.column ? `:${info.column}` : ""}`;
7682
- errorContainer.addChild(new Text(theme.fg("muted", ` at ${location}`), 0, 0));
7801
+ lines.push(theme.fg("muted", `at ${location}`));
7683
7802
  }
7684
- box.addChild(errorContainer);
7803
+ this.addChild(new Text(card(theme.getTheme().error, lines).join("\n"), 0, 0));
7685
7804
  if (this.options.showContext && info.context) {
7686
- box.addChild(new Spacer(1));
7687
- box.addChild(this.createCodeContext(info.context, info.line));
7805
+ this.addChild(new Spacer(1));
7806
+ this.addChild(this.createCodeContext(info.context, info.line));
7688
7807
  }
7689
7808
  if (this.options.showStack && info.stack) {
7690
- box.addChild(new Spacer(1));
7691
- box.addChild(new CollapsibleStackTrace(info.stack, { expanded: this.options.expanded }, this.ui));
7809
+ this.addChild(new Spacer(1));
7810
+ this.addChild(new CollapsibleStackTrace(info.stack, { expanded: this.options.expanded }, this.ui));
7692
7811
  }
7693
- const borderBottom = new Text(theme.fg("error", fitBorder(borderWidth, "╰", "╯")), 0, 0);
7694
- box.addChild(borderBottom);
7695
7812
  }
7696
7813
  getChatSpacingKind() {
7697
7814
  return "other";
@@ -7889,6 +8006,28 @@ const QUIET_CODE_HIGHLIGHT_THEME = {
7889
8006
  tag: chalk.hex("#c4b5fd"),
7890
8007
  name: chalk.hex("#c4b5fd")
7891
8008
  };
8009
+ /** The same roles in darker tones that read on light backgrounds. */
8010
+ const LIGHT_CODE_HIGHLIGHT_THEME = {
8011
+ default: (text) => theme.fg("toolArgs", text),
8012
+ keyword: chalk.hex("#7e22ce"),
8013
+ built_in: chalk.hex("#1d4ed8"),
8014
+ type: chalk.hex("#1d4ed8"),
8015
+ literal: chalk.hex("#b91c1c"),
8016
+ number: chalk.hex("#b45309"),
8017
+ string: chalk.hex("#15803d"),
8018
+ regexp: chalk.hex("#b91c1c"),
8019
+ title: chalk.hex("#1d4ed8"),
8020
+ function: chalk.hex("#1d4ed8"),
8021
+ params: chalk.hex("#3f3f46"),
8022
+ comment: chalk.hex("#71717a"),
8023
+ meta: chalk.hex("#52525b"),
8024
+ attr: chalk.hex("#b45309"),
8025
+ variable: chalk.hex("#3f3f46"),
8026
+ tag: chalk.hex("#7e22ce"),
8027
+ name: chalk.hex("#7e22ce")
8028
+ };
8029
+ const codeHighlightTheme = () => getThemeMode() === "light" ? LIGHT_CODE_HIGHLIGHT_THEME : CODE_HIGHLIGHT_THEME;
8030
+ const quietCodeHighlightTheme = () => getThemeMode() === "light" ? LIGHT_CODE_HIGHLIGHT_THEME : QUIET_CODE_HIGHLIGHT_THEME;
7892
8031
  const SHELL_CONTROL_WORDS = /* @__PURE__ */ new Set([
7893
8032
  "if",
7894
8033
  "then",
@@ -7999,6 +8138,8 @@ function extractContent(text) {
7999
8138
  /**
8000
8139
  * Enhanced tool execution component with collapsible sections
8001
8140
  */
8141
+ /** Shell output lines shown before the rest is folded behind ctrl+e. */
8142
+ const SHELL_OUTPUT_LINES = 8;
8002
8143
  var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
8003
8144
  contentBox;
8004
8145
  toolName;
@@ -8121,6 +8262,12 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
8121
8262
  this.quietShellPreviewRowFloor = Math.min(this.quietShellPreviewRowFloor, this.quietPreviewLineLimit);
8122
8263
  this.rebuild();
8123
8264
  }
8265
+ getToolCall() {
8266
+ return {
8267
+ toolName: this.toolName,
8268
+ args: this.args
8269
+ };
8270
+ }
8124
8271
  setCompactToolModeColor(color) {
8125
8272
  const nextColor = normalizeHexColor(color);
8126
8273
  if (this.compactToolModeColor === nextColor) return;
@@ -8402,7 +8549,7 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
8402
8549
  return highlight(preview, {
8403
8550
  language: getLanguageFromPath(path),
8404
8551
  ignoreIllegals: true,
8405
- theme: QUIET_CODE_HIGHLIGHT_THEME
8552
+ theme: quietCodeHighlightTheme()
8406
8553
  });
8407
8554
  } catch {
8408
8555
  return theme.fg("toolArgs", preview);
@@ -8551,9 +8698,6 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
8551
8698
  getQuietToolCircleColor(color) {
8552
8699
  return ensureTerminalGlyphContrast(color);
8553
8700
  }
8554
- formatToolBorder(char) {
8555
- return theme.bold(chalk.hex(ensureTerminalGlyphContrast(theme.getTheme().toolBorderSuccess))(char));
8556
- }
8557
8701
  getCompactToolLabelColor() {
8558
8702
  if (this.compactToolGroupLabelColor) return this.compactToolGroupLabelColor;
8559
8703
  return this.getOwnCompactToolLabelColor();
@@ -8947,14 +9091,13 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
8947
9091
  const to = limit ? from + limit - 1 : void 0;
8948
9092
  rangeDisplay = theme.fg("muted", to ? `:${from}-${to}` : `:${from}`);
8949
9093
  }
8950
- const border = (char) => this.formatToolBorder(char);
8951
9094
  if (!this.result || this.isPartial) {
8952
9095
  const path = argsObj?.path ? shortenPath(String(argsObj.path)) : "...";
8953
9096
  const status = this.getStatusIndicator();
8954
9097
  const pathDisplay = fullPath ? fileLink(theme.fg("toolArgs", path), fullPath, startLine) : theme.fg("toolArgs", path);
8955
9098
  const footerText = `${theme.bold(theme.fg("toolTitle", "view"))} ${pathDisplay}${rangeDisplay}${status}`;
8956
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
8957
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9099
+ this.startBlock();
9100
+ this.endBlock(footerText);
8958
9101
  return;
8959
9102
  }
8960
9103
  const status = this.getStatusIndicator();
@@ -8964,7 +9107,7 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
8964
9107
  const pathDisplay = fullPath ? fileLink(theme.fg("toolArgs", path), fullPath, startLine) : theme.fg("toolArgs", path);
8965
9108
  const footerText = `${theme.bold(theme.fg("toolTitle", "view"))} ${pathDisplay}${rangeDisplay}${status}`;
8966
9109
  this.addLeadingPadding();
8967
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9110
+ this.startBlock();
8968
9111
  const output = this.getFormattedOutput();
8969
9112
  if (output) {
8970
9113
  const maxLineWidth = this.renderWidth - 4 - 0;
@@ -8975,15 +9118,15 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
8975
9118
  if (hasMore) lines = lines.slice(0, collapsedLines);
8976
9119
  const borderedLines = lines.map((line) => {
8977
9120
  const truncated = truncateAnsi(line, maxLineWidth);
8978
- return border("│") + " " + theme.fg("toolOutput", truncated);
9121
+ return theme.fg("toolOutput", truncated);
8979
9122
  });
8980
- this.contentBox.addChild(new Text(borderedLines.join("\n"), 0, 0));
9123
+ this.blockLines(borderedLines);
8981
9124
  if (hasMore) {
8982
9125
  const remaining = totalLines - collapsedLines;
8983
- this.contentBox.addChild(new Text(border("│") + " " + theme.fg("muted", `... ${remaining} more lines (ctrl+e to expand)`), 0, 0));
9126
+ this.blockLine(theme.fg("muted", `... ${remaining} more lines (ctrl+e to expand)`));
8984
9127
  }
8985
9128
  }
8986
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9129
+ this.endBlock(footerText);
8987
9130
  }
8988
9131
  renderBashToolEnhanced() {
8989
9132
  const argsObj = this.args;
@@ -9002,37 +9145,26 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9002
9145
  const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
9003
9146
  const cwdSuffix = cwd ? theme.fg("muted", ` in ${cwd}`) : "";
9004
9147
  const timeSuffix = this.isPartial ? timeoutSuffix : this.getDurationSuffix();
9005
- const renderBorderedShell = (status, outputLines) => {
9006
- const border = (char) => this.formatToolBorder(char);
9007
- const footerPrompt = `${theme.bold(theme.fg("toolTitle", "$"))} `;
9008
- const footerSuffix = `${cwdSuffix}${timeSuffix}${status}`;
9009
- const termWidth = this.renderWidth;
9010
- const contentWidth = Math.max(20, termWidth - 0 - 4);
9011
- const horizontal = "─".repeat(contentWidth + 2);
9012
- const renderLine = (line, color = (value) => theme.fg("toolOutput", value)) => {
9013
- const truncated = truncateAnsi(line, contentWidth);
9014
- const padding = " ".repeat(Math.max(0, contentWidth - visibleWidth(truncated)));
9015
- return `${border("│")} ${color(truncated)}${padding} ${border("│")}`;
9016
- };
9017
- const displayOutput = outputLines.map((line) => renderLine(line)).join("\n");
9018
- const hasOutput = displayOutput.trim() !== "";
9019
- this.contentBox.addChild(new Text(`${border("╭")}${border(horizontal)}${border("╮")}`, 0, 0));
9020
- if (hasOutput) {
9021
- this.contentBox.addChild(new Text(displayOutput, 0, 0));
9022
- this.contentBox.addChild(new Text(`${border("├")}${border(horizontal)}${border("┤")}`, 0, 0));
9023
- }
9024
- const footerPromptWidth = visibleWidth(footerPrompt);
9025
- const footerWrapWidth = Math.max(1, contentWidth - 2 - footerPromptWidth);
9026
- const footerLines = this.wrapQuietShellCommand(command, footerWrapWidth);
9027
- const footerSuffixWidth = visibleWidth(footerSuffix);
9028
- const continuationIndent = " ".repeat(footerPromptWidth);
9029
- footerLines.forEach((footerLine, index) => {
9030
- const prefix = index === 0 ? footerPrompt : continuationIndent;
9031
- const suffix = index === footerLines.length - 1 && visibleWidth(footerLine) + footerSuffixWidth <= footerWrapWidth ? footerSuffix : "";
9032
- this.contentBox.addChild(new Text(renderLine(`${prefix}${footerLine}${suffix}`, (value) => value), 0, 0));
9033
- });
9034
- if (visibleWidth(footerLines[footerLines.length - 1] ?? "") + footerSuffixWidth > footerWrapWidth) this.contentBox.addChild(new Text(renderLine(`${continuationIndent}${footerSuffix}`, (value) => value), 0, 0));
9035
- this.contentBox.addChild(new Text(`${border("╰")}${border(horizontal)}${border("╯")}`, 0, 0));
9148
+ const renderShellBlock = (status, outputLines, failed) => {
9149
+ const prompt = `${theme.bold(theme.fg("toolTitle", "$"))} `;
9150
+ const suffix = `${cwdSuffix}${timeSuffix}${status}`;
9151
+ const titleWidth = Math.max(1, this.renderWidth - 0 - 2 - visibleWidth(prompt));
9152
+ const commandLines = this.wrapQuietShellCommand(command, titleWidth);
9153
+ const title = commandLines.map((line, i) => (i === 0 ? prompt : " ".repeat(visibleWidth(prompt))) + line);
9154
+ const last = title.length - 1;
9155
+ if (visibleWidth(commandLines[last] ?? "") + visibleWidth(suffix) <= titleWidth) title[last] += suffix;
9156
+ else title.push(" ".repeat(visibleWidth(prompt)) + suffix);
9157
+ let shown = outputLines;
9158
+ let hiddenNote;
9159
+ if (!this.expanded && outputLines.length > SHELL_OUTPUT_LINES) {
9160
+ const hidden = outputLines.length - SHELL_OUTPUT_LINES;
9161
+ shown = outputLines.slice(-8);
9162
+ hiddenNote = this.isPartial ? theme.fg("dim", `… ${hidden} earlier lines`) : theme.fg("dim", `… ${hidden} more lines · `) + theme.fg("muted", "ctrl+e") + theme.fg("dim", " to expand");
9163
+ }
9164
+ this.startBlock();
9165
+ if (hiddenNote) this.blockLine(hiddenNote);
9166
+ this.blockLines(shown.map((line) => theme.fg("toolOutput", line)));
9167
+ this.endBlock(title, failed);
9036
9168
  };
9037
9169
  if (!this.result || this.isPartial) {
9038
9170
  const status = this.getStatusIndicator();
@@ -9040,7 +9172,7 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9040
9172
  while (lines.length > 0 && lines[0] === "") lines.shift();
9041
9173
  while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
9042
9174
  if (maxStreamLines && lines.length > maxStreamLines) lines = lines.slice(-maxStreamLines);
9043
- renderBorderedShell(status, lines);
9175
+ renderShellBlock(status, lines);
9044
9176
  return;
9045
9177
  }
9046
9178
  const prepareOutputLines = (output) => {
@@ -9052,17 +9184,17 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9052
9184
  };
9053
9185
  const failed = this.getShellFailureLine() !== void 0;
9054
9186
  const output = this.streamingOutput.trim() || this.getFormattedOutput();
9055
- renderBorderedShell(this.getStatusIndicator(failed), prepareOutputLines(output));
9187
+ renderShellBlock(this.getStatusIndicator(failed), prepareOutputLines(output), failed);
9056
9188
  }
9057
9189
  /**
9058
9190
  * One call's slice of a shared quiet shell box. The first call in a run opens the box with a
9059
9191
  * `$ <path>` header, a call in a new directory adds another header, and the last call closes it.
9060
9192
  */
9061
9193
  renderQuietShellGroupRow() {
9062
- const border = (char) => this.formatToolBorder(char);
9063
9194
  const fullWidth = Math.max(20, this.renderWidth - 0 - 4);
9064
9195
  const naturalWidth = this.quietShellGroupWidth ?? this.getQuietShellNaturalWidth() ?? 0;
9065
9196
  const contentWidth = Math.min(fullWidth, Math.max(QUIET_SHELL_MIN_CONTENT_WIDTH, naturalWidth));
9197
+ const border = (char) => theme.bold(chalk.hex(ensureTerminalGlyphContrast(theme.getTheme().toolBorderSuccess))(char));
9066
9198
  const rule = (left, right) => `${border(left)}${border("─".repeat(contentWidth + 2))}${border(right)}`;
9067
9199
  const singleLine = (text) => text.replace(TERMINAL_SEQUENCE_RE, "").replace(/[\t\n\r\v\f]/g, " ").replace(/[\x00-\x08\x0e-\x1f\x7f-\x9f]/g, "");
9068
9200
  const row = (left, right = "") => {
@@ -9172,16 +9304,15 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9172
9304
  const timeSuffix = this.isPartial ? "" : this.getDurationSuffix();
9173
9305
  const label = isKill ? "kill" : isWait ? "wait" : "output";
9174
9306
  const renderBorderedProcess = (status, outputLines) => {
9175
- const border = (char) => this.formatToolBorder(char);
9176
9307
  const footerText = `${theme.bold(theme.fg("toolTitle", label))} ${theme.fg("toolArgs", `PID ${pid}`)}${timeSuffix}${status}`;
9177
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9308
+ this.startBlock();
9178
9309
  const maxLineWidth = this.renderWidth - 4 - 0;
9179
- const displayOutput = outputLines.map((line) => {
9310
+ const borderedLines = outputLines.map((line) => {
9180
9311
  const truncated = truncateAnsi(line, maxLineWidth);
9181
- return border("│") + " " + theme.fg("toolOutput", truncated);
9182
- }).join("\n");
9183
- if (displayOutput.trim()) this.contentBox.addChild(new Text(displayOutput, 0, 0));
9184
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9312
+ return theme.fg("toolOutput", truncated);
9313
+ });
9314
+ if (borderedLines.join("\n").trim()) this.blockLines(borderedLines);
9315
+ this.endBlock(footerText);
9185
9316
  };
9186
9317
  const prepareOutputLines = (output) => {
9187
9318
  let lines = output.split("\n");
@@ -9211,11 +9342,10 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9211
9342
  const oldStr = argsObj?.old_str ?? argsObj?.old_string;
9212
9343
  const newStr = argsObj?.new_str ?? argsObj?.new_string;
9213
9344
  if (oldStr != null && newStr != null) {
9214
- const border = (char) => this.formatToolBorder(char);
9215
9345
  const maxLineWidth = this.renderWidth - 4 - 0;
9216
9346
  const footerText = `${theme.bold(theme.fg("toolTitle", "edit"))} ${pathDisplay}${theme.fg("muted", startLine)}${status}`;
9217
9347
  this.addLeadingPadding();
9218
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9348
+ this.startBlock();
9219
9349
  const { lines: diffLines } = this.generateDiffLines(String(oldStr), String(newStr));
9220
9350
  const collapsedLines = this.getCollapsedLineLimit(15);
9221
9351
  const totalLines = diffLines.length;
@@ -9226,22 +9356,20 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9226
9356
  skippedAbove = totalLines - collapsedLines;
9227
9357
  linesToShow = diffLines.slice(-collapsedLines);
9228
9358
  }
9229
- if (skippedAbove > 0) this.contentBox.addChild(new Text(border("│") + " " + theme.fg("muted", `... ${skippedAbove} lines above (ctrl+e to expand)`), 0, 0));
9359
+ if (skippedAbove > 0) this.blockLine(theme.fg("muted", `... ${skippedAbove} lines above (ctrl+e to expand)`));
9230
9360
  const borderedLines = linesToShow.map((line) => {
9231
9361
  const truncated = truncateAnsi(line, maxLineWidth);
9232
- return border("│") + " " + theme.fg("toolOutput", truncated);
9362
+ return theme.fg("toolOutput", truncated);
9233
9363
  });
9234
- this.contentBox.addChild(new Text(borderedLines.join("\n"), 0, 0));
9235
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9364
+ this.blockLines(borderedLines);
9365
+ this.endBlock(footerText);
9236
9366
  return;
9237
9367
  }
9238
- const editBorder = (char) => this.formatToolBorder(char);
9239
9368
  const headerText = `${theme.bold(theme.fg("toolTitle", "edit"))} ${pathDisplay}${theme.fg("muted", startLine)}${status}`;
9240
- this.contentBox.addChild(new Text(editBorder("╭──"), 0, 0));
9241
- this.contentBox.addChild(new Text(`${editBorder("╰──")} ${headerText}`, 0, 0));
9369
+ this.startBlock();
9370
+ this.endBlock(headerText);
9242
9371
  return;
9243
9372
  }
9244
- const border = (char) => this.formatToolBorder(char);
9245
9373
  const status = this.getStatusIndicator();
9246
9374
  const termWidth = this.renderWidth;
9247
9375
  const availableForPath = termWidth - ("╰── edit " + startLine + " ✓").length - 6 - 0;
@@ -9250,7 +9378,7 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9250
9378
  const pathDisplay = fullPath ? fileLink(theme.fg("toolArgs", path), fullPath, startLineNum) : theme.fg("toolArgs", path);
9251
9379
  const footerText = `${theme.bold(theme.fg("toolTitle", "edit"))} ${pathDisplay}${theme.fg("muted", startLine)}${status}`;
9252
9380
  this.addLeadingPadding();
9253
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9381
+ this.startBlock();
9254
9382
  const finalOldStr = argsObj?.old_str ?? argsObj?.old_string;
9255
9383
  const finalNewStr = argsObj?.new_str ?? argsObj?.new_string;
9256
9384
  if (finalOldStr != null && finalNewStr != null && !this.result.isError) {
@@ -9266,15 +9394,15 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9266
9394
  skippedBefore = start;
9267
9395
  }
9268
9396
  const maxLineWidth = termWidth - 4 - 0;
9269
- if (skippedBefore > 0) this.contentBox.addChild(new Text(border("│") + " " + theme.fg("muted", `... ${skippedBefore} lines above`), 0, 0));
9397
+ if (skippedBefore > 0) this.blockLine(theme.fg("muted", `... ${skippedBefore} lines above`));
9270
9398
  const borderedLines = linesToShow.map((line) => {
9271
9399
  const truncated = truncateAnsi(line, maxLineWidth);
9272
- return border("│") + " " + theme.fg("toolOutput", truncated);
9400
+ return theme.fg("toolOutput", truncated);
9273
9401
  });
9274
- this.contentBox.addChild(new Text(borderedLines.join("\n"), 0, 0));
9402
+ this.blockLines(borderedLines);
9275
9403
  if (hasMore) {
9276
9404
  const remaining = totalLines - (skippedBefore + linesToShow.length);
9277
- if (remaining > 0) this.contentBox.addChild(new Text(border("│") + " " + theme.fg("muted", `... ${remaining} more lines (ctrl+e to expand)`), 0, 0));
9405
+ if (remaining > 0) this.blockLine(theme.fg("muted", `... ${remaining} more lines (ctrl+e to expand)`));
9278
9406
  }
9279
9407
  } else if (this.result.isError) {
9280
9408
  const output = this.getFormattedOutput();
@@ -9282,12 +9410,12 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9282
9410
  const maxLineWidth = termWidth - 4 - 0;
9283
9411
  const lines = output.split("\n").map((line) => {
9284
9412
  const truncated = truncateAnsi(line, maxLineWidth);
9285
- return border("│") + " " + theme.fg("error", truncated);
9413
+ return theme.fg("error", truncated);
9286
9414
  });
9287
- this.contentBox.addChild(new Text(lines.join("\n"), 0, 0));
9415
+ this.blockLines(lines);
9288
9416
  }
9289
9417
  }
9290
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9418
+ this.endBlock(footerText);
9291
9419
  const diagnostics = this.parseLSPDiagnostics();
9292
9420
  if (diagnostics && !diagnostics.hasIssues) this.contentBox.addChild(new Text(theme.fg("muted", ` ✓ No LSP issues`), 0, 0));
9293
9421
  else if (diagnostics && diagnostics.hasIssues) {
@@ -9371,16 +9499,14 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9371
9499
  const content = argsObj?.content ? String(argsObj.content) : "";
9372
9500
  if (!this.result || this.isPartial) {
9373
9501
  if (!content) {
9374
- const writeBorder = (char) => this.formatToolBorder(char);
9375
9502
  const path = argsObj?.path ? shortenPath(String(argsObj.path)) : "...";
9376
9503
  const status = this.getStatusIndicator();
9377
9504
  const pathDisplay = fullPath ? fileLink(theme.fg("toolArgs", path), fullPath) : theme.fg("toolArgs", path);
9378
9505
  const footerText = `${theme.bold(theme.fg("toolTitle", "write"))} ${pathDisplay}${status}`;
9379
- this.contentBox.addChild(new Text(writeBorder("╭──"), 0, 0));
9380
- this.contentBox.addChild(new Text(`${writeBorder("╰──")} ${footerText}`, 0, 0));
9506
+ this.startBlock();
9507
+ this.endBlock(footerText);
9381
9508
  return;
9382
9509
  }
9383
- const border = (char) => this.formatToolBorder(char);
9384
9510
  const status = this.getStatusIndicator();
9385
9511
  const termWidth = this.renderWidth;
9386
9512
  const maxLineWidth = termWidth - 4 - 0;
@@ -9390,8 +9516,8 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9390
9516
  const pathDisplay = fullPath ? fileLink(theme.fg("toolArgs", path), fullPath) : theme.fg("toolArgs", path);
9391
9517
  const footerText = `${theme.bold(theme.fg("toolTitle", "write"))} ${pathDisplay}${status}`;
9392
9518
  this.addLeadingPadding();
9393
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9394
- let lines = highlightCode(content, fullPath).split("\n");
9519
+ this.startBlock();
9520
+ let lines = highlightCode(content, fullPath, void 0, false).split("\n");
9395
9521
  const collapsedLines = this.getCollapsedLineLimit(20);
9396
9522
  const totalLines = lines.length;
9397
9523
  const hasMore = !this.expanded && totalLines > collapsedLines + 1;
@@ -9400,16 +9526,15 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9400
9526
  skippedAbove = totalLines - collapsedLines;
9401
9527
  lines = lines.slice(-collapsedLines);
9402
9528
  }
9403
- if (skippedAbove > 0) this.contentBox.addChild(new Text(border("│") + " " + theme.fg("muted", `... ${skippedAbove} lines above (ctrl+e to expand)`), 0, 0));
9529
+ if (skippedAbove > 0) this.blockLine(theme.fg("muted", `... ${skippedAbove} lines above (ctrl+e to expand)`));
9404
9530
  const borderedLines = lines.map((line) => {
9405
9531
  const truncated = truncateAnsi(line, maxLineWidth);
9406
- return border("│") + " " + theme.fg("toolOutput", truncated);
9532
+ return theme.fg("toolOutput", truncated);
9407
9533
  });
9408
- this.contentBox.addChild(new Text(borderedLines.join("\n"), 0, 0));
9409
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9534
+ this.blockLines(borderedLines);
9535
+ this.endBlock(footerText);
9410
9536
  return;
9411
9537
  }
9412
- const border = (char) => this.formatToolBorder(char);
9413
9538
  const status = this.getStatusIndicator();
9414
9539
  const termWidth = this.renderWidth;
9415
9540
  const maxLineWidth = termWidth - 4 - 0;
@@ -9419,18 +9544,18 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9419
9544
  const pathDisplay = fullPath ? fileLink(theme.fg("toolArgs", path), fullPath) : theme.fg("toolArgs", path);
9420
9545
  const footerText = `${theme.bold(theme.fg("toolTitle", "write"))} ${pathDisplay}${status}`;
9421
9546
  this.addLeadingPadding();
9422
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9547
+ this.startBlock();
9423
9548
  if (this.result.isError) {
9424
9549
  const output = this.getFormattedOutput();
9425
9550
  if (output) {
9426
9551
  const lines = output.split("\n").map((line) => {
9427
9552
  const truncated = truncateAnsi(line, maxLineWidth);
9428
- return border("│") + " " + theme.fg("error", truncated);
9553
+ return theme.fg("error", truncated);
9429
9554
  });
9430
- this.contentBox.addChild(new Text(lines.join("\n"), 0, 0));
9555
+ this.blockLines(lines);
9431
9556
  }
9432
9557
  } else if (content) {
9433
- let lines = highlightCode(content, fullPath).split("\n");
9558
+ let lines = highlightCode(content, fullPath, void 0, false).split("\n");
9434
9559
  const collapsedLines = this.getCollapsedLineLimit(20);
9435
9560
  const totalLines = lines.length;
9436
9561
  const hasMore = !this.expanded && totalLines > collapsedLines + 1;
@@ -9439,14 +9564,14 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9439
9564
  skippedAbove = totalLines - collapsedLines;
9440
9565
  lines = lines.slice(-collapsedLines);
9441
9566
  }
9442
- if (skippedAbove > 0) this.contentBox.addChild(new Text(border("│") + " " + theme.fg("muted", `... ${skippedAbove} lines above (ctrl+e to expand)`), 0, 0));
9567
+ if (skippedAbove > 0) this.blockLine(theme.fg("muted", `... ${skippedAbove} lines above (ctrl+e to expand)`));
9443
9568
  const borderedLines = lines.map((line) => {
9444
9569
  const truncated = truncateAnsi(line, maxLineWidth);
9445
- return border("│") + " " + theme.fg("toolOutput", truncated);
9570
+ return theme.fg("toolOutput", truncated);
9446
9571
  });
9447
- this.contentBox.addChild(new Text(borderedLines.join("\n"), 0, 0));
9572
+ this.blockLines(borderedLines);
9448
9573
  }
9449
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9574
+ this.endBlock(footerText);
9450
9575
  }
9451
9576
  renderListFilesEnhanced() {
9452
9577
  const argsObj = this.args;
@@ -9454,14 +9579,13 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9454
9579
  const path = argsObj?.path ? shortenPath(String(argsObj.path)) : "/";
9455
9580
  const pattern = argsObj?.pattern ? String(argsObj.pattern) : "";
9456
9581
  const patternDisplay = pattern ? " " + theme.fg("muted", pattern) : "";
9457
- const border = (char) => this.formatToolBorder(char);
9458
9582
  const status = this.getStatusIndicator();
9459
9583
  const maxLineWidth = this.renderWidth - 4 - 0;
9460
9584
  if (!this.result || this.isPartial) {
9461
9585
  const pathDisplay = fullPath ? fileLink(theme.fg("toolArgs", path), fullPath) : theme.fg("toolArgs", path);
9462
9586
  const footerText = `${theme.bold(theme.fg("toolTitle", "list"))} ${pathDisplay}${patternDisplay}${status}`;
9463
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9464
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9587
+ this.startBlock();
9588
+ this.endBlock(footerText);
9465
9589
  return;
9466
9590
  }
9467
9591
  const output = this.getFormattedOutput();
@@ -9481,18 +9605,17 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9481
9605
  }
9482
9606
  const pathDisplay = fullPath ? fileLink(theme.fg("toolArgs", path), fullPath) : theme.fg("toolArgs", path);
9483
9607
  const footerText = `${theme.bold(theme.fg("toolTitle", "list"))} ${pathDisplay}${patternDisplay}${summaryDisplay}${status}`;
9484
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9485
- if (skippedAbove > 0) this.contentBox.addChild(new Text(border("│") + " " + theme.fg("muted", `... ${skippedAbove} lines above (ctrl+e to expand)`), 0, 0));
9608
+ this.startBlock();
9609
+ if (skippedAbove > 0) this.blockLine(theme.fg("muted", `... ${skippedAbove} lines above (ctrl+e to expand)`));
9486
9610
  const borderedLines = lines.map((line) => {
9487
9611
  const truncated = truncateAnsi(line, maxLineWidth);
9488
- return border("│") + " " + theme.fg("toolOutput", truncated);
9612
+ return theme.fg("toolOutput", truncated);
9489
9613
  });
9490
- this.contentBox.addChild(new Text(borderedLines.join("\n"), 0, 0));
9491
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9614
+ this.blockLines(borderedLines);
9615
+ this.endBlock(footerText);
9492
9616
  }
9493
9617
  }
9494
9618
  renderLspInspectEnhanced() {
9495
- const border = (char) => this.formatToolBorder(char);
9496
9619
  const status = this.getStatusIndicator();
9497
9620
  const maxLineWidth = this.renderWidth - 4 - 0;
9498
9621
  const argsObj = this.args;
@@ -9506,17 +9629,17 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9506
9629
  ].filter(Boolean).join(" ");
9507
9630
  if (!this.result || this.isPartial) {
9508
9631
  const footerText = `${theme.bold(theme.fg("toolTitle", "lsp_inspect"))}${argsSummary ? " " + theme.fg("toolArgs", argsSummary) : ""}${status}`;
9509
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9510
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9632
+ this.startBlock();
9633
+ this.endBlock(footerText);
9511
9634
  return;
9512
9635
  }
9513
9636
  const rawText = this.result.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
9514
9637
  if (this.result.isError || !rawText.trim()) {
9515
9638
  const footerText = `${theme.bold(theme.fg("toolTitle", "lsp_inspect"))}${argsSummary ? " " + theme.fg("toolArgs", argsSummary) : ""}${status}`;
9516
9639
  const output = this.getFormattedOutput();
9517
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9518
- if (output) this.contentBox.addChild(new Text(border("│") + " " + theme.fg("error", output), 0, 0));
9519
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9640
+ this.startBlock();
9641
+ if (output) this.blockLine(theme.fg("error", output));
9642
+ this.endBlock(footerText);
9520
9643
  return;
9521
9644
  }
9522
9645
  let parsed;
@@ -9528,67 +9651,64 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9528
9651
  }
9529
9652
  if (parsed.error) {
9530
9653
  const footerText = `${theme.bold(theme.fg("toolTitle", "lsp_inspect"))}${argsSummary ? " " + theme.fg("toolArgs", argsSummary) : ""}${status}`;
9531
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9532
- this.contentBox.addChild(new Text(border("│") + " " + theme.fg("error", parsed.error), 0, 0));
9533
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9654
+ this.startBlock();
9655
+ this.blockLine(theme.fg("error", parsed.error));
9656
+ this.endBlock(footerText);
9534
9657
  return;
9535
9658
  }
9536
9659
  const footerText = `${theme.bold(theme.fg("toolTitle", "lsp_inspect"))}${argsSummary ? " " + theme.fg("toolArgs", argsSummary) : ""}${status}`;
9537
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9660
+ this.startBlock();
9538
9661
  if (parsed.hover) {
9539
9662
  const hoverLines = (parsed.hover.value || "").split("\n").filter((line) => line.trim() !== "");
9540
- if (hoverLines.length > 0) this.contentBox.addChild(new Text(border("│") + " " + theme.fg("toolArgs", "hover:"), 0, 0));
9663
+ if (hoverLines.length > 0) this.blockLine(theme.fg("toolArgs", "hover:"));
9541
9664
  for (const line of hoverLines) {
9542
9665
  const truncated = truncateAnsi(line, maxLineWidth - 2);
9543
- const prefix = border("│") + " ";
9544
- this.contentBox.addChild(new Text(prefix + theme.fg("text", truncated), 0, 0));
9666
+ this.blockLine(theme.fg("text", truncated));
9545
9667
  }
9546
9668
  }
9547
9669
  if (parsed.diagnostics && parsed.diagnostics.length > 0) {
9548
- this.contentBox.addChild(new Text(border("│"), 0, 0));
9549
- this.contentBox.addChild(new Text(border("│") + " " + theme.fg("toolArgs", "diagnostics:"), 0, 0));
9670
+ this.blockLine("");
9671
+ this.blockLine(theme.fg("toolArgs", "diagnostics:"));
9550
9672
  for (const diagnostic of parsed.diagnostics) {
9551
9673
  const diagLine = `${diagnostic.source ? `${diagnostic.severity} (${diagnostic.source})` : diagnostic.severity}: ${diagnostic.message}`;
9552
- this.contentBox.addChild(new Text(border("│") + " " + theme.fg(diagnostic.severity === "error" ? "error" : "text", truncateAnsi(diagLine, maxLineWidth - 2)), 0, 0));
9674
+ this.blockLine(theme.fg(diagnostic.severity === "error" ? "error" : "text", truncateAnsi(diagLine, maxLineWidth - 2)));
9553
9675
  }
9554
9676
  }
9555
9677
  if (parsed.definition && parsed.definition.length > 0) {
9556
- this.contentBox.addChild(new Text(border("│"), 0, 0));
9557
- this.contentBox.addChild(new Text(border("│") + " " + theme.fg("toolArgs", "definition:"), 0, 0));
9678
+ this.blockLine("");
9679
+ this.blockLine(theme.fg("toolArgs", "definition:"));
9558
9680
  for (const def of parsed.definition) {
9559
9681
  const location = def.location || "";
9560
9682
  const preview = def.preview || "";
9561
9683
  const parsedLoc = this.parseLspLocation(location);
9562
- const displayLoc = parsedLoc ? fileLink(theme.fg("toolOutput", parsedLoc.shortPath + ":" + parsedLoc.lineCol), parsedLoc.absPath, parsedLoc.line) : theme.fg("toolOutput", location);
9563
- const defLine = border("│") + " " + displayLoc;
9564
- this.contentBox.addChild(new Text(truncateAnsi(defLine, maxLineWidth), 0, 0));
9684
+ const defLine = parsedLoc ? fileLink(theme.fg("toolOutput", parsedLoc.shortPath + ":" + parsedLoc.lineCol), parsedLoc.absPath, parsedLoc.line) : theme.fg("toolOutput", location);
9685
+ this.blockLine(truncateAnsi(defLine, maxLineWidth));
9565
9686
  if (preview) {
9566
- const previewLine = border("│") + " " + theme.fg("text", truncateAnsi(preview, maxLineWidth - 3));
9567
- this.contentBox.addChild(new Text(previewLine, 0, 0));
9687
+ const previewLine = " " + theme.fg("text", truncateAnsi(preview, maxLineWidth - 3));
9688
+ this.blockLine(previewLine);
9568
9689
  }
9569
9690
  }
9570
9691
  }
9571
9692
  if (parsed.implementation && parsed.implementation.length > 0) {
9572
9693
  const implCount = parsed.implementation.length;
9573
9694
  const implLabel = implCount === 1 ? "implementation:" : `implementations (${implCount}):`;
9574
- this.contentBox.addChild(new Text(border("│"), 0, 0));
9695
+ this.blockLine("");
9575
9696
  const maxShow = this.expanded ? parsed.implementation.length : 5;
9576
9697
  const shown = parsed.implementation.slice(0, maxShow);
9577
9698
  const remaining = parsed.implementation.length - maxShow;
9578
- this.contentBox.addChild(new Text(border("│") + " " + theme.fg("toolArgs", implLabel), 0, 0));
9699
+ this.blockLine(theme.fg("toolArgs", implLabel));
9579
9700
  for (const loc of shown) {
9580
9701
  const parsedLoc = this.parseLspLocation(loc);
9581
- const displayLoc = parsedLoc ? fileLink(theme.fg("toolOutput", parsedLoc.shortPath + ":" + parsedLoc.lineCol), parsedLoc.absPath, parsedLoc.line) : theme.fg("toolOutput", loc);
9582
- const implLine = border("│") + " " + displayLoc;
9583
- this.contentBox.addChild(new Text(truncateAnsi(implLine, maxLineWidth), 0, 0));
9702
+ const implLine = parsedLoc ? fileLink(theme.fg("toolOutput", parsedLoc.shortPath + ":" + parsedLoc.lineCol), parsedLoc.absPath, parsedLoc.line) : theme.fg("toolOutput", loc);
9703
+ this.blockLine(truncateAnsi(implLine, maxLineWidth));
9584
9704
  }
9585
9705
  if (remaining > 0 && !this.expanded) {
9586
- const moreLine = border("│") + " " + theme.fg("toolOutput", `... ${remaining} more (ctrl+e to expand)`);
9587
- this.contentBox.addChild(new Text(moreLine, 0, 0));
9706
+ const moreLine = theme.fg("toolOutput", `... ${remaining} more (ctrl+e to expand)`);
9707
+ this.blockLine(moreLine);
9588
9708
  }
9589
9709
  }
9590
- if (!parsed.hover && !parsed.diagnostics?.length && !parsed.definition?.length && !parsed.implementation?.length) this.contentBox.addChild(new Text(border("│") + " " + theme.fg("muted", "No hover, diagnostics, definition, or implementation results"), 0, 0));
9591
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9710
+ if (!parsed.hover && !parsed.diagnostics?.length && !parsed.definition?.length && !parsed.implementation?.length) this.blockLine(theme.fg("muted", "No hover, diagnostics, definition, or implementation results"));
9711
+ this.endBlock(footerText);
9592
9712
  }
9593
9713
  /**
9594
9714
  * Parse an LSP location string like "$cwd/path:Lline:Cchar" into components.
@@ -9624,16 +9744,15 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9624
9744
  renderTaskWriteEnhanced() {
9625
9745
  const tasks = this.args?.tasks;
9626
9746
  const status = this.getStatusIndicator();
9627
- const border = (char) => this.formatToolBorder(char);
9628
9747
  const count = tasks?.length ?? 0;
9629
9748
  const countSuffix = count > 0 ? theme.fg("muted", ` (${count} tasks)`) : "";
9630
9749
  const footerText = `${theme.bold(theme.fg("toolTitle", "task_write"))}${countSuffix}${status}`;
9631
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9750
+ this.startBlock();
9632
9751
  if (!this.isPartial && this.result?.isError) {
9633
9752
  const output = this.getFormattedOutput();
9634
- if (output) this.contentBox.addChild(new Text(border("│") + " " + theme.fg("error", output), 0, 0));
9753
+ if (output) this.blockLine(theme.fg("error", output));
9635
9754
  }
9636
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9755
+ this.endBlock(footerText);
9637
9756
  }
9638
9757
  renderWebSearchEnhanced() {
9639
9758
  const argsObj = this.args;
@@ -9647,10 +9766,9 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9647
9766
  const status = this.getStatusIndicator();
9648
9767
  const queryDisplay = query ? ` ${theme.fg("toolArgs", `"${query}"`)}` : "";
9649
9768
  const footerText = `${theme.bold(theme.fg("toolTitle", "web_search"))}${queryDisplay}${status}`;
9650
- const border = (char) => this.formatToolBorder(char);
9651
9769
  if (!this.result || this.isPartial) {
9652
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9653
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9770
+ this.startBlock();
9771
+ this.endBlock(footerText);
9654
9772
  return;
9655
9773
  }
9656
9774
  if (this.result.isError) {
@@ -9661,7 +9779,7 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9661
9779
  if (output) {
9662
9780
  const maxLineWidth = this.renderWidth - 4 - 0;
9663
9781
  this.addLeadingPadding();
9664
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9782
+ this.startBlock();
9665
9783
  let lines = output.split("\n");
9666
9784
  const collapsedLines = this.getCollapsedLineLimit(10);
9667
9785
  const totalLines = lines.length;
@@ -9669,15 +9787,18 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9669
9787
  if (hasMore) lines = lines.slice(0, collapsedLines);
9670
9788
  const borderedLines = lines.map((line) => {
9671
9789
  const truncated = truncateAnsi(line, maxLineWidth);
9672
- return border("│") + " " + theme.fg("toolOutput", truncated);
9790
+ return theme.fg("toolOutput", truncated);
9673
9791
  });
9674
- this.contentBox.addChild(new Text(borderedLines.join("\n"), 0, 0));
9792
+ this.blockLines(borderedLines);
9675
9793
  if (hasMore) {
9676
9794
  const remaining = totalLines - collapsedLines;
9677
- this.contentBox.addChild(new Text(border("│") + " " + theme.fg("muted", `... ${remaining} more lines (ctrl+e to expand)`), 0, 0));
9795
+ this.blockLine(theme.fg("muted", `... ${remaining} more lines (ctrl+e to expand)`));
9678
9796
  }
9679
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9680
- } else this.contentBox.addChild(new Text(footerText, 0, 0));
9797
+ this.endBlock(footerText);
9798
+ } else {
9799
+ this.startBlock();
9800
+ this.endBlock(footerText);
9801
+ }
9681
9802
  }
9682
9803
  /**
9683
9804
  * Format web search results as a clean list of titles + URLs.
@@ -9738,8 +9859,7 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9738
9859
  return outcome ? `Outcome: ${outcome}` : "";
9739
9860
  }
9740
9861
  renderAgentSignalSendEnhanced() {
9741
- const border = (char) => this.formatToolBorder(char);
9742
- const maxLineWidth = Math.max(10, this.renderWidth - 0 - 4);
9862
+ const maxLineWidth = Math.max(10, this.renderWidth - 0 - 6);
9743
9863
  const args = this.args;
9744
9864
  const target = sanitizeAnsiForRendering(typeof args?.targetId === "string" ? args.targetId : "unknown peer");
9745
9865
  const priority = sanitizeAnsiForRendering(typeof args?.priority === "string" ? args.priority : "medium");
@@ -9751,34 +9871,33 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9751
9871
  const renderField = (label, value, preserveMessageFormatting = false) => {
9752
9872
  const displayValue = value || "—";
9753
9873
  const lines = preserveMessageFormatting ? this.wrapAgentSignalMessageLines(displayValue, maxLineWidth) : this.wrapPreviewLines(displayValue, maxLineWidth, maxLineWidth);
9754
- this.contentBox.addChild(new Text(`${border("│")} ${theme.fg("toolArgs", `${label}:`)}`, 0, 0));
9755
- for (const line of lines) this.contentBox.addChild(new Text(`${border("│")} ${theme.fg("text", line)}`, 0, 0));
9874
+ this.blockLine(`${theme.fg("toolArgs", `${label}:`)}`);
9875
+ for (const line of lines) this.blockLine(` ${theme.fg("text", line)}`);
9756
9876
  };
9757
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9877
+ this.startBlock();
9758
9878
  renderField("target", target);
9759
- this.contentBox.addChild(new Text(`${border("│")} ${theme.fg("toolArgs", "priority:")} ${theme.fg("text", priority)} ${theme.fg("toolArgs", "expects reply:")} ${theme.fg("text", expectsReply)}`, 0, 0));
9760
- this.contentBox.addChild(new Text(border("│"), 0, 0));
9879
+ this.blockLine(`${theme.fg("toolArgs", "priority:")} ${theme.fg("text", priority)} ${theme.fg("toolArgs", "expects reply:")} ${theme.fg("text", expectsReply)}`);
9880
+ this.blockLine("");
9761
9881
  renderField("message", message, true);
9762
9882
  if (outcome) {
9763
- this.contentBox.addChild(new Text(border("│"), 0, 0));
9883
+ this.blockLine("");
9764
9884
  renderField("outcome", outcome);
9765
9885
  }
9766
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9886
+ this.endBlock(footerText);
9767
9887
  }
9768
9888
  renderGenericToolEnhanced() {
9769
- const border = (char) => this.formatToolBorder(char);
9770
9889
  const status = this.getStatusIndicator();
9771
9890
  const argsSummary = this.formatArgsSummary();
9772
9891
  const footerText = `${theme.bold(theme.fg("toolTitle", this.toolName))}${argsSummary}${status}`;
9773
9892
  if (!this.result || this.isPartial) {
9774
9893
  const partialOutput = this.result ? this.getFormattedOutput() : "";
9775
9894
  const preview = partialOutput ? partialOutput.split("\n") : this.formatArgsPreview();
9776
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9895
+ this.startBlock();
9777
9896
  if (preview.length > 0) {
9778
- const previewLines = preview.map((line) => border("│") + " " + theme.fg("toolOutput", line));
9779
- this.contentBox.addChild(new Text(previewLines.join("\n"), 0, 0));
9897
+ const previewLines = preview.map((line) => theme.fg("toolOutput", line));
9898
+ this.blockLines(previewLines);
9780
9899
  }
9781
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9900
+ this.endBlock(footerText);
9782
9901
  return;
9783
9902
  }
9784
9903
  if (this.result.isError) {
@@ -9789,7 +9908,7 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9789
9908
  if (output) {
9790
9909
  const maxLineWidth = this.renderWidth - 4 - 0;
9791
9910
  this.addLeadingPadding();
9792
- this.contentBox.addChild(new Text(border("╭──"), 0, 0));
9911
+ this.startBlock();
9793
9912
  let lines = output.split("\n");
9794
9913
  const collapsedLines = this.getCollapsedLineLimit(10);
9795
9914
  const totalLines = lines.length;
@@ -9797,15 +9916,18 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9797
9916
  if (hasMore) lines = lines.slice(0, collapsedLines);
9798
9917
  const borderedLines = lines.map((line) => {
9799
9918
  const truncated = truncateAnsi(line, maxLineWidth);
9800
- return border("│") + " " + theme.fg("toolOutput", truncated);
9919
+ return theme.fg("toolOutput", truncated);
9801
9920
  });
9802
- this.contentBox.addChild(new Text(borderedLines.join("\n"), 0, 0));
9921
+ this.blockLines(borderedLines);
9803
9922
  if (hasMore) {
9804
9923
  const remaining = totalLines - collapsedLines;
9805
- this.contentBox.addChild(new Text(border("│") + " " + theme.fg("muted", `... ${remaining} more lines (ctrl+e to expand)`), 0, 0));
9924
+ this.blockLine(theme.fg("muted", `... ${remaining} more lines (ctrl+e to expand)`));
9806
9925
  }
9807
- this.contentBox.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
9808
- } else this.contentBox.addChild(new Text(footerText, 0, 0));
9926
+ this.endBlock(footerText);
9927
+ } else {
9928
+ this.startBlock();
9929
+ this.endBlock(footerText);
9930
+ }
9809
9931
  }
9810
9932
  /**
9811
9933
  * Format a compact args preview as key="value" pairs.
@@ -9885,10 +10007,35 @@ var ToolExecutionComponentEnhanced = class extends WidthAwareContainer {
9885
10007
  if (this.isPartial) return theme.fg("warning", ` ◌ background · ${this.backgroundTaskId}`);
9886
10008
  return isError ? theme.fg("error", ` ✗ background · ${this.backgroundTaskId}`) : theme.fg("success", ` ✓ background · ${this.backgroundTaskId}`);
9887
10009
  }
10010
+ /** Output lines of the block being built (see startBlock / endBlock). */
10011
+ pendingBlock = null;
10012
+ /** Start a tool block: collect output lines until endBlock prints the title and the panel. */
10013
+ startBlock() {
10014
+ this.pendingBlock = [];
10015
+ }
10016
+ blockLine(line) {
10017
+ (this.pendingBlock ??= []).push(line);
10018
+ }
10019
+ blockLines(lines) {
10020
+ for (const line of lines) this.blockLine(line);
10021
+ }
10022
+ /**
10023
+ * Finish the block: a "● title" row (the dot carries the status: green done, red failed, grey
10024
+ * running), then any collected output on a shaded half-block panel, indented under the title.
10025
+ */
10026
+ endBlock(title, isError = this.isErrorResult()) {
10027
+ const output = this.pendingBlock ?? [];
10028
+ this.pendingBlock = null;
10029
+ const width = Math.max(1, this.renderWidth - 0);
10030
+ this.contentBox.addChild(new Text(toolBlock(this.getStatusDot(isError), title, output, width).join("\n"), 0, 0));
10031
+ }
10032
+ getStatusDot(isError = this.isErrorResult()) {
10033
+ return statusDot(this.isPartial ? "running" : isError ? "error" : "done");
10034
+ }
9888
10035
  getStatusIndicator(isError = this.isErrorResult()) {
9889
10036
  const backgroundStatus = this.getBackgroundStatusIndicator(isError);
9890
10037
  if (backgroundStatus) return backgroundStatus;
9891
- return this.isPartial ? theme.fg("muted", " ⋯") : isError ? theme.fg("error", " ✗") : theme.fg("success", " ✓");
10038
+ return !this.isPartial && isError ? theme.fg("error", " ✗") : "";
9892
10039
  }
9893
10040
  getDurationSuffix() {
9894
10041
  const duration = this.formatDuration();
@@ -10010,13 +10157,13 @@ function getPlainCodeFromViewOutput(content, startLine) {
10010
10157
  return codeLines.join("\n");
10011
10158
  }
10012
10159
  /** Strip line number formatting (cat -n or workspace →) and apply syntax highlighting */
10013
- function highlightCode(content, path, startLine) {
10014
- const code = getPlainCodeFromViewOutput(content, startLine);
10160
+ function highlightCode(content, path, startLine, hasLineNumbers = true) {
10161
+ const code = hasLineNumbers ? getPlainCodeFromViewOutput(content, startLine) : content.replace(/\n+$/, "");
10015
10162
  try {
10016
10163
  return highlight(code, {
10017
10164
  language: getLanguageFromPath(path),
10018
10165
  ignoreIllegals: true,
10019
- theme: CODE_HIGHLIGHT_THEME
10166
+ theme: codeHighlightTheme()
10020
10167
  });
10021
10168
  } catch {
10022
10169
  return code;
@@ -10042,8 +10189,10 @@ function parseErrorFromContent(content) {
10042
10189
  //#endregion
10043
10190
  //#region src/tui/components/user-message.ts
10044
10191
  /**
10045
- * Component that renders a user message with a thin border that fits the content.
10192
+ * Component that renders a sent user message on the same half-block panel as the prompt.
10046
10193
  */
10194
+ /** Columns of shaded padding on each side of a sent message's text. */
10195
+ const PAD = 2;
10047
10196
  /**
10048
10197
  * Strip ANSI escape sequences from a string.
10049
10198
  */
@@ -10051,7 +10200,8 @@ function stripAnsi$1(s) {
10051
10200
  return s.replace(/\x1b\[[0-9;]*m/g, "");
10052
10201
  }
10053
10202
  /**
10054
- * A renderable wrapper that adds a thin box-drawing border sized to content.
10203
+ * A renderable wrapper that puts content on the prompt's half-block panel (full width, shaded, no border).
10204
+ * `label` (e.g. "steer") is shown before the text.
10055
10205
  */
10056
10206
  var BorderedBox = class {
10057
10207
  child;
@@ -10082,52 +10232,18 @@ var BorderedBox = class {
10082
10232
  return result;
10083
10233
  }
10084
10234
  renderUncached(width) {
10085
- const borderColor = (s) => this.borderColor ? chalk.hex(this.borderColor)(s) : this.pending ? chalk.hex(theme.getTheme().dim)(s) : chalk.hex(tintHex(mastra.green, 1))(s);
10086
- const maxInnerWidth = Math.max(1, width - 6 - 2 - 0 - 1);
10087
- const childLines = this.child.render(maxInnerWidth);
10235
+ const prefix = this.label ? theme.fg("dim", `${this.label} · `) : "";
10236
+ const prefixWidth = this.label ? visibleWidth(this.label) + 3 : 0;
10237
+ const contentWidth = Math.max(1, width - 0 - prefixWidth - PAD * 2);
10238
+ const childLines = this.child.render(contentWidth);
10088
10239
  if (childLines.length === 0) return [];
10089
- const trimmedLines = [];
10090
- let maxContentWidth = 0;
10091
- for (const line of childLines) {
10092
- const trimmed = line.replace(/\s+$/, "");
10093
- trimmedLines.push(trimmed);
10094
- const w = visibleWidth(stripAnsi$1(trimmed));
10095
- if (w > maxContentWidth) maxContentWidth = w;
10096
- }
10097
- maxContentWidth = Math.min(maxContentWidth, maxInnerWidth);
10098
- let boxInner = maxContentWidth + 2;
10099
- if (this.label) {
10100
- const labelOverhead = ` ${this.label} `.length + 2;
10101
- boxInner = Math.max(boxInner + 4, labelOverhead) - 4;
10102
- }
10103
- const boxWidth = boxInner + 4;
10104
- const lines = [];
10105
- const promptPrefix = chalk.hex(tintHex(mastra.green, 1))("»") + " ";
10106
- const promptWidth = 2;
10107
- if (this.label) {
10108
- const labelText = ` ${this.label} `;
10109
- const labelLen = labelText.length;
10110
- const remaining = Math.max(0, boxWidth - 2 - labelLen);
10111
- lines.push(borderColor("╭") + chalk.hex(theme.getTheme().dim)(labelText) + borderColor(`${"─".repeat(remaining)}╮`));
10112
- } else lines.push(borderColor(`╭${"─".repeat(boxWidth - 2)}╮`));
10113
- for (let i = 0; i < trimmedLines.length; i++) {
10114
- let trimmed = trimmedLines[i];
10115
- let vis = visibleWidth(stripAnsi$1(trimmed));
10116
- const lineMaxWidth = i === 0 ? boxInner - promptWidth : boxInner;
10117
- if (vis > lineMaxWidth) {
10118
- trimmed = truncateToWidth(trimmed, lineMaxWidth);
10119
- vis = visibleWidth(stripAnsi$1(trimmed));
10120
- }
10121
- if (i === 0) {
10122
- const padNeeded = Math.max(0, boxInner - vis - promptWidth);
10123
- lines.push(borderColor("│") + " " + promptPrefix + trimmed + " ".repeat(padNeeded) + " " + borderColor("│"));
10124
- } else {
10125
- const padNeeded = Math.max(0, boxInner - vis);
10126
- lines.push(borderColor("│") + " " + trimmed + " ".repeat(padNeeded) + " " + borderColor("│"));
10127
- }
10128
- }
10129
- lines.push(borderColor(`╰${"─".repeat(boxWidth - 2)}╯`));
10130
- return lines.map((l) => "" + l);
10240
+ const rows = childLines.map((line, i) => {
10241
+ let content = line.replace(/\s+$/, "");
10242
+ if (visibleWidth(stripAnsi$1(content)) > contentWidth) content = truncateToWidth(content, contentWidth);
10243
+ return `${" ".repeat(PAD)}${i === 0 ? prefix : " ".repeat(prefixWidth)}${content}`;
10244
+ });
10245
+ const fitWidth = Math.max(...rows.map((r) => visibleWidth(stripAnsi$1(r)))) + PAD;
10246
+ return halfBlockPanel(rows, Math.min(width - 0, fitWidth), promptSurface()).map((l) => "" + l);
10131
10247
  }
10132
10248
  };
10133
10249
  var UserMessageComponent = class extends Container {
@@ -10160,20 +10276,33 @@ var PendingUserMessageComponent = class extends Container {
10160
10276
  //#endregion
10161
10277
  //#region src/tui/components/tool-approval-dialog.ts
10162
10278
  /**
10163
- * Tool approval dialog component.
10164
- * Shows tool details and prompts user to approve or decline execution.
10279
+ * Tool approval prompt: one inline row in the chat, directly under the pending tool call.
10165
10280
  *
10166
10281
  * Keyboard shortcuts:
10167
10282
  * y — approve this one call
10168
10283
  * n / Esc — decline this call
10169
10284
  * a — always allow this category for this thread
10170
10285
  * Y — switch to YOLO mode (approve all)
10286
+ * Ctrl+E — show / hide long arguments in full (when the card lists them)
10287
+ */
10288
+ /** Long argument values (e.g. file contents) stop after this many lines until expanded with Ctrl+E. */
10289
+ const TARGET_MAX_LINES = 40;
10290
+ /**
10291
+ * Arguments come from the model, so control characters must not reach the terminal: escape sequences could
10292
+ * restyle, move or clear what the card shows. They're shown as visible \xNN escapes (the card shows exactly
10293
+ * what gets approved); newlines stay, CRLF becomes a newline and tabs become spaces.
10171
10294
  */
10172
- var ToolApprovalDialogComponent = class extends Box {
10295
+ function escapeControls(text) {
10296
+ return text.replace(/\r\n/g, "\n").replace(/\t/g, " ").replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f]/g, (c) => `\\x${c.charCodeAt(0).toString(16).padStart(2, "0")}`);
10297
+ }
10298
+ var ToolApprovalDialogComponent = class {
10173
10299
  toolName;
10174
10300
  args;
10175
10301
  categoryLabel;
10302
+ showTarget;
10176
10303
  onAction;
10304
+ requestRender;
10305
+ expanded = false;
10177
10306
  resolved = false;
10178
10307
  _focused = false;
10179
10308
  get focused() {
@@ -10183,28 +10312,84 @@ var ToolApprovalDialogComponent = class extends Box {
10183
10312
  this._focused = value;
10184
10313
  }
10185
10314
  constructor(options) {
10186
- super(2, 1, (text) => theme.bg("overlayBg", text));
10187
10315
  this.toolName = options.toolName;
10188
10316
  this.args = options.args;
10189
10317
  this.categoryLabel = options.categoryLabel;
10318
+ this.showTarget = options.showTarget ?? false;
10190
10319
  this.onAction = options.onAction;
10191
- this.buildUI();
10320
+ this.requestRender = options.requestRender;
10192
10321
  }
10193
- buildUI() {
10194
- this.addChild(new Text(theme.fg("warning", "⚠ Tool Approval Required"), 0, 0));
10195
- this.addChild(new Spacer(1));
10196
- this.addChild(new Text(theme.fg("accent", `Tool: `) + theme.fg("text", this.toolName), 0, 0));
10197
- if (this.categoryLabel) this.addChild(new Text(theme.fg("accent", `Category: `) + theme.fg("text", this.categoryLabel), 0, 0));
10198
- this.addChild(new Spacer(1));
10199
- this.addChild(new Text(theme.fg("muted", "Arguments:"), 0, 0));
10200
- const argsText = this.formatArgs(this.args);
10201
- for (const line of argsText.split("\n").slice(0, 10)) this.addChild(new Text(theme.fg("text", " " + line), 0, 0));
10202
- if (argsText.split("\n").length > 10) this.addChild(new Text(theme.fg("muted", " ... (truncated)"), 0, 0));
10203
- this.addChild(new Spacer(1));
10204
- const categoryHint = this.categoryLabel ? `lways allow ${this.categoryLabel.toLowerCase()}` : "lways allow category";
10205
- const dimColor = chalk.hex(theme.getTheme().dim);
10206
- const key = chalk.hex(theme.getTheme().text).bold;
10207
- this.addChild(new Text(theme.fg("accent", "Allow? ") + key("y") + dimColor("es ") + key("n") + dimColor("o ") + key("a") + dimColor(categoryHint + " ") + key("Y") + dimColor("olo"), 0, 0));
10322
+ invalidate() {}
10323
+ /**
10324
+ * One inline row under the pending tool call (which already shows the command / path):
10325
+ * ▎ Allow? y yes · a always allow Execute · Y YOLO · n no
10326
+ * On narrow terminals the options wrap under "Allow?".
10327
+ * When no visible row shows this exact call (`showTarget`), the card names the tool and lists its arguments:
10328
+ * ▎ Allow write_file?
10329
+ * ▎ path: src/auth.ts
10330
+ * ▎ y yes · a always allow Edit · Y YOLO · n no
10331
+ */
10332
+ render(width) {
10333
+ const warning = theme.getTheme().warning;
10334
+ const key = (k, label) => `${chalk.bold.hex(warning)(k)} ${theme.fg("muted", label)}`;
10335
+ const always = this.categoryLabel ? `always allow ${this.categoryLabel}` : "always allow category";
10336
+ const options = [
10337
+ key("y", "yes"),
10338
+ key("a", always),
10339
+ key("Y", "YOLO"),
10340
+ key("n", "no")
10341
+ ];
10342
+ const sep = theme.fg("dim", " · ");
10343
+ const room = width - 2;
10344
+ if (!this.showTarget) {
10345
+ const line = `${theme.bold(theme.fg("text", "Allow?"))} ${options.join(sep)}`;
10346
+ if (visibleWidth(line) <= room) return card(warning, [line]);
10347
+ }
10348
+ const rows = [this.showTarget ? theme.bold(theme.fg("text", `Allow ${this.toolName}?`)) : theme.bold(theme.fg("text", "Allow?")), ...this.showTarget ? this.targetArgRows(room - 2).map((row) => theme.fg("muted", ` ${row}`)) : []];
10349
+ let row = "";
10350
+ for (const option of options) {
10351
+ const next = row ? `${row}${sep}${option}` : option;
10352
+ if (row && visibleWidth(next) > room) {
10353
+ rows.push(row);
10354
+ row = option;
10355
+ } else row = next;
10356
+ }
10357
+ rows.push(row);
10358
+ return card(warning, rows).map((l) => truncateToWidth(l, width));
10359
+ }
10360
+ /**
10361
+ * Every argument in full, wrapped to `width`: this is what gets approved, so nothing is cut to one line.
10362
+ * Very long values (file contents and the like) stop after TARGET_MAX_LINES until Ctrl+E shows the rest.
10363
+ */
10364
+ targetArgRows(width) {
10365
+ const args = this.args;
10366
+ if (args === null || args === void 0 || typeof args !== "object") return [escapeControls(String(args ?? "(none)"))];
10367
+ const entries = Object.entries(args).filter(([, v]) => v !== null && v !== void 0);
10368
+ if (entries.length === 0) return ["(none)"];
10369
+ const rows = [];
10370
+ for (const [key, value] of entries) {
10371
+ const wrapped = escapeControls(`${key}: ${typeof value === "string" ? value : safeStringify(value)}`).split("\n").flatMap((line) => line ? wrapTextWithAnsi(line, Math.max(10, width)) : [""]);
10372
+ if (this.expanded || wrapped.length <= TARGET_MAX_LINES) rows.push(...wrapped);
10373
+ else {
10374
+ rows.push(...wrapped.slice(0, TARGET_MAX_LINES));
10375
+ rows.push(`… ${wrapped.length - TARGET_MAX_LINES} more lines · ctrl+e to show all`);
10376
+ }
10377
+ }
10378
+ return rows;
10379
+ }
10380
+ /**
10381
+ * Whether Ctrl+E belongs to the card (it lists the arguments). Otherwise Ctrl+E keeps its usual job of
10382
+ * expanding the tool row above, so a collapsed preview can be read in full before approving.
10383
+ */
10384
+ handlesExpand() {
10385
+ return this.showTarget;
10386
+ }
10387
+ /** Arguments as "key: value" lines (used by tests and for tools without a call row). */
10388
+ describeArgs() {
10389
+ return this.formatArgs(this.args);
10390
+ }
10391
+ get tool() {
10392
+ return this.toolName;
10208
10393
  }
10209
10394
  formatArgs(args) {
10210
10395
  if (args === null || args === void 0) return "(none)";
@@ -10236,6 +10421,11 @@ var ToolApprovalDialogComponent = class extends Box {
10236
10421
  this.emit({ type: "decline" });
10237
10422
  return;
10238
10423
  }
10424
+ if (data === "" && this.showTarget) {
10425
+ this.expanded = !this.expanded;
10426
+ this.requestRender?.();
10427
+ return;
10428
+ }
10239
10429
  switch (decodePrintableShortcut(data)) {
10240
10430
  case "y":
10241
10431
  this.emit({ type: "approve" });
@@ -10251,9 +10441,6 @@ var ToolApprovalDialogComponent = class extends Box {
10251
10441
  break;
10252
10442
  }
10253
10443
  }
10254
- render(maxWidth) {
10255
- return super.render(maxWidth);
10256
- }
10257
10444
  };
10258
10445
  var RenderScheduler = class {
10259
10446
  render;
@@ -10639,13 +10826,18 @@ function handleToolApprovalRequired(ctx, toolCallId, toolName, args) {
10639
10826
  const firePermissionResult = (decision) => {
10640
10827
  state.hookManager?.runPermissionResult("tool_approval", toolCallId, toolName, decision, args).catch(() => {});
10641
10828
  };
10829
+ const askPreview = toolName === "ask_user" ? state.pendingAskUserComponents.get(toolCallId) : void 0;
10830
+ askPreview?.updateArgs(args);
10831
+ const visibleCall = state.pendingTools.get(toolCallId)?.getToolCall?.();
10642
10832
  const dialog = new ToolApprovalDialogComponent({
10643
10833
  toolCallId,
10644
10834
  toolName,
10645
10835
  args,
10646
10836
  categoryLabel,
10837
+ showTarget: !askPreview && (state.quietMode || !visibleCall || visibleCall.toolName !== toolName || safeStringify(visibleCall.args) !== safeStringify(args)),
10838
+ requestRender: () => state.ui.requestRender(),
10647
10839
  onAction: (action) => {
10648
- state.ui.hideOverlay();
10840
+ removeApproval();
10649
10841
  state.pendingApprovalDismiss = null;
10650
10842
  if (action.type === "approve") {
10651
10843
  firePermissionResult("approved");
@@ -10675,8 +10867,13 @@ function handleToolApprovalRequired(ctx, toolCallId, toolName, args) {
10675
10867
  }
10676
10868
  }
10677
10869
  });
10870
+ const removeApproval = () => {
10871
+ if (state.activeInlineApproval === dialog) state.activeInlineApproval = void 0;
10872
+ state.chatContainer.removeChild(dialog);
10873
+ state.ui.requestRender();
10874
+ };
10678
10875
  state.pendingApprovalDismiss = (declineContext) => {
10679
- state.ui.hideOverlay();
10876
+ removeApproval();
10680
10877
  state.pendingApprovalDismiss = null;
10681
10878
  firePermissionResult("dismissed");
10682
10879
  state.session.respondToToolApproval({
@@ -10685,7 +10882,8 @@ function handleToolApprovalRequired(ctx, toolCallId, toolName, args) {
10685
10882
  declineContext
10686
10883
  });
10687
10884
  };
10688
- showModalOverlay(state.ui, dialog, { widthPercent: .7 });
10885
+ ctx.addChildBeforeFollowUps(dialog);
10886
+ state.activeInlineApproval = dialog;
10689
10887
  dialog.focused = true;
10690
10888
  flushRender(state);
10691
10889
  }
@@ -11246,9 +11444,12 @@ function removePendingUserMessage(state, messageId) {
11246
11444
  state.pendingSignalMessageComponentsById.delete(messageId);
11247
11445
  state.ui.requestRender();
11248
11446
  }
11249
- function clearPendingUserMessages(state) {
11250
- for (const pending of state.pendingSignalMessageComponentsById.values()) state.chatContainer.removeChild(pending.component);
11251
- state.pendingSignalMessageComponentsById.clear();
11447
+ function clearPendingUserMessages(state, keepIds = []) {
11448
+ for (const [id, pending] of state.pendingSignalMessageComponentsById) {
11449
+ if (keepIds.includes(id)) continue;
11450
+ state.chatContainer.removeChild(pending.component);
11451
+ state.pendingSignalMessageComponentsById.delete(id);
11452
+ }
11252
11453
  state.ui.requestRender();
11253
11454
  }
11254
11455
  function confirmMatchingPendingUserMessage(state, messageId, text, attachments) {
@@ -11554,14 +11755,6 @@ function addUserMessage(state, message, options) {
11554
11755
  return;
11555
11756
  }
11556
11757
  if (confirmMatchingPendingUserMessage(state, message.id, displayText, attachments)) return;
11557
- const dedupKey = displayText.trim();
11558
- const pendingEchoCounts = state.firedQueuedMessageTexts;
11559
- const dedupCount = pendingEchoCounts?.get(dedupKey) ?? 0;
11560
- if (dedupCount > 0) {
11561
- if (dedupCount === 1) pendingEchoCounts.delete(dedupKey);
11562
- else pendingEchoCounts.set(dedupKey, dedupCount - 1);
11563
- return;
11564
- }
11565
11758
  const legacyReminderMatch = exactDisplayText.match(/^<system-reminder(?<attrs>\s+[^>]*)?>(?<body>[\s\S]*?)<\/system-reminder>$/);
11566
11759
  if (legacyReminderMatch?.groups?.body) {
11567
11760
  const attrs = legacyReminderMatch.groups.attrs ?? "";
@@ -13453,56 +13646,90 @@ function formatGoalDuration(goal) {
13453
13646
  if (hours > 0) return minutes > 0 ? `${hours}hr${minutes}m` : `${hours}hr`;
13454
13647
  return `${minutes}m`;
13455
13648
  }
13649
+ /** Shorten a path under the home directory to ~/… */
13650
+ function shortPath(path) {
13651
+ const home = process.env.HOME;
13652
+ return home && (path === home || path.startsWith(`${home}/`)) ? `~${path.slice(home.length)}` : path;
13653
+ }
13654
+ /**
13655
+ * The path, then shorter forms that keep the last directories, like zsh's prompt truncation:
13656
+ * ~/dev/mastra/mastracode/tui → ~/…/mastracode/tui → ~/…/tui
13657
+ */
13658
+ function compactPaths(path) {
13659
+ const isHome = path.startsWith("~/");
13660
+ const isAbsolute = path.startsWith("/");
13661
+ const segments = (isHome ? path.slice(2) : isAbsolute ? path.slice(1) : path).split("/").filter(Boolean);
13662
+ const prefix = isHome ? "~/…/" : isAbsolute ? "/…/" : "…/";
13663
+ const paths = [path];
13664
+ for (const keep of [2, 1]) if (segments.length > keep) paths.push(prefix + segments.slice(-keep).join("/"));
13665
+ return paths;
13666
+ }
13667
+ function truncateEnd(value, maxWidth) {
13668
+ return [...value].slice(0, Math.max(0, maxWidth - 1)).join("") + "…";
13669
+ }
13670
+ /** ~/…/a-very-long-directory-name → ~/…/a-very-l…ry-name */
13671
+ function truncateLastSegment(path, maxWidth) {
13672
+ if (visibleWidth(path) <= maxWidth) return path;
13673
+ const slash = path.lastIndexOf("/");
13674
+ const head = path.slice(0, slash + 1);
13675
+ const name = [...path.slice(slash + 1)];
13676
+ const room = maxWidth - visibleWidth(head) - 1;
13677
+ if (room < 2) return truncateEnd(path, maxWidth);
13678
+ const start = Math.ceil(room * .6);
13679
+ return head + name.slice(0, start).join("") + "…" + name.slice(name.length - (room - start)).join("");
13680
+ }
13681
+ const PULSE_PERIOD_MS = 1600;
13682
+ /** How far the dot fades at its dimmest: 0 = no fade, 1 = disappears into the background. */
13683
+ const PULSE_DEPTH = 1;
13684
+ /** Terminal width at which the footer last needed two rows (see updateStatusLine). */
13685
+ const twoRowFooterWidth = /* @__PURE__ */ new WeakMap();
13686
+ function pulseDot(color, now) {
13687
+ const wave = (Math.cos(now / PULSE_PERIOD_MS * Math.PI * 2) + 1) / 2;
13688
+ const fade = PULSE_DEPTH * (1 - wave);
13689
+ const bg = getContrastBg();
13690
+ const channel = (i) => {
13691
+ const fg = parseInt(color.slice(i, i + 2), 16);
13692
+ return Math.round(fg + (parseInt(bg.slice(i, i + 2), 16) - fg) * fade);
13693
+ };
13694
+ return chalk.rgb(channel(1), channel(3), channel(5))("•");
13695
+ }
13456
13696
  /**
13457
- * Update the status line at the bottom of the TUI.
13458
- * Progressively reduces content to fit the terminal width.
13697
+ * Update the status line under the prompt and the Working row above it.
13698
+ *
13699
+ * Status line: " · "-separated parts, left-aligned with one column of padding:
13700
+ * mode · model · [fallback] · [goal] · [queued] · context · location
13701
+ * A long path shortens like zsh (~/…/parent/dir) to stay on one row; only when even that doesn't fit does
13702
+ * the location move to a second row.
13703
+ * It never changes while the agent runs; run progress (spinner, elapsed time, throughput) lives in the
13704
+ * Working row instead. Parts are dropped or shortened from the right until the first row fits.
13459
13705
  */
13460
13706
  function updateStatusLine(state) {
13461
13707
  if (!state.statusLine) return;
13462
- const termWidth = getTermWidth();
13463
- const SEP = " ";
13708
+ const termWidth = state.ui.terminal?.columns || process.stdout.columns || 80;
13709
+ const PAD = 1;
13710
+ const SEP = theme.fg("dim", " · ");
13711
+ const SEP_WIDTH = 3;
13712
+ const now = Date.now();
13464
13713
  const displayState = state.session.displayState.get();
13465
13714
  const omStatus = displayState.omProgress.status;
13466
13715
  const isJudging = Boolean(state.activeGoalJudge);
13467
13716
  const isObserving = omStatus === "observing";
13468
13717
  const showOMMode = !isJudging && (isObserving || omStatus === "reflecting");
13469
- let modeBadge = "";
13470
- let modeBadgeWidth = 0;
13471
13718
  const modes = state.controller.listModes();
13472
13719
  const configuredMode = state.session.mode.resolve();
13473
13720
  const currentMode = modes.length > 1 ? configuredMode : void 0;
13474
- const judgeModeColor = mastra.blue;
13475
13721
  const currentModeColor = currentMode?.metadata?.color;
13476
- const mainModeColor = typeof currentModeColor === "string" ? currentModeColor : void 0;
13477
- const configuredModeColor = configuredMode?.metadata?.color;
13478
- const contextIndicatorColor = typeof configuredModeColor === "string" ? configuredModeColor : mastra.green;
13479
- const modeColor = isJudging ? judgeModeColor : showOMMode ? isObserving ? getObserverColor() : getReflectorColor() : mainModeColor;
13480
- const tintBg = modeColor ? tintHex(modeColor, .15) : void 0;
13481
- const badgeName = isJudging ? "judge" : showOMMode ? isObserving ? "observe" : "reflect" : currentMode ? currentMode.name || currentMode.id || "unknown" : void 0;
13482
- if (badgeName && modeColor) {
13483
- const [mcr, mcg, mcb] = [
13484
- parseInt(modeColor.slice(1, 3), 16),
13485
- parseInt(modeColor.slice(3, 5), 16),
13486
- parseInt(modeColor.slice(5, 7), 16)
13487
- ];
13488
- let badgeBrightness = .9;
13489
- if (state.gradientAnimator?.isRunning()) {
13490
- const fade = state.gradientAnimator.getFadeProgress();
13491
- const easedFade = fade * fade * (3 - 2 * fade);
13492
- const offset = state.gradientAnimator.getOffset() % 1;
13493
- const animBrightness = .65 + .3 * (.5 + .5 * Math.sin(offset * Math.PI * 2 + Math.PI));
13494
- badgeBrightness = animBrightness + (.9 - animBrightness) * easedFade;
13495
- }
13496
- const mr = Math.floor(mcr * badgeBrightness);
13497
- const mg = Math.floor(mcg * badgeBrightness);
13498
- const mb = Math.floor(mcb * badgeBrightness);
13499
- const rightHalf = tintBg ? chalk.rgb(mr, mg, mb).bgHex(tintBg)("▌") : chalk.rgb(mr, mg, mb)("▌");
13500
- modeBadge = chalk.rgb(mr, mg, mb)("▐") + chalk.bgRgb(mr, mg, mb).hex("#000000").bold(badgeName.toLowerCase()) + rightHalf;
13501
- modeBadgeWidth = badgeName.length + 2;
13502
- } else if (badgeName) {
13503
- modeBadge = " " + theme.fg("dim", badgeName) + " ";
13504
- modeBadgeWidth = badgeName.length + 2;
13505
- }
13722
+ const rawModeColor = isJudging ? mastra.blue : showOMMode ? isObserving ? getObserverColor() : getReflectorColor() : typeof currentModeColor === "string" ? currentModeColor : void 0;
13723
+ const modeColor = rawModeColor ? displayModeColor(rawModeColor) : void 0;
13724
+ const modeName = isJudging ? "judge" : showOMMode ? isObserving ? "observe" : "reflect" : currentMode ? (currentMode.name || currentMode.id || "unknown").toLowerCase() : void 0;
13725
+ const modeInitial = modeName ? {
13726
+ plain: modeName.charAt(0),
13727
+ styled: modeColor ? chalk.bold.hex(modeColor)(modeName.charAt(0)) : theme.fg("dim", modeName.charAt(0))
13728
+ } : null;
13729
+ const modePart = modeName ? {
13730
+ plain: modeName,
13731
+ styled: modeColor ? chalk.bold.hex(modeColor)(modeName) : theme.fg("dim", modeName)
13732
+ } : null;
13506
13733
  const rawModelId = (isJudging ? state.activeGoalJudge?.modelId : showOMMode ? isObserving ? state.session.om.observer.modelId() : state.session.om.reflector.modelId() : state.session.model.get()) ?? "";
13507
13734
  let fullModelId = rawModelId.startsWith("fireworks-ai/accounts/fireworks/models/") ? "fireworks/" + rawModelId.slice(39) : rawModelId;
13508
13735
  fullModelId = fullModelId.replace(/\b([a-z]+-[a-z])(\d+)p(\d+)\b/g, "$1$2.$3");
@@ -13514,246 +13741,191 @@ function updateStatusLine(state) {
13514
13741
  };
13515
13742
  const shortModelId = compactModelId(fullModelId);
13516
13743
  const tinyModelId = shortModelId.includes("/") ? shortModelId : shortModelId.replace(/^claude-/, "").replace(/^(\w+)-(\d+)-(\d{1,2})$/, "$1 $2.$3");
13517
- const branch = state.projectInfo.gitBranch;
13518
- const threadTitle = state.currentThreadTitle && !isGenericTitle(state.currentThreadTitle) ? state.currentThreadTitle : null;
13519
- const activeGithubPrSubscriptions = state.activeGithubPrSubscriptions ?? [];
13520
- const githubPrLabel = activeGithubPrSubscriptions.length > 0 ? formatGithubPrLabel(state, activeGithubPrSubscriptions) : null;
13521
- const centerText = threadTitle || branch || (githubPrLabel ? "" : null);
13522
- const centerTextShort = centerText && centerText.length > 24 ? centerText.slice(0, 12) + ".." + centerText.slice(-8) : centerText;
13523
- const now = Date.now();
13524
- const queuedCount = state.pendingQueuedActions.length + displayState.queuedFollowUps;
13525
- const queuedLabel = queuedCount > 0 ? `${queuedCount} queued` : null;
13526
- const goalState = state.goalManager?.getGoal();
13527
- const goalDuration = !isJudging && goalState?.status === "active" ? formatGoalDuration(goalState) : null;
13528
- const goalMatchesActiveRun = goalState?.status === "active" && goalDuration !== null && state.agentRunStartedAt !== void 0 && Math.floor(getGoalDurationMs(goalState, now) / 6e4) === Math.floor((now - state.agentRunStartedAt) / 6e4);
13529
- const goalLabel = goalDuration ? goalMatchesActiveRun ? "goal" : `goal ${goalDuration}` : null;
13530
- const fallbackLabel = !isJudging && !showOMMode && state.fallbackStatus ? `Using fallback ${state.fallbackStatus.usingPack} (${state.fallbackStatus.failedPack} failed)` : null;
13531
- const formatDirPart = (value) => {
13532
- const separator = githubPrLabel && value ? " " : "";
13533
- return {
13534
- plain: githubPrLabel ? `${githubPrLabel.plain}${separator}${value}` : value,
13535
- styled: githubPrLabel ? `${githubPrLabel.styled}${separator}${theme.fg("thinkingText", value)}` : theme.fg("thinkingText", value)
13536
- };
13537
- };
13538
- const modelTrail = tintBg ? chalk.hex(tintBg)("▌") : "";
13539
- const styleModelId = (id) => {
13744
+ const modelPart = (id) => {
13540
13745
  if (!state.modelAuthStatus.hasAuth) {
13541
13746
  const envVar = state.modelAuthStatus.apiKeyEnvVar;
13542
- return theme.fg("dim", id) + theme.fg("error", " ✗") + theme.fg("muted", envVar ? ` (${envVar})` : " (no key)");
13543
- }
13544
- if (state.gradientAnimator?.isRunning() && modeColor) {
13545
- const fade = state.gradientAnimator.getFadeProgress();
13546
- const easedFade = fade * fade * (3 - 2 * fade);
13547
- const text = applyGradientSweep(id, state.gradientAnimator.getOffset(), modeColor, easedFade);
13548
- const styled = chalk.italic(text);
13549
- return (tintBg ? chalk.bgHex(tintBg)(styled) : styled) + modelTrail;
13550
- }
13551
- if (modeColor) {
13552
- const [cr, cg, cb] = [
13553
- parseInt(modeColor.slice(1, 3), 16),
13554
- parseInt(modeColor.slice(3, 5), 16),
13555
- parseInt(modeColor.slice(5, 7), 16)
13556
- ];
13557
- const idleBright = .8;
13558
- const fgStyled = chalk.rgb(Math.floor(cr * idleBright), Math.floor(cg * idleBright), Math.floor(cb * idleBright)).bold.italic(id);
13559
- return (tintBg ? chalk.bgHex(tintBg)(fgStyled) : fgStyled) + modelTrail;
13747
+ const hint = envVar ? ` (${envVar})` : " (no key)";
13748
+ return {
13749
+ plain: `${id} ✗${hint}`,
13750
+ styled: theme.fg("dim", id) + theme.fg("error", " ✗") + theme.fg("muted", hint)
13751
+ };
13560
13752
  }
13561
- return chalk.hex(mastra.specialGray).bold.italic(id);
13753
+ const lastRun = state.agentRunStartedAt === void 0 && state.lastAgentRunDurationMs !== void 0 ? formatStatusDuration(state.lastAgentRunDurationMs, { includeSeconds: true }) : "";
13754
+ if (!lastRun) return {
13755
+ plain: id,
13756
+ styled: theme.fg("secondary", id)
13757
+ };
13758
+ const reason = state.lastAgentRunEndReason;
13759
+ const icon = reason === "error" ? " ×" : reason === "aborted" ? "" : " ✓";
13760
+ const tone = reason === "error" ? "error" : reason === "aborted" ? "warning" : "success";
13761
+ return {
13762
+ plain: `${id} ${lastRun}${icon}`,
13763
+ styled: theme.fg("secondary", id) + " " + theme.fg(tone, lastRun + icon)
13764
+ };
13562
13765
  };
13563
- let shortModeBadge = "";
13564
- let shortModeBadgeWidth = 0;
13565
- if (badgeName && modeColor) {
13566
- const shortName = badgeName.toLowerCase().charAt(0);
13567
- const [mcr, mcg, mcb] = [
13568
- parseInt(modeColor.slice(1, 3), 16),
13569
- parseInt(modeColor.slice(3, 5), 16),
13570
- parseInt(modeColor.slice(5, 7), 16)
13571
- ];
13572
- let sBadgeBrightness = .9;
13573
- if (state.gradientAnimator?.isRunning()) {
13574
- const fade = state.gradientAnimator.getFadeProgress();
13575
- if (fade < 1) {
13576
- const offset = state.gradientAnimator.getOffset() % 1;
13577
- const animBrightness = .65 + .3 * (.5 + .5 * Math.sin(offset * Math.PI * 2 + Math.PI));
13578
- sBadgeBrightness = animBrightness + (.9 - animBrightness) * fade;
13579
- }
13580
- }
13581
- const sr = Math.floor(mcr * sBadgeBrightness);
13582
- const sg = Math.floor(mcg * sBadgeBrightness);
13583
- const sb = Math.floor(mcb * sBadgeBrightness);
13584
- const shortRightHalf = tintBg ? chalk.rgb(sr, sg, sb).bgHex(tintBg)("▌") : chalk.rgb(sr, sg, sb)("▌");
13585
- shortModeBadge = chalk.rgb(sr, sg, sb)("▐") + chalk.bgRgb(sr, sg, sb).hex("#000000").bold(shortName) + shortRightHalf;
13586
- shortModeBadgeWidth = shortName.length + 2;
13587
- } else if (badgeName) {
13588
- const shortName = badgeName.toLowerCase().charAt(0);
13589
- shortModeBadge = " " + theme.fg("dim", shortName) + " ";
13590
- shortModeBadgeWidth = shortName.length + 2;
13591
- }
13592
- const activeTimingLabel = state.agentRunStartedAt !== void 0 ? formatStatusDuration(now - state.agentRunStartedAt, { includeSeconds: true }) : "";
13593
- const activeTimingIsStale = state.agentRunStartedAt !== void 0 && state.agentRunLastStreamPartAt !== void 0 && now - state.agentRunLastStreamPartAt > 3 * 6e4;
13594
- const completedTimingLabel = !activeTimingLabel && state.lastAgentRunDurationMs !== void 0 ? formatStatusDuration(state.lastAgentRunDurationMs, { includeSeconds: true }) : "";
13595
- const completedTimingIcon = state.lastAgentRunEndReason === "error" ? "×" : completedTimingLabel && state.lastAgentRunEndReason !== "aborted" ? "✓" : "";
13596
- const timingLabel = activeTimingLabel || completedTimingLabel;
13597
- const buildLine = (opts) => {
13598
- const parts = [];
13599
- const timingPlain = timingLabel ? ` ${timingLabel}${completedTimingIcon ? ` ${completedTimingIcon}` : ""}` : "";
13600
- const timingColor = activeTimingIsStale ? theme.fg("error", timingLabel) : activeTimingLabel ? modeColor ? chalk.hex(modeColor)(timingLabel) : theme.fg("dim", timingLabel) : state.lastAgentRunEndReason === "aborted" ? theme.fg("warning", timingLabel) : state.lastAgentRunEndReason === "error" ? theme.fg("error", timingLabel) : theme.fg("success", timingLabel);
13601
- const timingIconColor = state.lastAgentRunEndReason === "aborted" ? "warning" : state.lastAgentRunEndReason === "error" ? "error" : "success";
13602
- const timingStyled = timingLabel ? ` ${timingColor}${completedTimingIcon ? ` ${theme.fg(timingIconColor, completedTimingIcon)}` : ""}` : "";
13603
- parts.push({
13604
- plain: `${opts.modelId}${tintBg ? " " : ""}${timingPlain}`,
13605
- styled: styleModelId(opts.modelId) + timingStyled
13606
- });
13607
- const visibleFallbackLabel = opts.showFallback === false ? null : fallbackLabel;
13608
- if (visibleFallbackLabel) parts.push({
13609
- plain: visibleFallbackLabel,
13610
- styled: theme.fg("warning", visibleFallbackLabel)
13611
- });
13612
- const useBadge = opts.badge === "short" ? shortModeBadge : modeBadge;
13613
- const useBadgeWidth = opts.badge === "short" ? shortModeBadgeWidth : modeBadgeWidth;
13614
- const ds = displayState;
13615
- const messageColor = contextIndicatorColor;
13616
- const memoryColor = mastraBrand.blue;
13617
- const unusedColor = "#3f3f46";
13618
- const messageSegmentStyler = ds.bufferingMessages && state.gradientAnimator?.isRunning() ? (segment) => applyGradientSweep(segment, state.gradientAnimator.getOffset(), messageColor, state.gradientAnimator.getFadeProgress()) : (segment) => chalk.hex(messageColor)(segment);
13619
- const memorySegmentStyler = ds.bufferingObservations && state.gradientAnimator?.isRunning() ? (segment) => applyGradientSweep(segment, state.gradientAnimator.getOffset(), memoryColor, state.gradientAnimator.getFadeProgress()) : (segment) => chalk.hex(memoryColor)(segment);
13620
- const unusedSegmentStyler = (segment) => chalk.hex(unusedColor)(segment);
13621
- const indicatorPart = !isJudging && opts.showOM !== false ? formatOMContextIndicator(ds.omProgress, {
13622
- messages: messageSegmentStyler,
13623
- memory: memorySegmentStyler,
13624
- unused: unusedSegmentStyler,
13625
- showBar: opts.showOMBar
13626
- }) : null;
13627
- if (opts.showQueue && goalLabel) parts.push({
13628
- plain: goalLabel,
13629
- styled: theme.fg("success", goalLabel)
13630
- });
13631
- if (opts.showQueue && queuedLabel) parts.push({
13632
- plain: queuedLabel,
13633
- styled: theme.fg("warning", queuedLabel)
13634
- });
13635
- const tpsLabel = state.tokensPerSec > 0 ? `${String(state.tokensPerSec).padStart(3)} t/s` : null;
13636
- const tpsPart = tpsLabel ? {
13637
- plain: tpsLabel,
13638
- styled: theme.fg("text", tpsLabel)
13639
- } : null;
13640
- let dirText = opts.dir !== void 0 ? opts.dir : opts.showDir ? centerText : null;
13641
- const rightParts = indicatorPart ? [tpsPart, indicatorPart].filter((part) => part !== null) : [];
13642
- const nonDirWidth = useBadgeWidth + [...parts, ...rightParts].reduce((sum, p, i) => sum + visibleWidth(p.plain) + (i > 0 ? 2 : 0), 0);
13643
- if (dirText !== null) {
13644
- const dirPart = formatDirPart(dirText);
13645
- const availableForDir = termWidth - nonDirWidth - 2 - 1;
13646
- const dirWidth = visibleWidth(dirPart.plain);
13647
- const availableForText = availableForDir - (githubPrLabel ? visibleWidth(githubPrLabel.plain) + (dirText ? 1 : 0) : 0);
13648
- const MIN_TRUNCATED_DIR = 10;
13649
- if (dirWidth > availableForDir && opts.allowDirTruncation === false) return null;
13650
- if (dirWidth > availableForDir && availableForText >= MIN_TRUNCATED_DIR) dirText = availableForText > 1 ? dirText.slice(0, availableForText - 1) + "…" : null;
13651
- else if (dirWidth > availableForDir) dirText = githubPrLabel && visibleWidth(githubPrLabel.plain) <= availableForDir ? "" : null;
13652
- }
13653
- if (dirText !== null) parts.push(formatDirPart(dirText));
13654
- if (indicatorPart) parts.push(...rightParts);
13655
- else if (tpsPart) parts.push(tpsPart);
13656
- const totalPlain = useBadgeWidth + parts.reduce((sum, p, i) => sum + visibleWidth(p.plain) + (i > 0 ? 2 : 0), 0);
13657
- if (totalPlain + 1 > termWidth) return null;
13658
- let styledLine;
13659
- const hasDir = dirText !== null;
13660
- if (indicatorPart && parts.length >= 2) {
13661
- const leftPartCount = 1 + (visibleFallbackLabel ? 1 : 0) + (opts.showQueue && goalLabel ? 1 : 0);
13662
- const leftParts = parts.slice(0, leftPartCount);
13663
- const centerParts = parts.slice(leftPartCount, -rightParts.length);
13664
- const leftSeparatorPlain = timingLabel ? " · " : " ";
13665
- const leftSeparatorStyled = timingLabel ? ` ${theme.fg("success", "·")} ` : " ";
13666
- const leftWidth = useBadgeWidth + leftParts.reduce((sum, p, i) => sum + visibleWidth(p.plain) + (i > 0 ? leftSeparatorPlain.length : 0), 0);
13667
- const centerWidth = centerParts.reduce((sum, p, i) => sum + visibleWidth(p.plain) + (i > 0 ? 2 : 0), 0);
13668
- const rightWidth = rightParts.reduce((sum, p, i) => sum + visibleWidth(p.plain) + (i > 0 ? 2 : 0), 0);
13669
- const totalContent = leftWidth + centerWidth + rightWidth;
13670
- const freeSpace = termWidth - totalContent;
13671
- const gapLeft = Math.floor(freeSpace / 2);
13672
- const gapRight = freeSpace - gapLeft;
13673
- styledLine = useBadge + leftParts.map((p) => p.styled).join(leftSeparatorStyled) + " ".repeat(Math.max(gapLeft, 1)) + centerParts.map((p) => p.styled).join(SEP) + " ".repeat(Math.max(gapRight, 1)) + rightParts.map((p) => p.styled).join(SEP);
13674
- } else if (hasDir && parts.length === 2) {
13675
- const mainStr = useBadge + parts[0].styled;
13676
- const dirPart = parts[parts.length - 1];
13677
- const gap = termWidth - totalPlain;
13678
- styledLine = mainStr + " ".repeat(gap + 2) + dirPart.styled;
13679
- } else styledLine = useBadge + parts.map((p) => p.styled).join(SEP);
13766
+ const fallbackPart = !isJudging && !showOMMode && state.fallbackStatus ? (() => {
13767
+ const label = `Using fallback ${state.fallbackStatus.usingPack} (${state.fallbackStatus.failedPack} failed)`;
13680
13768
  return {
13681
- plain: "",
13682
- styled: styledLine
13769
+ plain: label,
13770
+ styled: theme.fg("warning", label)
13683
13771
  };
13772
+ })() : null;
13773
+ const goalState = state.goalManager?.getGoal();
13774
+ const goalDuration = !isJudging && goalState?.status === "active" ? formatGoalDuration(goalState) : null;
13775
+ const goalMatchesActiveRun = goalState?.status === "active" && goalDuration !== null && state.agentRunStartedAt !== void 0 && Math.floor(getGoalDurationMs(goalState, now) / 6e4) === Math.floor((now - state.agentRunStartedAt) / 6e4);
13776
+ const goalLabel = goalDuration ? goalMatchesActiveRun ? "goal" : `goal ${goalDuration}` : null;
13777
+ const goalPart = goalLabel ? {
13778
+ plain: goalLabel,
13779
+ styled: theme.fg("success", goalLabel)
13780
+ } : null;
13781
+ const queuedCount = state.pendingSlashCommands.length + displayState.queuedFollowUps;
13782
+ const queuedPart = queuedCount > 0 ? {
13783
+ plain: `${queuedCount} queued`,
13784
+ styled: theme.fg("warning", `${queuedCount} queued`)
13785
+ } : null;
13786
+ const om = displayState.omProgress;
13787
+ const used = Math.max(0, om.pendingTokens) + Math.max(0, om.observationTokens);
13788
+ const capacity = Math.max(0, om.threshold) + Math.max(0, om.reflectionThreshold);
13789
+ const indicator = !isJudging ? formatOMContextIndicator(om, { showBar: false }) : null;
13790
+ const pct = capacity > 0 ? ` ${Math.round(used / capacity * 100)}%` : "";
13791
+ const sweep = (displayState.bufferingMessages || displayState.bufferingObservations) && state.gradientAnimator?.isRunning() ? state.gradientAnimator : void 0;
13792
+ const contextPart = indicator ? {
13793
+ plain: indicator.plain.trimEnd() + pct,
13794
+ styled: (sweep ? applyGradientSweep(indicator.plain.trimEnd(), sweep.getOffset(), displayState.bufferingMessages ? modeColor ?? theme.getTheme().accent : mastra.blue, sweep.getFadeProgress()) : indicator.styled.trimEnd()) + theme.fg("dim", pct)
13795
+ } : null;
13796
+ const branch = state.projectInfo.gitBranch;
13797
+ const threadTitle = state.currentThreadTitle && !isGenericTitle(state.currentThreadTitle) ? state.currentThreadTitle : null;
13798
+ const activeGithubPrSubscriptions = state.activeGithubPrSubscriptions ?? [];
13799
+ const githubPrLabel = activeGithubPrSubscriptions.length > 0 ? formatGithubPrLabel(state, activeGithubPrSubscriptions) : null;
13800
+ const locationPart = (maxWidth, fit) => {
13801
+ const prefix = githubPrLabel ? {
13802
+ plain: `${githubPrLabel.plain} `,
13803
+ styled: `${githubPrLabel.styled} `
13804
+ } : null;
13805
+ const room = maxWidth - (prefix ? visibleWidth(prefix.plain) : 0);
13806
+ const fits = (value) => visibleWidth(value) <= room;
13807
+ let text = null;
13808
+ if (threadTitle) {
13809
+ const title = fits(threadTitle) ? threadTitle : fit === "any" && room >= 10 ? truncateEnd(threadTitle, room) : null;
13810
+ if (title) text = {
13811
+ plain: title,
13812
+ styled: theme.fg("muted", title)
13813
+ };
13814
+ } else {
13815
+ const withBranch = (path) => ({
13816
+ plain: branch ? `${path} (${branch})` : path,
13817
+ styled: theme.fg("muted", path) + (branch ? theme.fg("dim", ` (${branch})`) : "")
13818
+ });
13819
+ const path = shortPath(state.projectInfo.rootPath ?? process.cwd());
13820
+ text = (fit === "full" ? [withBranch(path)] : compactPaths(path).map(withBranch)).find((candidate) => fits(candidate.plain)) ?? null;
13821
+ if (!text && fit === "any" && room >= 10) {
13822
+ const value = branch ? fits(branch) ? branch : truncateEnd(branch, room) : truncateLastSegment(compactPaths(path).at(-1), room);
13823
+ text = {
13824
+ plain: value,
13825
+ styled: theme.fg(branch ? "dim" : "muted", value)
13826
+ };
13827
+ }
13828
+ }
13829
+ if (!text) return prefix && visibleWidth(prefix.plain) - 1 <= maxWidth ? {
13830
+ plain: githubPrLabel.plain,
13831
+ styled: githubPrLabel.styled
13832
+ } : null;
13833
+ return prefix ? {
13834
+ plain: prefix.plain + text.plain,
13835
+ styled: prefix.styled + text.styled
13836
+ } : text;
13684
13837
  };
13685
- const result = buildLine({
13686
- modelId: fullModelId,
13687
- showDir: false,
13688
- dir: centerText,
13689
- allowDirTruncation: false,
13690
- showQueue: true
13691
- }) ?? buildLine({
13692
- modelId: fullModelId,
13693
- showOMBar: false,
13694
- showDir: false,
13695
- dir: centerText,
13696
- allowDirTruncation: false,
13697
- showQueue: true
13698
- }) ?? buildLine({
13699
- modelId: fullModelId,
13700
- showOMBar: false,
13701
- showDir: false,
13702
- dir: centerTextShort,
13703
- showQueue: true
13704
- }) ?? buildLine({
13705
- modelId: fullModelId,
13706
- showOMBar: false,
13707
- showDir: false,
13708
- showQueue: true
13709
- }) ?? buildLine({
13710
- modelId: fullModelId,
13711
- showOM: false,
13712
- showDir: false,
13713
- showQueue: true
13714
- }) ?? buildLine({
13715
- modelId: tinyModelId,
13716
- showOMBar: false,
13717
- showDir: false,
13718
- showQueue: true,
13719
- showFallback: true
13720
- }) ?? buildLine({
13721
- modelId: tinyModelId,
13722
- showOMBar: false,
13723
- showDir: false,
13724
- badge: "short",
13725
- showQueue: true,
13726
- showFallback: true
13727
- }) ?? buildLine({
13728
- modelId: tinyModelId,
13729
- showOM: false,
13730
- showDir: false,
13731
- badge: "short",
13732
- showQueue: true,
13733
- showFallback: true
13734
- }) ?? buildLine({
13735
- modelId: tinyModelId,
13736
- showOM: false,
13737
- showDir: false,
13738
- showFallback: true
13739
- }) ?? buildLine({
13740
- modelId: "",
13741
- showOMBar: false,
13742
- showDir: false,
13743
- badge: "short",
13744
- showQueue: true,
13745
- showFallback: false
13746
- }) ?? buildLine({
13747
- modelId: "",
13748
- showOM: false,
13749
- showDir: false,
13750
- badge: "short",
13751
- showFallback: false
13752
- });
13753
- state.statusLine.setText(result?.styled ?? shortModeBadge + styleModelId(tinyModelId));
13754
- if (state.memoryStatusLine) state.memoryStatusLine.setText("");
13838
+ const width = (parts) => PAD + parts.reduce((sum, p, i) => sum + visibleWidth(p.plain) + (i > 0 ? SEP_WIDTH : 0), 0) + 1;
13839
+ const fitsRow = (parts) => {
13840
+ const present = parts.filter((p) => p !== null);
13841
+ return width(present) <= termWidth ? present : null;
13842
+ };
13843
+ const withLocation = (parts, fit) => {
13844
+ const present = parts.filter((p) => p !== null);
13845
+ const location = locationPart(termWidth - width(present) - SEP_WIDTH, fit);
13846
+ return location ? fitsRow([...present, location]) : null;
13847
+ };
13848
+ const transient = [
13849
+ fallbackPart,
13850
+ goalPart,
13851
+ queuedPart
13852
+ ];
13853
+ const oneRow = twoRowFooterWidth.get(state) === termWidth ? null : withLocation([
13854
+ modePart,
13855
+ modelPart(fullModelId),
13856
+ ...transient,
13857
+ contextPart
13858
+ ], "full") ?? withLocation([
13859
+ modePart,
13860
+ modelPart(shortModelId),
13861
+ ...transient,
13862
+ contextPart
13863
+ ], "full") ?? withLocation([
13864
+ modePart,
13865
+ modelPart(fullModelId),
13866
+ ...transient,
13867
+ contextPart
13868
+ ], "compact") ?? withLocation([
13869
+ modePart,
13870
+ modelPart(shortModelId),
13871
+ ...transient,
13872
+ contextPart
13873
+ ], "compact");
13874
+ const row = oneRow ?? fitsRow([
13875
+ modePart,
13876
+ modelPart(fullModelId),
13877
+ ...transient,
13878
+ contextPart
13879
+ ]) ?? fitsRow([
13880
+ modePart,
13881
+ modelPart(shortModelId),
13882
+ ...transient,
13883
+ contextPart
13884
+ ]) ?? fitsRow([
13885
+ modePart,
13886
+ modelPart(tinyModelId),
13887
+ ...transient,
13888
+ contextPart
13889
+ ]) ?? fitsRow([
13890
+ modePart,
13891
+ modelPart(tinyModelId),
13892
+ goalPart,
13893
+ queuedPart,
13894
+ contextPart
13895
+ ]) ?? fitsRow([
13896
+ modePart,
13897
+ modelPart(tinyModelId),
13898
+ goalPart,
13899
+ queuedPart
13900
+ ]) ?? fitsRow([modePart, modelPart(tinyModelId)]) ?? fitsRow([modeInitial, modelPart(tinyModelId)]) ?? fitsRow([modePart]) ?? [];
13901
+ const secondRow = oneRow ? null : locationPart(termWidth - PAD - 1, "any");
13902
+ if (secondRow) twoRowFooterWidth.set(state, termWidth);
13903
+ else twoRowFooterWidth.delete(state);
13904
+ state.statusLine.setText(" ".repeat(PAD) + row.map((p) => p.styled).join(SEP));
13905
+ if (state.memoryStatusLine) state.memoryStatusLine.setText(secondRow ? " ".repeat(PAD) + secondRow.styled : "");
13906
+ updateActivityLine(state, modeColor, now);
13755
13907
  state.ui.requestRender();
13756
13908
  }
13909
+ /**
13910
+ * Working row above the prompt while the agent runs: spinner, elapsed time, throughput, interrupt hint.
13911
+ * Empty (renders nothing) when idle.
13912
+ */
13913
+ function updateActivityLine(state, modeColor, now) {
13914
+ if (!state.idleCounter) return;
13915
+ if (state.agentRunStartedAt === void 0) {
13916
+ state.idleCounter.setActivity("");
13917
+ return;
13918
+ }
13919
+ const spinner = pulseDot(modeColor ?? theme.getTheme().accent, now);
13920
+ const elapsed = formatStatusDuration(now - state.agentRunStartedAt, { includeSeconds: true });
13921
+ const stale = state.agentRunLastStreamPartAt !== void 0 && now - state.agentRunLastStreamPartAt > 3 * 6e4 ? formatStatusDuration(now - state.agentRunLastStreamPartAt, { includeSeconds: false }) : null;
13922
+ const parts = [theme.fg(stale ? "error" : "dim", elapsed)];
13923
+ if (stale) parts.push(theme.fg("warning", `no output for ${stale}`));
13924
+ if (state.tokensPerSec > 0) parts.push(theme.fg("dim", `${state.tokensPerSec} tok/s`));
13925
+ parts.push(`${theme.fg("muted", "esc")}${theme.fg("dim", " to interrupt")}`);
13926
+ const label = state.idleCounter.isThinking() ? "thinking" : "working ";
13927
+ state.idleCounter.setActivity(` ${spinner} ${theme.bold(theme.fg("secondary", label))} ${parts.join(theme.fg("dim", " · "))}`);
13928
+ }
13757
13929
  //#endregion
13758
13930
  //#region src/tui/commands/models-pack.ts
13759
13931
  const SHARE_PREFIX = "mastra-pack:";
@@ -21558,47 +21730,22 @@ function handleAgentEnd(ctx) {
21558
21730
  }
21559
21731
  function drainQueuedAction(ctx) {
21560
21732
  const { state } = ctx;
21561
- if (state.session.displayState.get().queuedFollowUps > 0) return true;
21562
- const nextAction = state.pendingQueuedActions.shift();
21563
- ctx.updateStatusLine();
21564
- if (!nextAction) return false;
21565
- if (nextAction === "message") {
21566
- const nextMessage = state.pendingFollowUpMessages.shift();
21567
- if (!nextMessage) return true;
21568
- ctx.addUserMessage({
21569
- id: `user-${Date.now()}`,
21570
- role: "user",
21571
- content: {
21572
- format: 2,
21573
- parts: [{
21574
- type: "text",
21575
- text: nextMessage.content
21576
- }, ...nextMessage.images?.map((img) => ({
21577
- type: "file",
21578
- data: img.data,
21579
- mimeType: img.mimeType
21580
- })) ?? []]
21581
- },
21582
- createdAt: /* @__PURE__ */ new Date()
21583
- });
21584
- const key = nextMessage.content.trim();
21585
- const counts = state.firedQueuedMessageTexts ??= /* @__PURE__ */ new Map();
21586
- counts.set(key, (counts.get(key) ?? 0) + 1);
21587
- flushRender(state);
21588
- ctx.fireMessage(nextMessage.content, nextMessage.images);
21589
- return true;
21590
- }
21733
+ if (state.session.displayState.get().queuedFollowUps > 0 || state.pendingQueueSubmissions > 0) return;
21591
21734
  const nextCommand = state.pendingSlashCommands.shift();
21592
21735
  const pendingMessageId = state.pendingSlashCommandMessageIds.shift();
21736
+ ctx.updateStatusLine();
21593
21737
  if (!nextCommand) {
21594
21738
  if (pendingMessageId) removePendingUserMessage(state, pendingMessageId);
21595
- return true;
21739
+ return;
21596
21740
  }
21597
21741
  if (pendingMessageId) removePendingUserMessage(state, pendingMessageId);
21598
21742
  ctx.handleSlashCommand(nextCommand).catch((error) => {
21599
21743
  ctx.showError(error instanceof Error ? error.message : "Queued slash command failed");
21600
21744
  });
21601
- return true;
21745
+ }
21746
+ function drainQueuedActionIfIdle(ctx) {
21747
+ if (ctx.state.session.stream.isActive()) return;
21748
+ drainQueuedAction(ctx);
21602
21749
  }
21603
21750
  function handleAgentAborted(ctx) {
21604
21751
  const { state } = ctx;
@@ -21624,11 +21771,7 @@ function handleAgentAborted(ctx) {
21624
21771
  state.activeGoalJudge = void 0;
21625
21772
  }
21626
21773
  state.followUpComponents = [];
21627
- state.pendingFollowUpMessages = [];
21628
- state.pendingQueuedActions = [];
21629
- state.pendingSlashCommands = [];
21630
- state.pendingSlashCommandMessageIds = [];
21631
- clearPendingUserMessages(state);
21774
+ clearPendingUserMessages(state, state.pendingSlashCommandMessageIds);
21632
21775
  for (const tool of state.pendingTools.values()) tool.stopLiveUpdates?.();
21633
21776
  state.idleCounter?.setThinking(false);
21634
21777
  state.pendingTools.clear();
@@ -21636,6 +21779,7 @@ function handleAgentAborted(ctx) {
21636
21779
  pruneChatContainer(state);
21637
21780
  ctx.updateStatusLine();
21638
21781
  flushRender(state);
21782
+ drainQueuedActionIfIdle(ctx);
21639
21783
  }
21640
21784
  function handleAgentError(ctx) {
21641
21785
  const { state } = ctx;
@@ -21646,11 +21790,7 @@ function handleAgentError(ctx) {
21646
21790
  state.activeGoalJudge = void 0;
21647
21791
  }
21648
21792
  state.followUpComponents = [];
21649
- state.pendingFollowUpMessages = [];
21650
- state.pendingQueuedActions = [];
21651
- state.pendingSlashCommands = [];
21652
- state.pendingSlashCommandMessageIds = [];
21653
- clearPendingUserMessages(state);
21793
+ clearPendingUserMessages(state, state.pendingSlashCommandMessageIds);
21654
21794
  for (const tool of state.pendingTools.values()) tool.stopLiveUpdates?.();
21655
21795
  state.idleCounter?.setThinking(false);
21656
21796
  state.pendingTools.clear();
@@ -21658,6 +21798,7 @@ function handleAgentError(ctx) {
21658
21798
  pruneChatContainer(state);
21659
21799
  ctx.updateStatusLine();
21660
21800
  flushRender(state);
21801
+ drainQueuedActionIfIdle(ctx);
21661
21802
  }
21662
21803
  /** Remove the judge display component from the chat container if present. */
21663
21804
  function removeJudgeComponent(state, component) {
@@ -21735,7 +21876,9 @@ function syncQuietThinkingStatus(ctx, message) {
21735
21876
  const { state } = ctx;
21736
21877
  const latest = message ? getRawParts(message).findLast((part) => part.type === "text" || part.type === "reasoning" || part.type === "tool-invocation") : void 0;
21737
21878
  const thinking = state.quietMode && state.hideThinkingBlock && latest?.type === "reasoning";
21738
- state.idleCounter?.setThinking(thinking);
21879
+ if (!state.idleCounter || state.idleCounter.isThinking() === thinking) return;
21880
+ state.idleCounter.setThinking(thinking);
21881
+ ctx.updateStatusLine();
21739
21882
  }
21740
21883
  function isToolPart(part) {
21741
21884
  return part.type === "tool-invocation";
@@ -22855,44 +22998,12 @@ const VOICE_WAVE_BARS = [
22855
22998
  "▇",
22856
22999
  "█"
22857
23000
  ];
22858
- const DEFAULT_PROMPT_ICON = "•";
22859
- const PROMPT_ICON_CHOICES = [
22860
- "☯",
22861
- "✺",
22862
- "☻",
22863
- "✿",
22864
- "◒",
22865
- "◓",
22866
- "♞",
22867
- "☘",
22868
- "☸",
22869
- "❂",
22870
- "❁",
22871
- "✽",
22872
- "❉",
22873
- "✹",
22874
- "❨",
22875
- "❩",
22876
- "✚",
22877
- "⚉",
22878
- "❣",
22879
- "❥",
22880
- "♫",
22881
- "❤"
22882
- ];
22883
- function getRandomPromptIcon(currentIcon) {
22884
- if (Math.random() < .99) return DEFAULT_PROMPT_ICON;
22885
- const nextChoices = PROMPT_ICON_CHOICES.filter((icon) => icon !== currentIcon);
22886
- const choices = nextChoices.length > 0 ? nextChoices : PROMPT_ICON_CHOICES;
22887
- return choices[Math.floor(Math.random() * choices.length)];
22888
- }
22889
23001
  var CustomEditor = class extends Editor {
22890
23002
  actionHandlers = /* @__PURE__ */ new Map();
22891
23003
  onCtrlD;
22892
23004
  escapeEnabled = true;
22893
23005
  onImagePaste;
22894
23006
  getModeColor;
22895
- getPromptAnimator;
22896
23007
  requestRender;
22897
23008
  pendingBracketedPaste = null;
22898
23009
  /**
@@ -22914,8 +23025,6 @@ var CustomEditor = class extends Editor {
22914
23025
  voiceDictationActive = false;
22915
23026
  _cachedModeColorHex;
22916
23027
  _cachedColorFn;
22917
- promptIcon = DEFAULT_PROMPT_ICON;
22918
- lastPromptWasInvisible = false;
22919
23028
  requestEditorRender() {
22920
23029
  if (this.requestRender) {
22921
23030
  this.requestRender();
@@ -22953,27 +23062,8 @@ var CustomEditor = class extends Editor {
22953
23062
  const isSlash = text.startsWith("/");
22954
23063
  const isAt = text.startsWith("@");
22955
23064
  const isBang = text.startsWith("!");
22956
- const color = this.getModeColor?.() || mastra.green;
22957
- const promptAnimator = this.getPromptAnimator?.();
22958
- const isPromptAnimated = !isSlash && !isAt && !isBang && Boolean(promptAnimator?.isRunning());
22959
- const fadeProgress = isPromptAnimated ? promptAnimator.getFadeProgress() : 1;
22960
- const isTransitioningIn = isPromptAnimated && promptAnimator.isFadingIn();
22961
- const isTransitioningOut = isPromptAnimated && promptAnimator.isFadingOut();
22962
- const promptOffset = isPromptAnimated ? promptAnimator.getOffset() : 0;
22963
- const pulseWave = isPromptAnimated ? (Math.sin(promptOffset * Math.PI * 2) + 1) / 2 : 0;
22964
- const transitionPhase = isTransitioningIn || isTransitioningOut ? 1 - fadeProgress : 1;
22965
- const chevronBrightness = isPromptAnimated ? isTransitioningIn ? transitionPhase < .5 ? Math.max(0, 1 - transitionPhase * 2) : 0 : isTransitioningOut ? transitionPhase <= .5 ? Math.max(0, 1 - transitionPhase * 2) : 0 : 0 : 1;
22966
- const dotBrightness = isPromptAnimated ? isTransitioningIn ? transitionPhase <= .5 ? 0 : Math.max(0, (transitionPhase - .5) * 2) : isTransitioningOut ? transitionPhase < .5 ? 0 : Math.max(0, (transitionPhase - .5) * 2) : pulseWave : 0;
22967
- const isSteadyPulse = isPromptAnimated && !isTransitioningIn && !isTransitioningOut;
22968
- if (!isPromptAnimated) {
22969
- this.promptIcon = DEFAULT_PROMPT_ICON;
22970
- this.lastPromptWasInvisible = false;
22971
- } else if (!isSteadyPulse) this.lastPromptWasInvisible = false;
22972
- const promptIsInvisible = isSteadyPulse && dotBrightness <= .05;
22973
- if (promptIsInvisible && !this.lastPromptWasInvisible) this.promptIcon = getRandomPromptIcon(this.promptIcon);
22974
- this.lastPromptWasInvisible = promptIsInvisible;
22975
- const promptChar = isSlash ? "/" : isAt ? "@" : isBang ? "!" : chevronBrightness > .05 ? "›" : dotBrightness > .05 ? this.promptIcon : " ";
22976
- const promptBrightness = isPromptAnimated ? Math.max(chevronBrightness, dotBrightness) : 1;
23065
+ const color = displayModeColor(this.getModeColor?.() || mastra.green);
23066
+ const promptChar = isSlash ? "/" : isAt ? "@" : isBang ? "!" : "→";
22977
23067
  if (this._cachedModeColorHex !== color) {
22978
23068
  this._cachedModeColorHex = color;
22979
23069
  this._cachedColorFn = chalk.hex(color);
@@ -22986,9 +23076,9 @@ var CustomEditor = class extends Editor {
22986
23076
  const bar = VOICE_WAVE_BARS[Math.round(wave * (VOICE_WAVE_BARS.length - 1))];
22987
23077
  const brightness = .5 + wave * .5;
22988
23078
  prompt = chalk.bold.rgb(Math.round(r * brightness), Math.round(g * brightness), Math.round(bValue * brightness))(bar);
22989
- } else prompt = chalk.bold.rgb(Math.round(r * promptBrightness), Math.round(g * promptBrightness), Math.round(bValue * promptBrightness))(promptChar);
22990
- const promptWidth = 4;
22991
- const contentWidth = width - 6;
23079
+ } else prompt = chalk.bold.rgb(r, g, bValue)(promptChar);
23080
+ const promptWidth = 3;
23081
+ const contentWidth = width - 4;
22992
23082
  const editorState = this.state;
22993
23083
  const decorativePrompt = isSlash ? "/" : isAt ? "@" : isBang ? "!" : void 0;
22994
23084
  const firstLine = editorState.lines[0];
@@ -23030,14 +23120,8 @@ var CustomEditor = class extends Editor {
23030
23120
  contentLines.push(line);
23031
23121
  }
23032
23122
  const result = [];
23033
- const hBarLen = width - 2;
23034
- const top = b("╭") + b("─").repeat(hBarLen) + b("╮");
23035
- const leftBorder = b("│");
23036
- const rightBorder = b("│");
23037
- const bottom = b("╰") + b("─").repeat(hBarLen) + b("╯");
23038
23123
  const textColorOpen = `\x1b[38;2;${parseHex(theme.getTheme().text).join(";")}m`;
23039
23124
  const textColorClose = "\x1B[39m";
23040
- result.push(top);
23041
23125
  const fullText = this.getText();
23042
23126
  let greyRemaining = this.voiceTranscriptText.length > 0 && fullText.endsWith(this.voiceTranscriptText) ? [...this.voiceTranscriptText].length : 0;
23043
23127
  const greyOpen = `\x1b[38;2;${parseHex(theme.getTheme().muted).join(";")}m`;
@@ -23046,12 +23130,11 @@ var CustomEditor = class extends Editor {
23046
23130
  contentLines[i] = line;
23047
23131
  greyRemaining -= consumed;
23048
23132
  }
23049
- for (let i = 0; i < contentLines.length; i++) {
23050
- const line = `${textColorOpen}${contentLines[i]}${textColorClose}`;
23051
- if (i === 0) result.push(`${leftBorder} ${prompt} ${line} ${rightBorder}`);
23052
- else result.push(`${leftBorder}${" ".repeat(promptWidth - 1)}${line} ${rightBorder}`);
23053
- }
23054
- result.push(bottom);
23133
+ const rows = contentLines.map((content, i) => {
23134
+ const line = `${textColorOpen}${content}${textColorClose}`;
23135
+ return i === 0 ? ` ${prompt} ${line}` : `${" ".repeat(promptWidth)}${line}`;
23136
+ });
23137
+ result.push(...fadePanel(rows, width, 1.5));
23055
23138
  for (const ind of scrollIndicators) result.push(ind);
23056
23139
  return result;
23057
23140
  }
@@ -24881,12 +24964,11 @@ function createTUIState(options) {
24881
24964
  pendingAskUserComponents: /* @__PURE__ */ new Map(),
24882
24965
  pendingSubmitPlanComponents: /* @__PURE__ */ new Map(),
24883
24966
  pendingInlineQuestions: [],
24884
- pendingFollowUpMessages: [],
24885
- pendingQueuedActions: [],
24886
24967
  followUpComponents: [],
24887
24968
  pendingSignalMessageComponentsById: /* @__PURE__ */ new Map(),
24888
24969
  pendingSlashCommands: [],
24889
24970
  pendingSlashCommandMessageIds: [],
24971
+ pendingQueueSubmissions: 0,
24890
24972
  pendingApprovalDismiss: null,
24891
24973
  projectInfo: detectProject(process.cwd()),
24892
24974
  modelAuthStatus: { hasAuth: true },
@@ -25386,6 +25468,77 @@ async function dispatchEvent(event, ectx, state) {
25386
25468
  }
25387
25469
  }
25388
25470
  //#endregion
25471
+ //#region src/tui/components/banner.ts
25472
+ /** 16 columns × 5 rows: the header logo. */
25473
+ const LOGO_HEADER = [
25474
+ " ⣠⣶⣶⣄ ⣠⣶⣶⣄",
25475
+ " ⢻⣿⣿⣿ ⣿⣿⣿⡟",
25476
+ " ⢀⣀ ⠈⠻⣿⣄⣠⣿⠟⢻⣷⣀⡀",
25477
+ "⢰⣿⣿⣿⡄ ⣿⣿⣿⣿ ⢸⣿⣿⣿⡆",
25478
+ "⠈⠻⠿⠟⠁ ⠙⠿⠿⠋ ⠈⠻⠿⠟⠁"
25479
+ ];
25480
+ /** 24 columns × 7 rows: setup screens. */
25481
+ const LOGO_LARGE = [
25482
+ " ⣴⣾⣿⣷⣦⡀ ⢀⣴⣾⣿⣷⣦",
25483
+ " ⢸⣿⣿⣿⣿⣿⡇ ⢸⣿⣿⣿⣿⣿⡇",
25484
+ " ⠻⢿⣿⣿⣿⡇ ⢸⣿⣿⣿⣿⣿",
25485
+ " ⠈⢻⣿⣷⡀⢀⣾⣿⡟⠙⢿⣿⣆",
25486
+ " ⣴⣾⣿⣿⣶⡀ ⣿⣿⣿⣿⣿⣿ ⢸⣿⣿⣿⣷⣦",
25487
+ "⠸⣿⣿⣿⣿⣿⡇ ⣿⣿⣿⣿⣿⣿ ⢸⣿⣿⣿⣿⣿⠇",
25488
+ " ⠹⢿⣿⡿⠟⠁ ⠘⠿⣿⣿⠿⠃ ⠈⠻⢿⣿⡿⠏"
25489
+ ];
25490
+ /** Logo rows in the accent color, padded to a common width. */
25491
+ function renderLogo(rows = LOGO_HEADER) {
25492
+ const w = Math.max(...rows.map((r) => visibleWidth(r)));
25493
+ return rows.map((r) => theme.fg("accent", r) + " ".repeat(w - visibleWidth(r)));
25494
+ }
25495
+ const LOGO_GAP = 3;
25496
+ /**
25497
+ * Header lines for a given width:
25498
+ * - wide: logo, then title + info beside it (the info block is padded to the logo's 5 rows)
25499
+ * - narrow: logo stacked above title + info
25500
+ * - very narrow or a custom app name: one text line
25501
+ */
25502
+ function renderHeader(width, { version, appName, info }) {
25503
+ const name = appName || "Mastra Code";
25504
+ const compact = theme.fg("accent", "◆") + " " + theme.bold(theme.fg("accent", name)) + theme.fg("dim", ` v${version}`);
25505
+ if (name !== "Mastra Code" || width < 30) return [compact];
25506
+ const title = theme.bold(theme.fg("text", name)) + theme.fg("dim", ` v${version}`);
25507
+ const fit = (room) => info.map((l) => theme.fg("muted", visibleWidth(l) <= room ? l : [...l].slice(0, Math.max(0, room - 1)).join("") + "…"));
25508
+ const logo = renderLogo();
25509
+ const room = width - visibleWidth(logo[0]) - LOGO_GAP;
25510
+ if (visibleWidth(title) <= room) {
25511
+ const infoLines = fit(room);
25512
+ const block = infoLines.length + 1 < logo.length ? [
25513
+ title,
25514
+ "",
25515
+ ...infoLines
25516
+ ] : [title, ...infoLines];
25517
+ const top = Math.floor((logo.length - block.length) / 2);
25518
+ return logo.map((row, i) => row + " ".repeat(LOGO_GAP) + (block[i - top] ?? ""));
25519
+ }
25520
+ return [
25521
+ ...logo,
25522
+ "",
25523
+ title,
25524
+ ...fit(width)
25525
+ ];
25526
+ }
25527
+ /** Startup header component; re-lays out on resize. */
25528
+ var HeaderComponent = class {
25529
+ options;
25530
+ paddingX;
25531
+ constructor(options, paddingX = 1) {
25532
+ this.options = options;
25533
+ this.paddingX = paddingX;
25534
+ }
25535
+ invalidate() {}
25536
+ render(width) {
25537
+ const pad = " ".repeat(this.paddingX);
25538
+ return renderHeader(width - this.paddingX * 2, this.options).map((l) => pad + l);
25539
+ }
25540
+ };
25541
+ //#endregion
25389
25542
  //#region src/tui/onboarding-inline.ts
25390
25543
  /**
25391
25544
  * Setup onboarding component.
@@ -25400,6 +25553,25 @@ async function dispatchEvent(event, ectx, state) {
25400
25553
  * The component renders as a settings overlay. On completion it fires
25401
25554
  * `onComplete` with the collected choices.
25402
25555
  */
25556
+ const STEP_LABELS = [
25557
+ "Welcome",
25558
+ "Sign in",
25559
+ "Models",
25560
+ "Memory",
25561
+ "Approval"
25562
+ ];
25563
+ const STEP_INDEX = {
25564
+ welcome: 0,
25565
+ auth: 1,
25566
+ modePack: 2,
25567
+ omPack: 3,
25568
+ yolo: 4,
25569
+ done: 5
25570
+ };
25571
+ /** "✓ Welcome ● Sign in ○ Models …": done steps checked, the current one bold. */
25572
+ function renderStepper(current) {
25573
+ return STEP_LABELS.map((label, i) => i < current ? `${theme.fg("success", "✓")} ${theme.fg("muted", label)}` : i === current ? `${theme.fg("accent", "●")} ${theme.bold(theme.fg("text", label))}` : theme.fg("dim", `○ ${label}`)).join(" ");
25574
+ }
25403
25575
  var OnboardingInlineComponent = class extends Box {
25404
25576
  tui;
25405
25577
  options;
@@ -25421,7 +25593,7 @@ var OnboardingInlineComponent = class extends Box {
25421
25593
  this._focused = value;
25422
25594
  }
25423
25595
  constructor(options) {
25424
- super(4, 2, (text) => theme.bg("overlayBg", text));
25596
+ super(4, 2, (text) => text);
25425
25597
  this.tui = options.tui;
25426
25598
  this.options = options;
25427
25599
  const prevModePack = options.previous?.modePackId ? options.modePacks.find((p) => p.id === options.previous.modePackId) : void 0;
@@ -25437,6 +25609,60 @@ var OnboardingInlineComponent = class extends Box {
25437
25609
  if (options.previous?.yolo != null) this.selectedYolo = options.previous.yolo;
25438
25610
  this.renderStep("welcome");
25439
25611
  }
25612
+ /**
25613
+ * Full-screen setup: the large logo, a step indicator and the current step, centered. Earlier steps
25614
+ * are not repeated (the indicator shows progress). On short terminals the gaps tighten, then the logo
25615
+ * shrinks, then it is dropped, so the step itself is never cut off.
25616
+ */
25617
+ render(width) {
25618
+ const rows = this.tui.terminal?.rows ?? 40;
25619
+ const contentWidth = Math.max(20, Math.min(width - 8, 80));
25620
+ const body = (this.stepBox ? this.stepBox.render(contentWidth) : []).map((line) => line.replace(/\s+$/, ""));
25621
+ while (body.length && body[0] === "") body.shift();
25622
+ while (body.length && body.at(-1) === "") body.pop();
25623
+ const center = (lines) => {
25624
+ const blockWidth = Math.max(0, ...lines.map((line) => visibleWidth(line)));
25625
+ const pad = " ".repeat(Math.max(0, Math.floor((width - blockWidth) / 2)));
25626
+ return lines.map((line) => line ? pad + line : "");
25627
+ };
25628
+ const stepper = center([renderStepper(STEP_INDEX[this.currentStep])]);
25629
+ const layouts = [
25630
+ [
25631
+ ...center(renderLogo(LOGO_LARGE)),
25632
+ "",
25633
+ "",
25634
+ ...stepper,
25635
+ "",
25636
+ "",
25637
+ ...center(body)
25638
+ ],
25639
+ [
25640
+ ...center(renderLogo(LOGO_LARGE)),
25641
+ "",
25642
+ ...stepper,
25643
+ "",
25644
+ ...center(body)
25645
+ ],
25646
+ [
25647
+ ...center(renderLogo(LOGO_HEADER)),
25648
+ "",
25649
+ ...stepper,
25650
+ "",
25651
+ ...center(body)
25652
+ ],
25653
+ [
25654
+ ...stepper,
25655
+ "",
25656
+ ...center(body)
25657
+ ],
25658
+ center(body)
25659
+ ];
25660
+ const content = layouts.find((layout) => layout.length <= rows) ?? layouts.at(-1);
25661
+ const top = Math.max(0, Math.floor((rows - content.length) / 2));
25662
+ const screen = [...Array.from({ length: top }, () => ""), ...content];
25663
+ while (screen.length < rows) screen.push("");
25664
+ return screen.map((line) => line + " ".repeat(Math.max(0, width - visibleWidth(line))));
25665
+ }
25440
25666
  get finished() {
25441
25667
  return this._finished;
25442
25668
  }
@@ -25487,7 +25713,7 @@ var OnboardingInlineComponent = class extends Box {
25487
25713
  stepCount = 0;
25488
25714
  makeBox() {
25489
25715
  this.clearStep();
25490
- this.stepBox = new Box(0, 1, (text) => theme.bg("overlayBg", text));
25716
+ this.stepBox = new Box(0, 1, (text) => text);
25491
25717
  if (this.stepCount > 0) this.addChild(new Spacer(1));
25492
25718
  this.stepCount++;
25493
25719
  this.addChild(this.stepBox);
@@ -25495,7 +25721,7 @@ var OnboardingInlineComponent = class extends Box {
25495
25721
  }
25496
25722
  renderWelcome() {
25497
25723
  const box = this.makeBox();
25498
- box.addChild(new Text(theme.bold(theme.fg("accent", "👋 Welcome to Mastra Code")), 0, 0));
25724
+ box.addChild(new Text(theme.bold(theme.fg("text", "Welcome to Mastra Code")), 0, 0));
25499
25725
  box.addChild(new Spacer(1));
25500
25726
  box.addChild(new Text(theme.fg("text", "Let's configure your models and preferences."), 0, 0));
25501
25727
  box.addChild(new Text(chalk.white("You can re-run this anytime with /setup."), 0, 0));
@@ -25527,7 +25753,7 @@ var OnboardingInlineComponent = class extends Box {
25527
25753
  }
25528
25754
  renderAuth() {
25529
25755
  const box = this.makeBox();
25530
- box.addChild(new Text(theme.bold(theme.fg("accent", "🔑 Authentication")), 0, 0));
25756
+ box.addChild(new Text(theme.bold(theme.fg("text", "Authentication")), 0, 0));
25531
25757
  box.addChild(new Spacer(1));
25532
25758
  const providers = this.options.authProviders;
25533
25759
  if (providers.length === 0) {
@@ -25580,7 +25806,7 @@ var OnboardingInlineComponent = class extends Box {
25580
25806
  box.addChild(new Text(theme.fg("dim", "Run /connect to configure an API key or authenticate with an account."), 0, 0));
25581
25807
  box.addChild(new Spacer(1));
25582
25808
  }
25583
- box.addChild(new Text(theme.bold(theme.fg("accent", "Model Packs")), 0, 0));
25809
+ box.addChild(new Text(theme.bold(theme.fg("text", "Model Packs")), 0, 0));
25584
25810
  box.addChild(new Spacer(1));
25585
25811
  box.addChild(new Text(theme.fg("text", "Choose default models for each mode (build / plan / fast):"), 0, 0));
25586
25812
  box.addChild(new Spacer(1));
@@ -25726,7 +25952,7 @@ var OnboardingInlineComponent = class extends Box {
25726
25952
  return;
25727
25953
  }
25728
25954
  const box = this.makeBox();
25729
- box.addChild(new Text(theme.bold(theme.fg("accent", "🧠 Observational Memory")), 0, 0));
25955
+ box.addChild(new Text(theme.bold(theme.fg("text", "Observational Memory")), 0, 0));
25730
25956
  box.addChild(new Spacer(1));
25731
25957
  box.addChild(new Text(theme.fg("text", "Choose the model for observational memory:"), 0, 0));
25732
25958
  box.addChild(new Text(theme.fg("dim", "https://mastra.ai/docs/memory/observational-memory"), 0, 0));
@@ -25781,7 +26007,7 @@ var OnboardingInlineComponent = class extends Box {
25781
26007
  }
25782
26008
  renderYolo() {
25783
26009
  const box = this.makeBox();
25784
- box.addChild(new Text(theme.bold(theme.fg("accent", "⚡ Tool Approval")), 0, 0));
26010
+ box.addChild(new Text(theme.bold(theme.fg("text", "Tool Approval")), 0, 0));
25785
26011
  box.addChild(new Spacer(1));
25786
26012
  box.addChild(new Text(theme.fg("text", "YOLO mode auto-approves all tool calls (edits, commands, etc)."), 0, 0));
25787
26013
  box.addChild(new Text(theme.fg("text", "You can toggle this anytime with Ctrl+Y or /yolo."), 0, 0));
@@ -25839,7 +26065,7 @@ var OnboardingInlineComponent = class extends Box {
25839
26065
  collapseStep(summary) {
25840
26066
  if (!this.stepBox) return;
25841
26067
  this.stepBox.clear();
25842
- this.stepBox.setBgFn((text) => theme.bg("overlayBg", text));
26068
+ this.stepBox.setBgFn((text) => text);
25843
26069
  this.stepBox.addChild(new Text(`${theme.fg("success", "✓")} ${theme.fg("text", summary)}`, 0, 0));
25844
26070
  this.selectList = void 0;
25845
26071
  this.activeInlineQuestion = void 0;
@@ -25866,80 +26092,6 @@ function showSessionError(state, error) {
25866
26092
  showError(state, error instanceof Error ? error.message : "Unknown error");
25867
26093
  }
25868
26094
  //#endregion
25869
- //#region src/tui/components/banner.ts
25870
- /**
25871
- * ASCII art banner for the Mastra Code TUI header.
25872
- * Renders "MASTRA CODE" or "MASTRA" in block-letter art with a green gradient.
25873
- */
25874
- const GRADIENT_STOPS = [
25875
- "#085314",
25876
- "#0d8020",
25877
- "#16c858",
25878
- "#62f69d",
25879
- "#a1fac7"
25880
- ];
25881
- const FULL_ART = [
25882
- "█▀▄▀█ ▄▀█ █▀ ▀█▀ █▀█ ▄▀█ █▀▀ █▀█ █▀▄ █▀▀",
25883
- "█ ▀ █ █▀█ ▀█ █ █▀▄ █▀█ █ █ █ █ █ █▀▀",
25884
- "▀ ▀ ▀ ▀ ▀▀ ▀ ▀ ▀ ▀ ▀ ▀▀▀ ▀▀▀ ▀▀ ▀▀▀"
25885
- ];
25886
- const SHORT_ART = [
25887
- "█▀▄▀█ ▄▀█ █▀ ▀█▀ █▀█ ▄▀█",
25888
- "█ ▀ █ █▀█ ▀█ █ █▀▄ █▀█",
25889
- "▀ ▀ ▀ ▀ ▀▀ ▀ ▀ ▀ ▀ ▀"
25890
- ];
25891
- /**
25892
- * Interpolate between two hex colors.
25893
- */
25894
- function lerpColor(hex1, hex2, t) {
25895
- const r1 = parseInt(hex1.slice(1, 3), 16);
25896
- const g1 = parseInt(hex1.slice(3, 5), 16);
25897
- const b1 = parseInt(hex1.slice(5, 7), 16);
25898
- const r2 = parseInt(hex2.slice(1, 3), 16);
25899
- const g2 = parseInt(hex2.slice(3, 5), 16);
25900
- const b2 = parseInt(hex2.slice(5, 7), 16);
25901
- return [
25902
- Math.round(r1 + (r2 - r1) * t),
25903
- Math.round(g1 + (g2 - g1) * t),
25904
- Math.round(b1 + (b2 - b1) * t)
25905
- ];
25906
- }
25907
- /**
25908
- * Color a single character based on its horizontal position in the gradient.
25909
- */
25910
- function gradientChar(ch, colIdx, totalCols) {
25911
- if (ch === " ") return " ";
25912
- const t = totalCols <= 1 ? .5 : colIdx / (totalCols - 1);
25913
- const segmentCount = GRADIENT_STOPS.length - 1;
25914
- const segment = Math.min(Math.floor(t * segmentCount), segmentCount - 1);
25915
- const frac = t * segmentCount - segment;
25916
- const [r, g, b] = lerpColor(GRADIENT_STOPS[segment], GRADIENT_STOPS[segment + 1], frac);
25917
- return chalk.rgb(r, g, b)(ch);
25918
- }
25919
- /**
25920
- * Apply left-to-right purple gradient to a line of text.
25921
- */
25922
- function colorLine(line) {
25923
- const chars = [...line];
25924
- return chars.map((ch, i) => gradientChar(ch, i, chars.length)).join("");
25925
- }
25926
- /**
25927
- * Render the banner header for the TUI.
25928
- *
25929
- * @param version - App version string (e.g. "0.2.0")
25930
- * @param appName - App name. Block art is only used for "Mastra Code" (default).
25931
- * @returns Styled multi-line string ready for display.
25932
- */
25933
- function renderBanner(version, appName) {
25934
- const name = appName || "Mastra Code";
25935
- if (name !== "Mastra Code") return theme.fg("accent", "◆") + " " + theme.bold(theme.fg("accent", name)) + theme.fg("dim", ` v${version}`);
25936
- const cols = process.stdout.columns || 80;
25937
- if (cols < 30) return theme.fg("accent", "◆") + " " + theme.bold(theme.fg("accent", "Mastra Code")) + theme.fg("dim", ` v${version}`);
25938
- const coloredLines = (cols >= 50 ? FULL_ART : SHORT_ART).map((line) => colorLine(line));
25939
- coloredLines.push(theme.fg("dim", `v${version}`));
25940
- return coloredLines.join("\n");
25941
- }
25942
- //#endregion
25943
26095
  //#region src/tui/components/idle-counter.ts
25944
26096
  /**
25945
26097
  * Live work/idle-time indicator shown above the user input.
@@ -25949,6 +26101,7 @@ var IdleCounterComponent = class extends Container {
25949
26101
  timingState;
25950
26102
  textChild;
25951
26103
  thinking = false;
26104
+ activity = "";
25952
26105
  constructor() {
25953
26106
  super();
25954
26107
  this.textChild = new Text("", 0, 0);
@@ -25958,17 +26111,18 @@ var IdleCounterComponent = class extends Container {
25958
26111
  this.timingState = timingState;
25959
26112
  this.update(now);
25960
26113
  }
25961
- /** Quiet mode shows the live "Thinking..." indicator here instead of in the chat. */
26114
+ /** Quiet mode's live thinking state; status-line.ts swaps the Working row's label while it is set. */
25962
26115
  setThinking(thinking) {
25963
- if (this.thinking === thinking) return;
25964
26116
  this.thinking = thinking;
25965
- this.update();
26117
+ }
26118
+ isThinking() {
26119
+ return this.thinking;
26120
+ }
26121
+ /** The Working row (built in status-line.ts) shares this slot; empty when idle. */
26122
+ setActivity(text) {
26123
+ this.activity = text;
25966
26124
  }
25967
26125
  update(now = Date.now()) {
25968
- if (this.thinking) {
25969
- this.textChild.setText(` ${theme.italic(theme.fg("thinkingText", "Thinking..."))}`);
25970
- return;
25971
- }
25972
26126
  const segments = this.timingState ? formatIdleStatusTimingSegments(this.timingState, now) : null;
25973
26127
  if (!segments) {
25974
26128
  this.textChild.setText("");
@@ -25978,8 +26132,8 @@ var IdleCounterComponent = class extends Container {
25978
26132
  this.textChild.setText(idle ? ` ${idle}` : "");
25979
26133
  }
25980
26134
  render(width) {
25981
- const rendered = super.render(width);
25982
- return rendered.length > 0 ? rendered : [""];
26135
+ if (this.activity) return ["", truncateToWidth(this.activity, width)];
26136
+ return ["", super.render(width)[0] ?? ""];
25983
26137
  }
25984
26138
  };
25985
26139
  function formatIdleStatusTimingSegments(state, now = Date.now()) {
@@ -26156,24 +26310,39 @@ function abortActiveGoalJudge(state) {
26156
26310
  }
26157
26311
  function buildLayout(state, refreshModelAuthStatus) {
26158
26312
  const appName = state.options.appName || "Mastra Code";
26159
- const banner = renderBanner(state.options.version || "0.1.0", appName);
26160
- const frontmatter = [
26313
+ const version = state.options.version || "0.1.0";
26314
+ const info = [
26161
26315
  `Project: ${state.projectInfo.name}`,
26162
26316
  `Resource ID: ${state.projectInfo.resourceId}`,
26163
26317
  state.projectInfo.gitBranch ? `Branch: ${state.projectInfo.gitBranch}` : null,
26164
26318
  state.projectInfo.isWorktree ? `Worktree of: ${state.projectInfo.mainRepoPath}` : null
26165
- ].filter(Boolean).map((line) => theme.fg("muted", line)).join("\n");
26319
+ ].filter((line) => Boolean(line));
26166
26320
  const sep = theme.fg("dim", " · ");
26167
26321
  const hintParts = [];
26168
- if (state.controller.listModes().length > 1) hintParts.push(`${theme.fg("accent", "⇧+Tab")} ${theme.fg("muted", "cycle modes")}`);
26169
- hintParts.push(`${theme.fg("accent", "/help")} ${theme.fg("muted", "info & shortcuts")}`);
26170
- const instructions = ` ${hintParts.join(sep)}`;
26171
- state.ui.addChild(new Spacer(1));
26172
- state.ui.addChild(new Text(banner, 1, 0));
26173
- state.ui.addChild(new Text(frontmatter, 1, 0));
26322
+ if (state.controller.listModes().length > 1) hintParts.push(keyHint("shift+tab", "cycle modes"));
26323
+ hintParts.push(keyHint("/help", "info & shortcuts"));
26324
+ const renderHints = (width) => {
26325
+ for (let count = hintParts.length; count > 0; count--) {
26326
+ const row = ` ${hintParts.slice(0, count).join(sep)}`;
26327
+ if (visibleWidth(row) <= width) return [row];
26328
+ }
26329
+ return [];
26330
+ };
26174
26331
  state.ui.addChild(new Spacer(1));
26175
- state.ui.addChild(new Text(instructions, 0, 0));
26332
+ state.ui.addChild(new HeaderComponent({
26333
+ version,
26334
+ appName,
26335
+ info
26336
+ }));
26176
26337
  state.ui.addChild(new Spacer(1));
26338
+ state.ui.addChild({
26339
+ render: renderHints,
26340
+ invalidate: () => {}
26341
+ });
26342
+ state.ui.addChild({
26343
+ render: () => state.chatContainer.children.length > 0 ? [""] : [],
26344
+ invalidate: () => {}
26345
+ });
26177
26346
  state.ui.addChild(state.chatContainer);
26178
26347
  state.taskProgress = new TaskProgressComponent();
26179
26348
  state.taskProgress.setQuietMode(state.quietMode);
@@ -26189,6 +26358,15 @@ function buildLayout(state, refreshModelAuthStatus) {
26189
26358
  state.footer.addChild(state.memoryStatusLine);
26190
26359
  state.ui.addChild(state.footer);
26191
26360
  updateStatusLine(state);
26361
+ let statusWidth = 0;
26362
+ const renderFooter = state.footer.render.bind(state.footer);
26363
+ state.footer.render = (width) => {
26364
+ if (width !== statusWidth) {
26365
+ statusWidth = width;
26366
+ updateStatusLine(state);
26367
+ }
26368
+ return renderFooter(width);
26369
+ };
26192
26370
  refreshModelAuthStatus();
26193
26371
  state.ui.setFocus(state.editor);
26194
26372
  installOverlayFocusHandoff(state.ui, state);
@@ -26713,7 +26891,7 @@ async function renderExistingTasks(state) {
26713
26891
  //#region src/tui/components/shell-output.ts
26714
26892
  /**
26715
26893
  * Streaming shell output component for the shell passthrough (! command).
26716
- * Shows a bordered box with live stdout/stderr and a status footer.
26894
+ * A tool-style block: "● $ command" with live stdout/stderr on a shaded panel below.
26717
26895
  */
26718
26896
  const MAX_LINES = 200;
26719
26897
  const COLLAPSED_LINES = 20;
@@ -26759,33 +26937,25 @@ var ShellStreamComponent = class extends WidthAwareContainer {
26759
26937
  }
26760
26938
  rebuildForWidth(termWidth) {
26761
26939
  this.clear();
26762
- const border = (char) => theme.bold(theme.fg("accent", char));
26763
- const maxLineWidth = termWidth - 6;
26764
26940
  const done = this.exitCode !== void 0;
26765
- const statusIcon = done ? this.exitCode === 0 ? theme.fg("success", " ✓") : theme.fg("error", " ✗") : theme.fg("muted", " ⋯");
26941
+ const failed = done && this.exitCode !== 0;
26766
26942
  const durationStr = done ? theme.fg("muted", ` ${formatDuration(Date.now() - this.startTime)}`) : "";
26767
- const footerText = `${theme.bold(theme.fg("toolTitle", "$"))} ${theme.fg("accent", this.command)}${durationStr}${statusIcon}`;
26768
- this.addChild(new Text(border("╭──"), 0, 0));
26943
+ const title = `${theme.bold(theme.fg("toolTitle", "$"))} ${theme.fg("toolArgs", this.command)}${durationStr}${failed ? theme.fg("error", " ✗") : ""}`;
26769
26944
  const displayLines = [...this.lines];
26770
26945
  if (this.trailingPartial && !done) displayLines.push(this.trailingPartial);
26771
26946
  while (displayLines.length > 0 && displayLines[0] === "") displayLines.shift();
26772
- if (displayLines.length > 0) {
26773
- const maxVisible = this.expanded ? MAX_LINES : COLLAPSED_LINES;
26774
- const truncated = displayLines.length > maxVisible;
26775
- const borderedLines = (truncated ? displayLines.slice(-maxVisible) : displayLines).map((line) => {
26776
- const truncatedLine = truncateAnsi(line, maxLineWidth);
26777
- return border("│") + " " + truncatedLine;
26778
- });
26779
- if (truncated) {
26780
- const remaining = displayLines.length - maxVisible;
26781
- const action = this.expanded ? "collapse" : "expand";
26782
- borderedLines.push(border("│") + " " + theme.fg("muted", `... ${remaining} more lines (Ctrl+E to ${action})`));
26783
- }
26784
- const displayOutput = borderedLines.join("\n");
26785
- if (displayOutput.trim()) this.addChild(new Text(displayOutput, 0, 0));
26947
+ const maxVisible = this.expanded ? MAX_LINES : COLLAPSED_LINES;
26948
+ const truncated = displayLines.length > maxVisible;
26949
+ const output = (truncated ? displayLines.slice(-maxVisible) : displayLines).map((line) => theme.fg("toolOutput", line));
26950
+ if (truncated) {
26951
+ const remaining = displayLines.length - maxVisible;
26952
+ const action = this.expanded ? "collapse" : "expand";
26953
+ output.unshift(theme.fg("dim", `… ${remaining} earlier lines · `) + theme.fg("muted", "ctrl+e") + theme.fg("dim", ` to ${action}`));
26786
26954
  }
26787
- this.addChild(new Text(`${border("╰──")} ${footerText}`, 0, 0));
26788
- if (done && this.exitCode !== 0) this.addChild(new Text(theme.fg("error", ` Exit code: ${this.exitCode}`), 0, 0));
26955
+ if (failed) output.push(theme.fg("error", `Exit code: ${this.exitCode}`));
26956
+ const dot = statusDot(done ? failed ? "error" : "done" : "running");
26957
+ const hasOutput = output.some((line) => line.trim());
26958
+ this.addChild(new Text(toolBlock(dot, title, hasOutput ? output : [], termWidth).join("\n"), 0, 0));
26789
26959
  this.invalidate();
26790
26960
  }
26791
26961
  getChatSpacingKind() {
@@ -27224,7 +27394,21 @@ var MastraTUI = class MastraTUI {
27224
27394
  this.state.quietModeMaxToolPreviewLines = savedSettings.preferences.quietModeMaxToolPreviewLines;
27225
27395
  const originalHandleInput = this.state.editor.handleInput.bind(this.state.editor);
27226
27396
  this.state.editor.handleInput = (data) => {
27227
- if (this.state.activeInlinePlanApproval) {
27397
+ if (this.state.activeOnboarding) {
27398
+ if (data === "") {
27399
+ this.state.activeOnboarding.cancel();
27400
+ this.state.activeOnboarding = void 0;
27401
+ originalHandleInput(data);
27402
+ } else this.state.activeOnboarding.handleInput(data);
27403
+ return;
27404
+ }
27405
+ if (this.state.activeInlineApproval) {
27406
+ const expandsRow = data === "" && !this.state.activeInlineApproval.handlesExpand?.();
27407
+ if (data !== "" && !expandsRow) {
27408
+ this.state.activeInlineApproval.handleInput(data);
27409
+ return;
27410
+ }
27411
+ } else if (this.state.activeInlinePlanApproval) {
27228
27412
  if (data !== "") {
27229
27413
  this.state.activeInlinePlanApproval.handleInput(data);
27230
27414
  return;
@@ -27235,13 +27419,6 @@ var MastraTUI = class MastraTUI {
27235
27419
  return;
27236
27420
  }
27237
27421
  }
27238
- if (this.state.activeOnboarding) if (data === "") {
27239
- this.state.activeOnboarding.cancel();
27240
- this.state.activeOnboarding = void 0;
27241
- } else {
27242
- this.state.activeOnboarding.handleInput(data);
27243
- return;
27244
- }
27245
27422
  originalHandleInput(data);
27246
27423
  };
27247
27424
  this.state.editor.onImagePaste = (image) => {
@@ -27249,7 +27426,6 @@ var MastraTUI = class MastraTUI {
27249
27426
  this.state.editor.insertTextAtCursor?.("[image] ");
27250
27427
  flushRender(this.state);
27251
27428
  };
27252
- this.state.editor.getPromptAnimator = () => this.state.gradientAnimator;
27253
27429
  setupKeyboardShortcuts(this.state, {
27254
27430
  stop: () => this.stop(),
27255
27431
  exit: (exitCode) => this.exit(exitCode),
@@ -27435,9 +27611,19 @@ var MastraTUI = class MastraTUI {
27435
27611
  this.state.messageComponentsById.set(signalId, component);
27436
27612
  }
27437
27613
  createPendingNewThread() {
27614
+ if (this.state.pendingNewThreadCreation) return this.state.pendingNewThreadCreation;
27438
27615
  if (!this.state.pendingNewThread) return void 0;
27439
27616
  this.state.pendingNewThread = false;
27440
- return this.state.session.thread.create().then(() => void 0);
27617
+ const creation = this.state.session.thread.create().then(() => void 0, (error) => {
27618
+ this.state.pendingNewThread = true;
27619
+ throw error;
27620
+ });
27621
+ this.state.pendingNewThreadCreation = creation;
27622
+ const clear = () => {
27623
+ if (this.state.pendingNewThreadCreation === creation) this.state.pendingNewThreadCreation = void 0;
27624
+ };
27625
+ creation.then(clear, clear);
27626
+ return creation;
27441
27627
  }
27442
27628
  sendOptimisticSignal(content, images, optimisticMessageId, pendingNewThread) {
27443
27629
  const send = () => {
@@ -27495,18 +27681,28 @@ var MastraTUI = class MastraTUI {
27495
27681
  const messageId = `queued-slash-${Date.now()}-${this.state.pendingSlashCommands.length}`;
27496
27682
  this.state.pendingSlashCommands.push(text);
27497
27683
  this.state.pendingSlashCommandMessageIds.push(messageId);
27498
- this.state.pendingQueuedActions.push("slash");
27499
27684
  addPendingUserMessage(this.state, messageId, text);
27500
27685
  updateStatusLine(this.state);
27501
27686
  return;
27502
27687
  }
27503
27688
  const { content, images } = consumePendingImages(text, this.state.pendingImages);
27504
27689
  this.state.pendingImages = [];
27505
- this.state.pendingFollowUpMessages.push({
27690
+ const files = images?.map((img) => ({
27691
+ data: img.data,
27692
+ mediaType: img.mimeType
27693
+ }));
27694
+ const queue = () => this.state.session.queueMessage({
27506
27695
  content,
27507
- images
27696
+ files
27697
+ });
27698
+ const pendingThread = this.createPendingNewThread();
27699
+ this.state.pendingQueueSubmissions++;
27700
+ (pendingThread ? pendingThread.then(queue) : queue()).catch((error) => {
27701
+ showSessionError(this.state, error);
27702
+ }).finally(() => {
27703
+ this.state.pendingQueueSubmissions--;
27704
+ if (this.state.pendingQueueSubmissions === 0) drainQueuedActionIfIdle(this.getEventContext());
27508
27705
  });
27509
- this.state.pendingQueuedActions.push("message");
27510
27706
  updateStatusLine(this.state);
27511
27707
  flushRender(this.state);
27512
27708
  }
@@ -28174,7 +28370,12 @@ var MastraTUI = class MastraTUI {
28174
28370
  }
28175
28371
  });
28176
28372
  this.state.activeOnboarding = component;
28177
- showModalOverlay(this.state.ui, component, { maxHeight: "80%" });
28373
+ showModalOverlay(this.state.ui, component, {
28374
+ widthPercent: 1,
28375
+ maxWidth: 1e4,
28376
+ maxHeight: "100%",
28377
+ minHeightPercent: 1
28378
+ });
28178
28379
  component.focused = true;
28179
28380
  });
28180
28381
  }
@@ -28383,4 +28584,4 @@ var MastraTUI = class MastraTUI {
28383
28584
  //#endregion
28384
28585
  export { mastraBrand as _, OMProgressComponent as a, TUI_CO_AUTHOR as b, ToolExecutionComponentEnhanced as c, detectTerminalTheme as d, applyThemeMode as f, mastra as g, getThemeMode as h, LoginDialogComponent as i, ModelSelectorComponent as l, getMarkdownTheme as m, createTUIState as n, formatOMStatus as o, getEditorTheme as p, LoginSelectorComponent as r, UserMessageComponent as s, MastraTUI as t, AssistantMessageComponent as u, restoreTerminalForeground as v, theme as y };
28385
28586
 
28386
- //# sourceMappingURL=tui-CDdSkr_Q.js.map
28587
+ //# sourceMappingURL=tui-5IfsALo2.js.map