feat: add undo functionality to revert the last turn and restore file changes

- Implemented `/undo` command to revert the most recent completed turn, restoring all modified files and rewinding message history.
- Introduced `FileMutation` type to capture file changes for undo operations.
- Enhanced session management to track and manage undo stack with a maximum of 20 turns.
- Updated command parsing to include user-defined commands from `.shiro/commands.md`, allowing for custom command definitions and substitutions.
- Added documentation for the new `/undo` command and user commands feature.
- Implemented tests for undo functionality and user command parsing to ensure reliability.
This commit is contained in:
asepharyana
2026-09-04 22:37:38 +07:00
parent e90bcac7c3
commit 4dac34b13f
17 changed files with 777 additions and 24 deletions
+11
View File
@@ -48,6 +48,7 @@ Written by `/provider`, editable by hand. Every field is optional.
| `permission` | which calls run, ask, or are refused, matched per command or path. See [permissions](permissions.md) |
| `registryUrl` | index for `/registry`. Omit for the default. See [registry](registry.md) |
| `subagentModel` | model used for `task` subagents. Omit to reuse the parent model. A cheaper model here cuts subagent cost (and latency) sharply for read-only searches. See [agents](agents.md) |
| `maxSpendUsd` | optional ceiling on estimated spend per session; a run that crosses it stops at the next model call. Adjustable live with `/max-spend`. See [cost control](#cost-control) |
| `mcpServers` | see [MCP](mcp.md) |
## Provider presets
@@ -235,3 +236,13 @@ the outcome, since later rules win. See [permissions](permissions.md).
**`registryUrl`** is the whole trust decision for installed skills and plugins. There are no
signatures, so pointing it at an index means trusting whoever controls that URL — including for
whatever they publish later. See [registry](registry.md).
## Cost control
Set `"maxSpendUsd"` to give a session a ceiling on estimated spend. Spend is estimated from the
billed model's published rates, so an unpriced model never trips the ceiling — and the ceiling is
a guard against a runaway loop, not a billing source. When cross the session stops at the next
model call with a notice.
View the ceiling and current spend with `/cost`; raise, lower, or clear it live with `/max-spend`.
`/cost` shows how close the run is to the ceiling alongside its token count.
+30
View File
@@ -0,0 +1,30 @@
# Undo
`/undo` reverts the most recent completed turn: it restores every file that turn
changed and rewinds the message history to the point before the turn began. It
can step back through up to 20 turns.
## How it works
Before a file-mutating tool (`write_file`, `edit_file`, `multi_edit`,
`apply_patch`) writes, it captures the current bytes of every file it is about
to touch. That snapshot is stored against the in-flight turn. When the turn
finishes (having changed at least one file and added at least one message), the
snapshots are pushed onto an undo log.
`/undo` pops the log and restores:
- a file that was edited — back to its prior contents;
- a file that was created — deleted;
- a file that was deleted — recreated with its prior contents;
- a move — the source restored and the moved copy removed.
Then it truncates the message history to the length it had when that turn
started, so the model no longer sees the reverted decisions.
## The honest limit
A `bash` command's effects cannot be snapshotted — a network call, a build
artifact, or a `git push` are not reversible by restoring file bytes. So `/undo`
covers file-tool edits (including `apply_patch` moves/deletes) and says so; it
does not pretend to reverse commands you ran via `bash`.
+36
View File
@@ -0,0 +1,36 @@
# User commands
Define your own slash commands in a markdown file: `.shiro/commands.md` at the
workspace root. Each command is a `## <name>` heading; an optional `> summary`
line under it becomes the `/`-menu description; the body is the prompt template
sent to the model.
```markdown
# My commands
## review
> review a change against the project rules
Read @AGENTS.md first, then critique:
$ARGUMENTS
## scaffold
> make a new module
Create a module named $1 in src/, with a test.
```
The body supports these substitutions:
- `$ARGUMENTS` — everything typed after the command name, verbatim.
- `$1` .. `$9` — the nth whitespace-separated argument (empty when absent).
- `` !`cmd` `` — replaced with the trimmed stdout of running `cmd` in a shell.
- `@path` — replaced with the contents of the file at `path` (workspace-rooted).
Substitutions apply in a safe order: shell reads first, then files, then `$n`
tags, so text a shell call produces is not itself re-read. A missing file or
failing shell keeps its literal text plus a bracketed note rather than throwing.
A user command whose name collides with a built-in is shadowed — the built-in
wins — so a project cannot hijack `/model` or `/help`. Type `/name` to run one;
it is also listed in the `/` completion menu.