Merge pull request #72220 from NousResearch/bb/tool-row-dedupe

fix(tools): stop the inline tool row stuttering its verb and repeating the command
This commit is contained in:
brooklyn! 2026-07-26 16:25:50 -05:00 committed by GitHub
commit 79b4a40568
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 61 additions and 5 deletions

View file

@ -340,6 +340,28 @@ describe('buildToolView title actions', () => {
expect(view.titleAction).toEqual({ prefix: '', text: 'Running', suffix: ' pnpm run lint' })
})
it('never stutters the verb or echoes the command when the backend context is a phrased label', () => {
// Older backends stamped tool.start with a *phrased* label
// ("Running sleep 70 + 2 commands") rather than a raw arg preview, and the
// desktop merges that into args.context. The row must still prepend its own
// verb exactly once, show the real command in the `$` transcript, and not
// repeat either string as detail.
const command = 'sleep 70; echo "a"; echo "b"'
const view = buildToolView(
part({
args: { command, context: 'Running sleep 70 + 2 commands' },
result: { exit_code: 0 },
toolName: 'terminal'
}),
''
)
expect(view.title).toBe('Ran sleep 70 + 2 commands')
expect(view.terminalCommand).toBe(command)
expect(view.detail).toBe('')
})
it('uses the runtime locale for title text and action placement', () => {
setRuntimeI18nLocale('ja')

View file

@ -128,9 +128,14 @@ function readFileDisplayTarget(args: Record<string, unknown>, result: Record<str
return [fileEditBasename(path), lineLabel].filter(Boolean).join(' ')
}
// The real command, preferring the actual argument over the backend's
// display preview. `context` is a *summarized* preview ("sleep 70 + 2
// commands") the gateway sends on tool.start before real args arrive — fine
// as a placeholder for the live title, wrong for the `$` transcript, which
// must show what actually ran.
function shellCommand(args: Record<string, unknown>): string {
return (
firstStringField(args, ['context', 'preview']) || firstStringField(args, ['command', 'code']) || contextValue(args)
firstStringField(args, ['command', 'code']) || firstStringField(args, ['context', 'preview']) || contextValue(args)
)
}
@ -1079,6 +1084,13 @@ function toolDetailText(
if (output || lines) {
return [output, lines].filter(Boolean).join('\n')
}
// A terminal row with no output already shows its command in the `$`
// transcript above; the generic fallback would print the same string a
// second time. `execute_code` has no transcript, so it keeps the fallback.
if (part.toolName === 'terminal') {
return ''
}
}
if (part.toolName === 'web_extract') {

View file

@ -1551,12 +1551,24 @@ def test_history_to_messages_preserves_tool_calls_for_resume_display():
assert server._history_to_messages(history) == [
{"role": "user", "text": "first prompt"},
{"context": "Searching files for resume", "name": "search_files", "role": "tool"},
{"context": "resume", "name": "search_files", "role": "tool"},
{"role": "assistant", "text": "first answer"},
{"role": "user", "text": "second prompt"},
]
def test_tool_ctx_sends_an_arg_preview_not_a_phrased_label():
# Clients phrase their own verb around this string: the TUI renders
# `Terminal("<ctx>")` and the desktop prepends "Running"/"Ran". Sending a
# pre-phrased label made both stutter ("Ran Running sleep 70 + 2 commands")
# and stood in for the real command in the desktop's `$` transcript.
assert server._tool_ctx("terminal", {"command": 'sleep 70; echo "a"; echo "b"'}) == (
"sleep 70 + 2 commands"
)
assert server._tool_ctx("read_file", {"path": "/tmp/demo/package.json"}) == "package.json"
assert server._tool_ctx("web_search", {"query": "weather in NYC"}) == "weather in NYC"
def test_history_to_messages_keeps_reasoning_only_assistant_turn():
# A thinking-only assistant turn (reasoning present, no visible text) is
# persisted and recallable, but was dropped from the resumed session view

View file

@ -4505,10 +4505,20 @@ def _session_info(agent, session: dict | None = None) -> dict:
def _tool_ctx(name: str, args: dict) -> str:
try:
from agent.display import build_tool_label
"""Argument preview for a tool row — never a phrased label.
return build_tool_label(name, args, max_len=80) or ""
Clients own their own phrasing: the TUI wraps this as ``Terminal("...")``
and the desktop prepends its own localized verb ("Running"/"Ran"). Sending
``build_tool_label`` here instead of the raw preview stutters the verb on
both surfaces ("Running Running sleep 70 + 2 commands") and leaks a display
label into the desktop's ``args.context``, where it stands in for the real
command. The friendly labels belong on the CLI spinner, which builds them
from ``build_tool_label`` at its own call sites.
"""
try:
from agent.display import build_tool_preview
return build_tool_preview(name, args, max_len=80) or ""
except Exception:
return ""