turbollm 1.9.2 → 1.9.4
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 +25 -6
- package/dist/cli.js +863 -223
- package/dist/webdist/assets/{AgentEditPage-CyzZ8vDr.js → AgentEditPage-CbJqYCVo.js} +1 -1
- package/dist/webdist/assets/{ChatScreen-DWVblPWw.js → ChatScreen-LaTz7fTZ.js} +3 -3
- package/dist/webdist/assets/CodeComposer-12z9dbZA.js +1 -0
- package/dist/webdist/assets/CodeHomeScreen-8A3Anu2L.js +1 -0
- package/dist/webdist/assets/{CodeSessionScreen-BtA5u3li.js → CodeSessionScreen-BoA5rPp7.js} +3 -3
- package/dist/webdist/assets/CustomizeScreen-BTBhphFE.js +4 -0
- package/dist/webdist/assets/DeveloperScreen-Dfy6Rgw4.js +1 -0
- package/dist/webdist/assets/{EnginesScreen-ChGdOOW9.js → EnginesScreen-Dwxe8B5p.js} +6 -6
- package/dist/webdist/assets/FsBrowser-DXxO1QdQ.js +1 -0
- package/dist/webdist/assets/{ModelLoadMenu-DzaxKmIV.js → ModelLoadMenu-C7wK_COK.js} +2 -2
- package/dist/webdist/assets/{ModelsScreen-wuI7h511.js → ModelsScreen-DnpYzOj2.js} +2 -2
- package/dist/webdist/assets/SettingsScreen-Tn8PlYcS.js +2 -0
- package/dist/webdist/assets/{SkillEditPage-XGTaeQ8o.js → SkillEditPage-Bzr1Mfqi.js} +1 -1
- package/dist/webdist/assets/{TokensScreen-B02y3G-H.js → TokensScreen-BrnFne8W.js} +1 -1
- package/dist/webdist/assets/{ToolApprovalBar-D4YRUMat.js → ToolApprovalBar-BfIwjh_G.js} +4 -4
- package/dist/webdist/assets/WorkspaceScreen-CHg7GC1T.js +1 -0
- package/dist/webdist/assets/{agent-api-D86F96i6.js → agent-api-C0qMHAE0.js} +1 -1
- package/dist/webdist/assets/{arc-BScxM0vj.js → arc-DGmQFsDA.js} +1 -1
- package/dist/webdist/assets/{architectureDiagram-3BPJPVTR-DbIjWGrc.js → architectureDiagram-3BPJPVTR-DSypGjGj.js} +1 -1
- package/dist/webdist/assets/{arrow-left-Cz7XgLgo.js → arrow-left-CuK9vFgW.js} +1 -1
- package/dist/webdist/assets/{badge-CRVn2top.js → badge-jlftBi-Q.js} +1 -1
- package/dist/webdist/assets/{blockDiagram-GPEHLZMM-COy0nYNX.js → blockDiagram-GPEHLZMM-DoYsQhuo.js} +1 -1
- package/dist/webdist/assets/{bot-C-sGABri.js → bot-CE50aKt3.js} +1 -1
- package/dist/webdist/assets/{c4Diagram-AAUBKEIU-CoiZua-v.js → c4Diagram-AAUBKEIU-DsQ4mGev.js} +1 -1
- package/dist/webdist/assets/channel-BBYa3XlX.js +1 -0
- package/dist/webdist/assets/{chat-api-CdVOTSst.js → chat-api-BQgsmVCJ.js} +1 -1
- package/dist/webdist/assets/{chat-queries-DINfS8uv.js → chat-queries-BuU4cQ0I.js} +1 -1
- package/dist/webdist/assets/{chevron-left-CbPqt11D.js → chevron-left-BoIfI017.js} +1 -1
- package/dist/webdist/assets/{chevron-right-C8EPnhrg.js → chevron-right-Btn5ue2K.js} +1 -1
- package/dist/webdist/assets/{chunk-2J33WTMH-B-ggs358.js → chunk-2J33WTMH-Cpm4yaej.js} +1 -1
- package/dist/webdist/assets/{chunk-4BX2VUAB-CBSHARpe.js → chunk-4BX2VUAB-Qkt8VpMd.js} +1 -1
- package/dist/webdist/assets/{chunk-55IACEB6-BikCaTgS.js → chunk-55IACEB6-D0lIZ5zv.js} +1 -1
- package/dist/webdist/assets/{chunk-727SXJPM-B8Uooh_X.js → chunk-727SXJPM-DmJbKG_f.js} +1 -1
- package/dist/webdist/assets/{chunk-AQP2D5EJ-C-wZiAmx.js → chunk-AQP2D5EJ-CP5FXopV.js} +1 -1
- package/dist/webdist/assets/{chunk-FMBD7UC4-lkRMU7V5.js → chunk-FMBD7UC4-CEHj-mOu.js} +1 -1
- package/dist/webdist/assets/{chunk-ND2GUHAM-CTBUDTXK.js → chunk-ND2GUHAM-DfaWkbDp.js} +1 -1
- package/dist/webdist/assets/{chunk-QZHKN3VN-CKOXFQ-c.js → chunk-QZHKN3VN-Cbt4eIAq.js} +1 -1
- package/dist/webdist/assets/{circle-check-dbo4u23c.js → circle-check-CyJ0DXgY.js} +1 -1
- package/dist/webdist/assets/{circle-x-CuPWHB4Y.js → circle-x-B2ihWKOZ.js} +1 -1
- package/dist/webdist/assets/classDiagram-4FO5ZUOK-3anTq5dR.js +1 -0
- package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-3anTq5dR.js +1 -0
- package/dist/webdist/assets/{common-RZkQjs09.js → common-CA-hju85.js} +1 -1
- package/dist/webdist/assets/{cose-bilkent-S5V4N54A-L3BPlYev.js → cose-bilkent-S5V4N54A-U7v4kFOg.js} +1 -1
- package/dist/webdist/assets/{dagre-BM42HDAG-DdFZFhxI.js → dagre-BM42HDAG-BrRqvg5R.js} +1 -1
- package/dist/webdist/assets/{diagram-2AECGRRQ-Bx4JyVtd.js → diagram-2AECGRRQ-CSOuZx8C.js} +1 -1
- package/dist/webdist/assets/{diagram-5GNKFQAL-DYxtCUYO.js → diagram-5GNKFQAL-Dc9npHHs.js} +1 -1
- package/dist/webdist/assets/{diagram-KO2AKTUF-BSV0zTVR.js → diagram-KO2AKTUF-CRMmrYOC.js} +1 -1
- package/dist/webdist/assets/{diagram-LMA3HP47-CS-L_g7_.js → diagram-LMA3HP47-DR-qZazu.js} +1 -1
- package/dist/webdist/assets/{diagram-OG6HWLK6-D8uK9mOu.js → diagram-OG6HWLK6-BRt3haop.js} +1 -1
- package/dist/webdist/assets/{dialog-fsfhY_Ib.js → dialog-OPLOEGn-.js} +1 -1
- package/dist/webdist/assets/{dropdown-menu-B6pYEdad.js → dropdown-menu-mOPaoWK5.js} +1 -1
- package/dist/webdist/assets/{erDiagram-TEJ5UH35-DX-3blsY.js → erDiagram-TEJ5UH35-D0M-EUsh.js} +1 -1
- package/dist/webdist/assets/{flowDiagram-I6XJVG4X-DUTsLqgt.js → flowDiagram-I6XJVG4X-DBufQOmB.js} +1 -1
- package/dist/webdist/assets/{ganttDiagram-6RSMTGT7-CwH0oq0S.js → ganttDiagram-6RSMTGT7-B6zSUp2B.js} +1 -1
- package/dist/webdist/assets/{git-branch-BiMfcJ1j.js → git-branch-D8GYPzhY.js} +1 -1
- package/dist/webdist/assets/{gitGraphDiagram-PVQCEYII-CmLeqvWh.js → gitGraphDiagram-PVQCEYII-CnSmYO2G.js} +1 -1
- package/dist/webdist/assets/index-COUI_CR-.js +13 -0
- package/dist/webdist/assets/index-DKAl71q8.css +1 -0
- package/dist/webdist/assets/{infoDiagram-5YYISTIA-BFTUzkbq.js → infoDiagram-5YYISTIA-F6nF57e3.js} +1 -1
- package/dist/webdist/assets/{ishikawaDiagram-YF4QCWOH-BdQIrqQU.js → ishikawaDiagram-YF4QCWOH-DXMvubwx.js} +1 -1
- package/dist/webdist/assets/{journeyDiagram-JHISSGLW-D1HmHaNc.js → journeyDiagram-JHISSGLW-BcMZWZZi.js} +1 -1
- package/dist/webdist/assets/{kanban-definition-UN3LZRKU-C5USgc8m.js → kanban-definition-UN3LZRKU-BhA3iHrI.js} +1 -1
- package/dist/webdist/assets/{linear-BDGxyxLo.js → linear-DCHP6NLs.js} +1 -1
- package/dist/webdist/assets/{lock-DCb387O_.js → lock-DEL9SW4n.js} +1 -1
- package/dist/webdist/assets/{mermaid.core-DWCJxsCl.js → mermaid.core-Bvxj3QV6.js} +4 -4
- package/dist/webdist/assets/{mindmap-definition-RKZ34NQL-BV3H5rWG.js → mindmap-definition-RKZ34NQL-DFW-B7At.js} +1 -1
- package/dist/webdist/assets/{pencil-BegDY0Zk.js → pencil-D3YoSpTL.js} +1 -1
- package/dist/webdist/assets/{personas-DNCENHUd.js → personas-BL5LWber.js} +2 -2
- package/dist/webdist/assets/{pieDiagram-4H26LBE5-DDN9VAOL.js → pieDiagram-4H26LBE5-D22oz4Bh.js} +1 -1
- package/dist/webdist/assets/{plus-DrZmKBTt.js → plus-Czof1-k0.js} +1 -1
- package/dist/webdist/assets/{quadrantDiagram-W4KKPZXB-Bx16Ru8w.js → quadrantDiagram-W4KKPZXB-rH5_lBaU.js} +1 -1
- package/dist/webdist/assets/{refresh-cw-B7Qjdj47.js → refresh-cw-eHr6ZrHD.js} +1 -1
- package/dist/webdist/assets/{requirementDiagram-4Y6WPE33-k0upGaul.js → requirementDiagram-4Y6WPE33-2GvaIJsP.js} +1 -1
- package/dist/webdist/assets/{rotate-ccw-B7o8hLk6.js → rotate-ccw-DBvaE0Sq.js} +1 -1
- package/dist/webdist/assets/{sankeyDiagram-5OEKKPKP-xyOWBpSr.js → sankeyDiagram-5OEKKPKP-VB7s7E26.js} +1 -1
- package/dist/webdist/assets/{sequenceDiagram-3UESZ5HK-BhBclCRW.js → sequenceDiagram-3UESZ5HK-DypBxMgR.js} +1 -1
- package/dist/webdist/assets/{skeleton-D15eL_7e.js → skeleton-COip0Uxa.js} +1 -1
- package/dist/webdist/assets/{sliders-horizontal-C4_t3PES.js → sliders-horizontal-BYbWJESN.js} +1 -1
- package/dist/webdist/assets/{sparkles-BT45JGMR.js → sparkles-Bunle1wO.js} +1 -1
- package/dist/webdist/assets/{star-SskkfhIb.js → star-CDjPloh2.js} +1 -1
- package/dist/webdist/assets/{stateDiagram-AJRCARHV-902Ow1i4.js → stateDiagram-AJRCARHV-De6Tx6UP.js} +1 -1
- package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-BJ8zIW-S.js +1 -0
- package/dist/webdist/assets/{switch-C-l-ej5M.js → switch-CrtIhLpg.js} +1 -1
- package/dist/webdist/assets/{terminal-55Og1Fyx.js → terminal-C_jrBBqy.js} +1 -1
- package/dist/webdist/assets/{timeline-definition-PNZ67QCA-BEyTzrTZ.js → timeline-definition-PNZ67QCA-DzMJnztZ.js} +1 -1
- package/dist/webdist/assets/{trash-2-B9nMCHMA.js → trash-2-BA8fBFXw.js} +1 -1
- package/dist/webdist/assets/useIsDesktop-Dg2G692v.js +1 -0
- package/dist/webdist/assets/{vennDiagram-CIIHVFJN-C77U6Nzk.js → vennDiagram-CIIHVFJN-BNR_zHHW.js} +1 -1
- package/dist/webdist/assets/{wardley-L42UT6IY-BZ10FiAs.js → wardley-L42UT6IY-Cm1Sh5Oj.js} +1 -1
- package/dist/webdist/assets/{wardleyDiagram-YWT4CUSO-DgrAaqyf.js → wardleyDiagram-YWT4CUSO-Bvni8f-6.js} +1 -1
- package/dist/webdist/assets/{wrench-DMY_WSQ9.js → wrench-Uhws4miP.js} +1 -1
- package/dist/webdist/assets/{xychartDiagram-2RQKCTM6-BlzVJUn3.js → xychartDiagram-2RQKCTM6-Dyk7o391.js} +1 -1
- package/dist/webdist/index.html +2 -2
- package/package.json +1 -1
- package/dist/webdist/assets/CodeComposer-BvNMY6PI.js +0 -1
- package/dist/webdist/assets/CodeHomeScreen-CmzaqOmH.js +0 -1
- package/dist/webdist/assets/CustomizeScreen-ACC_4SMn.js +0 -4
- package/dist/webdist/assets/DeveloperScreen-CSJZh91v.js +0 -1
- package/dist/webdist/assets/FsBrowser-D9RjrjvO.js +0 -1
- package/dist/webdist/assets/SettingsScreen-DsKi9vxM.js +0 -1
- package/dist/webdist/assets/WorkspaceScreen-DRoUxluw.js +0 -1
- package/dist/webdist/assets/channel-8eXCTkKl.js +0 -1
- package/dist/webdist/assets/check-Do9IgpFj.js +0 -1
- package/dist/webdist/assets/classDiagram-4FO5ZUOK-D5bPDMhb.js +0 -1
- package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-D5bPDMhb.js +0 -1
- package/dist/webdist/assets/copy-button-DKUAZ6Ig.js +0 -1
- package/dist/webdist/assets/index-CXwfn7-U.css +0 -1
- package/dist/webdist/assets/index-DaNuMYPC.js +0 -10
- package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-B5LKF3CJ.js +0 -1
- package/dist/webdist/assets/useIsDesktop-D2KX8NOA.js +0 -1
package/dist/cli.js
CHANGED
|
@@ -23,9 +23,9 @@ function hideChildConsoleWindows() {
|
|
|
23
23
|
|
|
24
24
|
// src/cli.ts
|
|
25
25
|
import { spawn as spawn8 } from "child_process";
|
|
26
|
-
import { openSync as openSync3, readFileSync as
|
|
26
|
+
import { openSync as openSync3, readFileSync as readFileSync19 } from "fs";
|
|
27
27
|
import { serve } from "@hono/node-server";
|
|
28
|
-
import { dirname as dirname12, join as
|
|
28
|
+
import { dirname as dirname12, join as join35 } from "path";
|
|
29
29
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
30
30
|
|
|
31
31
|
// src/config/config.ts
|
|
@@ -33,7 +33,7 @@ import { randomUUID } from "crypto";
|
|
|
33
33
|
import { cpSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
34
34
|
import { homedir } from "os";
|
|
35
35
|
import { dirname, join } from "path";
|
|
36
|
-
var SCHEMA_VERSION =
|
|
36
|
+
var SCHEMA_VERSION = 4;
|
|
37
37
|
var VRAM_HEADROOM_MIN_MB = 300;
|
|
38
38
|
var VRAM_HEADROOM_MAX_MB = 2048;
|
|
39
39
|
var VRAM_HEADROOM_DEFAULT_MB = 1024;
|
|
@@ -188,7 +188,7 @@ var ConfigStore = class _ConfigStore {
|
|
|
188
188
|
renameSync(tmp, this.filePath);
|
|
189
189
|
}
|
|
190
190
|
};
|
|
191
|
-
function migrate(raw,
|
|
191
|
+
function migrate(raw, from) {
|
|
192
192
|
const cfg2 = defaultConfig();
|
|
193
193
|
if (typeof raw.host === "string") cfg2.daemon.host = raw.host;
|
|
194
194
|
if (typeof raw.port === "number") cfg2.daemon.port = raw.port;
|
|
@@ -215,6 +215,7 @@ function migrate(raw, _from) {
|
|
|
215
215
|
cfg2[k] = v;
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
|
+
if (from < 4 && cfg2.telemetry.level === "off") cfg2.telemetry.level = "unset";
|
|
218
219
|
return cfg2;
|
|
219
220
|
}
|
|
220
221
|
function splitLaunchArgs(args) {
|
|
@@ -1404,6 +1405,10 @@ var Manager = class _Manager {
|
|
|
1404
1405
|
child = null;
|
|
1405
1406
|
startedAt = 0;
|
|
1406
1407
|
errInfo = null;
|
|
1408
|
+
/** Optional observer for load outcomes, wired in cli.ts (ADR-299). Kept as a
|
|
1409
|
+
* plain callback so Manager has no telemetry dependency and tests need no
|
|
1410
|
+
* stub. A throwing observer must never break a load. */
|
|
1411
|
+
onLoadSettled;
|
|
1407
1412
|
lastCommand = null;
|
|
1408
1413
|
lastActivity = 0;
|
|
1409
1414
|
logPathStr = "";
|
|
@@ -1427,9 +1432,11 @@ var Manager = class _Manager {
|
|
|
1427
1432
|
if (hooks?.beforeStart) await hooks.beforeStart();
|
|
1428
1433
|
await this.startInternal(opts);
|
|
1429
1434
|
await this.awaitNotStarting();
|
|
1435
|
+
this.reportLoad(this.state === "running", this.errInfo);
|
|
1430
1436
|
} catch (e) {
|
|
1431
1437
|
this.state = "error";
|
|
1432
1438
|
this.errInfo = { code: "load_failed", message: e instanceof Error ? e.message : String(e), exitCode: -1, logTail: [] };
|
|
1439
|
+
this.reportLoad(false, this.errInfo);
|
|
1433
1440
|
throw e;
|
|
1434
1441
|
}
|
|
1435
1442
|
});
|
|
@@ -1444,6 +1451,14 @@ var Manager = class _Manager {
|
|
|
1444
1451
|
await this.awaitNotStarting();
|
|
1445
1452
|
});
|
|
1446
1453
|
}
|
|
1454
|
+
/** Notify the load observer, swallowing anything it throws — an observer is
|
|
1455
|
+
* by definition not allowed to affect whether a model loaded. */
|
|
1456
|
+
reportLoad(ok, err7) {
|
|
1457
|
+
try {
|
|
1458
|
+
this.onLoadSettled?.(ok, err7);
|
|
1459
|
+
} catch {
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1447
1462
|
/** Wait until the engine leaves the 'starting' state (→ running or error/stopped),
|
|
1448
1463
|
* bounded by the engine kind's readiness window plus a small grace. The internal
|
|
1449
1464
|
* readiness loop flips the state and surfaces errors; this just keeps the load
|
|
@@ -1475,9 +1490,9 @@ var Manager = class _Manager {
|
|
|
1475
1490
|
}
|
|
1476
1491
|
}
|
|
1477
1492
|
const port2 = await allocPort(opts.preferredPort);
|
|
1478
|
-
const
|
|
1479
|
-
mkdirSync6(dirname2(
|
|
1480
|
-
const logStream = createWriteStream2(
|
|
1493
|
+
const logPath2 = join10(this.store.dir(), "logs", `engine-${opts.engine.id}.log`);
|
|
1494
|
+
mkdirSync6(dirname2(logPath2), { recursive: true });
|
|
1495
|
+
const logStream = createWriteStream2(logPath2);
|
|
1481
1496
|
logStream.write(
|
|
1482
1497
|
`[turbollm] starting engine "${opts.engine.name}" on internal port ${port2} (127.0.0.1 only \u2014 the engine's own port, NOT the TurboLLM app/UI port).
|
|
1483
1498
|
`
|
|
@@ -1507,7 +1522,7 @@ var Manager = class _Manager {
|
|
|
1507
1522
|
this.startedAt = Date.now();
|
|
1508
1523
|
this.errInfo = null;
|
|
1509
1524
|
this.lastActivity = Date.now();
|
|
1510
|
-
this.logPathStr =
|
|
1525
|
+
this.logPathStr = logPath2;
|
|
1511
1526
|
this.exited = new Promise((res) => {
|
|
1512
1527
|
this.resolveExited = res;
|
|
1513
1528
|
});
|
|
@@ -3311,11 +3326,23 @@ var ProvisionState = class {
|
|
|
3311
3326
|
this.s.part = part;
|
|
3312
3327
|
this.s.parts = parts;
|
|
3313
3328
|
}
|
|
3329
|
+
/** Optional observer for the terminal outcome, wired in cli.ts (ADR-299
|
|
3330
|
+
* `onboarding_step`). The error string is deliberately not passed — the only
|
|
3331
|
+
* consumer may never transmit free text. */
|
|
3332
|
+
onSettled;
|
|
3314
3333
|
done() {
|
|
3315
3334
|
this.s = { active: false, phase: "idle", backend: "", pct: 0, part: 1, parts: 1, error: null };
|
|
3335
|
+
this.settle(true);
|
|
3316
3336
|
}
|
|
3317
3337
|
fail(error) {
|
|
3318
3338
|
this.s = { active: false, phase: "error", backend: this.s.backend, pct: 0, part: 1, parts: 1, error };
|
|
3339
|
+
this.settle(false);
|
|
3340
|
+
}
|
|
3341
|
+
settle(ok) {
|
|
3342
|
+
try {
|
|
3343
|
+
this.onSettled?.(ok);
|
|
3344
|
+
} catch {
|
|
3345
|
+
}
|
|
3319
3346
|
}
|
|
3320
3347
|
};
|
|
3321
3348
|
|
|
@@ -3345,11 +3372,24 @@ var BuildState = class {
|
|
|
3345
3372
|
this.s.log.push(trimmed);
|
|
3346
3373
|
if (this.s.log.length > LOG_TAIL) this.s.log.splice(0, this.s.log.length - LOG_TAIL);
|
|
3347
3374
|
}
|
|
3375
|
+
/** Optional observer for the terminal outcome, wired in cli.ts (ADR-299
|
|
3376
|
+
* `onboarding_step`). A plain callback so this module keeps no telemetry
|
|
3377
|
+
* dependency; the error STRING is deliberately not passed, because the only
|
|
3378
|
+
* consumer may never send free text. */
|
|
3379
|
+
onSettled;
|
|
3348
3380
|
done() {
|
|
3349
3381
|
this.s = { ...this.s, active: false, phase: "done", error: null };
|
|
3382
|
+
this.settle(true);
|
|
3350
3383
|
}
|
|
3351
3384
|
fail(error) {
|
|
3352
3385
|
this.s = { ...this.s, active: false, phase: "error", error };
|
|
3386
|
+
this.settle(false);
|
|
3387
|
+
}
|
|
3388
|
+
settle(ok) {
|
|
3389
|
+
try {
|
|
3390
|
+
this.onSettled?.(ok);
|
|
3391
|
+
} catch {
|
|
3392
|
+
}
|
|
3353
3393
|
}
|
|
3354
3394
|
};
|
|
3355
3395
|
|
|
@@ -7004,6 +7044,18 @@ var DownloadManager = class {
|
|
|
7004
7044
|
provenancePath;
|
|
7005
7045
|
provenanceList = [];
|
|
7006
7046
|
nextSeq = 0;
|
|
7047
|
+
/** Optional observer for a download's terminal outcome, wired in cli.ts
|
|
7048
|
+
* (ADR-299 `onboarding_step`). Separate from `onComplete`, which fires only
|
|
7049
|
+
* on success and exists to trigger a rescan — reusing it would have made a
|
|
7050
|
+
* failed download indistinguishable from one that never started. The error
|
|
7051
|
+
* string is deliberately not passed: the only consumer may never send text. */
|
|
7052
|
+
onSettled;
|
|
7053
|
+
settle(outcome) {
|
|
7054
|
+
try {
|
|
7055
|
+
this.onSettled?.(outcome);
|
|
7056
|
+
} catch {
|
|
7057
|
+
}
|
|
7058
|
+
}
|
|
7007
7059
|
/** Permanent record of which repo+file each downloaded model came from (spec 10 §3).
|
|
7008
7060
|
* Consumed by the repo-detail route to mark a quant "Downloaded" for the exact repo
|
|
7009
7061
|
* it was pulled from. */
|
|
@@ -7276,6 +7328,7 @@ var DownloadManager = class {
|
|
|
7276
7328
|
this.onComplete();
|
|
7277
7329
|
} catch {
|
|
7278
7330
|
}
|
|
7331
|
+
this.settle("ok");
|
|
7279
7332
|
} catch (e) {
|
|
7280
7333
|
this.controllers.delete(rec.id);
|
|
7281
7334
|
rec.bytesPerSec = 0;
|
|
@@ -7287,6 +7340,7 @@ var DownloadManager = class {
|
|
|
7287
7340
|
rec.error = e instanceof Error ? e.message : String(e);
|
|
7288
7341
|
}
|
|
7289
7342
|
this.persist();
|
|
7343
|
+
this.settle(rec.status === "cancelled" ? "cancelled" : "fail");
|
|
7290
7344
|
} finally {
|
|
7291
7345
|
this.pump();
|
|
7292
7346
|
}
|
|
@@ -7422,9 +7476,8 @@ function repoSubdir(repo) {
|
|
|
7422
7476
|
|
|
7423
7477
|
// src/bench/bench.ts
|
|
7424
7478
|
import { execFile as execFile10 } from "child_process";
|
|
7425
|
-
import {
|
|
7426
|
-
import {
|
|
7427
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
7479
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
7480
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
7428
7481
|
|
|
7429
7482
|
// src/api/path-utils.ts
|
|
7430
7483
|
function inferRepoFromPath(filePath, modelDirs) {
|
|
@@ -7443,6 +7496,353 @@ function inferRepoFromPath(filePath, modelDirs) {
|
|
|
7443
7496
|
return null;
|
|
7444
7497
|
}
|
|
7445
7498
|
|
|
7499
|
+
// src/telemetry/queue.ts
|
|
7500
|
+
import { mkdirSync as mkdirSync10, readdirSync as readdirSync6, readFileSync as readFileSync7, rmSync as rmSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
7501
|
+
import { join as join19 } from "path";
|
|
7502
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
7503
|
+
|
|
7504
|
+
// src/telemetry/schema.ts
|
|
7505
|
+
var TELEMETRY_SCHEMA_VERSION = 1;
|
|
7506
|
+
var EVENT_NAMES = [
|
|
7507
|
+
"app_first_run",
|
|
7508
|
+
"daily_active",
|
|
7509
|
+
"onboarding_step",
|
|
7510
|
+
"model_first_load",
|
|
7511
|
+
"feature_first_use",
|
|
7512
|
+
"feature_used_daily",
|
|
7513
|
+
"error",
|
|
7514
|
+
"consent_choice",
|
|
7515
|
+
"bench_result"
|
|
7516
|
+
];
|
|
7517
|
+
var ENVELOPE_KEYS = ["schema", "event", "ts", "machineId", "app", "hw", "payload", "level"];
|
|
7518
|
+
var FEATURES = [
|
|
7519
|
+
"chat",
|
|
7520
|
+
"code",
|
|
7521
|
+
"research",
|
|
7522
|
+
"artifacts",
|
|
7523
|
+
"mcp",
|
|
7524
|
+
"agents",
|
|
7525
|
+
"autotune",
|
|
7526
|
+
"skills",
|
|
7527
|
+
"image"
|
|
7528
|
+
];
|
|
7529
|
+
var ONBOARDING_STEPS = ["engine_install", "engine_build", "model_download", "first_load"];
|
|
7530
|
+
var OUTCOMES = ["ok", "fail", "cancelled"];
|
|
7531
|
+
var FAIL_REASONS = [
|
|
7532
|
+
"oom",
|
|
7533
|
+
"no_engine",
|
|
7534
|
+
"bad_gguf",
|
|
7535
|
+
"unsupported_arch",
|
|
7536
|
+
"timeout",
|
|
7537
|
+
"cancelled",
|
|
7538
|
+
"other"
|
|
7539
|
+
];
|
|
7540
|
+
var COUNT_BUCKETS = ["1", "2-5", "6-20", "21-100", "100+"];
|
|
7541
|
+
var ERROR_FINGERPRINTS = [
|
|
7542
|
+
"cuda_oom",
|
|
7543
|
+
"engine_crash",
|
|
7544
|
+
"engine_start_timeout",
|
|
7545
|
+
"model_load_failed",
|
|
7546
|
+
"gateway_unreachable",
|
|
7547
|
+
"download_failed",
|
|
7548
|
+
"build_failed",
|
|
7549
|
+
"other"
|
|
7550
|
+
];
|
|
7551
|
+
var CONSENT_LEVELS = ["off", "anon", "full"];
|
|
7552
|
+
var MAX_IDENT_LEN = 96;
|
|
7553
|
+
function isSafeIdent(v) {
|
|
7554
|
+
if (typeof v !== "string" || v.length === 0 || v.length > MAX_IDENT_LEN) return false;
|
|
7555
|
+
if (!/^[A-Za-z0-9 ._:()+\-]+$/.test(v)) return false;
|
|
7556
|
+
if (v.includes("..")) return false;
|
|
7557
|
+
return true;
|
|
7558
|
+
}
|
|
7559
|
+
function isSafeOs(v) {
|
|
7560
|
+
return typeof v === "string" && /^[a-z0-9]{1,32}\/[a-z0-9]{1,32}$/.test(v);
|
|
7561
|
+
}
|
|
7562
|
+
var BENCH_PAYLOAD = {
|
|
7563
|
+
model: {
|
|
7564
|
+
name: { kind: "ident" },
|
|
7565
|
+
quant: { kind: "ident" },
|
|
7566
|
+
arch: { kind: "ident" },
|
|
7567
|
+
sizeBytes: { kind: "number" },
|
|
7568
|
+
moe: { kind: "boolean" }
|
|
7569
|
+
},
|
|
7570
|
+
engine: { version: { kind: "ident" } },
|
|
7571
|
+
params: {
|
|
7572
|
+
ctx: { kind: "number" },
|
|
7573
|
+
ngl: { kind: "number" },
|
|
7574
|
+
nglFit: { kind: "boolean", optional: true },
|
|
7575
|
+
nCpuMoe: { kind: "number" },
|
|
7576
|
+
nCpuMoeFit: { kind: "boolean", optional: true },
|
|
7577
|
+
parallel: { kind: "number" },
|
|
7578
|
+
kvTypeK: { kind: "ident" },
|
|
7579
|
+
flashAttn: { kind: "ident" }
|
|
7580
|
+
},
|
|
7581
|
+
result: {
|
|
7582
|
+
tps: { kind: "number" },
|
|
7583
|
+
ttftMs: { kind: "number" },
|
|
7584
|
+
// `null` is real data here: BenchResult.vramMb is `number | null`, meaning
|
|
7585
|
+
// "we could not measure VRAM on this box" — distinct from "not reported".
|
|
7586
|
+
vramMb: { kind: "number", optional: true, nullable: true },
|
|
7587
|
+
outcome: { enum: OUTCOMES }
|
|
7588
|
+
}
|
|
7589
|
+
};
|
|
7590
|
+
var HW_SPEC = {
|
|
7591
|
+
cpu: { kind: "ident" },
|
|
7592
|
+
ramMb: { kind: "number" }
|
|
7593
|
+
};
|
|
7594
|
+
var PAYLOAD_SPECS = {
|
|
7595
|
+
onboarding_step: { step: { enum: ONBOARDING_STEPS }, outcome: { enum: OUTCOMES } },
|
|
7596
|
+
model_first_load: { outcome: { enum: OUTCOMES }, failReason: { enum: FAIL_REASONS, optional: true } },
|
|
7597
|
+
feature_first_use: { feature: { enum: FEATURES } },
|
|
7598
|
+
feature_used_daily: { feature: { enum: FEATURES }, countBucket: { enum: COUNT_BUCKETS } },
|
|
7599
|
+
error: { fingerprint: { enum: ERROR_FINGERPRINTS } }
|
|
7600
|
+
};
|
|
7601
|
+
function checkFields(obj, spec, path) {
|
|
7602
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
7603
|
+
return `${path} must be an object`;
|
|
7604
|
+
}
|
|
7605
|
+
const o = obj;
|
|
7606
|
+
for (const key of Object.keys(o)) {
|
|
7607
|
+
if (!Object.prototype.hasOwnProperty.call(spec, key)) return `unknown field: ${path}.${key}`;
|
|
7608
|
+
}
|
|
7609
|
+
for (const [key, field] of Object.entries(spec)) {
|
|
7610
|
+
const v = o[key];
|
|
7611
|
+
if (v === void 0) {
|
|
7612
|
+
if (field.optional) continue;
|
|
7613
|
+
return `missing field: ${path}.${key}`;
|
|
7614
|
+
}
|
|
7615
|
+
const where = `${path}.${key}`;
|
|
7616
|
+
if (v === null && !("enum" in field) && field.nullable) continue;
|
|
7617
|
+
if ("enum" in field) {
|
|
7618
|
+
if (typeof v !== "string" || !field.enum.includes(v)) return `invalid value for ${where}: ${String(v)}`;
|
|
7619
|
+
} else if (field.kind === "ident") {
|
|
7620
|
+
if (!isSafeIdent(v)) return `invalid value for ${where}`;
|
|
7621
|
+
} else if (field.kind === "os") {
|
|
7622
|
+
if (!isSafeOs(v)) return `invalid value for ${where}`;
|
|
7623
|
+
} else if (field.kind === "number") {
|
|
7624
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return `invalid value for ${where}: ${String(v)}`;
|
|
7625
|
+
} else if (typeof v !== "boolean") {
|
|
7626
|
+
return `invalid value for ${where}: ${String(v)}`;
|
|
7627
|
+
}
|
|
7628
|
+
}
|
|
7629
|
+
return null;
|
|
7630
|
+
}
|
|
7631
|
+
function validateHw(hw) {
|
|
7632
|
+
if (typeof hw !== "object" || hw === null || Array.isArray(hw)) return "hw must be an object";
|
|
7633
|
+
const h = hw;
|
|
7634
|
+
const { gpus, ...flat } = h;
|
|
7635
|
+
const flatError = checkFields(flat, HW_SPEC, "hw");
|
|
7636
|
+
if (flatError !== null) return flatError;
|
|
7637
|
+
if (!Array.isArray(gpus)) return "missing field: hw.gpus";
|
|
7638
|
+
for (const g of gpus) {
|
|
7639
|
+
const e = checkFields(g, { name: { kind: "ident" }, vramMb: { kind: "number" } }, "hw.gpus[]");
|
|
7640
|
+
if (e !== null) return e;
|
|
7641
|
+
}
|
|
7642
|
+
return null;
|
|
7643
|
+
}
|
|
7644
|
+
function validatePayload(eventName, payload) {
|
|
7645
|
+
if (eventName === "bench_result") {
|
|
7646
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
7647
|
+
return "bench_result requires an object payload";
|
|
7648
|
+
}
|
|
7649
|
+
const p = payload;
|
|
7650
|
+
for (const key of Object.keys(p)) {
|
|
7651
|
+
if (!Object.prototype.hasOwnProperty.call(BENCH_PAYLOAD, key)) return `unknown field: payload.${key}`;
|
|
7652
|
+
}
|
|
7653
|
+
for (const [block, spec2] of Object.entries(BENCH_PAYLOAD)) {
|
|
7654
|
+
const e = checkFields(p[block], spec2, `payload.${block}`);
|
|
7655
|
+
if (e !== null) return e;
|
|
7656
|
+
}
|
|
7657
|
+
return null;
|
|
7658
|
+
}
|
|
7659
|
+
const spec = PAYLOAD_SPECS[eventName];
|
|
7660
|
+
if (spec === void 0) {
|
|
7661
|
+
return payload === void 0 ? null : `${eventName} takes no payload`;
|
|
7662
|
+
}
|
|
7663
|
+
return checkFields(payload, spec, "payload");
|
|
7664
|
+
}
|
|
7665
|
+
function validateEvent(raw) {
|
|
7666
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
7667
|
+
return { ok: false, reason: "event must be an object" };
|
|
7668
|
+
}
|
|
7669
|
+
const e = raw;
|
|
7670
|
+
if (typeof e.event !== "string" || !EVENT_NAMES.includes(e.event)) {
|
|
7671
|
+
return { ok: false, reason: `unknown event name: ${String(e.event)}` };
|
|
7672
|
+
}
|
|
7673
|
+
for (const key of Object.keys(e)) {
|
|
7674
|
+
if (!ENVELOPE_KEYS.includes(key)) {
|
|
7675
|
+
return { ok: false, reason: `unknown field: ${key}` };
|
|
7676
|
+
}
|
|
7677
|
+
}
|
|
7678
|
+
if (e.schema !== TELEMETRY_SCHEMA_VERSION) {
|
|
7679
|
+
return { ok: false, reason: `invalid value for schema: ${String(e.schema)}` };
|
|
7680
|
+
}
|
|
7681
|
+
if (e.event === "consent_choice") {
|
|
7682
|
+
for (const banned of ["machineId", "app", "hw", "ts", "payload"]) {
|
|
7683
|
+
if (banned in e) return { ok: false, reason: `consent_choice must not carry ${banned}` };
|
|
7684
|
+
}
|
|
7685
|
+
if (typeof e.level !== "string" || !CONSENT_LEVELS.includes(e.level)) {
|
|
7686
|
+
return { ok: false, reason: `invalid value for level: ${String(e.level)}` };
|
|
7687
|
+
}
|
|
7688
|
+
return { ok: true, event: e };
|
|
7689
|
+
}
|
|
7690
|
+
if ("level" in e) return { ok: false, reason: "level is only valid on consent_choice" };
|
|
7691
|
+
if (typeof e.ts !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(e.ts)) {
|
|
7692
|
+
return { ok: false, reason: "missing or malformed field: ts" };
|
|
7693
|
+
}
|
|
7694
|
+
if (typeof e.machineId !== "string" || !/^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(e.machineId)) {
|
|
7695
|
+
return { ok: false, reason: "missing or malformed field: machineId" };
|
|
7696
|
+
}
|
|
7697
|
+
const appError = checkFields(e.app, { version: { kind: "ident" }, os: { kind: "os" } }, "app");
|
|
7698
|
+
if (appError !== null) return { ok: false, reason: appError };
|
|
7699
|
+
if (e.event === "bench_result") {
|
|
7700
|
+
const hwError = validateHw(e.hw);
|
|
7701
|
+
if (hwError !== null) return { ok: false, reason: hwError };
|
|
7702
|
+
} else if ("hw" in e) {
|
|
7703
|
+
return { ok: false, reason: "hw is only valid on bench_result" };
|
|
7704
|
+
}
|
|
7705
|
+
const payloadError = validatePayload(e.event, e.payload);
|
|
7706
|
+
if (payloadError !== null) return { ok: false, reason: payloadError };
|
|
7707
|
+
return { ok: true, event: e };
|
|
7708
|
+
}
|
|
7709
|
+
|
|
7710
|
+
// src/telemetry/queue.ts
|
|
7711
|
+
var MAX_QUEUED_EVENTS = 500;
|
|
7712
|
+
function queueDir(dataDir) {
|
|
7713
|
+
return join19(dataDir, "telemetry", "queue");
|
|
7714
|
+
}
|
|
7715
|
+
function enqueue(dataDir, event) {
|
|
7716
|
+
try {
|
|
7717
|
+
const result = validateEvent(event);
|
|
7718
|
+
if (!result.ok) return false;
|
|
7719
|
+
const dir = queueDir(dataDir);
|
|
7720
|
+
mkdirSync10(dir, { recursive: true });
|
|
7721
|
+
writeFileSync8(join19(dir, `${Date.now()}-${randomUUID4()}.json`), JSON.stringify(result.event));
|
|
7722
|
+
trim(dir);
|
|
7723
|
+
return true;
|
|
7724
|
+
} catch {
|
|
7725
|
+
return false;
|
|
7726
|
+
}
|
|
7727
|
+
}
|
|
7728
|
+
function readQueue(dataDir) {
|
|
7729
|
+
const dir = queueDir(dataDir);
|
|
7730
|
+
let files;
|
|
7731
|
+
try {
|
|
7732
|
+
files = readdirSync6(dir).filter((f) => f.endsWith(".json")).sort();
|
|
7733
|
+
} catch {
|
|
7734
|
+
return [];
|
|
7735
|
+
}
|
|
7736
|
+
const out = [];
|
|
7737
|
+
for (const file of files) {
|
|
7738
|
+
try {
|
|
7739
|
+
const event = JSON.parse(readFileSync7(join19(dir, file), "utf8"));
|
|
7740
|
+
if (validateEvent(event).ok) out.push({ file, event });
|
|
7741
|
+
else remove(dataDir, file);
|
|
7742
|
+
} catch {
|
|
7743
|
+
remove(dataDir, file);
|
|
7744
|
+
}
|
|
7745
|
+
}
|
|
7746
|
+
return out;
|
|
7747
|
+
}
|
|
7748
|
+
function remove(dataDir, file) {
|
|
7749
|
+
try {
|
|
7750
|
+
rmSync10(join19(queueDir(dataDir), file), { force: true });
|
|
7751
|
+
} catch {
|
|
7752
|
+
}
|
|
7753
|
+
}
|
|
7754
|
+
function trim(dir) {
|
|
7755
|
+
try {
|
|
7756
|
+
const files = readdirSync6(dir).filter((f) => f.endsWith(".json")).sort();
|
|
7757
|
+
for (const stale of files.slice(0, Math.max(0, files.length - MAX_QUEUED_EVENTS))) {
|
|
7758
|
+
rmSync10(join19(dir, stale), { force: true });
|
|
7759
|
+
}
|
|
7760
|
+
} catch {
|
|
7761
|
+
}
|
|
7762
|
+
}
|
|
7763
|
+
|
|
7764
|
+
// src/telemetry/ledger.ts
|
|
7765
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync9, mkdirSync as mkdirSync11 } from "fs";
|
|
7766
|
+
import { join as join20 } from "path";
|
|
7767
|
+
var DAILY_KEEP = 30;
|
|
7768
|
+
function ledgerPath(dataDir) {
|
|
7769
|
+
return join20(dataDir, "telemetry", "ledger.json");
|
|
7770
|
+
}
|
|
7771
|
+
function read(dataDir) {
|
|
7772
|
+
try {
|
|
7773
|
+
const parsed = JSON.parse(readFileSync8(ledgerPath(dataDir), "utf8"));
|
|
7774
|
+
return Array.isArray(parsed) ? parsed.filter((k) => typeof k === "string") : [];
|
|
7775
|
+
} catch {
|
|
7776
|
+
return [];
|
|
7777
|
+
}
|
|
7778
|
+
}
|
|
7779
|
+
function claimOnce(dataDir, key) {
|
|
7780
|
+
try {
|
|
7781
|
+
const keys = read(dataDir);
|
|
7782
|
+
if (keys.includes(key)) return false;
|
|
7783
|
+
keys.push(key);
|
|
7784
|
+
const dailies = keys.filter((k) => k.startsWith("daily:")).slice(-DAILY_KEEP);
|
|
7785
|
+
const rest = keys.filter((k) => !k.startsWith("daily:"));
|
|
7786
|
+
mkdirSync11(join20(dataDir, "telemetry"), { recursive: true });
|
|
7787
|
+
writeFileSync9(ledgerPath(dataDir), JSON.stringify([...rest, ...dailies]));
|
|
7788
|
+
return true;
|
|
7789
|
+
} catch {
|
|
7790
|
+
return false;
|
|
7791
|
+
}
|
|
7792
|
+
}
|
|
7793
|
+
|
|
7794
|
+
// src/telemetry/first-load.ts
|
|
7795
|
+
function reportModelLoad(dataDir, emitter, outcome, failReason) {
|
|
7796
|
+
try {
|
|
7797
|
+
if (!emitter.canSend("model_first_load")) return;
|
|
7798
|
+
if (!claimOnce(dataDir, "once:model_first_load")) return;
|
|
7799
|
+
emitter.emit(
|
|
7800
|
+
"model_first_load",
|
|
7801
|
+
outcome === "fail" ? { outcome, failReason } : { outcome }
|
|
7802
|
+
);
|
|
7803
|
+
} catch {
|
|
7804
|
+
}
|
|
7805
|
+
}
|
|
7806
|
+
|
|
7807
|
+
// src/telemetry/classify.ts
|
|
7808
|
+
var OOM_SIGNS = [
|
|
7809
|
+
"out of memory",
|
|
7810
|
+
"outofmemory",
|
|
7811
|
+
"failed to allocate",
|
|
7812
|
+
"alloc_buffer",
|
|
7813
|
+
"memory allocation of size",
|
|
7814
|
+
"cudamalloc",
|
|
7815
|
+
"insufficient memory"
|
|
7816
|
+
];
|
|
7817
|
+
var ARCH_SIGNS = ["unknown model architecture", "unsupported model architecture", "unsupported architecture"];
|
|
7818
|
+
var GGUF_SIGNS = ["invalid magic", "error loading model", "failed to load model", "gguf_init", "corrupt"];
|
|
7819
|
+
function classifyLoadFailure(err7) {
|
|
7820
|
+
if (!err7) return "other";
|
|
7821
|
+
if (err7.code === "readiness_timeout") return "timeout";
|
|
7822
|
+
if (err7.code === "engine_unsupported") return "no_engine";
|
|
7823
|
+
const haystack = [err7.message ?? "", ...err7.logTail ?? []].join("\n").toLowerCase();
|
|
7824
|
+
if (haystack.includes("no_active_engine")) return "no_engine";
|
|
7825
|
+
if (haystack.includes("no_such_model")) return "bad_gguf";
|
|
7826
|
+
if (OOM_SIGNS.some((s) => haystack.includes(s))) return "oom";
|
|
7827
|
+
if (ARCH_SIGNS.some((s) => haystack.includes(s))) return "unsupported_arch";
|
|
7828
|
+
if (GGUF_SIGNS.some((s) => haystack.includes(s))) return "bad_gguf";
|
|
7829
|
+
return "other";
|
|
7830
|
+
}
|
|
7831
|
+
function classifyBenchFailure(results) {
|
|
7832
|
+
if (results.some((r) => r.outcome === "oom")) return "oom";
|
|
7833
|
+
if (results.some((r) => r.outcome === "timeout")) return "timeout";
|
|
7834
|
+
return "other";
|
|
7835
|
+
}
|
|
7836
|
+
|
|
7837
|
+
// src/telemetry/disabled.ts
|
|
7838
|
+
var TELEMETRY_ENV = "TURBOLLM_TELEMETRY";
|
|
7839
|
+
var OFF_VALUES = /* @__PURE__ */ new Set(["off", "0", "false", "no"]);
|
|
7840
|
+
function telemetryDisabled() {
|
|
7841
|
+
const raw = process.env[TELEMETRY_ENV];
|
|
7842
|
+
if (raw === void 0) return false;
|
|
7843
|
+
return OFF_VALUES.has(raw.trim().toLowerCase());
|
|
7844
|
+
}
|
|
7845
|
+
|
|
7446
7846
|
// src/models/card-sampling.ts
|
|
7447
7847
|
var FIELDS = [
|
|
7448
7848
|
{ key: "temp", alias: "temp(?:erature)?", min: 0, max: 2 },
|
|
@@ -7609,6 +8009,11 @@ var BenchRunner = class {
|
|
|
7609
8009
|
logWinner = null;
|
|
7610
8010
|
// The finished run's winning candidate, held (not persisted) until the user clicks Save.
|
|
7611
8011
|
winning = null;
|
|
8012
|
+
/** Journey telemetry (ADR-299 `model_first_load`). Set post-construction in
|
|
8013
|
+
* cli.ts, matching `Manager.onLoadSettled`'s wiring — the Emitter is built
|
|
8014
|
+
* after BenchRunner in the daemon's startup order. Absent under tests, which
|
|
8015
|
+
* construct BenchRunner directly and never exercise `run()`. */
|
|
8016
|
+
telemetry;
|
|
7612
8017
|
/** Live state for GET /status. */
|
|
7613
8018
|
status() {
|
|
7614
8019
|
return this.state;
|
|
@@ -7627,6 +8032,14 @@ var BenchRunner = class {
|
|
|
7627
8032
|
this.cancelled = true;
|
|
7628
8033
|
this.abort?.abort();
|
|
7629
8034
|
}
|
|
8035
|
+
/** Report `model_first_load` for a sweep's conclusion (ADR-299). A no-op when
|
|
8036
|
+
* no Emitter was injected (absent under tests, which never call `run()`).
|
|
8037
|
+
* Only the FIRST sweep's outcome across an install's lifetime is recorded —
|
|
8038
|
+
* see `reportModelLoad`'s own once-only claim — so a re-tune later on
|
|
8039
|
+
* cannot overwrite it. */
|
|
8040
|
+
reportFirstLoad(outcome, failReason) {
|
|
8041
|
+
if (this.telemetry) reportModelLoad(this.store.dir(), this.telemetry, outcome, failReason);
|
|
8042
|
+
}
|
|
7630
8043
|
/** Stop the engine — force-killed immediately when the run is cancelled (the user is actively
|
|
7631
8044
|
* waiting for it to stop, ADR-220), gracefully otherwise (lets llama-server release resources
|
|
7632
8045
|
* cleanly between candidates during a normal run). Every stop in this file goes through this
|
|
@@ -7739,11 +8152,13 @@ var BenchRunner = class {
|
|
|
7739
8152
|
await this.stopEngine();
|
|
7740
8153
|
}
|
|
7741
8154
|
if (this.cancelled) {
|
|
8155
|
+
this.reportFirstLoad("cancelled");
|
|
7742
8156
|
this.state = { running: false, modelKey, done: true, candidates: results };
|
|
7743
8157
|
return;
|
|
7744
8158
|
}
|
|
7745
8159
|
const profile = recommended && hasAnySampling(recommended) ? { ...best.profile, sampling: { ...best.profile.sampling, ...recommended } } : best.profile;
|
|
7746
8160
|
const kvAdvisory = kvSpeedAdvisory(best.cand.tps, profile.kvTypeK, caps.kvTypes);
|
|
8161
|
+
this.reportFirstLoad("ok");
|
|
7747
8162
|
this.winning = { modelKey, profile, cand: best.cand, entry, sys, engineVersion: active?.version ?? "", engineId: active?.id ?? "" };
|
|
7748
8163
|
this.logWinner = { params: best.cand.params, tps: best.cand.tps ?? 0, prefillTps: best.cand.prefillTps, ttftMs: best.cand.ttftMs ?? 0, vramMb: best.cand.vramMb };
|
|
7749
8164
|
this.state = {
|
|
@@ -7773,6 +8188,7 @@ var BenchRunner = class {
|
|
|
7773
8188
|
} else {
|
|
7774
8189
|
const memoryBound = results.length > 0 && results.every((r) => r.outcome === "oom");
|
|
7775
8190
|
const err7 = this.cancelled ? void 0 : memoryBound ? `This model doesn't fit on your GPU at ${baseProfile.ctx.toLocaleString()} context \u2014 even with maximum CPU offload it ran out of VRAM. Lower the context length and try again.` : "No candidate completed successfully.";
|
|
8191
|
+
this.reportFirstLoad(this.cancelled ? "cancelled" : "fail", this.cancelled ? void 0 : classifyBenchFailure(results));
|
|
7776
8192
|
this.state = { running: false, modelKey, done: true, error: err7, candidates: results };
|
|
7777
8193
|
}
|
|
7778
8194
|
}
|
|
@@ -7982,16 +8398,16 @@ var BenchRunner = class {
|
|
|
7982
8398
|
await this.stopEngine();
|
|
7983
8399
|
return fail("crash");
|
|
7984
8400
|
}
|
|
7985
|
-
const
|
|
8401
|
+
const logPath2 = this.manager.logPath();
|
|
7986
8402
|
const benchMessages = buildBenchMessages(profile.ctx);
|
|
7987
8403
|
phase("warming up\u2026");
|
|
7988
|
-
const warm = await this.prefillProbe(target, benchMessages, remaining(),
|
|
8404
|
+
const warm = await this.prefillProbe(target, benchMessages, remaining(), logPath2, stepPrefix);
|
|
7989
8405
|
if (warm !== "ok") {
|
|
7990
8406
|
await this.stopEngine();
|
|
7991
8407
|
return fail(warm.fault);
|
|
7992
8408
|
}
|
|
7993
8409
|
phase("measuring t/s\u2026");
|
|
7994
|
-
const measured = await this.runChatWatched(target, benchMessages, 128, remaining(),
|
|
8410
|
+
const measured = await this.runChatWatched(target, benchMessages, 128, remaining(), logPath2);
|
|
7995
8411
|
const vramAfter = await readGpuVramMb(sys);
|
|
7996
8412
|
await this.stopEngine();
|
|
7997
8413
|
if ("fault" in measured) return fail(measured.fault);
|
|
@@ -8003,7 +8419,7 @@ var BenchRunner = class {
|
|
|
8003
8419
|
* log (oom). Honors cancel + the global deadline. */
|
|
8004
8420
|
async awaitReady(testDeadline) {
|
|
8005
8421
|
const deadline = Math.min(Date.now() + READY_TIMEOUT_MS, testDeadline, this.deadline);
|
|
8006
|
-
const
|
|
8422
|
+
const logPath2 = this.manager.logPath();
|
|
8007
8423
|
for (; ; ) {
|
|
8008
8424
|
await sleep2(400);
|
|
8009
8425
|
if (this.cancelled) return "crash";
|
|
@@ -8014,7 +8430,7 @@ var BenchRunner = class {
|
|
|
8014
8430
|
if (tail.some((l) => OOM_RE.test(l))) return "oom";
|
|
8015
8431
|
return "crash";
|
|
8016
8432
|
}
|
|
8017
|
-
if (
|
|
8433
|
+
if (logPath2 && OOM_RE.test(readLiveTail(logPath2))) return "oom";
|
|
8018
8434
|
if (Date.now() > deadline) return "timeout";
|
|
8019
8435
|
}
|
|
8020
8436
|
}
|
|
@@ -8038,7 +8454,7 @@ var BenchRunner = class {
|
|
|
8038
8454
|
* in seconds instead of hanging out the per-test budget. A watchdog polls the engine state + the
|
|
8039
8455
|
* live engine log; on an OOM / "device not ready" / process death it aborts the request and the
|
|
8040
8456
|
* result is classified accordingly. Returns the timing, or a `fault` outcome. */
|
|
8041
|
-
async runChatWatched(target, messages, maxTokens, budgetMs,
|
|
8457
|
+
async runChatWatched(target, messages, maxTokens, budgetMs, logPath2) {
|
|
8042
8458
|
const probe2 = new AbortController();
|
|
8043
8459
|
let fault = null;
|
|
8044
8460
|
const watch = (async () => {
|
|
@@ -8055,7 +8471,7 @@ var BenchRunner = class {
|
|
|
8055
8471
|
probe2.abort();
|
|
8056
8472
|
return;
|
|
8057
8473
|
}
|
|
8058
|
-
if (
|
|
8474
|
+
if (logPath2 && OOM_RE.test(readLiveTail(logPath2))) {
|
|
8059
8475
|
fault = "oom";
|
|
8060
8476
|
probe2.abort();
|
|
8061
8477
|
return;
|
|
@@ -8082,7 +8498,7 @@ var BenchRunner = class {
|
|
|
8082
8498
|
* out the whole budget. Also aborts on an engine fault (OOM / "device not ready" / process death).
|
|
8083
8499
|
* Returns 'ok' once prefill completes (generation starts) — a config that gets here is viable and
|
|
8084
8500
|
* the warm prompt cache makes the following measured request fast and accurate. */
|
|
8085
|
-
async prefillProbe(target, messages, budgetMs,
|
|
8501
|
+
async prefillProbe(target, messages, budgetMs, logPath2, stepPrefix) {
|
|
8086
8502
|
const probe2 = new AbortController();
|
|
8087
8503
|
let fault = null;
|
|
8088
8504
|
const watch = (async () => {
|
|
@@ -8099,7 +8515,7 @@ var BenchRunner = class {
|
|
|
8099
8515
|
probe2.abort();
|
|
8100
8516
|
return;
|
|
8101
8517
|
}
|
|
8102
|
-
if (
|
|
8518
|
+
if (logPath2 && OOM_RE.test(readLiveTail(logPath2))) {
|
|
8103
8519
|
fault = "oom";
|
|
8104
8520
|
probe2.abort();
|
|
8105
8521
|
return;
|
|
@@ -8321,21 +8737,23 @@ var BenchRunner = class {
|
|
|
8321
8737
|
}
|
|
8322
8738
|
/** Queue an anonymized bench_result telemetry event (spec 09 §3) — ONLY when
|
|
8323
8739
|
* telemetry is on. Built from whitelisted fields only (never prompts, paths,
|
|
8324
|
-
* tokens).
|
|
8740
|
+
* tokens). Validation, bounding and fail-safety live in `telemetry/queue`
|
|
8741
|
+
* (ADR-299); this method owns only the consent check and the payload shape. */
|
|
8325
8742
|
queueTelemetry(record, entry, sys, appVersion, engineVersion) {
|
|
8326
8743
|
try {
|
|
8744
|
+
if (telemetryDisabled()) return;
|
|
8327
8745
|
const cfg2 = this.store.snapshot();
|
|
8328
8746
|
const level = cfg2.telemetry.level;
|
|
8329
8747
|
if (level !== "anon" && level !== "full") return;
|
|
8330
8748
|
let machineId = cfg2.telemetry.machineId;
|
|
8331
8749
|
if (!machineId) {
|
|
8332
|
-
machineId =
|
|
8750
|
+
machineId = randomUUID5();
|
|
8333
8751
|
this.store.update((c) => {
|
|
8334
8752
|
if (!c.telemetry.machineId) c.telemetry.machineId = machineId;
|
|
8335
8753
|
});
|
|
8336
8754
|
}
|
|
8337
|
-
|
|
8338
|
-
schema:
|
|
8755
|
+
enqueue(this.store.dir(), {
|
|
8756
|
+
schema: TELEMETRY_SCHEMA_VERSION,
|
|
8339
8757
|
event: "bench_result",
|
|
8340
8758
|
ts: record.ts,
|
|
8341
8759
|
machineId,
|
|
@@ -8351,10 +8769,7 @@ var BenchRunner = class {
|
|
|
8351
8769
|
params: record.params,
|
|
8352
8770
|
result: { tps: record.tps, ttftMs: record.ttftMs, vramMb: record.vramMb, outcome: "ok" }
|
|
8353
8771
|
}
|
|
8354
|
-
};
|
|
8355
|
-
const queueDir = join19(this.store.dir(), "telemetry", "queue");
|
|
8356
|
-
mkdirSync10(queueDir, { recursive: true });
|
|
8357
|
-
writeFileSync8(join19(queueDir, `${randomUUID4()}.json`), JSON.stringify(event));
|
|
8772
|
+
});
|
|
8358
8773
|
} catch {
|
|
8359
8774
|
}
|
|
8360
8775
|
}
|
|
@@ -8589,7 +9004,7 @@ function sleep2(ms) {
|
|
|
8589
9004
|
}
|
|
8590
9005
|
function readLiveTail(path) {
|
|
8591
9006
|
try {
|
|
8592
|
-
return
|
|
9007
|
+
return readFileSync9(path, "utf8").slice(-8e3);
|
|
8593
9008
|
} catch {
|
|
8594
9009
|
return "";
|
|
8595
9010
|
}
|
|
@@ -9678,11 +10093,11 @@ async function backfillPublishedDates(results) {
|
|
|
9678
10093
|
try {
|
|
9679
10094
|
const reader = resp.body.getReader();
|
|
9680
10095
|
const decoder = new TextDecoder();
|
|
9681
|
-
let
|
|
9682
|
-
while (
|
|
10096
|
+
let read2 = 0;
|
|
10097
|
+
while (read2 < DATE_PROBE_MAX_BYTES) {
|
|
9683
10098
|
const { done, value } = await reader.read();
|
|
9684
10099
|
if (done) break;
|
|
9685
|
-
|
|
10100
|
+
read2 += value.byteLength;
|
|
9686
10101
|
html += decoder.decode(value, { stream: true });
|
|
9687
10102
|
}
|
|
9688
10103
|
await reader.cancel().catch(() => {
|
|
@@ -10297,7 +10712,7 @@ var GenerationGate = class {
|
|
|
10297
10712
|
};
|
|
10298
10713
|
|
|
10299
10714
|
// src/agents/task-state.ts
|
|
10300
|
-
import { randomUUID as
|
|
10715
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
10301
10716
|
var MAX_TASKS = 50;
|
|
10302
10717
|
var KEEP_DONE_MS = 5 * 6e4;
|
|
10303
10718
|
var AgentTaskState = class {
|
|
@@ -10305,7 +10720,7 @@ var AgentTaskState = class {
|
|
|
10305
10720
|
/** Begin a task; returns its id (use it for step/finish). */
|
|
10306
10721
|
start(kind, agentId, label, convId) {
|
|
10307
10722
|
this.gc();
|
|
10308
|
-
const id =
|
|
10723
|
+
const id = randomUUID6();
|
|
10309
10724
|
this.tasks.set(id, { id, kind, convId, agentId, label, status: "running", steps: [], startedAt: Date.now() });
|
|
10310
10725
|
return id;
|
|
10311
10726
|
}
|
|
@@ -10356,7 +10771,7 @@ var AgentTaskState = class {
|
|
|
10356
10771
|
import { spawn as spawn4 } from "child_process";
|
|
10357
10772
|
import { homedir as homedir3 } from "os";
|
|
10358
10773
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
10359
|
-
import { join as
|
|
10774
|
+
import { join as join21, dirname as dirname8 } from "path";
|
|
10360
10775
|
var AUTH_TOKEN = "turbollm-local";
|
|
10361
10776
|
var SUPPORTED = {
|
|
10362
10777
|
claude: {
|
|
@@ -10494,13 +10909,13 @@ Add the "turbollm" provider by hand (see the Connect screen: TurboLLM UI \u2192
|
|
|
10494
10909
|
};
|
|
10495
10910
|
}
|
|
10496
10911
|
async function prepareOpencode(base2, apiKey, _modelKey, modelName, fs2 = realFs) {
|
|
10497
|
-
const path =
|
|
10498
|
-
const
|
|
10499
|
-
if ("corrupt" in
|
|
10500
|
-
const cfg2 =
|
|
10912
|
+
const path = join21(fs2.home, ".config", "opencode", "opencode.json");
|
|
10913
|
+
const read2 = await readConfigObject(fs2, path);
|
|
10914
|
+
if ("corrupt" in read2) return corruptConfigError(path, "opencode");
|
|
10915
|
+
const cfg2 = read2.obj;
|
|
10501
10916
|
const provider = asObject(cfg2.provider);
|
|
10502
10917
|
if (!provider) return corruptConfigError(path, "opencode");
|
|
10503
|
-
if (
|
|
10918
|
+
if (read2.lenient) {
|
|
10504
10919
|
return providerAlreadyPointsHere(provider.turbollm, base2) ? { ok: true } : commentedConfigError(path, "opencode");
|
|
10505
10920
|
}
|
|
10506
10921
|
provider.turbollm = {
|
|
@@ -10514,13 +10929,13 @@ async function prepareOpencode(base2, apiKey, _modelKey, modelName, fs2 = realFs
|
|
|
10514
10929
|
return { ok: true };
|
|
10515
10930
|
}
|
|
10516
10931
|
async function prepareKilo(base2, apiKey, _modelKey, modelName, fs2 = realFs) {
|
|
10517
|
-
const path =
|
|
10518
|
-
const
|
|
10519
|
-
if ("corrupt" in
|
|
10520
|
-
const cfg2 =
|
|
10932
|
+
const path = join21(fs2.home, ".config", "kilo", "kilo.jsonc");
|
|
10933
|
+
const read2 = await readConfigObject(fs2, path);
|
|
10934
|
+
if ("corrupt" in read2) return corruptConfigError(path, "Kilo Code");
|
|
10935
|
+
const cfg2 = read2.obj;
|
|
10521
10936
|
const provider = asObject(cfg2.provider);
|
|
10522
10937
|
if (!provider) return corruptConfigError(path, "Kilo Code");
|
|
10523
|
-
if (
|
|
10938
|
+
if (read2.lenient) {
|
|
10524
10939
|
return providerAlreadyPointsHere(provider.turbollm, base2) ? { ok: true } : commentedConfigError(path, "Kilo Code");
|
|
10525
10940
|
}
|
|
10526
10941
|
provider.turbollm = {
|
|
@@ -10535,16 +10950,16 @@ async function prepareKilo(base2, apiKey, _modelKey, modelName, fs2 = realFs) {
|
|
|
10535
10950
|
return { ok: true };
|
|
10536
10951
|
}
|
|
10537
10952
|
async function prepareOpenclaw(base2, apiKey, modelKey, modelName, fs2 = realFs) {
|
|
10538
|
-
const path =
|
|
10539
|
-
const
|
|
10540
|
-
if ("corrupt" in
|
|
10541
|
-
const cfg2 =
|
|
10953
|
+
const path = join21(fs2.home, ".config", "openclaw", "openclaw.json");
|
|
10954
|
+
const read2 = await readConfigObject(fs2, path);
|
|
10955
|
+
if ("corrupt" in read2) return corruptConfigError(path, "openclaw");
|
|
10956
|
+
const cfg2 = read2.obj;
|
|
10542
10957
|
const models = asObject(cfg2.models);
|
|
10543
10958
|
const providers = models && asObject(models.providers);
|
|
10544
10959
|
const agents = asObject(cfg2.agents);
|
|
10545
10960
|
const defaults = agents && asObject(agents.defaults);
|
|
10546
10961
|
if (!models || !providers || !agents || !defaults) return corruptConfigError(path, "openclaw");
|
|
10547
|
-
if (
|
|
10962
|
+
if (read2.lenient) {
|
|
10548
10963
|
return providerAlreadyPointsHere(providers.turbollm, base2) ? { ok: true } : commentedConfigError(path, "openclaw");
|
|
10549
10964
|
}
|
|
10550
10965
|
providers.turbollm = {
|
|
@@ -10563,7 +10978,7 @@ async function prepareOpenclaw(base2, apiKey, modelKey, modelName, fs2 = realFs)
|
|
|
10563
10978
|
return { ok: true };
|
|
10564
10979
|
}
|
|
10565
10980
|
async function preparePi(base2, apiKey, modelKey, modelName, fs2 = realFs) {
|
|
10566
|
-
const modelsPath =
|
|
10981
|
+
const modelsPath = join21(fs2.home, ".pi", "agent", "models.json");
|
|
10567
10982
|
const modelsRead = await readConfigObject(fs2, modelsPath);
|
|
10568
10983
|
if ("corrupt" in modelsRead) return corruptConfigError(modelsPath, "pi");
|
|
10569
10984
|
const modelsCfg = modelsRead.obj;
|
|
@@ -10582,7 +10997,7 @@ async function preparePi(base2, apiKey, modelKey, modelName, fs2 = realFs) {
|
|
|
10582
10997
|
await fs2.mkdir(dirname8(modelsPath));
|
|
10583
10998
|
await fs2.writeFile(modelsPath, JSON.stringify(modelsCfg, null, 2) + "\n");
|
|
10584
10999
|
}
|
|
10585
|
-
const settingsPath =
|
|
11000
|
+
const settingsPath = join21(fs2.home, ".pi", "agent", "settings.json");
|
|
10586
11001
|
const settingsRead = await readConfigObject(fs2, settingsPath);
|
|
10587
11002
|
if ("corrupt" in settingsRead) return corruptConfigError(settingsPath, "pi");
|
|
10588
11003
|
const settingsCfg = settingsRead.obj;
|
|
@@ -10818,18 +11233,18 @@ Install it: ${spec.install}
|
|
|
10818
11233
|
}
|
|
10819
11234
|
|
|
10820
11235
|
// src/daemon-pid.ts
|
|
10821
|
-
import { readFileSync as
|
|
10822
|
-
import { join as
|
|
11236
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync10, unlinkSync, existsSync as existsSync18 } from "fs";
|
|
11237
|
+
import { join as join22 } from "path";
|
|
10823
11238
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
10824
11239
|
function writePidfile(dir, pid, port2) {
|
|
10825
|
-
const path =
|
|
10826
|
-
|
|
11240
|
+
const path = join22(dir, "daemon.pid");
|
|
11241
|
+
writeFileSync10(path, JSON.stringify({ pid, port: port2 }));
|
|
10827
11242
|
}
|
|
10828
11243
|
function readPidfile(dir) {
|
|
10829
|
-
const path =
|
|
11244
|
+
const path = join22(dir, "daemon.pid");
|
|
10830
11245
|
if (!existsSync18(path)) return null;
|
|
10831
11246
|
try {
|
|
10832
|
-
const raw =
|
|
11247
|
+
const raw = readFileSync10(path, "utf8");
|
|
10833
11248
|
const parsed = JSON.parse(raw);
|
|
10834
11249
|
if (parsed !== null && typeof parsed === "object" && typeof parsed.pid === "number" && typeof parsed.port === "number") {
|
|
10835
11250
|
return parsed;
|
|
@@ -10847,7 +11262,7 @@ function resolveDaemonPort(dir, explicitPort, fallbackPort) {
|
|
|
10847
11262
|
}
|
|
10848
11263
|
function removePidfile(dir) {
|
|
10849
11264
|
try {
|
|
10850
|
-
unlinkSync(
|
|
11265
|
+
unlinkSync(join22(dir, "daemon.pid"));
|
|
10851
11266
|
} catch {
|
|
10852
11267
|
}
|
|
10853
11268
|
}
|
|
@@ -11074,16 +11489,16 @@ function runMcpServer(baseUrl, version2) {
|
|
|
11074
11489
|
// src/server.ts
|
|
11075
11490
|
import { Hono } from "hono";
|
|
11076
11491
|
import { cors } from "hono/cors";
|
|
11077
|
-
import { existsSync as existsSync24, readFileSync as
|
|
11492
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17, statSync as statSync6 } from "fs";
|
|
11078
11493
|
import { fileURLToPath } from "url";
|
|
11079
|
-
import { dirname as dirname11, join as
|
|
11494
|
+
import { dirname as dirname11, join as join32, normalize as normalize2 } from "path";
|
|
11080
11495
|
import { Agent, setGlobalDispatcher } from "undici";
|
|
11081
11496
|
|
|
11082
11497
|
// src/api/routes.ts
|
|
11083
11498
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
11084
|
-
import { existsSync as existsSync22, mkdirSync as
|
|
11499
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync16, readFileSync as readFileSync14, readdirSync as readdirSync10, realpathSync, rmSync as rmSync14, statSync as statSync5, writeFileSync as writeFileSync14 } from "fs";
|
|
11085
11500
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
11086
|
-
import { basename as basename3, dirname as dirname9, join as
|
|
11501
|
+
import { basename as basename3, dirname as dirname9, join as join28, resolve } from "path";
|
|
11087
11502
|
|
|
11088
11503
|
// src/comfyui/gate-template.ts
|
|
11089
11504
|
var GATE_VERSION = 2;
|
|
@@ -11222,7 +11637,7 @@ __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
|
|
11222
11637
|
}
|
|
11223
11638
|
|
|
11224
11639
|
// src/api/routes.ts
|
|
11225
|
-
import { randomUUID as
|
|
11640
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
11226
11641
|
import { homedir as homedir4, networkInterfaces as networkInterfaces2 } from "os";
|
|
11227
11642
|
|
|
11228
11643
|
// src/chat/chat-routes.ts
|
|
@@ -11618,8 +12033,8 @@ function buildSaveSkillTool(onSaveSkill) {
|
|
|
11618
12033
|
}
|
|
11619
12034
|
|
|
11620
12035
|
// src/agents/skills.ts
|
|
11621
|
-
import { join as
|
|
11622
|
-
import { existsSync as existsSync19, readFileSync as
|
|
12036
|
+
import { join as join23 } from "path";
|
|
12037
|
+
import { existsSync as existsSync19, readFileSync as readFileSync11, writeFileSync as writeFileSync11, mkdirSync as mkdirSync12, rmSync as rmSync11, readdirSync as readdirSync7 } from "fs";
|
|
11623
12038
|
var BUILTIN_SKILLS = [
|
|
11624
12039
|
{
|
|
11625
12040
|
id: "filesystem",
|
|
@@ -11708,22 +12123,22 @@ var SkillStore = class {
|
|
|
11708
12123
|
}
|
|
11709
12124
|
dataDir;
|
|
11710
12125
|
get skillsDir() {
|
|
11711
|
-
return
|
|
12126
|
+
return join23(this.dataDir, "skills");
|
|
11712
12127
|
}
|
|
11713
12128
|
list() {
|
|
11714
12129
|
const skills = [...BUILTIN_SKILLS];
|
|
11715
12130
|
const idSet = new Set(skills.map((s) => s.id));
|
|
11716
12131
|
if (!existsSync19(this.skillsDir)) return skills;
|
|
11717
|
-
for (const e of
|
|
12132
|
+
for (const e of readdirSync7(this.skillsDir, { withFileTypes: true })) {
|
|
11718
12133
|
let skill = null;
|
|
11719
12134
|
let id = "";
|
|
11720
12135
|
if (e.isDirectory()) {
|
|
11721
12136
|
id = e.name;
|
|
11722
|
-
const md =
|
|
12137
|
+
const md = join23(this.skillsDir, e.name, "SKILL.md");
|
|
11723
12138
|
if (existsSync19(md)) skill = this.loadMd(id, md);
|
|
11724
12139
|
} else if (e.isFile() && e.name.endsWith(".json")) {
|
|
11725
12140
|
id = e.name.slice(0, -5);
|
|
11726
|
-
skill = this.loadJson(
|
|
12141
|
+
skill = this.loadJson(join23(this.skillsDir, e.name), id);
|
|
11727
12142
|
}
|
|
11728
12143
|
if (!skill || !id || idSet.has(id)) continue;
|
|
11729
12144
|
idSet.add(id);
|
|
@@ -11739,30 +12154,30 @@ var SkillStore = class {
|
|
|
11739
12154
|
return this.list().find((s) => s.id === id);
|
|
11740
12155
|
}
|
|
11741
12156
|
has(id) {
|
|
11742
|
-
return isValidSkillId(id) && existsSync19(
|
|
12157
|
+
return isValidSkillId(id) && existsSync19(join23(this.skillsDir, id, "SKILL.md"));
|
|
11743
12158
|
}
|
|
11744
12159
|
write(skill) {
|
|
11745
12160
|
if (!isValidSkillId(skill.id)) throw new Error(`Invalid skill id: ${skill.id}`);
|
|
11746
|
-
const dir =
|
|
11747
|
-
|
|
11748
|
-
|
|
12161
|
+
const dir = join23(this.skillsDir, skill.id);
|
|
12162
|
+
mkdirSync12(dir, { recursive: true });
|
|
12163
|
+
writeFileSync11(join23(dir, "SKILL.md"), toSkillMd(skill), "utf8");
|
|
11749
12164
|
}
|
|
11750
12165
|
delete(id) {
|
|
11751
12166
|
if (!isValidSkillId(id)) return;
|
|
11752
|
-
|
|
11753
|
-
const legacy =
|
|
11754
|
-
if (existsSync19(legacy))
|
|
12167
|
+
rmSync11(join23(this.skillsDir, id), { recursive: true, force: true });
|
|
12168
|
+
const legacy = join23(this.skillsDir, `${id}.json`);
|
|
12169
|
+
if (existsSync19(legacy)) rmSync11(legacy);
|
|
11755
12170
|
}
|
|
11756
12171
|
loadMd(id, path) {
|
|
11757
12172
|
try {
|
|
11758
|
-
return fromSkillMd(id,
|
|
12173
|
+
return fromSkillMd(id, readFileSync11(path, "utf8"));
|
|
11759
12174
|
} catch {
|
|
11760
12175
|
return null;
|
|
11761
12176
|
}
|
|
11762
12177
|
}
|
|
11763
12178
|
loadJson(path, id) {
|
|
11764
12179
|
try {
|
|
11765
|
-
const data = JSON.parse(
|
|
12180
|
+
const data = JSON.parse(readFileSync11(path, "utf8"));
|
|
11766
12181
|
if (!data.name || typeof data.name !== "string") return null;
|
|
11767
12182
|
if (!data.instructions || typeof data.instructions !== "string") return null;
|
|
11768
12183
|
return { id, name: data.name, description: data.description || "", instructions: data.instructions, tools: Array.isArray(data.tools) ? data.tools : [], builtin: false };
|
|
@@ -11784,7 +12199,7 @@ function importSkillsFromFolder(store2, folder, opts = {}) {
|
|
|
11784
12199
|
if (depth > 5 || found.length >= max) return;
|
|
11785
12200
|
let entries;
|
|
11786
12201
|
try {
|
|
11787
|
-
entries =
|
|
12202
|
+
entries = readdirSync7(dir, { withFileTypes: true });
|
|
11788
12203
|
} catch {
|
|
11789
12204
|
return;
|
|
11790
12205
|
}
|
|
@@ -11792,14 +12207,14 @@ function importSkillsFromFolder(store2, folder, opts = {}) {
|
|
|
11792
12207
|
if (skillFile) {
|
|
11793
12208
|
const id = toSkillId(dir.split(/[\\/]/).pop() || "skill");
|
|
11794
12209
|
try {
|
|
11795
|
-
found.push({ id, text:
|
|
12210
|
+
found.push({ id, text: readFileSync11(join23(dir, skillFile.name), "utf8") });
|
|
11796
12211
|
} catch {
|
|
11797
12212
|
}
|
|
11798
12213
|
return;
|
|
11799
12214
|
}
|
|
11800
12215
|
for (const e of entries) {
|
|
11801
12216
|
if (found.length >= max) return;
|
|
11802
|
-
if (e.isDirectory() && !e.name.startsWith(".")) walk2(
|
|
12217
|
+
if (e.isDirectory() && !e.name.startsWith(".")) walk2(join23(dir, e.name), depth + 1);
|
|
11803
12218
|
}
|
|
11804
12219
|
};
|
|
11805
12220
|
walk2(folder, 0);
|
|
@@ -12960,7 +13375,7 @@ async function autoTitleFromConversation(d, convId) {
|
|
|
12960
13375
|
}
|
|
12961
13376
|
|
|
12962
13377
|
// src/auth.ts
|
|
12963
|
-
import { createHash as createHash5, randomBytes, randomUUID as
|
|
13378
|
+
import { createHash as createHash5, randomBytes, randomUUID as randomUUID7 } from "crypto";
|
|
12964
13379
|
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
12965
13380
|
var LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
|
|
12966
13381
|
function hashKey(raw) {
|
|
@@ -12978,7 +13393,7 @@ function generateApiKey() {
|
|
|
12978
13393
|
function provisionTunnelApiKey(d) {
|
|
12979
13394
|
const { full, hash, prefix } = generateApiKey();
|
|
12980
13395
|
const key = {
|
|
12981
|
-
id:
|
|
13396
|
+
id: randomUUID7(),
|
|
12982
13397
|
name: `tunnel-${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
12983
13398
|
hash,
|
|
12984
13399
|
prefix,
|
|
@@ -13214,8 +13629,8 @@ async function claudePermissionModeChoices(run = realHelpRunner) {
|
|
|
13214
13629
|
|
|
13215
13630
|
// src/terminal/terminal-manager.ts
|
|
13216
13631
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
13217
|
-
import { mkdirSync as
|
|
13218
|
-
import { join as
|
|
13632
|
+
import { mkdirSync as mkdirSync13, readFileSync as readFileSync12, readdirSync as readdirSync8, rmSync as rmSync12, writeFileSync as writeFileSync12 } from "fs";
|
|
13633
|
+
import { join as join24 } from "path";
|
|
13219
13634
|
|
|
13220
13635
|
// src/terminal/pty-session.ts
|
|
13221
13636
|
import { EventEmitter } from "events";
|
|
@@ -13337,21 +13752,21 @@ var PTYSession = class _PTYSession {
|
|
|
13337
13752
|
|
|
13338
13753
|
// src/terminal/terminal-manager.ts
|
|
13339
13754
|
function terminalPidDir(dataDir) {
|
|
13340
|
-
return
|
|
13755
|
+
return join24(dataDir, "run");
|
|
13341
13756
|
}
|
|
13342
13757
|
function terminalPidFile(dataDir, pid) {
|
|
13343
|
-
return
|
|
13758
|
+
return join24(terminalPidDir(dataDir), `terminal-${pid}.pid`);
|
|
13344
13759
|
}
|
|
13345
13760
|
function writeTerminalPid(dataDir, pid) {
|
|
13346
13761
|
try {
|
|
13347
|
-
|
|
13348
|
-
|
|
13762
|
+
mkdirSync13(terminalPidDir(dataDir), { recursive: true });
|
|
13763
|
+
writeFileSync12(terminalPidFile(dataDir, pid), JSON.stringify({ pid, owner: process.pid }));
|
|
13349
13764
|
} catch {
|
|
13350
13765
|
}
|
|
13351
13766
|
}
|
|
13352
13767
|
function clearTerminalPid(dataDir, pid) {
|
|
13353
13768
|
try {
|
|
13354
|
-
|
|
13769
|
+
rmSync12(terminalPidFile(dataDir, pid), { force: true });
|
|
13355
13770
|
} catch {
|
|
13356
13771
|
}
|
|
13357
13772
|
}
|
|
@@ -13368,20 +13783,20 @@ function readTerminalPidFiles(dataDir) {
|
|
|
13368
13783
|
const dir = terminalPidDir(dataDir);
|
|
13369
13784
|
let names;
|
|
13370
13785
|
try {
|
|
13371
|
-
names =
|
|
13786
|
+
names = readdirSync8(dir).filter((n) => /^terminal-\d+\.pid$/.test(n));
|
|
13372
13787
|
} catch {
|
|
13373
13788
|
return [];
|
|
13374
13789
|
}
|
|
13375
13790
|
const out = [];
|
|
13376
13791
|
for (const name of names) {
|
|
13377
|
-
const file =
|
|
13792
|
+
const file = join24(dir, name);
|
|
13378
13793
|
try {
|
|
13379
|
-
const { pid, owner } = JSON.parse(
|
|
13794
|
+
const { pid, owner } = JSON.parse(readFileSync12(file, "utf8"));
|
|
13380
13795
|
if (typeof pid === "number" && pid > 0) out.push({ pid, owner: typeof owner === "number" ? owner : 0, file });
|
|
13381
|
-
else
|
|
13796
|
+
else rmSync12(file, { force: true });
|
|
13382
13797
|
} catch {
|
|
13383
13798
|
try {
|
|
13384
|
-
|
|
13799
|
+
rmSync12(file, { force: true });
|
|
13385
13800
|
} catch {
|
|
13386
13801
|
}
|
|
13387
13802
|
}
|
|
@@ -13399,7 +13814,7 @@ function reapStaleTerminals(dataDir) {
|
|
|
13399
13814
|
} catch {
|
|
13400
13815
|
}
|
|
13401
13816
|
try {
|
|
13402
|
-
|
|
13817
|
+
rmSync12(file, { force: true });
|
|
13403
13818
|
} catch {
|
|
13404
13819
|
}
|
|
13405
13820
|
}
|
|
@@ -13414,7 +13829,7 @@ function killTrackedTerminalsSync(dataDir) {
|
|
|
13414
13829
|
} catch {
|
|
13415
13830
|
}
|
|
13416
13831
|
try {
|
|
13417
|
-
|
|
13832
|
+
rmSync12(file, { force: true });
|
|
13418
13833
|
} catch {
|
|
13419
13834
|
}
|
|
13420
13835
|
}
|
|
@@ -14241,9 +14656,9 @@ function catalogEngine(id) {
|
|
|
14241
14656
|
|
|
14242
14657
|
// src/engines/cuda-provision.ts
|
|
14243
14658
|
import { execFile as execFile11 } from "child_process";
|
|
14244
|
-
import { cpSync as cpSync2, existsSync as existsSync20, mkdirSync as
|
|
14659
|
+
import { cpSync as cpSync2, existsSync as existsSync20, mkdirSync as mkdirSync14, readdirSync as readdirSync9, rmSync as rmSync13 } from "fs";
|
|
14245
14660
|
import { tmpdir } from "os";
|
|
14246
|
-
import { join as
|
|
14661
|
+
import { join as join25 } from "path";
|
|
14247
14662
|
import { promisify as promisify8 } from "util";
|
|
14248
14663
|
var execFileP8 = promisify8(execFile11);
|
|
14249
14664
|
var REDIST_BASE = "https://developer.download.nvidia.com/compute/cuda/redist/";
|
|
@@ -14298,9 +14713,9 @@ async function provisionCuda(dataDir, onProgress, signal) {
|
|
|
14298
14713
|
phase: "resolving",
|
|
14299
14714
|
message: `Using CUDA ${version2}${driverMax ? ` (driver supports up to ${driverMax})` : ""}.`
|
|
14300
14715
|
});
|
|
14301
|
-
const root =
|
|
14302
|
-
const binDir =
|
|
14303
|
-
if (existsSync20(
|
|
14716
|
+
const root = join25(dataDir, "cuda", version2);
|
|
14717
|
+
const binDir = join25(root, "bin");
|
|
14718
|
+
if (existsSync20(join25(binDir, "nvcc.exe"))) {
|
|
14304
14719
|
onProgress({ phase: "assembling", message: `CUDA ${version2} already downloaded \u2014 reusing it.` });
|
|
14305
14720
|
return { root, binDir, version: version2 };
|
|
14306
14721
|
}
|
|
@@ -14315,38 +14730,38 @@ async function provisionCuda(dataDir, onProgress, signal) {
|
|
|
14315
14730
|
const rel = relOf(id);
|
|
14316
14731
|
if (rel) components.push({ id, url: REDIST_BASE + rel });
|
|
14317
14732
|
}
|
|
14318
|
-
const work =
|
|
14319
|
-
|
|
14320
|
-
|
|
14733
|
+
const work = join25(tmpdir(), `tllm-cuda-${version2}`);
|
|
14734
|
+
rmSync13(work, { recursive: true, force: true });
|
|
14735
|
+
mkdirSync14(work, { recursive: true });
|
|
14321
14736
|
const stageRoot = `${root}.partial`;
|
|
14322
|
-
|
|
14323
|
-
|
|
14737
|
+
rmSync13(stageRoot, { recursive: true, force: true });
|
|
14738
|
+
mkdirSync14(stageRoot, { recursive: true });
|
|
14324
14739
|
try {
|
|
14325
14740
|
for (let i = 0; i < components.length; i++) {
|
|
14326
14741
|
const { id, url } = components[i];
|
|
14327
|
-
const zip =
|
|
14742
|
+
const zip = join25(work, `${id}.zip`);
|
|
14328
14743
|
onProgress({ phase: "downloading", message: `Downloading ${id} (${i + 1}/${components.length})\u2026`, pct: 0 });
|
|
14329
14744
|
await downloadFile(url, zip, (p) => onProgress({ phase: "downloading", message: `Downloading ${id} (${i + 1}/${components.length})\u2026`, pct: p.pct }), signal);
|
|
14330
|
-
const exDir =
|
|
14331
|
-
|
|
14745
|
+
const exDir = join25(work, id);
|
|
14746
|
+
mkdirSync14(exDir, { recursive: true });
|
|
14332
14747
|
onProgress({ phase: "extracting", message: `Extracting ${id}\u2026` });
|
|
14333
14748
|
await extractArchive(zip, exDir);
|
|
14334
|
-
const inner =
|
|
14335
|
-
const srcRoot = inner ??
|
|
14749
|
+
const inner = readdirSync9(exDir).map((n) => join25(exDir, n)).find((p) => existsSync20(join25(p, "include")) || existsSync20(join25(p, "bin")) || existsSync20(join25(p, "lib")));
|
|
14750
|
+
const srcRoot = inner ?? join25(exDir, readdirSync9(exDir)[0] ?? "");
|
|
14336
14751
|
cpSync2(srcRoot, stageRoot, { recursive: true, force: true });
|
|
14337
|
-
|
|
14752
|
+
rmSync13(zip, { force: true });
|
|
14338
14753
|
}
|
|
14339
|
-
if (!existsSync20(
|
|
14754
|
+
if (!existsSync20(join25(stageRoot, "bin", "nvcc.exe"))) {
|
|
14340
14755
|
throw new Error("Assembled CUDA toolkit is missing bin/nvcc.exe \u2014 the component set may have changed.");
|
|
14341
14756
|
}
|
|
14342
|
-
|
|
14343
|
-
|
|
14757
|
+
rmSync13(root, { recursive: true, force: true });
|
|
14758
|
+
mkdirSync14(join25(dataDir, "cuda"), { recursive: true });
|
|
14344
14759
|
cpSync2(stageRoot, root, { recursive: true, force: true });
|
|
14345
14760
|
onProgress({ phase: "assembling", message: `CUDA ${version2} ready.` });
|
|
14346
14761
|
return { root, binDir, version: version2 };
|
|
14347
14762
|
} finally {
|
|
14348
|
-
|
|
14349
|
-
|
|
14763
|
+
rmSync13(work, { recursive: true, force: true });
|
|
14764
|
+
rmSync13(stageRoot, { recursive: true, force: true });
|
|
14350
14765
|
}
|
|
14351
14766
|
}
|
|
14352
14767
|
|
|
@@ -14423,7 +14838,7 @@ function recommendEngines(p, engines) {
|
|
|
14423
14838
|
// src/artifacts/screenshot.ts
|
|
14424
14839
|
import { existsSync as existsSync21 } from "fs";
|
|
14425
14840
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
14426
|
-
import { join as
|
|
14841
|
+
import { join as join26 } from "path";
|
|
14427
14842
|
function chromeCandidates() {
|
|
14428
14843
|
const env = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROME_PATH;
|
|
14429
14844
|
const out = env ? [env] : [];
|
|
@@ -14432,10 +14847,10 @@ function chromeCandidates() {
|
|
|
14432
14847
|
const pfx86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
|
|
14433
14848
|
const local = process.env["LOCALAPPDATA"] || "";
|
|
14434
14849
|
for (const base2 of [pf, pfx86, local].filter(Boolean)) {
|
|
14435
|
-
out.push(
|
|
14850
|
+
out.push(join26(base2, "Google", "Chrome", "Application", "chrome.exe"));
|
|
14436
14851
|
}
|
|
14437
14852
|
for (const base2 of [pfx86, pf].filter(Boolean)) {
|
|
14438
|
-
out.push(
|
|
14853
|
+
out.push(join26(base2, "Microsoft", "Edge", "Application", "msedge.exe"));
|
|
14439
14854
|
}
|
|
14440
14855
|
} else if (process.platform === "darwin") {
|
|
14441
14856
|
out.push(
|
|
@@ -14516,6 +14931,86 @@ async function screenshotArtifact(html, opts) {
|
|
|
14516
14931
|
}
|
|
14517
14932
|
}
|
|
14518
14933
|
|
|
14934
|
+
// src/telemetry/log.ts
|
|
14935
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync13, mkdirSync as mkdirSync15 } from "fs";
|
|
14936
|
+
import { join as join27 } from "path";
|
|
14937
|
+
var MAX_LOGGED = 200;
|
|
14938
|
+
function logPath(dataDir) {
|
|
14939
|
+
return join27(dataDir, "telemetry", "sent.json");
|
|
14940
|
+
}
|
|
14941
|
+
function readSentLog(dataDir) {
|
|
14942
|
+
try {
|
|
14943
|
+
const parsed = JSON.parse(readFileSync13(logPath(dataDir), "utf8"));
|
|
14944
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
14945
|
+
} catch {
|
|
14946
|
+
return [];
|
|
14947
|
+
}
|
|
14948
|
+
}
|
|
14949
|
+
function recordSent(dataDir, events) {
|
|
14950
|
+
try {
|
|
14951
|
+
const sentAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14952
|
+
const fresh = events.map((event) => ({ sentAt, event })).reverse();
|
|
14953
|
+
const next = [...fresh, ...readSentLog(dataDir)].slice(0, MAX_LOGGED);
|
|
14954
|
+
mkdirSync15(join27(dataDir, "telemetry"), { recursive: true });
|
|
14955
|
+
writeFileSync13(logPath(dataDir), JSON.stringify(next));
|
|
14956
|
+
} catch {
|
|
14957
|
+
}
|
|
14958
|
+
}
|
|
14959
|
+
|
|
14960
|
+
// src/telemetry/uploader.ts
|
|
14961
|
+
var INGEST_URL = "https://t.turbollm.dev/v1/events";
|
|
14962
|
+
function mayTransmit(level) {
|
|
14963
|
+
return level === "anon" || level === "full";
|
|
14964
|
+
}
|
|
14965
|
+
async function flush(dataDir, level, transport = httpTransport) {
|
|
14966
|
+
try {
|
|
14967
|
+
const queued = readQueue(dataDir);
|
|
14968
|
+
if (telemetryDisabled() || !mayTransmit(level)) {
|
|
14969
|
+
for (const q of queued) remove(dataDir, q.file);
|
|
14970
|
+
return;
|
|
14971
|
+
}
|
|
14972
|
+
if (queued.length === 0) return;
|
|
14973
|
+
const events = queued.map((q) => q.event);
|
|
14974
|
+
const accepted = await transport(events);
|
|
14975
|
+
if (accepted) {
|
|
14976
|
+
recordSent(dataDir, events);
|
|
14977
|
+
for (const q of queued) remove(dataDir, q.file);
|
|
14978
|
+
}
|
|
14979
|
+
} catch {
|
|
14980
|
+
}
|
|
14981
|
+
}
|
|
14982
|
+
var httpTransport = async (events) => {
|
|
14983
|
+
const signal = AbortSignal.timeout(1e4);
|
|
14984
|
+
const res = await fetch(INGEST_URL, {
|
|
14985
|
+
method: "POST",
|
|
14986
|
+
headers: { "content-type": "application/json" },
|
|
14987
|
+
body: JSON.stringify(events),
|
|
14988
|
+
signal
|
|
14989
|
+
});
|
|
14990
|
+
return res.ok;
|
|
14991
|
+
};
|
|
14992
|
+
|
|
14993
|
+
// src/telemetry/consent.ts
|
|
14994
|
+
var httpTransport2 = async (events) => {
|
|
14995
|
+
const res = await fetch(INGEST_URL, {
|
|
14996
|
+
method: "POST",
|
|
14997
|
+
headers: { "content-type": "application/json" },
|
|
14998
|
+
body: JSON.stringify(events),
|
|
14999
|
+
signal: AbortSignal.timeout(1e4)
|
|
15000
|
+
});
|
|
15001
|
+
return res.ok;
|
|
15002
|
+
};
|
|
15003
|
+
async function sendConsentChoice(dataDir, level, transport = httpTransport2) {
|
|
15004
|
+
try {
|
|
15005
|
+
if (telemetryDisabled()) return;
|
|
15006
|
+
if (!CONSENT_LEVELS.includes(level)) return;
|
|
15007
|
+
if (!claimOnce(dataDir, "consent_choice_sent")) return;
|
|
15008
|
+
const ping = { schema: TELEMETRY_SCHEMA_VERSION, event: "consent_choice", level };
|
|
15009
|
+
if (await transport([ping])) recordSent(dataDir, [ping]);
|
|
15010
|
+
} catch {
|
|
15011
|
+
}
|
|
15012
|
+
}
|
|
15013
|
+
|
|
14519
15014
|
// src/api/routes.ts
|
|
14520
15015
|
function err3(c, status, code, message) {
|
|
14521
15016
|
return c.json({ error: { code, message } }, status);
|
|
@@ -14577,7 +15072,7 @@ function registerApi(app2, d) {
|
|
|
14577
15072
|
let installedVersion = null;
|
|
14578
15073
|
if (gatePath) {
|
|
14579
15074
|
try {
|
|
14580
|
-
const src =
|
|
15075
|
+
const src = readFileSync14(join28(gatePath, "__init__.py"), "utf-8");
|
|
14581
15076
|
const m = src.match(/^GATE_VERSION\s*=\s*(\d+)/m);
|
|
14582
15077
|
if (m) installedVersion = Number(m[1]);
|
|
14583
15078
|
} catch {
|
|
@@ -14617,7 +15112,7 @@ function registerApi(app2, d) {
|
|
|
14617
15112
|
const sys = getSysInfo();
|
|
14618
15113
|
const vendor = primaryVendor(sys);
|
|
14619
15114
|
const recommended = recommendBackendId(vendor, sys.gpus.length > 0);
|
|
14620
|
-
const root =
|
|
15115
|
+
const root = join28(d.store.dir(), "engines");
|
|
14621
15116
|
const active = d.registry.active();
|
|
14622
15117
|
const regEngines = d.registry.list().engines;
|
|
14623
15118
|
const backendOf = (p) => p.match(/llama\.cpp-[^/\\]+-(cuda|rocm|sycl|vulkan|metal|cpu)/i)?.[1]?.toLowerCase();
|
|
@@ -14638,7 +15133,7 @@ function registerApi(app2, d) {
|
|
|
14638
15133
|
engineId: eng?.id ?? ""
|
|
14639
15134
|
};
|
|
14640
15135
|
});
|
|
14641
|
-
const mlxVenvPy =
|
|
15136
|
+
const mlxVenvPy = join28(root, "mlx", "venv", process.platform === "win32" ? "Scripts/python.exe" : "bin/python");
|
|
14642
15137
|
const mlxInstalledOnDisk = existsSync22(mlxVenvPy);
|
|
14643
15138
|
const mlxEngine = regEngines.find((e) => e.kind === "mlx");
|
|
14644
15139
|
const mlx = {
|
|
@@ -14654,7 +15149,7 @@ function registerApi(app2, d) {
|
|
|
14654
15149
|
const b = await body3(c);
|
|
14655
15150
|
const def = availableBackends().find((x) => x.id === b.backend);
|
|
14656
15151
|
if (!def) return err3(c, 400, "invalid_config_value", "Unknown backend for this platform.");
|
|
14657
|
-
const root =
|
|
15152
|
+
const root = join28(d.store.dir(), "engines");
|
|
14658
15153
|
const existing = installedBackendBuild(root, def.id);
|
|
14659
15154
|
if (existing) {
|
|
14660
15155
|
return c.json({ accepted: false, alreadyInstalled: true, build: existing.tag });
|
|
@@ -14698,7 +15193,7 @@ function registerApi(app2, d) {
|
|
|
14698
15193
|
app2.post("/api/v1/engines/backends/:id/update", async (c) => {
|
|
14699
15194
|
const def = availableBackends().find((x) => x.id === c.req.param("id"));
|
|
14700
15195
|
if (!def) return err3(c, 400, "invalid_config_value", "Unknown backend for this platform.");
|
|
14701
|
-
const root =
|
|
15196
|
+
const root = join28(d.store.dir(), "engines");
|
|
14702
15197
|
const oldBin = installedBackendBuild(root, def.id)?.bin ?? null;
|
|
14703
15198
|
if (!oldBin) return err3(c, 409, "not_installed", "Backend is not installed \u2014 download it first.");
|
|
14704
15199
|
{
|
|
@@ -14742,7 +15237,7 @@ function registerApi(app2, d) {
|
|
|
14742
15237
|
}
|
|
14743
15238
|
}
|
|
14744
15239
|
if (installedTag && installedTag !== latestTag) {
|
|
14745
|
-
|
|
15240
|
+
rmSync14(backendDir(root, def.id, installedTag), { recursive: true, force: true });
|
|
14746
15241
|
}
|
|
14747
15242
|
d.provision.done();
|
|
14748
15243
|
} catch (e) {
|
|
@@ -14757,7 +15252,7 @@ function registerApi(app2, d) {
|
|
|
14757
15252
|
app2.post("/api/v1/engines/backends/:id/enable", async (c) => {
|
|
14758
15253
|
const def = availableBackends().find((x) => x.id === c.req.param("id"));
|
|
14759
15254
|
if (!def) return err3(c, 400, "invalid_config_value", "Unknown backend for this platform.");
|
|
14760
|
-
const root =
|
|
15255
|
+
const root = join28(d.store.dir(), "engines");
|
|
14761
15256
|
const build2 = installedBackendBuild(root, def.id);
|
|
14762
15257
|
if (!build2) return err3(c, 409, "not_installed", "Backend is not installed on disk \u2014 download it first.");
|
|
14763
15258
|
let eng = d.registry.list().engines.find((e) => e.binPath === build2.bin);
|
|
@@ -14768,7 +15263,7 @@ function registerApi(app2, d) {
|
|
|
14768
15263
|
app2.delete("/api/v1/engines/backends/:id", async (c) => {
|
|
14769
15264
|
const def = availableBackends().find((x) => x.id === c.req.param("id"));
|
|
14770
15265
|
if (!def) return err3(c, 400, "invalid_config_value", "Unknown backend for this platform.");
|
|
14771
|
-
const root =
|
|
15266
|
+
const root = join28(d.store.dir(), "engines");
|
|
14772
15267
|
const backendDirRe = new RegExp(`[\\\\/]engines[\\\\/]llama\\.cpp-[^\\\\/]+-${def.id}[\\\\/]`);
|
|
14773
15268
|
const engs = d.registry.list().engines.filter((e) => backendDirRe.test(e.binPath));
|
|
14774
15269
|
const activeId = d.registry.active()?.id;
|
|
@@ -14790,7 +15285,7 @@ function registerApi(app2, d) {
|
|
|
14790
15285
|
const busy = engineWorkBusy(d);
|
|
14791
15286
|
if (busy) return err3(c, 409, "engine_already_running", busy);
|
|
14792
15287
|
}
|
|
14793
|
-
const root =
|
|
15288
|
+
const root = join28(d.store.dir(), "engines");
|
|
14794
15289
|
const upgrade = c.req.query("update") === "1";
|
|
14795
15290
|
void (async () => {
|
|
14796
15291
|
try {
|
|
@@ -14813,7 +15308,7 @@ function registerApi(app2, d) {
|
|
|
14813
15308
|
const busy = engineWorkBusy(d);
|
|
14814
15309
|
if (busy) return err3(c, 409, "engine_already_running", busy);
|
|
14815
15310
|
}
|
|
14816
|
-
const root =
|
|
15311
|
+
const root = join28(d.store.dir(), "engines");
|
|
14817
15312
|
const upgrade = c.req.query("update") === "1";
|
|
14818
15313
|
void (async () => {
|
|
14819
15314
|
try {
|
|
@@ -14830,12 +15325,12 @@ function registerApi(app2, d) {
|
|
|
14830
15325
|
});
|
|
14831
15326
|
app2.get("/api/v1/engines/catalog", (c) => {
|
|
14832
15327
|
const regEngines = d.registry.list().engines;
|
|
14833
|
-
const enginesRoot =
|
|
15328
|
+
const enginesRoot = join28(d.store.dir(), "engines");
|
|
14834
15329
|
const items = catalogForPlatform().map((e) => {
|
|
14835
15330
|
let installed2;
|
|
14836
15331
|
let enabled;
|
|
14837
15332
|
if (e.provision === "pip") {
|
|
14838
|
-
const pyPath =
|
|
15333
|
+
const pyPath = join28(
|
|
14839
15334
|
enginesRoot,
|
|
14840
15335
|
e.id,
|
|
14841
15336
|
"venv",
|
|
@@ -14844,7 +15339,7 @@ function registerApi(app2, d) {
|
|
|
14844
15339
|
installed2 = existsSync22(pyPath);
|
|
14845
15340
|
enabled = regEngines.some((x) => x.kind === e.kind);
|
|
14846
15341
|
} else if (e.id === "turboquant") {
|
|
14847
|
-
const tqDir =
|
|
15342
|
+
const tqDir = join28(enginesRoot, "turboquant");
|
|
14848
15343
|
installed2 = existsSync22(tqDir);
|
|
14849
15344
|
enabled = regEngines.some((x) => /[\\/]engines[\\/]turboquant[\\/]/.test(x.binPath));
|
|
14850
15345
|
} else if (e.id === "koboldcpp") {
|
|
@@ -14914,9 +15409,9 @@ function registerApi(app2, d) {
|
|
|
14914
15409
|
const busy = engineWorkBusy(d);
|
|
14915
15410
|
if (busy) return err3(c, 409, "engine_already_running", busy);
|
|
14916
15411
|
const name = (b.name ?? "").trim() || void 0;
|
|
14917
|
-
const enginesRoot =
|
|
15412
|
+
const enginesRoot = join28(d.store.dir(), "engines");
|
|
14918
15413
|
const toolchainDirs = d.store.snapshot().build.toolchainDirs;
|
|
14919
|
-
const buildRoot =
|
|
15414
|
+
const buildRoot = join28(enginesRoot, "build", buildDirName(repoUrl, branch, commit));
|
|
14920
15415
|
const ac = new AbortController();
|
|
14921
15416
|
buildAbort = ac;
|
|
14922
15417
|
d.build.start(name ?? repoUrl);
|
|
@@ -14931,7 +15426,7 @@ function registerApi(app2, d) {
|
|
|
14931
15426
|
);
|
|
14932
15427
|
} catch (e) {
|
|
14933
15428
|
try {
|
|
14934
|
-
|
|
15429
|
+
rmSync14(buildRoot, { recursive: true, force: true });
|
|
14935
15430
|
} catch {
|
|
14936
15431
|
}
|
|
14937
15432
|
if (e?.name === "AbortError") {
|
|
@@ -15026,7 +15521,7 @@ function registerApi(app2, d) {
|
|
|
15026
15521
|
const busy = engineWorkBusy(d);
|
|
15027
15522
|
if (busy) return err3(c, 409, "engine_already_running", busy);
|
|
15028
15523
|
}
|
|
15029
|
-
const root =
|
|
15524
|
+
const root = join28(d.store.dir(), "engines");
|
|
15030
15525
|
const upgrade = c.req.query("update") === "1";
|
|
15031
15526
|
void (async () => {
|
|
15032
15527
|
try {
|
|
@@ -15046,7 +15541,7 @@ function registerApi(app2, d) {
|
|
|
15046
15541
|
const busy = engineWorkBusy(d);
|
|
15047
15542
|
if (busy) return err3(c, 409, "engine_already_running", busy);
|
|
15048
15543
|
}
|
|
15049
|
-
const root =
|
|
15544
|
+
const root = join28(d.store.dir(), "engines");
|
|
15050
15545
|
const upgrade = c.req.query("update") === "1";
|
|
15051
15546
|
void (async () => {
|
|
15052
15547
|
try {
|
|
@@ -15071,14 +15566,14 @@ function registerApi(app2, d) {
|
|
|
15071
15566
|
const busy = engineWorkBusy(d);
|
|
15072
15567
|
if (busy) return err3(c, 409, "engine_already_running", busy);
|
|
15073
15568
|
}
|
|
15074
|
-
const root =
|
|
15569
|
+
const root = join28(d.store.dir(), "engines");
|
|
15075
15570
|
const upgrade = c.req.query("update") === "1";
|
|
15076
15571
|
void (async () => {
|
|
15077
15572
|
try {
|
|
15078
15573
|
d.provision.start("turboquant");
|
|
15079
15574
|
if (upgrade) {
|
|
15080
|
-
const tqDir =
|
|
15081
|
-
if (existsSync22(tqDir))
|
|
15575
|
+
const tqDir = join28(root, "turboquant");
|
|
15576
|
+
if (existsSync22(tqDir)) rmSync14(tqDir, { recursive: true, force: true });
|
|
15082
15577
|
}
|
|
15083
15578
|
const bin = await provisionTurboquant(
|
|
15084
15579
|
root,
|
|
@@ -15106,7 +15601,7 @@ function registerApi(app2, d) {
|
|
|
15106
15601
|
const busy = engineWorkBusy(d);
|
|
15107
15602
|
if (busy) return err3(c, 409, "engine_already_running", busy);
|
|
15108
15603
|
}
|
|
15109
|
-
const root =
|
|
15604
|
+
const root = join28(d.store.dir(), "engines");
|
|
15110
15605
|
const upgrade = c.req.query("update") === "1";
|
|
15111
15606
|
const hasNvidia = primaryVendor(getSysInfo()) === "nvidia";
|
|
15112
15607
|
void (async () => {
|
|
@@ -15114,7 +15609,7 @@ function registerApi(app2, d) {
|
|
|
15114
15609
|
d.provision.start("koboldcpp");
|
|
15115
15610
|
if (upgrade) {
|
|
15116
15611
|
const dir = koboldcppDir(root);
|
|
15117
|
-
if (existsSync22(dir))
|
|
15612
|
+
if (existsSync22(dir)) rmSync14(dir, { recursive: true, force: true });
|
|
15118
15613
|
}
|
|
15119
15614
|
const rt = await ensureKoboldcpp(root, hasNvidia, (p) => d.provision.progress(p.phase, p.pct, p.part, p.parts));
|
|
15120
15615
|
const eng = d.registry.addKoboldcpp(`KoboldCpp (${rt.version})`, rt.binPath, rt.version);
|
|
@@ -15137,14 +15632,14 @@ function registerApi(app2, d) {
|
|
|
15137
15632
|
const busy = engineWorkBusy(d);
|
|
15138
15633
|
if (busy) return err3(c, 409, "engine_already_running", busy);
|
|
15139
15634
|
}
|
|
15140
|
-
const root =
|
|
15635
|
+
const root = join28(d.store.dir(), "engines");
|
|
15141
15636
|
const upgrade = c.req.query("update") === "1";
|
|
15142
15637
|
void (async () => {
|
|
15143
15638
|
try {
|
|
15144
15639
|
d.provision.start("llamafile");
|
|
15145
15640
|
if (upgrade) {
|
|
15146
15641
|
const dir = llamafileDir(root);
|
|
15147
|
-
if (existsSync22(dir))
|
|
15642
|
+
if (existsSync22(dir)) rmSync14(dir, { recursive: true, force: true });
|
|
15148
15643
|
}
|
|
15149
15644
|
const rt = await ensureLlamafile(root, (p) => d.provision.progress(p.phase, p.pct, p.part, p.parts));
|
|
15150
15645
|
const eng = d.registry.addLlamafile(`llamafile (${rt.version})`, rt.binPath, rt.version);
|
|
@@ -15244,10 +15739,10 @@ function registerApi(app2, d) {
|
|
|
15244
15739
|
}
|
|
15245
15740
|
d.registry.remove(id);
|
|
15246
15741
|
if (purge && eng) {
|
|
15247
|
-
const enginesRoot =
|
|
15742
|
+
const enginesRoot = join28(d.store.dir(), "engines");
|
|
15248
15743
|
const purgeDir = engineInstallDir(eng, enginesRoot);
|
|
15249
15744
|
if (purgeDir && existsSync22(purgeDir)) {
|
|
15250
|
-
|
|
15745
|
+
rmSync14(purgeDir, { recursive: true, force: true });
|
|
15251
15746
|
}
|
|
15252
15747
|
d.registry.forgetCustomSource(customSourceKey(eng));
|
|
15253
15748
|
}
|
|
@@ -15329,16 +15824,16 @@ function registerApi(app2, d) {
|
|
|
15329
15824
|
}
|
|
15330
15825
|
let entries;
|
|
15331
15826
|
try {
|
|
15332
|
-
entries =
|
|
15827
|
+
entries = readdirSync10(real, { withFileTypes: true }).filter((d2) => !d2.name.startsWith(".")).map((d2) => {
|
|
15333
15828
|
let isDir = d2.isDirectory();
|
|
15334
15829
|
if (d2.isSymbolicLink()) {
|
|
15335
15830
|
try {
|
|
15336
|
-
isDir = statSync5(
|
|
15831
|
+
isDir = statSync5(join28(real, d2.name)).isDirectory();
|
|
15337
15832
|
} catch {
|
|
15338
15833
|
isDir = false;
|
|
15339
15834
|
}
|
|
15340
15835
|
}
|
|
15341
|
-
return { name: d2.name, path:
|
|
15836
|
+
return { name: d2.name, path: join28(real, d2.name), isDir };
|
|
15342
15837
|
}).sort((a, b) => a.isDir === b.isDir ? a.name.localeCompare(b.name) : a.isDir ? -1 : 1);
|
|
15343
15838
|
} catch {
|
|
15344
15839
|
return err3(c, 400, "fs_read_failed", "Could not read that folder (permission denied or not a directory).");
|
|
@@ -15481,17 +15976,17 @@ function registerApi(app2, d) {
|
|
|
15481
15976
|
}
|
|
15482
15977
|
let customNodes;
|
|
15483
15978
|
if (basename3(root).toLowerCase() === "custom_nodes") customNodes = root;
|
|
15484
|
-
else if (existsSync22(
|
|
15979
|
+
else if (existsSync22(join28(root, "custom_nodes"))) customNodes = join28(root, "custom_nodes");
|
|
15485
15980
|
else {
|
|
15486
15981
|
return err3(c, 400, "invalid_config_value", "No 'custom_nodes' folder here. Point me at your ComfyUI folder or its custom_nodes folder.");
|
|
15487
15982
|
}
|
|
15488
15983
|
const reqUrl = new URL(c.req.url);
|
|
15489
15984
|
const port2 = reqUrl.port || (reqUrl.protocol === "https:" ? "443" : "80");
|
|
15490
15985
|
const base2 = `http://127.0.0.1:${port2}`;
|
|
15491
|
-
const gateDir =
|
|
15986
|
+
const gateDir = join28(customNodes, "turbollm_gate");
|
|
15492
15987
|
try {
|
|
15493
|
-
|
|
15494
|
-
|
|
15988
|
+
mkdirSync16(gateDir, { recursive: true });
|
|
15989
|
+
writeFileSync14(join28(gateDir, "__init__.py"), gateNodeSource(base2));
|
|
15495
15990
|
} catch (e) {
|
|
15496
15991
|
return err3(c, 500, "fs_write_failed", `Could not write the gate node: ${e instanceof Error ? e.message : e}`);
|
|
15497
15992
|
}
|
|
@@ -15504,7 +15999,7 @@ function registerApi(app2, d) {
|
|
|
15504
15999
|
const dir = d.store.snapshot().comfyui.gatePath;
|
|
15505
16000
|
if (dir && existsSync22(dir)) {
|
|
15506
16001
|
try {
|
|
15507
|
-
|
|
16002
|
+
rmSync14(dir, { recursive: true, force: true });
|
|
15508
16003
|
} catch (e) {
|
|
15509
16004
|
return err3(c, 500, "fs_write_failed", `Could not remove the gate node: ${e instanceof Error ? e.message : e}`);
|
|
15510
16005
|
}
|
|
@@ -15538,7 +16033,7 @@ function registerApi(app2, d) {
|
|
|
15538
16033
|
while (!aborted) {
|
|
15539
16034
|
const path = d.manager.logPath();
|
|
15540
16035
|
if (path && existsSync22(path)) {
|
|
15541
|
-
const lines =
|
|
16036
|
+
const lines = readFileSync14(path, "utf8").split("\n");
|
|
15542
16037
|
for (; sent < lines.length - 1; sent++) {
|
|
15543
16038
|
await stream.writeSSE({ event: "line", data: JSON.stringify({ line: lines[sent].replace(/\r$/, "") }) });
|
|
15544
16039
|
}
|
|
@@ -15829,6 +16324,7 @@ function registerApi(app2, d) {
|
|
|
15829
16324
|
d.tools?.updateConfig(d.store.snapshot().tools);
|
|
15830
16325
|
await d.tools?.buildToolDefinitions();
|
|
15831
16326
|
}
|
|
16327
|
+
if (telemetryLevel !== void 0) void sendConsentChoice(d.store.dir(), telemetryLevel);
|
|
15832
16328
|
const after = d.store.snapshot().daemon;
|
|
15833
16329
|
const lanChanged = after.lanBind !== before.lanBind;
|
|
15834
16330
|
const portChanged = after.port !== before.port;
|
|
@@ -15847,9 +16343,9 @@ function registerApi(app2, d) {
|
|
|
15847
16343
|
if (transport === "stdio" && !b.command?.trim()) return err3(c, 400, "invalid_config_value", "command is required for stdio transport.");
|
|
15848
16344
|
if (transport === "sse" && !b.url?.trim()) return err3(c, 400, "invalid_config_value", "url is required for sse transport.");
|
|
15849
16345
|
if (transport === "sse" && !/^https?:\/\//i.test(b.url ?? "")) return err3(c, 400, "invalid_config_value", "url must be an http(s):// address.");
|
|
15850
|
-
const { randomUUID:
|
|
16346
|
+
const { randomUUID: randomUUID13 } = await import("crypto");
|
|
15851
16347
|
const server2 = {
|
|
15852
|
-
id:
|
|
16348
|
+
id: randomUUID13(),
|
|
15853
16349
|
name: b.name.trim(),
|
|
15854
16350
|
transport,
|
|
15855
16351
|
enabled: b.enabled !== false,
|
|
@@ -15857,7 +16353,7 @@ function registerApi(app2, d) {
|
|
|
15857
16353
|
};
|
|
15858
16354
|
const isMemoryServer = server2.transport === "stdio" && (server2.command?.includes("server-memory") || server2.args?.some((a) => a.includes("server-memory")));
|
|
15859
16355
|
if (isMemoryServer && !server2.env?.MEMORY_FILE_PATH) {
|
|
15860
|
-
server2.env = { ...server2.env, MEMORY_FILE_PATH:
|
|
16356
|
+
server2.env = { ...server2.env, MEMORY_FILE_PATH: join28(homedir4(), ".turbollm", "mcp-memory.jsonl") };
|
|
15861
16357
|
}
|
|
15862
16358
|
d.store.update((cfg2) => {
|
|
15863
16359
|
cfg2.mcp.servers.push(server2);
|
|
@@ -15911,6 +16407,15 @@ function registerApi(app2, d) {
|
|
|
15911
16407
|
const level = ["off", "anon", "full"].includes(raw) ? raw : "off";
|
|
15912
16408
|
return c.json(telemetryPreview(level, d.version));
|
|
15913
16409
|
});
|
|
16410
|
+
app2.get("/api/v1/telemetry/log", (c) => c.json({ entries: readSentLog(d.store.dir()) }));
|
|
16411
|
+
app2.post("/api/v1/telemetry/regenerate-id", (c) => {
|
|
16412
|
+
const machineId = randomUUID8();
|
|
16413
|
+
d.store.update((cfg2) => {
|
|
16414
|
+
cfg2.telemetry.machineId = machineId;
|
|
16415
|
+
});
|
|
16416
|
+
for (const q of readQueue(d.store.dir())) remove(d.store.dir(), q.file);
|
|
16417
|
+
return c.json({ machineId });
|
|
16418
|
+
});
|
|
15914
16419
|
app2.get("/api/v1/settings/network", (c) => {
|
|
15915
16420
|
const cfg2 = d.store.snapshot();
|
|
15916
16421
|
return c.json({
|
|
@@ -16062,7 +16567,7 @@ function registerApi(app2, d) {
|
|
|
16062
16567
|
if (!name) return err3(c, 400, "invalid_config_value", "name is required.");
|
|
16063
16568
|
const { full, hash, prefix } = generateApiKey();
|
|
16064
16569
|
const key = {
|
|
16065
|
-
id:
|
|
16570
|
+
id: randomUUID8(),
|
|
16066
16571
|
name,
|
|
16067
16572
|
hash,
|
|
16068
16573
|
prefix,
|
|
@@ -16098,7 +16603,7 @@ function registerApi(app2, d) {
|
|
|
16098
16603
|
const keyName = `cli-${cli}`;
|
|
16099
16604
|
const { full, hash, prefix } = generateApiKey();
|
|
16100
16605
|
const fresh = {
|
|
16101
|
-
id:
|
|
16606
|
+
id: randomUUID8(),
|
|
16102
16607
|
name: keyName,
|
|
16103
16608
|
hash,
|
|
16104
16609
|
prefix,
|
|
@@ -16162,6 +16667,13 @@ function settingsPayload(d) {
|
|
|
16162
16667
|
lanBind: cfg2.daemon.lanBind,
|
|
16163
16668
|
requireApiKey: cfg2.daemon.requireApiKey,
|
|
16164
16669
|
telemetryLevel,
|
|
16670
|
+
// Whether the user has ever actually answered (ADR-299 Decision 4).
|
|
16671
|
+
// `telemetryLevel` above deliberately collapses the first-run 'unset'
|
|
16672
|
+
// sentinel to 'off' so every consumer fails safe — but that also makes
|
|
16673
|
+
// "never asked" indistinguishable from "chose Off", and the first-run
|
|
16674
|
+
// consent card needs exactly that distinction. Hence a separate flag
|
|
16675
|
+
// rather than leaking 'unset' into the level enum.
|
|
16676
|
+
telemetryDecided: cfg2.telemetry.level !== "unset",
|
|
16165
16677
|
modelDefaults: cfg2.modelDefaults,
|
|
16166
16678
|
comfyui: cfg2.comfyui,
|
|
16167
16679
|
gateway: cfg2.gateway,
|
|
@@ -16199,7 +16711,12 @@ function settingsPayload(d) {
|
|
|
16199
16711
|
}
|
|
16200
16712
|
function telemetryPreview(level, version2) {
|
|
16201
16713
|
if (level === "off") {
|
|
16202
|
-
return {
|
|
16714
|
+
return {
|
|
16715
|
+
level,
|
|
16716
|
+
sends: true,
|
|
16717
|
+
note: "Telemetry is off. The only thing ever sent is this one-time record of your choice \u2014 no machine id, no hardware, no timestamp. Nothing else, ever.",
|
|
16718
|
+
payload: [{ schema: TELEMETRY_SCHEMA_VERSION, event: "consent_choice", level: "off" }]
|
|
16719
|
+
};
|
|
16203
16720
|
}
|
|
16204
16721
|
const sys = getSysInfo();
|
|
16205
16722
|
const hw = {
|
|
@@ -16295,19 +16812,19 @@ function engineInstallDir(eng, enginesRoot) {
|
|
|
16295
16812
|
return n.startsWith(norm + "/") || n.startsWith(norm + "\\");
|
|
16296
16813
|
};
|
|
16297
16814
|
if (eng.kind === "mlx") {
|
|
16298
|
-
const d =
|
|
16815
|
+
const d = join28(enginesRoot, "mlx", "venv");
|
|
16299
16816
|
return inside(d) ? d : null;
|
|
16300
16817
|
}
|
|
16301
16818
|
if (eng.kind === "rapid-mlx") {
|
|
16302
|
-
const d =
|
|
16819
|
+
const d = join28(enginesRoot, "rapid-mlx", "venv");
|
|
16303
16820
|
return inside(d) ? d : null;
|
|
16304
16821
|
}
|
|
16305
16822
|
if (eng.kind === "vllm") {
|
|
16306
|
-
const d =
|
|
16823
|
+
const d = join28(enginesRoot, "vllm", "venv");
|
|
16307
16824
|
return inside(d) ? d : null;
|
|
16308
16825
|
}
|
|
16309
16826
|
if (/[\\/]engines[\\/]turboquant[\\/]/.test(eng.binPath)) {
|
|
16310
|
-
const d =
|
|
16827
|
+
const d = join28(enginesRoot, "turboquant");
|
|
16311
16828
|
return inside(d) ? d : null;
|
|
16312
16829
|
}
|
|
16313
16830
|
if (eng.kind === "koboldcpp") {
|
|
@@ -16322,7 +16839,7 @@ function engineInstallDir(eng, enginesRoot) {
|
|
|
16322
16839
|
if (srcDir) return inside(srcDir) ? srcDir : null;
|
|
16323
16840
|
const llamaMatch = eng.binPath.replace(/\\/g, "/").match(/\/(llama\.cpp-[^/]+)\//i);
|
|
16324
16841
|
if (llamaMatch) {
|
|
16325
|
-
const d =
|
|
16842
|
+
const d = join28(enginesRoot, llamaMatch[1]);
|
|
16326
16843
|
return inside(d) ? d : null;
|
|
16327
16844
|
}
|
|
16328
16845
|
return null;
|
|
@@ -16340,7 +16857,7 @@ function cleanModelName(p) {
|
|
|
16340
16857
|
function readTail2(path, n) {
|
|
16341
16858
|
if (!path || !existsSync22(path)) return [];
|
|
16342
16859
|
try {
|
|
16343
|
-
const lines =
|
|
16860
|
+
const lines = readFileSync14(path, "utf8").replace(/[\r\n]+$/, "").split("\n").map((l) => l.replace(/\r$/, ""));
|
|
16344
16861
|
return lines.length > n ? lines.slice(-n) : lines;
|
|
16345
16862
|
} catch {
|
|
16346
16863
|
return [];
|
|
@@ -16530,7 +17047,7 @@ function buildConnectSnippets(cli, base2, apiKey, modelName, modelKey = modelNam
|
|
|
16530
17047
|
}
|
|
16531
17048
|
|
|
16532
17049
|
// src/chat/chat-agent-routes.ts
|
|
16533
|
-
import { randomUUID as
|
|
17050
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
16534
17051
|
function err4(c, status, code, message) {
|
|
16535
17052
|
return c.json({ error: { code, message } }, status);
|
|
16536
17053
|
}
|
|
@@ -16580,7 +17097,7 @@ function registerChatAgentRoutes(app2, d) {
|
|
|
16580
17097
|
return err4(c, 400, "too_many_agents", `Agent limit reached (${AGENT_CAP}).`);
|
|
16581
17098
|
}
|
|
16582
17099
|
const agent = {
|
|
16583
|
-
id:
|
|
17100
|
+
id: randomUUID9(),
|
|
16584
17101
|
name: b.name.trim(),
|
|
16585
17102
|
description: b.description?.trim() ?? "",
|
|
16586
17103
|
systemPrompt: b.systemPrompt ?? "",
|
|
@@ -16744,7 +17261,7 @@ import { streamSSE as streamSSE3 } from "hono/streaming";
|
|
|
16744
17261
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
16745
17262
|
|
|
16746
17263
|
// src/code/code-session.ts
|
|
16747
|
-
import { join as
|
|
17264
|
+
import { join as join31, resolve as resolve3 } from "path";
|
|
16748
17265
|
import { readFile as readFile2 } from "fs/promises";
|
|
16749
17266
|
import { Type, Unsafe } from "typebox";
|
|
16750
17267
|
import {
|
|
@@ -16973,7 +17490,7 @@ Cannot execute bash commands.`);
|
|
|
16973
17490
|
|
|
16974
17491
|
// src/code/containment.ts
|
|
16975
17492
|
import { realpathSync as realpathSync2 } from "fs";
|
|
16976
|
-
import { resolve as resolve2, dirname as dirname10, basename as basename4, join as
|
|
17493
|
+
import { resolve as resolve2, dirname as dirname10, basename as basename4, join as join29, sep } from "path";
|
|
16977
17494
|
var isWin = process.platform === "win32";
|
|
16978
17495
|
function canonicalize(input) {
|
|
16979
17496
|
if (typeof input !== "string" || input.length === 0) return null;
|
|
@@ -16989,7 +17506,7 @@ function canonicalize(input) {
|
|
|
16989
17506
|
for (let i = 0; i < 4096; i++) {
|
|
16990
17507
|
try {
|
|
16991
17508
|
const real = realpathSync2(cur);
|
|
16992
|
-
const full = tail.length ?
|
|
17509
|
+
const full = tail.length ? join29(real, ...[...tail].reverse()) : real;
|
|
16993
17510
|
return normForCompare(full);
|
|
16994
17511
|
} catch {
|
|
16995
17512
|
const parent = dirname10(cur);
|
|
@@ -17032,8 +17549,8 @@ function isContainedFromRoot(input, root) {
|
|
|
17032
17549
|
}
|
|
17033
17550
|
|
|
17034
17551
|
// src/code/persona.ts
|
|
17035
|
-
import { readFileSync as
|
|
17036
|
-
import { join as
|
|
17552
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
17553
|
+
import { join as join30 } from "path";
|
|
17037
17554
|
var MAX_SKILL_CATALOG_CHARS = 16e3;
|
|
17038
17555
|
var MAX_AGENTS_MD_CHARS = 2e4;
|
|
17039
17556
|
var MAX_AGENTS_MD_CANDIDATES = 8;
|
|
@@ -17151,7 +17668,7 @@ function skillCatalogBlock(skills) {
|
|
|
17151
17668
|
}
|
|
17152
17669
|
function readAgentsFile(path) {
|
|
17153
17670
|
try {
|
|
17154
|
-
const text =
|
|
17671
|
+
const text = readFileSync15(path, "utf8").trim();
|
|
17155
17672
|
if (!text) return null;
|
|
17156
17673
|
return text.length > MAX_AGENTS_MD_CHARS ? text.slice(0, MAX_AGENTS_MD_CHARS) : text;
|
|
17157
17674
|
} catch {
|
|
@@ -17161,7 +17678,7 @@ function readAgentsFile(path) {
|
|
|
17161
17678
|
function resolveAgentsFile(root, candidates) {
|
|
17162
17679
|
for (const entry of candidates.slice(0, MAX_AGENTS_MD_CANDIDATES)) {
|
|
17163
17680
|
if (!isContainedFromRoot(entry, root)) continue;
|
|
17164
|
-
const text = readAgentsFile(
|
|
17681
|
+
const text = readAgentsFile(join30(root, entry));
|
|
17165
17682
|
if (text !== null) return { name: entry, text };
|
|
17166
17683
|
}
|
|
17167
17684
|
return null;
|
|
@@ -17692,7 +18209,7 @@ async function runCodeSession(params) {
|
|
|
17692
18209
|
console.warn(`[code-session] history seed for session ${sessionId} produced ${sessionManager.getEntries().length}/${expectedEntries} expected entries \u2014 falling back to a plain-text history prefix on this turn's prompt`);
|
|
17693
18210
|
task = `${HISTORY_FALLBACK_PREFIX}${renderHistoryFallbackText(summaryText, priorMessages)}${HISTORY_FALLBACK_SUFFIX}${rawTask}`;
|
|
17694
18211
|
}
|
|
17695
|
-
const agentDir =
|
|
18212
|
+
const agentDir = join31(d.store.dir(), "pi-agent");
|
|
17696
18213
|
modelRegistry.registerProvider("local", {
|
|
17697
18214
|
baseUrl: `${target}/v1`,
|
|
17698
18215
|
apiKey: "agent-key",
|
|
@@ -17823,7 +18340,7 @@ async function runCodeSession(params) {
|
|
|
17823
18340
|
await sink({ event: "tool_call", data });
|
|
17824
18341
|
});
|
|
17825
18342
|
};
|
|
17826
|
-
const skillAgentDir =
|
|
18343
|
+
const skillAgentDir = join31(d.store.dir(), "pi-agent");
|
|
17827
18344
|
const skillResourceLoader = new DefaultResourceLoader({
|
|
17828
18345
|
cwd: repoRoot,
|
|
17829
18346
|
agentDir: skillAgentDir,
|
|
@@ -17966,7 +18483,7 @@ async function runCodeSession(params) {
|
|
|
17966
18483
|
await sink({ event: "tool_call", data });
|
|
17967
18484
|
});
|
|
17968
18485
|
};
|
|
17969
|
-
const subAgentDir =
|
|
18486
|
+
const subAgentDir = join31(d.store.dir(), "pi-agent");
|
|
17970
18487
|
const subResourceLoader = new DefaultResourceLoader({
|
|
17971
18488
|
cwd: repoRoot,
|
|
17972
18489
|
agentDir: subAgentDir,
|
|
@@ -18524,7 +19041,7 @@ async function compactCodeSession(params) {
|
|
|
18524
19041
|
return payload;
|
|
18525
19042
|
});
|
|
18526
19043
|
};
|
|
18527
|
-
const compactAgentDir =
|
|
19044
|
+
const compactAgentDir = join31(d.store.dir(), "pi-agent");
|
|
18528
19045
|
const compactResourceLoader = new DefaultResourceLoader({
|
|
18529
19046
|
cwd: repoRoot,
|
|
18530
19047
|
agentDir: compactAgentDir,
|
|
@@ -18626,7 +19143,7 @@ async function lookbackPreCompactionHistory(params) {
|
|
|
18626
19143
|
return payload;
|
|
18627
19144
|
});
|
|
18628
19145
|
};
|
|
18629
|
-
const lookbackAgentDir =
|
|
19146
|
+
const lookbackAgentDir = join31(d.store.dir(), "pi-agent");
|
|
18630
19147
|
const lookbackResourceLoader = new DefaultResourceLoader({
|
|
18631
19148
|
cwd: repoRoot,
|
|
18632
19149
|
agentDir: lookbackAgentDir,
|
|
@@ -19073,7 +19590,7 @@ function subscribeToBuffer(buffer, emitter, opts) {
|
|
|
19073
19590
|
}
|
|
19074
19591
|
|
|
19075
19592
|
// src/code/revert.ts
|
|
19076
|
-
import { existsSync as existsSync23, readFileSync as
|
|
19593
|
+
import { existsSync as existsSync23, readFileSync as readFileSync16, writeFileSync as writeFileSync15 } from "fs";
|
|
19077
19594
|
import { resolve as resolve4 } from "path";
|
|
19078
19595
|
import { applyPatch, parsePatch, reversePatch } from "diff";
|
|
19079
19596
|
function revertFileEdits(messages, repoRoot) {
|
|
@@ -19102,7 +19619,7 @@ function revertFileEdits(messages, repoRoot) {
|
|
|
19102
19619
|
}
|
|
19103
19620
|
let content;
|
|
19104
19621
|
try {
|
|
19105
|
-
content =
|
|
19622
|
+
content = readFileSync16(absPath, "utf8");
|
|
19106
19623
|
} catch {
|
|
19107
19624
|
failed.push(path);
|
|
19108
19625
|
continue;
|
|
@@ -19131,7 +19648,7 @@ function revertFileEdits(messages, repoRoot) {
|
|
|
19131
19648
|
continue;
|
|
19132
19649
|
}
|
|
19133
19650
|
try {
|
|
19134
|
-
|
|
19651
|
+
writeFileSync15(absPath, content);
|
|
19135
19652
|
reverted.push(path);
|
|
19136
19653
|
} catch {
|
|
19137
19654
|
failed.push(path);
|
|
@@ -19899,11 +20416,11 @@ function registerCodeRoutes(app2, d) {
|
|
|
19899
20416
|
}
|
|
19900
20417
|
|
|
19901
20418
|
// src/gateway/gateway.ts
|
|
19902
|
-
import { randomUUID as
|
|
20419
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
19903
20420
|
import { streamSSE as streamSSE4 } from "hono/streaming";
|
|
19904
20421
|
|
|
19905
20422
|
// src/gateway/anthropic.ts
|
|
19906
|
-
import { randomUUID as
|
|
20423
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
19907
20424
|
var UNSUPPORTED_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
|
|
19908
20425
|
"format",
|
|
19909
20426
|
"pattern",
|
|
@@ -20072,7 +20589,7 @@ function mapFromOpenAI(oai, modelName) {
|
|
|
20072
20589
|
}
|
|
20073
20590
|
const usage = oai.usage;
|
|
20074
20591
|
return {
|
|
20075
|
-
id: `msg_${
|
|
20592
|
+
id: `msg_${randomUUID10().replace(/-/g, "")}`,
|
|
20076
20593
|
type: "message",
|
|
20077
20594
|
role: "assistant",
|
|
20078
20595
|
model: modelName,
|
|
@@ -20368,7 +20885,7 @@ function registerGateway(app2, d) {
|
|
|
20368
20885
|
);
|
|
20369
20886
|
}
|
|
20370
20887
|
if (req.stream) {
|
|
20371
|
-
const msgId = `msg_${
|
|
20888
|
+
const msgId = `msg_${randomUUID11().replace(/-/g, "")}`;
|
|
20372
20889
|
const gen = streamToAnthropic(
|
|
20373
20890
|
res.body,
|
|
20374
20891
|
modelName,
|
|
@@ -20640,9 +21157,28 @@ async function recordOpenAiStreamUsage(d, body7, source, modelKey, codeSessionId
|
|
|
20640
21157
|
}
|
|
20641
21158
|
}
|
|
20642
21159
|
|
|
21160
|
+
// src/telemetry/feature-map.ts
|
|
21161
|
+
var BY_SEGMENT = {
|
|
21162
|
+
chat: "chat",
|
|
21163
|
+
code: "code",
|
|
21164
|
+
artifacts: "artifacts",
|
|
21165
|
+
mcp: "mcp",
|
|
21166
|
+
bench: "autotune",
|
|
21167
|
+
// Covers install/uninstall (explicit user setup) AND acquire/release (pushed
|
|
21168
|
+
// by ComfyUI's OWN gate node when an actual generation runs, not polled by
|
|
21169
|
+
// our UI) — unlike skills/chat-agents, every hit here is real image-gen
|
|
21170
|
+
// activity, not a side effect of an unrelated screen loading.
|
|
21171
|
+
comfyui: "image"
|
|
21172
|
+
};
|
|
21173
|
+
function featureForPath(path) {
|
|
21174
|
+
const m = /^\/api\/v1\/([^/?]+)/.exec(path);
|
|
21175
|
+
if (m === null) return null;
|
|
21176
|
+
return BY_SEGMENT[m[1]] ?? null;
|
|
21177
|
+
}
|
|
21178
|
+
|
|
20643
21179
|
// src/server.ts
|
|
20644
21180
|
setGlobalDispatcher(new Agent({ keepAliveMaxTimeout: 6e4, connections: 10 }));
|
|
20645
|
-
var WEB_ROOT =
|
|
21181
|
+
var WEB_ROOT = join32(dirname11(fileURLToPath(import.meta.url)), "webdist");
|
|
20646
21182
|
function createApp(d) {
|
|
20647
21183
|
const app2 = new Hono();
|
|
20648
21184
|
app2.use(
|
|
@@ -20667,6 +21203,11 @@ function createApp(d) {
|
|
|
20667
21203
|
});
|
|
20668
21204
|
app2.use("*", lanAuth(d));
|
|
20669
21205
|
app2.use("/api/v1/code/*", codeAuth(d));
|
|
21206
|
+
app2.use("*", async (c, next) => {
|
|
21207
|
+
const feature = d.telemetry ? featureForPath(c.req.path) : null;
|
|
21208
|
+
if (feature !== null) d.telemetry?.firstUse(feature);
|
|
21209
|
+
await next();
|
|
21210
|
+
});
|
|
20670
21211
|
app2.get("/healthz", (c) => c.json({ status: "ok", version: d.version }));
|
|
20671
21212
|
registerApi(app2, d);
|
|
20672
21213
|
registerChatRoutes(app2, d);
|
|
@@ -20680,12 +21221,12 @@ function createApp(d) {
|
|
|
20680
21221
|
if (path.startsWith("api/") || path.startsWith("v1/")) {
|
|
20681
21222
|
return c.json({ error: { code: "not_found", message: "Unknown endpoint." } }, 404);
|
|
20682
21223
|
}
|
|
20683
|
-
let file = normalize2(
|
|
21224
|
+
let file = normalize2(join32(WEB_ROOT, path || "index.html"));
|
|
20684
21225
|
if (!file.startsWith(WEB_ROOT) || !existsSync24(file) || statSync6(file).isDirectory()) {
|
|
20685
|
-
file =
|
|
21226
|
+
file = join32(WEB_ROOT, "index.html");
|
|
20686
21227
|
}
|
|
20687
21228
|
if (!existsSync24(file)) return c.text("web ui not built \u2014 run `npm run build:web`", 500);
|
|
20688
|
-
return new Response(
|
|
21229
|
+
return new Response(readFileSync17(file), { status: 200, headers: { "Content-Type": contentType(file) } });
|
|
20689
21230
|
});
|
|
20690
21231
|
return app2;
|
|
20691
21232
|
}
|
|
@@ -20711,21 +21252,21 @@ function contentType(file) {
|
|
|
20711
21252
|
|
|
20712
21253
|
// src/tunnel/manager.ts
|
|
20713
21254
|
import { execFile as execFile13, spawn as spawn7, spawnSync as spawnSync3 } from "child_process";
|
|
20714
|
-
import { mkdirSync as
|
|
20715
|
-
import { join as
|
|
21255
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync18, readdirSync as readdirSync11, rmSync as rmSync16, writeFileSync as writeFileSync16 } from "fs";
|
|
21256
|
+
import { join as join34 } from "path";
|
|
20716
21257
|
|
|
20717
21258
|
// src/tunnel/provision.ts
|
|
20718
|
-
import { chmodSync as chmodSync3, existsSync as existsSync25, mkdirSync as
|
|
20719
|
-
import { join as
|
|
21259
|
+
import { chmodSync as chmodSync3, existsSync as existsSync25, mkdirSync as mkdirSync17, rmSync as rmSync15 } from "fs";
|
|
21260
|
+
import { join as join33 } from "path";
|
|
20720
21261
|
var CLOUDFLARED_REPO = "cloudflare/cloudflared";
|
|
20721
21262
|
function cloudflaredDir(dataDir) {
|
|
20722
|
-
return
|
|
21263
|
+
return join33(dataDir, "cloudflared");
|
|
20723
21264
|
}
|
|
20724
21265
|
function cloudflaredBinName(platform = process.platform) {
|
|
20725
21266
|
return platform === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
20726
21267
|
}
|
|
20727
21268
|
function cloudflaredBinPath(dataDir, platform = process.platform) {
|
|
20728
|
-
return
|
|
21269
|
+
return join33(cloudflaredDir(dataDir), cloudflaredBinName(platform));
|
|
20729
21270
|
}
|
|
20730
21271
|
function cloudflaredAssetName(platform = process.platform, archStr = process.arch) {
|
|
20731
21272
|
if (platform === "win32") {
|
|
@@ -20754,18 +21295,18 @@ async function ensureCloudflared(dataDir, onProgress, signal) {
|
|
|
20754
21295
|
if (existsSync25(binPath)) return { binPath, version: version2 };
|
|
20755
21296
|
const asset = pickCloudflaredAsset(rel.assets ?? []);
|
|
20756
21297
|
if (!asset) throw new Error("no_release_asset");
|
|
20757
|
-
|
|
21298
|
+
mkdirSync17(dir, { recursive: true });
|
|
20758
21299
|
onProgress?.({ phase: "downloading", pct: 0 });
|
|
20759
21300
|
if (asset.name.endsWith(".tgz")) {
|
|
20760
|
-
const tmp =
|
|
21301
|
+
const tmp = join33(dir, asset.name);
|
|
20761
21302
|
try {
|
|
20762
21303
|
await downloadFile(asset.browser_download_url, tmp, onProgress, signal);
|
|
20763
21304
|
onProgress?.({ phase: "extracting", pct: -1 });
|
|
20764
21305
|
await extractArchive(tmp, dir);
|
|
20765
|
-
|
|
21306
|
+
rmSync15(tmp, { force: true });
|
|
20766
21307
|
} catch (e) {
|
|
20767
|
-
|
|
20768
|
-
|
|
21308
|
+
rmSync15(tmp, { force: true });
|
|
21309
|
+
rmSync15(dir, { recursive: true, force: true });
|
|
20769
21310
|
throw e;
|
|
20770
21311
|
}
|
|
20771
21312
|
const found = findFile(dir, "cloudflared");
|
|
@@ -20801,21 +21342,21 @@ function forceKill2(child) {
|
|
|
20801
21342
|
else child.kill("SIGKILL");
|
|
20802
21343
|
}
|
|
20803
21344
|
function tunnelPidDir(dataDir) {
|
|
20804
|
-
return
|
|
21345
|
+
return join34(dataDir, "run");
|
|
20805
21346
|
}
|
|
20806
21347
|
function tunnelPidFile(dataDir, pid) {
|
|
20807
|
-
return
|
|
21348
|
+
return join34(tunnelPidDir(dataDir), `tunnel-${pid}.pid`);
|
|
20808
21349
|
}
|
|
20809
21350
|
function writeTunnelPid(dataDir, pid) {
|
|
20810
21351
|
try {
|
|
20811
|
-
|
|
20812
|
-
|
|
21352
|
+
mkdirSync18(tunnelPidDir(dataDir), { recursive: true });
|
|
21353
|
+
writeFileSync16(tunnelPidFile(dataDir, pid), JSON.stringify({ pid, owner: process.pid }));
|
|
20813
21354
|
} catch {
|
|
20814
21355
|
}
|
|
20815
21356
|
}
|
|
20816
21357
|
function clearTunnelPid(dataDir, pid) {
|
|
20817
21358
|
try {
|
|
20818
|
-
|
|
21359
|
+
rmSync16(tunnelPidFile(dataDir, pid), { force: true });
|
|
20819
21360
|
} catch {
|
|
20820
21361
|
}
|
|
20821
21362
|
}
|
|
@@ -20832,20 +21373,20 @@ function readTunnelPidFiles(dataDir) {
|
|
|
20832
21373
|
const dir = tunnelPidDir(dataDir);
|
|
20833
21374
|
let names;
|
|
20834
21375
|
try {
|
|
20835
|
-
names =
|
|
21376
|
+
names = readdirSync11(dir).filter((n) => /^tunnel-\d+\.pid$/.test(n));
|
|
20836
21377
|
} catch {
|
|
20837
21378
|
return [];
|
|
20838
21379
|
}
|
|
20839
21380
|
const out = [];
|
|
20840
21381
|
for (const name of names) {
|
|
20841
|
-
const file =
|
|
21382
|
+
const file = join34(dir, name);
|
|
20842
21383
|
try {
|
|
20843
|
-
const { pid, owner } = JSON.parse(
|
|
21384
|
+
const { pid, owner } = JSON.parse(readFileSync18(file, "utf8"));
|
|
20844
21385
|
if (typeof pid === "number" && pid > 0) out.push({ pid, owner: typeof owner === "number" ? owner : 0, file });
|
|
20845
|
-
else
|
|
21386
|
+
else rmSync16(file, { force: true });
|
|
20846
21387
|
} catch {
|
|
20847
21388
|
try {
|
|
20848
|
-
|
|
21389
|
+
rmSync16(file, { force: true });
|
|
20849
21390
|
} catch {
|
|
20850
21391
|
}
|
|
20851
21392
|
}
|
|
@@ -20863,7 +21404,7 @@ function reapStaleTunnels(dataDir) {
|
|
|
20863
21404
|
} catch {
|
|
20864
21405
|
}
|
|
20865
21406
|
try {
|
|
20866
|
-
|
|
21407
|
+
rmSync16(file, { force: true });
|
|
20867
21408
|
} catch {
|
|
20868
21409
|
}
|
|
20869
21410
|
}
|
|
@@ -20878,7 +21419,7 @@ function killTrackedTunnelsSync(dataDir) {
|
|
|
20878
21419
|
} catch {
|
|
20879
21420
|
}
|
|
20880
21421
|
try {
|
|
20881
|
-
|
|
21422
|
+
rmSync16(file, { force: true });
|
|
20882
21423
|
} catch {
|
|
20883
21424
|
}
|
|
20884
21425
|
}
|
|
@@ -20967,12 +21508,94 @@ var TunnelManager = class {
|
|
|
20967
21508
|
}
|
|
20968
21509
|
};
|
|
20969
21510
|
|
|
21511
|
+
// src/telemetry/emit.ts
|
|
21512
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
21513
|
+
var REQUIRES_FULL = /* @__PURE__ */ new Set(["error"]);
|
|
21514
|
+
var Emitter = class {
|
|
21515
|
+
o;
|
|
21516
|
+
today;
|
|
21517
|
+
constructor(opts) {
|
|
21518
|
+
this.o = opts;
|
|
21519
|
+
this.today = opts.today ?? (() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
21520
|
+
}
|
|
21521
|
+
/**
|
|
21522
|
+
* Whether this event may be sent right now — kill switch, consent level, and
|
|
21523
|
+
* the per-event level requirement.
|
|
21524
|
+
*
|
|
21525
|
+
* Public because the once-only helpers MUST consult it before spending a
|
|
21526
|
+
* ledger claim. Claiming first would mean a user who browses the app before
|
|
21527
|
+
* answering the consent card burns every `first_use` key while telemetry is
|
|
21528
|
+
* off, and opting in afterwards would permanently lose the whole
|
|
21529
|
+
* feature-discovery picture — the exact data this system exists to collect.
|
|
21530
|
+
*/
|
|
21531
|
+
canSend(event) {
|
|
21532
|
+
if (telemetryDisabled()) return false;
|
|
21533
|
+
const level = this.o.store.snapshot().telemetry.level;
|
|
21534
|
+
if (level !== "anon" && level !== "full") return false;
|
|
21535
|
+
if (REQUIRES_FULL.has(event) && level !== "full") return false;
|
|
21536
|
+
return true;
|
|
21537
|
+
}
|
|
21538
|
+
/** Emit one event. Silently does nothing whenever it must not send. */
|
|
21539
|
+
emit(event, payload) {
|
|
21540
|
+
try {
|
|
21541
|
+
if (!this.canSend(event)) return;
|
|
21542
|
+
enqueue(this.o.dataDir, {
|
|
21543
|
+
schema: TELEMETRY_SCHEMA_VERSION,
|
|
21544
|
+
event,
|
|
21545
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21546
|
+
machineId: this.machineId(),
|
|
21547
|
+
app: { version: this.o.version, os: this.o.os },
|
|
21548
|
+
...payload === void 0 ? {} : { payload }
|
|
21549
|
+
});
|
|
21550
|
+
} catch {
|
|
21551
|
+
}
|
|
21552
|
+
}
|
|
21553
|
+
/** Emit an event at most once for the lifetime of this install (e.g.
|
|
21554
|
+
* `app_first_run`, which must count installs — not daemon starts). */
|
|
21555
|
+
once(event) {
|
|
21556
|
+
if (!this.canSend(event)) return;
|
|
21557
|
+
if (this.claim(`once:${event}`)) this.emit(event);
|
|
21558
|
+
}
|
|
21559
|
+
/** Emit `feature_first_use` the first time a feature is touched, ever. */
|
|
21560
|
+
firstUse(feature) {
|
|
21561
|
+
if (!this.canSend("feature_first_use")) return;
|
|
21562
|
+
if (this.claim(`first_use:${feature}`)) this.emit("feature_first_use", { feature });
|
|
21563
|
+
}
|
|
21564
|
+
/** Emit `daily_active` at most once per calendar day. */
|
|
21565
|
+
dailyActive() {
|
|
21566
|
+
if (!this.canSend("daily_active")) return;
|
|
21567
|
+
if (this.claim(`daily:${this.today()}`)) this.emit("daily_active");
|
|
21568
|
+
}
|
|
21569
|
+
/**
|
|
21570
|
+
* Mint the anonymous machine id lazily.
|
|
21571
|
+
*
|
|
21572
|
+
* Never called while consent is off, because `emit` returns before this
|
|
21573
|
+
* point — an install that never opts in must not even have an id generated
|
|
21574
|
+
* for it.
|
|
21575
|
+
*/
|
|
21576
|
+
machineId() {
|
|
21577
|
+
const existing = this.o.store.snapshot().telemetry.machineId;
|
|
21578
|
+
if (existing) return existing;
|
|
21579
|
+
const id = randomUUID12();
|
|
21580
|
+
this.o.store.update((c) => {
|
|
21581
|
+
if (!c.telemetry.machineId) c.telemetry.machineId = id;
|
|
21582
|
+
});
|
|
21583
|
+
return id;
|
|
21584
|
+
}
|
|
21585
|
+
/** Record a once-only key. Returns whether THIS call claimed it (i.e. whether
|
|
21586
|
+
* the caller should go on to emit). Shared with the consent ping so both
|
|
21587
|
+
* agree on what "once" means. */
|
|
21588
|
+
claim(key) {
|
|
21589
|
+
return claimOnce(this.o.dataDir, key);
|
|
21590
|
+
}
|
|
21591
|
+
};
|
|
21592
|
+
|
|
20970
21593
|
// src/cli.ts
|
|
20971
21594
|
hideChildConsoleWindows();
|
|
20972
21595
|
var version = "0.1.1";
|
|
20973
21596
|
try {
|
|
20974
|
-
const pkgPath =
|
|
20975
|
-
version = JSON.parse(
|
|
21597
|
+
const pkgPath = join35(dirname12(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
21598
|
+
version = JSON.parse(readFileSync19(pkgPath, "utf8")).version ?? version;
|
|
20976
21599
|
} catch {
|
|
20977
21600
|
}
|
|
20978
21601
|
var [nodeMajor, nodeMinor] = process.versions.node.split(".").map(Number);
|
|
@@ -20997,9 +21620,10 @@ function argValue(name, fallback) {
|
|
|
20997
21620
|
const i = process.argv.indexOf(name);
|
|
20998
21621
|
return i >= 0 && i + 1 < process.argv.length ? process.argv[i + 1] : fallback;
|
|
20999
21622
|
}
|
|
21623
|
+
if (hasFlag("--no-telemetry")) process.env[TELEMETRY_ENV] = "off";
|
|
21000
21624
|
function configuredDaemonPort(configPath) {
|
|
21001
21625
|
try {
|
|
21002
|
-
const cfg2 = JSON.parse(
|
|
21626
|
+
const cfg2 = JSON.parse(readFileSync19(configPath, "utf8"));
|
|
21003
21627
|
if (typeof cfg2.daemon?.port === "number" && cfg2.daemon.port > 0) return cfg2.daemon.port;
|
|
21004
21628
|
} catch {
|
|
21005
21629
|
}
|
|
@@ -21073,6 +21697,9 @@ Options:
|
|
|
21073
21697
|
tunnel (Cloud Launch) \u2014 prints the public URL + a required
|
|
21074
21698
|
access token. For running TurboLLM on a rented cloud GPU box.
|
|
21075
21699
|
--config <f> Path to a custom config file
|
|
21700
|
+
--no-telemetry Disable telemetry entirely, whatever this install's saved
|
|
21701
|
+
setting says (same as TURBOLLM_TELEMETRY=off). For CI,
|
|
21702
|
+
containers, and scripted daemons.
|
|
21076
21703
|
--stop Stop a running TurboLLM daemon and exit
|
|
21077
21704
|
--help, -h Show this help message
|
|
21078
21705
|
|
|
@@ -21119,7 +21746,7 @@ if (pruned > 0) console.log(`pruned ${pruned} dangling engine build(s)`);
|
|
|
21119
21746
|
var provision = new ProvisionState();
|
|
21120
21747
|
var build = new BuildState();
|
|
21121
21748
|
var updates = new UpdateChecker();
|
|
21122
|
-
var enginesDir =
|
|
21749
|
+
var enginesDir = join35(store.dir(), "engines");
|
|
21123
21750
|
void seedDefaultEngines(registry, enginesDir, provision).then(() => registry.ensureProbed());
|
|
21124
21751
|
var manager = new Manager(store);
|
|
21125
21752
|
var scanner = new Scanner(store);
|
|
@@ -21148,6 +21775,19 @@ var appUpdates = new AppUpdateChecker(version);
|
|
|
21148
21775
|
var deps = { store, registry, manager, scanner, hashes, db, provision, build, updates, appUpdates, hf, downloads, bench, modelRouter, comfy, tools: toolRegistry, version, startedAt };
|
|
21149
21776
|
deps.gate = new GenerationGate();
|
|
21150
21777
|
deps.agentTasks = new AgentTaskState();
|
|
21778
|
+
var telemetry = new Emitter({ dataDir: store.dir(), store, version, os: getSysInfo().os });
|
|
21779
|
+
deps.telemetry = telemetry;
|
|
21780
|
+
telemetry.dailyActive();
|
|
21781
|
+
manager.onLoadSettled = (ok, err7) => reportModelLoad(store.dir(), telemetry, ok ? "ok" : "fail", ok ? void 0 : classifyLoadFailure(err7));
|
|
21782
|
+
bench.telemetry = telemetry;
|
|
21783
|
+
build.onSettled = (ok) => telemetry.emit("onboarding_step", { step: "engine_build", outcome: ok ? "ok" : "fail" });
|
|
21784
|
+
provision.onSettled = (ok) => telemetry.emit("onboarding_step", { step: "engine_install", outcome: ok ? "ok" : "fail" });
|
|
21785
|
+
downloads.onSettled = (outcome) => telemetry.emit("onboarding_step", { step: "model_download", outcome });
|
|
21786
|
+
setTimeout(() => {
|
|
21787
|
+
telemetry.once("app_first_run");
|
|
21788
|
+
void flush(store.dir(), store.snapshot().telemetry.level);
|
|
21789
|
+
}, 3e3).unref();
|
|
21790
|
+
setInterval(() => void flush(store.dir(), store.snapshot().telemetry.level), 5 * 6e4).unref();
|
|
21151
21791
|
var tunnelRequested = hasFlag("--tunnel");
|
|
21152
21792
|
if (tunnelRequested) deps.tunnel = new TunnelManager(store.dir());
|
|
21153
21793
|
var app = createApp(deps);
|
|
@@ -21293,7 +21933,7 @@ var restarting = false;
|
|
|
21293
21933
|
function spawnReplacement() {
|
|
21294
21934
|
let out = "ignore";
|
|
21295
21935
|
try {
|
|
21296
|
-
out = openSync3(
|
|
21936
|
+
out = openSync3(join35(store.dir(), "restart.log"), "a");
|
|
21297
21937
|
} catch {
|
|
21298
21938
|
out = "ignore";
|
|
21299
21939
|
}
|