Repository navigation
feat(runtime-api): read what one tool call changed, from the snapshots around it - #6817
Merged
Hmbown merged 3 commits intoOct 6, 2026
Merged
Conversation
…s around it
A client that wants to say "this command changed these files" has had nothing to
read. The engine records a shell command's writes as a command execution, never
as a `file_change` item, so the only per-call attribution a client could find was
`metadata.mutation`, which only file tools publish. The turn's snapshot delta
covers the turn rather than the call, and deriving it per call meant diffing two
trees the Runtime API did not expose.
The engine already brackets every call that may write — a file tool, a shell
command, a program, a write-capable MCP tool — with a `tool:<call_id>` restore
point before it and a `post-tool:<call_id>` one after, and records both on the
turn. This adds the route that reads that pair, plus the two small pieces it
needs: `SnapshotRepo::has_tree`, so a receipt whose objects have been pruned is
reported as such instead of as a git failure, and `SnapshotRepo::patch_between`,
one path's unified diff between two trees.
GET /v1/threads/{id}/turns/{turn_id}/calls/{tool_call_id}/changes
answers with the change kind, the line counts, the revision the span left and the
patch, per path, all read from the two trees rather than from the working copy.
The answer is therefore what the call did, not what the file holds now. Every
entry carries the span's own revision and the restore point `file-revert` accepts,
so a command's write is revertible like any other.
What the span is not is attribution by cause. It is a window of time: another
writer in the same workspace during the call is inside the difference, and a path
the snapshots do not track (a `.gitignore` hit, `node_modules/`, a binary) cannot
appear at all. A call the engine judged read-only took no receipts, one whose
closing snapshot was lost has half a pair, and an older turn's trees may have
been pruned — each answers `state: "unavailable"` with its own `reason`, which is
a different answer from an empty `files` list and has to be read as "unknown"
rather than "changed nothing".
The tool call id is the model endpoint's own opaque string, echoed back verbatim,
so this compares it rather than parsing it: the charset a *record* id is held to
would refuse `call_01_…|<uuid>` and answer "no such call" for a call the turn
plainly recorded.
Signed-off-by: Ben Gao <bengao168@msn.com>
Contributor
Author
|
hopefully this could land in 0.10.1. it help users know what have changed over a turn more accurately |
# Conflicts: # crates/tui/src/snapshot/repo.rs
list_call_changes resolves the side repo on a blocking-pool thread. Under main's sealed test home that thread is a foreign reader: it resolves the isolated snapshot root instead of the test's store and every span reads as pruned. Mint the scope ticket on the handler and join it on the worker, the same pattern file-revert already uses. Evidence: call_change_route_reads_one_calls_workspace_span ok; patch_between/has_tree/run_git_drains neighbors 4/0.
Hmbown
pushed a commit
that referenced
this pull request
Oct 6, 2026
Reconcile the complete contributions from #6832 (@aboimpinto), #6867 (@hodeswildsmith455-boop), and #6805 (@LIghtJUNction) with the current Engine, provider identities, reviewed-plugin policy, and task lifecycle. Original contributor histories are recorded by subsequent resolved merges. Fix the already integrated contributor cases: snapshot corruption is an explicit unavailable/error result (#6817), malformed locales cannot select an incidental script (#6860), image metadata uses the exact uploaded bytes and respects available decoders (#6858), automation deletion waits for actual scheduler reconciliation (#6864), and blocking trust/skill reads stay off the async executor (#6869). Preserve #6857's compaction regression. Extend #6872's human-wait lifecycle guard to approval/elevation cards; retire only the matching ended parent request and refresh activity only after a delivered decision. Enforce configured finite approval deadlines in the Engine, including deadline/cancellation races and durable receipts. Serialize Native Windows ACL admission/retirement across Core processes with a logon-scoped kernel mutex, preserving exact SID/object validation. Add an actual child-process lock test; serialize DSH host tests in the existing extension-host lane. Windows execution proof remains hosted CI. Reconcile the vendored computer-use plugin with canonical main a656f67455fc while preserving Core's 0.12.1 embedding contract. Canonical b47/a656 tree passed Ubuntu/macOS/Windows source and package gates, including 28/28 Windows-focused tests and the controlled desktop fixture; this is separate from the new Engine head's CI verdict. Partial adaptation of the discovery-cache priority portion from PR #6393 by @AdityaVG13 (original ac33dd4). Preserve the best match under count and byte limits without importing the unfinished echolocation/fork design; the broader draft remains open. Validation: - npm test: 1286 passed, 0 failed, 7 skipped; web 767/767. - npm run check:web: lint, typecheck and production build passed. - Affected Rust selection: 801/803 initially passed; the two fixture/lifecycle expectation failures were corrected and each passed a focused rerun. - Additional focused Rust: approval 28/28, discovery cache 11/11, OrcaRouter synthetic catalog 3/3, and 33/33 lifecycle/API/routing checks. - Final CI-repair selection: 37/39 initially passed; the BMP feature-proxy and feature-registry summary failures were corrected; both corrected tests passed (2/2, 0 failures). - Qualified Clippy: six packages, all targets/all features, passed with the CI style allowances. Portable no-default-feature check passed; portable policy verifier 6/6 passed; formatting and diff checks passed. - Runtime contract: 55 measured metrics passed after explicit remeasurement; all 21 structural identities were unchanged. Twelve byte/token-estimate budgets account for bounded child-wait disclosure and contributor locale descriptions; no runtime field/prompt was removed to lower the budget. - Persistence budget was not qualified locally: the checker requires a clean tree and this shared checkout retains an unrelated operator file. Clean hosted CI, fresh stamped build and real DeepSeek TUI acceptance remain required before the integration PR can merge to main. Refs #6872, #6843, #6795. Co-authored-by: Paulo Aboim Pinto <paulo.aboim.pinto@gmail.com> Co-authored-by: hodeswildsmith455-boop <hodeswildsmith455-boop@users.noreply.github.com> Co-authored-by: LIghtJUNction <lightjunction.me@gmail.com> Co-authored-by: AdityaVG13 <adityavgcode@gmail.com> Signed-off-by: CodeWhale Bot <bot@codewhale.net>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
A client that wants to show what a shell command changed has had nothing to
read. The engine records a command's writes as a command execution rather than
as a
file_changeitem, so the only per-call attribution a client could find wasmetadata.mutation, which only file tools publish — the turn's snapshot deltacovers the turn, not the call.
The engine already brackets every call that may write — a file tool, a shell
command, a program, a write-capable MCP tool — with a
tool:<call_id>restorepoint before it and a
post-tool:<call_id>one after, and records both on theturn. This adds the route that reads that pair:
It answers with the change kind, the line counts, the revision the span left and
the patch, per path, all read from the two trees rather than from the working
copy — so the answer is what the call did, not what the file holds now. Every
entry carries the restore point
file-revertaccepts, so a command's own writeis revertible like any other.
The span is a window of time, not attribution by cause, and the route says so
where it matters: a call the engine judged read-only took no receipts, one whose
closing snapshot was lost has half a pair, an older turn's trees may have been
pruned, and a path the snapshots do not track cannot appear at all. Each of those
is
state: "unavailable"with its ownreason, never an emptyfileslist,because "unknown" and "changed nothing" are different answers.
The tool call id is the model endpoint's own opaque string, echoed back verbatim,
so this compares it rather than parsing it: the charset a record id is held to
would refuse
call_01_…|<uuid>and answer "no such call" for a call the turnplainly recorded.
Issue
No-Issue: a new capability for Runtime API clients; nothing in the tracker
describes it.
How I tested it
cargo test -p codewhale-tui --lib call_change_route— a route test over a realserver against a real side-git repo: an id containing
|resolves; every path,revision and patch comes from the span's own trees after the work tree moved on;
a read-only call answers
call_not_bounded; a half pair answerspost_snapshot_missing; pruned trees, and a workspace with no store at all,answer
snapshots_pruned; an unknown turn or a call the turn never ran is a 404;limit=0is a 400.cargo test -p codewhale-tui --lib snapshot::(101 passed),patch_between(2),has_tree(1),turn_artifact(7).cargo test -p codewhale-tui --test feature_registry— 6 passed. This adds aroute and no flag, so no new row;
docs/RUNTIME_API.mdis theruntime-apirow's doc and carries the contract.
cargo clippy -p codewhale-tui --all-targets --all-features --locked -- -D warnings -A clippy::uninlined_format_args -A clippy::too_many_arguments -A clippy::unnecessary_map_or— clean.cargo fmt --all -- --checkreports onlycrates/tui/src/child_env.rs, whicharrived unformatted with the tip commit on
mainand is not touched here; everyfile this PR touches is formatted (running the formatter across it would put an
unrelated hunk in this diff).
isolation (load-sensitive tests), and all are in modules this change does not
touch. On this machine
core::engine::tests::yolo_mode_does_not_prompt_for_mcp_actionoverflows a test thread's stack on
mainas well;RUST_MIN_STACK=16Mlets thesuite run to completion.
Checklist
maincargo fmt --allpasses, and tests cover new or changed behaviordocs/features.toml(cargo test -p codewhale-tui --test feature_registrychecks it)