dual-brain 0.2.18 → 0.2.19

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.
@@ -1151,7 +1151,10 @@ async function installGlobal() {
1151
1151
 
1152
1152
  // Resolve absolute path to hooks directory via import.meta.url
1153
1153
  const pkgRoot = join(__dirname, '..');
1154
- const hooksDir = join(pkgRoot, '.claude', 'hooks');
1154
+ // Hooks live at hooks/ in the published package, .claude/hooks/ in dev
1155
+ const hooksDir = existsSync(join(pkgRoot, 'hooks', 'head-guard.mjs'))
1156
+ ? join(pkgRoot, 'hooks')
1157
+ : join(pkgRoot, '.claude', 'hooks');
1155
1158
 
1156
1159
  // Warn if running from npx (ephemeral path)
1157
1160
  if (pkgRoot.includes('.npm/_npx') || pkgRoot.includes('npx-')) {
@@ -1178,9 +1181,9 @@ async function installGlobal() {
1178
1181
  })();
1179
1182
 
1180
1183
  if (hasProjectLocalHooks) {
1181
- console.log(' hooks already configured project-locally, skipping global hooks');
1182
- console.log(' (project .claude/settings.local.json already contains dual-brain hooks)');
1183
- } else {
1184
+ console.log(' project-local hooks detected (will take precedence in this workspace)');
1185
+ }
1186
+ {
1184
1187
  // Load existing settings (merge, never clobber)
1185
1188
  let existing = {};
1186
1189
  if (existsSync(globalSettingsPath)) {
@@ -4479,7 +4482,7 @@ async function askDefaultShell(cwd, rl, fx) {
4479
4482
  ` ${DIM}modifies${RST} ${YLW}.replit onBoot${RST}`,
4480
4483
  ` ${DIM}undo${RST} Settings → System → Startup`,
4481
4484
  '',
4482
- ` ${CYAN}[Y]${RST} Start on boot ${DIM}[n] Run manually${RST}`,
4485
+ ` ${CYAN}[Enter]${RST} Start on boot ${DIM}[n] Run manually${RST}`,
4483
4486
  ];
4484
4487
  process.stdout.write('\n' + panel('dual-brain setup', setupContent) + '\n');
4485
4488
 
@@ -31,16 +31,12 @@ try {
31
31
  const raw = readFileSync('/dev/stdin', 'utf8');
32
32
  input = JSON.parse(raw);
33
33
  } catch {
34
- // Can't parse input — fail closed to avoid guard bypass.
35
- const output = {
36
- hookSpecificOutput: {
37
- hookEventName: 'PreToolUse',
38
- permissionDecision: 'deny',
39
- permissionDecisionReason: '[dual-brain] head-guard could not parse hook input — blocking as a safety measure.',
40
- },
41
- };
42
- process.stdout.write(JSON.stringify(output));
43
- process.exit(2);
34
+ // Can't parse input — fail open. This hook's purpose is to block HEAD from
35
+ // implementing directly. If we can't parse stdin (e.g. subagent context where
36
+ // Claude Code doesn't pipe parseable JSON), blocking would incorrectly deny
37
+ // work agents. Allowing is safer: worst case HEAD slips through once, but
38
+ // work agents aren't blocked.
39
+ process.exit(0);
44
40
  }
45
41
 
46
42
  const toolName = input.tool_name || '';
package/install.mjs CHANGED
@@ -913,9 +913,9 @@ function install(workspace, env, mode) {
913
913
  'gpt-work-dispatcher.mjs', 'profiles.mjs',
914
914
  'summary-checkpoint.mjs', 'decision-ledger.mjs', 'control-panel.mjs',
915
915
  'risk-classifier.mjs', 'failure-detector.mjs',
916
- 'vibe-router.mjs', 'plan-generator.mjs', 'vibe-memory.mjs',
916
+ 'plan-generator.mjs', 'vibe-memory.mjs',
917
917
  'wave-orchestrator.mjs',
918
- 'task-classifier.mjs', 'model-registry.mjs',
918
+ 'model-registry.mjs',
919
919
  'auto-update-wrapper.mjs',
920
920
  'head-guard.mjs',
921
921
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dual-brain",
3
- "version": "0.2.18",
3
+ "version": "0.2.19",
4
4
  "description": "AI orchestration across Claude + OpenAI subscriptions — smart routing, budget awareness, and dual-brain collaboration",
5
5
  "type": "module",
6
6
  "bin": {
@@ -197,6 +197,7 @@ export function enter(userMessage, context = {}) {
197
197
  plan,
198
198
  nextDispatch: prepared,
199
199
  suggestion: prepared.blockers[0],
200
+ mode,
200
201
  };
201
202
  }
202
203
 
@@ -209,6 +210,7 @@ export function enter(userMessage, context = {}) {
209
210
  plan,
210
211
  nextDispatch: prepared,
211
212
  estimatedCost: plan.estimatedCost,
213
+ mode,
212
214
  };
213
215
  }
214
216
 
@@ -46,6 +46,12 @@ export function acquire({ force = false } = {}) {
46
46
  return { acquired: true, sessionId: _sessionId, existingSession: null, mode: 'primary' };
47
47
  }
48
48
 
49
+ // Same process (re-entry within same session) — always grant
50
+ if (existing.pid === process.pid) {
51
+ _sessionId = existing.sessionId;
52
+ return { acquired: true, sessionId: _sessionId, existingSession: null, mode: 'primary' };
53
+ }
54
+
49
55
  const age = Date.now() - existing.heartbeat;
50
56
 
51
57
  if (age > STALE_THRESHOLD_MS || force) {