Commit Graph

107 Commits

Author SHA1 Message Date
can1357 a1d1d4c44f feat(coding-agent/modes): added shared compact divider for handoff summary
- Routed `customType: "handoff"` messages to the compact divider path in Agent Hub and UI helpers.
- Added handoff summary expansion that extracts context text and strips `<handoff-context>` wrappers.
- Refactored shared divider rendering into `SummaryDividerComponent` used by compaction and handoff messages.
2026-06-14 04:29:11 +02:00
can1357 eb5cc85eed Merge PR #2528: emit per-turn token-usage row as standalone block 2026-06-14 02:56:50 +02:00
can1357 24c8bb24c6 feat(session): added modular session APIs and rebuilt listing/persistence behavior
- Added session-domain modules and exports for session-entries, context, listing, loader, and migrations.
- Changed persistence to async append writes plus writeTextAtomic, removing sync line APIs.
- Added compaction-aware session context rebuild with dangling tool-call cleanup.
- Added resumable session resolution with status inference, id/stem/suffix matching, and backup recovery.
2026-06-14 02:02:53 +02:00
metaphorics 35307b60a7 fix: emit per-turn token-usage row as a standalone block
With display.showTokenUsage on, the usage row was rendered inside the
assistant block above the turn's tool blocks. Finalizing the assistant
block was therefore deferred, and the late append recommitted the
already-committed tool rows, duplicating them in scrollback (worst with
parallel tool calls).

The assistant block now always finalizes as soon as a tool-call appears,
and the usage row is emitted as a standalone finalized block below the
turn's tool blocks across all three render paths (live event-controller,
transcript rebuild, agent-hub). The now-dead setUsageInfo/#usageInfo path
on AssistantMessageComponent is removed.
2026-06-14 08:17:36 +09:00
can1357 e30c642126 fix: corrected assistant render and reveal logic to skip blank thinking
- Filtered dot-only or blank thinking blocks so they no longer render as assistant thought.
- Adjusted assistant-message and streaming-reveal logic to use visible-thinking helpers for consistency.
2026-06-13 16:26:51 +02:00
can1357 c938fe4d27 feat(coding-agent): added focused subagent session mode for main-agent hub navigation
- Added a SessionFocusController to switch transcript and input context between main and subagent sessions.
- Added agent-hub Enter activation and double-left return behavior for focused local agents.
- Added view-session-based event and render logic to avoid stale focus-session state.
- Added status-line focused agent display with ghost icon and focused-mode border dimming.
2026-06-12 15:37:56 +02:00
can1357 3e90371f5c feat(coding-agent): added collaborative sessions with host and guest command support
- Added AES-GCM room-key crypto, relay link parsing, and invalid-link validation.
- Added `/collab`, `/join`, and `/leave` command handling for collaborative sessions.
- Added startup `join` argument wiring to execute `/join` during interactive launch.
- Added status-line, prompt, and command-routing updates for guest/host collaboration UX.
2026-06-12 10:54:28 +02:00
can1357 4f0d32a1c1 feat(snapcompact): added configurable compaction shape selection for snapcompact
- Added snapcompact.shape setting with auto and variant options in agent config.
- Implemented resolveShape support for forced variants, auto provider winners, and repricing.
- Threaded resolved shape into compaction and inline-image flows for pricing and rendering.
2026-06-12 07:00:34 +02:00
can1357 212e05dbbb feat: added Codex saved reset-credit redemption flow to usage tooling
- Added Codex reset-credit models, endpoints, and redemption methods.
- Added reset-credit usage data and cache invalidation for successful redemption.
- Added `/reset-usage` slash command and interactive account selector flow.
- Added contract tests for reset-credit listing, fallback, and consume outcomes.
2026-06-12 05:20:08 +02:00
can1357 6091931b9d feat(coding-agent): added scoped snapcompact system-prompt imaging modes
- Added `snapcompact.systemPrompt` enum modes `none`, `agents-md`, and `all` with default `none`.
- Added AGENTS.md-mode context extraction and mode-specific system-frame injection.
- Updated `/context` and `/debug` request reporting to include snapcompact deltas, swap reasons, and estimates.
- Normalized legacy boolean `snapcompact.systemPrompt` values to enum strings for compatibility.
2026-06-12 05:14:34 +02:00
can1357 2630faca1f feat(coding-agent): added snapcompact savings estimation to context reporting
- Added optional snapcompact savings calculation in context breakdown with caller opt-in and setting gate.
- Added /context command integration to surface snapcompact savings and related skip reasons.
- Added context report sections for vision capability, tool-result imaging counts, and next-request token totals.
- Added shared inline-swap planning/savings estimation and transformer use for planned frame swaps.
2026-06-12 04:59:05 +02:00
Can Bölük ed92eb8ff6 Merge pull request #2248 from chan1103/feat/smart-paste-text-fallback
feat(coding-agent): fall back to text paste when clipboard holds no image
2026-06-12 04:24:29 +02:00
can1357 51208e5de1 feat(coding-agent): added boundary-aware steering and idle steer-queue handling
- Added optional `hasSteeringMessages` config hook and limited steering checks to boundaries.
- Fixed interrupted tool-batch steering by keeping queued messages until boundary handling.
- Added idle text and image submissions to steer queueing when no input waiter exists.
- Auto-continued resumable sessions after queued steering and preserved submit metadata.
2026-06-12 03:30:51 +02:00
can1357 7c3407ac65 fix(coding-agent): converted Zod tool-parameter schemas to wire schema in five consumers
- Fixed `/dump` output (`session-dump-format.ts`) and the RPC `get_state` `dumpTools` payload (`rpc-mode.ts`) serializing Zod schema instances — leaking `def`/`shape` internals and stringified methods — by converting parameters through `zodToWireSchema` like providers receive.
- Fixed context-usage token estimation stringifying the Zod `def` tree, which overcounted tool schema tokens in the status line.
- Fixed tool-discovery indexing (`tool-index.ts`) returning empty `schemaKeys` for Zod tools, weakening BM25 ranking; keys are now recovered via wire conversion.
- Fixed the extension inspector panel rendering "(no arguments)" for Zod tools by reading properties off the converted wire schema.
- Added coverage in `tool-index.test.ts` plus new `context-usage.test.ts` and `session-dump-format.test.ts`, and a changelog entry.
2026-06-12 02:33:49 +02:00
can1357 23a1e244ba fix(coding-agent): froze detached task shimmer once blocks leave the transcript live region 2026-06-12 02:33:48 +02:00
can1357 926dfdea39 fix(coding-agent): restored side-channel IRC auto-reply for busy recipients when async execution is disabled
A subagent's send await:true to Main during a blocking task spawn was a structural deadlock: deliverIrcMessage queues mid-turn messages as step-boundary asides, but Main's next boundary requires the sender's own batch to finish, so the sender always burned the full irc.timeoutMs. Awaited sends now pass expectsReply through IrcBus.send; a mid-turn recipient with async.enabled off generates an ephemeral no-tools reply via runEphemeralTurn, records an irc:autoreply aside in its own history, and delivers it back over the bus with replyTo threading so the sender's waiter resolves.
2026-06-11 18:12:22 +02:00
can1357 8e237183d6 feat(coding-agent): unified IRC transcript rendering through shared card helper
- Added an exported `createIrcMessageCard` renderer to `tools/irc.ts` for incoming and relay IRC traffic.
- Updated IRC message rendering in `UiHelpers` to use the shared card component instead of manual transcript block construction.
- Included `replyTo` and timestamp-derived age metadata in the IRC transcript card output.
2026-06-10 23:40:38 +02:00
can1357 0f4f301a02 feat(coding-agent): made waiting job polls displaceable in the TUI
A job poll whose watched jobs are all still running and nothing was cancelled is pure 'still waiting' noise once a newer block exists. ToolExecutionComponent now detects isWaitingPollDetails on the result, leaves the block displaceable, and keeps its rows out of scrollback. EventController tracks the most recent displaceable poll and resolves it (sealing the block) on every event that proves another tool now owns the live region: a new tool call/result, a streaming arg start, an end-of-turn signal, or another job result that itself isn't displaceable. UiHelpers' transcript rebuild applies the same rule. Docs and the new job-poll-displacement test pin the behavior.
2026-06-10 17:53:07 +02:00
can1357 2b28816010 feat(coding-agent): replaced session observer with Agent Hub and inline compaction divider
The session-observer overlay is gone. The Agent Hub (ctrl+s, alt+a, or double-tap left arrow on an empty editor) presents one overlay with two views: a live registry table (status, unread irc count, current task, last activity; j/k to navigate, r to revive, x to abort/release) and per-agent chat (transcript + input line) — submitting revives a parked agent and steers it via the normal prompt path. Main is the ambient chat and stays out of the table.\n\nrenderInitialMessages no longer takes a prebuilt context: every redraw now reaches for AgentSession.buildTranscriptSessionContext() (full-history transcript with each compaction emitted inline at the point it fired, snapcompact frames re-attached on rebuild). UiHelpers drops the deferred-compaction render and the IRC autoreply branch that the new mailbox bus deprecated. CompactionSummaryMessage renders as a slim divider (── 📷 compacted · ctrl+o ──), expanding to the summary + snapcompact frame count. session-manager.buildSessionContext gains a { transcript: true } mode; an exported buildSessionContextFromFile() reads a session file without taking the writer lock so the hub chat view can tail any agent (parked or live). Theme picks up icon.camera + tool.irc symbol entries.
2026-06-10 17:51:42 +02:00
HugoAir Dev 765b972a00 feat(coding-agent): fall back to text paste when clipboard holds no image
app.clipboard.pasteImage dead-ended with a status message when the
clipboard held text, which made the chord useless on hosts that only
deliver that one keypress (VS Code's integrated terminal forwarding
Ctrl+V, Windows clipboard history via Win+V simulating Ctrl+V).

- handleImagePaste now pastes clipboard text through the editor's
  paste semantics when no image is available; an empty clipboard
  reports 'Clipboard is empty'.
- readTextFromClipboard reaches the Windows clipboard through host
  PowerShell under WSL (mirroring readImageFromClipboard), with CRLF
  normalization and UTF-8 output encoding.
- handleClipboardTextRawPaste's empty-clipboard status message moved
  to the actual empty branch (was shown after successful pastes).
- Clipboard reads are constructor-injectable for tests.

Implements proposal 1 of #1628.
2026-06-10 17:46:46 +09:00
can1357 bda3102451 ux(coding-agent): updated status glyphs and fixed extension model discovery refresh
- Added status.done and tool.* symbols to theme mappings and presets.
- Replaced generic success glyphs with contextual +/-, tool icons, and warnings.
- Mapped tool/task/job completions to status.done or status.enabled with icon overrides.
- Triggered runtime provider refresh after extension registration and warned on failure.
2026-06-08 18:28:15 +02:00
can1357 dfcdf18098 fix(coding-agent): fixed image handling during compaction and follow-up prompt submission
- Passed pending and input images into compaction queueing for steering and follow-up flows.
- Delivered queued follow-up/steer prompts with stored images via session.prompt and followUp.
- Normalized empty image arrays to undefined and cleared stale pending image state.
- Added regression tests for image-backed compaction queueing and follow-up forwarding.
2026-06-08 13:07:56 +02:00
can1357 b075fb79b6 fix(coding-agent/modes): fixed grouped read rows freezing on pending terminal read previews
- Replaced `readGroup.finalize()` with `readGroup.seal()` when ending assistant content, after tool runs, and at trailing read-group flushes to keep grouped reads in the live region until late results settle.
- Documented the fix in `packages/coding-agent/CHANGELOG.md` for grouped read rows that could freeze on terminals when results arrived after a sibling tool closed the run.
2026-06-08 06:31:21 +02:00
can1357 c7c0b67b8c feat(coding-agent/modes): reworked late diagnostics rendering in chat transcript
- Added a LateDiagnosticsMessageComponent to render delayed LSP diagnostics as grouped tree nodes in transcript messages.
- Updated message handling to use that component and honor tool-output expansion while reusing shared diagnostics formatting.
- Added tests for shared rendering output, collapsed/expanded limits, and empty diagnostics behavior.
2026-06-08 06:04:46 +02:00
can1357 02dc03d03f fix(coding-agent): updated late diagnostics to batch messages and drop stale results
- Added per-path edit versioning in `EditTool` to drop stale late diagnostics after later edits.
- Added deferred diagnostics queueing through `queueDeferredDiagnostics` and late-diagnostic yield batching.
- Updated `UiHelpers` to render late diagnostic file path and summary lines in the chat transcript.
2026-06-08 05:46:43 +02:00
can1357 1b2373425b feat(packages/coding-agent): set 30fps stream and stabilized read groups
- Reduced shimmer-driven UI animations and borders to a 30fps cadence.
- Collapsed consecutive read tool calls into one finalized transcript block.
- Tracked visible assistant content to finalize and reset read groups correctly.
- Updated running task rows to static bullet markers with shimmer-only subagent labels.
2026-06-07 08:42:51 +02:00
can1357 a78f4c6766 feat(coding-agent): enabled abort reasons to propagate and surface through streaming messages
- Added optional `reason` parameters to `Agent.abort` and `AgentSession.abort` APIs.
- Passed abort reasons through interrupt flows into underlying agent cancellation.
- Replaced hard-coded abort text with `resolveAbortLabel` for streaming and replayed messages.
- Fell back to generic `Request was aborted` text when no abort reason was provided.
2026-06-07 06:19:55 +02:00
can1357 52f7defeab fix(coding-agent-tools): fixed OSC8 links to use resolved file paths from session context
- Fixed edit/read/search/ast-edit/ast-grep outputs to resolve OSC8 links from session cwd.
- Fixed grouped-file output classification to honor headerBase and fileScope for parent path resolution.
- Fixed read and write renderers to use resolved source/resolved paths as hyperlink targets.
2026-06-07 05:37:10 +02:00
can1357 99e91138a4 feat(coding-agent): enabled component-based chat rendering with managed block lifecycles
- Added ChatBlock and ChatBlockHost with mount, finish, and dispose lifecycle callbacks.
- Added InteractiveModeContext.present and resetTranscript APIs and used them to mount/repaint blocks.
- Reworked controller rendering paths to emit command, event, extension, and selector outputs via ctx.present.
- Implemented component and container dispose hooks in tui so loaders and child blocks cleanup timers/effects.
2026-06-07 03:33:53 +02:00
can1357 4c5279b04b ux(modes): unified transcript spacing and block grouping for cleaner rendering
- Centralized transcript spacing by stripping blank edges and inserting separators.
- Removed per-component leading spacers and empty placeholders that added extra gaps.
- Introduced TranscriptBlock grouping so related outputs render as single transcript children.
- Updated transcript-related tests to validate one-row block separators and blank-line trimming.
2026-06-07 02:58:38 +02:00
can1357 6584758911 feat(copy): added quote block extraction to /copy targets
- Replaced regex code-block parser with line scanner masking fences.
- Exposed `>`-quoted runs as drillable copy targets per message.
- Added combined "all quotes" target when multiple quotes exist.
2026-06-07 02:38:06 +02:00
can1357 fde55bf927 fix(model): added bracket-affix stripping and string-keyed resolution cache
- Replaced WeakMap model cache with provider/id string keys for stable reuse.
- Returned official model ids directly when matched, before heuristics.
- Collapsed non-message token path to system prompt and tool schema totals.
2026-06-06 22:21:58 +02:00
can1357 40ed8852b5 feat(coding-agent): added app.display.reset bound to Ctrl+L
- Added `TUI.resetDisplay()` to force an immediate full-frame replay including native scrollback.
- Moved the persistent model selector default from Ctrl+L to Alt+M, preserving existing user remaps.
- Reserved Alt+M so extensions cannot shadow the model selector shortcut.
2026-06-06 17:19:57 +02:00
can1357 6810c549a7 feat(coding-agent/modes): interleaved runnable commands into /copy tree
- Emitted per-command cmd:N targets after the assistant message that issued them.
- Carried commands from text-less messages to the next visible message.
- Replaced the single trailing "last command" leaf.
2026-06-06 15:45:59 +02:00
can1357 126f4e8dcf feat(coding-agent): replaced /copy subcommands with picker tree
- Added a fullscreen /copy tree of recent assistant messages with nested code blocks and a live preview pane.
- Removed the /copy last|code|all|cmd subcommands in favor of tree selection.
- Extracted copy-target assembly into a testable util.
2026-06-06 15:15:54 +02:00
can1357 da636e3f51 feat(coding-agent): enabled clickable image and path links across chat and tools
- Added image-reference rendering to make `[Image #N]` placeholders clickable in chat.
- Added MIME-aware image blob materialization with extensioned sidecar paths.
- Added clickable path, line, and URL hyperlinks for read, search, and fetch outputs.
- Hardened OSC8 hyperlink emission with URI validation and control-byte/idempotency checks.
2026-06-05 23:38:22 +02:00
can1357 53c09417b4 feat(modes/components): added tokenized matching and selection navigation
- Added token-aware query matching so highlights mirror `HistoryStorage.search` tokenization.
- Added token highlighting, compact relative timestamps, and selected-row styling in results.
- Added PageUp/PageDown and Home/End selection shortcuts in `HistorySearchComponent`.
- Added selector matchers for `tui.select.pageUp` and `tui.select.pageDown`.
- Changed empty-result messaging to distinguish `No history yet` from `No matching history`.
- Added tests for page/home/end navigation and token-highlighting result rendering.
2026-06-04 16:02:34 +02:00
can1357 b97ddeac94 feat(tui): added inline-image budget capping live graphics
- Added `ImageBudget` keeping only N recent images live, demoting older ones to text via a full redraw plus Kitty graphics purge.
- Changed Kitty images to transmit-once + placement, so repaints re-send only tiny placement sequences instead of base64.
- Added `tui.maxInlineImages` setting (default 3, 0 disables) wired through the coding-agent renderers.
2026-06-03 23:03:54 +02:00
shoucandanghehe 569cc3442b feat(coding-agent): add assistant thinking renderers 2026-05-31 20:44:02 +08:00
can1357 346ae48b0c fix(session): prevented runtime model switches from persisting default role
- Restricted `setModel` to persist settings only when `persist: true` is passed; all runtime switches (Ctrl+P, `--model`, `/model`, model picker temp selections) no longer overwrite `modelRoles.default`.
- Changed `cycleRoleModels` to accept a direction ("forward"/"backward") instead of a `temporary` flag; both directions now use `applyRoleModel` without persisting.
- Added `persist: true` exclusively to the model picker's "Set as default" action in `SelectorController`.
- Added test suite covering persistence behavior for `setModel`, `cycleRoleModels`, and `cycleModel`.
2026-05-31 06:21:29 +02:00
roboomp 8178c9d5d9 fix(tui): respected selector navigation keybindings
Replaced raw Up/Down arrow matching in selector-style components with the shared tui.select navigation keybindings.

Added regression coverage for Ctrl+P/Ctrl+N selector remaps across the reported components.

Fixes #1535
2026-05-30 08:37:53 +00:00
can1357 7dd00c015b feat: hashline improvements for spark
- Redesigned hashline patch syntax from anchor-based (`A-B:`) to hunk-header format (`@@ A..B @@`) with unified-diff compatibility.
- Removed `autoDropPureInsertDuplicates` option and simplified apply behavior to preserve duplicated boundary and context lines.
- Changed repeat operator from `^A-B` to `&A..B` and range separator from `-` to `..` for consistency with hunk-header syntax.
- Added image resizing and dimension notes to eval tool output; improved write tool hashline header sanitation for legacy formats.
- Removed 521 lines of boundary-duplicate absorption code and simplified parser to auto-convert bare body rows and unified-diff contamination.
2026-05-28 03:06:52 +02:00
can1357 7c64576524 feat(hashline): replaced file-hash anchors with opaque snapshot-store tags
- Replaced 4-hex content-derived file hashes with 3-hex opaque tags minted by InMemorySnapshotStore, making tags session-bound pointers rather than content fingerprints.
- Removed lru-cache dependency; replaced LRU-bounded per-path rings with a flat 4096-slot global ring using a scrambled permutation to prevent LLM tag extrapolation.
- Made SnapshotStore required in Patcher (was optional); tag resolution now drives stale-anchor detection instead of recomputing hashes at apply time.
- Changed literal payload sigil from `|` to `+` and accepted `^A` shorthand for `^A-A`; added lenient recovery for bare bodies, lone `-` rows, and overlapping bare/concrete block pairs.
2026-05-28 01:00:23 +02:00
roboomp aec33a2f7b fix(tui): cleared history on session replacement
Add an explicit clear-scrollback render option for intentional transcript replacement and use it when switching, branching, reloading, or creating sessions. Regular active redraws continue to preserve terminal scrollback.

Fixes #1295
2026-05-25 18:44:22 +00:00
can1357 d6acef8b76 perf(status-line): replaced index-based token cache with message sidecar cache
- Added Symbol-keyed sidecar on each AgentMessage to memoize estimateTokens, with a cheap content fingerprint to detect in-place mutations.
- Fixed stale cache on same-length replaceMessages, post-hoc error attachment, and branch rebuild edge cases.
- Fixed usage fetch error backoff: stamped fetchedAt on failure so the 5-min TTL also gates retries during outages.
- Extracted computeNonMessageBreakdown as shared helper to prevent drift between status-line and context panel token counts.
2026-05-25 14:06:05 +02:00
can1357 8e74996513 chore: adjust tests 2026-05-25 12:22:43 +02:00
Can Bölük e4165a41e6 Merge branch 'main' into farm/bfe680ee/ctx-ui-notify-during-session-start-is-cl 2026-05-25 13:20:04 +03:00
can1357 7484299192 refactor(coding-agent/tools): removed bash fixup warning notices from command execution flow
- Removed the exported formatBashFixupNotice helper from bash command fixup utilities.
- Removed BashTool's one-time bash-fixup notice tracking and stopped emitting those notices when fixups were applied.
2026-05-25 12:18:35 +02:00
Leo Kim 4eb2919674 fix(coding-agent): incremental per-message token cache for status-line context% (avoid 1.1s freeze on long sessions)
Root cause (verified on user's environment):

- User commit `296641213` swapped status-line's context% computation from cheap `calculatePromptTokens(lastAssistantMessage.usage)` to `computeContextBreakdown(session)`, which walks EVERY message and runs native `countTokens` (~0.5 ms per message).

- The 2-second TTL cache helps for steady-state idle but every cache MISS is a full sweep.

- `updateEditorTopBorder()` is invoked on EVERY agent event (event-controller.ts:163 — `agent_start`, `delta`, `agent_end`, `tool_*`). Each delta during streaming can trigger a cache miss.

- User session has 2,312 messages → each full sweep is ~1,120 ms blocking.

- During streaming the UI freezes for ~1.1 s every ~2 s, producing the user-visible 'jittery rendering' ("버벅거림") and 'status bar disappearing' symptoms.

Fix:

`StatusLineComponent.getCachedContextBreakdown()` (renamed from `#getCachedContextBreakdown` so unit tests can exercise it directly) now uses an incremental per-message token cache that exploits the append-only nature of `session.messages`:

  1. Message tokens (the dominant cost): cached per-index. New messages are tokenized as they arrive; previously-cached messages are reused. The LAST message is always recomputed because its content may still be growing during streaming. Compaction (messages.length shrinks) resets the cache.

  2. Non-message tokens (system prompt + tools + skills): cached separately, invalidated only when a cheap inputs-identity fingerprint changes (model swap, skill toggle, tool registration). These rarely change during a session.

Required exposing three helpers from `modes/utils/context-usage.ts` (`estimateSkillsTokens`, `estimateToolSchemaTokens`, `computeNonMessageTokens`) so the status-line cache can call them directly.

Performance (2,300-message synthetic session, measured on user's M-series Mac):

  - COLD warm-up call: ~75 ms (one-time, runs at OMP startup before any streaming)

  - WARM refresh, no new message: ~0.04 ms (20 calls = 0.7 ms total)

  - WARM refresh, 1 new message: ~0.02 ms

vs. prior implementation:

  - Per cache-miss call: ~1,120 ms blocking

  - 28,000× speedup on warm-state refresh

`computeContextBreakdown` itself is untouched — `/context` slash command continues to use it, and its output matches the status-line context% for the same session state (parity preserved).

Tests: 6 new cases in `packages/coding-agent/test/status-line-context-cache.test.ts` covering cold/warm/append/compaction/non-message-invalidation/zero-messages and a perf smoke test asserting 20 warm refreshes on a 200-message session complete in <100 ms.

Full suite: 3,199 tests, 26 pre-existing failures (status-line accent / log_experiment timing-flaky / skills / github tool / workspace-tree / tool path — all unrelated and baseline-confirmed). Lint: 1 pre-existing import-order issue in `event-controller-plan-ready.test.ts` unchanged.
2026-05-25 12:11:15 +02:00
roboomp a916ae512e fix(coding-agent): preserved startup extension notifications
Keep chat notifications emitted during session_start visible after the initial transcript render rebuilds the chat container. Add regression coverage for preserving startup notifications during initial render.

Fixes #1316
2026-05-23 19:59:55 +00:00