Skip to content

fix(agent-core-v2): harden plugin load, install, and update check paths#1576

Open
wbxl2000 wants to merge 633 commits into
mainfrom
fix/v2-plugin-robustness
Open

fix(agent-core-v2): harden plugin load, install, and update check paths#1576
wbxl2000 wants to merge 633 commits into
mainfrom
fix/v2-plugin-robustness

Conversation

@wbxl2000

Copy link
Copy Markdown
Collaborator

Related Issue

None — the problems are explained below.

Problem

The agent-core-v2 plugin module (ported from packages/agent-core) had a set of robustness and parity regressions, found by a static diff review against v1:

  • A corrupt or unreadable plugins/installed.json took down whole sessions: the session skill catalog's ready stayed permanently rejected (skill list and the Skill tool failed), the main agent failed every turn while rendering plugin session-start content, and the plugin load error surfaced mislabeled as MCP_STARTUP_FAILED. In v1 this degraded silently and only management calls failed with a coded error.
  • Zip/github installs leaked kimi-plugin-zip-* temp dirs on failure and reported the soon-to-be-deleted temp path (instead of the user's URL) in manifest errors; a failed install or persist could also destroy the previous managed copy.
  • Stdio plugin MCP servers lost the managed Kimi endpoint env (KIMI_CODE_BASE_URL / KIMI_CODE_OAUTH_HOST) that v1 injected.
  • checkUpdates failed wholesale when a single repo errored, and reported phantom updates for branch/SHA-pinned installs.
  • After a cold resume, the first turn re-injected reminders the restored history already carried (silent wire replay never reached the injector's position tracking); unrelated skill changes also re-appended plugin session-start reminders.
  • PluginSummary.skillCount used a hand-rolled counter that diverged from real skill discovery (root SKILL.md fallback, nested sub-skills, frontmatter validation, name dedup).

What changed

All fixes are covered by new or updated tests (30+ cases); the full package suite passes (231 files / 3090 tests), plus typecheck (agent-core-v2 and kap-server), lint:domain, and git diff --check.

Plugin module (src/app/plugin/):

  • Load failure handling — consumption-plane reads (skill roots, session starts, MCP servers, hooks) degrade to empty so sessions keep working; management calls throw plugin.load_failed with the v1 repair hint (Fix the file at <home>/plugins/installed.json and run /plugins reload); a successful reloadPlugins() clears the error. The initial load is shared across concurrent callers and mutations are serialized.
  • Coded errorsgetPluginInfo and the manager's not-installed paths throw plugin.not_found; the contract now returns PluginInfo (non-optional), matching the wire contract.
  • Install robustness — temp dirs are cleaned on every failure path; zip/github manifest errors cite the original source (local-path errors keep the real path); install/persist failures roll back to the previous managed/<id> copy; records are swapped only after a successful persist.
  • Managed endpoint env — stdio plugin MCP servers again receive KIMI_CODE_BASE_URL / KIMI_CODE_OAUTH_HOST with v1 precedence (explicit env > KIMI_OAUTH_HOST legacy > persisted provider).
  • Update checks — concurrent per-repo checks with failure isolation and 10s timeouts; branch installs are pinned to the resolved commit SHA (via GitHub's Atom feed, still avoiding the REST API quota) and compared by SHA; tag/SHA pins no longer report phantom updates.
  • Skill countingskillCount now goes through the same discovery as the plugin skill source.

Supporting seams outside src/app/plugin/ (each is a small, generic change — no plugin-specific logic leaks into core):

  • contextInjector re-syncs injection positions from the real history when a wire restore completes (onRestored), fixing duplicate injections after cold resume for all variants, not just plugins.
  • ISessionSkillCatalog.onDidChange now carries the refreshed source id, so plugin session-start reminders fire only after the plugin source actually re-pulls (listening to onDidReload directly would race the async catalog refresh).
  • Skill discovery dedup keys are plugin-scoped (plugin.id + name) so same-named skills from different plugins no longer shadow each other; the scanner is exported for the standalone manager's default.
  • IProviderService gained ready so the managed-env lookup can await config load.

Points worth a deliberate look during review:

  • Update-check semantics: branch pins track the tracked ref's commit SHA; tag/SHA pins never report updates; a repo that fails to check is silently skipped rather than failing the whole call. These were open product questions and are the semantics this PR chose.
  • Managed env source: read through the bootstrap startup env snapshot to keep v1 precedence; this is a narrow exception to the "business domains never read bootstrap env" layering rule (v1 read process.env directly).
  • Known follow-ups (not in this PR): the provider-config base URL path is not trailing-slash-stripped (only the env path is, matching v1); if the Atom-feed SHA resolution fails, the install currently fails rather than falling back to the ref tarball.
  • Out of scope on purpose: hot MCP reconnect after plugin reload, plugin command snapshot semantics, auto-reload on ordinary resume (v1 does not reload either), and /api/v1 plugin surface alignment.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update. (Internal v2 engine fixes; no user-facing CLI behavior change.)

kermanx and others added 30 commits July 8, 2026 15:11
- write `kimi version <version>` to stderr first in text mode
- emit a `system.version` meta line first in stream-json mode
- gate behind isKimiV2Enabled() so the default v1 path is unchanged
…cap to 3000px (#1460)

* fix(agent-core): report EXIF-rotated image dimensions and raise edge cap to 3000px

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

* test: shrink oversized image fixtures to fit CI timeouts

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* test: shrink heavy JPEG fixtures and add explicit timeouts

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

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

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

* chore(changeset): consolidate image compression changesets

One entry covering the cap raise and the EXIF dimension fix, listed
for both the CLI and the SDK so the SDK changelog's compression
description (previously pinned at 2000px) stays accurate.
Align progressive tool disclosure with the discard-on-compaction model:
compaction no longer rebuilds loaded dynamic tool schemas. The boundary
announcement re-lists every loadable name, the model re-selects what it
still needs, and a from-memory call to a no-longer-loaded tool is
rejected by preflight with select guidance.

This removes the keep-all rebuild and its half-trigger budget heuristics
entirely: the post-compaction floor is back to users + summary, which is
structurally outside the auto-compaction trigger band, and the guard
baseline degenerates to summary + reinjected reminders. Every downstream
mechanism already treated the empty loaded set as its consistent base
state (ledger scan, pending clear at the compaction boundary, deferred
extras, preflight wording), so this is a strict simplification.

Co-authored-by: fengchenchen <fengchenchen@moonshot.ai>
Headless (`kimi -p`) failures could exit with code 0 when the event loop
drained during the shutdown cleanup (e.g. telemetry's unref'd retry backoff
when the network is blocked), because the rejection never reached the
process.exit(1) call. Set the failure exit code before any await in both
the run-prompt catch and the main catch, and keep the cleanup timeout ref'd
so the loop stays alive long enough for the rejection to propagate.
* feat(web): redesign cron reminder as a message bubble

Restyle the cron trigger notice as a right-aligned user-style message bubble that shows the scheduled prompt in full (wrapping across lines), with a small meta row beneath it for the schedule, status, job id and run time. Extract a shared MessageTime component used by both user messages and the cron reminder so the timestamp format and click-to-expand behavior stay consistent, and give the CronCreate/CronList/CronDelete tools distinct calendar icons.

* refactor(web): render cron reminders only as standalone turns

Remove the embedded cron block path from the web transcript projector so cron reminder fires always render through the standalone right-aligned bubble path.

* chore(web): simplify cron redesign changeset
)

* fix(web): composer model switch also updates global default model

The composer model switcher still switches the active session's model via
POST /sessions/{id}/profile (awaited, so the model pill reflects the result),
and additionally fires POST /api/v1/config with { default_model } as a
fire-and-forget side effect so new sessions inherit the chosen default. The
config request is skipped when the model already matches the current default.

* fix(web): route ModelPicker overlay selection through the default-model update

The overlay opened from the composer's "More models" row (and /model) is a
continuation of the same switch flow, so its selection now also bumps the
global default model instead of only switching the active session.

* fix(web): only persist the default model after a confirmed session switch

setModel now returns whether the switch was accepted (true for the draft
path), so the composer flow no longer writes a stale or invalid model alias
into the global config when the session-level switch failed and rolled back.
- port probeLoginShellPath/mergeLoginShellPath/applyLoginShellPath into
  _base/execEnv/loginShellPath.ts as a pure helper (no DI)
- export execFileText from environmentProbe for reuse by the probe
- run applyLoginShellPathFromNode concurrently with the host probe in
  HostEnvironmentService, mirroring kaos LocalKaos.create()

Aligns agent-core-v2 with kaos 021786f so the Bash tool finds
user-installed tools (e.g. Homebrew's gh) when kimi-code is launched
from a GUI or non-login shell.
- protocol: add optional agent_filter to client_hello and subscribe
- kap-server: carry per-subscription agent allowlists through the broadcaster
  and connection, narrowing live fan-out and replay to selected agents while
  keeping a single global sequence and bypassing the filter for global events
- agent-core-v2: degrade MiniDbQueryStore to a no-op read model when the
  query-store lock is held by another process instead of crashing the host
kermanx and others added 28 commits July 10, 2026 19:07
Since d5e1d76 every wire append rides the async persist queue whenever
a blob service is registered, but AgentWireRecordService.flush() only
awaited the log store. Callers - including the session-close path -
could complete a flush while records were still in flight on the queue,
and record-order assertions in tests raced it. Await the wire service
flush, which drains the persist queue, before flushing the log.
…ontinuation driver

The per-turn-boundary reminder test predates the goal continuation
driver and never passed: its second explicit prompt raced the
auto-launched continuation turn, which correctly holds the turn lane.
Treat the continuation turn as the second boundary and end it
deterministically by completing the goal through UpdateGoal, keeping
the once-per-boundary (never per-step) assertion.
…exhausted

Previously a goal whose hard budget was reached mid-turn was only
flipped to blocked while the turn kept running unbounded: the loop
continues unconditionally after a tool-calls step, and steer flushes or
Stop hooks could extend the turn indefinitely past the budget.

Now, when the over-budget step requested tool calls, a goal_budget_stop
system reminder is appended after the tool results telling the model the
goal is blocked (resumable via /goal resume), to stop immediately, that
further tool calls will be rejected, and to write a brief final status
message. The model gets exactly one grace step, during which tool calls
are answered with a soft rejection instead of executing. After the grace
step - or when the over-budget step had no pending tool calls - a new
AfterStepContext.stopTurn flag ends the turn, honored by the loop with
precedence over tool_calls and hook-set continue so nothing can extend
past the stop. The grace grant also respects maxStepsPerTurn so it can
never turn a budget stop into a max-steps turn failure.

Turn launch is gated as well: a prompt arriving while the active goal is
already over budget (e.g. after resuming an exhausted goal) blocks the
goal before the turn is marked goal-driven, so turnsUsed no longer
drifts, no spurious goal_continued telemetry fires, and the prompt runs
as an ordinary turn with the blocked-goal note injected.

This deliberately diverges from agent-core v1, which hard-stops with
zero grace and answers a prompt on an exhausted goal with a synthetic
model-less turn: budget overshoot is now bounded at one closing step in
exchange for consumed tool results and a user-facing wrap-up message.
- remap tool_calls finishReason to 'other' when the provider emitted no tool call structure
- prevents re-issuing the model call until maxSteps on a bare tool_calls signal
- add loop test covering the v1 'unknown' turn-lifecycle behavior
The v2 engine emits background-task lifecycle as `task.started` /
`task.terminated`, but v1 consumers (kimi-code TUI / `kimi -p`, node-sdk)
only handle `background.task.*` and silently dropped every task event when
talking to server-v2, while kimi-web handles the native spelling and has the
legacy one registered as known-but-unhandled.

Fan the legacy spelling out next to the native event in
SessionEventBroadcaster, reusing the same volatility so replay, journal and
the per-agent filter stay coherent between the two. kimi-web keeps the native
event and ignores the alias; the native /api/v2 stream is left unchanged.

Add a SessionEventBroadcaster test asserting both spellings are emitted.
- add Session-scope ISessionInitService that spawns the coder subagent,
  mirrors the run onto the main agent, reloads AGENTS.md and appends an
  init-variant system reminder, then flushes records
- add SESSION_INIT_FAILED error code and register the sessionInit domain in
  the layer map, package index and DI dependency graph
- drop the stale "flat (no subdirectories)" rule from the agent-core-dev skill
- replace per-method actionMap (resource:action) with a channel registry: each Service registers once by decorator id and all methods are invoked by reflection
- routes move from /api/v2/:sa to /api/v2/:service/:method across HTTP routes and the WS protocol
- add @moonshot-ai/klient: typed core/session/agent client over the HTTP channel that reuses agent-core-v2 service interfaces
- register klient in flake.nix and pnpm-lock; refresh kap-server tests, e2e, and the apiSurface snapshot
- add StepRequest / StepRequestQueue: the loop drains one batch per step,
  folding mergeable requests (steers) into the driver's step; a step that
  ran tools enqueues a ContinuationStepRequest, a plain message enqueues
  none, so the turn completes when the queue empties
- replace AfterStepContext.continue with explicit enqueue; a failed step is
  retried by head-inserting its driver request
- move prompt's private steer queue onto the loop via PromptStepRequest /
  SteerStepRequest / RetryStepRequest, which materialize their context
  messages at pop time (image-compression captions reroute to reminders
  on materialization)
- goal and externalHooks orchestrate continuations by enqueueing requests
  instead of setting ctx.continue; a request's message only lands when the
  loop pops it, so skipped or aborted launches leave no orphan messages
- remove the now-unused CancellationError
- delete the reworked turn tests (turn.test.ts, turn-ready.test.ts) and the
  cron test suites
- add translateProviderError in app/protocol/errors and apply it once in
  ModelImpl.request: raw provider failures become coded KimiErrors with the
  raw error preserved as cause and HTTP fields in details; abort shapes pass
  through untouched
- move provider-error mapping out of _base/errors/serialize so _base no
  longer imports llmProtocol; toErrorPayload/fromErrorPayload now round-trip
  cause chains recursively, capped at depth 8
- move context.overflow from LoopErrors to ProtocolErrors (wire code
  unchanged); move LoopError and the max-steps helpers into loop/loop.ts
- consolidate isAbortError into _base/utils/abort, dropping the duplicates in
  retry, cloudTransport, and the question/subagent task tools
- add unwrapErrorCause; classify retryability and HTTP status on the
  unwrapped cause in llmRequester and full-compaction
- align task-notification tests with enqueue-only delivery: drain the loop
  queue with one turn and assert on task.notified instead of prompt steer
- protocol: add recursive cause to KimiErrorPayload with a lazy zod schema
- add commit-align.md subskill: triage one main-branch commit against v2
  (aligned / partial / missing / not-applicable) and link it from SKILL.md
- delete docs/di-scope-domains.puml and the rendered svg, and drop the
  keep-the-map-in-sync requirement from verify.md, align.md, commit-align.md,
  and packages/agent-core-v2/AGENTS.md
- make loop admission, cancellation, and completion own turn execution
- extract step retry into a loop error recovery service
- preserve failed-step context and expose retry delay events
- add queued turn and step lifecycle handles with explicit admission modes
- move continuation and retry scheduling behind the loop service
- consolidate legacy prompt scheduling into the prompt domain
- align kap-server routes and tests with the new loop contract
- add WsSocket: persistent /api/v2/ws transport with hello handshake,
  heartbeat answers, per-call timeouts, and auto-reconnect that
  re-subscribes active listens; bearer token rides the
  kimi-code.bearer.<token> subprotocol for browser compatibility
- add WsKlient / WsChannel exposing core/session/agent scopes and
  listen(event, handler) over the shared socket
- add Klient#ws() lazy singleton with WebSocketImpl injection
- bind global fetch in HttpChannel to avoid "Illegal invocation" in browsers
- loop: beforeStep -> onWillBeginStep, afterStep -> onDidFinishStep
- toolExecutor: onWillExecuteTool -> onBeforeExecuteTool (ToolWillExecuteContext -> ToolBeforeExecuteContext)
- prompt: onWillSubmitPrompt -> onBeforeSubmitPrompt
- permissionMode: onChanged -> onDidChangeMode
- wireRecord: onRestoredRecord -> onDidRestoreRecord, onResumeEnded -> onDidFinishResume
- terminal: onData -> onProcessData, onExit -> onProcessExit
…models

- GET /api/v1/models now awaits refreshProviderModels({ scope: 'all' })
  before returning the model list, so the response always reflects the
  latest provider model metadata
- refresh failures are logged and swallowed, falling back to the
  persisted catalog instead of failing the request
Replace fire-and-forget OrderedHookSlot hooks with Emitter/Event-based
notifications for consumers that only observe, never intercept:

- usage: hooks.onDidRecord -> onDidRecord event
- permissionMode: hooks.onDidChangeMode -> onDidChangeMode event
- fullCompaction: hooks.onDidFinishCompaction -> onDidFinishCompaction event
- wireRecord: hooks.onDidFinishResume -> onDidFinishResume event
- agentLifecycle: hooks.onDidStopAgentTask -> onDidStopAgentTask event,
  announced via new notifyAgentTaskStopped() called by mirrorAgentRun

Interception-capable slots (onWillStartAgentTask, onWillCompact,
onDidRestoreRecord) stay as ordered hooks.
… when logged in

When the managed Kimi provider has an oauth ref, WebFetchService builds a MoonshotFetchURLProvider (bearer token + host identity headers) with the local fetcher as fallback, re-reading login state on every call; logged-out setups keep the local fetcher.
…ests

WebSearchProviderService now passes the host's IHostRequestHeaders (User-Agent + X-Msh-* device identity) as default headers to the Moonshot search provider, mirroring v1's kimiRequestHeaders.
The v2 boot path (kimi server run with the experimental flag) now seeds the CLI's Kimi identity headers (User-Agent + X-Msh-* device identity) into the engine through kap-server's seeds option, so outbound model, WebSearch, and FetchURL requests carry the same identity as direct CLI runs. kap-server's own package version is 0.0.0, so the identity has to come from the CLI.
- task: idle terminal notifications now use activeOrNewTurn admission,
  launching their own turn instead of waiting for the next user prompt
  (matches v1 turn.steer)
- workspaceRegistry: createOrTouch rejects missing or non-directory roots
  with fs.path_not_found, so a phantom cwd never reaches session creation
- kap-server: map FS_PATH_NOT_FOUND to protocol error 40409 on the session
  and RPC surfaces
- server-e2e: migrate the v2 smoke test from the local ServerClient to the
  typed Klient
- misc: switch loop/prompt clear() iteration to .slice(), align terminal
  event handler names, and tidy klient examples
The v1 WS broadcaster only re-emitted event.session.status_changed(running)
on turn.started and never emitted the idle/aborted transition on turn.ended.
kimi-web treats that event as the single source of session status (its
turn.ended projector deliberately does not synthesize idle), so a session
stuck at 'running' after the turn finished — most visibly for background
tasks, where ISessionActivity keeps reporting non-idle while the detached
task lives and even a REST pull never corrected it.

Re-emit event.session.status_changed after turn.ended on the same dispatch
queue, mapping reason cancelled/failed/blocked to aborted and otherwise
idle (previous_status 'running'), matching v1's _computeStatus. Update the
broadcaster tests and harden the wsV1Resync test helper so non-matching
frames no longer strand a waiter's timeout.
- listen messages accept a service name; kap-server resolves the Service
  via resolveService and subscribes through its onUpperCase member
- add listen_result acknowledgement and per-listen error reporting
  (onDidListenError) so failed subscriptions surface to the client
- support onWill-style events: payload carries eventId/signal/waitUntil,
  the client replies with event_result and the server can event_cancel
- klient proxy maps onUpperCase members to channel.listen; WsChannel
  shares one remote subscription across first/last listeners
- channel.call now forwards the complete argument array
- Degrade plugin consumption reads to empty when installed.json fails to
  load, surface plugin.load_failed with a repair hint on management calls,
  and recover after an explicit reload; serialize the initial load and
  mutations so concurrent first callers share one load.
- Clean up zip temp dirs on every failure path, report the original
  source in zip/github manifest errors, and roll back to the previous
  managed copy when an install or persist fails.
- Restore managed Kimi endpoint env injection for stdio plugin MCP
  servers.
- Check plugin updates concurrently with per-repo failure isolation and
  10s timeouts, track branch installs by commit SHA, and stop false
  update reports for tag/SHA pins.
- Throw plugin.not_found from getPluginInfo and the manager's
  not-installed paths.
- Count plugin skills through the real skill discovery path.
- Re-sync context injection positions after silent wire replay so cold
  resumes do not duplicate injections, and fire plugin session-start
  reminders only when the plugin skill source finishes refreshing.
@changeset-bot

changeset-bot Bot commented Jul 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2727720

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@moonshot-ai/agent-core-v2 Patch
@moonshot-ai/kap-server Patch
@moonshot-ai/klient Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Base automatically changed from kimi-code-v2 to main July 12, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants