* fix(kap-server): report the real event watermark as last_seq in getSession
GET /api/v1/sessions/{id} hardcoded last_seq to 0, so event subscribers
catching up from the fetched watermark always replayed the whole session
journal from seq 0. Historical work_changed frames then surfaced as
spurious busy state in clients. Return the broadcaster's journal cursor
(live seq, or the persisted watermark for cold sessions) so subscribers
land on the journal tail with an empty replay.
* fix(kap-server): capture the event cursor before session state in getSession
A profile update landing between the summary read and the cursor read
could produce a response pairing stale fields with a cursor that already
covers the update event; subscribers from that last_seq would then skip
the event entirely. Capture the cursor first so every state read is at
least as new as the watermark; later events still replay past it.
The watermark test now relies on the dispatch queue ordering behind
getCursor instead of wall-clock polling.
* fix(migration-legacy): faithfully migrate kimi-cli user data into kimi-code
* fix(migration-legacy): faithfully migrate kimi-cli user data into kimi-code
- drop the built-in escape assertion from RuntimeWorkspaceView.resolve so it is a pure path mapping
- return the validated path from assertAllowed and compose it explicitly in terminalService
- add a bash test covering cwd outside the workspace roots
* feat(cli): add the kimi session list subcommand
- add the session command group with list: cwd scope by default, --all/--cwd/--archived/--limit/--json
- pass includeArchived through the SDK listSessions options (v2 klient query, v1 includeArchive mapping)
* docs: condense the session list changeset to one sentence
* fix(cli): sanitize session list rows and tidy option types
- strip control characters and line breaks from user-controlled row fields
- declare cwd/limit as plain optional properties without undefined unions
- 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
* 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
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>
* 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.
* 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.
* 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>
#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>
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 #3414
Co-authored-by: kimi-agent-bot <kimi-agent-bot@users.noreply.github.com>
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
* 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
* 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
* 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
* 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.
* 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
* 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(persistence): guard file reads against torn writes from external editors
A config.toml overwritten in place (truncate + write, the way plain
writeFile and some editors save) can be read mid-write by a concurrent
config reload -- the truncated head often parses as valid TOML, so the
reload silently adopts a config with the models/providers sections
missing and wipes the model table until the next watcher-driven reload.
Observed as the prompts.test.ts flake: ProviderDiscoveryService's
reload raced the test's writeFile and setModel then failed with
"Model stub is not configured".
FileStorageService.read now verifies the read against the file's size
after the read and retries briefly on mismatch, and prompts.test.ts
swaps its in-place config overwrites for atomic tmp+rename writes.
* fix(persistence): confirm hot files with a second stat, list the CLI package in the changeset
A size match alone is not proof of a stable snapshot: a writer that
truncates or writes a partial chunk and then pauses lets read and stat
observe the same transient length. When the file's mtime is within
100ms of now, the read is only accepted after a second stat 15ms later
reports the same size and mtime; settled files still return after one
stat. The changeset now lists @moonshot-ai/kimi-code per the changeset
rules for user-perceivable fixes shipped through the CLI.
* revert(persistence): drop the hot-file double-stat confirmation
The confirmation delayed every read of a recently written file by 15ms
(45ms for files being appended), which shifted server boot and reload
timing enough to break real tests in CI (config changedFields baseline,
fork parity). The simple size-verify guard covers the race that was
actually observed; a read-side guard cannot be airtight against an
in-place writer paused mid-write anyway -- atomic writes are the real
contract.
---------
Co-authored-by: user <user@userdeMacBook-Pro.local>
* fix(agent-core-v2): cache workspace alias resolution across calls
resolveAliasIds re-read the workspace catalog and the whole session
index from disk on every call, so the by_workspace grouping loop and
the per-workspace session counts paid repeated full-file reads per
workspace per request (~2.4s per 50-group page at 1.1k workspaces,
23 pages serially during a client startup drain).
Cache both files as precomputed snapshots (by-id map plus a
root-key -> alias ids index) invalidated by the storage watch events,
which cover atomic rewrites and cross-process writes; storage backends
without watch fall back to reading through. The first resolution primes
the workspace merge via IWorkspaceService.list() so the cached catalog
matches what WorkspaceService.get() would have returned.
* chore: drop the changeset; the user-facing entry ships with the app changelog
* fix(agent-core-v2): coalesce cold alias snapshot loads and guard publication
Concurrent cold resolveAliasIds callers (the /workspaces route fans out
per-workspace counts with Promise.all) all passed the cache check before
any caller finished loading, re-running the full catalog and session
index reads the cache exists to avoid; memoize the in-flight load
promise so a cold batch shares one read. Also capture the invalidation
generation before each read and publish the snapshot only when it is
unchanged, so a mid-read file replacement cannot leave a stale snapshot
installed over the watch invalidation.
* fix(agent-core-v2): publish catalog invalidation through the persistence owner
A debounced fs watch was the only invalidation channel for the alias
catalog snapshot, so an in-process catalog write stayed invisible to
resolveAliasIds for up to the watch debounce window while the previous
read-through code observed every completed write immediately.
IWorkspacePersistence now exposes onDidChange: FileWorkspacePersistence
fires it synchronously on save and re-fires the underlying document
watch (covering atomic rewrites and cross-process writers), and the
aliases service subscribes to it instead of watching raw storage keys.
The session index snapshot keeps the filesystem watch, matching the
read-side ownership of that file.
* fix(agent-core-v2): invalidate session alias snapshots on append-log writes
A flushed session_index.jsonl append was invisible to resolveAliasIds
for up to the fs-watch debounce window, so a sessions request issued
right after a session create could resolve the workspace's aliases from
the pre-append snapshot. IAppendLogStore now publishes onDidWrite after
each durable flush (append batches and rewrites), and the aliases
service drops its session-index snapshot through that event; the raw
filesystem watch stays as the channel for cross-process writers.
* fix(agent-core-v2): fire append-log write events only after actual writes
Once a key has a LogState, every global flush() (WireService flushes
after ordinary agent persistence) completed it successfully and fired
onDidWrite unconditionally, so idle agent activity kept dropping the
alias session-index snapshot and forced full re-reads of an unchanged
index. drain() now reports whether it appended anything and the write
event fires only when a flush actually persisted a batch or a rewrite.
* fix(agent-core-v2): retry shared snapshot loads that span a write
Callers joining an in-flight single-flight load after a completed write
still received the pre-write snapshot: the generation check only guarded
cache publication, not the value returned to awaiters. Each load now
carries the generation it started at, and catalog()/sessionIndex()
re-read (coalesced through the same single-flight) when the settled
load's generation is stale.
* fix(agent-core-v2): report partial progress when an append-log drain fails
A drain that persisted one batch and then failed the next threw without
recording the durable write, so onDidWrite never fired for records that
were in fact persisted (the alias session-index snapshot then missed
its synchronous invalidation). The write box now threads through the
whole owned flush: each successful batch marks it, and the event fires
before the failure propagates.
* fix(agent-core-v2): retry the whole alias resolution across a mid-write
The per-snapshot retry guarded each read on its own, so a write landing
between the catalog and session-index reads returned an alias set
assembled across two generations. resolveAliasIds now captures the
generation once, reads both snapshots together, and retries the whole
resolution when either input was invalidated mid-flight. The
spanned-write test is reworked to gate after the load (so the snapshot
content genuinely predates the write), and a new case covers the
cross-generation mix directly.
* fix(agent-core-v2): replace the session-index fs watch with a size check
A resident chokidar watcher per server on the shared home directory
degraded watch delivery for unrelated files under test-suite boot
volume (the prompts suite lost the config.toml reload race and the
catalog missed a just-written model). In-process appends were already
covered synchronously by the append-log write event; cross-process
writers now surface through a per-call size comparison on the
append-only file, which costs one stat per resolve and needs no
resident watcher.
* fix(kap-server): only fold user-origin steers by content in the cold transcript
* fix(transcript): check marker-only origins before the steer content match
* fix(transcript): limit the steer bypass to marker-only skill triggers
* fix(transcript): consume the steer count for marker-only activations
* fix(transcript): pair steered contents with messages by origin kind
* refactor(kap-server): read the steer origin kind without nested casts
---------
Co-authored-by: kimi-agent-bot <kimi-agent-bot@users.noreply.github.com>
* feat(auth): split model readiness from sign-in state in /api/v1/auth
GET /api/v1/auth now reports models_ready (the default model resolves
against the configured catalog, providerless and env-injected models
included) instead of the compound ready flag, and no longer carries
default_model — config values are served by /config alone. The v1
summary schema follows.
OAuth managed-model refreshes now heal a lost default model: the
refresh snapshot includes defaultModel, so an unchanged catalog with
a missing default still lands the write-back branch and re-selects
one. The refresh also rebases onto a fresh config read after the
remote fetch, so a model or thinking change made during the fetch is
no longer overwritten. The shared discovery refresh path (scheduler,
POST /providers/{id}:refresh) heals the default the same way.
Config changes are now published to WS clients on every write path:
a debounced+trailing publisher bridges IConfigService section changes
to ConfigChanged with camelCase changedFields and a full config
projection, and the broadcaster forwards event.config.changed and
event.model_catalog.changed (both previously published but never
delivered). All three event types are registered in the event unions,
so session_event parsing and AsyncAPI describe them.
BREAKING CHANGE: GET /api/v1/auth drops the ready and default_model
fields in favor of models_ready; event.config.changed's changedFields
is now camelCase domain names instead of the raw snake_case request
keys (v1 summary schema follows).
* fix(kap-server): expose the session model in session list projections
GET /api/v1/sessions hardcoded agent_config.model to '' and the v2
projection had no model field at all, so clients could only learn a
session's model via the post-select /status read — which races the WS
replay and often never lands. SessionFacts now carries the live
session's model (same source as the snapshot route), toWireSession
emits it, and the v2 activity domain gains a nullable model field.
* fix(kap-server): gate prompt submission on the effective session model
The submit gate called ensureReady() with no override, so it only ever
validated config.default_model: a session with a bound model (or a
prompt carrying one) was rejected with 40113 whenever default_model was
missing or dangling. Pass the effective model (request model, then the
agent profile's bound model, falling back to default_model inside
ensureReady) on both the prompt submit and btw routes.
* fix(agent-core-v2): honor defaultProvider in model readiness resolution
resolveModelForReady stopped at the flat baseUrl fallback, so a model
that omits provider/providerId and relies on the configured
defaultProvider resolved at runtime (ModelCatalog.resolveProviderContext
falls back to it) while /api/v1/auth reported models_ready:false and the
send gate rejected the prompt. Mirror the runtime order (providerId ->
provider -> defaultProvider -> flat baseUrl) and pass the configured
default provider from both readiness callers.
* fix(kap-server): redact inline model credentials from config responses
toConfigResponse only redacted the providers section, so a model's
inline apiKey/oauth rode GET /config verbatim and, via the new
event.config.changed publisher, every WS connection plus the persistent
event journal. Project the models section the same way: strip
credential fields and report has_api_key.
* fix(agent-core-v2): honor defaultProvider in ensureReady credential checks
The readiness phase learned the defaultProvider fallback, but the
credential phase right after still derived the provider only from the
model's explicit fields: a model omitting provider/providerId passed
readiness yet missed the default provider's apiKey/OAuth material and
prompts failed with auth.token_missing. Mirror the same provider chain
(providerId -> provider -> defaultProvider) when resolving credentials.
* fix(kap-server): validate the model a profile bind will select at the prompt gate
The gate validated the session's current model even for a prompt that
switches profile without a model — but bind falls back to defaultModel
in that case, so a stale session model drew a misleading 40113 before
bind could run. Gate on bind's selection order instead: the request's
explicit model, then the default on a profile switch, then the session's
bound model.
* fix(kap-server): redact inline service credentials from config responses
The earlier redaction covered providers and models, but toConfigResponse
still passed the services section through verbatim: inline or
env-injected apiKey, oauth references, and credential-bearing
customHeaders rode GET /config and, via the event.config.changed
publisher, every WS connection plus the persistent event journal.
Project services the same way: strip apiKey/oauth into has_api_key and
report only the header names as custom_header_keys (the MCP
envKeys/headerKeys convention).
* fix(kap-server): keep unlisted config domains through event validation
The config.changed broadcaster returned the zod-parsed config, which
strips domains absent from configResponseSchema (mcp, identity,
model_catalog, image, tools, token_counting): changedFields named them
while the advertised full snapshot no longer matched GET /api/v1/config.
Make the response projection passthrough (defineRoute validates only
requests, so REST responses are unaffected).
* fix(agent-core-v2): use the exact configured key for model readiness lookups
resolveModelForReady trimmed the model id before the models-table lookup
while ModelCatalog and ensureReady use the configured string as an exact
record key: a whitespace-padded default_model was reported ready and then
crashed the submit gate with an internal error instead of 40113, and a
legitimate key containing spaces was reported dangling. Trim only rejects
blank values now; the lookup always uses the raw key.
* fix(protocol): keep unlisted config domains in the shared event projection
The shared configResponseSchema stripped domains it does not enumerate
(mcp, identity, model_catalog, image, tools, token_counting, subagent,
secondary_model), so event.config.changed parsed through agentEventSchema
named them in changedFields while omitting their values. Make the shared
projection passthrough like the kap-server-local one.
* fix(agent-core-v2): use the exact default_provider key in readiness checks
The defaultProvider fallback trimmed the configured value before the
providers-table lookup while ProviderService and ModelCatalog use the
configured string verbatim: a whitespace-padded default_provider could
build successfully yet report not-ready (40113), or report ready for a
provider runtime resolution cannot find. Trim only rejects blank values;
the lookup uses the raw key.
* chore: sync web dist from code-app
Rebuild the bundled web UI against this branch's /auth contract (models_ready, no ready/default_model): the previous bundle still read the old fields and stayed in the not-ready flow against this server.
code-app: 000d2594ff3e95b553be326126bab3f939b62944
* Revert "chore: sync web dist from code-app"
This reverts commit 9400a24a03863b3e8b780dda251540f824f02f3a.
* fix(oauth): rebase the default selection after the refresh fetch
A provider refresh snapshots the config before the remote catalog fetch;
when the user selects a default model while the fetch is in flight, the
stale snapshot's empty default made an otherwise unchanged catalog enter
the write path and the self-heal persisted the generated default over the
user's newer selection. Each branch now re-reads and rebases the
default/thinking selection after its fetch, before cloning, comparing,
or writing.
* style(kap-server): pass optional custom_header_keys without conditional spread
* feat(kap-server): support server-local path attachments
Web and desktop clients can now attach files, images, and videos to a
prompt by server-local absolute path instead of uploading a copy. The
daemon validates the path (absolute, realpath-resolved, non-sensitive,
local runtime only) and references the original file in place, so the
agent reads the original path; the upload flow is unchanged.
Submitted file attachments are also recorded on the prompt origin and
projected as typed transcript attachments, so web clients render
attachment chips for plain files without parsing the model-facing
notice text.
* fix(kap-server): forward file attachment metadata from skill activations
* Delete .changeset/web-attach-by-path.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* fix(auth): keep OAuth login alive when its own provisioning writes the provider
* fix(auth): settle the OAuth flow as authenticated before provisioning
* fix(auth): publish the login as authenticated only after provisioning completes
* fix(mcp): forward structuredContent only as fallback when content has no usable text
Servers that follow the MCP spec's backwards-compatibility SHOULD return
the same JSON both as a TextContent block and as structuredContent.
Forwarding both to the model sent the same data twice. structuredContent
now rides the mcp-structured-result block only when the content blocks
carry no usable text; _meta still always passes through.
* test(mcp): verify structured-content fallback over a real stdio MCP server
Round-trip four server result shapes (dual-emit, structuredContent-only,
prose+structured, vendor _meta) through StdioMcpClient and the output
pipeline, so the fallback behaviour is checked against real protocol
bytes instead of hand-built result objects.
* fix(mcp): dedupe structuredContent only against its verbatim serialization
The earlier has-usable-text gate also suppressed the structured payload
when content was a lossy human summary — the primary case from #2554
(list_projects returning 'N item(s)' while the items live in
structuredContent). Skip the structured block only when a content text
block parses to the same JSON value (semantic compare, key order and
formatting insensitive); summaries and structured-only results still
pass through.
* fix(mcp): forward structuredContent only when content does not already cover it
Replace the verbatim-serialization comparison with a size heuristic:
well-behaved servers render the same data into content (the spec's
dual-emit, or a faithful human reorganisation), and either way the text
measures at roughly the same size as the payload, so forwarding it would
double the information. Append the structured payload only when content
carries no usable text, or when the payload is more than twice the text
size — the signature of a lossy summary. Verified against a live video-
editor MCP whose tools all measure a json/text ratio of 1.2-1.9.
* fix(mcp): send content or structuredContent to the model, never both
Final policy: content and structuredContent are alternatives. content
wins whenever it carries anything usable (a media block or non-whitespace
text); structuredContent fills in only for an empty content array. There
is no reliable signal that the structured payload is richer than what the
server already rendered into content, so no size or structure heuristic
is attempted. _meta still always passes through.
* refactor(mcp): rename the structured-extras wrapper to mcp-result-extras
The block carries structuredContent and/or _meta; the old
mcp-structured-result name was inaccurate whenever it is a pure _meta
carrier.
* ci: retrigger after flaky mcpCore client-stdio close-buffering test
* ci: retrigger after flaky minidb concurrent writer/reader test
* fix(secondary-model): stop rewriting the section when providers refresh or are removed
Provider refresh, provider deletion/rename, catalog/registry import,
OAuth logout, and SDK removeProvider used to cascade into the user's
[secondary_model] block: pool entries were silently pruned, and the
whole section was deleted when its effective default dangled. The
cascade ran from a cache-refresh path (including an unattended 6h
scheduler), so upstream model-list changes could irreversibly destroy
hand-written configuration without any notice.
Config is user intent; the catalog is an availability snapshot. Stop
rewriting the section on every provider/models writer. An entry whose
model no longer resolves fails pool validation on the next session
create with a message naming the offending alias, which is the same
fail-fast contract hand-written typos already had.
* chore(sdk): add changeset for the removed secondary-model cascade export
* Delete .changeset/sdk-remove-secondary-model-cascade.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* Delete .changeset/secondary-model-no-silent-rewrite.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* feat(kap-server): add task detach action to move foreground tasks to background
* test(kimi-code-sdk): normalize v2-only parentToolCallId in task parity projections
* fix(agent-core-v2): distinguish user-initiated detach in tool result text
* feat(agent-core-v2): mention user-initiated backgrounding in the bash tool description
* feat(agent-core-v2): use a client-agnostic background-task panel hint in the bash tool description
* ci: retrigger checks
* feat(agent-core-v2): client-agnostic human_shell_hint and a detached_by_user marker in tool results
* fix(protocol,docs): declare parent_tool_call_id in the shared task schema and document the detach action
* style: remove added comments
* fix(agent-core-v2): clear the turn outcome when an undo rewinds the turn it describes
* fix(agent-core-v2): clear the turn outcome too when an undo outruns the tracked anchors
* fix(agent-core-v2): reconcile the persisted turn outcome against the replayed wire on restore
* fix(agent-core-v2): keep the persisted outcome when an undo rewinds only later turns
---------
Co-authored-by: kimi-agent-bot <kimi-agent-bot@users.noreply.github.com>
The drain probe treated any callTool failure as proof the transport close
had been processed, but a stdin write error (EPIPE) can win the race
against the child's exit notification, so the listener could be registered
before the close was buffered and the synchronous replay never fired.
Break the drain loop only on errors that are impossible before the SDK's
_onclose ran ('Not connected' / 'Connection closed' / the transport's
not-running guard).
Also drop the assertion that the replayed reason contains the child's
final stderr: the reason snapshots the stderr buffer at close time, which
can legitimately race delivery of the last stderr chunk (reproduced 2/40
under CPU load). Tail capture itself stays covered by the stderrSnapshot
assertion. Finally, flush the fixture's banner through the stderr write
callback before process.exit so the write cannot be truncated.
Co-authored-by: kimi-agent-bot <kimi-agent-bot@users.noreply.github.com>