* 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
* fix(agent-core-v2): stop tool actor teardown from aborting sibling tools
* refactor(agent-core-v2): hoist abort signal ownership to spawn sites
* refactor(agent-core-v2): derive cancellation from a Go-like abort scope tree
* fix(agent-core-v2): bind the abort scope's child abort
* feat(agent-core-v2): anchor compaction resumption on the latest user message
* fix(agent-core-v2): count the continuation anchor in tokensAfter
* fix(agent-core-v2): simplify the continuation anchor to a single statement
* fix(agent-core-v2): avoid absolute claims in the compaction continuation texts
* fix(agent-core-v2): flag the continuation anchor in compaction records so old records replay unchanged
* fix(agent-core-v2): replay the continuation anchor unconditionally, drop hasContinuation
The anchor is an injection (policy, not history): like the system prompt
and other reminders, it is re-rendered by current code at replay time, so
pre-anchor compaction records gain it on resume. The divergence is
self-limiting (only records written before this change, absorbed on first
resume) and needs no per-record format flag. Fold-length accounting counts
the anchor unconditionally to stay at parity with the rebuilt context.
* fix(vis): align the context projector with the compaction continuation anchor
Locate the compaction summary by origin instead of assuming it is the last
shape message (the anchor now follows it), and pin the anchor in the
projection expectations.
* refactor(agent-core-v2): drop anthropic_beta and google-vertex protocol names
* refactor(agent-core-v2): resolve human protocol variants at generate time
* docs(agent-core-v2): add the llm module design guide
* docs(agent-core-v2): align the llm guide with the shared syntax errors, failure ends, and anthropic_beta naming
* feat(tui): show each background agent's model in the /tasks list
* fix(agent-core-v2): carry model and thinking effort in tower spawn task metadata
* fix(tower): honor subagent timeout config for tower spawn tasks
TowerSpawnTool registered its detached subagent task with the hardcoded
DEFAULT_SUBAGENT_TIMEOUT_MS, bypassing the [subagent] timeout_ms setting
and KIMI_SUBAGENT_TIMEOUT_MS. Resolve the timeout through
resolveSubagentTimeoutMs(config) like AgentTool does, keeping the 2h
default when nothing is configured.
* test(agent-core-v2): deflake the sessionIndex perf baseline under load
* feat(agent-core-v2): carry user-verbatim mission context through tower briefings
TowerPlan missions accept an optional context field holding the user's
own key sentences verbatim, rendered into the mission file and every
briefing that embeds it. Reviewer briefings now include the full mission
text and the author's review-request, with an intent-first checklist;
worker briefings escalate substantive ambiguity to the tower before
acting and reconcile completion reports task by task.
* chore: merge tower changesets into one
* refactor(tui): narrow tasks-browser host appState with Pick
* docs(agent-core-v2): correct the subagent timeout description in the Agent tool
* docs(agent-core-v2): drop the timeout bullet from the Agent tool description
---------
Co-authored-by: konghuanjun <konghuanjun@moonshot.ai>
* feat(agent-core-v2): watch user-level skill roots so the catalog stays fresh
UserFileSkillSource previously only re-scanned the user skill roots
(KIMI_CODE_HOME/skills and ~/.agents/skills) on catalog load, so a skill
created while the daemon was running never appeared until a restart.
Mirror the workspace-root source: watch both user-level base directories
with a subtree filter limited to the skill-root candidates, debounce
(200ms) change events, and fire onDidChange so the catalog reloads the
'user' source automatically. Watch handles are registered on the
service's DisposableStore and disposed with the App scope.
Supersedes the skills:reload endpoint approach (#3597).
* fix(agent-core-v2): merge same-base watch candidates and drop test comments
Codex review on #3608:
- When KIMI_CODE_HOME equals the OS home dir, deduplicating watch bases
skipped the .agents/skills candidate entirely. Group candidates by
base instead so one watch covers both roots.
- Remove // phase labels from the catalog test: agent-core-v2 is a
comment-free zone enforced by scripts/check-no-comments.mjs (lint was
red on CI).
The decode-window split measured part arrival at the moment the event
loop resumed the pending await, so any loop-busy work (rendering,
timers, GC) between parts was reported as server time. Measure loop
utilization across the decode window with perf_hooks and split it into
loop-idle wait (server), per-part processing (client), and a new
clientBlockedMs bucket (loop busy with other work), carried through to
the step events, wire record, transcript contract, and the TUI debug
timing line.
Resuming a session dominated by cron-fired turns mounted an unbounded
transcript: limitAgentReplayByTurns only counted human-typed prompts as
turn boundaries, so the replay limit never engaged (10,478 records for
11 'turns' on an affected session). The mounted transcript made every
render frame O(total lines), starving the event loop during streaming
and collapsing client-side drain throughput.
Treat cron_job and cron_missed replay records as turn boundaries,
matching the TUI transcript window's own boundary rule.
* feat(agent-core-v2): remove the context budget reminders
PR #3423 shipped two behaviors together: the context budget reminders
(context_budget and compaction_ahead) and the compaction recovery
pointer. Remove the reminders so the recovery pointer's effect can be
measured on its own in an ablation run.
Removed: the features/contextBudget feature, the budget-only
CompactionStrategy.budget()/CompactionTriggerBudget/
IAgentFullCompactionService.budget() surface, the summarizer-input
reminder filter, and the context_budget_reminder /
compaction_ahead_reminder telemetry plus the ahead_* fields on
compaction_finished. The recovery pointer (Context Recovery footer,
wire journal line ranges, whole-line wire.jsonl reads, empty-history
compaction failure) is unchanged.
* chore: add the changeset for removing the context budget reminders
A malformed config entry (e.g. an unquoted dotted TOML key like
[models.kimi-k2.7-code]) parses into a nested object that lacks the
top-level model field. The v2 config schema marks model optional, so the
entry reaches profile matching with undefined, crashing getModels with
TypeError: Cannot read properties of undefined (reading 'toLowerCase').
matchKnownAnthropicModelProfile, matchUnknownClaudeProfile, and
parseAnthropicModelVersion now return undefined/null for undefined input
instead of throwing.
Fixes#2981
Co-authored-by: zhaopeiwen.dev <zhaopeiwen.dev@bytedance.com>
* feat(agent-core-v2): add env bindings for print-mode and bash task timeout config keys
Wire bashTaskTimeoutS, printBackgroundMode, printWaitCeilingS, and
printMaxTurns into taskEnvBindings as KIMI_CODE_BACKGROUND_* variables
so headless/print runs (e.g. container harnesses) can bound or disable
the print background policy without mounting a config file. bashTaskTimeoutS
accepts 0 (no timeout); invalid values are ignored. Env-set values count
as set for applyPrintModeConfigDefaults and win over the print fill.
Both integer parsers use Number.isSafeInteger: zod's int() rejects
unsafe integers, and since applySectionEnvBindings validates the combined
overlay, one unsafe value would otherwise discard the whole section's
env bindings, including valid siblings.
* Delete .changeset/print-task-env-bindings.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* fix(remote-control): release the lock before reporting off and re-read the server token per forward
* fix(remote-control): pass the token provider through the manager
- remote-control is always available: kimi rc, kimi web --remote-control and /remote-control no longer require KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL or the master flag
- add the [database] config section with base (session-index read model) and search (global search worker) kill switches, both defaulting to true
- rename the kill-switch env vars to KIMI_CODE_PERSISTENCE_MINIDB_READMODEL and KIMI_CODE_SEARCH_WORKER (precedence: env > config > default)
* refactor: remove the legacy agent-core v1 package
Delete packages/agent-core (v1 engine) and migrate node-sdk fully onto
agent-core-v2:
- node-sdk owns its public contract (error protocol, KimiConfig schema
and file IO, process-global logging facade, telemetry client
interface); event and interaction types re-export from
@moonshot-ai/protocol or agent-core-v2
- createKimiHarness is now the single harness factory, backed by the
v2 client; the v1 SDKRpcClient is deleted
- CLI and vscode run agent-core-v2 unconditionally;
KIMI_CODE_LEGACY_FLAG and useAgentCoreV1 are removed
- the wire.jsonl replay fold moves into agent-core-v2's replayBuilder
(foldWireRecords), and the v2 journal now persists tool-call display
so replay restores diff/todo displays
- TUI engineV2 branches collapse to the v2 path
* test(vscode): retry temp-dir teardown in replay-resume integration test
* fix(agent-core-v2): deduplicate repeated tool call ids inside the turn machine
* fix(agent-core-v2): keep the raw tool call id whenever a normalizer rewrites it
* refactor(agent-core-v2): absorb the human layer into src/human and rename vendored kosong to llm-adapter
* feat(agent-core-v2): preserve media attachment names in human message contract
* refactor(agent-core-v2): remove llm-adapter code duplicated in human/llm
* refactor(agent-core-v2): replace the agent loop with a facade over the human turn state machine
* refactor(agent-core-v2): rename the loop adapter layer to loop/machine
* test(acp-server): implement gateway resolve in the scripted provider stub
* refactor(agent-core-v2): restore v2 batch semantics in the loop machine tools adapter
- delegate each step's tool calls to the v2 tool executor as one whole
batch: beginBatch arms the expected call set from the llm.done entry,
per-actor execute calls collect into a pending barrier, and the full
batch is submitted in tool-call order once complete
- drop the serial batchChain/batchStopped/skipped approximation so the
executor's prepare-stage stopBatchAfterThis skip, veto short-circuit,
and ToolScheduler access-based concurrency apply natively again
* fix(build): update flake pnpmDeps hash for the post-absorb lockfile
* fix(agent-core-v2): align loop facade reservations with the machine queue by id
* fix(agent-core-v2): drain persisted wire records before turn end
* fix(agent-core-v2): route executor-level tool errors to the loop error path and backfill aborted tool results on forced abort
* fix(agent-core-v2): keep turn step retrying in the facade step numbering space
Preserve optional image and video filenames through media resolution, shared protocol schemas, turn events, and transcript reconstruction. Cover stored media reuse, unsupported image conversion, and steering with regression tests.
* fix(kimi-code): flush wire journals before print-mode exit
A print-mode turn's tail records (step.end / turn.ended / prompt.completed)
are dispatched fire-and-forget and reach the journal only through the wire
service's async persist queue. The print cleanup path never awaited that
queue: with telemetry disabled (KIMI_DISABLE_TELEMETRY=1) cleanup returns
in microseconds, and process.exit on the error path cut off the pending
append-log flush, dropping the failed turn's closing records. Flush every
session agent's dispatcher (which awaits the wire persist queue and the
append-log store) before disposing the app, bounded by the shutdown
timeout and best-effort so a persist failure never masks the turn outcome.
* fix(kimi-code): settle each agent's wire flush independently
Promise.all rejects as soon as one agent's flush fails; the best-effort
caller then proceeds to app.dispose() and process.exit while the remaining
agents' flushes are still in flight, re-exposing their journals to the
truncation this cleanup is meant to prevent. Await every flush with
Promise.allSettled instead (matches the drain helper's convention), and
cover it with a two-agent regression test.
* fix(kimi-code): run print-mode shutdown phases concurrently
The wire flush, v2 telemetry shutdown, and v1 telemetry shutdown each hold
a 3s allowance; run sequentially they can take 9s, past the 8s outer
cleanup bound — the caller's process.exit would then cut off the tail
(app.dispose included). The phases are independent, so await them
concurrently (worst case one allowance) while keeping the v2 shutdown
failure propagation and the best-effort semantics of the other two.
* fix(kimi-code): quiesce active turns before the print-mode wire flush
A termination signal (SIGINT/SIGTERM/SIGHUP) can arrive mid-turn: the
cleanup ran the wire flush immediately, while the still-running turn only
produced its cancellation and closing records from dispose()'s
fire-and-forget teardown — after the flush, and after the signal handler's
process.exit. Cancel every session agent's queued and active turns and
await loop idleness before flushing (best-effort, bounded by the shutdown
timeout); idle loops make this a no-op on the normal exit paths. Torn-down
agent scopes are skipped defensively, mirroring the flush's per-agent
settlement.
* fix(kimi-code): drain prompts and await prompt completion in print quiesce
Two follow-ups from review:
- The loop settles (releaseActiveTurn) before the prompt-settle chain
dispatches prompt.completed, so awaiting loop idleness alone let the
wire flush race the final record. Quiesce now also drains each agent's
prompt queue and awaits the tracked prompt completions (registered at
enqueue time) before the flush runs.
- Restore drainBackgroundTasks to Promise.all: a previous edit
unintentionally switched it to allSettled alongside the per-agent wire
flush, silently discarding persistence failures from
suppressTerminalNotification()/wait(). Only the independent wire
flushes are meant to be best-effort.
* fix(kimi-code): cover every agent's prompt queue in print quiesce
Tracking only the main run's prompt completion left subagent and
background prompts racing the wire flush the same way: the loop reports
idle before that agent's prompt-settle chain dispatches prompt.completed.
Replace the per-handle completion tracking with a uniform wait — after
draining and cancelling, poll every agent's prompt queue snapshot until
none reports an active or pending prompt. settle() clears the active
prompt and dispatches the record in one synchronous block, so an empty
snapshot proves the record is already queued for the flush.
* fix(agent-core-v2,kimi-code): cover the prompt launch window in print quiesce
A termination signal can also arrive while startNext() is mid-launch: the
prompt has left pending and is not active yet, so drain() cannot cancel it
and an active/pending snapshot reads empty. Quiesce would then flush and
exit while the launch still dispatches records afterward.
Expose the service's launching phase on PromptQueueSnapshot and make the
print cleanup's quiesce a repeat-until-idle loop: every pass drains,
cancels, and awaits the loops, then re-checks launching/active/pending, so
a prompt surfacing from the launch window is cancelled on the next pass
instead of escaping the flush.
* fix(kimi-code): freeze loop producers across the print-mode flush
An empty prompt queue is only a point-in-time observation: background
task completions and cron fires enqueue straight into the loop, bypassing
the prompt queue, so a late producer could still start a turn — and new
records — after the journals were flushed. Quiesce now leaves a
quiescence guard held on every loop once queues read empty; the caller
holds the release across the wire flush and app disposal, so late
submissions queue behind the guard and are rejected by disposal instead
of racing process.exit. Loops that refuse the guard (still busy) send the
quiesce into another drain/cancel/settle pass.
* fix(agent-core-v2): flush the wire journal when removing an agent
AgentLifecycleService.remove() quiesced the agent but never flushed its
wire journal: the records reached the append log only through the
fire-and-forget retirement flush, so a process exiting right after a
subagent's removal (e.g. print mode on a termination signal) could
truncate the closing records. Flush the agent's event dispatcher after
the quiesce and before disposal; a persist failure is reported without
blocking the removal.
* fix(agent-core-v2): wait for the prompt queue to go idle before flushing a removed agent
The remove() flush could still snapshot the persistence queue before the
cancelled prompt's final record was appended: loop.settled() resolves in
releaseActiveTurn() before the prompt-settle chain dispatches
prompt.aborted/prompt.completed, and prompt.drain() does not await that
settlement. Wait (bounded) for the prompt queue to report no launching,
active, or pending prompts before flushing; an unreadable snapshot counts
as idle so a wedged service cannot stall the removal.
* chore: widen the print wire-flush changeset to termination signals
* fix(agent-core-v2): re-cancel prompts that finish launching during removal
A prompt mid-launch (startNext awaiting daemon materialization or a
pre-submit hook) is invisible to drain() and has no turn to cancel yet;
the passive idle wait would then let it run to the deadline and flush
before its closing records existed. Repeat drain/cancel/settle on every
pass until the queue reads idle, and once idle hold a quiescence guard
across the flush and disposal so a late producer cannot start new work
in between.
* fix(kimi-code): stop task producers before the print-mode wire flush
Task termination bypasses both the prompt queue and the loop quiescence
guard: AgentTaskService dispatches TaskTerminated straight to the wire,
and disposal would force-stop still-running tasks after the journals were
already flushed. Stop every session agent's tasks up front in the quiesce
phase (mirroring AgentLifecycleService.remove()), so each task's
termination record is dispatched before the flush instead of racing
process.exit. keepAliveOnExit tasks stay exempt, matching remove().
* fix(agent-core-v2): do not let suppression failure short-circuit stopAllOnExit
A detached task's terminal-notification suppression failing (e.g. the
persist write rejects) used to reject the whole stopAllOnExit before
stopAll() ran, so every task stayed active — and callers settling the
rejection (print-mode cleanup, agent removal) proceeded as if the tasks
were stopped, losing their termination records at exit. Settle each
suppression independently, log the failure, and always stop the tasks.
keepAliveOnExit tasks remain exempt by design.
* chore(kimi-code,agent-core-v2): halve print wire-flush tests and trim comments
* feat(agent-core-v2): drop the experimental gate from turn-level file history
The file_history flag and its KIMI_CODE_EXPERIMENTAL_FILE_HISTORY env
var are removed; first-touch baselines, turn checkpoints, and the
changes/turnRecorded/contentAt reads now run unconditionally, so the
per-turn file diffs served by kap-server no longer depend on opt-in
configuration.
The changes REST response drops its enabled field — recorded alone
tells an authoritative empty result apart from a turn whose records
were never made, lost to a crash, or displaced by retention.
* fix(agent-core-v2): probe blob existence with a stat, not a directory listing
turnRecorded always answered false for turns whose snapshots lived under
the file-history/ prefix directory: BlobStoreService.has listed the
scope directory (a shallow readdir) and exact-matched the nested key
against top-level entries, so keyed blobs were never found and the
changes endpoint reported recorded=false for exactly the turns that had
edits. Stat the key through storage.size instead, and pin the
turn-recorded assertions against a real node-fs home so the harness
covers the production layout.
* fix(agent-core-v2): probe every keyed blob before calling a turn recorded
Merging the start and end entries let an end entry with key null (a
deleted or oversize-at-end file) shadow the start baseline, and probing
only one surviving key missed partial loss in the other phase: a
session displaced from the thirty-session window could still claim
authority for turns whose snapshots were gone. turnRecorded now stats
every distinct keyed blob across both checkpoints and reports the turn
unrecorded when any is missing.
* 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