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 committed 2026-09-09 18:06:31 +07:00
1 parent 0bdf642672
commit e709737df1
24 files changed
+720 -41

No files matched your search

+38
View File
@@ -56,6 +56,44 @@ test('load of an unknown id returns undefined instead of throwing', async () =>
expect(await store.load('nope')).toBeUndefined();
});
test('searchSessions finds a phrase inside transcripts', async () => {
await store.save(rec('aaa'));
await store.save({ ...rec('bbb'), messages: [{ role: 'user', content: 'fix the websocket reconnect' }] });
const hits = await store.searchSessions('websocket');
expect(hits.map((r) => r.id)).toEqual(['bbb']);
// case-insensitive
const lower = await store.searchSessions('WEBSOCKET');
expect(lower.map((r) => r.id)).toEqual(['bbb']);
});
test('searchSessions matches tool-input JSON, so a path is findable', async () => {
await store.save({
...rec('aaa'),
messages: [
{ role: 'user', content: 'update the config' },
{
role: 'assistant',
content: [
{
type: 'tool-call',
toolCallId: 'c1',
toolName: 'edit_file',
input: { path: 'src/config.ts', oldString: 'a', newString: 'b' },
},
],
},
],
});
const hits = await store.searchSessions('src/config.ts');
expect(hits.map((r) => r.id)).toEqual(['aaa']);
});
test('searchSessions on an empty query returns nothing', async () => {
expect(await store.searchSessions(' ')).toEqual([]);
});
test('save stamps updatedAt so list can order by recency', async () => {
await store.save(rec('aaa'));
const back = await store.load('aaa');