Skip to content

chore: merge origin/main into feat/hook-permission-decisions- #3 - #5

Merged
YeKc1M merged 79 commits into
feat/hook-permission-decisionsfrom
resolve-conflicts
Sep 4, 2026
Merged

chore: merge origin/main into feat/hook-permission-decisions- #3#5
YeKc1M merged 79 commits into
feat/hook-permission-decisionsfrom
resolve-conflicts

Conversation

@YeKc1M

@YeKc1M YeKc1M commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Related Issue

No linked issue — this PR syncs the feature branch with upstream main on this fork.

Problem

feat/hook-permission-decisions had fallen 77 commits behind origin/main (up to 60136588a). The branch needs the latest main to stay mergeable and to keep the hook permission decisions feature working against current code.

Verified beforehand: upstream main has not implemented an equivalent feature — on main, PermissionRequest hooks are still fire-and-forget (observation-only), with no way for a hook to approve or deny before the approval broker and no equivalent experimental flag.

What changed

Single merge commit 8f592d72d merging origin/main into the feature branch line.

  • All code merged cleanly; only docs/en/customization/hooks.md and docs/zh/customization/hooks.md conflicted (main restyled these pages while the branch added the PermissionRequest decision docs). Resolved by keeping main's restructured wording and re-inserting the branch's experimental content: the PermissionRequest table row stays ✓ (experimental) with a link to the hook_permission_decisions section, and the blocking-events callout keeps the exception note. en/zh kept in sync.
  • Feature seams verified intact after the merge: hook_permission_decisions flag definition, the flag check in agentExternalHooksService, and the hook-decision path in toolApprovalService.

Verification: tsc --noEmit clean, check-no-comments clean, oxlint 0 errors, and the full agent-core-v2 suite passes (348 files, 6285 tests), including the externalHooks (67) and toolApproval (34) tests.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue (external PRs: the issue must have a maintainer's /approve). — N/A, fork-internal sync PR
  • I have added tests that prove my feature works. — No new code; existing feature tests all pass
  • Ran gen-changesets skill, or this PR needs no changeset. — No changeset needed; the feature changeset hook-permission-decisions.md is already on the base branch
  • Ran gen-docs skill, or this PR needs no doc update. — Docs conflict resolutions keep en/zh hooks pages in sync

kimi-agent-bot and others added 30 commits August 29, 2026 18:53
…nshotAI#3366)

Co-authored-by: kimi-agent-bot <kimi-agent-bot@users.noreply.github.com>
* fix(transcript): preserve bundled prompt origin

* refactor(transcript): narrow steer origin projection
* fix(transcript): preserve turn prompt identity

* fix(transcript): backfill active prompt identity

* fix(transcript): seed late-bound turn identity

* fix(transcript): persist turn prompt identity

* fix(transcript): skip undone prompt identities

* fix(transcript): hide undone continuation turns

* docs(agent-core): update wire manifest

* fix(transcript): match cold turns by prompt identity

* fix(transcript): match internal prompt turns by origin

* fix(transcript): align undo anchors with context

* fix(transcript): prioritize cold turn matches

* fix(transcript): retire unmatched turn boundaries

* style(transcript): align cold matcher
* feat(agent-core-v2): spawn tower workers on a snapshot of base checkout WIP

Tower worktrees were created from the base branch tip, so a worker never
saw uncommitted changes sitting in the base checkout — it would start
building on a foundation that did not exist in its worktree.

When a mission branch is first created, the store now captures the base
checkout's dirty paths (staged, unstaged, and untracked per git status;
.tower/ and git's own ignore rules excluded; unmerged paths refuse the
spawn) into a synthetic snapshot commit built through a temporary index
(read-tree + add + write-tree + commit-tree), so the user's checkout,
index, and base branch are never touched. The mission branch starts at
that commit and the mission records it as spawnBase.

The merge gate diffs the branch from spawnBase instead of the base tip,
so snapshotted WIP is never mistaken for a worker scope violation, and
the reviewer briefing diffs from the same point. Alternatives
considered: applying the WIP as uncommitted changes inside the new
worktree (fragile — it mixes into the worker's first commit and can be
lost before that), and excluding the WIP file set inside the gate
(permanent holes in scope enforcement, hidden provenance). A snapshot
commit keeps the gate strict and makes the WIP an explicit, mergeable
part of the branch history.

TowerMerge now also refuses to merge while the main checkout holds
uncommitted changes in files the merge would overwrite (blocked reason
base-dirty) and tells the user to commit or stash them first; dirt that
does not intersect the merge no longer blocks it.

* fix(agent-core-v2): harden tower base-WIP snapshot edge cases

- fall back to the base branch as diff base once a rebase drops the
  snapshot commit (it is only used while still an ancestor), so scope
  checks, reviewer prompts, and conflict attribution stop blaming base
  changes on the worker
- run snapshot index commands from the worktree top-level and filter
  .tower by path segment, fixing dirty-base spawning from a repo
  subdirectory (porcelain paths are worktree-root relative)
- refuse to snapshot WIP collected from a checkout that is not the
  recorded base (or a detached HEAD) instead of mixing another
  branch's content into base history

* fix(agent-core-v2): let tower mode take over from a live but idle owner session

The tower-mode ownership check refused entry whenever the recorded
owner session was still materialized in the process. kap-server keeps
sessions materialized until explicit close, so a session that stopped
abnormally (closed tab, failed turn) owned the workspace tower forever
and no second session could enter tower mode.

Now the owner is only protected while actually occupied: entry is
refused when the owner session has an active turn or a pending
interaction; a live but idle owner is exited remotely (durable
TowerModeExit in its own event log) and the entering session takes
over.

* feat(agent-core-v2): keep tower mode active after tower teardown

* chore: add changeset for tower teardown staying active

* docs(tower): merge changeset

* docs(tower): merge changeset

* fix(agent-core-v2): make tower mode mutually exclusive with plan and swarm modes

* chore: add changeset for tower mode mutual exclusion

---------

Co-authored-by: konghuanjun <konghuanjun@moonshot.ai>
* fix(agent-core-v2): persist prompt resolution events so replays can reconcile queued prompts

* docs(changset): update prompt desc

---------

Co-authored-by: konghuanjun <konghuanjun@moonshot.ai>
…mental (MoonshotAI#3334)

* feat(secondary-model): enable the subagent model pool by default

* feat(secondary-model): graduate the subagent model pool out of experimental

* fix(secondary-model): honor the v2 default_model key on the legacy engine

* fix(secondary-model): enforce forced subagent pools on the legacy engine

* fix(tower): keep reviewers on primary model

* fix(secondary-model): live-apply picker saves and replace the legacy model key

* fix(secondary-model): accept default_model in the live-apply setter and clear on removal

* fix(secondary-model): revert the legacy v1 engine, keep the v2 pool opt-out

* feat(secondary-model): graduate pool with model source telemetry

* fix(secondary-model): keep legacy engine opt-in

* test(sdk): pin secondary-model engine divergence

* fix(tower): honor forced model for reviewers
…onshotAI#3392)

* feat(agent-core-v2): preserve config.toml formatting on writeback

Config persistence previously re-serialized the whole document on every
write, destroying user comments, key order, and blank lines. Writes now go
through a raw-text channel on the TOML document store: the file is scanned
into top-level domain regions, changed domains are diffed at key level so
only edited statements are rewritten, and untouched regions keep their
original bytes (including CRLF). The effort max-to-high migration performs
a single-line text replacement instead of a full rewrite.

Ambiguous constructs (dotted keys, arrays of tables, unmapped files) fall
back to re-serializing the affected domain, and any scanner failure falls
back to the previous whole-document write. Byte-identical results skip the
write entirely.

* feat(agent-core-v2): support quoted table headers in config writeback

The region scanner parsed only bare-key table headers, so files using
quoted segments such as [models."acme/m1"] or ["x.y"] fell back to a
whole-document rewrite, dropping user comments for exactly the configs
with the most hand-written content. Header parsing now walks TOML key
paths segment by segment (bare, basic-quoted with escape decoding,
literal-quoted), so quoted regions participate in key-level preservation
and are edited or removed in place. Emission of new sub-table headers
already quotes non-bare segments through smol-toml. Unparseable headers
still decline to the whole-document fallback.
…all permission modes (MoonshotAI#3290)

* feat(agent-core-v2): require approval for dangerous bash commands in all permission modes

* feat(agent-core-v2): deny dangerous commands in auto mode, unwrap command launchers, and skip the guard for non-interactive hosts

* feat(agent-core-v2): support disabling the dangerous-command guard via config

* test(node-sdk): project v2's env-materialized empty permission section in config parity
… degradation paths (MoonshotAI#3394)

* feat(agent-core-v2): add P0 telemetry events for auth, execution, and degradation paths

* test(agent-core-v2): drop telemetry emission assertions from existing test suites
…d Never Ask (MoonshotAI#3403)

TUI copy for the three permission modes (manual/yolo/auto) now uses the
new display names and descriptions across the /permission selector,
footer badge, toggle notices, goal/swarm start prompts, session replay,
and /status. The /yolo and /auto commands are renamed to
/ask-when-needed and /never-ask, with the old names kept as aliases.
…ervices (MoonshotAI#3402)

* refactor(agent-core-v2): migrate todo domain to agent-scoped DI service

* refactor(agent-core-v2): migrate skill domain to agent-scoped DI service

* refactor(agent-core-v2): migrate reminder domain to agent-scoped DI service

* refactor(agent-core-v2): migrate interaction domain to agent-scoped DI service

* refactor(agent-core-v2): migrate cron domain to agent-scoped DI service

* refactor(agent-core-v2): migrate dateChange domain to agent-scoped DI service

* refactor(agent-core-v2): migrate goal domain to agent-scoped DI service

* refactor(agent-core-v2): remove agent runtime infrastructure

* docs(agent-core-v2): rewrite agent domain standard as agent-scoped DI service

* fix(agent-core-v2): restore actor services on late feature re-provide and order agent-created events after metadata registration
…er Ask (MoonshotAI#3406)

Follow-up to MoonshotAI#3403 for the user docs: living pages (en + zh mirrors)
now use the new mode names and descriptions — slash-commands and
interaction references for /ask-when-needed (aliases /yolo, /yes) and
/never-ask (alias /auto), CLI flag and config glosses, guides,
customization pages, and the docs AGENTS.md terminology table. Wire
and config ids (manual/yolo/auto) are unchanged; release notes are
historical and untouched.
… staging (MoonshotAI#3405)

* feat(cli): compress native update artifacts and decompress them while staging

Co-authored-by: qer <wbxl2000@outlook.com>

* fix(cli): generate compressed native artifacts in the publish job

The zip's checksum never pairs with the bare executable the updater
verifies after inflating, and artifacts produced in the matrix job never
leave the runner (the workflow's upload-artifact path lists only the
zip). Extract each zip in the publish job, hash the bare executable, and
emit the .zst/.tar.gz artifacts there so the manifest entry pairs
checksum with what compressed inflates to, and everything new reaches
the release via the existing upload glob — no workflow changes needed.

Co-authored-by: qer <wbxl2000@outlook.com>

* fix(cli): keep the .exe suffix in windows manifest filenames

The updater's bare-binary fallback downloads entry.filename, and the CDN
layout carries .exe on Windows — without it the fallback 404s. The .zst
and .tar.gz artifact names stay unchanged. Also fold the manifest
coverage into the pre-existing release-artifacts test (its fake-zip
fixture predates extraction) and pin the end-to-end chain there.

Co-authored-by: qer <wbxl2000@outlook.com>

---------

Co-authored-by: kimi-agent-bot <kimi-agent-bot@users.noreply.github.com>
Co-authored-by: qer <wbxl2000@outlook.com>
)

* fix(agent-core-v2): allow sessions without valid models

* fix(agent-core-v2): wait for model initialization

* fix(agent-core-v2): align session lifecycle dependencies
…paths (MoonshotAI#3415)

git status --porcelain=v1 without -z C-quotes non-ASCII paths under
git's default core.quotePath, and parsePorcelain consumed the quoted
form verbatim while the posix() helper turned each \3xx octal escape
into a fake path segment — the changes tree then split those segments
on '/' and rendered Chinese filenames as bogus directory chains, and
diff requests for such entries failed the same way.

Run porcelain with -z (NUL-separated, never quoted; rename records are
XY new\0old with the new path first) and drop the posix() helper —
porcelain already emits '/' separators on every platform. The v1 engine
shares the bug and is intentionally left unchanged for now.

Refs MoonshotAI#3414

Co-authored-by: kimi-agent-bot <kimi-agent-bot@users.noreply.github.com>
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
Signed-off-by: 7Sageer <sag77r@hotmail.com>
…#3418)

MoonshotAI#3411 dropped two positional constructor params from
WorkspaceInstanceManager, shifting the agent-profile stub the
Reflect.construct fixture pins by index, and turned the broken
subagent-pool session create from VALIDATION_FAILED into a success;
both suites fail on main since then.

Co-authored-by: user <user@userdeMacBook-Pro.local>
…mpaction (MoonshotAI#3409)

* fix(agent-core-v2): re-remind subdirectory AGENTS.md after context compaction

Deliver the discovery reminder through the reminder runtime's register()
channel so it re-injects whenever the previous reminder leaves the context
(compaction, /clear, or undo), instead of firing a one-shot notify() that
compaction silently drops while the known-set survives.

Discovery bookkeeping splits into agentsMdReminder.pending (discovered, not
yet read) and agentsMdReminder.known (injected or read); the provider injects
only pending paths not covered by the last injection's disclosure. A newly
created AGENTS.md lands in pending until read; a modified one counts as
injected.

* fix(agent-core-v2): drop deleted AGENTS.md from reminder bookkeeping

A discovered-but-unread AGENTS.md that gets deleted stayed in
agentsMdReminder.pending, so the next re-injection after context loss
pointed the model at a file that no longer exists. Remove deleted paths
from both the pending and known sets on the watcher event.

* Delete .changeset/agents-md-reminder-compaction-reset.md

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

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
…rted telemetry (MoonshotAI#3419)

* feat(agent-core-v2): report enabled experimental flags in session_started telemetry

Add a sorted, comma-separated experimental_flags property to the v2
session_started event so feature-gated metrics gain a session-granular
exposure denominator, and carry the post-apply enabled set on the TUI
experimental_features_apply event to bound mid-session flag flips.

* feat(node-sdk): carry experimental flags on the harness session_started row

The v2 in-process path emits session_started twice (engine-side from
SessionLifecycleService, harness-side from KimiHarness), and only the
engine row carried experimental_flags, so consumers could double-count
or miss the flag dimension. Merge a per-emission dynamic getter into the
harness row, wired to the in-process engine's flag service, so both
rows report the same enabled flag set.

* fix(node-sdk): keep experimental_flags canonical on session_started

A caller-supplied experimental_flags in per-call sessionStartedProperties
could overwrite the engine-owned dynamic value, breaking row-to-row flag
consistency. Merge the dynamic properties after the session-scoped ones
so engine-owned keys always win.

* fix(agent-core-v2): count tower as exposure only once assembled

The tower flag reacts live to mid-process flips, but TowerFeature's
tools and profiles are assembled at App scope construction, so a session
started after an in-process flip cannot actually use tower. Filter it
out of experimental_flags on both session_started producers unless
isTowerFeatureAssembled says the feature is assembled in this process,
keeping the exposure denominator honest.

* test: recalibrate fixtures for the added constructor param and drop timing-sensitive tower assertion

The WorkspaceInstanceManager test builds the service with positional
args; the new flags param shifted unitHostFactory one slot. The tower
inclusion assertion in the SDK integration test depended on feature
assembly winning the race against async config load, which slow CI
runners lose; tower assembled-state coverage stays with the tower
service tests.

* refactor(agent-core-v2): move flag exposure gating into the flag definition

Telemetry consumers hardcoded tower's assemble-at-startup semantics to
keep it out of experimental_flags until assembled. Push the gate into
FlagDefinitionInput as an optional isExposed predicate owned by the
flag's own domain, expose it as IFlagService.exposedIds(), and let both
session_started producers call it — no feature-specific knowledge leaks
into the SDK or the session lifecycle.
…overy reminder (MoonshotAI#3420)

* fix(agent-core-v2): drop compaction mechanics from the AGENTS.md discovery reminder

The re-injection trigger is harness mechanics the model cannot act on, and the unread-path condition never fires from the model's perspective. End the reminder at the actionable instruction.

* fix(agent-core-v2): reword the AGENTS.md discovery reminder lead

Lead with the files instead of the paths, and replace harness vocabulary (touched / covered by / injected instructions) with terms the model can place: apply to, your system prompt.
MoonshotAI#3399)

Tower mode is now entered only manually — /tower on, or /tower
<base-branch> to also pin the local branch missions merge back into. The
agent can no longer enter it on its own: TowerInit refuses while the mode
is off and points at the slash command.

/tower <base-branch> is deterministic end to end: a missing base branch is
created from the current checkout (uncommitted changes committed onto it
as a labeled WIP snapshot), the checkout switches to it, and the tower
workspace is initialized immediately instead of relying on the model to
apply the base. With an existing workspace the tower rebases onto the
requested base when no missions are open and refuses naming the blocking
missions otherwise. The base rides the tower_mode.enter event into the new
tower.base state key and TowerInit falls back to it.

Tower agent deaths (failed/timed_out/killed/lost) are recorded in the
tower protocol: TowerStatus marks dead roster entries and warns about
missions whose owner died, with a resume hint. The tower console prompt
now requires a per-worker deliverables summary before TowerTeardown.

The session profile REST surface accepts tower_base so web clients can
turn tower mode on with /tower <base-branch> instead of sending the
argument as a prompt.

Co-authored-by: konghuanjun <konghuanjun@moonshot.ai>
…oonshotAI#3421)

* fix(agent-core-v2): persist plan.revision as agent-relative key

- replace the session-scoped `path` field in durable plan.revision records
  with an agent-relative `key`, resolved to a display path at read and
  projection time against the current agent/session scope
- migrate legacy `path` records during wire restore and rewrite the journal
  once migration succeeds, emitting low-cardinality
  `wire_plan_revision_migrated` telemetry
- tolerate journal truncation during the migration rebuild and keep the
  legacy `path` display in cold transcript folds for unmigrated records
- keep external transcript marker `path` and meta `reviewPath` projections
  unchanged

* perf(agent-core-v2): fork sessions without materializing the target

- copy session files byte-for-byte (wire.jsonl included, parallel file
  writes) and append the forked marker per agent instead of parsing and
  rewriting every wire journal
- write the forked session's state.json, agent registry, and session
  index entry directly without creating a live session or agent scopes;
  fork returns SessionMeta and SessionForkedEvent drops the scope handle
- keep turnIndex truncation on the per-record path
- kap-server fork/createChild routes, the klient facade and wire
  contract, and the node-sdk forkSession resume the forked session
  through the normal resume path when a live handle is needed
- stop healing truncated source wires during fork; the forked session
  repairs its own copy lazily on resume
- add the `kimi fork [--cwd] [-y]` CLI command
…hotAI#3422)

- add mtime to the file system storage service (node-fs stat, memory write clock)
- record the source max mtime in the session index checkpoint at projection
- compare the cheap mtime signal before adopting a persisted manifest on prepare
- refresh the checkpoint mtime after reconcile so warm starts stay scan-free
RealKai42 and others added 27 commits September 2, 2026 17:32
… subagent stop reasons (MoonshotAI#3459)

* feat(agent-core-v2): add a handoff step after forced stops and report subagent stop reasons

- When the tool-call repeat breaker stops a turn, the loop now runs one
  text-only handoff step; tool calls issued in that step are refused and
  the turn ends with the same stop reason.
- Tool results can carry stopTurnReason; LoopRunResult and turn.ended
  expose the forced stop reason.
- Subagent results report stop_reason, a resume hint, and a next-step
  line; a turn that ends without a final message fails with
  agent.no_final_message, and the step-cap failure is rephrased for the
  parent model.
- Remove the subagent summary length policy and its continuation prompt.

* chore(agent-core-v2): regenerate the state manifest after merging main

* fix(agent-core-v2): run the handoff step at the step cap and surface swarm stop reasons

- The per-turn step cap no longer skips a queued handoff step, so a
  forced stop that lands on the last permitted step still produces the
  text handoff instead of a max_steps failure.
- AgentSwarm results carry the worker's stop reason and render it as a
  stop_reason attribute; a stuck worker now also triggers the resume hint.

* fix(agent-core-v2): keep the repeat-breaker classification and persist turn.ended stop reasons

- A handoff that ends without text keeps stop_reason repeat_breaker on
  the parent-facing failure instead of collapsing to no_final_message.
- Only a user cancellation maps to stop_reason cancelled; other kills
  report stopped and keep the resume hint.
- turn.ended persists stopReason in its durable record and the wire
  manifest lists it.
…needed or never-ask mode (MoonshotAI#3473)

* feat(kimi-code): show file-change warning when switching to ask-when-needed or never-ask mode

* Update permission-mode-file-warning.md

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

* feat(kimi-code): show file-change warning on goal and swarm permission switches

* fix(kimi-code): indent every line of multi-line notice details

* feat(kimi-code): render the permission-mode file-change warning in warning color

* refactor(kimi-code): render the file-change warning via showStatus

* fix(kimi-code): defer the goal permission-switch notice until the goal starts

* refactor(kimi-code): move the file-change warning copy into the TUI constant directory

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
…ayered registry (MoonshotAI#3430)

* refactor(agent-core-v2): rebuild telemetry context as a scope-bound layered registry

* fix(agent-core-v2): prefer ambient model in telemetry envelope and repair print-runner stub

* fix(agent-core-v2): delete only the fragment registered by this telemetry binding

* fix(agent-core-v2): bind telemetry context writes to the binding's own fragment

* fix(agent-core-v2): keep stale telemetry emissions on the binding-owned fragment

* refactor(agent-core-v2): mirror turn-request trace_id into ambient telemetry context

* refactor(agent-core-v2): telemetry key-resolution cleanups

Drop the dead defineAgentTelemetryEvent branch in declaredKeysFor: agent_id is already copied unconditionally as identity plumbing before the declared-key loop, and the loop itself skips agent_id, so the branch had no runtime effect. Name the wire session id property key (WIRE_SESSION_ID_PROPERTY) so the session_id-to-sessionId mapping is explicit.

* refactor(agent-core-v2): flow ambient telemetry context to every event unconditionally

Remove the registry-key filtering and the FILTERED_CONTEXT_KEYS hard
filter from composeTelemetryProperties: the merged ambient now reaches
every event's properties, with session_id still remapped to the
camelCase sessionId key and explicit defined values winning on
collision. Explicit undefined values no longer clobber ambient fields.

With filtering gone, correctness for time-varying fields rests on
lifecycle discipline, so the loop now clears the ambient trace_id when
a step starts (mirroring the activeRequestTrace reset) instead of
relying on an explicit undefined payload value to mask the stale
mirror.

* refactor(agent-core-v2): move thinking_effort into the turn telemetry context

* refactor(agent-core-v2): replace the telemetry fragment registry with parent references
…oonshotAI#3471)

FeatureAssemblyService ran during the eager bootstrap batch, while
ConfigService was still loading config.toml asynchronously, so
flags.enabled() always read false for config-sourced flags and gated
features (e.g. tower via [experimental] tower = true) never assembled.

registerFeature now accepts a { flag } option; gated registrations are
assembled in a deferred pass once IConfigService.ready resolves, and
IFeatureAssemblyService.ready exposes that pass. WorkspaceInstanceManager
.materialize also awaits config.ready so session creation cannot race
the deferred assembly.
* fix(agent-core-v2): rework tower mode enter failures and config-flag assembly

- IAgentTowerService.enter() returns a typed TowerEnterResult: the 4
  former silent failure points now carry their reason (not-main-agent /
  experiment-off / feature-not-assembled / owned-by-live-session with
  the owner session id + title); kap-server and node-sdk map each reason
  to a distinct SESSION_TOWER_MODE_INVALID message via
  towerEnterFailureMessage() instead of one misleading catch-all.
- Fix config-sourced [experimental] tower flag never assembling the
  tower feature: ConfigService seeds its state synchronously at
  construction with a best-effort readFileSync of the config document it
  owns, so the flag is already visible when TowerFeature's constructor
  runs during App-scope creation. No event machinery: a runtime config
  flip updates flag reads but does not re-run feature constructors — a
  restart is required, pinned by test. docs/flag.md + docs/features.md
  document the contract.
- Consolidated changeset for @moonshot-ai/kimi-code.

* fix(tower): release handler when teardown

---------

Co-authored-by: konghuanjun <konghuanjun@moonshot.ai>
…hotAI#3477)

The process-level uncaughtException handler installed by startServer
called process.exit(1), so a single stray exception killed every
session the process served — and, embedded in the desktop main
process, killed the whole app past the shell's own crash guard. Log
the error at error level and keep serving instead.

createServerLogger gains an optional stream so hosts can route server
logs into their own log channel.
…context (MoonshotAI#3484)

* fix(agent-core-v2): write the bound model into the ambient telemetry context

* fix(agent-core-v2): restore the ambient telemetry model after profile replay
… restart (MoonshotAI#3478)

* fix(agent-core-v2): rebuild persisted subagents when resuming after a restart

Agent(resume=<id>) only consulted the in-memory agent roster, so once a
session was reopened in a new process every persisted subagent id failed
with "does not exist" even though its wire log and metadata were intact.
The resume path now reads the session metadata once, verifies the target
is a subagent owned by the caller, and re-creates its agent scope from
the persisted records when it is not live before running the turn.

* fix(agent-core-v2): sync rebuilt subagents to the caller mode and match rules by their persisted profile

A subagent rebuilt for resume replayed the permission mode it was
spawned with, so a child persisted in yolo kept running in yolo after
the parent had switched back to manual; the rebuild now copies the
caller's current mode the way a fresh spawn does.

Permission rules with a profile subject such as Agent(coder) were
matched against the fallback label while the target was offline,
because only live agents exposed their profile during execution
resolution. Every spawn site now records the profile name in the
subagent labels and the resume path reads it back for display and rule
matching when the agent is not live.

Adds a kap-server e2e case that spawns a subagent, restarts the server
on the same home directory, resumes it through the Agent tool, and
checks that its prior context reaches the next model request.

* fix(agent-core-v2): keep pinned permission modes when rebuilding a subagent for resume

Tower workers are pinned to auto mode at spawn and skipped by the
permission-mode broadcast, but the rebuild path synced every restored
subagent to the caller's mode, so a dead worker recovered through
Agent(resume=...) after a restart lost its pinned mode. The exception is
now a single predicate next to the worker profile, shared by the
broadcast and the rebuild.

* fix(agent-core-v2): record subagent profiles in the lifecycle instead of at each spawn site

The persisted profile name that offline resumes use for display and
permission-rule matching was written by three spawn sites, and the
session-init child was still missing it. The lifecycle now records it
centrally: create() takes the profile from the binding it is given and
fork() takes the override or the source agent's profile, so every
subagent spawn is covered and the spawn sites no longer pass it.

* chore: merge the subagent-resume changesets into one
… failure episode (MoonshotAI#3493)

* fix(agent-core-v2): track session index mirror give-up event once per failure episode

The give-up telemetry fired on every failed flush once the consecutive
failure count crossed the threshold, so a session with a persistently
failing mirror reported hundreds of thousands of events per day. Emit
it only when crossing the threshold; the counter resets on the next
successful flush, so a later episode reports again.

* fix(agent-core-v2): latch the mirror give-up event per failure episode

The equality guard missed episodes where unpublished-manifest flushes
(manifest === undefined also increments the counter) pushed the count
past the threshold before the first throwing flush. Latch the report
per episode instead; the latch resets on the next successful flush.
…cts (MoonshotAI#3504)

* test: speed up kap-server suite and exclude minidb from default projects

* test(kap-server): restore baseline server after sessions tests replace it
…try in v2 print mode (MoonshotAI#3498)

* fix(kimi-code): honor KIMI_DISABLE_TELEMETRY and restore crash telemetry in v2 print mode

* fix(kimi-code): attribute v2 print crash telemetry to the resolved session model
… scanning (MoonshotAI#3503)

* fix(tree-sitter-bash): recognize heredocs in character-level balanced scanning

scanBalancedStatements treated heredoc bodies as ordinary characters, so a stray quote, paren, or backtick inside a heredoc body (for example an apostrophe in a PR body passed through a command substitution) broke the scan and produced ERROR nodes; the resulting hasError made dangerous-command-ask judge the whole command unanalyzable and prompt for approval even in yolo mode.

Parse << and <<- delimiters (excluding <<< herestrings) with the same unquoting rules as the token-level heredoc reader, queue pending bodies, and skip them line-wise at newlines. Skip arithmetic $(( ... )) regions via scanBalanced so a left-shift << is never mistaken for a heredoc operator.

* fix(agent-core-v2): raise the bash parse wall-clock budget to 500ms

A 20ms wall-clock budget could abort an otherwise fine parse under CPU contention, GC pauses, or cold-start JIT, flipping the permission verdict to unanalyzable (spurious approval prompts) or silently dropping AGENTS.md re-reminders. Normal commands parse in well under 1ms; maxNodes stays the deterministic cap, and 500ms remains a backstop against pathological parser loops.

* fix(tree-sitter-bash): skip comments and legacy arithmetic during heredoc-aware scanning

The heredoc-aware scan queued a heredoc for any << it encountered, including inside comments (echo $(printf x # <<EOF\n)) and legacy arithmetic expansions (echo $(echo $[x << 2]\n)); both are valid bash that parsed cleanly before, and the regression flipped them to hasError and an unanalyzable permission verdict.

Skip # comments to end of line when the preceding character starts a new word, and skip ${ ... } / $[ ... ] expansions as balanced units (mirroring skipDollar), so << is only recognized where a redirection operator can actually appear.

* fix(tree-sitter-bash): skip word-glued subscripts during heredoc-aware scanning

Indexed assignments such as echo $(a[x<<2]=3\n) put arithmetic inside a word-glued [ ... ] subscript; the heredoc-aware scan read the << shift operator there as a heredoc start, regressing valid bash that parsed cleanly before to hasError and an unanalyzable permission verdict.

Skip a [ ... ] region as a balanced unit when the bracket immediately follows a word character (subscripts and glued glob classes); a bracket at word start keeps the existing character scan, so real heredocs after words (cat foo[ab]<<EOF) and bare [ command arguments are unaffected. The subscript regression is covered by a unit case only: the reference parser splits subscript arithmetic into binary_expression while this parser keeps it an opaque word, a structural difference that predates this change and has no differential fixture yet.

* fix(tree-sitter-bash): skip conditional regions and look through continuations in heredoc-aware scanning

Two more character-scan contexts queued bogus heredocs after the heredoc-aware scan: a [[ ... ]] conditional region (echo $( [[ x == @(<<EOF) ]]\n) and regex right-hand sides such as [[ x =~ <<a ]]), and a # preceded by a backslash-newline continuation (echo $(printf foo\<newline>#bar)), where removing the continuation keeps the hash inside the preceding word instead of starting a comment. Both are valid bash that parsed cleanly before.

Skip word-start [[ ... ]] as a balanced region (a << inside a conditional is never a heredoc operator; a bracket in argument position keeps the character scan), and walk back over \+newline pairs before classifying a # as a comment. The extglob conditional case joins the differential fixtures; the continuation case is unit-only because the reference parser errors on it.

* fix(tree-sitter-bash): scan substitutions as part of heredoc delimiters

A heredoc delimiter containing a substitution (echo $(cat <<$(foo)\nbody\n$(foo)\n)) was truncated at the first paren, so the queued delimiter never matched the body closing line and the scan swallowed the rest of the range, regressing valid bash to hasError and an unanalyzable permission verdict.

scanHeredocDelimiter now scans $( ), ${ }, $[ ], and backtick regions wholesale as part of the delimiter word (recursing with the heredoc-aware statement scanner for $( )), mirroring how bash treats the whole word as the delimiter. The case stays unit-only because unquoted delimiters hit the already-registered heredoc-content-chunks structural difference with the reference parser.
…otAI#3485)

* docs(zh): restyle configuration and customization sections

Editorial pass across 11 pages: clear explanatory dashes, replace arrow
cross-references with inline links, compress oversized table cells while
keeping operational facts (value ranges, override precedence, activation
conditions), split >5-sentence paragraphs by theme, fold interface
contracts and low-frequency internals into details blocks, add map
sentences to multi-paragraph sections, add subcommand overview table to
kimi-command reference. Add /provider manager screenshot to media.

* docs(zh): restore dangerous_command_guard, fix trust prompt default and secondary-model default

- config-files: restore the dangerous_command_guard paragraph dropped
  during the style pass (regression, content from upstream MoonshotAI#3290)
- mcp: the trust prompt defaults to Trust this folder per
  trust-prompt.test.ts; docs had the direction reversed (pre-existing)
- config-files: subagent model pool defaults on since MoonshotAI#3334;
  KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL=0 disables (pre-existing staleness)

* docs(zh,en): sync en mirrors and fix anchor slugs

Add restyled en mirrors for all 11 configuration/customization pages,
mirroring the zh structure (section parity, map sentences, compressed
cells, details folds) while keeping en phrasing.

Fix anchor slugs in both locales (underscore kept, dots dropped per
@mdit-vue slugify): loop_control, openai_responses, kimi_model_,
systemmd variants; retarget renamed permission-mode section
(yolo/auto -> The three permission modes / 三种权限模式).
…oonshotAI#3499)

* fix(telemetry): deduplicate session_started and model switch events

* fix(telemetry): keep engine session_started for direct v2 SDK clients and avoid model_switch race

* fix(telemetry): preserve model_switch when activation rebinds the same alias

* fix(telemetry): route TUI reload through harness

* test: update /reload message-flow test for harness reload route
* refactor(agent-core-v2): remove the staleGuard feature

Drop the read-before-edit runtime guard: Edit/Write executions are no
longer vetoed when the target file was never read or its mtime changed
since the last read, and successful Read/Edit/Write no longer refresh a
recorded mtime. Removes the staleGuard replayable state key and the
staleGuard.recorded / staleGuard.cleared durable wire events; old wires
keep replaying through the unknown-type skip path. apps/vis keeps
projecting and rendering those historical records via locally declared
legacy record types.

* fix(agent-core-v2): skip retired wire record types silently during restore

Restore reports every journal record whose type has no registered event
class through onUnexpectedError. Sessions written before the staleGuard
removal can hold a staleGuard.recorded entry per successful
Read/Edit/Write, so loading one floods the log with WireError stacks.
Keep a retired-type list of record types that were once durable
vocabulary; restore skips them without reporting, while genuinely
unknown records stay on the error path.
… compaction notes at the wire journal (MoonshotAI#3423)

* feat(agent-core-v2): remind the model of its context budget and point compaction notes at the wire journal

Add the contextBudget feature: a context_budget reminder that restates used/max/trigger tokens as usage crosses half, three quarters and ninety percent of the compaction trigger, and a compaction_ahead reminder delivered once per window when the trigger is within ten percent of the context window, so the model can persist and verify state while it can still call tools. Both read IAgentFullCompactionService.budget(), which derives from the same CompactionTriggerBudget that drives auto compaction, and both are stripped from the summarizer input.

Behind compaction_recovery_pointer, compaction records the wire journal line range it covered (wireLines on context.apply_compaction, folded into the replayable fullCompaction.wireRanges key) and appends a Context Recovery footer to the model-facing contextSummary with the on-disk wire.jsonl path, every earlier window's line range, and a primer on reading the journal; the UI-facing summary stays the note plus TODO. Read returns wire.jsonl lines under the sessions directory untruncated and spill-exempt so a single record can be read back after Grep locates it, and the compaction instruction tells the summarizer a recovery pointer follows the note.

Both flags default on; KIMI_CODE_EXPERIMENTAL_CONTEXT_BUDGET_REMINDERS=0 and KIMI_CODE_EXPERIMENTAL_COMPACTION_RECOVERY_POINTER=0 disable them. Telemetry gains context_budget_reminder, compaction_ahead_reminder, and ahead_* fields on compaction_finished.

* feat(agent-core-v2): ship context budget reminders and the recovery pointer without flags

- Remove the two experimental flags; both behaviors now ship unconditionally and the compaction instruction carries the recovery note in its template.
- Lower-bound recovery windows at the latest context.clear journal record so a window never points into history the user discarded.
- Add the appended recovery footer's estimated tokens to summaryOutputTokens so tokens_after and the post-compaction token floor stay honest.

* fix(agent-core-v2): cap event log reads and refuse empty-history compaction

- Cap a single wire.jsonl record read at 150k chars, below the window-minus-trigger margin, so one read can never push the context past the model window; the note points at sed | jq for longer records.
- Keep at least the last record when tail-reading the event log instead of returning silently empty output with a contradictory note.
- Fail the compaction when an overflow shrink would drop every message, instead of compacting an empty history and replacing the context with a groundless note.

* fix(agent-core-v2): drop the redundant ninety bucket and soften ahead-reminder wording

- Remove the 90% context-budget bucket: the compaction-ahead threshold is always at or below it, so it only echoed the stronger last-chance reminder moments later.
- Stop suggesting a commit as a way to persist state before compaction; files and the todo list cover it without prompting unwanted commits.
- Make the event-log note's primer reference conditional on a compaction having run.

* chore: merge the compaction changesets into one
…nshotAI#3521)

Co-authored-by: kimi-agent-bot <kimi-agent-bot@users.noreply.github.com>
Co-authored-by: qer <wbxl2000@outlook.com>
…inline their answers (MoonshotAI#3522)

* fix(agent-core-v2): keep background questions open past turn end and inline their answers

Background AskUserQuestion reused the generic task pipeline end to end, which
broke it in two ways: the pending interaction was still bound to the asking
turn, so it was cancelled the moment the agent finished its turn, and the
turn-end cancel response was then misread as an answer. On top of that the
completion notification only carried a pointer to the task output file,
forcing an extra Read round trip for a few bytes of JSON.

- Detach background question interactions from the asking turn so they stay
  pending until answered, stopped, or the agent closes.
- Treat cancelled interaction responses as dismissals.
- Inline the answer JSON in the question task notification and word the
  notification as answered or dismissed; fall back to the output file only
  when the answer exceeds the inline budget.
- Trim the background launch result to task id, status, and one next step.
- Fold transcript notification summaries before inline answer blocks.

* fix(agent-core-v2): fail background questions on tool errors and translate interaction cancellations in the question service

Follow-up hardening from review:

- The interaction kernel's cancellation response now has a named shape,
  InteractionCancellation, and SessionQuestionService.request translates it
  into a dismissal (null) before handing the result to callers. The
  AskUserQuestion tool no longer inspects answer maps for a cancelled key,
  so a bare answer map can never be mistaken for a cancellation.
- QuestionBackgroundTask settles as failed with the tool's message as the
  stop reason when the question tool reports an error, instead of writing
  the error text as completed output. The generic task notification then
  carries the reason, and the answered/dismissed wording is not used.
- The question notification only says answered or dismissed when the task
  output parses as an answers payload; any other output keeps the generic
  completed wording.
…ion mode (MoonshotAI#3529)

* feat(agent-core-v2): allow unanalyzable bash commands in auto permission mode

* feat(agent-core-v2): drop the dangerous command guard in auto permission mode
# Conflicts:
#	docs/en/customization/hooks.md
#	docs/zh/customization/hooks.md
@YeKc1M
YeKc1M merged commit 280e4b4 into feat/hook-permission-decisions Sep 4, 2026
27 checks passed
@YeKc1M
YeKc1M deleted the resolve-conflicts branch September 4, 2026 06:53
@YeKc1M
YeKc1M restored the resolve-conflicts branch September 4, 2026 07:00
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.