/** * Split a tool-call list into sequential vs parallel groups. * Same-path write_file / search_replace share a lock key. */ const SEQUENTIAL = new Set([ 'ask_user_question', 'exit_plan_mode', 'update_goal', 'enter_plan_mode', 'task', 'send_subagent_message', 'wait_tasks', 'kill_task', ]); function isSequential(name) { return SEQUENTIAL.has(String(name || '')); } function pathLockKey(name, args) { args = args || {}; if (name === 'write_file' || name === 'search_replace') { return String(args.path || args.file || '') .replace(/\\/g, '/') .replace(/\/+/g, '/'); } return ''; } function groups(calls) { const out = []; let current = null; for (const call of calls || []) { const seq = isSequential(call && call.name); if (seq) { if (current) { out.push(current); current = null; } out.push({ sequential: true, calls: [call] }); } else { if (!current) current = { sequential: false, calls: [] }; current.calls.push(call); } } if (current) out.push(current); return out; } function withPathLock(locks, key, fn) { if (!key) return Promise.resolve().then(fn); const prev = locks.get(key) || Promise.resolve(); const curr = prev.then(fn, fn); locks.set( key, curr.then( () => {}, () => {} ) ); return curr; } module.exports = { SEQUENTIAL, isSequential, pathLockKey, groups, withPathLock };