mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-24 16:44:53 +00:00
72 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d23583a469 | fix(kap-server): use HTTP error statuses for session export | ||
|
|
14530835e8 | fix(web): make session export atomic | ||
|
|
3631a0f555 | fix(web): bound session export resources | ||
|
|
357771d0a0 | feat(web): add session diagnostic export | ||
|
|
e91a616f21
|
fix(web): dedupe optimistic user message against snapshot resync (#1620)
* fix(web): dedupe optimistic user message against snapshot resync * fix(web): match snapshot messages by identity |
||
|
|
098623ed9f
|
chore(web): drop the /help, /model, /provider, and /permission slash commands (#1615)
* chore(web): drop the /help, /model, /provider, and /permission slash commands * chore: drop the changeset |
||
|
|
e223549a79
|
fix(web): make mid-turn delta offsets step-relative (#1609)
* fix(web): make mid-turn delta offsets step-relative Reset in-flight text and client stream alignment at step boundaries so resync seeds only the current step instead of duplicating prior steps. * fix(web): dedupe resync-seeded messages by normalized content The exact-JSON content signature missed duplicates when the two copies differed by thinking signature, tool progress, part boundaries, or the tool set (finished parallel tools leave running_tools). Reduce content to concatenated stream text plus sorted tool-call ids and treat a covered subset as the duplicate, merging the seed's tool progress into the existing cards before dropping it. |
||
|
|
f338fcdac4
|
fix(web): restore swarm member list after page refresh (#1589)
* fix(web): restore swarm member list after page refresh * chore: add changeset for swarm roster refresh fix |
||
|
|
2da45fc419
|
fix(web): restore the goal card after a page refresh (#1606)
* fix(web): restore the goal card after a page refresh * fix(web): assert goal endpoint URL without stringification * fix(web): skip goal recovery write when a live goal event wins the race * fix(web): track goal events with a per-session version so clears win the recovery race |
||
|
|
dc309a7dfb
|
fix(web): keep context usage live on the v2 engine (#1601) | ||
|
|
4feca6b073
|
fix(agent-core-v2): align rate-limit retries with v1 (#1598) | ||
|
|
924d5c9141
|
feat(web): dev backend switcher and engine badge for dual-engine debugging (#1592)
Add the plumbing to debug kimi-web against the v1 (server) and v2 (kap-server) engines side by side: - root dev:v1 / dev:v2 scripts; v2 boots kap-server with the multi_server flag on a fixed port so both engines can run at once - dev-proxy backend switcher: GET/POST /__kimi-dev/backend repoints the /api/v1 proxy at runtime (HTTP + WS) without a Vite restart - dev-only sidebar backend pill reading /meta's backend field, with a one-click switcher menu; Settings shows the backend engine too - re-fetch /meta on every WS (re)connect so the badge stays truthful across backend restarts and switches - strip the browser Origin header on proxied requests: v1's WS upgrade path rejected the Vite-origin vs server-Host mismatch with 403 |
||
|
|
ceb158dc54
|
feat(v2): land agent-core-v2 engine and kap-server behind experimental flag (#1441)
Some checks are pending
CI / typecheck (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Release / Desktop release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix: adapt grep tool to agent-core-v2 * fix(agent-core-v2): enrich PATH from the user's login shell at startup - port probeLoginShellPath/mergeLoginShellPath/applyLoginShellPath into _base/execEnv/loginShellPath.ts as a pure helper (no DI) - export execFileText from environmentProbe for reuse by the probe - run applyLoginShellPathFromNode concurrently with the host probe in HostEnvironmentService, mirroring kaos LocalKaos.create() Aligns agent-core-v2 with kaos |
||
|
|
b1942bd571
|
fix(web): keep the connecting splash and retry the first-load auth check (#1574)
* fix(web): retry the first-load auth check behind the connecting splash * fix(web): fall back instead of retrying deterministic 4xx auth-check failures * fix(web): show the connection error on the splash while retrying * fix(web): keep the first quick auth-check failure silent on the splash * chore: remove accidentally committed dist-web symlink * fix(web): hold onboarding until first load settles; drop unsupported 4xx auth fallback |
||
|
|
3a7aad653f
|
fix(web): finish local prompt state from session snapshot after a reconnect (#1572) | ||
|
|
f901b9e1da
|
fix(web): persist server access token across tabs and browser restarts (#1567)
* fix(web): persist server access token across tabs and browser restarts The web UI kept the server bearer credential in sessionStorage, which is tab-scoped and cleared on tab close, so users had to re-enter the token for every new tab and after every mobile tab eviction. Mirror it to localStorage instead: the token already lives on disk at <KIMI_CODE_HOME>/server.token and rides in the launch URL fragment, so browser-profile persistence does not materially widen exposure for this local tool. Existing sessionStorage copies are migrated on first boot, and a 401 (e.g. after `kimi server rotate-token`) still clears the stored credential. * fix(web): avoid clearing a fresh shared token from a stale tab localStorage is shared across tabs, so the unconditional removal let a tab holding a stale in-memory credential erase a newer token another tab had just persisted (e.g. after `kimi server rotate-token` one tab stores the fresh fragment token, then an older tab's delayed 401 wiped it). Only clear the persisted copy when it still matches the rejected credential this tab was using. * test(web): fix type-aware lint findings in server-auth tests Drop return-await on the dynamic import, brace the void arrow in the toThrow assertion, and remove a redundant String() conversion — CI runs oxlint --type-aware, which flags these where the plain local run does not. * fix(web): expire persisted server credentials after 7 days * fix(web): clear legacy session token even when localStorage is blocked setCredential ran persistCredential and the legacy sessionStorage cleanup in one try, so a localStorage.setItem failure (private mode, quota) skipped the cleanup: a stale session-scoped credential left behind was re-migrated on the next reload and 401'd into another token prompt. Split the session cleanup into its own best-effort block. |
||
|
|
d2c2c33f3e
|
feat(web): type absolute paths directly in the workspace picker (#1556)
* feat(web): type absolute paths directly in the workspace picker The add-workspace dialog's fuzzy-search box now doubles as an absolute path entry: input starting with "/" or "~" is validated live (missing path / missing parent get specific errors plus prefix-matched candidates), and a valid path live-follows the folder browser so the existing "Open this folder" button submits it. Enter accepts the first candidate or opens a valid path; Esc clears the box. The collapsed paste-path section at the bottom is removed, and the degraded (no-browse) mode reuses the same box with format-only validation. * fix(web): recognize Windows absolute paths and gate Open button in path mode Two review fixes on the workspace picker's path entry: - PATH_LIKE now also matches Windows drive (C:\x, C:/x) and UNC (\\srv\x) forms, matching node:path.isAbsolute on the daemon side. Without this, Windows users in degraded (no-browse) mode had no way to submit a path at all. Parent-dir splitting and trailing-separator trimming are now separator-aware (drive roots are preserved). - "Open this folder" is disabled while path mode has no validated target. Previously, after typing a valid prefix and then an invalid path, the button still submitted the stale followed prefix. * fix(web): submit the typed lexical root when adding a workspace in path mode fs:browse canonicalizes via realpath, but workspace/session ids are based on the lexical root. For a symlinked cwd (/tmp/project -> /private/tmp/ project on macOS), live-follow stored the resolved target in currentPath and "Open this folder" emitted it, so sessions under the typed cwd would not group under the workspace. Keep the typed normalized path for the add action and use the browse result only to populate the visible browser. * fix(web): handle workspace path input edge cases |
||
|
|
37bb4b870e
|
fix(web): keep ReadMediaFile media rendering after session resume (#1552)
Tool-role messages reached the snapshot/messages REST projection with their content flattened to text, dropping image/video/audio parts, so a ReadMediaFile result rendered as an image while streaming but fell back to a generic tool card after a reload. Pass the raw content parts through when a tool result carries media, matching the live tool.result event shape the web client already parses. |
||
|
|
f80b2eaf04
|
fix(kimi-web): single-source session status to stop duplicate turn-end notifications (#1542)
The web client received two sessionStatusChanged events per turn transition: one projected client-side from the raw turn.started/turn.ended stream, one mapped from the daemon's event.session.status_changed. After the tag scheme in #1479 keyed the completion notification by prompt id, the second (redundant) idle event lost the cached prompt id and fell back to a Date.now() tag, so every turn end popped a second "Turn finished" notification and replayed the completion sound. Stop projecting sessionStatusChanged from the raw turn stream (turn.started, turn.ended, and the in-flight snapshot seed). The daemon's event.session.status_changed is the single source of status transitions: it is computed from live daemon state (covering awaiting-approval / awaiting-question / aborted), carries the authoritative previousStatus and currentPromptId, and is deduped per real transition server-side. The turn stream keeps its content responsibilities (message finalization, usage, duration); seedInFlight keeps seeding the partially-streamed message while status comes from the snapshot's authoritative session record. |
||
|
|
04041eb998
|
fix(web): hide injected system asides in user message bubbles (#1535)
Some checks are pending
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Desktop release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(web): hide injected system asides in user message bubbles * fix(web): preserve literal <system> tags in user prompts * chore: fold duplicate web changeset into caption-hiding entry |
||
|
|
ec8dc3456c
|
fix(web): stop sending prompts into a busy turn on the web UI (#1522)
* fix(web): prevent duplicate first prompts and keep goal drives from looking idle
- Guard startSessionAndSendPrompt with a per-workspace reentry lock so a
double-click / repeated Enter during draft-session creation cannot fire
two concurrent first prompts into the same new session.
- Track goal.active in the agent event projector so turn.ended between
goal-driven continuation turns keeps the session 'running' instead of
projecting a false 'idle' that drains the local queue into a still-busy
core (turn.agent_busy).
- Show a 'starting conversation…' loading state on the empty-session
landing while the first prompt is being created and submitted.
- Persist the resolved model in startSessionAndActivateSkill so the first
skill turn on a fresh session does not fail with 'Model not set'.
* chore: add changeset for web first-prompt fixes
* fix(web): close remaining first-prompt and goal-settle gaps
- Pass the starting guard through the dock composer: draft-session
creation selects the new session before submit, which swaps the empty
composer for the dock; disabling both composers closes the last path
to a concurrent first POST. Also take the workspace lock in
startSessionAndActivateSkill / startSessionAndOpenSideChat.
- Emit the owed idle when a goal settles (blocked/paused/completed) in
the inter-turn gap after a turn.ended was projected as 'running', so
sending state, in-flight flags and queued prompts flush instead of
the session staying 'running' forever.
* style(web): fix eqeqeq lint error in first-prompt guard
* fix(web): clear owed idle when a new goal turn starts
The idle debt from a 'running' projection survived turn.started, so an
UpdateGoal('complete'|'blocked') landing mid-turn in the NEXT goal turn
synthesized an early idle. onSessionIdle could then drain queued prompts
into a core that was mid-turn again, re-opening the turn.agent_busy race
for multi-turn goals. Clear the debt on turn.started: from that point the
turn's own turn.ended carries the idle with goalActive already false.
* fix(web): make first-prompt starting state workspace-id-agnostic
isStartingFirstPrompt now reads from the lock set directly (size > 0)
instead of the current activeWorkspaceId. createDraftSession can swap
activeWorkspaceId to a registered id mid-flight; a workspace-keyed read
would then return false while the first prompt is still in the create/
select/submit window, re-enabling the composer and reopening the
duplicate first-submit race.
* revert(web): drop goal-aware idle projection from agentEventProjector
The goalActive / idleOwed shadow state machine grew through multiple
review rounds and still leaves edge cases (snapshot-seeded turns, mid-
turn goal updates). Roll it back to the simple 'turn.ended projects idle'
behavior. Goal-driven sessions can once again race a queued prompt into a
busy core; this is accepted as a known limitation to be resolved properly
in a follow-up that has the core emit an authoritative idle signal.
* chore: align changeset with actual fix scope
* test(web): update profile-patch expectation for model field
|
||
|
|
735922c291
|
feat(kimi-web): add status-aware browser notifications (#1479)
* feat(kimi-web): add approval notification storage key and i18n copy
* feat(kimi-web): add approval notification helpers and tests
* feat(kimi-web): wire approval notifications and guard completion alerts
* fix(kimi-web): extract shouldNotifyCompletion helper and add tests
* feat(kimi-web): add approval notification settings toggle
* chore(kimi-web): add changeset and tidy notification module comment
- Align approval notification tag with spec (kimi-approval-${approvalId})
- Update module header to describe all three notification kinds
* fix(kimi-web): make notifications fire reliably
- Key completion notification tags by turn (sid + promptId) and question
tags by request id, so a stale notification left in the notification
center no longer swallows every follow-up alert in the same session
- Suppress notifications only while the window is actually focused, not
merely visible (document.hasFocus() on top of visibilityState)
- Play the attention sound when a tool needs approval, matching the
completion and question sounds
* chore(kimi-web): simplify changeset
|
||
|
|
b0809ddac8
|
feat(web): prefix skill slash commands with skill: to distinguish them from built-in commands (#1492) | ||
|
|
2ad0120c2a
|
feat(web): redesign cron reminder as a message bubble (#1480)
* feat(web): redesign cron reminder as a message bubble Restyle the cron trigger notice as a right-aligned user-style message bubble that shows the scheduled prompt in full (wrapping across lines), with a small meta row beneath it for the schedule, status, job id and run time. Extract a shared MessageTime component used by both user messages and the cron reminder so the timestamp format and click-to-expand behavior stay consistent, and give the CronCreate/CronList/CronDelete tools distinct calendar icons. * refactor(web): render cron reminders only as standalone turns Remove the embedded cron block path from the web transcript projector so cron reminder fires always render through the standalone right-aligned bubble path. * chore(web): simplify cron redesign changeset |
||
|
|
16dc940834
|
fix(web): recover from stale background websocket (#1451) | ||
|
|
809a88cb34
|
fix(web): run new-session slash actions (skills, /goal, /btw) from the empty composer (#1445)
* fix(web): activate slash skills on the new-session screen The composer now lists workspace skills before a session exists, but activating one from the empty-session composer silently did nothing: activateSkill() short-circuits when there is no active session id, so the command was cleared with no turn started. Mirror the first-prompt path: when a slash skill is activated with no active session (but a workspace is active), create the session first and activate on the new session id. Extract the shared session-creation block out of startSessionAndSendPrompt into createDraftSession and add a startSessionAndActivateSkill counterpart; activateSkill now also accepts an explicit session id so a concurrent session switch can't redirect it. Unrecognized '/cmd' text still falls through to a plain-text message as before. * fix(web): persist draft modes when a new session is opened via skill The skill-activate request carries only `args`, so for a skill launched from the new-session composer createDraftSession's local plan/swarm maps never reached the daemon, and the first skill turn ran at default modes while the UI showed them enabled. Persist the draft plan/swarm modes to the new session's profile inside createDraftSession (by the new session id), and teach persistSessionProfile to take an explicit session id so a concurrent session switch during the snapshot load can't write the patch to the wrong session. Plain prompts already send planMode/swarmMode on the prompt request itself, so this is redundant but harmless on the first-prompt path. Goal mode is a one-shot flag consumed per send, not a profile field, so there is nothing to persist for it. Add coverage that the profile is persisted before activation when draft plan/swarm modes are enabled. * fix(web): await draft-mode profile POST before activating a skill The previous fix wrote the draft plan/swarm modes to the new session's profile but fire-and-forget: persistSessionProfile returned immediately after starting POST /profile, so startSessionAndActivateSkill sent :activate without waiting. Since skill activation carries only args, the daemon could process :activate (and start the turn) before applyAgentState from /profile finished, running the first skill turn at default modes while the UI showed them enabled. Make persistSessionProfile return the update promise and await it inside createDraftSession, so any origin that follows (the skill activation, or a plain prompt) only starts after the profile is applied. Existing callers still fire-and-forget via `void persistSessionProfile(...)`. Test coverage now blocks activation behind a deferred profile POST and asserts it is issued only after the profile resolves. * refactor(web): persist draft modes on the skill path only Move the awaited plan/swarm profile write out of createDraftSession and into startSessionAndActivateSkill. The create path now only builds the session and mirrors draft modes into the per-session maps, matching the old first-prompt behavior: plain prompts already send planMode/swarmMode on the prompt request itself, so a /profile write there was redundant. The profile write stays on the one path that needs it: skill activation carries only args, so the draft modes must be stored on the new session and applied before :activate is sent. * fix(web): persist draft permission and thinking for new-session skill /auto, /yolo, and /thinking on the new-session composer only update rawState (there is no session to persist to yet). A plain first prompt still honors them because submitPromptInternal sends permissionMode and thinking on the request, but skill activation carries only args, so the first skill turn was running at daemon defaults. Include permissionMode and thinking in the awaited profile patch alongside planMode/swarmMode before activating, so the skill turn matches the controls the UI shows. * fix(web): start /goal from the new-session composer createGoal() short-circuited when there was no active session, so `/goal <objective>` from the empty-session composer silently cleared and ran nothing — the same bug class as the slash-skill activation fixed earlier in this PR. Mirror startSessionAndSendPrompt: when no session exists but a workspace is active, create one first then target it with the goal profile update and the objective prompt. Send via submitPromptInternal with the explicit sid so a concurrent session switch during creation can't redirect it. Plain prompts already carry their own permissionMode / thinking / plan / swarm, so no profile fallback is needed here. * fix(web): use active-workspace fallback for empty-composer /goal On a fresh-booted empty workspace, load() never writes rawState.activeWorkspaceId (no most-recent session to anchor it). The UI still has a usable workspace via the client-wide activeWorkspaceId computed, which falls back to the first sidebar-visible workspace — but createGoal read the raw value directly and silently no-op'd when it was null. Normal first prompts and skill activations didn't hit this because App.vue passes the computed activeWorkspaceId in. Make createGoal use the same fallback so a first-session `/goal <objective>` works in empty workspaces too. * fix(web): start /btw from the new-session composer openSideChat() reads rawState.activeSessionId directly, so `/btw [<question>]` from the empty-session composer silently no-oped — it still set detailTarget to 'btw', leaving the side chat panel open but empty. Add startSessionAndOpenSideChat(workspaceId, prompt?) that creates the parent session first, then calls a new sideChat.openSideChatOn(sid, prompt) which targets the explicit parent session id (race-safe against a concurrent session switch, like the skill activation case). Route through it from the empty-composer branch in openSideChatTab. Side-chat prompts now also carry model / thinking / permissionMode / plan / swarm (via a shared sendSideChatPromptOn), so a BTW first turn matches the UI even when the parent /profile is still in flight. Unlike skill activation, this means the BTW path needs no profile fallback. Tests: startSessionAndOpenSideChat creates a session then opens BTW on the new id, works without an initial question, and is a no-op for an unknown workspace; sendSideChatPromptOn carries the runtime controls on the submitted prompt. * fix(web): preserve send queue when creating goals Switching createGoal from sendPrompt to submitPromptInternal avoided the activeSessionId race during the empty-composer create window, but it also bypassed sendPrompt's queue guard: when a goal is created against an already-active session that is running another turn, the prompt posted immediately instead of being locally queued. Restore the guard for the overwhelmingly common case (the goal still targets the active session): route through sendPrompt when activeSessionId still matches the resolved sid, which enqueues when the session is running or a prompt is already in flight. Only fall back to the explicit- session submitPromptInternal(sid) when activeSessionId moved during the create window, so a concurrent session switch can't redirect the goal prompt. The newly-created session is idle+not-in-flight in that branch, so the explicit submit does not race another turn. Add a regression test: createGoal against a running existing session enqueues instead of submits. * fix(web): coerce thinking for skill/BTW and handle goal creation failures Three follow-ups from review: - createGoal now wraps createDraftSession in a try/catch: App.vue invokes it fire-and-forget, so a rejection from session creation previously leaked as an unhandled rejection with no operation failure surfaced. Mirrors the skill / BTW / first-prompt paths that already wrap it. - startSessionAndActivateSkill coerces the draft thinking level against the new session's model before persisting the profile (via coercePromptThinking), matching what the first-prompt path submits. A value carried over from another/default model (e.g. 'max' from an effort model) would otherwise be persisted verbatim and the first skill turn would run at a level the UI wouldn't send for this model. - sendSideChatPromptOn coerces thinking against the parent session's model the same way normal prompts do (coerceThinkingForModel against the model catalog). Model catalog threaded in via a new 'models' dep on useSideChat. Same reasoning: stale rawState.thinking must not be submitted raw into a BTW first turn. * fix(web): clear staged goal mode when submitting explicit /goal When the empty composer has goal mode staged (e.g. the user runs bare `/goal`, then `/goal <objective>`) and an explicit objective is then submitted, createDraftSession copies draftModes.goalMode into goalModeBySession[sid]. createGoal then updateSession(goalObjective) for the explicit goal, and sendPrompt(trimmed) re-enters submitPromptInternal which sees goalModeBySession[sid] still set and POSTs a second goalObjective. The daemon rejects that as an existing goal, the catch in submitPromptInternal rolls back the optimistic message, and the user's objective prompt never lands. Clear the staged goalModeBySession[sid] flag right after the explicit update, since `/goal <objective>` has exactly the same effect as the flag's consumption. |
||
|
|
2374bc41c3
|
feat(kimi-web): render cron fire notices in the web chat (#1426)
* feat(kimi-web): render cron fire notices as in-transcript cards Show scheduled-reminder fires as distinct notice cards in the web chat, both live and after a reload, instead of hiding them. Each card carries a humanized schedule, a dimmed job id, and a collapsible prompt body. * fix(kimi-web): isolate cron notice prompt id and route prefixed events - Give synthesized cron messages a fresh promptId so a fire mid-turn cannot be reconciled into the optimistic user echo and hide it. - Add cron.fired to KNOWN_AGENT_CORE_TYPES so event.-prefixed frames reach the projector and render live too. * fix(kimi-web): truncate long one-line cron prompts when collapsed Slice the first line to the collapse limit with an ellipsis when a single-line prompt exceeds it, so the collapsed state actually truncates and the expand toggle is not a no-op for common one-line reminders. * fix(kimi-web): keep cron notices from reconciling into optimistic user echoes Skip optimistic-echo reconciliation for user messages whose origin is cron_job or cron_missed: their prompt text can coincide with a still- optimistic user message, and the loose content match would otherwise replace the user's turn with the cron notice instead of appending. * fix(kimi-web): keep in-turn cron injections from breaking tool results A cron injection steered into an active turn lands inside that turn's message sequence, between a tool use and its result. Treating it as a hard user-turn boundary flushed the pending assistant group, so the next tool result had no group to fold into and the tool rendered without output. Embed such in-turn cron notices as a block inside the assistant group instead, and only render a cron at a turn boundary as its own turn. * fix(kimi-web): only embed cron notices while a tool is in flight Embedding whenever a group was pending was too broad: on REST snapshots without prompt ids the whole transcript shares one group, so an idle cron fire merged into the previous assistant answer and its own reply kept going in that group. Embed only while the group has a running tool (a cron sandwiched between a tool use and its result); flush to its own turn otherwise. * fix(kimi-web): omit synthetic prompt id on cron notices The synthesized cron message carried a cron_pr_ promptId that the web client caches into promptIdBySession for Stop/abort. Because it is not a real daemon prompt id, it clobbered the active promptId, so Stop first aborted a nonexistent prompt and only recovered via the error fallback. Omit the promptId; the reducer already skips optimistic-echo reconciliation for cron-origin messages, so it is not needed for de-dup. |
||
|
|
c5e3e80041
|
feat(web): render AgentSwarm as an inline tool card (#1425)
* feat(web): render AgentSwarm as an inline tool card Replace the bottom SwarmCard footer and the messagesToTurns live-skip with one dedicated inline tool card for AgentSwarm. The card shows a phase overview plus a per-subagent accordion: live progress while it runs, parsed aggregated result once it completes (and after a refresh that has already dropped the live tasks). Refresh and resync keep member identity metadata (swarmIndex, parentToolCallId, subagentType, runInBackground) stable across skeleton task replacement in the reducer, and the .content-wrap flex layout is hardened against the overflow compression that previously displaced the footer. * fix(web): handle swarm review feedback - SwarmTool: when AgentSwarm fails before producing a structured agent_swarm_result (e.g. argument validation), render the raw tool output instead of the "waiting for subagents" placeholder so the failure cause is visible. - resolveSwarmMembers: source live members from the AppTask store keyed by parentToolCallId rather than buildSwarmGroups, which filters out single-member groups. A resume-only AgentSwarm now streams its live progress before the final result arrives. The badge counter still relies on buildSwarmGroups's filter. * fix(web): carry streamed subagent text into swarm rows Swarm subagents that stream normal assistant output accumulate it on AppTask.text (text-kind taskProgress), not outputLines. The new live member map was dropping `text`, so a still-composing subagent rendered an empty / stale row until the structured result arrived. - Add `text` to SwarmMember and thread it through buildSwarmGroups and swarmMembersByToolCall. - SwarmTool: prefer member.text for both the row activity preview and the expanded body; fall back to outputLines / summary. - Tests cover text propagation through both helpers. * fix(web): merge swarm result rows and fall back to raw output Address the two latest swarm review comments: - Rows: when a parsed agent_swarm_result coexists with live AppTasks (which the detail panel also depends on), the inline card previously only rendered the live members. Interrupted swarms can carry state="not_started" / outcome="aborted" result entries for items that never spawned a task; those rows were dropped until a refresh cleared the live tasks. Extract the row model into buildSwarmCardRows and merge result-only aborted/not-started rows with the live member rows. - Fallback: when the tool is no longer running but produced no structured result (argument validation, parser miss, or legacy legacy transcript), render the raw tool output instead of "Waiting for subagents…" so the final text / failure cause is visible to the user. * fix(web): parse swarm result subagent bodies defensively Producer writes subagent body unescaped, so a subagent that analyzes or emits an AgentSwarm snippet can include a literal "</subagent>" inside its body. The non-greedy regex treated that as the row close and truncated the body in the result-only path (post-refresh where the AppTask store is gone). Rewrite the parser to scan opening tags, then resolve each row's body as everything up to the last "</subagent>" before the next row's opening tag (or document end), preserving embedded close-tag strings. Add tests for a literal "</subagent>" within a single body and across sibling rows. * fix(web): only count top-level subagent result tags A subagent body that contains a literal `<subagent ...>` tag — for example emitting an AgentSwarm/XML snippet — was being pre-collected as another result row, splitting the real body and producing duplicate / bogus subagents after refresh where the AppTask store is gone. Rewrite parseSubagents with a depth-tracking tokenizer: scan every `<subagent ...>` / `</subagent>` token in order, push a real row frame only at the outermost level, and treat openings / closings while nested inside another body as body text. Drop the now-inaccurate "literal </subagent> without matching open" regression tests; replace with tests that verify a balanced nested snippet stays inside the parent body and does not register as a separate row. |
||
|
|
578f7d334c
|
fix(web): reconcile session from snapshot on reopen (#1409)
* fix(web): reconcile session from snapshot on reopen * fix(web): discard stale snapshot when a newer prompt races reopen * fix(web): harden reopen snapshot against first-open and optimistic-send races * fix(web): keep evicted reopens subscribed when a snapshot races * fix(web): let resync snapshots bypass the reopen staleness guard * fix(web): force-apply the snapshot after an undo * fix(web): gate session reopen on durable seq instead of updatedAt * refactor(web): always rebuild reopened sessions from a snapshot * fix(web): sharpen reopen snapshot discard and skip rebuilds mid-stream * refactor(web): unconditionally apply session snapshots, drop the staleness guard * fix(web): preserve loaded older messages when reopen snapshots apply |
||
|
|
4aacddc432
|
fix(kimi-web): clarify desktop notification title and icon (#1434) | ||
|
|
c12f30951f
|
feat(agent-core): feed AskUserQuestion answers back as question text and option labels (#1414)
* feat(agent-core): feed AskUserQuestion answers back as question text and option labels
The flattened answers record the model receives was keyed by synthesized
ids (q_0 / opt_0_1), forcing a cross-message positional lookup against the
original tool call to understand what the user picked — both unreadable in
transcripts and a real model-misreads-the-choice badcase.
- toAgentCoreResponse now takes the original broker request and translates
wire ids back to question text (keys) and option labels (values);
unknown ids are kept verbatim, missing request falls back to raw ids
- wire protocol unchanged: clients still answer with option ids; the
resolve route reads the pending request before settling it
- question texts must be unique per call and option labels unique per
question, enforced in the tool execution path (AJV cannot express the
zod refine) and mirrored on the exported schemas
- web transcript card resolves both the new label form and legacy id
transcripts; TUI and ACP paths already produced the text form
* fix(agent-core): align multi-select answer join across clients and harden question schema
- Join multi-select labels with ', ' in the server translator, matching
what the TUI reverse-RPC path already emits, so the model sees one
format regardless of which client answered
- Trim segments in the web transcript resolver before label matching:
TUI-answered multi-select transcripts (', '-joined) previously lost
their highlight to a spurious leading-space Other row
- Move the question-text/legacy-q_<i> answer lookup out of the SFC into
askUserToolParse as answerFor(), per that module's testability intent
- Require non-empty question text and option labels (.min(1)) so empty
strings are rejected by AJV at the tool boundary instead of failing
deeper in the protocol layer
* fix(agent-core): resolve option ids only within the answered question
The translator's option-id lookup was a single flat map across all
questions, so a stale or malformed response pairing one question with
another question's option id (q_1 + opt_0_0) was silently translated
into a label that was never offered for that question. Scope the lookup
to the answered question's own options; cross-question and unknown ids
now both pass through verbatim, staying diagnosable.
|
||
|
|
c5c6282f44
|
feat(web): render AskUserQuestion result as an option list (#1391)
Some checks are pending
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Desktop release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(web): render AskUserQuestion result as an option list
Parse the AskUserQuestion tool result and echo the full option list, highlighting the chosen option(s) and dimming the rest, instead of dumping raw JSON. Handles single/multi select, free-text Other answers, and the dismissed state. Answers are zipped back to the input questions by index since the input carries no ids.
* fix(web): drop the stray indent in the tool-call card body
The expanded body of every tool-call card carried a hard-coded 36px left indent (a magic number meant to align with the header name). Drop it so expanded content aligns with the header's 11px padding instead. The design-system preview page mirrored the same rule and is updated to match.
* fix(web): align markdown diff block with the design system
Restyle the local ```diff renderer in chat Markdown to match the ~/diff panel: code text keeps the normal ink colour, the +/- sign carries the add/del colour, and rows use a soft background with an inset accent bar instead of dyeing the text green/red. The header and copy button now match the standard code-block chrome (height, hover, focus ring).
* fix(web): match the markdown diff block chrome to code blocks
Make the local markdown diff renderer's shell identical to a regular code block: swap the text copy button for the same icon button, inherit the header's text-xs sizing, and keep the container / header / code-area padding, background, radius and shadow in lockstep with .code-block-container and .code-block-header. The diff-specific row tinting (soft background, inset accent bar, coloured sign) is kept since that is what makes it a diff.
* fix(web): fall back to raw output for non-answer AskUserQuestion results
Background launches and error cases return plain-text tool output (task_id/status lines, or a failure reason), not the { answers } JSON the card expects. The card used to render an empty, fully-unselected option list in those cases, hiding the task id or error. Now the card only renders the option list when the output parses as the answer payload; otherwise it shows the raw output. Addresses review feedback (P2).
* fix(web): localize AskUserQuestion result labels
The card hard-coded user-visible strings (Dismissed, answer/answers, Answered, the (+N more) summary), so Chinese-locale transcripts mixed in English. Move them into the en/zh tools locale files and read them via t(). Addresses review feedback (P2).
|
||
|
|
be7c9916b0
|
fix(web): refill attachments when editing a queued or undone message (#1357)
Some checks are pending
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Desktop release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(web): refill attachments when editing a queued or undone message
Queued prompts that carry images/video are no longer remove-only: clicking one loads its text and attachments back into the composer. Undo ("edit & resend") now restores the message's attachments too, not just its text. useAttachmentUpload gains loadAttachments, which reuses the existing fileIds (no re-upload) and fetches authenticated blob URLs for protected getFileUrl previews so the refilled thumbnails don't 401.
* fix(web): forward attachment refills through ChatDock
When the normal chat dock is mounted (non-empty conversation), bindChatDock receives the ChatDock instance, but ChatDock only exposed loadForEdit/focus — so the new loadAttachmentsForEdit fell back to a no-op and editing a queued media prompt or undoing a media message still dropped the attachments. ChatDock now forwards loadAttachmentsForEdit to the underlying Composer.
* fix(web): replace composer attachments when refilling edits
loadForEdit(text) overwrites the composer text, but loadAttachments appended to any unsent draft attachments, so a later submit sent the stale draft files together with the edited message's files. Make loadAttachments replace the current session's attachments (revoking their object URLs) so an edit/undo replaces the whole composer, mirroring loadForEdit.
* fix(web): clear stale attachments on text-only edits
When the composer already has attachments loaded (for example after editing a queued media prompt) and the user then edits a text-only queued prompt or undoes a text-only message, loadComposerForEdit skipped loadAttachmentsForEdit (the only path that clears the strip) because the new attachment list was empty/undefined, so the next submit would send the stale media with the new text. Always call loadAttachmentsForEdit(attachments ?? []) so text-only edits replace the strip with an empty set.
* fix(web): make fileId-less refilled media resendable
When editing a user turn whose media was base64-inlined by the server (no fileId), loadAttachments used to add a non-uploading chip with no fileId, which handleSubmit silently drops on resend (and an image-only edit would not submit at all). Re-upload the data URL to obtain a fileId so the attachment is actually resendable; when re-upload is unavailable, skip the chip instead of showing a misleading ready attachment. Also factor a patchAttachment helper.
* chore: prefix web changeset entry
The gen-changesets rules require web-app changelog entries to start with 'web: ' so the synced release notes classify web UI changes correctly.
* fix(web): don't dequeue a prompt when the composer is hidden
When a queued media item is clicked while the dock is showing a pending question or approval, ChatDock has no nested Composer (only rendered in its v-else), so loadComposerForEdit no-ops — but handleEditQueued still dequeued the item, losing it. Make ChatDock.loadForEdit report whether the nested composer is present, have loadComposerForEdit return success, and only dequeue when the load actually succeeds.
* fix(web): preserve URL-backed media when refilling composer
When an undone turn contains media with source.kind === 'url' (a URL but no fileId), loadAttachments used to fall through and drop it. Re-upload the URL (data: or http(s):) to obtain a fileId so URL-backed media is preserved on edit/resend; if the URL can't be fetched (CORS / non-2xx) the chip is dropped instead of shown as a misleading ready attachment.
|
||
|
|
26b90225d2
|
feat(web): support multi-level thinking effort selection (#1344)
Some checks are pending
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Desktop release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(web): support multi-level thinking effort selection Surface each model's declared reasoning efforts (support_efforts / default_effort) in the web model picker as a segmented control, replacing the on/off toggle. ThinkingLevel is now an open string, the model catalog carries the effort metadata to the web app, and the mobile settings sheet and /thinking command cycle through the available levels. * fix(web): preserve persisted thinking level before models load coerceThinkingForModel returned 'on' for any non-'off' level when the active model was still undefined (catalog not loaded yet), which rewrote a persisted/default effort like 'high' to 'on' and silently dropped the model's declared effort on later prompts. Keep the requested level as-is until loadModels() re-runs coercion with the real model. Addresses Codex P1 review on modelThinking.ts. * fix(web): coerce stale thinking level against the active model When selecting another session, the persisted thinking level can be a boolean 'on'/'off' carried over from a previous model. Deriving the pill suffix and active segment from that raw value could show "thinking: off" on an always-on model or hide the concrete effort behind a bare "thinking" tag. Coerce the level against the current model before deriving display state. Addresses Codex P2 review on Composer.vue. * fix(web): submit coerced thinking level for the prompt's target model The send paths (submitPromptInternal / steerPrompt) previously submitted rawState.thinking verbatim, so a value carried over from another session (e.g. 'max' from an effort model) was sent to a model that doesn't declare it, even though the composer already showed the coerced default. Coerce the level against the target model before submitting so the first turn runs with the level the UI displays. Addresses Codex P2 review on Composer.vue. * fix(web): coerce stale thinking in /thinking and mobile settings Two more surfaces derived their active state from the raw persisted level, which is stale when the saved level came from a different model: - /thinking slash command: indexing the raw value (e.g. 'on' from a boolean model) into an effort model's segments returned -1 and jumped to 'off' instead of advancing from the model's default effort. - Mobile settings sheet: clamping the raw prop fell back to the first segment, showing/selecting 'off' or the first effort while the composer and prompt submission coerce to the model default. Coerce the level against the active model in both places, matching the composer and send-path behavior. Addresses Codex P2 reviews on App.vue and MobileSettingsSheet.vue. |
||
|
|
ec758c747a
|
fix(web): make uploaded videos play in the chat (#1343)
* feat(media): materialize video uploads to cache and reference by path - copy TUI video placeholders into the shared cache instead of inlining the original source path - emit <video path="..."> tags so ReadMediaFile / the provider's VideoUploader owns upload behavior - apply the same cache materialization to server prompt video submissions, matching the TUI flow - update TUI unit tests and server e2e test to assert cache-path behavior * fix(web): make uploaded videos play in the chat Render the server's <video path> tag as a real video and reconcile the echoed user message so the bubble no longer shows raw markup or a duplicate. Serve file downloads with byte-range support and fetch video bytes with the bearer credential into a blob URL, since browsers cannot authorize a <video> src on their own. Also let users click an uploaded image to open it in the preview panel. * fix(web): use authenticated source for uploaded image previews openMediaPreview stored the raw getFileUrl as sourceUrl, and FilePreview renders it with a native <img> that sends no Authorization header, so the enlarge action 401'd for uploaded images. When the media carries a fileId, fetch the bytes through the authenticated API client and preview a blob URL instead, revoking it when the preview is replaced or closed. * fix(web): ignore stale authenticated media fetches AuthMedia fetches the file bytes asynchronously; when the component is reused with a new fileId before a prior fetch resolves (e.g. queued thumbnails keyed by index), the older response could still create a blob URL and show the previous file. Add a per-request sequence guard (and an unmount guard) so a stale response is discarded and its blob URL revoked instead of being applied. * fix(web): gate media path tags on file-store id shape Treating any standalone <video path="..."> text as an uploaded daemon file and stripping the basename into getFileUrl is only valid for server cache files named after the file-store id (f_…). TUI/ReadMediaFile tags use arbitrary cache names like <uuid>-<label>, and older transcripts may point at paths like /tmp/foo.mp4; those produced a broken /files/<basename> request. Only extract a fileId when the basename matches the file-store id shape, otherwise leave the raw tag as text. * fix(web): invalidate pending media preview on close Closing an uploaded-image preview before getFileBlob() resolved left previewRequestSeq untouched, so the fetch callback still passed its seq check, created a blob URL, then skipped attaching it because previewFile was already null — leaking up to the file size until another preview opened. Bump previewRequestSeq on close so the in-flight callback bails before creating the blob URL. * fix(web): defer authenticated media fetch until near viewport AuthMedia fetched the full image/video into a Blob on mount whenever a fileId was present, bypassing native loading="lazy" and preload="metadata". Opening a session with several historical large video uploads started many full downloads and held all blobs in memory even if the user never scrolled to or played them. Use an IntersectionObserver to defer the fetch until the element nears the viewport. * fix(web): revoke preview blob when leaving the file panel Switching to another detail panel only flips detailTarget and never calls closeFilePreview, so an in-flight getFileBlob could still create a blob URL after the file panel hid, and an already-shown blob URL was held until the next file preview. Check detailTarget before creating the blob URL, and reset/revoke the preview when detailTarget leaves 'file'. --------- Co-authored-by: haozhe.yang <yanghaozhe@moonshot.ai> |
||
|
|
b905dd4910
|
feat(web): redesign web UI and add design system (#1258)
* feat: redesign web ui & add design system * feat(web): add motion to redesigned UI and add changesets Animate toast enter/leave, dialog open, and workspace-list and tool-row expand/collapse instead of snapping, and add the changesets covering the web redesign. * fix(web): remove undo message exit animation * fix(web): route agent tools to AgentTool and focus dialog on open - toolRegistry matched the raw 'agent' name, but normalizeToolName folds agent/subagent into 'task', so agent calls fell through to GenericTool and lost the inline Open button for the subagent detail panel. - Dialog's focus watcher only fired on change; callers that mount with open already true (Login, Settings, ...) never moved focus into the modal. Run it immediately so initial focus and restore-on-close work. * feat(web): add logo long-press design-system easter egg Hold the sidebar logo for 3 seconds to open a dialog showing the design system page. Also trim and rebalance the redesign changesets. * fix(web): silence Sidebar v-show warning by making it single-root Nest the design-system Teleport inside the sidebar <aside> so the component has a single element root. App applies v-show to Sidebar, which needs an element root to attach to; the fragment root logged a "non-element root node" warning on every reactive update and the collapse did not take effect. * fix(web): thinking toggle, tool-group i18n, agent detail button - Show the default-thinking switch as on whenever thinking is effectively enabled (enabled !== false), matching the core resolver. - Route the grouped tool-call header and status through vue-i18n. - Hide the subagent "Open detail" button when no matching task exists (e.g. a completed foreground subagent after a refresh). * fix(web): use strict equality in agent detail button guard oxlint eqeqeq flagged the loose != null check; resolveAgentTaskId returns string | undefined, so compare with !== undefined. * chore(web): remove stray design mockups and screenshots Remove the design exploration mockups, screenshots, prompts, and notes that were accidentally committed under apps/kimi-web/design. Keep design-system.html, which the sidebar logo easter egg still references. * fix(web): keep sessions on continuation failure, treat absent thinking as on - Keep sessions already loaded from earlier pages when a continuation page fetch fails, instead of replacing the workspace with an empty page. - Treat an absent thinking config as enabled in the settings toggle, matching the core resolver (thinking is on unless explicitly disabled). * feat(web): open design-system easter egg on 10 logo clicks Replace the 3-second long-press trigger with 10 consecutive clicks on the Kimi mark; the count resets after a short idle. The long-press was unreliable because pointerleave cancelled the timer on any drift. * fix(web): treat cancelled swarm members as finished phaseForTask now lets a terminal status (completed/failed/cancelled) override a stale subagentPhase, so a cancelled swarm member no longer stays live and suppresses the finished AgentSwarm card. * feat(web): use 1s long-press for design-system easter egg Switch the logo easter egg back to a long-press, shortened to 1 second, and make it reliable this time: use pointer capture plus touch-action:none so a slight drift no longer cancels the hold. * fix(web): use plain Spinner for activity notices ActivityNotice renders for non-chat loading states (e.g. compaction), so it must use Spinner per the design-system rule that reserves MoonSpinner for the chat first-response state. * docs(web): add a11y guidance to design system * docs(web): drop stale design README link * fix(web): restore model search focus and define panel header weight - Bind the model picker's search Input to searchRef so useDialogFocus moves focus into it on open instead of the dialog's close button. - Use the defined --weight-semibold token for panel header titles (--weight-bold is not declared, so the shorthand was invalid). * fix(web): let any open dialog own Escape over the side panel Track open design-system Dialog instances in a shared count and include it in App.vue's anyOverlayOpen, so a dialog whose open state lives outside App.vue (such as the sidebar session search) captures Escape before the background side panel closes. * fix(web): base dock-work flag on filtered dock task lists Foreground subagents are excluded from the dock task lists, so a session whose only task is a foreground subagent no longer renders an empty workbar above the composer. * fix(web): create subagent task before forwarding text deltas A client that subscribes from a snapshot after subagent.spawned already fired never received the lifecycle taskCreated; the reducer only applies taskProgress to existing tasks, so assistant text deltas were dropped and the live subagent detail stayed blank. Emit taskCreated (via patchSubagent) before the text progress, mirroring the tool-progress path. * fix(web): keep plan, swarm, and goal mode toggles per session Plan, swarm, and goal modes were stored as global scalars on the web client and a single localStorage key each, so they leaked across sessions. Bind them to the active session via per-session maps, persist per session, and apply server status/events to the originating session so background sessions keep independent state. * fix(web): keep subagent detail reachable for synthesized tasks When the web client subscribes after a subagent already spawned, the synthesized subagent task has no parentToolCallId, so the Agent tool's Open-detail button was hidden and the panel would not open. Fall back to the single unmapped subagent task when resolving the detail target in both the button visibility and the panel open paths. * fix(web): keep session kebab menu from being clipped Teleport the SessionRow kebab menu to body and anchor it with fixed positioning so the collapsing group-sessions list's overflow:hidden no longer clips the dropdown. * fix(web): apply staged modes to the created session by id When starting the first prompt, apply the staged plan/swarm/goal modes to the just-created session's per-session maps by id instead of via the activeSessionId-based setters, so a session switch during the selectSession await can't drop the modes for this session or pollute another. * refactor(web): unify confirmation dialogs into a single modal Replace the inconsistent confirmation patterns (native confirm(), two-step menu arming, hand-rolled inline strips, bare buttons) with one modal ConfirmDialog driven by a global useConfirmDialog() composable, and consolidate the duplicated confirm/cancel i18n keys. * feat(web): inline message queue with separate stop button - Send button always sends/enqueues; interrupt moves to a separate red Stop button shown only while running, so the two can no longer be confused. - Queued prompts now render inline at the tail of the transcript (after the running turn) instead of behind the dock panel: click to edit, remove, drag the grip to reorder, with image thumbnails and a "next up" marker. - Remove the dock queue panel and the QueuePane component; Steer stays on Ctrl/Cmd+S. * chore: add changeset for web queue UX * feat(web): prompt reliability, sidebar menu, and composer/markdown polish - Fix spurious errors when question/approval/task actions were already complete - Add loading feedback to question and approval prompts; block double-clicks - Make the question "Other" option selectable by row click and let Enter advance/submit - Consolidate workspace section actions into an overflow menu - Tighten markdown prose line-height and block spacing - Recall input history only when the caret is at text start |
||
|
|
882cf355a9
|
fix(web): persist workspace rename and hide provider manager (#1234)
* fix(web): persist workspace rename via the daemon update API * fix(web): hide the unshipped provider manager * fix(web): fall back to a local override for derived workspace renames * fix(web): preserve workspace name override across registration |
||
|
|
bfe8e6ace3
|
fix(web): surface error when adding a workspace by path fails (#1236)
* fix(web): surface error when adding a workspace by path fails Previously, addWorkspaceByPath swallowed any error from the daemon and created a local-only workspace that could not host sessions and vanished on reload, so an invalid absolute path appeared to succeed but never took effect. Now the error is reported to the user and no fake workspace is added. * fix(web): preserve pending prompt when adding workspace fails addWorkspaceByPath now returns a boolean success signal. handleAddWorkspace only clears pendingWorkspaceSubmit and sends the prompt on success, so a daemon-rejected path no longer drops the user's pending submission; they can retry with a valid path. * fix(web): keep workspace picker open when add fails Move closing the add-workspace dialog until after a successful add. On a daemon-rejected path the picker now stays open with the user's input intact, so they can correct the path and retry; the pending submission is consumed by a successful retry or dropped only when the user explicitly closes the picker. * fix(web): show add-workspace errors inline in the picker When the daemon rejects a path, the global warning toast rendered behind the picker's backdrop (z-index 60 vs 200) and could auto-dismiss unseen. Surface the failure as an inline error inside the dialog instead, so it is visible and persists until the user retries or closes. addWorkspaceByPath now returns a boolean and leaves surfacing to the caller; the picker shows a localized inline message. |
||
|
|
a3f9cec8a9
|
fix(web): deduplicate workspaces shown in the sidebar (#1221)
Collapse registered workspaces that share a root in the daemon registry (preferring the canonical id) and in the web sidebar merge, so the same folder no longer renders as two identical, synchronously-selected entries. |
||
|
|
1dab2c2268
|
feat(web): preserve side panel and scope input history per session (#1181)
Some checks are pending
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(web): preserve open side panel across session switches * feat(web): scope composer input history to current session * fix(web): suppress side panel open animation on session switch * fix(web): preserve per-session scroll position on session switch * chore: add changeset for per-session scroll position * fix(web): preserve follow-bottom state per session * fix(web): scope composer attachments to their session * fix(web): restore saved scroll position on session switch |
||
|
|
fc3d69dbdc
|
feat(web): add completion sound and question notifications (#1179)
* feat(web): play a sound when a turn completes Synthesize a short chime when a session finishes a turn. Opt-in via Settings -> Notifications (off by default); the audio context is unlocked on the first user gesture so it also plays while the tab is backgrounded. * feat(web): notify and play a sound when a question needs an answer Reuse the existing notification/sound toggles so they also fire when the agent asks a question (the awaiting-answer state). Generalize the Settings labels to cover both cases. * fix(web): don't queue the chime on a suspended audio context A suspended AudioContext has a frozen clock, so tones scheduled on it would play stale when the context later resumes (e.g. on the next click). Only schedule the chime when the context is actually running; if it is still suspended, try to unlock it for next time and skip this one. * fix(web): gate question notifications behind explicit opt-in Question notifications surface question text, so they must not fire for users who only opted into turn-completion alerts (which default on). Split question notifications into their own persisted preference that defaults off, with a separate Settings toggle. Completion notifications keep their existing default-on behavior. * fix(web): show the question text in question notifications Lead with the actionable question text in the desktop notification body, keeping the short header as context (e.g. 'Storage: Which database?'). Previously the header alone was shown, so users had to open the tab to learn what was being asked. |
||
|
|
ff6e8bbd7c
|
fix(web): clear composer draft synchronously on send (#1163)
When the first message of an empty session is submitted, the optimistic user turn unmounts the empty-session composer before the post-flush text watcher can persist the cleared draft. The docked composer then mounts and reloads the stale text from localStorage. Clear the persisted draft synchronously in the submit / steer / slash command paths instead of relying on the text watcher, so the next mount always starts empty. |
||
|
|
81ba48f455
|
feat(web): auto-grow composer and add expandable editing mode (#1121)
* feat(web): auto-grow composer and add expandable editing mode - Grow the chat textarea with its content up to a 1/4-viewport cap. - Add an expand toggle above the send button for a taller editor; in that mode Enter inserts a newline and Cmd/Ctrl+Enter or the button sends, then the editor collapses back. * fix(web): reset expanded composer state on session change The composer instance is reused across sessions (not keyed by session id), so the expanded preference leaked into the next session's draft, leaving it stuck in the tall editor with Enter inserting newlines. Collapse back when the active session changes. * fix(web): match expand-toggle threshold to theme resting height The modern/kimi global theme overrides the composer min-height to 40px (the scoped default is 56px), so a hard-coded 56px threshold kept the expand toggle hidden until a third line under the default theme. Read the computed min-height from the element instead. * fix(web): recompute expand-toggle visibility after collapsing While expanded the computed min-height is 70vh, so a multi-line draft measured there sets isGrown=false. Collapsing did not recompute it, hiding the toggle even though the collapsed draft was still multi-line. Recompute growth after every toggle via a shared helper. The expanded state itself is unchanged and stays at 70vh until toggled or sent. * fix(web): collapse expanded editor on slash-command submit Known slash commands return early from handleSubmit, above the post-send collapse, so sending an expanded /goal, /btw, /compact, or skill command left an empty 70vh editor. Collapse in the slash-command path too. * fix(web): refocus textarea after toggling expand Clicking the expand toggle leaves focus on the button, so subsequent keystrokes do not reach the textarea and Enter would activate the button again instead of inserting a newline. Return focus to the textarea after toggling. * fix(web): refit textarea when collapsing after image-only sends When the expanded editor collapses on an image-only send, the text is already empty so the draft watcher never re-runs autosize; the textarea kept the inline height measured at 70vh and the collapsed cap left an oversized empty box. Route all send/steer collapses through a helper that re-runs autosize after the 70vh min-height is removed. --------- Co-authored-by: liruifengv <liruifeng1024@gmail.com> |
||
|
|
6194d3fad3
|
perf(web): reuse sessions reference to stop sidebar re-render on streaming deltas (#1113) | ||
|
|
d554f9ac87
|
feat(web): show full accumulated subagent progress (#1109)
* feat(web): show full accumulated subagent progress The subagent detail panel only showed the most recent 40 progress lines because the reducer truncated the accumulated output. Keep the full history so the panel reflects the entire process as it grows. * fix(web): preserve subagent progress across task updates The projector emits a taskCreated (without reducer-owned outputLines) right before every taskProgress, and the taskCreated branch replaced the task object outright, resetting outputLines to empty on each progress event. So the panel still only showed the latest chunk. Preserve the accumulated outputLines when replacing a task, and update the test to mirror the real taskCreated-before-taskProgress path. * feat(web): clean up subagent progress text Drop the noisy 'Started a step' line and summarize tool calls with a concise target (path / command / pattern) instead of the full JSON args, so the subagent progress panel shows what the subagent is actually doing. * fix(web): strip numeric index from subagent tool result names Some subagents name tool calls with a trailing index (e.g. Read_0, Bash_4), which surfaced in tool.result progress lines since the label did not resolve. Strip the index before resolving the label. * fix(web): bound non-subagent task output and subagent progress text Restore a tail cap for background bash/tool task output (which can grow without bound) while keeping subagent progress in full, and cap individual subagent tool.progress chunks so a single huge output cannot dominate the panel. * feat(web): group and fold subagent progress output Drop the noisy 'Finished' lines, group tool output under its call, and fold long output (first 5 + last 2 lines, expandable) so the subagent progress panel shows the call rhythm at a glance. |
||
|
|
18f7c34a07
|
feat(web): show edit diffs inline in tool call cards (#1103)
* feat(web): show edit diffs inline in tool call cards Render a line-by-line diff inside Edit/Write tool cards in the web chat, reusing the existing diff-line style from the changes panel. The diff is built client-side from the tool input, so no protocol or server changes are needed; the header chip now shows real +/- counts. * fix(web): extend diff line backgrounds through horizontal scroll Diff rows were only as wide as the viewport, so the add/del background stopped where long lines overflowed and the area revealed by horizontal scroll had no color. Size each row to its content so the background paints the full line. * fix(web): make all diff rows fill the longest line width Size the diff container to the longest line and have every row fill it, so add/del backgrounds form one continuous band across the whole horizontal scroll instead of stopping at each line's own length. * fix(web): resolve oxlint errors in diff line builder Use Array.at(-1) and Array.from instead of index access and new Array(length), which oxlint flags as errors. * fix(web): show tool output for failed edit/write calls When an Edit/Write call fails, render the tool output (the failure reason) instead of the requested diff, so error cards explain why no change was applied. * fix(web): fall back to tool output for replace_all and append edits A single-pair diff misrepresents replace_all edits (which change many occurrences) and from-empty diffs misrepresent append writes (which extend an existing file). Skip the synthetic diff in those cases and render the truthful tool output instead. * feat(web): open edit diffs in the right-side detail panel Move the Edit/Write diff out of the inline tool-card body into the shared right-side detail layer, opened by clicking the card (like the subagent detail panel). The panel shows the line diff when it faithfully represents the operation, otherwise the tool output (replace_all, append, errors), so failed calls explain why no change was applied. * feat(web): toggle detail panels closed when their trigger is clicked again Clicking the same Edit/Write card, file link, or git-status area a second time now collapses the right-side detail panel, matching the existing thinking / compaction / subagent toggle behavior. * fix(web): cap client-side edit diff to avoid freezing on large inputs The line-level LCS allocates an (oldLines+1) x (newLines+1) matrix and runs eagerly when an Edit/Write card renders. For large edits (e.g. a 5k x 5k block, ~25M cells) this can freeze or exhaust the chat. Cap the matrix at 1M cells and fall back to the raw tool output beyond that. * fix(web): keep the edit diff panel in sync with live tool state Store only the tool id and re-derive the diff panel payload from the live tool call in the session turns, so a panel opened while the tool is still running reflects later status/output changes (e.g. an eventual error shows the failure output instead of the stale attempted diff). * fix(web): do not render a synthetic diff for Write calls Write only reports the new content, so the client cannot tell a new file from an overwrite of an existing one. A from-empty diff showed overwrites as all additions and no deletions, which is misleading. Fall back to the tool output for every Write (append already did). * fix(web): also cap diff output rows, not just the LCS matrix The matrix-size cap alone let through asymmetric edits (e.g. one line replaced by hundreds of thousands) whose small matrix still produced a huge row array that the chip computed on render. Cap each side's line count too, so the diff output is bounded. * fix(web): keep edit/write cards expandable when the diff panel is unwired Clicking an Edit/Write card opens the right-side diff panel, but the nested ChatPane in the side chat does not wire that event, so the click became a no-op and the card could no longer expand to show its output. Gate the panel behavior behind a toolDiffPanel prop (enabled only in the main conversation); elsewhere the cards expand inline as before. * fix(web): close the tool diff panel when its target disappears When a session resync removes the tool call an open diff panel is showing, toolDiffTarget becomes null but detailTarget stayed 'toolDiff', leaving an empty aside that Escape would not close. Gate sidePanelVisible on toolDiffVisible so the panel collapses once its target is gone. |
||
|
|
f1fad7222c
|
fix: reduce streaming stutter in the web chat (#1085)
Some checks are pending
CI / test (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(web): coalesce streaming token updates into one render per frame * fix(server): disable Nagle on WebSocket socket for lower streaming latency * fix(web): flush pending streaming deltas before re-subscribing * fix(web): flush pending streaming deltas before forgetting a session |
||
|
|
884b65a040
|
fix(web): coalesce snapshot reloads on resync (#1087)
Avoid concurrent session snapshot requests when resync_required fires repeatedly, while still allowing one queued rerun after the in-flight reload settles. |
||
|
|
98d3e5b71d
|
feat(web): stabilize and drag-reorder workspaces in the sidebar (#1047)
* 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 |
||
|
|
ac1882fe28
|
feat(web): persist collapsed workspace groups to localStorage (#1045) |