Ship the batch: undo, parallel subagents, lazy MCP, hot-reloaded skills; docs and CI/CD

Loop and ergonomics batch across Now/Next and Maintenance:
- /undo and /redo via pre-prompt file snapshots (snapshot.ts)
- task takes a tasks[] array and runs investigations concurrently (subagent.ts)
- lazy MCP tools: mcp_list/mcp_inspect/mcp_call meta-tools, eager opt-in (mcp.ts, config.ts)
- skill tool reads its list live so a mid-session install is callable next turn (skills.ts)
- tool-name lists (tool-kinds.ts) derived from a mutating() marker; gates previously ungated writes
- prune/session recovery path summarized, and step-back doom-loop primitive (step-back.ts)
- @file completion re-walks on a slow cooldown; estimateTokens and pricing labeled as estimates

Docs: README, CHANGELOG, docs/{mcp,architecture,development} updated to match.
CI/CD: bun install-store cache and concurrency gates on both workflows; release.yml now
composes file-based release notes via scripts/make-release-notes.ts and verifies every binary.
This commit is contained in:
Muhammad Zakir Ramadhan
2026-09-17 17:49:19 +07:00
parent ffa9a02c26
commit f8c3cc2d8e
57 changed files with 3808 additions and 573 deletions
+55
View File
@@ -324,3 +324,58 @@ test('a finished subagent reports its token use to the parent', async () =>
expect(usageEvents).toHaveLength(1);
expect(usageEvents[0]).toMatchObject({ kind: 'explore' });
}));
test('two investigations in one task call overlap in wall-clock time', async () =>
inTempDir(async () => {
await Bun.write('a.ts', 'AAA\n');
await Bun.write('b.ts', 'BBB\n');
// Track how many subagent sleeps are in flight at once. Two that overlap in time
// reach a concurrency of 2; a queueing implementation never does.
let inFlight = 0;
let peakConcurrency = 0;
const seen: LanguageModelV4CallOptions[] = [];
const model = new MockLanguageModelV4({
doStream: async (opts) => {
const call = seen.length;
seen.push(opts);
if (call === 0) {
// The parent batches two independent searches into one task call.
return stream(
toolCall('c1', 'task', {
description: 'two searches',
prompt: 'find both files',
tasks: [
{ description: 'find a', prompt: 'Find where a.ts is mentioned.' },
{ description: 'find b', prompt: 'Find where b.ts is mentioned.' },
],
}),
);
}
// Each subagent sleeps before replying. Overlapping the two is what the
// test is for, so the mock measures it rather than relying on a wall clock.
inFlight++;
peakConcurrency = Math.max(peakConcurrency, inFlight);
await Bun.sleep(200);
inFlight--;
if (call <= 2) return stream(text(`found ${call === 1 ? 'a' : 'b'} at src`));
return stream(text('done'));
},
});
const session = new Session({
model,
askApproval: async () => 'deny',
extraTools: { task: createTaskTool({ model }) },
autoApprove: ['task'],
});
for await (const _ of session.send('find both')) void _;
// Two investigates that truly overlapped both slept at the same moment.
expect(peakConcurrency).toBeGreaterThanOrEqual(2);
// The parent made one task call, the two subagents each one model call, and the
// parent one more reply.
expect(seen.length).toBeGreaterThanOrEqual(4);
}));