freebuff 0.1.2 → 0.1.5
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/README.md +2 -2
- package/launcher.js +298 -46
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ and keep the extracted `freebuff` binary and `tree-sitter.wasm` together.
|
|
|
33
33
|
|
|
34
34
|
## Why Freebuff?
|
|
35
35
|
|
|
36
|
-
**Choice of leading models** — The regular CLI picker includes GLM 5.3 Flash, DeepSeek, GPT-6 Luna, MiMo 2.6 Flash, MiMo 2.6 Pro, Solar Mini 4, Solar Pro 4, Space Bunny Alpha, Gemini 3.8 Flash, and Muse Spark 1.
|
|
36
|
+
**Choice of leading models** — The regular CLI picker includes GLM 5.3 Flash, DeepSeek, GPT-6 Luna, MiMo 2.6 Flash, MiMo 2.6 Pro, Solar Mini 4, Solar Pro 4, Space Bunny Alpha, Gemini 3.8 Flash, and Muse Spark 1.3, with GLM 5.3 Flash selected by default.
|
|
37
37
|
|
|
38
38
|
**Fast** — 5–10× speed up. 3–5× tokens per second compared to Claude, plus context gathering in seconds.
|
|
39
39
|
|
|
@@ -43,7 +43,7 @@ and keep the extracted `freebuff` binary and `tree-sitter.wasm` together.
|
|
|
43
43
|
|
|
44
44
|
**How can it be free?** Freebuff is supported by text ads.
|
|
45
45
|
|
|
46
|
-
**What models do you use?** In full mode, the regular picker includes GLM 5.3 Flash, DeepSeek V4.1 Flash, GPT-6 Luna, MiMo 2.6 Flash, MiMo 2.6 Pro, Solar Mini 4, Solar Pro 4, Space Bunny Alpha, Gemini 3.8 Flash, and Muse Spark 1.
|
|
46
|
+
**What models do you use?** In full mode, the regular picker includes GLM 5.3 Flash, DeepSeek V4.1 Flash, GPT-6 Luna, MiMo 2.6 Flash, MiMo 2.6 Pro, Solar Mini 4, Solar Pro 4, Space Bunny Alpha, Gemini 3.8 Flash, and Muse Spark 1.3. Space Bunny Alpha is a beta stealth model from an anonymous provider that retains prompts. Gemini 3.8 Flash and Muse Spark 1.3 require a paid plan: without one they are listed but locked, and selecting one opens the plans page. GPT-6 Luna and MiMo 2.6 Pro are open to every full-access account. Muse Spark 1.3 has a 1M-token context and is rate limited across all users, so at busy times it queues and then answers on DeepSeek V4 Flash instead of leaving you waiting. GLM 5.3 Flash is the CLI default, and it costs no session at all, so the default is always available no matter how much you have used. Limited mode uses GLM 5.3 Flash, DeepSeek V4.1 Flash, MiMo 2.6 Flash, Solar Mini 4, and Solar Pro 4. Most models draw on your normal daily sessions rather than a separate limit; GLM 5.3 Flash, DeepSeek V4.1 Flash, MiMo 2.6 Flash, Solar Mini 4, and Solar Pro 4 are unmetered at full access. Models may still serve from a quantized (Q8_0) build.
|
|
47
47
|
|
|
48
48
|
**Which countries is Freebuff available in?** All countries. Freebuff runs in "full" mode in the US, Canada, UK, EU, and other select countries, and in "limited" mode everywhere else (or while using a VPN). See [freebuff.com](https://freebuff.com) for the full list.
|
|
49
49
|
|
package/launcher.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { spawn } = require('child_process')
|
|
3
|
+
const { execFileSync, spawn } = require('child_process')
|
|
4
4
|
const crypto = require('crypto')
|
|
5
5
|
const fs = require('fs')
|
|
6
6
|
const http = require('http')
|
|
@@ -35,15 +35,51 @@ const PLATFORM_TARGET_KEYS = [
|
|
|
35
35
|
|
|
36
36
|
const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '[::1]'])
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Registrable domains that serve release downloads. An https override must
|
|
40
|
+
* name one of these or a subdomain of one.
|
|
41
|
+
*/
|
|
42
|
+
const RELEASE_ORIGIN_DOMAINS = ['codebuff.com', 'freebuff.com']
|
|
43
|
+
|
|
44
|
+
function isReleaseOriginHost(hostname) {
|
|
45
|
+
const host = hostname.toLowerCase()
|
|
46
|
+
return RELEASE_ORIGIN_DOMAINS.some(
|
|
47
|
+
(domain) => host === domain || host.endsWith(`.${domain}`),
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A release version the launcher will put into a download URL or a file
|
|
53
|
+
* name: strict `MAJOR.MINOR.PATCH` with an optional dot-separated
|
|
54
|
+
* alphanumeric prerelease, and nothing else — no build metadata, no
|
|
55
|
+
* leading `v`, no whitespace, `/`, `..`, `%` or `?`. The version reaches
|
|
56
|
+
* `/api/releases/download/<version>/<file>` and the partial-archive path
|
|
57
|
+
* from the npm registry document or the local metadata file, and neither
|
|
58
|
+
* is a place that should be able to rewrite a URL path or escape a
|
|
59
|
+
* directory.
|
|
60
|
+
*/
|
|
61
|
+
const STRICT_RELEASE_VERSION =
|
|
62
|
+
/^(0|[1-9]\d{0,8})\.(0|[1-9]\d{0,8})\.(0|[1-9]\d{0,8})(?:-(?:0|[1-9]\d{0,8}|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d{0,8}|\d*[A-Za-z-][0-9A-Za-z-]*))*)?$/
|
|
63
|
+
|
|
64
|
+
function isStrictReleaseVersion(version) {
|
|
65
|
+
return (
|
|
66
|
+
typeof version === 'string' &&
|
|
67
|
+
version.length <= 64 &&
|
|
68
|
+
STRICT_RELEASE_VERSION.test(version)
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
38
72
|
/**
|
|
39
73
|
* Decide where release archives are downloaded from.
|
|
40
74
|
*
|
|
41
75
|
* NEXT_PUBLIC_CODEBUFF_APP_URL is read at runtime, so anyone able to set an
|
|
42
|
-
* environment variable could point the launcher at
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
76
|
+
* environment variable could point the launcher at another origin. The
|
|
77
|
+
* archive's sha256 comes from npm, not from that origin, so a substituted
|
|
78
|
+
* archive is refused either way; the origin is still held to the hosts that
|
|
79
|
+
* actually serve releases. The override is honoured only over https to a
|
|
80
|
+
* codebuff.com / freebuff.com host, or over http/https on a loopback host
|
|
81
|
+
* (local release servers in development and tests). Anything else is
|
|
82
|
+
* ignored with a warning and the default origin is used.
|
|
47
83
|
*/
|
|
48
84
|
function resolveDownloadOrigin(configuredOrigin, { warn = () => {} } = {}) {
|
|
49
85
|
if (typeof configuredOrigin !== 'string' || configuredOrigin.trim() === '') {
|
|
@@ -62,15 +98,25 @@ function resolveDownloadOrigin(configuredOrigin, { warn = () => {} } = {}) {
|
|
|
62
98
|
}
|
|
63
99
|
|
|
64
100
|
const isLocal = LOCAL_HOSTNAMES.has(parsed.hostname.toLowerCase())
|
|
101
|
+
const isHttp = parsed.protocol === 'http:' || parsed.protocol === 'https:'
|
|
102
|
+
// Only an origin: credentials or a query would ride along on every request.
|
|
103
|
+
const isBareOrigin =
|
|
104
|
+
!parsed.username && !parsed.password && !parsed.search && !parsed.hash
|
|
105
|
+
if (isHttp && isLocal && isBareOrigin) {
|
|
106
|
+
return trimmed
|
|
107
|
+
}
|
|
65
108
|
if (
|
|
66
|
-
parsed.protocol === 'https:'
|
|
67
|
-
(parsed.
|
|
109
|
+
parsed.protocol === 'https:' &&
|
|
110
|
+
isReleaseOriginHost(parsed.hostname) &&
|
|
111
|
+
isBareOrigin
|
|
68
112
|
) {
|
|
69
113
|
return trimmed
|
|
70
114
|
}
|
|
71
115
|
|
|
72
116
|
warn(
|
|
73
|
-
|
|
117
|
+
parsed.protocol === 'https:'
|
|
118
|
+
? `Ignoring NEXT_PUBLIC_CODEBUFF_APP_URL=${parsed.protocol}//${parsed.host}: release downloads come only from codebuff.com or freebuff.com (or localhost). Downloading from ${DEFAULT_DOWNLOAD_ORIGIN}.`
|
|
119
|
+
: `Ignoring NEXT_PUBLIC_CODEBUFF_APP_URL=${parsed.protocol}//${parsed.host}: release downloads must use https (http is allowed only for localhost). Downloading from ${DEFAULT_DOWNLOAD_ORIGIN}.`,
|
|
74
120
|
)
|
|
75
121
|
return DEFAULT_DOWNLOAD_ORIGIN
|
|
76
122
|
}
|
|
@@ -146,6 +192,9 @@ function createLauncher(productConfig) {
|
|
|
146
192
|
// Tests only. os.homedir() ignores $HOME under `bun test`, so pointing HOME
|
|
147
193
|
// at a temp dir is not enough to keep a test off the real ~/.config.
|
|
148
194
|
configDir: configDirOverride = null,
|
|
195
|
+
// Tests only: stands in for the OS AVX2 probe (/proc/cpuinfo, sysctl),
|
|
196
|
+
// returning true, false, or null for "could not tell".
|
|
197
|
+
cpuAvx2Probe: cpuAvx2ProbeOverride = null,
|
|
149
198
|
} = productConfig
|
|
150
199
|
|
|
151
200
|
/**
|
|
@@ -282,6 +331,19 @@ function createLauncher(productConfig) {
|
|
|
282
331
|
const BUN_ILLEGAL_INSTRUCTION_PANIC =
|
|
283
332
|
/^panic(?:\([^)\n]*\))?: Illegal instruction at address 0x[0-9a-f]+/im
|
|
284
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Bun's `CPU:` header line lists the features it detected (`CPU: sse42
|
|
336
|
+
* popcnt avx avx2`). avx2 in that list is Bun's own CPUID saying the CPU has
|
|
337
|
+
* it, so an illegal instruction on such a machine is not a missing AVX2.
|
|
338
|
+
*/
|
|
339
|
+
const BUN_CPU_LINE = /^CPU:([^\n]*)$/m
|
|
340
|
+
|
|
341
|
+
function stderrReportsAvx2Present(stderrTail) {
|
|
342
|
+
if (!stderrTail) return false
|
|
343
|
+
const match = BUN_CPU_LINE.exec(sanitizeForReplay(stderrTail))
|
|
344
|
+
return Boolean(match && /(?:^|\s)avx2(?:\s|$)/i.test(match[1]))
|
|
345
|
+
}
|
|
346
|
+
|
|
285
347
|
function stderrReportsMissingCpuFeature(code, signal, stderrTail) {
|
|
286
348
|
if (!stderrTail || (code === 0 && !signal)) return false
|
|
287
349
|
const text = sanitizeForReplay(stderrTail)
|
|
@@ -442,12 +504,75 @@ function createLauncher(productConfig) {
|
|
|
442
504
|
return null
|
|
443
505
|
}
|
|
444
506
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
507
|
+
/**
|
|
508
|
+
* `/proc/cpuinfo` answers for the kernel's view of the CPU: true or false
|
|
509
|
+
* from its `flags` lines, null when it has none to read.
|
|
510
|
+
*/
|
|
511
|
+
function parseCpuinfoAvx2(text) {
|
|
512
|
+
if (typeof text !== 'string' || !/^flags\s*:/m.test(text)) return null
|
|
513
|
+
return /^flags\s*:.*\bavx2\b/m.test(text)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** `sysctl -n hw.optional.avx2_0` prints 1 or 0 on an Intel Mac. */
|
|
517
|
+
function parseSysctlAvx2(output) {
|
|
518
|
+
const value = typeof output === 'string' ? output.trim() : ''
|
|
519
|
+
if (value === '1') return true
|
|
520
|
+
if (value === '0') return false
|
|
521
|
+
return null
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Where probeCpuAvx2() gets its answer, recorded as the evidence for a pin.
|
|
525
|
+
function cpuProbeSource() {
|
|
526
|
+
if (process.platform === 'linux') return 'cpuinfo'
|
|
527
|
+
if (process.platform === 'darwin') return 'sysctl'
|
|
528
|
+
return 'probe'
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
let _cpuProbeCache
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Ask the OS whether this CPU has AVX2: true, false, or null for "could not
|
|
535
|
+
* tell". Linux reads /proc/cpuinfo. macOS asks sysctl, which costs a
|
|
536
|
+
* subprocess, so this runs only on the crash path, when re-checking an old
|
|
537
|
+
* pin, and when picking a download target for a baseline install, never on
|
|
538
|
+
* an ordinary launch. Windows has no probe: the one we had tripped Defender
|
|
539
|
+
* (see detectMachineHasAvx2), so there Bun's crash report is the evidence.
|
|
540
|
+
*/
|
|
541
|
+
function probeCpuAvx2() {
|
|
542
|
+
if (_cpuProbeCache === undefined) {
|
|
543
|
+
_cpuProbeCache = cpuAvx2ProbeOverride
|
|
544
|
+
? cpuAvx2ProbeOverride()
|
|
545
|
+
: probeCpuAvx2FromOs()
|
|
450
546
|
}
|
|
547
|
+
return _cpuProbeCache
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function probeCpuAvx2FromOs() {
|
|
551
|
+
if (process.arch !== 'x64') return null
|
|
552
|
+
if (process.platform === 'linux') {
|
|
553
|
+
try {
|
|
554
|
+
return parseCpuinfoAvx2(fs.readFileSync('/proc/cpuinfo', 'utf8'))
|
|
555
|
+
} catch {
|
|
556
|
+
return null
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (process.platform === 'darwin') {
|
|
560
|
+
// x64 node on Apple Silicon runs under Rosetta, and sysctl (arm64 only)
|
|
561
|
+
// then describes the host, not what Rosetta emulates. No answer there.
|
|
562
|
+
if (/\bApple\b/.test(os.cpus()[0]?.model || '')) return null
|
|
563
|
+
try {
|
|
564
|
+
return parseSysctlAvx2(
|
|
565
|
+
execFileSync('/usr/sbin/sysctl', ['-n', 'hw.optional.avx2_0'], {
|
|
566
|
+
encoding: 'utf8',
|
|
567
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
568
|
+
timeout: 2000,
|
|
569
|
+
}),
|
|
570
|
+
)
|
|
571
|
+
} catch {
|
|
572
|
+
return null
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return null
|
|
451
576
|
}
|
|
452
577
|
|
|
453
578
|
let _hasAvx2Cache
|
|
@@ -464,17 +589,33 @@ function createLauncher(productConfig) {
|
|
|
464
589
|
return true
|
|
465
590
|
}
|
|
466
591
|
|
|
467
|
-
// A recorded
|
|
468
|
-
//
|
|
469
|
-
//
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
592
|
+
// A recorded verdict outranks everything below it. Every record now names
|
|
593
|
+
// its evidence: Bun's crash report, the OS probe, or 'crash' for an
|
|
594
|
+
// illegal instruction nothing could explain.
|
|
595
|
+
//
|
|
596
|
+
// A record WITHOUT evidence was written by an older launcher, which pinned
|
|
597
|
+
// baseline on any SIGILL, including a bug on a CPU that has AVX2. Re-check
|
|
598
|
+
// those against the OS once: a CPU with AVX2 gets its record dropped and
|
|
599
|
+
// the optimized build back on the next update (see getDownloadTargetKey).
|
|
600
|
+
const record = readCpuFeatureRecord()
|
|
601
|
+
if (record && (record.avx2 || record.evidence)) {
|
|
602
|
+
return record.avx2
|
|
603
|
+
}
|
|
604
|
+
if (record) {
|
|
605
|
+
const probed = probeCpuAvx2()
|
|
606
|
+
if (probed !== true) {
|
|
607
|
+
writeCpuFeatureRecord(
|
|
608
|
+
false,
|
|
609
|
+
probed === false ? cpuProbeSource() : 'crash',
|
|
610
|
+
)
|
|
611
|
+
return false
|
|
612
|
+
}
|
|
613
|
+
clearCpuFeatureRecord()
|
|
473
614
|
}
|
|
474
615
|
|
|
475
616
|
// Linux can just ask, and a file read is cheap enough not to cache.
|
|
476
617
|
if (process.platform === 'linux') {
|
|
477
|
-
return
|
|
618
|
+
return probeCpuAvx2() ?? true
|
|
478
619
|
}
|
|
479
620
|
|
|
480
621
|
// Everything else assumes AVX2 — true of every x64 CPU since ~2013.
|
|
@@ -492,42 +633,77 @@ function createLauncher(productConfig) {
|
|
|
492
633
|
return true
|
|
493
634
|
}
|
|
494
635
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
636
|
+
/**
|
|
637
|
+
* Positive evidence that this CPU runs the optimized build: a recorded
|
|
638
|
+
* verdict with evidence, or the OS probe. The optimistic default does not
|
|
639
|
+
* count. This is what lets a baseline install move back to the optimized
|
|
640
|
+
* build; without evidence, baseline stays put.
|
|
641
|
+
*/
|
|
642
|
+
function knownToHaveAvx2() {
|
|
643
|
+
if (!machineHasAvx2()) return false
|
|
644
|
+
const record = readCpuFeatureRecord()
|
|
645
|
+
if (record?.avx2 === true && record.evidence) return true
|
|
646
|
+
return probeCpuAvx2() === true
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Called from the illegal-instruction fallback, only when the CPU is shown
|
|
650
|
+
// to lack AVX2 or nothing can say either way (see tryFallbackToBaseline).
|
|
651
|
+
// Persisting here is what keeps the optimistic assumption above from costing
|
|
652
|
+
// more than a single crash — and it is deliberately separate from the
|
|
653
|
+
// metadata target, so a lost or rewritten metadata file can't resurrect the
|
|
654
|
+
// AVX2 build on a CPU that can't run it.
|
|
655
|
+
function recordMachineLacksAvx2(evidence = 'crash') {
|
|
500
656
|
_hasAvx2Cache = false
|
|
501
|
-
|
|
657
|
+
writeCpuFeatureRecord(false, evidence)
|
|
502
658
|
}
|
|
503
659
|
|
|
504
660
|
function getCpuFeatureCachePath() {
|
|
505
661
|
return path.join(CONFIG.configDir, 'cpu-features.json')
|
|
506
662
|
}
|
|
507
663
|
|
|
508
|
-
|
|
664
|
+
/** `{ avx2, evidence }`; evidence is null on a record from an older launcher. */
|
|
665
|
+
function readCpuFeatureRecord() {
|
|
509
666
|
try {
|
|
510
667
|
const cache = JSON.parse(
|
|
511
668
|
fs.readFileSync(getCpuFeatureCachePath(), 'utf8'),
|
|
512
669
|
)
|
|
513
|
-
|
|
670
|
+
if (typeof cache.avx2 !== 'boolean') return null
|
|
671
|
+
return {
|
|
672
|
+
avx2: cache.avx2,
|
|
673
|
+
evidence:
|
|
674
|
+
typeof cache.evidence === 'string' && cache.evidence
|
|
675
|
+
? cache.evidence
|
|
676
|
+
: null,
|
|
677
|
+
}
|
|
514
678
|
} catch {
|
|
515
679
|
return null
|
|
516
680
|
}
|
|
517
681
|
}
|
|
518
682
|
|
|
519
|
-
function
|
|
683
|
+
function readCachedAvx2() {
|
|
684
|
+
return readCpuFeatureRecord()?.avx2 ?? null
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function writeCpuFeatureRecord(value, evidence) {
|
|
520
688
|
try {
|
|
521
689
|
fs.mkdirSync(CONFIG.configDir, { recursive: true })
|
|
522
690
|
fs.writeFileSync(
|
|
523
691
|
getCpuFeatureCachePath(),
|
|
524
|
-
JSON.stringify({ avx2: value }),
|
|
692
|
+
JSON.stringify({ avx2: value, evidence }),
|
|
525
693
|
)
|
|
526
694
|
} catch {
|
|
527
695
|
// Best effort; we'll just re-probe next launch.
|
|
528
696
|
}
|
|
529
697
|
}
|
|
530
698
|
|
|
699
|
+
function clearCpuFeatureRecord() {
|
|
700
|
+
try {
|
|
701
|
+
fs.unlinkSync(getCpuFeatureCachePath())
|
|
702
|
+
} catch {
|
|
703
|
+
// Already gone, or unwritable; either way nothing pins baseline now.
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
531
707
|
function getDefaultTargetKey() {
|
|
532
708
|
const override = getTargetOverride()
|
|
533
709
|
if (override) {
|
|
@@ -580,6 +756,17 @@ function createLauncher(productConfig) {
|
|
|
580
756
|
|
|
581
757
|
const metadata = getCurrentMetadata()
|
|
582
758
|
if (metadata?.target && isTargetAllowedForThisMachine(metadata.target)) {
|
|
759
|
+
// An installed baseline keeps running (isTargetAllowedForThisMachine
|
|
760
|
+
// always allows it), but it is not a life sentence: once there is
|
|
761
|
+
// evidence the CPU has AVX2, the next download goes back to the optimized
|
|
762
|
+
// build. That is how a machine sent to baseline by a crash that was not a
|
|
763
|
+
// missing AVX2 re-probes on the next version.
|
|
764
|
+
if (
|
|
765
|
+
metadata.target === getBaselineFallbackTargetKey() &&
|
|
766
|
+
knownToHaveAvx2()
|
|
767
|
+
) {
|
|
768
|
+
return getDefaultTargetKey()
|
|
769
|
+
}
|
|
583
770
|
return metadata.target
|
|
584
771
|
}
|
|
585
772
|
|
|
@@ -606,7 +793,7 @@ function createLauncher(productConfig) {
|
|
|
606
793
|
|
|
607
794
|
const body = await streamToString(res)
|
|
608
795
|
const packageData = JSON.parse(body)
|
|
609
|
-
if (
|
|
796
|
+
if (!isStrictReleaseVersion(packageData.version)) {
|
|
610
797
|
return null
|
|
611
798
|
}
|
|
612
799
|
|
|
@@ -795,6 +982,9 @@ function createLauncher(productConfig) {
|
|
|
795
982
|
}
|
|
796
983
|
|
|
797
984
|
function getPartialArchivePath(version, targetKey) {
|
|
985
|
+
if (!isStrictReleaseVersion(version)) {
|
|
986
|
+
throw new Error(`Invalid release version for an archive path: ${version}`)
|
|
987
|
+
}
|
|
798
988
|
return path.join(
|
|
799
989
|
CONFIG.configDir,
|
|
800
990
|
`.${packageName}-${version}-${targetKey}.tar.gz.part`,
|
|
@@ -1027,6 +1217,21 @@ function createLauncher(productConfig) {
|
|
|
1027
1217
|
) {
|
|
1028
1218
|
const fileName = PLATFORM_TARGETS[targetKey]
|
|
1029
1219
|
|
|
1220
|
+
// Before anything is built from it: the version becomes a URL path
|
|
1221
|
+
// segment and part of a file name below.
|
|
1222
|
+
if (!isStrictReleaseVersion(version)) {
|
|
1223
|
+
const error = new Error(
|
|
1224
|
+
`Refusing to download ${packageName}: ${JSON.stringify(String(version)).slice(0, 80)} is not a release version.`,
|
|
1225
|
+
)
|
|
1226
|
+
error.code = 'EBADVERSION'
|
|
1227
|
+
error.retryable = false
|
|
1228
|
+
trackUpdateFailed(error.message, 'invalid', {
|
|
1229
|
+
stage: 'version_check',
|
|
1230
|
+
target: targetKey,
|
|
1231
|
+
})
|
|
1232
|
+
throw error
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1030
1235
|
if (!fileName) {
|
|
1031
1236
|
const error = new Error(
|
|
1032
1237
|
`Unsupported platform: ${process.platform} ${process.arch}`,
|
|
@@ -1713,7 +1918,7 @@ function createLauncher(productConfig) {
|
|
|
1713
1918
|
// feature (see stderrReportsMissingCpuFeature), is proof this CPU cannot
|
|
1714
1919
|
// run this build; a Windows startup abort is a strong suspicion (see
|
|
1715
1920
|
// isStartupCpuFeatureCrash).
|
|
1716
|
-
|
|
1921
|
+
let confirmed =
|
|
1717
1922
|
isIllegalInstructionExit(code, signal) ||
|
|
1718
1923
|
stderrReportsMissingCpuFeature(code, signal, stderrTail)
|
|
1719
1924
|
if (!confirmed && !isStartupCpuFeatureCrash(code, signal, msAlive)) {
|
|
@@ -1736,16 +1941,35 @@ function createLauncher(productConfig) {
|
|
|
1736
1941
|
return false
|
|
1737
1942
|
}
|
|
1738
1943
|
|
|
1739
|
-
//
|
|
1740
|
-
//
|
|
1741
|
-
//
|
|
1742
|
-
//
|
|
1743
|
-
//
|
|
1744
|
-
//
|
|
1745
|
-
//
|
|
1746
|
-
if
|
|
1747
|
-
|
|
1748
|
-
|
|
1944
|
+
// An illegal instruction proves the binary used an instruction this
|
|
1945
|
+
// machine refused, not that the instruction was AVX2: a Bun or JIT bug
|
|
1946
|
+
// raises SIGILL too. So ask for evidence before pinning baseline, which
|
|
1947
|
+
// otherwise sticks for good.
|
|
1948
|
+
//
|
|
1949
|
+
// - The CPU lacks AVX2 (Bun's `Features: no_avx2`, or the OS probe):
|
|
1950
|
+
// record it. Persisting before the download is what caps the cost at
|
|
1951
|
+
// one crash: even if the download or the relaunch fails, we never
|
|
1952
|
+
// optimistically pick the AVX2 build again.
|
|
1953
|
+
// - The CPU has AVX2 (the OS probe, or avx2 in Bun's `CPU:` line): this
|
|
1954
|
+
// crash is something else. Run baseline for now, since it may still
|
|
1955
|
+
// start, and record the evidence so the next update goes back to the
|
|
1956
|
+
// optimized build (getDownloadTargetKey). At worst one crash per release.
|
|
1957
|
+
// - No evidence either way (Windows without Bun's report, an unreadable
|
|
1958
|
+
// /proc/cpuinfo, Rosetta): a confirmed crash is pinned as before.
|
|
1959
|
+
// A suspected crash records nothing: installing the baseline already keeps
|
|
1960
|
+
// this machine on it, and cpu-features.json should not assert a CPU
|
|
1961
|
+
// limitation we never observed.
|
|
1962
|
+
const verdict = crashAvx2Verdict(code, signal, stderrTail)
|
|
1963
|
+
if (verdict.avx2 === true) {
|
|
1964
|
+
writeCpuFeatureRecord(true, verdict.evidence)
|
|
1965
|
+
confirmed = false
|
|
1966
|
+
} else if (verdict.avx2 === false) {
|
|
1967
|
+
recordMachineLacksAvx2(verdict.evidence)
|
|
1968
|
+
confirmed = true
|
|
1969
|
+
} else if (confirmed) {
|
|
1970
|
+
recordMachineLacksAvx2('crash')
|
|
1971
|
+
}
|
|
1972
|
+
const cpuHasAvx2 = verdict.avx2 === true
|
|
1749
1973
|
|
|
1750
1974
|
let version = metadata?.version || null
|
|
1751
1975
|
let binaryChecksums = null
|
|
@@ -1763,9 +1987,11 @@ function createLauncher(productConfig) {
|
|
|
1763
1987
|
})
|
|
1764
1988
|
console.error('')
|
|
1765
1989
|
console.error(
|
|
1766
|
-
|
|
1767
|
-
? `${packageName}
|
|
1768
|
-
:
|
|
1990
|
+
cpuHasAvx2
|
|
1991
|
+
? `${packageName} crashed, but this CPU supports AVX2; using the older-CPU binary until the next update.`
|
|
1992
|
+
: confirmed
|
|
1993
|
+
? `${packageName} is switching to the older-CPU binary for this machine.`
|
|
1994
|
+
: `${packageName} crashed on startup; trying the older-CPU binary.`,
|
|
1769
1995
|
)
|
|
1770
1996
|
|
|
1771
1997
|
try {
|
|
@@ -1781,6 +2007,27 @@ function createLauncher(productConfig) {
|
|
|
1781
2007
|
return true
|
|
1782
2008
|
}
|
|
1783
2009
|
|
|
2010
|
+
/**
|
|
2011
|
+
* What the evidence says about AVX2 after a crash: `{ avx2, evidence }`,
|
|
2012
|
+
* avx2 null when nothing can tell. Bun's own verdict that AVX2 is missing
|
|
2013
|
+
* wins, then the OS probe, then avx2 in Bun's `CPU:` line.
|
|
2014
|
+
*/
|
|
2015
|
+
function crashAvx2Verdict(code, signal, stderrTail) {
|
|
2016
|
+
if (
|
|
2017
|
+
stderrTail &&
|
|
2018
|
+
!(code === 0 && !signal) &&
|
|
2019
|
+
BUN_NO_AVX2_FEATURE.test(sanitizeForReplay(stderrTail))
|
|
2020
|
+
) {
|
|
2021
|
+
return { avx2: false, evidence: 'bun-report' }
|
|
2022
|
+
}
|
|
2023
|
+
const probed = probeCpuAvx2()
|
|
2024
|
+
if (probed !== null) return { avx2: probed, evidence: cpuProbeSource() }
|
|
2025
|
+
if (stderrReportsAvx2Present(stderrTail)) {
|
|
2026
|
+
return { avx2: true, evidence: 'bun-report' }
|
|
2027
|
+
}
|
|
2028
|
+
return { avx2: null, evidence: null }
|
|
2029
|
+
}
|
|
2030
|
+
|
|
1784
2031
|
function attachExitHandler(child, allowBaselineFallback = true) {
|
|
1785
2032
|
const exitListener = async (code, signal) => {
|
|
1786
2033
|
// A child we never watched (only reachable from a test) reports no age
|
|
@@ -1843,6 +2090,11 @@ function createLauncher(productConfig) {
|
|
|
1843
2090
|
detectMachineHasAvx2,
|
|
1844
2091
|
recordMachineLacksAvx2,
|
|
1845
2092
|
readCachedAvx2,
|
|
2093
|
+
readCpuFeatureRecord,
|
|
2094
|
+
parseCpuinfoAvx2,
|
|
2095
|
+
parseSysctlAvx2,
|
|
2096
|
+
stderrReportsAvx2Present,
|
|
2097
|
+
getDownloadTargetKey,
|
|
1846
2098
|
isIllegalInstructionExit,
|
|
1847
2099
|
isStartupCpuFeatureCrash,
|
|
1848
2100
|
stderrReportsMissingCpuFeature,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "freebuff",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "The world's strongest free coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"provenance": false
|
|
42
42
|
},
|
|
43
43
|
"binaryChecksums": {
|
|
44
|
-
"linux-x64": "
|
|
45
|
-
"linux-x64-baseline": "
|
|
46
|
-
"linux-arm64": "
|
|
47
|
-
"darwin-x64": "
|
|
48
|
-
"darwin-x64-baseline": "
|
|
49
|
-
"darwin-arm64": "
|
|
50
|
-
"win32-x64": "
|
|
51
|
-
"win32-x64-baseline": "
|
|
44
|
+
"linux-x64": "2c0f5b23b563830cbfb99f4fcc57e2657524f77159f2a8088b67f8279f7db3f9",
|
|
45
|
+
"linux-x64-baseline": "194b9ea6667ef7503412f4edca2d0e256cf8dc592e658bc147a46105f15aa44e",
|
|
46
|
+
"linux-arm64": "7609d2cad1055d0df2c9d73b95a10c98439eee77a78d739084af408c33d8e6b4",
|
|
47
|
+
"darwin-x64": "a1c7aa7fc361d1f5095d5c50dab409101a411b14951933c55b8f03d777600f5f",
|
|
48
|
+
"darwin-x64-baseline": "165d9077f04af2d30063d06445f0ddc2aa1feafadb034140d2f66435df4ae1c6",
|
|
49
|
+
"darwin-arm64": "2e99d58f7805c5ff8975e94ee6c31d23bfd2d0dd1094806659a1fa6c4c9c68e0",
|
|
50
|
+
"win32-x64": "ba239c2aae754927e11655cbc534c6c5dcfc3c41e6f8a8e9c4ede256acf23fc3",
|
|
51
|
+
"win32-x64-baseline": "cb1d911d296d481ffb1165bda1999c6e5633bb0d183c584fc121da2db7cf8abc"
|
|
52
52
|
}
|
|
53
53
|
}
|