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:
@@ -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.
|
||||
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user