* fix(kimi-code): show clipboard image paste hint only once per image
The footer hint repeated on every terminal focus whenever an image remained in the clipboard, which became noisy. Replace the 30s time-based cooldown with a per-image gate: the hint shows once for a given image and stays quiet until the clipboard is observed empty and a new image appears.
* fix(kimi-code): suppress clipboard image hint for images present at startup
The footer hint fired during initialization whenever an image was already in the clipboard, treating it as new. The first clipboard observation after start now only establishes a baseline, so only images copied during the session trigger the hint.
* fix(kimi-code): show hint for first image copied after startup
* fix(kimi-code): make clipboard image probe non-blocking
The startup baseline probe in ClipboardImageHintController calls clipboardHasImage(), which on Linux/WSL ran wl-paste/xclip/powershell via spawnSync. The probe only reaches its first await after those synchronous calls, so a slow or wedged helper could freeze the TUI launch for up to the 1s-2s tool timeouts even when the user never focuses with an image.
Add an async runCommandAsync built on spawn with timeout-based kill, and route the Linux/WSL image detection through it so the event loop is never blocked. Keep the synchronous runCommand for the explicit paste-read path.
* chore: add changeset for non-blocking clipboard probe
* chore: simplify clipboard image hint changeset
PgUp/PgDn are often captured by terminal or tmux scrollback, so add Ctrl+U and Ctrl+D as full-page up and down alternatives, matching the existing PgUp/PgDn behavior.
When compaction is in progress and the editor has a draft, Ctrl-C now clears the draft first instead of cancelling compaction, matching the streaming behavior. The clear-text logic is shared between the compaction and streaming branches.
* fix(agent-core): surface git context failures for explore subagents
collectGitContext collapsed every git failure (spawn error, non-zero exit, timeout) into null, so explore subagents silently lost git context with no signal. Now a definitive 'not a git repository' injects an explicit unavailable signal so the subagent does not waste turns probing git history, while other failures are logged and surface as an empty block. The block is all-or-nothing so a partial snapshot (e.g. a timed-out status making a dirty tree look clean) is never shown.
* fix(agent-core): use rev-parse for branch to support git < 2.22
`git branch --show-current` was added in Git 2.22 and fails (exit 129) on older Git even in a valid repository. Because the branch probe is fatal, this dropped the whole git-context block for older-Git users. Switch to `git rev-parse --abbrev-ref HEAD`, which is supported across Git versions, and filter the `HEAD` output produced in detached-HEAD state.
* fix(agent-core): show whatever git info is available in explore context
Git probes fail in perfectly normal states — no `origin` remote, no commits yet (unborn branch), detached HEAD, older Git — so a failed probe no longer aborts the whole collection. Each probe is now best-effort: failures are logged and their section is omitted, and the block is dropped only when nothing useful was collected. Branch is read via `symbolic-ref --short HEAD`, which works in unborn repositories and on older Git; it fails in detached-HEAD state, where the Branch section is just omitted.
* fix(web): stop dismissing questions after a 60 second timeout
The server's question broker auto-expired AskUserQuestion requests after 60s, which dismissed the question even when the user simply needed more time. Remove the timeout, and the now-unused expires_at field, so a question stays pending until the user answers or explicitly dismisses it.
* fix(cli): resolve spawn EFTYPE on Windows for kimi web and /web
* chore(changeset): clarify affected Windows installation methods
---------
Co-authored-by: qer <wbxl2000@outlook.com>
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
The Write tool previously failed when a parent directory was missing, forcing a manual mkdir round trip. It now creates missing parents recursively before writing.
The model-facing skill listing silently sliced long descriptions to 250 characters with no marker, so neither the user nor the model could tell a description was cut. Truncated entries now end with an ellipsis and the truncation walks whole grapheme clusters so it never splits a surrogate pair or combining sequence.
* feat(tui): redesign /plugins as a tabbed panel
Split the /plugins manager into Installed / Official / Third-party /
Custom tabs. The Official and Third-party marketplace catalogs load
lazily, so /plugins opens instantly and keeps working offline, with
fetch failures shown inline instead of closing the panel. The tab strip
is shared with the /model provider tabs via the new renderTabStrip
helper.
* fix(tui): show untiered marketplace entries and update badges
Address Codex review feedback on the /plugins tab redesign:
- Untiered marketplace entries (no `tier` field) now appear on the
Third-party tab instead of being invisible in both marketplace tabs.
- Installed plugins whose marketplace version is newer than the local
version render an `update <local> → <latest>` badge again, and
up-to-date plugins show `installed · v<version>` — restoring the
update visibility the pre-redesign marketplace UI had.
* fix(tui): decode Space for installed-plugin toggle
In terminals that send printable keys via Kitty/CSI-u sequences (e.g. VS
Code's integrated terminal), the Space key arrives as a printable char
rather than a Key.space match, so the Installed-tab Space toggle silently
stopped working. Check both matchesKey(Key.space) and the decoded
printable char to match the MCP selector and other dialogs.
* fix(tui): open custom marketplaces on the Third-party tab
When `/plugins marketplace <source>` points at a custom catalog whose
entries omit `tier`, those entries are classified into the Third-party
tab. Opening on Official left the visible tab empty and Enter could not
install anything, unlike the old marketplace picker which showed all
entries from the supplied source. Open on Third-party when a custom
source is supplied; the default catalog still lands on Official.
* docs(plugins): drop open-url wording and hyphenate Shift-Tab
Address Codex review feedback:
- The marketplace Enter action is install/update only (open-url rows were
removed), so say "install or update" instead of "open or install" and
drop the leftover changeset sentence about setup URLs.
- Use `Shift-Tab` (hyphen) instead of `Shift+Tab` to match the docs
typography convention.
* fix(tui): keep marketplace selection valid while loading
When the Official/Third-party catalog is still loading, `entries` is empty
and pressing ↓ computed `Math.min(-1, selectedIndex + 1)` = -1. The later
Enter then read `entries[-1]` and the first install silently did nothing.
Clamp the index to 0 while there are no entries.
* fix(tui): count tab separators in tab-strip fit check
renderTabStrip declared a strip to fit whenever the sum of tab cell widths
fit, but the returned string also inserts single spaces between tabs via
`segments.join(' ')`. At widths around 43-45 columns for a four-tab strip
this declared a fit while the joined line was wider, so the trailing tab
got truncated instead of showing the `<`/`>` scroll markers. Count the
inter-tab separators in both the full-fit check and the scrolling window
fit check.
* docs(plugins): fix Kimi Datasource redirect anchor
The datasource.md redirect pointed at ./plugins.html#kimi-datasource, but
plugins.md no longer has a `## Kimi Datasource` heading — it is now
`## Official Plugins`. Update the en/zh redirect targets and fallback
links to #official-plugins / #官方插件 so the link lands on an existing
anchor.
* docs(plugins): restore concise Kimi Datasource section
The `## Official Plugins` section had replaced the original
`## Kimi Datasource` section, leaving the datasource.md redirect pointing
at a missing anchor and the Datasource capabilities/usage unreachable.
Restore a concise `## Kimi Datasource` section (intro + OAuth login +
install steps + usage) in both en and zh so the #kimi-datasource anchor
is valid again and the content is reachable.
* docs(plugins): restore Installing-from-GitHub subheading
The tab-redesign rewrite had dropped the `### Installing from GitHub` /
`### 从 GitHub 安装` subheading and its lead sentence, leaving only the
four URL forms. Restore the heading and lead sentence in both en and zh.
* docs(plugins): expand Kimi Datasource and tidy marketplace docs
- Condense the Official / Third-party / Custom tab overview and trust-badge note
- Trim the custom marketplace JSON section to the minimal id + source shape
- Move and expand the Kimi Datasource section with install, usage, and coverage
* docs(plugins): fix heading style and drop Next steps section
- Use sentence case for the Datasource headings (How to use, What you can do)
- Rename the Datasource caveat heading to Billing and limitations / 计费与限制 to avoid a duplicate Notes / 注意事项 anchor
- Remove the Next steps section, which linked back to the on-page Datasource anchor
* fix(tui): repaint plugins panel from current theme palette
The /plugins panel and MCP selector captured a palette snapshot at construction. In auto theme mode, applyResolvedAutoTheme swaps currentTheme.palette and re-renders without remounting the open panel, so it kept stale colors until closed.
Read currentTheme.palette during render instead, drop the colors opt from both components and their call sites, and add a regression test that switches palettes on a mounted panel.
* fix(tui): repaint model tab strip from current theme palette
TabbedModelSelectorComponent cached a palette snapshot in opts and used it only for the tab strip. In auto theme mode the inner model list repaints from currentTheme but the strip kept the old colors until the dialog was closed.
Read currentTheme.palette on the render path instead, drop the colors opt and its three call sites, and add a regression test that switches palettes on a mounted selector and asserts the strip repaints. This removes the last palette snapshot among editor-replacement dialogs.
Oversized AGENTS.md files are no longer silently truncated. The full
content is injected, and a warning is shown in the TUI status bar and the
web UI when the combined AGENTS.md size exceeds the recommended 32 KB.
A generic session-warnings API backs this so future warning types can be
added without changing the API surface.
* feat(web): stabilize and drag-reorder workspaces in the sidebar
* fix(web): preserve dragged workspace order after refresh
* fix(web): float session to top of its group on new message
* fix(web): align workspace drop order with insertion marker
* fix(web): allow dropping a workspace after the last item
* fix(web): use reordered workspaces for active fallback
* fix(web): honor drag order for next-workspace fallback on removal
The web app ran a client-side policy that auto-approved every approval request in auto/yolo mode, including plan reviews, sensitive file access, and other asks the daemon intentionally sends for user confirmation. The daemon already resolves auto/yolo server-side, so drop the client-side auto-approve and let those requests reach the approval UI.
* fix(web): make clipboard copy work over plain HTTP
The Clipboard API (navigator.clipboard) is only exposed in secure contexts. When the web UI is served over plain HTTP, every copy action threw synchronously and silently failed. Route all copy call sites through a helper that falls back to execCommand('copy') in insecure contexts, and surface success or failure feedback to the user.
* test: address review feedback and a flaky goal-badge test
- clipboard test: drop the jsdom environment and mock the small navigator/document surface in the default node environment, per the kimi-web "pure logic tests only" rule.
- footer-goal-badge test: assert the absence of the "[goal" badge instead of the bare "goal" substring, which could match a rotating working tip ("/goal ...") and fail depending on Date.now().
Tint the sidebar logo yellow and append the connected backend host:port to the title when the page is served by the Vite dev server, so local development tabs are easy to tell apart. Inert in production.
* feat(kimi-code): add lightweight clipboard image detection
* fix(clipboard): correct Linux X11 image detection and extract shared helpers
- Extract shared clipboard constants/helpers into clipboard-common.ts
- Fix Linux X11 branch calling macOS-only osascript
- Use native hasImage() on Linux X11, macOS, and Windows with fallbacks
- Compute xclip result once and reuse on Linux
- Add test coverage for unsupported MIME types, empty targets, failures, WSL, and native fallbacks
* fix(kimi-code): restore Wayland/WSL xclip fallback in clipboard image detection
* feat(kimi-code): add clipboard image hint controller
* fix(kimi-code): clipboard image hint focus race and cleanup
* fix(kimi-code): prevent clipboard image hint from clearing unrelated hints and stale reads
* fix(clipboard-image-hint): lifecycle issues and platform-dependent tests
* fix(kimi-code): invalidate pending clipboard hint read on stop
* feat(kimi-code): wire clipboard image hint controller into TUI
* style(kimi-code): wrap void expression in braces to fix lint warning
* style(kimi-code): prefer nullish coalescing in clipboard image detection
* chore: add changeset for clipboard image footer hint
* fix(kimi-code): let clipboard image hint observe non-consuming focus events
* fix(kimi-code): extend clipboard image hint display duration to 4 seconds
* docs: mention clipboard image footer hint in interaction guide
* chore: downgrade clipboard image hint changeset to patch
* Revert "docs: mention clipboard image footer hint in interaction guide"
This reverts commit 0fd50dcc9b.
* fix(cli): avoid treating copied Finder files as images on macOS
Filter file-like native clipboard formats in clipboardHasImage() before
calling native hasImage(), mirroring the guard already used by
readClipboardMedia(). This prevents copied Finder files from being
mis-detected as pasteable images because macOS exposes their
thumbnails as image data.
* fix(cli): align image detection with paste path on macOS and Windows
Remove osascript and PowerShell fallbacks from clipboardHasImage() on
macOS and Windows. The paste reader (readClipboardMedia()) only uses the
native clipboard module for images on those platforms, so detecting images
via methods the reader cannot consume produced misleading footer hints.
Linux fallbacks (wl-paste / xclip / PowerShell under WSL) remain because
they match the actual paste path.
* fix(tui): do not truncate inline image escape sequences
UserMessageComponent applied truncateToWidth() to every rendered line,
including the Kitty / iTerm2 inline image escape sequences produced by
ImageThumbnail. pi-tui treats the embedded base64 payload inside those
sequences as visible text, so truncation chopped the escape code and left
behind '0m...' garbage instead of the image.
Skip truncation for lines that contain an inline image protocol sequence;
the image already respects maxWidthCells via ImageThumbnail.
* fix(tui): clear stale rows when content shrinks
Enable pi-tui's setClearOnShrink so that when a tall inline image is
replaced by shorter content (e.g. after sending a message), the terminal
rows the image previously occupied are cleared. Without this, pi-tui's
differential renderer can leave behind artifacts such as duplicated input
boxes.
* chore: add changesets for inline image rendering fixes
* test(cli): stabilize pi-tui capability mocks in concurrent test runs
* test(cli): use setCapabilities instead of mocked getCapabilities
Move the 40 feature-specific components out of the flat components/ into
chat/, settings/, dialogs/, and mobile/ subdirectories, leaving 9 shared
layout components at the top level. Recompute every relative import (no
path alias in the web app), refresh the line-1 path comments, and update
the layout description in AGENTS.md.
No behavior change; typecheck / test / build / lint all pass.
* refactor(tui): extract toolbar tip constants to tui/constant/tips.ts
* feat(tui): add WORKING_TIPS subset to tip constants
* feat(tui): allow MoonLoader to render a dim tip suffix
* feat(tui): pass optional tip through ActivityPaneComponent
* feat(tui): show working tip behind composing spinner
* feat(tui): hide working tip when spinner line does not fit
* chore: add changeset for working tips
* feat(tui): guard setAvailableWidth to skip unchanged widths
* feat(tui): show working tips on moon loader and compaction
* feat(tui): use singular 'Tip:' label for loading tips
* fix(tui): keep the same loading tip across waiting/thinking/tool/composing within one turn
* feat: show contextual working tips behind loading spinners
- Add pickRandomWorkingTip() for per-step tip selection
- Cache tips by loading kind (moon/composing) so continuous tool bursts keep the same tip
- Update tip inventory with /web, /plugins, /goal, /sessions, etc.
- Add unit tests for random tip selection
* docs: update working-tips changeset summary
Move the image/video attachment state, the file-picker / paste / drag-drop
handlers, the upload machinery, the preview lightbox, and the paste-listener
+ object-URL cleanup lifecycle out of Composer into useAttachmentUpload.
The composer keeps handleSubmit / handleSteer (which read the attachments to
build the payload) and the hasUpload toolbar flag; it consumes the returned
refs and handlers directly. The destructured names match the originals so the
template bindings are unchanged. handleSubmit / handleSteer now call the
composable's clearAfterSubmit() to revoke object URLs and drop the list.
Composer.vue: 1937 -> 1787 lines. Adds unit tests for useAttachmentUpload. No
behavior change.
Move the composer's text ref, textarea ref, autosize helper, the per-session
draft load/save watchers, and the loadForEdit handle into useComposerDraft.
The returned text/textareaRef/autosize refs are passed straight through to the
history / slash / mention composables as their deps, so the rest of the
component is unchanged.
Composer.vue: 1987 -> 1937 lines. Adds unit tests for useComposerDraft. No
behavior change.
Move the @-mention menu's open/items/active/loading state, the @token
detection, debounced search, and insertion logic out of Composer into
useMentionMenu. The composer keeps the keydown orchestration (it also
juggles the slash menu and history recall) and consumes the returned refs
directly; the destructured refs are aliased back to the original names so
the rest of the component is unchanged.
Move the FileItem view type into types.ts (mirroring the FileData move) so
the .ts composable can import it without hitting the type-aware lint rule
against importing types from .vue files; MentionMenu re-exports it for the
existing .vue consumers.
Composer.vue: 2035 -> 1987 lines. Adds unit tests for useMentionMenu. No
behavior change.
Move the slash menu's open/items/active state, the filter logic, and item
selection out of Composer into useSlashMenu. The composable takes the text
ref, textarea ref, autosize, a skills getter, and the emit/history-push
callbacks as deps.
The composer keeps the keydown orchestration (arrow keys, Enter/Tab, Escape)
because it also juggles the mention menu and history recall; it consumes the
returned open/items/active refs directly and calls update/select. The
destructured refs are aliased back to the original names so the rest of the
component is unchanged.
Composer.vue: 2058 -> 2035 lines. Adds unit tests for useSlashMenu. No
behavior change.
* fix(web): persist input history so recall works after the first message
The composer has two mutually-exclusive instances: the empty-session
composer and the docked composer. The first message of a new session is
sent by the empty composer, which unmounts as soon as the first turn
appears; the docked composer then mounted with an empty in-memory history,
so ArrowUp did nothing until a second message was sent. The history was
also lost on every page reload.
Persist the history to localStorage as a single global list and re-read it
on mount. Global (not per-session) because a new session has no id until
after the first submit, so per-session keys would not line up across the
empty -> docked handoff. Caps the list at 200 entries.
Adds persistence-focused unit tests (surviving a remount, the 200-entry
cap, and a malformed stored value).
* fix(web): record slash commands in input history too
Move the history.push call ahead of the slash-command branch so that known
commands (with or without args, e.g. /goal <task> or /model) are recorded
and can be recalled with ArrowUp, instead of only plain messages. Steer
already pushed; only the submit slash path was missing it.
* fix(web): record menu-selected slash commands in history
Bare slash commands picked from the slash menu (e.g. /model, /login) go
through selectSlashCommand and emit directly, never reaching handleSubmit,
so they were not recorded even after the typed-slash fix. Push the command
name before emitting. acceptsInput commands are still recorded later by
handleSubmit together with their argument.
In the /model picker, Enter still switches the model and saves it as the
default; Alt+S now switches only for the current session without writing
to the config file.
* refactor(web): extract input-history recall into a composable
Move the shell-style up/down recall of previously sent messages out of
Composer into useInputHistory. The composable owns the history list, the
browsing cursor, and the textarea caret/selection work needed to apply a
recalled entry, taking the text ref, textarea ref, and autosize as deps.
The composer keeps the keydown orchestration (which also juggles the slash
and mention menus) and calls into the composable for push / recall / caret /
browsing state.
Composer.vue: 2104 -> 2050 lines. No behavior change.
* test(web): cover useInputHistory recall behavior
Add unit tests for the extracted input-history composable: push dedup and
empty-skip, walking backward/forward through entries, restoring the live
draft (empty and non-empty), empty-history no-op, resetBrowsing, and the
caretAtFirstLine gate.
Stop plain Tab from opening the file completion list when the autocomplete menu is closed; Tab now only accepts the selected item while the menu is open.
After Tab-completing a slash command name, reopen the menu to show its subcommands instead of falling back to file completions.
Move the beta conversation outline (proportional bubbles, viewport
indicator, hover tooltip) into a dedicated ConversationToc component.
The child owns the nav markup, the tooltip hover state, and its own
visibility (mobile / session-loading / single-turn), while the metric
derivation and scroll-driven viewport/active-turn tracking stay in the
pane because they are coupled to the scroll container.
ConversationPane.vue: 1613 -> 1422 lines. No behavior change.
* feat(tui): add ctrl+t to expand the todo list
Toggle between the truncated view and the full list; the shortcut only takes effect while the list actually overflows.
* docs(keyboard): document ctrl+t todo expand shortcut
* chore(changeset): mark todo expand shortcut as patch
* docs(agents): clarify minor vs patch in gen-changesets skill
* fix(tui): clear pending exit when toggling the todo list
Render the command in the in-flight Bash card body so it is visible while the command runs, and let Ctrl+O expand the full command before the result arrives.
* refactor(web): extract pure turn-rendering helpers from ChatPane
* chore: add changeset for chat pane helper extraction
* test(kimi-web): cover chat turn-rendering helpers
Pure-logic tests for the helpers extracted from ChatPane, focused on
assistantRenderBlocks (tool-stack grouping, interrupt/media break, single
tool) plus the formatting/boundary helpers. Doubles as a safety net
confirming the extraction preserved behavior.
The question adapter only set allow_other on the wire when the SDK item carried an otherLabel/otherDescription, but the AskUserQuestion tool never provides those fields. Web clients honor allow_other, so the free-text option silently disappeared in the web UI while the TUI (which renders it unconditionally) kept working. Set allow_other unconditionally to match the tool's 'users always have an Other option' contract.
The lowercase -c now maps to --continue, shown in help as the primary short flag. The uppercase -C still works as a hidden alias since commander does not allow two short flags on a single option.
* feat(web): remove sidebar and panel max-width limits
Make the resize handle max width optional so the web sidebar and right-side detail/preview panel can be resized beyond their previous fixed maximums.
* fix(web): keep sidebar resize handle reachable on narrow windows
Cap the restored sidebar width at a viewport-aware maximum (viewport width minus the conversation pane minimum) so a width saved on a wide display cannot push the resize handle or collapse button off-screen on a narrower window. The cap updates on resize.
* fix(web): cap preview panel to viewport and share panel-width logic
Apply the same viewport-aware maximum to the right-side detail/preview panel and extract the viewport tracking and width clamping into a shared composable used by both panels.
* fix(web): keep resize caps reactive and reserve room for the preview
Make the resize handle read its max width reactively so a viewport-derived cap keeps working as the window grows after mount. Also have the sidebar reserve the preview panel's minimum width whenever the right-side panel is open, so the conversation column can never be squeezed to zero.
* fix(web): clamp sidebar content width and ignore stale preview target
Render the Sidebar content at the clamped width so controls stay reachable when the saved width exceeds the viewport cap. Also stop reserving space for a hidden right panel by keying the sidebar preview-open check off detailTarget instead of the stale previewTarget.
* fix(web): clamp drag start to current resize cap
When the saved width exceeds the current cap (after the window narrows or a side panel opens), start the drag from the clamped width so the handle responds immediately instead of first covering an invisible delta.
* fix(web): clear detailTarget when closing side chat via /btw
The bare /btw close path called client.closeSideChat() directly, which hid the panel but left detailTarget set to 'btw', so the sidebar kept reserving room for a hidden right panel. Route it through the detail-layer close which clears detailTarget.
Read `.kimi-code/local.toml` through the persistence (local) kaos instead of
the ACP tool kaos during session bootstrap. The ACP reverse-RPC bridge needs
the session registered on the client, which is not true until `session/new`
returns, so reading through it failed with "unknown session" and blocked new
threads in ACP editors such as Zed.
Fixes#988
* fix(web): clear all per-session state when archiving or removing a session
* fix(web): clear queued and in-flight prompt state in session teardown
* fix(web): unsubscribe session before clearing its state in teardown
* fix(web): cancel pending WS subscriptions on unsubscribe