* feat(oauth): add the dynamically_loaded_tools capability from the official models endpoint
* feat(agent-core-v2): support per-server deferred disclosure of MCP tools
* feat(klient): mirror per-server deferred in the MCP config contract
* fix(agent-core-v2): honor per-server deferred for the needs-auth MCP tool
* fix(agent-core-v2): register select_tools regardless of the profile tool allowlist
* feat(agent-core-v2): default MCP servers to inline tool disclosure unless deferred is enabled
* refactor(agent-core-v2): fold the prompt queue into the agent loop service
Delete AgentPromptService/IAgentPromptService: prompt submit/queue/steer/
abort now ride the agent machine queue through IAgentLoopService's extended
surface (submitPrompt/submitSteerPrompt/enqueuePrompt/steerPrompts/
abortPrompt/drainPrompts/injectPrompt/retryPrompt/promptQueue plus
hooks.onBeforeSubmitPrompt and the promptAdmission reservation slot).
Durable prompt.* event classes move to agent/prompt/promptEvents.ts and keep
their registrations; promptAdmissionKey keeps its fold. Dead assets go away:
promptResolutionKey, PROMPT_ALREADY_COMPLETED (kap-server 40903 path),
promptLaunchingKey and clear(). Compaction gating (queue while compacting,
resume on finish) moves into the loop reservation drain.
The 'agentPromptService' wire name keeps its shape: a thin AgentPromptChannel
facade (same decorator id) forwards submit/submitSteer to the loop, so
klient memory/ipc/debug transports and the kap-server dispatcher are
unchanged.
* test(agent-core-v2): re-pin prompt queue semantics on the loop service
Rewrite promptService.test.ts (25 cases, count unchanged) against the real
AgentLoopService with the scripted-LLM harness, fold the launch-window
reservation out of promptQueue().pending to match the old snapshot contract
exactly, and update the debug-channels pin to the facade's submit/submitSteer
surface.
* refactor(agent-core-v2): lint-align the loop prompt queue surface
* fix(agent-core-v2): settle prompt completions on the turn-result chain
Match the deleted service's microtask position: prompt.completed/aborted and
the completion deferreds fire from a result.then chain attached at bind time
instead of synchronously inside endTurn, so wire/emit ordering with
post-turn producers (tool unregister, next-prompt launch) stays identical to
the baseline snapshots. Steered children move onto the reservation so the
chain can settle them after the active turn is gone.
* refactor(agent-core-v2): move the prompt-submit gate into the agent machine
* refactor(agent-core-v2): keep image compression captions inline in prompt messages
* refactor(agent-core-v2): drop the durable prompt admission facility
* refactor(kap-server): resolve prompt auth at turn runtime instead of submit time
* refactor(agent-core-v2): teach the agent machine queue metadata, multi-id steer, gate rewrite, and pause
* refactor(agent-core-v2): converge prompt submission on machine queue user entries
* refactor(agent-core-v2): replace loop prompt records with a waiter registry fed by machine events
* feat(agent-core-v2): make the compaction attempt limit configurable
Add loop_control.compaction_max_attempts (default 5) to control the
maximum total attempts for a failing compaction request, replacing the
hardcoded MAX_COMPACTION_RETRY_ATTEMPTS constant. The value flows
through ProfileModelContext into the compaction retry loop, covering
both retryable generation errors and empty/truncated response shrinks.
* fix(agent-core-v2): bound compaction shrink attempts and compute retry delays lazily
Address codex review on #3750: the empty/truncated shrink path allowed
maxAttempts + 1 requests (throwing only when the shrink counter exceeded
the limit), and the backoff delay array was allocated eagerly with
maxAttempts - 1 entries before the first request. The shrink path now
stops at the configured limit, and each delay is computed lazily from
the retry index.
* fix(agent-core-v2): count every compaction request against the shared attempt limit
Address the second codex review round on #3750: the retryable-error and
empty/truncated recovery paths kept separate counters, and the retry
budget reset after each shrink, so alternating failure modes could issue
more requests than compaction_max_attempts. A single requestAttempts
counter now gates all recovery paths (the overflow shrink path keeps its
own tighter cap of 3), making the configured value a true maximum on
total requests per compaction round.
* feat(agent-core-v2): persist the human runtime mirror as human.* records in the wire journal
Introduce mode-2 coexistence: the machine eventStore journal becomes a
tree-projection-filtered adapter over the same wire file (single writer via
the WireService append queue), the engine switches from memoryJournal to the
file journal with a seed fallback while no human records exist, and cold
readers skip the new record family constructionally. Adds downgrade
round-trip e2e coverage, whitelists agent.switched/context.undone from
unknown-record telemetry, asserts the undo switch triple after flush, and
resets the machine engine after an undo rerun so machine and wire turn
clocks stay aligned.
Squashed from:
refact(agent-core-v2): project wire journal as an in-memory branch tree behind IAgentJournal
refact(agent-core-v2): undo as branch switch with dual-emitted legacy record and restore replay
refact(agent-core-v2): queue late attachments during restore rerun and flatten fork sources to the active chain
feat(agent-core-v2): persist the human runtime mirror as human.* records in the wire journal
* feat(agent-core-v2): add full compaction to the human agent domain
* refactor(agent-core-v2): keep pending input state events out of the wire journal
* refactor(agent-core-v2): flip agent DI scope ownership to session-spawned agent actors
* refactor(agent-core-v2): build the agent wire and blob views in the session layer
* refactor(agent-core-v2): persist human domain records under the agent.* wire namespace
- replace the human. record prefix with an explicit whitelist (AGENT_WIRE_RECORD_TYPES) guarded against the Event2 vocabulary; legacy human.agent.* journals stay readable through a transition prefix
- unknown-type records skipped during restore now go to the log file instead of the console (malformed records still report to the console)
* refactor(agent-core-v2): compose the llm recovery chain outside the turn machine
The turn machine hardcoded credentialsRecovery as the head of its
recovery chain and interpreted a refreshCredentials flag on proposals,
coupling the turn to the credentials domain. Strategies now return
self-describing records (strategy/action, optional messages, optional
opaque prepare effect), the turn only runs prepare and/or swaps
messages, and the engine composes the chain via chainRecoveries.
* refactor(agent-core-v2): inline the two-strategy recovery composition
With self-describing proposals the chain is a one-liner at each call
site; the named chainRecoveries combinator had only two call sites and
no third strategy to justify it.
The Remote Control HTTP tunnel validated each base64 message with a
backtracking regular expression. Requests larger than ~3.5MB (base64
~5MB) exhausted the V8 Irregexp backtracking stack and threw
RangeError, which the catch-all mapped to a 400 with an empty body,
surfacing as a generic connection failure on the web client.
Replace the regex with an O(n) character check, reject oversize
requests from the base64 length before decoding, and map non-SyntaxError
failures to 502 instead of 400.
* fix(agent-core-v2): rebuild session and search indexes on unrecoverable storage failures
* fix(agent-core-v2): propagate search sync truncation and track session count in index freshness
* fix(minidb): wipe derived stores under their own locks during rebuild
* fix(agent-core-v2): isolate wiped stores, invalidate stale projections, and account sync failures
* feat(agent-core-v2): replace session index freshness scans with a dirty journal and targeted reconciliation
* fix(agent-core-v2): split transient failures by op kind, surface failed startup reconciliation, and cool down search session skips
* feat(agent-core-v2): allow rm -rf targeting /tmp and /temp paths
The dangerous command guard flagged every recursive force rm regardless
of target. rm -rf now falls through without an ask when all operands are
literal paths under /tmp or /temp (segment-level prefix, no .. escape);
mixed, non-literal, or out-of-prefix targets stay dangerous.
* chore: add changeset for rm -rf temp path allowance
* feat(remote-control): compress textual tunnel responses with gzip
* fix(remote-control): honor explicit gzip exclusion over accept-encoding wildcard
* fix(remote-control): skip gzip for 206 responses and vary by accept-encoding
* fix(remote-control): compress tunnel responses off the event loop
* fix(remote-control): vary identity responses and version compressed etags
* fix(remote-control): drop etags from gzipped tunnel responses
* fix(agent-core-v2): honor request-level toolMessageConversion in openai formats
The requester already forwarded LlmRequestConfig.toolMessageConversion
into formatRequest, but the openai chat and openai-responses formats
only read the trait hook, so the explicit request config was silently
ignored. Resolve the mode as request config, then trait default, then
the protocol default, and pass the resolved value into lowering.
* refactor(agent-core-v2): split the protocol trait into per-protocol dialects and provider connection
The ProtocolTrait interface bundled endpoint/headers connection config,
model capability, message/params conversion hooks, and error
classification into one bag, and every format received the whole bag
whether or not it consumed each hook — hooks a protocol ignores were
accepted by the type system and silently dead at runtime.
Split it by consumer:
- ProviderConnection (protocol/connection.ts): endpoint env
declaration and default headers, still resolved per request inside
generate.
- Per-protocol typed dialects (OpenAIDialect, OpenAIResponsesDialect,
AnthropicDialect, GoogleGenAIDialect): only the customization points
each protocol actually consumes; data-shaped hooks (reasoningKey,
toolCallIdPolicy, toolMessageConversion, strictThinkingValidation)
are data fields, and message/history hooks use the protocol wire
types instead of Record<string, unknown>.
- convertError moves to a requester option, capability to a provider
variant field.
Dialects are bound when the format is created (createOpenAIFormat and
siblings), so FormatRequestInput and the stream parser carry request
data only; ProtocolTrait is deleted. The thinking hook returns the
kwargs and the preserveThinking flag together as ThinkingApplication,
and the kimi dialect emits the final thinking params directly instead
of routing them through an extra_body flatten in buildParams. The
llm-adapter layer is migrated to the same {connection, dialect,
convertError} assembly.
* refactor(agent-core-v2): compose format stages and dialect hooks in the requester pipeline
* refactor(agent-core-v2): rename protocol dialects to traits
* fix(agent-core-v2): include the system message in openai mergeHistory input
* refactor(agent-core-v2): enforce the format/trait contract boundary
- move CONTEXT_MANAGEMENT_BETA into anthropic/contract and have the kimi
trait import wire types from each protocol's contract.ts instead of
format/lower; export the four contract modules from human/index.ts and
guard the boundary in check-import-boundaries
- rename protocol/trait.ts to protocol/thinking.ts and move TraitContext
to protocol/base.ts; clean up the remaining dialect-era test variable
- type extractUsage as OpenAIRawUsage / OpenAIResponsesRawUsage instead of
Record<string, unknown> and drop the requester-side casts
* refactor(agent-core-v2): thread the request config by spread and drop dead format types
- the four requesters spread LlmRequestConfig into the plan input instead
of hand-enumerating fields, so a new cross-protocol config field cannot
be silently dropped per protocol
- ProtocolFormat loses the phantom TRequest/TResponse type parameters;
the dead OpenAIRawResponse/AnthropicRawResponse/GoogleRawChunk contract
types go with them
- the thinking test now names the extra_body flatten behavior explicitly
* refactor(agent-core-v2): keep protocol format modules internal to the requester pipeline
- drop the four format barrels (and openai/reasoning-key) from
human/index.ts; each base's public seam is now exactly
contract / trait / requester
- stop re-exporting contract wire types from format/lower modules so the
neutral vocabulary has a single home; move the *LoweredMessage staging
types out of contract into the owning format module
- guard the seam in check-import-boundaries: only llm/requester/bases
code and tests may import format/lower/patterns/reasoning-key
* refactor(agent-core-v2): plug llm credentials in through request config
Replace the withAuth/withAuthUpload requester decorators with a
credential contribution point on LlmRequestConfig: the llm machine's
request actor (machine path) and ModelRequesterImpl (direct path)
resolve credentials per attempt, and a recoverable 401 is recovered by
invalidating and re-resolving — emitted as llm.recovering on the turn
machine's credentials branch.
* fix(agent-core-v2): surface request-actor failures as llm.failed.remote
A rejection inside the llm machine's detached request actor (credential
resolution, message resolvers, or a throwing requester) could not reach
the machine, leaving the turn stuck in thinking. Convert it to an
llm.failed.remote event so the turn lands in its failed state.
* docs(agent-core-v2): attribute retry/recovery to the turn machine in llm.md
* refactor(agent-core-v2): unify credentials in human/credentials, turn-owned 401 recovery
* fix(agent-core-v2): credential recovery for direct paths, abort guard, turn-snapshotted credentials
* feat(agent-core-v2): IModelCatalog.generate with stream credential recovery
* refactor(agent-core-v2): inline credential recovery at call sites
Remove the attemptWithCredentialRecovery/streamWithCredentialRecovery
helpers from human/credentials; the single-retry recovery is now written
out at each direct call site (catalog ping/generate, full compaction,
media upload) so the control flow reads linearly without a wrapper hop.
The credentials module keeps only the provider factories and the
credential application helpers; recoverability and invalidation stay on
the LlmCredentialProvider itself.
* fix(agent-core-v2): settle the queued turn when a machine turn settles before gating
When the llm machine fails before requester.generate() runs — for
example when OAuth credential resolution rejects — the loop cleared the
pending machine turn marker on turnSettled without ever binding or
settling the queued reservation, leaving the submitter's ready/result
promises and settled() hanging. The loop now settles the unbound
reservation explicitly: cancelled on abort, failed otherwise, mirroring
the evaluateSettle error propagation.
* fix(agent-core-v2): bind and end pre-gate failures through the normal turn lifecycle
The direct settlement resolved turn.result without endTurn(), so no
TurnPrompt/TurnStarted/TurnEnded/AgentErrorEvent was published and the
next queued reservation was never launched. The unbound reservation is
now bound via beginActiveTurn and ended via endTurn (failed, or
cancelled on abort), chained onto afterChain when the previous turn is
still settling, so pre-gate failures get the same lifecycle events and
queue progression as any other failed turn.
* fix(agent-core-v2): settle seeded turns on pre-gate failure, preserve coded credential errors, close steps on recovering
- Notification-seeded machine turns that fail before gating now bind a
seeded reservation and end through endTurn like queued ones, instead
of leaving the nudge pending and settled() hanging
- The llm machine's request actor attaches the raw error to
llm.failed.remote and the turn machine prefers it for its terminal
failure, so coded errors (e.g. auth.login_required) survive to
endTurn's error payload
- projectMachineEvent now closes the current machine step on recovering
exactly as it does on retrying, keeping step.begin/step.end balanced
when credential recovery re-enters the request
* fix(kap-server): add generate to IModelCatalog test fakes
* fix(klient): migrate examples to the credentials API
* test(agent-core-v2): drop credential recovery coverage
* refactor(agent-core-v2): fold kimi-oauth credential adapter into credentials/
* refactor(agent-core-v2): pair credential refresh with invalidate, optional Model.credentials, abort-safe request actor
- oauthCredentials.invalidate() now starts the forced refresh eagerly and
hands the pending token to the next resolve(), so the force signal can no
longer leak into or be consumed by unrelated resolves
- Model.credentials becomes optional, dropping the {} as never /
staticCredentials(undefined) filler in test fakes
- the llm machine request actor swallows abort outcomes instead of
reporting llm.failed.remote, so an aborted turn settles as aborted
rather than failed
* refactor(agent-core-v2): credentials recovery strategy chain, shared credential-recovery executors, single-proposal failure triage
* fix(agent-core-v2): settle message-less notifications on pre-gate failure, recover credentials in media resolver uploads
* test(kap-server): add the IModelCatalog.generate stub to the history suite
* fix(agent-core-v2): honor request-level toolMessageConversion in openai formats
The requester already forwarded LlmRequestConfig.toolMessageConversion
into formatRequest, but the openai chat and openai-responses formats
only read the trait hook, so the explicit request config was silently
ignored. Resolve the mode as request config, then trait default, then
the protocol default, and pass the resolved value into lowering.
* refactor(agent-core-v2): split the protocol trait into per-protocol dialects and provider connection
The ProtocolTrait interface bundled endpoint/headers connection config,
model capability, message/params conversion hooks, and error
classification into one bag, and every format received the whole bag
whether or not it consumed each hook — hooks a protocol ignores were
accepted by the type system and silently dead at runtime.
Split it by consumer:
- ProviderConnection (protocol/connection.ts): endpoint env
declaration and default headers, still resolved per request inside
generate.
- Per-protocol typed dialects (OpenAIDialect, OpenAIResponsesDialect,
AnthropicDialect, GoogleGenAIDialect): only the customization points
each protocol actually consumes; data-shaped hooks (reasoningKey,
toolCallIdPolicy, toolMessageConversion, strictThinkingValidation)
are data fields, and message/history hooks use the protocol wire
types instead of Record<string, unknown>.
- convertError moves to a requester option, capability to a provider
variant field.
Dialects are bound when the format is created (createOpenAIFormat and
siblings), so FormatRequestInput and the stream parser carry request
data only; ProtocolTrait is deleted. The thinking hook returns the
kwargs and the preserveThinking flag together as ThinkingApplication,
and the kimi dialect emits the final thinking params directly instead
of routing them through an extra_body flatten in buildParams. The
llm-adapter layer is migrated to the same {connection, dialect,
convertError} assembly.
* refactor(agent-core-v2): compose format stages and dialect hooks in the requester pipeline
* refactor(agent-core-v2): rename protocol dialects to traits
* fix(agent-core-v2): include the system message in openai mergeHistory input
* refactor(agent-core-v2): enforce the format/trait contract boundary
- move CONTEXT_MANAGEMENT_BETA into anthropic/contract and have the kimi
trait import wire types from each protocol's contract.ts instead of
format/lower; export the four contract modules from human/index.ts and
guard the boundary in check-import-boundaries
- rename protocol/trait.ts to protocol/thinking.ts and move TraitContext
to protocol/base.ts; clean up the remaining dialect-era test variable
- type extractUsage as OpenAIRawUsage / OpenAIResponsesRawUsage instead of
Record<string, unknown> and drop the requester-side casts
* refactor(agent-core-v2): thread the request config by spread and drop dead format types
- the four requesters spread LlmRequestConfig into the plan input instead
of hand-enumerating fields, so a new cross-protocol config field cannot
be silently dropped per protocol
- ProtocolFormat loses the phantom TRequest/TResponse type parameters;
the dead OpenAIRawResponse/AnthropicRawResponse/GoogleRawChunk contract
types go with them
- the thinking test now names the extra_body flatten behavior explicitly
* refactor(agent-core-v2): keep protocol format modules internal to the requester pipeline
- drop the four format barrels (and openai/reasoning-key) from
human/index.ts; each base's public seam is now exactly
contract / trait / requester
- stop re-exporting contract wire types from format/lower modules so the
neutral vocabulary has a single home; move the *LoweredMessage staging
types out of contract into the owning format module
- guard the seam in check-import-boundaries: only llm/requester/bases
code and tests may import format/lower/patterns/reasoning-key
* feat(kap-server): add flat entity message protocol (v3 WS + history API)
* feat(kap-server): add task notification user messages, skill origins, and history has_more
* fix(kap-server): page history by turns so oversized turns stay reachable
* fix(kap-server): synthesize foreground subagent tasks in the cold fold when no task records exist
* fix(kap-server): dedupe queued prompts for turn records without a prompt id
* fix(kap-server): adapt v3 projection to agent-core-v2 API changes and filter empty thinking
* feat(kap-server): align v3 protocol with design revision 1094 and add agent.state
---------
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
* fix(agent-core-v2): tower resume, wake, teardown, and mission-resolution reliability
- Require run_in_background=true in every roster-agent resume guidance and veto foreground roster resumes, abstaining when the profile has no background task tools; resume ids are trimmed before the roster lookup.
- Wake the tower when workers send inbox messages to it or broadcast, coalesced into a single notification per batch.
- Make teardown idempotent: worktrees git no longer knows are reported as already removed instead of failing.
- Resolve branch-to-mission unambiguously (closed missions skipped, latest open wins, merges of closed-only branches refused), reject plan-time branch collisions against existing missions and unowned git refs, and reserve the protocol names tower/all.
* fix(agent-core-v2): validate reserved tower names before spawn side effects and stamp reviews with their mission
- TowerSpawn rejects the reserved protocol names tower/all up front; the roster backstop no longer fires after the worktree, subagent, and detached task already exist.
- Reviews now record the mission they were written for; TowerMerge refuses when the latest clean review was stamped for a different mission, or carries no stamp while the branch is shared with other mission records.
* fix(agent-core-v2): pin tower reviews to the mission assigned at reviewer spawn
The reviewer's roster entry now records the mission resolved when it was spawned, and submitReview stamps that assignment instead of recomputing at submit time, so a review stays pinned to the mission the reviewer was briefed on even if the branch's resolution moved on; roster-less submissions fall back to the current resolution.
* chore: drop the enable hint from the tower changeset
* refactor(agent-core-v2): omit undefined fields in tower frontmatter rendering
renderFrontmatter now accepts and skips undefined values, so callers pass optional properties (review mission, inbox scope/action/consent_ref) directly instead of using conditional spreads, per the repository rule for optional object properties.
* fix(agent-core-v2): leave reviews by unpinned tower reviewers unstamped
A roster reviewer without a recorded mission assignment (entries persisted before reviewMissionId existed) no longer gets a stamp inferred from the submission-time branch resolution, which could name a different mission than the one it was briefed on; unstamped reviews on shared branches are refused by the sibling-mission gate until a pinned re-review, while unshared branches merge as before. Tower-submitted reviews keep the resolution fallback.
* fix(agent-core-v2): probe tower worktree registration per path instead of parsing porcelain output
git worktree list --porcelain emits paths containing newlines literally, so the line-based parser truncated them and teardown misreported those worktrees as already removed; the -z flag suggested for this is unavailable on the git versions the project supports (2.34). teardown now checks each mission worktree directly: git -C <path> rev-parse --git-dir resolving under <repo>/.git/worktrees means registered, which round-trips any filesystem path on any git version.
* fix(agent-core-v2): resolve the common git dir when probing tower worktree registration
In a linked checkout repoRoot/.git is a file and worktree admin directories live under the repository's common git directory, so every mission worktree failed the containment check and teardown left them on disk; the probe now resolves git rev-parse --git-common-dir first.
* docs(agent-core-v2): permit foreground roster resumes when background task tools are inactive
* fix(agent-core-v2): reject blank or whitespace-padded tower agent names
Recipients are trimmed at send time, so a name carrying surrounding whitespace would register an unreachable agent or alias a reserved protocol name; spawn and roster registration now reject such names up front.
* fix(agent-core-v2): drop queued tower inbox wakes on tower exit
The wake notify handle is now retained and dropped by exit(), so a queued wake can no longer inject a stale TowerInbox prompt after the user leaves tower mode.
* fix(agent-core-v2): drop queued tower inbox wakes when availability changes
reconcileTowerProjection now drops a pending wake and resets the wake state whenever the effective projection turns inactive, covering live flag-disable and feature-disassembly paths that never call exit().
* fix(agent-core-v2): truncate the subject preview in tower inbox wakes
The full subject stays in the inbox file, but the synthetic wake message now caps it at 120 characters so a body-sized subject cannot flood the tower's context.
* fix(agent-core-v2): recheck branch ownership at tower spawn time
A branch created between TowerPlan and TowerSpawn is no longer silently checked out as the mission's base: addWorktree now refuses it unless the mission already has an owner (a genuine continuation) or the worktree directory exists (a retry of an earlier spawn attempt).
* fix(agent-core-v2): abort tower spawns on worktree protocol refusals
TowerSpawn used to downgrade every addWorktree failure to a continuing warning, so a branch-ownership refusal still registered the worker and recorded an owner, which then satisfied the ownership check for later spawns; protocol errors now abort the spawn while git-level reuse cases (existing worktree directory) continue as warnings.
* fix(tower): select the review stamped for the mission being merged
Review rounds are per-reviewer, so a higher-round review written for a
closed sibling mission shadowed a fresh clean review stamped for the
mission being merged, and the gate's re-review advice could not succeed
until the new reviewer exceeded the old round count. The merge gate now
prefers reviews stamped for the resolved mission and falls back to
unstamped legacy reviews only when no stamped review exists; a branch
whose reviews all belong to other missions reports 'no review'.
* fix(tower): create unowned mission branches atomically
The spawn-time branch ownership check and the subsequent worktree
creation were a check-then-act sequence: a branch created by another
process in between was silently checked out instead of created from the
recorded base, letting unrelated history into the worker's worktree.
worktreeAddNewBranch now attempts the creation directly so git itself
refuses an existing ref, and the store maps that refusal to a
TowerProtocolError so the spawn aborts instead of degrading to a
warning.
* fix(tower): weigh later legacy verdicts on unshared branches
Review rounds are per-reviewer, so ordering reviews by round said
nothing about recency across reviewers: a mission-stamped clean review
shadowed a later unstamped P1 from a legacy (unpinned) reviewer, and
the gate merged on the stale clean verdict. Reviews are now ordered by
file mtime, and on a branch with a single mission the candidate set
keeps unstamped reviews alongside stamped ones — an unstamped verdict
there is unambiguous. Shared branches keep the stamped-only preference
so the legacy ambiguity guard still applies.
* fix(tower): accept only the registered worktree as proof of a prior spawn
The spawn-time ownership refusal was bypassed by any directory entry at
the planned worktree path: an unowned branch plus a plain mkdir let the
spawn continue past the refusal (the failed git worktree add degraded
to a warning), and the unrelated branch could later reach the merge
gate. The exception now requires the path to be a registered worktree
checked out on the mission branch — exactly what a crashed first spawn
attempt leaves behind — while a plain or foreign directory is refused.
* fix(tower): warn when a tower message has no running task to deliver it
A TowerSend to an idle roster agent only writes a file — nothing wakes
the recipient, so the fleet stalls until someone happens to resume it
(seen in the field: a tower messaged a worker back for another review
round without resuming it, and the mission sat idle for hours until the
human nudged the session). When the tower messages a roster agent that
has no active task in this session, the tool result now notes that the
message sits in its inbox until it is delivered with
Agent(resume=..., run_in_background=true). Broadcasts and sends from
workers are unchanged: workers cannot resume peers — the tower relays
wake-ups.
* fix(tower): record review submission order in the review itself
Review order was derived from file mtime — filesystem metadata that
coarse-tick filesystems quantize and that copies or archive restores
can rewrite, so a newer blocking verdict could sort behind an older
clean one and let the merge gate proceed. submitReview now stamps a
store-wide monotonically increasing seq into the review frontmatter
and reviewsFor orders by it; mtime remains only as the tie-breaker for
legacy reviews that predate the field and for same-seq races between
concurrent submissions.
* chore: shorten the tower changeset and cover the merge gate
* chore: condense the tower changeset to the three fixed areas
---------
Co-authored-by: konghuanjun <konghuanjun@moonshot.ai>
* fix(mcp): preserve original attachments omitted from model output
* test: register session media storage in agent lifecycle fixture
* fix(mcp): retain media originals and bound attachment details
* fix(mcp): resolve session attachments and honor cancellation
* test: check concrete media references in provider requests
* fix(media): read session attachments from their owning storage
* fix(media): preserve attachment access across runtime and history changes
* fix(mime): recognize structured application text attachments
* feat(agent-core-v2): add event-sourced store core and rewire human agent/session
human/eventStore core (schema-first defineEvent factories, serial
fold-first dispatch with snapshotting, late-join slices, journal
reset), agent events/slices/historySchema, machine rewrite around a
store actor with mirror consumption, session stores with
turnIndex-boundary undo, todo slice, v2 migration to the new event
types, and the loop engine bridge.
* refactor(agent-core-v2): make the agent machine self-consistent and the store actor static
Phantom-turn root cause: local patches plus the async store.changed
mirror were two consistency models for the same data. The machine now
owns its state (every transition is a local assign plus a store.append
dual write) and consumes only store.ready/reset/error.
- store actor becomes static storeActor logic with the engine passed
via invoke input; the session machine registers agentActor and
spawns it by name
- SessionStores.open/fork return the engine directly
- store.reset aborts the old abort scope (cascades to in-flight
turn/tools/background)
- no-op drain events are no longer journaled
- engine bridge seeds the initial turn id into the journal before
folding, and resetHistory is awaited before the retry notify
- tests pass the store via machine input and wait for the log to
catch up before store assertions; drop debug residue
* refactor(agent-core-v2): drop journal snapshots for plain full-replay event sourcing
Snapshots serialized the whole slice state every 500 events and, on
an asynchronous backend, their fire-and-forget append could still be
queued when the next dispatch read journal.nextSeq(), making the
recorded FoldContext.ref.seq one less than the persisted event's
sequence and shifting turnIndex undo boundaries. Removing the
mechanism eliminates the race and the full-state rewrite; refold now
always seeds from initial state and replays every event record
(legacy snapshot entries are ignored).
* feat(agent-core-v2): warn on [models] entries missing the model field
* refactor(agent-core-v2): contribute model config diagnostics from the models section
Move collectMalformedModelEntries next to ModelRecordSchema and derive
the known object-field set from the schema shape instead of hardcoding
it, so future object fields cannot drift out of sync. Sections now
contribute load-time diagnostics through a collectDiagnostics hook,
mirroring the deprecations contribution.
The human/llm provider-catalog state machine becomes the single source of
truth for the runtime model/provider directory. IModelCatalog's data plane
reads through a new llm-adapter catalog-runtime adapter that mirrors config
section diffs into catalog upserts, and CatalogEntry cache invalidation is
driven precisely by the machine's changed events instead of a blunt clear.
The persistence layer is untouched: config.toml stays the full source of
truth and refresh results keep being written to it.
Also remove the model resolution trace/inspect stack end to end:
ResolutionTraceCollector, the llm-adapter model/contract inspection modules,
IModelCatalog.inspect, the capability explainability indirection, and the
kimi-inspect model inspector panel.
- the human agent state machine now assigns turn ids and echoes the drained queue item in turn.started; the facade adopts machine ids instead of reserving its own
- the turn machine produces the step ordinal and surfaces it via a first-class step.started fact; the llm requester layer stays step-agnostic
- turn.prompt wire records carry an explicit turnId; turnKey and transcript foldFacts read it directly with a counting fallback for legacy records
- queued prompts are addressed by promptId: cancel(turnId) is active-turn only, cancelQueued targets queue entries, Turn.id resolves once the turn starts
- drop PromptPayload.disabledTools and its klient/node-sdk pass-through (no client consumer)
* fix(agent-core-v2): avoid repeated truncation of file reads
* fix(agent-core-v2): resume reads within long lines
Add column offsets and forward recovery for bounded long-line reads. Reject malformed UTF-16 in Read instead of replacing invalid input.
* fix(agent-core-v2): warn on lossy UTF-16 reads
Return readable text after strict UTF-16 decoding fails, with a warning retained in budgeted read continuations.
* fix(agent-core-v2): avoid redundant tail scans and detect file changes
* feat(agent-core-v2): accept HEIC, HEIF, and BMP images for Kimi models
Key the accepted image MIME set by the provider the agent is bound to
instead of a single global allowlist: Kimi accepts PNG/JPEG/GIF/WebP plus
BMP/HEIC/HEIF, every other provider keeps the baseline set. The provider
image policy lives in the human layer (`llm/media/image-formats`) so both
the agent-side gates and the Anthropic protocol lowering share it.
- thread the provider type through every ingestion gate: ReadMediaFile,
prompt entry, MCP tool results, session media references, kap-server
prompt/skill routes; expose it via IAgentProfileService.getModelProviderType
- ReadMediaFile sends accepted formats it cannot re-encode locally inline
up to the provider's per-image budget (5MB for Kimi, 3.75MB otherwise)
and points at a conversion command above that; full_resolution uses the
same provider budget
- acp-server no longer gates formats itself and leaves the decision to the
engine prompt pipeline, which knows the bound provider
- the Anthropic lowering allowlist follows the bound provider so Kimi over
the Anthropic-compatible protocol no longer rejects HEIC client-side
* fix(agent-core-v2): resolve the image format policy on the production paths
Follow-ups from review of the provider-aware image formats:
- the Anthropic lowering read the policy from `LlmModel.provider`, which
the adapter registry sets to the route id (`anthropic`) rather than the
model's provider type, so Kimi over the Anthropic-compatible protocol
still rejected HEIC client-side; the accepted set now comes from a
`ProtocolTrait.acceptedImageMimes` hook that the Kimi traits implement,
and the test goes through the real trait with the routed provider id
- kap-server prompt ingestion gated media against the currently bound
model even when the same request selected another model; the profile
service now resolves the provider type for a requested alias
- the session media memo served a cached inline image to a provider that
does not accept its format; memo hits are re-judged against the current
provider before being returned
- the media tools registrar keyed ReadMediaFile only by alias and
capabilities, so a reload that changed the alias's provider type or
protocol kept the stale tool; both now take part in the key
* test(kap-server): keep the optional chain the lint autofix dropped
* refactor(interaction): replace per-agent InteractionService with human-native session facade
- add human/interaction: global interaction machine + tag-query facade (enqueue/respond/findAll/findOne/wait)
- add session-scope SessionInteractionService owning one interaction actor per session
- rewire approval/question/userTool/sessionActivity to the session facade and delete features/interaction
* refactor(interaction): rewire kap-server, klient and node-sdk to the session interaction facade
- kap-server routes, ws broadcaster and transcript binding read ISessionInteractionService (tags replace origin)
- klient memory dispatcher delegates to the session service; zod contract mirrors origin -> tags
- node-sdk session wiring subscribes the session service directly
- register interaction wire events in their new home and regenerate the wire manifest
* refactor(agent-core-v2): replace interaction DI services with a global human singleton
resolve rebase conflicts
- scrub all ambient KIMI_CODE_* env vars in the agent-core-v2 and
kap-server test setups so developer shell env cannot leak in
- drop the load-sensitive stepRetry fake-timer cases and the three
FSEvents-based skill catalog watch cases
- give node-sdk, klient and the vscode extension project an explicit
15s testTimeout matching their integration-test nature
- retry temp-dir removal in taskManager teardown to absorb async
terminal-effect writes
* feat(agent-core-v2): allow read-only tools in /btw side questions
The btw child agent previously vetoed every tool call. Allow the
read-only tools Read, Grep, and Glob so side questions about the
codebase can be answered from current file contents; write and
execute tools stay disabled.
* docs: address review — single-sentence changeset, inline-code tool names
* chore: drop the benefit clause from the btw changeset
* fix(agent-core-v2): clarify subagent model disclosure in Agent and AgentSwarm prompts
The Available models section annotated "primary" with its alias only
when the caller's model was already listed, and stayed anonymous exactly
when the annotation was needed. The same model was reachable three ways
with an undisclosed thinking-level difference, the [main model] marker
duplicated the primary handle under a second name, and AgentSwarm
repeated the whole list verbatim.
- primary now always names its binding: primary (= <alias>)
- drop the [main model] marker; keep [default]
- state the pool entries' thinking level in a closing note
- AgentSwarm renders a compact one-line summary instead of the full block
- keep the selection guidance only in the model parameter description
* Delete .changeset/clear-subagent-model-list.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* fix(agent-core-v2): drop the hard-task guidance from the model parameter descriptions
* fix(agent-core-v2): render a disabled-thinking variant of the pool thinking note
* test(agent-core-v2): drop the added prompt-text variant tests, keep updated assertions
* fix(agent-core-v2): state the pool thinking note as one unconditional binding rule
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* fix(telemetry): stop silently dropping key event attributes
- turn_ended now carries error_type (the engine error code) when a turn
fails, matching the v2 event catalog definition that no emitter set
- TelemetryClient reports properties dropped by sanitizeProperties via a
new onUnexpectedError hook, wired to the CLI, web, and v2-print logs
- session_started client attribution keys emit empty strings instead of
null so they survive payload flattening
* fix(telemetry): sanitize at sink accept time and guard the drop handler
Addresses review on #3638:
- Properties are sanitized when the sink accepts the event (at track time
on the direct path, at drain time for the pre-init queue), so drops on
events queued before initialization are reported once
onUnexpectedError is installed
- The onUnexpectedError hook is invoked behind a guard, mirroring the
agent-core-v2 unexpectedError path, so a faulty host handler cannot
abort track()
* fix(telemetry): snapshot queued properties at track time
Addresses review on #3638: the pre-init queue now stores the sanitized
snapshot taken at track time (only primitives survive, so the snapshot
is exact), and the dropped keys ride along as pending diagnostics that
are replayed once the handler is installed. Callers mutating a reused
properties object between track() and initialization can no longer
alter the emitted event or its drop reports
* feat(kimi-code): two-line collapsed tool cards with width-aware headers
Collapsed cards now show the call on line one and a single dim outcome
row on line two: a Bash command's last output line (the live tail while
running) with a line-count chip, a Grep/Glob path sample, or a generic
tool's first output line. Read groups stay header-only and honor the
global expand state; failed calls keep their preview. Headers split into
head / flexible / tail segments so a long command fills the terminal
width before the chip.
* feat(kimi-code): NotifyUser tool and mid-turn update panel
Add an experimental NotifyUser tool (flag notify_user, off by default)
that agent-core-v2 offers only when the host declares the update_panel
UI capability at bootstrap; the TUI declares it through the SDK harness
options. The system prompt's guidance to send updates is injected only
when the tool is active for the agent. The TUI renders the turn's
updates in a panel above the editor (stacked, 8-row window, Ctrl+N pages
back) that the next turn clears, and keeps a one-line card in the
transcript.
* feat: add subagent updates and bidirectional paging
* fix: preserve session tools and prompts when toggling updates
* fix: correct notification delivery and CI regressions
* feat(secondary-model): drop the experimental flag from the subagent model pool
* feat(secondary-model): sync the web bundle without the experimental gate