hermes-agent/apps/desktop/src/store/tool-drafting.ts
Brooklyn Nicholson a3c46ac09e refactor(desktop): give live tool runs a one-line ticker
A live run was the settled view under a CSS max-height: the real rows,
capped at ~6.75rem, with an escape hatch that let an open diff lift the cap
entirely. So it was neither a single line nor an honest expanded block, and a
run could balloon mid-turn.

Split the two presentations instead. Live, a run is its summary plus a
one-line reel that slides each finished action up and out, so a turn touching
thirty files reads as one line ticking over in place. Settled, the summary is
the whole of it until opened. Cards — file edits, clarify, image_generate —
leave the run entirely and stay on screen where they happened, since the diff
or the question IS the point of the turn.

Drops useToolWindow, the bounded-window threshold, the scroll container, the
fade mask and the :has([data-tool-open]) escape hatch, all of which existed to
make "the real rows, but shorter" work.

Also unifies transcript scaffolding on one colour: thinking headers painted
--ui-text-secondary and tool summaries --ui-text-tertiary under a shared
opacity, which is two greys for one kind of line. Both now render through
ScaffoldRow at a token pitched between them.
2026-07-27 18:42:46 -05:00

45 lines
1.4 KiB
TypeScript

import { atom, computed } from 'nanostores'
// The tool whose arguments the model is streaming right now, and when it
// started. Generating a large payload (a 45 KB write_file) takes seconds during
// which the tool has not begun and the transcript has nothing to say.
// Per-session because a transcript may be a tile, and a tile must never narrate
// the primary chat's work.
const keyFor = (sessionId: string | null | undefined): string => sessionId ?? ''
export interface DraftingTool {
name: string
since: number
}
export const $draftingToolSessions = atom<Record<string, DraftingTool>>({})
/** What `sessionId` is drafting, if anything. */
export function sessionDraftingTool(sessionId: null | string) {
return computed($draftingToolSessions, sessions => sessions[keyFor(sessionId)] ?? null)
}
export function setSessionDraftingTool(sessionId: string | null | undefined, name: string): void {
const key = keyFor(sessionId)
const sessions = $draftingToolSessions.get()
if (!name) {
if (!(key in sessions)) {
return
}
const next = { ...sessions }
delete next[key]
$draftingToolSessions.set(next)
return
}
// Re-announcing the same tool keeps the original timestamp, so the elapsed
// count reads as one continuous wait rather than restarting.
if (sessions[key]?.name === name) {
return
}
$draftingToolSessions.set({ ...sessions, [key]: { name, since: Date.now() } })
}