dual-brain 7.1.15 → 7.1.16
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 +0 -21
- package/package.json +2 -2
- package/src/decide.mjs +56 -56
- package/src/dispatch.mjs +77 -16
- package/src/profile.mjs +2 -2
- package/src/session.mjs +5 -3
package/bin/dual-brain.mjs
CHANGED
|
@@ -391,27 +391,6 @@ async function cmdGo(args) {
|
|
|
391
391
|
}, cwd);
|
|
392
392
|
} else {
|
|
393
393
|
result = await dispatch({ decision, prompt, files, cwd });
|
|
394
|
-
if (result.status === 'completed' && result.type === 'native-agent') {
|
|
395
|
-
const nd = result.nativeDispatch || {};
|
|
396
|
-
const promptPreview = (nd.prompt || prompt).slice(0, 100);
|
|
397
|
-
const promptSuffix = (nd.prompt || prompt).length > 100 ? '...' : '';
|
|
398
|
-
console.log(`\nRouted: ${decision.provider}/${nd.model || decision.model} (${decision.tier})`);
|
|
399
|
-
console.log('To dispatch, use the Agent tool with:');
|
|
400
|
-
console.log(` model: ${nd.model || decision.model}`);
|
|
401
|
-
console.log(` prompt: ${promptPreview}${promptSuffix}`);
|
|
402
|
-
if (nd.isolation) console.log(` isolation: ${nd.isolation}`);
|
|
403
|
-
if (nd.maxTurns) console.log(` maxTurns: ${nd.maxTurns}`);
|
|
404
|
-
saveSession({
|
|
405
|
-
objective: prompt,
|
|
406
|
-
branch: null,
|
|
407
|
-
filesChanged: files,
|
|
408
|
-
commandsRun: [`dual-brain go "${prompt}"`],
|
|
409
|
-
lastResult: { status: 'success', summary: `native-agent routed to ${nd.model || decision.model}` },
|
|
410
|
-
provider: decision.provider,
|
|
411
|
-
nextAction: null,
|
|
412
|
-
}, cwd);
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
394
|
const statusLine = result.status === 'completed' ? 'Done' : `Failed (exit ${result.exitCode})`;
|
|
416
395
|
console.log(`\n${statusLine} in ${(result.durationMs / 1000).toFixed(1)}s`);
|
|
417
396
|
if (result.summary) console.log(result.summary);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dual-brain",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.16",
|
|
4
4
|
"description": "AI orchestration across Claude + OpenAI subscriptions — smart routing, budget awareness, and dual-brain collaboration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"url": "https://github.com/1xmint/dual-brain.git"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
|
-
"test": "node hooks/test-orchestrator.mjs",
|
|
41
|
+
"test": "node .claude/hooks/test-orchestrator.mjs",
|
|
42
42
|
"test:core": "node --test src/test.mjs",
|
|
43
43
|
"postinstall": "echo 'dual-brain installed. Run: dual-brain install (in your project) to set up hooks.'"
|
|
44
44
|
},
|
package/src/decide.mjs
CHANGED
|
@@ -71,49 +71,31 @@ const MODEL_CAPABILITIES = {
|
|
|
71
71
|
effortLevels: ['low', 'medium', 'high'],
|
|
72
72
|
costTier: 'medium',
|
|
73
73
|
},
|
|
74
|
-
'gpt-
|
|
74
|
+
'gpt-4o': {
|
|
75
75
|
provider: 'openai',
|
|
76
|
-
tierFit: ['
|
|
77
|
-
contextWindow:
|
|
76
|
+
tierFit: ['execute', 'think'],
|
|
77
|
+
contextWindow: 128_000,
|
|
78
|
+
strengths: ['refactor', 'debug', 'code-generation', 'test', 'multimodal'],
|
|
79
|
+
weaknesses: ['cost vs mini'],
|
|
80
|
+
effortLevels: ['low', 'medium', 'high'],
|
|
78
81
|
costTier: 'medium',
|
|
79
|
-
strengths: ['code-generation', 'analysis'],
|
|
80
|
-
weaknesses: [],
|
|
81
|
-
effortLevels: null,
|
|
82
82
|
},
|
|
83
|
-
'gpt-
|
|
83
|
+
'gpt-4o-mini': {
|
|
84
84
|
provider: 'openai',
|
|
85
85
|
tierFit: ['search'],
|
|
86
|
-
contextWindow:
|
|
87
|
-
costTier: '
|
|
88
|
-
strengths: ['quick-tasks', 'search'],
|
|
86
|
+
contextWindow: 128_000,
|
|
87
|
+
costTier: 'cheap',
|
|
88
|
+
strengths: ['quick-tasks', 'search', 'classification'],
|
|
89
89
|
weaknesses: ['complex-edits', 'architecture'],
|
|
90
90
|
effortLevels: null,
|
|
91
91
|
},
|
|
92
|
-
'
|
|
93
|
-
provider: 'openai',
|
|
94
|
-
tierFit: ['execute'],
|
|
95
|
-
contextWindow: 200_000,
|
|
96
|
-
costTier: 'medium',
|
|
97
|
-
strengths: ['code-generation', 'refactoring'],
|
|
98
|
-
weaknesses: ['architecture', 'security'],
|
|
99
|
-
effortLevels: null,
|
|
100
|
-
},
|
|
101
|
-
'gpt-5.4': {
|
|
102
|
-
provider: 'openai',
|
|
103
|
-
tierFit: ['execute', 'think'],
|
|
104
|
-
contextWindow: 1_050_000,
|
|
105
|
-
strengths: ['refactor', 'debug', 'code-generation', 'test'],
|
|
106
|
-
weaknesses: ['cost'],
|
|
107
|
-
effortLevels: ['low', 'medium', 'high', 'xhigh'],
|
|
108
|
-
costTier: 'medium',
|
|
109
|
-
},
|
|
110
|
-
'gpt-5.5': {
|
|
92
|
+
'o3': {
|
|
111
93
|
provider: 'openai',
|
|
112
94
|
tierFit: ['think'],
|
|
113
|
-
contextWindow:
|
|
114
|
-
strengths: ['architecture', 'security', 'review', 'planning', 'complex-debug'],
|
|
95
|
+
contextWindow: 200_000,
|
|
96
|
+
strengths: ['architecture', 'security', 'review', 'planning', 'complex-debug', 'deep-reasoning'],
|
|
115
97
|
weaknesses: ['cost', 'latency'],
|
|
116
|
-
effortLevels: ['low', 'medium', 'high'
|
|
98
|
+
effortLevels: ['low', 'medium', 'high'],
|
|
117
99
|
costTier: 'expensive',
|
|
118
100
|
},
|
|
119
101
|
};
|
|
@@ -127,9 +109,9 @@ const CLAUDE_MODELS_BY_PLAN = {
|
|
|
127
109
|
};
|
|
128
110
|
|
|
129
111
|
const OPENAI_MODELS_BY_PLAN = {
|
|
130
|
-
'$20': ['gpt-
|
|
131
|
-
'$100': ['gpt-
|
|
132
|
-
'$200': ['gpt-
|
|
112
|
+
'$20': ['gpt-4o-mini', 'gpt-4.1-mini', 'gpt-4.1', 'gpt-4o'],
|
|
113
|
+
'$100': ['gpt-4o-mini', 'gpt-4.1-mini', 'gpt-4.1', 'gpt-4o', 'o4-mini', 'o3'],
|
|
114
|
+
'$200': ['gpt-4o-mini', 'gpt-4.1-mini', 'gpt-4.1', 'gpt-4o', 'o4-mini', 'o3'],
|
|
133
115
|
};
|
|
134
116
|
|
|
135
117
|
// Token fallback estimates per tier (no real usage data)
|
|
@@ -188,9 +170,9 @@ function getHealthScores(tier, cwd) {
|
|
|
188
170
|
const claudeClass = tier === 'search' ? 'haiku'
|
|
189
171
|
: tier === 'think' ? 'opus'
|
|
190
172
|
: 'sonnet';
|
|
191
|
-
const openaiClass = tier === 'search' ? 'gpt-
|
|
192
|
-
: tier === 'think' ? '
|
|
193
|
-
: 'gpt-
|
|
173
|
+
const openaiClass = tier === 'search' ? 'gpt-4o-mini'
|
|
174
|
+
: tier === 'think' ? 'o3'
|
|
175
|
+
: 'gpt-4o';
|
|
194
176
|
|
|
195
177
|
// Trigger cooldown expiry check (transitions hot→probing automatically)
|
|
196
178
|
checkCooldown('claude', claudeClass, cwd);
|
|
@@ -208,26 +190,35 @@ function getHealthScores(tier, cwd) {
|
|
|
208
190
|
* Return true if both providers should analyze this task.
|
|
209
191
|
* Requires: (critical risk OR architecture/security intent OR complex+high-risk)
|
|
210
192
|
* AND profile has both providers available with dual mode enabled.
|
|
211
|
-
*
|
|
193
|
+
*
|
|
194
|
+
* designImpact bypasses the hasBothProviders check — it is a mandatory review
|
|
195
|
+
* gate, not optional collaboration. When only one provider is available the
|
|
196
|
+
* caller should check degradedDualBrain on the decision output.
|
|
197
|
+
* @param {{ intent?: string, risk?: string, complexity?: string, designImpact?: boolean }} detection
|
|
212
198
|
* @param {object} profile
|
|
213
199
|
* @returns {boolean}
|
|
214
200
|
*/
|
|
215
201
|
export function shouldDualBrain(detection, profile) {
|
|
216
202
|
const { intent = '', risk = 'low', complexity = 'simple', designImpact = false } = detection;
|
|
217
203
|
const dualEnabled = profile?.dual_brain_enabled !== false;
|
|
204
|
+
if (!dualEnabled) return false;
|
|
205
|
+
|
|
218
206
|
const hasBothProviders = !!(
|
|
219
207
|
profile?.providers?.claude?.enabled &&
|
|
220
208
|
profile?.providers?.claude?.plan &&
|
|
221
209
|
profile?.providers?.openai?.enabled &&
|
|
222
210
|
profile?.providers?.openai?.plan
|
|
223
211
|
);
|
|
224
|
-
if (!dualEnabled || !hasBothProviders) return false;
|
|
225
212
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
213
|
+
if (designImpact) return true;
|
|
214
|
+
|
|
215
|
+
if (!hasBothProviders) return false;
|
|
216
|
+
|
|
217
|
+
const criticalRisk = risk === 'critical';
|
|
218
|
+
const archOrSecurity = ['architecture', 'security'].includes(intent);
|
|
219
|
+
const complexHighRisk = complexity === 'complex' && risk === 'high';
|
|
229
220
|
|
|
230
|
-
return criticalRisk || archOrSecurity || complexHighRisk
|
|
221
|
+
return criticalRisk || archOrSecurity || complexHighRisk;
|
|
231
222
|
}
|
|
232
223
|
|
|
233
224
|
// ─── Internal: select model for provider ─────────────────────────────────────
|
|
@@ -251,18 +242,18 @@ function pickOpenAIModel(detection, available) {
|
|
|
251
242
|
const needsMini = SEARCH_INTENTS.includes(intent) && effort === 'low';
|
|
252
243
|
const needsCodex = ['refactor', 'debug'].includes(intent) && complexity !== 'trivial';
|
|
253
244
|
|
|
254
|
-
const pref = needsTop ? '
|
|
255
|
-
: needsMini ? 'gpt-
|
|
256
|
-
: needsCodex ? 'gpt-
|
|
257
|
-
: 'gpt-
|
|
245
|
+
const pref = needsTop ? 'o3'
|
|
246
|
+
: needsMini ? 'gpt-4o-mini'
|
|
247
|
+
: needsCodex ? 'gpt-4o'
|
|
248
|
+
: 'gpt-4o';
|
|
258
249
|
|
|
259
250
|
// Walk down rank until we find an available model
|
|
260
|
-
const rank = ['gpt-
|
|
251
|
+
const rank = ['gpt-4o-mini', 'gpt-4.1-mini', 'gpt-4.1', 'gpt-4o', 'o4-mini', 'o3'];
|
|
261
252
|
const idx = rank.indexOf(pref);
|
|
262
253
|
for (let i = idx; i >= 0; i--) {
|
|
263
254
|
if (available.includes(rank[i])) return rank[i];
|
|
264
255
|
}
|
|
265
|
-
return available[0] ?? 'gpt-
|
|
256
|
+
return available[0] ?? 'gpt-4o-mini';
|
|
266
257
|
}
|
|
267
258
|
|
|
268
259
|
function applyHealthDowngrade(model, score, provider, available, isHighStakes) {
|
|
@@ -280,14 +271,14 @@ function applyHealthDowngrade(model, score, provider, available, isHighStakes) {
|
|
|
280
271
|
}
|
|
281
272
|
return available[0] ?? 'haiku';
|
|
282
273
|
} else {
|
|
283
|
-
const oaiRank = ['gpt-
|
|
274
|
+
const oaiRank = ['gpt-4o-mini', 'gpt-4.1-mini', 'gpt-4.1', 'gpt-4o', 'o4-mini', 'o3'];
|
|
284
275
|
const idx = oaiRank.indexOf(model);
|
|
285
276
|
const steps = score === 0 ? 2 : 1;
|
|
286
277
|
const downIdx = Math.max(0, idx - steps);
|
|
287
278
|
for (let i = downIdx; i <= idx; i++) {
|
|
288
279
|
if (available.includes(oaiRank[i])) return oaiRank[i];
|
|
289
280
|
}
|
|
290
|
-
return available[0] ?? 'gpt-
|
|
281
|
+
return available[0] ?? 'gpt-4o-mini';
|
|
291
282
|
}
|
|
292
283
|
}
|
|
293
284
|
|
|
@@ -297,7 +288,7 @@ function applyProfileBias(model, profile, provider, available, tier) {
|
|
|
297
288
|
// Prefer cheapest available that also fits the required tier
|
|
298
289
|
const ranks = {
|
|
299
290
|
claude: ['haiku', 'sonnet', 'opus'],
|
|
300
|
-
openai: ['gpt-
|
|
291
|
+
openai: ['gpt-4o-mini', 'gpt-4.1-mini', 'gpt-4.1', 'gpt-4o', 'o4-mini', 'o3'],
|
|
301
292
|
};
|
|
302
293
|
for (const m of ranks[provider]) {
|
|
303
294
|
if (!available.includes(m)) continue;
|
|
@@ -310,7 +301,7 @@ function applyProfileBias(model, profile, provider, available, tier) {
|
|
|
310
301
|
// Prefer best available, keep current if already best
|
|
311
302
|
const ranks = {
|
|
312
303
|
claude: ['opus', 'sonnet', 'haiku'],
|
|
313
|
-
openai: ['
|
|
304
|
+
openai: ['o3', 'o4-mini', 'gpt-4o', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4o-mini'],
|
|
314
305
|
};
|
|
315
306
|
for (const m of ranks[provider]) {
|
|
316
307
|
if (available.includes(m)) return m;
|
|
@@ -341,7 +332,7 @@ function pickEffort(model, detection) {
|
|
|
341
332
|
function pickModes(model, detection) {
|
|
342
333
|
const { intent = '', complexity = 'simple' } = detection;
|
|
343
334
|
const caps = MODEL_CAPABILITIES[model] ?? {};
|
|
344
|
-
const thinkingModels = ['sonnet', 'opus', '
|
|
335
|
+
const thinkingModels = ['sonnet', 'opus', 'o3', 'gpt-4o'];
|
|
345
336
|
const lightIntents = ['search', 'format', 'explain', 'lookup'];
|
|
346
337
|
|
|
347
338
|
return {
|
|
@@ -350,7 +341,7 @@ function pickModes(model, detection) {
|
|
|
350
341
|
&& !lightIntents.includes(intent),
|
|
351
342
|
fastMode: model === 'opus',
|
|
352
343
|
extendedContext: ['sonnet', 'opus'].includes(model),
|
|
353
|
-
webSearch: ['gpt-4.1-mini', 'gpt-4.1', 'gpt-
|
|
344
|
+
webSearch: ['gpt-4o-mini', 'gpt-4.1-mini', 'gpt-4.1', 'gpt-4o'].includes(model),
|
|
354
345
|
};
|
|
355
346
|
}
|
|
356
347
|
|
|
@@ -583,12 +574,21 @@ export function decideRoute({ profile = {}, detection = {}, cwd } = {}) {
|
|
|
583
574
|
const modes = pickModes(model, detection);
|
|
584
575
|
const sandbox = pickSandbox(model, detection);
|
|
585
576
|
|
|
577
|
+
const hasBothProviders = !!(
|
|
578
|
+
profile?.providers?.claude?.enabled &&
|
|
579
|
+
profile?.providers?.claude?.plan &&
|
|
580
|
+
profile?.providers?.openai?.enabled &&
|
|
581
|
+
profile?.providers?.openai?.plan
|
|
582
|
+
);
|
|
583
|
+
const degradedDualBrain = !!(dual && detection.designImpact && !hasBothProviders);
|
|
584
|
+
|
|
586
585
|
const decision = {
|
|
587
586
|
provider,
|
|
588
587
|
model,
|
|
589
588
|
effort,
|
|
590
589
|
tier,
|
|
591
590
|
dualBrain: dual,
|
|
591
|
+
...(degradedDualBrain && { degradedDualBrain: true }),
|
|
592
592
|
modes,
|
|
593
593
|
sandbox,
|
|
594
594
|
explanation: '',
|
package/src/dispatch.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { redact } from './redact.mjs';
|
|
|
18
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
19
|
const USAGE_DIR = join(__dirname, '..', '.dualbrain', 'usage');
|
|
20
20
|
const TIER_TIMEOUT_MS = { search: 60_000, execute: 120_000, think: 180_000 };
|
|
21
|
-
const CLAUDE_MODEL_IDS = { opus: 'claude-opus-4-
|
|
21
|
+
const CLAUDE_MODEL_IDS = { opus: 'claude-opus-4-6', sonnet: 'claude-sonnet-4-6', haiku: 'claude-haiku-4-5-20251001' };
|
|
22
22
|
|
|
23
23
|
// ─── Median dispatch time tracker (in-process, for slow-response detection) ──
|
|
24
24
|
// Rolling window of recent dispatch durations keyed by "provider:modelClass"
|
|
@@ -272,7 +272,7 @@ async function detectRuntime() {
|
|
|
272
272
|
/** Valid CLI model flags per provider */
|
|
273
273
|
const VALID_MODELS = {
|
|
274
274
|
claude: ['opus', 'sonnet', 'haiku'],
|
|
275
|
-
openai: ['o4-mini', 'o3', '
|
|
275
|
+
openai: ['o4-mini', 'o3', 'o1', 'o1-mini', 'gpt-4o', 'gpt-4o-mini', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4-turbo'],
|
|
276
276
|
};
|
|
277
277
|
|
|
278
278
|
/** Safest default model for a given provider + tier */
|
|
@@ -507,14 +507,15 @@ function compressResult(rawOutput = '', maxLength = 300) {
|
|
|
507
507
|
}
|
|
508
508
|
|
|
509
509
|
// ─── Core runner ──────────────────────────────────────────────────────────────
|
|
510
|
-
function runProcess(cmd, cwd, timeoutMs) {
|
|
510
|
+
function runProcess(cmd, cwd, timeoutMs, env) {
|
|
511
511
|
return new Promise((resolve) => {
|
|
512
512
|
const [bin, ...args] = cmd;
|
|
513
513
|
const start = Date.now();
|
|
514
514
|
let stdout = '';
|
|
515
515
|
let stderr = '';
|
|
516
516
|
|
|
517
|
-
const
|
|
517
|
+
const spawnEnv = env ? { ...process.env, ...env } : undefined;
|
|
518
|
+
const proc = spawn(bin, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'], ...(spawnEnv ? { env: spawnEnv } : {}) });
|
|
518
519
|
|
|
519
520
|
proc.stdout.on('data', (d) => { stdout += d; });
|
|
520
521
|
proc.stderr.on('data', (d) => { stderr += d; });
|
|
@@ -633,31 +634,91 @@ async function dispatch(input = {}) {
|
|
|
633
634
|
}
|
|
634
635
|
|
|
635
636
|
// ── Native Claude Code dispatch ──────────────────────────────────────────────
|
|
636
|
-
// When running inside Claude Code AND the provider is claude,
|
|
637
|
-
//
|
|
638
|
-
//
|
|
637
|
+
// When running inside Claude Code AND the provider is claude, execute via the
|
|
638
|
+
// claude CLI directly (foreground subprocess) so results are captured and returned.
|
|
639
|
+
// DUAL_BRAIN_DISPATCH=1 is set so the enforce-tier hook allows this agent call.
|
|
639
640
|
if (isInsideClaude() && effectiveProvider === 'claude') {
|
|
640
641
|
const nativeDescriptor = buildNativeDispatch(
|
|
641
642
|
effectiveDecision,
|
|
642
643
|
prompt,
|
|
643
644
|
{ worktree: input.worktree, maxTurns: input.maxTurns },
|
|
644
645
|
);
|
|
646
|
+
|
|
647
|
+
const command = buildCommand(effectiveDecision, prompt, files, cwd);
|
|
648
|
+
|
|
645
649
|
if (dryRun) {
|
|
646
|
-
return {
|
|
650
|
+
return {
|
|
651
|
+
status: 'dry-run',
|
|
652
|
+
provider: effectiveProvider,
|
|
653
|
+
model: effectiveModel,
|
|
654
|
+
command,
|
|
655
|
+
nativeDispatch: nativeDescriptor,
|
|
656
|
+
exitCode: null,
|
|
657
|
+
summary: null,
|
|
658
|
+
durationMs: 0,
|
|
659
|
+
usage: null,
|
|
660
|
+
error: null,
|
|
661
|
+
};
|
|
647
662
|
}
|
|
663
|
+
|
|
648
664
|
_recordDispatchBudget(prompt);
|
|
665
|
+
|
|
666
|
+
const dispatchEnv = { DUAL_BRAIN_DISPATCH: '1' };
|
|
667
|
+
const { exitCode, stdout, stderr, durationMs } = await runProcess(command, cwd, timeoutMs, dispatchEnv);
|
|
668
|
+
|
|
669
|
+
// Extract token usage from JSON output if available
|
|
670
|
+
let usage = null;
|
|
671
|
+
try {
|
|
672
|
+
const parsed = JSON.parse(stdout);
|
|
673
|
+
if (parsed?.usage) {
|
|
674
|
+
usage = { inputTokens: parsed.usage.input_tokens ?? 0, outputTokens: parsed.usage.output_tokens ?? 0 };
|
|
675
|
+
}
|
|
676
|
+
} catch {}
|
|
677
|
+
|
|
678
|
+
const success = exitCode === 0;
|
|
679
|
+
const errorText = (stderr || stdout).slice(0, 500);
|
|
680
|
+
const summary = success ? compressResult(stdout) : compressResult(stderr || stdout);
|
|
681
|
+
|
|
682
|
+
// ── Health tracking ────────────────────────────────────────────────────
|
|
683
|
+
if (success) {
|
|
684
|
+
recordDuration(effectiveProvider, effectiveModel, durationMs);
|
|
685
|
+
const median = medianDuration(effectiveProvider, effectiveModel);
|
|
686
|
+
if (median !== null && durationMs > median * 3) {
|
|
687
|
+
markDegraded(effectiveProvider, effectiveModel, cwd);
|
|
688
|
+
} else {
|
|
689
|
+
markHealthy(effectiveProvider, effectiveModel, cwd);
|
|
690
|
+
}
|
|
691
|
+
const totalTokens = (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0);
|
|
692
|
+
recordDispatch(effectiveProvider, effectiveModel, totalTokens, cwd);
|
|
693
|
+
} else {
|
|
694
|
+
if (RATE_LIMIT_PATTERNS.test(errorText)) {
|
|
695
|
+
markHot(effectiveProvider, effectiveModel, cwd);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// ── End health tracking ────────────────────────────────────────────────
|
|
699
|
+
|
|
700
|
+
recordUsage({
|
|
701
|
+
provider: effectiveProvider,
|
|
702
|
+
model: effectiveModel,
|
|
703
|
+
tier,
|
|
704
|
+
durationMs,
|
|
705
|
+
inputTokens: usage?.inputTokens ?? null,
|
|
706
|
+
outputTokens: usage?.outputTokens ?? null,
|
|
707
|
+
success,
|
|
708
|
+
});
|
|
709
|
+
|
|
649
710
|
return {
|
|
650
|
-
status: 'completed',
|
|
711
|
+
status: success ? 'completed' : 'failed',
|
|
651
712
|
type: 'native-agent',
|
|
652
713
|
provider: effectiveProvider,
|
|
653
714
|
model: effectiveModel,
|
|
654
|
-
command
|
|
715
|
+
command,
|
|
655
716
|
nativeDispatch: nativeDescriptor,
|
|
656
|
-
exitCode
|
|
657
|
-
summary
|
|
658
|
-
durationMs
|
|
659
|
-
usage
|
|
660
|
-
error:
|
|
717
|
+
exitCode,
|
|
718
|
+
summary,
|
|
719
|
+
durationMs,
|
|
720
|
+
usage,
|
|
721
|
+
error: success ? null : errorText.slice(0, 200),
|
|
661
722
|
};
|
|
662
723
|
}
|
|
663
724
|
|
|
@@ -743,7 +804,7 @@ async function dispatchDualBrain(input = {}) {
|
|
|
743
804
|
const tier = decision.tier ?? 'execute';
|
|
744
805
|
|
|
745
806
|
const claudeDecision = { ...decision, provider: 'claude', model: decision.model ?? 'sonnet', tier };
|
|
746
|
-
const _oaiDefault = tier === 'think' ? '
|
|
807
|
+
const _oaiDefault = tier === 'think' ? 'o3' : tier === 'search' ? 'gpt-4o-mini' : 'gpt-4o';
|
|
747
808
|
const openaiDecision = { ...decision, provider: 'openai', model: decision.openaiModel ?? _oaiDefault, tier };
|
|
748
809
|
|
|
749
810
|
const validatedClaude = validateDispatch(claudeDecision, rt);
|
package/src/profile.mjs
CHANGED
|
@@ -520,9 +520,9 @@ function isSoloBrain(profile) {
|
|
|
520
520
|
function getHeadModel(profile) {
|
|
521
521
|
const providers = getAvailableProviders(profile);
|
|
522
522
|
if (providers.length === 0) return 'sonnet';
|
|
523
|
-
if (providers.length === 1) return providers[0].name === 'openai' ? 'gpt-
|
|
523
|
+
if (providers.length === 1) return providers[0].name === 'openai' ? 'gpt-4o' : 'sonnet';
|
|
524
524
|
const top = providers.reduce((a, b) => (b.rank > a.rank ? b : a));
|
|
525
|
-
return top.name === 'openai' ? 'gpt-
|
|
525
|
+
return top.name === 'openai' ? 'gpt-4o' : 'sonnet';
|
|
526
526
|
}
|
|
527
527
|
|
|
528
528
|
// ---------------------------------------------------------------------------
|
package/src/session.mjs
CHANGED
|
@@ -228,6 +228,7 @@ function isRealPrompt(text) {
|
|
|
228
228
|
if (/^[✅❌📦🔗⚠️🚀🎉🔧📝]/.test(t)) return false;
|
|
229
229
|
if (/Claude (history|binary|versions) symlink/.test(t)) return false;
|
|
230
230
|
if (t.startsWith('# AGENTS.md')) return false;
|
|
231
|
+
if (t === 'login' || t === 'logout') return false;
|
|
231
232
|
if (t.startsWith('/')) return false;
|
|
232
233
|
if (t.startsWith('[Pasted')) return false;
|
|
233
234
|
return true;
|
|
@@ -462,8 +463,8 @@ export function importReplitSessions(cwd = process.cwd()) {
|
|
|
462
463
|
|
|
463
464
|
// Build session list
|
|
464
465
|
for (const [id, sess] of bySession) {
|
|
465
|
-
// Skip sessions outside the recency window (timestamps are in
|
|
466
|
-
if (sess.lastTimestamp
|
|
466
|
+
// Skip sessions outside the recency window (timestamps are in ms)
|
|
467
|
+
if (sess.lastTimestamp < cutoff) continue;
|
|
467
468
|
// Derive display name
|
|
468
469
|
let name = sess.firstPrompt;
|
|
469
470
|
if (!name) {
|
|
@@ -802,7 +803,8 @@ export function buildSessionIndex(cwd = process.cwd()) {
|
|
|
802
803
|
|
|
803
804
|
// Track timestamps
|
|
804
805
|
if (entry.timestamp) {
|
|
805
|
-
const
|
|
806
|
+
const raw = typeof entry.timestamp === 'number' ? entry.timestamp : Date.parse(entry.timestamp);
|
|
807
|
+
const ts = raw > 1e12 ? raw / 1000 : raw;
|
|
806
808
|
if (ts > lastTimestamp) lastTimestamp = ts;
|
|
807
809
|
}
|
|
808
810
|
|