dual-brain 0.3.33 → 0.3.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dual-brain.mjs +140 -31
- package/package.json +1 -1
package/bin/dual-brain.mjs
CHANGED
|
@@ -1462,21 +1462,16 @@ async function cmdRuntimeSwitch(args = []) {
|
|
|
1462
1462
|
const cwd = process.cwd();
|
|
1463
1463
|
let sessions = [];
|
|
1464
1464
|
try { sessions = enrichSessions(importReplitSessions(cwd), cwd); } catch {}
|
|
1465
|
-
const
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
id: active.sessionId,
|
|
1469
|
-
tool: active.provider,
|
|
1470
|
-
smartName: active.sessionName || active.conversationId || active.sessionId,
|
|
1471
|
-
})
|
|
1472
|
-
: sessions[0];
|
|
1465
|
+
const resolved = resolveCurrentConversationSession(cwd, sessions);
|
|
1466
|
+
const active = resolved.active;
|
|
1467
|
+
const sess = resolved.session;
|
|
1473
1468
|
if (!sess) {
|
|
1474
|
-
console.log('No
|
|
1469
|
+
console.log('No current logical conversation found. Resume the intended chat through dual-brain first.');
|
|
1475
1470
|
return;
|
|
1476
1471
|
}
|
|
1477
1472
|
|
|
1478
1473
|
const profile = loadProfile(cwd);
|
|
1479
|
-
const settingsTerminalId =
|
|
1474
|
+
const settingsTerminalId = resolved.terminalId || getTerminalId();
|
|
1480
1475
|
const settings = loadSessionSettings(cwd, settingsTerminalId);
|
|
1481
1476
|
let provider = _sessionTool(sess);
|
|
1482
1477
|
let reason = 'head-runtime-switch';
|
|
@@ -1522,6 +1517,11 @@ async function cmdRuntimeSwitch(args = []) {
|
|
|
1522
1517
|
settings.headModel = existingPending.model;
|
|
1523
1518
|
if (!explicitEffort && existingPending.effort) settings.effort = existingPending.effort;
|
|
1524
1519
|
}
|
|
1520
|
+
const runningHead = inferRunningHeadConfig(cwd, sess.id, provider);
|
|
1521
|
+
if (!explicitModel && !explicitLevel && _modelMatchesProvider(runningHead.model, provider)) {
|
|
1522
|
+
settings.headModel = runningHead.model;
|
|
1523
|
+
if (!explicitEffort && runningHead.effort) settings.effort = runningHead.effort;
|
|
1524
|
+
}
|
|
1525
1525
|
|
|
1526
1526
|
const headPolicy = _headPolicyFor(provider, profile, settings);
|
|
1527
1527
|
if (!settings.headModel || !_modelMatchesProvider(settings.headModel, provider)) {
|
|
@@ -1549,7 +1549,7 @@ async function cmdRuntimeSwitch(args = []) {
|
|
|
1549
1549
|
});
|
|
1550
1550
|
|
|
1551
1551
|
const launchArgs = provider === _sessionTool(sess)
|
|
1552
|
-
?
|
|
1552
|
+
? runtimeLaunchArgsForPending(sess, cwd, pending)
|
|
1553
1553
|
: ['handoff', '--to', provider];
|
|
1554
1554
|
|
|
1555
1555
|
console.log('');
|
|
@@ -1598,16 +1598,10 @@ async function cmdSwitchover(args = []) {
|
|
|
1598
1598
|
const cwd = process.cwd();
|
|
1599
1599
|
let sessions = [];
|
|
1600
1600
|
try { sessions = enrichSessions(importReplitSessions(cwd), cwd); } catch {}
|
|
1601
|
-
const
|
|
1602
|
-
const sess =
|
|
1603
|
-
? (sessions.find(s => s.id === active.sessionId) || {
|
|
1604
|
-
id: active.sessionId,
|
|
1605
|
-
tool: active.provider,
|
|
1606
|
-
smartName: active.sessionName || active.conversationId || active.sessionId,
|
|
1607
|
-
})
|
|
1608
|
-
: sessions[0];
|
|
1601
|
+
const resolved = resolveCurrentConversationSession(cwd, sessions);
|
|
1602
|
+
const sess = resolved.session;
|
|
1609
1603
|
if (!sess) {
|
|
1610
|
-
console.log('No
|
|
1604
|
+
console.log('No current logical conversation found. Resume the intended chat through dual-brain first.');
|
|
1611
1605
|
return;
|
|
1612
1606
|
}
|
|
1613
1607
|
|
|
@@ -2428,6 +2422,87 @@ function loadTerminalState(cwd, terminalId) {
|
|
|
2428
2422
|
} catch { return null; }
|
|
2429
2423
|
}
|
|
2430
2424
|
|
|
2425
|
+
function loadLatestTerminalState(cwd) {
|
|
2426
|
+
try {
|
|
2427
|
+
const dir = join(cwd, '.dualbrain');
|
|
2428
|
+
const files = readdirSync(dir)
|
|
2429
|
+
.filter(f => /^terminal-.*\.json$/.test(f))
|
|
2430
|
+
.map(f => {
|
|
2431
|
+
const full = join(dir, f);
|
|
2432
|
+
const state = JSON.parse(readFileSync(full, 'utf8'));
|
|
2433
|
+
return { state, mtime: statSync(full).mtimeMs };
|
|
2434
|
+
})
|
|
2435
|
+
.filter(x => x.state?.sessionId)
|
|
2436
|
+
.sort((a, b) => (b.state.timestamp || b.mtime || 0) - (a.state.timestamp || a.mtime || 0));
|
|
2437
|
+
return files[0]?.state || null;
|
|
2438
|
+
} catch { return null; }
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
function sessionFromState(state, sessions = []) {
|
|
2442
|
+
if (!state?.sessionId) return null;
|
|
2443
|
+
return sessions.find(s => s.id === state.sessionId) || {
|
|
2444
|
+
id: state.sessionId,
|
|
2445
|
+
tool: state.tool || state.provider || 'claude',
|
|
2446
|
+
smartName: state.sessionName || state.conversationId || state.sessionId,
|
|
2447
|
+
};
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
function resolveCurrentConversationSession(cwd, sessions = [], { allowLatestTerminal = true } = {}) {
|
|
2451
|
+
const terminalId = getTerminalId();
|
|
2452
|
+
const active = readActiveConversation(cwd);
|
|
2453
|
+
if (active?.terminalId === terminalId) {
|
|
2454
|
+
return {
|
|
2455
|
+
session: sessionFromState({
|
|
2456
|
+
sessionId: active.sessionId,
|
|
2457
|
+
tool: active.provider,
|
|
2458
|
+
sessionName: active.sessionName,
|
|
2459
|
+
conversationId: active.conversationId,
|
|
2460
|
+
}, sessions),
|
|
2461
|
+
terminalId,
|
|
2462
|
+
source: 'active-terminal',
|
|
2463
|
+
active,
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
const terminalState = loadTerminalState(cwd, terminalId);
|
|
2468
|
+
if (terminalState?.sessionId) {
|
|
2469
|
+
return {
|
|
2470
|
+
session: sessionFromState(terminalState, sessions),
|
|
2471
|
+
terminalId,
|
|
2472
|
+
source: 'terminal-state',
|
|
2473
|
+
active: null,
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
if (allowLatestTerminal) {
|
|
2478
|
+
const latestState = loadLatestTerminalState(cwd);
|
|
2479
|
+
if (latestState?.sessionId) {
|
|
2480
|
+
return {
|
|
2481
|
+
session: sessionFromState(latestState, sessions),
|
|
2482
|
+
terminalId: latestState.terminalId || null,
|
|
2483
|
+
source: 'latest-terminal-state',
|
|
2484
|
+
active: null,
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
if (active?.sessionId) {
|
|
2490
|
+
return {
|
|
2491
|
+
session: sessionFromState({
|
|
2492
|
+
sessionId: active.sessionId,
|
|
2493
|
+
tool: active.provider,
|
|
2494
|
+
sessionName: active.sessionName,
|
|
2495
|
+
conversationId: active.conversationId,
|
|
2496
|
+
}, sessions),
|
|
2497
|
+
terminalId: active.terminalId || null,
|
|
2498
|
+
source: 'active-other-terminal',
|
|
2499
|
+
active,
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
return { session: null, terminalId, source: 'none', active: null };
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2431
2506
|
function sessionSettingsPath(cwd, terminalId = getTerminalId()) {
|
|
2432
2507
|
return join(cwd, '.dualbrain', `session-settings-${terminalId}.json`);
|
|
2433
2508
|
}
|
|
@@ -2597,15 +2672,7 @@ function parseProviderSwitchCommand(input) {
|
|
|
2597
2672
|
}
|
|
2598
2673
|
|
|
2599
2674
|
function getActionSession(cwd, recentSessions = []) {
|
|
2600
|
-
|
|
2601
|
-
if (active) {
|
|
2602
|
-
return recentSessions.find(s => s.id === active.sessionId) || {
|
|
2603
|
-
id: active.sessionId,
|
|
2604
|
-
tool: active.provider,
|
|
2605
|
-
smartName: active.sessionName || active.conversationId || active.sessionId,
|
|
2606
|
-
};
|
|
2607
|
-
}
|
|
2608
|
-
return recentSessions[0] || null;
|
|
2675
|
+
return resolveCurrentConversationSession(cwd, recentSessions).session;
|
|
2609
2676
|
}
|
|
2610
2677
|
|
|
2611
2678
|
async function switchProviderNow(cwd, session, target = null) {
|
|
@@ -2781,13 +2848,46 @@ function readActiveConversation(cwd) {
|
|
|
2781
2848
|
} catch { return null; }
|
|
2782
2849
|
}
|
|
2783
2850
|
|
|
2851
|
+
function inferRunningHeadConfig(cwd, sessionId, provider) {
|
|
2852
|
+
const active = readActiveConversation(cwd);
|
|
2853
|
+
if (!active?.childPid || (sessionId && active.sessionId !== sessionId)) return {};
|
|
2854
|
+
const fallback = {
|
|
2855
|
+
provider: active.provider || provider,
|
|
2856
|
+
model: _modelMatchesProvider(active.model, provider) ? active.model : null,
|
|
2857
|
+
effort: active.effort || null,
|
|
2858
|
+
};
|
|
2859
|
+
try {
|
|
2860
|
+
const args = execSync(`ps -o args= -p ${Number(active.childPid)}`, {
|
|
2861
|
+
cwd,
|
|
2862
|
+
encoding: 'utf8',
|
|
2863
|
+
timeout: 1000,
|
|
2864
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
2865
|
+
}).trim();
|
|
2866
|
+
if (!args) return {};
|
|
2867
|
+
const parts = args.match(/(?:[^\s"]+|"[^"]*")+/g)?.map(s => s.replace(/^"|"$/g, '')) || [];
|
|
2868
|
+
const modelIdx = parts.indexOf('--model');
|
|
2869
|
+
const effortIdx = parts.indexOf('--effort');
|
|
2870
|
+
const model = modelIdx !== -1 ? parts[modelIdx + 1] : null;
|
|
2871
|
+
const effort = effortIdx !== -1 ? parts[effortIdx + 1] : null;
|
|
2872
|
+
return {
|
|
2873
|
+
provider: active.provider || provider,
|
|
2874
|
+
model: _modelMatchesProvider(model, provider) ? model : fallback.model,
|
|
2875
|
+
effort: effort || fallback.effort || null,
|
|
2876
|
+
};
|
|
2877
|
+
} catch { return fallback; }
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2784
2880
|
async function processPendingRuntimeSwitch(cwd) {
|
|
2785
2881
|
const pending = readPendingRuntimeSwitch(cwd);
|
|
2786
2882
|
if (!pending || pending.status !== 'confirmed' || pending.autoLaunch === false) return false;
|
|
2787
2883
|
|
|
2788
2884
|
let sessions = [];
|
|
2789
2885
|
try { sessions = enrichSessions(importReplitSessions(cwd), cwd); } catch {}
|
|
2790
|
-
const sess = sessions.find(s => s.id === pending.sessionId) ||
|
|
2886
|
+
const sess = sessions.find(s => s.id === pending.sessionId) || {
|
|
2887
|
+
id: pending.sessionId,
|
|
2888
|
+
tool: pending.fromProvider || pending.provider || 'claude',
|
|
2889
|
+
smartName: pending.sessionName || pending.sessionId,
|
|
2890
|
+
};
|
|
2791
2891
|
if (!sess) {
|
|
2792
2892
|
clearPendingRuntimeSwitch(cwd);
|
|
2793
2893
|
return false;
|
|
@@ -2811,7 +2911,12 @@ async function processPendingRuntimeSwitch(cwd) {
|
|
|
2811
2911
|
|
|
2812
2912
|
const launchArgs = runtimeLaunchArgsForPending(sess, cwd, pending);
|
|
2813
2913
|
process.stdout.write(` Launching: ${targetTool} ${launchArgs.join(' ')}\n\n`);
|
|
2814
|
-
writeActiveConversation(cwd, sess, targetTool
|
|
2914
|
+
writeActiveConversation(cwd, sess, targetTool, {
|
|
2915
|
+
model: pending.model || null,
|
|
2916
|
+
effort: pending.effort || null,
|
|
2917
|
+
automode: pending.automode === true,
|
|
2918
|
+
bypassPermissions: pending.bypassPermissions === true,
|
|
2919
|
+
});
|
|
2815
2920
|
try {
|
|
2816
2921
|
await launchSupervisedHead(targetTool, launchArgs, cwd, sess);
|
|
2817
2922
|
} finally {
|
|
@@ -2831,6 +2936,10 @@ function writeActiveConversation(cwd, session, tool, extra = {}) {
|
|
|
2831
2936
|
terminalId,
|
|
2832
2937
|
ownerPid: process.pid,
|
|
2833
2938
|
childPid: extra.childPid || null,
|
|
2939
|
+
model: extra.model || null,
|
|
2940
|
+
effort: extra.effort || null,
|
|
2941
|
+
automode: extra.automode === true,
|
|
2942
|
+
bypassPermissions: extra.bypassPermissions === true,
|
|
2834
2943
|
startedAt: new Date().toISOString(),
|
|
2835
2944
|
lastHeartbeat: new Date().toISOString(),
|
|
2836
2945
|
mode: 'active-head',
|
package/package.json
CHANGED