Skip to content

Feat/astron coding plan#2000

Open
7723qqq wants to merge 299 commits into
MoonshotAI:mainfrom
7723qqq:feat/astron-coding-plan
Open

Feat/astron coding plan#2000
7723qqq wants to merge 299 commits into
MoonshotAI:mainfrom
7723qqq:feat/astron-coding-plan

Conversation

@7723qqq

@7723qqq 7723qqq commented Jul 21, 2026

Copy link
Copy Markdown

Related Issue

Resolve #(issue_number)

Problem

What changed

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.

Kimi Agent and others added 30 commits June 28, 2026 01:11
Bumps @moonshot-ai/kimi-native-tools, @moonshot-ai/agent-core, and
@moonshot-ai/kimi-code at patch level. The wording is intentionally
written for the CLI user surface so the release notes line up with
the user-visible effect: bash/grep/structured-grep move off the
Node event loop, in-process RPC becomes opt-in via the experimental
flag, and session resume becomes cheaper by skipping the redundant
existence lookup.
…-arg O(n^2)

Two optimizations bundled because both touch openai-responses.ts
in adjacent code paths and the split would have produced a
half-broken intermediate state.

1. Shared undici Agent

Add packages/kosong/src/http/undici-agent.ts exporting
createSharedAgent() and createSharedFetch(). Both return the
same process-wide undici Agent on every call, configured with:

  - keepAliveTimeout: 60s — long-lived idle keep-alive so the
    next turn reuses the same TCP+TLS session as the previous
    one.
  - connections: 64 — bounded pool so a runaway caller cannot
    open hundreds of sockets to the same origin.
  - connectTimeout / headersTimeout / bodyTimeout: explicit so a
    dead host fails fast instead of pinning a turn's socket.
  - pipelining: 1 — opt-in for HTTP/1.1 pipelining if a provider
    ever supports it (Anthropic and OpenAI do not today).

Wire the shared Agent into the two OpenAI SDKs that already
accept a custom httpClient (openai v6 client):

  - providers/openai-legacy.ts (chat completions): default
    options.httpClient to the shared Agent when the caller does
    not pass one explicitly. Existing callers that pass
    httpClient keep their override.
  - providers/openai-responses.ts (responses API): same default.

The Anthropic SDK has no httpClient hook, so route through the
SDK's fetch option with createSharedFetch() — a fetch wrapper
that forwards every call to undici with the shared dispatcher.

The Google GenAI SDK exposes neither fetch nor httpClient; it
is left on the default global fetch for now. That provider is
the smallest share of agent-core workloads, so leaving it on
the default is a measured tradeoff rather than a regression.

Expected user-visible effect: the first LLM call of a fresh
CLI session pays roughly 50-150 ms less for TCP+TLS setup;
subsequent calls in the same session reuse the warm connection.

2. Tool-call argument streaming without O(n^2)

providers/openai-responses.ts accumulates streamed tool-call
arguments via string concatenation (each delta runs
``get() + delta``, copying the full accumulated string).
For a tool call streaming N deltas of total length L, that is
O(N^2 · L) of memory traffic.

Switch to a per-stream-index array of chunks:

  - The map now holds `string[]` instead of `string`.
  - `appendFunctionCallArguments` pushes to the array (amortized
    O(1) per delta).
  - `getFunctionCallArguments` joins the array only on demand
    (once per tool call, in `yieldFinalArgumentsSuffix`).
  - `replaceFunctionCallArguments` swaps in a single-element
    array when the SDK hands us the full final string, so the
    diff against the streamed prefix stays correct.

Behavior is identical: the consumer sees the same complete
string at the same point in the stream. Empty-string replaces
keep the array (never delete the entry) so a subsequent delta
or final-suffix still has somewhere to land.

Expected user-visible effect: large streamed tool arguments
(e.g. long shell commands or URLs) allocate O(N) instead of
O(N^2 · L) bytes, saving hundreds of ms of CPU and GC pressure
on calls that stream >1 KB of argument text.

Dependency: add undici ^7.27.0 to packages/kosong. This matches
what is already in the lockfile from agent-core's transitive
deps, so no new fetch is required.
Two changes that together stabilize Anthropic's prompt cache
across calls within a session.

1. Cache_control TTL option

The Anthropic SDK has long accepted prompt-cache TTL hints
beyond the default `ephemeral` (5-minute idleness recycle),
but the kosong adapter hardcoded `ephemeral` at every
breakpoint. Expose a new `cacheControlTtl` option on
`AnthropicOptions` accepting `'ephemeral' | '5m' | '1h'`,
defaulting to `ephemeral` to preserve existing behavior.

The TTL is threaded into all three cache_control sites:

  - The system prompt text block.
  - The last content block of the last message (via the
    existing `injectCacheControlOnLastBlock` helper, which
    now takes a `ttl` argument).
  - The last tool in the tools array (newly stamped once at
    conversion time, see below).

The SDK type only declares the `ephemeral` shape on
`cache_control`, but the wire protocol has accepted `5m`
and `1h` for a long time. The few SDK-side assignments
cast through the typed `{ type: 'ephemeral' }` shape so the
type checker stays strict while the runtime values flow
through unchanged.

The cheap per-read price of `1h` is meaningfully lower than
`ephemeral` for long compaction-heavy sessions, where the
same prompt prefix is reused across many turns.

2. Memoized AnthropicToolParam[]

Previously, every `generate()` call rebuilt
`AnthropicToolParam[]` from scratch, then stamped
`cache_control` on the last entry. Anthropic's prompt cache
matches exact-prefix bytes, so any allocation churn
(different object identity, different property order, etc.)
invalidates the cache for the entire prompt.

Extract a `_getOrBuildToolsParam` private method that:

  - Computes a stable JSON fingerprint of the input tool set,
    sorting by tool name first so the same set registered in
    different orders fingerprints identically.
  - Returns the previously cached `AnthropicToolParam[]` when
    the fingerprint matches.
  - Otherwise converts, stamps `cache_control` once on the
    last tool, and caches.

Subsequent calls within a session hit the cached array by
reference, so the wire bytes are byte-identical and Anthropic's
prompt cache stays warm across turns.

Tests: packages/kosong/test/anthropic-cache-control.test.ts
covers the TTL default + override paths, fingerprint
stability across registration order, array reuse, cache
invalidation when the tool set changes, and the cache_control
stamp on the last tool (or absence when no tools are present).

Expected user-visible effect: TTFT on the second and
subsequent turns of a long session drops from seconds to
hundreds of ms (cache hit) when the same prompt prefix is
reused. Sessions with a stable tool set across turns see the
biggest win.
Two small follow-ups from the upstream-main sync:

- test/services/message-transcript.test.ts: the MessageService now
  calls rpc.resumeSession directly (the previous
  listSessions + resumeSession round-trip was removed by the
  perf commit that landed alongside the empty-snapshot
  fixture). Update the rpc stub to make resumeSession return
  the same summary that listSessions used to, instead of
  returning undefined. All 20 tests in the file pass.

- migration-legacy/test/sessions/__snapshots__/fixtures.snapshot.test.ts.snap:
  refresh the snapshot directory names. The test creates a
  fresh random tmpdir per run via mkdtemp, so the captured
  names in the snapshot drift between runs. Refresh via
  pnpm vitest run -u so the next run matches.
Mirror the parts of fsSearchService that the TS side was
re-implementing in pure Node into Rust so the TS path can
delegate to a single compiled function instead of an array of
per-pattern regex compilations.

- src/glob.rs: add `glob_matches_any(globs, path) -> bool`.
  Compiles the input globs into a single `GlobSet` and tests
  the path once. Uses `.literal_separator(true)` and case-
  sensitive matching to mirror `globToRegExp` in
  `fsSearchService.ts` — `*` does not cross `/`, only
  `**` does. This also matches ripgrep's `--glob` semantics.
  Note: do not reuse `build_glob_matcher` from this file; it
  sets `.case_insensitive(true)` for the discovery path, which
  is not what fsSearchService wants.

- src/grep.rs: add the structured-grep surface that the fs:grep
  service in agent-core consumes. New types:
    - GrepStructuredMatch: line, col, text, before, after
    - GrepStructuredFileHit: path + matches
    - GrepStructuredResult: files, files_scanned, truncated,
      error
  Plus a counter for files scanned, and the start of the
  structured walker that fsSearchService will plug into.

  The structured walker is not yet wired into an napi binding;
  agent-core's fsSearchService still falls back to the TS
  implementation. The next commit will close that loop.

Tested locally: cargo build clean, cargo clippy clean, cargo
test 121/121 pass with these additions.
Fix Windows test failures caused by source code returning backslash-free
paths (via pathe.normalize) while test assertions expected native
backslashes, and the inverse case for POSIX-mocked platform tests.

- apps/kimi-code/test/native/cache-base.test.ts: replace backslashes with
  forward slashes before equality checks for the darwin/linux cases that
  are mocked on a Windows host
- packages/kaos/test/cmd.test.ts: add missing space before the redirect
  operator in the cmd.exe echo-redirect (Windows would otherwise treat
  the > as part of the literal text)
- packages/kaos/test/local.test.ts: normalize expected paths for getcwd,
  gethome, iterdir, glob, and isolation tests; add kaos.chdir(tmp) so the
  kill-tree test can find its pidfile; skip the kill-tree test on
  Windows where the process reaper races with tmpdir cleanup
- packages/server/test/fs-*.test.ts: broaden absolute-path checks to
  accept Windows drive-letter prefixes; strip leading ./ or .\ so the
  gitignore / grep result paths match by basename
Add platform guards so tests that exercise Unix-only semantics
(unix file mode bits, launchd plists, systemd units) skip cleanly
on Windows hosts where they cannot meaningfully run.

- packages/server/test/svc/launchd.test.ts: skip entire file on
  non-darwin hosts (macOS-only supervisor backend)
- packages/server/test/svc/systemd.test.ts: skip entire file on
  non-linux hosts (Linux-only supervisor backend)
- packages/server/test/services-auth.test.ts,
  packages/server/test/auth-wiring.e2e.test.ts,
  packages/server/test/lock.test.ts,
  packages/server/test/fs-browse.e2e.test.ts: skip individual cases
  that assert 0o600 / 0o700 file modes or chmod 0o000 / 0o700 setup
- packages/oauth/test/storage.test.ts: skip cases that assert 0o600
  token file mode and 0o700 credentials directory mode
- packages/migration-legacy/test/atomic-write.test.ts: skip the case
  that asserts 0o600 atomic-write target mode
- packages/agent-core/test/utils/per-id-json-store.test.ts,
  packages/agent-core/test/mcp/oauth-store.test.ts,
  packages/agent-core/test/agent/background/persist.test.ts: skip
  cases that assert private 0o600 / 0o700 store modes
The hook runner/integration tests had several commands that relied on
POSIX shell semantics that don't hold under cmd.exe on Windows:

- `echo '{...}'` — cmd.exe emits the literal single quotes around the
  payload, breaking JSON.parse in the runner.
- `echo 'msg' >&2; exit 2` — `>&2` is a bash-only stderr redirection.
- `#!/bin/bash` shebang in a script file — no bash guarantee on Windows.

Switch the affected cases to `node -e "..."` invocations that produce
identical output on every host. Keep the existing POSIX-`sh`-compatible
cases (e.g. plain `echo ok`, `exit 1`, `sleep 10`) that already work
cross-platform via `shell: true`.
The migrator's `computeWorkdirBucket` was importing `resolve`/`basename`
from `node:path`, while `agent-core`'s `encodeWorkDirKey` (which the
session picker uses to find migrated sessions) imports them from `pathe`.
On Windows hosts the two return different absolute paths for the same
input — `node:path.resolve('/Users/example/proj')` becomes
`C:\Users\example\proj`, while `pathe.resolve(...)` keeps POSIX form. The
result was that migrated sessions wrote under bucket
`wd_proj_<hash-of-C:\...>` but the picker looked up
`wd_proj_<hash-of-/Users/...>` — invisible until a manual re-migrate.

- Switch `src/sessions/workdir-bucket.ts` to use `pathe`, matching
  agent-core's normalization.
- Add `pathe` as a direct dependency (previously only transitive via
  the vitest tree).
- Update `test/sessions/workdir-bucket.test.ts` reference impl to use
  `pathe` so its byte-identical parity check holds cross-platform.
- Re-record `test/sessions/__snapshots__/fixtures.snapshot.test.ts.snap`
  with the new bucket hash and harden the redaction to escape JSON
  backslashes before splitting the temp-target path, so the snapshot
  stays stable on Windows going forward.
The skill scanner's `resolveSkillRoots` returns paths through `pathe`,
which on Windows emits forward slashes even when the host uses
backslashes. The tests compare against `realpath(path.join(...))`, and
`node:fs/promises.realpath` returns the OS-native backslash form — so
on Windows the assertions diverged by separator only.

Wrap each `realpath(path.join(...))` (and the bare `realpath(...)`
extras) with a `normalizedRealpath` helper that runs `pathe.normalize`
on the result. The 20 affected cases now pass on Windows hosts; the
POSIX behavior is unchanged because pathe.normalize on POSIX is a
no-op.
The SDK tests compared `SessionStore` / `resolveConfigPath` /
`exportSessionDirectory` outputs against `node:path.join` of the same
inputs. On Windows the source (which uses `pathe.join` internally) emits
forward slashes, but `node:path.join` keeps native backslashes — so the
assertions diverged by separator only.

- Switch `test/session-runtime-helpers.ts` and the six test files that
  exercise path-comparison to import `join` (and `normalize` where
  needed) from `pathe`, matching the source.
- Wrap `mkdtemp` results in `normalize` inside `makeTempDir` so the
  helper returns forward-slash paths on every host.
- Add `pathe` as a direct `devDependency` of `@moonshot-ai/kimi-code-sdk`
  (previously only transitive via vitest).
- `test/session-skills.test.ts`: normalize the `realpath` of the
  resolved skill dir before stringifying into the expected event.
- `test/config.test.ts`: include the new `native_tools` and
  `rpc_microtask` flags in the experimental-features `arrayContaining`
  check, matching the additions to the registry this branch introduced.
# Conflicts:
#	apps/kimi-code/src/tui/components/messages/status-message.ts
#	apps/kimi-code/test/scripts/native/paths.test.ts
#	apps/kimi-code/test/tui/components/editor/file-mention-provider.test.ts
#	apps/kimi-code/test/tui/components/panels/plan-box.test.ts
#	apps/kimi-web/test/lib-logic.test.ts
#	flake.nix
#	packages/agent-core/src/agent/compaction/full.ts
#	packages/agent-core/src/agent/tool/index.ts
#	packages/agent-core/src/profile/default/explore.yaml
#	packages/agent-core/src/services/fs/fsSearchService.ts
#	packages/agent-core/src/tools/builtin/file/glob.md
#	packages/agent-core/src/tools/builtin/file/glob.ts
#	packages/agent-core/src/tools/builtin/file/grep.ts
#	packages/agent-core/test/agent/harness/agent.ts
#	packages/agent-core/test/agent/plan.test.ts
#	packages/agent-core/test/hooks/engine.test.ts
#	packages/agent-core/test/hooks/integration.test.ts
#	packages/agent-core/test/hooks/runner.test.ts
#	packages/kaos/test/cmd.test.ts
#	packages/kaos/test/local.test.ts
#	packages/kosong/src/providers/anthropic.ts
#	packages/migration-legacy/src/sessions/workdir-bucket.ts
#	packages/migration-legacy/test/sessions/fixtures.snapshot.test.ts
#	packages/migration-legacy/test/sessions/workdir-bucket.test.ts
#	packages/node-sdk/test/session-skills.test.ts
#	packages/server/test/fs-browse.e2e.test.ts
#	packages/server/test/fs-path-safety.test.ts
#	packages/server/test/svc/launchd.test.ts
#	packages/server/test/svc/systemd.test.ts
#	pnpm-lock.yaml
- Add transient Xunfei error codes to retry: 10006-10011, 10012, 10110, 10222, 10223, 11202, 11203, 11210
- Remove 10015 (appid blacklisted, not transient)
- Normalize Xunfei rate-limit codes (11202, 11203, 11210) to APIProviderRateLimitError
- Add PROVIDER_STREAM_INTERRUPTED_MESSAGE_PATTERN for upstream stream ended errors
- Use longer backoff for rate-limit errors: 15s/30s/60s
- Disable Anthropic SDK internal retries (maxRetries: 0)
- Add transient error requeue to subagent batch scheduler (max 2 retries, 5s/10s backoff)
- Reconstruct kosong error types from KimiErrorPayload in runChildTurnToCompletion
- Add PROVIDER_STREAM_INTERRUPTED_MESSAGE_PATTERN to classifyAnthropicSdkError
- Add 5s/10s/30s backoff for 503/overload errors (between transient and rate-limit)
- Add 'system is busy' to PROVIDER_OVERLOAD_MESSAGE_PATTERN
- Import APIStatusError as value for instanceof check
…content moderation from retry

- convertAnthropicError: plain Error now goes through classifyAnthropicSdkError
  instead of being wrapped as ChatProviderError (which bypassed retry detection)
- classifyAnthropicSdkError: extract HTTP status code from reverse-proxy error
  messages like '429 {...}' or '500 {...}' that arrive as plain Error
- Add PROVIDER_CONTENT_MODERATION_PATTERN to exclude sensitive_words_detected,
  content filter, and safety policy errors from retry (permanent failures)
- Fix regex to match sensitive_words_detected with underscores
1.  将跨包的Event类型重命名为ProtocolEvent以避免和VSCode风格的Event类型冲突
2.  新增TurnStepRetryingEvent事件处理,添加CLI和TUI的重试状态提示
3.  修复Windows路径分隔符兼容问题,更新pnpm工作区配置
新增:
- 图片压缩裁剪原生能力
- 文本截断token原生实现
- 工具访问冲突检测原生逻辑
- 新增image_compress和tool_access模块
- 添加image依赖支持PNG/JPEG处理

优化:
- 工具调度器跳过无访问任务检测
- 重试机制添加抖动避免并发碰撞
- 流式消息拷贝优化性能
- 令牌计算缓存优化
- 工具结果目录自动清理

修复:
- 移除冗余的glob_matches_any函数
- 调整ReadMediaFileTool移除冗余尺寸计算
- 新增输出截断模块实现流式工具输出按行和总字符数限制
- 为NAPI绑定添加输出截断的JS接口
- 重构图片压缩/裁剪逻辑,添加alpha预乘/解乘避免透明色渗色
- 修复图片处理跳过EXIF旋转的问题,添加转置状态检查
- 优化图片压缩测试用例,放宽MIME类型和字节大小校验
* fix: keep prompt goals running until terminal

* fix: reject invalid prompt goal commands

* fix: ignore stale prompt goal status checks
…ibit (MoonshotAI#1518)

The r1/r2/r3 reminders injected into repeated tool results led with
prohibition verdicts and, in r2, echoed the repeated tool name and full
arguments back into the context, reinforcing the very pattern they were
meant to break. Rewrite them to state the situation factually and hand
the model a concrete next action: an expectation-setting sentence for
the next call (r1), a forced decision menu of falsify / ask-user /
conclude (r2), and a final hand-off summary without further tool calls
(r3). Detection, thresholds (3/5/8/12), force-stop, and telemetry are
unchanged.
…onshotAI#1501)

* feat(oauth): parse boosterWallet extra usage from /usages

* feat(oauth): expose extraUsage on AuthManagedUsageResult

* feat(kimi-code): render Extra Usage section in /usage panel

* fix(kimi-code): address Task 3 review feedback for extra usage section

* feat(kimi-code): render Extra Usage section in /status panel

* feat(kimi-code): wire extraUsage into /usage and /status commands

* chore(extra-usage): address final review findings for fuel pack feature

- Update changeset to cover both kimi-code and kimi-code-sdk packages

- Add parser clamp tests and toolkit null-case test

- Replace 'as never' casts in usage-panel tests

- Wrap long import line in status-panel

* chore: temporarily log /usages raw response for debugging

* fix(oauth): accept BOOSTER balance type and drop debug log

* fix(oauth): drop reset hint from Extra Usage and revert periodEnd parsing

* fix(oauth): treat missing amountLeft as zero extra usage and drop debug log

* revert: keep missing amountLeft defaulting to 0 (fully used)

* feat(extra-usage): show monthly cap usage bar and balance in /usage and /status

* fix(extra-usage): show balance and unlimited marker when no monthly cap

* fix(extra-usage): show monthly used with unlimited marker and balance

* fix(extra-usage): label Used and use English Unlimited

* feat(extra-usage): render balance, monthly used and monthly limit as labeled rows

* fix(extra-usage): move Balance row to the bottom

* fix(extra-usage): format currency values with two decimals for column alignment

* fix(extra-usage): right-align currency values so numbers line up

* fix(extra-usage): align currency symbol and decimal point in usage rows
* feat(kimi-web): add approval notification storage key and i18n copy

* feat(kimi-web): add approval notification helpers and tests

* feat(kimi-web): wire approval notifications and guard completion alerts

* fix(kimi-web): extract shouldNotifyCompletion helper and add tests

* feat(kimi-web): add approval notification settings toggle

* chore(kimi-web): add changeset and tidy notification module comment

- Align approval notification tag with spec (kimi-approval-${approvalId})

- Update module header to describe all three notification kinds

* fix(kimi-web): make notifications fire reliably

- Key completion notification tags by turn (sid + promptId) and question
  tags by request id, so a stale notification left in the notification
  center no longer swallows every follow-up alert in the same session
- Suppress notifications only while the window is actually focused, not
  merely visible (document.hasFocus() on top of visibilityState)
- Play the attention sound when a tool needs approval, matching the
  completion and question sounds

* chore(kimi-web): simplify changeset
…otAI#1522)

* fix(web): prevent duplicate first prompts and keep goal drives from looking idle

- Guard startSessionAndSendPrompt with a per-workspace reentry lock so a
  double-click / repeated Enter during draft-session creation cannot fire
  two concurrent first prompts into the same new session.
- Track goal.active in the agent event projector so turn.ended between
  goal-driven continuation turns keeps the session 'running' instead of
  projecting a false 'idle' that drains the local queue into a still-busy
  core (turn.agent_busy).
- Show a 'starting conversation…' loading state on the empty-session
  landing while the first prompt is being created and submitted.
- Persist the resolved model in startSessionAndActivateSkill so the first
  skill turn on a fresh session does not fail with 'Model not set'.

* chore: add changeset for web first-prompt fixes

* fix(web): close remaining first-prompt and goal-settle gaps

- Pass the starting guard through the dock composer: draft-session
  creation selects the new session before submit, which swaps the empty
  composer for the dock; disabling both composers closes the last path
  to a concurrent first POST. Also take the workspace lock in
  startSessionAndActivateSkill / startSessionAndOpenSideChat.
- Emit the owed idle when a goal settles (blocked/paused/completed) in
  the inter-turn gap after a turn.ended was projected as 'running', so
  sending state, in-flight flags and queued prompts flush instead of
  the session staying 'running' forever.

* style(web): fix eqeqeq lint error in first-prompt guard

* fix(web): clear owed idle when a new goal turn starts

The idle debt from a 'running' projection survived turn.started, so an
UpdateGoal('complete'|'blocked') landing mid-turn in the NEXT goal turn
synthesized an early idle. onSessionIdle could then drain queued prompts
into a core that was mid-turn again, re-opening the turn.agent_busy race
for multi-turn goals. Clear the debt on turn.started: from that point the
turn's own turn.ended carries the idle with goalActive already false.

* fix(web): make first-prompt starting state workspace-id-agnostic

isStartingFirstPrompt now reads from the lock set directly (size > 0)
instead of the current activeWorkspaceId. createDraftSession can swap
activeWorkspaceId to a registered id mid-flight; a workspace-keyed read
would then return false while the first prompt is still in the create/
select/submit window, re-enabling the composer and reopening the
duplicate first-submit race.

* revert(web): drop goal-aware idle projection from agentEventProjector

The goalActive / idleOwed shadow state machine grew through multiple
review rounds and still leaves edge cases (snapshot-seeded turns, mid-
turn goal updates). Roll it back to the simple 'turn.ended projects idle'
behavior. Goal-driven sessions can once again race a queued prompt into a
busy core; this is accepted as a known limitation to be resolved properly
in a follow-up that has the core emit an authoritative idle signal.

* chore: align changeset with actual fix scope

* test(web): update profile-patch expectation for model field
…otAI#1494)

* feat(tui): add Kimi WebBridge install entry to /plugins panel

Surface a hardcoded Kimi WebBridge entry at the top of the Official tab in the /plugins panel. Selecting it opens the WebBridge install page in the user's browser instead of going through the plugin install flow, since WebBridge is a browser extension plus local daemon rather than an installable plugin package.

* fix(tui): restrict WebBridge open-url shortcut to the pinned row

Match the hardcoded pinned WebBridge entry by object reference instead of by id. A curated or custom marketplace entry on the Third-party tab can legitimately reuse the kimi-webbridge id; routing by id hijacked Enter on those rows and opened the WebBridge page instead of installing. The Official tab still dedupes a same-id official catalog entry so the pinned row is not duplicated.

* fix(tui): label WebBridge plugins row as "open in browser"

The previous "webpage" status did not make it clear that selecting this row opens an external page rather than installing in-app. "open in browser" states the action directly and contrasts with the install label on regular plugin rows.

* test(tui): navigate past pinned WebBridge row in marketplace install tests

Two message-flow tests pressed Enter on the Official tab assuming index 0 was the Kimi Datasource entry. The hardcoded Kimi WebBridge row now leads that tab, so move down one row before installing.
Git Bash does not recognize .bat/.cmd extension. detect_shell_for() routes .bat/.cmd to cmd.exe /c, keeps Git Bash for other commands. Also adds chars_written=0 assertion for over-budget marker in output_truncate tests.
7723qqq added 28 commits July 20, 2026 15:24
… + budget shape)

Two systemic bugs in the Rust goal-engine boundary caused ~67 goal/budget
tests to fail:

- The native goal functions return JSON *strings*, but the TS wrappers
  passed them through as objects, so `.ok` / `.error` were always undefined
  and validation crashed or misbehaved. Add callNativeSyncJson to parse the
  result, applied to all eight goal-engine wrappers.

- The engine reads budget limits as top-level tokenBudget / turnBudget /
  wallClockBudgetMs fields, but GoalState stores them under budgetLimits.
  Add engineGoalShape to flatten the limits before serializing, applied to
  the four engine call sites in GoalMode.
…ative engine

Two follow-ups to the native goal-engine integration, completing the goal
test cluster (goal-session + goal tools now fully green):

- The Rust engine serializes budget_limited / usage_limited in camelCase
  (budgetLimited / usageLimited) while the TS GoalStatus is snake_case.
  syncStateFromEngine copied the raw value, leaking camelCase into state.
  Add normalizeEngineStatus to map it back.

- The native engine does not enforce the objective max-length bound, so
  over-long objectives slipped through when the native path was taken.
  Enforce the empty and length limits in TS before delegating criterion
  normalization to the engine.

Test updates to match the intentional behaviors the batch-added tests
pre-dated: budget exhaustion now yields budget_limited (not blocked), the
3-turn blocked audit requires three attempts, resume is a user action
(UpdateGoal rejects active), and objectives are capped at 4000 chars.
…eric version, unknown fields)

Implement the manifest behaviors the batch-added tests describe:

- Accept unicode plugin names: PLUGIN_NAME_REGEX now uses \p{L}\p{N} so
  e.g. Chinese names are valid, while spaces/punctuation are still rejected.
- Coerce a numeric `version` from JSON to a string (the manifest field is
  typed string).
- Emit an info diagnostic for any top-level field outside the known set,
  replacing the hardcoded unsupported-runtime-field list (which is subsumed).
- A declared-but-empty commands dir now yields [] (only entries that all
  fail to resolve leave commands unset).
The compaction logic is unchanged, but the tokenizer now yields slightly
different counts, which broke 5 inline snapshots and 4 hardcoded telemetry
assertions in full.test.ts. Regenerate the snapshots and update the
hard-coded tokens_before/tokens_after/input_tokens/output_tokens values to
match. No implementation changes.
Add min(1) to the required string inputs so empty values are rejected at
schema-validation time instead of reaching the tool body:

- Read/Glob/Grep: path / pattern
- Write: path and content
- TaskOutput/TaskStop: task_id
…ia detection

- resolvePathAccess now throws PATH_INVALID for paths containing a NUL byte,
  so Read/Write/Edit and friends refuse them before any filesystem access.
- detectFileType no longer trusts the native detector blindly for media: the
  Rust side keys off the file extension, so a .png whose bytes lack an image
  signature was misreported as an image. Only take the native answer for text
  or magic-confirmed media; otherwise fall through to the TS cross-validation
  that reports unknown.
…tests

- tryNativeGlobMatch ignored its nocase option, so permission path rules
  matched case-sensitively and a case-only path variant (e.g. Secrets.env)
  could bypass a rule written for secrets.env. Fold both sides to lowercase
  when nocase is requested.
- Refresh 4 inline snapshots whose token counts shifted with the tokenizer.
- Update the no-approval-RPC test: the manager intentionally blocks (never
  silently auto-approves) when there is no approval channel; the observer
  hooks still do not fire.
…lFlags stub

setActiveTools now consults agent.experimentalFlags (github_tools), which a
real Agent always provides; the minimal mock lacked it, so every registration
threw "Cannot read properties of undefined (reading 'enabled')". Fixes all 21
failures in the suite.
… timeout msg

- Reject empty/whitespace-only prompt and empty resume at the schema layer.
- Re-throw AbortError from the Agent tool instead of converting it into a
  "subagent error" result, so an aborted call stops the turn rather than
  looking like a retryable tool failure.
- The foreground subagent timeout message said "Agent resume timed out" even
  for spawned agents; make it the generic "Agent timed out after {{timeout}}."
  (all locale copies).
Several toBrokerRequest tests live in the toAgentCoreResponse describe block
and referenced the inProc fixture that was scoped to the first block, failing
with "inProc is not defined". Hoist the fixture so both blocks can use it.
estimateTokensForMessage only summed text/think parts, so image/audio/video
parts counted as 0 tokens and media-heavy messages looked nearly free to
compaction and overflow budgets. Add MEDIA_TOKEN_ESTIMATE per media part in
both the native-batch and TS paths (the per-part estimator already did this).
…y commands

- Hook commands whose first token is a shell builtin (exit/true/false/echo…)
  cannot be spawned directly (ENOENT), so route them through /bin/sh -c like
  metacharacter commands. This makes `exit 2` correctly produce a block.
- Skip hooks with an empty/whitespace command instead of running an empty
  shell that yields a spurious allow result.
- Test updates: the spawn-options builder uses shell:false (shell selection
  lives in parseHookCommand), and the empty-stderr default block reason is
  asserted at the triggerBlock decision layer where it is applied.
…r input

The native goal-reminder renderer reads budget fields at the top level of the
goal object, but the snapshot nests them under goal.budget, so the rendered
reminder dropped the Budgets lines and budget-band guidance. Spread the budget
report into the goal before serializing for the native renderer.
…cp.json

resolveMcpJsonPaths returned ~/.kimi-code/mcp-project.json for the
project-local path, contradicting the documented layering (user-global
~/.kimi-code/mcp.json, project-root .mcp.json, project-local
<cwd>/.kimi-code/mcp.json) and breaking project-local overrides.
- Treat a set-but-empty KIMI_MODEL_NAME as invalid (throw) instead of
  silently ignoring it like an unset variable.
- Reject non-canonical integers (leading zeros) for the numeric env vars.
- When stripping the synthesized env model leaves the models map empty, drop
  it entirely so the stripped config carries models: undefined rather than an
  empty object (and always assign models so the emptied map overrides the
  original instead of leaking through the spread).
…ng slashes

- normalizeAdditionalDirs now strips trailing path separators (pathe keeps
  them), so 'shared/' and 'shared' normalize to the same entry.
- Appending an additional dir that resolves outside both the project root and
  the user's home is rejected, so a raw absolute path cannot pull in an
  arbitrary external directory (relative and ~/ paths still resolve as before).
- Fix the no-.git test to expect the working-dir fallback for projectRoot.
- remove() is a no-op for a plugin that is not installed instead of throwing.
- writeInstalled uses a unique tmp filename so concurrent installs do not
  clobber a shared .tmp path (the later rename failed with ENOENT).
- reload() keeps a plugin whose manifest fails to load in an error state
  (rather than dropping it) and reports it in the reload errors.
- Fix the reload test to use the imported rm helper.
… snapshots

- promptMetadataTextFromPayload returns '' (not undefined) for empty or
  caption-only payloads.
- The catch-all long-token redaction now requires a non-lowercase character,
  so a benign run of lowercase letters is not mistaken for a secret; the test
  asserts the text survives, truncated to the 4000-char metadata cap.
- basic.test: the scoped flag resolver now reports native_tools (enabled by
  default); refresh basic/plan inline snapshots for current token counts.
A negative temperature is not a valid sampling value; parseFloatEnv accepts
any finite number, so validate the sign in applyKimiEnvSamplingParams.
…ault, fix goal loop stall

- Require completionCriterion in CreateGoal; instruct model to AskUserQuestion
  when missing (TS fallback + Rust engine)
- Enable goal_completion_verifier flag by default
- Fix driveGoal silent stall when native engine returns null/unavailable
  by falling back to TS continuation prompt for active goals
- Add pulsing animation to '思考中' thinking label in footer
Includes goal completion verifier, i18n updates, config tweaks,
and dependency updates (pnpm-lock.yaml)
… apply code quality fixes

- Require concrete completionCriterion (min 10 chars) when creating goals via
  the model-facing CreateGoal tool; reject vague objectives that lack a
  verifiable check. The tool description now mirrors the v1 engine's stricter
  wording, and the goal service validates both max and min length.
- Fix parseVerdict to handle non-empty but unmarked verifier output without
  silently passing, wrap verifier subagent spawn in try/catch, and add the
  missing 'created' GoalChangeKind.
- Fix syntax error in miniDbQueryStore.test.ts (stray closing brace).
- Replace JSON.parse/stringify with structuredClone in config cloneRecord.
- Add workspace path traversal guard in transcript readColdRoster.
- Clean up plugin managed directory on remove.
- Route hook failures through EventBus instead of console.error.
- Move WebSocket registry removal into onClose to close the stale-entry window.
- Add dispose() to TranscriptService and deprecation notes to v1 goal files.
- Fix botched dispose merge in KimiWebviewProvider.
…breakdown

- Replace 120+ hardcoded English strings in agent-core-v2 tool output
  with t() calls (toolsV2.* namespace)
- Add inputTokensUsed / outputTokensUsed to goal budget tracking
  (was output-only, now counts all tokens with separate breakdown)
- Update goal injection and outcome-prompts display
- Files: 21 source files across i18n, agent-core-v2, and goal domain
@changeset-bot

changeset-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 441f846

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

This PR includes changesets to release 12 packages
Name Type
@moonshot-ai/kimi-code Minor
@moonshot-ai/agent-core Minor
@moonshot-ai/kimi-code-sdk Patch
@moonshot-ai/agent-core-v2 Patch
@moonshot-ai/i18n-shared Minor
@moonshot-ai/kap-server Patch
@moonshot-ai/kimi-web Patch
kimi-code Patch
@moonshot-ai/minidb Minor
@moonshot-ai/acp-adapter Patch
@moonshot-ai/migration-legacy Patch
@moonshot-ai/server 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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 441f8465ad

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (nativeModule) return nativeModule;
// eslint-disable-next-line @typescript-eslint/no-require-imports
try {
const mod = require('@moonshot-ai/kimi-native-tools') as NativeModule;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use ESM-safe loading for the i18n native module

When the CLI is run through its ESM entry path (for example the tsx dev commands, and any unbundled ESM output), require is not defined. The first t() call during program setup hits this line and the catch path also calls bare require, so normal startup/help can abort before the CLI is usable; create a require with createRequire(import.meta.url) or use imports for the native loader.

Useful? React with 👍 / 👎.

baseUrl: platform.baseUrl,
apiKey,
};
config.defaultModel = `astron/${selection.model.id}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist Astron model aliases for the v1 harness

When a user enables the Astron flag and completes this TUI login, the saved config only contains providers.astron plus defaultModel = astron/.... The interactive CLI still constructs the legacy SDK harness in runShell(), whose legacy ProviderManager.resolveProviderConfig() looks up config.models?.[model] and throws if the alias is absent; the v2 Astron overlay is not registered in legacy agent-core, so the next session fails with “Model ... is not configured.” Persist the selected Astron model aliases here or add an equivalent v1 overlay before setting this default.

Useful? React with 👍 / 👎.

resolve(projectRoot, 'dist-native', 'bin', arch, 'kimi-agent' + ext),
];
try {
const fs = require('node:fs');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use ESM-safe fs loading in the Rust adapter

With agent.engine = "rust" in the ESM dev/runtime path, this bare require throws ReferenceError before any candidate path is checked, the catch swallows it, and findBinary() always returns null even when the Rust binary exists. That makes createRunTurnOverride() silently fall back to the JS engine for every configured Rust run; import fs or create a CommonJS require via createRequire(import.meta.url).

Useful? React with 👍 / 👎.


import DESCRIPTION from './workflow.md?raw';

const WorkflowOperationSchema = z.enum(['run', 'status', 'wait', 'cancel']);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Expose the advertised workflow list operation

The TUI advertises /workflow list and sends the model a hint to use the Workflow tool to list built-ins, but the tool schema rejects any operation: "list" before execution and no handler calls IWorkflowService.listBuiltins(). In that scenario the model cannot perform the requested list operation through the tool; add list to the schema/handler or have the slash command render the built-in list directly.

Useful? React with 👍 / 👎.

Comment thread flake.nix
@@ -65,8 +65,10 @@
./packages/acp-adapter
./packages/agent-core
./packages/agent-core-v2
./packages/i18n

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add new workspaces to the Nix workspace lists

This flake update adds only some of the new pnpm workspaces; packages/i18n-shared and packages/kimi-agent are now matched by pnpm-workspace.yaml and are dependencies of apps/kimi-code, but they are missing from both workspacePaths/workspaceNames. In a Nix build their sources are omitted from the fileset and pnpmConfigHook cannot fetch those workspace dependencies, so the CLI build path breaks; add ./packages/i18n-shared, ./packages/kimi-agent, @moonshot-ai/i18n-shared, and @moonshot-ai/kimi-agent.

Useful? React with 👍 / 👎.

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