dual-brain 0.3.21 → 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 +225 -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 {
@@ -2986,7 +3150,14 @@ async function mainScreen(rl, ask) {
2986
3150
  try {
2987
3151
  const { getOpenTasks } = await import('../dist/src/ledger.js');
2988
3152
  const open = getOpenTasks(cwd);
2989
- if (open.length > 0) openTasks.push(`continue: ${open[0].intent.slice(0, 30)}`);
3153
+ if (open.length > 0) {
3154
+ const intent = String(open[0].intent || '').replace(/\s+/g, ' ').trim();
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
+ }
3160
+ }
2990
3161
  } catch {}
2991
3162
  suggestions = openTasks.length > 0
2992
3163
  ? [openTasks[0], 'review changes', 'run tests']
@@ -3164,7 +3335,7 @@ async function mainScreen(rl, ask) {
3164
3335
  [`s`, 'settings & profiles'],
3165
3336
  [`d`, 'doctor — diagnose issues'],
3166
3337
  [`t`, 'team settings'],
3167
- [`a`, profile.automode ? 'auto mode (on)' : 'auto mode (off)'],
3338
+ [`a`, getEffectiveAutomode(profile, cwd) ? 'auto mode (on)' : 'auto mode (off)'],
3168
3339
  [`q`, 'quit'],
3169
3340
  ];
3170
3341
  for (const [key, label] of shortcuts) {
@@ -3303,6 +3474,15 @@ async function mainScreen(rl, ask) {
3303
3474
  const cmd = classified.command;
3304
3475
  const args = classified.args;
3305
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
+
3306
3486
  if (cmd === 'resume' || cmd === 'r') {
3307
3487
  if (recentSessions.length === 0) return { next: 'new-session' };
3308
3488
  return { next: 'sessions' };
@@ -3368,11 +3548,7 @@ async function mainScreen(rl, ask) {
3368
3548
  const num = parseInt(pick, 10);
3369
3549
  if (!isNaN(num) && num >= 1 && num <= Math.min(results.length, 9)) {
3370
3550
  const sess = results[num - 1];
3371
- const { spawnSync: sp2 } = await import('node:child_process');
3372
- const tool = sess.tool === 'codex' ? 'codex' : 'claude';
3373
- const launchArgs = tool === 'codex' ? _codexResumeArgs(sess.id, cwd) : _claudeResumeArgs(sess.id, cwd);
3374
- process.stdout.write(`\n Launching: ${tool} ${launchArgs.join(' ')}\n\n`);
3375
- sp2(tool, launchArgs, { stdio: 'inherit' });
3551
+ return await launchSessionWithLease(sess, cwd, ask);
3376
3552
  }
3377
3553
  return { next: 'main' };
3378
3554
  }
@@ -3384,13 +3560,13 @@ async function mainScreen(rl, ask) {
3384
3560
  if (cmd === 'auto') {
3385
3561
  const cwd2 = process.cwd();
3386
3562
  const prof = loadProfile(cwd2);
3387
- const nextAuto = !(prof.automode ?? prof.settings?.automode ?? false);
3388
- prof.automode = nextAuto;
3389
- prof.settings = { ...(prof.settings || {}), automode: nextAuto };
3390
- saveProfile(prof, { cwd: cwd2 });
3391
- const state = prof.automode ? '\x1b[32mON\x1b[0m' : '\x1b[2mOFF\x1b[0m';
3392
- process.stdout.write(`\n Automode: ${state}\n`);
3393
- 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`);
3394
3570
  await ask(' Press Enter to continue...');
3395
3571
  return { next: 'main' };
3396
3572
  }
@@ -3488,7 +3664,7 @@ async function mainScreen(rl, ask) {
3488
3664
  }
3489
3665
 
3490
3666
  // Automode: if HEAD says it's safe, just go — no confirmation needed
3491
- const automode = profile.automode ?? profile.settings?.automode ?? false;
3667
+ const automode = getEffectiveAutomode(profile, cwd);
3492
3668
  if (automode) {
3493
3669
  process.stdout.write(`\n \x1b[36m⚡\x1b[0m ${summary} (${model}, depth: ${hj?.depth || '?'})\n`);
3494
3670
  return { next: 'go', prompt: input, model, _loopResult: hj._loopResult };
@@ -3515,13 +3691,7 @@ async function mainScreen(rl, ask) {
3515
3691
  return { next: 'new-session' };
3516
3692
  }
3517
3693
  const sess = recentSessions[0];
3518
- const { spawnSync } = await import('node:child_process');
3519
- const tool = _sessionTool(sess);
3520
- const launchArgs = _sessionLaunchArgs(sess, cwd);
3521
- process.stdout.write(`\n Launching: ${tool} ${launchArgs.join(' ')}\n\n`);
3522
- spawnSync(tool, launchArgs, { stdio: 'inherit' });
3523
- saveTerminalState(cwd, getTerminalId(), sess.id, sess.tool || 'claude');
3524
- return { next: 'main' };
3694
+ return await launchSessionWithLease(sess, cwd, ask);
3525
3695
  }
3526
3696
 
3527
3697
  // Number 1-9 → resume that session
@@ -3536,13 +3706,7 @@ async function mainScreen(rl, ask) {
3536
3706
  if (ctx.filesTouched.length > 0) process.stdout.write(` Files touched: ${ctx.filesTouched.join(', ')}\n`);
3537
3707
  }
3538
3708
  } catch {}
3539
- const { spawnSync } = await import('node:child_process');
3540
- const tool = _sessionTool(sess);
3541
- const launchArgs = _sessionLaunchArgs(sess, cwd);
3542
- process.stdout.write(`\n Launching: ${tool} ${launchArgs.join(' ')}\n\n`);
3543
- spawnSync(tool, launchArgs, { stdio: 'inherit' });
3544
- saveTerminalState(cwd, getTerminalId(), sess.id, sess.tool || 'claude');
3545
- return { next: 'main' };
3709
+ return await launchSessionWithLease(sess, cwd, ask);
3546
3710
  }
3547
3711
 
3548
3712
  if (choice === 'n') { return { next: 'new-session' }; }
@@ -3581,11 +3745,7 @@ async function mainScreen(rl, ask) {
3581
3745
  const num = parseInt(pick, 10);
3582
3746
  if (!isNaN(num) && num >= 1 && num <= Math.min(results.length, 9)) {
3583
3747
  const sess = results[num - 1];
3584
- const { spawnSync } = await import('node:child_process');
3585
- const tool = sess.tool === 'codex' ? 'codex' : 'claude';
3586
- const launchArgs = tool === 'codex' ? _codexResumeArgs(sess.id, cwd) : _claudeResumeArgs(sess.id, cwd);
3587
- process.stdout.write(`\n Launching: ${tool} ${launchArgs.join(' ')}\n\n`);
3588
- spawnSync(tool, launchArgs, { stdio: 'inherit' });
3748
+ return await launchSessionWithLease(sess, cwd, ask);
3589
3749
  }
3590
3750
  return { next: 'main' };
3591
3751
  }
@@ -3595,11 +3755,11 @@ async function mainScreen(rl, ask) {
3595
3755
  if (choice === 'i') { return { next: 'import-picker' }; }
3596
3756
  if (choice === 'a') {
3597
3757
  const prof = loadProfile(cwd);
3598
- const nextAuto = !(prof.automode ?? prof.settings?.automode ?? false);
3599
- prof.automode = nextAuto;
3600
- prof.settings = { ...(prof.settings || {}), automode: nextAuto };
3601
- saveProfile(prof, { cwd });
3602
- 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`);
3603
3763
  await ask(' Press Enter to continue...');
3604
3764
  return { next: 'main' };
3605
3765
  }
@@ -4176,8 +4336,11 @@ async function settingsScreen(rl, ask) {
4176
4336
  // Load current work style
4177
4337
  const profile = loadProfile(cwd);
4178
4338
  const currentBias = profile?.bias || profile?.mode || 'balanced';
4179
- const automode = profile.automode ?? profile.settings?.automode ?? false;
4180
- 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';
4181
4344
 
4182
4345
  // Work style current markers
4183
4346
  const _stIsFast = ['cost-saver', 'auto', 'solo-claude', 'solo-openai'].includes(currentBias);
@@ -4225,8 +4388,8 @@ async function settingsScreen(rl, ask) {
4225
4388
  ? `${RED}bypass approvals and sandbox${RESET}`
4226
4389
  : `${GREEN}safe approvals + workspace sandbox${RESET}`;
4227
4390
  const convLines = [
4228
- ` ${DIM}Auto mode${RESET} ${autoMark} ${automode ? 'run safe tasks immediately' : 'ask before launching tasks'}`,
4229
- ` ${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}`,
4230
4393
  ` ${DIM}Claude resume${RESET} ${bypassPermissions ? '--dangerously-skip-permissions' : 'normal permissions'}`,
4231
4394
  ` ${DIM}Codex resume${RESET} ${bypassPermissions ? '--dangerously-bypass-approvals-and-sandbox' : 'workspace-write + on-request'}`,
4232
4395
  ];
@@ -4350,19 +4513,20 @@ async function settingsScreen(rl, ask) {
4350
4513
  // Conversation behavior toggles
4351
4514
  if (choice === 'o') {
4352
4515
  const nextAuto = !automode;
4353
- profile.automode = nextAuto;
4354
- profile.settings = { ...(profile.settings || {}), automode: nextAuto };
4355
- saveProfile(profile, { cwd });
4356
- 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`);
4357
4520
  await ask(' Press Enter to continue...');
4358
4521
  return { next: 'settings' };
4359
4522
  }
4360
4523
 
4361
4524
  if (choice === 'v') {
4362
4525
  if (bypassPermissions) {
4363
- profile.bypassPermissions = false;
4364
- saveProfile(profile, { cwd });
4365
- 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`);
4366
4530
  await ask(' Press Enter to continue...');
4367
4531
  return { next: 'settings' };
4368
4532
  }
@@ -4371,9 +4535,10 @@ async function settingsScreen(rl, ask) {
4371
4535
  process.stdout.write(' Use it only in trusted workspaces where the user explicitly accepts the risk.\n');
4372
4536
  const confirm = (await ask(' Type YES to enable bypass mode: ')).trim();
4373
4537
  if (confirm === 'YES') {
4374
- profile.bypassPermissions = true;
4375
- saveProfile(profile, { cwd });
4376
- 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`);
4377
4542
  } else {
4378
4543
  process.stdout.write('\n Permission mode unchanged.\n\n');
4379
4544
  }
@@ -5804,13 +5969,7 @@ async function sessionsScreen(rl, ask) {
5804
5969
  const sess = sessions[cursor];
5805
5970
  cleanup();
5806
5971
  process.stdout.write('\n');
5807
- const tool = _sessionTool(sess);
5808
- const launchArgs = _sessionLaunchArgs(sess, cwd);
5809
- process.stdout.write(`\n Launching: ${tool} ${launchArgs.join(' ')}\n\n`);
5810
- const { spawnSync } = await import('node:child_process');
5811
- spawnSync(tool, launchArgs, { stdio: 'inherit' });
5812
- saveTerminalState(cwd, getTerminalId(), sess.id, sess.tool || 'claude');
5813
- resolve({ next: 'main' });
5972
+ resolve(await launchSessionWithLease(sess, cwd, null));
5814
5973
  return;
5815
5974
  }
5816
5975
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dual-brain",
3
- "version": "0.3.21",
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": {