features: /changes, /search, /fork, web_search, prompt memoization, per-turn spend cap, workspace refresh
ci / check (macos-latest) (push) Canceled after 0s
ci / check (ubuntu-latest) (push) Canceled after 0s
ci / check (windows-latest) (push) Canceled after 0s

- /changes diffs the last turn's file snapshot (added/modified/deleted), reusing undo infra via SnapshotStack.peek()
- system prompt memoized behind version counters (notebook/memory/skills/plugins/tools/workspace); hit-rate in /cost, foundation for provider caching
- web_search: DuckDuckGo Lite, keyless, 5 results, SSRF-filtered, in the net set with ask permission
- /search <query>: full-text grep over saved sessions incl. tool-input JSON
- workspace file list re-walks at a turn boundary after writes
- maxSpendPerTurn: per-turn cap stops a runaway step with a notice
- /fork: branch the session at the last turn boundary, original untouched

821 tests pass, typecheck clean, build green
This commit is contained in:
asepharyana
2026-09-09 18:06:31 +07:00
parent 0bdf642672
commit e709737df1
24 changed files with 720 additions and 41 deletions
+1 -1
View File
@@ -247,7 +247,7 @@ the reasoning.
| `tools.ts` | file and shell tools, tool sets, ripgrep bridge, bash streaming and interrupt |
| `tools-git.ts` | read-only git tools, spawned with a fixed argv |
| `commit.ts` | `git_commit_message`, a nested model call over the staged diff |
| `tools-net.ts` | `web_fetch`, private-address and redirect checks |
| `tools-net.ts` | `web_fetch`, `web_search`, private-address and redirect checks |
| `ignore.ts` | gitignore-aware walker, path jail |
| `complete.ts` | `@path` token extraction, ranking, insertion |
| `registry.ts` | external index, validation, install and removal |
+2
View File
@@ -21,6 +21,7 @@ Written by `/provider`, editable by hand. Every field is optional.
"thinking": "medium",
"maxRetries": 3,
"maxSpendUsd": 5,
"maxSpendPerTurn": 0.5,
"subagentModel": "gpt-5-nano",
"plugins": ["guard", "time"],
"toolSets": ["edit-plus", "extra", "git"],
@@ -45,6 +46,7 @@ Written by `/provider`, editable by hand. Every field is optional.
| `thinking` | default level: `off`, `low`, `medium`, `high`, `max` |
| `maxRetries` | retries per model call for transient failures. Default 3 |
| `maxSpendUsd` | session spend ceiling: warn at 80%, refuse the next turn at 100%. Headless exits non-zero naming the ceiling. Only enforced on priced models |
| `maxSpendPerTurn` | per-turn spend ceiling: a single turn past this line is stopped at a step boundary, even when the session ceiling is far away. Only enforced on priced models |
| `subagentModel` | model id for `explore` subagents, which search rather than reason. Omit to share the parent's model. `/cost` reports subagent spend separately |
| `plugins` | which builtin plugins to enable. Omit for `["guard", "secrets", "protect", "time", "no-force-push", "no-net-pipe", "no-root", "no-env-write"]` |
| `toolSets` | optional tool sets beyond `core`: `edit-plus`, `nav`, `extra`, `git`, and `net`. Omit for the defaults; `net` is opt-in. See [tools](tools.md) |
+1 -1
View File
@@ -16,7 +16,7 @@ There is no terminal to approve on, so every gated tool is denied unless `--yolo
```
$ shiro -p "add a test for paginate()"
shiro: headless denies write_file, edit_file, multi_edit, apply_patch, bash, web_fetch and mcp tools unless --yolo is passed
shiro: headless denies write_file, edit_file, multi_edit, apply_patch, bash, web_fetch, web_search and mcp tools unless --yolo is passed
[tool] write_file {"path":"test/paginate.test.ts",...}
[denied] write_file (run with --yolo to allow tool use in headless mode)
```
+2 -1
View File
@@ -37,6 +37,7 @@ remain are the ones worth reading.
| `move_file` | both ends; one match is enough |
| `apply_patch` | every file marker path in the patch |
| `web_fetch` | the URL |
| `web_search` | the query |
| `read_many_files` | every path in the batch; one match is enough |
| `glob` `grep` | the pattern |
| `git_diff` `git_log` `git_blame` | the path, when given |
@@ -99,7 +100,7 @@ With no `permission` config:
| `glob` `grep` `list_dir` | `allow` |
| the git tools | `allow` — they cannot mutate anything |
| `task`, and every session tool | `allow` — they touch the agent's own state |
| `write_file` `edit_file` `multi_edit` `apply_patch` `move_file` `delete_file` `bash` `web_fetch` | `ask` |
| `write_file` `edit_file` `multi_edit` `apply_patch` `move_file` `delete_file` `bash` `web_fetch` `web_search` | `ask` |
| anything else, including every `mcp__*` tool | `ask` |
Credentials are denied on read rather than gated, because there is no recovery. A model that
+13 -1
View File
@@ -71,7 +71,7 @@ Sets let you switch off what a project does not need:
| `nav` | `find_symbol` `json_query` | navigation and structured reads |
| `extra` | 20 tools: line edits, fs inspect, git extensions, code/env reads | on by default |
| `git` | `git_status` `git_diff` `git_log` `git_show` `git_blame` `git_branch` `git_commit_message` | ~2,180 B + message |
| `net` | `web_fetch` | opt in |
| `net` | `web_fetch`, `web_search` | opt in |
```json
{ "toolSets": ["edit-plus"] }
@@ -394,6 +394,18 @@ private and loopback addresses are refused, redirects are checked one hop at a t
body is capped. The result is untrusted page content, not an instruction, and the call asks for
approval. It belongs to the opt-in `net` set.
## `web_search`
```
query what to search for
```
Searches the web (DuckDuckGo Lite — no API key) and returns up to five results with title,
URL, and snippet. Results are re-checked against the same private-address rule as `web_fetch`,
so a result cannot point the model at localhost or a cloud metadata endpoint. Same `net` set,
same approval, same "untrusted text" framing: search results are a stranger's claims, and a
`web_fetch` on one of them is the right follow-up.
## Git tools
All five are read-only and therefore approval-free. Each spawns `git` with a fixed argument