mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-22 23:54:24 +00:00
143 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0303b82c3e
|
fix: align v1 protocol handshake and tool_result media passthrough (#1630)
* fix(protocol): make server_hello heartbeat_ms optional kap-server dropped the server-initiated WS heartbeat and no longer emits heartbeat_ms in server_hello, but the published v1 schema still required it, so spec-compliant clients rejected the handshake before subscribing. - mark heartbeat_ms optional in serverHelloPayloadSchema (advisory only) - add a ws-control test for a server_hello without heartbeat_ms - align kimi-web WireServerHello and the server-e2e handshake assertion * fix(agent-core-v2): pass media parts through tool_result projection - keep raw kosong content-part array for tool results carrying image/video/audio parts instead of flattening to text - restore ReadMediaFile media rendering after session reload/resume * chore: add changeset for optional server_hello heartbeat_ms * docs(agent-core-v2): move tool_result media rationale to module header Per the agent-core-v2 comment conventions, comments live solely in the top-of-file block — move the media-passthrough rationale out of the buildProtocolContent JSDoc into the module header. |
||
|
|
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 |
||
|
|
4ec2e7fab1
|
feat(server): default to kap-server and remove the v1 server package (#1617)
* feat(server): default to kap-server and remove the v1 server package - kimi server run / kimi web now boot kap-server (agent-core-v2 engine) unconditionally; the KIMI_CODE_EXPERIMENTAL_FLAG gate on the server path is gone (the kimi -p print-mode gate stays) - move the OS service manager (svc: launchd/systemd/schtasks) from packages/server into packages/kap-server and export it there - repoint the CLI server subcommands, tests, and dev scripts at kap-server; relabel the web dev backend presets default/multi - delete packages/server and update workspace bookkeeping (flake.nix, pnpm-lock.yaml, changeset ignore docs, AGENTS.md, agent-core-dev skill) * test(server-e2e): remove scenarios that depend on v1 debug endpoints Scenarios 04-stateless-controls, 10-prompt-queue-steer and 12-send-and-cancel assert through the /api/v1/debug/prompts/* introspection routes, which only the deleted v1 server mounted — kap-server's --debug-endpoints is a documented no-op, so these scenarios can only 404 now. The vitest e2e files using the same surface already skip when it is absent. |
||
|
|
32cbd0cf61
|
fix(web): let workspace picker fit its content (#1611)
* fix(web): size workspace picker from full content * chore: add web workspace picker changeset * refactor(web): use intrinsic workspace picker sizing * fix(web): cap workspace picker to conversation pane |
||
|
|
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 |
||
|
|
49a8c84a49
|
feat(web): cap markdown table column width at 700px (#1587)
* feat(web): cap markdown table column width at 700px * fix(web): clamp table column width through the cell content box |
||
|
|
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 |
||
|
|
6fc1deb453
|
fix(web): wide markdown tables scroll internally and break out on desktop (#1577)
* fix(web): scroll wide markdown tables inside their own wrapper * feat(web): break wide markdown tables out of the reading column * fix(web): sample the full TOC rail for wide-table occlusion * refactor(web): use rect overlap for the TOC occlusion check |
||
|
|
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) | ||
|
|
9d96b538bf
|
fix(web): scroll wide markdown tables inside their own wrapper (#1575) | ||
|
|
5a208cb041
|
fix(web): auto-enable default thinking effort when switching to an effort-capable model (#1475)
Some checks are pending
CI / typecheck (push) Waiting to run
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
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): auto-enable default thinking effort when switching to an effort-capable model * chore: add changeset * fix(web): preserve thinking off when reselecting current model |
||
|
|
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. |
||
|
|
1d3dba5683
|
fix(web): match current model by id in model picker dropdown (#1565)
Model names and display names can collide across providers, so the composer dropdown's checkmark matched by name and lit up every same-named entry. Resolve the current model through its unique id everywhere (dropdown check, thinking controls, status resolution). |
||
|
|
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 |
||
|
|
c98238699c
|
fix(web): avoid repeated session list scans in sidebar computeds (#1563) | ||
|
|
264525eb51
|
fix(web): prevent chat scroll yank while browsing history (#1553)
* fix(web): preserve scroll position while loading older messages * fix(web): prevent chat scroll yank while browsing history * fix(web): stop stale auto-follow writes * fix(web): anchor long tool histories * fix(web): stabilize scroll anchor cancellation |
||
|
|
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
|
||
|
|
170ae44205
|
style(web): polish the session sidebar (#1519)
* feat(web): use sidebar fold/unfold icons for sidebar toggle * feat(web): move settings entry to a sidebar footer row * feat(web): fully collapse sidebar with animated width transition * feat(web): redesign sidebar colors, spacing and macos desktop chrome * feat(desktop): center traffic lights on the 48px header row * fix(web): restore webkit thin scrollbars and unify sidebar icon sizes * feat(web): add Kbd keycap component and justify sidebar search shortcut * style(web): rework sidebar palette and pin a resident sidebar toggle * fix(desktop): sync window appearance with web UI theme so dimmed traffic lights stay visible * feat(web): adopt Kimi design icons in the sidebar via a local icon collection * style(web): mute workspace group title color in the sidebar * style(web): refine sidebar typography, unify shortcut keycaps, float workspace row actions * style(web): cap sidebar draggable width at 480px * style(web): derive sidebar row height from type and padding, float the kebab * chore: add changeset for sidebar UI polish * fix(nix): update pnpmDeps hash * style(web): put the sidebar collapse button inside the header on non-mac * fix(nix): update pnpmDeps hash |
||
|
|
ad30a1c632
|
style(web): polish web UI typography and controls (#1502)
* style(web): polish sidebar and tool typography - Use UI font and medium weight for sidebar and composer controls - Add reusable shortcut and tool output blocks - Cap long tool output at 50 lines with a scrollbar - Update sidebar show-more copy and muted styling * style(web): refine workspace picker sizing * style(web): align composer mode menus * style(web): tune list and question typography * style(web): reuse shortcut keys in approvals * style(web): size workspace picker from content * feat(web): localize chat status labels * style(web): refine composer toolbar controls * style(web): use complete Inter variable font * style(web): tune sidebar workspace typography * style(web): polish composer and workspace picker * style(web): refine markdown and thinking typography * chore: add web UI polish changeset * fix(web): pin Inter package for Nix build * style(web): polish goal tool calls * style: polish goal mode display * fix: layer latest message pill below menus * style: align goal strip content |
||
|
|
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) | ||
|
|
0cc9831a2f
|
fix(web): composer model switch also updates global default model (#1491)
* fix(web): composer model switch also updates global default model
The composer model switcher still switches the active session's model via
POST /sessions/{id}/profile (awaited, so the model pill reflects the result),
and additionally fires POST /api/v1/config with { default_model } as a
fire-and-forget side effect so new sessions inherit the chosen default. The
config request is skipped when the model already matches the current default.
* fix(web): route ModelPicker overlay selection through the default-model update
The overlay opened from the composer's "More models" row (and /model) is a
continuation of the same switch flow, so its selection now also bumps the
global default model instead of only switching the active session.
* fix(web): only persist the default model after a confirmed session switch
setModel now returns whether the switch was accepted (true for the draft
path), so the composer flow no longer writes a stale or invalid model alias
into the global config when the session-level switch failed and rolled back.
|
||
|
|
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 |
||
|
|
b30a45efec
|
feat(web): support Enter key to confirm archive and other dialogs (#1490) | ||
|
|
11c6a37ce0
|
fix(web): dismiss stale connection error toast after ws reconnect (#1474) | ||
|
|
ee385456d0
|
refactor(web): migrate icons to unplugin-icons (#1467)
* refactor(web): migrate icons to unplugin-icons Replace the hand-written gen-icon-data.mjs + @iconify/utils runtime rendering with unplugin-icons build-time imports. The public API (<Icon name>, iconSvg, IconName, SIZE_PX, NAME_TO_REMIX) is unchanged; 127+ call sites are untouched. - add unplugin-icons@^23.0.0 (devDep) + Vite Icons() plugin (compiler: vue3) - rewrite src/lib/icons.ts: static ~icons/ri/* imports (component + ?raw) for 56 distinct Remix icons across 59 IconName entries - Icon.vue renders <component :is> with unknown-name fallback - append ICON_GROUPS export for DesignSystemView catalog - DesignSystemView: v-for catalog, remove legacy-script references - delete gen-icon-data.mjs, gen-icon-catalog.mjs, icon-data.ts, gen:icons script - remove @iconify/vue and @iconify/utils from dependencies; move @iconify-json/ri to devDependencies - drop Icon.vue from check-style ICON_EXEMPT (no hand-written <svg>) * refactor(web): drop unused NAME_TO_REMIX icon mapping NAME_TO_REMIX was a Record<IconName, string> table introduced to map internal icon names to their ri: ids. After the unplugin-icons migration it has no production consumers — only icons.test.ts imported it (for two drift tests) and DesignSystemView mentioned it in descriptive copy. The ICONS table already conveys the same ri: id via each entry's paired component + ?raw imports (e.g. RiFolderOpenLine / RawFolderOpenLine). - remove NAME_TO_REMIX const from src/lib/icons.ts (-63 lines) - remove NAME_TO_REMIX import + describe block from icons.test.ts - update DesignSystemView §02 copy: describe the import-pair idiom and stop claiming ICON_GROUPS is sourced from NAME_TO_REMIX * chore: add changeset for web icon migration * chore(nix): bump pnpmDeps hash for unplugin-icons Adding unplugin-icons changed pnpm-lock.yaml, so the fixed-output pnpmDeps derivation hash is stale. Update to the hash reported by the Nix Build CI run. |
||
|
|
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. |
||
|
|
9b18dc46cf
|
fix(kimi-web): shrink collapsed thinking block on mobile (#1461) | ||
|
|
903e8ed93a
|
fix(web): clarify archive session confirmation copy (#1428)
* fix(web): clarify archive session confirmation copy * fix(web): drop delete mention from archive confirmation copy * feat(web): add archived-sessions restore entry to mobile settings Mobile used a separate settings bottom-sheet with no archived-sessions restore surface, so the archive confirmation pointing users to Settings to restore was untrue on mobile. Add an Archived sessions sub-view to the mobile settings sheet with search, sort and restore, mirroring the desktop Settings tab. * fix(web): refresh mobile archived list and use Input primitive Refresh the archived-sessions list each time the mobile sub-view opens so sessions archived from the mobile switcher show up without remounting the sheet, and replace the hand-rolled search input plus inline svg with the shared Input primitive. * fix(web): use Button primitive for mobile archived restore Render the mobile Archived sessions restore action with the shared Button primitive instead of a hand-rolled button and scoped CSS, matching the desktop archived list and the app's design-system rule. |
||
|
|
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. |
||
|
|
ac5b5e4cbf
|
fix(kimi-web): keep tool components from jumping on expand or collapse (#1433)
* fix(kimi-web): keep tool components from jumping on expand or collapse Also show the scroll-to-bottom button whenever scrolled up, and render a fallback icon for tools without a dedicated glyph. * fix(kimi-web): preserve bottom follow during content-only resizes Late-loading media can grow after scrollKey has run; keep chasing the bottom on content growth, and only suppress follow during the pinned expand/collapse window. |
||
|
|
d86fa38e11
|
fix(kimi-web): disable text hyphenation and code ligatures (#1438)
Set hyphens: none (with the -webkit- prefix) on body so chat and markdown text never gain a hyphen glyph at a line break; components still control where lines wrap via word-break/overflow-wrap. Disable the ligatures that coding fonts enable by default (liga/calt/ss01) on native code elements so code renders literally, e.g. != stays as two characters. |
||
|
|
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. |
||
|
|
a5fbcb75b4
|
fix(kimi-web): keep composer toolbar usable on narrow windows (#1436)
The desktop composer toolbar renders every control on one row and relies on overflow:hidden to fit, so between the mobile breakpoint and a wide window it clipped its own content. Shed secondary ink below 980px (the context readout moves into the ring tooltip, the model name truncates earlier, permission is capped) and keep the context ring visible on phones too, instead of sending it to the settings sheet. |
||
|
|
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) | ||
|
|
6c0ce09414
|
feat(server): support restoring and listing archived sessions (#1073)
* feat(server): support restoring and listing archived sessions - add a `:restore` session action that clears the archived flag in state.json and returns the restored session - add an `archived_only` list query param, mutually exclusive with `include_archive`, that post-filters to archived sessions - keep the implementation in the server layer as a temporary measure until agent-core exposes restore natively * fix(server): paginate archived-only sessions before response * feat(web): add archived sessions page in Settings Browse, search, filter by workspace, sort, and restore archived sessions from a new Archived tab in Settings, backed by the server archived_only list and :restore action. * fix(web): keep archived Load more visible when a page filters to empty When a search or workspace filter empties the loaded archived page, the Load more button was hidden inside the non-empty branch, so users could not fetch older pages to find a match. Move the button out so it stays available whenever more archived pages exist. * fix(server): preserve after_id bound while draining archived pages Draining an archived_only request that starts from after_id would switch to before_id and cross the pivot, reintroducing the pivot and older sessions. Take a single filtered page for after_id instead of draining past the lower bound. * fix(server): drain archived_only within the after_id bound An archived_only request starting from after_id now keeps paging toward older sessions until it reaches the pivot, instead of treating the first page as exhaustive. The loop stops as soon as it encounters the pivot session itself, so it never reintroduces the pivot or anything older. * feat(web): drain all archived pages for global search and sort When the user searches, sorts, or changes the workspace filter in the Archived settings page, fetch every remaining archived page first so the client-side filter and sort run over the full set rather than only the pages loaded so far. * refactor(web): load all archived sessions upfront instead of paginating Fetch every archived session once when the Archived settings tab opens and drop frontend pagination entirely. Search, sort and workspace filter now run over the full set, removing the empty-page and cursor bookkeeping that previously caused bugs. --------- Co-authored-by: qer <wbxl2000@outlook.com> |
||
|
|
fa6d198b01
|
fix(kimi-web): fix input caret and todo strikethrough (#1423)
* fix(kimi-web): keep composer caret visible when input is empty The composer textarea coloured its empty state with `--faint`, and with no caret-color set the caret inherited that faint colour and nearly vanished until the first character was typed. Pin the caret to --color-text so it stays readable regardless of the placeholder state. * fix(kimi-web): use currentColor for done todo strikethrough TodoCard pinned the done-state strikethrough to --color-line-strong, which is lighter than the faint text in light theme and darker in dark theme, so the line looked washed-out and broken against the text. Drop the override so the line inherits the text colour, matching TasksPane and the design-system examples. * fix(kimi-web): do not reserve width for hidden workspace row actions The workspace header's more/add buttons were hidden with opacity: 0, which kept them in the flex layout and permanently reserved ~60px on the right, so the workspace name (flex:1) truncated long before the row was full. Hide them with display: none and restore display: inline-flex on hover/focus/open, so the name fills the row and only truncates once the buttons actually appear. The same actions remain reachable via the right-click menu and the section kebab, so removing them from the tab order when hidden is acceptable. * fix(kimi-web): keep workspace header row height stable on hover Lock .gh-top to the sm IconButton height (26px) so revealing the hover actions no longer grows the row and nudges the path line and the groups below it. Follow-up to the display: none change: that freed the horizontal space but let the row height collapse when the buttons were hidden. * chore: add changeset for kimi-web UI fixes * fix(kimi-web): restore keyboard access to workspace actions Revert the display: none change (and the min-height that accompanied it) for the workspace row's hover actions, going back to opacity-hidden buttons. The display: none approach removed the buttons from the tab order, and the 'create in workspace' add button has no keyboard-accessible alternative in the right-click or section menus, so keyboard users lost that action. Restoring opacity keeps the buttons focusable again, at the cost of the workspace name truncating a little earlier to reserve their space. Addresses the P2 review on PR #1423. |
||
|
|
e95fc83cc2
|
fix: honor web font size setting (#1394)
* fix: honor web font size setting * fix: scale web font-size dependents --------- Co-authored-by: liruifengv <liruifeng1024@gmail.com> |
||
|
|
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.
|