Skip to content

feat: track assistant turn changes#408

Merged
Astro-Han merged 17 commits into
devfrom
codex/i339-turn-changes
May 3, 2026
Merged

feat: track assistant turn changes#408
Astro-Han merged 17 commits into
devfrom
codex/i339-turn-changes

Conversation

@Astro-Han

@Astro-Han Astro-Han commented May 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add a turn-change tracking layer for files explicitly changed by PawWork file-writing tools during an assistant turn.
  • Add whole-turn Undo/Redo APIs with conflict preflight and local restore data.
  • Replace the confusing branch-level last-turn diff fallback with an inline assistant-turn changes panel, including expandable diffs, open file, and show in folder actions.
  • Redact sensitive file content from display records, tool metadata, session diffs, share payloads, and export payloads while keeping Undo/Redo available.

Why

Fixes #339.

The previous diff block could show the current branch snapshot instead of the files changed by the current assistant turn. That made the UI feel unrelated to the conversation. This PR changes the product model to show the latest assistant turn's tracked file-writing results, regardless of Git state, while keeping the old snapshot diff as a fallback for sessions without turn-change records.

Related Issue

Fixes #339

Human Review Status

Completed. This PR was squash-merged into dev after final review, green checks, and zero unresolved review threads.

Review Focus

  • Verify the new turn-change restore records stay local-only and are not exposed through share/export/sync-style paths.
  • Review the Undo/Redo conflict behavior for multi-file turns, especially created/deleted files and sensitive files.
  • Review the inline UI layout for one-line file rows, stable icon positions, fixed-size Undo/Redo confirmation, and expandable diff behavior.
  • Confirm the v1 product wording is acceptable: this tracks PawWork file-writing tools, not arbitrary shell side effects.

Risk Notes

  • Adds a SQLite migration for turn_change_display and turn_change_restore.
  • Undo/Redo writes files and uses preflight hash checks before writing. If any file has changed since the turn, the operation is blocked instead of partially applying.
  • Turn-change display refresh currently uses assistant completion fetch plus one delayed retry; this PR does not add a dedicated turn-change.updated event.
  • Sensitive files are status-only in UI/export/share/session_diff/tool metadata. Restore data remains local so Undo/Redo can still work.
  • Large or binary content is status-only and non-expandable. The current v1 inline restore limit is 2 MiB per file.
  • Shell/package-manager/plugin side effects are not tracked by this v1 layer unless they go through PawWork file-writing tools.

Follow-up Candidates

These are intentionally not blockers for this PR and do not need a new umbrella issue right now:

  • Add a dedicated turn-change.updated event if assistant-completion fetch plus delayed retry proves flaky in real use.
  • Add content-level secret scanning for display diffs, so non-obvious files such as config.json can be downgraded to status-only when they contain secrets.
  • Define and implement restore behavior for symlinks, file modes, and executable bits if users hit that limitation.
  • Move toward a shared file-level write lock across write/edit/apply_patch/Undo/Redo when the write path is centralized.
  • Replace the frontend hand-written fetch calls with generated SDK endpoints when the API surface is ready for that cleanup.
  • Polish secondary UI copy such as truncated summaries and per-file blocked reasons if user feedback shows confusion.

How To Verify

Focused backend tests: 104 passed, 0 failed
Command: cd packages/opencode && bun test test/session/turn-change.test.ts test/session/session-artifacts.test.ts test/tool/write.test.ts test/tool/edit.test.ts test/tool/apply_patch.test.ts test/session/export.test.ts test/share/share-next.test.ts --timeout 30000

opencode typecheck: passed
Command: cd packages/opencode && bun run typecheck

app typecheck: passed
Command: cd packages/app && bun run typecheck

ui typecheck: passed
Command: cd packages/ui && bun run typecheck

Migration check: passed
Command: cd packages/opencode && bun drizzle-kit check

App production build: passed with existing chunk-size/dynamic-import warnings
Command: cd packages/app && bun run build

Diff check: no whitespace errors
Command: git diff --check

Screenshots or Recordings

Visible UI changed.

I attempted Browser Use against the local Vite app, but the Browser Use runtime timed out twice in this session. I then used local Playwright as a fallback to load http://127.0.0.1:49720/ and capture /tmp/pawwork-i339-app.png. The page loaded with title PawWork; console errors were expected sidecar connection failures because only the frontend Vite server was running, not the backend sidecar. The actual turn-change block was covered by component build/typecheck rather than a live sidecar-backed session screenshot.

Checklist

  • Human review status is stated above as pending, approved, or not required
  • I linked the related issue, or stated why there is no issue
  • This PR has type, scope, and priority labels, or I requested maintainer labeling
  • I described the review focus and any meaningful risks
  • I listed the relevant verification steps and the key result for each
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope
  • I manually checked visible UI or copy changes when needed, with screenshots or recordings
  • I considered macOS and Windows impact for desktop, packaging, updater, signing, paths, shell, or permissions changes
  • I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant
  • I reviewed the final diff for unrelated changes and suspicious dependency changes
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English

Summary by CodeRabbit

  • New Features

    • Per-turn file change history in the timeline with expandable diffs, undo/redo (confirmation), open file / show-in-folder actions, prefetching, and truncated/omitted handling.
  • Bug Fixes / UX

    • Clear blocked undo/redo feedback via error toasts and cached fallbacks for conflicts, missing restores, permissions, size, or write failures.
  • Security & Privacy

    • Redaction of sensitive file contents/diffs across exports, sync/share, and tools; diagnostics suppressed for sensitive targets.
  • Chores

    • Migration, server endpoints, UI strings, styles, and runtime wiring to enable the feature.
  • Tests

    • Extensive end-to-end and unit tests covering finalize/undo/redo, redaction, overflow/large-file safeguards, BOM handling, and error cases.

@coderabbitai

coderabbitai Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds per-session, per-assistant-turn file-change tracking with DB schema, a TurnChange service (record/finalize/get/undo/redo), tool integrations recording writes and redacting sensitive diffs, server routes for fetch/undo/redo, client prefetch/UI wiring and styles, runtime/test wiring, and tests.

Turn-Change + Sensitivity Cross‑Cut

Layer / File(s) Summary
Data Shape / Schema
packages/opencode/migration/20260503120000_turn_changes/migration.sql, packages/opencode/src/session/session.sql.ts
Adds turn_change_display and turn_change_restore tables and exported Drizzle table defs to persist per-session/per-message displays and per-file restore snapshots (FKs, timestamps, indexes, uniqueness).
Core Implementation
packages/opencode/src/session/turn-change.ts
New TurnChange service with exported types/Zod schemas, BOM-aware hashing/normalization, recordWrite/finalize/get/undo/redo, per-session async lock, transactional finalize, preflight validations, apply/rollback logic, overflow/truncation handling, and MutationResult semantics.
Persistence Read/Apply Helpers
packages/opencode/src/session/turn-change.ts
Filesystem state loaders, hashing, applyState write/restore helpers, overflow row handling, and open-path/display normalization.
Server API & Publish
packages/opencode/src/server/instance/session.ts
Adds GET /:sessionID/turn-change/:messageID (nullable) and POST .../undo / .../redo routes that assert session not-busy, invoke TurnChange undo/redo, and publish derived FileWatcher/LSP events when applied.
Processor / Runtime Wiring
packages/opencode/src/session/processor.ts, packages/opencode/src/effect/app-runtime.ts, packages/opencode/src/tool/registry.ts
Wires TurnChange.defaultLayer into processor/runtime/tool registry; processor calls TurnChange.finalize after assistant message completion; app/runtime/tool layers updated and tests wired.
Tooling Integration (recording + sensitivity)
packages/opencode/src/tool/sensitive.ts, packages/opencode/src/tool/apply_patch.ts, packages/opencode/src/tool/edit.ts, packages/opencode/src/tool/write.ts
Adds sensitive-path detection & sanitizers; tools detect sensitive targets, redact per-file patch/additions/deletions for sensitive entries, suppress LSP diagnostics for sensitive changes, and call TurnChange.recordWrite capturing before/after existence/content/BOM (moves recorded as unlink+write).
Summary / Export / Revert / Share
packages/opencode/src/session/summary.ts, packages/opencode/src/session/export.ts, packages/opencode/src/session/revert.ts, packages/opencode/src/share/share-next.ts
Sanitizes diffs/tool parts via sanitizeSensitiveDiffs/sanitizeSensitiveToolPart before persisting/exporting/sharing; summary/revert use nullish-safe additions/deletions handling.
Client state & fetching
packages/app/src/pages/session/message-timeline.tsx
Adds TurnChangeDisplay type, turnChanges store, turnChangeFetch (auth GET; POST for undo/redo with blocked-toast fallback), prefetch effect with one retry, and wires turnChanges + action handlers into SessionTurn.
UI rendering & styles
packages/ui/src/components/session-turn.tsx, packages/ui/src/components/session-turn.css, packages/app/src/i18n/*, packages/ui/src/i18n/*, packages/ui/src/components/session-turn-changes.ts
SessionTurn gains optional turnChanges + turnChangeActions props, renders per-turn expandable per-file displays, undo/redo confirmation UX, omitted/truncated indicators, file open/show-in-folder actions, new CSS + i18n keys; adds TurnChangeDisplay/TurnChangeFile types and helpers.
Tests & Test Wiring
packages/opencode/test/*, packages/opencode/test/tool/*
Extensive tests for finalize/undo/redo semantics, sensitivity redaction across export/share/tool flows, large/binary/BOM/conflict/permission cases, path-sensitivity unit tests, and runtime wiring updates to include TurnChange.defaultLayer.
sequenceDiagram
    actor User
    participant Client as Client (message-timeline)
    participant UI as UI (session-turn)
    participant Server as Server (session routes)
    participant DB as Database
    participant FS as Filesystem

    User->>Client: Open session view
    Client->>Server: GET /session/:id/turn-change/:messageID
    Server->>DB: Load turn_change_display + restore rows
    DB-->>Server: Display + restores
    Server-->>Client: TurnChangeDisplay (or null)
    Client->>UI: Provide turnChanges + actions
    User->>UI: Click Undo (confirm)
    UI->>Client: turnChangeActions.undo(messageID)
    Client->>Server: POST /session/:id/turn-change/:messageID/undo
    Server->>DB: Validate display state & load restores
    Server->>FS: Preflight check & apply file state changes
    FS-->>Server: write results
    Server->>DB: Persist updated display state
    Server-->>Client: MutationResult {status:"applied"|"blocked", display?}
    Client->>UI: Update displayed turnChange
Loading
sequenceDiagram
    participant Tool as Tool (edit/apply_patch/write)
    participant Sensitive as Sensitive (sanitizer)
    participant TurnChange as TurnChange
    participant DB as Database
    participant Processor as Processor

    Tool->>Sensitive: isSensitivePath / sanitize inputs
    Sensitive-->>Tool: sanitized metadata / sensitive flag
    Tool->>TurnChange: recordWrite(sessionID,messageID,path,before,after)
    TurnChange->>DB: Insert/Upsert turn_change_restore rows
    DB-->>TurnChange: OK
    Processor->>TurnChange: finalize(sessionID,messageID)
    TurnChange->>DB: Read restore rows, compute Display
    TurnChange->>Sensitive: sanitizeSensitiveDiffs(diffs)
    Sensitive-->>TurnChange: redacted diffs
    TurnChange->>DB: Upsert turn_change_display, mark restore rows finalized
    DB-->>TurnChange: OK
Loading

🎯 4 (Complex) | ⏱️ ~65 minutes

Suggested labels: enhancement

🐰
I nibbled strings and braided hash,
Hid secrets safe beneath my sash.
Each turn I track with quiet feet,
Undo hops back — the files repeat.
A rabbit guards your diffed sheet.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: track assistant turn changes' directly summarizes the main feature addition of enabling per-turn file change tracking.
Description check ✅ Passed The PR description is comprehensive, covering Summary, Why, Related Issue, Review Focus, Risk Notes, Verification, and Checklist sections matching the template structure.
Linked Issues check ✅ Passed The code changes address all core objectives from issue #339: providing git-independent turn-change tracking for non-git projects, supporting ignored files, showing assistant-turn results instead of branch snapshots, keeping restore data local, and implementing whole-turn Undo/Redo with preflight checks.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing turn-change tracking, Undo/Redo APIs, UI updates, sensitive content redaction, and necessary database/data-model infrastructure. No unrelated refactors or external changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/i339-turn-changes

Review rate limit: 8/10 reviews remaining, refill in 6 minutes and 40 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a turn-based file change tracking system, allowing users to view, undo, and redo modifications made by the assistant. It introduces a new TurnChange module, database migrations for persistence, and UI components for the message timeline. Additionally, a sensitive data sanitization layer is added to redact secrets and credentials from diffs and tool outputs. The review feedback identifies several critical improvements for the TurnChange logic, including preventing binary file corruption during restoration, using relative paths for database portability, and optimizing database queries to avoid performance bottlenecks when recording multiple file changes.

Comment thread packages/opencode/src/session/turn-change.ts Outdated
Comment thread packages/opencode/src/session/turn-change.ts Outdated
Comment thread packages/opencode/src/session/turn-change.ts Outdated
Comment thread packages/opencode/src/session/turn-change.ts Outdated
Comment thread packages/opencode/src/session/turn-change.ts Outdated
Comment thread packages/app/src/pages/session/message-timeline.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/opencode/src/tool/apply_patch.ts (1)

153-167: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the overwritten destination state for move undo.

The move path is always recorded as before: { exists: false }, but this tool already allows moving onto an existing destination. In that case, undo will delete the destination instead of restoring the file that was overwritten there.

Also applies to: 307-321

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/tool/apply_patch.ts` around lines 153 - 167, The move
operation currently records the destination (movePath) as always non-existent so
undos will delete overwritten files instead of restoring them; update the logic
around movePath in apply_patch (where movePath is computed and fileChanges.push
is called, and the related similar block at lines ~307-321) to detect if
movePath exists before applying the move, capture its prior state (exists,
content, mode/bom/sensitive flags as appropriate) and include that "before"
snapshot in the fileChanges entry so the undo path can restore an overwritten
destination rather than deleting it; keep using
assertExternalDirectoryEffect(movePath) but add a read/stat of movePath and
populate the fileChanges move entry with the destination's previous metadata.
🧹 Nitpick comments (2)
packages/opencode/src/session/session.sql.ts (1)

150-151: ⚡ Quick win

Align index names with the repo naming convention.

The new index names don’t follow the <table>_<column>_idx pattern consistently, which makes schema naming less predictable.

Proposed rename example
-    uniqueIndex("turn_change_display_message_idx").on(table.session_id, table.message_id),
+    uniqueIndex("turn_change_display_session_id_message_id_idx").on(table.session_id, table.message_id),

-    uniqueIndex("turn_change_restore_file_idx").on(table.session_id, table.message_id, table.file_path),
-    index("turn_change_restore_message_idx").on(table.session_id, table.message_id),
+    uniqueIndex("turn_change_restore_session_id_message_id_file_path_idx").on(table.session_id, table.message_id, table.file_path),
+    index("turn_change_restore_session_id_message_id_idx").on(table.session_id, table.message_id),

As per coding guidelines, indexes should follow <table>_<column>_idx format.

Also applies to: 173-174

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/session/session.sql.ts` around lines 150 - 151, The
index names for the turn_change_display indexes don't follow the
<table>_<column>_idx pattern; update the uniqueIndex and index calls so they use
the table name plus the full column name and _idx suffix (e.g., change
uniqueIndex("turn_change_display_message_idx") to
"turn_change_display_message_id_idx" and change
index("turn_change_display_session_idx") to
"turn_change_display_session_id_idx"), and make the same renames for the other
two index calls referenced at lines 173-174; locate these in the code where
uniqueIndex(...) and index(...) are called on the table.session_id and
table.message_id columns and replace the string names accordingly.
packages/opencode/test/share/share-next.test.ts (1)

366-432: ⚡ Quick win

Add a regression for the message sync path too.

This only proves Session.Event.Diff is redacted. ShareNext also syncs message records, and those can embed summary.diffs, so the current suite would miss the leak if that path regresses. A small case that publishes or full-syncs a message carrying a sensitive embedded diff would close the gap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/share/share-next.test.ts` around lines 366 - 432, Add
a regression test that covers the message sync path by publishing or
full-syncing a message that contains an embedded sensitive diff in its summary
(e.g., summary.diffs with a patch containing secrets) and assert ShareNext
redacts those secrets the same way as Session.Event.Diff; locate the test
harness in share-next.test.ts and reuse the existing HttpClient mock (`seen`
array) and wired environment, then publish a Session.Event.Message event (or
insert a message that will be synced) for the same session id and assert the
recorded sync payload body does not contain secret strings or patch markers and
that the serialized message entry marks the diff as sensitive (use the same
expectations pattern as the existing Session.Event.Diff assertions).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app/src/pages/session/message-timeline.tsx`:
- Around line 371-376: The code adds key (`${id}:${message.id}`) to
fetchedTurnChanges before awaiting turnChangeFetch, so transient failures leave
the key cached and prevent retries; fix by only adding the key after a
successful fetch (move fetchedTurnChanges.add(key) into the success path of the
promise returned by turnChangeFetch) or, if you prefer to keep the pre-add,
remove the key in the catch handler (call fetchedTurnChanges.delete(key)) so
failed attempts are not permanently recorded; update the code around
turnChangeFetch, fetchedTurnChanges, and setTurnChanges to ensure keys are only
retained on success.

In `@packages/opencode/migration/20260503120000_turn_changes/migration.sql`:
- Around line 14-28: The migration adds a NOT NULL column `position` on table
`turn_change_restore` but TurnChange.recordWrite (in TurnChange.recordWrite)
does not supply `position`, causing insert failures; either make `position`
nullable or give it a sensible DEFAULT (e.g., 0) in the CREATE TABLE statement,
or update the writer function TurnChange.recordWrite to include and populate
`position` on insert—pick one consistent fix and adjust the CREATE TABLE or the
TurnChange.recordWrite insert logic accordingly.

In `@packages/opencode/src/server/instance/session.ts`:
- Around line 567-623: Add the same session-busy guard to the turn-change
undo/redo route handlers: before calling TurnChange.undo(params) and
TurnChange.redo(params), call SessionRunState.assertNotBusy(...) (using the
sessionID from params) to block mutations when a session run is in progress;
place the check at the start of each POST handler so the request returns/throws
if the session is busy, mirroring other routes in this file.

In `@packages/opencode/src/session/turn-change.ts`:
- Around line 126-127: The code uses string.length to check against
DISPLAY_LIMIT (2 MiB) which measures UTF-16 code units, not bytes; change the
checks to use byte length (e.g., use Buffer.byteLength(s, 'utf8') or
TextEncoder().encode(s).length) for beforeText and afterText, and update the
computation of the `large` boolean accordingly. Replace occurrences where
`beforeText.length` or `afterText.length` are compared to DISPLAY_LIMIT
(including the other occurrence referenced around the 253-254 area) so they
compute byte length first and compare bytes to the DISPLAY_LIMIT (ensure
DISPLAY_LIMIT is interpreted as bytes).
- Around line 253-254: The current early returns drop oversized edits (checks
against RESTORE_LIMIT on input.before/input.after) so large-file writes never
appear in turn tracking; instead, detect when either input.before.exists or
input.after.exists has content.length > RESTORE_LIMIT and emit a status-only
(non-expandable) turn-change entry via the existing turn-change creation path
rather than returning early, preserving normal processing for other cases;
reference RESTORE_LIMIT and the input.before/input.after checks and reuse the
same turn-change emission logic but mark the entry as
non-expandable/status-only.

In `@packages/opencode/src/share/share-next.ts`:
- Around line 279-286: The sync payload currently includes message infos
verbatim which lets MessageV2.info.summary.diffs leak; when building the sync
batch in the sync(...) call update the mapping over messages (where you
currently spread ({ type: "message", data: item.info })) to sanitize or remove
embedded diffs by running item.info.summary.diffs through sanitizeSensitiveDiffs
(or deleting that field) before emitting each message record, and apply the same
sanitization logic in the incremental handler for MessageV2.Event.Updated so
that any updated MessageV2.info is also stripped of summary.diffs; reference the
sync(...) call, the messages.map(...) emission, sanitizeSensitiveDiffs,
MessageV2.Event.Updated and sanitizeSensitiveToolPart to locate both places to
change.

In `@packages/opencode/src/tool/edit.ts`:
- Around line 117-118: TurnChange.recordWrite currently stores only { exists,
content } so when you strip a BOM into contentOld/contentNew (variables
bomDiscarded, contentOld, contentNew) undo/redo restores BOM-less bytes; update
all calls to TurnChange.recordWrite in edit.ts and the matching call in write.ts
to include the BOM state (e.g., { exists, content, bomDiscarded } or a boolean
like hadBOM) in the payload, ensure TurnChange.recordWrite signature and any
consumers accept and persist that extra field, and when restoring apply the BOM
flag to reinsert the original BOM bytes so the restore path is byte-accurate
(also update the other two occurrences that mirror this logic).

In `@packages/opencode/src/tool/sensitive.ts`:
- Around line 80-89: sanitizeSensitiveDiffs currently returns a reduced object
for sensitive files which strips keys downstream code expects (e.g. patch,
additions, deletions) causing runtime errors; update sanitizeSensitiveDiffs to
preserve the original diff shape by spreading the original item and only
overriding/redacting sensitive values: keep all original keys from T (use
{...item}) and set/override sensitive: true, status: item.status ?? "modified",
and replace sensitive content fields (e.g. patch -> "[REDACTED]",
additions/deletions -> null or 0) rather than removing them; use isSensitivePath
to detect sensitive files and ensure the returned object type still conforms to
the expected diff shape while retaining SensitiveStatus handling.

In `@packages/ui/src/components/session-turn.tsx`:
- Around line 567-570: The fallback inside the Show component currently
hardcodes "Updated" when file.additions and file.deletions are undefined;
instead, use the file's actual status field (e.g., file.status or
file.changeType) in that fallback so binary/large adds or deletes are labeled
correctly. Update the fallback span at the Show block to render a mapped value
from file.status (translate statuses like "added"/"deleted"/"modified" to the
same UI strings) so entries without numeric counts show their real change type
rather than always "Updated".

---

Outside diff comments:
In `@packages/opencode/src/tool/apply_patch.ts`:
- Around line 153-167: The move operation currently records the destination
(movePath) as always non-existent so undos will delete overwritten files instead
of restoring them; update the logic around movePath in apply_patch (where
movePath is computed and fileChanges.push is called, and the related similar
block at lines ~307-321) to detect if movePath exists before applying the move,
capture its prior state (exists, content, mode/bom/sensitive flags as
appropriate) and include that "before" snapshot in the fileChanges entry so the
undo path can restore an overwritten destination rather than deleting it; keep
using assertExternalDirectoryEffect(movePath) but add a read/stat of movePath
and populate the fileChanges move entry with the destination's previous
metadata.

---

Nitpick comments:
In `@packages/opencode/src/session/session.sql.ts`:
- Around line 150-151: The index names for the turn_change_display indexes don't
follow the <table>_<column>_idx pattern; update the uniqueIndex and index calls
so they use the table name plus the full column name and _idx suffix (e.g.,
change uniqueIndex("turn_change_display_message_idx") to
"turn_change_display_message_id_idx" and change
index("turn_change_display_session_idx") to
"turn_change_display_session_id_idx"), and make the same renames for the other
two index calls referenced at lines 173-174; locate these in the code where
uniqueIndex(...) and index(...) are called on the table.session_id and
table.message_id columns and replace the string names accordingly.

In `@packages/opencode/test/share/share-next.test.ts`:
- Around line 366-432: Add a regression test that covers the message sync path
by publishing or full-syncing a message that contains an embedded sensitive diff
in its summary (e.g., summary.diffs with a patch containing secrets) and assert
ShareNext redacts those secrets the same way as Session.Event.Diff; locate the
test harness in share-next.test.ts and reuse the existing HttpClient mock
(`seen` array) and wired environment, then publish a Session.Event.Message event
(or insert a message that will be synced) for the same session id and assert the
recorded sync payload body does not contain secret strings or patch markers and
that the serialized message entry marks the diff as sensitive (use the same
expectations pattern as the existing Session.Event.Diff assertions).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 94d4c0f4-1f0e-4cf2-b933-d4cca0871f11

📥 Commits

Reviewing files that changed from the base of the PR and between 080261e and 34161dc.

📒 Files selected for processing (23)
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/opencode/migration/20260503120000_turn_changes/migration.sql
  • packages/opencode/src/server/instance/session.ts
  • packages/opencode/src/session/export.ts
  • packages/opencode/src/session/processor.ts
  • packages/opencode/src/session/revert.ts
  • packages/opencode/src/session/session.sql.ts
  • packages/opencode/src/session/summary.ts
  • packages/opencode/src/session/turn-change.ts
  • packages/opencode/src/share/share-next.ts
  • packages/opencode/src/tool/apply_patch.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/sensitive.ts
  • packages/opencode/src/tool/write.ts
  • packages/opencode/test/session/export.test.ts
  • packages/opencode/test/session/session-artifacts.test.ts
  • packages/opencode/test/session/turn-change.test.ts
  • packages/opencode/test/share/share-next.test.ts
  • packages/opencode/test/tool/apply_patch.test.ts
  • packages/opencode/test/tool/edit.test.ts
  • packages/opencode/test/tool/write.test.ts
  • packages/ui/src/components/session-turn.css
  • packages/ui/src/components/session-turn.tsx

Comment thread packages/app/src/pages/session/message-timeline.tsx Outdated
Comment thread packages/opencode/migration/20260503120000_turn_changes/migration.sql Outdated
Comment thread packages/opencode/src/server/instance/session.ts
Comment thread packages/opencode/src/session/turn-change.ts Outdated
Comment thread packages/opencode/src/session/turn-change.ts Outdated
Comment thread packages/opencode/src/share/share-next.ts
Comment thread packages/opencode/src/tool/edit.ts
Comment thread packages/opencode/src/tool/sensitive.ts
Comment thread packages/ui/src/components/session-turn.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/opencode/test/session/turn-change.test.ts (1)

149-172: ⚡ Quick win

Add redo assertion for the BOM round-trip path.

This test proves undo BOM restoration, but not redo back to non-BOM. Adding that assertion will catch BOM preflight regressions.

Suggested test extension
         expect((await TurnChange.undo(turn)).status).toBe("applied")
         const bytes = await fs.readFile(target)
         expect([...bytes.slice(0, 3)]).toEqual([0xef, 0xbb, 0xbf])
         expect(bytes.toString("utf-8")).toBe("\uFEFFbefore\n")
+
+        expect((await TurnChange.redo(turn)).status).toBe("applied")
+        const bytesRedo = await fs.readFile(target)
+        expect(bytesRedo.toString("utf-8")).toBe("after\n")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/session/turn-change.test.ts` around lines 149 - 172,
Extend the test to assert redo restores the file back to the non‑BOM state:
after the existing undo assertions (using TurnChange.undo(turn) and verifying
the file contains BOM/"\uFEFFbefore\n"), call TurnChange.redo(turn) and expect
its status toBe("applied"), then read the file bytes and assert the BOM bytes
are removed (e.g., first three bytes are not 0xEF,0xBB,0xBF) and the UTF‑8
string equals "after\n"; use the same fixtures/turn used with
TurnChange.recordWrite and TurnChange.finalize so it targets the same file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/server/instance/session.ts`:
- Around line 45-59: The code always publishes File.Event.Edited even when the
computed watcher event is "unlink", which causes removed files to be treated as
edited; update the logic around File.Event.Edited (the bus.publish call that
uses File.Event.Edited and file.openPath) to skip publishing when the derived
event === "unlink" (use the same computed `event` used for
FileWatcher.Event.Updated), and ensure lsp.touchFile is only called for
non-"unlink" cases (touchFile(file.openPath, true) already uses this check—align
the Edited publish to the same condition). Reference: File.Event.Edited,
FileWatcher.Event.Updated, event, file.openPath, lsp.touchFile, mode,
file.status.

In `@packages/opencode/src/session/turn-change.ts`:
- Around line 184-193: In currentState(), after reading the file content with
fs.readFile, normalize/remove a UTF‑8 BOM by calling Bom.split(content) and use
the split result's content (not the raw string with BOM) for the returned
FileState.content and for computing the SHA256 hash; this ensures the BOM is
handled consistently with other snapshots (edit.ts, apply_patch.ts) and prevents
false preflight diffs—use the Bom.split API and keep changes localized inside
currentState().

In `@packages/opencode/src/tool/apply_patch.ts`:
- Around line 328-335: The move branch currently records the source deletion via
TurnChange.recordWrite but omits the file BOM, so restores can lose byte-order
info; update the before object in the move branch to include the source BOM
(e.g., add a bom property set from the change object such as change.bom or
change.oldBom) alongside exists and content when calling TurnChange.recordWrite
for move operations (keep using change.filePath, change.oldContent, and
change.movePath as-is).

In `@packages/opencode/test/share/share-next.test.ts`:
- Around line 494-507: The test assumes the message payload is at body.data[0];
instead, find the entry whose parsed body contains an object with type ===
"message" before asserting redaction. Modify the assertions to iterate/search
the seen array (variable seen), parse each entry.body into body, locate const
msg = body.data.find(d => d.type === "message"), then assert on
msg.data.summary.diffs (and on serialized) instead of body.data[0]; keep the
existing checks for absence of secrets and sensitive: true in the diff object.

---

Nitpick comments:
In `@packages/opencode/test/session/turn-change.test.ts`:
- Around line 149-172: Extend the test to assert redo restores the file back to
the non‑BOM state: after the existing undo assertions (using
TurnChange.undo(turn) and verifying the file contains BOM/"\uFEFFbefore\n"),
call TurnChange.redo(turn) and expect its status toBe("applied"), then read the
file bytes and assert the BOM bytes are removed (e.g., first three bytes are not
0xEF,0xBB,0xBF) and the UTF‑8 string equals "after\n"; use the same
fixtures/turn used with TurnChange.recordWrite and TurnChange.finalize so it
targets the same file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a0be23c0-25ab-4c2f-ba6f-a65c14d51884

📥 Commits

Reviewing files that changed from the base of the PR and between 34161dc and 69aeca8.

📒 Files selected for processing (15)
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/opencode/migration/20260503120000_turn_changes/migration.sql
  • packages/opencode/src/server/instance/session.ts
  • packages/opencode/src/session/session.sql.ts
  • packages/opencode/src/session/turn-change.ts
  • packages/opencode/src/share/share-next.ts
  • packages/opencode/src/tool/apply_patch.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/sensitive.ts
  • packages/opencode/src/tool/write.ts
  • packages/opencode/test/session/export.test.ts
  • packages/opencode/test/session/session-artifacts.test.ts
  • packages/opencode/test/session/turn-change.test.ts
  • packages/opencode/test/share/share-next.test.ts
  • packages/ui/src/components/session-turn.tsx
✅ Files skipped from review due to trivial changes (2)
  • packages/opencode/migration/20260503120000_turn_changes/migration.sql
  • packages/opencode/test/session/export.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/opencode/test/session/session-artifacts.test.ts
  • packages/opencode/src/share/share-next.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/sensitive.ts
  • packages/opencode/src/session/session.sql.ts

Comment thread packages/opencode/src/server/instance/session.ts Outdated
Comment thread packages/opencode/src/session/turn-change.ts
Comment thread packages/opencode/src/tool/apply_patch.ts
Comment thread packages/opencode/test/share/share-next.test.ts
@Astro-Han Astro-Han added bug Something isn't working P1 High priority app Application behavior and product flows ui Design system and user interface harness Model harness, prompts, tool descriptions, and session mechanics labels May 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/session/turn-change.ts`:
- Around line 452-476: In the mutate function (and the similar block at 487-494)
some permission failures are being classified as generic
"conflict"/"write_failed"; instead, when permission checks fail (the branches
where canRestore(...) indicates the restore is unavailable or where
currentState(...) indicates lack of permission/availability), return a
MutationResult with reason: "permission_denied" and the appropriate files
payload instead of mapping them to "conflict" or
"unsupported_size"/"unavailable"; update the logic that builds the blocked array
and the final return so any item whose failure originated from a permission
check yields reason "permission_denied" (and keep existing mappings only for
true content conflicts/write errors).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7fc1675c-c076-4d3a-b9b5-71f1879afc42

📥 Commits

Reviewing files that changed from the base of the PR and between be03ff3 and 2411a4a.

📒 Files selected for processing (1)
  • packages/opencode/src/session/turn-change.ts

Comment thread packages/opencode/src/session/turn-change.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/opencode/test/session/turn-change.test.ts (1)

241-266: ⚡ Quick win

Add a mutate-path persistence failure regression test.

You already isolate finalize DB failures; please add the analogous case for TurnChange.undo/redo where files were applied but display-state update fails, and assert rollback + write_failed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/session/turn-change.test.ts` around lines 241 - 266,
Add a new test similar to the "finalize failure is isolated from caller cleanup"
case that simulates a persistence/display-state update failure during
TurnChange.undo and TurnChange.redo: create a turn via createTurn(), record a
write with TurnChange.recordWrite(...) that applies a file change to the temp
fixture, then mock the persistence/display update used by undo/redo (e.g., spyOn
Database.transaction or the specific method that updates display state) to throw
an error; call TurnChange.undo(turn) and TurnChange.redo(turn) and assert they
do not throw, that the filesystem changes are rolled back, and that the
TurnChange entry reflects a write_failed state (use TurnChange.get(turn) to
verify); restore the mock afterward.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/session/turn-change.ts`:
- Around line 532-538: The DB update to TurnChangeDisplayTable after applying
file mutations can fail and leave files changed while display state remains
stale; modify the flow in the code around Database.use/Database.use(...) and the
code that applies file mutations so the DB persistence and file changes are
atomic: either (a) perform the TurnChangeDisplayTable update inside the same
transactional boundary as file metadata/state persistence (use a DB transaction)
before committing file mutations, or (b) if you must apply files first, catch
errors from the Database.use(...) call and immediately revert the applied file
changes (invoke the existing undo routine for the change or run a compensating
operation) then rethrow or surface the error. Ensure you reference the
persistedDisplay/nextState/time variables and update logic so the rollback path
uses the same identifiers to restore previous file state and keep undo/redo
consistent.

---

Nitpick comments:
In `@packages/opencode/test/session/turn-change.test.ts`:
- Around line 241-266: Add a new test similar to the "finalize failure is
isolated from caller cleanup" case that simulates a persistence/display-state
update failure during TurnChange.undo and TurnChange.redo: create a turn via
createTurn(), record a write with TurnChange.recordWrite(...) that applies a
file change to the temp fixture, then mock the persistence/display update used
by undo/redo (e.g., spyOn Database.transaction or the specific method that
updates display state) to throw an error; call TurnChange.undo(turn) and
TurnChange.redo(turn) and assert they do not throw, that the filesystem changes
are rolled back, and that the TurnChange entry reflects a write_failed state
(use TurnChange.get(turn) to verify); restore the mock afterward.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 70f2f113-016e-4dea-a3fd-cc67b55b2e7f

📥 Commits

Reviewing files that changed from the base of the PR and between 2411a4a and 0e2f9ef.

📒 Files selected for processing (2)
  • packages/opencode/src/session/turn-change.ts
  • packages/opencode/test/session/turn-change.test.ts

Comment thread packages/opencode/src/session/turn-change.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/ui/src/components/session-turn.tsx (1)

371-376: 💤 Low value

Consider edge case for root-level files.

parentPath returns the original value when there's no separator or when the separator is at index 0. For a root-level file like "file.txt" (no slash), index is -1, so it returns the filename itself rather than "." or an empty string.

This is likely fine since showInFolder will receive the file path (and platform APIs typically handle this), but worth verifying the platform behavior for root-level deleted files.

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/components/session-turn.tsx` around lines 371 - 376,
parentPath currently returns the original value when there is no separator or
when the separator is at index 0, which yields incorrect results for root-level
paths; update parentPath so that if index < 0 (no separator) it returns "."
(current directory) and if index === 0 (path like "/file.txt") it returns "/"
(root), otherwise return value.slice(0, index); change this logic inside the
parentPath function referenced in session-turn.tsx so showInFolder receives a
canonical parent path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/ui/src/components/session-turn.tsx`:
- Around line 371-376: parentPath currently returns the original value when
there is no separator or when the separator is at index 0, which yields
incorrect results for root-level paths; update parentPath so that if index < 0
(no separator) it returns "." (current directory) and if index === 0 (path like
"/file.txt") it returns "/" (root), otherwise return value.slice(0, index);
change this logic inside the parentPath function referenced in session-turn.tsx
so showInFolder receives a canonical parent path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f25cf12c-a634-48f0-b59d-6ba9825c2864

📥 Commits

Reviewing files that changed from the base of the PR and between 0e2f9ef and bcff173.

📒 Files selected for processing (7)
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/files-tab.tsx
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/ui/src/components/session-turn.tsx
  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/ui/src/i18n/en.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/pages/session/message-timeline.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/opencode/src/tool/write.ts (1)

76-92: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Make the file write and turn-change recording atomic.

The file is already written, optionally reformatted, and File.Event.Edited is already published before turnChange.recordWrite() runs. If that tracking step fails, the outer Effect.orDie still turns the tool into a hard failure even though the filesystem was mutated, and the new undo/redo flow has no restore record for that write.

Please either persist the turn-change record before emitting success side effects, or roll the file back/delete the newly created file when tracking fails.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/tool/write.ts` around lines 76 - 92, The write flow
currently writes the file and publishes File.Event.Edited before calling
turnChange.recordWrite, risking filesystem mutation without a persisted undo
record; change the sequence so you persist the turn-change record first (call
turnChange.recordWrite(...) using finalContent and desiredBom, with the same
before/after payload) and only after that succeeds call
bus.publish(File.Event.Edited, ...); if you cannot move recordWrite earlier due
to relying on post-format content from Bom.syncFile/format.file, then wrap the
recordWrite call in a try/catch and on failure rollback the filesystem to the
original state (use fs.writeWithDirs(filepath, Bom.join(contentOld, source.bom))
or delete the new file when exists was false) before rethrowing the error so the
file system and recorded state remain atomic; reference functions/variables:
fs.writeWithDirs, format.file, Bom.syncFile, bus.publish,
turnChange.recordWrite, contentOld, finalContent, desiredBom, exists,
source.bom, ctx.sessionID, ctx.messageID.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/tool/write.ts`:
- Around line 61-68: The code calls isSensitivePath(filepath) with an absolute
path which misclassifies sensitivity; compute the project-relative path (the
same value passed to patterns, e.g. path.relative(Instance.worktree, filepath))
and pass that relative path into isSensitivePath instead of the absolute
filepath so sensitivity is determined from the repo-relative segments (update
the variable used in the call near where permission/patterns/metadata are built
around isSensitivePath, safeFilepathMetadata, and patterns).

---

Outside diff comments:
In `@packages/opencode/src/tool/write.ts`:
- Around line 76-92: The write flow currently writes the file and publishes
File.Event.Edited before calling turnChange.recordWrite, risking filesystem
mutation without a persisted undo record; change the sequence so you persist the
turn-change record first (call turnChange.recordWrite(...) using finalContent
and desiredBom, with the same before/after payload) and only after that succeeds
call bus.publish(File.Event.Edited, ...); if you cannot move recordWrite earlier
due to relying on post-format content from Bom.syncFile/format.file, then wrap
the recordWrite call in a try/catch and on failure rollback the filesystem to
the original state (use fs.writeWithDirs(filepath, Bom.join(contentOld,
source.bom)) or delete the new file when exists was false) before rethrowing the
error so the file system and recorded state remain atomic; reference
functions/variables: fs.writeWithDirs, format.file, Bom.syncFile, bus.publish,
turnChange.recordWrite, contentOld, finalContent, desiredBom, exists,
source.bom, ctx.sessionID, ctx.messageID.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d63c1ba-ee75-40dc-b33d-f286a2bfca98

📥 Commits

Reviewing files that changed from the base of the PR and between bcff173 and 5138f35.

📒 Files selected for processing (23)
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/opencode/src/effect/app-runtime.ts
  • packages/opencode/src/server/instance/session.ts
  • packages/opencode/src/session/processor.ts
  • packages/opencode/src/session/turn-change.ts
  • packages/opencode/src/tool/apply_patch.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/src/tool/write.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/processor-effect.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/session/turn-change.test.ts
  • packages/opencode/test/tool/apply_patch.test.ts
  • packages/opencode/test/tool/edit.test.ts
  • packages/opencode/test/tool/write.test.ts
  • packages/ui/src/components/session-turn.css
  • packages/ui/src/components/session-turn.tsx
  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
✅ Files skipped from review due to trivial changes (5)
  • packages/opencode/src/tool/registry.ts
  • packages/ui/src/i18n/zh.ts
  • packages/opencode/src/tool/edit.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • packages/opencode/src/session/processor.ts
  • packages/ui/src/i18n/en.ts
  • packages/opencode/test/tool/write.test.ts
  • packages/opencode/src/server/instance/session.ts
  • packages/opencode/test/tool/edit.test.ts
  • packages/ui/src/components/session-turn.css
  • packages/opencode/test/session/turn-change.test.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/opencode/src/session/turn-change.ts
  • packages/ui/src/components/session-turn.tsx
  • packages/opencode/src/tool/apply_patch.ts

Comment thread packages/opencode/src/tool/write.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/opencode/test/session/turn-change.test.ts (1)

432-475: ⚡ Quick win

Assert the redo call is actually rejected after invalidation.

This test only checks the display flag. It would be stronger to also call TurnChange.redo(first) and assert that the old turn can no longer be reapplied, so the behavioral contract stays covered too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/session/turn-change.test.ts` around lines 432 - 475,
After checking the redoAvailable flag is false, actually attempt to redo the
original turn and assert it fails: call await TurnChange.redo(first) and expect
the returned status to indicate failure (e.g., "rejected" or the appropriate
failure status used by TurnChange) so the test covers behavioral invalidation as
well as the display flag; keep the existing checks on
TurnChange.get(first)?.redoAvailable and use the same first messageID/sessionID
fixture.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/tool/apply_patch.ts`:
- Line 104: The current catch around Bom.readFile(...) at the call site in
apply_patch (and the similar block at lines 171-173) turns every read failure
into undefined which can cause recordWrite(...) to mark non-existent state and
lead to deleting real files; change the error handling so only a “file not
found” / not-found specific error is converted to undefined and all other errors
are rethrown (i.e., let the Effect fail) so the patch aborts on genuine read
failures; update the Bom.readFile(...) pipe/Effect.catch logic to inspect the
thrown error (or its tag) and return undefined only for the not-found case,
otherwise re-raise the error.

---

Nitpick comments:
In `@packages/opencode/test/session/turn-change.test.ts`:
- Around line 432-475: After checking the redoAvailable flag is false, actually
attempt to redo the original turn and assert it fails: call await
TurnChange.redo(first) and expect the returned status to indicate failure (e.g.,
"rejected" or the appropriate failure status used by TurnChange) so the test
covers behavioral invalidation as well as the display flag; keep the existing
checks on TurnChange.get(first)?.redoAvailable and use the same first
messageID/sessionID fixture.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 531a8611-e0aa-4282-9c2b-ec5c7a79a8ca

📥 Commits

Reviewing files that changed from the base of the PR and between a3ca9a0 and 5dbcc97.

📒 Files selected for processing (7)
  • packages/opencode/src/session/turn-change.ts
  • packages/opencode/src/tool/apply_patch.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/sensitive.ts
  • packages/opencode/src/tool/write.ts
  • packages/opencode/test/session/turn-change.test.ts
  • packages/opencode/test/tool/sensitive.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/opencode/src/tool/write.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/opencode/src/session/turn-change.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/sensitive.ts

Comment thread packages/opencode/src/tool/apply_patch.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/tool/sensitive.ts`:
- Around line 161-164: The redaction for tool === "apply_patch" currently
hardcodes every file's status to "modified", losing add/delete info; update the
logic so sensitivePatchPaths returns (or provide a new helper that returns)
objects with both file and status (e.g., {file, status}), then map those results
to ({ file, status, sensitive: true }) instead of setting status: "modified";
reference sensitivePatchPaths, the apply_patch branch, and value.patchText to
locate and change the mapping.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 972da3e2-f532-4ecf-909a-48ef38592844

📥 Commits

Reviewing files that changed from the base of the PR and between d66c2db and e0ae6ce.

📒 Files selected for processing (7)
  • packages/opencode/src/session/turn-change.ts
  • packages/opencode/src/tool/apply_patch.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/sensitive.ts
  • packages/opencode/src/tool/write.ts
  • packages/opencode/test/session/turn-change.test.ts
  • packages/opencode/test/tool/sensitive.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/opencode/src/session/turn-change.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/opencode/test/tool/sensitive.test.ts
  • packages/opencode/test/session/turn-change.test.ts

Comment thread packages/opencode/src/tool/sensitive.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/ui/src/components/session-turn.tsx`:
- Around line 349-353: The custom parentPath function strips root separators and
can produce incorrect targets; replace the splitter logic by calling the shared
getDirectory helper (already imported) wherever parentPath or the similar
slice-based splitter is used (both the parentPath function and the second
occurrence around lines 593-597), i.e., remove the replaceAll/lastIndexOf/slice
logic and return getDirectory(value) instead so root paths (e.g., /foo.txt or
C:\foo.txt) are preserved.
- Around line 521-531: The button for undo/redo is rendered whenever
turnActionLabel() is truthy even if the actual handler (turnChangeActions.undo
or turnChangeActions.redo) is undefined, producing a clickable no-op; update the
render guard so the button is only shown when both turnActionLabel() is set and
the corresponding handler exists (check turnChangeActions.undo or
turnChangeActions.redo based on action) or require the handler props when turn
changes are enabled, and ensure mutateTurnChange is only called when the handler
is present (also keep resetConfirm behavior); reference turnActionLabel,
mutateTurnChange, turnChangeActions.undo, turnChangeActions.redo, Show, and
resetConfirm to locate where to add the conditional.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bc897144-d467-4ae7-b8cb-c069795e739c

📥 Commits

Reviewing files that changed from the base of the PR and between eda4810 and 661db91.

📒 Files selected for processing (3)
  • packages/ui/src/components/session-turn-changes.ts
  • packages/ui/src/components/session-turn-turn-changes.test.ts
  • packages/ui/src/components/session-turn.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/ui/src/components/session-turn-turn-changes.test.ts

Comment thread packages/ui/src/components/session-turn.tsx Outdated
Comment thread packages/ui/src/components/session-turn.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/ui/src/components/session-turn.tsx (1)

328-338: 💤 Low value

Consider wrapping the async action in try-catch.

mutateTurnChange awaits the undo/redo handler but doesn't catch errors. If turnChangeFetch throws on a network failure (e.g., !res.ok), this would result in an unhandled promise rejection. The blocked-operation toast is handled inside turnChangeFetch, but actual exceptions would bubble up silently.

🛡️ Optional defensive fix
 const mutateTurnChange = async () => {
   const current = turnChange()
   const id = current?.messageID
   if (!id) return
   const action = turnChangeAction(current)
   if (!action || !hasTurnChangeActionHandler(current, props.turnChangeActions)) return
   if (!primeConfirm(action)) return
   resetConfirm()
-  if (action === "undo") await props.turnChangeActions?.undo?.(id)
-  else await props.turnChangeActions?.redo?.(id)
+  try {
+    if (action === "undo") await props.turnChangeActions?.undo?.(id)
+    else await props.turnChangeActions?.redo?.(id)
+  } catch {
+    // Network errors are logged globally; UI remains consistent
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/components/session-turn.tsx` around lines 328 - 338,
mutateTurnChange currently awaits the undo/redo handlers
(props.turnChangeActions?.undo/redo) but does not catch exceptions, so any
thrown errors (e.g., from turnChangeFetch) will bubble as unhandled rejections;
wrap the core async operation in a try-catch around the awaited call (after
confirming action and calling resetConfirm) and in the catch log or surface the
error (e.g., via process logger/toast) and ensure confirm state is reset
appropriately; reference mutateTurnChange, turnChangeAction,
hasTurnChangeActionHandler, primeConfirm, resetConfirm, and
props.turnChangeActions?.undo/redo when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/ui/src/components/session-turn.tsx`:
- Around line 328-338: mutateTurnChange currently awaits the undo/redo handlers
(props.turnChangeActions?.undo/redo) but does not catch exceptions, so any
thrown errors (e.g., from turnChangeFetch) will bubble as unhandled rejections;
wrap the core async operation in a try-catch around the awaited call (after
confirming action and calling resetConfirm) and in the catch log or surface the
error (e.g., via process logger/toast) and ensure confirm state is reset
appropriately; reference mutateTurnChange, turnChangeAction,
hasTurnChangeActionHandler, primeConfirm, resetConfirm, and
props.turnChangeActions?.undo/redo when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fd534774-61e8-4d0b-b86a-60fca35b046e

📥 Commits

Reviewing files that changed from the base of the PR and between 661db91 and 4c69108.

📒 Files selected for processing (3)
  • packages/ui/src/components/session-turn-changes.ts
  • packages/ui/src/components/session-turn-turn-changes.test.ts
  • packages/ui/src/components/session-turn.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/ui/src/components/session-turn-turn-changes.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows bug Something isn't working harness Model harness, prompts, tool descriptions, and session mechanics P1 High priority ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Session turn diff shows nothing for non-git projects and filters out git-ignored files

1 participant