mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
fix(tui): anchor details to stream timeline
This commit is contained in:
parent
7143d22a83
commit
a0aebad673
3 changed files with 64 additions and 77 deletions
|
|
@ -379,6 +379,10 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev:
|
|||
const inlineDiffText =
|
||||
ev.payload.inline_diff && getUiState().inlineDiffs ? stripAnsi(String(ev.payload.inline_diff)).trim() : ''
|
||||
|
||||
if (inlineDiffText) {
|
||||
turnController.flushStreamingSegment()
|
||||
}
|
||||
|
||||
turnController.recordToolComplete(
|
||||
ev.payload.tool_id,
|
||||
ev.payload.name,
|
||||
|
|
@ -386,17 +390,10 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev:
|
|||
inlineDiffText ? '' : ev.payload.summary
|
||||
)
|
||||
|
||||
if (!inlineDiffText) {
|
||||
return
|
||||
if (inlineDiffText) {
|
||||
turnController.pushInlineDiffSegment(inlineDiffText)
|
||||
}
|
||||
|
||||
// Anchor the diff to where the edit happened in the turn — between
|
||||
// the narration that preceded the tool call and whatever the agent
|
||||
// streams afterwards. The previous end-merge put the diff at the
|
||||
// bottom of the final message even when the edit fired mid-turn,
|
||||
// which read as "the agent wrote this after saying that".
|
||||
turnController.pushInlineDiffSegment(inlineDiffText)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,11 +38,7 @@ const diffSegmentBody = (msg: Msg): null | string => {
|
|||
return m ? m[1]! : null
|
||||
}
|
||||
|
||||
const insertBeforeFirstDiff = (segments: Msg[], msg: Msg): Msg[] => {
|
||||
const index = segments.findIndex(segment => segment.kind === 'diff')
|
||||
|
||||
return index < 0 ? [...segments, msg] : [...segments.slice(0, index), msg, ...segments.slice(index)]
|
||||
}
|
||||
const hasDetails = (msg: Msg): boolean => Boolean(msg.thinking || msg.tools?.length || msg.toolTokens)
|
||||
|
||||
export interface InterruptDeps {
|
||||
appendMessage: (msg: Msg) => void
|
||||
|
|
@ -69,6 +65,7 @@ class TurnController {
|
|||
persistSpawnTree?: (subagents: SubagentProgress[], sessionId: null | string) => Promise<void>
|
||||
protocolWarned = false
|
||||
reasoningText = ''
|
||||
reasoningSegmentOffset = 0
|
||||
segmentMessages: Msg[] = []
|
||||
pendingSegmentTools: string[] = []
|
||||
statusTimer: Timer = null
|
||||
|
|
@ -94,6 +91,7 @@ class TurnController {
|
|||
clearReasoning() {
|
||||
this.reasoningTimer = clear(this.reasoningTimer)
|
||||
this.reasoningText = ''
|
||||
this.reasoningSegmentOffset = 0
|
||||
this.toolTokenAcc = 0
|
||||
patchTurnState({ reasoning: '', reasoningTokens: 0, toolTokens: 0 })
|
||||
}
|
||||
|
|
@ -181,29 +179,33 @@ class TurnController {
|
|||
|
||||
flushStreamingSegment() {
|
||||
const raw = this.bufRef.trimStart()
|
||||
|
||||
if (!raw) {
|
||||
return
|
||||
}
|
||||
|
||||
const split = hasReasoningTag(raw) ? splitReasoning(raw) : { reasoning: '', text: raw }
|
||||
const split = raw ? (hasReasoningTag(raw) ? splitReasoning(raw) : { reasoning: '', text: raw }) : { reasoning: '', text: '' }
|
||||
|
||||
if (split.reasoning && !this.reasoningText.trim()) {
|
||||
this.reasoningText = split.reasoning
|
||||
patchTurnState({ reasoning: this.reasoningText, reasoningTokens: estimateTokensRough(this.reasoningText) })
|
||||
}
|
||||
|
||||
const text = split.text
|
||||
const thinking = this.reasoningText.slice(this.reasoningSegmentOffset).trim()
|
||||
const msg: Msg = {
|
||||
role: split.text ? 'assistant' : 'system',
|
||||
text: split.text,
|
||||
...(!split.text && { kind: 'trail' as const }),
|
||||
...(thinking && {
|
||||
thinking,
|
||||
thinkingTokens: estimateTokensRough(thinking)
|
||||
}),
|
||||
...(this.pendingSegmentTools.length && { tools: this.pendingSegmentTools })
|
||||
}
|
||||
|
||||
this.streamTimer = clear(this.streamTimer)
|
||||
|
||||
if (text) {
|
||||
const tools = this.pendingSegmentTools
|
||||
|
||||
this.segmentMessages = [...this.segmentMessages, { role: 'assistant', text, ...(tools.length && { tools }) }]
|
||||
this.pendingSegmentTools = []
|
||||
if (split.text || hasDetails(msg)) {
|
||||
this.segmentMessages = [...this.segmentMessages, msg]
|
||||
}
|
||||
|
||||
this.reasoningSegmentOffset = this.reasoningText.length
|
||||
this.pendingSegmentTools = []
|
||||
this.bufRef = ''
|
||||
patchTurnState({ streamPendingTools: [], streamSegments: this.segmentMessages, streaming: '' })
|
||||
}
|
||||
|
|
@ -295,7 +297,6 @@ class TurnController {
|
|||
const finalText = split.text
|
||||
const existingReasoning = this.reasoningText.trim() || String(payload.reasoning ?? '').trim()
|
||||
const savedReasoning = [existingReasoning, existingReasoning ? '' : split.reasoning].filter(Boolean).join('\n\n')
|
||||
const savedReasoningTokens = savedReasoning ? estimateTokensRough(savedReasoning) : 0
|
||||
const savedToolTokens = this.toolTokenAcc
|
||||
const tools = this.pendingSegmentTools
|
||||
|
||||
|
|
@ -312,32 +313,20 @@ class TurnController {
|
|||
return body === null || (!finalHasOwnDiffFence && !finalText.includes(body))
|
||||
})
|
||||
|
||||
const hasDiffSegment = segments.some(msg => msg.kind === 'diff')
|
||||
const detailsBelongBeforeDiff = hasDiffSegment && (tools.length > 0 || Boolean(savedReasoning))
|
||||
|
||||
const finalMessages = detailsBelongBeforeDiff
|
||||
? insertBeforeFirstDiff(segments, {
|
||||
kind: 'trail',
|
||||
role: 'system',
|
||||
text: '',
|
||||
thinking: savedReasoning || undefined,
|
||||
thinkingTokens: savedReasoning ? savedReasoningTokens : undefined,
|
||||
toolTokens: savedToolTokens || undefined,
|
||||
...(tools.length && { tools })
|
||||
})
|
||||
: [...segments]
|
||||
const finalThinking = savedReasoning.slice(this.reasoningSegmentOffset).trim()
|
||||
const finalDetails: Msg = {
|
||||
kind: 'trail',
|
||||
role: 'system',
|
||||
text: '',
|
||||
thinking: finalThinking || undefined,
|
||||
thinkingTokens: finalThinking ? estimateTokensRough(finalThinking) : undefined,
|
||||
toolTokens: savedToolTokens || undefined,
|
||||
...(tools.length && { tools })
|
||||
}
|
||||
const finalMessages = hasDetails(finalDetails) ? [...segments, finalDetails] : [...segments]
|
||||
|
||||
if (finalText) {
|
||||
finalMessages.push({
|
||||
role: 'assistant',
|
||||
text: finalText,
|
||||
...(!detailsBelongBeforeDiff && {
|
||||
thinking: savedReasoning || undefined,
|
||||
thinkingTokens: savedReasoning ? savedReasoningTokens : undefined,
|
||||
toolTokens: savedToolTokens || undefined,
|
||||
...(tools.length && { tools })
|
||||
})
|
||||
})
|
||||
finalMessages.push({ role: 'assistant', text: finalText })
|
||||
}
|
||||
|
||||
const wasInterrupted = this.interrupted
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue