mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(coding-agent): render Mermaid diagrams (#7624)
This commit is contained in:
parent
46b53b995b
commit
66534fbdc7
12 changed files with 287 additions and 2 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -3284,6 +3284,15 @@
|
|||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/grok-mermaid": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/grok-mermaid/-/grok-mermaid-0.2.2.tgz",
|
||||
"integrity": "sha512-XcJEP5dDC8liHBh52mlLjU18fNvu1ckFsu0QpIG3+APZ270fsj9wxpiA6cOURmbUEuoMVgjbC2+UYgTdCqqgzA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
|
|
@ -5535,6 +5544,7 @@
|
|||
"cross-spawn": "7.0.6",
|
||||
"diff": "8.0.4",
|
||||
"glob": "13.0.6",
|
||||
"grok-mermaid": "0.2.2",
|
||||
"highlight.js": "10.7.3",
|
||||
"hosted-git-info": "9.0.3",
|
||||
"ignore": "7.0.5",
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@
|
|||
- Added a draggable transcript scrollbar to fullscreen mode with configurable `auto`, `always`, and `hidden` modes through `/settings`; `always` reserves the rightmost column.
|
||||
- Added page scrolling and marked-message navigation shortcuts to fullscreen mode.
|
||||
- Added an optional `scrollbarThumb` theme color for fullscreen scrollbar thumbs, falling back to `selectedBg`.
|
||||
- Added configurable themed Unicode rendering for supported Mermaid diagrams in interactive messages, including optional rendering while streaming.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
|||
|
|
@ -228,6 +228,7 @@ When multiple sources specify a session directory, precedence is `--session-dir`
|
|||
| Setting | Type | Default | Description |
|
||||
|---------|------|---------|-------------|
|
||||
| `markdown.codeBlockIndent` | string | `" "` | Indentation for code blocks |
|
||||
| `markdown.mermaid` | string | `"streaming"` | Mermaid rendering mode: `"off"`, `"final"`, or `"streaming"` |
|
||||
|
||||
### Resources
|
||||
|
||||
|
|
|
|||
10
packages/coding-agent/install-lock/package-lock.json
generated
10
packages/coding-agent/install-lock/package-lock.json
generated
|
|
@ -514,6 +514,7 @@
|
|||
"cross-spawn": "7.0.6",
|
||||
"diff": "8.0.4",
|
||||
"glob": "13.0.6",
|
||||
"grok-mermaid": "0.2.2",
|
||||
"highlight.js": "10.7.3",
|
||||
"hosted-git-info": "9.0.3",
|
||||
"ignore": "7.0.5",
|
||||
|
|
@ -1311,6 +1312,15 @@
|
|||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/grok-mermaid": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/grok-mermaid/-/grok-mermaid-0.2.2.tgz",
|
||||
"integrity": "sha512-XcJEP5dDC8liHBh52mlLjU18fNvu1ckFsu0QpIG3+APZ270fsj9wxpiA6cOURmbUEuoMVgjbC2+UYgTdCqqgzA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight.js": {
|
||||
"version": "10.7.3",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
|
||||
|
|
|
|||
10
packages/coding-agent/npm-shrinkwrap.json
generated
10
packages/coding-agent/npm-shrinkwrap.json
generated
|
|
@ -19,6 +19,7 @@
|
|||
"cross-spawn": "7.0.6",
|
||||
"diff": "8.0.4",
|
||||
"glob": "13.0.6",
|
||||
"grok-mermaid": "0.2.2",
|
||||
"highlight.js": "10.7.3",
|
||||
"hosted-git-info": "9.0.3",
|
||||
"ignore": "7.0.5",
|
||||
|
|
@ -1301,6 +1302,15 @@
|
|||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/grok-mermaid": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/grok-mermaid/-/grok-mermaid-0.2.2.tgz",
|
||||
"integrity": "sha512-XcJEP5dDC8liHBh52mlLjU18fNvu1ckFsu0QpIG3+APZ270fsj9wxpiA6cOURmbUEuoMVgjbC2+UYgTdCqqgzA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight.js": {
|
||||
"version": "10.7.3",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@
|
|||
"cross-spawn": "7.0.6",
|
||||
"diff": "8.0.4",
|
||||
"glob": "13.0.6",
|
||||
"grok-mermaid": "0.2.2",
|
||||
"highlight.js": "10.7.3",
|
||||
"hosted-git-info": "9.0.3",
|
||||
"ignore": "7.0.5",
|
||||
|
|
|
|||
|
|
@ -54,8 +54,11 @@ export interface ThinkingBudgetsSettings {
|
|||
high?: number;
|
||||
}
|
||||
|
||||
export type MermaidRenderingMode = "off" | "final" | "streaming";
|
||||
|
||||
export interface MarkdownSettings {
|
||||
codeBlockIndent?: string; // default: " "
|
||||
mermaid?: MermaidRenderingMode; // default: "streaming"
|
||||
}
|
||||
|
||||
export interface WarningSettings {
|
||||
|
|
@ -1245,6 +1248,18 @@ export class SettingsManager {
|
|||
return this.settings.markdown?.codeBlockIndent ?? " ";
|
||||
}
|
||||
|
||||
getMermaidRenderingMode(): MermaidRenderingMode {
|
||||
const mode = this.settings.markdown?.mermaid;
|
||||
return mode === "off" || mode === "final" ? mode : "streaming";
|
||||
}
|
||||
|
||||
setMermaidRenderingMode(mode: MermaidRenderingMode): void {
|
||||
this.globalSettings.markdown ??= {};
|
||||
this.globalSettings.markdown.mermaid = mode;
|
||||
this.markModified("markdown", "mermaid");
|
||||
this.save();
|
||||
}
|
||||
|
||||
getWarnings(): WarningSettings {
|
||||
return { ...(this.settings.warnings ?? {}) };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
import { Marked, type Token } from "@earendil-works/pi-tui";
|
||||
import { type MermaidArt, render, type Span } from "grok-mermaid";
|
||||
import type { MarkdownTransformer } from "../../../core/extensions/types.ts";
|
||||
import type { MermaidRenderingMode } from "../../../core/settings-manager.ts";
|
||||
import type { Theme } from "../theme/theme.ts";
|
||||
|
||||
const markdownParser = new Marked();
|
||||
|
||||
interface MermaidTransformerOptions {
|
||||
getMode: () => MermaidRenderingMode;
|
||||
theme?: Theme;
|
||||
}
|
||||
|
||||
function isMermaid(token: Token): token is Token & { type: "code"; text: string; lang?: string } {
|
||||
return token.type === "code" && token.lang?.trim().split(/\s+/, 1)[0]?.toLowerCase() === "mermaid";
|
||||
}
|
||||
|
||||
function codeSpan(line: string): string {
|
||||
// Encode each diagram row as inline code (` ... `) so Markdown preserves its spacing and
|
||||
// box-drawing characters. Use a non-breaking space for blank rows because an
|
||||
// empty code span has no visible height.
|
||||
const content = line || "\u00a0";
|
||||
// CommonMark code spans use matching backtick delimiters, so choose one
|
||||
// longer than any backtick run in the content (``hel`lo`` -> <code>hel`lo</code>).
|
||||
// If the content starts or ends with a backtick, separating it from the
|
||||
// delimiter with a space keeps that backtick as content; CommonMark removes
|
||||
// the padding when rendering (`` `edge` `` -> <code>`edge`</code>).
|
||||
// Mermaid labels can preserve backticks, for example:
|
||||
// `┌──────────────┐ ┌──────────────┐`
|
||||
// ```│ plain ` tick ├───▶│ two `` ticks │```
|
||||
// `└──────────────┘ └──────────────┘`
|
||||
const longestBacktickRun = Math.max(0, ...Array.from(content.matchAll(/`+/g), (match) => match[0].length));
|
||||
const fence = "`".repeat(longestBacktickRun + 1);
|
||||
const padding = content.startsWith("`") || content.endsWith("`") ? " " : "";
|
||||
return `${fence}${padding}${content}${padding}${fence}`;
|
||||
}
|
||||
|
||||
function styleSpan(span: Span, theme: Theme): string {
|
||||
switch (span.cls) {
|
||||
case "border":
|
||||
return theme.fg("borderMuted", span.text);
|
||||
case "text":
|
||||
return theme.fg("text", span.text);
|
||||
case "edge":
|
||||
return theme.fg("accent", span.text);
|
||||
case "edgeLabel":
|
||||
return theme.fg("muted", span.text);
|
||||
case "title":
|
||||
return theme.fg("accent", theme.bold(span.text));
|
||||
case "none":
|
||||
return span.text;
|
||||
}
|
||||
}
|
||||
|
||||
function themedLines(art: MermaidArt, theme: Theme): string[] {
|
||||
return art.styled.map((row) => row.map((span) => styleSpan(span, theme)).join(""));
|
||||
}
|
||||
|
||||
/** Create a transformer that replaces top-level Mermaid code blocks with Unicode terminal diagrams. */
|
||||
export function createMermaidMarkdownTransformer(options: MermaidTransformerOptions): MarkdownTransformer {
|
||||
return (markdown, context) => {
|
||||
const mode = options.getMode();
|
||||
if (
|
||||
mode === "off" ||
|
||||
context.messageType === "assistant-thinking" ||
|
||||
(context.isStreaming && mode !== "streaming")
|
||||
) {
|
||||
return markdown;
|
||||
}
|
||||
|
||||
return markdownParser
|
||||
.lexer(markdown)
|
||||
.map((token) => {
|
||||
if (!isMermaid(token)) return token.raw;
|
||||
const art = render(token.text);
|
||||
if (!art || art.width > context.availableWidth) return token.raw;
|
||||
if (!context.isStreaming && art.warnings.length > 0) {
|
||||
const suffix = art.warnings.length > 1 ? ` (+${art.warnings.length - 1} more)` : "";
|
||||
const warning = `Mermaid diagram not rendered: ${art.warnings[0]}${suffix}`;
|
||||
const styledWarning = options.theme ? options.theme.fg("warning", warning) : warning;
|
||||
return `${token.raw}\n${codeSpan(styledWarning)} \n`;
|
||||
}
|
||||
const lines = options.theme ? themedLines(art, options.theme) : art.plain;
|
||||
// Markdown hard breaks keep every diagram row on its own line.
|
||||
return `${lines.map(codeSpan).join(" \n")}\n`;
|
||||
})
|
||||
.join("");
|
||||
};
|
||||
}
|
||||
|
|
@ -14,7 +14,12 @@ import {
|
|||
Text,
|
||||
} from "@earendil-works/pi-tui";
|
||||
import { formatHttpIdleTimeoutMs, HTTP_IDLE_TIMEOUT_CHOICES } from "../../../core/http-dispatcher.ts";
|
||||
import type { DefaultProjectTrust, UiMode, WarningSettings } from "../../../core/settings-manager.ts";
|
||||
import type {
|
||||
DefaultProjectTrust,
|
||||
MermaidRenderingMode,
|
||||
UiMode,
|
||||
WarningSettings,
|
||||
} from "../../../core/settings-manager.ts";
|
||||
import {
|
||||
getSelectListTheme,
|
||||
getSettingsListTheme,
|
||||
|
|
@ -67,6 +72,7 @@ export interface SettingsConfig {
|
|||
terminalTheme: TerminalTheme;
|
||||
availableThemes: string[];
|
||||
hideThinkingBlock: boolean;
|
||||
mermaidRenderingMode: MermaidRenderingMode;
|
||||
showCacheMissNotices: boolean;
|
||||
collapseChangelog: boolean;
|
||||
enableInstallTelemetry: boolean;
|
||||
|
|
@ -100,6 +106,7 @@ export interface SettingsCallbacks {
|
|||
onThemeChange: (theme: string) => void;
|
||||
onThemePreview?: (theme: string) => void;
|
||||
onHideThinkingBlockChange: (hidden: boolean) => void;
|
||||
onMermaidRenderingModeChange: (mode: MermaidRenderingMode) => void;
|
||||
onShowCacheMissNoticesChange: (shown: boolean) => void;
|
||||
onCollapseChangelogChange: (collapsed: boolean) => void;
|
||||
onEnableInstallTelemetryChange: (enabled: boolean) => void;
|
||||
|
|
@ -529,6 +536,13 @@ export class SettingsSelectorComponent extends Container {
|
|||
currentValue: config.hideThinkingBlock ? "true" : "false",
|
||||
values: ["true", "false"],
|
||||
},
|
||||
{
|
||||
id: "mermaid-rendering",
|
||||
label: "Mermaid diagrams",
|
||||
description: "Render Mermaid code blocks as Unicode diagrams",
|
||||
currentValue: config.mermaidRenderingMode,
|
||||
values: ["off", "final", "streaming"],
|
||||
},
|
||||
{
|
||||
id: "cache-miss-notices",
|
||||
label: "Cache miss notices",
|
||||
|
|
@ -793,6 +807,9 @@ export class SettingsSelectorComponent extends Container {
|
|||
case "hide-thinking":
|
||||
callbacks.onHideThinkingBlockChange(newValue === "true");
|
||||
break;
|
||||
case "mermaid-rendering":
|
||||
callbacks.onMermaidRenderingModeChange(newValue as MermaidRenderingMode);
|
||||
break;
|
||||
case "cache-miss-notices":
|
||||
callbacks.onShowCacheMissNoticesChange(newValue === "true");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ import { ExtensionSelectorComponent } from "./components/extension-selector.ts";
|
|||
import { FooterComponent, formatTokens } from "./components/footer.ts";
|
||||
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.ts";
|
||||
import { LoginDialogComponent } from "./components/login-dialog.ts";
|
||||
import { createMermaidMarkdownTransformer } from "./components/mermaid.ts";
|
||||
import { ModelSelectorComponent } from "./components/model-selector.ts";
|
||||
import {
|
||||
type AuthSelectorProvider,
|
||||
|
|
@ -431,6 +432,10 @@ export class InteractiveMode {
|
|||
// Thinking block visibility state
|
||||
private hideThinkingBlock = false;
|
||||
private outputPad = 1;
|
||||
private readonly mermaidMarkdownTransformer: MarkdownTransformer = createMermaidMarkdownTransformer({
|
||||
getMode: () => this.settingsManager.getMermaidRenderingMode(),
|
||||
theme,
|
||||
});
|
||||
|
||||
// Skill commands: command name -> skill file path
|
||||
private skillCommands = new Map<string, string>();
|
||||
|
|
@ -1951,7 +1956,7 @@ export class InteractiveMode {
|
|||
}
|
||||
|
||||
private getMarkdownTransformers(): MarkdownTransformer[] {
|
||||
return this.session.extensionRunner.getMarkdownTransformers();
|
||||
return [this.mermaidMarkdownTransformer, ...this.session.extensionRunner.getMarkdownTransformers()];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -4358,6 +4363,7 @@ export class InteractiveMode {
|
|||
terminalTheme: this.themeController.getTerminalTheme(),
|
||||
availableThemes: getAvailableThemes(),
|
||||
hideThinkingBlock: this.hideThinkingBlock,
|
||||
mermaidRenderingMode: this.settingsManager.getMermaidRenderingMode(),
|
||||
collapseChangelog: this.settingsManager.getCollapseChangelog(),
|
||||
enableInstallTelemetry: this.settingsManager.getEnableInstallTelemetry(),
|
||||
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
||||
|
|
@ -4442,6 +4448,11 @@ export class InteractiveMode {
|
|||
this.chatContainer.clear();
|
||||
this.rebuildChatFromMessages();
|
||||
},
|
||||
onMermaidRenderingModeChange: (mode) => {
|
||||
this.settingsManager.setMermaidRenderingMode(mode);
|
||||
this.chatContainer.invalidate();
|
||||
this.ui.requestRender();
|
||||
},
|
||||
onShowCacheMissNoticesChange: (shown) => {
|
||||
this.settingsManager.setShowCacheMissNotices(shown);
|
||||
this.rebuildChatFromMessages();
|
||||
|
|
|
|||
99
packages/coding-agent/test/mermaid.test.ts
Normal file
99
packages/coding-agent/test/mermaid.test.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import type { MarkdownTransformContext } from "../src/core/extensions/types.ts";
|
||||
import type { MermaidRenderingMode } from "../src/core/settings-manager.ts";
|
||||
import { createMermaidMarkdownTransformer } from "../src/modes/interactive/components/mermaid.ts";
|
||||
import type { Theme } from "../src/modes/interactive/theme/theme.ts";
|
||||
|
||||
interface TransformOptions {
|
||||
maxWidth?: number;
|
||||
isStreaming?: boolean;
|
||||
messageType?: MarkdownTransformContext["messageType"];
|
||||
mode?: MermaidRenderingMode;
|
||||
theme?: Theme;
|
||||
}
|
||||
|
||||
function transformMermaid(markdown: string, options: TransformOptions = {}): string {
|
||||
const transformer = createMermaidMarkdownTransformer({
|
||||
getMode: () => options.mode ?? "streaming",
|
||||
theme: options.theme,
|
||||
});
|
||||
return transformer(markdown, {
|
||||
availableWidth: options.maxWidth ?? 100,
|
||||
isStreaming: options.isStreaming ?? false,
|
||||
messageType: options.messageType ?? "assistant",
|
||||
});
|
||||
}
|
||||
|
||||
describe("Mermaid rendering", () => {
|
||||
it("replaces Mermaid code blocks with Unicode diagrams", () => {
|
||||
const markdown = "Before\n\n```mermaid\nflowchart LR\n A[Start] --> B[Done]\n```\nAfter";
|
||||
const rendered = transformMermaid(markdown);
|
||||
|
||||
expect(rendered).toContain("Before");
|
||||
expect(rendered).toContain("┌───────┐");
|
||||
expect(rendered).toContain("│ Start ├───▶│ Done │");
|
||||
expect(rendered).toContain("└───────┘ └──────┘`\nAfter");
|
||||
expect(rendered).not.toContain("```mermaid");
|
||||
expect(rendered).toContain("After");
|
||||
});
|
||||
|
||||
it("leaves unsupported and oversized diagrams unchanged", () => {
|
||||
const unsupported = '```mermaid\npie\n title Pets\n "Dogs" : 4\n```';
|
||||
const oversized = "```mermaid\nflowchart LR\n A[Start] --> B[Done]\n```";
|
||||
|
||||
expect(transformMermaid(unsupported)).toBe(unsupported);
|
||||
expect(transformMermaid(oversized, { maxWidth: 10 })).toBe(oversized);
|
||||
});
|
||||
|
||||
it("maps semantic spans through the Pi theme", () => {
|
||||
const theme = {
|
||||
fg: (color: string, text: string) => `<${color}>${text}</${color}>`,
|
||||
bold: (text: string) => `<bold>${text}</bold>`,
|
||||
} as Theme;
|
||||
const rendered = transformMermaid("```mermaid\nflowchart LR\n A --> B\n```", { theme });
|
||||
|
||||
expect(rendered).toContain("<borderMuted>");
|
||||
expect(rendered).toContain("<accent>");
|
||||
});
|
||||
|
||||
it("renders incomplete Mermaid blocks during streaming", () => {
|
||||
const partialMarkdown = "```mermaid\nflowchart LR\n A --> B";
|
||||
|
||||
expect(transformMermaid(partialMarkdown, { isStreaming: true })).toContain("───▶");
|
||||
});
|
||||
|
||||
it("falls back to the code block with a warning after streaming", () => {
|
||||
const markdown = "```mermaid\nflowchart LR\n A[Foo]:::highlight --> B[Bar]\n```";
|
||||
const final = transformMermaid(markdown);
|
||||
const followedByText = transformMermaid(`${markdown}\nFollowing text`);
|
||||
const streaming = transformMermaid(markdown, { isStreaming: true });
|
||||
|
||||
expect(final).toContain(markdown);
|
||||
expect(final).toContain("```\n`Mermaid diagram not rendered");
|
||||
expect(final).toContain('dropped, expected a link: ":::highlight --> B[Bar]"');
|
||||
expect(final).not.toContain("more)");
|
||||
expect(followedByText).toContain(" \nFollowing text");
|
||||
expect(streaming).not.toContain("Mermaid diagram not rendered");
|
||||
expect(streaming).not.toContain("```mermaid");
|
||||
expect(streaming).toContain("│ Foo │");
|
||||
});
|
||||
|
||||
it("summarizes additional partial-render warnings", () => {
|
||||
const markdown = "```mermaid\nflowchart LR\n A[Foo]:::highlight --> B[Bar]\n C[Baz]:::other --> D[Qux]\n```";
|
||||
const rendered = transformMermaid(markdown);
|
||||
|
||||
expect(rendered).toContain(markdown);
|
||||
expect(rendered).toContain('dropped, expected a link: ":::highlight --> B[Bar]"');
|
||||
expect(rendered).toContain("(+1 more)");
|
||||
expect(rendered).not.toContain('dropped, expected a link: ":::other --> D[Qux]"');
|
||||
});
|
||||
|
||||
it("respects rendering modes and skips thinking blocks", () => {
|
||||
const markdown = "```mermaid\nflowchart LR\n A --> B\n```";
|
||||
|
||||
expect(transformMermaid(markdown, { mode: "off" })).toBe(markdown);
|
||||
expect(transformMermaid(markdown, { mode: "final", isStreaming: true })).toBe(markdown);
|
||||
expect(transformMermaid(markdown, { mode: "final" })).not.toContain("```mermaid");
|
||||
expect(transformMermaid(markdown, { messageType: "assistant-thinking" })).toBe(markdown);
|
||||
});
|
||||
});
|
||||
|
|
@ -455,6 +455,27 @@ describe("SettingsManager", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("markdown.mermaid", () => {
|
||||
it("defaults to streaming and persists rendering modes", async () => {
|
||||
const manager = SettingsManager.create(projectDir, agentDir);
|
||||
|
||||
expect(manager.getMermaidRenderingMode()).toBe("streaming");
|
||||
|
||||
manager.setMermaidRenderingMode("final");
|
||||
await manager.flush();
|
||||
|
||||
expect(manager.getMermaidRenderingMode()).toBe("final");
|
||||
const savedSettings = JSON.parse(readFileSync(join(agentDir, "settings.json"), "utf-8"));
|
||||
expect(savedSettings.markdown.mermaid).toBe("final");
|
||||
});
|
||||
|
||||
it("falls back to streaming for unsupported values", () => {
|
||||
writeFileSync(join(agentDir, "settings.json"), JSON.stringify({ markdown: { mermaid: "sometimes" } }));
|
||||
|
||||
expect(SettingsManager.create(projectDir, agentDir).getMermaidRenderingMode()).toBe("streaming");
|
||||
});
|
||||
});
|
||||
|
||||
describe("shellCommandPrefix", () => {
|
||||
it("should load shellCommandPrefix from settings", () => {
|
||||
const settingsPath = join(agentDir, "settings.json");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue