bitpull 0.1.19 → 0.1.20

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 (2) hide show
  1. package/bitpull.cjs +29 -8
  2. package/package.json +1 -1
package/bitpull.cjs CHANGED
@@ -18538,7 +18538,7 @@ async function heartbeat(ctx) {
18538
18538
  boxKey: ctx.config.boxKeys.publicKey,
18539
18539
  ...ctx.groups ? { groupStates: ctx.groups.reports() } : {},
18540
18540
  // Development runs have no version number.
18541
- ...true ? { agentVersion: "0.1.19" } : {}
18541
+ ...true ? { agentVersion: "0.1.20" } : {}
18542
18542
  });
18543
18543
  }
18544
18544
  async function startGroup(ctx, modelId) {
@@ -19289,9 +19289,9 @@ async function executeJob(ctx, job, sandbox, signal, keyWaitMs = KEY_WAIT_MS) {
19289
19289
  const safe = containerJobFrom(job, secret, ctx.config.lending);
19290
19290
  outcome = sandbox ? await runContainer(safe, sandbox, signal) : { ok: false, usage: {}, error: "no_sandbox" };
19291
19291
  } else {
19292
- const limit = await aiLimitReached(ctx.config.lending.aiLimits);
19292
+ const limit = job.own ? null : await aiLimitReached(ctx.config.lending.aiLimits);
19293
19293
  outcome = limit ? { ok: false, usage: {}, error: `lender_limit_reached: ${limit}` } : await runAiTask(spec, job.timeoutSec, ctx.config.ollamaUrl, signal);
19294
- if (outcome.ok) await recordAiUsage((outcome.usage.promptTokens ?? 0) + (outcome.usage.completionTokens ?? 0)).catch(() => void 0);
19294
+ if (outcome.ok && !job.own) await recordAiUsage((outcome.usage.promptTokens ?? 0) + (outcome.usage.completionTokens ?? 0)).catch(() => void 0);
19295
19295
  }
19296
19296
  } catch (err) {
19297
19297
  const message = err.message;
@@ -19611,7 +19611,7 @@ async function doctor(cfg) {
19611
19611
  }
19612
19612
 
19613
19613
  // apps/agent/src/control.ts
19614
- var APP_VERSION = true ? "0.1.19" : "dev";
19614
+ var APP_VERSION = true ? "0.1.20" : "dev";
19615
19615
  var UPDATE_MANIFEST = "https://github.com/SamuealPK/bitpull-releases/releases/latest/download/latest.json";
19616
19616
  var inDesktopApp = () => /bitpull-agent(\.exe)?$/i.test(process.execPath);
19617
19617
  function newerVersion(a, b) {
@@ -30906,6 +30906,7 @@ var DATA = 2;
30906
30906
  var CLOSE = 3;
30907
30907
  var CHUNK = 64 * 1024;
30908
30908
  var PAUSE_BYTES = 4 * 1024 * 1024;
30909
+ var KEEPALIVE_MS = 25e3;
30909
30910
  var TunnelCipher = class {
30910
30911
  constructor(groupKey, hostSalt, workerSalt, role) {
30911
30912
  this.role = role;
@@ -30998,6 +30999,13 @@ async function startTunnel(o) {
30998
30999
  ws = socket;
30999
31000
  cipher = null;
31000
31001
  const mySalt = (0, import_node_crypto8.randomBytes)(32);
31002
+ let answered = true;
31003
+ const keepalive = setInterval(() => {
31004
+ if (!answered) return socket.terminate();
31005
+ answered = false;
31006
+ if (socket.readyState === socket.OPEN) socket.ping();
31007
+ }, o.keepaliveMs ?? KEEPALIVE_MS);
31008
+ socket.on("pong", () => answered = true);
31001
31009
  socket.on("message", (data, isBinary2) => {
31002
31010
  try {
31003
31011
  const buf = data;
@@ -31019,6 +31027,7 @@ async function startTunnel(o) {
31019
31027
  }
31020
31028
  });
31021
31029
  socket.on("close", () => {
31030
+ clearInterval(keepalive);
31022
31031
  o.onStatus?.(false);
31023
31032
  cipher = null;
31024
31033
  closeAll();
@@ -31130,14 +31139,15 @@ var freePort = () => new Promise((resolve, reject) => {
31130
31139
  s.close(() => resolve(port));
31131
31140
  });
31132
31141
  });
31142
+ var engineReason = (tail) => tail.trim().split("\n").filter((l) => !/^\s*\d+\s+\S+\s+0x/.test(l)).pop() ?? "";
31133
31143
  function startEngine(exe, args, onExit) {
31134
31144
  const dir = (0, import_node_path17.dirname)(exe);
31135
31145
  const env = { ...process.env, LD_LIBRARY_PATH: dir, DYLD_LIBRARY_PATH: dir, LLAMA_CACHE: (0, import_node_path17.join)(bitpullHome(), "group-cache") };
31136
31146
  const child = (0, import_node_child_process8.spawn)(exe, args, { cwd: dir, windowsHide: true, stdio: ["ignore", "ignore", "pipe"], env });
31137
31147
  let tail = "";
31138
- child.stderr?.on("data", (d) => tail = (tail + d.toString()).slice(-300));
31148
+ child.stderr?.on("data", (d) => tail = (tail + d.toString()).slice(-4e3));
31139
31149
  child.on("error", (err) => onExit(err.message));
31140
- child.on("close", (code) => onExit(`engine stopped (${code}): ${tail.trim().split("\n").pop() ?? ""}`));
31150
+ child.on("close", (code) => onExit(`engine stopped (${code}): ${engineReason(tail)}`));
31141
31151
  return child;
31142
31152
  }
31143
31153
  var RETRY_MS = 6e4;
@@ -31189,6 +31199,12 @@ var GroupManager = class {
31189
31199
  this.failures.set(id, (this.failures.get(id) ?? 0) + 1);
31190
31200
  this.set(id, "error", err.message ?? String(err));
31191
31201
  }
31202
+ /** A tunnel fell: the engine lost its connection for good, so it starts again as soon as it can. */
31203
+ reconnect(id) {
31204
+ this.running.get(id)?.stop();
31205
+ this.running.delete(id);
31206
+ this.set(id, "waiting");
31207
+ }
31192
31208
  async start(o) {
31193
31209
  const stops = [];
31194
31210
  const entry = { order: o, stop: () => stops.forEach((s) => s()) };
@@ -31221,11 +31237,15 @@ var GroupManager = class {
31221
31237
  const file = await ensureModel(model, (p) => this.set(o.id, "downloading", `${p}%`));
31222
31238
  if (!alive()) return;
31223
31239
  this.set(o.id, "waiting");
31224
- const tunnels = await Promise.all(o.workers.map((workerId) => startTunnel({ ...common, workerId, role: "host" })));
31240
+ let started = false;
31241
+ const tunnels = await Promise.all(
31242
+ o.workers.map((workerId) => startTunnel({ ...common, workerId, role: "host", onStatus: (c) => !c && started && alive() && this.reconnect(o.id) }))
31243
+ );
31225
31244
  for (const t of tunnels) stops.push(t.stop);
31226
31245
  while (alive() && !tunnels.every((t) => t.connected())) await new Promise((r) => setTimeout(r, 1e3));
31227
31246
  if (!alive()) return;
31228
31247
  this.set(o.id, "loading");
31248
+ started = true;
31229
31249
  const port = await freePort();
31230
31250
  const apiKey = (0, import_node_crypto9.randomBytes)(24).toString("hex");
31231
31251
  const exe = await findExe(dir, exeName("llama-server"));
@@ -31241,6 +31261,7 @@ var GroupManager = class {
31241
31261
  if (!alive()) return;
31242
31262
  entry.url = url;
31243
31263
  entry.apiKey = apiKey;
31264
+ this.failures.delete(o.id);
31244
31265
  this.set(o.id, "ready");
31245
31266
  } catch (err) {
31246
31267
  if (alive()) this.fail(o.id, err);
@@ -31450,7 +31471,7 @@ function openBrowser(url) {
31450
31471
  const args = (0, import_node_os13.platform)() === "win32" ? ["/c", "start", "", url] : [url];
31451
31472
  (0, import_node_child_process9.spawn)(cmd, args, { stdio: "ignore", detached: true }).unref();
31452
31473
  }
31453
- var agentVersion = () => true ? "0.1.19" : "dev";
31474
+ var agentVersion = () => true ? "0.1.20" : "dev";
31454
31475
  var dashboardUrl = (cfg) => `http://127.0.0.1:${cfg.controlPort}/#token=${cfg.controlToken}`;
31455
31476
  var AppNotRunning = class extends Error {
31456
31477
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitpull",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "BitPull (bitpull.cloud): share signed, AI-readable code, computing power and open AI models with AI tools through MCP. CLI and MCP server.",
5
5
  "mcpName": "io.github.SamuealPK/bitpull",
6
6
  "bin": {