diff --git a/ui-tui/src/__tests__/appChromeStatusRule.test.tsx b/ui-tui/src/__tests__/appChromeStatusRule.test.tsx index de823162df2..c428c9dc55f 100644 --- a/ui-tui/src/__tests__/appChromeStatusRule.test.tsx +++ b/ui-tui/src/__tests__/appChromeStatusRule.test.tsx @@ -129,6 +129,41 @@ describe('StatusRule background-subagent indicator', () => { expect(textContent(element)).not.toContain('⛓') }) + it('spells out the auto-resume hint when idle with subagents in flight', () => { + const element = StatusRule({ + ...baseProps, + usage: { ...baseProps.usage, active_subagents: 1 } + }) + + expect(textContent(element)).toContain('resumes when subagent finishes') + }) + + it('pluralizes the resume hint for multiple in-flight subagents', () => { + const element = StatusRule({ + ...baseProps, + usage: { ...baseProps.usage, active_subagents: 3 } + }) + + expect(textContent(element)).toContain('resumes when 3 subagents finish') + }) + + it('hides the resume hint mid-turn (a busy turn owns the indicator)', () => { + const element = StatusRule({ + ...baseProps, + busy: true, + turnStartedAt: Date.now(), + usage: { ...baseProps.usage, active_subagents: 2 } + }) + + expect(textContent(element)).not.toContain('resumes when') + }) + + it('omits the resume hint when no subagents are running', () => { + const element = StatusRule({ ...baseProps }) + + expect(textContent(element)).not.toContain('resumes when') + }) + it('drops the subagent segment before the bg segment on a narrow terminal', () => { // cols=44 is below the subagents breakpoint (92) but the bg breakpoint // (88) too — both gone. Assert the lower-priority subagent indicator is diff --git a/ui-tui/src/components/appChrome.tsx b/ui-tui/src/components/appChrome.tsx index ed0588f5420..9992b227390 100644 --- a/ui-tui/src/components/appChrome.tsx +++ b/ui-tui/src/components/appChrome.tsx @@ -512,6 +512,15 @@ export function StatusRule({ const showBg = segs.bg && bgCount > 0 && fits(SEP + stringWidth(`${bgCount} bg`)) const subagentCount = typeof usage.active_subagents === 'number' ? usage.active_subagents : 0 const showSubagents = segs.subagents && subagentCount > 0 && fits(SEP + stringWidth(`⛓ ${subagentCount}`)) + // Parked-background reassurance: a top-level delegate_task runs in the + // background, so the turn ends (idle) while the subagent keeps working and its + // result re-enters as a fresh turn later. When idle with work still in flight, + // spell out that the agent resumes on its own — no spinner, nothing to poll. + // Width-budgeted like every tail segment, so it drops first on a tight + // terminal where ⛓ already carries the signal. + const resumeHintText = + subagentCount === 1 ? '↩ resumes when subagent finishes' : `↩ resumes when ${subagentCount} subagents finish` + const showResumeHint = !busy && subagentCount > 0 && fits(SEP + stringWidth(resumeHintText)) // Dev-gated readout (HERMES_DEV_CREDITS), lowest priority, // so it consumes tail budget LAST and drops first on a narrow terminal. const showDevCredits = !!devCreditsText && fits(SEP + stringWidth(devCreditsText)) @@ -624,6 +633,12 @@ export function StatusRule({ ⛓ {subagentCount} ) : null} + {showResumeHint ? ( + + {' │ '} + {resumeHintText} + + ) : null} {showDevCredits ? ( {' │ '}