freebuff 0.0.196 → 0.0.197

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.
Files changed (3) hide show
  1. package/index.js +1 -0
  2. package/launcher.js +72 -6
  3. package/package.json +9 -9
package/index.js CHANGED
@@ -29,6 +29,7 @@ const launcher = createLauncher({
29
29
  wrapperVersion: packageJson.version,
30
30
  binaryChecksums: packageJson.binaryChecksums ?? null,
31
31
  telemetryEvent: 'cli.update_freebuff_failed',
32
+ deferUpdatesUntilExit: true,
32
33
  })
33
34
 
34
35
  module.exports = launcher
package/launcher.js CHANGED
@@ -139,6 +139,9 @@ function createLauncher(productConfig) {
139
139
  startupBanner = [],
140
140
  telemetryEvent = 'cli.update_codebuff_failed',
141
141
  telemetryProperties = {},
142
+ // Freebuff sessions remain open between turns. The launcher cannot know
143
+ // when a restart is safe, so keep the process alive until the user exits.
144
+ deferUpdatesUntilExit = false,
142
145
  tempDownloadDirName = `.${packageName}-download-temp`,
143
146
  // Tests only. os.homedir() ignores $HOME under `bun test`, so pointing HOME
144
147
  // at a temp dir is not enough to keep a test off the real ~/.config.
@@ -267,7 +270,14 @@ function createLauncher(productConfig) {
267
270
  binaryName,
268
271
  binaryPath: path.join(configDir, binaryName),
269
272
  metadataPath: path.join(configDir, `${packageName}-metadata.json`),
270
- tempDownloadDir: path.join(configDir, tempDownloadDirName),
273
+ // A deferred download may wait here for an hour. Another invocation
274
+ // must not replace its staged binary (or delete it while downloading).
275
+ tempDownloadDir: path.join(
276
+ configDir,
277
+ deferUpdatesUntilExit
278
+ ? `${tempDownloadDirName}-${process.pid}`
279
+ : tempDownloadDirName,
280
+ ),
271
281
  userAgent: `${packageName}-cli`,
272
282
  requestTimeout: 20000,
273
283
  downloadRequestTimeout: 120000,
@@ -276,6 +286,7 @@ function createLauncher(productConfig) {
276
286
  }
277
287
 
278
288
  const CONFIG = createConfig(packageName)
289
+ const pendingUpdates = new WeakMap()
279
290
  const { downloadFile, httpGet, withRetries } = createReleaseHttpClient({
280
291
  env: process.env,
281
292
  userAgent: CONFIG.userAgent,
@@ -1085,7 +1096,10 @@ function createLauncher(productConfig) {
1085
1096
  }
1086
1097
  }
1087
1098
 
1088
- function installStagedBinary({ tempBinaryPath, version, targetKey }) {
1099
+ function installStagedBinary(
1100
+ { tempBinaryPath, version, targetKey },
1101
+ { quiet = false } = {},
1102
+ ) {
1089
1103
  const replacements = []
1090
1104
  const metadataTempPath = `${CONFIG.metadataPath}.new.${process.pid}`
1091
1105
 
@@ -1133,8 +1147,10 @@ function createLauncher(productConfig) {
1133
1147
  }
1134
1148
  }
1135
1149
 
1136
- term.clearLine()
1137
- console.log(`Download complete! Starting ${displayName}...`)
1150
+ if (!quiet) {
1151
+ term.clearLine()
1152
+ console.log(`Download complete! Starting ${displayName}...`)
1153
+ }
1138
1154
  }
1139
1155
 
1140
1156
  async function downloadBinary(
@@ -1240,7 +1256,11 @@ function createLauncher(productConfig) {
1240
1256
  })
1241
1257
  }
1242
1258
 
1243
- async function checkForUpdates(runningProcess, exitListener) {
1259
+ async function checkForUpdates(
1260
+ runningProcess,
1261
+ exitListener,
1262
+ fetchLatestRelease = getLatestRelease,
1263
+ ) {
1244
1264
  // main() schedules this 100ms after launch, so the binary it was handed can
1245
1265
  // already be dead — a startup crash that handed off to the baseline
1246
1266
  // fallback, most of all. Updating around a corpse would race that
@@ -1259,9 +1279,14 @@ function createLauncher(productConfig) {
1259
1279
  try {
1260
1280
  const currentVersion = getCurrentVersion()
1261
1281
 
1262
- const latestRelease = await getLatestRelease()
1282
+ const latestRelease = await fetchLatestRelease()
1263
1283
  const latestVersion = latestRelease?.version ?? null
1264
1284
  if (!latestVersion) return
1285
+ if (
1286
+ runningProcess.exitCode !== null ||
1287
+ runningProcess.signalCode !== null
1288
+ )
1289
+ return
1265
1290
 
1266
1291
  if (
1267
1292
  // Download new version if current version is unknown or outdated.
@@ -1274,6 +1299,19 @@ function createLauncher(productConfig) {
1274
1299
  { quiet: true, binaryChecksums: latestRelease.binaryChecksums },
1275
1300
  )
1276
1301
 
1302
+ if (deferUpdatesUntilExit) {
1303
+ // No signals, terminal output, or binary/WASM replacement while the
1304
+ // CLI owns the session. Even an idle model picker can admit a session
1305
+ // while the download is in flight. A download that loses the race
1306
+ // with exit must not install into a fallback child's running files.
1307
+ if (
1308
+ runningProcess.exitCode === null &&
1309
+ runningProcess.signalCode === null
1310
+ )
1311
+ pendingUpdates.set(runningProcess, stagedBinary)
1312
+ return
1313
+ }
1314
+
1277
1315
  term.clearLine()
1278
1316
 
1279
1317
  runningProcess.removeListener('exit', exitListener)
@@ -1313,6 +1351,33 @@ function createLauncher(productConfig) {
1313
1351
  }
1314
1352
  }
1315
1353
 
1354
+ function installPendingUpdate(child) {
1355
+ const staged = pendingUpdates.get(child)
1356
+ pendingUpdates.delete(child)
1357
+ if (!staged) return
1358
+
1359
+ try {
1360
+ // A second launcher may have installed a newer release while this
1361
+ // session was open. Never roll its installation back on our exit.
1362
+ const currentVersion = getCurrentVersion()
1363
+ if (
1364
+ currentVersion &&
1365
+ compareVersions(currentVersion, staged.version) >= 0
1366
+ ) {
1367
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true, force: true })
1368
+ return
1369
+ }
1370
+ installStagedBinary(staged, { quiet: true })
1371
+ console.log(
1372
+ `Updated ${displayName} to ${staged.version}. Ready for your next launch.`,
1373
+ )
1374
+ } catch (error) {
1375
+ // Installation rolls back on failure. Updating must not change the
1376
+ // user's exit code or resurrect a CLI they just closed.
1377
+ console.error(`Could not install ${displayName} update: ${error.message}`)
1378
+ }
1379
+ }
1380
+
1316
1381
  /**
1317
1382
  * Make captured output safe to print back to the terminal.
1318
1383
  *
@@ -1693,6 +1758,7 @@ function createLauncher(productConfig) {
1693
1758
  exitAlternateScreen: shouldExitAlternateScreen(code, signal),
1694
1759
  })
1695
1760
  printCrashDiagnostics(code, signal, { msAlive, stderrTail })
1761
+ installPendingUpdate(child)
1696
1762
  process.exit(signal ? 1 : code || 0)
1697
1763
  }
1698
1764
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freebuff",
3
- "version": "0.0.196",
3
+ "version": "0.0.197",
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": "43a19d7def718fbd3ab316ae9dcf90b0839e50c4ed9688c9b99794a7b258d2aa",
45
- "linux-x64-baseline": "2f2b277ba60369535828c5945561ea3d64c0de4d7cbca29389899197f3d86520",
46
- "linux-arm64": "481d8becd750d13ce5da9e41e33029bfae63d417a29752cbdd36b9c5fef42f8b",
47
- "darwin-x64": "cf82f805d6646d00551f47b9d25fa0691062d815480902d0d66e83feeddf8767",
48
- "darwin-x64-baseline": "b0e2dc5bfee7eccc2fbc861cfcbd28e0a0a806e8ce84ad2d0628f463118ccfae",
49
- "darwin-arm64": "31481e33ebd1ce657871e638814a0cf40983ee34826f320948e7c3a27c1aa606",
50
- "win32-x64": "a3915eb379f9c5e7d001fd209831aa2ff9bd610f2e7a33f6f66b0d8d7f46cda7",
51
- "win32-x64-baseline": "6562e33a4842207a7be9a0c69dda3a621ed412a0be50d96177771f34330da52c"
44
+ "linux-x64": "d7c809028c05beedda432b6bedb8defbdaa63f9ab189ca212306a539d351ca1f",
45
+ "linux-x64-baseline": "5e22f749d0ab0dda6fab36d39280824833109919c7ce4895d595e5ee6588a1fe",
46
+ "linux-arm64": "22d7607e57d215e3f1a290111c6e2bb1c71bbc5b84136e6661b626437fecad99",
47
+ "darwin-x64": "0b55ac5383f5ed8f2e4b1739428d435be1b6d729fb3f56c05b1564dd2dd97333",
48
+ "darwin-x64-baseline": "9588e4c8428ca996004fb57dbe021027d54abb1d69c937f830874c0585c4a815",
49
+ "darwin-arm64": "f732bf1a71a57e312a973264a835ca68eb55ce5e56371f052c5894576df2b016",
50
+ "win32-x64": "fa7309df307038cd474b6ba248754fdea011b245d1cffa2ca42d9f79f3ba71b4",
51
+ "win32-x64-baseline": "7fce9f5abe69b7b3babb63dc338b0c349dffbafbe9d0ccb731ef9c2bed8af89d"
52
52
  }
53
53
  }