- Send sendChatAction("typing") every 4s while agent is processing,
started on AgentStart and stopped on AgentEnd/SessionShutdown
- configPath() now checks project-local .kit/ first, then falls back
to ~/.config/kit/kit-telegram.json for cross-project portability
- ctx.Abort(): cancel current agent turn and clear queue without
injecting a new message (App.Abort + App.IsBusy methods)
- ctx.IsIdle(): check whether the agent is currently processing
- ctx.Compact(CompactConfig): trigger async context compaction with
OnComplete/OnError callbacks (App.CompactAsync method)
- ctx.SendMultimodalMessage(text, []FilePart): send text+image messages
to the agent, bridging ext.FilePart to fantasy.FilePart via RunWithFiles
- ctx.GetSessionUsage() SessionUsage: expose aggregated session token
usage and cost from the UsageTracker
New types: CompactConfig, FilePart, SessionUsage
Wired in both context setups in cmd/root.go with nil-guard defaults
in runner.go and Yaegi symbol exports in symbols.go
Move reasoning tag detection from the provider and UI layers into the agent layer. This prevents raw XML tags from leaking into text streams while ensuring structured reasoning events are emitted correctly for all callers.
- Add `baseUrl` and `apiKey` fields to CustomModelConfig (config and models packages)
- Store them on ModelInfo so they travel through the registry
- createCustomProvider resolves URL/key from model definition first,
falling back to global --provider-url / --provider-api-key
- Fix registry initialisation: call ReloadGlobalRegistry() in InitConfig()
so customModels from config are visible on startup (not just at init time)
- Include custom provider in GetLLMProviders() so custom models appear
in the /model selector
- Hide the built-in custom/custom stub from the selector when user-defined
custom models are present
- Add full-width bordered container with rounded border and primary color
- Add max height constraint to prevent terminal overflow
- Improve selection highlighting with inverted colors matching PopupList style
- Change cursor indicator from › to > for consistency
- Add separator lines between header, content, and footer
- Add footer showing current filter mode
Steering messages (Ctrl+S during agent work) now carry file attachments
just like queued messages do. Previously, pasted images were silently
dropped when steering.
Changes:
- Add SteerMessage struct with Text and Files fields
- Update steer channel from chan string to chan SteerMessage
- Add SteerWithFiles methods through the stack (UI, app, SDK)
- Update PrepareStep to include files in injected user messages
- Add full-width bordered container with rounded border and primary color
- Add max height constraint to prevent terminal overflow
- Improve selection highlighting with inverted colors matching PopupList style
- Change cursor indicator from › to > for consistency
- Use MutedBorder for tree lines and Success color for active marker
- Update search display format to match PopupList (
Herald's codeBlockWithLineNumbers() hardcodes PaddingTop(1) and
PaddingBottom(1), adding invisible blank lines with background color
above and below the code content. These padding lines occupy line
indices in the rendered item but are visually indistinguishable from
empty space, causing mouse click coordinates to map to the wrong
content line (consistently 1 row off in tool output blocks).
Strip the padding lines after CodeBlock rendering since the Compose
separator above and Figure caption below already provide adequate
visual spacing.
Add dedicated renderGrepBody function for the grep tool, replacing the
previous behavior of routing it through renderBashBody. The grep tool now:
- Shows a caption with total match count (e.g., '8 matches' or '1 match')
- Displays truncation info when matches exceed maxLsLines
- Uses consistent Figure component styling with ls, read, find, and bash tools
- Uses 'match/matches' terminology appropriate for grep results
Add dedicated renderFindBody function for the find tool, replacing the
previous behavior of routing it through renderBashBody. The find tool now:
- Shows a caption with total result count (e.g., '12 results')
- Displays truncation info when results exceed maxLsLines
- Uses consistent Figure component styling with ls, read, and bash tools
Apply the same Figure component pattern to the ls tool for consistency
with read and bash tools. The caption now appears below the directory
listing and shows the count of hidden entries when truncated.
Replace inline truncation hints and exit code labels with herald's
Figure component. Captions now appear below content and show:
- read: filename • lines X-Y of Z • offset=N to continue
- bash: N more lines • exit code N
This provides consistent visual grouping and cleaner metadata
display for tool output blocks.
- Replace detachedWithCancel (goroutine-based) with context.WithoutCancel
+ valuesContext; the old goroutine would fire immediately if the parent
was already cancelled/deadline-exceeded, causing 'failed after 0s'
- Kit.Subagent() pre-flight: if the incoming ctx is already done, reset
to context.Background() before applying the subagent timeout
- Both Subagent() error paths now return a non-nil *SubagentResult with
Elapsed set, so the tool response always shows accurate timing
- Narrow viperInitMu scope in Kit.New(): snapshot viper state + call
BuildProviderConfig under the lock, then release before SetupAgent /
MCP loading; parallel subagent spawns no longer serialise on viper I/O
- AgentSetupOptions gains ProviderConfig + scalar fields so SetupAgent
can skip viper reads when a pre-built config is supplied
- Add subagent_test.go covering the fixed context detachment behaviour
- Add fsnotify-based file watcher that auto-reloads extensions on .go
file changes in autoloaded dirs with 300ms debounce
- Add /reload-ext built-in command (alias /re) for manual reload
- Add Agent.SetExtraTools() so extension tools update on reload
instead of being baked in at agent creation time
- Run reload async via tea.Cmd to avoid prog.Send() deadlock when
extension handlers call ctx.Print() during SessionStart/Shutdown
- Wire watcher lifecycle into cmd/root.go with graceful shutdown
- Break scanner bar into individual LED segments with single-space gaps
- Center KIT text over the scanner bar (13-space indent for all lines)
- Maintain original 46-char total width for the scanner bar
- Add ValidateModelString() to ModelsRegistry for format, provider,
and model name validation with typo suggestions
- Validate model in Kit.Subagent() before expensive Kit.New() setup
- Remove silent fallback to parent model on creation failure
- Error propagates as tool result so calling agent can self-correct
- Add registry_test.go covering format, provider, and suggestion cases
- Add PopupList: generic themed popup with fuzzy search, scrolling,
keyboard navigation, and centered overlay rendering
- Refactor ModelSelectorComponent to delegate to PopupList instead
of implementing its own full-screen rendering and input handling
- Render /model selector as a centered overlay on top of the chat
view instead of replacing the entire screen
- PopupList accepts a pluggable FilterFunc for domain-specific
fuzzy matching (model selector wires its own scoring)
- Add 11 tests for PopupList covering navigation, search, selection,
cancellation, filtering, rendering, and edge cases
- Wire fantasy's OnReasoningEnd callback through the full event chain:
agent → SDK (ReasoningCompleteEvent) → app → TUI
- Freeze reasoning duration in both StreamComponent and
StreamingMessageItem as soon as reasoning ends, not when the
next assistant text chunk arrives
- Fix accent color on duration label in render.ReasoningBlock to
match the live streaming style (VeryMuted prefix + Accent duration)
- Refactor go-edit-lint to collect edited .go files during the agent
turn via OnToolResult, then run gopls + golangci-lint once in
OnAgentEnd instead of after every individual edit/write call
- Use ctx.SendMessage() to inject diagnostics as a follow-up prompt
when issues are found, replacing the old tool-result rewriting
- Show a green 'all clean' block when no issues are detected
- Fix StopReason docs in skills/kit-extensions/SKILL.md: the value is
'error' on failure, 'completed' when the LLM returns empty, or the
raw provider value (e.g. 'stop', 'end_turn') passed through — not
the previously documented 'completed'/'cancelled'/'error' enum
Remove 8 unused exports from clipboard package:
- CopyToClipboardWithMessage, IsClipboardSupported
- ToastMsg, ToastType, ToastInfo, ToastSuccess, ToastWarning, ToastError
These were remnants of a toast notification feature that was never
wired up. No callers exist anywhere in the codebase.
The getItemAndLineAtY() method was using item.Height() which returns 0
for reasoning blocks (StreamingMessageItem with role='reasoning') because
their render cache is intentionally never populated (they include a live
duration timer).
This caused all items below a reasoning block to have incorrect Y
coordinates — clicking on the reasoning text would highlight the
assistant text below it instead.
Two fixes:
1. getItemAndLineAtY() now uses renderedHeight() which calls Render()
and counts lines — matching exactly what View() does. This is the
single source of truth for item height during hit-testing.
2. StreamingMessageItem.Height() now falls back to Render(0) when
cachedRender is empty, fixing the same issue for other callers
(GotoBottom, ScrollBy, clampOffset, etc.).
- Add ForkToNewSession method to create new session with history up to target
- Add NewTreeSelectorForFork showing only user messages (flat list)
- Update performFork to create and switch to new session file
- Update /fork command description in docs and help text
Previously /fork just branched within the same session file like /tree.
Now /fork creates a completely new session file with parent_session reference,
matching Pi's behavior exactly.
When loading session history, some assistant messages contain text parts
with only whitespace (e.g., single space ' '). These were being rendered
as empty message blocks, causing extra vertical spacing in the UI.
Fix by trimming whitespace from message content before checking if it's
non-empty in renderSessionHistory().
Changes:
- Apply strings.TrimSpace() to user message content before rendering
- Apply strings.TrimSpace() to assistant message content before rendering
This prevents empty/whitespace-only message blocks from being added to
the scrollback when resuming sessions.
Extract pure rendering functions into internal/ui/render/blocks.go
to eliminate code duplication between streaming and historical
message rendering paths.
Changes:
- Create render package with UserBlock, AssistantBlock, ReasoningBlock,
SystemBlock, ErrorBlock, and ToolBlock functions
- Update MessageRenderer methods to use shared render functions
- Update StreamingMessageItem to use shared render functions
- Reduce ~77 lines of duplicated code across message_items.go and messages.go
All existing tests pass, no functional changes.
- Change border from MutedBorder to Primary for visibility
- Add full-width background styles for all popup items
- Use inverse colors for selected item (primary bg, background fg)
- Add background to scroll indicators and footer
- Add bottom margin for visual depth/shadow effect
Auto-scroll was being disabled when users manually scrolled (mouse wheel,
PgUp, etc.) but never re-enabled. Now it reactivates when submitting a
new message so the conversation view jumps to the bottom to show the
latest content.
Add a dedicated /feature-request prompt that guides users through creating
well-formed feature requests using the GitHub feature_request template.
The prompt focuses on:
- Problem-first description
- Clear motivation and use cases
- Optional proposed implementation
- Conventional commit-style titles (feat: ...)
Usage: /feature-request <description of the feature>
The file-issue prompt now references the structured GitHub issue templates
(bug_report, feature_request, documentation) and guides users to use the
--template flag with gh issue create for consistent issue formatting.
Add structured GitHub issue templates for:
- Bug reports (with reproduction steps, code, component)
- Feature requests (with motivation and proposed implementation)
- Documentation issues
Also add a /file-issue kit prompt for quickly filing issues from the TUI.
The templates enforce conventional commit-style titles and include
checklists to ensure issues are well-formed before submission.
Remove 'j' and 'k' keybindings from model, session, and tree selectors
to allow typing those characters for fuzzy filtering. Navigation now
uses only arrow keys (↑/↓) which matches the existing help text.
Fix /resume, /model, and /tree selectors to render in the alternate
screen buffer instead of terminal scrollback. All three selector
components now set AltScreen=true on their tea.View returns.
- Remove scrollbackBuf, appendScrollback(), drainScrollback() and all
call sites — the entire terminal scrollback pipeline was dead code
since the alt screen migration
- Remove StreamComponent.render(), renderCache, renderDirty,
scrollbackFlushedLines, viewContent(), and ConsumeOverflow() body —
rendering is now handled by StreamingMessageItem in the ScrollList
- Remove SetHeight and ConsumeOverflow from streamComponentIface since
height is managed by ScrollList and overflow is a no-op
- Remove redundant AltScreen/MouseMode/ReportFocus/KeyboardEnhancements
boilerplate from 6 child View() methods — parent already sets these
- Convert two orphan appendScrollback calls (extension default text,
shell command output) to proper ScrollList message items
- Update ~30 stale comments referencing tea.Println and scrollback buffer
- Render ASCII logo and startup info exclusively in the ScrollList
instead of printing to stdout/terminal scrollback
- Remove PrintStartupInfo() and move kitBanner() to ui.KitBanner()
- Fix separator spacing: use single pre-rendered item with embedded
blank lines to avoid left-border artifacts on spacing rows
- Rewrite renderSessionHistory() to populate ScrollList with proper
MessageItems instead of legacy appendScrollback() calls
- Clear m.messages on /clear, /new, and /resume so the ScrollList
resets correctly when switching sessions
- Add pendingGotoBottom flag to defer scroll-to-bottom until after
distributeHeight() recalculates the correct viewport height
- Fix pre-existing test failures: initialize scrollList in test helper,
update 5 tests from tea.Println assertions to ScrollList checks
Added a horizontal rule (────) with blank lines above and below to
visually separate the startup info from the conversation history.
The separator uses theme.Border color and spans 80 characters, providing
a clear visual break between startup messages and the chat content.
This makes it easier to distinguish where the conversation starts when
scrolling back through history.
Added AddStartupMessageToScrollList() method that renders startup info
(model, context, skills, extensions, MCP tools) and extension startup
messages as system messages in the ScrollList.
This ensures startup info is visible and scrollable in alt screen mode,
rather than being printed before BubbleTea starts and becoming hidden
when alt screen takes over.
Changes:
- AppModelOptions: Added StartupExtensionMessages field
- AppModel: Store and render startup messages in Init()
- AddStartupMessageToScrollList(): Renders startup info + extension messages
- cmd/root.go: Pass startupExtensionMessages to NewAppModel
The startup info now appears at the top of conversation history and can
be scrolled back to at any time.
Enhanced clampOffset() to detect when the viewport has scrolled past the
bottom of the content (would show empty space) and automatically reposition
to show the last line of content at the bottom of the viewport.
This prevents the 'floating' effect where multiple PgDn or scroll down
operations would push content off the top while showing blank space below.
The clamping logic:
1. Calculates total content height
2. If content fits in viewport, forces position to top
3. Otherwise, checks if remaining content < viewport height
4. If so, repositions to show exactly the last line at viewport bottom
Also updated clampOffset to use rendered height calculation (handles
non-cached items like reasoning blocks) instead of cached Height().
Root cause: GotoBottom() was calculating heights using Height() which returns
0 for non-cached items. Reasoning blocks never cache renders due to live
duration updates, causing incorrect scroll calculations during reasoning →
assistant transitions.
Fix: Calculate heights directly from rendered strings instead of relying on
cached Height() values. This ensures accurate scroll positioning for all
message types.
Changes:
- ScrollList.GotoBottom(): Render items and calculate height from string
- ScrollList.AtBottom(): Same pattern for bottom detection
- appendStreamingChunk(): Call GotoBottom() directly for existing messages
- refreshContent(): Remove redundant GotoBottom() (handled by SetItems)
Tested with 'explore this repo' prompt - autoscroll now works correctly
throughout reasoning and assistant streaming phases.
- Use max() instead of if statement for min value
- Use strings.SplitSeq for efficient iteration
- Use range over int instead of explicit loop counter
- Remove unused functions:
- InputComponent.renderPopup()
- AppModel.renderStream()
- AppModel.renderStreamingBashOutput()
- AppModel.printCompactResult()
flushStreamContent() was creating a new StyledMessageItem when tool calls
started, but we already had a StreamingMessageItem with the same content.
Now we:
- Mark the existing StreamingMessageItem as complete
- Only create a new message as fallback if no streaming item exists
This fixes text duplication when assistant messages precede tool calls.
- Limit streaming bash output to 20 lines max during live display
- Remove streaming bash item when tool completes
- Replace with truncated tool result block
- Expand background color to full terminal width with proper indentation
- Matches renderBashBody styling (lineIndent + width)
This prevents long-running commands from growing the UI forever while
still showing live output up to a reasonable height.
- Add StreamingBashOutputItem to message_items.go
- Update ToolOutputEvent handler to append chunks to bash item in ScrollList
- Remove old renderStreamingBashOutput() that broke layout
- Bash output now streams inline with messages instead of separate section
- Auto-scrolls to bottom during streaming
- Marks bash item complete on ToolResultEvent
Fixes layout breaking when bash commands produce streaming output.
- Add StreamCallback parameter to compaction.Compact() for streaming text deltas
- Update generateSummary() to use fantasy.Agent.Stream() when callback provided
- Fix compactSplitTurn() to stream both history and turn prefix summaries
- Add SDK event subscription in CompactConversation() goroutine
- Update UI to handle streaming compaction like regular assistant messages
- Compaction summaries now stream word-by-word instead of appearing all at once
Fixes issue where compaction would show incomplete context (e.g. only 'nce')
by ensuring both history summary and turn prefix are streamed to the UI.
- Move popup rendering from inline (below input) to centered overlay
- Add RenderPopupCentered() method to InputComponent
- Implement overlayContent() helper for line-by-line merging
- Popup now appears in center of screen above all content
- Prevents overflow issues when typing / at bottom of terminal