* Announce a live running → settled transition at the transcript tail: the * card flip itself happens wherever the card sits in the scrollback, which is * usually off-screen by the time a long task ends. Only live transitions fire * — a run first seen settled (own result, stored replay) stays sil
(state: MakaPiTranscriptState, entry: MakaPiToolEntry)
| 1628 | * settle reported twice (event + folded poll) notifies exactly once. |
| 1629 | */ |
| 1630 | function pushShellRunSettledNotice(state: MakaPiTranscriptState, entry: MakaPiToolEntry): void { |
| 1631 | const result = entry.result?.kind === 'shell_run' ? entry.result : undefined; |
| 1632 | if (!result) return; |
| 1633 | const failed = |
| 1634 | result.status === 'failed' || result.status === 'timed_out' || result.status === 'orphaned'; |
| 1635 | const verb = |
| 1636 | result.status === 'completed' |
| 1637 | ? 'completed' |
| 1638 | : result.status === 'cancelled' |
| 1639 | ? 'stopped' |
| 1640 | : result.status === 'timed_out' |
| 1641 | ? 'timed out' |
| 1642 | : result.status; |
| 1643 | const parts: string[] = []; |
| 1644 | if (result.exitCode !== undefined) parts.push(`exit ${result.exitCode}`); |
| 1645 | const secs = Math.round((entry.durationMs ?? 0) / 1000); |
| 1646 | if (secs >= 1) parts.push(`${secs}s`); |
| 1647 | const suffix = parts.length > 0 ? ` (${parts.join(' · ')})` : ''; |
| 1648 | const failure = |
| 1649 | failed && result.failureMessage ? ` — ${result.failureMessage.split('\n', 1)[0]}` : ''; |
| 1650 | state.entries.push({ |
| 1651 | kind: 'notice', |
| 1652 | level: failed ? 'error' : 'info', |
| 1653 | text: `Background task ${verb}: ${result.cmd.split('\n', 1)[0]}${suffix}${failure}`, |
| 1654 | }); |
| 1655 | } |
| 1656 | |
| 1657 | /** A user turn: a dim `>` quote prefix per line, no speaker label. */ |
| 1658 | function renderUserBlock(text: string, width: number): string[] { |
no test coverage detected