dual-brain 0.3.28 → 0.3.30
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 +122 -9
- package/package.json +1 -1
package/bin/dual-brain.mjs
CHANGED
|
@@ -331,6 +331,38 @@ async function checkForUpdates(currentVersion) {
|
|
|
331
331
|
} catch {}
|
|
332
332
|
return null;
|
|
333
333
|
}
|
|
334
|
+
|
|
335
|
+
async function maybeAutoUpdateAndRelaunch(args, { cmd, isInteractive } = {}) {
|
|
336
|
+
if (!isInteractive) return false;
|
|
337
|
+
if (process.env.DUAL_BRAIN_NO_AUTO_UPDATE === '1') return false;
|
|
338
|
+
if (process.env.DUAL_BRAIN_AUTO_UPDATED === '1') return false;
|
|
339
|
+
const skip = new Set(['update', 'upgrade', 'install', 'uninstall', 'shell-hook', '--version', '-v', '--help', '-h']);
|
|
340
|
+
if (skip.has(cmd)) return false;
|
|
341
|
+
|
|
342
|
+
const current = readVersion();
|
|
343
|
+
const latest = await checkForUpdates(current);
|
|
344
|
+
if (!latest) return false;
|
|
345
|
+
|
|
346
|
+
const { spawnSync } = await import('node:child_process');
|
|
347
|
+
process.stdout.write(`\n Updating dual-brain ${current} -> ${latest}...\n`);
|
|
348
|
+
const install = spawnSync('npm', ['install', '-g', `dual-brain@${latest}`], {
|
|
349
|
+
stdio: 'inherit',
|
|
350
|
+
cwd: process.cwd(),
|
|
351
|
+
});
|
|
352
|
+
if (install.status !== 0) {
|
|
353
|
+
process.stdout.write(` Update failed; continuing with ${current}.\n\n`);
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
process.stdout.write(` Updated to ${latest}. Restarting dual-brain...\n\n`);
|
|
358
|
+
const relaunched = spawnSync('dual-brain', args, {
|
|
359
|
+
stdio: 'inherit',
|
|
360
|
+
cwd: process.cwd(),
|
|
361
|
+
env: { ...process.env, DUAL_BRAIN_AUTO_UPDATED: '1' },
|
|
362
|
+
});
|
|
363
|
+
process.exit(relaunched.status ?? 0);
|
|
364
|
+
}
|
|
365
|
+
|
|
334
366
|
function flag(args, name) { const i = args.indexOf(name); return i !== -1 ? (args[i + 1] ?? true) : null; }
|
|
335
367
|
function err(msg) { process.stderr.write(`Error: ${msg}\n`); process.exit(1); }
|
|
336
368
|
function vtrace(msg) { process.stderr.write(`[verbose] ${msg}\n`); }
|
|
@@ -1532,12 +1564,38 @@ async function cmdRuntimeSwitch(args = []) {
|
|
|
1532
1564
|
}
|
|
1533
1565
|
|
|
1534
1566
|
async function cmdAuto(args = []) {
|
|
1535
|
-
const switchArgs = ['--automode', '--confirm'];
|
|
1567
|
+
const switchArgs = ['--automode', '--confirm', '--apply'];
|
|
1536
1568
|
const levelIdx = args.findIndex(a => a === '--level' || a === '--intelligence' || a === '--intelligence-level');
|
|
1537
1569
|
if (levelIdx !== -1 && args[levelIdx + 1]) switchArgs.push('--level', args[levelIdx + 1]);
|
|
1538
1570
|
await cmdRuntimeSwitch(switchArgs);
|
|
1539
1571
|
}
|
|
1540
1572
|
|
|
1573
|
+
async function cmdSwitchover(args = []) {
|
|
1574
|
+
const cwd = process.cwd();
|
|
1575
|
+
let sessions = [];
|
|
1576
|
+
try { sessions = enrichSessions(importReplitSessions(cwd), cwd); } catch {}
|
|
1577
|
+
const active = readActiveConversation(cwd);
|
|
1578
|
+
const sess = active
|
|
1579
|
+
? (sessions.find(s => s.id === active.sessionId) || {
|
|
1580
|
+
id: active.sessionId,
|
|
1581
|
+
tool: active.provider,
|
|
1582
|
+
smartName: active.sessionName || active.conversationId || active.sessionId,
|
|
1583
|
+
})
|
|
1584
|
+
: sessions[0];
|
|
1585
|
+
if (!sess) {
|
|
1586
|
+
console.log('No resumable session found.');
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
let target = null;
|
|
1591
|
+
const joined = args.join(' ').toLowerCase();
|
|
1592
|
+
if (/\b(gpt|codex|openai)\b/.test(joined)) target = 'codex';
|
|
1593
|
+
if (/\bclaude\b/.test(joined)) target = 'claude';
|
|
1594
|
+
target ||= _sessionTool(sess) === 'codex' ? 'claude' : 'codex';
|
|
1595
|
+
|
|
1596
|
+
await cmdRuntimeSwitch(['--to', target, '--automode', '--confirm', '--apply', '--reason', 'head-switchover']);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1541
1599
|
async function cmdUpdate() {
|
|
1542
1600
|
const cwd = process.cwd();
|
|
1543
1601
|
console.log(' Updating Dual Brain...');
|
|
@@ -2501,6 +2559,44 @@ function applyIntelligenceCommand(cmd, cwd) {
|
|
|
2501
2559
|
return { scope: 'this session', level: cmd.level };
|
|
2502
2560
|
}
|
|
2503
2561
|
|
|
2562
|
+
function parseProviderSwitchCommand(input) {
|
|
2563
|
+
const text = input.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
2564
|
+
if (/\b(switch over|switchover|switch)\b.*\b(auto|automode|auto mode|smart auto)\b/.test(text)) {
|
|
2565
|
+
return { provider: null, automode: true };
|
|
2566
|
+
}
|
|
2567
|
+
if (!/\b(switchover|switch over|switch provider|other provider|continue in (gpt|codex|claude|other provider)|switch (to|into) (gpt|codex|claude|openai))\b/.test(text)) {
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
let provider = null;
|
|
2571
|
+
if (/\b(gpt|codex|openai)\b/.test(text)) provider = 'codex';
|
|
2572
|
+
if (/\bclaude\b/.test(text)) provider = 'claude';
|
|
2573
|
+
return { provider };
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
function getActionSession(cwd, recentSessions = []) {
|
|
2577
|
+
const active = readActiveConversation(cwd);
|
|
2578
|
+
if (active) {
|
|
2579
|
+
return recentSessions.find(s => s.id === active.sessionId) || {
|
|
2580
|
+
id: active.sessionId,
|
|
2581
|
+
tool: active.provider,
|
|
2582
|
+
smartName: active.sessionName || active.conversationId || active.sessionId,
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
return recentSessions[0] || null;
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
async function switchProviderNow(cwd, session, target = null) {
|
|
2589
|
+
if (!session) return false;
|
|
2590
|
+
const currentTool = _sessionTool(session);
|
|
2591
|
+
const targetTool = target || (currentTool === 'codex' ? 'claude' : 'codex');
|
|
2592
|
+
const brief = _sessionBrief(session, targetTool);
|
|
2593
|
+
setSessionResumeProvider(cwd, session, targetTool, 'head-provider-switch');
|
|
2594
|
+
writeHandoffConversationLease(cwd, session, currentTool, targetTool, brief);
|
|
2595
|
+
markSessionSuperseded(cwd, session, targetTool, 'head-provider-switch');
|
|
2596
|
+
await cmdSwitch([targetTool, brief]);
|
|
2597
|
+
return true;
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2504
2600
|
async function offerRuntimeReloadAfterModeChange(cwd, ask, recentSessions, label = 'runtime settings') {
|
|
2505
2601
|
const cyan = '\x1b[36m';
|
|
2506
2602
|
const reset = '\x1b[0m';
|
|
@@ -3330,6 +3426,11 @@ function classifyInput(input) {
|
|
|
3330
3426
|
return { tier: 'free', command: 'intelligence', args: [], intelligenceCommand };
|
|
3331
3427
|
}
|
|
3332
3428
|
|
|
3429
|
+
const providerSwitchCommand = parseProviderSwitchCommand(trimmed);
|
|
3430
|
+
if (providerSwitchCommand) {
|
|
3431
|
+
return { tier: 'free', command: 'provider-switch', args: [], providerSwitchCommand };
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3333
3434
|
// Tier 0: SKILL — slash commands (checked first, deterministic)
|
|
3334
3435
|
if (trimmed.startsWith('/')) {
|
|
3335
3436
|
try {
|
|
@@ -4224,6 +4325,19 @@ async function mainScreen(rl, ask) {
|
|
|
4224
4325
|
return await offerRuntimeReloadAfterModeChange(cwd, ask, recentSessions, `Intelligence ${result.level}`);
|
|
4225
4326
|
}
|
|
4226
4327
|
|
|
4328
|
+
if (cmd === 'provider-switch') {
|
|
4329
|
+
if (classified.providerSwitchCommand.automode) {
|
|
4330
|
+
await cmdAuto(['--level', String(_getIntelligenceLevel(loadProfile(cwd), loadSessionSettings(cwd)))]);
|
|
4331
|
+
return { next: 'main' };
|
|
4332
|
+
}
|
|
4333
|
+
const sess = getActionSession(cwd, recentSessions);
|
|
4334
|
+
if (!sess) return { next: 'new-session' };
|
|
4335
|
+
const target = classified.providerSwitchCommand.provider;
|
|
4336
|
+
process.stdout.write(`\n Switching HEAD to ${target === 'claude' ? 'Claude' : target === 'codex' ? 'Codex/GPT' : 'the other provider'}...\n\n`);
|
|
4337
|
+
await switchProviderNow(cwd, sess, target);
|
|
4338
|
+
return { next: 'main' };
|
|
4339
|
+
}
|
|
4340
|
+
|
|
4227
4341
|
if (cmd === 'resume' || cmd === 'r') {
|
|
4228
4342
|
if (recentSessions.length === 0) return { next: 'new-session' };
|
|
4229
4343
|
return { next: 'sessions' };
|
|
@@ -4548,10 +4662,7 @@ async function switchProviderScreen(rl, ask, ctx = {}) {
|
|
|
4548
4662
|
}
|
|
4549
4663
|
if (choice === 'b' || choice === 'q') return { next: 'main' };
|
|
4550
4664
|
|
|
4551
|
-
|
|
4552
|
-
writeHandoffConversationLease(cwd, session, currentTool, target, brief);
|
|
4553
|
-
markSessionSuperseded(cwd, session, target, 'manual-provider-switch');
|
|
4554
|
-
await cmdSwitch([target, brief]);
|
|
4665
|
+
await switchProviderNow(cwd, session, target);
|
|
4555
4666
|
return { next: 'main' };
|
|
4556
4667
|
}
|
|
4557
4668
|
|
|
@@ -7756,6 +7867,9 @@ async function main() {
|
|
|
7756
7867
|
|
|
7757
7868
|
const args = process.argv.slice(2);
|
|
7758
7869
|
const cmd = args[0];
|
|
7870
|
+
const isInteractive = process.stdin.isTTY;
|
|
7871
|
+
|
|
7872
|
+
await maybeAutoUpdateAndRelaunch(args, { cmd, isInteractive });
|
|
7759
7873
|
|
|
7760
7874
|
// Session start marker — feeds routing advisor with cross-session timing signals
|
|
7761
7875
|
try {
|
|
@@ -7766,9 +7880,6 @@ async function main() {
|
|
|
7766
7880
|
if (cmd === '--help' || cmd === '-h') { printHelp(); return; }
|
|
7767
7881
|
if (cmd === '--version' || cmd === '-v') { console.log(readVersion()); return; }
|
|
7768
7882
|
|
|
7769
|
-
// Interactive-only commands: enter screen state machine (only when TTY)
|
|
7770
|
-
const isInteractive = process.stdin.isTTY;
|
|
7771
|
-
|
|
7772
7883
|
if (cmd === 'menu') {
|
|
7773
7884
|
if (!isInteractive) {
|
|
7774
7885
|
process.stderr.write('dual-brain menu requires an interactive terminal.\n');
|
|
@@ -8023,9 +8134,11 @@ async function main() {
|
|
|
8023
8134
|
if (cmd === 'pr') { await cmdPR(args.slice(1)); return; }
|
|
8024
8135
|
if (cmd === 'status') { await cmdStatus(args.slice(1)); return; }
|
|
8025
8136
|
if (cmd === 'handoff') { await cmdHandoff(args.slice(1)); return; }
|
|
8137
|
+
if (cmd === 'switch' && /\b(auto|automode|auto mode|smart auto)\b/i.test(args.join(' '))) { await cmdAuto(args.slice(1)); return; }
|
|
8026
8138
|
if (cmd === 'switch') { await cmdSwitch(args.slice(1)); return; }
|
|
8027
8139
|
if (cmd === 'runtime-switch') { await cmdRuntimeSwitch(args.slice(1)); return; }
|
|
8028
8140
|
if (cmd === 'auto' || cmd === 'automode' || cmd === 'smart-auto') { await cmdAuto(args.slice(1)); return; }
|
|
8141
|
+
if (cmd === 'switchover' || cmd === 'switch-over') { await cmdSwitchover(args.slice(1)); return; }
|
|
8029
8142
|
if (cmd === 'update' || cmd === 'upgrade') { await cmdUpdate(); return; }
|
|
8030
8143
|
if (cmd === 'hot') { cmdHot(args[1]); return; }
|
|
8031
8144
|
if (cmd === 'cool') { cmdCool(args[1]); return; }
|
|
@@ -8090,7 +8203,7 @@ fi
|
|
|
8090
8203
|
// If cmd is not a recognized subcommand, treat the entire arg list as a task.
|
|
8091
8204
|
// e.g. `dual-brain fix failing tests` → same as `dual-brain go "fix failing tests"`
|
|
8092
8205
|
const KNOWN_COMMANDS = new Set([
|
|
8093
|
-
'menu', 'init', 'install', 'uninstall', 'auth', 'go', 'do', 'plan', 'ship', 'think', 'review', 'pr', 'status', 'handoff', 'switch', 'runtime-switch', 'auto', 'automode', 'smart-auto', 'hot', 'cool',
|
|
8206
|
+
'menu', 'init', 'install', 'uninstall', 'auth', 'go', 'do', 'plan', 'ship', 'think', 'review', 'pr', 'status', 'handoff', 'switch', 'switchover', 'switch-over', 'runtime-switch', 'auto', 'automode', 'smart-auto', 'hot', 'cool',
|
|
8094
8207
|
'remember', 'forget', 'break-glass', 'specialists', 'search', 'shell-hook', 'watch', 'update', 'upgrade',
|
|
8095
8208
|
'--help', '-h', '--version', '-v',
|
|
8096
8209
|
...Object.keys(loadSpecialistRegistry()),
|
package/package.json
CHANGED