dual-brain 0.2.9 → 0.2.11
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 +278 -66
- package/package.json +8 -2
- package/src/agents/registry.mjs +405 -0
- package/src/collaboration.mjs +545 -0
- package/src/detect.mjs +73 -1
- package/src/dispatch.mjs +47 -5
- package/src/head.mjs +705 -263
- package/src/pipeline.mjs +387 -163
- package/src/provider-context.mjs +257 -0
package/bin/dual-brain.mjs
CHANGED
|
@@ -18,7 +18,21 @@ import {
|
|
|
18
18
|
loadCredentials, saveCredentials, getCredentialSummary, detectCredentials, addCredential, removeCredential, checkCredentialHealth,
|
|
19
19
|
} from '../src/profile.mjs';
|
|
20
20
|
|
|
21
|
-
import { detectTask } from '../src/detect.mjs';
|
|
21
|
+
import { detectTask, primeAgentRegistry } from '../src/detect.mjs';
|
|
22
|
+
|
|
23
|
+
// ─── Agent/skill registry cache (populated at startup) ───────────────────────
|
|
24
|
+
// These are set by _primeRegistryCache() so classifyInput can use them
|
|
25
|
+
// synchronously without async overhead on each keystroke.
|
|
26
|
+
let _cachedMatchSkill = null;
|
|
27
|
+
let _cachedSkillToTaskBrief = null;
|
|
28
|
+
|
|
29
|
+
async function _primeRegistryCache() {
|
|
30
|
+
try {
|
|
31
|
+
const reg = await import('../src/agents/registry.mjs');
|
|
32
|
+
_cachedMatchSkill = reg.matchSkill;
|
|
33
|
+
_cachedSkillToTaskBrief = reg.skillToTaskBrief;
|
|
34
|
+
} catch {}
|
|
35
|
+
}
|
|
22
36
|
|
|
23
37
|
import {
|
|
24
38
|
decideRoute, getAvailableModels,
|
|
@@ -2034,11 +2048,56 @@ function makeBoxRow(content, W) {
|
|
|
2034
2048
|
|
|
2035
2049
|
// ─── Command palette: input classifier ───────────────────────────────────────
|
|
2036
2050
|
|
|
2051
|
+
// HEAD state — loaded lazily, shared across REPL turns
|
|
2052
|
+
let _headState = null;
|
|
2053
|
+
let _headModuleCache = null;
|
|
2054
|
+
|
|
2055
|
+
async function _getHeadModule() {
|
|
2056
|
+
if (!_headModuleCache) {
|
|
2057
|
+
try {
|
|
2058
|
+
_headModuleCache = await import('../src/head.mjs');
|
|
2059
|
+
} catch {
|
|
2060
|
+
_headModuleCache = null;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
return _headModuleCache;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
function _getHeadState() {
|
|
2067
|
+
if (!_headState) {
|
|
2068
|
+
try {
|
|
2069
|
+
const head = _headModuleCache;
|
|
2070
|
+
_headState = head ? head.loadState() : null;
|
|
2071
|
+
} catch {
|
|
2072
|
+
_headState = null;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
return _headState;
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
const FREE_COMMANDS = new Map([
|
|
2079
|
+
['resume', 'resume'], ['r', 'resume'],
|
|
2080
|
+
['status', 'status'], ['sessions', 'sessions'], ['ss', 'sessions'],
|
|
2081
|
+
['settings', 'settings'], ['s', 'settings'],
|
|
2082
|
+
['team', 'team'], ['t', 'team'],
|
|
2083
|
+
['doctor', 'doctor'], ['d', 'doctor'],
|
|
2084
|
+
['health', 'health'], ['h', 'health'],
|
|
2085
|
+
['projects', 'projects'], ['p', 'projects'],
|
|
2086
|
+
['help', 'help'], ['?', 'help'],
|
|
2087
|
+
['quit', 'quit'], ['q', 'quit'], ['exit', 'quit'],
|
|
2088
|
+
['budget', 'budget'], ['b', 'budget'],
|
|
2089
|
+
['auto', 'auto'], ['automode', 'auto'],
|
|
2090
|
+
]);
|
|
2091
|
+
|
|
2037
2092
|
/**
|
|
2038
|
-
* Classify user input
|
|
2039
|
-
*
|
|
2040
|
-
*
|
|
2041
|
-
*
|
|
2093
|
+
* Classify user input using HEAD's cognitive pipeline.
|
|
2094
|
+
* Returns a tier-compatible object that maps HEAD's deliberation to the
|
|
2095
|
+
* existing REPL routing: free/skill/cheap/full, plus HEAD judgment metadata.
|
|
2096
|
+
*
|
|
2097
|
+
* { tier: 'free', command, args } — deterministic, zero tokens
|
|
2098
|
+
* { tier: 'skill', skill, args, command } — slash command
|
|
2099
|
+
* { tier: 'cheap', headJudgment } — question → haiku
|
|
2100
|
+
* { tier: 'full', headJudgment, model } — work task → dispatch
|
|
2042
2101
|
*/
|
|
2043
2102
|
function classifyInput(input) {
|
|
2044
2103
|
const trimmed = input.trim();
|
|
@@ -2047,37 +2106,23 @@ function classifyInput(input) {
|
|
|
2047
2106
|
const cmd = parts[0].toLowerCase();
|
|
2048
2107
|
const args = parts.slice(1);
|
|
2049
2108
|
|
|
2050
|
-
// Tier
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
['d', 'doctor'],
|
|
2063
|
-
['health', 'health'],
|
|
2064
|
-
['h', 'health'],
|
|
2065
|
-
['projects', 'projects'],
|
|
2066
|
-
['p', 'projects'],
|
|
2067
|
-
['help', 'help'],
|
|
2068
|
-
['?', 'help'],
|
|
2069
|
-
['quit', 'quit'],
|
|
2070
|
-
['q', 'quit'],
|
|
2071
|
-
['exit', 'quit'],
|
|
2072
|
-
['budget', 'budget'],
|
|
2073
|
-
['b', 'budget'],
|
|
2074
|
-
]);
|
|
2109
|
+
// Tier 0: SKILL — slash commands (checked first, deterministic)
|
|
2110
|
+
if (trimmed.startsWith('/')) {
|
|
2111
|
+
try {
|
|
2112
|
+
if (typeof _cachedMatchSkill === 'function') {
|
|
2113
|
+
const skill = _cachedMatchSkill(trimmed);
|
|
2114
|
+
if (skill) {
|
|
2115
|
+
const skillArgs = trimmed.replace(/^\/\w+\s*/, '');
|
|
2116
|
+
return { tier: 'skill', skill, args: skillArgs, command: skill.command };
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
} catch {}
|
|
2120
|
+
}
|
|
2075
2121
|
|
|
2122
|
+
// Tier 1: FREE — exact command matches (zero tokens, no HEAD needed)
|
|
2076
2123
|
if (FREE_COMMANDS.has(cmd)) {
|
|
2077
2124
|
return { tier: 'free', command: FREE_COMMANDS.get(cmd), args };
|
|
2078
2125
|
}
|
|
2079
|
-
|
|
2080
|
-
// Multi-word free commands
|
|
2081
2126
|
if (lower.startsWith('search ')) {
|
|
2082
2127
|
return { tier: 'free', command: 'search', args: parts.slice(1) };
|
|
2083
2128
|
}
|
|
@@ -2085,14 +2130,65 @@ function classifyInput(input) {
|
|
|
2085
2130
|
return { tier: 'free', command: 'init --replit', args: [] };
|
|
2086
2131
|
}
|
|
2087
2132
|
|
|
2088
|
-
//
|
|
2133
|
+
// ── HEAD cognitive pipeline: replaces regex-based cheap/full split ──────
|
|
2134
|
+
const head = _headModuleCache;
|
|
2135
|
+
if (head) {
|
|
2136
|
+
const state = _getHeadState() || head.freshState();
|
|
2137
|
+
const turn = head.processTurn(state, trimmed, {});
|
|
2138
|
+
_headState = state; // persist across turns
|
|
2139
|
+
|
|
2140
|
+
const judgment = {
|
|
2141
|
+
depth: turn.depth,
|
|
2142
|
+
action: turn.action,
|
|
2143
|
+
shouldAskUser: turn.shouldAskUser,
|
|
2144
|
+
shouldDispatch: turn.shouldDispatch,
|
|
2145
|
+
shouldClarify: turn.shouldClarify,
|
|
2146
|
+
shouldThink: turn.shouldThink,
|
|
2147
|
+
rationale: turn.rationale,
|
|
2148
|
+
confidence: turn.result.confidence,
|
|
2149
|
+
obligations: turn.result.obligations,
|
|
2150
|
+
surfaceNoticings: turn.result.surfaceNoticings,
|
|
2151
|
+
};
|
|
2152
|
+
|
|
2153
|
+
// Map HEAD's depth → tier + model
|
|
2154
|
+
if (turn.depth === 'reflexive' && !turn.shouldDispatch) {
|
|
2155
|
+
return { tier: 'cheap', headJudgment: judgment };
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
// HEAD says clarify → cheap tier (ask a question, don't dispatch work)
|
|
2159
|
+
if (turn.shouldClarify) {
|
|
2160
|
+
return { tier: 'cheap', headJudgment: judgment };
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// HEAD says think/plan → full tier with opus
|
|
2164
|
+
if (turn.shouldThink) {
|
|
2165
|
+
return { tier: 'full', headJudgment: judgment, model: 'opus' };
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
// HEAD says dispatch → full tier, model based on depth
|
|
2169
|
+
if (turn.shouldDispatch) {
|
|
2170
|
+
const model = turn.depth === 'deep' ? 'opus' : 'sonnet';
|
|
2171
|
+
return { tier: 'full', headJudgment: judgment, model };
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
// HEAD says respond (not dispatch) → cheap
|
|
2175
|
+
if (turn.action.type === 'respond') {
|
|
2176
|
+
return { tier: 'cheap', headJudgment: judgment };
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
// Default: let depth drive it
|
|
2180
|
+
if (turn.depth === 'light' || turn.depth === 'reflexive') {
|
|
2181
|
+
return { tier: 'cheap', headJudgment: judgment };
|
|
2182
|
+
}
|
|
2183
|
+
return { tier: 'full', headJudgment: judgment };
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
// ── Fallback: HEAD not loaded, use simple heuristics ───────────────────
|
|
2089
2187
|
const QUESTION_WORDS = /^(why|what|how|where|when|who|is my|check|show me|explain|tell me|list|am i|are there|does|did|can i|will|should i)/i;
|
|
2090
|
-
|
|
2091
|
-
if (QUESTION_WORDS.test(lower) || QUESTION_CONTAINS.test(lower)) {
|
|
2188
|
+
if (QUESTION_WORDS.test(lower)) {
|
|
2092
2189
|
return { tier: 'cheap' };
|
|
2093
2190
|
}
|
|
2094
2191
|
|
|
2095
|
-
// Tier 3: FULL — everything else is a work task
|
|
2096
2192
|
return { tier: 'full' };
|
|
2097
2193
|
}
|
|
2098
2194
|
|
|
@@ -2176,11 +2272,16 @@ async function mainScreen(rl, ask) {
|
|
|
2176
2272
|
const auth = await detectAuth();
|
|
2177
2273
|
|
|
2178
2274
|
// ── Dashboard load animation (full mode only) ─────────────────────────────
|
|
2179
|
-
|
|
2275
|
+
let fx = null;
|
|
2276
|
+
try { fx = await Promise.race([getFx(), new Promise(r => setTimeout(() => r(null), 3000))]); } catch {}
|
|
2180
2277
|
let dashSpinner = null;
|
|
2181
2278
|
if (fx && fx.getMode && fx.getMode() === 'full') {
|
|
2182
2279
|
dashSpinner = fx.spinner('Loading dashboard...').start();
|
|
2183
2280
|
}
|
|
2281
|
+
// Safety: kill spinner after 8s no matter what
|
|
2282
|
+
const _spinnerTimeout = dashSpinner ? setTimeout(() => {
|
|
2283
|
+
if (dashSpinner) { try { dashSpinner.stop(); } catch {} dashSpinner = null; }
|
|
2284
|
+
}, 8000) : null;
|
|
2184
2285
|
|
|
2185
2286
|
// ── One-time default shell prompt for returning users (never asked before) ─
|
|
2186
2287
|
if (profile.setupComplete && !profile.defaultShellAsked) {
|
|
@@ -2199,10 +2300,10 @@ async function mainScreen(rl, ask) {
|
|
|
2199
2300
|
const claudeExpired = claudeSub?.expiresAt && Date.parse(claudeSub.expiresAt) < now;
|
|
2200
2301
|
const openaiExpired = openaiSub?.expiresAt && Date.parse(openaiSub.expiresAt) < now;
|
|
2201
2302
|
|
|
2202
|
-
// Silent OAuth token auto-refresh
|
|
2303
|
+
// Silent OAuth token auto-refresh (3s timeout — never block dashboard)
|
|
2203
2304
|
try {
|
|
2204
2305
|
const { autoRefreshToken } = await import('../src/profile.mjs');
|
|
2205
|
-
await autoRefreshToken(cwd);
|
|
2306
|
+
await Promise.race([autoRefreshToken(cwd), new Promise(r => setTimeout(r, 3000))]);
|
|
2206
2307
|
} catch {}
|
|
2207
2308
|
|
|
2208
2309
|
// Append-only session archive sync
|
|
@@ -2258,7 +2359,7 @@ async function mainScreen(rl, ask) {
|
|
|
2258
2359
|
} else {
|
|
2259
2360
|
process.stdout.write(` ${DIM}${interrupted.reason} · ${interrupted.ageLabel}${RST}\n`);
|
|
2260
2361
|
}
|
|
2261
|
-
process.stdout.write(`
|
|
2362
|
+
process.stdout.write(` \x1b[36mEnter\x1b[0m resume \x1b[36mn\x1b[0m new \x1b[36ms\x1b[0m skip\n\n`);
|
|
2262
2363
|
|
|
2263
2364
|
// Wait for a keypress to decide what to do with the card
|
|
2264
2365
|
const readline2 = await import('node:readline');
|
|
@@ -2598,6 +2699,7 @@ async function mainScreen(rl, ask) {
|
|
|
2598
2699
|
});
|
|
2599
2700
|
|
|
2600
2701
|
// ── Resolve dashboard spinner before rendering ────────────────────────────
|
|
2702
|
+
if (_spinnerTimeout) clearTimeout(_spinnerTimeout);
|
|
2601
2703
|
if (dashSpinner) dashSpinner.succeed('Dashboard ready');
|
|
2602
2704
|
|
|
2603
2705
|
// ── Stale hint ────────────────────────────────────────────────────────────
|
|
@@ -2641,12 +2743,22 @@ async function mainScreen(rl, ask) {
|
|
|
2641
2743
|
process.stdout.write(panel('Signals', recentLines, { width: panelW }) + '\n\n');
|
|
2642
2744
|
}
|
|
2643
2745
|
|
|
2644
|
-
//
|
|
2645
|
-
const
|
|
2746
|
+
// Shortcut bar — always visible so the user never has to guess
|
|
2747
|
+
const autoLabel = profile.automode ? `\x1b[32m⚡auto\x1b[0m` : `${DIM}auto${RST}`;
|
|
2748
|
+
const shortcutItems = [
|
|
2749
|
+
`${CYAN}Enter${RST} resume`,
|
|
2750
|
+
`${CYAN}n${RST} new`,
|
|
2751
|
+
`${CYAN}/${RST} search`,
|
|
2752
|
+
`${CYAN}s${RST} settings`,
|
|
2753
|
+
`${CYAN}d${RST} doctor`,
|
|
2754
|
+
autoLabel,
|
|
2755
|
+
`${CYAN}q${RST} quit`,
|
|
2756
|
+
];
|
|
2757
|
+
process.stdout.write(` ${DIM}${shortcutItems.join(' ')}${RST}\n\n`);
|
|
2758
|
+
|
|
2759
|
+
// Input bar — rendered below shortcut bar
|
|
2646
2760
|
const inputLeft = tuiPrompt('task or command...');
|
|
2647
|
-
|
|
2648
|
-
const inputGap = Math.max(1, panelW - inputLeftW - 8);
|
|
2649
|
-
process.stdout.write(` ${inputLeft}${' '.repeat(inputGap)}${inputHint}\n`);
|
|
2761
|
+
process.stdout.write(` ${inputLeft}\n`);
|
|
2650
2762
|
|
|
2651
2763
|
// ── Key handling ──────────────────────────────────────────────────────────
|
|
2652
2764
|
// Use raw keypress mode so we can show a live type-to-start buffer.
|
|
@@ -2848,6 +2960,17 @@ async function mainScreen(rl, ask) {
|
|
|
2848
2960
|
await ask('\n Press Enter to continue...');
|
|
2849
2961
|
return { next: 'main' };
|
|
2850
2962
|
}
|
|
2963
|
+
if (cmd === 'auto') {
|
|
2964
|
+
const cwd2 = process.cwd();
|
|
2965
|
+
const prof = loadProfile(cwd2);
|
|
2966
|
+
prof.automode = !prof.automode;
|
|
2967
|
+
saveProfile(prof, { cwd: cwd2 });
|
|
2968
|
+
const state = prof.automode ? '\x1b[32mON\x1b[0m' : '\x1b[2mOFF\x1b[0m';
|
|
2969
|
+
process.stdout.write(`\n Automode: ${state}\n`);
|
|
2970
|
+
process.stdout.write(` ${prof.automode ? 'Tasks dispatch immediately (HEAD still gates dangerous ops)' : 'Tasks require Enter to confirm'}\n\n`);
|
|
2971
|
+
await ask(' Press Enter to continue...');
|
|
2972
|
+
return { next: 'main' };
|
|
2973
|
+
}
|
|
2851
2974
|
if (cmd === 'init --replit') {
|
|
2852
2975
|
await cmdInit(rl);
|
|
2853
2976
|
return { next: 'main' };
|
|
@@ -2855,20 +2978,94 @@ async function mainScreen(rl, ask) {
|
|
|
2855
2978
|
// fallthrough: unknown free command → treat as full task
|
|
2856
2979
|
}
|
|
2857
2980
|
|
|
2858
|
-
// Tier
|
|
2981
|
+
// Tier 0.5: SKILL — slash command routed through agent registry
|
|
2982
|
+
if (classified.tier === 'skill') {
|
|
2983
|
+
const skill = classified.skill;
|
|
2984
|
+
const skillArgs = classified.args || '';
|
|
2985
|
+
|
|
2986
|
+
// Free skills (e.g. /status) run deterministically with no agent
|
|
2987
|
+
if (skill.tier === 'free' || !skill.agent) {
|
|
2988
|
+
if (skill.command === 'status') {
|
|
2989
|
+
await cmdStatus([]);
|
|
2990
|
+
await ask('\n Press Enter to continue...');
|
|
2991
|
+
return { next: 'main' };
|
|
2992
|
+
}
|
|
2993
|
+
return { next: 'main' };
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
// Build the task brief from the skill declaration
|
|
2997
|
+
let brief = null;
|
|
2998
|
+
try {
|
|
2999
|
+
if (typeof _cachedSkillToTaskBrief === 'function') {
|
|
3000
|
+
brief = _cachedSkillToTaskBrief(input, skillArgs);
|
|
3001
|
+
}
|
|
3002
|
+
} catch {}
|
|
3003
|
+
|
|
3004
|
+
const model = brief?.model || skill.model || 'sonnet';
|
|
3005
|
+
const prompt = brief?.objective || `/${skill.command} ${skillArgs}`.trim();
|
|
3006
|
+
|
|
3007
|
+
process.stdout.write(`\n Skill: /${skill.command} Agent: ${skill.agent} Model: ${model}\n`);
|
|
3008
|
+
if (skill.description) process.stdout.write(` ${skill.description}\n`);
|
|
3009
|
+
process.stdout.write(` \x1b[36mEnter\x1b[0m run \x1b[36mn\x1b[0m cancel\n\n`);
|
|
3010
|
+
const skillConfirm = (await ask(' > ')).trim().toLowerCase();
|
|
3011
|
+
if (skillConfirm === 'n' || skillConfirm === 'no') return { next: 'main' };
|
|
3012
|
+
|
|
3013
|
+
return { next: 'go', prompt, model };
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
// Tier 2: CHEAP — question/diagnostic/reflexive
|
|
2859
3017
|
if (classified.tier === 'cheap') {
|
|
2860
|
-
|
|
2861
|
-
|
|
3018
|
+
const hj = classified.headJudgment;
|
|
3019
|
+
const model = hj ? 'haiku' : 'haiku';
|
|
3020
|
+
if (hj?.surfaceNoticings?.length > 0) {
|
|
3021
|
+
for (const n of hj.surfaceNoticings) {
|
|
3022
|
+
process.stdout.write(`\n \x1b[33m[HEAD]\x1b[0m ${n.observation}\n`);
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
process.stdout.write(`\n Routing to ${model} for quick answer...\n`);
|
|
3026
|
+
return { next: 'go', prompt: input, model };
|
|
2862
3027
|
}
|
|
2863
3028
|
|
|
2864
|
-
// Tier 3: FULL — work task,
|
|
3029
|
+
// Tier 3: FULL — work task, HEAD-informed dispatch
|
|
2865
3030
|
if (classified.tier === 'full') {
|
|
3031
|
+
const hj = classified.headJudgment;
|
|
3032
|
+
const model = classified.model || 'sonnet';
|
|
2866
3033
|
const summary = input.length > 60 ? input.slice(0, 57) + '...' : input;
|
|
2867
|
-
|
|
2868
|
-
|
|
3034
|
+
|
|
3035
|
+
// Surface HEAD noticings before confirming
|
|
3036
|
+
if (hj?.surfaceNoticings?.length > 0) {
|
|
3037
|
+
for (const n of hj.surfaceNoticings) {
|
|
3038
|
+
process.stdout.write(`\n \x1b[33m[HEAD]\x1b[0m ${n.observation}`);
|
|
3039
|
+
}
|
|
3040
|
+
process.stdout.write('\n');
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
// HEAD's shouldAskUser gates the dispatch — dangerous/irreversible ops
|
|
3044
|
+
if (hj?.shouldAskUser) {
|
|
3045
|
+
const reason = hj.obligations?.find(o => o.type === 'askBeforeIrreversi')?.description || hj.rationale;
|
|
3046
|
+
process.stdout.write(`\n \x1b[31m⚠ CAUTION\x1b[0m ${reason}\n`);
|
|
3047
|
+
process.stdout.write(` Task: ${summary}\n`);
|
|
3048
|
+
process.stdout.write(` Depth: ${hj.depth} Model: ${model}\n`);
|
|
3049
|
+
process.stdout.write(` \x1b[36mEnter\x1b[0m proceed \x1b[36mn\x1b[0m cancel\n\n`);
|
|
3050
|
+
const confirm = (await ask(' > ')).trim().toLowerCase();
|
|
3051
|
+
if (confirm === 'n' || confirm === 'no') return { next: 'main' };
|
|
3052
|
+
return { next: 'go', prompt: input, model };
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
// Automode: if HEAD says it's safe, just go — no confirmation needed
|
|
3056
|
+
const automode = profile.automode ?? profile.settings?.automode ?? false;
|
|
3057
|
+
if (automode) {
|
|
3058
|
+
process.stdout.write(`\n \x1b[36m⚡\x1b[0m ${summary} (${model}, depth: ${hj?.depth || '?'})\n`);
|
|
3059
|
+
return { next: 'go', prompt: input, model };
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
// Manual mode — show depth, wait for confirmation
|
|
3063
|
+
process.stdout.write(`\n Launch: ${summary}\n`);
|
|
3064
|
+
process.stdout.write(` Depth: ${hj?.depth || '?'} Model: ${model}\n`);
|
|
3065
|
+
process.stdout.write(` \x1b[36mEnter\x1b[0m go \x1b[36mn\x1b[0m cancel\n\n`);
|
|
2869
3066
|
const confirm = (await ask(' > ')).trim().toLowerCase();
|
|
2870
3067
|
if (confirm === 'n' || confirm === 'no') return { next: 'main' };
|
|
2871
|
-
return { next: 'go', prompt: input };
|
|
3068
|
+
return { next: 'go', prompt: input, model };
|
|
2872
3069
|
}
|
|
2873
3070
|
|
|
2874
3071
|
// Default fallback
|
|
@@ -2983,27 +3180,36 @@ async function paletteHelpScreen(rl, ask) {
|
|
|
2983
3180
|
const DIM = '\x1b[2m';
|
|
2984
3181
|
const RESET = '\x1b[0m';
|
|
2985
3182
|
|
|
3183
|
+
const CYAN = '\x1b[36m';
|
|
2986
3184
|
const lines = [
|
|
2987
3185
|
top,
|
|
2988
|
-
row(
|
|
3186
|
+
row(`${CYAN}Keyboard Shortcuts${RESET} (single key, no Enter needed)`),
|
|
3187
|
+
sep,
|
|
3188
|
+
row(`${CYAN}Enter${RESET} Resume last session`),
|
|
3189
|
+
row(`${CYAN}n${RESET} New coding session`),
|
|
3190
|
+
row(`${CYAN}1-9${RESET} Resume session by number`),
|
|
3191
|
+
row(`${CYAN}/${RESET} Search session history`),
|
|
3192
|
+
row(`${CYAN}s${RESET} Settings`),
|
|
3193
|
+
row(`${CYAN}d${RESET} Doctor (repo diagnostics)`),
|
|
3194
|
+
row(`${CYAN}t${RESET} Team`),
|
|
3195
|
+
row(`${CYAN}i${RESET} Import sessions`),
|
|
3196
|
+
row(`${CYAN}q${RESET} Quit`),
|
|
3197
|
+
row(`${CYAN}?${RESET} This help`),
|
|
3198
|
+
sep,
|
|
3199
|
+
row(`${CYAN}Typed Commands${RESET} (type then Enter)`),
|
|
2989
3200
|
sep,
|
|
2990
|
-
row(`${DIM}resume r${RESET} Resume last session`),
|
|
2991
3201
|
row(`${DIM}status${RESET} Provider health + budget`),
|
|
2992
|
-
row(`${DIM}sessions
|
|
3202
|
+
row(`${DIM}sessions${RESET} List recent sessions`),
|
|
2993
3203
|
row(`${DIM}search <query>${RESET} Search session history`),
|
|
2994
|
-
row(`${DIM}budget
|
|
2995
|
-
row(`${DIM}health
|
|
2996
|
-
row(`${DIM}doctor d${RESET} Repo diagnostics`),
|
|
2997
|
-
row(`${DIM}settings s${RESET} Settings screen`),
|
|
2998
|
-
row(`${DIM}team t${RESET} Team screen`),
|
|
2999
|
-
row(`${DIM}help ?${RESET} Show this help`),
|
|
3000
|
-
row(`${DIM}quit q${RESET} Exit`),
|
|
3204
|
+
row(`${DIM}budget${RESET} Token usage + routing`),
|
|
3205
|
+
row(`${DIM}health${RESET} System health check`),
|
|
3001
3206
|
sep,
|
|
3002
|
-
row(
|
|
3003
|
-
row(`${DIM}Questions (why/how/what) → haiku (cheap)${RESET}`),
|
|
3004
|
-
row(`${DIM}Work tasks → confirm then dispatch${RESET}`),
|
|
3207
|
+
row(`${CYAN}Natural Language${RESET} (just type)`),
|
|
3005
3208
|
sep,
|
|
3006
|
-
row(
|
|
3209
|
+
row(`Questions → quick answer (haiku)`),
|
|
3210
|
+
row(`Work tasks → HEAD evaluates, then dispatches`),
|
|
3211
|
+
sep,
|
|
3212
|
+
row(`${DIM}Enter${RESET} go back`),
|
|
3007
3213
|
bot,
|
|
3008
3214
|
];
|
|
3009
3215
|
|
|
@@ -5967,6 +6173,12 @@ async function cmdSpecialistGo(specialist, args) {
|
|
|
5967
6173
|
// ─── Entry point ─────────────────────────────────────────────────────────────
|
|
5968
6174
|
|
|
5969
6175
|
async function main() {
|
|
6176
|
+
// Prime agent + skill registries early so detectTask and classifyInput
|
|
6177
|
+
// can match agents/skills synchronously during interactive sessions.
|
|
6178
|
+
primeAgentRegistry().catch(() => {});
|
|
6179
|
+
_primeRegistryCache().catch(() => {});
|
|
6180
|
+
_getHeadModule().catch(() => {});
|
|
6181
|
+
|
|
5970
6182
|
const args = process.argv.slice(2);
|
|
5971
6183
|
const cmd = args[0];
|
|
5972
6184
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dual-brain",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "AI orchestration across Claude + OpenAI subscriptions — smart routing, budget awareness, and dual-brain collaboration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,7 +31,10 @@
|
|
|
31
31
|
"./integrity": "./src/integrity.mjs",
|
|
32
32
|
"./prompt-audit": "./src/prompt-audit.mjs",
|
|
33
33
|
"./head": "./src/head.mjs",
|
|
34
|
-
"./templates": "./src/templates.mjs"
|
|
34
|
+
"./templates": "./src/templates.mjs",
|
|
35
|
+
"./agents": "./src/agents/registry.mjs",
|
|
36
|
+
"./collaboration": "./src/collaboration.mjs",
|
|
37
|
+
"./provider-context": "./src/provider-context.mjs"
|
|
35
38
|
},
|
|
36
39
|
"keywords": [
|
|
37
40
|
"claude-code",
|
|
@@ -102,6 +105,9 @@
|
|
|
102
105
|
"src/prompt-audit.mjs",
|
|
103
106
|
"src/head.mjs",
|
|
104
107
|
"src/templates.mjs",
|
|
108
|
+
"src/agents/registry.mjs",
|
|
109
|
+
"src/collaboration.mjs",
|
|
110
|
+
"src/provider-context.mjs",
|
|
105
111
|
"bin/*.mjs",
|
|
106
112
|
"hooks/enforce-tier.mjs",
|
|
107
113
|
"hooks/cost-logger.mjs",
|