diff --git a/router/bin.js b/router/bin.js index 51386e8..183b1e3 100755 --- a/router/bin.js +++ b/router/bin.js @@ -156,6 +156,7 @@ async function discover() { shortName: sanitizeShortName(info.projectShortName), // dedupeShortNames 会按冲突重设 projectPath: info.projectPath, pid: info.pid, + startedAt: Date.parse(info.startedAt) || 0, // 同项目双实例时 dedupe 按它选代表 url: info.url, tools: tools, resources: resources, @@ -212,12 +213,30 @@ function notifyToolsChanged() { * 冲突的用 projectPath 末段(client / server)加后缀区分,单实例保持原名不变。 */ function dedupeShortNames() { - var counts = {}; + // 第一步:同 projectPath 的重复实例是异常态(spawn 竞态留下的双实例),不能进普通撞名改名—— + // 否则后缀同为路径末段,两个实例都被改名(forest-forest / forest-forest-2), + // 没人保住 base 名 → `__*` 前缀路由全部失配(2026-06-11 forest 96802/26426 双实例事故)。 + // 规则:组内 startedAt 最新的实例代表该项目参与命名,其余改名 `-dup`,仍可显式触达。 + var byPath = {}; for (var ed of editors.values()) { - counts[ed.baseShortName] = (counts[ed.baseShortName] || 0) + 1; + var k = ed.projectPath || ed.url; + (byPath[k] || (byPath[k] = [])).push(ed); } + var reps = []; + Object.keys(byPath).forEach(function (k) { + var group = byPath[k].sort(function (a, b) { return (b.startedAt || 0) - (a.startedAt || 0); }); + reps.push(group[0]); + for (var i = 1; i < group.length; i++) { + group[i].shortName = group[i].baseShortName + '-dup' + (group[i].pid || i); + logErr('duplicate editor on same project', k, 'pid=' + group[i].pid, '→', group[i].shortName); + } + }); + // 第二步:代表之间的撞名(不同项目 projectShortName 相同,如 my-app/client 与 my-app/server + // 都算成 my-app)按 projectPath 末段加后缀区分,单实例保持原名不变。 + var counts = {}; + reps.forEach(function (ed) { counts[ed.baseShortName] = (counts[ed.baseShortName] || 0) + 1; }); var used = {}; - for (var ed2 of editors.values()) { + reps.forEach(function (ed2) { if (counts[ed2.baseShortName] > 1) { var suffix = sanitizeShortName(path.basename(ed2.projectPath || 'unknown')); var name = ed2.baseShortName + '-' + suffix; @@ -229,7 +248,7 @@ function dedupeShortNames() { } else { ed2.shortName = ed2.baseShortName; } - } + }); } function buildAggregatedToolList() {