dual-brain 0.3.22 → 0.3.24

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.
Files changed (2) hide show
  1. package/bin/dual-brain.mjs +222 -66
  2. package/package.json +1 -1
@@ -26,21 +26,18 @@ import { detectTask, primeAgentRegistry } from '../dist/src/detect.js';
26
26
 
27
27
  function _claudeResumeArgs(sessionId, cwd) {
28
28
  const args = ['--resume', sessionId];
29
- const prof = loadProfile(cwd || process.cwd());
30
- if (prof.bypassPermissions) args.push('--dangerously-skip-permissions');
29
+ if (getEffectiveBypassPermissions(cwd || process.cwd())) args.push('--dangerously-skip-permissions');
31
30
  return args;
32
31
  }
33
32
 
34
33
  function _claudeNewArgs(cwd) {
35
34
  const args = [];
36
- const prof = loadProfile(cwd || process.cwd());
37
- if (prof.bypassPermissions) args.push('--dangerously-skip-permissions');
35
+ if (getEffectiveBypassPermissions(cwd || process.cwd())) args.push('--dangerously-skip-permissions');
38
36
  return args;
39
37
  }
40
38
 
41
39
  function _codexResumeArgs(sessionId, cwd) {
42
- const prof = loadProfile(cwd || process.cwd());
43
- if (prof.bypassPermissions) {
40
+ if (getEffectiveBypassPermissions(cwd || process.cwd())) {
44
41
  return ['--dangerously-bypass-approvals-and-sandbox', 'resume', sessionId];
45
42
  }
46
43
  return ['--sandbox', 'workspace-write', '--ask-for-approval', 'on-request', 'resume', sessionId];
@@ -2084,6 +2081,168 @@ function loadTerminalState(cwd, terminalId) {
2084
2081
  } catch { return null; }
2085
2082
  }
2086
2083
 
2084
+ function sessionSettingsPath(cwd, terminalId = getTerminalId()) {
2085
+ return join(cwd, '.dualbrain', `session-settings-${terminalId}.json`);
2086
+ }
2087
+
2088
+ function loadSessionSettings(cwd, terminalId = getTerminalId()) {
2089
+ try {
2090
+ return JSON.parse(readFileSync(sessionSettingsPath(cwd, terminalId), 'utf8'));
2091
+ } catch { return {}; }
2092
+ }
2093
+
2094
+ function saveSessionSettings(cwd, settings, terminalId = getTerminalId()) {
2095
+ const dir = join(cwd, '.dualbrain');
2096
+ mkdirSync(dir, { recursive: true });
2097
+ writeFileSync(sessionSettingsPath(cwd, terminalId), JSON.stringify({
2098
+ ...settings,
2099
+ terminalId,
2100
+ updatedAt: new Date().toISOString(),
2101
+ }, null, 2) + '\n');
2102
+ }
2103
+
2104
+ function getEffectiveAutomode(profile, cwd) {
2105
+ const session = loadSessionSettings(cwd || process.cwd());
2106
+ if (typeof session.automode === 'boolean') return session.automode;
2107
+ return profile.automode ?? profile.settings?.automode ?? false;
2108
+ }
2109
+
2110
+ function getEffectiveBypassPermissions(cwd) {
2111
+ const workspace = cwd || process.cwd();
2112
+ const session = loadSessionSettings(workspace);
2113
+ if (typeof session.bypassPermissions === 'boolean') return session.bypassPermissions;
2114
+ const profile = loadProfile(workspace);
2115
+ return !!profile.bypassPermissions;
2116
+ }
2117
+
2118
+ function parseModeCommand(input) {
2119
+ const text = input.trim().toLowerCase().replace(/\s+/g, ' ');
2120
+ const wantsSession = /\b(this|current)\s+(session|conversation|terminal|chat)\b|\bfor this\b|\bfor current\b/.test(text);
2121
+ const wantsGlobal = /\b(default|global|profile|always|future sessions|all sessions)\b/.test(text);
2122
+ const scope = wantsGlobal && !wantsSession ? 'profile' : 'session';
2123
+
2124
+ let key = null;
2125
+ if (/\b(auto|automode|auto mode)\b/.test(text)) key = 'automode';
2126
+ if (/\b(bypass|permission|permissions|approval|approvals|sandbox|safe mode)\b/.test(text)) key = 'bypassPermissions';
2127
+ if (!key) return null;
2128
+
2129
+ let value = null;
2130
+ if (/\b(on|enable|enabled|yes|true)\b/.test(text)) value = true;
2131
+ if (/\b(off|disable|disabled|no|false)\b/.test(text)) value = false;
2132
+ if (key === 'bypassPermissions' && /\b(safe|sandbox|approval|approvals)\b/.test(text) && /\bmode\b/.test(text) && !/\bbypass\b/.test(text)) {
2133
+ value = false;
2134
+ }
2135
+ if (value === null) return null;
2136
+
2137
+ return { key, value, scope };
2138
+ }
2139
+
2140
+ function applyModeCommand(cmd, cwd) {
2141
+ if (cmd.scope === 'profile') {
2142
+ const profile = loadProfile(cwd);
2143
+ if (cmd.key === 'automode') {
2144
+ profile.automode = cmd.value;
2145
+ profile.settings = { ...(profile.settings || {}), automode: cmd.value };
2146
+ } else {
2147
+ profile.bypassPermissions = cmd.value;
2148
+ }
2149
+ saveProfile(profile, { cwd });
2150
+ return { scope: 'default', value: cmd.value };
2151
+ }
2152
+
2153
+ const settings = loadSessionSettings(cwd);
2154
+ settings[cmd.key] = cmd.value;
2155
+ saveSessionSettings(cwd, settings);
2156
+ return { scope: 'this session', value: cmd.value };
2157
+ }
2158
+
2159
+ function pidAlive(pid) {
2160
+ if (!pid || pid === process.pid) return false;
2161
+ try {
2162
+ process.kill(pid, 0);
2163
+ return true;
2164
+ } catch { return false; }
2165
+ }
2166
+
2167
+ function activeConversationPath(cwd) {
2168
+ return join(cwd, '.dualbrain', 'active-conversation.json');
2169
+ }
2170
+
2171
+ function readActiveConversation(cwd) {
2172
+ try {
2173
+ const lease = JSON.parse(readFileSync(activeConversationPath(cwd), 'utf8'));
2174
+ if (!lease?.sessionId) return null;
2175
+ if (!pidAlive(lease.ownerPid)) return null;
2176
+ return lease;
2177
+ } catch { return null; }
2178
+ }
2179
+
2180
+ function writeActiveConversation(cwd, session, tool) {
2181
+ const dir = join(cwd, '.dualbrain');
2182
+ const terminalId = getTerminalId();
2183
+ const lease = {
2184
+ conversationId: session.id,
2185
+ sessionId: session.id,
2186
+ provider: tool,
2187
+ terminalId,
2188
+ ownerPid: process.pid,
2189
+ startedAt: new Date().toISOString(),
2190
+ lastHeartbeat: new Date().toISOString(),
2191
+ mode: 'active-head',
2192
+ };
2193
+ mkdirSync(dir, { recursive: true });
2194
+ writeFileSync(activeConversationPath(cwd), JSON.stringify(lease, null, 2) + '\n');
2195
+ return lease;
2196
+ }
2197
+
2198
+ function clearActiveConversation(cwd, sessionId) {
2199
+ try {
2200
+ const lease = JSON.parse(readFileSync(activeConversationPath(cwd), 'utf8'));
2201
+ if (!sessionId || lease.sessionId === sessionId || lease.ownerPid === process.pid) {
2202
+ unlinkSync(activeConversationPath(cwd));
2203
+ }
2204
+ } catch {}
2205
+ }
2206
+
2207
+ async function confirmConversationTakeover(sess, cwd, ask) {
2208
+ const active = readActiveConversation(cwd);
2209
+ if (!active || active.sessionId !== sess.id || active.ownerPid === process.pid) return 'launch';
2210
+ const label = sess.smartName || sess.name || sess.prompts?.first || sess.firstPrompt || sess.id;
2211
+ process.stdout.write('\n');
2212
+ process.stdout.write(` Session already active: ${String(label).replace(/\s+/g, ' ').slice(0, 80)}\n`);
2213
+ process.stdout.write(` Owner: ${active.terminalId || 'unknown terminal'} · pid ${active.ownerPid} · ${active.provider || 'provider'}\n\n`);
2214
+ process.stdout.write(' Enter back t take over n new session\n\n');
2215
+ if (!ask) return 'cancel';
2216
+ const choice = (await ask(' Choice: ')).trim().toLowerCase();
2217
+ if (choice === 't' || choice === 'takeover' || choice === 'take over') {
2218
+ return 'takeover';
2219
+ }
2220
+ if (choice === 'n') return 'new';
2221
+ return 'cancel';
2222
+ }
2223
+
2224
+ async function launchSessionWithLease(sess, cwd, ask = null) {
2225
+ const decision = await confirmConversationTakeover(sess, cwd, ask);
2226
+ if (decision === 'new') return { next: 'new-session' };
2227
+ if (decision === 'cancel') return { next: 'main' };
2228
+
2229
+ const { spawnSync } = await import('node:child_process');
2230
+ const tool = _sessionTool(sess);
2231
+ const launchArgs = _sessionLaunchArgs(sess, cwd);
2232
+ if (decision === 'takeover') {
2233
+ process.stdout.write(' Taking over active conversation in this terminal.\n');
2234
+ }
2235
+ writeActiveConversation(cwd, sess, tool);
2236
+ process.stdout.write(`\n Launching: ${tool} ${launchArgs.join(' ')}\n\n`);
2237
+ try {
2238
+ spawnSync(tool, launchArgs, { stdio: 'inherit' });
2239
+ } finally {
2240
+ saveTerminalState(cwd, getTerminalId(), sess.id, sess.tool || tool);
2241
+ clearActiveConversation(cwd, sess.id);
2242
+ }
2243
+ return { next: 'main' };
2244
+ }
2245
+
2087
2246
  // ─── PR Detection ─────────────────────────────────────────────────────────────
2088
2247
 
2089
2248
  /**
@@ -2427,6 +2586,11 @@ function classifyInput(input) {
2427
2586
  const cmd = parts[0].toLowerCase();
2428
2587
  const args = parts.slice(1);
2429
2588
 
2589
+ const modeCommand = parseModeCommand(trimmed);
2590
+ if (modeCommand) {
2591
+ return { tier: 'free', command: 'mode', args: [], modeCommand };
2592
+ }
2593
+
2430
2594
  // Tier 0: SKILL — slash commands (checked first, deterministic)
2431
2595
  if (trimmed.startsWith('/')) {
2432
2596
  try {
@@ -2988,7 +3152,11 @@ async function mainScreen(rl, ask) {
2988
3152
  const open = getOpenTasks(cwd);
2989
3153
  if (open.length > 0) {
2990
3154
  const intent = String(open[0].intent || '').replace(/\s+/g, ' ').trim();
2991
- if (intent.length >= 5) openTasks.push(`continue: ${intent.slice(0, 30)}`);
3155
+ const words = intent.split(' ').filter(Boolean);
3156
+ const looksLikeAccidentalKeys = words.length > 0 && words.every(w => w.length === 1);
3157
+ if (intent.length >= 5 && !looksLikeAccidentalKeys) {
3158
+ openTasks.push(`continue: ${intent.slice(0, 30)}`);
3159
+ }
2992
3160
  }
2993
3161
  } catch {}
2994
3162
  suggestions = openTasks.length > 0
@@ -3167,7 +3335,7 @@ async function mainScreen(rl, ask) {
3167
3335
  [`s`, 'settings & profiles'],
3168
3336
  [`d`, 'doctor — diagnose issues'],
3169
3337
  [`t`, 'team settings'],
3170
- [`a`, profile.automode ? 'auto mode (on)' : 'auto mode (off)'],
3338
+ [`a`, getEffectiveAutomode(profile, cwd) ? 'auto mode (on)' : 'auto mode (off)'],
3171
3339
  [`q`, 'quit'],
3172
3340
  ];
3173
3341
  for (const [key, label] of shortcuts) {
@@ -3306,6 +3474,15 @@ async function mainScreen(rl, ask) {
3306
3474
  const cmd = classified.command;
3307
3475
  const args = classified.args;
3308
3476
 
3477
+ if (cmd === 'mode') {
3478
+ const result = applyModeCommand(classified.modeCommand, cwd);
3479
+ const keyLabel = classified.modeCommand.key === 'automode' ? 'Auto mode' : 'Bypass permissions';
3480
+ const state = result.value ? '\x1b[32mON\x1b[0m' : '\x1b[2mOFF\x1b[0m';
3481
+ process.stdout.write(`\n ${keyLabel}: ${state} \x1b[2m(${result.scope})\x1b[0m\n\n`);
3482
+ await ask(' Press Enter to continue...');
3483
+ return { next: 'main' };
3484
+ }
3485
+
3309
3486
  if (cmd === 'resume' || cmd === 'r') {
3310
3487
  if (recentSessions.length === 0) return { next: 'new-session' };
3311
3488
  return { next: 'sessions' };
@@ -3371,11 +3548,7 @@ async function mainScreen(rl, ask) {
3371
3548
  const num = parseInt(pick, 10);
3372
3549
  if (!isNaN(num) && num >= 1 && num <= Math.min(results.length, 9)) {
3373
3550
  const sess = results[num - 1];
3374
- const { spawnSync: sp2 } = await import('node:child_process');
3375
- const tool = sess.tool === 'codex' ? 'codex' : 'claude';
3376
- const launchArgs = tool === 'codex' ? _codexResumeArgs(sess.id, cwd) : _claudeResumeArgs(sess.id, cwd);
3377
- process.stdout.write(`\n Launching: ${tool} ${launchArgs.join(' ')}\n\n`);
3378
- sp2(tool, launchArgs, { stdio: 'inherit' });
3551
+ return await launchSessionWithLease(sess, cwd, ask);
3379
3552
  }
3380
3553
  return { next: 'main' };
3381
3554
  }
@@ -3387,13 +3560,13 @@ async function mainScreen(rl, ask) {
3387
3560
  if (cmd === 'auto') {
3388
3561
  const cwd2 = process.cwd();
3389
3562
  const prof = loadProfile(cwd2);
3390
- const nextAuto = !(prof.automode ?? prof.settings?.automode ?? false);
3391
- prof.automode = nextAuto;
3392
- prof.settings = { ...(prof.settings || {}), automode: nextAuto };
3393
- saveProfile(prof, { cwd: cwd2 });
3394
- const state = prof.automode ? '\x1b[32mON\x1b[0m' : '\x1b[2mOFF\x1b[0m';
3395
- process.stdout.write(`\n Automode: ${state}\n`);
3396
- process.stdout.write(` ${prof.automode ? 'Tasks dispatch immediately (HEAD still gates dangerous ops)' : 'Tasks require Enter to confirm'}\n\n`);
3563
+ const nextAuto = !getEffectiveAutomode(prof, cwd2);
3564
+ const settings = loadSessionSettings(cwd2);
3565
+ settings.automode = nextAuto;
3566
+ saveSessionSettings(cwd2, settings);
3567
+ const state = nextAuto ? '\x1b[32mON\x1b[0m' : '\x1b[2mOFF\x1b[0m';
3568
+ process.stdout.write(`\n Automode: ${state} \x1b[2m(this session)\x1b[0m\n`);
3569
+ process.stdout.write(` ${nextAuto ? 'Tasks dispatch immediately (HEAD still gates dangerous ops)' : 'Tasks require Enter to confirm'}\n\n`);
3397
3570
  await ask(' Press Enter to continue...');
3398
3571
  return { next: 'main' };
3399
3572
  }
@@ -3491,7 +3664,7 @@ async function mainScreen(rl, ask) {
3491
3664
  }
3492
3665
 
3493
3666
  // Automode: if HEAD says it's safe, just go — no confirmation needed
3494
- const automode = profile.automode ?? profile.settings?.automode ?? false;
3667
+ const automode = getEffectiveAutomode(profile, cwd);
3495
3668
  if (automode) {
3496
3669
  process.stdout.write(`\n \x1b[36m⚡\x1b[0m ${summary} (${model}, depth: ${hj?.depth || '?'})\n`);
3497
3670
  return { next: 'go', prompt: input, model, _loopResult: hj._loopResult };
@@ -3518,13 +3691,7 @@ async function mainScreen(rl, ask) {
3518
3691
  return { next: 'new-session' };
3519
3692
  }
3520
3693
  const sess = recentSessions[0];
3521
- const { spawnSync } = await import('node:child_process');
3522
- const tool = _sessionTool(sess);
3523
- const launchArgs = _sessionLaunchArgs(sess, cwd);
3524
- process.stdout.write(`\n Launching: ${tool} ${launchArgs.join(' ')}\n\n`);
3525
- spawnSync(tool, launchArgs, { stdio: 'inherit' });
3526
- saveTerminalState(cwd, getTerminalId(), sess.id, sess.tool || 'claude');
3527
- return { next: 'main' };
3694
+ return await launchSessionWithLease(sess, cwd, ask);
3528
3695
  }
3529
3696
 
3530
3697
  // Number 1-9 → resume that session
@@ -3539,13 +3706,7 @@ async function mainScreen(rl, ask) {
3539
3706
  if (ctx.filesTouched.length > 0) process.stdout.write(` Files touched: ${ctx.filesTouched.join(', ')}\n`);
3540
3707
  }
3541
3708
  } catch {}
3542
- const { spawnSync } = await import('node:child_process');
3543
- const tool = _sessionTool(sess);
3544
- const launchArgs = _sessionLaunchArgs(sess, cwd);
3545
- process.stdout.write(`\n Launching: ${tool} ${launchArgs.join(' ')}\n\n`);
3546
- spawnSync(tool, launchArgs, { stdio: 'inherit' });
3547
- saveTerminalState(cwd, getTerminalId(), sess.id, sess.tool || 'claude');
3548
- return { next: 'main' };
3709
+ return await launchSessionWithLease(sess, cwd, ask);
3549
3710
  }
3550
3711
 
3551
3712
  if (choice === 'n') { return { next: 'new-session' }; }
@@ -3584,11 +3745,7 @@ async function mainScreen(rl, ask) {
3584
3745
  const num = parseInt(pick, 10);
3585
3746
  if (!isNaN(num) && num >= 1 && num <= Math.min(results.length, 9)) {
3586
3747
  const sess = results[num - 1];
3587
- const { spawnSync } = await import('node:child_process');
3588
- const tool = sess.tool === 'codex' ? 'codex' : 'claude';
3589
- const launchArgs = tool === 'codex' ? _codexResumeArgs(sess.id, cwd) : _claudeResumeArgs(sess.id, cwd);
3590
- process.stdout.write(`\n Launching: ${tool} ${launchArgs.join(' ')}\n\n`);
3591
- spawnSync(tool, launchArgs, { stdio: 'inherit' });
3748
+ return await launchSessionWithLease(sess, cwd, ask);
3592
3749
  }
3593
3750
  return { next: 'main' };
3594
3751
  }
@@ -3598,11 +3755,11 @@ async function mainScreen(rl, ask) {
3598
3755
  if (choice === 'i') { return { next: 'import-picker' }; }
3599
3756
  if (choice === 'a') {
3600
3757
  const prof = loadProfile(cwd);
3601
- const nextAuto = !(prof.automode ?? prof.settings?.automode ?? false);
3602
- prof.automode = nextAuto;
3603
- prof.settings = { ...(prof.settings || {}), automode: nextAuto };
3604
- saveProfile(prof, { cwd });
3605
- process.stdout.write(`\n Automode: ${prof.automode ? '\x1b[32mON\x1b[0m' : '\x1b[2mOFF\x1b[0m'}\n\n`);
3758
+ const nextAuto = !getEffectiveAutomode(prof, cwd);
3759
+ const settings = loadSessionSettings(cwd);
3760
+ settings.automode = nextAuto;
3761
+ saveSessionSettings(cwd, settings);
3762
+ process.stdout.write(`\n Automode: ${nextAuto ? '\x1b[32mON\x1b[0m' : '\x1b[2mOFF\x1b[0m'} \x1b[2m(this session)\x1b[0m\n\n`);
3606
3763
  await ask(' Press Enter to continue...');
3607
3764
  return { next: 'main' };
3608
3765
  }
@@ -4179,8 +4336,11 @@ async function settingsScreen(rl, ask) {
4179
4336
  // Load current work style
4180
4337
  const profile = loadProfile(cwd);
4181
4338
  const currentBias = profile?.bias || profile?.mode || 'balanced';
4182
- const automode = profile.automode ?? profile.settings?.automode ?? false;
4183
- const bypassPermissions = !!profile.bypassPermissions;
4339
+ const automode = getEffectiveAutomode(profile, cwd);
4340
+ const sessionSettings = loadSessionSettings(cwd);
4341
+ const bypassPermissions = getEffectiveBypassPermissions(cwd);
4342
+ const autoScope = typeof sessionSettings.automode === 'boolean' ? 'session' : 'default';
4343
+ const permissionScope = typeof sessionSettings.bypassPermissions === 'boolean' ? 'session' : 'default';
4184
4344
 
4185
4345
  // Work style current markers
4186
4346
  const _stIsFast = ['cost-saver', 'auto', 'solo-claude', 'solo-openai'].includes(currentBias);
@@ -4228,8 +4388,8 @@ async function settingsScreen(rl, ask) {
4228
4388
  ? `${RED}bypass approvals and sandbox${RESET}`
4229
4389
  : `${GREEN}safe approvals + workspace sandbox${RESET}`;
4230
4390
  const convLines = [
4231
- ` ${DIM}Auto mode${RESET} ${autoMark} ${automode ? 'run safe tasks immediately' : 'ask before launching tasks'}`,
4232
- ` ${DIM}Permissions${RESET} ${permMark} ${permMode}`,
4391
+ ` ${DIM}Auto mode${RESET} ${autoMark} ${automode ? 'run safe tasks immediately' : 'ask before launching tasks'} ${DIM}[${autoScope}]${RESET}`,
4392
+ ` ${DIM}Permissions${RESET} ${permMark} ${permMode} ${DIM}[${permissionScope}]${RESET}`,
4233
4393
  ` ${DIM}Claude resume${RESET} ${bypassPermissions ? '--dangerously-skip-permissions' : 'normal permissions'}`,
4234
4394
  ` ${DIM}Codex resume${RESET} ${bypassPermissions ? '--dangerously-bypass-approvals-and-sandbox' : 'workspace-write + on-request'}`,
4235
4395
  ];
@@ -4353,19 +4513,20 @@ async function settingsScreen(rl, ask) {
4353
4513
  // Conversation behavior toggles
4354
4514
  if (choice === 'o') {
4355
4515
  const nextAuto = !automode;
4356
- profile.automode = nextAuto;
4357
- profile.settings = { ...(profile.settings || {}), automode: nextAuto };
4358
- saveProfile(profile, { cwd });
4359
- process.stdout.write(`\n Auto mode: ${nextAuto ? GREEN + 'ON' + RESET : DIM + 'OFF' + RESET}\n\n`);
4516
+ const settings = loadSessionSettings(cwd);
4517
+ settings.automode = nextAuto;
4518
+ saveSessionSettings(cwd, settings);
4519
+ process.stdout.write(`\n Auto mode: ${nextAuto ? GREEN + 'ON' + RESET : DIM + 'OFF' + RESET} ${DIM}(this session)${RESET}\n\n`);
4360
4520
  await ask(' Press Enter to continue...');
4361
4521
  return { next: 'settings' };
4362
4522
  }
4363
4523
 
4364
4524
  if (choice === 'v') {
4365
4525
  if (bypassPermissions) {
4366
- profile.bypassPermissions = false;
4367
- saveProfile(profile, { cwd });
4368
- process.stdout.write(`\n Permission mode: ${GREEN}safe approvals + workspace sandbox${RESET}\n\n`);
4526
+ const settings = loadSessionSettings(cwd);
4527
+ settings.bypassPermissions = false;
4528
+ saveSessionSettings(cwd, settings);
4529
+ process.stdout.write(`\n Permission mode: ${GREEN}safe approvals + workspace sandbox${RESET} ${DIM}(this session)${RESET}\n\n`);
4369
4530
  await ask(' Press Enter to continue...');
4370
4531
  return { next: 'settings' };
4371
4532
  }
@@ -4374,9 +4535,10 @@ async function settingsScreen(rl, ask) {
4374
4535
  process.stdout.write(' Use it only in trusted workspaces where the user explicitly accepts the risk.\n');
4375
4536
  const confirm = (await ask(' Type YES to enable bypass mode: ')).trim();
4376
4537
  if (confirm === 'YES') {
4377
- profile.bypassPermissions = true;
4378
- saveProfile(profile, { cwd });
4379
- process.stdout.write(`\n Permission mode: ${RED}bypass approvals and sandbox${RESET}\n\n`);
4538
+ const settings = loadSessionSettings(cwd);
4539
+ settings.bypassPermissions = true;
4540
+ saveSessionSettings(cwd, settings);
4541
+ process.stdout.write(`\n Permission mode: ${RED}bypass approvals and sandbox${RESET} ${DIM}(this session)${RESET}\n\n`);
4380
4542
  } else {
4381
4543
  process.stdout.write('\n Permission mode unchanged.\n\n');
4382
4544
  }
@@ -5807,13 +5969,7 @@ async function sessionsScreen(rl, ask) {
5807
5969
  const sess = sessions[cursor];
5808
5970
  cleanup();
5809
5971
  process.stdout.write('\n');
5810
- const tool = _sessionTool(sess);
5811
- const launchArgs = _sessionLaunchArgs(sess, cwd);
5812
- process.stdout.write(`\n Launching: ${tool} ${launchArgs.join(' ')}\n\n`);
5813
- const { spawnSync } = await import('node:child_process');
5814
- spawnSync(tool, launchArgs, { stdio: 'inherit' });
5815
- saveTerminalState(cwd, getTerminalId(), sess.id, sess.tool || 'claude');
5816
- resolve({ next: 'main' });
5972
+ resolve(await launchSessionWithLease(sess, cwd, null));
5817
5973
  return;
5818
5974
  }
5819
5975
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dual-brain",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
4
4
  "description": "AI orchestration across Claude + OpenAI subscriptions — smart routing, budget awareness, and dual-brain collaboration",
5
5
  "type": "module",
6
6
  "bin": {