Skip to content

feat(chat-history): end the transcript with a live agent status trail - #1188

Merged
sudomaggie merged 1 commit into
developfrom
feat/chat-status-trail
Sep 1, 2026
Merged

feat(chat-history): end the transcript with a live agent status trail#1188
sudomaggie merged 1 commit into
developfrom
feat/chat-status-trail

Conversation

@Lando801

@Lando801 Lando801 commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Problem

A running round told the user almost nothing, and it told it twice.

PlanningFooter rendered a row saying "Planning next step..." / "Agent is
typing...", but usePlanningIndicator hides that row for a second after every
store mutation — so during a tool batch, which is exactly when a long round is
least legible, the transcript simply ended. Nothing on screen said how long the
round had been going, and the finished turn's "Agent worked for X" bar only
appeared once the round was already over.

Nothing carried the session's identity either: the transcript stopped at the
last event rather than at the agent that produced it.

Solution

One row now closes the transcript, for the whole session:

◼  Agent working for 1h 20m 26s · Agent is typing...
◼  Waiting for your reply
◼  Agent is idle
◼  2 running tasks · Agent is idle
◼  Last refreshed 5 minutes ago
  • AgentStatusTrail replaces PlanningFooter. The activity phrase became
    this line's second segment rather than a second line, and it keeps its exact
    behaviour — same usePlanningIndicator count, same stable variant index, same
    agentTyping/compacting selection (pickPlanningVariant was extracted, not
    rewritten). The duration is the row's anchor so the row cannot flicker when
    the phrase hides itself between beats.
  • "Agent working for X" is the present-tense twin of the finished turn's
    "Agent worked for X" bar, anchored to the same ChatGroupMeta.startMs, so the
    live line and the line it becomes are the same sentence over the same span.
  • Phase mirrors the sidebar dot. resolveTrailPhase applies the same
    precedence buildSessionMenuItem uses — asking above running above resting —
    reading the session's own status via isSessionInProgress /
    isSessionPendingAsking, with liveness through the existing
    resolveTailTurnAgentWorking. The trail, the tail-turn collapse phase and the
    sidebar dot now share one rule.
  • A quiet round stops reading as live. No transcript activity for five
    minutes and the trail drops to resting whatever the status says: nothing
    writes a terminal status for an imported transcript when that process exits,
    and a dropped agent:complete leaves the native atom on running. Either way
    the old behaviour would have been a counter climbing forever next to work that
    had stopped.
  • Imported transcripts do not claim agent state. ORGII does not run an
    imported Claude Code / Codex / Cursor agent and cannot know it is idle, so
    those sessions report DataSourceConfig.lastScannedAt — the same timestamp
    the Runtime scanning panel shows — instead of "Agent is idle".
  • The mark is the session's own harness, via SessionIdentityIcon (the
    sidebar row's projection), breathing on a slow agent-pulse while running and
    parked at that animation's own low point when resting, so switching phases
    changes whether it moves, never where it sits.

Live context tokens and the background-task count are deliberately not on the
running line: the composer's context pill and ActiveProcesses already carry
them. Running tasks do appear while resting, where "Agent is idle" beside two
live shells would be false.

Potential risks

  • The row is now always present once a session has a turn, where the old
    planning row came and went. That is a persistent ~36px row at the end of every
    transcript, including historical and imported ones. Visual judgement call; the
    cheapest dial is dropping the resting label to icon-only.
  • Not seen running. ORGII is Tauri-only with no browser preview, so nothing
    here is confirmed against real pixels — in particular whether text-2 plus a
    long planning phrase reads well at the 800px measure. Everything below is
    compiler- and test-level evidence.
  • PlanningFooter is deleted. Its only consumer was this footer slot
    (verified by grep), but any out-of-tree import would break.
  • Locale coverage is mechanical. The seven planning.statusTrail keys were
    authored for all 13 locales without a native-speaker review.
  • Timer churn is confined to the leaf row: the state object carries
    startedAtMs, not a ticking value, so ChatHistoryList's memo is not
    invalidated once a second. A 1s clock runs only while running, a 30s clock only
    while showing "last refreshed".
  • Scoped surfaces (subagent cells, cloud member-turn runners) still supply
    their own liveness and suppress the token/asking paths, since they have no
    sidebar row to agree with. Not exercised beyond unit coverage.

Verification

Commands run, all on this branch's content:

  • npx tsc --noEmit --pretty false — 0 errors (in the working checkout).
    An isolated worktree typecheck at this commit timed out after 10 minutes,
    a known stall in this repo; instead I diffed the one file that had drifted
    against origin/develop and confirmed by grep that no PlanningFooter
    reference survives and that the rebased barrel still exports everything its
    two importers (ChatHistoryList, ChatHistoryListTypes) pull from it.
  • npx eslint src/engines/ChatPanel/ — clean.
  • npm run check:circular — no circular dependencies across 6407 modules.
  • npx vitest run src/engines/ChatPanel/ src/i18n src/util/session src/store/session src/scaffold/NavigationSidebar — 272 files / 1919 tests passed.

Regressions reproduced before fixing, not assumed:

  • Reverting the footer-slot gate to planningIndicatorCount > 0 alone fails 2
    tests in ChatHistoryListStatusTrail.test.ts; restoring it passes.
  • Removing the stale term from resolveTrailPhase fails 3 tests across
    agentStatusTrailMath.test.ts and useAgentStatusTrail.test.ts; restoring
    passes.

New coverage: 44 tests over four files — duration formatting, activity and
phase precedence (including that waiting_for_user is itself an in-progress
status and must not read as running), the quiet-window timer arming and
re-arming under fake timers, the composed line under a real renderer, and the
footer slot surviving in both the virtualized and static render paths.

Also verified: .animate-agent-pulse, its @keyframes, and
.motion-reduce\:animate-none are actually emitted by compiling the Tailwind
config; every locale's planning.statusTrail key set matches the keys the
component renders, with no orphans.

Not run: the E2E suite, and the app itself.

Replaces the PlanningFooter row with a single status line that persists for
the whole session: "Agent working for 1h 20m 26s · Agent is typing..." while
a round runs, and a resting agent mark between rounds.

The row reports the session's own state through the same helpers the sidebar
dot uses, so the two surfaces can never disagree, and it stops calling a
session "running" once its transcript has been quiet for five minutes.
@sudomaggie
sudomaggie merged commit 361e67e into develop Sep 1, 2026
6 checks passed
@Harry19081 Harry19081 added enhancement New feature or request UX Improvements to user experience, workflow smoothness agent Agent runtime, behavior, memory, providers, or orchestration chat Chat, composer, markdown, canvas, browser, or terminal surfaces labels Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Agent runtime, behavior, memory, providers, or orchestration chat Chat, composer, markdown, canvas, browser, or terminal surfaces enhancement New feature or request UX Improvements to user experience, workflow smoothness

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants