fix(tui): surface verbose tool details (#30225)
* fix(tui): surface verbose tool details Emit redacted structured verbose args/results to the TUI so /verbose verbose can show full tool detail without reopening stdout, and fail closed if redaction is unavailable. Salvages #29011. Co-authored-by: helix4u <4317663+helix4u@users.noreply.github.com> * fix(tui): address verbose detail review Label verbose tool failures as errors, cover forced verbose reasoning, and avoid new diff type warnings from the redaction regression tests. * fix(tui): bound verbose tool payloads Cap verbose tool detail text before emitting JSON-RPC events and preserve verbose results on inline diff completions. * fix(tui): align termux argv test with gc flag Update the stale TUI launch expectation so the Termux freshness path matches the current direct Node argv. --------- Co-authored-by: helix4u <4317663+helix4u@users.noreply.github.com>
This commit is contained in:
@@ -491,13 +491,13 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev:
|
||||
|
||||
case 'reasoning.delta':
|
||||
if (ev.payload?.text) {
|
||||
turnController.recordReasoningDelta(ev.payload.text)
|
||||
turnController.recordReasoningDelta(ev.payload.text, Boolean(ev.payload.verbose))
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case 'reasoning.available':
|
||||
turnController.recordReasoningAvailable(String(ev.payload?.text ?? ''))
|
||||
turnController.recordReasoningAvailable(String(ev.payload?.text ?? ''), Boolean(ev.payload?.verbose))
|
||||
|
||||
return
|
||||
|
||||
@@ -517,12 +517,18 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev:
|
||||
|
||||
case 'tool.start':
|
||||
turnController.recordTodos(ev.payload.todos)
|
||||
turnController.recordToolStart(ev.payload.tool_id, ev.payload.name ?? 'tool', ev.payload.context ?? '')
|
||||
turnController.recordToolStart(
|
||||
ev.payload.tool_id,
|
||||
ev.payload.name ?? 'tool',
|
||||
ev.payload.context ?? '',
|
||||
ev.payload.args_text ? stripAnsi(String(ev.payload.args_text)) : undefined
|
||||
)
|
||||
|
||||
return
|
||||
case 'tool.complete': {
|
||||
const inlineDiffText =
|
||||
ev.payload.inline_diff && getUiState().inlineDiffs ? stripAnsi(String(ev.payload.inline_diff)).trim() : ''
|
||||
const resultText = ev.payload.result_text ? stripAnsi(String(ev.payload.result_text)) : undefined
|
||||
|
||||
if (inlineDiffText) {
|
||||
turnController.recordInlineDiffToolComplete(
|
||||
@@ -530,7 +536,8 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev:
|
||||
ev.payload.tool_id,
|
||||
ev.payload.name,
|
||||
ev.payload.error,
|
||||
ev.payload.duration_s
|
||||
ev.payload.duration_s,
|
||||
resultText
|
||||
)
|
||||
} else {
|
||||
turnController.recordToolComplete(
|
||||
@@ -539,7 +546,8 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev:
|
||||
ev.payload.error,
|
||||
ev.payload.summary,
|
||||
ev.payload.duration_s,
|
||||
ev.payload.todos
|
||||
ev.payload.todos,
|
||||
resultText
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { hasReasoningTag, splitReasoning } from '../lib/reasoning.js'
|
||||
import {
|
||||
boundedLiveRenderText,
|
||||
buildToolTrailLine,
|
||||
buildVerboseToolTrailLine,
|
||||
estimateTokensRough,
|
||||
isTransientTrailLine,
|
||||
sameToolTrailGroup,
|
||||
@@ -542,8 +543,8 @@ class TurnController {
|
||||
}
|
||||
}
|
||||
|
||||
recordReasoningAvailable(text: string) {
|
||||
if (this.interrupted || !getUiState().showReasoning) {
|
||||
recordReasoningAvailable(text: string, force = false) {
|
||||
if (this.interrupted || (!force && !getUiState().showReasoning)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -560,8 +561,8 @@ class TurnController {
|
||||
this.pulseReasoningStreaming()
|
||||
}
|
||||
|
||||
recordReasoningDelta(text: string) {
|
||||
if (this.interrupted || !getUiState().showReasoning) {
|
||||
recordReasoningDelta(text: string, force = false) {
|
||||
if (this.interrupted || (!force && !getUiState().showReasoning)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -587,14 +588,15 @@ class TurnController {
|
||||
error?: string,
|
||||
summary?: string,
|
||||
duration?: number,
|
||||
todos?: unknown
|
||||
todos?: unknown,
|
||||
resultText?: string
|
||||
) {
|
||||
if (this.interrupted) {
|
||||
return
|
||||
}
|
||||
|
||||
this.recordTodos(todos)
|
||||
const line = this.completeTool(toolId, fallbackName, error, summary, duration)
|
||||
const line = this.completeTool(toolId, fallbackName, error, summary, duration, resultText)
|
||||
|
||||
this.pendingSegmentTools = [...this.pendingSegmentTools, line]
|
||||
this.flushPendingToolsIntoLastSegment()
|
||||
@@ -606,30 +608,42 @@ class TurnController {
|
||||
toolId: string,
|
||||
fallbackName?: string,
|
||||
error?: string,
|
||||
duration?: number
|
||||
duration?: number,
|
||||
resultText?: string
|
||||
) {
|
||||
if (this.interrupted) {
|
||||
return
|
||||
}
|
||||
|
||||
this.flushStreamingSegment()
|
||||
this.pushInlineDiffSegment(diffText, [this.completeTool(toolId, fallbackName, error, '', duration)])
|
||||
this.pushInlineDiffSegment(diffText, [this.completeTool(toolId, fallbackName, error, '', duration, resultText)])
|
||||
this.publishToolState()
|
||||
}
|
||||
|
||||
private completeTool(toolId: string, fallbackName?: string, error?: string, summary?: string, duration?: number) {
|
||||
private completeTool(
|
||||
toolId: string,
|
||||
fallbackName?: string,
|
||||
error?: string,
|
||||
summary?: string,
|
||||
duration?: number,
|
||||
resultText?: string
|
||||
) {
|
||||
const done = this.activeTools.find(tool => tool.id === toolId)
|
||||
const name = done?.name ?? fallbackName ?? 'tool'
|
||||
const label = toolTrailLabel(name)
|
||||
const fallbackDuration = done?.startedAt ? (Date.now() - done.startedAt) / 1000 : undefined
|
||||
|
||||
const line = buildToolTrailLine(
|
||||
name,
|
||||
done?.context || '',
|
||||
Boolean(error),
|
||||
error || summary || '',
|
||||
duration ?? fallbackDuration
|
||||
)
|
||||
const line =
|
||||
done?.verboseArgs || resultText
|
||||
? buildVerboseToolTrailLine(
|
||||
name,
|
||||
done?.context || '',
|
||||
Boolean(error),
|
||||
duration ?? fallbackDuration,
|
||||
done?.verboseArgs,
|
||||
error || resultText || summary || ''
|
||||
)
|
||||
: buildToolTrailLine(name, done?.context || '', Boolean(error), error || summary || '', duration ?? fallbackDuration)
|
||||
|
||||
this.activeTools = this.activeTools.filter(tool => tool.id !== toolId)
|
||||
|
||||
@@ -675,7 +689,7 @@ class TurnController {
|
||||
}, STREAM_BATCH_MS)
|
||||
}
|
||||
|
||||
recordToolStart(toolId: string, name: string, context: string) {
|
||||
recordToolStart(toolId: string, name: string, context: string, verboseArgs?: string) {
|
||||
if (this.interrupted) {
|
||||
return
|
||||
}
|
||||
@@ -688,7 +702,7 @@ class TurnController {
|
||||
const sample = `${name} ${context}`.trim()
|
||||
|
||||
this.toolTokenAcc += sample ? estimateTokensRough(sample) : 0
|
||||
this.activeTools = [...this.activeTools, { context, id: toolId, name, startedAt: Date.now() }]
|
||||
this.activeTools = [...this.activeTools, { context, id: toolId, name, startedAt: Date.now(), verboseArgs }]
|
||||
|
||||
patchTurnState({ toolTokens: this.toolTokenAcc, tools: this.activeTools })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user