From f4207428317c207834fec228998ba01dec72a4e1 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Mon, 27 Apr 2026 16:54:10 +0800 Subject: [PATCH] feat(cli,core): LLM-generated summary labels for tool-call batches (#3538) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(cli,core): generate tool-use summaries for compact mode After each tool batch completes, fire a parallel fast-model call to generate a short git-commit-subject-style label summarizing what the batch accomplished (e.g. "Read txt files", "Searched in auth/"). In compact mode the label replaces the generic "Tool × N" header so N parallel tool calls collapse to a single semantic row. The fast-model call (~1s) runs fire-and-forget, overlapped with the next turn's API stream, so there is no perceived latency. Missing fast model, aborted turns, and model failures all degrade silently to the existing rendering. The summary is also emitted as a `tool_use_summary` history entry with `precedingToolUseIds`, keeping the shape compatible with SDK clients that want to render collapsed tool views on their own. Gated by `experimental.emitToolUseSummaries` (default on). Can be overridden per-session with `QWEN_CODE_EMIT_TOOL_USE_SUMMARIES=0|1`. The system prompt and truncation rules (300 chars per tool field, 200 chars of trailing assistant text as intent prefix) match the existing behavior seen in other tools that emit the same message type, so SDK consumers see a consistent shape across clients. * fix(core): bound cleanSummary quote-strip regex to avoid ReDoS CodeQL js/polynomial-redos flagged the /^["'`]+|["'`]+$/g pattern in cleanSummary because its input comes from an LLM (treated as uncontrolled). The original regex is anchored and linear in practice, but tightening the quantifier to {1,10} both satisfies the static check and caps engine work on pathological model output with a long run of quotes. Ten opening/closing quotes is well past anything a real label would produce. * fix(cli): render tool_use_summary inline so full mode also shows the label The summary was only visible in compact mode because the full-mode ToolGroupMessage ignored the compactLabel prop. Compact mode got away with this because mergeCompactToolGroups triggers refreshStatic(), which re-renders the merged tool_group with its newly-looked-up label. Full mode has no such refresh path, so when the fast-model call resolves *after* the tool_group has been committed to the append-only , there is no way to retroactively decorate it. Switch to rendering `tool_use_summary` as its own inline history item (a single dim `●