mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 01:27:35 +00:00
* fix(native-chat): the working line shows only what the agent is doing now A chat's live "Working…" line could show an old notice, such as "Claude hit a temporary problem and is retrying.", long after the agent had moved on and was running new commands. When the host had no live activity for the turn, the line fell back to the newest status row in the turn, and any status row qualified: retry warnings, "Context compacted", "Cancellation requested.", and notification summaries. Those rows record the past and already appear in the transcript. The line now reads only the host's live, per-turn activity, which is never saved and is cleared at turn boundaries. Without it, the line says Thinking or Working…. Desktop and mobile share the selector, so both change. * test(codex): guard that a subagent's compaction never becomes the parent's live activity
101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
import { readAgentJournalTurn } from './agent-session-turn-record'
|
|
import type { AgentJournalRenderItem } from './agent-session-journal-types'
|
|
import type { AgentSessionTurnActivity } from './agent-session-wire'
|
|
import { isRootAgentJournalItem } from './agent-session-journal-producer'
|
|
import { normalizePromptField } from './agent-status-field-normalization'
|
|
import { describeActiveToolCall, formatActiveToolLabel } from './native-chat-tool-activity'
|
|
|
|
export type NativeChatTurnActivity = { kind: 'description'; text: string }
|
|
|
|
function activityLine(text: string): string | null {
|
|
let end = text.length
|
|
while (end > 0) {
|
|
const start = text.lastIndexOf('\n', end - 1) + 1
|
|
const latest = text.slice(start, end).trim()
|
|
if (latest) {
|
|
return normalizePromptField(latest) || null
|
|
}
|
|
if (start === 0) {
|
|
break
|
|
}
|
|
end = start - 1
|
|
}
|
|
return null
|
|
}
|
|
|
|
function recentToolActivityLabels(items: readonly AgentJournalRenderItem[]): Set<string> {
|
|
const labels = new Set<string>()
|
|
let foundRunning = false
|
|
let foundSettled = false
|
|
for (let index = items.length - 1; index >= 0 && (!foundRunning || !foundSettled); index -= 1) {
|
|
const body = items[index]?.body
|
|
if (body?.kind !== 'tool-call') {
|
|
continue
|
|
}
|
|
const isRunning = body.state === 'running'
|
|
if ((isRunning && foundRunning) || (!isRunning && foundSettled)) {
|
|
continue
|
|
}
|
|
const descriptor = describeActiveToolCall({
|
|
type: 'tool-call',
|
|
name: body.name,
|
|
input: body.input,
|
|
state: body.state
|
|
})
|
|
const candidates = [
|
|
formatActiveToolLabel(descriptor),
|
|
descriptor.preview,
|
|
descriptor.preview ? `${descriptor.toolName} ${descriptor.preview}` : descriptor.toolName
|
|
]
|
|
for (const candidate of candidates) {
|
|
const label = activityLine(candidate)?.toLowerCase()
|
|
if (label) {
|
|
labels.add(label)
|
|
}
|
|
}
|
|
foundRunning ||= isRunning
|
|
foundSettled ||= !isRunning
|
|
}
|
|
return labels
|
|
}
|
|
|
|
function repeatsRecentToolLabel(text: string, labels: ReadonlySet<string>): boolean {
|
|
const normalized = text.toLowerCase()
|
|
for (const label of labels) {
|
|
if (
|
|
normalized === label ||
|
|
normalized.startsWith(`${label} `) ||
|
|
normalized.endsWith(` ${label}`) ||
|
|
normalized.includes(` ${label} `)
|
|
) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
/** The live provider activity for this turn; callers provide the broad fallback.
|
|
* Journal rows are history, not the present, so none of them ever becomes this line. */
|
|
export function selectStructuredAgentTurnActivity(
|
|
items: readonly AgentJournalRenderItem[],
|
|
turnId: string | null,
|
|
providerActivity?: AgentSessionTurnActivity | null
|
|
): NativeChatTurnActivity | null {
|
|
if (!turnId || providerActivity?.turnId !== turnId) {
|
|
return null
|
|
}
|
|
const text = activityLine(providerActivity.text)
|
|
if (!text) {
|
|
return null
|
|
}
|
|
const turnStartIndex = items.findLastIndex((item) => {
|
|
const turn = readAgentJournalTurn(item.body)
|
|
return turn?.turnId === turnId && turn.state === 'running'
|
|
})
|
|
// Root-scoped: a subagent's tool label must not suppress the parent's own line.
|
|
const turnItems = items.slice(Math.max(0, turnStartIndex)).filter(isRootAgentJournalItem)
|
|
return repeatsRecentToolLabel(text, recentToolActivityLabels(turnItems))
|
|
? null
|
|
: { kind: 'description', text }
|
|
}
|