Files
asepharyana e709737df1
ci / check (macos-latest) (push) Canceled after 0s
ci / check (ubuntu-latest) (push) Canceled after 0s
ci / check (windows-latest) (push) Canceled after 0s
features: /changes, /search, /fork, web_search, prompt memoization, per-turn spend cap, workspace refresh
- /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
2026-09-09 18:06:31 +07:00

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 ChangeSummary type exported from src/session.ts.
  • /changes command in src/commands.ts (type 'changes').
  • changesPanel() in src/ui/panel-bodies.ts.
  • Wire in App.tsx switch 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.

  • Session stores a version map {notebook, memory, skill, plugin, tool}.
  • Bump versions: onNotebookChange already 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.

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.
  • checkUrl for 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_NAMES includes web_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 on title and serialized messages[].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-runs walk() and updates this.opts.workspaceFiles. Only if fileChangeSeq > lastWalkSeq.
  • Track lastWalkSeq in Session.
  • Call in send() finally block, after drainPendingHotReload.
  • 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?: number in SessionOptions + Config.
  • In send(), snapshot this.spend().usd at turn start. At each step completion (after usage), check if spend delta exceeds cap → abort with notice.
  • /cost shows per-turn cap if set.
  • Config loading: config.maxSpendPerTurn optional 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 to atIndex (default: before the last user message), returns deep-cloned copy. Does NOT modify current session.
  • /fork command (type 'fork') — forks to a new in-memory session with the cloned messages, preserving same options. Returns the new session's id.
  • For simplicity: /fork clones messages up to the second-to-last user message, pushes into a new session object, and reports the id. The user then /resume the 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