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.
- package/dist/cli.js +284 -55
- 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
|
|
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
|
|
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
|
|
3369
|
-
*
|
|
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
|
|
3415
|
-
* dependency; the error STRING is deliberately
|
|
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(
|
|
3512
|
+
this.settle("ok");
|
|
3421
3513
|
}
|
|
3422
3514
|
fail(error) {
|
|
3423
3515
|
this.s = { ...this.s, active: false, phase: "error", error };
|
|
3424
|
-
this.settle(
|
|
3516
|
+
this.settle("fail");
|
|
3425
3517
|
}
|
|
3426
|
-
|
|
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?.(
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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)
|
|
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 =
|
|
24666
|
-
version = JSON.parse(
|
|
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(
|
|
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 =
|
|
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) =>
|
|
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(
|
|
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.
|
|
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",
|