turbollm 1.10.0 → 1.10.1

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/dist/cli.js +284 -55
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -34,9 +34,9 @@ function hideChildConsoleWindows() {
34
34
 
35
35
  // src/cli.ts
36
36
  import { spawn as spawn10 } from "child_process";
37
- import { openSync as openSync3, readFileSync as readFileSync19 } from "fs";
37
+ import { openSync as openSync3, readFileSync as readFileSync20 } from "fs";
38
38
  import { serve } from "@hono/node-server";
39
- import { dirname as dirname12, join as join36 } from "path";
39
+ import { dirname as dirname12, join as join37 } from "path";
40
40
  import { fileURLToPath as fileURLToPath2 } from "url";
41
41
 
42
42
  // src/config/config.ts
@@ -3348,6 +3348,95 @@ function isStaleCapabilities(flags) {
3348
3348
  return false;
3349
3349
  }
3350
3350
 
3351
+ // src/telemetry/classify.ts
3352
+ var OOM_SIGNS = [
3353
+ "out of memory",
3354
+ "outofmemory",
3355
+ "failed to allocate",
3356
+ "alloc_buffer",
3357
+ "memory allocation of size",
3358
+ "cudamalloc",
3359
+ "insufficient memory",
3360
+ "not enough memory",
3361
+ "unable to allocate",
3362
+ "bad_alloc",
3363
+ "out of vram",
3364
+ "vram allocation failed",
3365
+ "cuda_error_out_of_memory",
3366
+ "resource_exhausted"
3367
+ ];
3368
+ var ARCH_SIGNS = [
3369
+ "unknown model architecture",
3370
+ "unsupported model architecture",
3371
+ "unsupported architecture",
3372
+ "architecture not supported",
3373
+ "unknown architecture"
3374
+ ];
3375
+ var GGUF_SIGNS = [
3376
+ "invalid magic",
3377
+ "error loading model",
3378
+ "failed to load model",
3379
+ "gguf_init",
3380
+ "corrupt",
3381
+ "unexpected end of file",
3382
+ "unexpectedly reached end of file",
3383
+ "truncated",
3384
+ "failed to read tensor",
3385
+ "wrong number of tensors",
3386
+ "tensor not found"
3387
+ ];
3388
+ function classifyLoadFailure(err8) {
3389
+ if (!err8) return "other";
3390
+ if (err8.code === "readiness_timeout") return "timeout";
3391
+ if (err8.code === "engine_unsupported") return "no_engine";
3392
+ const haystack = [err8.message ?? "", ...err8.logTail ?? []].join("\n").toLowerCase();
3393
+ if (haystack.includes("no_active_engine")) return "no_engine";
3394
+ if (haystack.includes("no_such_model")) return "bad_gguf";
3395
+ if (OOM_SIGNS.some((s) => haystack.includes(s))) return "oom";
3396
+ if (ARCH_SIGNS.some((s) => haystack.includes(s))) return "unsupported_arch";
3397
+ if (GGUF_SIGNS.some((s) => haystack.includes(s))) return "bad_gguf";
3398
+ return "other";
3399
+ }
3400
+ function classifyBenchFailure(results) {
3401
+ if (results.some((r) => r.outcome === "oom")) return "oom";
3402
+ if (results.some((r) => r.outcome === "timeout")) return "timeout";
3403
+ return "other";
3404
+ }
3405
+ function classifyEngineErrorFingerprint(err8) {
3406
+ if (!err8) return "other";
3407
+ if (err8.code === "readiness_timeout") return "engine_start_timeout";
3408
+ if (err8.code === "model_load_failed") return "model_load_failed";
3409
+ const haystack = [err8.message ?? "", ...err8.logTail ?? []].join("\n").toLowerCase();
3410
+ if (OOM_SIGNS.some((s) => haystack.includes(s))) return "cuda_oom";
3411
+ if (ARCH_SIGNS.some((s) => haystack.includes(s))) return "model_load_failed";
3412
+ if (GGUF_SIGNS.some((s) => haystack.includes(s))) return "model_load_failed";
3413
+ if (err8.code === "engine_exited" || err8.code === "engine_spawn_failed") return "engine_crash";
3414
+ return "other";
3415
+ }
3416
+ var NETWORK_SIGNS = [
3417
+ "enotfound",
3418
+ "econnreset",
3419
+ "econnrefused",
3420
+ "etimedout",
3421
+ "fetch failed",
3422
+ "network",
3423
+ "check your connection"
3424
+ ];
3425
+ var NO_ASSET_SIGNS = ["404", "no downloadable binary", "no release asset", "not found in"];
3426
+ var UNSUPPORTED_PLATFORM_SIGNS = ["no prebuilt binary for this operating system", "unsupported platform", "unsupported architecture"];
3427
+ var DISK_FULL_SIGNS = ["enospc", "no space left"];
3428
+ var PERMISSION_SIGNS = ["eacces", "eperm", "permission denied", "access is denied"];
3429
+ function classifyProvisionFailure(message) {
3430
+ if (!message) return "other";
3431
+ const haystack = message.toLowerCase();
3432
+ if (UNSUPPORTED_PLATFORM_SIGNS.some((s) => haystack.includes(s))) return "unsupported_platform";
3433
+ if (NO_ASSET_SIGNS.some((s) => haystack.includes(s))) return "no_asset";
3434
+ if (DISK_FULL_SIGNS.some((s) => haystack.includes(s))) return "disk_full";
3435
+ if (PERMISSION_SIGNS.some((s) => haystack.includes(s))) return "permission_denied";
3436
+ if (NETWORK_SIGNS.some((s) => haystack.includes(s))) return "network";
3437
+ return "other";
3438
+ }
3439
+
3351
3440
  // src/engines/provision-state.ts
3352
3441
  var ProvisionState = class {
3353
3442
  s = { active: false, phase: "idle", backend: "", pct: 0, part: 1, parts: 1, error: null };
@@ -3365,8 +3454,9 @@ var ProvisionState = class {
3365
3454
  this.s.parts = parts;
3366
3455
  }
3367
3456
  /** Optional observer for the terminal outcome, wired in cli.ts (ADR-299
3368
- * `onboarding_step`). The error string is deliberately not passed — the only
3369
- * consumer may never transmit free text. */
3457
+ * `onboarding_step`). The raw error STRING is deliberately never passed —
3458
+ * only `failReason`, a `classifyProvisionFailure` enum member, so the only
3459
+ * consumer still never sees free text even though it now learns *why*. */
3370
3460
  onSettled;
3371
3461
  done() {
3372
3462
  this.s = { active: false, phase: "idle", backend: "", pct: 0, part: 1, parts: 1, error: null };
@@ -3374,11 +3464,11 @@ var ProvisionState = class {
3374
3464
  }
3375
3465
  fail(error) {
3376
3466
  this.s = { active: false, phase: "error", backend: this.s.backend, pct: 0, part: 1, parts: 1, error };
3377
- this.settle(false);
3467
+ this.settle(false, classifyProvisionFailure(error));
3378
3468
  }
3379
- settle(ok) {
3469
+ settle(ok, failReason) {
3380
3470
  try {
3381
- this.onSettled?.(ok);
3471
+ this.onSettled?.(ok, failReason);
3382
3472
  } catch {
3383
3473
  }
3384
3474
  }
@@ -3411,21 +3501,31 @@ var BuildState = class {
3411
3501
  if (this.s.log.length > LOG_TAIL) this.s.log.splice(0, this.s.log.length - LOG_TAIL);
3412
3502
  }
3413
3503
  /** Optional observer for the terminal outcome, wired in cli.ts (ADR-299
3414
- * `onboarding_step`). A plain callback so this module keeps no telemetry
3415
- * dependency; the error STRING is deliberately not passed, because the only
3416
- * consumer may never send free text. */
3504
+ * `onboarding_step` / ADR-327 `error`). A plain callback so this module keeps
3505
+ * no telemetry dependency; the error STRING is deliberately never passed,
3506
+ * because the only consumer may never send free text. Three outcomes, not
3507
+ * two — `cancelled` is distinct from `fail` (PR #105 review finding): a
3508
+ * build the user deliberately aborted must not be reported as a crash. */
3417
3509
  onSettled;
3418
3510
  done() {
3419
3511
  this.s = { ...this.s, active: false, phase: "done", error: null };
3420
- this.settle(true);
3512
+ this.settle("ok");
3421
3513
  }
3422
3514
  fail(error) {
3423
3515
  this.s = { ...this.s, active: false, phase: "error", error };
3424
- this.settle(false);
3516
+ this.settle("fail");
3425
3517
  }
3426
- settle(ok) {
3518
+ /** A deliberate user cancellation. Same terminal UI shape as `fail()` (phase
3519
+ * 'error', so the UI still shows the run ended and why) — only the observer
3520
+ * outcome differs. A cancel is a choice, not a failure, and must not be
3521
+ * reported as one. */
3522
+ cancel(message) {
3523
+ this.s = { ...this.s, active: false, phase: "error", error: message };
3524
+ this.settle("cancelled");
3525
+ }
3526
+ settle(outcome) {
3427
3527
  try {
3428
- this.onSettled?.(ok);
3528
+ this.onSettled?.(outcome);
3429
3529
  } catch {
3430
3530
  }
3431
3531
  }
@@ -15142,6 +15242,14 @@ var FAIL_REASONS = [
15142
15242
  "cancelled",
15143
15243
  "other"
15144
15244
  ];
15245
+ var PROVISION_FAIL_REASONS = [
15246
+ "network",
15247
+ "no_asset",
15248
+ "unsupported_platform",
15249
+ "disk_full",
15250
+ "permission_denied",
15251
+ "other"
15252
+ ];
15145
15253
  var COUNT_BUCKETS = ["1", "2-5", "6-20", "21-100", "100+"];
15146
15254
  var ERROR_FINGERPRINTS = [
15147
15255
  "cuda_oom",
@@ -15197,7 +15305,14 @@ var HW_SPEC = {
15197
15305
  ramMb: { kind: "number" }
15198
15306
  };
15199
15307
  var PAYLOAD_SPECS = {
15200
- onboarding_step: { step: { enum: ONBOARDING_STEPS }, outcome: { enum: OUTCOMES } },
15308
+ // `failReason` is optional and, today, only ever sent for `step: 'engine_install'`
15309
+ // (classified by `classifyProvisionFailure`, PROVISION_FAIL_REASONS) — mirrors
15310
+ // `model_first_load.failReason`'s own "optional, only present on a real failure"
15311
+ // shape. Not step-conditional at the schema level (this validator is flat per
15312
+ // event name, not per payload value) — enforced by convention at the one call
15313
+ // site that sends it (cli.ts's `provision.onSettled`), same as elsewhere in this
15314
+ // file's payload specs.
15315
+ onboarding_step: { step: { enum: ONBOARDING_STEPS }, outcome: { enum: OUTCOMES }, failReason: { enum: PROVISION_FAIL_REASONS, optional: true } },
15201
15316
  model_first_load: { outcome: { enum: OUTCOMES }, failReason: { enum: FAIL_REASONS, optional: true } },
15202
15317
  feature_first_use: { feature: { enum: FEATURES } },
15203
15318
  feature_used_daily: { feature: { enum: FEATURES }, countBucket: { enum: COUNT_BUCKETS } },
@@ -15379,36 +15494,6 @@ function reportModelLoad(dataDir, emitter, outcome, failReason) {
15379
15494
  }
15380
15495
  }
15381
15496
 
15382
- // src/telemetry/classify.ts
15383
- var OOM_SIGNS = [
15384
- "out of memory",
15385
- "outofmemory",
15386
- "failed to allocate",
15387
- "alloc_buffer",
15388
- "memory allocation of size",
15389
- "cudamalloc",
15390
- "insufficient memory"
15391
- ];
15392
- var ARCH_SIGNS = ["unknown model architecture", "unsupported model architecture", "unsupported architecture"];
15393
- var GGUF_SIGNS = ["invalid magic", "error loading model", "failed to load model", "gguf_init", "corrupt"];
15394
- function classifyLoadFailure(err8) {
15395
- if (!err8) return "other";
15396
- if (err8.code === "readiness_timeout") return "timeout";
15397
- if (err8.code === "engine_unsupported") return "no_engine";
15398
- const haystack = [err8.message ?? "", ...err8.logTail ?? []].join("\n").toLowerCase();
15399
- if (haystack.includes("no_active_engine")) return "no_engine";
15400
- if (haystack.includes("no_such_model")) return "bad_gguf";
15401
- if (OOM_SIGNS.some((s) => haystack.includes(s))) return "oom";
15402
- if (ARCH_SIGNS.some((s) => haystack.includes(s))) return "unsupported_arch";
15403
- if (GGUF_SIGNS.some((s) => haystack.includes(s))) return "bad_gguf";
15404
- return "other";
15405
- }
15406
- function classifyBenchFailure(results) {
15407
- if (results.some((r) => r.outcome === "oom")) return "oom";
15408
- if (results.some((r) => r.outcome === "timeout")) return "timeout";
15409
- return "other";
15410
- }
15411
-
15412
15497
  // src/telemetry/disabled.ts
15413
15498
  var TELEMETRY_ENV = "TURBOLLM_TELEMETRY";
15414
15499
  var OFF_VALUES = /* @__PURE__ */ new Set(["off", "0", "false", "no"]);
@@ -15618,6 +15703,9 @@ var BenchRunner = class {
15618
15703
  this.telemetry.emit("onboarding_step", { step: "first_load", outcome });
15619
15704
  }
15620
15705
  reportModelLoad(this.store.dir(), this.telemetry, outcome, failReason);
15706
+ if (outcome === "fail") {
15707
+ this.telemetry.error(failReason === "oom" ? "cuda_oom" : failReason === "timeout" ? "engine_start_timeout" : "other");
15708
+ }
15621
15709
  }
15622
15710
  /** Stop the engine — force-killed immediately when the run is cancelled (the user is actively
15623
15711
  * waiting for it to stop, ADR-220), gracefully otherwise (lets llama-server release resources
@@ -20296,7 +20384,7 @@ function registerApi(app2, d) {
20296
20384
  }
20297
20385
  if (e?.name === "AbortError") {
20298
20386
  d.build.log("Build cancelled.");
20299
- d.build.fail("Build cancelled.");
20387
+ d.build.cancel("Build cancelled.");
20300
20388
  } else {
20301
20389
  const asmIssue = d.build.get().log.some((l) => /No CMAKE_ASM_COMPILER could be found|ASM compiler identification is unknown/i.test(l));
20302
20390
  const raw = e instanceof Error ? e.message : String(e);
@@ -20371,7 +20459,7 @@ function registerApi(app2, d) {
20371
20459
  } catch (e) {
20372
20460
  if (e?.name === "AbortError") {
20373
20461
  d.build.log("CUDA download cancelled.");
20374
- d.build.fail("CUDA download cancelled.");
20462
+ d.build.cancel("CUDA download cancelled.");
20375
20463
  } else {
20376
20464
  d.build.fail(`Could not download CUDA: ${e instanceof Error ? e.message : String(e)}`);
20377
20465
  }
@@ -24226,7 +24314,15 @@ async function recordOpenAiStreamUsage(d, body8, source, modelKey, codeSessionId
24226
24314
 
24227
24315
  // src/telemetry/feature-map.ts
24228
24316
  var BY_SEGMENT = {
24317
+ // `chat` (bare) only covers `/api/v1/chat/stop` — the Stop-generation button. Real
24318
+ // chat traffic (sending a message, creating/loading a conversation) lives under
24319
+ // `/api/v1/conversations/*` instead (PR #105 review finding: `chat` alone was
24320
+ // silently under-counting the product's own core feature). `conversations` IS the
24321
+ // chat feature's own primary data endpoint, not a foreign feature fetched as a side
24322
+ // effect the way `/api/v1/skills`/`/api/v1/chat-agents` are (see the comment below) —
24323
+ // so, unlike those two, mapping it here doesn't risk corrupting an unrelated signal.
24229
24324
  chat: "chat",
24325
+ conversations: "chat",
24230
24326
  code: "code",
24231
24327
  artifacts: "artifacts",
24232
24328
  mcp: "mcp",
@@ -24272,7 +24368,10 @@ function createApp(d) {
24272
24368
  app2.use("/api/v1/code/*", codeAuth(d));
24273
24369
  app2.use("*", async (c, next) => {
24274
24370
  const feature = d.telemetry ? featureForPath(c.req.path) : null;
24275
- if (feature !== null) d.telemetry?.firstUse(feature);
24371
+ if (feature !== null) {
24372
+ d.telemetry?.firstUse(feature);
24373
+ d.telemetry?.useFeature(feature);
24374
+ }
24276
24375
  await next();
24277
24376
  });
24278
24377
  app2.get("/healthz", (c) => c.json({ status: "ok", version: d.version }));
@@ -24578,6 +24677,89 @@ var TunnelManager = class {
24578
24677
 
24579
24678
  // src/telemetry/emit.ts
24580
24679
  import { randomUUID as randomUUID14 } from "crypto";
24680
+
24681
+ // src/telemetry/daily-usage.ts
24682
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync17, mkdirSync as mkdirSync19 } from "fs";
24683
+ import { join as join36 } from "path";
24684
+ var cache = /* @__PURE__ */ new Map();
24685
+ function usagePath(dataDir) {
24686
+ return join36(dataDir, "telemetry", "daily-usage.json");
24687
+ }
24688
+ function readFromDisk(dataDir) {
24689
+ try {
24690
+ const parsed = JSON.parse(readFileSync19(usagePath(dataDir), "utf8"));
24691
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
24692
+ return parsed;
24693
+ } catch {
24694
+ return {};
24695
+ }
24696
+ }
24697
+ function writeToDisk(dataDir, usage) {
24698
+ mkdirSync19(join36(dataDir, "telemetry"), { recursive: true });
24699
+ writeFileSync17(usagePath(dataDir), JSON.stringify(usage));
24700
+ }
24701
+ function loadCache(dataDir) {
24702
+ let usage = cache.get(dataDir);
24703
+ if (usage === void 0) {
24704
+ usage = readFromDisk(dataDir);
24705
+ cache.set(dataDir, usage);
24706
+ }
24707
+ return usage;
24708
+ }
24709
+ function bucketCount(n) {
24710
+ if (n <= 1) return "1";
24711
+ if (n <= 5) return "2-5";
24712
+ if (n <= 20) return "6-20";
24713
+ if (n <= 100) return "21-100";
24714
+ return "100+";
24715
+ }
24716
+ function recordFeatureUse(dataDir, feature, today) {
24717
+ try {
24718
+ const usage = loadCache(dataDir);
24719
+ const existing = usage[feature];
24720
+ if (existing === void 0) {
24721
+ usage[feature] = { day: today, count: 1 };
24722
+ return null;
24723
+ }
24724
+ if (existing.day === today) {
24725
+ usage[feature] = { day: today, count: existing.count + 1 };
24726
+ return null;
24727
+ }
24728
+ const rolled = { day: existing.day, bucket: bucketCount(existing.count) };
24729
+ usage[feature] = { day: today, count: 1 };
24730
+ writeToDisk(dataDir, usage);
24731
+ return rolled;
24732
+ } catch {
24733
+ return null;
24734
+ }
24735
+ }
24736
+ function flushStaleDailyUsage(dataDir, today) {
24737
+ try {
24738
+ const usage = loadCache(dataDir);
24739
+ const rolled = [];
24740
+ let changed = false;
24741
+ for (const [feature, entry] of Object.entries(usage)) {
24742
+ if (entry.day === today) continue;
24743
+ rolled.push({ feature, day: entry.day, bucket: bucketCount(entry.count) });
24744
+ delete usage[feature];
24745
+ changed = true;
24746
+ }
24747
+ if (changed) writeToDisk(dataDir, usage);
24748
+ return rolled;
24749
+ } catch {
24750
+ return [];
24751
+ }
24752
+ }
24753
+ function persistDailyUsage(dataDir) {
24754
+ try {
24755
+ const usage = cache.get(dataDir);
24756
+ if (usage === void 0) return;
24757
+ writeToDisk(dataDir, usage);
24758
+ } catch {
24759
+ }
24760
+ }
24761
+
24762
+ // src/telemetry/emit.ts
24581
24763
  var REQUIRES_FULL = /* @__PURE__ */ new Set(["error"]);
24582
24764
  var Emitter = class {
24583
24765
  o;
@@ -24634,6 +24816,45 @@ var Emitter = class {
24634
24816
  if (!this.canSend("daily_active")) return;
24635
24817
  if (this.claim(`daily:${this.today()}`)) this.emit("daily_active");
24636
24818
  }
24819
+ /**
24820
+ * Count one use of `feature` toward today's tally, emitting a bucketed
24821
+ * `feature_used_daily` for the previous day the moment the calendar day
24822
+ * rolls over. Consent is checked BEFORE any count is recorded — same
24823
+ * reasoning as `firstUse` above (see its doc comment): usage while
24824
+ * telemetry is off must never silently contribute to a count reported
24825
+ * after the user opts in later.
24826
+ */
24827
+ useFeature(feature) {
24828
+ if (!this.canSend("feature_used_daily")) return;
24829
+ const rolled = recordFeatureUse(this.o.dataDir, feature, this.today());
24830
+ if (rolled) this.emit("feature_used_daily", { feature, countBucket: rolled.bucket });
24831
+ }
24832
+ /**
24833
+ * Roll over any feature whose tally is from a day earlier than today, then
24834
+ * persist whatever's left (today's still-in-progress counts) to disk.
24835
+ * Called periodically (cli.ts, alongside the queue flush) for two reasons:
24836
+ * a feature used only on a user's LAST active day still gets reported
24837
+ * instead of waiting indefinitely for a next use that may never come, AND
24838
+ * same-day counts — which `useFeature` only ever keeps in memory, to avoid
24839
+ * a disk write on every matching API request — get a chance to survive a
24840
+ * crash instead of living only until the next rollover.
24841
+ */
24842
+ flushDailyUsage() {
24843
+ if (!this.canSend("feature_used_daily")) return;
24844
+ for (const { feature, bucket } of flushStaleDailyUsage(this.o.dataDir, this.today())) {
24845
+ this.emit("feature_used_daily", { feature, countBucket: bucket });
24846
+ }
24847
+ persistDailyUsage(this.o.dataDir);
24848
+ }
24849
+ /**
24850
+ * Emit `error` — full level only (`REQUIRES_FULL` above), and deliberately
24851
+ * NOT once-only: unlike `model_first_load`'s single most-valuable-attempt
24852
+ * signal, every crash after the first is still a real data point (ADR-299's
24853
+ * "what is failing?" journey question, previously never wired to anything).
24854
+ */
24855
+ error(fingerprint) {
24856
+ this.emit("error", { fingerprint });
24857
+ }
24637
24858
  /**
24638
24859
  * Mint the anonymous machine id lazily.
24639
24860
  *
@@ -24662,8 +24883,8 @@ var Emitter = class {
24662
24883
  hideChildConsoleWindows();
24663
24884
  var version = "0.1.1";
24664
24885
  try {
24665
- const pkgPath = join36(dirname12(fileURLToPath2(import.meta.url)), "..", "package.json");
24666
- version = JSON.parse(readFileSync19(pkgPath, "utf8")).version ?? version;
24886
+ const pkgPath = join37(dirname12(fileURLToPath2(import.meta.url)), "..", "package.json");
24887
+ version = JSON.parse(readFileSync20(pkgPath, "utf8")).version ?? version;
24667
24888
  } catch {
24668
24889
  }
24669
24890
  var [nodeMajor, nodeMinor] = process.versions.node.split(".").map(Number);
@@ -24691,7 +24912,7 @@ function argValue(name, fallback) {
24691
24912
  if (hasFlag("--no-telemetry")) process.env[TELEMETRY_ENV] = "off";
24692
24913
  function configuredDaemonPort(configPath) {
24693
24914
  try {
24694
- const cfg2 = JSON.parse(readFileSync19(configPath, "utf8"));
24915
+ const cfg2 = JSON.parse(readFileSync20(configPath, "utf8"));
24695
24916
  if (typeof cfg2.daemon?.port === "number" && cfg2.daemon.port > 0) return cfg2.daemon.port;
24696
24917
  } catch {
24697
24918
  }
@@ -24816,7 +25037,7 @@ if (pruned > 0) console.log(`pruned ${pruned} dangling engine build(s)`);
24816
25037
  var provision = new ProvisionState();
24817
25038
  var build = new BuildState();
24818
25039
  var updates = new UpdateChecker();
24819
- var enginesDir = join36(store.dir(), "engines");
25040
+ var enginesDir = join37(store.dir(), "engines");
24820
25041
  void seedDefaultEngines(registry, enginesDir, provision).then(() => registry.ensureProbed());
24821
25042
  var manager = new Manager(store);
24822
25043
  var scanner = new Scanner(store);
@@ -24871,15 +25092,23 @@ manager.onLoadSettled = (ok, err8) => {
24871
25092
  telemetry.emit("onboarding_step", { step: "first_load", outcome });
24872
25093
  }
24873
25094
  reportModelLoad(store.dir(), telemetry, outcome, ok ? void 0 : classifyLoadFailure(err8));
25095
+ if (!ok) telemetry.error(classifyEngineErrorFingerprint(err8));
24874
25096
  };
24875
25097
  bench.telemetry = telemetry;
24876
- provision.onSettled = (ok) => telemetry.emit("onboarding_step", { step: "engine_install", outcome: ok ? "ok" : "fail" });
24877
- downloads.onSettled = (outcome) => telemetry.emit("onboarding_step", { step: "model_download", outcome });
25098
+ provision.onSettled = (ok, failReason) => telemetry.emit("onboarding_step", { step: "engine_install", outcome: ok ? "ok" : "fail", ...failReason ? { failReason } : {} });
25099
+ downloads.onSettled = (outcome) => {
25100
+ telemetry.emit("onboarding_step", { step: "model_download", outcome });
25101
+ if (outcome === "fail") telemetry.error("download_failed");
25102
+ };
25103
+ build.onSettled = (outcome) => {
25104
+ if (outcome === "fail") telemetry.error("build_failed");
25105
+ };
24878
25106
  setTimeout(() => {
24879
25107
  telemetry.once("app_first_run");
24880
25108
  void flush(store.dir(), store.snapshot().telemetry.level);
24881
25109
  }, 3e3).unref();
24882
25110
  setInterval(() => void flush(store.dir(), store.snapshot().telemetry.level), 5 * 6e4).unref();
25111
+ setInterval(() => telemetry.flushDailyUsage(), 5 * 6e4).unref();
24883
25112
  var tunnelRequested = hasFlag("--tunnel");
24884
25113
  if (tunnelRequested) deps.tunnel = new TunnelManager(store.dir());
24885
25114
  deps.codeRuns = new CodeRunManager(deps);
@@ -25049,7 +25278,7 @@ var restarting = false;
25049
25278
  function spawnReplacement() {
25050
25279
  let out = "ignore";
25051
25280
  try {
25052
- out = openSync3(join36(store.dir(), "restart.log"), "a");
25281
+ out = openSync3(join37(store.dir(), "restart.log"), "a");
25053
25282
  } catch {
25054
25283
  out = "ignore";
25055
25284
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbollm",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
4
4
  "description": "TurboLLM — local LLM platform: run any inference engine auto-tuned to your GPU, with a web UI and OpenAI/Anthropic-compatible API. Point Claude Code at your own machine in one command.",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "author": "Mohit Soni",