From 597e7e6f13455a6c58aa5e07cbe2809c247ae587 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 3 Dec 2025 10:30:01 +0000 Subject: [PATCH] Refactor: extract tool meta formatter + debouncer --- src/auto-reply/command-reply.ts | 59 ++++------------------- src/auto-reply/tool-meta.ts | 83 +++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 51 deletions(-) create mode 100644 src/auto-reply/tool-meta.ts diff --git a/src/auto-reply/command-reply.ts b/src/auto-reply/command-reply.ts index 57351321e..de3c7a3ee 100644 --- a/src/auto-reply/command-reply.ts +++ b/src/auto-reply/command-reply.ts @@ -12,6 +12,13 @@ import { enqueueCommand } from "../process/command-queue.js"; import type { runCommandWithTimeout } from "../process/exec.js"; import { runPiRpc } from "../process/tau-rpc.js"; import { applyTemplate, type TemplateContext } from "./templating.js"; +import { + TOOL_RESULT_DEBOUNCE_MS, + createToolDebouncer, + formatToolAggregate, + formatToolPrefix, + shortenMeta, +} from "./tool-meta.js"; import type { ReplyPayload } from "./types.js"; type CommandReplyConfig = NonNullable["reply"] & { @@ -115,61 +122,11 @@ function normalizeToolResults( .map((tr) => ({ text: (tr.text ?? "").trim(), toolName: tr.toolName?.trim() || undefined, - meta: tr.meta ? shortenMetaPath(tr.meta) : undefined, + meta: tr.meta ? shortenMeta(tr.meta) : undefined, })) .filter((tr) => tr.text.length > 0); } -function formatToolPrefix(toolName?: string, meta?: string) { - const label = toolName?.trim() || "tool"; - const extra = meta?.trim(); - return extra ? `[🛠️ ${label} ${extra}]` : `[🛠️ ${label}]`; -} - -function shortenPath(p: string): string { - const home = process.env.HOME; - if (home && p.startsWith(home + "/")) return p.replace(home, "~"); - if (home && p === home) return "~"; - return p; -} - -function shortenMetaPath(meta: string): string { - if (!meta) return meta; - const colonIdx = meta.indexOf(":"); - if (colonIdx === -1) return shortenPath(meta); - const base = meta.slice(0, colonIdx); - const rest = meta.slice(colonIdx); - return `${shortenPath(base)}${rest}`; -} - -function formatToolAggregate(toolName?: string, metas?: string[]) { - const filtered = (metas ?? []).filter(Boolean).map(shortenMetaPath); - if (!filtered.length) return formatToolPrefix(toolName); - - // Group paths under common directory to reduce noise - const grouped: Record = {}; - for (const m of filtered) { - const parts = m.split("/"); - if (parts.length > 1) { - const dir = parts.slice(0, -1).join("/"); - const base = parts.at(-1) ?? m; - if (!grouped[dir]) grouped[dir] = []; - grouped[dir].push(base); - } else { - if (!grouped["."]) grouped["."] = []; - grouped["."].push(m); - } - } - - const segments = Object.entries(grouped).map(([dir, files]) => { - const brace = files.length > 1 ? `{${files.join(", ")}}` : files[0]; - if (dir === ".") return brace; - return `${dir}/${brace}`; - }); - - return `${formatToolPrefix(toolName)} ${segments.join("; ")}`; -} - export function summarizeClaudeMetadata(payload: unknown): string | undefined { if (!payload || typeof payload !== "object") return undefined; const obj = payload as Record; diff --git a/src/auto-reply/tool-meta.ts b/src/auto-reply/tool-meta.ts new file mode 100644 index 000000000..7f6b4f525 --- /dev/null +++ b/src/auto-reply/tool-meta.ts @@ -0,0 +1,83 @@ +export const TOOL_RESULT_DEBOUNCE_MS = 1000; + +function shortenPath(p: string): string { + const home = process.env.HOME; + if (home && (p === home || p.startsWith(`${home}/`))) return p.replace(home, "~"); + return p; +} + +export function shortenMeta(meta: string): string { + if (!meta) return meta; + const colonIdx = meta.indexOf(":"); + if (colonIdx === -1) return shortenPath(meta); + const base = meta.slice(0, colonIdx); + const rest = meta.slice(colonIdx); + return `${shortenPath(base)}${rest}`; +} + +export function formatToolAggregate( + toolName?: string, + metas?: string[], +): string { + const filtered = (metas ?? []).filter(Boolean).map(shortenMeta); + const label = toolName?.trim() || "tool"; + const prefix = `[🛠️ ${label}]`; + if (!filtered.length) return prefix; + + // Group by directory and brace-collapse filenames + const grouped: Record = {}; + for (const m of filtered) { + const parts = m.split("/"); + if (parts.length > 1) { + const dir = parts.slice(0, -1).join("/"); + const base = parts.at(-1) ?? m; + (grouped[dir] ||= []).push(base); + } else { + (grouped["."] ||= []).push(m); + } + } + + const segments = Object.entries(grouped).map(([dir, files]) => { + const brace = files.length > 1 ? `{${files.join(", ")}}` : files[0]; + if (dir === ".") return brace; + return `${dir}/${brace}`; + }); + + return `${prefix} ${segments.join("; ")}`; +} + +export function formatToolPrefix(toolName?: string, meta?: string) { + const label = toolName?.trim() || "tool"; + const extra = meta?.trim() ? shortenMeta(meta) : undefined; + return extra ? `[🛠️ ${label} ${extra}]` : `[🛠️ ${label}]`; +} + +export function createToolDebouncer( + onFlush: (toolName: string | undefined, metas: string[]) => void, + windowMs = TOOL_RESULT_DEBOUNCE_MS, +) { + let pendingTool: string | undefined; + let pendingMetas: string[] = []; + let timer: NodeJS.Timeout | null = null; + + const flush = () => { + if (!pendingTool && pendingMetas.length === 0) return; + onFlush(pendingTool, pendingMetas); + pendingTool = undefined; + pendingMetas = []; + if (timer) { + clearTimeout(timer); + timer = null; + } + }; + + const push = (toolName?: string, meta?: string) => { + if (pendingTool && toolName && pendingTool !== toolName) flush(); + if (!pendingTool) pendingTool = toolName; + if (meta) pendingMetas.push(meta); + if (timer) clearTimeout(timer); + timer = setTimeout(flush, windowMs); + }; + + return { push, flush }; +}