dual-brain 0.2.12 → 0.2.14

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.
@@ -20,6 +20,24 @@ import {
20
20
 
21
21
  import { detectTask, primeAgentRegistry } from '../src/detect.mjs';
22
22
 
23
+ // ─── Claude launch helper ────────────────────────────────────────────────────
24
+ // Builds launch args respecting user's bypass preference from profile.
25
+ // Never hardcode --dangerously-skip-permissions — it's a user choice.
26
+
27
+ function _claudeResumeArgs(sessionId, cwd) {
28
+ const args = ['--resume', sessionId];
29
+ const prof = loadProfile(cwd || process.cwd());
30
+ if (prof.bypassPermissions) args.push('--dangerously-skip-permissions');
31
+ return args;
32
+ }
33
+
34
+ function _claudeNewArgs(cwd) {
35
+ const args = [];
36
+ const prof = loadProfile(cwd || process.cwd());
37
+ if (prof.bypassPermissions) args.push('--dangerously-skip-permissions');
38
+ return args;
39
+ }
40
+
23
41
  // ─── Agent/skill registry cache (populated at startup) ───────────────────────
24
42
  // These are set by _primeRegistryCache() so classifyInput can use them
25
43
  // synchronously without async overhead on each keystroke.
@@ -2286,10 +2304,12 @@ async function mainScreen(rl, ask) {
2286
2304
  // ── One-time default shell prompt for returning users (never asked before) ─
2287
2305
  if (profile.setupComplete && !profile.defaultShellAsked) {
2288
2306
  if (dashSpinner) { dashSpinner.stop(); dashSpinner = null; }
2289
- const wantsDefault = await askDefaultShell(cwd, rl, fx);
2290
- profile.defaultShellAsked = true;
2291
- profile.isDefaultShell = wantsDefault;
2292
- saveProfile(profile, { cwd });
2307
+ try {
2308
+ const wantsDefault = await askDefaultShell(cwd, rl, fx);
2309
+ profile.defaultShellAsked = true;
2310
+ profile.isDefaultShell = wantsDefault;
2311
+ saveProfile(profile, { cwd });
2312
+ } catch { profile.defaultShellAsked = true; }
2293
2313
  }
2294
2314
 
2295
2315
  const claudeSub = profile?.providers?.claude;
@@ -2312,17 +2332,20 @@ async function mainScreen(rl, ask) {
2312
2332
  syncSessionMirror(cwd);
2313
2333
  } catch {}
2314
2334
 
2315
- // Auto-refresh expired subscriptions
2316
- if (claudeExpired || openaiExpired) {
2317
- const { spawnSync } = await import('node:child_process');
2318
- if (claudeExpired) {
2319
- const r = spawnSync('claude', ['auth', 'login'], { stdio: 'inherit', timeout: 30000 });
2320
- if (r.status === 0) { claudeSub.expiresAt = null; saveProfile(profile, { cwd }); }
2321
- }
2322
- if (openaiExpired) {
2323
- const r = spawnSync('codex', ['login'], { stdio: 'inherit', timeout: 30000 });
2324
- if (r.status === 0) { openaiSub.expiresAt = null; saveProfile(profile, { cwd }); }
2325
- }
2335
+ // Auto-refresh expired subscriptions (skip during dashboard load — don't block)
2336
+ // Users can manually run 'j' (claude login) or 'k' (codex login) from the menu
2337
+ if ((claudeExpired || openaiExpired) && !dashSpinner) {
2338
+ try {
2339
+ const { spawnSync } = await import('node:child_process');
2340
+ if (claudeExpired) {
2341
+ const r = spawnSync('claude', ['auth', 'login'], { stdio: 'pipe', timeout: 5000 });
2342
+ if (r.status === 0) { claudeSub.expiresAt = null; saveProfile(profile, { cwd }); }
2343
+ }
2344
+ if (openaiExpired) {
2345
+ const r = spawnSync('codex', ['login'], { stdio: 'pipe', timeout: 5000 });
2346
+ if (r.status === 0) { openaiSub.expiresAt = null; saveProfile(profile, { cwd }); }
2347
+ }
2348
+ } catch {}
2326
2349
  }
2327
2350
 
2328
2351
  // Build session index in background (powers search + smart resume)
@@ -2331,8 +2354,9 @@ async function mainScreen(rl, ask) {
2331
2354
  buildSessionIndex(cwd);
2332
2355
  } catch {}
2333
2356
 
2334
- // Gather recent sessions
2335
- const allSessions = enrichSessions(importReplitSessions(cwd), cwd);
2357
+ // Gather recent sessions (wrapped — never hang the dashboard)
2358
+ let allSessions = [];
2359
+ try { allSessions = enrichSessions(importReplitSessions(cwd), cwd); } catch {}
2336
2360
  const recentSessions = allSessions.slice(0, 3);
2337
2361
  const staleCount = allSessions.filter(s => {
2338
2362
  const ageMs = s.lastActive ? Date.now() - new Date(s.lastActive).getTime() : 0;
@@ -2414,7 +2438,7 @@ async function mainScreen(rl, ask) {
2414
2438
  if (cardChoice === 'resume') {
2415
2439
  const { spawnSync } = await import('node:child_process');
2416
2440
  process.stdout.write(` Launching: claude --resume ${interrupted.sessionId}\n\n`);
2417
- spawnSync('claude', ['--resume', interrupted.sessionId, '--dangerously-skip-permissions'], { stdio: 'inherit' });
2441
+ spawnSync('claude', _claudeResumeArgs(interrupted.sessionId, cwd), { stdio: 'inherit' });
2418
2442
  saveTerminalState(cwd, getTerminalId(), interrupted.sessionId, 'claude');
2419
2443
  return { next: 'main' };
2420
2444
  }
@@ -3082,7 +3106,7 @@ async function mainScreen(rl, ask) {
3082
3106
  const sess = recentSessions[0];
3083
3107
  const { spawnSync } = await import('node:child_process');
3084
3108
  process.stdout.write(`\n Launching: claude --resume ${sess.id}\n\n`);
3085
- spawnSync('claude', ['--resume', sess.id, '--dangerously-skip-permissions'], { stdio: 'inherit' });
3109
+ spawnSync('claude', _claudeResumeArgs(sess.id, cwd), { stdio: 'inherit' });
3086
3110
  saveTerminalState(cwd, getTerminalId(), sess.id, sess.tool || 'claude');
3087
3111
  return { next: 'main' };
3088
3112
  }
@@ -3101,7 +3125,7 @@ async function mainScreen(rl, ask) {
3101
3125
  } catch {}
3102
3126
  const { spawnSync } = await import('node:child_process');
3103
3127
  process.stdout.write(`\n Launching: claude --resume ${sess.id}\n\n`);
3104
- spawnSync('claude', ['--resume', sess.id, '--dangerously-skip-permissions'], { stdio: 'inherit' });
3128
+ spawnSync('claude', _claudeResumeArgs(sess.id, cwd), { stdio: 'inherit' });
3105
3129
  saveTerminalState(cwd, getTerminalId(), sess.id, sess.tool || 'claude');
3106
3130
  return { next: 'main' };
3107
3131
  }
@@ -5089,7 +5113,7 @@ async function sessionDetailScreen(rl, ask, ctx = {}) {
5089
5113
  console.log(`\n Launching: claude --resume ${sess.id}\n`);
5090
5114
  try {
5091
5115
  const { spawnSync } = await import('node:child_process');
5092
- spawnSync('claude', ['--resume', sess.id, '--dangerously-skip-permissions'], { stdio: 'inherit' });
5116
+ spawnSync('claude', _claudeResumeArgs(sess.id, cwd), { stdio: 'inherit' });
5093
5117
  } catch {
5094
5118
  console.log(' Could not launch claude CLI. Run manually:');
5095
5119
  console.log(` claude --resume ${sess.id}`);
@@ -5239,7 +5263,7 @@ async function sessionsScreen(rl, ask) {
5239
5263
  process.stdout.write('\n');
5240
5264
  process.stdout.write(`\n Launching: claude --resume ${sess.id}\n\n`);
5241
5265
  const { spawnSync } = await import('node:child_process');
5242
- spawnSync('claude', ['--resume', sess.id, '--dangerously-skip-permissions'], { stdio: 'inherit' });
5266
+ spawnSync('claude', _claudeResumeArgs(sess.id, cwd), { stdio: 'inherit' });
5243
5267
  saveTerminalState(cwd, getTerminalId(), sess.id, sess.tool || 'claude');
5244
5268
  resolve({ next: 'main' });
5245
5269
  return;
@@ -5387,7 +5411,7 @@ async function sessionManageScreen(rl, ask, ctx = {}) {
5387
5411
  if (choice === 'o') {
5388
5412
  const { spawnSync } = await import('node:child_process');
5389
5413
  console.log(`\n Launching: claude --resume ${sess.id}\n`);
5390
- spawnSync('claude', ['--resume', sess.id, '--dangerously-skip-permissions'], { stdio: 'inherit' });
5414
+ spawnSync('claude', _claudeResumeArgs(sess.id, cwd), { stdio: 'inherit' });
5391
5415
  return { next: 'sessions' };
5392
5416
  }
5393
5417
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dual-brain",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "AI orchestration across Claude + OpenAI subscriptions — smart routing, budget awareness, and dual-brain collaboration",
5
5
  "type": "module",
6
6
  "bin": {
package/src/head.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import { readDiagnosticNoticings } from '../.claude/hooks/diagnostic-companion.mjs';
3
4
 
4
5
  const STATE_DIR = join(process.cwd(), '.dualbrain');
5
6
  const STATE_FILE = join(STATE_DIR, 'head-state.json');
@@ -483,6 +484,20 @@ export function notice(situation, state, context = {}) {
483
484
  }
484
485
  }
485
486
 
487
+
488
+ // Diagnostic companion: feed tool-call pattern observations into deliberation
489
+ try {
490
+ const diagnosticNoticings = readDiagnosticNoticings();
491
+ for (const dn of diagnosticNoticings) {
492
+ noticings.push({
493
+ type: 'diagnostic',
494
+ severity: dn.severity || 'medium',
495
+ observation: dn.observation,
496
+ shouldSurface: dn.severity === 'high',
497
+ });
498
+ }
499
+ } catch {}
500
+
486
501
  return noticings;
487
502
  }
488
503