Commit graph

82 commits

Author SHA1 Message Date
7Sageer
95a656ca61
fix(agent-core): strip the no-op subagent model parameter while the secondary-model experiment is off (#2449)
The Agent/AgentSwarm tool schemas always advertised a \`model\` choice
parameter, so the secondary-model concept entered the prompt even with
the experiment disabled. Gate the advertised JSON schema on the flag in
both engines: off (the default) drops the parameter, on keeps it, and
spawn-time resolution already falls back to the caller's model either
way.

Also scrub ambient KIMI_CODE_EXPERIMENTAL_* env vars in both packages'
vitest setup so flag-dependent tool schemas in llm.tools_snapshot stay
deterministic regardless of the developer shell.
2026-07-31 13:09:56 +08:00
Kai
691ec4679e
fix: remove the blocking wait from the TaskOutput tool (#2379)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix: remove the blocking wait from the TaskOutput tool

The block/timeout parameters let a model stall the whole turn waiting
for a background task (up to 3600s), even though completion already
arrives via automatic notification. Remove both parameters from the v1
and v2 engines (kept in model-facing parity), simplify retrieval_status
to success/not_ready, and update the tool, Bash, and Agent prompt
wording plus user docs accordingly. Stale callers passing block are
silently treated as a non-blocking snapshot.

* fix: align background-task prompts with the non-blocking TaskOutput

The compaction reminder promised TaskOutput could fetch a task's result
for tasks that are still running, where it now returns not_ready —
reword it to snapshot semantics and point at the completion
notification. Also list AskUserQuestion(background=true) as a task
source in the TaskOutput description.

* test: exercise stale TaskOutput args through the runtime validator

A stale block/timeout argument never reaches the tool: the executor's
preflight validates args against the closed tool schema and rejects
them immediately, so the old test documented silent-tolerance semantics
the runtime never exhibits. Assert the real behavior through
compileToolArgsValidator/validateToolArgs instead, and drop
statement-adjacent comments to match the package's header-only comment
convention.
2026-07-30 01:26:56 +08:00
7Sageer
efac96c8a9
feat(agent-core): custom agent files and secondary model on the v1 engine (#2232)
* feat(agent-core): custom agent files and secondary model on the v1 engine

Migrate the custom agentfile and secondary-model capabilities from
agent-core-v2 to the v1 engine so they work in the TUI and plain
kimi -p sessions:

- discover Markdown agent files from user/project/extra/explicit
  directories with the v2 precedence rules, a merged session profile
  catalog replacing the hardcoded builtin profile lookups, SYSTEM.md
  main prompt override, and ${base_prompt} backed by the effective
  default
- --agent/--agent-file now work in print mode on the default engine;
  CreateSessionOptions gains agentProfile/agentFiles
- [secondary_model] config + KIMI_SECONDARY_MODEL/EFFORT bind newly
  spawned subagents to a cheaper model behind the secondary-model
  experiment flag, with primary/secondary model params on Agent and
  AgentSwarm and upfront session warnings
- full disallowedTools deny semantics (exact names + mcp__ globs)
  evaluated by the tool manager and persisted in the agent wire

* fix(cli): guard optional agentFiles in the prompt runner

runPrompt is also driven programmatically (headless goal flow) with
options that never pass through the CLI parser defaults, so agentFiles
can be undefined; mirror the addDirs optional-chaining pattern. Also
extend the SDK experimental-feature assertion with the secondary-model
flag.

* fix(agent-core): preserve custom agent bindings on v1

* fix(agent-core): narrow secondary model error hints

* fix(agent-core): persist custom agent profile bindings

* Delete .changeset/sdk-agent-profile-options.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* Update v1-custom-agent-files.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* Update v1-secondary-model.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* Update v1-custom-agent-files.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* fix(agent-core): keep SYSTEM.md a prompt-only overlay for delegation

* docs: update agent file and secondary model availability wording

* fix(cli): reject --agent-file combined with session resume

The resume path only forwards the agent file's name for the bound-profile
assertion; the file's content is never re-applied (the session keeps its
creation-time catalog snapshot). Previously the combination was silently
accepted, so an edited file (or a same-named one) appeared to apply but did
not. Reject it at option validation and document the constraint.

* refactor(agent-core): share prompt-section prose and note v2 twins in agentfile headers

The Windows notes, additional-dirs and skills prose blocks existed twice:
inline in the builtin default template (system.md) and as constants in the
agent-file renderer (from-file.ts). Extract them to profile/prompt-sections.ts
as the single source: system.md renders them through injected KIMI_* template
variables and from-file.ts imports the same constants. Rendered prompts are
byte-identical for all four builtin profiles across macOS/Windows and
skills/dirs on/off; a new test pins system.md to the shared constants.

Also mark each profile/agentfile file with the path of its agent-core-v2
counterpart so format/semantics changes land in both engines.

* feat(cli): add /secondary_model command for the subagent model

Mirror /model: a picker with a thinking-effort step that persists [secondary_model] and live-applies to the current session via a new Session.setSecondaryModel RPC (node-sdk wrapper included), so newly spawned subagents bind the new model right away. The /model picker now hides the synthesized __secondary__ derived entry; docs and the update-config builtin skill mention the section.

* feat(tui): show the bound model in subagent run stats

Subagents report their model alias via agent.status.updated after spawn; resolve it to a display name and surface it in tool-call subagent stats and agent-group rows.

* fix(agent-core): validate agent profile before session persistence

* fix(agent-core): refresh subagent tools after model switch

* fix(agent-core): show subagent model preferences

* fix(agent-core): preserve secondary model recipe on live apply

* fix(agent-core): make secondary model apply explicit

* fix(tui): refresh secondary model display state

* chore: merge secondary model changesets into one

* Add /secondary_model command for subagent configuration

Show each subagent's model in the subagent card header and agent-group rows. Requires the secondary-model experiment (KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL=1); run /secondary_model to pick a model and thinking effort, applied to the current session immediately.

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* fix(agent-core): align explicit agent file precedence

* fix(agent-core): let disallowedTools deny select_tools

* chore(cli): drop engine mention from --agent/--agent-file help text

* feat(cli): support --agent/--agent-file in the interactive TUI

Bind the selected agent profile to the startup session when launching
the TUI with --agent/--agent-file, including the session created after
an OAuth login at startup. Sessions created later in the process (/new)
keep the default profile.

Make both flags creation-only in every mode: combining them with
--session/--continue is now rejected in print mode too, since resume
restores the bound agent from the session automatically.

* fix(agent-core): persist new secondary-model selections under env overrides

stripSecondaryModelConfig restored secondary_model.model/default_effort
from raw whenever KIMI_SECONDARY_MODEL/KIMI_SECONDARY_EFFORT was set, so
a /secondary_model pick made under the env vars was silently discarded
on write. Restore from raw only when the value being written still
equals the env value (an overlay round-trip), mirroring the pointer
check in stripEnvModelConfig; a genuinely different selection now
reaches config.toml.

* fix(cli): report the effective secondary model when env overrides the pick

/secondary_model toasted the picked alias even when
KIMI_SECONDARY_MODEL/KIMI_SECONDARY_EFFORT made the session bind a
different model. Read the effective binding back from the reloaded
config (as /model does from session status) and warn with the
env-overridden values instead.

* feat(tui): show the bound model name in the AgentSwarm panel header

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-07-29 12:06:26 +08:00
Kai
4c763f6763
feat: send prompt-attached videos directly with the prompt (#1999)
Some checks are pending
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
CI / test-windows (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat: send prompt-attached videos directly with the prompt

Videos attached to a prompt (pasted in the TUI, uploaded in the web UI)
previously reached the model only after it opened the file with
ReadMediaFile — an extra tool round trip that could leave the video
unseen when the model never made that call. They are now uploaded
through the model provider's file channel and embedded directly in the
user message as the provider-issued reference; ReadMediaFile stays as
the fallback and as the model's own way to open video files.

- agent-core: new uploadVideo agent RPC and Session.uploadVideo in the
  SDK; the TUI uploads pasted videos at submit time, falling back to
  the file-tag form on failure
- kap-server: inline file-source video prompt parts at the REST edge
  and map provider file ids back to local uploads behind
  GET /files/llm/{llm_id}
- kimi-web: play provider-referenced prompt videos after reload/resume

* fix: fall back to inline video when the upload channel fails

ReadMediaFile only used the provider's video upload channel when one
was bound, and surfaced a hard tool error when the upload itself
failed — on providers without a files endpoint (or a transient upload
failure) the model was told the video could not be read at all. Now a
missing or failing upload falls back to delivering the video inline
(base64), the same shape providers without an upload channel already
get. Both engines (agent-core and agent-core-v2) are fixed.

* test: cover prompt video edge cases and failure paths

Stress coverage for the prompt video pipeline:

- agent-core uploadVideo RPC: extension/magic classification
  (.txt with video bytes accepted, extension trusted when magic is
  absent), exact 100MB boundary, directory and nonexistent paths
- TUI: mixed per-video outcomes (one inlined, one tag fallback),
  submission order behind an in-flight upload, queued video messages
  carrying final uploaded parts
- kap-server: per-video provider-id mappings, Range requests through
  the llm redirect, mapping persistence across a server restart

* fix: surface auth rejections from the video upload channel

The base64 fallback for a failing video upload must not mask auth
rejections: a 401/403 (surfaced as provider.auth_error) drives the
credential force-refresh and a clear auth error, while an inline
payload would just be rejected again by the next request. Only a
missing or broken upload channel (no files endpoint, network/server
errors) falls back to inline delivery.

* fix: keep the by-design no-hook video error from degrading to inline

Main's contract for a provider with no video upload hook is an honest
"does not support video upload" tool error — an inline payload would
be dropped on that protocol's wire anyway. The base64 fallback now only
applies to an upload channel that exists but failed at runtime. The
no-hook throw gets a stable type (VideoUploadUnsupportedError) so the
two cases are told apart without matching message text.

* fix: constrain the llm video route param to a safe alphabet

The provider file id is used as a blob-store key, and the node-fs
backend joins scope and key into a storage path. An id containing an
encoded path separator (%2F) could address a different storage path
than the intended llm-video mapping; the route now only accepts the
provider-id alphabet.

* style(agent-core-v2): move added explanations into top-of-file comment blocks

The package's comment convention keeps comments solely in the top-of-file
block; relocate the new notes (url-source id pairing, video delivery
fallback, VideoUploadUnsupportedError) from functions and schema fields
into their module headers.

* fix: reject prompt video uploads when the model lacks video input

The uploadVideo RPC only checked the provider's upload channel, so an
SDK caller on a text-only or unknown-capability model could obtain a
valid-looking video_url part the current model is not supposed to
accept. The TUI capability guard does not cover this public path, so
the agent now gates on video_in itself.

* fix: sniff uploaded video bytes before inlining them

The inline branch trusted the upload-time content type; now the bytes
are sniffed first, and anything magic-confirmed as a non-video kind
(e.g. an image mislabeled as video/mp4) falls back to the file-tag
form instead of being uploaded. Like the image gate, bytes are
authoritative where the container format allows it — an MPEG-PS
lookalike still rides the extension, matching ReadMediaFile.

* test: keep the generation stub pending until the abort lands

An immediately-answered 404 ends the turn (non-retryable) before the
test's abort call, racing the cleanup into a 409; the stub now hangs
generation until the abort cancels it.

* fix: validate prompt file references before mutating session controls

A stale file_id failed inside media resolution — after the model,
thinking and permission overrides had already been applied — so a
rejected prompt still changed the session's controls. File references
are now checked up front, keeping failed submits side-effect free.

* fix: serialize prompt submissions per session

A slow provider video upload let a later text-only request reach the
queue ahead of an earlier video one, silently reordering the
conversation for REST clients and multiple tabs. Submissions to the
same session now chain, matching the ordering the TUI already
guarantees locally.

* fix: play reloaded provider videos through the authenticated fetch path

A video recovered from an ms:// reference carried only the bare redirect
URL, which 401s under daemon auth when loaded natively. The attachment
now keeps the provider file id (llmFileId) end to end, and AuthMedia
fetches the bytes with the Bearer credential through the daemon's llm
redirect — the same blob-URL path uploaded files already use.

* fix: fence pending video submits against session and model switches

A slow paste-upload left the TUI idle, so /new, the session picker or
/model could fire mid-upload; the continuation then dispatched the old
session's provider reference into the newly selected session or a
model that cannot resolve it. The dispatch now re-checks that the
session and model are unchanged and asks for a resend instead.

* fix: preserve the caller's video path verbatim in uploadVideo

Trimming the path changed the filesystem target before validation and
upload, so a name that legitimately starts or ends with whitespace
resolved to the wrong file; the trim is now only the emptiness check.

* fix: forward provider-issued ids on image URL prompt parts too

The shared url-source schema accepts id for image and video parts, but
only the video path forwarded it, dropping provider-keyed image ids
between prompt acceptance and the model request.

* fix(web): reconcile inlined video echoes into the optimistic user message

The loose user-message matcher counted media parts and <video path>
tags but not the [video:ms://…] text shape, so a racing server echo of
an inlined upload slipped through as a duplicate user bubble.

* fix: queue bash submits behind a pending video upload

A bash-mode submit could start while a pasted video was still
uploading, recording shell context before the earlier prompt was
dispatched and reordering user actions; it now chains behind the
upload like normal submits.

* test: cast the driver through unknown for the session-switch fence test

* fix: validate prompt file references before resolving the prompt agent

A stale file_id posted to a fresh or cold session materialized the main
agent (registering it in session metadata and igniting agent-scoped
services) before the request was rejected. The file check now runs
first, so failed submits create nothing and mutate nothing.

* fix: key the playback mapping by the id embedded in the reference URL

Projections and clients read the provider id from the ms:// URL, not
the id field, so an upload that returns an id-less or mismatched
reference would inline fine but 404 on playback. The mapping now
derives its key from the URL, falling back to the explicit id.

* fix: sanitize provider video ids on the write side of the playback map

The read route got the safe-alphabet guard, but recordLlmVideoRef still
used the provider-returned id verbatim as a blob-store key; a crafted
provider response could write the mapping outside the llm-video
namespace. Out-of-alphabet ids are now dropped on both put and get.

* fix(web): keep recovered provider videos resendable through the edit path

The composer reload path only honored fileId and fell back to a
bare fetch(url) without the Bearer token, so editing a reloaded
provider-video turn dropped the chip after a 401. llmFileId is now
threaded through and the bytes are re-uploaded via the authenticated
llm redirect.

* fix: fall back to inline video for no-hook providers whose wire carries it

The by-design no-hook error is only the honest answer when the wire
would drop an inline payload anyway (the OpenAI family). Protocols
that convert video_url (kimi, anthropic, google-genai, vertex) now
take the base64 fallback instead of failing every video read; the
registrar computes the flag from the model's protocol.

* test: satisfy the full IBlobStore shape in the playback-map stub

The tsgo typecheck job rejects a structural stub missing _serviceBrand
and list even though plain tsc accepted it.

* fix: queue prompt-producing slash commands behind pending uploads

Skill activations and plugin commands started their turn immediately
while a pasted video was still uploading, so the earlier video prompt
queued behind them and user actions ran out of order. Both paths now
chain behind inputSubmitChain (and re-check session/model at dispatch)
like normal and bash submits.

* fix: validate media kinds in the prompt file-reference preflight

The preflight only proved a referenced file exists; a real upload used
with the wrong kind (e.g. a PDF submitted as video) still passed it and
mutated session controls before assertMediaFile rejected the request.
The kind assertion now runs up front with the existence check.

* fix(web): play sent and recovered videos in the file preview

The media preview returned early for every kind except image, so a
user-turn video chip's play action was a no-op even with llmFileId
threaded through. The preview now handles video: bytes come from the
authenticated file/llm fetch into a blob URL and render in a native
player.

* fix(web): preview recovered videos with the authenticated blob URL

The llm re-upload branch fetched bytes with auth but kept the protected
redirect URL as the chip preview, which 401s as a native video src; the
fetched blob now becomes the preview URL, mirroring the fileId branch.

* style(agent-core-v2): move the inline-fallback note into the module header

Same package comment convention as before: rationale lives in the
top-of-file block, not beside the registration call.

* fix: fence delayed bash submits to the originating session

The chained bash callback ran runShellCommandFromInput against whatever
session was active at dispatch time, so a command submitted in session
A could execute in session B's workspace and be recorded there after a
mid-upload switch. The originating session is now captured at submit
and re-verified at dispatch, like the prompt and skill paths.

* fix: emit prompt video telemetry from the agent scope

video_upload is an agent-level event requiring ambient agent identity,
but the uploader was built with the Core-scoped session view, leaving
prompt-upload events unattributable. The route now resolves telemetry
from the target agent for the uploader while image compression keeps
the session-scoped view.

* fix: preserve provider image ids through legacy projections and web mappers

The url-source id accepted by the prompt schema was dropped again by
the legacy message projection and the web wire mapper, so provider-
keyed image references lost their id across messages, snapshots, and
undo responses. It now flows through both directions.

* fix(web): revoke recovered video blob URLs before dropping attachments

A failed llm re-upload removed the attachment without revoking the
freshly created preview blob URL, pinning the whole video in the
browser blob store until page unload on every failed edit attempt.

* fix: serialize foreground slash commands behind pending uploads

/compact and /init started their turn while a pasted video was still
uploading, so the earlier message landed after them — and compaction
summarized the context without it. Prompt-producing builtin commands
now share the same queueBehindPendingUploads chain (with the
session/model dispatch fence) as skill, plugin, and bash submits.

* fix: defer session controls until media preparation succeeds

Media resolution now runs before any profile/model/thinking/permission/
denylist mutation, with the uploader resolved transiently from the
requested (or currently bound) model — a failed submission leaves the
session's controls untouched. A concurrent model switch during
preparation is rejected with session.busy instead of enqueueing a
reference uploaded for the previous model.

* chore: bump the new SDK video upload API as a minor release

Session.uploadVideo is new public API surface, not a patch-level tweak.

* fix: re-check busy state before draining upload-queued commands

A slash command deferred behind a video upload ran the moment the video
prompt dispatched, landing on an already-running turn: beginSessionRequest
wiped the active turn's live pane, and /init or /compact started on top
of it. The deferred callbacks for skills, plugin commands, /compact and
/init now re-run the resolver's busy check at dispatch and show the same
blocked message the user would get when typing while streaming.

* fix: resolve profile-bound models before choosing the uploader

A first prompt carrying "profile" without "model" resolved the upload
model from the still-unbound alias, so no uploader was installed and
every attached video fell back to a tool-read tag even when the
configured default model supports provider upload. The transient
resolution now mirrors AgentProfileService.bind: an explicit body model
wins, a profile bind falls back to the configured default model, and
only otherwise does the currently bound alias apply.

* refactor: resolve prompt videos at request time inside the engine

Move prompt-video delivery out of the submission edge: the TUI submits
synchronously with a local file:// part the v1 turn resolves before the
message enters history, and kap-server carries an internal kimi-file://
reference the v2 requester resolves against the effective model with an
app-scoped upload cache. History keeps the durable local file id, the
/messages projection emits structured video parts, and the web plays
videos back through the authenticated /files channel - deleting the
submit fences, per-session serialization, provider-id reverse mapping,
redirect endpoint, and the unreleased SDK upload API.

* fix: propagate abort through video upload delivery instead of degrading

A turn cancelled mid-upload used to be treated as an ordinary upload
failure: v1 fell back to an inline base64 part and appended the degraded
message to history, and the v2 resolver memoized the tag fallback for the
rest of the agent's lifetime. Both catch sites now check the delivery
signal itself - abort rejections vary in shape by provider - and re-throw
so cancellation ends the turn (v1, classified as cancelled via the abort
reason) or the request (v2, not memoized, so the next turn uploads).

* fix: keep the tag form for no-upload providers whose wire drops inline video

An OpenAI-family model configured with video_in but no provider upload
channel used to receive prompt videos as an inline base64 part - which
chat completions rejects and the Responses adapter degrades to an
omitted-video placeholder, persisting ~4/3x the file size in history for
bytes the model never sees. The prompt path now mirrors the v2 resolver's
protocol gate and degrades to the <video path> tag instead; ReadMediaFile's
own delivery is unchanged. Also merges the prompt-video changesets into a
single user-facing entry.

* fix: escape the NUL separator in the video upload cache key

The cache-key template literal contained a literal NUL byte instead of
the \0 escape, which made Git classify the whole source file as binary
- no inline diffs, unreliable text tooling. The escape produces the
byte-identical runtime string, so hashed cache keys are unchanged.

* fix: check the abort signal before the inline video fallback

The no-uploader inline path (and the post-upload-failure fall-through)
never consulted the delivery signal, so cancelling a turn while the video
bytes were being read still base64-encoded the file and appended the
degraded message to history. The inline branch now re-throws the abort
reason first, matching the upload catch.

* fix: retry transient prompt video upload failures on later steps

A generic upload failure used to memoize its tag fallback for the rest of
the agent's lifetime, freezing a transient files-endpoint error into a
permanently degraded video. The resolver now marks failure-born fallbacks
as non-memoizable: the current request keeps the lightweight tag form and
the next step retries the upload. Structural outcomes (successful uploads,
capability and sniff fallbacks, no-hook inline) stay memoized for
step-retry stability.
2026-07-22 13:46:00 +08:00
Haozhe
31449728b7
fix(security): close FetchURL SSRF bypasses and DNS-rebinding window (#1791)
Some checks are pending
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Desktop 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
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / VSIX package audit (darwin-arm64) (push) Waiting to run
CI / VSIX package audit (all) (push) Waiting to run
CI / VSIX package audit (win32-x64) (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Release / Native release artifact (push) Blocked by required conditions
* fix(security): close FetchURL SSRF bypasses and DNS-rebinding window

- resolve hostnames via DNS and reject any target resolving to loopback /
  RFC1918 / link-local / CGNAT / ULA ranges, including IPv4-mapped IPv6
  forms (e.g. localtest.me, [::ffff:7f00:1]) — the static host denylist
  only matched literals and could be bypassed by crafted domains
- follow redirects manually with full per-hop revalidation (10-hop cap)
  instead of auto-following, so a public URL cannot 302 the fetcher at
  internal services or cloud metadata endpoints
- pin each connection to the DNS answers the check validated (per-hop
  undici Agent with a pinned lookup), closing the TOCTOU / DNS-rebinding
  window between the check and the connect; skipped when a proxy is
  configured or allowPrivateAddresses is set
- apply to both agent-core and agent-core-v2 providers, with SSRF /
  redirect / pinning test coverage

* chore: add changeset for FetchURL SSRF hardening

* test: bridge undici type declarations in fetch pinning tests

* fix(security): keep dispatcher option lib-agnostic for DOM typecheck consumers

* fix(security): address review — pin NO_PROXY bypasses, drain oversized bodies, header-only comments
2026-07-17 11:21:05 +08:00
qer
d1ca65e1de
feat(vscode): migrate extension to Node SDK (#1769)
* feat(vscode): migrate extension to Node SDK

* fix(vscode): address CI failures

* fix(vis): handle token count records

* fix(vscode): keep chat toolbar and header readable at narrow widths

* fix(vscode): map yolo to core yolo permission and honor the global yolo setting

* docs(vscode): record Node SDK migration design

* docs(vscode): split breaking changes out of the 0.6.0 changelog

* fix(vscode): keep a resumed session's thinking effort instead of reapplying the default

* fix(vscode): announce session status when a view attaches so the display matches it

* fix(vscode): align webview thinking effort handling with the TUI
2026-07-16 17:27:21 +08:00
7Sageer
70211a727f
feat: attach trace_id to telemetry events (#1724)
* feat: attach KFC trace_id to telemetry events

Capture the x-trace-id response header in both kimi providers (kosong
and agent-core-v2 llmProtocol) via the openai SDK withResponse(), and
thread it through the request result chain into telemetry so events can
be joined with server-side request logs by trace id instead of
session_id + time window.

- kosong / llmProtocol: StreamedMessage/GenerateResult.traceId,
  GenerateOptions.onTraceId early capture (available mid-stream),
  APIStatusError.traceId on the error path
- v1 (agent-core): per-turn traceIdByTurn on TurnFlow; attach trace_id
  to turn_ended/turn_interrupted/api_error/cancel/tool_call(+dedup,
  repeat)/permission_approval_result/question_*/compaction_*
- v2 (agent-core-v2): AgentTelemetryContext carries turn_id/trace_id
  updated by llmRequester; registry gains trace_id on 13 events plus
  turn_id on turn_* and turn_id/step_no on api_error

* fix: attribute trace_id to the failed request on error paths

Review follow-ups for trace_id attribution on failure paths:

- v1: chatWithRetry feeds a failed attempt's APIStatusError trace id into
  the turn's traceIdByTurn before the loop dispatches turn.interrupted,
  so turn_ended/turn_interrupted on error turns attribute to the failed
  request instead of the previous successful step.
- v2: llmRequester keeps the onTraceId-captured trace in per-request
  state and trackApiError prefers it over error extraction, so failures
  after response headers arrived (empty response, mid-stream decode
  errors) no longer clear the ambient trace_id.
- v2: compaction_failed falls back to the ambient trace_id once a
  summarizer request actually hit the wire, covering mid-stream failures
  whose error carries no trace.
- kosong: parseTraceId filters empty x-trace-id headers and both kimi
  providers read the header through it.
- v2 test harness forwards onTraceId to GenerateFn so tests can simulate
  the early header capture.

* fix: attribute v1 api_error to the in-flight request on post-headers failures

- v1: a failure after response headers arrived (mid-stream decode error,
  empty response) carries no trace on the error itself, so api_error lost
  the trace the client had already captured via onTraceId. Add a per-step
  in-flight capture (written by onTraceId, cleared at step begin/end) and
  fall back to it when the error carries no trace, aligning with v2's
  per-request requestTraceId. Failures before any response headers
  (network errors, local aborts) still report no trace rather than
  leaking a previous request's.
- v2: document that ambient trace_id distribution assumes serialized LLM
  requests per agent, naming the two supported paths that break it
  (after-step compaction below the block ratio; inject-path turns during
  a manual compaction).

* fix: isolate telemetry trace attribution

* fix: isolate telemetry trace by request

* fix: declare @moonshot-ai/protocol dependency of agent-core-v2

The trace-by-request refactor imports types from @moonshot-ai/protocol
in toolContract and toolExecutorService, but the package was not
declared, failing clean CI installs (TS2307). Also drop three type
imports that are unused after the merge with main.
2026-07-16 09:14:12 +08:00
Haozhe
5d6ff022b1
feat(agent-core): drop default timeouts for print-mode background tasks (#1737)
* feat(agent-core): drop default timeouts for print-mode background tasks

- add `bash_task_timeout_s` config under [background] (0 = no timeout),
  covering the background Bash default and the re-arm after a foreground
  command is moved to the background on timeout
- allow `[subagent] timeout_ms = 0` (no timeout) and thread it through
  Agent/AgentSwarm task registration and the swarm batch timer
- fill both with 0 in print-mode config defaults so `kimi -p` never kills
  background work by wall-clock and only the model stops a task;
  interactive defaults are unchanged
- sync the Bash tool description/parameter text with the effective
  default and update user docs (en/zh) plus changeset

* test(agent-core): satisfy KimiConfig providers requirement in print-defaults tests

* fix(agent-core): clear the foreground deadline on detach when the background timeout is disabled

`detach()` only re-arms when `detachTimeoutMs` is defined, so passing
`undefined` with `bash_task_timeout_s = 0` kept the armed foreground
deadline and a manually detached command was still killed at its
foreground timeout. Pass `0` instead so the reset clears the timer.
Caught by Codex review on #1737.
2026-07-15 14:35:32 +08:00
Kai
32a89c3643
fix(agent-core): prevent oversized media from poisoning sessions (#1657) 2026-07-14 12:46:45 +08:00
Kai
7c889f3a96
fix(agent-core): mark auto-approved plan exits as not user-reviewed (#1638)
* fix(agent-core): mark auto-approved plan exits as not user-reviewed

In auto permission mode ExitPlanMode is approved without user
involvement, but its result read "## Approved Plan:" exactly like a
genuine user approval and the transcript showed a green "Approved"
chip. The model treated that as a signal to start executing, even
when the user had asked it to stop after planning.

- Emit an "auto-approved, not user-reviewed" result with a note that
  execution follows the user's original instructions (v1 + v2).
- Extend the auto-mode reminder: plan approvals are automatic and are
  not a user signal to proceed (v1 + v2).
- Render an "Auto-approved" warning-toned chip in the TUI, keeping
  backward compatibility with the old result marker.
- Document the Auto mode plan-exit behavior in interaction guides.

* fix(agent-core): only mark plan exits as auto-approved in auto permission mode

Address review feedback on the auto-approved plan exit change:

- Branch the direct-execution output on the permission mode in both
  engines: only auto mode yields the "auto-approved, not
  user-reviewed" result and telemetry outcome. In manual / yolo modes
  the direct path means a configured or session allow/ask rule let the
  call through — an explicit user decision that keeps the user-approved
  output, the Approved transcript chip, and the approved outcome.
- Move the v2 rationale out of the method body into the file header,
  per the agent-core-v2 comment convention.
- Drop the absolute "only Auto mode" wording from the interaction
  guides (en/zh).
2026-07-14 11:30:47 +08:00
liruifengv
83e175399f
feat: auto-background timed-out foreground bash commands (#1591)
* feat: auto-background timed-out foreground bash commands

* fix: discourage blocking TaskOutput waits on background tasks

* test: avoid unsafe string conversion in bash timeout test

* fix: align bash timeout description with auto-background opt-out

* feat(agent-core-v2): auto-background timed-out bash commands

- detach timed-out foreground Bash tasks and re-arm the background deadline
- align TaskOutput guidance with non-blocking background task handling
- add klient SEA end-to-end coverage for the v2 server

* fix(klient): clean up lint errors in auto-background e2e example

---------

Co-authored-by: haozhe.yang <yanghaozhe@moonshot.ai>
2026-07-13 21:08:21 +08:00
_Kerman
5502b90b42
test: cut slow suite runtimes and isolate experimental flag from host env (#1595) 2026-07-13 17:45:22 +08:00
Haozhe
faefad0e29
feat(agent-core): configurable subagent timeout with 2h default (#1562)
* feat(agent-core): make subagent timeout configurable and raise default to 2h

- add `[subagent] timeout_ms` config (env `KIMI_SUBAGENT_TIMEOUT_MS` overrides) to replace the hardcoded 30-minute cap for Agent / AgentSwarm subagents
- raise the default subagent timeout from 30 minutes to 2 hours
- thread the value through tool construction so foreground and background subagents use it, with the timeout message reflecting the effective value
2026-07-12 11:48:06 +08:00
Kai
db61c9e2dd
fix: refuse unsupported image formats instead of poisoning sessions (#1536)
* fix: refuse unsupported image formats instead of poisoning sessions

Images in formats providers reject (AVIF, HEIC, BMP, TIFF, ICO) used to
pass through to the API, and the resulting HTTP 400 repeated on every
later turn because the image_url stayed in the session history.

Add a single format policy (accepted set: PNG/JPEG/GIF/WebP) enforced at
every ingestion point: ReadMediaFile refuses with a per-OS conversion
command; MCP tool results, REST uploads, and ACP prompts replace the
image with a text notice; and turn.prompt/steer gates as the last-funnel
backstop so the SDK/RPC path cannot poison a session either. Accepted
MIME aliases (image/jpg, case/whitespace) are forwarded in canonical
form, and data URLs carrying MIME parameters can no longer slip past the
gate. Remote image URLs pass through (no bytes to inspect).

* fix: canonicalize accepted data URLs with MIME parameters

The format gate compared only the MIME token when deciding whether to
rebuild a data URL, so an accepted image carrying MIME parameters
(`data:image/jpeg;charset=utf-8;base64,...`) was forwarded with its
original header. The Anthropic provider splits the data URL and
exact-matches the full header against its whitelist, so the part still
poisoned the session. Rebuild to the byte-exact canonical URL whenever
the original differs, covering aliases, case/whitespace, and parameters
with one comparison.

Addresses review feedback on PR #1536.

* fix: parse data URLs case-insensitively in the image format gate

An uppercase `;BASE64,` marker is legal (RFC 2045 encoding names are
case-insensitive), but the parser required a lowercase match and
returned null, so the gate treated the URL as remote and forwarded it:
an unsupported image could still land in the session history, and the
Anthropic provider's lowercase-only split then threw on every turn.
Match the scheme and marker case-insensitively; the canonical rebuild
emits the lowercase form.

Addresses review feedback on PR #1536.

* fix: harden image format handling against mislabeled and legacy images

Two more ways an unsupported image could reach the provider are closed:

- Bytes, not labels, decide the format. A data-URL image whose declared
  MIME disagrees with its magic bytes (e.g. AVIF bytes an image search
  tool labels image/png) is now gated on the sniffed format at every entry
  point (MCP results, ACP, SDK/RPC prompt, REST inline and file uploads),
  so a mislabel cannot slip past the gate.
- A poisoned image already in the session history no longer kills the
  session: a server image-format 400 (or kosong's client-side image
  rejection) now retries once with every media part replaced by a text
  marker, mirroring the 413 media-degraded recovery. The recovery also
  fires during compaction, and the transient-retry fallback no longer
  burns the retry budget on image-format errors before the dedicated
  recovery can run.

* fix: reject remote image URLs ending in an unsupported extension

Remote image URLs (MCP resource_link, REST `kind: 'url'`) carry no bytes
to sniff, so a link ending in `.avif` (or `.heic`, `.bmp`, `.tiff`,
`.ico`) would pass through and be fetched server-side — and rejected.
Reject such URLs by their path extension instead (query/fragment
ignored, case-insensitive); extensionless or accepted-extension URLs
still pass through to the provider and the 400 recovery.

* fix: tighten image format handling for parameterized MIMEs and recovery scope

Address two review findings on PR #1536:

- A declared media type with parameters (e.g. image/jpeg; charset=utf-8)
  is no longer misread as unsupported: normalizeImageMime now strips
  parameters, matching the data-URL parser, so an accepted image with
  parameters is forwarded instead of dropped.
- The image-format recovery predicate is narrowed to specific
  format/data rejection phrases, so a 400 about image count, size, or
  image-input support no longer triggers a media-stripped resend that
  would let the model answer blind to the user's images.

* fix

* fix: scope image format recovery to images and flag remote SVG URLs

- The media_type/mime_type recovery match now requires the message to
  mention an image, so a video/audio media_type rejection surfaces
  instead of triggering a blind media-stripped resend.
- unsupportedImageMimeFromUrl flags .svg URLs as image/svg+xml without
  touching the shared suffix map (SVG stays text for the file tools),
  so remote SVG images get the intended notice instead of a provider
  rejection.

Addresses review feedback on PR #1536.

* fix: reject remote MCP images by their declared MIME type

An MCP resource_link with an extensionless or signed URL gives the
extension gate nothing to work with, and convertMCPContentBlock was
discarding the declared mimeType — an honestly-declared AVIF/HEIC link
from an image search tool still became an image_url and poisoned the
session. Reject on the declared MIME when the server provides one:
unsupported declarations become a text notice that keeps the URL so the
model can fetch and convert it; accepted declarations pass through as
before.

Addresses review feedback on PR #1536.

* fix: keep image format recovery image-specific and preserve dropped URLs in notices

- Drop the bare `media` alternative from the image-format recovery
  patterns so audio/video media rejections ("unsupported media type",
  "invalid media type") can never be misclassified as image errors and
  blindly media-stripped; every pattern now mentions "image" literally.
- Remote image URLs rejected by their extension now keep the URL in the
  replacement notice (gateImageFormatParts and the REST url path), so the
  model can still fetch and convert the image — matching the declared-MIME
  resource_link path.

Addresses review feedback on PR #1536.

* fix: drop malformed data URLs at ingestion instead of letting them poison the session

A `data:` URL that fails to parse (missing `;base64,` separator, empty
MIME, …) was treated like a remote URL and passed through the format
gate; the provider then rejects it on every turn, and the read-side
media-stripped recovery keeps paying that round-trip until compaction.
Detect unparseable `data:` URLs in gateImageFormatParts and replace them
with a (truncated) notice at ingestion, covering the MCP/ACP/SDK/turn
paths that share the gate.

Addresses review feedback on PR #1536.
2026-07-10 19:36:00 +08:00
Kai
046b6c4175
fix(agent-core): scope [image] config limits to the owning core (#1521)
* fix(agent-core): scope [image] config limits to the owning core

* fix(agent-core): thread harness [image] max_edge_px to TUI paste and ACP ingestion

* chore(changeset): simplify entry to user-facing wording
2026-07-09 20:55:52 +08:00
Kai
1bf2c9afee
feat: keep image-heavy sessions within provider request-size limits (#1508)
* feat(kosong): classify HTTP 413 request-body-too-large as a dedicated error type

* feat(agent-core): lower default image downscale cap to 2000px and make it configurable

* feat(agent-core): strip media to text markers and retry when the compaction request is too large

* feat(agent-core): cap model-initiated image reads with a configurable byte budget

* feat(agent-core): resend with degraded media when the provider rejects the request body as too large

* test(agent-core): add explicit timeouts to encode-heavy image budget tests

* feat: add WebP decoding support with wasm integration

- Introduced a new WebP decoding module using @jsquash/webp's wasm decoder.
- Implemented functions to decode WebP images and check for animated WebP formats.
- Updated image compression tests to include scenarios for WebP handling, including encoding and decoding.
- Enhanced error handling for API request size limits to accommodate various error messages.
- Updated pnpm lockfile to include new dependencies for WebP encoding and decoding.

* chore(changeset): consolidate this PR's entries into one

* fix(nix): update pnpmDeps hash for merged lockfile

* feat(agent-core): refuse HEIC/HEIF reads with platform-matched conversion guidance
2026-07-09 18:05:14 +08:00
Luyu Cheng
173bdfdab1
fix: resume sessions with missing workdir (#1517) 2026-07-09 16:46:57 +08:00
Luyu Cheng
131700097a
fix: clarify goal blocked audit guidance (#1481) 2026-07-07 22:29:40 +08:00
Kai
474ce289dd
fix(agent-core): report EXIF-rotated image dimensions and raise edge cap to 3000px (#1460)
* fix(agent-core): report EXIF-rotated image dimensions and raise edge cap to 3000px

Image compression now reports original dimensions in the decoded
(EXIF-rotated) space, matching the coordinate system of the sent image
and of ReadMediaFile region readback; previously portrait JPEGs
(orientation 5-8) got swapped width/height in captions. The longest-edge
downscale cap rises from 2000px to 3000px, and the default jimp resize
path is documented as the anti-aliased area-average one so it is not
accidentally switched to a point-sampled interpolation mode.

* test: shrink oversized image fixtures to fit CI timeouts

The 3600x3600 fixtures introduced for the 3000px edge cap nearly doubled
the pixel area jimp has to decode and deflate, pushing the slowest
compression tests past the 5s vitest timeout on CI runners. 3600x1800
keeps every fixture over the cap while restoring roughly the workload of
the old 2600x2600 fixtures that CI handled comfortably.

* test: pin anti-aliased downscale quality with executable guards

A 1px checkerboard probe pins the compressor to full-coverage averaging
at integer and fractional ratios, with jimp's point-sampled BILINEAR
mode kept as the executable aliasing counter-example (it collapses the
50%-gray pattern to solid black at 4:1). Also guards the other classic
downscale bugs: transparent-pixel color bleed, mean-brightness drift,
iterative recompression degradation, and zero-size collapse on extreme
aspect ratios.

* fix(agent-core): report decoded EXIF-rotated dimensions in ReadMediaFile notes

The media note derived its original-dimensions line from the header
sniff, which reports pre-rotation values for EXIF orientation 5-8
JPEGs. The sent image and region readback both live in the decoded
(rotated) space, so portrait photos got axis-swapped coordinate
guidance. Once a decode has happened — compression or crop — its
dimensions now overwrite the sniffed ones.

* fix(agent-core): improve handling of EXIF orientation in image dimensions and metadata

* fix(agent-core): sniff EXIF orientation and step budget fallback through 2000px

Two follow-ups to the EXIF and 3000px-cap changes:

sniffImageDimensions now reads the JPEG EXIF Orientation tag (pure
header parse, both byte orders) and reports display-space dimensions
for orientations 5-8. Passthrough images — never decoded — previously
kept the pre-rotation header size in compression results and media
read notes, disagreeing with the decoded space that region readback
uses.

encodeWithinBudget steps the over-budget fallback through 2000px
before the 1000px last resort. Raising the cap to 3000px had left a
regression window: an image whose 2000px encode fits the byte budget
was sent at 1000px where the old 2000px cap used to send it at
2000px.

* fix(kimi-code): record pasted image dimensions in display space

The TUI paste path recorded attachment and original dimensions from its
raw header parser, which ignores EXIF orientation. For a portrait JPEG
the submit-time caption then contradicted the sent image's aspect and
region readback coordinates were axis-swapped. Dimensions now come from
the compression result, which reports display space on both the
compressed and passthrough paths; parseImageMeta remains only the
format/mime gate.

* feat(agent-core): add image compression and crop telemetry

Every image ingestion path now reports an image_compress event —
outcome (compressed / passthrough fast, guard, unsupported, unhelpful,
error), input/output formats, byte and pixel sizes, EXIF transposition,
and duration — and region readback reports an image_crop event with a
failure classification and the region's share of the original area.

Wiring is per call site via a new CompressImageOptions.telemetry
option, so the outcome split and timing are measured inside the
compressor while each caller only names its source: ReadMediaFile
(tool construction, like GrepTool), MCP tool results (McpOutputOptions),
server prompt ingestion (ICoreProcessService now exposes the host
telemetry client), ACP prompts (session track adapter), and TUI paste
(host.track adapter). Properties are numeric/enum only — never paths
or content — and a throwing client can never affect the compression
result.

* fix(agent-core): run the full JPEG quality ladder at fallback sizes

The fallback rescales encoded only at quality 20, so a JPEG whose
ladder failed at the fitted size collapsed straight to the lowest
quality even when the smaller size left budget headroom for a higher
rung (the realistic window is the 1000px step, where the 4x pixel
drop pays for q80/q60). Each fallback edge now walks the same
q80-to-q20 ladder as the fitted size.

* test: shrink heavy JPEG fixtures and add explicit timeouts

The fallback-ladder test runs ~11 pure-JS JPEG encodes and the EXIF
paste test decodes, rotates, and re-encodes a 6.5MP frame; both sat at
the edge of the 5s vitest timeout on CI runners. Narrower fixtures cut
the pixel area (the ladder test keeps its width above 2000px so the
full fallback chain still runs) and explicit 15s timeouts absorb runner
variance.

* fix(server): scope prompt image compression telemetry to the session

The prompt-ingestion image_compress events were emitted with the bare
host telemetry client, while every agent-side source inherits a
session-scoped client — so prompt_inline/prompt_file events could not
be correlated with their session. The route now wraps the client with
withTelemetryContext({ sessionId }) like rpc/core-impl does for
session telemetry.

* chore(changeset): consolidate image compression changesets

One entry covering the cap raise and the EXIF dimension fix, listed
for both the CLI and the SDK so the SDK changelog's compression
description (previously pinned at 2000px) stays accurate.
2026-07-07 21:38:43 +08:00
Luyu Cheng
d1a964fba9
fix: forbid model-driven goal pauses (#1476) 2026-07-07 20:02:43 +08:00
Luyu Cheng
e9ef9399d0
fix(agent-core): harden goal-mode budget and outcome flow (#1456) 2026-07-07 15:23:57 +08:00
Kai
743f66e547
refactor: move tool-result metadata into a structured note side channel (#1437)
* fix: stop rendering <system> notes from tool results in the terminal and web UIs

Tool results carry <system> blocks as side-channel notes for the model (ReadMediaFile summaries, Read status, MCP image captions, error/empty sentinels). Keep them in history for the model, but strip them at every core-to-UI boundary so they no longer render as plain text. vis is intentionally left untouched to preserve the model's-eye view for debugging.

* fix: keep error/empty status text visible when stripping tool-result <system> tags

Unwrap the tool error/empty sentinels (<system>ERROR: ...</system>, <system>Tool output is empty.</system>) instead of deleting them: keep the human-readable text and drop only the tags. Otherwise a failed or empty tool result rendered as a blank output, indistinguishable from a rendering bug. The model still reads the wrapped form in history.

* refactor: move tool-result metadata into a structured note side channel

Tool-produced model-facing metadata (ReadMediaFile summaries, Read status
lines, MCP image-compression captions) was baked into tool output as
<system> text, so every UI had to strip it back out and three copies of
the model-view normalization had silently drifted apart.

- ExecutableToolResult gains `note`: content rendered to the model but
  never to UIs; records and history now store the raw output plus the
  structured isError/note fields
- the model view is rendered exactly once at the LLM projection boundary
  by renderToolResultForModel; the transcript and vis hand-copies are
  deleted (vis now calls the same function for its model view, fixing
  their drifted empty-output checks)
- ReadMediaFile / Read / MCP captions write `note`; tool outputs stay
  pure data, and text-only results keep a single text part (note joined
  with a newline) so provider tool content stays a plain string
- all UI-side <system> stripping is removed; failed tools show their own
  error text with the structured isError flag
- wire protocol 1.4 -> 1.5 migrates existing records' tool-produced
  <system> blocks into `note` on resume

* fix: provider-neutral wording, no wire migration, direct optional fields

- "The attached image was downsampled" replaces directional wording that
  depended on provider serialization order (inline media vs flatten-and-
  re-attach)
- drop the 1.4 -> 1.5 wire migration: legacy records replay verbatim, so
  the model view of old sessions stays byte-identical to what the model
  originally saw and UIs show the legacy <system> text as-is; this also
  removes the risk of the migration misclassifying user data that quotes
  tool metadata, and the additive note field needs no version bump
- pass optional result fields as undefined instead of conditional spreads
  (repo convention)

* fix: enforce the note contract at the trust boundary; narrow the TUI system-tag guard

- normalizeToolResult now keeps a note only when it is a non-empty string:
  tools and finalize hooks are arbitrary JS, and a malformed note (null,
  number, object) would previously persist into the record and crash every
  subsequent LLM projection of the session. Everything downstream now
  trusts note to be string | undefined.
- the TUI tool body suppression matches the full <system-reminder> tag
  instead of any <system prefix: reminder piggy-backing stays hidden,
  while real output that merely starts with a literal <system> tag (file
  contents, MCP text) stays visible, covered through the real
  ToolCallComponent path.

* fix: return MCP compression captions as data instead of extracting them from text

compressImageContentParts now returns { parts, captions } — captions come
back from the compressor as structured data and are never inserted into
the parts, so the MCP pipeline no longer pattern-matches text to move
them into the note side channel. Tool output that merely quotes a
caption (a doc, a log, a test fixture) stays verbatim in the output.
Also corrects the stale claim that prompt ingestion uses this helper
(it compresses per image while constructing the part).

* docs: correct the image-compression re-export comment; export CompressedContentParts

The package-root comment still described compressImageContentParts as the
input-stage helper every ingestion site calls; prompt ingestion compresses
per image with compressBase64ForModel / compressImageForModel, and the MCP
pipeline is the walker's only caller. Also export the walker's
CompressedContentParts return type so public-API consumers can name it.

* feat: wrap tool status sentinels in <system> so the model can tell harness verdicts from tool output

The error/empty status text is model-only after the note refactor (UIs
render the raw output and style failures via the structured isError
flag), so the earlier plain-text wording served no remaining audience.
Wrapping the statuses in <system> gives every piece of system-generated
text inside a tool result the same marker:

- failed calls get '<system>ERROR: Tool execution failed.</system>'
  unconditionally — the ERROR:-prefix guard is removed, so the harness
  verdict can no longer be confused with tool output that happens to
  start with error-like text
- empty outputs render as '<system>Tool output is empty.</system>'; the
  plain placeholder the loop layer bakes into records is still
  recognized and upgraded at projection time

* style: collapse an internal helper docstring per the services subtree convention
2026-07-07 11:40:27 +08:00
Kai
c12f30951f
feat(agent-core): feed AskUserQuestion answers back as question text and option labels (#1414)
* feat(agent-core): feed AskUserQuestion answers back as question text and option labels

The flattened answers record the model receives was keyed by synthesized
ids (q_0 / opt_0_1), forcing a cross-message positional lookup against the
original tool call to understand what the user picked — both unreadable in
transcripts and a real model-misreads-the-choice badcase.

- toAgentCoreResponse now takes the original broker request and translates
  wire ids back to question text (keys) and option labels (values);
  unknown ids are kept verbatim, missing request falls back to raw ids
- wire protocol unchanged: clients still answer with option ids; the
  resolve route reads the pending request before settling it
- question texts must be unique per call and option labels unique per
  question, enforced in the tool execution path (AJV cannot express the
  zod refine) and mirrored on the exported schemas
- web transcript card resolves both the new label form and legacy id
  transcripts; TUI and ACP paths already produced the text form

* fix(agent-core): align multi-select answer join across clients and harden question schema

- Join multi-select labels with ', ' in the server translator, matching
  what the TUI reverse-RPC path already emits, so the model sees one
  format regardless of which client answered
- Trim segments in the web transcript resolver before label matching:
  TUI-answered multi-select transcripts (', '-joined) previously lost
  their highlight to a spurious leading-space Other row
- Move the question-text/legacy-q_<i> answer lookup out of the SFC into
  askUserToolParse as answerFor(), per that module's testability intent
- Require non-empty question text and option labels (.min(1)) so empty
  strings are rejected by AJV at the tool boundary instead of failing
  deeper in the protocol layer

* fix(agent-core): resolve option ids only within the answered question

The translator's option-id lookup was a single flat map across all
questions, so a stale or malformed response pairing one question with
another question's option id (q_1 + opt_0_0) was silently translated
into a label that was never offered for that question. Scope the lookup
to the answered question's own options; cross-question and unknown ids
now both pass through verbatim, staying diagnosable.
2026-07-06 16:37:54 +08:00
Kai
175b95f3af
fix(agent-core): route image-compression captions through hidden system reminders (#1348)
* fix(agent-core): route image-compression captions through hidden system reminders

Prompt ingestion (server upload/base64 route, TUI paste, ACP) annotates a
compressed image with an inline <system> caption inside the user's own
message. That raw markup rendered verbatim in every user-visible history
projection (TUI session replay, web UI) and leaked into session titles.

Split the caption out at the appendUserMessage chokepoint and deliver it
through the built-in system-reminder injection (origin
{kind: 'injection', variant: 'image_compression'}), which every UI already
hides. The model still receives the full note; ingestion sites and the wire
protocol are unchanged. Session titles/lastPrompt strip the caption the same
way. Tool-result captions (MCP) keep the established <system> convention.

Covered by unit tests plus an end-to-end smoke suite that drives
rpc.prompt/steer through the real turn pipeline and asserts the provider
wire request, stored history, replay records, and resume parity.

* chore: tighten changeset wording per gen-changesets conventions
2026-07-03 16:21:10 +08:00
Kai
0fc0ae380b
feat(agent-core): announce image compression and keep originals readable (#1304)
* feat(agent-core): announce image compression and keep originals readable

Every image ingestion point (ReadMediaFile, MCP tool results, clipboard
paste, REST upload/inline base64, ACP) now places a <system> caption next
to a compressed image stating the original vs. delivered dimensions, byte
size, and format, so downsampling is never silent to the model.

Originals stay readable: file uploads point at the stored blob, and
in-memory images are persisted into the session's media-originals dir
(content-addressed, size-capped, removed with the session; temp-dir
fallback when no session is known).

ReadMediaFile gains region (crop in original-image pixel coordinates,
delivered at full fidelity) and full_resolution (skip downscaling, with
an explicit error over the per-image byte limit), so the model can zoom
into fine detail instead of silently degrading on large images.

* fix(agent-core): exempt compression captions from the MCP text budget

The caption announcing an image's compression was inserted before the
shared 100K text budget was applied, so a chatty MCP result (page text +
screenshot) consumed the budget first and the caption was evicted — or
sliced mid-string into an unclosed <system> fragment — while the
downsampled image survived, silently reintroducing the exact degradation
the caption exists to report, and orphaning the persisted original.

Split the size-limit pass in two and reorder the pipeline: the text
budget now runs on the tool's own text BEFORE compression inserts
captions (exempt by construction), and the per-part binary cap still
runs after compression so compressible screenshots are kept.

* fix(agent-core): harden crop error reporting and document readback semantics

- cropImageForModel rejects non-finite region coordinates with a clean
  message instead of surfacing the codec's internal validation dump
- the full_resolution and cropped-region over-budget errors now include
  exact byte counts alongside the rounded sizes, so a file a hair over
  budget no longer reads "is 3.8 MB, over the 3.8 MB limit"
- read-media.md notes that re-reading a file without region or
  full_resolution reproduces the same downsampled image
2026-07-02 19:07:56 +08:00
Kai
ace7901066
feat(agent-core): compress oversized images before sending to the model (#1243)
* feat(agent-core): compress oversized images before sending to the model

Downsample images to a 2000px longest-edge and per-image byte budget at the
single prompt-ingestion chokepoint (the prompt/steer RPC) and on tool results
(ReadMediaFile, MCP), so every client transport — CLI, web, desktop, ACP, SDK —
is covered uniformly inside the core. PNG screenshots stay lossless and only
degrade to JPEG when the byte budget cannot otherwise be met. Best-effort: the
original image is sent unchanged if compression fails.

* fix(agent-core): serialize prompt/steer RPCs to avoid a turn-claim race

The prompt/steer RPC handlers await image compression before turn.launch()
synchronously claims the active turn, so two overlapping calls could both
compress first — letting the faster-to-compress one win the turn and strand the
other on agent_busy. Run these two RPCs through a per-agent serialization chain
so they claim in submit order; cancel and the other RPCs stay immediate.

* fix: update flake.nix pnpmDeps hash for the jimp dependency

Adding jimp to the workspace changed pnpm-lock.yaml, so the pnpmDeps
fixed-output hash was stale and the nix build failed. Update it to the value
the CI nix build reported.

* fix(agent-core): guard image compression against decompression bombs

A tiny-byte, huge-dimension image (e.g. a solid 30000x30000 PNG) would be fully
decoded into a multi-gigabyte bitmap by Jimp before any resize — an OOM vector
the byte budget never catches. Skip compression when the sniffed pixel count
exceeds MAX_DECODE_PIXELS (~100 MP), before the decode; oversized images pass
through uncompressed as they did before compression existed.

* fix(agent-core): cap decode byte size before compressing images

Compression runs before downstream size caps (e.g. the 10MB MCP per-part
limit), so a huge or invalid base64 image from an MCP tool was Buffer.from-
decoded — and handed to Jimp — just to be dropped afterward. Add a
MAX_DECODE_BYTES ceiling (64MB, overridable) checked before the base64 decode
and before Jimp, the byte-side complement to the pixel-count guard; oversized
payloads pass through uncompressed.

* refactor(agent-core): compress images at ingestion, not on the turn RPC

Move image compression off the prompt/steer RPC path and back to each ingestion
site (CLI paste, server upload resolution, ACP conversion; ReadMediaFile and MCP
already compressed at their producers). Compressing on the RPC control path put
an async step before the synchronous turn-claim, which spawned a series of
races: prompt/steer interleaving, and — with a cancel arriving mid-compression —
an ineffective abort that let a cancelled prompt launch anyway.

Treating compression as a pure input-stage transform (done while the content
part is built, before it ever enters the agent loop) removes those races
structurally: rpc.prompt/steer are plain synchronous handlers again, and the
serialization/cancel-window machinery is gone. Records stay compressed, resume
stays consistent, and coverage degrades gracefully (a new client that skips
compression just sends a larger image, as before this feature).

* fix: compress inline base64 prompts and honor ACP cancels mid-compression

Two contained ingestion-site follow-ups:

- server: resolvePromptMediaFiles now also compresses images submitted as an
  inline `{ kind: 'base64' }` source, not just uploaded files, so the REST
  inline-base64 path gets the same downsampling.
- acp-adapter: AcpSession tracks a pending-abort flag while prompt() awaits
  image compression (before any turn exists). A session/cancel in that window
  flips it, so the prompt returns `cancelled` instead of launching a turn the
  client already stopped.

* fix(acp-adapter): cover all concurrent pre-turn prompts on cancel

The pending-abort marker was a single session field, so with two
`session/prompt` requests compressing large inline images at once the later
one overwrote it and a `session/cancel` could mark only one — the other
launched after the client had cancelled. Track a token per in-flight prompt in
a set and flip them all on cancel so every pre-turn prompt is covered.

* chore(node-sdk): declare jimp as a devDependency

The SDK re-exports the image compressor, whose lazy `import('jimp')` (inside
the bundled agent-core code) is inlined into the published dist. jimp was
resolved only transitively via agent-core, so declare it as an explicit build
input here — matching the CLI — to make the bundling reliable rather than
phantom. It stays a devDependency: jimp is bundled, not a runtime dependency.
2026-07-01 19:36:48 +08:00
Kai
e47ca10267
feat(agent-core): slim WebSearch to query-only; fetch page content via FetchURL (#1260)
* feat(agent-core): slim WebSearch to query-only; fetch page content via FetchURL

The coding search backend no longer honors `limit`/`enable_page_crawling` and
always returns full page content, which was token-heavy and frequently
truncated. Realign the web tools around a search+fetch split:

- WebSearch request sends only `text_query`; the tool exposes only `query`.
- Search results drop inline page content and add the source site; full page
  content is now fetched on demand via FetchURL.
- Add a citation reminder to both WebSearch and FetchURL results (in the
  FetchURL front note so it survives body truncation).
- Update tool descriptions and reference docs accordingly.

* fix(agent-core): satisfy no-base-to-string lint and drop redundant | undefined

- Assert the exact serialized WebSearch request body instead of String()-coercing
  a BodyInit, fixing the type-aware no-base-to-string lint error.
- Drop redundant `| undefined` from WebSearchResult optional fields per AGENTS.md.
- Add changeset.

* chore(changeset): bump agent-core to minor for WebSearch input-contract change

Removing the `limit`/`include_content` tool inputs tightens a closed
(`additionalProperties: false`) schema, so previously-valid args are now
rejected — an incompatible change for a released package. Bump minor rather
than patch.
2026-07-01 18:46:15 +08:00
Kai
86e0c9201e
feat(agent-core): rework compaction to keep only user prompts and summary (#1214)
* feat(agent-core): rework compaction to keep only user prompts and summary

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore(changeset): consolidate compaction changesets into one

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

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

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

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

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

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

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

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

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

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

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

The provider-agnostic projector stays structure-preserving: lenient
providers (OpenAI/Kimi) keep distinct turns for clearer message
boundaries; only strict providers normalize, where the requirement lives.
2026-07-01 01:17:30 +08:00
7Sageer
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
2026-06-29 18:30:09 +08:00
7Sageer
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>
2026-06-29 17:40:58 +08:00
Kai
9c9716125e
feat: Harden the default system prompt and built-in tool descriptions (#1102)
* feat(agent-core): strengthen default system prompt

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Each change is covered by a description assertion.

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

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

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

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

Each change is covered by a description assertion.

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

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

Each change is covered by a description assertion.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix: persist oversized tool results

* fix: link background task notifications to saved output

* fix: avoid lossy tool result budgeting

* fix

* fix: include fallback task output previews

* fix

* fix
2026-06-24 14:42:11 +08:00
7Sageer
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.
2026-06-24 14:05:27 +08:00
7Sageer
b84704bff3
perf(kaos): optimize large file reads (#971) 2026-06-23 15:07:40 +08:00
liruifengv
e7dd13804d
feat(tui): detach foreground tasks to background with Ctrl+B (#976)
* feat(tui): detach foreground tasks to background with Ctrl+B

- Add Ctrl+B shortcut to detach all foreground Bash/subagent tasks at once

- Show "Press Ctrl+B to run in background" hint in tool cards (Agent immediately, Bash after 10s) and in the agent group panel

- Mark detached foreground subagents as ◐ backgrounded instead of ✓ Completed

- Filter foreground tasks out of the /tasks panel (they appear after detach)

- Steer the model away from blocking on TaskOutput after a detach

* fix(tui): address Codex review on Ctrl+B detach

- Preserve `◐ backgrounded` for detached subagents inside AgentGroupComponent by reusing getDerivedSubagentPhase in getSubagentSnapshot

- Distinguish detached-from-foreground subagents from started-in-background ones so the latter still read as `done`

- Let Ctrl+B fall through to readline backward-char at the idle prompt instead of always consuming the key

* fix(tui): address follow-up Codex review on detach hints

- Auto-clear the "No foreground task running." hint via showDetachHint so it doesn't stick on the footer

- Don't clobber a newer transient hint (e.g. exit confirmation) when the detach hint timer fires

- Add FooterComponent.getTransientHint()

* fix(tui): address Codex review on backgrounded gating and /tasks counts

- Only mark foreground-running subagent cards as backgrounded (skip done/backgrounded cards so background resumes don't mutate older rows)

- Count /tasks header from the filtered (background-only) task set so foreground-only sessions don't read misleading counts
2026-06-22 20:59:24 +08:00
liruifengv
c0eeca2469
feat: add workspace add-dir support (#812)
* feat: add workspace add-dir support

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

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

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

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

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

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

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

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

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

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

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

Remove the now-unused appendUserMessage RPC and SDK method.

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

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

* feat: reopen file mention completion after accepting a directory

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

* feat: show inline argument hints for slash commands

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

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

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

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

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

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

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

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

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

* chore: clarify add-dir changeset for users

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

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

FileSystemAgentRecordPersistence.append buffers records and flushes asynchronously, so readMainWire can read the wire before the local-command-stdout record lands. Flush the main agent's records explicitly in the two add-dir runtime tests to make them deterministic.
2026-06-22 19:42:13 +08:00
7Sageer
d434d8f0d8
refactor(agent-core): unify image extension sniff-failed detection (#974)
Merge the two duplicate image-extension guards in detectFileType into a single mode-independent rule: an image extension without confirming magic is not an image in any mode. The video extension fallback stays before the NUL check so video containers with no magic still win. No behavior change.

Add and align tests: Read rejects an image-extension file with non-image bytes as not readable instead of redirecting to ReadMediaFile; file-type asserts the sniff-failed image case in both media and text modes.
2026-06-22 19:31:48 +08:00
7Sageer
27300797f2
fix(agent-core): reject image extensions whose bytes fail to sniff (#970)
When ReadMediaFile reads a file whose bytes have no recognisable image magic (for example a `.png` that is actually plain text), detectFileType previously fell back to the extension MIME type and built a data URL whose bytes did not match the declared format, which the model API rejected.

Every image format the model accepts (PNG/JPEG/GIF/WebP) has a reliable magic signature, so a failed sniff means the bytes are not a supported image. Report such files as `unknown` so ReadMediaFile returns a clear error instead of a bad data URL. The extension fallback is kept for video containers with no magic signature (for example MPEG-PS `.mpg`); format acceptance beyond that is left to the provider.
2026-06-22 19:07:34 +08:00
_Kerman
ba64072559
feat: detach foreground tasks to background (#821)
Some checks failed
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / typecheck (push) Has been cancelled
Nix Build / Check flake.nix workspace sync (push) Has been cancelled
Release / Release (push) Has been cancelled
Release / Native release artifact (push) Has been cancelled
Nix Build / nix build .#kimi-code (push) Has been cancelled
Release / Deploy docs (push) Has been cancelled
Release / Publish native release assets (push) Has been cancelled
2026-06-20 21:24:22 +08:00
Luyu Cheng
cde7ca51cc
feat(goal): support guided goal authoring (#839) 2026-06-18 18:48:23 +08:00
7Sageer
66b4d658a0
fix: dispose process stdio after managed commands (#822)
* fix: dispose process stdio after managed commands

* refactor: share stream close detection
2026-06-16 22:09:11 +08:00
Luyu Cheng
4578f05f44
fix: surface skill directory in the loaded-skill context block (#785) 2026-06-15 22:29:00 +08:00
_Kerman
a562ef54e5
refactor: decouple agent skill registry (#784) 2026-06-15 20:39:29 +08:00
Kai
4516f62f6a
feat(agent-core): refine system prompt context (#777)
* feat(agent-core): collapse hidden dirs in prompt

Keep hidden directory entries visible in the cwd snapshot while omitting their contents to reduce prompt noise, and document the tools models should use to inspect hidden paths.

* feat(agent-core): clarify prompt AGENTS context

Add a marker when AGENTS.md content is truncated by the prompt budget and move dynamic system prompt context after the static guidance.

* chore: add prompt refinement changesets
2026-06-15 18:38:42 +08:00
_Kerman
046856b740
fix: prefer media headers when reading media files (#765) 2026-06-15 12:09:05 +08:00
_Kerman
dff9fd4e32
chore: use raw query imports for prompt sources (#682) 2026-06-12 11:47:44 +08:00
_Kerman
dcf30754d0
feat: stream shell tool output (#676) 2026-06-12 11:10:20 +08:00
_Kerman
ff80327344
fix: propagate kaos env overlays (#654) 2026-06-11 17:21:32 +08:00
_Kerman
4e5043b03b
fix: require AgentSwarm to run alone (#643)
Some checks are pending
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
2026-06-11 13:58:35 +08:00
Haozhe
7ec738c4a1
fix(agent-core): suppress premature stream close on shell timeout or kill (#604)
- Add terminating-state guard to skip ERR_STREAM_PREMATURE_CLOSE when a process is killed or times out
- Ensure timeout/kill reason is reported instead of the internal stream error
- Add changeset for agent-core and kimi-code
2026-06-10 20:02:47 +08:00