dual-brain 0.3.28 → 0.3.29
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 +85 -7
- 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`); }
|
|
@@ -2501,6 +2533,41 @@ function applyIntelligenceCommand(cmd, cwd) {
|
|
|
2501
2533
|
return { scope: 'this session', level: cmd.level };
|
|
2502
2534
|
}
|
|
2503
2535
|
|
|
2536
|
+
function parseProviderSwitchCommand(input) {
|
|
2537
|
+
const text = input.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
2538
|
+
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)) {
|
|
2539
|
+
return null;
|
|
2540
|
+
}
|
|
2541
|
+
let provider = null;
|
|
2542
|
+
if (/\b(gpt|codex|openai)\b/.test(text)) provider = 'codex';
|
|
2543
|
+
if (/\bclaude\b/.test(text)) provider = 'claude';
|
|
2544
|
+
return { provider };
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
function getActionSession(cwd, recentSessions = []) {
|
|
2548
|
+
const active = readActiveConversation(cwd);
|
|
2549
|
+
if (active) {
|
|
2550
|
+
return recentSessions.find(s => s.id === active.sessionId) || {
|
|
2551
|
+
id: active.sessionId,
|
|
2552
|
+
tool: active.provider,
|
|
2553
|
+
smartName: active.sessionName || active.conversationId || active.sessionId,
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
return recentSessions[0] || null;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
async function switchProviderNow(cwd, session, target = null) {
|
|
2560
|
+
if (!session) return false;
|
|
2561
|
+
const currentTool = _sessionTool(session);
|
|
2562
|
+
const targetTool = target || (currentTool === 'codex' ? 'claude' : 'codex');
|
|
2563
|
+
const brief = _sessionBrief(session, targetTool);
|
|
2564
|
+
setSessionResumeProvider(cwd, session, targetTool, 'head-provider-switch');
|
|
2565
|
+
writeHandoffConversationLease(cwd, session, currentTool, targetTool, brief);
|
|
2566
|
+
markSessionSuperseded(cwd, session, targetTool, 'head-provider-switch');
|
|
2567
|
+
await cmdSwitch([targetTool, brief]);
|
|
2568
|
+
return true;
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2504
2571
|
async function offerRuntimeReloadAfterModeChange(cwd, ask, recentSessions, label = 'runtime settings') {
|
|
2505
2572
|
const cyan = '\x1b[36m';
|
|
2506
2573
|
const reset = '\x1b[0m';
|
|
@@ -3330,6 +3397,11 @@ function classifyInput(input) {
|
|
|
3330
3397
|
return { tier: 'free', command: 'intelligence', args: [], intelligenceCommand };
|
|
3331
3398
|
}
|
|
3332
3399
|
|
|
3400
|
+
const providerSwitchCommand = parseProviderSwitchCommand(trimmed);
|
|
3401
|
+
if (providerSwitchCommand) {
|
|
3402
|
+
return { tier: 'free', command: 'provider-switch', args: [], providerSwitchCommand };
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3333
3405
|
// Tier 0: SKILL — slash commands (checked first, deterministic)
|
|
3334
3406
|
if (trimmed.startsWith('/')) {
|
|
3335
3407
|
try {
|
|
@@ -4224,6 +4296,15 @@ async function mainScreen(rl, ask) {
|
|
|
4224
4296
|
return await offerRuntimeReloadAfterModeChange(cwd, ask, recentSessions, `Intelligence ${result.level}`);
|
|
4225
4297
|
}
|
|
4226
4298
|
|
|
4299
|
+
if (cmd === 'provider-switch') {
|
|
4300
|
+
const sess = getActionSession(cwd, recentSessions);
|
|
4301
|
+
if (!sess) return { next: 'new-session' };
|
|
4302
|
+
const target = classified.providerSwitchCommand.provider;
|
|
4303
|
+
process.stdout.write(`\n Switching HEAD to ${target === 'claude' ? 'Claude' : target === 'codex' ? 'Codex/GPT' : 'the other provider'}...\n\n`);
|
|
4304
|
+
await switchProviderNow(cwd, sess, target);
|
|
4305
|
+
return { next: 'main' };
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4227
4308
|
if (cmd === 'resume' || cmd === 'r') {
|
|
4228
4309
|
if (recentSessions.length === 0) return { next: 'new-session' };
|
|
4229
4310
|
return { next: 'sessions' };
|
|
@@ -4548,10 +4629,7 @@ async function switchProviderScreen(rl, ask, ctx = {}) {
|
|
|
4548
4629
|
}
|
|
4549
4630
|
if (choice === 'b' || choice === 'q') return { next: 'main' };
|
|
4550
4631
|
|
|
4551
|
-
|
|
4552
|
-
writeHandoffConversationLease(cwd, session, currentTool, target, brief);
|
|
4553
|
-
markSessionSuperseded(cwd, session, target, 'manual-provider-switch');
|
|
4554
|
-
await cmdSwitch([target, brief]);
|
|
4632
|
+
await switchProviderNow(cwd, session, target);
|
|
4555
4633
|
return { next: 'main' };
|
|
4556
4634
|
}
|
|
4557
4635
|
|
|
@@ -7756,6 +7834,9 @@ async function main() {
|
|
|
7756
7834
|
|
|
7757
7835
|
const args = process.argv.slice(2);
|
|
7758
7836
|
const cmd = args[0];
|
|
7837
|
+
const isInteractive = process.stdin.isTTY;
|
|
7838
|
+
|
|
7839
|
+
await maybeAutoUpdateAndRelaunch(args, { cmd, isInteractive });
|
|
7759
7840
|
|
|
7760
7841
|
// Session start marker — feeds routing advisor with cross-session timing signals
|
|
7761
7842
|
try {
|
|
@@ -7766,9 +7847,6 @@ async function main() {
|
|
|
7766
7847
|
if (cmd === '--help' || cmd === '-h') { printHelp(); return; }
|
|
7767
7848
|
if (cmd === '--version' || cmd === '-v') { console.log(readVersion()); return; }
|
|
7768
7849
|
|
|
7769
|
-
// Interactive-only commands: enter screen state machine (only when TTY)
|
|
7770
|
-
const isInteractive = process.stdin.isTTY;
|
|
7771
|
-
|
|
7772
7850
|
if (cmd === 'menu') {
|
|
7773
7851
|
if (!isInteractive) {
|
|
7774
7852
|
process.stderr.write('dual-brain menu requires an interactive terminal.\n');
|
package/package.json
CHANGED