mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
281 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f2c7ec75d3
|
ci: release packages (#1224)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
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
|
||
|
|
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.
|
||
|
|
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
|
||
|
|
ceb27f5e44
|
feat(server): add GUI store API mirroring localStorage (#1231)
Some checks are pending
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / 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
Release / Release (push) Waiting to run
* feat(server): add GUI store API mirroring localStorage - add /api/v1/gui/store/* endpoints (getItem/setItem/removeItem/clear/length) mirroring the browser localStorage interface - add IGuiStoreService persisting opaque string values to ~/.kimi-code/gui.toml via smol-toml with atomic writes and an in-process write lock - wire protocol schema, service, routes, and DI registration; add e2e tests and update the API surface snapshot * chore: add changeset for gui store api * fix(server): harden GUI store key handling and file permissions - use a null-prototype record and an own-property check so keys that exist on Object.prototype (toString, constructor, __proto__) behave like ordinary keys - write gui.toml with 0600 permissions so unsent drafts and input history stay private to the owning user |
||
|
|
5cb80ce879
|
feat: support plugin slash commands (#1204)
* feat(agent-core): support plugin slash commands * feat(node-sdk): expose listPluginCommands * feat(kimi-code): register and dispatch plugin slash commands * chore: add changeset for plugin commands * feat(agent-core): activate plugin commands server-side * feat(node-sdk): add activatePluginCommand * feat(kimi-code): render plugin command activations compactly * feat(agent-core): recurse plugin command directories and preserve namespace * fix(kimi-code): parse nested plugin command names * fix(agent-core): update prompt metadata for plugin command turns * fix(kimi-code): replay plugin command turns as command cards * fix: treat plugin-command origins as real user prompts in undo * fix(kimi-code): guard model-empty and clear plugin command render ids * fix: propagate plugin_command to web and vis turn projectors * fix(kimi-code): refresh plugin commands through auth flow * fix(kimi-web): render plugin command cards in chat pane * fix(kimi-web): render plugin command card in desktop chat view * fix(kimi-code): treat slash-activation cards as transcript turn boundaries * fix(kimi-code): count slash-activation entries when trimming transcript turns * fix(kimi-code): preserve plugin command args in undo selector |
||
|
|
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. |
||
|
|
a3f9cec8a9
|
fix(web): deduplicate workspaces shown in the sidebar (#1221)
Collapse registered workspaces that share a root in the daemon registry (preferring the canonical id) and in the web sidebar merge, so the same folder no longer renders as two identical, synchronously-selected entries. |
||
|
|
b41f108584
|
ci: release packages (#1197)
Some checks are pending
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
14d9e98903
|
feat(server): auto-refresh provider model catalog and push change events (#1207)
* feat(server): auto-refresh provider models and push change events
- add scheduled provider-model refresh in the daemon (configurable
interval + refresh-on-start) plus manual endpoints:
POST /providers:refresh and POST /providers/{id}:refresh
- publish global event.model_catalog.changed when a refresh changes
the catalog so connected clients can resync
- extract the refresh orchestrator into @moonshot-ai/kimi-code-oauth so
the CLI and server share managed/open-platform/custom-registry logic
- wire the web daemon client to the new refresh endpoints
* chore: add changeset for provider model auto-refresh
* fix(web): reload model and provider caches on catalog change events
When the daemon's scheduled refresh changes the catalog, the pushed
event.model_catalog.changed only advanced the websocket sequence, leaving
the web composer's model/provider refs stale until an unrelated reload.
Reload both caches when the event arrives.
* test(sdk): cover event.model_catalog.changed in event exhaustiveness
|
||
|
|
063538744f
|
refactor(agent-core): align malformed tool args with schema validation (#1209)
Some checks are pending
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
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 / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(agent-core): repair malformed tool args JSON Attempt jsonrepair when tool call arguments fail JSON.parse, then continue schema validation. Return malformed JSON errors with a concise expected schema hint so the model can retry with corrected arguments. * chore(nix): update pnpm deps hash Update the fixed-output pnpmDeps hash after adding jsonrepair so the Nix build can fetch dependencies. * refactor(agent-core): drop tool args JSON repair Stop repairing malformed tool call arguments. Fall back to an empty object on JSON parse failure and let schema validation produce the retry error, while preserving valid arguments for unknown tools in the transcript. * chore: add changeset for tool args validation |
||
|
|
53e79e4bae
|
chore(telemetry): add ripgrep fallback telemetry (#1203)
Some checks are pending
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-windows (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 / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
|
||
|
|
c82dcf9cd8
|
refactor(agent-core): use ripgrep for Glob tool (#1068)
* refactor(agent-core): use ripgrep for Glob tool Glob now shares Grep's ripgrep subprocess plumbing: it respects .gitignore by default, supports brace patterns natively, adds an include_ignored option, and returns only files. * fix(glob): address review findings on ripgrep migration - Run rg with cwd pinned to the search root so glob patterns containing a slash (e.g. src/**/*.ts) match under an absolute search root. - Keep include_dirs as a deprecated, ignored parameter so older calls are not rejected by parameter validation. - Surface stdout truncation and drop half-written trailing paths when the rg output buffer is capped. - Document that a bare pattern (e.g. *.ts) matches recursively, and sync user docs, the explore profile prompt, and the TUI summary to the new files-only / gitignore behavior. - Add real-ripgrep integration tests covering sort order, recursion, brace patterns, and the absolute-search-root case. * fix(glob): keep partial results on traversal errors --------- Co-authored-by: hynor <hynor@users.noreply.github.com> Co-authored-by: Kai <me@kaiyi.cool> |
||
|
|
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. |
||
|
|
0df1812502
|
fix: render provider HTML error messages instead of blank lines (#1191)
When a provider returns an HTML error page (e.g. nginx 413 Request Entity Too Large), the error message carried CRLF line endings and raw HTML. The trailing carriage returns made the TUI render the error line as blank. Extract the page title for the wire message and strip carriage returns before rendering. |
||
|
|
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. |
||
|
|
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> |
||
|
|
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.
|
||
|
|
dfcfdfd9dd
|
feat(web): hide empty sessions from the session list (#1166)
* chore(web): remove the /sessions slash command
* feat(web): hide empty sessions from the session list
Add an optional exclude_empty parameter to the session list API; the web client passes it so unused "New Session" entries are hidden by default, with pagination and has_more computed on the filtered set.
* fix(protocol): add exclude_empty to the session list query schema
Keep the shared protocol schema in sync with the server route so clients using the protocol type see the new parameter.
* fix(protocol): keep exclude_empty off the child session list schema
listSessionChildrenQuerySchema aliased the main list schema, so it inherited exclude_empty even though the /sessions/{id}/children route does not filter by it. Split it so generated clients are not misled.
|
||
|
|
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 |
||
|
|
794db55538
|
fix(agent-core): cap compaction output at 128k by default (#1156) | ||
|
|
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> |
||
|
|
da63403207
|
ci: release packages (#1124)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
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 |
||
|
|
bf51fb7a10
|
fix(server): skip Unix-only permission check on Windows for server token (#1135) | ||
|
|
e5eaeb4634
|
ci: skip server e2e tests on windows (#1126) | ||
|
|
0886bff2bc
|
feat(server): add --allowed-host flag for DNS-rebinding allowlist (#1128)
- add `kimi server run --allowed-host <host...>` (repeatable or comma-separated; leading dot matches a domain suffix) and thread it through daemon spawn into startServer - merge CLI allowed hosts with KIMI_CODE_ALLOWED_HOSTS for both the HTTP and WebSocket Host checks - include the rejected host and allow guidance in the 403 error message |
||
|
|
184acf5db5
|
feat: support hooks in plugins (#1127)
* feat(agent-core): support per-hook cwd and env in HookEngine * feat(agent-core): support hooks in plugin manifest and aggregate via PluginManager * feat(agent-core): merge plugin hooks into session hook engine * chore: add changeset for plugin hooks |
||
|
|
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.
|
||
|
|
e736349a7c
|
feat(feedback): support attaching logs and codebase (#1120)
* feat(feedback): support attaching logs and codebase Add an attachment picker to /feedback (none / logs / logs + codebase). Codebase uploads scan the working directory with sensitive files excluded and are sent through a new multipart upload API on the oauth/node-sdk layers. * fix(feedback): fall back to logs when codebase scan fails * tiny fix * fix(feedback): make diagnostic uploads partial-safe * refactor(feedback): reuse harness session export and normalize upload url types * docs(slash-commands): note optional feedback attachments * refactor(feedback): reorganize feedback upload modules Move the attachment orchestration out of tui/commands/info.ts into a dedicated feedback/feedback-attachments.ts, and split the former codebase-upload/attach.ts into a generic multipart uploader (feedback/upload.ts) and an archive lifecycle module (feedback/archive.ts). Both session and codebase archives now flow through a single upload lifecycle, which also removes the temp-dir leak that occurred when codebase packaging failed. Rename FeedbackCodebaseArchive to FeedbackArchive and the codebase-upload/ directory to codebase/ so module boundaries match their actual responsibilities (scan + package only). |
||
|
|
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. |
||
|
|
5f36e763ca
|
ci: release packages (#1061)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
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. |
||
|
|
46808e1cff
|
perf(prompt): filter session lookup by sessionId in _requireSession (#1107)
- pass sessionId to listSessions instead of fetching all sessions - update test mock to mirror the store's sessionId filter |
||
|
|
bf9b01e0d8
|
feat(server): make --host opt-in for LAN binding (#1105)
* feat(server): bind `kimi server` to 0.0.0.0 by default - change DEFAULT_SERVER_HOST to 0.0.0.0 and default --insecure-no-tls on so a bare `kimi server run` clears the non-loopback TLS gate - add LOCAL_SERVER_HOST (127.0.0.1) for connectable URLs when bound to the wildcard, used by lockConnectHost and the service status URL - pin the OS-supervised install to --host 127.0.0.1 so the always-on service stays loopback-only * feat(server): make --host opt-in for LAN binding - default kimi server run/web binds 127.0.0.1 when --host is omitted - treat bare --host as 0.0.0.0 while keeping --host <host> explicit - update server help/banner text and host binding tests |
||
|
|
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 |
||
|
|
77412b89fa
|
fix(server): import bcryptjs via default export for ESM dev runner (#1104)
- bcryptjs is a CommonJS package whose index.js re-exports via
`module.exports = require("./dist/bcrypt.js")`
- Node's cjs-module-lexer cannot detect named exports through that
require() indirection, so `import { compare, hash } from 'bcryptjs'`
threw "Named export 'compare' not found" under the tsx dev runner
(make dev), even though vitest and the esbuild bundle handled it fine
- switch to the default import and destructure, which works across tsx,
vitest and the bundled binary
|
||
|
|
f059649ce8
|
feat(agent-core): suggest update-config command in max-steps error (#1099) | ||
|
|
60dfb68a2d
|
feat(server): add bearer-token auth and safe host exposure (#1006)
* test(server): add API surface snapshot guardrail
Boot startServer on port 0 and snapshot the documented v1 route table derived from /openapi.json paths, plus the reachability of doc/meta endpoints (/healthz, /openapi.json, /asyncapi.json, /). Gives later auth/--host phases an intentional diff when routes change. M0 makes no production behavior change.
* test(server): add e2e server harness with token support
Add test/helpers/serverHarness.ts: boot() wraps startServer with an isolated lock + home dir and returns a handle (server, address, baseUrl, wsUrl, token, close) plus authedFetch/authedWs that carry Authorization: Bearer <token> (and the kimi-code.bearer.<token> WS subprotocol). serviceOverrides is the generic DI seam later phases use to inject a fixed-token auth service; IAuthTokenService is not referenced yet. closeAll() tears down every booted server and socket. M0 makes no production behavior change; typecheck-only gate.
* feat(server): add privateFiles 0600 atomic write/read utility
* feat(server): add per-start tokenStore
* feat(server): add env-based bcrypt password hash utility
* feat(server): add IAuthTokenService DI seam
* feat(server): add global onRequest auth hook with bypass + redaction
* fix(server): stop reflecting Host header in /asyncapi.json
* feat(server): add WS bearer subprotocol constant and parser
* feat(server): enforce bearer token auth on WS upgrade
* feat(server): add Host header allowlist middleware
* feat(server): add Origin/CORS middleware
* feat(server): wire Host/Origin checks into HTTP and WS
* feat(server): wire token auth, Host/Origin, and WS auth into start.ts
* fix(server): create lock file with 0600 permissions
* fix(server): suppress debug routes on non-loopback binds
* feat(kimi-code): read server token and send Authorization on CLI calls
* feat(kimi-code): inject server token into /web URL fragment
* feat(server): add bindClassify for loopback/lan/public classification
* feat(kimi-code): register --host flag and pass it through the daemon
* feat(server): require password and TLS opt-out on non-loopback binds
* feat(server): rate-limit repeated auth failures on non-loopback binds
* feat(server): disable shutdown and terminals on public binds by default
* feat(server): add security response headers on non-loopback binds
* test(server): cover LAN/public host-exposure hardening end to end
* docs(server): add deployment security and threat-model guide
* changeset: minor kimi-code for server auth and host exposure
* feat(kimi-web): add server bearer-token auth support
* fix: repair CI for server auth and host exposure
- Replace native @node-rs/bcrypt with pure-JS bcryptjs so the ESM CLI
bundle and the SEA native bundle both build without native-addon
require issues (node-rs/bcrypt broke the ESM smoke and the SEA
check-bundle allowlist).
- Remove dead cleanup references (stopSpinner, authLogoBlinkTimer) in
apps/kimi-web App.vue that failed vue-tsc.
- Fix lint: drop empty spread fallbacks in the e2e auth-header merge,
void the intentionally-async WS upgrade listener, add missing
assertions to satisfy jest/expect-expect, and convert a ternary
statement to if/else.
- Send the bearer token in the snapshot perf/smoke tests so they pass
under the new global auth hook.
- Refresh the pnpmDeps hash in flake.nix for the updated lockfile.
* feat(server): persist bearer token and add rotate-token command
- persist the server bearer token in <home>/server.token (0600) and reuse it across restarts instead of per-start server-<pid>.token
- add `kimi server rotate-token` to regenerate the token; the token store reloads on mtime/inode change so rotation applies without restart
- print the token and Vite-style Local/Network URLs in the startup banner
- allow non-loopback binds with bearer-token-only auth (password now optional) and update SECURITY.md
- surface daemon boot failures immediately with the exit reason and log tail instead of waiting for the spawn timeout
* feat(server): print full token URLs and re-print links after rotate
- Drop the ready-panel border so token URLs print in full for copying; keep the Kimi sprite beside the title.
- Re-print Local/Network access links after `server rotate-token` (host/port from the lock).
- Extract shared access-URL helpers into access-urls.ts.
- Unify link and token colors between the banner and rotate-token.
* feat(server): dim URL #token= fragment and de-highlight token
- Render the `#token=…` fragment in a dim gray so the host/port stands out in the banner and rotate-token links.
- De-highlight the standalone token; set it off with surrounding whitespace instead of color.
- Add splitTokenFragment helper.
* refactor(cli): polish server ready banner and rotate-token output
- move version onto the ready banner title line; drop the separate
Ready:/Version: rows and the startup-time metric
- reorder rotate-token output so the new token sits between the
invalidation note and the access links
- update server CLI tests for the new layout
* feat(server): warn on reuse and refine ready banner
- Warn when `server run` reuses an already-running daemon (its options are not applied) and show the running server's actual URLs.
- Show a `Network: off use --host 0.0.0.0 to enable` hint on loopback binds.
- Move the version onto the title line and drop the startup-time metric.
* fix(web): relabel auth dialog to token and cover full page
- Relabel the server auth dialog from "password" to "token"; the server accepts the bearer token, with the password only as a fallback.
- Make the auth dialog overlay fully opaque so it covers the whole page instead of revealing the login page underneath.
* fix: resolve CI failures on web auth PR
- Replace chalk.yellow named color with chalk.hex(darkColors.warning)
in the server reuse notice to satisfy the chalk named color guard.
- Update pnpmDeps hash in flake.nix to match the regenerated
pnpm-lock.yaml so the Nix build succeeds.
- Retry rmSync in ws-broadcast e2e teardown to ride out EBUSY /
ENOTEMPTY races while the server flushes files after close().
* test(server): update API surface snapshot for warnings route
The feat/web-auth branch adds GET /api/v1/sessions/{session_id}/warnings
(packages/server/src/routes/sessions.ts), so the API surface guardrail
snapshot needs to record the new documented v1 route.
|
||
|
|
d6e524682d
|
perf(web): page session list per workspace on first load (#1084)
* perf(web): page session list per workspace on first load Load only the first page of sessions per workspace instead of draining every session up front, so the initial request count scales with the number of workspaces rather than the total number of sessions. The per-workspace "show more" button now fetches the next page on demand, and searching lazily loads the full list so results stay complete. To keep per-workspace paging working for sessions created with cwd only, the workspace registry now also surfaces directories that have sessions but were never explicitly registered. * fix(web): trust server hasMore for session pagination Stop deriving per-workspace hasMore from the workspace session_count. After a local archive/delete the count is stale (archiveSession only removes the local session), so loadedCount < total stayed true after the server returned its final page and re-fetched empty pages forever. The server's page.hasMore is authoritative for whether more pages exist, so use it directly and keep session_count only as a label total. * fix(web): fall back to global session walk when no workspaces listed When /workspaces is unavailable or empty on older or partially-failing daemons while /sessions still works, the per-workspace initial load produced no sessions and the sidebar rendered blank. Reuse the existing global walk as a fallback in that case so history still shows. * fix(agent-core): keep workspace deletion durable Record deleted workspace ids as tombstones in the registry and skip them during derived registration. Deleting a workspace only removes its registry entry (session buckets stay on disk by design), so without the tombstone the next derived-registration scan recreated the workspace, making deletion non-durable for any workspace with history. An explicit re-add clears the tombstone. * fix(web): track session paging cursor per workspace Compute the load-more cursor from the end of the last fetched page instead of the oldest loaded session. A deep-linked older session appended out of band would otherwise become the cursor, so the next page started after it and skipped every session between the first page and the deep link. * fix(web): load more sessions in the mobile switcher The mobile switcher still used the old local display-expansion logic, but each workspace now starts with only the first page of sessions. Wire its show-more button to loadMoreSessions (matching the desktop sidebar) so workspaces with more sessions can page beyond the first page on mobile. * fix(agent-core): align derived workspace id with its session bucket Session buckets are keyed by normalizeWorkDir (resolve, not realpath), so registering a derived workspace via createOrTouch (which realpaths) produced a different workspace id for a symlinked cwd, and per-workspace session lookups then read the wrong bucket and returned empty. Register derived workspaces with the resolved bucket key instead. * fix(agent-core): skip archived-only buckets in derived registration A bucket that only contains archived sessions would otherwise be registered as an empty workspace on a plain GET /workspaces, surfacing an empty sidebar group that the old session-derived fallback (which ignored archived sessions) kept hidden. Skip derived buckets with no active sessions. * refactor(agent-core): derive workspaces from the session index on the fly Stop persisting derived workspaces into the registry. Persisting made the registry a second data source that drifted from the session store (symlinked cwds, archived-only buckets, deleted/unmounted roots), each producing a bug. Instead compute derived workspaces fresh in list() from the session index and resolve their ids in resolveRoot() via the same index, so the session store stays the single source of truth. The deletion tombstone is kept so explicitly removed workspaces are not re-derived. * fix(agent-core): tombstone derived workspaces on delete After deriving workspaces from the session index, derived ids are valid list results but absent from the registry file, so delete() threw before writing the tombstone and DELETE on a derived workspace 404ed and reappeared on the next list(). Resolve the derived id and write the tombstone for it too. * fix(web): use local session count once a workspace is fully loaded mergedWorkspaces kept the server session_count as a floor even after a workspace had no more pages, so archiving the last session left the header showing 1 until a reload. Once a workspace is fully loaded (hasMore === false) the local count is exact, so prefer it; keep the server count as a floor only while pages remain. |
||
|
|
8fc6aa5f68
|
fix(server): broadcast session metadata updates to all clients (#1081)
- deliver session.meta.updated to every connection instead of only session subscribers, so title changes sync across all clients - emit session.meta.updated when a session is explicitly renamed |
||
|
|
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 |
||
|
|
f1fad7222c
|
fix: reduce streaming stutter in the web chat (#1085)
Some checks are pending
CI / test (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(web): coalesce streaming token updates into one render per frame * fix(server): disable Nagle on WebSocket socket for lower streaming latency * fix(web): flush pending streaming deltas before re-subscribing * fix(web): flush pending streaming deltas before forgetting a session |
||
|
|
0e227ba18a
|
fix(agent-core): surface git context failures for explore subagents (#1067)
* fix(agent-core): surface git context failures for explore subagents collectGitContext collapsed every git failure (spawn error, non-zero exit, timeout) into null, so explore subagents silently lost git context with no signal. Now a definitive 'not a git repository' injects an explicit unavailable signal so the subagent does not waste turns probing git history, while other failures are logged and surface as an empty block. The block is all-or-nothing so a partial snapshot (e.g. a timed-out status making a dirty tree look clean) is never shown. * fix(agent-core): use rev-parse for branch to support git < 2.22 `git branch --show-current` was added in Git 2.22 and fails (exit 129) on older Git even in a valid repository. Because the branch probe is fatal, this dropped the whole git-context block for older-Git users. Switch to `git rev-parse --abbrev-ref HEAD`, which is supported across Git versions, and filter the `HEAD` output produced in detached-HEAD state. * fix(agent-core): show whatever git info is available in explore context Git probes fail in perfectly normal states — no `origin` remote, no commits yet (unborn branch), detached HEAD, older Git — so a failed probe no longer aborts the whole collection. Each probe is now best-effort: failures are logged and their section is omitted, and the block is dropped only when nothing useful was collected. Branch is read via `symbolic-ref --short HEAD`, which works in unborn repositories and on older Git; it fails in detached-HEAD state, where the Branch section is just omitted. |
||
|
|
ff177155ca
|
fix(web): stop auto-dismissing pending questions and approvals on a timeout (#1070)
* fix(web): stop dismissing questions after a 60 second timeout The server's question broker auto-expired AskUserQuestion requests after 60s, which dismissed the question even when the user simply needed more time. Remove the timeout, and the now-unused expires_at field, so a question stays pending until the user answers or explicitly dismisses it. |
||
|
|
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 |
||
|
|
4b837d6bfb
|
feat: auto-create missing parent directories when writing files (#1065)
The Write tool previously failed when a parent directory was missing, forcing a manual mkdir round trip. It now creates missing parents recursively before writing. |
||
|
|
ee69e16dc8
|
fix: use session cwd for stdio MCP servers (#1057) | ||
|
|
a752a5309b
|
fix(agent-core): mark truncated skill descriptions with an ellipsis (#1064)
The model-facing skill listing silently sliced long descriptions to 250 characters with no marker, so neither the user nor the model could tell a description was cut. Truncated entries now end with an ellipsis and the truncation walks whole grapheme clusters so it never splits a surrogate pair or combining sequence. |
||
|
|
66640380eb
|
feat: replace silent AGENTS.md truncation with a visible warning (#1040)
Oversized AGENTS.md files are no longer silently truncated. The full content is injected, and a warning is shown in the TUI status bar and the web UI when the combined AGENTS.md size exceeds the recommended 32 KB. A generic session-warnings API backs this so future warning types can be added without changing the API surface. |