7 Commits

Author SHA1 Message Date
TAI-YUAN GUO
f5907405a0
fix(workspace): don't infer created or deleted files from truncated snapshots (#7172)
* fix(workspace): don't infer created or deleted files from truncated snapshots

A bounded workspace scan stops at max_scanned_files and marks the snapshot
truncated. Absence from such a snapshot is unknown, so creation is only
inferred from a complete before snapshot and deletion only from a complete
after snapshot. get_changed_paths() is the single filter for the summary,
text capture and output-delivery detection.

The scanner now walks subdirectories in sorted order so the truncation
boundary is reproducible across the before and after scans, and the docs
note that the budget is consumed root by root (workspace before outputs).

Supersedes #5231.

Co-authored-by: liutian <91778998+ImcLiuQian@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(runtime): record truncated delivery verification skips

Record verification_skipped=truncated_pre_run_snapshot in run.delivery
receipts when a truncated pre-run scan leaves no provable output changes.
Carry the snapshot into both normal and exceptional receipt finalization.

Keep observed-output verification and terminal statuses unchanged. Cover
success, failure, cancellation and capture failures through the real worker,
and document the per-run receipt marker.

---------

Co-authored-by: liutian <91778998+ImcLiuQian@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-22 16:52:53 +08:00
lukyturnflow
c8875320a1
fix(workspace): publish the snapshot text cache byte-exactly on every host (#7110)
* fix(workspace): publish the snapshot text cache byte-exactly on every host

* test(workspace): pin diff line counts and guard the text cache on Windows CI

* ci(workspace): run Windows text-cache guards on source changes

Include the workspace_changes package in the Windows workflow's PR path
filter so scanner or diff regressions run the native Windows guards before
merge, rather than waiting for the weekly schedule.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-22 15:42:32 +08:00
Vanzeren
6f53fd5e99
feat(runtime): enforce artifact delivery from workspace snapshots (#4494) 2026-07-27 22:27:16 +08:00
Andrew Chen
f113f10f36
fix(workspace-changes): count diff body lines starting with "-- "/"++ " (#4303)
`_count_diff_lines` skipped every line starting with "+++ "/"--- " to drop
difflib's two file headers. But a deleted hunk-body line whose content
begins with "-- " (e.g. a SQL comment `-- get users`) becomes the diff
line `--- get users`, and an added `++ ...` line becomes `+++ ...`, so
those were dropped too — undercounting the user-visible +N/-M change
summary (WorkspaceChangeSummary, per-file WorkspaceFileChange, and the
run-event string in recorder.py).

`difflib.unified_diff` always emits the two headers first, so skip them
by position (`lines[2:]`) instead. Hunk `@@` lines start with `@` and are
still ignored. Numeric/plain diffs are unaffected.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 17:48:04 +08:00
Daoyuan Li
b565e6c0f0
fix(workspace-changes): classify a symlink replacing a file distinctly from deleted (#4170)
* fix(workspace-changes): classify a symlink replacing a file distinctly from deleted

scan_workspace_roots() skipped every symlinked path entirely
(host_file.is_symlink() -> continue), so the path was completely absent
from a snapshot instead of being recorded as a metadata-only stub the
way binary/large/sensitive-looking files already are. When an agent run
replaces a tracked file with a symlink (e.g. rm config.txt && ln -s
/some/other/path config.txt), the after-snapshot never contained that
path at all, so compare_snapshots()'s _status() saw after_file=None and
reported plain "deleted" -- silently hiding that the path is still alive
on disk, now as a symlink that can point anywhere on the host, including
outside the workspace root.

Add a "symlink" classification mirroring the existing binary/large/
sensitive pattern: scan_workspace_roots() now records a symlink as a
metadata-only FileSnapshot stub (symlink=True, symlink_target from
os.readlink(), lstat'd without ever following the link) instead of
omitting it. _status() reports "symlink_created" whenever a symlink
newly occupies a path that was not already a symlink (brand new or
replacing a prior file), so the security-relevant fact surfaces
distinctly instead of collapsing into "deleted". A symlink genuinely
removed with nothing replacing it is unchanged: still "deleted".

Verified against a real POSIX symlink (WSL; native Windows symlink
creation needs elevated privilege) driving the unmodified
scan_workspace_roots()/compare_snapshots() functions, and via a
patch-file revert/reapply cycle on this same fix to confirm the added
regression tests fail before and pass after.

* fix(workspace-changes): count symlink_created in the changed-file badge

getChangedFileCount summed only created + modified + deleted, so a run
whose only change was a symlink replacing a file (reported as the new
symlink_created status, not deleted) produced a count of 0 and
WorkspaceChangeBadge hid the badge entirely -- the exact scenario this
PR targets, with the opposite of the intended result.

Add symlink_created to the frontend WorkspaceChangeSummary interface
(already emitted by the backend) and include it in the count. The
per-file StatusIcon/statusLabel "modified" fallthrough is unaffected
and left as a follow-up, per review.

Regression test reverts cleanly to reproduce a count of 0 pre-fix.

* fix(workspace-changes): rank symlink_created in file sort and complete the type contract

sortWorkspaceChanges's statusRank had no entry for the new
symlink_created status, so statusRank[left.status] - statusRank[right.status]
evaluated to NaN for any comparison involving a symlink-created file,
violating Array#sort's ordering contract instead of producing a
deterministic order. The frontend WorkspaceChangeStatus and
DiffUnavailableReason unions, and the WorkspaceFileChange symlink
fields, also stayed narrower than what the backend now emits, so
TypeScript's satisfies Record<...> guard on statusRank could not catch
the gap.

Widen WorkspaceChangeStatus to include "symlink_created" and
DiffUnavailableReason to include "symlink", add the matching
symlink/symlink_target_before/symlink_target_after fields to
WorkspaceFileChange, and give symlink_created a rank alongside
modified in statusRank -- restoring the satisfies guard's ability to
catch a future unranked status. unavailableLabel now has an explicit
"symlink" branch (new symlinkUnavailable i18n string, en-US + zh-CN)
instead of falling through to the generic label.

Also fixes the failing e2e-tests CI check: the existing
workspace-changes.spec.ts mock summary predates the symlink_created
field, so getChangedFileCount computed 1 + 1 + 0 + undefined = NaN and
the badge rendered "Edited NaN files" instead of "Edited 2 files".
Added symlink_created: 0 to the mock to match the real backend
contract.

New sortWorkspaceChanges unit tests revert cleanly against the
unranked statusRank to reproduce the NaN-driven misordering. pnpm test
(626 tests), pnpm check, and pnpm format are clean, and the
previously-failing e2e spec plus the full e2e suite (94 tests) pass.
2026-07-21 10:22:55 +08:00
Vanzeren
823c47bcc2
fix(backend): stop classifying UTF-16 markdown files as binary (#3966)
* fix: detect utf16 markdown workspace diffs

* fix: tighten binary detection in workspace diff scanner

* fix: decode utf-8-sig before utf-8 to strip bom from diff content
2026-07-06 22:24:14 +08:00
Ryker_Feng
00161811bd
feat: add workspace change review for agent runs (#3945)
* feat: add workspace change review

* chore: format workspace change files

* fix: optimize workspace change summaries

* style: refine workspace change badge scale

* fix: restore workspace change user context import

* fix(frontend): gate workspace change badge to assistant messages

Only pass run_id to assistant MessageListItems so the workspace-change
badge can never render under a user's prompt, which carries the same
run_id. Assert single badge render in the E2E flow.

* fix(workspace-changes): address review feedback on diff parsing and badge

- Restrict unified-diff header detection to "+++ "/"--- " (trailing space)
  in both backend _count_diff_lines and frontend getWorkspaceChangeLineClass
  so content lines beginning with +++/--- are counted/styled correctly
- Gate WorkspaceChangeBadge to ai messages so tool messages folded into an
  assistant group don't render a duplicate badge
- Add regression tests for both diff-classification fixes
- Apply prettier formatting to workspace-changes files flagged by CI

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-07-05 15:38:01 +08:00