feat(client): send command ids and write preconditions from every domain client

Merged
GN
11d ago
+2544-422

Summary

  • The terminal, file, git, worktree, workspace and install clients did not send a command id. A resend after a lost reply could run the action twice, and a file save could overwrite a newer file without a warning.
  • This PR makes every client of those areas send a command id. File saves and git commits also send what they expect to replace, so a stale write is refused.
  • The host now refuses an action that must have a command id when the id is missing.
  • This is the last of four pull requests. Pull requests 1088, 1091 and 1092 are the host and client engine parts.

Related Issue

Closes #1068

I searched open and closed pull requests. No other pull request covers this work. It completes the stack of 1088, 1091 and 1092.

Type of Change

  • feat: new feature
  • fix: bug fix
  • docs: documentation only
  • style: formatting or non-functional styling changes
  • refactor: internal cleanup with no behavior change
  • perf: performance improvement
  • test: adds or updates tests
  • build: build or dependency changes
  • ci: CI/CD workflow changes
  • chore: maintenance or tooling
  • revert: reverts a previous change

What Changed

  • The web HTTP client adds an X-Command-Id header to each action that changes state. The terminal socket client sends a command id for spawn, write, kill, rotate and revoke.
  • The desktop terminal, git, worktree, workspace and install commands pass a command id to the host.
  • The editor sends the hash of the file it read as the expected content on save. If the file changed, the editor shows a conflict with an Overwrite action. After an unconfirmed save the editor drops the old hash.
  • The git status store remembers the head commit and sends it as the expected head on commit.
  • The host refuses these actions with invalid_command_id (or INVALID_COMMAND_ID) when the id is missing. Reads, idempotent actions and prompts keep an optional id.
  • A write that the host cannot confirm after a long delay is reported as an unknown outcome. It is not run again.
  • Tests and e2e specs now send command ids. The changelog has one entry for the feature.

How It Was Tested

  • bun run ci (Biome lint + format + imports, strict mode)
  • bun run typecheck
  • bun run test
  • cargo clippy --all-targets -- -D warnings (in src-tauri)
  • cargo test (in src-tauri)
  • Manual verification completed
  • Not applicable

I also ran cargo clippy --bin termul-server --features standalone-server -- -D warnings. New tests cover a resend with the same id for each action, a missing id, a stale file hash, a stale expected head and the desktop error mapping.

Screenshots or Recordings

No new screen. The editor conflict now has an Overwrite action. It is covered by store tests, not by a screenshot.

CI & Review Gate

  • All CI checks pass (PR Validation, Rust Checks, Build Verification, Security Scans)
  • Code review comments from CodeRabbit / Claude have been addressed or resolved
  • No unresolved review findings remain

Checklist

  • My PR title follows the conventional commit format used by this repo
  • I linked the related issue or explained why none exists
  • I updated docs when needed
  • I added or updated tests when needed
  • I verified the change does not introduce unrelated modifications
  • I read AGENTS.md and followed the contributor guidelines
  • A human reviewed the complete diff before submission

Summary by cubic

Every domain client now sends a command id on mutations, so a resend after a lost reply never runs the action twice, and the host refuses ledgered commands that lack one. File saves and git commits carry preconditions so a stale write is refused instead of silently overwriting newer content.

Behavior

  • All terminal, file, git, worktree, workspace, and install clients mint a commandId (desktop arg, HTTP X-Command-Id, or socket commandId) and resend an uncertain action under the same id, keyed by the full request so changed content gets a fresh id.
  • A save sends the hash of the file as read and a commit its expected head; the host answers a mismatch with PRECONDITION_FAILED, and a commit with no readable head fails closed rather than committing with no precondition. When HEAD cannot be read, the commit footer still renders and no precondition is sent.
  • New terminal input queues behind buffered input that has not yet been confirmed, and a save whose outcome the host cannot confirm is reconciled with the user; a refused save shows the "file changed externally" conflict with an Overwrite action, and auto-save stops retrying that file.
  • The host now rejects a ledgered action without a command id (INVALID_COMMAND_ID / invalid_command_id) before running it; reads and idempotent actions still need none.

Summary by CodeRabbit

  • New Features

    • Added safeguards against overwriting files changed elsewhere. You can reload the latest version or choose to overwrite it.
    • Git commits are now checked against the current branch state, and file writes can be checked against the expected file version.
    • Added more reliable handling for repeated or interrupted terminal, Git, workspace, and file operations.
  • Bug Fixes

    • Auto-save now avoids files with external changes and suppresses unnecessary retry notifications after conflicts.