Commit graph

102 commits

Author SHA1 Message Date
qer
4ccfc26594 fix: make undo plan-aware 2026-07-02 21:53:32 +08:00
Kai
8ac337a2b2
fix(agent-core): harden strict-provider wire compliance so malformed history can't brick a session (#1241)
* feat(agent-core): rework compaction to keep only user prompts and summary

* refactor(agent-core): rewrite compaction summary as first-person handoff

Rework the full-compaction summary to read as the agent's own continuing
notes instead of a third-party report:

- compaction-instruction.md: free-form first-person continuation that
  preserves exact commands, paths and outcomes, states the precise next
  action, and flags claimed-but-unverified work rather than trusting it.
- compaction-summary-prefix.md: skeptical "your own working notes"
  framing; drop the collaborative third-party prefix.
- system.md: add compaction-awareness guidance so the model continues
  naturally from a summary and re-checks any reported "done".
- Rename the compaction helpers module to handoff.ts.

Update tests and regenerate snapshots for the new prompt text, and fill
in contextSummary in the restored-compaction replay expectations.

* fix(agent-core): count image/audio/video parts in token estimation

estimateTokensForContentPart returned 0 for image_url/audio_url/video_url,
so auto-compaction triggers, the overflow-shrink budget, the kept-user
budget, and the reported context size all went blind to media — a
media-heavy session could overflow the model window while the estimate
reported a near-empty context. Media parts now carry a fixed estimate
(MEDIA_TOKEN_ESTIMATE), and the content-part switch is exhaustive so a new
ContentPart kind must declare its estimate rather than silently count as
zero.

* feat(agent-core): re-surface active background tasks after compaction

Folding the live context to [recent user prompts, summary] drops the
messages that started background tasks and their status updates, so the
model could forget a task is still running and spawn a duplicate.
injectAfterCompaction now appends a system-reminder listing active
background tasks (with guidance to use TaskOutput/TaskList/TaskStop
instead of re-spawning). It runs only post-compaction and carries an
injection origin, so the next compaction drops and rebuilds it rather
than stacking copies; the all-user-role post-compaction shape is
preserved (no tool-pairing reintroduced).

* test(agent-core): add compaction scenario guards and risk probes

Adds compaction-scenarios.test.ts driving the real Agent/ContextMemory/
FullCompaction machinery:

- A guard test locking in that repeated compaction folds the prior summary
  into the new one instead of stacking two summaries.
- Seven `it.fails` probes that executably reproduce known, currently-accepted
  edge-case defects so the suite stays green while documenting each one
  precisely; any of them will flip red (forcing removal of `.fails`) the day
  the behavior is fixed. They cover: assistant/tool appended during an
  in-flight summarizer call being dropped; unbounded shrink on empty
  summaries; the fixed 20k kept-user budget overflowing a small model window;
  a tool result orphaned when compaction starts mid-exchange; legacy
  compaction records dropping their verbatim tail on replay; micro-compaction
  clearing recent tool results in an overflow-shrunk suffix; and media being
  discarded when the oldest kept user message is truncated.

* fix(agent-core): repair tool_use/tool_result adjacency in projected context

A tool call and its result can end up non-adjacent in history — a
background-task notification or flushed steer lands between them, or an
interrupted/nested step delays the result — which strict providers reject
with HTTP 400. The projector now moves each tool_use's result up to
immediately follow it (projection-time only; the stored history is
untouched), and full compaction projects its summarizer input with a
synthetic result for any still-open call so the summary request stays
well-formed. Micro-compaction only surfaced this latent ordering by busting
the prompt cache, so it now defaults off.

Includes projector adjacency regression tests, a context-level integration
test, and a compaction synthesize-missing guard; the prior "keeps an
unresolved tool exchange out of the compaction prompt" test is updated to
the now-well-formed (synthetic-result) behavior.

* fix(agent-core): preserve the verbatim tail when restoring legacy compactions

A pre-rework `context.apply_compaction` record used
`[summary, ...history.slice(compactedCount)]` semantics and kept a verbatim
recent tail, but it has no `keptUserMessageCount`. The reworked applyCompaction
re-folded such records into the all-user shape, dropping the recent
assistant/tool tail — so resuming a session compacted by an older version
silently lost its most recent context.

On restore of such a record (gated on records.restoring, no keptUserMessageCount,
and compactedCount < history length) reproduce the old shape instead. The
forward/live path is unchanged; the projector's tool-adjacency repair keeps the
restored tail well-formed, and compaction only runs at clean step boundaries so
the tail has no open exchange. The legacy-tail probe now passes as a regression
guard via the real restore path.

* fix(agent-core): align legacy compaction foldedLength with live restore

The transcript reducer re-derived foldedLength for pre-rework
context.apply_compaction records (no keptUserMessageCount) using the new
kept-user+summary rule, but ContextMemory's restore now reproduces the legacy
[summary, ...history.slice(compactedCount)] shape for those records. The two
diverged for legacy sessions, so MessageService's foldedLength-vs-live-history
comparison could mis-handle GET /messages (miss or misorder recent output).

The reducer now mirrors the live legacy fold: when compactedCount is below the
pre-compaction length it computes 1 + (length - compactedCount); otherwise it
falls back to the kept-user derivation. The MessageService transcript test's
fixture is corrected to a new-format record, matching its all-user live mock.

* fix(kosong): merge a follow-up user turn into the preceding tool_results

The Anthropic message merge keyed on isToolResultOnly(last) ===
isToolResultOnly(converted), which left a tool_result-only user turn
followed by a plain-text user turn unmerged. After tool-exchange repair
this shape (assistant tool_use -> tool_result -> injected notification)
produces two adjacent user messages, which strict Anthropic-compatible
backends reject with HTTP 400.

Switch to the asymmetric predicate isToolResultOnly(last) ||
!isToolResultOnly(converted): a tool-result-only running message absorbs
whatever user turn follows (parallel tool_results or a trailing text),
yielding a valid [tool_result, ..., text] message; a plain-text running
message still only absorbs plain text. [tool_result, text] is valid for
both native Anthropic (which concatenates anyway) and strict backends.

* test(agent-core): pin micro-compaction flag in the shrunk-suffix probe

The 'does not clear recent tool results when projecting a shrunk suffix'
probe is an it.fails that only documents a real defect while
micro-compaction is active. It inherited the ambient
KIMI_CODE_EXPERIMENTAL master switch, so its pass/fail flipped with the
runner: green locally (master switch on) but a hard failure in CI, where
the flag defaults off and MicroCompaction.compact() is a no-op that
leaves the tool result intact.

Enable KIMI_CODE_EXPERIMENTAL_MICRO_COMPACTION explicitly for this probe
so it deterministically exercises the micro-compaction path regardless of
the environment.

* fix(agent-core): harden full compaction against in-flight races, unbounded shrink, and media loss

Three compaction-path fixes surfaced by review, each flipping its
documenting it.fails probe to a passing it:

- Append race (CMP-02): after the summarizer returns, the post-summary
  history check only compared the compacted prefix. A live step appending
  to the tail while a manual/SDK compaction was in flight slipped through —
  an appended assistant/tool turn is neither summarized (the summary covers
  only the snapshot) nor kept (the rebuild keeps user input), so it
  vanished. Now cancel when the appended tail contains a non-user message;
  an appended user message is still kept (rebuild picks it up), preserving
  the existing 'keeps messages appended while compacting an unchanged
  prefix' behavior.

- Unbounded empty/truncated shrink: an empty or truncated summary dropped
  the oldest message and reset retryCount, so a model that kept returning
  empty could issue ~one request per history entry. Bound the shrink
  attempts by MAX_COMPACTION_RETRY_ATTEMPTS, mirroring the overflow-shrink
  counter.

- Media dropped on truncation (CMP-07): truncating the oldest kept user
  message replaced its whole content with one text block, discarding any
  image/audio/video. Keep the non-text parts and spend the remaining budget
  (maxTokens minus their cost) on truncated text.

* fix(vis): mirror legacy compaction tail in the model-mode projector

For a pre-rework context.apply_compaction record (no keptUserMessageCount),
agent-core's ContextMemory restore and the transcript reducer keep the old
[summary, ...history.slice(compactedCount)] tail — a verbatim recent tail
including assistant/tool. The vis model-mode projector always applied the
new kept-user selection, so opening an older compacted session in model
mode hid the assistant/tool tail the resumed agent still holds (and
surfaced a pre-compaction user message the agent dropped).

Branch on a missing keptUserMessageCount with compactedCount < history
length and reproduce the legacy shape, matching the agent-core restore.

* fix(agent-core): cancel compaction on any droppable user-role tail

The in-flight append guard cancelled only when the tail grew with a
non-user role. A user-role message that compaction would still drop — a
background-task notification, hook/cron reminder, or shell-command output —
slipped through: appended after the summary snapshot (so absent from the
summary) and dropped by the all-user rebuild (which keeps only real user
input), vanishing silently.

Key the guard on the same predicate applyCompaction uses (!isRealUserInput)
so it cancels whenever the appended tail holds anything compaction would
drop. A real user message is still kept, so a live user turn racing a
manual/SDK compaction continues to complete.

* fix(agent-core): exclude pre-clear prompts from legacy folded length

The transcript reducer's legacy fallback (records predating
keptUserMessageCount, compacted with no verbatim tail) re-derived the
kept-user count from the whole transcript, including messages before the
last context.clear. Live ContextMemory rebuilds _history from post-clear
messages only, so counting pre-clear prompts overstated foldedLength;
MessageService then saw context.history.length <= foldedLength and skipped
appending unflushed live tail messages, dropping recent output from the
messages endpoint for old sessions compacted after a clear.

Derive only from entries at or after clearFloor to match the live context.

* fix(agent-core): drop media when truncating the oldest kept prompt

Revert the media-preserving truncation: keeping non-text parts on the
truncated boundary message overshot the kept-user budget when the media
alone exceeded it, and reordered interleaved text/media parts. Both codex
(no media-aware truncation) and Claude Code (strips media at compaction)
decline to preserve media on a truncated message, since media cannot be
partially truncated and keeping it whole breaks the budget.

truncateUserMessage now keeps only the truncated text. Recent messages
that fit the budget are still kept verbatim with their media; only the
oldest, partially-overflowing boundary message loses its attachments.

* fix(agent-core): make manual compaction and turns mutually exclusive

A manual/SDK compaction could start while a turn was streaming, or a new
turn could launch while a compaction was in flight. Either way the turn
mutates the shared context (streaming content into an existing assistant
message, or appending new messages) during the summarizer await, and that
output is neither summarized nor preserved by the all-user rebuild —
silent loss that object-identity checks can't detect (the streamed message
is mutated in place).

Guard both directions so the agent does one of {turn, compaction} at a
time: begin() refuses a manual compaction while a turn is active, and
launch() refuses a new turn while a compaction is in progress. Auto
compaction is exempt — it runs from within the turn at a step boundary,
which blocks the turn for its duration.

* chore(changeset): consolidate compaction changesets into one

* chore(agent-core): drop external-product references from compaction comments

* test(agent-core): add Anthropic wire-compliance smoke tests for compaction

Drive real compaction output and the compaction summarizer projection
through the real Anthropic provider conversion and assert the wire request
is well-formed: strict user/assistant alternation and every tool_use
answered by an adjacent tool_result. Locks in the cross-layer guarantee
(projector merge + Anthropic consecutive-user merge + adjacency repair +
synthesizeMissing) that compacted sessions stay valid for strict
Anthropic-compatible backends.

* fix(agent-core): defer and replay inputs during manual compaction instead of rejecting

Manual/SDK compaction runs outside a turn, so the earlier guard rejected
prompts/steers that arrived while it held the context. That broke three
things: a REST/web prompt got stuck 'running' (no terminal turn event), a
background-task/cron steer was silently lost (null was read as 'buffered'
but nothing was), and a follow-up prompt could land in the window after
isCompacting cleared but before reminders were reinjected.

Reuse the existing defer-and-replay model instead of rejecting:

- steer() and launch() buffer into steerBuffer while a compaction is in
  progress (returning null = buffered), mirroring how an active turn defers
  input.
- FullCompaction.compactionWorker keeps isCompacting true through
  refreshSystemPrompt + injectAfterCompaction (moving markCompleted and the
  completed event after reinjection), then replays the buffer via
  TurnFlow.onCompactionFinished — on success, on an A1 prefix/tail cancel,
  and on failure/abort.
- onCompactionFinished flushes into an active turn if one exists, else
  launches a fresh turn from the deferred input.

No PromptService change: a deferred prompt's eventual turn.started lets it
associate the pending prompt and clear it on turn.ended.

* feat(kosong): detect tool_use/tool_result adjacency errors

Add isToolExchangeAdjacencyError to classify the strict-provider 400 raised
when an assistant tool_use is not correctly paired with its tool_result
(missing, stray, or non-adjacent), excluding context-overflow 400s. Lets the
agent loop recognize the error and resend a wire-compliant request instead of
leaving the session stuck.

* fix(agent-core): close mid-history orphan tool calls and resend wire-compliant after a strict 400

Strict providers (Anthropic) reject a request whose assistant tool_use is not
answered by an adjacent tool_result, and the same malformed history is re-sent
every turn, permanently bricking the session.

- Projector now closes a mid-history tool call whose result is missing entirely
  (a later turn proves it is not in-flight) with a synthetic result; the
  trailing in-flight call is still left untouched.
- Add a strict projection (synthesize every open call, drop stray results) and,
  on a tool_use/tool_result adjacency 400, resend the request once with it.
- Report every projection repair (reorder / synthesize / drop) via log and
  telemetry, deduped by signature, so a silently-mangled history leaves a trace.
  Trailing-tail synthesis (expected under compaction) is not flagged.

* fix(kosong): merge consecutive user turns for strict providers

Gemini/Vertex require strictly alternating user/model turns and reject
consecutive user turns with HTTP 400. They arise after compaction (kept
prompts + user-role summary + injected reminders) and when a turn is
steered in right after a tool result. Anthropic already merged them
inline; the Google converter did not, so post-compaction requests failed.

Extract the asymmetric merge into a shared mergeConsecutiveUserMessages
helper applied at each strict provider's conversion boundary: refactor
Anthropic to use it (behavior unchanged) and apply it at the Google
converter's exit. A conformance suite drives every strict provider with
the post-compaction shape and a steer-after-tool-result shape, asserting
no consecutive same-role turns reach the wire, so a new strict provider
cannot silently omit the merge.

The provider-agnostic projector stays structure-preserving: lenient
providers (OpenAI/Kimi) keep distinct turns for clearer message
boundaries; only strict providers normalize, where the requirement lives.

* feat(kosong): recognize the broader structural request-rejection family

Add isRecoverableRequestStructureError, covering the strict-provider 400s that
stem from a malformed message array re-sent every turn: tool_use/tool_result
pairing, empty/whitespace-only text blocks, a non-user first message, and
non-alternating roles. Context-overflow 400s are excluded (handled by
compaction). Lets the loop trigger one strict, wire-compliant resend for the
whole family rather than only tool-pairing errors.

* fix(agent-core): sanitize whitespace and strict-resend structural 400s, with diagnostics

- Drop empty AND whitespace-only text blocks in projection (Anthropic rejects
  whitespace-only with "text content blocks must contain non-whitespace text",
  which otherwise sticks a session); treat whitespace-only tool output as empty.
- Broaden the post-400 strict resend to the whole structural family and add two
  strict-only passes to the strict projection: drop leading non-user messages
  (first message must be user) and merge consecutive assistant turns.
- Log + telemetry for every wire repair the projector applies (reorder,
  synthesize, drop orphan, drop leading, merge assistants, drop whitespace),
  deduped by signature; log the strict resend outcome (recovered or still
  rejected) so a stuck session always leaves a trace.

* fix(agent-core): normalize empty-equivalent tool result arrays to the empty placeholder

A tool result whose ContentPart[] output has no sendable content (an empty array,
or only empty/whitespace-only text blocks) was returned verbatim, so projection
stripped the blank blocks, left the tool message empty, and threw on every send —
bricking the session locally. String outputs were already normalized; do the same
for arrays. A non-text part or any non-whitespace text still keeps the real
output.

* chore(changeset): simplify the wire-compliance changeset
2026-07-01 02:16:19 +08:00
Kai
86e0c9201e
feat(agent-core): rework compaction to keep only user prompts and summary (#1214)
* feat(agent-core): rework compaction to keep only user prompts and summary

* refactor(agent-core): rewrite compaction summary as first-person handoff

Rework the full-compaction summary to read as the agent's own continuing
notes instead of a third-party report:

- compaction-instruction.md: free-form first-person continuation that
  preserves exact commands, paths and outcomes, states the precise next
  action, and flags claimed-but-unverified work rather than trusting it.
- compaction-summary-prefix.md: skeptical "your own working notes"
  framing; drop the collaborative third-party prefix.
- system.md: add compaction-awareness guidance so the model continues
  naturally from a summary and re-checks any reported "done".
- Rename the compaction helpers module to handoff.ts.

Update tests and regenerate snapshots for the new prompt text, and fill
in contextSummary in the restored-compaction replay expectations.

* fix(agent-core): count image/audio/video parts in token estimation

estimateTokensForContentPart returned 0 for image_url/audio_url/video_url,
so auto-compaction triggers, the overflow-shrink budget, the kept-user
budget, and the reported context size all went blind to media — a
media-heavy session could overflow the model window while the estimate
reported a near-empty context. Media parts now carry a fixed estimate
(MEDIA_TOKEN_ESTIMATE), and the content-part switch is exhaustive so a new
ContentPart kind must declare its estimate rather than silently count as
zero.

* feat(agent-core): re-surface active background tasks after compaction

Folding the live context to [recent user prompts, summary] drops the
messages that started background tasks and their status updates, so the
model could forget a task is still running and spawn a duplicate.
injectAfterCompaction now appends a system-reminder listing active
background tasks (with guidance to use TaskOutput/TaskList/TaskStop
instead of re-spawning). It runs only post-compaction and carries an
injection origin, so the next compaction drops and rebuilds it rather
than stacking copies; the all-user-role post-compaction shape is
preserved (no tool-pairing reintroduced).

* test(agent-core): add compaction scenario guards and risk probes

Adds compaction-scenarios.test.ts driving the real Agent/ContextMemory/
FullCompaction machinery:

- A guard test locking in that repeated compaction folds the prior summary
  into the new one instead of stacking two summaries.
- Seven `it.fails` probes that executably reproduce known, currently-accepted
  edge-case defects so the suite stays green while documenting each one
  precisely; any of them will flip red (forcing removal of `.fails`) the day
  the behavior is fixed. They cover: assistant/tool appended during an
  in-flight summarizer call being dropped; unbounded shrink on empty
  summaries; the fixed 20k kept-user budget overflowing a small model window;
  a tool result orphaned when compaction starts mid-exchange; legacy
  compaction records dropping their verbatim tail on replay; micro-compaction
  clearing recent tool results in an overflow-shrunk suffix; and media being
  discarded when the oldest kept user message is truncated.

* fix(agent-core): repair tool_use/tool_result adjacency in projected context

A tool call and its result can end up non-adjacent in history — a
background-task notification or flushed steer lands between them, or an
interrupted/nested step delays the result — which strict providers reject
with HTTP 400. The projector now moves each tool_use's result up to
immediately follow it (projection-time only; the stored history is
untouched), and full compaction projects its summarizer input with a
synthetic result for any still-open call so the summary request stays
well-formed. Micro-compaction only surfaced this latent ordering by busting
the prompt cache, so it now defaults off.

Includes projector adjacency regression tests, a context-level integration
test, and a compaction synthesize-missing guard; the prior "keeps an
unresolved tool exchange out of the compaction prompt" test is updated to
the now-well-formed (synthetic-result) behavior.

* fix(agent-core): preserve the verbatim tail when restoring legacy compactions

A pre-rework `context.apply_compaction` record used
`[summary, ...history.slice(compactedCount)]` semantics and kept a verbatim
recent tail, but it has no `keptUserMessageCount`. The reworked applyCompaction
re-folded such records into the all-user shape, dropping the recent
assistant/tool tail — so resuming a session compacted by an older version
silently lost its most recent context.

On restore of such a record (gated on records.restoring, no keptUserMessageCount,
and compactedCount < history length) reproduce the old shape instead. The
forward/live path is unchanged; the projector's tool-adjacency repair keeps the
restored tail well-formed, and compaction only runs at clean step boundaries so
the tail has no open exchange. The legacy-tail probe now passes as a regression
guard via the real restore path.

* fix(agent-core): align legacy compaction foldedLength with live restore

The transcript reducer re-derived foldedLength for pre-rework
context.apply_compaction records (no keptUserMessageCount) using the new
kept-user+summary rule, but ContextMemory's restore now reproduces the legacy
[summary, ...history.slice(compactedCount)] shape for those records. The two
diverged for legacy sessions, so MessageService's foldedLength-vs-live-history
comparison could mis-handle GET /messages (miss or misorder recent output).

The reducer now mirrors the live legacy fold: when compactedCount is below the
pre-compaction length it computes 1 + (length - compactedCount); otherwise it
falls back to the kept-user derivation. The MessageService transcript test's
fixture is corrected to a new-format record, matching its all-user live mock.

* fix(kosong): merge a follow-up user turn into the preceding tool_results

The Anthropic message merge keyed on isToolResultOnly(last) ===
isToolResultOnly(converted), which left a tool_result-only user turn
followed by a plain-text user turn unmerged. After tool-exchange repair
this shape (assistant tool_use -> tool_result -> injected notification)
produces two adjacent user messages, which strict Anthropic-compatible
backends reject with HTTP 400.

Switch to the asymmetric predicate isToolResultOnly(last) ||
!isToolResultOnly(converted): a tool-result-only running message absorbs
whatever user turn follows (parallel tool_results or a trailing text),
yielding a valid [tool_result, ..., text] message; a plain-text running
message still only absorbs plain text. [tool_result, text] is valid for
both native Anthropic (which concatenates anyway) and strict backends.

* test(agent-core): pin micro-compaction flag in the shrunk-suffix probe

The 'does not clear recent tool results when projecting a shrunk suffix'
probe is an it.fails that only documents a real defect while
micro-compaction is active. It inherited the ambient
KIMI_CODE_EXPERIMENTAL master switch, so its pass/fail flipped with the
runner: green locally (master switch on) but a hard failure in CI, where
the flag defaults off and MicroCompaction.compact() is a no-op that
leaves the tool result intact.

Enable KIMI_CODE_EXPERIMENTAL_MICRO_COMPACTION explicitly for this probe
so it deterministically exercises the micro-compaction path regardless of
the environment.

* fix(agent-core): harden full compaction against in-flight races, unbounded shrink, and media loss

Three compaction-path fixes surfaced by review, each flipping its
documenting it.fails probe to a passing it:

- Append race (CMP-02): after the summarizer returns, the post-summary
  history check only compared the compacted prefix. A live step appending
  to the tail while a manual/SDK compaction was in flight slipped through —
  an appended assistant/tool turn is neither summarized (the summary covers
  only the snapshot) nor kept (the rebuild keeps user input), so it
  vanished. Now cancel when the appended tail contains a non-user message;
  an appended user message is still kept (rebuild picks it up), preserving
  the existing 'keeps messages appended while compacting an unchanged
  prefix' behavior.

- Unbounded empty/truncated shrink: an empty or truncated summary dropped
  the oldest message and reset retryCount, so a model that kept returning
  empty could issue ~one request per history entry. Bound the shrink
  attempts by MAX_COMPACTION_RETRY_ATTEMPTS, mirroring the overflow-shrink
  counter.

- Media dropped on truncation (CMP-07): truncating the oldest kept user
  message replaced its whole content with one text block, discarding any
  image/audio/video. Keep the non-text parts and spend the remaining budget
  (maxTokens minus their cost) on truncated text.

* fix(vis): mirror legacy compaction tail in the model-mode projector

For a pre-rework context.apply_compaction record (no keptUserMessageCount),
agent-core's ContextMemory restore and the transcript reducer keep the old
[summary, ...history.slice(compactedCount)] tail — a verbatim recent tail
including assistant/tool. The vis model-mode projector always applied the
new kept-user selection, so opening an older compacted session in model
mode hid the assistant/tool tail the resumed agent still holds (and
surfaced a pre-compaction user message the agent dropped).

Branch on a missing keptUserMessageCount with compactedCount < history
length and reproduce the legacy shape, matching the agent-core restore.

* fix(agent-core): cancel compaction on any droppable user-role tail

The in-flight append guard cancelled only when the tail grew with a
non-user role. A user-role message that compaction would still drop — a
background-task notification, hook/cron reminder, or shell-command output —
slipped through: appended after the summary snapshot (so absent from the
summary) and dropped by the all-user rebuild (which keeps only real user
input), vanishing silently.

Key the guard on the same predicate applyCompaction uses (!isRealUserInput)
so it cancels whenever the appended tail holds anything compaction would
drop. A real user message is still kept, so a live user turn racing a
manual/SDK compaction continues to complete.

* fix(agent-core): exclude pre-clear prompts from legacy folded length

The transcript reducer's legacy fallback (records predating
keptUserMessageCount, compacted with no verbatim tail) re-derived the
kept-user count from the whole transcript, including messages before the
last context.clear. Live ContextMemory rebuilds _history from post-clear
messages only, so counting pre-clear prompts overstated foldedLength;
MessageService then saw context.history.length <= foldedLength and skipped
appending unflushed live tail messages, dropping recent output from the
messages endpoint for old sessions compacted after a clear.

Derive only from entries at or after clearFloor to match the live context.

* fix(agent-core): drop media when truncating the oldest kept prompt

Revert the media-preserving truncation: keeping non-text parts on the
truncated boundary message overshot the kept-user budget when the media
alone exceeded it, and reordered interleaved text/media parts. Both codex
(no media-aware truncation) and Claude Code (strips media at compaction)
decline to preserve media on a truncated message, since media cannot be
partially truncated and keeping it whole breaks the budget.

truncateUserMessage now keeps only the truncated text. Recent messages
that fit the budget are still kept verbatim with their media; only the
oldest, partially-overflowing boundary message loses its attachments.

* fix(agent-core): make manual compaction and turns mutually exclusive

A manual/SDK compaction could start while a turn was streaming, or a new
turn could launch while a compaction was in flight. Either way the turn
mutates the shared context (streaming content into an existing assistant
message, or appending new messages) during the summarizer await, and that
output is neither summarized nor preserved by the all-user rebuild —
silent loss that object-identity checks can't detect (the streamed message
is mutated in place).

Guard both directions so the agent does one of {turn, compaction} at a
time: begin() refuses a manual compaction while a turn is active, and
launch() refuses a new turn while a compaction is in progress. Auto
compaction is exempt — it runs from within the turn at a step boundary,
which blocks the turn for its duration.

* chore(changeset): consolidate compaction changesets into one

* chore(agent-core): drop external-product references from compaction comments

* test(agent-core): add Anthropic wire-compliance smoke tests for compaction

Drive real compaction output and the compaction summarizer projection
through the real Anthropic provider conversion and assert the wire request
is well-formed: strict user/assistant alternation and every tool_use
answered by an adjacent tool_result. Locks in the cross-layer guarantee
(projector merge + Anthropic consecutive-user merge + adjacency repair +
synthesizeMissing) that compacted sessions stay valid for strict
Anthropic-compatible backends.

* fix(agent-core): defer and replay inputs during manual compaction instead of rejecting

Manual/SDK compaction runs outside a turn, so the earlier guard rejected
prompts/steers that arrived while it held the context. That broke three
things: a REST/web prompt got stuck 'running' (no terminal turn event), a
background-task/cron steer was silently lost (null was read as 'buffered'
but nothing was), and a follow-up prompt could land in the window after
isCompacting cleared but before reminders were reinjected.

Reuse the existing defer-and-replay model instead of rejecting:

- steer() and launch() buffer into steerBuffer while a compaction is in
  progress (returning null = buffered), mirroring how an active turn defers
  input.
- FullCompaction.compactionWorker keeps isCompacting true through
  refreshSystemPrompt + injectAfterCompaction (moving markCompleted and the
  completed event after reinjection), then replays the buffer via
  TurnFlow.onCompactionFinished — on success, on an A1 prefix/tail cancel,
  and on failure/abort.
- onCompactionFinished flushes into an active turn if one exists, else
  launches a fresh turn from the deferred input.

No PromptService change: a deferred prompt's eventual turn.started lets it
associate the pending prompt and clear it on turn.ended.

* fix(kosong): merge consecutive user turns for strict providers

Gemini/Vertex require strictly alternating user/model turns and reject
consecutive user turns with HTTP 400. They arise after compaction (kept
prompts + user-role summary + injected reminders) and when a turn is
steered in right after a tool result. Anthropic already merged them
inline; the Google converter did not, so post-compaction requests failed.

Extract the asymmetric merge into a shared mergeConsecutiveUserMessages
helper applied at each strict provider's conversion boundary: refactor
Anthropic to use it (behavior unchanged) and apply it at the Google
converter's exit. A conformance suite drives every strict provider with
the post-compaction shape and a steer-after-tool-result shape, asserting
no consecutive same-role turns reach the wire, so a new strict provider
cannot silently omit the merge.

The provider-agnostic projector stays structure-preserving: lenient
providers (OpenAI/Kimi) keep distinct turns for clearer message
boundaries; only strict providers normalize, where the requirement lives.
2026-07-01 01:17:30 +08:00
liruifengv
108299be3c
refactor!: overhaul thinking config and effort resolution (#1132)
* feat: support multi-level thinking effort switching

- kimi provider: emit thinking.effort in the new wire format; keep reasoning_effort mirrored during the transition
- model catalog: thread support_efforts / default_effort from oauth through to /models
- config schema: add supportEfforts / defaultEffort on model aliases
- TUI: multi-segment thinking control in /model, new /effort command, footer effort display
- switch status uses displayName and distinguishes model vs effort-only changes

* docs: add thinking effort design plans

- thinking-effort-switching.md: implemented multi-level effort switching
- thinking-model-overhaul.md: follow-up refactor plan for the thinking state model

* docs: collapse thinking overhaul plan into a single PR

* refactor!: overhaul thinking config and effort resolution

Replace default_thinking and thinking.mode with a single [thinking] enabled/effort table. ThinkingEffort is now an open string ('off' | 'on' | model-declared effort); effort levels come from each model's support_efforts instead of a fixed enum.

Centralize default and always_thinking clamp logic in resolveThinkingEffort/defaultThinkingEffortFor, and honor an explicitly configured effort when an always_thinking model is forced back on.

TUI keeps a single thinkingEffort field instead of the boolean + level pair; 'on' is normalized to the model default at the UI boundary.

BREAKING CHANGE: default_thinking and thinking.mode are removed from config; migrate to [thinking] enabled/effort.

* refactor: rename residual thinking level wording to effort

Rename comments, error messages, parameter names, the SetThinkingPayload wire field (level -> effort), and TUI local variables so the thinking effort naming is consistent throughout. No behavior change.

* refactor: rename remaining camelCase thinking level identifiers to effort

Rename liveLevel/prevLevel/levelChanged/commitLevel/effectiveLevel to liveEffort/prevEffort/effortChanged/commitEffort/effectiveEffort in the TUI model picker and config commands.

* refactor: eliminate remaining thinking level wording in comments and tests

Rename levelLabel -> effortLabel, EffortSelectorOptions.levels -> efforts, and 'effort level(s)' / 'default level' / 'requested level' wording in comments, error messages, slash-command description, and test titles to effort. Also restore the withThinking(effort) parameter rename in the Kimi provider that was accidentally reverted.

* fix: address codex review feedback on thinking effort handling

- OpenAI thinkingEffortToReasoningEffort and Anthropic clampEffort now normalize 'on' / unrecognized efforts instead of throwing, so boolean non-Kimi models no longer crash on session start.

- ACP resolveCurrentThinkingEnabled treats a non-empty thinking.effort as enabled, matching agent-core's resolveThinkingEffort.

- REST promptThinkingSchema accepts any non-empty effort string so model-declared efforts are not rejected at the API boundary.

* test: align kimi e2e expectations with supportEfforts-gated reasoning_effort

The kimi provider now sends reasoning_effort only when the model declares support_efforts; boolean models (no support_efforts) send only thinking.type. Update the kimi e2e tests to drop the stale reasoning_effort expectation for the boolean test model.

* test: cover [thinking] effort parsing in config.test

Add effort = "high" to the documented [thinking] table in the config parse test and assert config.thinking.effort is resolved, so the new [thinking] effort field has direct parse coverage.

* docs: add thinking test coverage gap analysis

Capture the explore agent's test coverage review for the thinking overhaul PR, including P1/P2 gaps and the two open design questions, for follow-up test additions.

* feat(oauth): parse nested think_efforts from /models response

The /models endpoint now returns effort levels under a nested think_efforts object ({ support, valid_efforts, default_effort }). Parse it preferentially in both managed-kimi-code and open-platform model parsing, falling back to the legacy flat support_efforts / default_effort fields for older servers.

* refactor(oauth): only read nested think_efforts; gate on support=true

Drop the legacy flat support_efforts / default_effort fallback. The think_efforts object is now the single source, and its support flag gates the whole object — when support is not true, valid_efforts and default_effort are ignored entirely.

* chore: remove unused parseStringArray import in open-platform

* docs: finalize thinking effort release notes

Downgrade the changeset to minor with an English summary, drop the version-specific 'added in 1.0.0' info block, and present the deprecated config fields as a table (field / deprecated in 0.21.0 / description).

* refactor: drop temporary refresh toggles and kimi reasoning_effort mirror

Remove the always-true REFRESH_MODELS_ON_PICKER_OPEN / REFRESH_PROVIDER_MODELS_ON_STARTUP toggles and their stale re-enable TODOs, and stop sending reasoning_effort from the kimi provider (thinking.effort is the only wire field now).

* fix(tui): avoid persisting "on" as thinking effort

* fix: preserve persisted thinking effort across login and provider setup

* fix(tui): show actual thinking effort in /status and footer

* test(tui): align message-flow expectations with effort persistence and /status display

* fix(vis): rename thinkingLevel to thinkingEffort in config.update analysis
2026-06-30 22:34:13 +08:00
Kai
42e37eb898
feat(timing): split TTFT into api-server and client portions (#1228)
* feat(timing): split TTFT into api-server and client portions

Time-to-first-token previously lumped in-process request building
(message serialization, param assembly) together with network + server
latency, making it impossible to tell whether a slow turn was the client
or the API server.

Add an `onRequestSent` hook to kosong's GenerateOptions, fired by every
provider immediately before it dispatches the network call. The window
from request start to dispatch is attributed to the client; the window
from dispatch to the first streamed token is attributed to the API
server. The split flows through the step.end / turn.step.completed
events (and therefore wire.jsonl) and is surfaced in three places:

- KIMI_CODE_DEBUG=1: `TTFT: 2.5s (api 2.4s + client 100ms)`
- session log: new `llm response` line with the timing breakdown
- vis: firstToken/api + firstToken/client rows and timeline label

The split is omitted (total only) when a provider does not report the
boundary, preserving backward compatibility.

* feat(timing): split the decode window into server vs client time

Time-to-first-token now reports a client/server split, but the slow part
of a long turn is the decode window (inter-token streaming), which was
still a single opaque number. Profiling long sessions showed decode
throughput halving over a session's lifetime independent of context
size, which the synchronous per-chunk stream pipeline can cause: kosong
awaits the host callback for every streamed part, so a loaded main
thread throttles how fast tokens are pulled off the wire.

Account for this directly in the stream loop: the time awaiting the next
part (server + network) versus the time spent processing each part
in-process (deep copy, host callback, part merge). The split is reported
through onStreamEnd and flows through the step.end / turn.step.completed
events (and wire.jsonl) into the same three surfaces as the TTFT split:

- KIMI_CODE_DEBUG=1: `TPS: 40.0 tok/s (200 tokens in 5.0s; server 4.6s + client 400ms)`
- session log: serverDecodeMs / clientConsumeMs on the `llm response` line
- vis: streamDuration/server + streamDuration/client rows and timeline label

A large, growing client share confirms host-side throttling; a dominant
server share points at the server/connection. The per-chunk accounting is
wrapped in try/finally so it stays correct across `continue` and aborts,
and is omitted when the stream reports nothing.
2026-06-30 19:15:02 +08:00
7Sageer
10ffb7d9f9
chore(telemetry): normalize telemetry property keys to snake_case (#1196)
- Rename camelCase telemetry keys to snake_case on compaction_finished, compaction_failed, micro_compaction_finished, and the tool error event (tokens_before, tokens_after, compacted_count, retry_count, thinking_level, error_type, input_tokens/output_tokens, and the micro compaction config/effect keys).

- Emit a fixed client-attribution key set (client_id/name/version/ui_mode, null when absent) from both session_started producers (core-impl and kimi-harness) so they share a stable schema.

- Drop the duplicate current/latest keys on update_prompted and the redundant ui_mode on server_started.

- Additive fields: login.method=oauth and question_answered.answered.

Telemetry-only change; no changeset.
2026-06-29 17:40:50 +08:00
Haozhe
c8d1d76de5
test(agent-core): align turn telemetry expectations with provider_type (#1194)
Update the turn_started/turn_ended and api_error telemetry assertions to
expect provider_type, matching the telemetry property rename in #1184.
2026-06-29 14:44:26 +08:00
Kai
821847cb4b
feat(managed-kimi-code): route anthropic protocol via beta api (#1186)
* feat(managed-kimi-code): route anthropic protocol via beta api

- kosong: add betaApi option to use client.beta.messages.create
- agent-core: thread alias betaApi into the anthropic provider config
- oauth: route managed models on the anthropic protocol through the beta Messages API

* feat(providers): add KIMI_CODE_CUSTOM_HEADERS support

- Add KIMI_CODE_CUSTOM_HEADERS env var for custom outbound LLM headers
- Send User-Agent to non-Kimi providers
- Forward Kimi identity headers to model catalog fetches
- Support defaultHeaders in Google GenAI provider

* feat(agent-core): add protocol attrs to turn and api error telemetry

- Add type/protocol/alias to api_error for per-protocol error attribution

- Add turn_ended event with reason/duration/mode/type/protocol

- Add type/protocol to turn_interrupted

* chore(oauth): remove hardcoded internal dev endpoint from shared OAuth base URLs

---------

Co-authored-by: haozhe.yang <yanghaozhe@moonshot.ai>
2026-06-29 14:24:01 +08:00
7Sageer
49e93893a6
refactor: standardize telemetry property names (#1184)
Align outlier telemetry events with the conventions already used by
tool_call, api_error, permission_approval_result, and the plan events:

- duration / latency_ms / duration_s -> duration_ms
- success boolean -> outcome enum ('success' | 'error')
- bare type -> provider_type

The exit event switches from seconds to milliseconds to match the
duration_ms convention, so its numeric scale changes accordingly.
2026-06-29 12:34:38 +08:00
Haozhe
cf558cd742
feat(managed-kimi-code): support Anthropic-compatible protocol (#1170)
* fix(agent-core): recover from context overflow 413

- track provider-observed effective context limit after overflow
- compact with the reduced limit before retrying the turn
- treat large plain 413 responses as recoverable context overflow
- add CLI patch changeset

* feat(managed-kimi-code): support Anthropic-compatible protocol

- switch managed provider to anthropic when models declare anthropic protocol
- add base64 video content blocks to the kosong anthropic provider
- downgrade unsupported media parts to text placeholders by capability
- pass prompt cache key as Anthropic metadata.user_id for session affinity

* feat(agent-core): add protocol/type to request and video upload telemetry

- turn_started now carries `type` (configured provider wire type) and
  `protocol` (effective transport, i.e. alias.protocol ?? provider.type)
- new video_upload event reports mime type, size, latency and
  success/failure, plus type/protocol/model context
- ResolvedRuntimeProvider gains `type` and `protocol` fields
2026-06-28 13:04:03 +08:00
qer
794db55538
fix(agent-core): cap compaction output at 128k by default (#1156) 2026-06-27 23:24:57 +08:00
Qkunio
d02b5c4984
fix(agent-core): use max output size for compaction budget (#1129)
* fix(agent-core): use max output size for compaction budget

* chore: add changeset for compaction max output size fix

* docs: rephrase compaction changeset

---------

Co-authored-by: qkunio <qkunio@163.com>
Co-authored-by: qer <wbxl2000@outlook.com>
2026-06-27 16:40:31 +08:00
qer
76c643bcb6
feat: cap completion tokens to remaining context window for chat-completions (#1131)
* feat: cap completion tokens to remaining context window for chat-completions

* test: cover dynamic completion budget for kimi and openai-legacy

* fix: leave compaction budget uncapped to avoid one-token summaries

* fix: add hookCount to plugins-selector test mocks
2026-06-26 19:12:04 +08:00
Kai
9c9716125e
feat: Harden the default system prompt and built-in tool descriptions (#1102)
* feat(agent-core): strengthen default system prompt

Add high-confidence, prompt-only guardrails to the default agent system prompt:

- Personality/candor: extend the HELPFUL/CONCISE/ACCURATE line with CANDID, and
  require plainly stating what could not be run, reproduced, or verified.
- Reminders: avoid cheerleading; voice evidence-based disagreement; deliver
  complete code with no placeholders; update now-stale comments/docstrings after
  a change; re-check the user's latest request before finalizing a reply.
- Context Management: explain automatic compaction — continue from the summary,
  re-establish transient state with tools, do not restart from scratch.
- Output formatting: replies render as Markdown in the terminal; keep lists flat;
  no emojis unless the user uses them first.
- Project Information: frame injected AGENTS.md as project context, not a
  privileged instruction channel that can override system rules.

Prompt text only; no code or template-variable changes.

* feat(agent-core): hoist key working rules into the system prompt

Lift a few high-leverage rules from individual tool descriptions up into the
default system prompt, so they shape default behavior before any specific tool
is in play (kept terse and integrated, not bolted on):

- Planning: for multi-step or multi-file work, maintain a `TodoList` (one item
  in_progress, mark done as it finishes) and prefer `EnterPlanMode` first when
  the approach isn't settled.
- Default to making progress, not asking: once the goal is clear and sanctioned,
  carry it through and work blockers yourself; ask only when the answer would
  change the next step. Explicitly does not override stopping to discuss an
  unclear goal or waiting for go-ahead before writing code.
- Tool routing: prefer dedicated tools (Read/Glob/Grep/Write/Edit) over raw
  shell when one fits; keep Bash for genuine shell work.
- Definition of done: verify with the checks that cover the change before
  marking it complete, independent of whether a TodoList is in use.
- Delegation: explore subagents also keep intermediate file contents out of your
  own context — you get a conclusion back, not a pile of dumps.

Prompt text only; no code or template-variable changes.

* fix: clarify guidelines for file pattern matching and tool usage in explore.yaml and system.md

* fix(agent-core): hide the Skills section from agents without the Skill tool

Subagents (coder/explore/plan) inherit the root system prompt but lack the Skill tool, yet KIMI_SKILLS was rendered unconditionally — leaking the full skill listing into agents that cannot invoke any skill. Gate KIMI_SKILLS on the profile's tool set and wrap the '# Skills' section in {% if KIMI_SKILLS %} so it disappears for those profiles.

Also note in the Working Directory section that Bash enforces none of the workspace/secret-file guards, so the model must hold that discipline itself.

Tests: assert the Skills section renders for the root agent and is absent for Skill-less subagents; update the prompt-rendering fixtures for the new gating.

* fix(agent-core): gate Agent, background-task, and TodoList guidance by tool availability

Subagents (coder/explore/plan) inherit the root system prompt but lack the Agent, TaskList, and TodoList tools, so they were shown usage guidance for tools they cannot call. Derive HAS_AGENT/HAS_TASKLIST/HAS_TODOLIST from each profile's tool set and gate those sections with inline {% if %}, so they render only for agents that hold the tool.

Root rendering is byte-identical (the inline tags collapse to the original text when the flag is set). The cross-tool secret-file guard stays shared, since explore/plan still hold Read/Grep/Glob.

Tests: assert the gated guidance is present for the root agent and absent for explore/plan, while the shared secret-file guard remains.

* refactor(agent-core): move Agent-delegation and Glob-anchor guidance into the tool descriptions

The Agent-delegation paragraph in the system prompt duplicated mechanics already documented on the Agent tool itself (new-vs-resume, zero-context briefing, foreground default / run_in_background threshold), so remove it. HAS_AGENT still gates the explore-delegation bullet, which carries the 'when to delegate' nudge the tool description deliberately omits.

Move the proactive 'anchor the pattern up front' guidance into the Glob tool description (it previously only described the reactive 'refine after hitting the cap' path) and drop the now-redundant Glob bullet from the system prompt.

Tests: drop the assertions tied to the removed Agent paragraph; HAS_AGENT gating stays covered via the explore bullet.

* test(agent-core): add guidance for blast-radius and concrete examples in agent profiles

* docs: update descriptions for skill-tool and fetch-url; enhance web-search citation instructions

* feat(agent-core): disclose enforced constraints in tool descriptions; fix GetGoal field doc

Surface runtime-enforced behavior in the Agent / AgentSwarm / AskUserQuestion / Goal
tool descriptions so the model learns the rules from the tool, not from a failed call:

- Agent: resuming excludes subagent_type (setting both is rejected)
- AgentSwarm: at least 2 items unless resuming, prompt_template required and must
  contain {{item}}, distinct resulting prompts; plus Agent-vs-AgentSwarm fan-out note
- AskUserQuestion: result is {answers}; an empty answers with a dismissal note means
  the user declined — fall back to best judgment instead of re-asking
- CreateGoal: creating fails when a goal already exists (use replace)
- SetGoalBudget: state the hard 1s-24h time-budget band
- UpdateGoal: do not mark blocked merely because work is hard/slow/incomplete
- GetGoal: drop the advertised self-report / evaluator-verdict fields — GoalSnapshot
  never held them, so the tool never returned them

Each change is covered by a description assertion.

* fix(agent-core): soften AskUserQuestion answers-keying wording to match the code

The answers object is passed through from the host/RPC layer (QuestionAnswers is
Record<string, string | true>); this code does not key it by question text. Describe
what the keys identify instead of asserting a guarantee the code does not provide.

* feat(agent-core): tighten Bash/Grep/Write/Edit tool descriptions

- Bash: prefer the cwd argument (or absolute paths) over a cd from an earlier
  call, since each call runs in a fresh shell
- Grep: note that files_with_matches is ordered most-recently-modified first
- Write: do not create documentation/README files unless the user asks
- Edit: frame replace_all with its rename-across-file use-case

Each change is covered by a description assertion.

* feat(agent-core): refine plan-mode/todo/cron tool descriptions

- ExitPlanMode: describe what a good plan contains (specific, verifiable steps
  grounded in the codebase, not vague filler)
- TodoList: stop calling it useful 'in Plan mode' — plan-mode planning goes to
  the plan file; TodoList tracks execution progress
- CronCreate: warn that a one-shot whose pinned day/month already passed this
  year is rejected; document the 50-task session cap and the 8 KiB prompt cap
- CronCreate: drop the bench-only KIMI_CRON_NO_STALE / KIMI_CRON_NO_JITTER env
  knobs from the model-facing description (CI-only; the model never sets them)

Each change is covered by a description assertion.

* refactor(agent-core): dedupe ExitPlanMode options docs into the param schema; trim EnterPlanMode workflow

- ExitPlanMode: the options field mechanics (label format, recommended, count,
  single-option=plain-approval, reserved labels) now live only in the options
  param describe; the tool description routes to it and keeps the yolo/manual UI
  behavior it uniquely documents. The options consistency test now enforces a
  single source of truth (describe) plus the schema-consistency guard, instead
  of requiring the same facts in both surfaces.
- EnterPlanMode: trim the duplicated 'What Happens in Plan Mode' steps to a
  pointer (the full workflow is injected unconditionally once plan mode is
  active), keeping the explore-subagent recommendation.

* fix(agent-core): correct prompt/code inaccuracies found in the final audit

Every item below was re-verified against the live code:

- Skill: drop the never-fired recursion-depth cap (production never seeds depth);
  keep the <kimi-skill-loaded> 'already loaded, don't re-invoke' guard
- TaskOutput: terminal_reason can also be `failed`, not just timed_out/stopped
- Grep: count_matches emits per-file `path:count`, with the total reported separately
- Plan mode: the reminder names TaskStop/CronCreate/CronDelete as blocked (they are
  hard-denied by plan-mode-guard-deny)
- Bash: the failure trailer is non-zero-exit-specific; timeout/interrupt differ
- CreateGoal: replace also covers a blocked goal, not just active/paused
- UpdateGoal: it also injects the completion/blocked outcome prompt, so it does more
  than 'only record the status'
- FetchURL: state the universal http/https contract instead of provider-internal SSRF
  and 10 MiB limits (the primary Moonshot fetcher enforces neither)
- TodoList: query mode triggers on omitting `todos`, not on zero args
- TaskList: command/PID/exit code are shell-task fields only
- CronCreate: the returned fields include `cron`
- SetGoalBudget: turn/token budgets are rounded up to >= 1, not rejected below 1

Each change is covered by a description/param assertion; plan.test.ts snapshots
refreshed for the longer plan-mode reminder.

* fix(agent-core): gate prompt tool guidance on runtime availability, not declared profile tools

The HAS_* / Skills gating computed flags from the profile's declared tools, but
Agent/AgentSwarm only register when a subagentHost exists (ToolManager
.initializeBuiltinTools). A runtime built without a subagentHost (e.g. direct SDK
construction) therefore rendered the explore-delegation guidance for an Agent
tool the model could not call.

SystemPromptContext now carries an optional availableTools; buildTemplateVars
gates on it when present and falls back to the declared tools otherwise. useProfile
passes the profile tools minus Agent/AgentSwarm when no subagentHost is wired, so
the render reflects what the model can actually call. The normal session path
(subagentHost always defaulted) is unchanged.

* fix(agent-core): exempt the plan-mode plan file from the Write *.md ban

Plan mode writes its plan to plans/<id>.md (plan/index.ts) and the reminder tells
the model to create it with Write when missing, which contradicted Write's blanket
'do not create *.md unless asked' guard. Carve the plan file out of the ban.

* fix(agent-core): scope plan-mode prompt guidance and the Write *.md ban to runtime reality

- Gate the TodoList bullet's "enter plan mode via EnterPlanMode" suggestion
  on a new HAS_ENTERPLANMODE flag. A custom profile that keeps TodoList but
  drops EnterPlanMode no longer steers the model toward a tool it cannot call;
  the default profile render is unchanged.
- Reframe the Write *.md prohibition around intent (unsolicited docs) instead
  of a blanket extension ban, so artifacts a task or project instruction
  requires — the plan-mode plan file, a repo-mandated changeset — are no
  longer contradicted by the tool's own rules.

* refactor(agent-core): move tool-coupled guidance into tool descriptions

The default system prompt carried tool-usage guidance behind {% if HAS_* %}
gates that re-derived, in prose, the availability the tool schema already
encodes — and the same guidance was duplicated in each tool's own
description. Drop the four gated blocks (background Bash, Agent/explore,
TodoList, EnterPlanMode) and the compaction TaskList/TodoList bullets; the
tool descriptions, shipped only when the tool is registered, already carry
the same instructions, so subagents and tool-trimmed profiles are no longer
pointed at tools they lack.

Fold the two genuinely unique lines into the tool descriptions: bash.md
gains "return control after starting a background task", agent.md gains the
context-hygiene reason to delegate. Collapse the compaction bullets into one
tool-agnostic sentence. Remove the now-unused availableTools / HAS_* render
machinery.

* fix(skill-tool): clarify no-reinvoke guard and argument handling in tool description

* feat(fetch-url): indicate content retrieval mode in output for better model context

* fix(agent-core): correct goal-budget rounding and task-output failure docs

set-goal-budget.md said turn/token budgets are "rounded up", but the code uses
Math.round — say "rounded to the nearest whole number" instead. task-output.md
implied every failed task carries terminal_reason/stop_reason, but a plain
non-zero command exit carries only status plus exit_code; describe that exit_code
path and reserve terminal_reason for non-exit endings (timeout, explicit stop,
or an internal error with no exit code).

* fix(agent-core): scope free-work guidance by role and steer one-shots near-term

The blast-radius paragraph told every profile that local work — including
editing files — may be done freely, but the read-only explore/plan subagents
render it too; scope it to "work your role permits" so it no longer undercuts
their read-only constraints.

The one-shot cron guidance leaned on a year-boundary heuristic ("avoid a
day/month already passed this year") that misfires across Dec 31 to Jan 1 and
duplicated a limit the code already enforces. Replace it with a plain near-term
nudge and leave the hard future-window guard in code.

* fix(enter-plan-mode): clarify availability of Agent tool in plan mode description

* fix(agent-core): surface Grep count_matches total and pagination in output

count_matches put the aggregate "Found N occurrences" summary and the
"Results truncated... use offset=N to see more" notice on the result's
message field, which normalizeToolResult drops before the result reaches the
model. The model saw only the path:count lines and could miss the total and,
worse, the pagination cue — so it would not know to page through truncated
counts. Append both to output after the path:count lines, the same way the
content and files_with_matches modes already inline their notices.

* fix(grep): reorder count summary and results in output for clarity

* chore(changeset): consolidate prompt-hardening changesets into one

Squash the five per-change changesets for this PR into a single concise
entry; they all bump @moonshot-ai/kimi-code (patch) for the same
system-prompt and tool-description hardening work.

* fix(agent-core): stop the agent from blocking on background tasks

Both the Agent and Bash background-launch messages invited the model to "peek
at progress" via TaskOutput, and the foreground-vs-background guidance had been
thinned to a single parameter hint. Together that led the model to launch a
background subagent and then immediately wait on it through TaskOutput —
defeating the point of background execution.

Make both launch messages take the same anti-wait stance the user-detach path
already uses (do NOT wait, poll, or call TaskOutput on it), restore
foreground-by-default guidance in the Agent background description (run in the
background only when you have other work and do not need the result to proceed),
and add a TaskOutput backstop against using it to sit and wait. Also fold the
fix into the consolidated changeset.
2026-06-26 16:56:40 +08:00
liruifengv
b51e13538d
ci: run unit tests on windows (#1037)
* ci: run unit tests on windows

* fix(migration-legacy): align workdir bucket key with agent-core

computeWorkdirBucket used a local node:path-based resolve that yields backslash-separated paths on Windows, while agent-core's encodeWorkDirKey uses pathe (forward slashes on every platform). The SHA-256 inputs diverged, so migrated sessions were written to a bucket that the session picker never reads, making them invisible on Windows.

Alias computeWorkdirBucket to encodeWorkDirKey so both sides stay byte-identical, drop the local slugify copy, and update the workdir-bucket test reference accordingly.

* test(acp-adapter): expect platform-native separators in e2e-fs path

The e2e-fs test asserted the fs/readTextFile wire path as the raw POSIX targetPath, but AcpKaos.toClientPath converts '/' to '\' when the inner LocalKaos reports pathClass 'win32' (Windows). On Windows the wire path became '\Users\test\x.ts' and the assertion failed.

Mirror toClientPath in the test: expect backslash separators on win32 and the raw path otherwise. Implementation is unchanged.

* test(sdk): normalize workDir and skillDir paths in session tests

SessionStore.create/list and the skill loader normalize paths through pathe (forward slashes). The SDK tests compared the resulting workDir and skill loaded-dir against raw mkdtemp / node:path strings, which use backslashes on Windows (and node:fs realpath also returns backslashes for the skill dir), failing three toMatchObject assertions.

Build the expected paths with agent-core's normalizeWorkDir so they match the internal pathe representation on every platform. The skill dir keeps its realpath() (the loader realpaths the root) and only normalizes separators.

* test(skill): normalize realpath to forward slashes in scanner tests

resolveSkillRoots normalizes every root.path through fs.realpath followed by replacing backslashes with forward slashes (scanner.ts). The scanner tests compared root.path against node:fs realpath directly, which returns backslashes on Windows, so twenty assertions failed (toEqual / toContain / toHaveLength) even though the resolved paths were identical.

Wrap realpath at the top of the test file to mirror the implementation's normalization, so every comparison uses the same forward-slash form on every platform.

* test: skip Unix-only permission tests on Windows

The Unix file-permission assertions (mode bits like 0o600 / 0o700 and chmod 000 making a path unreadable) have no equivalent on Windows, which uses ACLs; fs.chmod there can only toggle the read-only bit. These six tests failed on Windows with mismatched mode values or a missing 40411.

Skip them on win32 via it.skipIf(process.platform === 'win32'): oauth FileTokenStorage (0600 file, 0700 dir), agent-core BackgroundTaskPersistence (0700 tasks dir), agent-core createPerIdJsonStore (0700 subdir), migration-legacy atomicWrite (0600 file), and server fs:browse (chmod 000 -> 40411).

* test(tui): make platform-sensitive assertions cross-platform

The TUI implementations are already platform-aware (pathe-style paths, pathToFileURL, quoteShellArg cmd/POSIX quoting, Alt+V on Windows for paste expansion), but the tests hard-coded POSIX expectations and failed on Windows.

Align the assertions with the implementation's platform behavior: footer-goal-badge matches the '[goal' badge prefix instead of /goal/ (toolbar tips contain '/goal'); tool-call expects backslash relative paths on win32; plan-box builds the file:// URL via pathToFileURL; custom-editor sends Alt+V on win32 for paste expansion; file-mention-provider normalizes the expected description to forward slashes; kimi-tui-startup builds the resume command with quoteShellArg; kimi-tui-message-flow builds the expected install path with resolve().

* test: align path assertions with pathe on Windows

Several test suites asserted paths produced by node:path/node:os/node:fs against values that agent-core, node-sdk and kaos normalize through pathe (forward slashes). On Windows the two forms diverge (backslashes vs forward slashes), failing about 19 assertions.

Mirror the implementation's normalization in the assertions via a local toPosix helper (or agent-core's normalizeWorkDir), so expected paths use forward slashes on every platform: kaos LocalKaos, node-sdk export/list/resume/config/transport sessions, cli FileMentionProvider, and agent-core skill-session.

* test(native): build path expectations with node:path.resolve

paths.mjs builds every path with node:path.resolve, which yields backslash-separated absolute paths on Windows. The path-helpers tests asserted against template strings that mixed the backslash appRoot with forward-slash segments, so Object.is failed on Windows even though the strings looked identical.

Build the expectations with the same resolve(appRoot, ...) helper so the separators match on every platform.

* fix: make Windows CI tests pass across all packages

Fix the remaining Windows CI failures so the Windows test job can go green. The changes fall into a few categories:

- Path separators: agent-core/node-sdk/kaos normalize paths via pathe (forward slashes); align test expectations and a couple of implementations (native cache base, workspace registry) with that.

- Platform-only services: skip launchd/systemd manager suites on win32 (Windows uses schtasks).

- Process/signal lifecycle: skip or relax tests that rely on POSIX signals / SIGTERM semantics that Windows does not support.

- Hook shell syntax: rewrite hook test commands from POSIX shell (single quotes, semicolons, stderr redirects, if/then/fi) to node -e / .cjs files that run under cmd.exe.

- CRLF: make Bash tool description stripping tolerate CRLF line endings.

- Misc: realpath short-name divergence, port-retry timing, telemetry spawn, fs-watch timing, snapshot path normalization, etc.

* fix: remove unused basename import in workspaceRegistryService

Fix lint error (no-unused-vars): basename from node:path is no longer used after switching to posixBasename from pathe.

* fix: align resume harness pathClass and wait for banner state on Windows

Two more Windows CI fixes:

- createResumeNoSideEffectKaos now reports pathClass 'win32' on Windows so tool descriptions (e.g. Glob's Windows note) match the live agent in expectResumeMatches, fixing usage/description deep-equal drift.

- kimi-tui-startup once-banner test now waits for writeBannerDisplayState to land before asserting, since the atomic write can lag behind the render on Windows.

* fix: resolve remaining Windows unit test failures

Make the new Windows CI job green across agent-core, kaos, node-sdk and server:

- Align the resume harness kaos pathClass with the live agent so platform-conditional tool descriptions (Glob's Windows note) match in expectResumeMatches instead of drifting on win32.
- Rewrite hook commands in agent-core tests as cross-platform node one-liners; single-quote echo, >&2 and ';' do not work under cmd.exe.
- Add .gitattributes enforcing LF so raw-imported templates (e.g. the compaction instruction) produce byte-identical token counts on Windows and POSIX.
- Terminate the full process tree on Windows in both the hook runner and kaos (taskkill /T /F) so grandchildren cannot outlive their parent and keep the cwd locked.
- Normalize workDir path separators in two kimi-sdk session tests to match the stored canonical form.
- Avoid cmd.exe arg-quoting pitfalls in the kaos cmd.exe test, and run the Windows process-tree kill test from a script file with the pid path passed via argv.
- Give the first fs-git e2e test more time on Windows and retry the temp-dir cleanup; skip the fs-watch overflow-burst assertion on Windows where fs-event coalescing prevents the single-window spike.

* ci: retrigger checks

* fix: resolve remaining Windows failures after merging main

- Terminate the spawned git/gh process tree on Windows in FsGitService (taskkill /T /F on timeout) so a timed-out 'gh pr view' cannot leave a grandchild holding the workspace cwd, which made the fs-git e2e cleanup fail with EPERM.

- Give the fs:git_status e2e suite a longer timeout on Windows and retry the temp-dir cleanup longer to ride out the slower child-process teardown.

- Make the third-party plugin install trust test assert the resolved install path via node:path so it matches the Windows-resolved path (D:\tmp\...) as well as the POSIX one.

* fix: align workspace registry roots and harden fs-git cleanup on Windows

- workspace-registry test: compare normalized (forward-slash) roots, since the registry and session index both store workDir via pathe.resolve (forward slashes on every platform). realpath() yields backslashes on Windows and diverged from the stored root.

- fs-git e2e: bump the temp-dir cleanup retries and the afterEach timeout, since Windows child-process teardown after server.close() is asynchronous and can keep the workspace cwd locked for several seconds.

* test: stub openUrl in kimi-tui-message-flow feedback tests

The /feedback command falls back to openUrl(FEEDBACK_ISSUE_URL) when submission fails, which spawned a real browser window on every test run. Mock #/utils/open-url (matching the existing login/message-replay/server test convention) so the suite never opens a browser.

* test: harden fs-git e2e cleanup against Windows cwd locks

On Windows, git/gh child processes and the session core process can outlive server.close() and keep the temp workspace as their cwd, so rmSync fails with EPERM even after a long retry. Add rmSyncRobust that retries and, if the cwd is still locked, swallows EPERM/EBUSY on Windows — the OS reclaims the temp dir and a cleanup hiccup must not fail an otherwise-passing test.

* test: harden server e2e cleanup against async teardown races

server.close() does not fully await the server's asynchronous teardown, so on a loaded CI runner the temp home/workspace dirs can still be held or written to when the afterEach rmSync runs, failing with EPERM (Windows) or ENOTEMPTY (Linux). Use a rmSyncRobust helper (retry + swallow EPERM/EBUSY/ENOTEMPTY) in the fs-git and question e2e cleanup. Also fix a leftover `throw err` (renamed to `throw error`) that broke the typecheck.
2026-06-26 11:56:41 +08:00
qer
fe667d7c2e
fix(reload): re-inject plugin session-start reminder after /reload (#1086)
* fix(reload): re-inject plugin session-start reminder after /reload

Reload reloaded plugins and resumed the session, but the model kept seeing the stale plugin session-start reminder from before the reload, so plugin skill changes only took effect in a fresh session.

Append a fresh plugin_session_start reminder to the main agent after reload, gated on a new forcePluginSessionStartReminder flag that only the explicit /reload command sets, so config and experiment toggles that reuse the reload RPC do not spam the transcript.

* fix(reload): keep reload result fresh and neutralize stale plugin reminder

Append the plugin session-start reminder before constructing ResumeSessionResult so SDK callers reading getResumeState() see the refreshed plugin context instead of a pre-reload snapshot.

When a plugin with a prior plugin_session_start reminder is disabled or removed, append a neutralizing reminder so the model does not keep following stale plugin instructions.

* fix(reload): neutralize stale plugin reminder after compaction

A full compaction folds the discrete plugin_session_start reminder into a compaction_summary, so the origin-only scan no longer detects it. Also treat a compaction_summary in history as a signal to neutralize, so disabling or removing a plugin after compaction still emits a superseding 'no active plugin session starts' reminder.

* fix(reload): thread plugin reminder option through KimiHarness.reloadSession

KimiHarness.reloadSession is a public SDK entry point; forward forcePluginSessionStartReminder to both the active-session and RPC reload paths so SDK callers using the harness can opt into the refreshed plugin reminder too.
2026-06-25 23:16:55 +08:00
liruifengv
2db5fc20ec
feat: add shell mode (!) to the CLI (#1079)
* feat: add shell mode (`!`) to the CLI

Add shell mode, letting users run shell commands directly from the prompt
with `!`. Output streams live into the transcript, supports backgrounding
(ctrl+b), cancellation (Esc / Ctrl+C), input queuing while running, and
enters the conversation context with resume support.

* feat(kimi-code): show shell mode label on editor border and add tip

Render a "! shell mode" label on the top-left of the editor border while the editor is in `!` bash mode, so the active mode is visible at a glance. Also add a rotating toolbar tip (`! to run a shell command`) to surface the feature.

* feat(kimi-code): refine shell mode queue, history, and display

- Keep `!` commands out of input history so they never resurface as bare text stripped of their `!`.

- Make `!` commands non-steerable: Ctrl-S skips them (they stay queued to run after the current task) and the steer hint is only shown when something is actually steerable.

- Render queued `!` commands with a `$` prompt and the shell-mode hue so they read as commands, not as text to send to the model.

- Echo executed shell commands with a `$` prompt instead of `!`.

* fix(kimi-code): sanitize shell output and harden rendering

Captured shell command output can contain terminal control sequences (colours, cursor moves, alternate-screen switches, OSC hyperlinks, carriage-return spinners, bells). pi-tui's Text passes strings straight to the terminal, so any unhandled sequence was executed by the terminal and fought with pi-tui's own cursor control, producing the blank-screen-plus-leftover-characters mess after running commands like pnpm dev or a nested TUI.

- Sanitize CSI (incl. private modes), OSC, single-char ESC and C0 control chars (keeping newline and tab) in both the finished/resume view (previously unsanitized) and the running tail.

- Make the sanitize, format, and ShellRunComponent render paths never-throw, and cap the live running buffer, so a misbehaving command cannot crash the TUI.

- Dispose transcript children on clear so ShellRunComponent's timer is released on /clear or session switch.

* fix(kimi-code): render shell command echo with $ instead of sparkles

The shell command echo is a 'user' transcript entry, so UserMessageComponent prefixed it with the USER_MESSAGE_BULLET (sparkles), producing 'sparkles $ command'.

Add an optional bullet override to UserMessageComponent / TranscriptEntry and set it to an empty string for the shell echo (both live and resume), so the '$ command' content sits at the leading column where the sparkles marker used to be. Normal user messages keep the sparkles bullet.

* fix(kimi-code): enter shell mode when pasting a !-prefixed command

The bash-mode trigger only handled the single ! keystroke, so a pasted !cmd was inserted as literal text in prompt mode and submitted as a normal message.

After pi-tui inserts pasted content, detect an empty-prompt buffer that now starts with !, switch to bash mode, and strip the leading ! so the buffer holds only the command, matching the typed ! path.

* fix(kimi-code): restore shell mode when recalling a queued command

recallLastQueued() dropped the queued item's mode, and the Up-arrow recall only restored the text. A queued ! command (queued while another command runs, which resets the editor to prompt mode) therefore came back as a normal prompt and was submitted as a message instead of a shell command.

Return the full QueuedMessage from recallLastQueued() and restore editor.inputMode (plus the onInputModeChange sync) from the recalled item's mode.

* feat(kimi-code): use violet as the shell mode color

Replace the claude-code-style magenta/rose shellMode token with a violet that is distinct from plan-mode blue, the user role amber, success green, error red, and the teal accent.

Custom themes that omit the token fall back to this new default via the base+overrides merge, so existing custom themes keep working unchanged.

* chore: refine the shell mode changeset

* docs: document shell mode

Add a Shell mode section to the interaction guide and list the ! and Ctrl+B shortcuts in the keyboard reference, in both English and Chinese.

* test(protocol): include shell events in volatile classification check

shell.output and shell.started were added as volatile event types for shell mode; update the snapshot test's volatile-type list and count accordingly.

* fix(agent-core): surface shell command failure reason with no output

When a ! shell command fails without producing stdout/stderr (non-zero exit with no output, timeout, spawn failure), the failure reason lived only in the tool result's output and the TUI showed '(no output)'. Fold it into stderr so the live view and replay show what went wrong.

* fix(kimi-code): decode CSI-u ! to enter shell mode

In terminals with the Kitty keyboard protocol (VSCode integrated terminal, Kitty), pressing ! arrives as a CSI-u sequence, so the raw normalized === '!' comparison never matched and shell mode could not be entered by typing !. Decode with printableChar before comparing, matching every other printable-key check in the TUI.

* fix(kimi-code): do not steer while a shell command is running

Ctrl-S steers queued input into the running turn, but a shell command is not an agent turn, so steering during streamingPhase === 'shell' would launch a turn before the command output is recorded. Keep Ctrl-S a no-op during shell runs; queued messages stay queued.

* fix(agent-core): escape bash tag delimiters in shell output

Shell command output is arbitrary text; if it contains a bash tag delimiter such as </bash-stdout>, the recorded pseudo-XML wrapper breaks and replay extracts the wrong slice. Escape the content when wrapping it in agent-core and unescape when extracting during replay, so output survives round-trip intact.

* docs: document the shellMode theme token

The shellMode color token was added to the palette but not propagated to its mirrors. Add it to the custom-theme docs token table, the theme JSON schema, and the custom-theme skill token list.

* feat(agent-core): reset background task deadline on detach

Add a resettable deadline timer to BackgroundManager and let tasks register a detach timeout; when a foreground task is moved to the background, its deadline resets to the background default counted from the detach moment.

Wire this into shell mode so ! commands run with a 3-minute foreground timeout and get 10 minutes once detached to the background, instead of staying bounded by the original 60-second foreground deadline.

* feat(agent-core): lower shell mode foreground timeout to 2 minutes
2026-06-25 21:24:53 +08:00
liruifengv
27ef516695
feat(agent-core): add config hint to max-steps-per-turn error (#1097)
* feat(agent-core): add config hint to max-steps-per-turn error

* test(agent-core): assert config hint in max-steps error
2026-06-25 15:49:44 +08:00
_Kerman
ea6a4bfe6e
fix: preserve long tool output (#1062)
* fix: persist truncated foreground bash output

* fix: persist oversized tool results

* fix: link background task notifications to saved output

* fix: avoid lossy tool result budgeting

* fix

* fix: include fallback task output previews

* fix

* fix
2026-06-24 14:42:11 +08:00
_Kerman
c240bfab7d
fix(agent-core): realign mid-history interrupted tool calls on resume (#1027) 2026-06-23 22:39:19 +08:00
liruifengv
c0eeca2469
feat: add workspace add-dir support (#812)
* feat: add workspace add-dir support

Add multi-directory /add-dir management with session-only or project-remembered persistence, directory completion, confirmation UI, and runtime workspace/permission wiring.

* fix: honor --add-dir for resumed sessions

Pass CLI additional directories through shell and prompt resume paths, resolve caller-relative dirs against workDir, and add regression coverage.

* fix: keep additional dirs AGENTS.md out of default context

Load only user-level and cwd AGENTS.md by default, while preserving additional directory listings in the prompt context.

* feat: append /add-dir result as user message

Add a session appendUserMessage RPC and use it after /add-dir so the command result is recorded as a normal user message and surfaced in the transcript.

* docs: add add-dir research and follow-up todos

Document the add-dir / local-command-stdout research findings and the follow-up tasks for stdout wrapping, slash file completion, and hints.

* feat: wrap /add-dir output as local-command-stdout

Insert the /add-dir result as a user-role <local-command-stdout> record with an injection origin directly inside Session.addAdditionalDir. It enters the model context on the next turn but does not start a turn, and stays out of the live and resumed transcript; the transient status toast is kept for immediate feedback. --add-dir is unaffected since it bypasses addAdditionalDir.

Remove the now-unused appendUserMessage RPC and SDK method.

* feat: reopen /add-dir completion after accepting a directory

Generalize the slash-argument completion reopen so it fires whenever the text before the cursor ends with '/', not only when the literal '/' key is typed. After Tab-accepting a directory (or auto-applying a single-child dir), the next level's completion list reappears automatically, so repeated Tab keeps drilling down into subdirectories. '@' file mention is unaffected.

* feat: reopen file mention completion after accepting a directory

Extend the path completion reopen so it also fires for '@' file mentions. After Tab-accepting a directory in an '@' mention, the next level's completion list reappears automatically, matching the '/add-dir' continuous-Tab behavior.

* feat: show inline argument hints for slash commands

Render a dim ghost-text argument hint inside the input box after a slash command that takes arguments, replacing the popup-only hint that was easy to miss. The hint appears once the command is typed and disappears as soon as an argument is entered, and is truncated to fit the box width. Add argument hints for /compact, /swarm, /goal and /title; /add-dir already had one.

* test: remove stale additional-dirs AGENTS.md assertion

The subagent-host test still asserted that an additional directory's AGENTS.md content appears in the agent system prompt, but additional-dirs AGENTS.md has been intentionally excluded from the default context since an earlier commit (covered by context.test.ts). Drop the stale assertion.

* fix: resolve /add-dir paths against workdir and persist via kaos

Resolve user-supplied /add-dir paths against the current workdir instead of the project root, so launching from a subdirectory behaves like the CLI --add-dir flag. Also route the local.toml read/write through the kaos abstraction instead of host fs, so the remember path works for non-local sessions.

* fix: expand ~ in /add-dir paths before resolving

The /add-dir completer emits ~/... values, but the core treated ~/foo as a relative path because pathe isAbsolute('~/foo') is false, producing <workDir>/~/foo. Expand ~ and ~/ to the home directory (via kaos.gethome()) before resolving.

* chore: remove add-dir dev docs from the branch

These were working notes (research and follow-up todos) that don't belong in the PR.

* chore: clarify add-dir changeset for users

* docs: document /add-dir, --add-dir, and local.toml

* test: flush records before reading wire in add-dir runtime tests

FileSystemAgentRecordPersistence.append buffers records and flushes asynchronously, so readMainWire can read the wire before the local-command-stdout record lands. Flush the main agent's records explicitly in the two add-dir runtime tests to make them deterministic.
2026-06-22 19:42:13 +08:00
7Sageer
4292ae9f9b
fix: surface provider content filter and preserve context tokens (#963)
* fix: surface provider content filter and preserve context tokens

* fix: complete filtered turn handling across surfaces

- context: accumulate token estimate for zero-usage steps to preserve
  the tokenCount / tokenCountCoveredMessageCount invariant
- turn/goal: pause the goal when a turn is blocked by safety policy
- subagent: surface a filtered child turn as a distinct error
- acp: map filtered to the native ACP refusal stop reason
- tui: show a filtered-specific message in the btw panel
- cli: drop the redundant content_filter suffix from the error message
- tests: cover filtered across cli, web, acp, and goal flows
2026-06-22 17:32:05 +08:00
7Sageer
3443a00a43
feat(agent-core): add thinkingLevel to compaction telemetry (#954)
Report the effective thinking effort (off/low/medium/high/xhigh/max) on
compaction_finished, compaction_failed, and micro_compaction_finished events.
The value matches the thinking level applied to the compaction provider, so we
can analyze how thinking mode affects compaction token usage, duration, and
failures.
2026-06-22 12:09:29 +08:00
_Kerman
ba64072559
feat: detach foreground tasks to background (#821)
Some checks failed
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / typecheck (push) Has been cancelled
Nix Build / Check flake.nix workspace sync (push) Has been cancelled
Release / Release (push) Has been cancelled
Release / Native release artifact (push) Has been cancelled
Nix Build / nix build .#kimi-code (push) Has been cancelled
Release / Deploy docs (push) Has been cancelled
Release / Publish native release assets (push) Has been cancelled
2026-06-20 21:24:22 +08:00
_Kerman
7644f1036c
fix: drop empty text blocks during projection (#910)
Some checks are pending
CI / build (push) Waiting to run
CI / test (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 / Publish native release assets (push) Blocked by required conditions
2026-06-19 23:33:38 +08:00
Luyu Cheng
cde7ca51cc
feat(goal): support guided goal authoring (#839) 2026-06-18 18:48:23 +08:00
Haozhe
9a8fea5c85
feat(web): introduce Kimi web app and daemon gateway (#625)
* docs(reports): collapse P3 plan into a single final-solution doc

Drop the per-step TDD/commit scaffolding; keep the substance as one final
approach per area (what it does, files to touch, key types/events/projection,
component responsibilities, verification, risks, sequencing).

* fix(kimi-web): normalize chat block spacing

Group consecutive tool cards structurally so chat block spacing is applied consistently without leaking card borders or shadows.

* feat(web): land P3 — goal / swarm / subagent + terminal + view split

Implements the locked P3 design end-to-end:
- subagent lifecycle projection (spawned→started→suspended→completed/failed) +
  inline Agent / AgentGroup cards; swarm progress card (multi-column) derived
  from swarmIndex; goal dock strip (expandable) from goal.updated; plan/goal/
  swarm activation badges in the composer status line.
- terminal as a view (xterm + WS terminal_* frames with since_seq replay) and a
  tab/view-dimension split (usePaneLayout tree + ViewGroup + SplitLayout, VSCode
  editor-group style), persisted to localStorage.
Adds swarm-groups / subagent-goal / agent-group-turns unit tests and stub-daemon
seeds. 98 tests pass; vue-tsc + oxlint clean; production build OK.

Accepted by review (see reports/web-p3-acceptance.md); no blocking issues.

* docs(reports): P3 landing acceptance review

Comprehensive acceptance of the P3 landing (f5a7f21c): per-area verdicts, the
terminal 'map' crash explained as a stale-stub test artifact, non-blocking
recommendations, and verification record (98 tests, vue-tsc/oxlint clean, prod
build, in-browser smoke). No serious issues found; no code changed per the
'only fix serious issues' instruction.

* fix(terminal): make node-pty load and spawn in packaged + pnpm-dev builds

Two distinct PTY failures:
- 'Failed to load native module: pty.node' (npx/published daemon): node-pty was
  transitively bundled via @moonshot-ai/services (alwaysBundle), inlining its JS
  while its native binary can't be bundled and wasn't shipped. Mark node-pty
  external in tsdown (neverBundle) and declare it as a runtime dependency of
  @moonshot-ai/kimi-code so npm/npx installs it with its prebuilt pty.node.
- 'posix_spawnp failed' (local pnpm dev): node-pty's prebuilds/*/spawn-helper
  loses its +x bit through pnpm's store extraction. Add a root postinstall
  (scripts/fix-node-pty-perms.mjs) that restores the executable bit; verified it
  fixes a reproducible spawn failure.

Also harden defaultShell() to fall back on an empty (not just unset) $SHELL.

Note: the SEA standalone binary still needs node-pty's pty.node + spawn-helper
wired into scripts/native/native-deps.mjs (not addressed here; npx path covers
the reported case).

* fix(web): use a real monospace font + tighter line height in the terminal

xterm's fontFamily takes a literal font string, so 'var(--mono)' never resolved
and the terminal fell back to courier with loose metrics — the wrong-looking
font and spacing. Pass the actual JetBrains Mono stack, await document.fonts
before xterm measures the cell (so the variable font isn't mismeasured), tighten
lineHeight 1.25 → 1.1, and pin letterSpacing 0.

* style(web): drop the staggered line-in animation on expanded tool-call output

Remove the per-line kimi-line-in stagger on `.box.open .bb > div` (modern/kimi
themes) and its keyframes — expanding a tool card no longer animates each output
line in.

* feat(web): move the tool-call summary into the card when expanded

Previously the command/summary always sat on the header. Now it shows on the
header only while collapsed; expanding hides it from the header and renders it
at the top of the card body (above the output) — so it appears exactly once and
the expanded header stays clean. Re-adds the .bb-summary style and a mount test.

* feat(web): show the full, un-truncated summary in the expanded tool card

The expanded body has room to wrap, so it shouldn't keep the header's '…'
clip. Add a `full` flag to toolSummary that skips the length clip and use it for
the .bb-summary; the collapsed header keeps the clipped form (CSS ellipsis still
guards overflow). Extends the mount test to cover full-vs-clipped.

* revert(web): keep the sending moon until the turn ends

Reverts 980ff9d4: dropping the moon the instant the first token streamed wasn't
wanted. Remove the assistantDelta/messageUpdated clear so sendingBySession is
again cleared only on turn end (onSessionIdle), restoring the prior behavior,
and delete the now-moot sending-moon test.

* style(web): bump composer textarea font-size to 14px

The composer input (.ph) under the modern/kimi themes was 13px while the
terminal-theme baseline is 14px. Unify on 14px so the textarea text matches
the rest of the composer.

* fix(web): dedupe the daemon echo of an image steer (no double user bubble)

Steering an image while a turn was running rendered TWO user bubbles and the
steer text looked like it never landed. Two causes:

1. The reducer matched the daemon's user-message echo to our optimistic copy by
   exact content equality. Image content serializes differently on each side
   (our {source:{kind:'file',fileId}} vs the daemon's resolved URL/base64), so
   the echo never matched and appended a duplicate. Match by prompt_id first
   (stamped on the optimistic message at submit), falling back to content.

2. Optimistic message ids were msg_opt_<Date.now()>. A queued send + a steer in
   the same millisecond collided on one id, so the prompt_id stamp landed on the
   wrong message. Use a monotonic counter for a unique id per optimistic message.

steerPrompt now also stamps the real prompt_id onto its optimistic echo, like
submitPromptInternal already did.

* fix(web): don't flash the chat pane when opening an empty session

Selecting a never-opened session set sessionLoading=true until its snapshot
arrived, so the chat pane (loading spinner) rendered for a beat before the
empty-composer. A session the daemon reports as empty (messageCount 0) has
nothing to load — keep sessionLoading false for it so the empty-composer shows
immediately. Non-empty sessions still show the loading state.

* fix(web): auto-scroll to the latest content after a mid-stream refresh

Refreshing while a turn was streaming left two things parked above the live
output:

- The thinking block's inner 5-line window stayed at its TOP. Its scroll watcher
  only re-pins when already at the bottom, but a refresh delivers the whole
  thinking text at once with scrollTop 0. Pin a streaming block to its latest
  line on mount.

- The transcript could stop short of the bottom: the first scroll runs before
  markdown highlighting/images lay out and grow the content. Re-pin on the next
  couple of frames (only while still following) so a refresh ends at the latest
  content.

* fix(web): stop subagent turns from fragmenting the parent transcript

A subagent runs under the parent session id and streams its own turn / step /
delta / tool frames over the SAME session channel, each tagged with the
subagent's agentId. The web projector folded them into the parent transcript,
which produced the reported bug: empty 'skeleton' assistant bubbles (a subagent
turn.step.started opened a parent assistant message the main agent never filled)
and fragmented snippets (subagent deltas appended to the parent).

Skip transcript-building frames whose agentId is a non-main subagent, mirroring
the server's InFlightTurnTracker (which already tracks only main-agent
activity). Subagent progress is unaffected — it flows through the
subagent.* -> task -> AgentCard path, which is intentionally not gated.

* feat(web): remove the floating todo/background-task overlay

The wide-screen float-stack pinned a todo card + running-tasks card to the
top-right of the chat. Drop the overlay entirely (and the now-unused
TasksCard.vue) — todos and background tasks live in their own ~/todo and ~/tasks
tabs, so the overlay was a redundant, transcript-covering duplicate.

* feat(web): show all background tasks in the tasks tab, scroll on overflow

The tasks tab capped the list at 5 rows and showed '… +N more', hiding the rest
even with plenty of room. Render every task and let the list scroll internally
once it overflows the pane, so nothing is silently dropped.

* feat(web): running spinner + unread blue dot left of the session title

The gutter slot left of each session title (which kept the title aligned under
the workspace name) now carries a status indicator instead of being an empty
spacer:
- a small SVG spinner (Kimi-blue arc) while the session is running, replacing
  the old absolutely-positioned pulse dot;
- an unread blue dot when a BACKGROUND session finished a turn the user hasn't
  opened yet. Tracked via unreadBySession (set on idle for a non-active session,
  cleared when the session is selected).

* feat(web): unify archive/remove wording + keep the confirm within the title

- Clarify the two list-removal actions: a session is 'Archive' (归档), a
  workspace is 'Remove workspace' (移除工作区) — the workspace menu used the bare
  'Delete', which read as the same action as the session archive.
- Keep the session row's archive-confirm strip aligned under the title: the
  leading gutter slot now persists in the confirm state, so the confirm row
  starts at the title's left boundary instead of spilling to the row edge.

* feat(web): new-conversation button + workspace picker on the empty composer

- Add a compose button in the sidebar header (top-left) that starts a new
  conversation in the active workspace. It wires up the previously-dead 'create'
  emit (handleCreateSession → openWorkspaceDraft).
- On the empty composer, add a workspace picker below the hint so a new
  conversation can be started in any workspace without leaving the screen
  (switching enters that workspace's draft via openWorkspaceDraft).

* feat(web): add a Fork entry to the session row menu

Forking already worked via the /fork command and the daemon's :fork route, but
had no discoverable affordance. Add a 'Fork session' item to each session row's
kebab menu; forkSession() now takes an optional session id so any row (not just
the active one) can be forked.

* feat(web): recall sent messages with ArrowUp/ArrowDown in the composer

Shell-style history: ArrowUp on the first line of the composer walks back
through previously sent messages; ArrowDown on the last line walks forward and
finally restores the live draft. Editing the text leaves history-browsing, and
the edge-line guards keep multi-line cursor movement intact. Submitting (or
steering) a message appends it to the history (consecutive duplicates skipped).

* feat(web): capture console.log/info/debug + reusable log export

The client trace only captured console.error/warn. Capture every console level
(log/info/debug too) when tracing is enabled, so the exported troubleshooting
log reflects the full front-end console. Extract the JSONL download into a
reusable downloadTraceLog() (the debug panel now calls it; a settings 'Export
log' action can reuse it).

* feat(web): extract settings into a dedicated Settings page

Settings used to live in the sidebar account popover (a cramped fixed dropdown
that mixed appearance, language, account and the daemon endpoint). Move them
into a dedicated SettingsDialog modal opened from the header gear:
- Appearance (theme / colour scheme / accent), Language
- Account (provider, add workspace, reopen onboarding, sign in/out)
- Advanced (daemon endpoint, Export log — reuses downloadTraceLog)

The sidebar popover and its anchoring/positioning code are removed; the gear now
just emits openSettings. A Notifications section is added next (T14).

* feat(web): browser notification when a turn completes (with a settings toggle)

When a session finishes a turn and the user isn't already watching it (page
hidden, or a different session is active), fire a browser system notification
titled with the session, clicking it focuses the window and opens the session.
Opt-in via a new Notifications toggle in the Settings page; enabling it requests
OS permission and the preference is persisted (stays off if the user blocks it).

* feat(web): modes selector (plan/goal/swarm) + fix swarm double-render

- The plan pill at the composer's bottom-left becomes a 'Modes' popover that
  groups Plan (a working client toggle) with Goal and Swarm. Each shows its
  activated state (plan on / goal active / swarm n/m), and goal/swarm focus
  their card in the chat when active. The menu is position:fixed so the composer
  input row can't paint over it.
- Fix the swarm 'two blocks' bug: a multi-member swarm rendered BOTH inline as an
  AgentGroup AND as its SwarmCard. messagesToTurns now skips the inline block for
  swarm members (same membership test as buildSwarmGroups), so the swarm shows
  once — its special card in the chat flow.

Note: starting a goal/swarm from the web needs a daemon REST endpoint (the goal
RPC isn't exposed over REST and the daemon doesn't interpret slash commands in
prompts); display + activation state are wired here.

* feat(web): add a chat context header (workspace/session, git, open, copy, PR)

A thin bar above the chat shows the workspace / session breadcrumb, the git
branch with ahead/behind + changed-file count, an 'open in editor' action
(daemon fs:open on the workspace root), and a 'copy all conversation' action
(reuses ChatPane.copyConversation). It also has a GitHub PR slot that renders
when PR data is available — the daemon doesn't expose PR status yet, so it's
wired but currently passed null. Hidden on mobile and for the empty composer.

* feat(web): default path + fuzzy recursive search in the add-workspace browser

- Open the folder browser at the path kimi-web is working in (the active
  workspace root, falling back to $HOME) instead of always at $HOME.
- The filter becomes an fzf-style search: typing runs a bounded, debounced
  RECURSIVE subsequence-fuzzy walk under the current folder (capped depth/dirs/
  results, cancellable) and lists matching directories by relative path. The
  result list keeps a fixed height, so the dialog never resizes while searching.
- Collapse the paste-an-absolute-path field behind a secondary 'enter a path'
  toggle (auto-expanded when the daemon can't browse).

* chore(web): remove the non-functional /undo slash command

/undo had no daemon endpoint — it only pushed an 'undo not implemented' warning,
so it was a dead menu entry. Remove it from the slash list, the command router,
and the client. The full slash-command review with deletion suggestions for the
remaining commands is in reports/web-goal2-fixes.md (T17).

* docs(reports): results report for the second web TODO sweep (19 items)

* test(web): provide browser storage in vitest under node 24

* docs(reports): add web goal2 acceptance notes

* feat: show shortPath over branch in sidebar workspace header

* feat(kimi-code-web): use rounded chat bubble icon for new session button

* feat(kimi-code-web): add workspace creation in empty composer and tidy settings dialog

* feat: add manual swarm and goal activation to web ui

- Extend protocol schemas with swarm_mode, goal_objective, goal_control

- Add stub diff-dispatch in PromptService for new runtime controls

- Wire swarm/goal state through useKimiWebClient and daemon events

- Add Swarm toggle and Goal create/pause/resume/cancel in Composer modes menu

- Update StatusPanel and MobileSettingsSheet with swarm indicator/toggle

- Add bilingual i18n strings and update fixtures/tests

* feat: remove copy-conversation button from view-tabs

- Drop showCopyConversation / copyConversationCopied props from TabBar and ViewGroup

- Remove the share-conversation button markup and styles from TabBar

- Clean up related i18n strings in en/zh sidebar locales

- Keep the existing ChatHeader copy-all button and internal copy state unchanged

* feat: reorder chat-header layout and simplify git status styling

- Move Copy all button next to the workspace/session title on the left

- Move git branch/status and Open-in-editor to the right

- Shorten editor button label via new openInEditorShort i18n key

- Render ahead/behind/changes as plain colored text without pills

- Update en/zh header locale files

* style: make chat-header action buttons borderless icon + text

- Remove border, background, border-radius, and padding from .ch-act

- Keep label collapse on narrow widths, drop obsolete padding override

* feat: move copy-all to kebab menu and add session actions in chat-header

* feat: redesign chat-header open button with open-in menu

* feat: align chat-header diff stats with git ++/-- red/green style

* style(web): thinner, fainter scrollbars across all components

* fix(web): re-pin chat to bottom when a turn finishes streaming

* fix(web): keep the working moon spinning after a refresh mid-stream

* fix(web): subagent card margins in bubble layout + expandable task/result detail

* feat(web): rebuild subagent cards from the transcript so they survive a refresh

* fix(web): stop code blocks getting stuck on the loading skeleton

markstream's CodeBlock shows a skeleton while !stream && loading, and its
loading prop defaults to true. We never set it, so every settled code block
waited on shiki to highlight before showing anything; a screenful of code
(long session / fast burst) overwhelms shiki and the skeletons get stuck,
leaving the whole page blank. Pin loading:false so blocks render their
plain-text fallback immediately and upgrade to highlighted when ready.

* fix(web): tick running task timers + make task rows expandable to view output

* fix(web): dedupe image-steer echo via a loose (text+image-count) match

The daemon's messageCreated echo can land before submitPrompt stamps the
prompt_id onto the optimistic copy, and an image serializes differently
(file ref vs resolved URL), so neither the prompt_id nor exact-content match
fired and the echo rendered as a SECOND user bubble. Add a loose fallback
matching on text + image-count so the echo reconciles regardless of order.

* fix(web): let ↑/↓ walk all the way through input history once browsing

Recalling a multi-line entry left the caret on its last line, and the
'ArrowUp only on the first line' gate then refused to recall further, so
history only ever went one step back. Once browsing (historyIndex set), walk
history directly regardless of caret line; typing still exits browsing.

* feat(web): minimize button on question/approval cards + stack option label/desc

- Add a minimize toggle so a blocking question/approval can collapse to a thin
  header bar instead of covering the chat; number-key shortcuts are gated while
  collapsed so an unseen option can't be picked.
- Stack each option's label above its description (was squeezed side-by-side
  into many thin lines when the description was long).
Note: there is no question/approval timeout in the codebase (ask-user waits
indefinitely), so the '10 minute' request is a no-op.

* feat(web): archive-confirm text matches title size; workspace remove always hides

- Bump the 'archive session?' confirm label to the session-title size (14px)
  so it lines up with the title instead of reading as a smaller note.
- 'Remove workspace' now always hides the sidebar entry, even when it still has
  sessions: record the root in a persisted hidden set so mergedWorkspaces stops
  re-deriving it from session cwds. History/sessions are untouched; re-adding
  the same path un-hides it.

* feat(web): persist unsent composer drafts per session in localStorage

The composer text is saved under a per-session key as you type and restored
when you switch back to that session or reload the page; sending/steering
clears it. New-session drafts use a '__new__' key.

* feat(web): implement undo + edit-and-resend the last user message

- Wire the daemon POST /sessions/{id}:undo endpoint: client.undo(count) reverts
  the last turn(s) and re-syncs the snapshot. Restore the /undo slash command.
- Add an 'edit & resend' button on the latest user message: it undoes the last
  exchange and refills the composer with that message's text for editing.

* fix(web): reflect the agent's plan mode in the composer toggle

The agent reports plan mode via agent.status.updated (e.g. it auto-entered plan
mode for a 'make a plan' prompt), but the projector only forwarded swarmMode, so
the composer's plan toggle never lit up. Carry planMode on sessionUsageUpdated,
sync it into state, and also read it from GET /status — mirroring swarmMode.

* fix(web): hide an empty {} argument from the tool-call title (kept in details)

An empty tool argument was rendered as a noisy '{}' in the collapsed tool-card
header. toolSummary now returns '' for empty args in header (non-full) mode while
the expanded body still shows it.

* feat(web): show file/media preview as a split pane (peer of chat/files)

On desktop, opening a preview from a chat link/media now splits the layout and
shows it as a 'preview' view at the chat/files level (a transient tab in that
group, closeable via the group's close button) instead of a separate right-side
panel — matching the split buttons. Mobile keeps the full-screen side panel; the
preview view isn't persisted across reloads.

* docs(reports): results report for the third web TODO sweep (16 items)

* chore(kimi-web): temporarily hide open-in-app header menu

* docs: design doc for temporarily disabling swarm and goal modes in web composer

* feat(web): gray out swarm and goal modes with not-supported label

* docs: design doc for composer queue bubble + expanded panel

* feat(kimi-web): move undo button out of bubble with new icon and confirm step

* fix: inherit split layout attributes

* fix(kimi-web): smooth moon spinner speed

* feat(web): wire swarm and goal controls to agent-core

- enable swarm toggle and goal input/pause/resume/cancel controls in Composer\n- add goal error codes and agent-core-to-protocol route mappings\n- wire enterSwarm/exitSwarm and create/pause/resume/cancelGoal RPCs in PromptService\n- bootstrap swarmMode from agent-core state and track it in the shadow\n- update tests for swarm/goal dispatch and session status serialization

* fix(tui): only show provider refresh status for added models

 Skip removed / metadata-only provider updates when reporting model list changes.\n\n add: test to enforce the behavior.

* feat(tasks): add background task command and output polling to web

- include command field on protocol/server tasks\n- support withOutput/outputBytes on task get endpoint\n- poll running task output and fetch final output in web client\n- show bash command and terminal output in TasksPane with copy buttons

* feat(web,server): wire open-in app menu to daemon endpoint

- add /fs:open-in endpoint and command builders for vscode, cursor, finder, iterm, terminal\n- expose installed open_in_apps via meta response\n- filter OpenInMenu by available apps and remove antigravity target\n- support optional line number when opening files in apps\n- add unit tests for open-in launch commands

* feat(kimi-web): expose git diff line stats in chat header

- add additions/deletions to fs:git_status protocol and daemon response\n- compute aggregate diff stats with git diff --numstat HEAD\n- render +N/-N counter and detached HEAD label in chat header\n- update tests and stub daemon fixtures

* fix(kimi-web): hide terminal tab temporarily

* feat(kimi-web): add UI font size setting

* fix(kimi-web): repin chat after tail layout settles

* feat(kimi-web): show live subagent progress

* fix(kimi-web): align plaintext colors in dark mode

* feat(kimi-web): stream running bash output

* fix(kimi-web): avoid shiki overload on large messages

* feat(kimi-web): preselect recommended questions

* feat(kimi-web): add conversation outline nav

* fix(kimi-web): tighten mobile layouts

* feat(kimi-web): animate undo removal

* feat(kimi-web): reorganize bottom dock

* fix(web): lengthen session row running spinner arc

* feat(kimi-web): turn goal mode into a toolbar toggle

Turn the Modes menu's 'Goal' row from a dedicated input form into a
switch that arms the main composer. When goal mode is on, the composer
placeholder prompts for an objective and the next submitted prompt
is sent as a goal via updateSession({ goalObjective }).

The armed state is surfaced in the composer toolbar the same way as
Plan and Swarm: the Modes pill shows a 'Goal' tag and the menu switch
is highlighted. An active (agent-driven) goal continues to expose
Pause / Resume controls in the menu.

Also includes minor bottom-dock spacing alignment changes to keep
goal chips, workbar, and composer visually consistent.

* feat(config): add config API endpoint with redaction support

add GET/POST /api/v1/config routes\nadd ConfigService and config protocol schemas\nredact api_key in config response\nadd web client bindings and config event handling\nadd e2e tests for config routes and ws-broadcast

* fix: keep web tool calls collapsed by default

* feat(kimi-web): split dock work panel into bash, subagent, and todos tabs

- Replace the single Background tasks tab with separate Bash and
  Subagent tabs in the bottom dock work panel.
- Add i18n labels for the new dock tabs in both en and zh.
- Filter task lists by kind and reuse TasksPane for each tab.
- Align left edges of Bash/Subagent/Todos tab bodies and match
  the dock panel width/background to the Goal card style.
- Hide TasksPane header title when rendered inside the dock to
  avoid duplicate headings.
- Make the dock tab header show only the current tab name instead
  of clickable buttons.
- Hide Goal card title summary on expand while keeping layout
  alignment for status/progress/chevron.
- Update unit tests for the three-chip dock behavior.
- Add changesets for the dock split and alignment fixes.

* feat(kimi-web): beta proportional conversation outline with viewport indicator and hover tooltip

* fix(kimi-web): avoid streaming markdown placeholders

* fix(kimi-web): hide legacy conversation outline when beta TOC is off

* chore(kimi-web): rename beta settings section to Experimental / 实验性

* fix(web): scale UI font size consistently

Apply the web font size preference across readable text using the shared UI font scale, keep fixed icon glyph sizes pinned, and raise the default font size to 15px.

* fix(web): remove task tabs from tab bar

* feat(web): refresh OAuth model metadata for always-thinking models

* fix(kimi-web): use 'Sub Agent' and 'Mode' in English labels

* fix(kimi-web): keep swarm subagents across background-task refreshes

REST /tasks lists only the main agent's background-task store and never
returns foreground swarm subagents (kind 'subagent'), which arrive purely
through the WS event stream. Both the 1s output poll and the session-load
task fetch rebuilt tasksBySession from that REST list, so a plain replace
dropped the subagents on every refresh and the next event re-added them —
flickering the swarm/subagent cards, their live "currently doing" line,
and the dock "running" count about once per second.

Add keepLiveSubagents() to carry WS-owned subagent tasks across the REST
refresh (REST stays authoritative for the background tasks it does return)
and use it at both rebuild sites.

* fix(kimi-web): hide completed swarm cards from conversation bottom stack

- Filter out swarm groups whose members are all completed/failed.
- Preserve markstream-vue .table-node styles without overriding its layout.
- Add unit tests for swarm stack visibility.

* feat(session): add session-level abort and expose current_prompt_id in snapshot

- add POST /sessions/{sid}:abort to cancel running turns without prompt_id\n- expose current_prompt_id in in-flight turn snapshot\n- wire session-level abort fallback in kimi-web stop button\n- add IPromptService.abortBySession and getCurrentPromptId\n- update protocol, server, services, and web tests

* fix(server): allow aborting queued prompts and add server-e2e send/cancel coverage

add server-e2e scenario (12-send-and-cancel) and vitest cases for send prompt / cancel prompt flows, including repeated ESC idempotency\nsupport aborting queued prompts in PromptService.abort and add abortSession helper to DaemonClient/HttpClient\nhandle SSE transport case in MCP server mapping and fix related typecheck issues\nadd changeset for @moonshot-ai/services and @moonshot-ai/kimi-code

* fix(kimi-web): prevent file preview scroll jump when opening a file at a line

* fix(kimi-web): show just now for sessions created less than a minute ago

* fix(kimi-web): keep sidebar logo intact and hide product name on narrow sidebars

* fix(kimi-web): preserve markdown code gutter

* feat(web): carry message createdAt into ChatTurn

* feat(web): add formatMessageTime utility

* feat(i18n): localize yesterday label for message timestamps

* feat(web): render timestamp below user query bubble

* fix(web): move user query timestamp outside the bubble

* fix(web): place timestamp on same line as undo action

* fix(web): swap timestamp and undo positions, reveal undo text on hover

* feat(web): make timestamp a button that toggles full date time

* feat(web): update sidebar branding and enlarge session tags; clean up changesets

- Replace "Kimi Code Web" + "BETA" with "Kimi Code" + version pill

- Enlarge session pending tags to match the title font size

- Ignore internal private packages in changeset config

- Remove stale changesets that only affected ignored packages

- Document web release flow in README

* style(web): equalize timestamp and undo button heights and alignment

* feat(web): make workspace names bolder to distinguish from session titles

* feat(kimi-web): rename themes to Explore/Native and remove accent selector

* style(web): nudge undo icon up by 1px

* style(web): align both meta actions to the right

* style(web): remove gap between undo and timestamp buttons

* style(web): nudge undo icon up by another 0.5px

* feat(web): tune workspace name font-weight to 500

* fix(kimi-web): center tag/question text and limit shell-cmd height in approval card

* style(kimi-web): remove icons from session pending tags

* feat(kimi-web): limit recent workspaces in empty-composer picker

* feat(kimi-web): rename sidebar new-workspace button to new-chat and adjust empty conversation title

* style(kimi-web): refine sidebar typography, spacing and font settings

* style(kimi-web): unify Composer typography with sidebar

- Use --ui-font-size for queue text and --ui-font-size-xs for queue labels/bubbles.

- Remove mono font-family from composer queue/bubble elements.

- Normalize perm/mode/model pill text color to --text.

- Set placeholder color to --muted.

* style(kimi-web): keep model pill color dimmed

Revert the model selector pill text color from --text back to --dim

so it stays visually secondary, matching the original design intent.

* style(kimi-web): adjust ChatHeader git status spacing and badge layout

* style(kimi-web): polish composer dock chip styles

* feat(kimi-web): refresh session list relative times on a 30s clock

* fix(kimi-web): decode base64 file content in the preview pane

* feat(kimi-web): show per-session answer/approve tags in the sidebar

* feat(kimi-web): pop the KAP debug panel out into a separate window

* feat(kimi-web): open subagent detail in the side panel; inline agent-live

* style(kimi-web): align DiffView focus outline with KMBlue

* feat(kimi-web): surface goal protocol errors; ignore global config-changed events

* feat(kimi-web): support video attachments in user messages end-to-end

* feat(kimi-web): add /swarm and /goal slash commands

* feat(kimi-web): add /btw side chat backed by child sessions

* style(kimi-web): pin user message bubble font size to 15px

* style(kimi-web): use Lucide PR icon and text-only git status in header

* fix(kimi-web): update undo tooltip copy and reduce hover delay

* fix(kimi-web): auto-scroll to bottom on send, session switch, and tab switch

- Scroll side-chat panel to bottom after sending and while streaming
- Reset scroll baseline on session switch to avoid stale lastScrollTop
- Reset scroll baseline when returning from files tab to chat
- Reset scroll baseline after user sends a message
- Include @moonshot-ai/kimi-code so CLI rebuilds bundle the updated web app

* fix(kimi-web): drop duplicate config-changed case shadowing the real handler

A stopgap no-op `case 'event.config.changed'` (added before the config
feature landed) ended up earlier in the switch than the real configChanged
mapper after merging origin/feat/web, silently swallowing config events.
Remove the no-op so the proper handler runs.

* style(kimi-web): unify PR badge with git status pills and drop changes count

* style(kimi-web): remove unused changes computed in ChatHeader

* fix: keep packaged web build in sync

Build kimi-web before copying packaged web assets and surface the build version plus short commit in the settings dialog.

* fix(web): support slash command input tails

* fix(web): scope composer dock to chat tab

* fix(web): route btw through side-channel agents

* feat(web): open changed files from git status

* feat(web): copy final assistant summary

* feat(web): add tabbed model picker

* fix(web): keep composer input height fixed

* fix(web): preview composer attachments

* feat(web): open workspace links in files tab

* fix(web): stabilize subagent progress

* docs(web): design tab split workflow

* feat(web): connect daemon config settings

* fix: resolve CI failures on feat/web

- add missing 'event.config.changed' case in exhaustive switch test\n- fix oxlint errors (unused import, string spread, unsafe stringification)\n- update protocol test fixtures for additions/deletions, open_in_apps, swarm_mode\n- fix services mcp transport switch exhaustiveness for sse\n- update nix pnpm deps hash

* fix(server): suppress debug logs by default

- route BridgeClientAPI and PromptService debug logs through ILogService instead of console.error\n- lower SessionClientsService debug logs from info to debug\n- add changeset for @moonshot-ai/services, @moonshot-ai/server and @moonshot-ai/kimi-code

* fix(kimi-web): remove model picker top blue bar and widen dialog

- Remove the inset blue box-shadow from the model picker header.

- Increase the default dialog width from 620px to 760px.

* fix(kimi-web): replace model picker checkmark with icon

- Swap the textual checkmark for a proper SVG check icon in ModelPicker,

  matching the icon used in the composer model dropdown.

* fix(kimi-web): hide the Open in app menu

- Remove OpenInMenu usage from ChatHeader and the prop/event plumbing

  through ConversationPane and App.

- Remove the now-obsolete test case in files-tab-no-git.test.ts.

* feat(kimi-web): scope composer dock to chat tab and polish BTW side chat

- Move the composer dock into the chat tab only so it no longer appears in
  split file, task, preview, or BTW panes.
- Render the BTW side chat as a split side pane scoped to the active session,
  and keep its messages out of the main conversation transcript.
- Remove the side-chat panel header, relabel the tab to Side chat / 侧边聊天,
  and use the shared moon spinner while waiting for the first token.
- Suppress the generic Started a step progress text for side-channel agents.

* feat(server): expose live session status via HTTP and WebSocket

- add status field to session status response schema and event.session.status_changed\n- compute session lifecycle status in SessionService from approvals, questions, prompts, and turns\n- broadcast event.session.status_changed globally to all WebSocket connections\n- re-export new event types from agent-core\n- add e2e and unit tests for status computation and broadcasting

* fix(kimi-web): label sidebar session removal as Archive

* feat(kimi-web): make tab/split chrome accessible

TabBar is now a real ARIA tablist: role=tab buttons with aria-selected,
roving tabindex, Left/Right/Home/End keyboard nav, focus-visible styling,
and aria-controls wired to each ViewGroup's tabpanel (role=tabpanel +
aria-labelledby).

ViewGroup split-right/split-down/close buttons get localized aria-labels
(no longer English title-only) and a 28x28 hit area.

* fix(kimi-web): move auth banner into layout flow

The onboarding/auth banner was position:fixed over the top of the
conversation column, covering the desktop ChatHeader and the mobile
top bar. Wrap the app grid in a flex-column shell and render the banner
as the shell's first in-flow child so it reserves its own height above
both the sidebar/header and the mobile top bar instead of overlapping
navigation.

* fix(kimi-web): enlarge and label header/sidebar icon buttons

Unify icon-button hit areas and keyboard affordances:
- ChatHeader kebab: 28x28 target, aria-label + aria-expanded/haspopup,
  focus-visible ring.
- Sidebar workspace kebab (.gh-more): 24x24 target, aria-label, stays
  visible on keyboard focus (it was hover-only), focus ring.
- Sidebar per-workspace add (.gh-add): aria-label + larger tap target.
- Focus rings on the settings button and new-chat/new-workspace buttons.

* feat(kimi-web): give overlay dialogs modal focus management

Add useDialogFocus(): records the opener, moves focus into the dialog on
open, and restores focus to the opener on close. Wire it plus
aria-modal="true" / tabindex="-1" into ModelPicker, LoginDialog and
ProviderManager (Escape-to-close was already present). ModelPicker keeps
focusing its search box on open. Covered by a model-picker focus test.

SettingsDialog is intentionally left for a follow-up to avoid colliding
with in-flight settings work.

* feat(kimi-web): consistent rules for the right-side detail layer

The transient detail panels (thinking, compaction summary, subagent
detail, mobile file/media preview) now share one set of rules:
- the aside is a labelled role=complementary region (aria-hidden when
  collapsed),
- Escape closes whichever panel is open — handled in App on the capture
  phase so it takes precedence over the conversation's "Esc interrupts a
  run" handler instead of firing both,
- every close button has an aria-label (not just a title) and a
  focus-visible ring; thinking/subagent close targets bumped to 28x28,
- FilePreview toolbar buttons get focus-visible rings too.

* fix(kimi-web): calm the diff line colors

Added/removed diff lines washed the entire row in green/red (12% tint +
fully coloured text), which competed with reading the code. Drop the
background to a faint 7% tint plus a left accent bar, color only the +/-
sign, and let the code text keep the normal ink color so the content —
not the color wash — is what stands out.

* docs(web): record tab/split convergence + UI audit follow-through

Implementation note for the tab/split work: the final three-layer view
model (persistent chat/files tabs, transient preview/btw tabs, right-side
detail layer), the transient-view routing rules, a per-task status table,
which audit suggestions were absorbed vs intentionally skipped, and why
the SettingsDialog focus item is deferred (concurrent in-flight rewrite).

* feat(kimi-web): add side-tab navigation to SettingsDialog

* feat(session): persist session archive state and add include_archive list filter

- Replace deleteSession with archiveSession RPC and REST endpoint\n- Persist archived flag in session state and filter archived sessions by default\n- Add optional include_archive query parameter to list archived sessions\n- Expose archived flag on session responses through protocol and web types\n- Rename web session delete events/handlers to archive

* feat(kimi-web): give SettingsDialog modal focus management

Completes the dialog-focus baseline (task 8): wire useDialogFocus +
aria-modal/tabindex into SettingsDialog now that the side-tab rewrite has
landed. Focus moves into the dialog on open and returns to the opener on
close; covered by a settings-dialog focus test.

* refactor(workspace): centralize registry into a single workspaces.json

- replace per-bucket workspace.json files with one workspaces.json registry\n- serialize registry reads/writes through an opQueue to avoid races\n- delete now removes only the registry entry, leaving the session bucket intact\n- update workspace e2e tests and scenario for the new storage model

* feat(workspace): add workspace lifecycle WS events

- publish event.workspace.created/updated/deleted from the registry service\n- broadcast them to every connection via the __global__ watermark\n- add protocol types/schemas and frontend mapping for real-time workspace sync\n- cover with protocol, broadcast, and exhaustive-switch tests

* feat(fs): add fs:mkdir action for creating directories

- add fsMkdir request/response schemas and FS_ALREADY_EXISTS (40919) error code
- implement IFsService.mkdir guarded by resolveSafePath, returning the created directory entry
- register the mkdir action in the fs route dispatcher with EEXIST/ENOENT mapping
- add protocol schema tests and server e2e coverage

* style(kimi-web): set fixed heights for all modal dialogs

* feat(kimi-web): add collapsible sidebar

* refactor(web): temporarily hide new workspace button in sidebar

* feat(kimi-web): add starred models support to model picker and composer dropdown

- Persist starred model ids in localStorage via useKimiWebClient.

- Pin starred models to the top of the All tab in ModelPicker.

- Show a Starred section in the Composer quick-switch dropdown, including models from other providers.

- Render a star glyph on each starred model row in both pickers.

- Add --star CSS variable with a brighter yellow across themes.

- Add tests for starred model ordering and Composer dropdown rendering.

* test(kimi-web): cover chat dock composer alignment

* feat(server): expose GitHub pull request in git status and web header

Add a pullRequest field to the session fs:git_status response, looked up via gh pr view with a 5s timeout, GH_NO_UPDATE_NOTIFIER/GH_PROMPT_DISABLED, and a 60s per-cwd cache.\nNormalize gh state to open/merged/closed and fail soft to null so git status never breaks.\nWire the web chat header PR badge to the active session.

* feat(kimi-web): surface 5-state session status with a separate busy flag

The session view-model collapsed every lifecycle state to running|idle,
so awaiting-input and aborted sessions were indistinguishable and the
spinner span while a session was actually waiting on the user.

Session now carries the real `status` (idle/running/awaitingApproval/
awaitingQuestion/aborted) plus a separate `busy` flag (running + a real
task in flight). SessionRow spins only when busy, shows awaiting tags
from status as a fallback for background sessions, and a distinct aborted
tag; SessionsDialog and MobileSwitcherSheet distinguish the states too.

(The producers in useKimiWebClient already landed via an earlier commit;
this adds the Session type, the UI, and labels so the tree type-checks.)

* fix(kimi-web): persist unread dots across a page reload

unreadBySession was pure in-memory state seeded empty on every load, with
no localStorage persistence and no server-side read cursor — so a browser
refresh dropped every sidebar unread dot. Persist the `true` entries to
localStorage (compact: only unread sessions are stored) and seed the map
from storage on init; opening a session clears the flag and the stored
entry. Covered by a reload test in the session-cache suite.

Note: also carries pre-existing empty-session-flash test edits that were
already part of this file's working state.

* style(kimi-web): redraw collapse/expand sidebar icons

Use an indent-style glyph: three lines with a directional chevron, mirrored between the collapse (left) and expand (right) states.

* feat: add server-hosted web UI and document its packages

- wire kimi-web into root and CI typecheck (vue-tsc) and refresh the Nix pnpm hash
- consolidate per-feature changesets into a single server-hosted-web-ui entry
- make agentEventProjector.shortJson resilient to stringify failures and adjust tests
- add AGENTS.md for kimi-web and server; add READMEs for server and services
- expand root AGENTS.md project map and clarify the flake.nix workspace-sync rule

* test(kimi-web): cover empty-session flash + draft-send paths

Tests and changeset for the empty-session-flash fix (the sessionsKnownEmpty
/ sessionLoading logic itself already landed in an earlier commit):
selecting a locally-created session shows the empty composer with no
loading flash, an existing session reported as empty still loads its
snapshot (messageCount is not trusted), and sending straight from the
draft composer does not flash the empty state.

* chore: ignore generated docs and reports

* style(apps/kimi-web): remove app shell top border

* fix(build): build kimi-web assets before native SEA build

The native SEA build embeds the Kimi web SPA from apps/kimi-code/dist-web (see scripts/native/02-sea-blob.mjs) and fails when that directory is missing. Build kimi-web and stage its assets via copy-web-assets.mjs before running build:native:sea.

- flake.nix: add the web build + asset copy to buildPhase so `nix build` works.
- _native-build.yml: add the same prep step before the SEA build so CI release / manual native bundles keep working.

Fixes "Kimi web build output was not found at .../dist-web" in the nix build and the CI native build stage.

* feat(web): hide context indicator on empty session composer

* feat(kimi-web): unify detail panel layout

* fix(web): improve dark toc tooltip contrast

* fix(web): keep markdown code blocks mounted

* fix(web): tighten dark color contrast

* fix(web): close dock cards on outside click

* feat(web): show session content summaries

* fix(web): report web client telemetry

* refactor(services): merge @moonshot-ai/services into agent-core

- move services/src/** into agent-core/src/services/** and delete the standalone @moonshot-ai/services package
- re-export service contracts/implementations from agent-core src/index.ts
- update all server, test, and kimi-web imports to @moonshot-ai/agent-core
- enable experimentalDecorators in tsconfig and adjust dev/build configs
- sync workspace registry (changeset config, flake.nix, pnpm-lock)

* refactor(server): remove Swagger UI and --swagger flag

- drop @fastify/swagger-ui and the dev-only --swagger option
- keep /openapi.json via @fastify/swagger (bundled in the SEA)
- simplify the native SEA build (no swagger-ui external or asset copy)
- update tests, docs, and lockfile

* feat(server): add on-demand daemon for kimi web with idle shutdown

- make kimi web non-blocking by spawning or reusing a single detached daemon per device (via ~/.kimi-code/server/lock), auto-picking a free port on conflict
- daemon self-exits after a 1-minute grace once the last web WebSocket client disconnects (new onConnectionCountChange hook + createIdleShutdownHandler)
- add getLiveLock helper for daemon discovery
- hide kimi server install/uninstall/start/stop/restart/status (service-ization) for now; implementation preserved for later re-exposure

* fix(web): improve mobile dialog layouts

* fix(ci): resolve lint, typecheck, and nix build failures

- add startBtw to IPromptService test mocks (agent-core, server)
- remove useless spread in PromptService session cleanup
- add assertion to concurrent-connection WS handshake test
- bind idle onConnectionCountChange callback in server run
- type getSessionSnapshot mock via vi.mocked in kimi-web test
- update pnpmDeps hash in flake.nix

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(web): distinguish native markdown links

* fix(vis-web): enable experimentalDecorators for typecheck

vis-web type-checks agent-core source (via source exports), whose services use legacy parameter decorators for DI. Without experimentalDecorators, tsc reports TS1206 "Decorators are not valid here" and the CI typecheck job fails.

* Revert "feat(web): show session content summaries"

This reverts commit 8de58eacbc.

* fix(web): align active toc-bubble highlight with bubble edges

* fix(web): hide conversation toc when chat pane is too narrow

* feat(server): add `kimi server ps` to list active clients

- add GET /api/v1/connections endpoint backed by IConnectionRegistry
- record connection metadata (connectedAt, remoteAddress, userAgent) on WsConnection
- add connection wire schema in @moonshot-ai/protocol
- add kimi server ps CLI command with table and --json output

* feat(kimi-web): add queue chip to chat dock workbar

* fix(agent-core): hide console window when spawning git/gh on Windows

On Windows, spawning a console-subsystem executable (gh.exe / git.exe)
from the background Kimi server creates a visible console window that
flashes on screen. Set windowsHide: true (a no-op on POSIX) to suppress it.

* feat(server): add startup and ws connection telemetry for kimi web

- bootstrap telemetry in `kimi web`/`kimi server run` via initializeServerTelemetry (ui_mode=web, honors telemetry=false)
- wire the real client into KimiCore so agent-core events carry the enriched context
- emit server_started after the server listens; flush telemetry on shutdown
- emit ws_connected/ws_disconnected from WSGateway via WSGatewayOptions.telemetry
- re-export loadRuntimeConfigSafe/resolveConfigPath from the SDK for host config reads

* feat(web): dynamic page title based on session or workspace

* fix(web): show recently active sessions at the top of the web session list

* feat(web): show running indicator in dynamic page title

* feat(kimi-web): show elapsed time for completed assistant turns

* fix(kimi-web): resolve TDZ error on App mount

* refactor(kimi-web): align turn duration and support multi-tab timing

* feat: display per-turn wall-clock duration in web chat

* feat(web): use animated spinner in page title while running

* feat(server): add kill command and background server run

- add `kimi server kill` to stop the running daemon (graceful API + forced PID kill)
- add `POST /api/v1/shutdown` so the server can terminate itself
- make `kimi server run` start in the background and print the ready banner
- route `kimi web` through the same path as `server run` so it prints the banner too

* fix(server): remove duplicate startBtw key in prompt e2e test

Resolves eslint no-dupe-keys and TS1117 errors that broke the lint and typecheck CI jobs.

* chore: bundle Inter font locally

* fix(nix): update pnpmDeps hash for new font dependency

The local Inter font dependency changed pnpm-lock.yaml, so refresh the fixed-output derivation hash to match what the nix builder computes. Resolves the nix build .#kimi-code CI failure.

* fix(server): restore web client telemetry and stabilize skills cleanup

Restore forwarding of x-kimi-client-* headers into session creation telemetry, which was dropped during the services-to-agent-core merge and left the new-session telemetry test with empty records.\n\nRetry the skills e2e sandbox cleanup to ride out ENOTEMPTY races when the core process flushes files into the sandboxed home after close().

* chore: remove trailing blank lines

* chore(changeset): remove consumed changeset files

These 20 changeset files were applied during the version bump and are no longer needed.

* chore: clean up web release changesets

* docs: clean up kimi web readme

* chore: scope package lint to cli release

* chore: remove temporary design docs and preview files from PR

Remove files that were not intended for submission:
- docs HTML research/design archives
- docs/superpowers specs added during design phase
- apps/kimi-web icon preview pages and one-off test script

* chore(kimi-web): remove dev stub daemon

The real server package is now available; the throwaway stub daemon
is no longer needed for development.

* test(server-e2e): accept aborted image prompt scenario

* chore: update flake.nix workspace paths and add new changesets

- Added new packages: daemon, server-e2e, and kimi-migration-legacy to the workspacePaths in flake.nix.
- Introduced new changeset for "@moonshot-ai/kimi-code-sdk" to add host-side config helpers.
- Removed outdated changesets related to server-hosted web UI and server web APIs.

* feat(server): daemonize by default and fall back to port +1

- kimi server run now spawns a background daemon by default; --foreground keeps the terminal attached
- default server port moves from 7878 to 58627 across CLI, web, e2e, and docs
- listenWithPortRetry retries on port + 1 when a third party holds the port (capped at 100)
- lock gains updatePort so status/kill/ps find the daemon on its real bound port

* fix(cli): resolve oxlint unbound-method errors in server run

---------

Signed-off-by: qer <wbxl2000@outlook.com>
Co-authored-by: qer <wbxl2000@outlook.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 20:53:46 +08:00
7Sageer
53cab3683f
refactor(agent-core): rename micro compaction telemetry event to micro_compaction_finished (#846)
* feat(agent-core): record context length before and after micro compaction

* test(agent-core): assert context length fields in micro compaction telemetry

* chore: drop changeset for telemetry-only change

* refactor(agent-core): rename micro compaction telemetry event to micro_compaction_finished

* fix(agent-core): align micro compaction context telemetry

* chore: drop changeset for telemetry-only change

* refactor(agent-core): align micro compaction token telemetry fields
2026-06-17 20:31:55 +08:00
7Sageer
843a731097
fix: classify OAuth token refresh errors by cause (#838)
Map OAuth token-fetch failures to distinct public error codes instead of collapsing them all to auth.login_required:

- missing/revoked tokens or 401/403 from the refresh endpoint -> auth.login_required
- transport failures and 429/5xx after internal retries -> provider.connection_error
- anything else is rethrown as-is (surfaces as internal) rather than guessed

The refresh helper already retries internally, so the agent loop does not
re-retry these. Both the managed provider and the standalone SDK provider
share the same mapping.
2026-06-17 18:34:42 +08:00
Luyu Cheng
a71b2e3123
fix(agent-core): restore turn counter from loop events on resume (#833)
* fix(agent-core): restore turn counter from loop events on resume

On cold-start resume the turn counter was rebuilt by counting `turn.prompt`
records, but `restorePrompt()` set `activeTurn = 'resuming'` on the first
record and then early-returned for the rest, so only one historical turn was
counted regardless of how many actually ran. After resume the next prompt
reused turn ids already present in history, producing duplicate raw turnIds
across the session (confirmed in real `wire.jsonl` sessions: e.g. history
turns 0-4 followed by post-resume turns 1-5).

Derive the restored counter from the persisted loop events instead: every
turn that ran — prompted, goal continuation, or steer-launched — emits loop
events carrying its real turnId, even though only prompted turns write a
`turn.prompt` record. `observeRestoredTurnId()` raises the counter to the max
turnId seen during replay so resume continues from `max + 1`. It only ever
raises the counter, runs solely on the resume path, and adds O(1) work per
already-iterated record. This also self-heals already-damaged sessions, which
resume past the highest id ever recorded.

Add resume tests covering multiple prompted turns, goal-continuation turns
with no `turn.prompt` record, and repeated resume cycles.

* chore: add changeset for resume turn counter fix
2026-06-17 18:24:14 +08:00
_Kerman
65423c673b
fix: retry think-only compaction summaries with a smaller prefix (#836)
Some checks are pending
CI / build (push) Waiting to run
CI / test (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 / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
2026-06-17 15:17:15 +08:00
7Sageer
66b4d658a0
fix: dispose process stdio after managed commands (#822)
* fix: dispose process stdio after managed commands

* refactor: share stream close detection
2026-06-16 22:09:11 +08:00
_Kerman
90fc04b707
refactor: simplify LLM request logging (#823) 2026-06-16 21:13:31 +08:00
_Kerman
7b5b818815
fix: continue compaction while context remains blocked (#813) 2026-06-16 16:48:27 +08:00
_Kerman
3e6196e6b2
fix: build replay ranges from replay records (#805) 2026-06-16 15:20:23 +08:00
Luyu Cheng
4578f05f44
fix: surface skill directory in the loaded-skill context block (#785) 2026-06-15 22:29:00 +08:00
_Kerman
e10b25f9be
fix: stop recording resume version metadata (#786) 2026-06-15 21:04:53 +08:00
_Kerman
a562ef54e5
refactor: decouple agent skill registry (#784) 2026-06-15 20:39:29 +08:00
_Kerman
c6a996756c
fix: close interrupted tool calls on resume (#768) 2026-06-15 19:02:17 +08:00
_Kerman
0ab72d7d19
test: update compaction usage snapshots (#770) 2026-06-15 13:50:06 +08:00
_Kerman
2f7218cba4
test: fix resume test assertions for compaction replay records (#681) 2026-06-12 11:32:30 +08:00
_Kerman
911e7c3fcf
fix: replay compaction records on resume (#617) 2026-06-12 11:10:52 +08:00
_Kerman
dcf30754d0
feat: stream shell tool output (#676) 2026-06-12 11:10:20 +08:00
liruifengv
596cadd465
feat: support always-thinking models via supports_thinking_type (#662)
Some checks are pending
CI / build (push) Waiting to run
CI / test (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 / Publish native release assets (push) Blocked by required conditions
Map the /models three-state supports_thinking_type field ('only' /
'no' / 'both', taking precedence over the legacy supports_reasoning
boolean) onto the existing always_thinking capability:

- oauth: parse the field in both /models parsers; 'only' emits
  always_thinking alongside thinking, 'no' suppresses thinking even
  when supports_reasoning is set, absent falls back to the legacy
  boolean. Default thinking selection is forced on for 'only' (and
  off for 'no') models during login and provider refresh
- TUI: render the thinking control with a fixed On/Off layout — locked
  models show a greyed-out "Off (Unsupported)" segment, and
  non-thinking models mirror the style with "On (Unsupported)"
- agent-core: clamp thinkingLevel at the getter so a stale
  thinking-off config can never reach the request builder, status
  events, or subagent inheritance
- acp-adapter: derive alwaysThinking from capabilities, collapse the
  thinking select to a single locked "on" entry, and ignore off
  requests for locked models while re-emitting the snapshot
2026-06-11 23:16:02 +08:00
_Kerman
ff80327344
fix: propagate kaos env overlays (#654) 2026-06-11 17:21:32 +08:00
_Kerman
a2c5e1be25
fix: add minor improvements for Mira (#649) 2026-06-11 15:50:33 +08:00
_Kerman
4e5043b03b
fix: require AgentSwarm to run alone (#643)
Some checks are pending
CI / build (push) Waiting to run
CI / test (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 / Publish native release assets (push) Blocked by required conditions
2026-06-11 13:58:35 +08:00
qer
b253a82a7a
feat(agent-core): add Interrupt hook for user-interrupted turns (#607)
Fires an observation-only Interrupt event when a turn is aborted by the user (e.g. pressing Esc). Previously neither Stop nor StopFailure fired on interrupt, so external tooling that tracks status from hooks stayed stuck on a working state.
2026-06-10 15:35:25 +08:00
_Kerman
494554eac5
feat: add undo selector (#615) 2026-06-10 14:22:02 +08:00
liruifengv
32d7080837
fix(skill): clarify active skill prompts (#598)
* fix(skill): clarify active skill prompts

* fix(skill): preserve nested skill trigger
2026-06-10 12:46:23 +08:00