@f5xc-salesdemos/xcsh 19.41.1 → 19.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -8
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/modes/components/assistant-message.ts +9 -5
- package/src/modes/theme/mermaid-cache.ts +68 -32
- package/src/modes/theme/mermaid-palette.ts +43 -0
- package/src/modes/theme/theme.ts +16 -2
- package/src/tools/mermaid-renderer.ts +35 -6
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "19.
|
|
4
|
+
"version": "19.42.0",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5xc-salesdemos/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -51,13 +51,13 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
53
53
|
"@mozilla/readability": "^0.6",
|
|
54
|
-
"@f5xc-salesdemos/xcsh-stats": "19.
|
|
55
|
-
"@f5xc-salesdemos/pi-agent-core": "19.
|
|
56
|
-
"@f5xc-salesdemos/pi-ai": "19.
|
|
57
|
-
"@f5xc-salesdemos/pi-natives": "19.
|
|
58
|
-
"@f5xc-salesdemos/pi-resource-management": "19.
|
|
59
|
-
"@f5xc-salesdemos/pi-tui": "19.
|
|
60
|
-
"@f5xc-salesdemos/pi-utils": "19.
|
|
54
|
+
"@f5xc-salesdemos/xcsh-stats": "19.42.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-agent-core": "19.42.0",
|
|
56
|
+
"@f5xc-salesdemos/pi-ai": "19.42.0",
|
|
57
|
+
"@f5xc-salesdemos/pi-natives": "19.42.0",
|
|
58
|
+
"@f5xc-salesdemos/pi-resource-management": "19.42.0",
|
|
59
|
+
"@f5xc-salesdemos/pi-tui": "19.42.0",
|
|
60
|
+
"@f5xc-salesdemos/pi-utils": "19.42.0",
|
|
61
61
|
"@sinclair/typebox": "^0.34",
|
|
62
62
|
"@xterm/headless": "^6.0",
|
|
63
63
|
"ajv": "^8.20",
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.42.0",
|
|
21
|
+
"commit": "5959e8371dc1aaa802e7a5b3bc51642328816b2a",
|
|
22
|
+
"shortCommit": "5959e83",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.
|
|
25
|
-
"commitDate": "2026-06-
|
|
26
|
-
"buildDate": "2026-06-
|
|
24
|
+
"tag": "v19.42.0",
|
|
25
|
+
"commitDate": "2026-06-24T01:36:24Z",
|
|
26
|
+
"buildDate": "2026-06-24T02:03:24.964Z",
|
|
27
27
|
"dirty": true,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5xc-salesdemos/xcsh",
|
|
30
30
|
"repoSlug": "f5xc-salesdemos/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/5959e8371dc1aaa802e7a5b3bc51642328816b2a",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.42.0"
|
|
33
33
|
};
|
|
@@ -2,7 +2,7 @@ import type { AssistantMessage, ImageContent, Usage } from "@f5xc-salesdemos/pi-
|
|
|
2
2
|
import { Container, Image, ImageProtocol, Markdown, Spacer, TERMINAL, Text } from "@f5xc-salesdemos/pi-tui";
|
|
3
3
|
import { formatNumber, logger } from "@f5xc-salesdemos/pi-utils";
|
|
4
4
|
import { settings } from "../../config/settings";
|
|
5
|
-
import { hasPendingMermaid, prerenderMermaid } from "../../modes/theme/mermaid-cache";
|
|
5
|
+
import { hasPendingMermaid, mermaidThemeSignature, prerenderMermaid } from "../../modes/theme/mermaid-cache";
|
|
6
6
|
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
7
7
|
import { resolveImageOptions } from "../../tools/render-utils";
|
|
8
8
|
|
|
@@ -86,10 +86,14 @@ export class AssistantMessageComponent extends Container {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
#triggerMermaidPrerender(message: AssistantMessage): void {
|
|
89
|
-
|
|
89
|
+
// ASCII/Unicode mermaid renders in any terminal — do NOT gate on image protocol.
|
|
90
|
+
if (this.#prerenderInFlight) return;
|
|
90
91
|
|
|
92
|
+
const sig = mermaidThemeSignature(theme);
|
|
91
93
|
// Check if any text content has pending mermaid blocks
|
|
92
|
-
const hasPending = message.content.some(
|
|
94
|
+
const hasPending = message.content.some(
|
|
95
|
+
c => c.type === "text" && c.text.trim() && hasPendingMermaid(c.text, sig),
|
|
96
|
+
);
|
|
93
97
|
if (!hasPending) return;
|
|
94
98
|
|
|
95
99
|
this.#prerenderInFlight = true;
|
|
@@ -98,8 +102,8 @@ export class AssistantMessageComponent extends Container {
|
|
|
98
102
|
void (async () => {
|
|
99
103
|
try {
|
|
100
104
|
for (const content of message.content) {
|
|
101
|
-
if (content.type === "text" && content.text.trim() && hasPendingMermaid(content.text)) {
|
|
102
|
-
prerenderMermaid(content.text);
|
|
105
|
+
if (content.type === "text" && content.text.trim() && hasPendingMermaid(content.text, sig)) {
|
|
106
|
+
prerenderMermaid(content.text, theme);
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
} catch (error) {
|
|
@@ -1,44 +1,84 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
extractMermaidBlocks,
|
|
3
|
+
logger,
|
|
4
|
+
type MermaidAsciiRenderOptions,
|
|
5
|
+
type MermaidColorMode,
|
|
6
|
+
pickColorMode,
|
|
7
|
+
renderMermaidAsciiSafe,
|
|
8
|
+
tintMermaidNodes,
|
|
9
|
+
} from "@f5xc-salesdemos/pi-utils";
|
|
10
|
+
import { buildMermaidAsciiTheme, buildNodeAccents } from "./mermaid-palette";
|
|
11
|
+
import type { Theme } from "./theme";
|
|
2
12
|
|
|
3
|
-
|
|
13
|
+
// Keyed by `${sourceHash}|${themeSignature}` so a theme switch re-renders rather
|
|
14
|
+
// than serving a stale, differently-colored diagram.
|
|
15
|
+
const cache = new Map<string, string | null>();
|
|
4
16
|
|
|
5
17
|
let onRenderNeeded: (() => void) | null = null;
|
|
6
18
|
|
|
7
|
-
/**
|
|
8
|
-
* Set callback to trigger TUI re-render when mermaid ASCII renders become available.
|
|
9
|
-
*/
|
|
19
|
+
/** Set callback to trigger a TUI re-render when new mermaid renders become available. */
|
|
10
20
|
export function setMermaidRenderCallback(callback: (() => void) | null): void {
|
|
11
21
|
onRenderNeeded = callback;
|
|
12
22
|
}
|
|
13
23
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
function colorModeFor(theme: Theme, override?: MermaidColorMode): MermaidColorMode {
|
|
25
|
+
if (override) return override;
|
|
26
|
+
return pickColorMode({ noColor: Boolean(Bun.env.NO_COLOR), trueColor: theme.getColorMode() === "truecolor" });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function paletteSignature(theme: Theme): string {
|
|
30
|
+
return `${JSON.stringify(buildMermaidAsciiTheme(theme))}|${buildNodeAccents(theme).join(",")}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Stable signature identifying a theme's mermaid appearance — used for cache keying. */
|
|
34
|
+
export function mermaidThemeSignature(theme: Theme): string {
|
|
35
|
+
return `${colorModeFor(theme)}:${paletteSignature(theme)}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RenderMermaidThemedOptions {
|
|
39
|
+
/** Force a color mode (otherwise derived from the theme + NO_COLOR). */
|
|
40
|
+
colorMode?: MermaidColorMode;
|
|
41
|
+
/** Extra layout options (padding, useAscii, …). */
|
|
42
|
+
render?: MermaidAsciiRenderOptions;
|
|
20
43
|
}
|
|
21
44
|
|
|
22
45
|
/**
|
|
23
|
-
* Render
|
|
24
|
-
*
|
|
46
|
+
* Render mermaid source to a themed, per-node-tinted ASCII/Unicode string.
|
|
47
|
+
* Applies the theme's per-role palette, then the best-effort node-tint pass.
|
|
48
|
+
* Result is memoized per (source, theme, colorMode). Returns null on parse failure.
|
|
25
49
|
*/
|
|
26
|
-
export function
|
|
50
|
+
export function renderMermaidThemed(source: string, theme: Theme, opts?: RenderMermaidThemedOptions): string | null {
|
|
51
|
+
const mode = colorModeFor(theme, opts?.colorMode);
|
|
52
|
+
const key = `${Bun.hash(source.trim())}|${mode}:${paletteSignature(theme)}`;
|
|
53
|
+
const cached = cache.get(key);
|
|
54
|
+
if (cached !== undefined) return cached;
|
|
55
|
+
|
|
56
|
+
const asciiTheme = buildMermaidAsciiTheme(theme);
|
|
57
|
+
const colored = renderMermaidAsciiSafe(source, { ...opts?.render, colorMode: mode, theme: asciiTheme });
|
|
58
|
+
if (colored == null) {
|
|
59
|
+
cache.set(key, null);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const result = mode === "none" ? colored : tintMermaidNodes(colored.split("\n"), buildNodeAccents(theme)).join("\n");
|
|
63
|
+
cache.set(key, result);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Get a pre-rendered mermaid diagram by source hash + theme signature, or null. */
|
|
68
|
+
export function getMermaidAscii(hash: bigint | number, themeSig: string): string | null {
|
|
69
|
+
return cache.get(`${hash}|${themeSig}`) ?? null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Render and cache every mermaid block in `markdown` for the given theme. */
|
|
73
|
+
export function prerenderMermaid(markdown: string, theme: Theme): void {
|
|
27
74
|
const blocks = extractMermaidBlocks(markdown);
|
|
28
75
|
if (blocks.length === 0) return;
|
|
29
76
|
|
|
77
|
+
const sig = mermaidThemeSignature(theme);
|
|
30
78
|
let hasNew = false;
|
|
31
|
-
|
|
32
79
|
for (const { source, hash } of blocks) {
|
|
33
|
-
if (cache.has(hash)) continue;
|
|
34
|
-
|
|
35
|
-
const ascii = renderMermaidAsciiSafe(source);
|
|
36
|
-
if (ascii) {
|
|
37
|
-
cache.set(hash, ascii);
|
|
38
|
-
hasNew = true;
|
|
39
|
-
} else {
|
|
40
|
-
cache.set(hash, null);
|
|
41
|
-
}
|
|
80
|
+
if (cache.has(`${hash}|${sig}`)) continue;
|
|
81
|
+
if (renderMermaidThemed(source, theme) != null) hasNew = true;
|
|
42
82
|
}
|
|
43
83
|
|
|
44
84
|
if (hasNew && onRenderNeeded) {
|
|
@@ -52,17 +92,13 @@ export function prerenderMermaid(markdown: string): void {
|
|
|
52
92
|
}
|
|
53
93
|
}
|
|
54
94
|
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
*/
|
|
58
|
-
export function hasPendingMermaid(markdown: string): boolean {
|
|
95
|
+
/** Check whether `markdown` has mermaid blocks not yet cached for this theme. */
|
|
96
|
+
export function hasPendingMermaid(markdown: string, themeSig: string): boolean {
|
|
59
97
|
const blocks = extractMermaidBlocks(markdown);
|
|
60
|
-
return blocks.some(({ hash }) => !cache.has(hash));
|
|
98
|
+
return blocks.some(({ hash }) => !cache.has(`${hash}|${themeSig}`));
|
|
61
99
|
}
|
|
62
100
|
|
|
63
|
-
/**
|
|
64
|
-
* Clear the mermaid cache.
|
|
65
|
-
*/
|
|
101
|
+
/** Clear the mermaid cache. */
|
|
66
102
|
export function clearMermaidCache(): void {
|
|
67
103
|
cache.clear();
|
|
68
104
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map the active xcsh UI theme onto a mermaid `AsciiTheme` (per-role hues) and a
|
|
3
|
+
* cycling list of node-accent ANSI escapes for the per-node tint pass. Reading the
|
|
4
|
+
* theme keeps diagrams on-brand and dark/light adaptive.
|
|
5
|
+
*/
|
|
6
|
+
import type { MermaidAsciiRenderOptions } from "@f5xc-salesdemos/pi-utils";
|
|
7
|
+
import type { Theme } from "./theme";
|
|
8
|
+
|
|
9
|
+
type AsciiTheme = NonNullable<MermaidAsciiRenderOptions["theme"]>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Per-role color palette: red node borders, gold labels, blue edges, green
|
|
13
|
+
* arrowheads. These show through wherever the per-node tint pass doesn't apply
|
|
14
|
+
* (edges, arrows, subgraph frames, and as the fallback when tinting is skipped).
|
|
15
|
+
*/
|
|
16
|
+
export function buildMermaidAsciiTheme(theme: Theme): AsciiTheme {
|
|
17
|
+
return {
|
|
18
|
+
fg: theme.getFgHex("mdHeading", "#febc38"), // node/edge labels
|
|
19
|
+
border: theme.getFgHex("accent", "#ca260a"), // node + subgraph borders
|
|
20
|
+
line: theme.getFgHex("mdLink", "#0088fa"), // edge lines
|
|
21
|
+
arrow: theme.getFgHex("success", "#00ff88"), // arrowheads
|
|
22
|
+
corner: theme.getFgHex("mdLink", "#0088fa"),
|
|
23
|
+
junction: theme.getFgHex("accent", "#ca260a"),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* ANSI foreground escapes cycled across detected node boxes so each node reads as
|
|
29
|
+
* a distinct hue. Drawn from vivid, well-separated theme roles.
|
|
30
|
+
*/
|
|
31
|
+
export function buildNodeAccents(theme: Theme): string[] {
|
|
32
|
+
const roles = ["chromeAccent", "success", "warning", "mdLink", "accent"] as const;
|
|
33
|
+
const seen = new Set<string>();
|
|
34
|
+
const accents: string[] = [];
|
|
35
|
+
for (const role of roles) {
|
|
36
|
+
const ansi = theme.getFgAnsi(role);
|
|
37
|
+
if (!seen.has(ansi)) {
|
|
38
|
+
seen.add(ansi);
|
|
39
|
+
accents.push(ansi);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return accents;
|
|
43
|
+
}
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
|
16
16
|
import chalk from "chalk";
|
|
17
17
|
// Embed theme JSON files at build time
|
|
18
18
|
import { defaultThemes } from "./defaults";
|
|
19
|
-
import { getMermaidAscii } from "./mermaid-cache";
|
|
19
|
+
import { getMermaidAscii, mermaidThemeSignature } from "./mermaid-cache";
|
|
20
20
|
|
|
21
21
|
export { getLanguageFromPath } from "../../utils/lang-from-path";
|
|
22
22
|
|
|
@@ -1316,6 +1316,7 @@ const langMap: Record<string, SymbolKey> = {
|
|
|
1316
1316
|
|
|
1317
1317
|
export class Theme {
|
|
1318
1318
|
#fgColors: Record<ThemeColor, string>;
|
|
1319
|
+
#fgHex: Partial<Record<ThemeColor, string>>;
|
|
1319
1320
|
#bgColors: Record<ThemeBg, string>;
|
|
1320
1321
|
#symbols: SymbolMap;
|
|
1321
1322
|
|
|
@@ -1327,8 +1328,12 @@ export class Theme {
|
|
|
1327
1328
|
symbolOverrides: Partial<Record<SymbolKey, string>>,
|
|
1328
1329
|
) {
|
|
1329
1330
|
this.#fgColors = {} as Record<ThemeColor, string>;
|
|
1331
|
+
this.#fgHex = {};
|
|
1330
1332
|
for (const [key, value] of Object.entries(fgColors) as [ThemeColor, string | number][]) {
|
|
1331
1333
|
this.#fgColors[key] = fgAnsi(value, mode);
|
|
1334
|
+
// Retain the raw hex (when the role resolves to one) for consumers that need
|
|
1335
|
+
// a color value rather than an ANSI escape — e.g. the mermaid AsciiTheme.
|
|
1336
|
+
if (typeof value === "string" && value.startsWith("#")) this.#fgHex[key] = value;
|
|
1332
1337
|
}
|
|
1333
1338
|
// gutterError remains optional — neither shipped theme carries it
|
|
1334
1339
|
this.#fgColors.gutterError ??= this.#fgColors.error;
|
|
@@ -1386,6 +1391,15 @@ export class Theme {
|
|
|
1386
1391
|
return ansi;
|
|
1387
1392
|
}
|
|
1388
1393
|
|
|
1394
|
+
/**
|
|
1395
|
+
* Raw hex (e.g. "#ca260a") for a color role, for consumers that need a color
|
|
1396
|
+
* value rather than an ANSI escape (e.g. the mermaid AsciiTheme). Roles that
|
|
1397
|
+
* resolve to "" (default fg) or an ansi256 index yield `fallback`.
|
|
1398
|
+
*/
|
|
1399
|
+
getFgHex(color: ThemeColor, fallback = "#cccccc"): string {
|
|
1400
|
+
return this.#fgHex[color] ?? fallback;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1389
1403
|
/** Convert a ThemeColor's fg ANSI code to a bg ANSI code (swap \x1b[38; → \x1b[48;). */
|
|
1390
1404
|
fgColorAsBg(color: ThemeColor): string {
|
|
1391
1405
|
return this.getFgAnsi(color).replace("\x1b[38;", "\x1b[48;");
|
|
@@ -2495,7 +2509,7 @@ export function getMarkdownTheme(): MarkdownTheme {
|
|
|
2495
2509
|
underline: (text: string) => theme.underline(text),
|
|
2496
2510
|
strikethrough: (text: string) => chalk.strikethrough(text),
|
|
2497
2511
|
symbols: getSymbolTheme(),
|
|
2498
|
-
getMermaidAscii,
|
|
2512
|
+
getMermaidAscii: (hash: bigint | number) => getMermaidAscii(hash, mermaidThemeSignature(theme)),
|
|
2499
2513
|
highlightCode: (code: string, lang?: string): string[] => {
|
|
2500
2514
|
const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
|
|
2501
2515
|
try {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
/** TUI renderer for the render_mermaid tool — bordered
|
|
1
|
+
/** TUI renderer for the render_mermaid tool — bordered, themed, colorized diagram output. */
|
|
2
2
|
import type { Component } from "@f5xc-salesdemos/pi-tui";
|
|
3
3
|
import { Text } from "@f5xc-salesdemos/pi-tui";
|
|
4
|
+
import { detectDiagramType, type MermaidDiagramType } from "@f5xc-salesdemos/pi-utils";
|
|
4
5
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
6
|
+
import { renderMermaidThemed } from "../modes/theme/mermaid-cache";
|
|
5
7
|
import type { Theme } from "../modes/theme/theme";
|
|
6
8
|
import { CachedOutputBlock, F5_TOOL_BORDER_COLOR, renderStatusLine } from "../tui";
|
|
7
9
|
import type { RenderMermaidToolDetails } from "./render-mermaid";
|
|
@@ -10,6 +12,26 @@ import { addSection, formatErrorMessage, replaceTabs } from "./render-utils";
|
|
|
10
12
|
const TOOL_TITLE = "Mermaid";
|
|
11
13
|
const MAX_DIAGRAM_LINES = 40;
|
|
12
14
|
|
|
15
|
+
/** Human-friendly caption for the diagram-type badge in the block header. */
|
|
16
|
+
function diagramTypeLabel(type: MermaidDiagramType): string {
|
|
17
|
+
switch (type) {
|
|
18
|
+
case "flowchart":
|
|
19
|
+
return "flowchart";
|
|
20
|
+
case "sequence":
|
|
21
|
+
return "sequence diagram";
|
|
22
|
+
case "class":
|
|
23
|
+
return "class diagram";
|
|
24
|
+
case "er":
|
|
25
|
+
return "ER diagram";
|
|
26
|
+
case "state":
|
|
27
|
+
return "state diagram";
|
|
28
|
+
case "xychart":
|
|
29
|
+
return "xychart";
|
|
30
|
+
default:
|
|
31
|
+
return "diagram";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
13
35
|
type MermaidRenderArgs = {
|
|
14
36
|
mermaid?: string;
|
|
15
37
|
};
|
|
@@ -31,7 +53,7 @@ export const mermaidRenderer = {
|
|
|
31
53
|
},
|
|
32
54
|
options: RenderResultOptions,
|
|
33
55
|
uiTheme: Theme,
|
|
34
|
-
|
|
56
|
+
args?: MermaidRenderArgs,
|
|
35
57
|
): Component {
|
|
36
58
|
const isError = result.isError === true;
|
|
37
59
|
|
|
@@ -44,11 +66,18 @@ export const mermaidRenderer = {
|
|
|
44
66
|
const meta: string[] = [];
|
|
45
67
|
const rawText = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
46
68
|
|
|
47
|
-
// Strip artifact reference from
|
|
69
|
+
// Strip artifact reference from the plain result text (fallback path).
|
|
48
70
|
const artifactIdx = rawText.indexOf("\n\nSaved artifact:");
|
|
49
|
-
const
|
|
71
|
+
const plainText = artifactIdx >= 0 ? rawText.slice(0, artifactIdx) : rawText;
|
|
72
|
+
|
|
73
|
+
// Prefer re-rendering from the original source: this yields the themed,
|
|
74
|
+
// per-role-colored, node-tinted diagram. Fall back to the plain result text.
|
|
75
|
+
const source = args?.mermaid?.trim();
|
|
76
|
+
const diagramText = (source ? renderMermaidThemed(source, uiTheme) : null) ?? plainText;
|
|
77
|
+
const caption = source ? diagramTypeLabel(detectDiagramType(source)) : "diagram";
|
|
50
78
|
|
|
51
|
-
|
|
79
|
+
// Keep the diagram's own colors — do NOT recolor each line a flat tool color.
|
|
80
|
+
const diagramLines = diagramText.split("\n").map(line => replaceTabs(line));
|
|
52
81
|
addSection(sections, "Diagram", diagramLines, uiTheme, MAX_DIAGRAM_LINES);
|
|
53
82
|
|
|
54
83
|
if (result.details?.artifactId) {
|
|
@@ -59,7 +88,7 @@ export const mermaidRenderer = {
|
|
|
59
88
|
{
|
|
60
89
|
title: TOOL_TITLE,
|
|
61
90
|
titleColor: "dim",
|
|
62
|
-
description: uiTheme.fg("muted",
|
|
91
|
+
description: uiTheme.fg("muted", caption),
|
|
63
92
|
meta: meta.length > 0 ? meta : undefined,
|
|
64
93
|
},
|
|
65
94
|
uiTheme,
|