/** * @fileoverview Mermaid "task lifecycle + tool calls" diagram generator. * * v0.8.0: replaces the per-task page's old "Execution flow (Mermaid)" * section (which only listed tool calls in a flat sequence) with a * two-level view that nests each tool call under the operation record * that produced it. The structure is: * * Start([Task #X start]) --> opN subgraph --> End([Task #X end]) * โ”‚ * โ”œโ”€โ”€ tool_1["๐Ÿ”ง edit_file / adapter.ts"] * โ””โ”€โ”€ tool_2["๐Ÿ”ง bash / ls -la"] * * Each tool node has a `click tool_1 callback` directive so the browser * navigates to the tool table row when the user clicks the node. The * callback is `window.__toolClick(toolId)` โ€” see `public/app.js` and * `public/mermaid-renderer.js` for the implementation. * * v0.8.2 (fix/mermaid-readable-labels): replaces the raw * `. ` operation-subgraph labels (which leaked technical * details such as `plan_doc: docs/.../plan.md - 485 - worktree: HEAD * a487b25f from main`) with a concise, human-readable label derived * from a `PHASE_LABELS` metadata map. The map is keyed by a canonical * phase id (`init-task`, `setup-worktree`, `plan`, `tdd-implement`, * `code-review`, `cli-e2e-test`, `final-verify`, `merge-to-main`, * `summary-report`, `task-complete`) and each entry has an emoji, a * friendly name, and a short description. `getPhaseLabel(op)` parses * the operation title for a `Phase N: <name>` pattern, looks up the * matching phaseId via `PHASE_ALIASES`, and returns * `<emoji> Phase N: <friendly-name><br/><description>`. Unknown * titles fall back to a sanitized version (technical fields stripped * at the first ` - <field>:` marker). The same logic is mirrored in * `public/app.js` so the client-side re-render stays in sync. * * This module is a pure function. No DOM access. No I/O. Easy to unit * test and to compose with other renderers. */ import type { OperationRecord } from "./cli-tasks-adapter.js"; import type { ToolCallRecord } from "./types.js"; /** * Canonical phase metadata. Each phase is a node in the workflow * (tongagent-rd-pipeline in our reference deployment). The `name` * field is what the user sees in the Mermaid label; the `description` * is a one-line tooltip-like context. We keep this list small and * frozen โ€” adding a phase means a deliberate change to the workflow. */ export interface PhaseMeta { emoji: string; name: string; description: string; } export declare const PHASE_LABELS: Record<string, PhaseMeta>; /** * Aliases that map the various spellings the workflow uses in * `milestoneTitle` to a canonical phaseId. The lookup is performed * case-insensitively, with whitespace + punctuation normalised, so * "Plan" / "plan" / "(Plan)" / "Plan โ€” ..." all resolve the same way. * Extend this map (rather than special-casing in `getPhaseLabel`) when * the workflow grows new phase aliases. */ export declare const PHASE_ALIASES: Record<string, string>; /** * Try to extract "Phase N" + name from an operation title. Returns * `null` if no `Phase N` token is present. Exported for tests so the * parser is independently testable. * * Recognised shapes: * "Phase 3: Plan - plan_doc: ..." * "Phase 3 (Plan) ๅฎŒๆˆ โ€” ..." * "Phase 3 Plan ๅฎŒๆ•ดๅพช็Žฏ ..." * "Phase 3. Plan: ..." * "phase 5 code-review: PASS" (lowercase ok) * "Task #X ๅˆ›ๅปบๆˆๅŠŸ๏ผˆPhase 1 ไปปๅŠก็ฎก็†ๅฎŒๆˆ๏ผ‰" (Phase N anywhere in the title) * * The name is captured up to the first stop-token (`-`, `โ€”`, `:`, * `,`, `ใ€‚`, `ๅฎŒๆˆ`, `pass`, newline), so trailing technical details * like `plan_doc:` or `worktree: HEAD ...` don't leak in. Hyphens * inside the name (e.g. `setup-worktree`, `code-review`) are * preserved. */ export declare function extractPhaseInfo(title: string): { num: string; name: string; } | null; /** * Map a raw phase name to a canonical phaseId via `PHASE_ALIASES`. * Returns `null` if no alias matches. */ export declare function resolvePhaseId(phaseName: string): string | null; /** * v2.0.8 (fix/mermaid-max-text-size): hard cap a label string at * `max` characters (default 60) so Mermaid 10's per-label text-size * budget is never exceeded. The user-reported runtime error * โš ๏ธ Maximum text size in diagram exceeded * aborts the render and falls back to raw source; the trigger was * long operation titles leaking technical fields (plan paths, * commit hashes, worktree pointers) into the phase / path labels. * * Truncation policy: * - text.length <= max โ†’ returned as-is (no ellipsis) * - Latin text โ†’ break at the last space inside the * first `max` chars (preserves word * boundaries, so "alpha beta gamma" * truncates to "alpha beta " + "โ€ฆ") * - CJK / no-space text โ†’ hard-truncate to `max` chars and append * "โ€ฆ" (each CJK code point counts as 1; * we do not pretend they are 2-width) * * The result is wrapped in a `@TracedAs("mermaid.label.truncate")` * span so the host's OTel pipeline can observe the truncation rate. */ export declare const TRUNCATE_FOR_MERMAID_DEFAULT_MAX = 60; export declare function truncateForMermaid(text: string, max?: number): string; export declare function sanitiseTitleForLabel(title: string): string; /** * Build a human-readable Mermaid label for a single operation * record. The output is a single line (with `<br/>` for visual * line-break in the Mermaid render) and contains no Mermaid-hostile * characters. Examples: * * getPhaseLabel({ title: "Phase 3: Plan - plan_doc: ... - worktree: HEAD ..." }) * -> "๐Ÿ“ Phase 3: Plan<br/>brainstorming + writing-plans" * * getPhaseLabel({ title: "Phase 5 code-review: PASS" }) * -> "๐Ÿ” Phase 5: Code Review<br/>BLOCKING/IMPORTANT/MINOR/NIT" * * getPhaseLabel({ title: "TDD ๅฎŒๆ•ดๅพช็Žฏ็ป“ๆŸ" }) * -> "๐Ÿงช TDD ๅพช็Žฏ<br/>RED โ†’ GREEN โ†’ REFACTOR" * * getPhaseLabel({ title: "RED: 3 failing tests added" }) * -> "โš™๏ธ RED: 3 failing tests added" (fallback, no Phase prefix) * * getPhaseLabel({ title: "" }) * -> "โš™๏ธ op" (milestoneType fallback) * * v2.0.8: the final return value is run through `truncateForMermaid` * so the rendered label never blows Mermaid 10's per-label * text-size budget (root cause of the user-reported * "Maximum text size in diagram exceeded" runtime error). */ export declare function getPhaseLabel(op: Pick<OperationRecord, "title" | "milestoneType">): string; /** Minimal shape we need from a tool call. Re-export for convenience. */ export type { ToolCallRecord }; /** What the Mermaid node identifier looks like for a given tool call. */ export declare function toolNodeId(sequence: number): string; /** Mermaid node identifier for an operation record (by sequence). */ export declare function opNodeId(sequence: number): string; /** Mermaid node identifier for the "pre-task" / "post-task" bookends. */ export declare const PRE_TASK_NODE = "pre_task"; export declare const POST_TASK_NODE = "post_task"; export declare const START_NODE = "task_start"; export declare const END_NODE = "task_end"; /** * v2.6.2 (fix/task-show-mermaid-bucket-chunking, Task #3139): the * maximum number of tool-call nodes allowed inside a single pre-task * or post-task bucket subgraph. When the bucket has more than this many * tools, `buildTaskLifecycleDiagram` splits it into multiple sequential * subgraphs (`pre_task_1`, `pre_task_2`, ...). * * Why 50? The original "Maximum text size in diagram exceeded" failure * mode triggered for Task #3139 โ€” a 281-tool session with no operation * data dumped every tool into the pre-task bucket. v2.5.20 (Task #3044) * raised Mermaid's `maxTextSize` to 200,000 chars but never addressed the * root cause: a single bucket accumulating hundreds of tools produces a * single subgraph whose layout cost grows O(Nยฒ) and whose label * readability collapses. Splitting at 50 tools keeps each subgraph well * under the budget (โ‰ˆ 6,000 chars per chunk) and makes the flow explicit * (the legacy render had no internal edges inside a single subgraph). * * The threshold is intentionally conservative โ€” a small bucket * (โ‰ค 50 tools) keeps the legacy "edge-free cluster" rendering so any * existing visual snapshots stay byte-identical. */ export declare const MAX_TOOLS_PER_BUCKET_CHUNK = 50; /** Options controlling the diagram. */ export interface BuildTaskLifecycleDiagramOpts { /** Task id โ€” used only in the start/end labels. */ taskId: number; /** Operations in ascending sequence order (sorted by `timestamp`). */ operations: OperationRecord[]; /** Tool calls in ascending sequence order (sorted by `timestamp`). */ toolCalls: ToolCallRecord[]; /** * If true, the diagram will be returned as a fully-rendered Mermaid * `flowchart TD` block. If false, returns just the body (no header). * Defaults to true. */ withHeader?: boolean; /** * Optional label suffix for the end node (e.g. "โœ… completed" or * "โŒ failed"). Defaults to "end". */ endLabel?: string; } /** Outcome of a single tool-call โ†’ operation assignment (mostly for tests). */ export interface ToolAssignment { toolSequence: number; opSequence: number | null; bucket: "pre-task" | "post-task" | "in-op"; } /** * Decide which operation (if any) "owns" a given tool call. The * heuristic: a tool with timestamp T belongs to opN if * opN.timestamp <= T < opN+1.timestamp * (sorted ascending by timestamp). Tools before the first op go to * "pre-task", tools after the last op go to "post-task". * * Exported so tests can verify the bucketing independently of the * Mermaid rendering. */ export declare function assignToolsToOps(operations: OperationRecord[], toolCalls: ToolCallRecord[]): ToolAssignment[]; /** * Pick the most descriptive path label from a tool's args. We try a * bunch of canonical field names; if none matches, return the empty * string (the caller will decide whether to show just the tool name). */ export declare function pickPathLabel(args: Record<string, unknown> | undefined): string; /** * Replace Mermaid-unfriendly characters in a label with safe equivalents. * * v0.9.7 (fix/task-show-mermaid-cjk-label): the v0.9.0 implementation * collapsed `<>"|` to `_` so the legacy fallback could not pass CJK * through. The user-visible result was a string of underscores in the * rendered Mermaid SVG. We now only collapse the two characters that * Mermaid's grammar actually treats as delimiters (`"` breaks the * label; the control whitespace chars are silently dropped, so we * collapse them to a single space for readability). All other code * points โ€” CJK, emoji, `<>|#&'\\`, etc. โ€” pass through unchanged. */ export declare function escapeMermaidLabel(s: string): string; /** * v0.9.0 (fix/mermaid-cjk-encoding): encode every non-ASCII code point * in `s` as a `\uXXXX` escape sequence (or surrogate pair for U+FFFF+) * AFTER stripping Mermaid-hostile punctuation. The escape is round- * trippable by the JSON parser / SVG `<text>` decoder, so CJK glyphs * reappear in the rendered diagram instead of being collapsed to * underscores. * * v0.9.7 (fix/task-show-mermaid-cjk-label): the `\uXXXX` strategy was * based on the assumption that Mermaid 10's lexer would decode the * escape sequence back to the original glyph before rendering the * SVG. Real-browser verification (Task #2668) proved that assumption * wrong: the rendered SVG contains the literal text `\u4efb\u52a1 * \u7ba1\u7406` rather than the CJK glyphs. Browsers do not interpret * JavaScript string escapes inside a Mermaid label. * * The fix is to emit the raw UTF-8 CJK directly: Mermaid 10 + the * browser's HTML/SVG parser handle UTF-8 inside `subgraph ...["โ€ฆ"]` * and `node["โ€ฆ"]` labels natively. This function therefore now has * the same responsibility as `escapeMermaidLabel` โ€” strip only the * Mermaid-syntax-hostile characters โ€” but with a span attribute * (`mermaid.label.encode`) attached for tracing parity with the * client-side mirror. */ export declare function encodeMermaidLabelText(s: string): string; /** Build the diagram source. */ export declare function buildTaskLifecycleDiagram(opts: BuildTaskLifecycleDiagramOpts): string; /** * v2.1.1 (fix/zoom-toolbar-persist): the single source of truth for * the Mermaid zoom toolbar `<template>` block. The client-side * `public/mermaid-renderer.js#attachZoomToolbar()` clones this * template via `getElementById("mermaid-zoom-toolbar-template")` on * every successful mount / SSE update. The block MUST be embedded in * both: * - `public/index.html` (session-forest index page), and * - the task detail HTML rendered by `src/server.ts#renderTaskPage()`. * * The `public/index.html` copy is hand-written (the file ships as * static HTML and isn't processed by the TypeScript build), but the * canonical markup lives here. Keep them byte-for-byte identical โ€” * `test/zoom-toolbar-persistence.test.ts` pins this contract. */ export declare const MERMAID_ZOOM_TOOLBAR_TEMPLATE = "\n <div class=\"mermaid-zoom-toolbar\" role=\"toolbar\" aria-label=\"Mermaid diagram zoom controls\">\n <button type=\"button\" class=\"mermaid-zoom-btn\" data-mermaid-zoom=\"out\" aria-label=\"Zoom out\" title=\"Zoom out (\u2212)\">\u2212</button>\n <button type=\"button\" class=\"mermaid-zoom-btn\" data-mermaid-zoom=\"reset\" aria-label=\"Reset zoom\" title=\"Reset zoom (0)\">100%</button>\n <button type=\"button\" class=\"mermaid-zoom-btn\" data-mermaid-zoom=\"in\" aria-label=\"Zoom in\" title=\"Zoom in (+)\">+</button>\n </div>"; //# sourceMappingURL=task-detail-mermaid.d.ts.map