dual-brain 0.3.2 → 0.3.4
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 +93 -2
- package/dist/src/auto-handoff.d.ts +76 -0
- package/dist/src/auto-handoff.js +361 -0
- package/dist/src/auto-handoff.js.map +1 -0
- package/package.json +3 -2
package/bin/dual-brain.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// dual-brain — CLI entry point. Commands: init, go, think, review, status, remember, forget
|
|
2
|
+
// dual-brain — CLI entry point. Commands: init, go, think, review, status, handoff, remember, forget
|
|
3
3
|
|
|
4
4
|
import { appendFileSync, existsSync, readFileSync, mkdirSync, writeFileSync, statSync, readdirSync, unlinkSync, watch as fsWatch } from 'node:fs';
|
|
5
5
|
import { join, dirname, basename, extname } from 'node:path';
|
|
@@ -294,6 +294,9 @@ Commands:
|
|
|
294
294
|
--dry-run Show routing decision without executing
|
|
295
295
|
--files a.mjs,b.mjs Provide file context for risk classification
|
|
296
296
|
--verbose, -v Print routing trace (intent, risk, health, model selection)
|
|
297
|
+
handoff Cross-provider switch (auto-detect limited provider)
|
|
298
|
+
--to claude|codex Force handoff to a specific provider
|
|
299
|
+
--show Show current handoff context without switching
|
|
297
300
|
think "question" Multi-round architecture decision with dual-brain
|
|
298
301
|
pr Show PR status for current branch
|
|
299
302
|
pr create Create PR from current branch with auto-generated description
|
|
@@ -1138,6 +1141,93 @@ const PROVIDER_MODEL_CLASSES = {
|
|
|
1138
1141
|
openai: ['gpt-4.1-mini', 'gpt-4.1', 'gpt-5.2', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.4', 'gpt-5.5'],
|
|
1139
1142
|
};
|
|
1140
1143
|
|
|
1144
|
+
async function cmdHandoff(args = []) {
|
|
1145
|
+
const cwd = process.cwd();
|
|
1146
|
+
const fxH = await getFx();
|
|
1147
|
+
const toProvider = flag(args, '--to');
|
|
1148
|
+
const showOnly = args.includes('--show');
|
|
1149
|
+
|
|
1150
|
+
let autoHandoff;
|
|
1151
|
+
try {
|
|
1152
|
+
autoHandoff = await import('../dist/src/auto-handoff.js');
|
|
1153
|
+
} catch (e) {
|
|
1154
|
+
err(`Could not load auto-handoff module: ${e.message}`);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const { executeHandoff, detectLimitReached, exportSessionContext, getHandoffUX } = autoHandoff;
|
|
1158
|
+
|
|
1159
|
+
// --show: display current handoff context without switching
|
|
1160
|
+
if (showOnly) {
|
|
1161
|
+
const context = exportSessionContext(cwd);
|
|
1162
|
+
console.log(box('Handoff Context'));
|
|
1163
|
+
console.log('');
|
|
1164
|
+
if (context) {
|
|
1165
|
+
console.log(JSON.stringify(context, null, 2));
|
|
1166
|
+
} else {
|
|
1167
|
+
fxH.info('No active session context to export.');
|
|
1168
|
+
}
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const profile = loadProfile(cwd);
|
|
1173
|
+
const providers = getAvailableProviders(profile);
|
|
1174
|
+
|
|
1175
|
+
if (providers.length < 2 && !toProvider) {
|
|
1176
|
+
fxH.warn('Only one provider configured — nothing to hand off to.');
|
|
1177
|
+
fxH.info('Run: dual-brain init to configure a second provider.');
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Detect which provider is limited
|
|
1182
|
+
const limitStatus = detectLimitReached(cwd);
|
|
1183
|
+
const ux = getHandoffUX(limitStatus, toProvider || null);
|
|
1184
|
+
|
|
1185
|
+
// Display UX message
|
|
1186
|
+
if (ux.title) console.log(box(ux.title));
|
|
1187
|
+
if (ux.message) {
|
|
1188
|
+
console.log('');
|
|
1189
|
+
fxH.info(ux.message);
|
|
1190
|
+
}
|
|
1191
|
+
if (ux.detail) {
|
|
1192
|
+
fxH.dim(ux.detail);
|
|
1193
|
+
}
|
|
1194
|
+
console.log('');
|
|
1195
|
+
|
|
1196
|
+
// If --to is specified, force handoff regardless of limit status
|
|
1197
|
+
if (toProvider) {
|
|
1198
|
+
const target = toProvider.toLowerCase();
|
|
1199
|
+
if (target !== 'claude' && target !== 'codex') {
|
|
1200
|
+
err('--to must be "claude" or "codex"');
|
|
1201
|
+
}
|
|
1202
|
+
const fromProvider = target === 'codex' ? 'anthropic' : 'openai';
|
|
1203
|
+
console.log(` ⚡ Switching to ${target}...`);
|
|
1204
|
+
console.log('');
|
|
1205
|
+
const { spawnHandoff } = autoHandoff;
|
|
1206
|
+
const result = spawnHandoff({ fromProvider, cwd, auto: true, interactive: true });
|
|
1207
|
+
if (!result.success) {
|
|
1208
|
+
fxH.error(result.message);
|
|
1209
|
+
}
|
|
1210
|
+
// If spawn succeeded, the child process takes over — we don't return
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// Auto-detect: if a provider is limited, switch to the other
|
|
1215
|
+
if (limitStatus.limited) {
|
|
1216
|
+
const target = limitStatus.switchTo;
|
|
1217
|
+
const fromProvider = limitStatus.provider;
|
|
1218
|
+
console.log(` ⚡ ${fromProvider} limit reached → switching to ${target}...`);
|
|
1219
|
+
console.log('');
|
|
1220
|
+
const { spawnHandoff } = autoHandoff;
|
|
1221
|
+
const result = spawnHandoff({ fromProvider, cwd, auto: true, interactive: true });
|
|
1222
|
+
if (!result.success) {
|
|
1223
|
+
fxH.error(result.message);
|
|
1224
|
+
}
|
|
1225
|
+
} else {
|
|
1226
|
+
fxH.success('No provider is currently limited. No handoff needed.');
|
|
1227
|
+
fxH.dim('Use --to claude or --to codex to force a switch.');
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1141
1231
|
function cmdHot(providerArg) {
|
|
1142
1232
|
if (!providerArg) err('Usage: dual-brain hot <provider> (claude | openai)');
|
|
1143
1233
|
const provider = providerArg.toLowerCase();
|
|
@@ -6769,6 +6859,7 @@ async function main() {
|
|
|
6769
6859
|
if (cmd === 'ship') { await cmdShip(); return; }
|
|
6770
6860
|
if (cmd === 'pr') { await cmdPR(args.slice(1)); return; }
|
|
6771
6861
|
if (cmd === 'status') { await cmdStatus(args.slice(1)); return; }
|
|
6862
|
+
if (cmd === 'handoff') { await cmdHandoff(args.slice(1)); return; }
|
|
6772
6863
|
if (cmd === 'hot') { cmdHot(args[1]); return; }
|
|
6773
6864
|
if (cmd === 'cool') { cmdCool(args[1]); return; }
|
|
6774
6865
|
if (cmd === 'remember') { cmdRemember(args[1]); return; }
|
|
@@ -6832,7 +6923,7 @@ fi
|
|
|
6832
6923
|
// If cmd is not a recognized subcommand, treat the entire arg list as a task.
|
|
6833
6924
|
// e.g. `dual-brain fix failing tests` → same as `dual-brain go "fix failing tests"`
|
|
6834
6925
|
const KNOWN_COMMANDS = new Set([
|
|
6835
|
-
'init', 'install', 'uninstall', 'auth', 'go', 'do', 'plan', 'ship', 'think', 'review', 'pr', 'status', 'hot', 'cool',
|
|
6926
|
+
'init', 'install', 'uninstall', 'auth', 'go', 'do', 'plan', 'ship', 'think', 'review', 'pr', 'status', 'handoff', 'hot', 'cool',
|
|
6836
6927
|
'remember', 'forget', 'break-glass', 'specialists', 'search', 'shell-hook', 'watch',
|
|
6837
6928
|
'--help', '-h', '--version', '-v',
|
|
6838
6929
|
...Object.keys(loadSpecialistRegistry()),
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto-handoff.ts — Automatic cross-provider handoff system.
|
|
3
|
+
*
|
|
4
|
+
* When one provider hits its rate limit, this module exports the conversation
|
|
5
|
+
* context and switches to the other provider seamlessly.
|
|
6
|
+
*/
|
|
7
|
+
export interface LimitStatus {
|
|
8
|
+
limited: boolean;
|
|
9
|
+
provider: string;
|
|
10
|
+
resetsAt?: string;
|
|
11
|
+
otherAvailable: boolean;
|
|
12
|
+
otherProvider?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface HandoffContext {
|
|
15
|
+
objective: string;
|
|
16
|
+
filesChanged: string[];
|
|
17
|
+
recentDecisions: Array<{
|
|
18
|
+
model: string;
|
|
19
|
+
tier: string;
|
|
20
|
+
timestamp: string;
|
|
21
|
+
}>;
|
|
22
|
+
conversationSummary: string;
|
|
23
|
+
currentPhase: string;
|
|
24
|
+
exportedAt: string;
|
|
25
|
+
}
|
|
26
|
+
export interface HandoffOpts {
|
|
27
|
+
fromProvider: string;
|
|
28
|
+
cwd?: string;
|
|
29
|
+
auto?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface HandoffResult {
|
|
32
|
+
success: boolean;
|
|
33
|
+
command?: string[];
|
|
34
|
+
contextFile?: string;
|
|
35
|
+
message: string;
|
|
36
|
+
}
|
|
37
|
+
export interface HandoffUXMessage {
|
|
38
|
+
text: string;
|
|
39
|
+
action: 'auto-switch' | 'prompt-setup' | 'wait';
|
|
40
|
+
command?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if a provider is rate-limited.
|
|
44
|
+
* Reads from provider-manager state and checks for common rate limit patterns.
|
|
45
|
+
*/
|
|
46
|
+
export declare function detectLimitReached(provider: string, cwd?: string): LimitStatus;
|
|
47
|
+
/**
|
|
48
|
+
* Read the current session state and produce a portable context object.
|
|
49
|
+
*/
|
|
50
|
+
export declare function exportSessionContext(cwd?: string): HandoffContext;
|
|
51
|
+
/**
|
|
52
|
+
* Generate a prompt string that can be fed to the other provider to resume work.
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildHandoffPrompt(context: HandoffContext): string;
|
|
55
|
+
/**
|
|
56
|
+
* Execute the full handoff flow:
|
|
57
|
+
* 1. Detect which provider to switch TO
|
|
58
|
+
* 2. Export context
|
|
59
|
+
* 3. Build the handoff prompt
|
|
60
|
+
* 4. Return the command to launch the other CLI
|
|
61
|
+
*
|
|
62
|
+
* Does NOT actually spawn the process (caller decides).
|
|
63
|
+
*/
|
|
64
|
+
export declare function executeHandoff(opts: HandoffOpts): HandoffResult;
|
|
65
|
+
/**
|
|
66
|
+
* Seamlessly spawn the other provider CLI with handoff context.
|
|
67
|
+
* Replaces the current process — user stays in the same terminal,
|
|
68
|
+
* conversation continues with the new provider.
|
|
69
|
+
*/
|
|
70
|
+
export declare function spawnHandoff(opts: HandoffOpts & {
|
|
71
|
+
interactive?: boolean;
|
|
72
|
+
}): HandoffResult;
|
|
73
|
+
/**
|
|
74
|
+
* Return the user-facing message for a rate limit situation.
|
|
75
|
+
*/
|
|
76
|
+
export declare function getHandoffUX(status: LimitStatus): HandoffUXMessage;
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto-handoff.ts — Automatic cross-provider handoff system.
|
|
3
|
+
*
|
|
4
|
+
* When one provider hits its rate limit, this module exports the conversation
|
|
5
|
+
* context and switches to the other provider seamlessly.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import { atomicWriteJson, readJsonSafe } from './integrity.js';
|
|
11
|
+
import { getProviderState } from './provider-manager.js';
|
|
12
|
+
import { loadSession } from './session.js';
|
|
13
|
+
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
14
|
+
const HANDOFF_DIR = '.dual-brain/handoff';
|
|
15
|
+
const HANDOFF_FILE = 'latest.json';
|
|
16
|
+
const DISPATCH_LOG = '.dual-brain/dispatch-log.ndjson';
|
|
17
|
+
/** Map provider names to CLI commands. */
|
|
18
|
+
const PROVIDER_CLI = {
|
|
19
|
+
anthropic: 'claude',
|
|
20
|
+
openai: 'codex',
|
|
21
|
+
};
|
|
22
|
+
/** Map provider names to their "other". */
|
|
23
|
+
const OTHER_PROVIDER = {
|
|
24
|
+
anthropic: 'openai',
|
|
25
|
+
openai: 'anthropic',
|
|
26
|
+
claude: 'openai',
|
|
27
|
+
};
|
|
28
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
29
|
+
function normalizeProvider(provider) {
|
|
30
|
+
return provider === 'claude' ? 'anthropic' : provider;
|
|
31
|
+
}
|
|
32
|
+
function handoffDir(cwd) {
|
|
33
|
+
return join(cwd || process.cwd(), HANDOFF_DIR);
|
|
34
|
+
}
|
|
35
|
+
function handoffFile(cwd) {
|
|
36
|
+
return join(handoffDir(cwd), HANDOFF_FILE);
|
|
37
|
+
}
|
|
38
|
+
function ensureHandoffDir(cwd) {
|
|
39
|
+
const dir = handoffDir(cwd);
|
|
40
|
+
if (!existsSync(dir)) {
|
|
41
|
+
mkdirSync(dir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Read recent routing decisions from the dispatch log.
|
|
46
|
+
*/
|
|
47
|
+
function readRecentDecisions(cwd, limit = 5) {
|
|
48
|
+
try {
|
|
49
|
+
const logPath = join(cwd || process.cwd(), DISPATCH_LOG);
|
|
50
|
+
if (!existsSync(logPath))
|
|
51
|
+
return [];
|
|
52
|
+
const content = readFileSync(logPath, 'utf8');
|
|
53
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
54
|
+
// Take the last N entries
|
|
55
|
+
const recent = lines.slice(-limit);
|
|
56
|
+
const decisions = [];
|
|
57
|
+
for (const line of recent) {
|
|
58
|
+
try {
|
|
59
|
+
const entry = JSON.parse(line);
|
|
60
|
+
decisions.push({
|
|
61
|
+
model: entry.model || 'unknown',
|
|
62
|
+
tier: entry.tier || entry.provider || 'unknown',
|
|
63
|
+
timestamp: entry.timestamp || new Date().toISOString(),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Skip malformed lines
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return decisions;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Format a relative time string for when the limit resets.
|
|
78
|
+
*/
|
|
79
|
+
function formatResetTime(resetsAt) {
|
|
80
|
+
const delta = new Date(resetsAt).getTime() - Date.now();
|
|
81
|
+
if (delta <= 0)
|
|
82
|
+
return 'now';
|
|
83
|
+
const minutes = Math.ceil(delta / 60_000);
|
|
84
|
+
if (minutes < 60)
|
|
85
|
+
return `${minutes}m`;
|
|
86
|
+
const hours = Math.ceil(minutes / 60);
|
|
87
|
+
return `${hours}h`;
|
|
88
|
+
}
|
|
89
|
+
// ─── Exported Functions ─────────────────────────────────────────────────────
|
|
90
|
+
/**
|
|
91
|
+
* Check if a provider is rate-limited.
|
|
92
|
+
* Reads from provider-manager state and checks for common rate limit patterns.
|
|
93
|
+
*/
|
|
94
|
+
export function detectLimitReached(provider, cwd) {
|
|
95
|
+
try {
|
|
96
|
+
const normalized = normalizeProvider(provider);
|
|
97
|
+
const state = getProviderState(normalized, cwd);
|
|
98
|
+
const other = OTHER_PROVIDER[normalized] || (normalized === 'anthropic' ? 'openai' : 'anthropic');
|
|
99
|
+
const limited = state.status === 'rate-limited' || state.status === 'down';
|
|
100
|
+
let otherAvailable = false;
|
|
101
|
+
if (limited) {
|
|
102
|
+
const otherState = getProviderState(other, cwd);
|
|
103
|
+
otherAvailable = otherState.status === 'healthy' || otherState.status === 'degraded';
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
limited,
|
|
107
|
+
provider: normalized,
|
|
108
|
+
resetsAt: state.cooldownUntil || undefined,
|
|
109
|
+
otherAvailable,
|
|
110
|
+
otherProvider: other,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return {
|
|
115
|
+
limited: false,
|
|
116
|
+
provider: normalizeProvider(provider),
|
|
117
|
+
otherAvailable: false,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Read the current session state and produce a portable context object.
|
|
123
|
+
*/
|
|
124
|
+
export function exportSessionContext(cwd) {
|
|
125
|
+
try {
|
|
126
|
+
const session = loadSession(cwd || process.cwd());
|
|
127
|
+
const decisions = readRecentDecisions(cwd);
|
|
128
|
+
const objective = session?.objective || '(no objective recorded)';
|
|
129
|
+
const filesChanged = session?.filesChanged || [];
|
|
130
|
+
const currentPhase = session?.nextAction || 'unknown';
|
|
131
|
+
// Build a conversation summary from session state
|
|
132
|
+
const summaryParts = [];
|
|
133
|
+
if (session?.objective) {
|
|
134
|
+
summaryParts.push(`Working on: ${session.objective}`);
|
|
135
|
+
}
|
|
136
|
+
if (session?.lastResult?.summary) {
|
|
137
|
+
summaryParts.push(`Last result: ${session.lastResult.summary}`);
|
|
138
|
+
}
|
|
139
|
+
if (session?.commandsRun?.length) {
|
|
140
|
+
const recentCmds = session.commandsRun.slice(-3);
|
|
141
|
+
summaryParts.push(`Recent commands: ${recentCmds.join(', ')}`);
|
|
142
|
+
}
|
|
143
|
+
if (session?.branch) {
|
|
144
|
+
summaryParts.push(`Branch: ${session.branch}`);
|
|
145
|
+
}
|
|
146
|
+
const conversationSummary = summaryParts.length > 0
|
|
147
|
+
? summaryParts.join('. ')
|
|
148
|
+
: '(no session context available)';
|
|
149
|
+
return {
|
|
150
|
+
objective,
|
|
151
|
+
filesChanged,
|
|
152
|
+
recentDecisions: decisions,
|
|
153
|
+
conversationSummary,
|
|
154
|
+
currentPhase,
|
|
155
|
+
exportedAt: new Date().toISOString(),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return {
|
|
160
|
+
objective: '(failed to export context)',
|
|
161
|
+
filesChanged: [],
|
|
162
|
+
recentDecisions: [],
|
|
163
|
+
conversationSummary: '(export error)',
|
|
164
|
+
currentPhase: 'unknown',
|
|
165
|
+
exportedAt: new Date().toISOString(),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Generate a prompt string that can be fed to the other provider to resume work.
|
|
171
|
+
*/
|
|
172
|
+
export function buildHandoffPrompt(context) {
|
|
173
|
+
const lines = [
|
|
174
|
+
'# Handoff Context — Resuming Work From Another Provider',
|
|
175
|
+
'',
|
|
176
|
+
'## Objective',
|
|
177
|
+
context.objective,
|
|
178
|
+
'',
|
|
179
|
+
];
|
|
180
|
+
if (context.filesChanged.length > 0) {
|
|
181
|
+
lines.push('## Files Changed');
|
|
182
|
+
for (const f of context.filesChanged) {
|
|
183
|
+
lines.push(`- ${f}`);
|
|
184
|
+
}
|
|
185
|
+
lines.push('');
|
|
186
|
+
}
|
|
187
|
+
if (context.conversationSummary && context.conversationSummary !== '(no session context available)') {
|
|
188
|
+
lines.push('## Session Summary');
|
|
189
|
+
lines.push(context.conversationSummary);
|
|
190
|
+
lines.push('');
|
|
191
|
+
}
|
|
192
|
+
if (context.recentDecisions.length > 0) {
|
|
193
|
+
lines.push('## Recent Routing Decisions');
|
|
194
|
+
for (const d of context.recentDecisions) {
|
|
195
|
+
lines.push(`- ${d.model} (${d.tier}) at ${d.timestamp}`);
|
|
196
|
+
}
|
|
197
|
+
lines.push('');
|
|
198
|
+
}
|
|
199
|
+
if (context.currentPhase && context.currentPhase !== 'unknown') {
|
|
200
|
+
lines.push('## Current Phase');
|
|
201
|
+
lines.push(context.currentPhase);
|
|
202
|
+
lines.push('');
|
|
203
|
+
}
|
|
204
|
+
lines.push('## Instructions');
|
|
205
|
+
lines.push('Continue working on the objective above. The previous provider hit its rate limit.');
|
|
206
|
+
lines.push('Pick up where it left off — do not ask the user to repeat themselves.');
|
|
207
|
+
lines.push(`Context exported at: ${context.exportedAt}`);
|
|
208
|
+
return lines.join('\n');
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Execute the full handoff flow:
|
|
212
|
+
* 1. Detect which provider to switch TO
|
|
213
|
+
* 2. Export context
|
|
214
|
+
* 3. Build the handoff prompt
|
|
215
|
+
* 4. Return the command to launch the other CLI
|
|
216
|
+
*
|
|
217
|
+
* Does NOT actually spawn the process (caller decides).
|
|
218
|
+
*/
|
|
219
|
+
export function executeHandoff(opts) {
|
|
220
|
+
try {
|
|
221
|
+
const { fromProvider, cwd, auto } = opts;
|
|
222
|
+
const normalized = normalizeProvider(fromProvider);
|
|
223
|
+
const other = OTHER_PROVIDER[normalized] || (normalized === 'anthropic' ? 'openai' : 'anthropic');
|
|
224
|
+
// Check that the other provider is available
|
|
225
|
+
const otherState = getProviderState(other, cwd);
|
|
226
|
+
if (otherState.status === 'rate-limited' || otherState.status === 'down') {
|
|
227
|
+
return {
|
|
228
|
+
success: false,
|
|
229
|
+
message: `Cannot handoff: ${other} is also ${otherState.status}. Both providers unavailable.`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
// Export session context
|
|
233
|
+
const context = exportSessionContext(cwd);
|
|
234
|
+
// Build handoff prompt
|
|
235
|
+
const prompt = buildHandoffPrompt(context);
|
|
236
|
+
// Write context to handoff file
|
|
237
|
+
ensureHandoffDir(cwd);
|
|
238
|
+
const contextFilePath = handoffFile(cwd);
|
|
239
|
+
const handoffData = {
|
|
240
|
+
context,
|
|
241
|
+
prompt,
|
|
242
|
+
fromProvider: normalized,
|
|
243
|
+
toProvider: other,
|
|
244
|
+
auto: auto ?? false,
|
|
245
|
+
createdAt: new Date().toISOString(),
|
|
246
|
+
};
|
|
247
|
+
atomicWriteJson(contextFilePath, handoffData);
|
|
248
|
+
// Build the CLI command
|
|
249
|
+
const cli = PROVIDER_CLI[other] || other;
|
|
250
|
+
const command = [cli, '--resume', contextFilePath];
|
|
251
|
+
const autoLabel = auto ? ' (auto)' : '';
|
|
252
|
+
return {
|
|
253
|
+
success: true,
|
|
254
|
+
command,
|
|
255
|
+
contextFile: contextFilePath,
|
|
256
|
+
message: `Handoff${autoLabel}: ${normalized} → ${other}. Context saved to ${contextFilePath}`,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
message: `Handoff failed: ${message}`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Seamlessly spawn the other provider CLI with handoff context.
|
|
269
|
+
* Replaces the current process — user stays in the same terminal,
|
|
270
|
+
* conversation continues with the new provider.
|
|
271
|
+
*/
|
|
272
|
+
export function spawnHandoff(opts) {
|
|
273
|
+
try {
|
|
274
|
+
const result = executeHandoff(opts);
|
|
275
|
+
if (!result.success || !result.command || !result.contextFile)
|
|
276
|
+
return result;
|
|
277
|
+
const [cli, ...cliArgs] = result.command;
|
|
278
|
+
const contextData = readJsonSafe(result.contextFile);
|
|
279
|
+
const prompt = contextData?.prompt || '';
|
|
280
|
+
// Write the prompt to a temp file the CLI can read
|
|
281
|
+
const promptFile = join(opts.cwd || process.cwd(), '.dual-brain/handoff/prompt.md');
|
|
282
|
+
writeFileSync(promptFile, prompt, 'utf8');
|
|
283
|
+
// Build the command based on which CLI we're launching
|
|
284
|
+
let spawnArgs;
|
|
285
|
+
if (cli === 'codex') {
|
|
286
|
+
// Codex: pass prompt directly
|
|
287
|
+
spawnArgs = ['-p', prompt.slice(0, 4000)];
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
// Claude: use -p flag with prompt
|
|
291
|
+
spawnArgs = ['-p', prompt.slice(0, 4000), '--no-input'];
|
|
292
|
+
}
|
|
293
|
+
if (opts.interactive !== false) {
|
|
294
|
+
// Spawn with inherited stdio — user stays in same terminal
|
|
295
|
+
const child = spawn(cli, spawnArgs, {
|
|
296
|
+
stdio: 'inherit',
|
|
297
|
+
cwd: opts.cwd || process.cwd(),
|
|
298
|
+
});
|
|
299
|
+
child.on('exit', (code) => {
|
|
300
|
+
process.exit(code ?? 0);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
success: true,
|
|
305
|
+
command: [cli, ...spawnArgs],
|
|
306
|
+
contextFile: result.contextFile,
|
|
307
|
+
message: `⚡ Switching to ${cli}...`,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
312
|
+
return {
|
|
313
|
+
success: false,
|
|
314
|
+
message: `Auto-spawn failed: ${message}. Run manually: dual-brain handoff --show`,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Return the user-facing message for a rate limit situation.
|
|
320
|
+
*/
|
|
321
|
+
export function getHandoffUX(status) {
|
|
322
|
+
const providerLabel = PROVIDER_CLI[status.provider] || status.provider;
|
|
323
|
+
const otherLabel = status.otherProvider ? (PROVIDER_CLI[status.otherProvider] || status.otherProvider) : 'other';
|
|
324
|
+
// Both providers limited
|
|
325
|
+
if (status.limited && !status.otherAvailable) {
|
|
326
|
+
// Check if we have a reset time
|
|
327
|
+
if (status.resetsAt) {
|
|
328
|
+
const timeStr = formatResetTime(status.resetsAt);
|
|
329
|
+
return {
|
|
330
|
+
text: `⚡ Both providers at limit. Resets in ${timeStr}.`,
|
|
331
|
+
action: 'wait',
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
text: `⚡ Both providers at limit. Resets in ~5m.`,
|
|
336
|
+
action: 'wait',
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
// Limited but other is available
|
|
340
|
+
if (status.limited && status.otherAvailable && status.otherProvider) {
|
|
341
|
+
return {
|
|
342
|
+
text: `⚡ ${providerLabel} limit reached → switching to ${otherLabel}`,
|
|
343
|
+
action: 'auto-switch',
|
|
344
|
+
command: `dual-brain handoff --to ${status.otherProvider}`,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
// Limited but other provider not configured
|
|
348
|
+
if (status.limited && !status.otherAvailable) {
|
|
349
|
+
return {
|
|
350
|
+
text: `⚡ ${providerLabel} limit reached. Set up ${otherLabel} to continue? Run: dual-brain init`,
|
|
351
|
+
action: 'prompt-setup',
|
|
352
|
+
command: 'dual-brain init',
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
// Not limited (shouldn't normally be called, but handle gracefully)
|
|
356
|
+
return {
|
|
357
|
+
text: `${providerLabel} is operating normally.`,
|
|
358
|
+
action: 'wait',
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=auto-handoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-handoff.js","sourceRoot":"","sources":["../../src/auto-handoff.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dual-brain",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "AI orchestration across Claude + OpenAI subscriptions — smart routing, budget awareness, and dual-brain collaboration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"./model-profiles": { "types": "./dist/src/model-profiles.d.ts", "default": "./dist/src/model-profiles.js" },
|
|
19
19
|
"./signal": { "types": "./dist/src/signal.d.ts", "default": "./dist/src/signal.js" },
|
|
20
20
|
"./routing-advisor": { "types": "./dist/src/routing-advisor.d.ts", "default": "./dist/src/routing-advisor.js" },
|
|
21
|
-
"./types": { "types": "./dist/src/types.d.ts", "default": "./dist/src/types.js" }
|
|
21
|
+
"./types": { "types": "./dist/src/types.d.ts", "default": "./dist/src/types.js" },
|
|
22
|
+
"./auto-handoff": { "types": "./dist/src/auto-handoff.d.ts", "default": "./dist/src/auto-handoff.js" }
|
|
22
23
|
},
|
|
23
24
|
"keywords": [
|
|
24
25
|
"claude-code",
|