Commit graph

381 commits

Author SHA1 Message Date
Liu Zhongnuo
ea0626ad48
fix(agent-core): gate background questions on task controls (#3159)
* fix(agent-core): gate background questions on task controls

* fix(agent-core): recheck background question policy
2026-08-23 20:40:23 +08:00
7Sageer
d723cc47ee
feat(agent-core-v2): add the unified MCP management plane (#3002)
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-vscode-legacy (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
* feat(agent-core-v2): add the unified MCP management plane

Port the v1 MCP management plane (#2858) onto the v2 DI x Scope engine:

- App-scope IMcpOAuthService shared by every workspace handler and
  session overlay: credential events, single-flight refresh, proactive
  refresh timers, OAuthTokenTransaction-serialized writes, offline
  tokenState, shutdown. Providers read tokens through the store so
  grants written or revoked by another process are honored immediately;
  http/sse transports ride the transaction fetch.
- IMcpConfigStore: the single write point for the user-level mcp.json
  over the filesystem byte store, byte-identical to v1's format, with
  per-entry validation, name normalization, __proto__-safe parsing, a
  mutation tail, and an onDidWrite event.
- IMcpRegistryService: the unified read view over the layered config
  files (with per-entry origins) and plugin manifests (full descriptors
  incl. disabled, with provenance); collisions stay visible and runtime
  resolution ranks an enabled plugin above the file layers.
- IMcpManagementService: guarded CRUD, connection-test probes, the
  locator-addressed inspection/auth-status surface, and
  locator-addressed OAuth begin/complete/cancel/reset with ambiguity
  rejection. Engine services stay ungated; the mcp_management flag
  gates the edge exposure.
- Workspace runtime aligns with v1 precedence (an enabled plugin entry
  wins over the file layers, shadows revive), and management writes
  reload immediately via onDidWrite instead of the watch debounce.
- node-sdk v2 facade delegates to the engine service (deleting its
  in-process duplication); kap-server exposes /api/v2/mcp/* and klient
  gains global.mcp.*, both flag-gated.

* fix(agent-core-v2): settle early and cancelled MCP OAuth callbacks

* refactor(node-sdk): write session MCP persists through the engine config store

* refactor(agent-core-v2): strip comments from the MCP management plane files

* fix(agent-core-v2): harden MCP management readiness

* test(node-sdk): cover offline MCP auth statuses

* fix(agent-core-v2): isolate stdio MCP probes

* fix(klient): normalize MCP OAuth errors

* fix(mcp): honor workspace CRUD context and refresh timing

* fix(mcp): drain OAuth refreshes during shutdown

* fix(mcp): guard CRUD across registry collisions

* fix(mcp): canonicalize trust and refresh scheduling

* fix(mcp): close callback listener on setup failure

* fix(mcp): preserve trust and oauth behavior

* fix(oauth): retain refresh tokens after SDK saves

* fix(oauth): stop proactive sweep during shutdown

* fix: await MCP workspace reconciliation

* fix: serialize MCP OAuth and trust cleanup

* fix: reject persisted MCP plugin collisions

* fix: reconcile MCP workspaces concurrently

* fix(mcp): check project-layer trust at the queried cwd

* fix(mcp): expire abandoned OAuth flows after an idle timeout

* fix(mcp): keep mutable user entries writable past read-only collisions

* fix(mcp): abort the auth::complete long poll on client disconnect

* fix(mcp): map OAuth flow failures to wire code 40929

* docs(mcp): note probe credential effects and plane semantics

* chore: add the SDK changeset for MCP management cwd params

* feat(mcp): expose the management plane without the experimental flag

* fix(mcp): preserve auth management semantics

* fix(agent-core-v2): bound MCP OAuth auth-server requests and the shutdown drain

* fix(node-sdk): restate engine MCP management errors as KimiError

* fix(agent-core-v2): preserve shared OAuth flow lifetime

* fix(agent-core-v2): close MCP OAuth cancellation and shutdown gaps

- bound the authorization-code exchange with the request timeout and the
  flow/caller abort signals, and make shutdown abort hung begins and close
  their callback listeners immediately
- keep token-transaction effect coalescing intact when durable tokens carry
  local stamps, and serialize the meta sidecar and tokens-saved event with
  the token write inside the lock
- drain transport-driven grants, their trailing SDK save continuations, and
  interactive completions during shutdown, with a cancellable deadline

* fix(agent-core-v2): harden MCP probe runtime resolution and path handling

- resolve stdio probes against the containing workspace's runtimes and
  reject out-of-workspace probes for non-local runtime_id instead of
  silently falling back to a local-only transient registry
- share one Windows-aware path canonicalization across the config loader,
  registry trust lookup, trust records, and workspace matching
- keep a UTF-8 BOM fatal for the user-level mcp.json store, matching the
  workspace loader and v1
- validate completeServerAuth timeoutMs bounds at the engine boundary

* fix(agent-core-v2): await workspace MCP reconciliation on plugin mutations

Plugin install/enable/disable/remove now resolve only after reload
listeners settle their waitUntil work, so a disabled plugin's MCP server
cannot linger connected and an enabled one is visible to the next
session, matching v1. The workspace MCP consumer joins the barrier while
keeping its log-only failure tolerance; delivery is awaited outside the
mutation queue to avoid self-deadlock through consumption reads.

* fix(mcp): close the SDK, klient, and server edge gaps

- register mcp.oauth_failed in the v1 error registry and restate unknown
  engine codes as internal instead of minting undeclared KimiError codes
- route persisted session MCP adds through the same KimiError restating
  as the global management methods
- give the klient IPC transport a per-call timeout so completeAuth's long
  poll outlives the 30s default, clamped to the Node timer ceiling, and
  align the contract timeoutMs upper bound with REST
- await the MCP OAuth service shutdown directly in SDK and server close
  before scope disposal

* fix(agent-core-v2): keep file-over-plugin MCP precedence and harden the plane

- Revert the v1-style precedence flip: the workspace merge and
  resolveRuntimeTarget keep the file entry above plugins (v2's historical
  order; the divergence from v1 is deliberate and documented in AGENTS.md).
- Guards follow each engine's winner: project-layer entries stay read-only,
  while plugin entries never block user-level writes, so a file entry may
  shadow a plugin and removing it revives the plugin. The parity suite pins
  the engine split for a persisted session add over a plugin-owned name.
- inspectServers tolerates a wire-encoded null targets array: klient's ipc
  transport sends null for an omitted leading optional argument.
- Fire the config store's onDidWrite after the mutation tail settles, so a
  write listener can re-enter the store without deadlocking the queue;
  concurrent-mutation and re-entrant-listener tests pin both contracts.

* chore: condense the sdk MCP changeset to one sentence

* test(node-sdk): pin verify:false auth-status parity and fix the sdk changeset
2026-08-22 18:22:37 +08:00
wszqkzqk
30e7f62d2c
fix(kaos): resolve Git Bash POSIX paths for file tools on Windows (#2200)
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-vscode-legacy (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
Add a shell path bridge that translates between win32 paths and the
MSYS2/Git Bash path dialect. File tools resolve model-supplied paths
through it before canonicalization and workspace checks: drive-letter
forms translate lexically, root-relative paths resolve via cygpath -w
with per-segment caching, and every failure mode falls back to the
previous behavior.

Fixes #2199
2026-08-20 22:42:16 +08:00
Haozhe
3fdce983f8
fix: drain in-flight persistence and log writes on session close and shutdown (#3122) 2026-08-20 17:19:20 +08:00
Kimi Agent
d96b4a0149
fix: fail fast on provider-filtered empty responses (#3101)
* fix: fail fast on provider-filtered empty responses

An APIEmptyResponseError carrying finishReason 'filtered' (OpenAI
content_filter, Anthropic refusal) is deterministic: replaying the same
request re-triggers the provider safety filter. Both isRetryableGenerateError
implementations (kosong, agent-core-v2) treated every empty response as
retryable, so step retry replayed the doomed request the full 10 attempts
before the filter notice surfaced. Return non-retryable for filtered empty
responses in both engines; the error already carries the provider.filtered
code, so the turn fails immediately with the existing filter notice.

* fix: skip the compaction shrink-retry for filtered empty responses

Both full-compaction loops routed every APIEmptyResponseError into the
shrink-and-continue branch before isRetryableGenerateError was consulted,
so a filtered response was retried with shrinking input instead of failing
fast. Exclude finishReason 'filtered' from the shrink branch in both
engines; it now falls through to the retryability check and throws
immediately. Add end-to-end tests (real kosong generate over a filtered
think-only stream) asserting a single attempt with the history untouched.

---------

Co-authored-by: kimi-agent-bot <kimi-agent-bot@users.noreply.github.com>
2026-08-20 11:55:26 +08:00
liruifengv
3d7762003a
feat(kimi-code): support two OAuth login endpoints (#2862)
* feat(kimi-code): add China/International region selection for OAuth login

- Add region profiles (cn/overseas) and resolver in @moonshot-ai/kimi-code-oauth:
  env override → persisted login host → install-channel marker → default cn
- /login now offers Kimi Code (China) / Kimi Code (International); the CLI
  login entries (kimi login, kimi acp --login) accept --region cn|overseas
- Update/plugin/site/telemetry endpoints derive from the selected region;
  plugin trust list covers both .com and .ai hosts
- kap-server: POST /oauth/login accepts an optional region; new GET /oauth/region

* fix(oauth): keep an explicit default-slot login ahead of the install marker

A China login persists no oauthHost (the default credential slot carries
no host trace), so after switching back from International the resolver
fell through to a stale overseas install marker. Treat a persisted
default-slot oauth ref (key === oauth/kimi-code) as an explicit-cn signal
that outranks the marker; getRegion() on the v2 side mirrors it.

* fix(agent-core-v2): thread the default-slot key through capability region resolution

Capability installs resolved the region from the persisted oauthHost only,
so an explicit China login (which persists no host) lost to a stale
overseas install marker. Pass the oauth ref key through as well, matching
getRegion(). Also move the region contract notes into the auth.ts file
header per the package comment convention.

* fix(agent-core-v2): honor the region-marker opt-out for the telemetry endpoint

Hosts that set KIMI_CODE_REGION_MARKER=off (the desktop embedded server)
skip the install marker in getRegion(), but the default telemetry endpoint
still consulted it, so a stale overseas marker could split the reported
region from the telemetry destination.

* feat(cli): show region site domains in login platform selector

* chore: reword oauth login changesets

* fix: honor the region marker opt-out in the CLI and capability resolvers

* refactor: rename login region values to mainland-cn and global

* fix: keep the --region help text in English

* fix: simplify the --region help text to site domains

* feat: drop the suggested login platform order

* feat: split a browser-safe region profile table out of the region resolver

* Revert "feat: split a browser-safe region profile table out of the region resolver"

This reverts commit a037b1143e.

* fix: read the install marker from the bootstrapped home directory

* fix: resolve the server plugin marketplace from the active login region

* feat: expose the login region option through the klient auth facade

* fix: drop a comment from the v2 auth region test

* fix: keep scoped base-only logins on their environment for a bare login

* fix: invalidate the region cache on the provider-manager logout path

* fix: route client-config fetches through the active region profile

* fix: resolve the telemetry endpoint per flush so a login region switch applies in-process

* test: expect the telemetry endpoint resolver in the CLI init assertions

* fix: resolve the default telemetry endpoint from the bootstrapped home

* chore: reword the oauth login changeset around the two login methods

* chore: trim the oauth login changeset to the headline

* feat: let hosts override the region marker env through the server bootstrap env bag
2026-08-20 11:24:02 +08:00
7Sageer
86674ac895
test: remove wording-pinning tests of model-facing prose across both suites (#3031)
* test: replace prose-pinning system-prompt tests with a structural sharing check

The two removed tests pinned exact sentences of the default system prompt
('reversibility and blast radius', 'premature abstraction', optional-tool
phrasings that must not appear, ...). They broke on any intentional
wording change while only catching regressions that reused the same words.

The one real contract underneath — shared, ungated sections must render
byte-identically in the root agent and every subagent profile — is now
checked structurally by slicing the section out of the root prompt and
asserting the other profiles contain it, regardless of its wording.

* test: remove wording-pinning tests of model-facing prose across both suites

Sweep of the class identified in #3030: assertions pinning the exact
English wording of product model-facing text (system prompt, reminder
and injection .md files, tool descriptions, shipped profile/skill
bodies). They break on any intentional rewording yet only catch
regressions that reuse the same words.

Across 35 files (~60 test cases, net -1131 lines):
- deleted dedicated wording tests: 'exposes current metadata and
  schema' description pins, goal/plan/todo reminder content tests,
  tower skill-body prose pins, goal-outcome.test.ts;
- trimmed wording assertions from behavioral tests that otherwise
  stand alone; kept identifiers (tool names, XML tags, section
  markers), structural properties (wrapping/escaping/gating/cadence),
  fixture data, tool outputs and error messages;
- re-anchored a few gating tests on exported constants
  (WINDOWS_PATH_HINT, DEFAULT_REPLY_STYLE_GUIDE) instead of prose
  literals.

Deferred for a follow-up decision: ~15 tests whose prose pin is the
only discriminator of which reminder/budget-band fired (constants not
exported). Wire baselines and snapshot machinery untouched.
2026-08-18 16:36:57 +08:00
7Sageer
5c8df5973e
test: drop the assertion for the removed ambiguous-means-task example (#3030)
#3028 removed the 'treat ambiguous requests as tasks' rule and its
'locate the method in the code' example from the default system prompt.
The profile test pinned that example verbatim, so it now fails on main.
The removal was intentional; update the test to the new contract.
2026-08-18 15:02:17 +08:00
7Sageer
40e1784089
fix: tone down over-proactiveness in the default system prompt (#3028)
* fix: tone down over-proactiveness in the default system prompt

The default system prompt pushed the agent to act before discussing:
ambiguous requests were explicitly resolved to tasks, the opening framed
the primary goal as taking action, and 'default to making progress, not
to asking' discouraged clarifying questions.

Trim both copies (agent-core and agent-core-v2) by deletion only:
the ambiguous-means-task rule and its example, the action-framed opening
clause, the 'default to taking action with tools' paragraph, the
duplicated must-use-tools sentence (kept once in Ultimate Reminders),
and the 'default to making progress, not to asking' bullet. Operational
guidance and the execution guards stay untouched.

* Delete .changeset/tame-system-prompt-proactiveness.md

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

* fix: drop the tool-use and no-placeholder bullets from the default system prompt

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-08-18 14:47:52 +08:00
bj456736
d3150fe947
chore: remove internal-network references from comments and test fixtures (#3029)
* chore: remove internal-network references from comments and test fixtures

- Reword two comments that named the internal free-tokens model
  registration flow; the generic OAuth / managed wording carries the
  same meaning
- Replace the qianxun.example placeholder base URL in google-genai and
  runtime-provider tests with genai-gateway.example
- Swap realistic-looking LAN fixture IPs in the kimi web banner tests
  (192.168.98.66, 10.8.12.216) for RFC 5737 documentation addresses
  (192.0.2.66, 198.51.100.216)

* chore: retrigger CI (flaky kap-server searchRoute title-indexing test)

---------

Co-authored-by: bj456736 <bj456736@users.noreply.github.com>
2026-08-18 14:14:56 +08:00
7Sageer
59dde734f3
feat(agent-core): unify the v1 MCP management plane (#2858)
* feat(agent-core): unify the v1 MCP management plane

- McpServerRegistry: one config view over global (layered mcp.json),
  plugin (manifests, read-only, final effective config), and caller
  (SDK-injected) servers; name collisions keep both entries.
- Write plane: add/update/removeGlobalMcpServer mutate the user-level
  file and push into live sessions; getGlobalMcpServer returns the
  effective config; mutations of read-only entries are rejected.
- testGlobalMcpServer accepts an inline config; addSessionMcpServer
  connects a server in one live session with an optional persist flag;
  reconnect accepts a replacement config and re-resolves via the registry.
- One process-wide McpOAuthService shared with every session: obtained_at
  stamps, offline token state, single-flight and proactive refresh, and
  credential events. Sessions self-subscribe in the constructor, so even
  initializing sessions see every event; token writes serialize through
  the process-local OAuthTokenTransaction per credential identity.
- inspectAppMcpServers + locator-addressed begin/complete/cancel/reset
  cover plugin servers; inspection output redacts env/headers to sorted
  key lists; locator OAuth ops reject ambiguous shared runtime names.
- The legacy auth-status surface reads the registry (offline by default,
  verify=true probes) and never mutates credentials.
- VS Code panel receives source/origin/mutable and hides mutating
  actions on read-only entries.
- v2 client facade in node-sdk mirrors the surface over agent-core-v2
  (plugin inventory stays v1-only for now).

* fix(agent-core): close the v1 MCP live-session reconciliation gaps

Recompute each live session's MCP target from the registry's runtime
resolution (enabled plugin > project layer > user file; caller injection
shadows everything) behind every config mutation, instead of per-path
patching: shadowed file layers recover when a plugin winner is disabled or
removed, removing a user-level entry resurrects its project-layer shadow,
disabled plugin descriptors no longer block removals, persisted session
adds validate against the session's project layer and broadcast to other
live sessions, and per-session sync failures are logged with context.

Session status entries and read-only management entries now report
redacted config views (envKeys/headerKeys instead of literal env/headers
values); core-internal reconciliation compares full configs via the
connection manager's raw-entry accessor.

OAuth: interactive flows are serialized per credential (concurrent
begins join the in-flight flow instead of clobbering its PKCE/state), a
malformed credential meta sidecar no longer aborts core start, grants
inside the refresh-ahead window refresh immediately while far-future
grants re-arm through a max-length timer, and the service shuts its
timers and flows down with KimiCore/SDKRpcClient close.

* fix(agent-core): route the proactive MCP OAuth refresh through the token transaction

 refreshNow ran its /token request with the SDK default fetch, outside the
 credential-serializing OAuthTokenTransaction that every other token write
 uses; a slower response carrying an older rotating refresh token could
 overwrite a newer grant written by a concurrent transport-side refresh.

* fix(agent-core): keep disabled MCP servers out of auth-state classification

The unified mcpServerAuthState dropped the previous enabled short-circuit,
so a disabled oauth-flagged server reported oauth-required — or was even
probed over the network — instead of not-applicable.

* fix(kimi-code-sdk): short-circuit disabled MCP servers in the v2 auth-status classifier

The v2 parity copy of v1's mcpServerAuthState missed the same enabled
guard v1 just regained; a disabled oauth-flagged entry would report
oauth-required (or be probed). The parity suite now pins the disabled
case on both engines.

* fix(kimi-code): refresh the VS Code MCP list with the workspace cwd after mutations

The add/update/remove RPCs return a cwd-less management list, so the
webview broadcast dropped project-layer entries until the next full load;
re-list with the workspace cwd after every mutation instead.

* fix(agent-core): keep SDK token saves matched to the OAuth token transaction

saveTokens stamped obtained_at onto a fresh object before calling
tokenTransaction.save, so it never matched the exact payload the
transaction recorded for a grant fetch; the consume path was dead and
every save re-wrote. Between the fetch and the SDK callback an intervening
clear could then be overwritten — the resurrected grant came back after a
reset. The write callback stamps the durable record instead.

* fix(agent-core): reject ambiguous legacy name-based MCP auth lookups

The legacy begin/reset auth RPCs took the registry's first name match,
silently starting OAuth for one entry of a runtime-name collision while
the locator path refused the same ambiguity; align them on the shared
uniqueness rule and point callers at the locator-addressed variants.

* fix(agent-core): propagate registry errors during live-session MCP sync

resolveMcpRuntimeTarget collapsed every registry failure into "no target":
a project config file that turned malformed mid-session made sync treat a
still-configured server as gone (tearing down the live connection) and
made config-aware reconnects report "no longer configured" instead of the
actionable config error. Absence still resolves to undefined; malformed
config now propagates — per-session sync logs and keeps the entry, and
reconnect surfaces config.invalid.

* fix(agent-core): close the remaining registry-error and ambiguity gaps

The management guard lookup mapped every registry failure to "absent",
so a malformed project config let a persisted session add write a
user-level entry over an unknown state; only not-found is a miss now. And
the name-only connection test now shares the auth paths' uniqueness rule
instead of probing the first match of a runtime-name collision.

* fix(agent-core): probe the enabled MCP entry under a disabled-name collision

The name-only connection test counted enabled matches for its ambiguity
guard but still probed the first registry match, and the file layers list
before plugins. With a disabled file entry shadowing an enabled plugin of
the same runtime name, Test probed the disabled entry instead of the one a
live session would run. Select the sole enabled match, falling back to the
first entry only when every match is disabled so it reports as disabled.

* fix(agent-core): let session-local MCP adds shadow plugin entries

Caller injection shadows every registry source at session start, plugins
included, and reconciliation leaves caller entries untouched; the live
non-persist add path rejected plugin-owned names anyway, so SDK clients
could not apply the same per-session override without a restart. Gate the
plugin-source rejection on persist: session-local adds connect as caller,
while persisted adds stay rejected as user-level writes behind a read-only
owner.

* fix(agent-core): normalize session MCP names before connecting

The persisted store trims server names, but addSessionMcpServer used the
raw name for the live connect and cross-session reconciliation: a padded
name persisted under the trimmed key while the requesting session ran and
reconciled the raw one, and a blank name connected with no identity at
all. Normalize once up front (rejecting blank) so the store write, the
session entry, and reconciliation agree on the same server.

* fix(agent-core,node-sdk): close the collision-selection and probe-freshness gaps

The legacy name-only auth resolver started from the first registry match,
so a disabled file-layer shadow plus an enabled plugin of the same runtime
name was misread as an ambiguity conflict; select the sole enabled match
before judging ambiguity, exactly like the test probe path. On the v2
client, addSessionMcpServer connected the raw name while the store wrote
the trimmed key — normalize once for both, and route the verify-triggered
auth probes through the per-call OAuth service instead of the cached one
whose providers snapshot tokens at construction, so a grant saved after
the first probe is honored.

* fix(agent-core): normalize global MCP mutation names and guard disabled reconnect swaps

The global add/update/remove mutations guarded and reconciled with the raw
server name while the store persisted the trimmed key, so a padded name
left live sessions unreconciled and could slip past the plugin read-only
guard; normalize once before lookup, persistence, and reconciliation. And
a config-carrying reconnect assigned the replacement before the disabled
check fired, leaving a connected entry that reported the disabled config;
reject disabled replacements before mutating, keeping the same error.

* fix(agent-core): skip proactive refresh while an interactive flow owns the credential

refreshNow reset the shared provider's flow state before and after the
token request; when a proactive timer (or a manual refresh) fired while
beginAuthorization was waiting on the browser callback for the same store
key, that wiped the redirect URL, PKCE verifier, and state the in-flight
flow needed — complete() then failed the exchange even though the user
authorized. Refresh now skips when an interactive flow is active for the
credential: the flow delivers fresh tokens on completion, and the 401
transport path is the backstop if it fails.

* fix(agent-core): allow global MCP adds over disabled plugin descriptors

A disabled plugin entry is absent from the runtime target, but the
read-only guard still treated it as the owner, so a user-level fallback
could only exist if it predated the plugin disable. Relax the shared
guard: disabled plugin descriptors never block mutations (disabled
project entries still shadow the user file and keep their rejection).

* fix(node-sdk): close the v2 session-MCP parity gaps

A v2 reconnect with an explicit enabled:false replacement config used
connect()'s upsert semantics — closing the live client and reporting
success where v1's manager reconnect rejects before applying anything;
reject disabled replacements up front with the same error. And a persisted
v2 session add never consulted the workspace config, so a same-named
project-layer entry was silently shadowed: the user-level write never
takes effect while the direct workspace-manager upsert displaces the
project config for every live session. Resolve the workspace layers and
reject like v1's read-only rule.

* fix(agent-core): keep __proto__-named MCP servers through config parsing

A z.record() parse rebuilds its output via property assignment, so a
server literally named __proto__ hit the prototype setter and vanished
before validation; the layer merge then repeated the same trap with plain
object accumulators. Parse the server map entry-by-entry over the JSON own
keys and accumulate into null-prototype maps, so session startup and the
unified registry keep the declared server and its origin.

* fix(node-sdk): begin v2 MCP auth against a fresh OAuth service

The v2 begin path ran through the cached globalMcpOAuth, whose providers
snapshot tokens at construction: a grant another process saved (or reset)
after that cache materialized was invisible, so begin could open a browser
flow over a valid grant, or report already-authorized off a removed one.
Build the service per call — the read path and the verify probes already
do — and route the status list through the same helper. The test fixture
grows a real token endpoint honoring one rotating refresh token; the
regression fails against the cached-service implementation on v2.

* fix(agent-core): broadcast SDK-driven MCP token invalidations to live sessions

* test(agent-core-v2): give the no-op reconnect test runtime plumbing

The branch added the case against a bare McpConnectionManager, but #2961
made stdio connects resolve the runtime through runtimeResolver, matching
every other case in the file.
2026-08-17 13:19:51 +08:00
Luyu Cheng
d96cd03770
fix(cli): pre-send warning and clearer error for over-long /goal objectives (#2928)
Some checks are pending
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-vscode-legacy (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (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(cli): warn on over-long /goal objectives before sending and keep the input

- Show a live footer warning in the TUI while a typed /goal objective
  exceeds the 4000-character limit, measuring paste-expanded text only
  when the input can be a /goal command.
- Restore the rejected /goal input into the editor instead of losing it.
- Include the file-reference workaround in the GOAL_OBJECTIVE_TOO_LONG
  error messages (TUI, goal queue, agent-core, agent-core-v2).

* fix(cli): keep the /goal length warning in its own footer slot

A transient hint (exit confirm, detach, image paste) that displaced the
warning previously left the footer blank after clearing, because no
editor change re-applied it. The footer now renders the warning from a
dedicated slot whenever no transient hint is active, so the warning
returns on its own.

* fix(cli): gate the /goal length warning on trimmed text

Submitted text is trimmed before slash-command dispatch, so leading
whitespace still runs /goal — normalize with trimStart in the gate and
in the length check to match.

* fix(cli): restore input rejected by the slash-command busy gate

An idle-only command submitted while streaming/compacting was rejected
after the editor buffer had already been cleared, losing hand-typed
input (e.g. an over-long /goal objective that never reached the local
validation).

* fix(cli): restore input at the post-creation busy re-check

The lazy-session race rejects an idle-only command after a first prompt
has already started a turn; the editor buffer is long cleared by then,
so give the submitted input back like the dispatch blocked branch does.

* fix(cli): close the remaining input-loss and gate gaps around /goal

- Restore the submitted input when lazy session creation fails before a
  session-requiring command runs.
- Restore only into a still-empty editor after async gates, so a draft
  typed while creation was pending is never overwritten.
- Expand pastes that can complete a partially typed /goal command
  (e.g. /go[paste #1 …]) in the length-warning gate.

* fix(cli): never displace newer UI state with a delayed input restore

A session-less /goal submission restores its input only after an async
gap (lazy session creation). If the user opened an editor-replacement
panel meanwhile, restoring would tear it down (and leave activeDialog
inconsistent). Track editorReplacementMounted in TUIState and gate all
delayed restores through canRestoreSubmittedInput.

* refactor(cli): move canRestoreSubmittedInput into commands/resolve

Avoids the goal.ts <-> dispatch.ts runtime import cycle flagged by
import/no-cycle; the helper takes a structural host shape instead.

* fix(cli): match the slash parser's delimiter in the /goal length warning

parseSlashInput splits the command name at a literal space only, so a
newline or tab after /goal dispatches as a plain message — the warning
must not fire for inputs the dispatcher will not treat as a goal.
2026-08-15 02:55:33 +08:00
github-actions[bot]
13d86f8b7b
ci: release packages (#2881)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-14 20:51:17 +08:00
oocz
102984aa66
fix: settle cancelled MCP OAuth callbacks (#2899)
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 / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Co-authored-by: yuchengzhen <yuchengzhen@moonshot.cn>
2026-08-13 22:56:06 +08:00
7Sageer
c9bfe8b2c8
feat: replace the secondary-model experiment with a declarative subagent model pool (#2700)
* feat: replace secondary-model experiment with [subagent.models] pool

Add a declarative subagent model pool to agent-core-v2: [subagent.models]
maps [models] entry ids to selection hints rendered in the Agent/AgentSwarm
tool descriptions, and [subagent].default_model picks the spawn model when
the caller passes none. The tools' model parameter becomes a free-form
alias string (stripped when no pool is configured), description rendering
is caller-aware (primary (alias) [main model]), and a session-start
validation service fails fast with CONFIG_INVALID on a missing/invalid
default_model or an unresolvable pool alias.

Remove the secondary-model experiment from the v2 engine, node-sdk,
kap-server, and the TUI (the /secondary_model command), and drop the
agent-profile modelPreference / model_preference frontmatter field on v2.
The legacy v1 engine keeps the experiment unchanged; v2 ignores leftover
[secondary_model] config silently.

* fix(agent-core-v2): harden subagent model-pool validation and error/picker mapping

Deep-review follow-ups to the [subagent.models] pool:

- validate the pool before session materialization (after config.ready)
  and before the fork file copy, so a broken pool no longer leaves
  orphaned session dirs or leaked MCP overlay connections; the
  Session-scope validation service stays as a backstop
- reject the reserved "primary" pool alias at startup, and again
  defensively in resolveSubagentBinding so a pool broken by a runtime
  config edit fails loudly at spawn instead of binding the wrong model
- keep the [default] marker when the caller's own model is the pool
  default (primary (alias) [main model] [default])
- recompile the cached tool-args validator when a tool advertises a new
  schema object (mid-session pool edits no longer hit a stale validator)
- map config.invalid to VALIDATION_FAILED in kap-server's session routes,
  the debug transport mapper, and the catch-all error handler
- hide the v1-synthesized __secondary__ entry from the /model and
  /provider pickers again
- fold per-export doc blocks into file headers per package comment
  conventions; add pre-flight/reserved-key/validator/mapping tests and
  document that create/resume/fork all fail on a broken pool

* feat: re-add /secondary_model and accept a lone subagent default_model

- v2 engine: a pool-less [subagent] default_model forms an implicit
  single-entry pool — validated at session create/resume/fork like an
  explicit pool, and advertised through the Agent/AgentSwarm model
  parameter.
- Tool descriptions: the caller's own alias is a normal pool entry
  marked [main model]; the primary line stays distinct because only it
  inherits the caller's thinking level.
- TUI: /secondary_model returns, persisting [subagent] default_model
  (merging into an existing pool with an empty description); the picker
  hides the no-op Thinking footer and rejects the reserved primary
  alias.
- kap-server: /api/v1/config accepts and echoes subagent; the
  snake-to-camel patch conversion preserves user-defined map keys under
  providers/models/experimental/raw without leaking preserve mode into
  a colliding alias's own fields.
- v1 config schema learns subagent.defaultModel/models so the shared
  config.toml round-trips; the v1 engine still ignores them at runtime.
- Docs (en/zh) and changesets updated.

* docs: use public model identifiers in the subagent model pool examples

* refactor: rename /secondary_model to /secondary-model

* test: cover the /secondary-model command name resolution

* Revert "test: cover the /secondary-model command name resolution"

This reverts commit 98a4a6d999.

* feat(agent-core-v2): move the subagent model pool to [secondary_model]

The pool keys (default_model, [secondary_model.models]) now live in their
own [secondary_model] config section instead of [subagent], which keeps
only timeout_ms; legacy [subagent] pool keys are ignored with a
deprecation warning. The SDK config contract carries the pool on the
secondaryModel field, so the TUI /secondary-model command (now also
aliased /subagent-model) and the kap-server /config wire read and write
it directly with no translation layer.

* docs: correct default engine guidance

* feat(agent-core-v2): pin subagents to default_model with [secondary_model] force

force = true removes the main agent's per-spawn model choice: the Agent
and AgentSwarm tools stop advertising the model parameter and every spawn
binds default_model; an explicit choice, "primary" included, is rejected.
The setting requires default_model, rejects a [secondary_model.models]
table, and is validated loudly at session create/resume/fork (lifecycle
preflight plus the Session-scope backstop). The v1 engine declares the
key for write round-trips and excludes it from the recipe patch.

Also documents pool entries as per-alias thinking-level variants via
default_effort overrides.

* docs: use real managed model aliases in the secondary_model examples

The pool examples invented aliases (kimi-hs, fable, codex) and referenced
non-existent model IDs (model = "codex"); they now reference only the
managed aliases provisioned by /login (kimi-code/k3,
kimi-code/kimi-for-coding, kimi-code/kimi-for-coding-highspeed), with the
effort variant derived as kimi-for-coding-highspeed-deep. Also replaces the
versioned kimi-k2.5 alias with kimi-for-coding per the docs model-ID rule.

* chore: simplify the subagent model pool changeset

* feat(agent-core-v2): honor the legacy [secondary_model] model key as a fallback default

* refactor(node-sdk): export the reserved model-alias constants from the SDK

Restore the SECONDARY_DERIVED_MODEL_ALIAS re-export and add
PRIMARY_SUBAGENT_MODEL_CHOICE so the TUI imports both from
@moonshot-ai/kimi-code-sdk instead of vendoring local copies.

* feat(agent-core-v2): keep the subagent model pool behind the secondary-model experiment

Restore the secondary-model flag gating so this change only adds the pool:
with the experiment off the [secondary_model] pool keys stay inert — the
Agent/AgentSwarm tools strip the model parameter, spawns inherit the
caller's model, and startup pool validation is skipped. The /secondary-model
slash command is gated behind the experiment again, and the docs and
changeset describe the flag.

* fix(node-sdk): cascade provider removal into the subagent model pool

Deleting a provider left [secondary_model] entries pointing at the removed
model aliases; with the secondary-model experiment on, every subsequent
session create/resume/fork then failed pool validation. planProviderRemoval
now filters dangling pool entries, and drops the whole section when its
effective default (defaultModel, or the legacy recipe's model fallback)
dangles — folded into the same atomic multi-section replace.

* fix(agent-core-v2): close two subagent model pool validation gaps

Spawn-time resolveSubagentBinding now rejects force combined with a
[secondary_model.models] table, matching the startup pre-flight — a live
session could otherwise reach that invalid state through a deep-merged
config patch and only fail on the next create/resume. The session
lifecycle pre-flight also awaits the kosong model/provider registries'
ready alongside config.ready, so a cold bootstrap no longer fails a valid
pool with CONFIG_INVALID against an empty registry.

* test(agent-core-v2): stub the model/provider registries in handler-chain tests

SessionLifecycleService now awaits IModelService/IProviderService readiness
in its pool pre-flight, so the tests that assemble the real service through
a hand-built container must register the two tokens.

* fix(kap-server): cascade REST provider deletion into the subagent model pool

The DELETE /providers route rewrote only the providers and models
sections, so a pool referencing one of the deleted provider's aliases was
left dangling and the engine's create/resume/fork pool validation failed
every subsequent session until the user repaired the TOML by hand. Filter
dangling pool entries and drop the section when its effective default
dangles, mirroring the SDK's planProviderRemoval semantics.

* fix: keep the subagent pool consistent on provider replace and preserve legacy recipe fields

PUT /providers rebuilds the provider's alias set and can drop or rename
aliases referenced by [secondary_model]; the pool now cascades there too —
renamed aliases are repointed (mirroring the global default-pointer
migration), dropped aliases are filtered, and the section is cleared when
its effective default dangles.

The v2 [secondary_model] schema also declares the legacy recipe patch
fields (default_effort, max_output_size, ...) so validation no longer
strips them: pool resolution keeps ignoring them, but config reads/writes
now round-trip losslessly instead of silently deleting them from
config.toml on any pool write.

* fix(agent-core-v2): cascade the subagent pool on catalog refresh writes

A background provider/model refresh rewrites the [models] table without
touching [secondary_model], so a dropped alias left the pool dangling and
every subsequent session create/resume/fork failed validation until the
user repaired the TOML by hand — the same gap the SDK and REST write
paths already had, but triggered unattended.

The cascade helper now lives in agent-core-v2 next to the section it
protects (cascadeSubagentModelPool): the discovery service folds the pool
into the same atomic replaceSections transition, and kap-server's
provider write routes reuse the shared helper instead of a local copy.

* fix: cover the last two model-table write paths for the subagent pool

ModelsDevImportService's catalog and custom-registry imports rebuild the
[models] table without the pool cascade, so an import that drops a pooled
alias left a dangling pool behind; both final write passes now fold the
pool through cascadeSubagentModelPool (the drop passes deliberately skip
it). The StubConfigService test double now treats a null section value as
a delete, matching the real ConfigService.

The TUI's provider overwrite flow removes an existing provider before
re-adding it, which ran the removal cascade against a model table where
every alias of that provider was absent and silently dropped the pool;
the flow now snapshots secondaryModel up front and restores the entries
that survive the re-add, via the cascade helper re-exported from the SDK.
2026-08-13 12:29:03 +08:00
Liu Zhongnuo
504e6292ed
feat(mcp): inspect effective authorization state in v1 (#2856)
* feat(mcp): inspect effective authorization state

* test(agent-core-v2): register MCP auth coordinator fixture

* fix(mcp): validate runtime names against full catalog

* fix(mcp): reconnect after pending auth updates

* docs(mcp): describe auth coordinator collaborator

* fix(mcp): ignore disabled runtime name collisions

* fix(mcp): serialize OAuth token refresh

* test(mcp): await OAuth credential writes

* fix(mcp): queue trailing credential reconnect

* fix(oauth): preserve access-only refresh winners

* fix(mcp): preserve legacy offline auth state

* fix(mcp): redact inspection credentials

* refactor(mcp): keep app inspection on v1

* fix(mcp): guard legacy auth status mutations

* fix(mcp): avoid deterministic legacy auth probes

* fix(mcp): cover initialization credential updates

---------

Co-authored-by: 刘仲诺 <liuzhongnuo@dev.msh.team>
2026-08-13 12:00:04 +08:00
Haozhe
3b0936d8e0
fix(agent-core): only discover the root SKILL.md for plugin skills fallback (#2847)
When a plugin manifest omits `skills` and the plugin root contains a
SKILL.md, the fallback treated the whole plugin root as a generic skill
scan directory, so sibling Markdown files such as CHANGELOG.md were
misidentified as skills and inflated the plugin skill count.

Mark the fallback root as root-skill-only so discovery parses only the
root SKILL.md; explicit `skills` entries (including "./") keep the
directory scan semantics. Applied to both agent-core and agent-core-v2.
2026-08-12 14:24:48 +08:00
Haozhe
101c4d1997
feat(agent-core-v2): remove Agent and AgentSwarm from builtin profile tool lists (#2837)
* feat(agent-core-v2): remove Agent and AgentSwarm from builtin profile tool lists

The builtin agent and coder profiles no longer expose the Agent and
AgentSwarm tools, so sessions on the v2 engine do not offer subagent
delegation by default. The tools themselves remain registered; profiles
that list them explicitly can still opt in.

* feat(agent-core): remove Agent and AgentSwarm from builtin profile tool lists

Align the v1 builtin agent/coder profiles with the v2 change: the
default profiles no longer offer subagent delegation, while the tools
stay registered for profiles that list them explicitly.

The parity projection drops v1's inactive Agent/AgentSwarm roster
entries: v1 reports registered-but-inactive builtin tools where v2 only
registers the tools a profile lists, so an inactive entry has no v2
counterpart. Active entries still compare in full.

* fix: keep Agent and AgentSwarm in the builtin agent profile

Scope the removal to the coder subagent profile on both engines: the
main agent keeps Agent/AgentSwarm so default sessions can still
delegate, while coder subagents no longer spawn nested subagents by
default. Snapshots and token counts shift only for the embedded coder
tool list; the v1 parity projection needs no change since the main
agent rosters match again.
2026-08-12 11:41:02 +08:00
oocz
01c74e9372
fix(agent-core): isolate builtin profile catalogs per session (#2740)
Some checks failed
CI / build (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test (5) (push) Has been cancelled
CI / test-pi-tui (push) Has been cancelled
CI / test-windows (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-08-08 14:26:17 +08:00
Liu Zhongnuo
437a1b8ba1
fix(sdk): probe MCP auth status through connection (#2731)
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
2026-08-07 22:18:53 +08:00
Liu Zhongnuo
0b2e803d5e
feat(sdk): expose global MCP auth status (#2706)
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 / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
2026-08-07 13:11:18 +08:00
Haozhe
02c026d487
feat(agent-core-v2): tombstone removed MCP servers and freeze plugin prompt inputs (#2694)
* feat(mcp): tombstone removed MCP servers and apply plugin changes immediately (20 files)

- add 'removed' MCP server status: workspace config removals call markRemoved
  instead of remove, keeping tool registrations alive while short-circuiting
  calls with a removal notice
- fire onDidReload after every plugin mutation (install/enable/disable/remove)
  so workspace consumers refresh contributions immediately
- TUI renders the removed status in the MCP panel/startup summary and shows an
  apply-immediately hint on the v2 engine

* feat(agent-core-v2): freeze plugin prompt inputs for live agents (2 files)

- snapshot the model skill listing and plugin system-prompt sections on the
  first successful prompt build and reuse the frozen values for the agent's
  lifetime, so plugin install / enable / disable / remove / reload never
  rewrites a live agent's prompt (same keep-live-sessions-stable philosophy
  as the MCP tombstone)
- freeze only on success: a not-yet-ready skill catalog or a failed
  enabledSystemPrompts() read must not pin empty values for the agent's
  lifetime
- refreshSystemPrompt still rebuilds on catalog change events but reuses
  the frozen values, so the prompt only moves when non-plugin inputs change
  (AGENTS.md, [tools] section, session tool policy, compaction); new agents
  snapshot the then-current state

* chore(changeset): add changesets for MCP tombstone and frozen plugin prompt inputs

* docs: describe immediate plugin changes and the removed MCP status on the v2 engine

* fix(klient): mirror the removed MCP server status in the wire contract

* docs: drop the legacy-engine behavior notes from the plugin and MCP pages

* fix(agent-core-v2): freeze plugin sections only on a loaded snapshot

- enabledSystemPrompts() resolves to its consumption fallback (never
  rejects) while the initial plugin load has failed; freezing that empty
  read locked plugin sections out of the live agent even after a later
  successful reload
- expose hasLoadedSnapshot() on IPluginService so resolvePluginSections
  can tell a real empty snapshot from the fallback before freezing
2026-08-06 19:19:51 +08:00
liruifengv
7b2784b9b7
feat: surface the bound model and thinking effort on subagent UIs (#2679)
* feat: surface the bound model on subagent UIs

The subagent.spawned event now carries the display-normalized model alias
(the derived __secondary__ entry resolves to its base alias), so clients can
show which model a subagent is bound to. The TUI subagent card, swarm panel
header, and background-agent entry show it at spawn; the WS snapshot roster
and REST /tasks (background/detached subagents) carry it too, keeping the
model visible across client reconnects.

* feat: carry the subagent thinking effort alongside the model

The spawned event, snapshot roster, and REST /tasks now also carry the
child's effective thinking effort (read from the child profile at spawn, the
same vocabulary as agent.status.updated). UIs show it only when it diverges
from the main session's current effort — an inherited level adds no
information, and 'off' is never shown.

* feat(tui): show the bound model and effort in the /tasks browser

The task browser's Detail pane renders Model and Effort rows for agent
tasks (raw alias and level — it is the inspector surface, so no diff
filtering), and its minimum height grows to fit the new rows. The values
were already persisted on SubagentTaskInfo; the TaskInfo union, its zod
schemas (protocol, kap-server, klient contract), and the v1 type
declaration now carry them so nothing strips them in transit.

* feat(tui): show concrete subagent effort levels unconditionally

Display rule simplified: any concrete effort tier (low/high/max/…) is
shown next to the model — including when it matches the main session's
level. Only the boolean states stay hidden: 'off' (no thinking) and 'on'
(generic thinking) carry no level information.

* docs: trim the changeset entry

* fix(tui): keep the model and effort on background-agent entries across resume

replayBackgroundProjection only copied agentId/parentToolCallId/
description, so a background subagent that outlived a resume lost its
model/effort on the later terminal transcript entry. The projection now
threads the persisted values (catalog-mapped model; boolean effort states
dropped), and session replay passes the loaded model catalog through.

* fix(agent-core-v2): normalize the derived secondary alias regardless of the flag

A child bound while the secondary-model experiment was on keeps
__secondary__ in its persisted binding; if the flag is later switched off
with the recipe still configured, resolveSecondaryModel() gated the
normalization and the sentinel leaked back onto resumed subagents.
subagentDisplayModel now reads the recipe straight from config (the flag
gates new bindings, not the interpretation of existing ones), which also
drops SessionSwarmService's now-unused IFlagService dependency. Also adds
the SDK package to the release: the new SubagentSpawnedEvent/AgentTaskInfo
fields are SDK-visible types.

* fix(agent-core-v2): normalize the status-frame model at the source

A derived-bound child republishes agent.status.updated right after spawn
with its raw modelAlias, which overwrote the spawned event's normalized
display model on single-subagent cards (swarm headers were first-wins and
escaped). emitStatusUpdated now maps through subagentDisplayModel, a no-op
for the never-derived main agent. Also moves the inline comments added by
this branch into top-of-file headers per the v2 comment convention.

* fix(tui): clamp the /tasks detail frame to the available body

At terminals near the minimum height the forced 10-row detail frame
overflowed the body and truncated the preview frame's border. The detail
height now caps out at whatever leaves the preview its borders plus one
content row, with a regression test at exactly MIN_HEIGHT.

* fix: normalize inherited derived aliases and keep model/effort on replayed terminal entries

- resolveSubagentBinding's caller-fallback branch also maps through
  subagentDisplayModel: a caller itself bound to the derived entry (a
  resumed subagent making a nested Agent call) no longer publishes
  __secondary__.
- The replayed background-task terminal notification builds its metadata
  with the persisted model (catalog-mapped) and concrete effort, matching
  the live completion path.
- Drops the inline comments this branch added inside v2 test bodies; the
  scenario context lives in the source file headers.
2026-08-06 16:59:45 +08:00
Haozhe
f881cdd970
feat(cli): default CLI surfaces to the agent-core-v2 engine (#2627)
Some checks are pending
CI / lint (push) Waiting to run
CI / typecheck (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
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
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
* feat(cli): default to agent-core-v2 engine with KIMI_CODE_LEGACY_FLAG opt-out

- invert the engine gate: isKimiV2Enabled() now returns true unless
  KIMI_CODE_LEGACY_FLAG is truthy; KIMI_CODE_EXPERIMENTAL_FLAG no longer
  selects the engine
- replace the experimental `kimi acp-v2` command with the native v2
  implementation as the default `kimi acp`; the legacy acp-adapter path
  remains under the legacy flag
- drop the acp-v2 experimental flag from the registry
- rename the dev:cli:v2 script to dev:cli:legacy
- update en/zh docs for the new default engine and the legacy flag

* feat(cli): route export and provider through the engine gate

- select the harness via isKimiV2Enabled(): agent-core-v2 by default,
  the legacy harness when KIMI_CODE_LEGACY_FLAG is truthy
- close the harness after each one-shot command so the v2 engine's
  watchers do not keep the process alive
- document both commands in the KIMI_CODE_LEGACY_FLAG env-var entry
2026-08-05 14:42:23 +08:00
Haozhe
2a4990182d
docs: slim root AGENTS.md, move deep package docs to package guides (#2626)
- move the kimi-inspect, kap-server, transcript, and minidb project-map
  entries into new package-level AGENTS.md files
- move the standalone Agent class rule to packages/agent-core/AGENTS.md
- merge root-only agent-core-v2 facts (MCP persistence, trust routes,
  seed contract examples) into packages/agent-core-v2/AGENTS.md
- compress the remaining long project-map entries to summaries with
  pointers, and add an anti-bloat rule for map entries
- drop the stale server-e2e entry; the package no longer exists
2026-08-05 13:43:33 +08:00
zy
2ee6e43124
fix(mcp): re-register the OAuth client when its redirect URI no longer matches (#2620)
The callback listener binds a random port per flow, while DCR
registration records the redirect URI of the flow that created it —
so every interactive authorization after the first was rejected with
"Invalid redirect URI", an error rendered only in the user's browser
while the client waited for a callback that never came. Detect the
mismatch before invoking auth() and drop the stale registration so the
flow re-registers with the current callback URI (v1 + v2).

Resolve #2606

Co-authored-by: zouying <zouying@moonshot.cn>
2026-08-05 10:24:24 +08:00
zy
74c321e4c6
fix(mcp): drop protocol-reserved _meta keys from model-visible output (#2600)
* fix(mcp): drop protocol-reserved _meta keys from model-visible output

Follow-up to #2596. The MCP spec reserves _meta key prefixes whose labels
include "modelcontextprotocol" or "mcp" for protocol use; those entries
carry host/protocol plumbing rather than model-facing data, so filter them
out before serializing the <mcp-structured-result> block. Unprefixed and
vendor-prefixed keys still pass through — their semantics belong to the
server. Also moves the v2 implementation commentary into the module header
per the agent-core-v2 comment convention.

* fix(mcp): reserve _meta prefixes only when a label follows mcp/modelcontextprotocol

Per the spec's key-name rules a prefix is reserved when a
modelcontextprotocol or mcp label is followed by at least one more label;
a trailing reserved word (com.example.mcp/) is a legitimate vendor
namespace and now passes through.

---------

Co-authored-by: zouying <zouying@moonshot.cn>
2026-08-04 20:03:10 +08:00
zy
3126422757
feat(mcp): carry an absolute expiresAt on the OAuth authorization-url update (#2609)
* feat(mcp): carry an absolute expiresAt on the OAuth authorization-url update

The authenticate flow waits for the OAuth callback with a fixed budget,
but the authorization-url tool update surfaced to embedding hosts did not
say when that window ends — hosts had to hardcode a mirror of the
15-minute constant to render countdowns. Include the absolute deadline
(now + effective wait timeout) in the update payload for v1 and v2.

Resolve #2607

* fix(protocol,kap-server): accept expiresAt in the OAuth authorization-url update schemas

The zod validators mirrored the pre-expiresAt payload shape and would
strip the new field at the kap-server boundary.

---------

Co-authored-by: zouying <zouying@moonshot.cn>
2026-08-04 20:02:47 +08:00
zy
c32e661faa
fix(mcp): pass structuredContent and _meta through to model-visible tool output (#2596)
MCP tool results were narrowed to {content, isError}, dropping the
spec-defined structuredContent field and _meta metadata. Servers that
return structured contracts in these fields (validated against
outputSchema, or namespaced metadata such as browser-handoff payloads)
were invisible to the agent. Surface them as a serialized
<mcp-structured-result> block appended to the tool output, still subject
to the existing text budget.

Co-authored-by: zouying <zouying@moonshot.cn>
2026-08-04 18:01:42 +08:00
Haozhe
1328b32037
feat(acp): add experimental agent-core-v2 ACP server (kimi acp-v2) (#2571)
* feat(acp): add agent-core-v2 ACP server

- add ACP session lifecycle, configuration, permissions, and event bridging
- expose the experimental kimi acp-v2 command with terminal authentication
- add integration coverage and workspace build configuration

* test: use neutral example domains in test fixtures and docs

- replace placeholder hostnames (evil.com, foo.com, internal.corp,
  real.corp) with example.test / example.com in agent-core-v2 and
  kap-server tests
- replace fixture emails (x@y.com, a@x.com) with example addresses in
  minidb tests and README

* fix(acp): align acp-server with agent-core-v2 interfaces and address review

- add missing appendText to AcpHostFileSystem (IHostFileSystem drift)
- replace IAgentPromptService.prompt with inject
- use Turn.cancel() instead of abortController
- gate FS reverse-RPCs on client capabilities, fallback to local FS
- return PROTOCOL_VERSION constant instead of echoing client version
- remove misleading mcpCapabilities from initialize response
- dispose old session wrapper before replacing on load/resume
- fix object stringification lint error in convert.ts
- add acp-v2 to expected CLI sub-command list in test

* fix(acp): use enqueue for prompt submission, stop advertising unimplemented builtins

- replace IAgentPromptService.inject with enqueue so onBeforeSubmitPrompt
  hooks (prompt-blocking policy) are not bypassed
- stop advertising builtin slash commands (/help, /status, etc.) until
  builtin command execution is implemented
- add comment explaining appendText stays local (ACP has no append RPC)
- update skills test to match new availableCommands behavior

* fix(acp): filter turn events by turnId, surface auth failures as auth_required

- track turnId in driveTurn and ignore events from unrelated turns,
  preventing queued prompts from settling on the running turn
- reject prompt requests with auth_required when turn fails with an
  auth-related error code, enabling ACP client re-auth flow

* fix(acp): gate acp-v2 behind experimental flag, filter sessions by cwd

- add acp-v2 experimental flag (KIMI_CODE_EXPERIMENTAL_ACP_V2) and gate
  CLI command registration behind it
- filter session/list results by requested cwd instead of returning
  sessions from all workspaces
- detect hook-blocked prompts via PromptHandle.state and add TODO for
  streaming block messages once the hook context exposes them

* refactor(acp-server): rewire ACP server onto the klient facade

- replace direct agent-core-v2 scope/service access (ISessionLifecycleService,
  ISessionIndex, IEventBus, ISessionInteractionService, etc.) with the Klient
  facade: klient.global.sessions / klient.session(id) / agent('main') handles
- drive turns via agent.prompt() + session-level agent event subscriptions
  instead of per-prompt IEventBus wiring; settle on turn.ended
- route approval/question bridging through session.interactions events
- hide the thinking config option and skill catalog behind KLIENT-GAP markers
  until klient exposes those surfaces
- acp-fs: pass realpath through to the local inner backend
- klient: session.restore() rejects both null and undefined handles

* feat(agent-core-v2): add session delete and ephemeral per-session MCP servers

- add ISessionLifecycleService.delete: close a live session first, then
  remove its persisted data, evict the index read-model entry, and append
  a deleted tombstone to session_index.jsonl; unknown ids raise
  session.not_found
- add CreateSessionOptions/ResumeSessionOptions.mcpServers: session-owned
  MCP overlay merged over the workspace manager via
  MergedMcpConnectionView (an ephemeral name shadows a workspace server),
  never persisted, released when the session scope tears down
- return PromptLaunchResult from activateSkill so callers get the
  launched turn id and activation failures (unknown skill, busy) surface
- add ISessionSkillCatalog.list() as a wire-friendly catalog snapshot
- add ISessionIndex.remove for read-model eviction on delete

* feat(klient): expose session delete, per-session MCP, skills, and stream events

- session lifecycle contract: delete, resume/restore options, and
  CreateSessionOptions.mcpServers (ephemeral per-session MCP servers)
- add the session skills contract and facade accessors for the
  wire-friendly skill catalog snapshot
- register tool.call.delta, tool.progress, and compaction.* agent stream
  events so consumers can subscribe with typed payloads

* feat(acp-server): align ACP v2 server with acp-adapter capabilities

- complete the klient-facade rewire: ACP client connection holder and
  the terminal/* reverse-RPC runner routed through the Agent scope
- negotiate the protocol version on initialize instead of pinning v1
- compress oversized prompt images at the ACP ingestion point with a
  format gate, caption, and persisted originals; a cancel arriving
  mid-compression settles the prompt as cancelled without a turn
- stream tool call args via tool.call.delta (lazy pending create,
  cumulative replace, started upgrade) and refresh titles via
  tool.progress status updates
- report compaction progress and results after /compact via the
  compaction.* events
- answer unknown slash commands locally instead of sending them to the
  model
- accept legacy "<id>,thinking" model ids and legacy approve /
  approve_for_session approval option ids
- keep sessions without cwd metadata in cwd-filtered session/list
- sanitize wire errors: auth codes map to auth_required, turn.agent_busy
  to invalid_request, everything else to a fixed internal-error message
- bump @agentclientprotocol/sdk to ^1.3.0

* fix(cli): drop stale registerServerCommand call and sherif ACP SDK split

- commands.ts called registerServerCommand, which no longer exists on
  current main (the deprecated `kimi server` shim is registered via
  registerWebCommand), breaking typecheck, build, and every CLI test
  that builds the program
- sherif rejects the @agentclientprotocol/sdk major split between
  acp-adapter (^0.23.0, production kimi acp) and acp-server (^1.3.0,
  experimental); the two hosts legitimately target different SDK
  majors, so ignore the dependency in the sherif invocation

* test: update fixtures for acp-v2 flag and domain rename, refresh nix deps hash

- kap-server origin.test: two CORS cases still used foo.com after the
  whitelist moved to foo.example.com, so the origin was no longer
  whitelisted and the expected CORS headers were withheld
- node-sdk config.test: expect the new acp-v2 experimental flag in the
  harness feature metadata
- flake.nix: update the fetchPnpmDeps hash for the
  @agentclientprotocol/sdk 1.3.0 lockfile change

* fix(acp): widen the ACP v2 auth gate beyond OAuth-only providers

The gate consulted only auth.summarize(), which iterates providers
declaring an oauth section — configurations that authenticate with a
plain apiKey or provider env-bag credentials (no OAuth at all) were
rejected with auth_required even though the default model is fully
usable.

- klient: expose authSummaryService.ensureReady on the global auth
  facade (the contract already declared it)
- acp-server: gate on the engine's own readiness probe for the default
  model — config apiKey / env-bag / OAuth token all count, matching how
  the model is actually used — and fall back to "any logged-in OAuth
  provider" (the legacy adapter's first branch)
- test: an apiKey-only config passes the gate with auth enforcement on;
  the OAuth logout regression is unchanged

* fix(acp): reject concurrent prompts instead of displacing the in-flight turn

A second session/prompt while a turn is running overwrote the session's
only TurnDriver: the engine quietly queues plain prompts submitted
during an active turn (the launch resolves undefined, indistinguishable
from a hook-blocked launch), so the first prompt never settled and both
turns' events went unattributed.

Guard both model-bound launch paths (plain prompt and skill activation)
with a synchronous in-flight check and reject with invalid_request
(turn.agent_busy), matching the legacy adapter's busy semantics. Local
slash handling (builtins, unknown-command answers) is unaffected.
2026-08-04 10:20:24 +08:00
7Sageer
98ef0f0b2f
fix(agent-core): replay v2 profile.bind records so resumed sessions keep their tools (#2567)
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(agent-core): replay v2 profile.bind records so resumed sessions keep their tools

Sessions created by the v2 engine (CLI 0.31+, wire protocol 1.5) persist
the profile binding, including the tool allowlist, as a profile.bind
record. The v1 replay path had no branch for it and silently dropped the
record, so a session resumed by a v1 host (e.g. the VS Code extension via
kimi-code-sdk) never called setActiveTools and sent requests with no
tools at all (observed server-side as tools_count=0; the model emits
reasoning only and stops with empty content).

v1 replay now maps profile.bind onto config.update + setActiveTools when
activeToolNames is an array, skips the record otherwise so the
resume-time default-profile fallback still applies, and treats
tools.reset_active_tools as a no-op.

* fix(vis): handle v2 profile records in context projection

* fix(agent-core): avoid synthetic replay for v2 profile binds

* fix(vis): render v2 profile wire records
2026-08-03 20:59:46 +08:00
Haozhe
6ba75a173b
feat(config): add deprecation mechanism and rename loop retry limit (#2572)
* feat(config): add deprecation mechanism and rename loop retry limit

- agent-core-v2 config: declarative section `deprecations` (deprecated TOML
  keys are ignored and report a warning diagnostic; the file is never
  rewritten) and env binding `deprecatedEnv` (old var still resolves as a
  fallback with a warning), surfaced via the new
  `IConfigService.onDidChangeDiagnostics` event
- loop_control: rename `max_retries_per_step` to `max_attempts_per_step` and
  `KIMI_LOOP_MAX_RETRIES_PER_STEP` to `KIMI_LOOP_MAX_ATTEMPTS_PER_STEP`;
  `max_steps_per_run` moves onto the same mechanism (no longer silently
  mapped)
- kap-server: push the global `event.config.warning` WS event to every
  connection whenever the config warning set changes
- TUI: show config diagnostics in warning yellow at startup instead of the
  dim startup notice
- docs: config-files/env-vars (en+zh), regenerated config manifest, and the
  agent-core-dev config guide

* feat(cli): validate config.toml against v2 section registry in doctor

- add v2/validate-config.ts: validate config.toml with the agent-core-v2
  ConfigRegistry, reporting registered-section schema failures as errors
  and unknown top-level keys / deprecated keys and env vars as non-fatal
  warnings
- route `kimi doctor` config validation through the v2 validator when the
  KIMI_CODE_EXPERIMENTAL_FLAG master switch is on (lazy dynamic import,
  keeping the v2 module graph off the default path)
- let doctor checks surface non-fatal warning messages on OK results

* chore: downgrade loop-control changeset to patch
2026-08-03 20:17:01 +08:00
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
github-actions[bot]
bc28e9d802
ci: release packages (#2342)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-30 14:56:30 +08:00
liruifengv
40172c7ca9
feat: unify the host identity across OAuth, telemetry, and kap-server (#2382)
* refactor(oauth): make X-Msh-Platform an explicit host identity field

X-Msh-Platform was hardcoded to kimi_code_cli in createKimiDeviceHeaders,
so non-CLI hosts could not state their own platform and the desktop had
to patch the header after the fact. KimiHostIdentity now carries a
required platform (every host declares its own value; the CLI constant
stays the fallback only for direct createKimiDeviceHeaders callers), and
userAgentProduct is renamed to productName so the transport identity
uses one name everywhere.

All in-repo identity constructions pass platform explicitly; the wire
value for CLI and VS Code hosts is unchanged (kimi_code_cli).

* feat(agent-core-v2): carry the host identity in the bootstrap snapshot

Replace the flat clientVersion field with a required clientIdentity
(KimiHostIdentity) so every consumer reads the same host identity
object: OAuthToolkitService now passes it to the OAuth toolkit, which
means the OAuth device-flow endpoints (device authorization, token
polling, refresh) on the kap-server path finally send the full X-Msh-*
device headers instead of none, and the telemetry cloud appender reads
client_version from the same source. A built-in CLI fallback keeps bare
bootstrap() calls in tests working; composition roots must pass their
own identity.

The session export manifest grows an optional desktopVersion field
(payload plumbed through; filled by kap-server in a follow-up).

* feat(agent-core): thread the host identity into the managed auth facades

The v1 managed auth facade constructed its OAuth toolkit without an
identity, so token refreshes from inside the core went out without any
X-Msh-* device headers. createManagedAuthFacade now takes an optional
KimiHostIdentity and every call site supplies one:
CoreProcessService._defaultOAuthTokenResolver forwards the core
process's options.identity (the same source _defaultKimiRequestHeaders
uses), and the DI-held services (oauth / auth summary / model catalog)
read it from a new optional identity field on IEnvironmentService. The
library-level "no identity, no device headers" contract is unchanged.

* feat(kap-server)!: require the host identity and derive request headers from it

ServerStartOptions.hostIdentity is now a required ServerHostIdentity
(KimiHostIdentity + optional prompt display fields), replacing both the
old optional HostIdentityOverrides (renamed to PromptIdentityOverrides,
its productName field now displayName) and the version option (renamed
to serverVersion — it is the engine version reported as server_version,
while the host product version travels in hostIdentity.version).

The server now feeds bootstrap's clientIdentity from hostIdentity and
derives the default outbound headers (User-Agent + X-Msh-*) from it via
createKimiDefaultHeaders, so kap-server-hosted OAuth flows and model /
WebSearch requests carry the real host identity instead of a hardcoded
kimi-code-cli fallback UA. Explicit header seeds still win as an escape
hatch.

Session export manifests record the host product version: kimiCodeVersion
now carries hostIdentity.version (the engine version no longer appears),
and desktop exports (desktop: true) are additionally stamped with a
desktopVersion field. The instance registry keeps its host_version wire
field for compatibility (kimi-inspect reads it); only the in-memory name
changed to serverVersion.

* feat(cli): wire the CLI host identity into the kimi web server

kimi web now passes createKimiCodeHostIdentity(version) as the server's
hostIdentity, so web-UI OAuth flows and the engine's outbound requests
carry the explicit CLI identity (productName + version + platform). The
explicit hostRequestHeadersSeed is dropped — kap-server derives the same
headers from hostIdentity — and buildKimiDefaultHeaders goes away with
its only consumer.

* test(klient): drop clientVersion from the bootstrap contract parity list

* chore: add changesets for the host identity unification

* feat(cli): tag kimi web requests with a (web) User-Agent suffix

kimi web shares the CLI product token and platform, so its outbound
requests were indistinguishable from direct CLI runs upstream. Its host
identity now carries userAgentSuffix 'web', putting web-UI traffic at
kimi-code-cli/<version> (web) while X-Msh-Platform stays kimi_code_cli.

* fix(klient): keep the env() clientVersion wire field after the bootstrap identity switch

The bootstrap snapshot replaced the flat clientVersion scalar with
clientIdentity, which broke klient's env() fan-out (RPCError: method not
found). The wire surface keeps clientVersion — now sourced from
clientIdentity.version — and bootstrapService gains a clientIdentity
read (registered in envContract with an object schema) for consumers
that want the full identity.

* feat(oauth): send the product User-Agent on OAuth requests

The OAuth endpoints used to receive only the X-Msh-* device headers
(undici's default UA otherwise), which left the OAuth host unable to
distinguish runtime surfaces — notably kimi web, whose platform matches
the CLI and whose only distinguishing mark is the (web) UA suffix. The
toolkit now feeds the full identity headers (User-Agent + X-Msh-*) into
every device authorization, token polling, and refresh request; the
request-header type widens from DeviceHeaders to OAuthRequestHeaders.

* feat(vscode): report kimi_code_vscode as the extension's platform

The VS Code extension inherited the CLI's hardcoded X-Msh-Platform value;
with platform now an explicit identity field it declares its own, so the
managed endpoints and OAuth host can tell extension traffic apart from
CLI runs.

* refactor(agent-core-v2)!: require the client identity at the composition root

The bootstrap fallback identity fabricated a kimi-code-cli/unknown host
for any caller that forgot to pass one — the same silent-misreport
pattern this series set out to remove, and it made "required" a lie.
BootstrapInput.clientIdentity is now required, so a missing identity
fails at compile time instead of being papered over. Test and example
callers pass a shared fixture (klient examples and test engines get one
each); the node-sdk v2 client asserts its host identity with the oauth
helper. Also folds DeviceHeaders from an interface into a type alias so
it stays assignable to the widened OAuthRequestHeaders record.

* feat(oauth)!: require and validate the platform in device headers

Drops the quiet CLI fallback in createKimiDeviceHeaders (the same
silent-misreport pattern removed from the bootstrap identity): platform
is now a required option, validated with the same required-ASCII rule as
the version — empty or all-non-ASCII values throw instead of emitting a
blank X-Msh-Platform, and header-unsafe characters are stripped rather
than sent raw.

* fix(node-sdk): seed the host request headers on the v2 client path

The interactive v2 engine path (experimental flag) bootstrapped without
a hostRequestHeaders seed, so managed vendor calls went out with the
SDK's default User-Agent (OpenAI/JS) and no X-Msh-* at all — v1 passes
the full identity headers on the same requests. The v2 client now seeds
the headers from its asserted host identity, and a test pins the seed.

* chore: simplify the CLI changeset wording
2026-07-30 13:45:41 +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
fa2c5ce18b
feat: support plugin-contributed custom agents (#2365)
* feat: support plugin-contributed custom agents

* fix: await plugin loading before agent catalog

* fix: refresh plugin agents on v1 reload

* test(agent-core-v2): add enabledSystemPrompts to the plugin service stub
2026-07-29 21:59:48 +08:00
7Sageer
02d77b20d9
feat(agent-core-v2): let plugins contribute system prompt instructions via the manifest systemPrompt field (#2314)
* feat(agent-core-v2): let plugins contribute system prompt instructions via the manifest systemPrompt field

* feat(agent-core-v2): add systemPromptPath to load plugin system prompt from a file

* docs: explain plugin system prompt templates

* fix(agent-core-v2): refresh plugin system prompts after changes

* fix(agent-core-v2): freeze restored profile bindings and converge plugin contributions at session scope

- restore no longer re-renders or re-persists prompts: a resumed agent
  keeps its replayed profile binding (prompt and tool set) as persisted
- a new Session-level convergence point reloads plugin skills into the
  session skill catalog before fanning out to every live agent prompt,
  and every catalog-kind plugin mutation awaits the whole pipeline;
  MCP-only toggles carry a distinct change kind and skip it
- live refreshes after a restart re-resolve the bound profile by name
  and rebind the full slice (prompt, disallowed tools, active tools)
  atomically, warning and keeping the persisted state when the profile
  is gone; renders reuse the first-render timestamp and unchanged
  prompts are not re-persisted, so convergence never churns the wire
- cap plugin system-prompt contributions (32 KB per field/file, 64 KB
  aggregate per prompt build) with manifest diagnostics and warnings
- bump the changeset to minor: this is a new user-facing capability

* fix(agent-core-v2): register the new session domain and dedupe the missing-profile warning

- add sessionPluginContribution to the domain-layer registry so
  lint:domain stays green
- emit system-prompt-refresh-profile-missing once per profile name,
  matching the service's other deduped warnings
- document the convergence timeout escape hatch and the klient
  exclusion of enabledSystemPrompts

* fix(agent-core-v2): dedupe the plugin budget warning and surface section read failures

- emit plugin-sections-oversized once per skipped-plugin signature
- let enabledSystemPrompts failures propagate to the refresh catch
  (keeps the current prompt and warns) instead of silently rendering
  and persisting a prompt without plugin instructions
- cover the convergence timeout cut-off with a fake-timers test
- clarify that the first-render timestamp anchors per process

* fix(agent-core-v2): serialize session convergence and restore onDidReload timing

- run at most one convergence per session and bound each change's wait
  by the timeout, so a fan-out emitter never interleaves deliveries
  after a timed-out convergence
- fire onDidReload as soon as the reload commits again, keeping hook
  reloads independent of prompt convergence
- sign the plugin budget warning with an unambiguous key

* docs(agent-core-v2): align convergence wording with the serialized semantics

- the timeout retry promise only holds once stalled work clears
- note the per-session serial delivery cost model on the plugin change
  contract and the dual-queue invariant on the service

* fix(agent-core-v2): keep empty plugin sections byte-neutral in the prompt template

- place ${plugin_sections} on the same template line as
  ${skills_section} so prompts without either block render exactly as
  before this feature
- note on the change contract that waitUntil work must not call back
  into plugin mutations, and spell out the per-session convergence
  order in the user docs

* fix(agent-core-v2): pin a fork's profile so refresh triggers never rebind it

- applyBindingSnapshot left the fork with no pinned profile, which
  routed in-process forks into the post-restart catalog rebind and
  could reset an inherited tool set; forks now inherit the source
  agent's pinned profile object
- pin the first-render timestamp reuse with a ${now}-embedding test
  and document the anchored ${now} semantics
- tighten the plugin docs budget and resume-refresh wording

* fix(agent-core-v2): join in-flight convergence during agent bootstrap

- an agent created while a plugin convergence is in flight now waits
  for it, and a restored agent refreshes once after it, so a plugin
  mutation never straddles an agent's bootstrap
- warn on a non-string systemPrompt field and strip a UTF-8 BOM from
  systemPromptPath files before trimming
- correct the consumption-surface wording (every CLI surface on the
  experimental flag, not just kimi -p), the per-session queueing note,
  and the single-plugin combined budget clause

* fix(agent-core-v2): bound the bootstrap convergence join by the timeout

A permanently wedged convergence kept convergeTail pending forever,
and the unconditional settled() wait in bindBootstrap would have
blocked every later agent creation in that session; the join now
races the shared convergence timeout and continues (a restored agent
still refreshes once, which never touches the tail), and the timeout
constant moves to the contract for reuse

* fix(agent-core-v2): close the convergence race against in-progress restores

- a convergence fan-out could land while an agent's wire log is still
  replaying, dispatching a replay-visible config record whose effect
  the rest of the replay then overwrites; refreshSystemPrompt now
  skips while the wire restore is in progress
- convergence completion is tracked by a generation counter; bootstrap
  compares it (after a bounded join) and refreshes a restored agent
  exactly once when a round completed after its creation began,
  replacing the wasConverging flag that could miss both windows

* fix(agent-core-v2): bound each convergence so a wedged participant cannot stop the pipeline

- the fan-out now races the convergence timeout, so convergeTail always
  settles: a permanently hung refresh delays its round (blocked entries
  drain oldest-first on later changes) instead of killing the session's
  convergence for good
- warn when agent bootstrap stops waiting on a stalled convergence
- diagnose a blank systemPromptPath and pin the plugin-root escape
  guard with traversal, absolute-path, and symlink tests

* fix(agent-core-v2): bound the skill reload, preserve user-tool overlays, roll the prompt clock daily

- the convergence's skill-reload segment now races the same timeout as
  the fan-out, so no segment of the pipeline can wedge a session for
  good; it continues with the previous catalog and retries next change
- a cold rebind that resets the tool set replays session-added user
  tools onto the new base instead of dropping them for the rest of the
  process
- the rendered timestamp re-anchors when the UTC date rolls over, so
  long-lived processes keep a fresh clock while steady-state renders
  stay byte-stable within a day
- the plugin budget warning dedupes per plugin id, and the docs note
  that systemPromptPath content is frozen until the next reload

* feat(agent-core-v2): converge cold plugin changes on resume through a drift-free gate

- restore replays the persisted binding untouched, then bootstrap
  refreshes only when drift-free inputs changed while the session was
  cold: the catalog profile's tool set/denylist, or the plugin-sections
  baseline persisted alongside the prompt on the existing bind/update
  payloads; directory-listing and date drift wait for live triggers,
  so quiet resumes append no replay-visible records
- the rendered timestamp is day-precision (UTC date at 00:00,
  re-anchored on rollover), keeping steady-state renders byte-stable
  across resumes and sessions on the same day
- consolidate both timeout helpers onto a shared raceOutcome, and drop
  the generation counter the gate supersedes
- align the plugin-sections precedence prose with the AGENTS.md
  disclaimer (no self-granted authority, system instructions win on
  conflict)

* fix(agent-core-v2): bound the restored-prompt gate and land the sections baseline

- the gate's plugin-sections read now races the convergence timeout, so
  agent creation never blocks behind an unrelated plugin mutation
- refreshes serialize per agent through a tail, so overlapping triggers
  cannot write prompts out of order
- when plugin sections change but a plugin-free custom prompt does not,
  the new baseline lands as a sections-only update instead of making
  every later resume re-render in vain
- align the system prompt's Date and Time paragraph with the
  day-precision anchored timestamp

* Update plugin system-prompt instructions in changeset

Live sessions pick up plugin changes, while the default TUI and `kimi -p` paths ignore these fields.

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

* refactor(agent-core-v2): keep plugin skill reload user-driven

Plugin mutations still converge live agent prompts, but the session
skill catalog goes back to refreshing only on explicit plugin reload,
as before: the prompt feature does not need skill convergence, and the
pre-existing manual-reload semantics stay uniform across all plugin
contributions. Removes the convergence-driven skill reload, the
reloadSource de-privatization, and their tests; restores the
PluginSkillSource onDidReload forwarding and its catalog tests.

* refactor(agent-core-v2): apply plugin system-prompt changes only on explicit reload

Drop the live convergence machinery (the plugin onDidChange barrier,
the sessionPluginContribution fan-out, the restored-prompt drift gate,
and the day-precision render clock) so plugin system-prompt sections
take effect at the same point as every other plugin contribution:
/plugins reload or a new session. The profile now refreshes when the
session skill catalog re-pulls its plugin source on reload, reading
both the skill list and the prompt sections fresh.

* feat(agent-core): let plugins contribute system prompt instructions via the manifest systemPrompt field

* chore(agent-core-v2): remove inline implementation comment

* docs: clarify plugin prompt refresh semantics

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-07-29 20:30:07 +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
STAR-QUAKE
de0ba9d065
fix(agent-core): count validation-rejected tool calls toward the repeat breaker (#2313)
* fix(agent-core): count validation-rejected tool calls toward the repeat breaker

Args-rejected calls returned before prepareToolExecution, so the breaker
never counted them and the model could re-issue the same invalid call
until maxSteps. Register them in finalizeToolResult so reminders fire at
3/5/8 and the turn force-stops at 12.

* fix(agent-core): key parse-failed repeats on raw argument text

Malformed JSON arguments normalize to {} on parse failure, which keyed
every malformed-but-different attempt identically and could force-stop a
turn whose calls were evolving rather than identical. Register skipped
calls on the raw arguments text when parsing failed.

---------

Co-authored-by: fengchenchen <fengchenchen@moonshot.ai>
2026-07-28 18:39:26 +08:00
Petrichor
cdbd33c13c
fix(kosong): fail fast on quota-exhausted 429 instead of retrying (#1857)
* fix(kosong): fail fast on quota-exhausted 429 instead of retrying

A 429 caused by an exhausted account quota or insufficient balance
(Moonshot error.type "exceeded_current_quota_error", OpenAI
"insufficient_quota") can never succeed on retry, yet it was classified
as APIProviderRateLimitError and silently retried for the whole budget
(10 attempts, ~3 minutes of backoff) with no UI feedback — the session
appeared frozen on every request.

Introduce APIProviderQuotaExhaustedError, minted in
normalizeAPIStatusError from the structured body error.type/error.code
forwarded by convertOpenAIError, with billing-anchored message patterns
as a fallback for gateways that flatten the body to text. The new class
is excluded from isRetryableGenerateError (fail fast, even when a
retry-after header is present) and from isProviderRateLimitError (no
swarm requeue/suspend). toKimiErrorPayload and translateProviderError
map it to provider.api_error (retryable: false) instead of
provider.rate_limit, and classifyApiError reports it as
quota_exhausted in telemetry. agent-core-v2 mirrors the same fix.

Transient rate-limit 429s keep the existing retry, backoff, and
Retry-After behavior (verified end-to-end against a mock provider:
quota body fails after attempt 1/10; rate-limit body still walks the
full 10-attempt ladder).

Behavior changes to note: quota-failed swarm subagents now fail
instead of suspending indefinitely as "Rate limited...", and quota
errors cross the wire as provider.api_error rather than
provider.rate_limit.

* fix(kosong): classify quota exhaustion in OpenAI Responses stream errors

Responses response.failed / error SSE events carry no HTTP status and
were minted by errorFromOpenAIResponsesEvent as either a rate-limit
error (rate_limit_exceeded / embedded status_code=429) or a base
ChatProviderError — and the base class falls into the retryable
unclassified-failure fallback, so an insufficient_quota event still
burned the whole retry budget on the openai_responses path. Route the
event code and message through the same quota-exhausted check before
the rate-limit branch, in kosong and the agent-core-v2 mirror. Covers
all three entry paths (error events, response.failed, nested gateway
frames) since they share the single converter.

* style(agent-core-v2): drop inline comments per AGENTS.md header-only rule

agent-core-v2 comments live solely in the top-of-file block, never
beside functions or statements; the kosong twins keep the full
rationale.

* refactor(kosong,agent-core-v2): move quota-429 checks to vendor hook

Per review on #1857: the knowledge of how a backend signals quota
exhaustion is vendor-specific and must not run for every
OpenAI-compatible provider from the shared conversion layer.

- Add a convertError hook: ProtocolTrait.convertError in agent-core-v2
  (single-value, last-declarer-wins, bound by composeOpenAIChatHooks /
  composeAnthropicHooks / traitConvertError) and an equivalent optional
  hook parameter on convertOpenAIError / convertAnthropicError. Bases
  consult it with the raw failure (SDK error on HTTP paths, raw event on
  the Responses in-stream path) after the abort guard, before their own
  rules.
- Declare Moonshot's quota signals (exceeded_current_quota_error,
  billing wordings) on the Kimi side: kimiOpenAITrait and
  kimiAnthropicTrait in v2, the KimiChatProvider and KimiFiles catch
  sites in kosong, all through the new classifyKimiQuotaError.
- Drop the options parameter from normalizeAPIStatusError and the
  shared quota code/pattern tables: the contract layer keeps only the
  vendor-neutral APIProviderQuotaExhaustedError type and its retry /
  rate-limit / wire-mapping semantics.
- The OpenAI bases keep recognizing only OpenAI's own documented
  insufficient_quota code (HTTP and Responses stream events) as
  protocol knowledge of that wire.

Behavior: kimi and openai provider types classify exactly as before;
an unregistered vendor speaking Moonshot billing wordings through a
plain openai transport now stays a retryable rate limit by design.

* fix(kosong,agent-core,agent-core-v2): wire kimi quota hook fully

Follow-up to the second review round on #1857, all four findings:

- Kimi-over-Anthropic (legacy engine): AnthropicOptions gains the same
  optional convertError hook as the OpenAI bases, threaded through
  AnthropicStreamedMessage and every catch site, and the provider
  manager's anthropic route now passes classifyKimiQuotaError for
  provider type kimi — a quota-exhausted 429 over this transport
  previously still burned the retry budget. classifyKimiQuotaError now
  also walks error -> .error -> .error.error for the code/type, since
  the Anthropic SDK keeps the full body on .error instead of hoisting.
- v2 telemetry: ApiErrorKind gains 'quota_exhausted' and
  classifyApiError checks APIProviderQuotaExhaustedError before the
  generic 429 branch, matching the legacy engine's reporting.
- Hook contract: converted ChatProviderErrors now pass through before
  the vendor hook is consulted in convertOpenAIError /
  convertAnthropicError (both engines), so the hook sees each raw
  failure exactly once even when a stream-minted error crosses an
  outer catch; tests assert the single consult.
- protocolTrait: the convertError member doc shrinks to the concise
  style and the consult contract moves into the file header's
  composition rules.

* test(kosong,agent-core,agent-core-v2): lock quota hook assembly paths

Third review round on #1857:

- Fix the v2 anthropic base header and AnthropicHooks doc still claiming
  withThinking is the only hook.
- Drop the two remaining non-header JSDoc blocks in protocolTrait.ts per
  the AGENTS.md header-only rule; the consult contract already lives in
  the file header.
- Update the ProtocolTrait contract test to the seventeen-hook shape
  (convertError included) and cover the traitConvertError binding.
- Add real-assembly regression probes: the v2 registry composes a
  (kimi, anthropic) provider whose mocked SDK client throws a Moonshot
  quota 429 and generate rejects with the non-retryable
  APIProviderQuotaExhaustedError (a plain anthropic composition keeps
  the same 429 retryable); the legacy ProviderManager routing test
  asserts convertError is classifyKimiQuotaError on the kimi-anthropic
  route and absent for plain anthropic; the legacy provider threads
  options.convertError to its generate catch.

* test(kosong,agent-core-v2): cover KimiFiles quota 429 and drop stale docs

Fourth review round on #1857:

- Drop the AnthropicHooks member JSDoc (its content already lives in the
  anthropic.ts and anthropicHooks.ts file headers) and fix the anthropic
  contrib header still calling the hook set single-hook.
- Add the missing KimiFiles regression in both engines: a mocked files
  client rejecting with a Moonshot quota 429 makes uploadVideo reject
  with the non-retryable APIProviderQuotaExhaustedError, locking the
  classifyKimiQuotaError argument at the upload catch sites.
2026-07-28 14:35:12 +08:00
Luyu Cheng
0cef160c4b
fix(agent-core): continue goal pursuit when a goal turn hits the per-turn step limit (#2210)
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
Release / Native release artifact (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 / 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 / Publish native release assets (push) Blocked by required conditions
* fix(agent-core): continue goal pursuit when a goal turn hits the per-turn step limit

* chore(agent-core-v2): regenerate state manifest

* fix(agent-core): start goal pursuit when the goal-creating turn hits the step limit

* refactor(agent-core-v2): drop step-cap narration from goal module and test
2026-07-27 00:30:33 +08:00
Luyu Cheng
c497af60e6
fix(tui): steer user messages into the running turn while a goal is active (#2153)
Some checks failed
CI / build (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test (5) (push) Has been cancelled
CI / test-pi-tui (push) Has been cancelled
CI / test-windows (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
Nix Build / nix build .#kimi-code (push) Has been cancelled
Release / Deploy docs (push) Has been cancelled
Release / Native release artifact (push) Has been cancelled
Release / Publish native release assets (push) Has been cancelled
* fix(tui): steer user messages into the running turn while a goal is active

* fix(tui): reset request state when steering mid-goal input fails

* fix(agent-core): update session prompt metadata on steer
2026-07-25 02:12:24 +08:00
Liu Zhongnuo
f06eb5c60e
feat(agent-core): defer registered user tools (#2119)
Some checks are pending
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
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 / build (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(agent-core): defer registered user tools

Allow hosts to mark registered user tools as deferred so select_tools can discover and load their schemas on demand in both agent-core implementations.

* fix(agent-core): hide unregistered deferred tools

Filter loaded deferred user-tool schemas from provider history and loaded-state checks after unregister while preserving canonical history and disconnected MCP behavior.

* fix(agent-core-v2): drop stale inline schemas

Stop treating previously loaded user-tool schemas as active after the same tool is re-registered for inline disclosure.
2026-07-24 17:48:36 +08:00
Kai
66f611aae9
fix: echo thinking under the reasoning field the endpoint actually uses (#2104)
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 / 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 / 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
OpenAI-compatible endpoints disagree on the wire field for reasoning
content: `reasoning_content` (DeepSeek/Moonshot convention, older vLLM)
vs `reasoning` (OpenAI GPT-OSS guidance, current vLLM — which also only
accepts `reasoning` on the request side, vllm-project/vllm#38488).
Reading only `reasoning_content` silently dropped thinking, and the lost
thinking then never made it back into later requests.

Add per-endpoint dialect detection: inbound responses are scanned in
priority order (reasoning_content, reasoning_details, reasoning; first
string value wins), the carrying key is remembered, and outbound
messages echo thinking under the same key (default reasoning_content).
An explicit `reasoningKey` / `reasoning_key` config still pins the
dialect.

- kosong: shared reasoning-key module; wire the kimi and openai-legacy
  providers; the dialect cell is shared across per-step provider clones.
- agent-core-v2: same mechanism in the vendored openai-legacy base; the
  kimi trait no longer pins reasoning_content (the default already is),
  keeping Moonshot wire behavior byte-identical while adapting to vLLM.
- agent-core: memoize the base provider behind ConfigState.provider so
  the detected dialect survives across turns instead of being rebuilt
  per access.
2026-07-23 21:15:00 +08:00
7Sageer
5fdbdb4a22
feat: configure web search/fetch services via KIMI_WEB_* env vars (#2096)
* feat: configure web search/fetch services via KIMI_WEB_* env vars

KIMI_WEB_SEARCH_BASE_URL / KIMI_WEB_SEARCH_API_KEY and
KIMI_WEB_FETCH_BASE_URL / KIMI_WEB_FETCH_API_KEY overlay the
[services] config section field by field in both engines (env wins
over config.toml), so the WebSearch and FetchURL backends can be
pointed at a Moonshot service without OAuth login. The v2 engine now
also honors the explicit [services.moonshot_fetch] section
(config > managed OAuth > local), which it previously parsed but
never consumed.

* fix(agent-core-v2): guard stripServicesEnv against clearing the services section

config.replace(SERVICES_SECTION, undefined) — the logout deprovisioning
path in authService — passes undefined straight into the section strip,
and the composed stripServicesEnv dereferenced it (value[key]), throwing
TypeError and aborting logout mid-cleanup. Add the same isPlainObject
guard the other strip implementations (stripProvidersEnv,
stripEnvBoundFields) already have, plus a regression test that also
locks in the env overlay staying effective after the file value is
cleared.

* style(agent-core-v2): follow header-only comment convention

* fix: isolate env web service credentials

* Add env vars for web search and fetch services

The new environment variables `KIMI_WEB_SEARCH_BASE_URL`, `KIMI_WEB_SEARCH_API_KEY`, `KIMI_WEB_FETCH_BASE_URL`, and `KIMI_WEB_FETCH_API_KEY` take priority over the corresponding fields in `config.toml`. The `kimi web` backend now honors the `[services.moonshot_fetch]` config section.

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

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-07-23 17:36:16 +08:00
7Sageer
527d485d92
feat: add global default MCP server timeout configs (#2065)
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 / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat: add global default MCP server startup timeout config

Add a `[mcp] startup_timeout_ms` config.toml section with a
`KIMI_MCP_STARTUP_TIMEOUT_MS` env override as the global default MCP
server connection (startup + tool discovery) timeout. Precedence:
per-server `startupTimeoutMs` in mcp.json > env var > config.toml >
built-in 30s default.

* feat: add global default MCP tool call timeout config

Extend the `[mcp]` section with `tool_timeout_ms` and the
`KIMI_MCP_TOOL_TIMEOUT_MS` env override as the global default for
single MCP tool calls, mirroring the startup timeout: a per-server
`toolTimeoutMs` in mcp.json still wins, and unset entries fall back to
the SDK built-in 60s default.

* chore: shorten the mcp timeouts changeset

* feat(agent-core): add global default MCP server timeout configs

Port the `[mcp]` section (`startup_timeout_ms` / `tool_timeout_ms`)
and the `KIMI_MCP_STARTUP_TIMEOUT_MS` / `KIMI_MCP_TOOL_TIMEOUT_MS` env
overrides to agent-core (v1), mirroring the v2 semantics: per-server
fields in mcp.json > env vars > config.toml > built-in defaults. The
v1 TOML loader gains explicit `mcp` read/write mappings, and both
connection-manager construction sites (Session, testGlobalMcpServer RPC)
pass the resolved defaults through.

* fix(agent-core): validate and apply MCP timeout defaults

* fix: propagate MCP startup timeout to SDK requests

* refactor(agent-core-v2): resolve MCP default timeouts at connect time

Keep the session connection manager synchronously lazy instead of gating
its existence on config readiness: resolveDefaultTimeouts is read from the
mcp config section at each (re)connect, so AgentMcpService's eager
construction stays unconditionally safe and reconnects pick up changed
preferences. The initial connect still awaits config.ready for a
deterministic snapshot. Also restore the ISessionMcpService method docs,
bump the changeset to minor, and fix the v1 env-parse comment.

* chore(agent-core-v2): regenerate config manifest for the mcp section

* Add global default MCP server timeouts configuration

Specify the new global default MCP server timeouts in both the config file and environment variables.

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

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-07-23 12:41:08 +08:00
github-actions[bot]
8bf5bacba9
ci: release packages (#1989)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-22 17:24:39 +08:00
Haozhe
8250e590f3
docs(cron): drop references to the non-existent kimi resume command (#2050)
* docs(cron): drop references to the non-existent `kimi resume` command

- point user docs at the real resume command `kimi --session`
- reword cron tool descriptions and code comments in agent-core and
  agent-core-v2 to describe session resume without naming a subcommand

* chore: add changeset for cron docs wording fix
2026-07-22 16:59:08 +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