{"version":3,"file":"tool-signal.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,0BAA0B,CAAC;AAiEzF,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAqB9D;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACjH,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAA;CAAE,CAe3G;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAwBlF;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,SAAS;IACpD,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,IAAI,CAAC,CAAmC;IAEhD,YAAY,KAAK,EAAE,eAAe,EAEjC;IAED,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAGrC;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAK9B;CACD","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport { getTextOutput } from \"../../../core/tools/render-utils.js\";\nimport { getCwdRelativePath } from \"../../../utils/paths.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * The \"radar\" view's one-line signal for a tool call.\n *\n * Radar answers a different question than the other two views. `peek` and\n * `full` show what a tool *said*; radar shows the *shape of the run* — the same\n * three columns for every tool, so a screen of them reads as a map:\n *\n *   ● bash            npm run check                             412 lines\n *   ● read            packages/tui/src/keys.ts                 1451 lines\n *   ● SearchCodebase  toolOutputView                             27 lines\n *   ● edit            src/core/keybindings.ts                         ok\n *   ● websearch       release notes                                error\n *\n * Column 1 is the status dot the other views already use. Column 2 is the tool,\n * padded to a fixed width so the verbs line up and a run of reads is a visible\n * block. Column 3 is the call's subject. Flush right is the signal: how much\n * came back, which is what tells you whether a search actually found anything\n * without opening it.\n */\n\n/** Width of the tool-name column. Fits every built-in name — `SearchCodebase` is the longest. */\nconst VERB_WIDTH = 14;\n\n/** Gap between the verb column and the subject. */\nconst VERB_GAP = 2;\n\n/** Minimum space kept for the flush-right signal before the subject is trimmed. */\nconst MIN_SIGNAL_GAP = 2;\n\n/**\n * Argument names that carry a call's subject, most specific first.\n *\n * Extensions and MCP servers bring their own argument names, so the list is a\n * preference order rather than a contract: anything unmatched falls back to the\n * first string-valued argument.\n */\nconst SUBJECT_KEYS = [\n\t\"command\",\n\t\"file_path\",\n\t// Before `path`: for grep/find the pattern is what identifies the call and\n\t// the path is only the scope it was run over.\n\t\"pattern\",\n\t\"query\",\n\t\"url\",\n\t\"path\",\n\t\"prompt\",\n\t\"description\",\n\t\"task_id\",\n\t\"subagent_type\",\n\t\"name\",\n\t\"id\",\n];\n\nfunction firstStringArg(args: Record<string, unknown>): string | undefined {\n\tfor (const value of Object.values(args)) {\n\t\tif (typeof value === \"string\" && value.trim()) return value;\n\t}\n\treturn undefined;\n}\n\n/** The single most identifying argument of a call, as a one-line string. */\nexport function toolSubject(args: unknown, cwd: string): string {\n\tif (typeof args !== \"object\" || args === null || Array.isArray(args)) return \"\";\n\n\tconst record = args as Record<string, unknown>;\n\tlet subject: string | undefined;\n\tfor (const key of SUBJECT_KEYS) {\n\t\tconst value = record[key];\n\t\tif (typeof value === \"string\" && value.trim()) {\n\t\t\tsubject = value;\n\t\t\tbreak;\n\t\t}\n\t}\n\tsubject ??= firstStringArg(record);\n\tif (!subject) return \"\";\n\n\t// Paths are the most common subject and the least readable in absolute form.\n\tconst relative = getCwdRelativePath(subject, cwd);\n\tconst display = relative && relative.length < subject.length ? relative : subject;\n\n\t// Multi-line commands (heredocs, chained scripts) collapse to their first line.\n\treturn display.replace(/\\s+/g, \" \").trim();\n}\n\nexport interface ToolSignalInput {\n\ttoolName: string;\n\targs: unknown;\n\tcwd: string;\n\tresult?: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>; isError: boolean };\n\tisPartial: boolean;\n\tshowImages: boolean;\n\t/** The newest call in the transcript. Themes that set `activeToolBg` mark it. */\n\tisLatest?: boolean;\n}\n\n/**\n * The flush-right signal: how much output came back, or why none did.\n *\n * Line count is the useful magnitude for nearly every tool here — it is what\n * separates a search that found one hit from one that found four hundred. A\n * result with no text (a successful `edit`, a `TodoWrite`) has no magnitude\n * worth printing, so it reports the outcome instead.\n */\nexport function toolSignal(input: ToolSignalInput): { text: string; color: \"success\" | \"error\" | \"warning\" } {\n\tif (!input.result) {\n\t\treturn { text: input.isPartial ? \"running\" : \"\", color: \"warning\" };\n\t}\n\tif (input.result.isError) {\n\t\treturn { text: \"error\", color: \"error\" };\n\t}\n\n\tconst output = getTextOutput(input.result, input.showImages);\n\tif (!output.trim()) {\n\t\treturn { text: \"ok\", color: \"success\" };\n\t}\n\n\tconst lines = output.split(\"\\n\").length;\n\treturn { text: `${lines} ${lines === 1 ? \"line\" : \"lines\"}`, color: \"success\" };\n}\n\n/**\n * Render one radar row, already padded and coloured, without the status dot —\n * the caller prepends that so the dot stays inline with the first line the same\n * way it does in the other two views.\n */\nexport function renderToolSignalLine(input: ToolSignalInput, width: number): string {\n\tconst name = input.toolName.slice(0, VERB_WIDTH);\n\tconst verb = name.padEnd(VERB_WIDTH + VERB_GAP, \" \");\n\tconst signal = toolSignal(input);\n\tconst subject = toolSubject(input.args, input.cwd);\n\n\t// `width` already excludes the caller's status-dot prefix.\n\tconst available = Math.max(0, width);\n\tconst signalWidth = signal.text ? visibleWidth(signal.text) + MIN_SIGNAL_GAP : 0;\n\tconst subjectWidth = Math.max(0, available - visibleWidth(verb) - signalWidth);\n\tconst shownSubject = subjectWidth > 0 ? truncateToWidth(subject, subjectWidth) : \"\";\n\n\tconst left = verb + shownSubject;\n\tconst pad = Math.max(signal.text ? MIN_SIGNAL_GAP : 0, available - visibleWidth(left) - visibleWidth(signal.text));\n\n\t// The marker stroke covers the verb and the subject — the part you read —\n\t// and stops before the flush-right signal, which keeps its status colour on\n\t// the page. A highlighter runs over the words, not the whole line.\n\tconst stroke = input.isLatest === true && theme.hasBg(\"activeToolBg\");\n\tconst content = theme.fg(\"toolTitle\", theme.bold(verb)) + theme.fg(\"toolOutput\", shownSubject);\n\n\treturn (\n\t\t(stroke ? theme.bg(\"activeToolBg\", content) : content) + \" \".repeat(pad) + theme.fg(signal.color, signal.text)\n\t);\n}\n\n/**\n * Component wrapper so a radar row slots into the same container the call\n * renderers use, and re-renders on resize like everything else.\n */\nexport class ToolSignalComponent implements Component {\n\tprivate input: ToolSignalInput;\n\tprivate memo?: { width: number; out: string[] };\n\n\tconstructor(input: ToolSignalInput) {\n\t\tthis.input = input;\n\t}\n\n\tsetInput(input: ToolSignalInput): void {\n\t\tthis.input = input;\n\t\tthis.memo = undefined;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.memo = undefined;\n\t}\n\n\trender(width: number): string[] {\n\t\tif (this.memo && this.memo.width === width) return this.memo.out;\n\t\tconst out = [renderToolSignalLine(this.input, width)];\n\t\tthis.memo = { width, out };\n\t\treturn out;\n\t}\n}\n"]}