- /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
4.7 KiB
Plan: 7 new features for shiro-neko
Scope: add 7 features to the shiro-neko codebase. All touch src/session.ts and related
files. Tests follow existing patterns (bun:test, MockLanguageModelV4, mkdtemp chdir).
Features
1. Turn change summary (/changes)
Files: src/session.ts, src/commands.ts, src/ui/App.tsx, src/ui/panel-bodies.ts, test/changes.test.ts
Reuse undo snapshot infra: after turn, diff turnBeforeFiles vs afterFiles.
Session.lastTurnSummary(): ChangeSummary | undefined— returns{ added: string[], modified: string[], deleted: string[] }from the most recent snapshot's beforeFiles vs afterFiles.- Add
ChangeSummarytype exported fromsrc/session.ts. /changescommand insrc/commands.ts(type'changes').changesPanel()insrc/ui/panel-bodies.ts.- Wire in
App.tsxswitch case + show after 'done' if files were changed. - Test: create files, write_file, assert summary has them; empty turn → undefined.
2. System prompt memoization
Files: src/session.ts, test/prompt-cache.test.ts
Version counters on volatile parts: notebookVersion, memoryVersion, skillVersion, pluginVersion, toolVersion.
Sessionstores a version map{notebook, memory, skill, plugin, tool}.- Bump versions:
onNotebookChangealready bumps notebook; memory.add/bump triggers callback; skills/plugins rebuilt → increment; tools rebuilt → increment. - Cache
{versionKey: string, text: string}in Session. systemFor()builds versionKey from all 5, returns cache hit if identical.- Expose
Session.promptCacheStats(): {hits: number, misses: number}for /cost. - Test: two calls with same version → cache hit; bump one → miss.
3. Web search tool (web_search)
Files: src/tools-net.ts, test/tools-net.test.ts
Add web_search to netTools (same set as web_fetch, opt-in).
- Backend:
config.search?.backend(default'duckduckgo-lite'). - Implementation: fetch
https://lite.duckduckgo.com/lite/?q=<encoded>→ parse result HTML for title+url+snippet. No API key needed. - Max 5 results. Return as numbered markdown list.
checkUrlfor safety on results, skip any that resolve private.- Description: "Search the web for information. Returns up to 5 results with titles, URLs, and snippets."
- Export
NET_TOOL_NAMESincludesweb_search.
4. /search — search saved sessions
Files: src/store.ts, src/commands.ts, src/ui/App.tsx, test/search.test.ts
Full-text grep over session transcripts on disk.
store.searchSessions(query: string, limit?: number): SessionRecord[]— case-insensitive substring match ontitleand serializedmessages[].content(string only; JSON content stringified for matching)./search <query>command (type'search').- Returns panel with matching sessions: id prefix, title, first matched line.
- Test: save session with specific content, search, assert match.
5. Workspace file refresh at turn boundary
Files: src/session.ts, src/cli.tsx (minor), test/workspace-refresh.test.ts
When fileChangeSeq bumps (files written this turn), re-walk workspace at turn end.
Session.refreshWorkspaceFiles(): void— re-runswalk()and updatesthis.opts.workspaceFiles. Only iffileChangeSeq > lastWalkSeq.- Track
lastWalkSeqin Session. - Call in
send()finally block, afterdrainPendingHotReload. - Expose
Session.setWorkspaceFiles(files)for tests. - Test: start with empty workspace, write file via write_file, refresh, assert file appears.
6. Per-turn spend cap
Files: src/session.ts, src/config.ts, src/ui/panel-bodies.ts, test/spend.test.ts
maxSpendPerTurn?: numberinSessionOptions+Config.- In
send(), snapshotthis.spend().usdat turn start. At each step completion (after usage), check if spend delta exceeds cap → abort with notice. /costshows per-turn cap if set.- Config loading:
config.maxSpendPerTurnoptional number. - Test: mock model, set tiny cap, assert turn stops early.
7. /fork — fork session at a turn
Files: src/session.ts, src/commands.ts, src/ui/App.tsx, test/fork.test.ts
Session.fork(atIndex?: number): ModelMessage[]— takes messages up toatIndex(default: before the last user message), returns deep-cloned copy. Does NOT modify current session./forkcommand (type'fork') — forks to a new in-memory session with the cloned messages, preserving same options. Returns the new session's id.- For simplicity:
/forkclones messages up to the second-to-last user message, pushes into a new session object, and reports the id. The user then/resumethe forked session. - Test: build session with 3 turns, fork at -1, assert copy has 1 fewer user message.
Verification
bun run typecheck
bun test
bun run build