turbollm 1.10.0 → 1.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/cli.js +319 -62
  2. package/dist/webdist/assets/{AgentEditPage-qGzBfP6d.js → AgentEditPage-CFxWKFKa.js} +1 -1
  3. package/dist/webdist/assets/{ChatScreen-D0G5dkfp.js → ChatScreen-CTooMkHT.js} +1 -1
  4. package/dist/webdist/assets/{CodeComposer-Da87vS4k.js → CodeComposer-COwhPOhR.js} +1 -1
  5. package/dist/webdist/assets/{CodeHomeScreen-DU5GGdGi.js → CodeHomeScreen-Cj1xAoUo.js} +1 -1
  6. package/dist/webdist/assets/{CodeSessionScreen-Cgj25Rkp.js → CodeSessionScreen-V9-7hQju.js} +1 -1
  7. package/dist/webdist/assets/{CustomizeScreen-Dm6mG0zr.js → CustomizeScreen-CJvRx-tG.js} +1 -1
  8. package/dist/webdist/assets/{DeveloperScreen-BVe8HEzP.js → DeveloperScreen-CoPotQpU.js} +1 -1
  9. package/dist/webdist/assets/{EnginesScreen-ChX3oICU.js → EnginesScreen-BUv0pJQW.js} +2 -2
  10. package/dist/webdist/assets/ModelDetailDialog-lYcC9WmN.js +1 -0
  11. package/dist/webdist/assets/{ModelLoadMenu-Dhnm8dEz.js → ModelLoadMenu-D7QPq4Eq.js} +1 -1
  12. package/dist/webdist/assets/{ModelsScreen-CqFCDDL6.js → ModelsScreen-CzY28vxf.js} +1 -1
  13. package/dist/webdist/assets/{RoutineEditPage-BL932a0r.js → RoutineEditPage-CCNKZWJO.js} +1 -1
  14. package/dist/webdist/assets/{RoutinesPanel-De8YnBNe.js → RoutinesPanel-DIAdnFgF.js} +1 -1
  15. package/dist/webdist/assets/{SettingsScreen-UpQEQLrO.js → SettingsScreen-DSO9et1h.js} +1 -1
  16. package/dist/webdist/assets/{SidebarResizeHandle-CwKbNZ1T.js → SidebarResizeHandle-CTKybDtZ.js} +1 -1
  17. package/dist/webdist/assets/{SkillEditPage-BvSU02Jt.js → SkillEditPage-sYIbxMua.js} +1 -1
  18. package/dist/webdist/assets/{TokensScreen-DWYd_FJP.js → TokensScreen-U53Af6Qm.js} +1 -1
  19. package/dist/webdist/assets/{ToolApprovalBar-C2yMawx1.js → ToolApprovalBar-DouGFUhG.js} +3 -3
  20. package/dist/webdist/assets/WorkspaceScreen-XVHKj6V7.js +1 -0
  21. package/dist/webdist/assets/{agent-api-BDGYxu1V.js → agent-api-CbZW6U0W.js} +1 -1
  22. package/dist/webdist/assets/{alert-dialog-Cvh1bWmi.js → alert-dialog-Bmx12MjB.js} +1 -1
  23. package/dist/webdist/assets/{arc-DJUqJnyS.js → arc-Dd3jjp1g.js} +1 -1
  24. package/dist/webdist/assets/{architectureDiagram-3BPJPVTR-CSoVRmcb.js → architectureDiagram-3BPJPVTR-BR5uEm28.js} +1 -1
  25. package/dist/webdist/assets/{arrow-left-DnuoPO3d.js → arrow-left-j1bXiZDl.js} +1 -1
  26. package/dist/webdist/assets/{badge-DBnDZ8IB.js → badge-DJwa0mpu.js} +1 -1
  27. package/dist/webdist/assets/{blockDiagram-GPEHLZMM-B2tUVQkN.js → blockDiagram-GPEHLZMM-s-U3l46D.js} +1 -1
  28. package/dist/webdist/assets/{bot-BqK-Cvvj.js → bot-CwWPnHhZ.js} +1 -1
  29. package/dist/webdist/assets/{c4Diagram-AAUBKEIU-CK_SZ0No.js → c4Diagram-AAUBKEIU-nAf8RB3W.js} +1 -1
  30. package/dist/webdist/assets/channel-4e9Zw4DS.js +1 -0
  31. package/dist/webdist/assets/{chat-api-BWC8yB8f.js → chat-api-Vp38MmPT.js} +1 -1
  32. package/dist/webdist/assets/{chat-queries-Coy7H-R4.js → chat-queries-CYVi1fKX.js} +1 -1
  33. package/dist/webdist/assets/{chevron-left-C-tiOyyN.js → chevron-left-C7VOgCa-.js} +1 -1
  34. package/dist/webdist/assets/{chevron-right-DTJBClgw.js → chevron-right-Cvc6jjhp.js} +1 -1
  35. package/dist/webdist/assets/{chunk-2J33WTMH-bEj-T-z0.js → chunk-2J33WTMH-BLfjL7Zw.js} +1 -1
  36. package/dist/webdist/assets/{chunk-4BX2VUAB-Cif7ru1F.js → chunk-4BX2VUAB-Dlin1rha.js} +1 -1
  37. package/dist/webdist/assets/{chunk-55IACEB6-CDzzG-CE.js → chunk-55IACEB6-DCLFQcNB.js} +1 -1
  38. package/dist/webdist/assets/{chunk-727SXJPM-D0TNxHzv.js → chunk-727SXJPM-Dx3Y7Esd.js} +1 -1
  39. package/dist/webdist/assets/{chunk-AQP2D5EJ-Cyj0A4j9.js → chunk-AQP2D5EJ-BE9fOlAZ.js} +1 -1
  40. package/dist/webdist/assets/{chunk-FMBD7UC4-D6xVpEis.js → chunk-FMBD7UC4-aQyGnVKT.js} +1 -1
  41. package/dist/webdist/assets/{chunk-ND2GUHAM-DfrJt1zq.js → chunk-ND2GUHAM-DChmLkpD.js} +1 -1
  42. package/dist/webdist/assets/{chunk-QZHKN3VN-wRcKgWAJ.js → chunk-QZHKN3VN-Cg8HsKLL.js} +1 -1
  43. package/dist/webdist/assets/{circle-check-DyRGib7M.js → circle-check-ByAt4HHi.js} +1 -1
  44. package/dist/webdist/assets/{circle-x-B5hYGJpb.js → circle-x-BrZO5ZPK.js} +1 -1
  45. package/dist/webdist/assets/classDiagram-4FO5ZUOK-BpPs---J.js +1 -0
  46. package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-BpPs---J.js +1 -0
  47. package/dist/webdist/assets/{common-Zsod6hYN.js → common-DfiDFdG6.js} +1 -1
  48. package/dist/webdist/assets/{cose-bilkent-S5V4N54A-CVCCOgV-.js → cose-bilkent-S5V4N54A-BaX2n7he.js} +1 -1
  49. package/dist/webdist/assets/{dagre-BM42HDAG-BAkC4UTn.js → dagre-BM42HDAG-L-zSD6Id.js} +1 -1
  50. package/dist/webdist/assets/{diagram-2AECGRRQ-DL9rbgkc.js → diagram-2AECGRRQ-DubS0XE-.js} +1 -1
  51. package/dist/webdist/assets/{diagram-5GNKFQAL-DGC1tJsz.js → diagram-5GNKFQAL-DcUkYGcO.js} +1 -1
  52. package/dist/webdist/assets/{diagram-KO2AKTUF-COJcOzCk.js → diagram-KO2AKTUF-Bs5XJw3q.js} +1 -1
  53. package/dist/webdist/assets/{diagram-LMA3HP47-D5LDvdeL.js → diagram-LMA3HP47-CynTZtzf.js} +1 -1
  54. package/dist/webdist/assets/{diagram-OG6HWLK6-rZkdD7HA.js → diagram-OG6HWLK6-Ces3__8l.js} +1 -1
  55. package/dist/webdist/assets/{dropdown-menu-DD_r8t6u.js → dropdown-menu-CikrwibY.js} +1 -1
  56. package/dist/webdist/assets/{erDiagram-TEJ5UH35-DcFmBQnY.js → erDiagram-TEJ5UH35-B4pp5Bmb.js} +1 -1
  57. package/dist/webdist/assets/{external-link-BYGp_q8v.js → external-link-B6bNonST.js} +1 -1
  58. package/dist/webdist/assets/{file-text-Ednnzze6.js → file-text-tVVaHTZE.js} +1 -1
  59. package/dist/webdist/assets/{flowDiagram-I6XJVG4X-bjdPqmij.js → flowDiagram-I6XJVG4X-DYw6zosJ.js} +1 -1
  60. package/dist/webdist/assets/{folder-plus-VQ2YW037.js → folder-plus-CahK1jG-.js} +1 -1
  61. package/dist/webdist/assets/{ganttDiagram-6RSMTGT7-cioHCnwQ.js → ganttDiagram-6RSMTGT7-BXp-7Nw6.js} +1 -1
  62. package/dist/webdist/assets/{git-branch-BQAi48as.js → git-branch-D-ZVoool.js} +1 -1
  63. package/dist/webdist/assets/{gitGraphDiagram-PVQCEYII-CLHLJora.js → gitGraphDiagram-PVQCEYII-DzcmQ_Rc.js} +1 -1
  64. package/dist/webdist/assets/{index-Dh7kXOE1.js → index-x5TIbXu2.js} +2 -2
  65. package/dist/webdist/assets/{infoDiagram-5YYISTIA-DMq4ds-X.js → infoDiagram-5YYISTIA-BYGyZ8S1.js} +1 -1
  66. package/dist/webdist/assets/{ishikawaDiagram-YF4QCWOH-DlqgfsZZ.js → ishikawaDiagram-YF4QCWOH-CJBmxkmw.js} +1 -1
  67. package/dist/webdist/assets/{journeyDiagram-JHISSGLW-f4hD-rvS.js → journeyDiagram-JHISSGLW-CXi8N9Vd.js} +1 -1
  68. package/dist/webdist/assets/{kanban-definition-UN3LZRKU-CIlgPK2Q.js → kanban-definition-UN3LZRKU-B0AzyNbO.js} +1 -1
  69. package/dist/webdist/assets/{linear-Dwee7zpr.js → linear-BWBxaDER.js} +1 -1
  70. package/dist/webdist/assets/{lock-Bvay5gWG.js → lock-Cc8h_hdf.js} +1 -1
  71. package/dist/webdist/assets/{mermaid.core-J5pMsJoH.js → mermaid.core-ejH4ogS8.js} +4 -4
  72. package/dist/webdist/assets/{mindmap-definition-RKZ34NQL-dojADpM-.js → mindmap-definition-RKZ34NQL-D43jvdzR.js} +1 -1
  73. package/dist/webdist/assets/{pencil-BMbt1aP-.js → pencil-CBakPF4F.js} +1 -1
  74. package/dist/webdist/assets/{personas-In3Y_0cx.js → personas-B0p0BX69.js} +1 -1
  75. package/dist/webdist/assets/{pieDiagram-4H26LBE5-BkzBQcFB.js → pieDiagram-4H26LBE5-LEL1ZCx7.js} +1 -1
  76. package/dist/webdist/assets/{plus-iQMJ0v11.js → plus-B1dZKSGV.js} +1 -1
  77. package/dist/webdist/assets/{quadrantDiagram-W4KKPZXB-DkRwNabn.js → quadrantDiagram-W4KKPZXB-BM9YYsxt.js} +1 -1
  78. package/dist/webdist/assets/{refresh-cw-CQM4Lk5m.js → refresh-cw-CyuRU30b.js} +1 -1
  79. package/dist/webdist/assets/{requirementDiagram-4Y6WPE33-lmlcs3qi.js → requirementDiagram-4Y6WPE33-BzpmVcdD.js} +1 -1
  80. package/dist/webdist/assets/{rotate-ccw-DdsgYBnk.js → rotate-ccw-B4FPn_uu.js} +1 -1
  81. package/dist/webdist/assets/{sankeyDiagram-5OEKKPKP-lPssl2qV.js → sankeyDiagram-5OEKKPKP-CJjdQhEE.js} +1 -1
  82. package/dist/webdist/assets/{sequenceDiagram-3UESZ5HK-Cf7xZenl.js → sequenceDiagram-3UESZ5HK-DAtWr5sY.js} +1 -1
  83. package/dist/webdist/assets/{skeleton-DavzJK3x.js → skeleton-D4gswV0g.js} +1 -1
  84. package/dist/webdist/assets/{sliders-horizontal-BZJPHQPY.js → sliders-horizontal-zbQIfrTb.js} +1 -1
  85. package/dist/webdist/assets/{sparkles-Bz-IcHBC.js → sparkles-C9BYrK2a.js} +1 -1
  86. package/dist/webdist/assets/{star-Bu8NPW3a.js → star-C-V7uYuS.js} +1 -1
  87. package/dist/webdist/assets/{stateDiagram-AJRCARHV-CDvT3iZf.js → stateDiagram-AJRCARHV-BzqjpVkC.js} +1 -1
  88. package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-Jr4V5989.js +1 -0
  89. package/dist/webdist/assets/{switch-DdYgDvMh.js → switch-DJGUE0gu.js} +1 -1
  90. package/dist/webdist/assets/{terminal-c3Jeo9pG.js → terminal-DYWE0kIS.js} +1 -1
  91. package/dist/webdist/assets/{timeline-definition-PNZ67QCA-C_oMQ4Rj.js → timeline-definition-PNZ67QCA-DUWSWDIu.js} +1 -1
  92. package/dist/webdist/assets/{trash-2-B3sskeB1.js → trash-2-Bh6pLoU3.js} +1 -1
  93. package/dist/webdist/assets/{useIsDesktop-CkjcxpzL.js → useIsDesktop-BBFHOzWe.js} +1 -1
  94. package/dist/webdist/assets/{vennDiagram-CIIHVFJN-h6SJOwAI.js → vennDiagram-CIIHVFJN-O3R7gjwP.js} +1 -1
  95. package/dist/webdist/assets/{vram-D8-r3zny.js → vram-DuXXfpwQ.js} +1 -1
  96. package/dist/webdist/assets/{wardley-L42UT6IY-DhiFwT8Y.js → wardley-L42UT6IY-BoHQfIuF.js} +1 -1
  97. package/dist/webdist/assets/{wardleyDiagram-YWT4CUSO-Cj5qu2fK.js → wardleyDiagram-YWT4CUSO-CsNf6Ma0.js} +1 -1
  98. package/dist/webdist/assets/{wrench-_XyytN3N.js → wrench-DfrljGuQ.js} +1 -1
  99. package/dist/webdist/assets/{xychartDiagram-2RQKCTM6-CooqJBuV.js → xychartDiagram-2RQKCTM6-CA58m8lE.js} +1 -1
  100. package/dist/webdist/index.html +1 -1
  101. package/package.json +1 -1
  102. package/dist/webdist/assets/ModelDetailDialog-D1PpiVaP.js +0 -1
  103. package/dist/webdist/assets/WorkspaceScreen-Bh36JTwH.js +0 -1
  104. package/dist/webdist/assets/channel-vN9Rfh_6.js +0 -1
  105. package/dist/webdist/assets/classDiagram-4FO5ZUOK-O-iQ0ssF.js +0 -1
  106. package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-O-iQ0ssF.js +0 -1
  107. package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-CMrT-fsV.js +0 -1
package/dist/cli.js CHANGED
@@ -34,9 +34,9 @@ function hideChildConsoleWindows() {
34
34
 
35
35
  // src/cli.ts
36
36
  import { spawn as spawn10 } from "child_process";
37
- import { openSync as openSync3, readFileSync as readFileSync19 } from "fs";
37
+ import { openSync as openSync3, readFileSync as readFileSync20 } from "fs";
38
38
  import { serve } from "@hono/node-server";
39
- import { dirname as dirname12, join as join36 } from "path";
39
+ import { dirname as dirname12, join as join37 } from "path";
40
40
  import { fileURLToPath as fileURLToPath2 } from "url";
41
41
 
42
42
  // src/config/config.ts
@@ -2319,6 +2319,7 @@ async function probe(bin) {
2319
2319
  if (!version2) version2 = "unknown";
2320
2320
  const flags = extractFlags(h.out);
2321
2321
  const kvTypes = detectKvTypes(h.out, flags.includes("--cache-type-k"));
2322
+ const flagInfo = flags.map((f) => classifyFlag(f, h.out));
2322
2323
  const ENUM_RE = /--spec-type\s+(\[[^\]\n]+\]|[a-z][a-z0-9_-]*(?:[,|][a-z0-9_-]+)+)/gi;
2323
2324
  for (const m2 of h.out.matchAll(ENUM_RE)) {
2324
2325
  for (const v2 of m2[1].replace(/[[\]]/g, "").split(/[,|]/)) {
@@ -2326,7 +2327,7 @@ async function probe(bin) {
2326
2327
  if (t) flags.push(`spec-type:${t}`);
2327
2328
  }
2328
2329
  }
2329
- return { version: version2, capabilities: { kvTypes, flags: [...new Set(flags)].sort() } };
2330
+ return { version: version2, capabilities: { kvTypes, flags: [...new Set(flags)].sort(), flagInfo } };
2330
2331
  }
2331
2332
  function detectFormat(bin) {
2332
2333
  let fd;
@@ -2362,12 +2363,37 @@ function extractFlags(helpText) {
2362
2363
  }
2363
2364
  function detectKvTypes(helpText, hasCacheTypeFlag) {
2364
2365
  const kvTypes = hasCacheTypeFlag ? [...KNOWN_KV] : ["f16"];
2365
- const kvFlagBlock = /--cache-type-k\s+\S+\b([\s\S]{0,400}?)(?=\n\s*\(default|\n\s{2,}-[a-zA-Z]|\n\n|$)/i.exec(helpText);
2366
- if (kvFlagBlock) {
2367
- for (const t of ["turbo2", "turbo3", "turbo4"]) if (kvFlagBlock[1].includes(t) && !kvTypes.includes(t)) kvTypes.push(t);
2366
+ const cacheTypeK = classifyFlag("--cache-type-k", helpText);
2367
+ if (cacheTypeK.kind === "enum") {
2368
+ for (const extra of cacheTypeK.enumValues ?? []) if (!kvTypes.includes(extra)) kvTypes.push(extra);
2368
2369
  }
2369
2370
  return kvTypes;
2370
2371
  }
2372
+ function parseEnumList(blockText) {
2373
+ const labeled = /allowed values:\s*([\s\S]+)/i.exec(blockText);
2374
+ const bracketed = /\[([^\]\n]+)\]/.exec(blockText);
2375
+ const raw = labeled?.[1] ?? bracketed?.[1];
2376
+ if (!raw) return [];
2377
+ const values = raw.split(/[,|]/).map((s) => s.trim()).filter((s) => /^[a-z][a-z0-9_-]*$/i.test(s));
2378
+ return values.length >= 2 ? values : [];
2379
+ }
2380
+ function classifyFlag(flagName, helpText) {
2381
+ const escaped = flagName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2382
+ const ALIAS = "-{1,2}[a-zA-Z][a-zA-Z0-9-]*";
2383
+ const re = new RegExp(
2384
+ `(?:^|\\n)[ \\t]*(?:${ALIAS}[ \\t]*,[ \\t]*)*${escaped}(?![-a-zA-Z0-9])(?:[ \\t]*,[ \\t]*${ALIAS})*( *)(\\S+)?([\\s\\S]{0,400}?)(?=\\n\\s*\\(default|\\n[ \\t]*${ALIAS}|\\n\\n|$)`,
2385
+ "i"
2386
+ );
2387
+ const m = re.exec(helpText);
2388
+ if (!m) return { name: flagName, kind: "valued" };
2389
+ const gap = m[1] ?? "";
2390
+ const placeholder = m[2] ?? "";
2391
+ const block = m[3] ?? "";
2392
+ const enumValues = parseEnumList(placeholder + "\n" + block);
2393
+ if (enumValues.length > 0) return { name: flagName, kind: "enum", enumValues };
2394
+ if (gap.length !== 1 || !placeholder) return { name: flagName, kind: "boolean" };
2395
+ return { name: flagName, kind: "valued" };
2396
+ }
2371
2397
  function firstNonEmptyLine(s) {
2372
2398
  for (const ln of s.split("\n")) {
2373
2399
  const t = ln.trim();
@@ -3330,7 +3356,8 @@ var Registry = class {
3330
3356
  * NextN/MTP gating has the data it needs without a manual re-probe. */
3331
3357
  async ensureProbed() {
3332
3358
  for (const e of this.list().engines) {
3333
- if (e.version && !isStaleCapabilities(e.capabilities.flags)) continue;
3359
+ if (e.kind !== "llama-server") continue;
3360
+ if (e.version && !isStaleCapabilities(e.capabilities.flags, e.capabilities.flagInfo)) continue;
3334
3361
  try {
3335
3362
  await this.reprobe(e.id);
3336
3363
  } catch {
@@ -3342,12 +3369,102 @@ function customSourceKey(s) {
3342
3369
  if (!s.sourceRepo) return s.binPath;
3343
3370
  return `${normRepoUrl(s.sourceRepo)}#${(s.sourceBranch ?? "").trim().toLowerCase()}#${(s.sourceCommit ?? "").trim().toLowerCase()}`;
3344
3371
  }
3345
- function isStaleCapabilities(flags) {
3372
+ function isStaleCapabilities(flags, flagInfo) {
3346
3373
  if (flags.includes("--spec-type") && !flags.some((f) => f.startsWith("spec-type:"))) return true;
3347
3374
  if (["--draft-max", "--draft-min", "--draft", "--draft-n", "--draft-n-min"].some((f) => flags.includes(f))) return true;
3375
+ if (flagInfo === void 0) return true;
3348
3376
  return false;
3349
3377
  }
3350
3378
 
3379
+ // src/telemetry/classify.ts
3380
+ var OOM_SIGNS = [
3381
+ "out of memory",
3382
+ "outofmemory",
3383
+ "failed to allocate",
3384
+ "alloc_buffer",
3385
+ "memory allocation of size",
3386
+ "cudamalloc",
3387
+ "insufficient memory",
3388
+ "not enough memory",
3389
+ "unable to allocate",
3390
+ "bad_alloc",
3391
+ "out of vram",
3392
+ "vram allocation failed",
3393
+ "cuda_error_out_of_memory",
3394
+ "resource_exhausted"
3395
+ ];
3396
+ var ARCH_SIGNS = [
3397
+ "unknown model architecture",
3398
+ "unsupported model architecture",
3399
+ "unsupported architecture",
3400
+ "architecture not supported",
3401
+ "unknown architecture"
3402
+ ];
3403
+ var GGUF_SIGNS = [
3404
+ "invalid magic",
3405
+ "error loading model",
3406
+ "failed to load model",
3407
+ "gguf_init",
3408
+ "corrupt",
3409
+ "unexpected end of file",
3410
+ "unexpectedly reached end of file",
3411
+ "truncated",
3412
+ "failed to read tensor",
3413
+ "wrong number of tensors",
3414
+ "tensor not found"
3415
+ ];
3416
+ function classifyLoadFailure(err8) {
3417
+ if (!err8) return "other";
3418
+ if (err8.code === "readiness_timeout") return "timeout";
3419
+ if (err8.code === "engine_unsupported") return "no_engine";
3420
+ const haystack = [err8.message ?? "", ...err8.logTail ?? []].join("\n").toLowerCase();
3421
+ if (haystack.includes("no_active_engine")) return "no_engine";
3422
+ if (haystack.includes("no_such_model")) return "bad_gguf";
3423
+ if (OOM_SIGNS.some((s) => haystack.includes(s))) return "oom";
3424
+ if (ARCH_SIGNS.some((s) => haystack.includes(s))) return "unsupported_arch";
3425
+ if (GGUF_SIGNS.some((s) => haystack.includes(s))) return "bad_gguf";
3426
+ return "other";
3427
+ }
3428
+ function classifyBenchFailure(results) {
3429
+ if (results.some((r) => r.outcome === "oom")) return "oom";
3430
+ if (results.some((r) => r.outcome === "timeout")) return "timeout";
3431
+ return "other";
3432
+ }
3433
+ function classifyEngineErrorFingerprint(err8) {
3434
+ if (!err8) return "other";
3435
+ if (err8.code === "readiness_timeout") return "engine_start_timeout";
3436
+ if (err8.code === "model_load_failed") return "model_load_failed";
3437
+ const haystack = [err8.message ?? "", ...err8.logTail ?? []].join("\n").toLowerCase();
3438
+ if (OOM_SIGNS.some((s) => haystack.includes(s))) return "cuda_oom";
3439
+ if (ARCH_SIGNS.some((s) => haystack.includes(s))) return "model_load_failed";
3440
+ if (GGUF_SIGNS.some((s) => haystack.includes(s))) return "model_load_failed";
3441
+ if (err8.code === "engine_exited" || err8.code === "engine_spawn_failed") return "engine_crash";
3442
+ return "other";
3443
+ }
3444
+ var NETWORK_SIGNS = [
3445
+ "enotfound",
3446
+ "econnreset",
3447
+ "econnrefused",
3448
+ "etimedout",
3449
+ "fetch failed",
3450
+ "network",
3451
+ "check your connection"
3452
+ ];
3453
+ var NO_ASSET_SIGNS = ["404", "no downloadable binary", "no release asset", "not found in"];
3454
+ var UNSUPPORTED_PLATFORM_SIGNS = ["no prebuilt binary for this operating system", "unsupported platform", "unsupported architecture"];
3455
+ var DISK_FULL_SIGNS = ["enospc", "no space left"];
3456
+ var PERMISSION_SIGNS = ["eacces", "eperm", "permission denied", "access is denied"];
3457
+ function classifyProvisionFailure(message) {
3458
+ if (!message) return "other";
3459
+ const haystack = message.toLowerCase();
3460
+ if (UNSUPPORTED_PLATFORM_SIGNS.some((s) => haystack.includes(s))) return "unsupported_platform";
3461
+ if (NO_ASSET_SIGNS.some((s) => haystack.includes(s))) return "no_asset";
3462
+ if (DISK_FULL_SIGNS.some((s) => haystack.includes(s))) return "disk_full";
3463
+ if (PERMISSION_SIGNS.some((s) => haystack.includes(s))) return "permission_denied";
3464
+ if (NETWORK_SIGNS.some((s) => haystack.includes(s))) return "network";
3465
+ return "other";
3466
+ }
3467
+
3351
3468
  // src/engines/provision-state.ts
3352
3469
  var ProvisionState = class {
3353
3470
  s = { active: false, phase: "idle", backend: "", pct: 0, part: 1, parts: 1, error: null };
@@ -3365,8 +3482,9 @@ var ProvisionState = class {
3365
3482
  this.s.parts = parts;
3366
3483
  }
3367
3484
  /** Optional observer for the terminal outcome, wired in cli.ts (ADR-299
3368
- * `onboarding_step`). The error string is deliberately not passed — the only
3369
- * consumer may never transmit free text. */
3485
+ * `onboarding_step`). The raw error STRING is deliberately never passed —
3486
+ * only `failReason`, a `classifyProvisionFailure` enum member, so the only
3487
+ * consumer still never sees free text even though it now learns *why*. */
3370
3488
  onSettled;
3371
3489
  done() {
3372
3490
  this.s = { active: false, phase: "idle", backend: "", pct: 0, part: 1, parts: 1, error: null };
@@ -3374,11 +3492,11 @@ var ProvisionState = class {
3374
3492
  }
3375
3493
  fail(error) {
3376
3494
  this.s = { active: false, phase: "error", backend: this.s.backend, pct: 0, part: 1, parts: 1, error };
3377
- this.settle(false);
3495
+ this.settle(false, classifyProvisionFailure(error));
3378
3496
  }
3379
- settle(ok) {
3497
+ settle(ok, failReason) {
3380
3498
  try {
3381
- this.onSettled?.(ok);
3499
+ this.onSettled?.(ok, failReason);
3382
3500
  } catch {
3383
3501
  }
3384
3502
  }
@@ -3411,21 +3529,31 @@ var BuildState = class {
3411
3529
  if (this.s.log.length > LOG_TAIL) this.s.log.splice(0, this.s.log.length - LOG_TAIL);
3412
3530
  }
3413
3531
  /** Optional observer for the terminal outcome, wired in cli.ts (ADR-299
3414
- * `onboarding_step`). A plain callback so this module keeps no telemetry
3415
- * dependency; the error STRING is deliberately not passed, because the only
3416
- * consumer may never send free text. */
3532
+ * `onboarding_step` / ADR-327 `error`). A plain callback so this module keeps
3533
+ * no telemetry dependency; the error STRING is deliberately never passed,
3534
+ * because the only consumer may never send free text. Three outcomes, not
3535
+ * two — `cancelled` is distinct from `fail` (PR #105 review finding): a
3536
+ * build the user deliberately aborted must not be reported as a crash. */
3417
3537
  onSettled;
3418
3538
  done() {
3419
3539
  this.s = { ...this.s, active: false, phase: "done", error: null };
3420
- this.settle(true);
3540
+ this.settle("ok");
3421
3541
  }
3422
3542
  fail(error) {
3423
3543
  this.s = { ...this.s, active: false, phase: "error", error };
3424
- this.settle(false);
3544
+ this.settle("fail");
3545
+ }
3546
+ /** A deliberate user cancellation. Same terminal UI shape as `fail()` (phase
3547
+ * 'error', so the UI still shows the run ended and why) — only the observer
3548
+ * outcome differs. A cancel is a choice, not a failure, and must not be
3549
+ * reported as one. */
3550
+ cancel(message) {
3551
+ this.s = { ...this.s, active: false, phase: "error", error: message };
3552
+ this.settle("cancelled");
3425
3553
  }
3426
- settle(ok) {
3554
+ settle(outcome) {
3427
3555
  try {
3428
- this.onSettled?.(ok);
3556
+ this.onSettled?.(outcome);
3429
3557
  } catch {
3430
3558
  }
3431
3559
  }
@@ -15142,6 +15270,14 @@ var FAIL_REASONS = [
15142
15270
  "cancelled",
15143
15271
  "other"
15144
15272
  ];
15273
+ var PROVISION_FAIL_REASONS = [
15274
+ "network",
15275
+ "no_asset",
15276
+ "unsupported_platform",
15277
+ "disk_full",
15278
+ "permission_denied",
15279
+ "other"
15280
+ ];
15145
15281
  var COUNT_BUCKETS = ["1", "2-5", "6-20", "21-100", "100+"];
15146
15282
  var ERROR_FINGERPRINTS = [
15147
15283
  "cuda_oom",
@@ -15197,7 +15333,14 @@ var HW_SPEC = {
15197
15333
  ramMb: { kind: "number" }
15198
15334
  };
15199
15335
  var PAYLOAD_SPECS = {
15200
- onboarding_step: { step: { enum: ONBOARDING_STEPS }, outcome: { enum: OUTCOMES } },
15336
+ // `failReason` is optional and, today, only ever sent for `step: 'engine_install'`
15337
+ // (classified by `classifyProvisionFailure`, PROVISION_FAIL_REASONS) — mirrors
15338
+ // `model_first_load.failReason`'s own "optional, only present on a real failure"
15339
+ // shape. Not step-conditional at the schema level (this validator is flat per
15340
+ // event name, not per payload value) — enforced by convention at the one call
15341
+ // site that sends it (cli.ts's `provision.onSettled`), same as elsewhere in this
15342
+ // file's payload specs.
15343
+ onboarding_step: { step: { enum: ONBOARDING_STEPS }, outcome: { enum: OUTCOMES }, failReason: { enum: PROVISION_FAIL_REASONS, optional: true } },
15201
15344
  model_first_load: { outcome: { enum: OUTCOMES }, failReason: { enum: FAIL_REASONS, optional: true } },
15202
15345
  feature_first_use: { feature: { enum: FEATURES } },
15203
15346
  feature_used_daily: { feature: { enum: FEATURES }, countBucket: { enum: COUNT_BUCKETS } },
@@ -15379,36 +15522,6 @@ function reportModelLoad(dataDir, emitter, outcome, failReason) {
15379
15522
  }
15380
15523
  }
15381
15524
 
15382
- // src/telemetry/classify.ts
15383
- var OOM_SIGNS = [
15384
- "out of memory",
15385
- "outofmemory",
15386
- "failed to allocate",
15387
- "alloc_buffer",
15388
- "memory allocation of size",
15389
- "cudamalloc",
15390
- "insufficient memory"
15391
- ];
15392
- var ARCH_SIGNS = ["unknown model architecture", "unsupported model architecture", "unsupported architecture"];
15393
- var GGUF_SIGNS = ["invalid magic", "error loading model", "failed to load model", "gguf_init", "corrupt"];
15394
- function classifyLoadFailure(err8) {
15395
- if (!err8) return "other";
15396
- if (err8.code === "readiness_timeout") return "timeout";
15397
- if (err8.code === "engine_unsupported") return "no_engine";
15398
- const haystack = [err8.message ?? "", ...err8.logTail ?? []].join("\n").toLowerCase();
15399
- if (haystack.includes("no_active_engine")) return "no_engine";
15400
- if (haystack.includes("no_such_model")) return "bad_gguf";
15401
- if (OOM_SIGNS.some((s) => haystack.includes(s))) return "oom";
15402
- if (ARCH_SIGNS.some((s) => haystack.includes(s))) return "unsupported_arch";
15403
- if (GGUF_SIGNS.some((s) => haystack.includes(s))) return "bad_gguf";
15404
- return "other";
15405
- }
15406
- function classifyBenchFailure(results) {
15407
- if (results.some((r) => r.outcome === "oom")) return "oom";
15408
- if (results.some((r) => r.outcome === "timeout")) return "timeout";
15409
- return "other";
15410
- }
15411
-
15412
15525
  // src/telemetry/disabled.ts
15413
15526
  var TELEMETRY_ENV = "TURBOLLM_TELEMETRY";
15414
15527
  var OFF_VALUES = /* @__PURE__ */ new Set(["off", "0", "false", "no"]);
@@ -15618,6 +15731,9 @@ var BenchRunner = class {
15618
15731
  this.telemetry.emit("onboarding_step", { step: "first_load", outcome });
15619
15732
  }
15620
15733
  reportModelLoad(this.store.dir(), this.telemetry, outcome, failReason);
15734
+ if (outcome === "fail") {
15735
+ this.telemetry.error(failReason === "oom" ? "cuda_oom" : failReason === "timeout" ? "engine_start_timeout" : "other");
15736
+ }
15621
15737
  }
15622
15738
  /** Stop the engine — force-killed immediately when the run is cancelled (the user is actively
15623
15739
  * waiting for it to stop, ADR-220), gracefully otherwise (lets llama-server release resources
@@ -19464,7 +19580,7 @@ var ALL = [
19464
19580
  platforms: ["win32", "darwin", "linux"],
19465
19581
  support: "experimental",
19466
19582
  installEndpoint: "",
19467
- note: "Upstream publishes prebuilt binaries, but TurboLLM builds from source here. The KVarN cache types (kvarn2...kvarn8, set via --cache-type-k/-v) aren't in the auto-tune sweep yet \u2014 use the custom/raw flags field to set them manually.",
19583
+ note: "Upstream publishes prebuilt binaries, but TurboLLM builds from source here. The KVarN cache types (kvarn2...kvarn8, set via --cache-type-k/-v) show up directly in the KV cache type dropdown, but aren't in the auto-tune sweep yet \u2014 pick one manually.",
19468
19584
  variants: [
19469
19585
  {
19470
19586
  id: "beellama-source",
@@ -20296,7 +20412,7 @@ function registerApi(app2, d) {
20296
20412
  }
20297
20413
  if (e?.name === "AbortError") {
20298
20414
  d.build.log("Build cancelled.");
20299
- d.build.fail("Build cancelled.");
20415
+ d.build.cancel("Build cancelled.");
20300
20416
  } else {
20301
20417
  const asmIssue = d.build.get().log.some((l) => /No CMAKE_ASM_COMPILER could be found|ASM compiler identification is unknown/i.test(l));
20302
20418
  const raw = e instanceof Error ? e.message : String(e);
@@ -20371,7 +20487,7 @@ function registerApi(app2, d) {
20371
20487
  } catch (e) {
20372
20488
  if (e?.name === "AbortError") {
20373
20489
  d.build.log("CUDA download cancelled.");
20374
- d.build.fail("CUDA download cancelled.");
20490
+ d.build.cancel("CUDA download cancelled.");
20375
20491
  } else {
20376
20492
  d.build.fail(`Could not download CUDA: ${e instanceof Error ? e.message : String(e)}`);
20377
20493
  }
@@ -24226,7 +24342,15 @@ async function recordOpenAiStreamUsage(d, body8, source, modelKey, codeSessionId
24226
24342
 
24227
24343
  // src/telemetry/feature-map.ts
24228
24344
  var BY_SEGMENT = {
24345
+ // `chat` (bare) only covers `/api/v1/chat/stop` — the Stop-generation button. Real
24346
+ // chat traffic (sending a message, creating/loading a conversation) lives under
24347
+ // `/api/v1/conversations/*` instead (PR #105 review finding: `chat` alone was
24348
+ // silently under-counting the product's own core feature). `conversations` IS the
24349
+ // chat feature's own primary data endpoint, not a foreign feature fetched as a side
24350
+ // effect the way `/api/v1/skills`/`/api/v1/chat-agents` are (see the comment below) —
24351
+ // so, unlike those two, mapping it here doesn't risk corrupting an unrelated signal.
24229
24352
  chat: "chat",
24353
+ conversations: "chat",
24230
24354
  code: "code",
24231
24355
  artifacts: "artifacts",
24232
24356
  mcp: "mcp",
@@ -24272,7 +24396,10 @@ function createApp(d) {
24272
24396
  app2.use("/api/v1/code/*", codeAuth(d));
24273
24397
  app2.use("*", async (c, next) => {
24274
24398
  const feature = d.telemetry ? featureForPath(c.req.path) : null;
24275
- if (feature !== null) d.telemetry?.firstUse(feature);
24399
+ if (feature !== null) {
24400
+ d.telemetry?.firstUse(feature);
24401
+ d.telemetry?.useFeature(feature);
24402
+ }
24276
24403
  await next();
24277
24404
  });
24278
24405
  app2.get("/healthz", (c) => c.json({ status: "ok", version: d.version }));
@@ -24578,6 +24705,89 @@ var TunnelManager = class {
24578
24705
 
24579
24706
  // src/telemetry/emit.ts
24580
24707
  import { randomUUID as randomUUID14 } from "crypto";
24708
+
24709
+ // src/telemetry/daily-usage.ts
24710
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync17, mkdirSync as mkdirSync19 } from "fs";
24711
+ import { join as join36 } from "path";
24712
+ var cache = /* @__PURE__ */ new Map();
24713
+ function usagePath(dataDir) {
24714
+ return join36(dataDir, "telemetry", "daily-usage.json");
24715
+ }
24716
+ function readFromDisk(dataDir) {
24717
+ try {
24718
+ const parsed = JSON.parse(readFileSync19(usagePath(dataDir), "utf8"));
24719
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
24720
+ return parsed;
24721
+ } catch {
24722
+ return {};
24723
+ }
24724
+ }
24725
+ function writeToDisk(dataDir, usage) {
24726
+ mkdirSync19(join36(dataDir, "telemetry"), { recursive: true });
24727
+ writeFileSync17(usagePath(dataDir), JSON.stringify(usage));
24728
+ }
24729
+ function loadCache(dataDir) {
24730
+ let usage = cache.get(dataDir);
24731
+ if (usage === void 0) {
24732
+ usage = readFromDisk(dataDir);
24733
+ cache.set(dataDir, usage);
24734
+ }
24735
+ return usage;
24736
+ }
24737
+ function bucketCount(n) {
24738
+ if (n <= 1) return "1";
24739
+ if (n <= 5) return "2-5";
24740
+ if (n <= 20) return "6-20";
24741
+ if (n <= 100) return "21-100";
24742
+ return "100+";
24743
+ }
24744
+ function recordFeatureUse(dataDir, feature, today) {
24745
+ try {
24746
+ const usage = loadCache(dataDir);
24747
+ const existing = usage[feature];
24748
+ if (existing === void 0) {
24749
+ usage[feature] = { day: today, count: 1 };
24750
+ return null;
24751
+ }
24752
+ if (existing.day === today) {
24753
+ usage[feature] = { day: today, count: existing.count + 1 };
24754
+ return null;
24755
+ }
24756
+ const rolled = { day: existing.day, bucket: bucketCount(existing.count) };
24757
+ usage[feature] = { day: today, count: 1 };
24758
+ writeToDisk(dataDir, usage);
24759
+ return rolled;
24760
+ } catch {
24761
+ return null;
24762
+ }
24763
+ }
24764
+ function flushStaleDailyUsage(dataDir, today) {
24765
+ try {
24766
+ const usage = loadCache(dataDir);
24767
+ const rolled = [];
24768
+ let changed = false;
24769
+ for (const [feature, entry] of Object.entries(usage)) {
24770
+ if (entry.day === today) continue;
24771
+ rolled.push({ feature, day: entry.day, bucket: bucketCount(entry.count) });
24772
+ delete usage[feature];
24773
+ changed = true;
24774
+ }
24775
+ if (changed) writeToDisk(dataDir, usage);
24776
+ return rolled;
24777
+ } catch {
24778
+ return [];
24779
+ }
24780
+ }
24781
+ function persistDailyUsage(dataDir) {
24782
+ try {
24783
+ const usage = cache.get(dataDir);
24784
+ if (usage === void 0) return;
24785
+ writeToDisk(dataDir, usage);
24786
+ } catch {
24787
+ }
24788
+ }
24789
+
24790
+ // src/telemetry/emit.ts
24581
24791
  var REQUIRES_FULL = /* @__PURE__ */ new Set(["error"]);
24582
24792
  var Emitter = class {
24583
24793
  o;
@@ -24634,6 +24844,45 @@ var Emitter = class {
24634
24844
  if (!this.canSend("daily_active")) return;
24635
24845
  if (this.claim(`daily:${this.today()}`)) this.emit("daily_active");
24636
24846
  }
24847
+ /**
24848
+ * Count one use of `feature` toward today's tally, emitting a bucketed
24849
+ * `feature_used_daily` for the previous day the moment the calendar day
24850
+ * rolls over. Consent is checked BEFORE any count is recorded — same
24851
+ * reasoning as `firstUse` above (see its doc comment): usage while
24852
+ * telemetry is off must never silently contribute to a count reported
24853
+ * after the user opts in later.
24854
+ */
24855
+ useFeature(feature) {
24856
+ if (!this.canSend("feature_used_daily")) return;
24857
+ const rolled = recordFeatureUse(this.o.dataDir, feature, this.today());
24858
+ if (rolled) this.emit("feature_used_daily", { feature, countBucket: rolled.bucket });
24859
+ }
24860
+ /**
24861
+ * Roll over any feature whose tally is from a day earlier than today, then
24862
+ * persist whatever's left (today's still-in-progress counts) to disk.
24863
+ * Called periodically (cli.ts, alongside the queue flush) for two reasons:
24864
+ * a feature used only on a user's LAST active day still gets reported
24865
+ * instead of waiting indefinitely for a next use that may never come, AND
24866
+ * same-day counts — which `useFeature` only ever keeps in memory, to avoid
24867
+ * a disk write on every matching API request — get a chance to survive a
24868
+ * crash instead of living only until the next rollover.
24869
+ */
24870
+ flushDailyUsage() {
24871
+ if (!this.canSend("feature_used_daily")) return;
24872
+ for (const { feature, bucket } of flushStaleDailyUsage(this.o.dataDir, this.today())) {
24873
+ this.emit("feature_used_daily", { feature, countBucket: bucket });
24874
+ }
24875
+ persistDailyUsage(this.o.dataDir);
24876
+ }
24877
+ /**
24878
+ * Emit `error` — full level only (`REQUIRES_FULL` above), and deliberately
24879
+ * NOT once-only: unlike `model_first_load`'s single most-valuable-attempt
24880
+ * signal, every crash after the first is still a real data point (ADR-299's
24881
+ * "what is failing?" journey question, previously never wired to anything).
24882
+ */
24883
+ error(fingerprint) {
24884
+ this.emit("error", { fingerprint });
24885
+ }
24637
24886
  /**
24638
24887
  * Mint the anonymous machine id lazily.
24639
24888
  *
@@ -24662,8 +24911,8 @@ var Emitter = class {
24662
24911
  hideChildConsoleWindows();
24663
24912
  var version = "0.1.1";
24664
24913
  try {
24665
- const pkgPath = join36(dirname12(fileURLToPath2(import.meta.url)), "..", "package.json");
24666
- version = JSON.parse(readFileSync19(pkgPath, "utf8")).version ?? version;
24914
+ const pkgPath = join37(dirname12(fileURLToPath2(import.meta.url)), "..", "package.json");
24915
+ version = JSON.parse(readFileSync20(pkgPath, "utf8")).version ?? version;
24667
24916
  } catch {
24668
24917
  }
24669
24918
  var [nodeMajor, nodeMinor] = process.versions.node.split(".").map(Number);
@@ -24691,7 +24940,7 @@ function argValue(name, fallback) {
24691
24940
  if (hasFlag("--no-telemetry")) process.env[TELEMETRY_ENV] = "off";
24692
24941
  function configuredDaemonPort(configPath) {
24693
24942
  try {
24694
- const cfg2 = JSON.parse(readFileSync19(configPath, "utf8"));
24943
+ const cfg2 = JSON.parse(readFileSync20(configPath, "utf8"));
24695
24944
  if (typeof cfg2.daemon?.port === "number" && cfg2.daemon.port > 0) return cfg2.daemon.port;
24696
24945
  } catch {
24697
24946
  }
@@ -24816,7 +25065,7 @@ if (pruned > 0) console.log(`pruned ${pruned} dangling engine build(s)`);
24816
25065
  var provision = new ProvisionState();
24817
25066
  var build = new BuildState();
24818
25067
  var updates = new UpdateChecker();
24819
- var enginesDir = join36(store.dir(), "engines");
25068
+ var enginesDir = join37(store.dir(), "engines");
24820
25069
  void seedDefaultEngines(registry, enginesDir, provision).then(() => registry.ensureProbed());
24821
25070
  var manager = new Manager(store);
24822
25071
  var scanner = new Scanner(store);
@@ -24871,15 +25120,23 @@ manager.onLoadSettled = (ok, err8) => {
24871
25120
  telemetry.emit("onboarding_step", { step: "first_load", outcome });
24872
25121
  }
24873
25122
  reportModelLoad(store.dir(), telemetry, outcome, ok ? void 0 : classifyLoadFailure(err8));
25123
+ if (!ok) telemetry.error(classifyEngineErrorFingerprint(err8));
24874
25124
  };
24875
25125
  bench.telemetry = telemetry;
24876
- provision.onSettled = (ok) => telemetry.emit("onboarding_step", { step: "engine_install", outcome: ok ? "ok" : "fail" });
24877
- downloads.onSettled = (outcome) => telemetry.emit("onboarding_step", { step: "model_download", outcome });
25126
+ provision.onSettled = (ok, failReason) => telemetry.emit("onboarding_step", { step: "engine_install", outcome: ok ? "ok" : "fail", ...failReason ? { failReason } : {} });
25127
+ downloads.onSettled = (outcome) => {
25128
+ telemetry.emit("onboarding_step", { step: "model_download", outcome });
25129
+ if (outcome === "fail") telemetry.error("download_failed");
25130
+ };
25131
+ build.onSettled = (outcome) => {
25132
+ if (outcome === "fail") telemetry.error("build_failed");
25133
+ };
24878
25134
  setTimeout(() => {
24879
25135
  telemetry.once("app_first_run");
24880
25136
  void flush(store.dir(), store.snapshot().telemetry.level);
24881
25137
  }, 3e3).unref();
24882
25138
  setInterval(() => void flush(store.dir(), store.snapshot().telemetry.level), 5 * 6e4).unref();
25139
+ setInterval(() => telemetry.flushDailyUsage(), 5 * 6e4).unref();
24883
25140
  var tunnelRequested = hasFlag("--tunnel");
24884
25141
  if (tunnelRequested) deps.tunnel = new TunnelManager(store.dir());
24885
25142
  deps.codeRuns = new CodeRunManager(deps);
@@ -25049,7 +25306,7 @@ var restarting = false;
25049
25306
  function spawnReplacement() {
25050
25307
  let out = "ignore";
25051
25308
  try {
25052
- out = openSync3(join36(store.dir(), "restart.log"), "a");
25309
+ out = openSync3(join37(store.dir(), "restart.log"), "a");
25053
25310
  } catch {
25054
25311
  out = "ignore";
25055
25312
  }
@@ -1 +1 @@
1
- import{j as e}from"./radix-fDePPJsY.js";import{b as M,u as W,r as b}from"./vendor-DnjiPEcz.js";import{u as $,b as G,d as H,e as Y,f as J,a as E,B as y,c as B,t as h,A as C}from"./index-Dh7kXOE1.js";import{f as U}from"./chat-api-BWC8yB8f.js";import{f as V,a as X}from"./agent-api-BDGYxu1V.js";import{P as z}from"./personas-In3Y_0cx.js";import{C as Z}from"./chevron-left-C-tiOyyN.js";import{R as ee}from"./rotate-ccw-DdsgYBnk.js";import{T as te}from"./trash-2-B3sskeB1.js";import{C as se}from"./chevron-right-DTJBClgw.js";const ne=()=>({name:"",description:"",systemPrompt:"",skillIds:[],tools:[]});function oe(s){const d=[],x=new Map;for(const n of s){const o=/^mcp__([^_]+(?:_[^_]+)*)__/.exec(n.name);if(!o){d.push(n);continue}const i=o[1],p=/^\[([^\]]+)\]/.exec(n.description),c=p?p[1]:i.replace(/_/g," ");x.has(i)||x.set(i,{key:i,label:c,tools:[]}),x.get(i).tools.push(n)}return{builtin:d,groups:[...x.values()].sort((n,o)=>n.label.localeCompare(o.label))}}function ae(s){return s.replace(/^mcp__[^_]+(?:_[^_]+)*__/,"")}function le(s){return s.replace(/^\[[^\]]+\]\s*/,"")}function q({tool:s,checked:d,onToggle:x}){return e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[12px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:d,onChange:x}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"font-mono text-ink",children:ae(s.name)}),s.description&&e.jsx("span",{className:"text-faint",children:le(s.description)})]})]})}function re({group:s,selected:d,onToggleAll:x,onToggleOne:n}){const[o,i]=b.useState(!1),p=s.tools.map(l=>l.name),c=p.filter(l=>d.has(l)).length,a=c===p.length,f=c>0&&!a,u=b.useRef(null);return b.useEffect(()=>{u.current&&(u.current.indeterminate=f)},[f]),e.jsxs("div",{className:"rounded-md border border-border",children:[e.jsxs("div",{className:"flex items-center gap-2 px-2.5 py-2",children:[e.jsx("input",{ref:u,type:"checkbox",checked:a,onChange:()=>x(p,!a)}),e.jsxs("button",{type:"button",onClick:()=>i(l=>!l),className:"flex flex-1 items-center gap-1.5 text-left text-[13px] text-ink",children:[e.jsx(se,{size:12,className:B("shrink-0 text-faint transition-transform",o&&"rotate-90")}),s.label,e.jsxs("span",{className:"text-[11px] text-faint",children:["(",c,"/",p.length,")"]})]})]}),o&&e.jsx("div",{className:"flex flex-col gap-1.5 border-t border-border px-3 py-2 pl-9",children:s.tools.map(l=>e.jsx(q,{tool:l,checked:d.has(l.name),onToggle:()=>n(l.name)},l.name))})]})}function ie({tools:s,selected:d,onChange:x}){const n=new Set(d),{builtin:o,groups:i}=oe(s),p=a=>x(n.has(a)?d.filter(f=>f!==a):[...d,a]),c=(a,f)=>{const u=d.filter(l=>!a.includes(l));x(f?[...u,...a]:u)};return s.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No tools available yet."}):e.jsxs("div",{className:"flex flex-col gap-2",children:[o.length>0&&e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:o.map(a=>e.jsx(q,{tool:a,checked:n.has(a.name),onToggle:()=>p(a.name)},a.name))}),i.map(a=>e.jsx(re,{group:a,selected:n,onToggleAll:c,onToggleOne:p},a.key))]})}function ke(){const{agentId:s="new"}=M(),d=W(),x=$(),n=s==="new",o=!n&&z.some(t=>t.id===s),i=o?z.find(t=>t.id===s):void 0,p=G(),c=!n&&!o?(p.data??[]).find(t=>t.id===s):void 0,a=H(),f=Y(),u=J(),l=o?f.data?.[s]:void 0,_=E({queryKey:X.list(),queryFn:V,staleTime:0}),k=_.data??[],I=E({queryKey:["available-tools"],queryFn:U}),j=I.data??[],[m,g]=b.useState(ne()),[S,T]=b.useState(!1),[A,P]=b.useState(!1),[D,w]=b.useState(!1),[R,L]=b.useState("skills");n&&!S&&(k.length||j.length)&&(g(t=>({...t,skillIds:k.map(r=>r.id),tools:j.map(r=>r.name)})),T(!0)),!n&&!o&&!S&&c&&(g({name:c.name,description:c.description,systemPrompt:c.systemPrompt,skillIds:c.skillIds,tools:c.tools}),T(!0)),o&&!S&&i&&f.isSuccess&&(k.length||j.length)&&(g({name:l?.name??i.name,description:l?.description??i.description,systemPrompt:l?.systemPrompt??i.systemPrompt,skillIds:l?.skillIds??k.map(t=>t.id),tools:l?.tools??j.map(t=>t.name)}),T(!0));const N=()=>d("/customize"),Q=t=>g(r=>({...r,skillIds:r.skillIds.includes(t)?r.skillIds.filter(v=>v!==t):[...r.skillIds,t]})),O=()=>{if(!m.name.trim()){h.error("Name is required.");return}P(!0);const t={name:m.name.trim(),description:m.description.trim(),systemPrompt:m.systemPrompt,skillIds:m.skillIds,tools:m.tools};if(o){u.save.mutate({id:s,override:t},{onSuccess:()=>{h.success("Agent saved."),N()},onError:v=>{h.error(v instanceof C?v.message:"Could not save agent."),P(!1)}});return}const r={onSuccess:()=>{x.invalidateQueries({queryKey:["chat-agents"]}),h.success(n?"Agent created.":"Agent saved."),N()},onError:v=>{h.error(v instanceof C?v.message:"Could not save agent."),P(!1)}};n?a.add.mutate(t,r):a.update.mutate({id:s,patch:t},r)},F=()=>{u.reset.mutate(s,{onSuccess:()=>{h.success("Reset to default."),i&&g({name:i.name,description:i.description,systemPrompt:i.systemPrompt,skillIds:k.map(t=>t.id),tools:j.map(t=>t.name)})},onError:t=>h.error(t instanceof C?t.message:"Could not reset agent.")})},K=()=>{a.remove.mutate(s,{onSuccess:()=>{h.success("Agent deleted."),N()},onError:t=>h.error(t instanceof C?t.message:"Could not delete agent.")})};return!n&&!o&&p.isSuccess&&!c?e.jsxs("div",{className:"flex w-full flex-col items-center gap-3 px-8 py-16",children:[e.jsx("p",{className:"text-[14px] text-muted",children:"That agent no longer exists."}),e.jsx(y,{size:"sm",variant:"outline",onClick:N,children:"Back to agents"})]}):e.jsxs("div",{className:"flex w-full max-w-4xl flex-col gap-5 px-4 py-6 md:px-8",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",onClick:N,className:"text-faint hover:text-ink",title:"Back to agents",children:e.jsx(Z,{size:18})}),e.jsx("span",{className:"text-[15px] font-medium text-ink",children:n?"New agent":o?l?"Edit agent (modified built-in)":"Edit agent (built-in)":"Edit agent"}),e.jsxs("div",{className:"ml-auto flex items-center gap-2",children:[o&&e.jsxs(y,{size:"sm",variant:"outline",onClick:F,disabled:u.reset.isPending,children:[e.jsx(ee,{size:13})," Reset to default"]}),e.jsx(y,{size:"sm",variant:"outline",onClick:N,children:"Cancel"}),e.jsx(y,{size:"sm",onClick:O,disabled:A||!m.name.trim(),children:A?"Saving…":"Save"})]})]}),o&&e.jsx("p",{className:"rounded-md border border-border bg-panel px-3 py-2 text-[12px] text-muted",children:"Built-in agents can be edited in place — Reset to default discards your changes and restores TurboLLM's original prompt and access."}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Name"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"Release Notes Writer",value:m.name,onChange:t=>g(r=>({...r,name:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Description"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"What this agent is for, in one line",value:m.description,onChange:t=>g(r=>({...r,description:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"System prompt"}),e.jsx("textarea",{className:"min-h-[160px] resize-y rounded-md border border-border bg-bg px-3 py-2 font-mono text-[12px] leading-relaxed text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"You are…",value:m.systemPrompt,onChange:t=>g(r=>({...r,systemPrompt:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-2.5",children:[e.jsx("div",{className:"inline-flex w-fit rounded-md border border-border p-0.5",children:["skills","tools"].map(t=>e.jsx("button",{type:"button",onClick:()=>L(t),className:B("rounded px-3 py-1.5 text-[13px] font-medium transition-colors",R===t?"bg-accent/12 text-accent":"text-muted hover:text-ink"),children:t==="skills"?`Skills (${m.skillIds.length})`:`Tools (${m.tools.length})`},t))}),R==="skills"?_.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):k.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No skills yet — create one under Customize → Skills."}):e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:k.map(t=>e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[13px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:m.skillIds.includes(t.id),onChange:()=>Q(t.id)}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"text-ink",children:t.name}),t.description&&e.jsx("span",{className:"text-[12px] text-muted",children:t.description})]})]},t.id))}):I.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):e.jsx(ie,{tools:j,selected:m.tools,onChange:t=>g(r=>({...r,tools:t}))})]}),!n&&!o&&(D?e.jsxs("div",{className:"flex items-center gap-2 self-start rounded-md border border-border bg-panel p-2 text-[12px]",children:[e.jsx("span",{className:"flex-1 text-muted",children:"Delete this agent?"}),e.jsx("button",{type:"button",onClick:K,className:"rounded px-2 py-1 text-[color:var(--err)] hover:bg-[color:color-mix(in_srgb,var(--err)_12%,transparent)]",children:"Delete"}),e.jsx("button",{type:"button",onClick:()=>w(!1),className:"rounded px-2 py-1 text-faint hover:text-ink",children:"Cancel"})]}):e.jsxs("button",{type:"button",onClick:()=>w(!0),className:"flex items-center gap-1.5 self-start text-[12px] text-faint hover:text-[color:var(--err)]",children:[e.jsx(te,{size:13})," Delete agent"]}))]})}export{ke as AgentEditPage};
1
+ import{j as e}from"./radix-fDePPJsY.js";import{b as M,u as W,r as b}from"./vendor-DnjiPEcz.js";import{u as $,b as G,d as H,e as Y,f as J,a as E,B as y,c as B,t as h,A as C}from"./index-x5TIbXu2.js";import{f as U}from"./chat-api-Vp38MmPT.js";import{f as V,a as X}from"./agent-api-CbZW6U0W.js";import{P as z}from"./personas-B0p0BX69.js";import{C as Z}from"./chevron-left-C7VOgCa-.js";import{R as ee}from"./rotate-ccw-B4FPn_uu.js";import{T as te}from"./trash-2-Bh6pLoU3.js";import{C as se}from"./chevron-right-Cvc6jjhp.js";const ne=()=>({name:"",description:"",systemPrompt:"",skillIds:[],tools:[]});function oe(s){const d=[],x=new Map;for(const n of s){const o=/^mcp__([^_]+(?:_[^_]+)*)__/.exec(n.name);if(!o){d.push(n);continue}const i=o[1],p=/^\[([^\]]+)\]/.exec(n.description),c=p?p[1]:i.replace(/_/g," ");x.has(i)||x.set(i,{key:i,label:c,tools:[]}),x.get(i).tools.push(n)}return{builtin:d,groups:[...x.values()].sort((n,o)=>n.label.localeCompare(o.label))}}function ae(s){return s.replace(/^mcp__[^_]+(?:_[^_]+)*__/,"")}function le(s){return s.replace(/^\[[^\]]+\]\s*/,"")}function q({tool:s,checked:d,onToggle:x}){return e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[12px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:d,onChange:x}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"font-mono text-ink",children:ae(s.name)}),s.description&&e.jsx("span",{className:"text-faint",children:le(s.description)})]})]})}function re({group:s,selected:d,onToggleAll:x,onToggleOne:n}){const[o,i]=b.useState(!1),p=s.tools.map(l=>l.name),c=p.filter(l=>d.has(l)).length,a=c===p.length,f=c>0&&!a,u=b.useRef(null);return b.useEffect(()=>{u.current&&(u.current.indeterminate=f)},[f]),e.jsxs("div",{className:"rounded-md border border-border",children:[e.jsxs("div",{className:"flex items-center gap-2 px-2.5 py-2",children:[e.jsx("input",{ref:u,type:"checkbox",checked:a,onChange:()=>x(p,!a)}),e.jsxs("button",{type:"button",onClick:()=>i(l=>!l),className:"flex flex-1 items-center gap-1.5 text-left text-[13px] text-ink",children:[e.jsx(se,{size:12,className:B("shrink-0 text-faint transition-transform",o&&"rotate-90")}),s.label,e.jsxs("span",{className:"text-[11px] text-faint",children:["(",c,"/",p.length,")"]})]})]}),o&&e.jsx("div",{className:"flex flex-col gap-1.5 border-t border-border px-3 py-2 pl-9",children:s.tools.map(l=>e.jsx(q,{tool:l,checked:d.has(l.name),onToggle:()=>n(l.name)},l.name))})]})}function ie({tools:s,selected:d,onChange:x}){const n=new Set(d),{builtin:o,groups:i}=oe(s),p=a=>x(n.has(a)?d.filter(f=>f!==a):[...d,a]),c=(a,f)=>{const u=d.filter(l=>!a.includes(l));x(f?[...u,...a]:u)};return s.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No tools available yet."}):e.jsxs("div",{className:"flex flex-col gap-2",children:[o.length>0&&e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:o.map(a=>e.jsx(q,{tool:a,checked:n.has(a.name),onToggle:()=>p(a.name)},a.name))}),i.map(a=>e.jsx(re,{group:a,selected:n,onToggleAll:c,onToggleOne:p},a.key))]})}function ke(){const{agentId:s="new"}=M(),d=W(),x=$(),n=s==="new",o=!n&&z.some(t=>t.id===s),i=o?z.find(t=>t.id===s):void 0,p=G(),c=!n&&!o?(p.data??[]).find(t=>t.id===s):void 0,a=H(),f=Y(),u=J(),l=o?f.data?.[s]:void 0,_=E({queryKey:X.list(),queryFn:V,staleTime:0}),k=_.data??[],I=E({queryKey:["available-tools"],queryFn:U}),j=I.data??[],[m,g]=b.useState(ne()),[S,T]=b.useState(!1),[A,P]=b.useState(!1),[D,w]=b.useState(!1),[R,L]=b.useState("skills");n&&!S&&(k.length||j.length)&&(g(t=>({...t,skillIds:k.map(r=>r.id),tools:j.map(r=>r.name)})),T(!0)),!n&&!o&&!S&&c&&(g({name:c.name,description:c.description,systemPrompt:c.systemPrompt,skillIds:c.skillIds,tools:c.tools}),T(!0)),o&&!S&&i&&f.isSuccess&&(k.length||j.length)&&(g({name:l?.name??i.name,description:l?.description??i.description,systemPrompt:l?.systemPrompt??i.systemPrompt,skillIds:l?.skillIds??k.map(t=>t.id),tools:l?.tools??j.map(t=>t.name)}),T(!0));const N=()=>d("/customize"),Q=t=>g(r=>({...r,skillIds:r.skillIds.includes(t)?r.skillIds.filter(v=>v!==t):[...r.skillIds,t]})),O=()=>{if(!m.name.trim()){h.error("Name is required.");return}P(!0);const t={name:m.name.trim(),description:m.description.trim(),systemPrompt:m.systemPrompt,skillIds:m.skillIds,tools:m.tools};if(o){u.save.mutate({id:s,override:t},{onSuccess:()=>{h.success("Agent saved."),N()},onError:v=>{h.error(v instanceof C?v.message:"Could not save agent."),P(!1)}});return}const r={onSuccess:()=>{x.invalidateQueries({queryKey:["chat-agents"]}),h.success(n?"Agent created.":"Agent saved."),N()},onError:v=>{h.error(v instanceof C?v.message:"Could not save agent."),P(!1)}};n?a.add.mutate(t,r):a.update.mutate({id:s,patch:t},r)},F=()=>{u.reset.mutate(s,{onSuccess:()=>{h.success("Reset to default."),i&&g({name:i.name,description:i.description,systemPrompt:i.systemPrompt,skillIds:k.map(t=>t.id),tools:j.map(t=>t.name)})},onError:t=>h.error(t instanceof C?t.message:"Could not reset agent.")})},K=()=>{a.remove.mutate(s,{onSuccess:()=>{h.success("Agent deleted."),N()},onError:t=>h.error(t instanceof C?t.message:"Could not delete agent.")})};return!n&&!o&&p.isSuccess&&!c?e.jsxs("div",{className:"flex w-full flex-col items-center gap-3 px-8 py-16",children:[e.jsx("p",{className:"text-[14px] text-muted",children:"That agent no longer exists."}),e.jsx(y,{size:"sm",variant:"outline",onClick:N,children:"Back to agents"})]}):e.jsxs("div",{className:"flex w-full max-w-4xl flex-col gap-5 px-4 py-6 md:px-8",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",onClick:N,className:"text-faint hover:text-ink",title:"Back to agents",children:e.jsx(Z,{size:18})}),e.jsx("span",{className:"text-[15px] font-medium text-ink",children:n?"New agent":o?l?"Edit agent (modified built-in)":"Edit agent (built-in)":"Edit agent"}),e.jsxs("div",{className:"ml-auto flex items-center gap-2",children:[o&&e.jsxs(y,{size:"sm",variant:"outline",onClick:F,disabled:u.reset.isPending,children:[e.jsx(ee,{size:13})," Reset to default"]}),e.jsx(y,{size:"sm",variant:"outline",onClick:N,children:"Cancel"}),e.jsx(y,{size:"sm",onClick:O,disabled:A||!m.name.trim(),children:A?"Saving…":"Save"})]})]}),o&&e.jsx("p",{className:"rounded-md border border-border bg-panel px-3 py-2 text-[12px] text-muted",children:"Built-in agents can be edited in place — Reset to default discards your changes and restores TurboLLM's original prompt and access."}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Name"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"Release Notes Writer",value:m.name,onChange:t=>g(r=>({...r,name:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Description"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"What this agent is for, in one line",value:m.description,onChange:t=>g(r=>({...r,description:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"System prompt"}),e.jsx("textarea",{className:"min-h-[160px] resize-y rounded-md border border-border bg-bg px-3 py-2 font-mono text-[12px] leading-relaxed text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"You are…",value:m.systemPrompt,onChange:t=>g(r=>({...r,systemPrompt:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-2.5",children:[e.jsx("div",{className:"inline-flex w-fit rounded-md border border-border p-0.5",children:["skills","tools"].map(t=>e.jsx("button",{type:"button",onClick:()=>L(t),className:B("rounded px-3 py-1.5 text-[13px] font-medium transition-colors",R===t?"bg-accent/12 text-accent":"text-muted hover:text-ink"),children:t==="skills"?`Skills (${m.skillIds.length})`:`Tools (${m.tools.length})`},t))}),R==="skills"?_.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):k.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No skills yet — create one under Customize → Skills."}):e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:k.map(t=>e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[13px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:m.skillIds.includes(t.id),onChange:()=>Q(t.id)}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"text-ink",children:t.name}),t.description&&e.jsx("span",{className:"text-[12px] text-muted",children:t.description})]})]},t.id))}):I.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):e.jsx(ie,{tools:j,selected:m.tools,onChange:t=>g(r=>({...r,tools:t}))})]}),!n&&!o&&(D?e.jsxs("div",{className:"flex items-center gap-2 self-start rounded-md border border-border bg-panel p-2 text-[12px]",children:[e.jsx("span",{className:"flex-1 text-muted",children:"Delete this agent?"}),e.jsx("button",{type:"button",onClick:K,className:"rounded px-2 py-1 text-[color:var(--err)] hover:bg-[color:color-mix(in_srgb,var(--err)_12%,transparent)]",children:"Delete"}),e.jsx("button",{type:"button",onClick:()=>w(!1),className:"rounded px-2 py-1 text-faint hover:text-ink",children:"Cancel"})]}):e.jsxs("button",{type:"button",onClick:()=>w(!0),className:"flex items-center gap-1.5 self-start text-[12px] text-faint hover:text-[color:var(--err)]",children:[e.jsx(te,{size:13})," Delete agent"]}))]})}export{ke as AgentEditPage};