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.
@@ -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.15",
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-5.2': {
74
+ 'gpt-4o': {
75
75
  provider: 'openai',
76
- tierFit: ['search', 'execute'],
77
- contextWindow: 200_000,
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-5.4-mini': {
83
+ 'gpt-4o-mini': {
84
84
  provider: 'openai',
85
85
  tierFit: ['search'],
86
- contextWindow: 200_000,
87
- costTier: 'low',
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
- 'gpt-5.3-codex': {
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: 1_000_000,
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', 'xhigh'],
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-4.1-mini', 'gpt-4.1', 'gpt-5.2', 'gpt-5.4-mini'],
131
- '$100': ['gpt-4.1-mini', 'gpt-4.1', 'gpt-5.2', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.4', 'gpt-5.5'],
132
- '$200': ['gpt-4.1-mini', 'gpt-4.1', 'gpt-5.2', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.4', 'gpt-5.5'],
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-4.1-mini'
192
- : tier === 'think' ? 'gpt-5.5'
193
- : 'gpt-5.4';
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
- * @param {{ intent?: string, risk?: string, complexity?: string }} detection
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
- const criticalRisk = risk === 'critical';
227
- const archOrSecurity = ['architecture', 'security'].includes(intent);
228
- const complexHighRisk = complexity === 'complex' && risk === 'high';
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 || designImpact;
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 ? 'gpt-5.5'
255
- : needsMini ? 'gpt-4.1-mini'
256
- : needsCodex ? 'gpt-5.3-codex'
257
- : 'gpt-5.4';
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-4.1-mini', 'gpt-4.1', 'gpt-5.2', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.4', 'gpt-5.5'];
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-4.1-mini';
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-4.1-mini', 'gpt-4.1', 'gpt-5.2', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.4', 'gpt-5.5'];
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-4.1-mini';
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-4.1-mini', 'gpt-4.1', 'gpt-5.2', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.4', 'gpt-5.5'],
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: ['gpt-5.5', 'gpt-5.4', 'gpt-5.3-codex', 'gpt-5.4-mini', 'gpt-5.2', 'gpt-4.1', 'gpt-4.1-mini'],
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', 'gpt-5.5', 'gpt-5.4'];
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-5.4-mini'].includes(model),
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-5', sonnet: 'claude-sonnet-4-5', haiku: 'claude-haiku-4-5' };
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', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-5.2', 'gpt-5.3-codex', 'gpt-5.3-codex-spark', 'gpt-5.4-mini', 'gpt-5.4', 'gpt-5.5'],
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 proc = spawn(bin, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
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, return a
637
- // structured native-agent descriptor instead of spawning a subprocess.
638
- // The caller (CLI or plugin) is responsible for actually invoking the Agent tool.
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 { status: 'dry-run', provider: effectiveProvider, model: effectiveModel, command: null, nativeDispatch: nativeDescriptor, exitCode: null, summary: null, durationMs: 0, usage: null, error: null };
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: null,
715
+ command,
655
716
  nativeDispatch: nativeDescriptor,
656
- exitCode: 0,
657
- summary: `Routed to ${effectiveProvider}/${effectiveModel} (${effectiveDecision.tier})`,
658
- durationMs: 0,
659
- usage: null,
660
- error: null,
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' ? 'gpt-5.5' : tier === 'search' ? 'o4-mini' : 'gpt-5.4';
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-5.4' : 'sonnet';
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-5.4' : 'sonnet';
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 seconds)
466
- if (sess.lastTimestamp * 1000 < cutoff) continue;
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 ts = typeof entry.timestamp === 'number' ? entry.timestamp : Date.parse(entry.timestamp) / 1000;
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