turbollm 1.9.7 → 1.9.8

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 (102) hide show
  1. package/README.md +16 -14
  2. package/dist/cli.js +178 -46
  3. package/dist/webdist/assets/{AgentEditPage-C8RGdWJF.js → AgentEditPage-D2MKlhC4.js} +1 -1
  4. package/dist/webdist/assets/{ChatScreen-N3uMBH5o.js → ChatScreen-BT8jCGTN.js} +1 -1
  5. package/dist/webdist/assets/{CodeComposer-CiCzwc9F.js → CodeComposer-BCwZMgGI.js} +1 -1
  6. package/dist/webdist/assets/{CodeHomeScreen-CL0EstEl.js → CodeHomeScreen-Wcb_8M4g.js} +1 -1
  7. package/dist/webdist/assets/{CodeSessionScreen-Cmpd5nk-.js → CodeSessionScreen-CgANva49.js} +1 -1
  8. package/dist/webdist/assets/{CustomizeScreen-BIdHpMx-.js → CustomizeScreen-DV9OIXsG.js} +1 -1
  9. package/dist/webdist/assets/{DeveloperScreen-Du9T6j_f.js → DeveloperScreen-vndr1xLZ.js} +1 -1
  10. package/dist/webdist/assets/{EnginesScreen-CFhygpHp.js → EnginesScreen-DZ20QyGt.js} +1 -1
  11. package/dist/webdist/assets/{FsBrowser-yCDndpa8.js → FsBrowser-BVRCuSAt.js} +1 -1
  12. package/dist/webdist/assets/{ModelLoadMenu-B6OgZ7AZ.js → ModelLoadMenu-DHeG2IT7.js} +1 -1
  13. package/dist/webdist/assets/{ModelsScreen-DAcHEngS.js → ModelsScreen-BxpUJPoo.js} +1 -1
  14. package/dist/webdist/assets/{SettingsScreen-CgwmrEYG.js → SettingsScreen-DxbU7U2g.js} +1 -1
  15. package/dist/webdist/assets/{SkillEditPage-swxEgg6z.js → SkillEditPage-C8z0asiI.js} +1 -1
  16. package/dist/webdist/assets/{TokensScreen-1gAoLbZq.js → TokensScreen-27qcX_mG.js} +1 -1
  17. package/dist/webdist/assets/{ToolApprovalBar-D-VPdhAA.js → ToolApprovalBar-qM2wqtNL.js} +3 -3
  18. package/dist/webdist/assets/WorkspaceScreen-BVZ7QWkL.js +1 -0
  19. package/dist/webdist/assets/{agent-api-C4rG-Jhm.js → agent-api-B8x62EvQ.js} +1 -1
  20. package/dist/webdist/assets/{arc-DN-erBAi.js → arc-BW8F_BQA.js} +1 -1
  21. package/dist/webdist/assets/{architectureDiagram-3BPJPVTR-C_Y3sT8y.js → architectureDiagram-3BPJPVTR-zfH49ZGf.js} +1 -1
  22. package/dist/webdist/assets/{arrow-left-Dv89StCL.js → arrow-left-A509Fetv.js} +1 -1
  23. package/dist/webdist/assets/{badge-C367M9ui.js → badge-BEGHN2Gs.js} +1 -1
  24. package/dist/webdist/assets/{blockDiagram-GPEHLZMM-DIhPpxoy.js → blockDiagram-GPEHLZMM-B36qo5WU.js} +1 -1
  25. package/dist/webdist/assets/{bot-CkS7SpAq.js → bot-DmS4yJ_3.js} +1 -1
  26. package/dist/webdist/assets/{c4Diagram-AAUBKEIU-DhxTzCHS.js → c4Diagram-AAUBKEIU-DAnyJ1zY.js} +1 -1
  27. package/dist/webdist/assets/channel-6n-J43t-.js +1 -0
  28. package/dist/webdist/assets/{chat-api-BjLw__7G.js → chat-api-D8Zk6kRh.js} +1 -1
  29. package/dist/webdist/assets/{chat-queries-DkW-crMK.js → chat-queries-M23Lkm4X.js} +1 -1
  30. package/dist/webdist/assets/{chevron-left-CvC9QlMY.js → chevron-left-BwGlXiTi.js} +1 -1
  31. package/dist/webdist/assets/{chevron-right-dxKQD1jQ.js → chevron-right-B8nbhuzl.js} +1 -1
  32. package/dist/webdist/assets/{chunk-2J33WTMH-BStbX98A.js → chunk-2J33WTMH-CKSa-SWg.js} +1 -1
  33. package/dist/webdist/assets/{chunk-4BX2VUAB-Dt_NYZlI.js → chunk-4BX2VUAB-BJ5ORVQz.js} +1 -1
  34. package/dist/webdist/assets/{chunk-55IACEB6-Bhp_kq7k.js → chunk-55IACEB6-Cg3bYYac.js} +1 -1
  35. package/dist/webdist/assets/{chunk-727SXJPM-DSlmyTXu.js → chunk-727SXJPM-uerTg8Pr.js} +1 -1
  36. package/dist/webdist/assets/{chunk-AQP2D5EJ-DTjPEmPv.js → chunk-AQP2D5EJ-DqdFefiE.js} +1 -1
  37. package/dist/webdist/assets/{chunk-FMBD7UC4-CJFy7xKH.js → chunk-FMBD7UC4-DHVbvOSl.js} +1 -1
  38. package/dist/webdist/assets/{chunk-ND2GUHAM-U5KxKYoG.js → chunk-ND2GUHAM-Bd_Jx54f.js} +1 -1
  39. package/dist/webdist/assets/{chunk-QZHKN3VN-DqU8C1o_.js → chunk-QZHKN3VN-iIS0rOO4.js} +1 -1
  40. package/dist/webdist/assets/{circle-check-X2gW5Lao.js → circle-check-CxTKIoUQ.js} +1 -1
  41. package/dist/webdist/assets/{circle-x-EHdG_-2l.js → circle-x-DtTsKVOs.js} +1 -1
  42. package/dist/webdist/assets/classDiagram-4FO5ZUOK-aMec4COx.js +1 -0
  43. package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-aMec4COx.js +1 -0
  44. package/dist/webdist/assets/{common-CR_Lyg91.js → common-BLhKAx-E.js} +1 -1
  45. package/dist/webdist/assets/{cose-bilkent-S5V4N54A-BYKK4Cf1.js → cose-bilkent-S5V4N54A-Q0XTfDW4.js} +1 -1
  46. package/dist/webdist/assets/{dagre-BM42HDAG-g4hhdP8F.js → dagre-BM42HDAG-BpEjp0Mo.js} +1 -1
  47. package/dist/webdist/assets/{diagram-2AECGRRQ-Dr_agpgo.js → diagram-2AECGRRQ-CQgcaxwM.js} +1 -1
  48. package/dist/webdist/assets/{diagram-5GNKFQAL-HhGT-1mW.js → diagram-5GNKFQAL-BAXLYnn8.js} +1 -1
  49. package/dist/webdist/assets/{diagram-KO2AKTUF-DAbDCiEN.js → diagram-KO2AKTUF-hS4UiX21.js} +1 -1
  50. package/dist/webdist/assets/{diagram-LMA3HP47-69Vaqnrj.js → diagram-LMA3HP47-BSH8Svf2.js} +1 -1
  51. package/dist/webdist/assets/{diagram-OG6HWLK6-BfoIa3zL.js → diagram-OG6HWLK6-ChkrDAcn.js} +1 -1
  52. package/dist/webdist/assets/{dialog-J9fwiM_6.js → dialog-Cif52qlh.js} +1 -1
  53. package/dist/webdist/assets/{dropdown-menu-CzGm9m23.js → dropdown-menu-CEXJQu9r.js} +1 -1
  54. package/dist/webdist/assets/{erDiagram-TEJ5UH35-_kvCnb2f.js → erDiagram-TEJ5UH35-CoSbJaoh.js} +1 -1
  55. package/dist/webdist/assets/{flowDiagram-I6XJVG4X-CyoD5NFD.js → flowDiagram-I6XJVG4X-CxmlDLjH.js} +1 -1
  56. package/dist/webdist/assets/{ganttDiagram-6RSMTGT7-BHbeuBsb.js → ganttDiagram-6RSMTGT7-D9DcHOHi.js} +1 -1
  57. package/dist/webdist/assets/{git-branch-DwkQBy50.js → git-branch-wVIAC1y2.js} +1 -1
  58. package/dist/webdist/assets/{gitGraphDiagram-PVQCEYII-CLiLu2eD.js → gitGraphDiagram-PVQCEYII-PZXq-NT0.js} +1 -1
  59. package/dist/webdist/assets/{index-sdlWNoLv.js → index-BQecSQnE.js} +9 -9
  60. package/dist/webdist/assets/index-DsKBTDFr.css +1 -0
  61. package/dist/webdist/assets/{infoDiagram-5YYISTIA-DUsHPouL.js → infoDiagram-5YYISTIA-OdiZto4C.js} +1 -1
  62. package/dist/webdist/assets/{ishikawaDiagram-YF4QCWOH-dmOwTeXN.js → ishikawaDiagram-YF4QCWOH-ByqCES2J.js} +1 -1
  63. package/dist/webdist/assets/{journeyDiagram-JHISSGLW-B-ROjE-9.js → journeyDiagram-JHISSGLW-iAvir64R.js} +1 -1
  64. package/dist/webdist/assets/{kanban-definition-UN3LZRKU-BcmeWTJv.js → kanban-definition-UN3LZRKU-Coq1b_4K.js} +1 -1
  65. package/dist/webdist/assets/{linear-D_7tH0LK.js → linear-CFHN7GNP.js} +1 -1
  66. package/dist/webdist/assets/{lock-B6DBc8H2.js → lock-C-qBN6Je.js} +1 -1
  67. package/dist/webdist/assets/{mermaid.core-COPgjG_G.js → mermaid.core-BbE5ZQQb.js} +4 -4
  68. package/dist/webdist/assets/{mindmap-definition-RKZ34NQL-DYa-6SsO.js → mindmap-definition-RKZ34NQL-BJI67qBG.js} +1 -1
  69. package/dist/webdist/assets/{pencil-dcCQp7PP.js → pencil-CLrsEWvO.js} +1 -1
  70. package/dist/webdist/assets/{personas-sJAzYoeu.js → personas-DjECKK03.js} +1 -1
  71. package/dist/webdist/assets/{pieDiagram-4H26LBE5-Btte96Dk.js → pieDiagram-4H26LBE5-ClFRQd-P.js} +1 -1
  72. package/dist/webdist/assets/{plus-BvjpqMip.js → plus-CnpqRmY0.js} +1 -1
  73. package/dist/webdist/assets/{quadrantDiagram-W4KKPZXB-NS-IUaYv.js → quadrantDiagram-W4KKPZXB-C6h_Xp8y.js} +1 -1
  74. package/dist/webdist/assets/{refresh-cw-CJO18sMX.js → refresh-cw-C92HxozQ.js} +1 -1
  75. package/dist/webdist/assets/{requirementDiagram-4Y6WPE33-6S8LhS5B.js → requirementDiagram-4Y6WPE33-otwWbKYv.js} +1 -1
  76. package/dist/webdist/assets/{rotate-ccw-Dg4hTNG6.js → rotate-ccw-CTVQYcfd.js} +1 -1
  77. package/dist/webdist/assets/{sankeyDiagram-5OEKKPKP-DzP_aBJ0.js → sankeyDiagram-5OEKKPKP-DbXQbMGj.js} +1 -1
  78. package/dist/webdist/assets/{sequenceDiagram-3UESZ5HK-DbM6WtvX.js → sequenceDiagram-3UESZ5HK-DVeUokYb.js} +1 -1
  79. package/dist/webdist/assets/{skeleton-BWg_uQbt.js → skeleton-Bgx_ejYi.js} +1 -1
  80. package/dist/webdist/assets/{sliders-horizontal-BEN8YNq8.js → sliders-horizontal-kCZOXks3.js} +1 -1
  81. package/dist/webdist/assets/{sparkles-BXKX67cv.js → sparkles-CK3xY-de.js} +1 -1
  82. package/dist/webdist/assets/{star-UYX7iaVC.js → star-Bl6Mcr3J.js} +1 -1
  83. package/dist/webdist/assets/{stateDiagram-AJRCARHV-DQd2hln9.js → stateDiagram-AJRCARHV-BTTdXeL2.js} +1 -1
  84. package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-PhwvulL3.js +1 -0
  85. package/dist/webdist/assets/{switch-CbjIB99k.js → switch-_1we_sIC.js} +1 -1
  86. package/dist/webdist/assets/{terminal-DVNTdtpQ.js → terminal-BITU7jD3.js} +1 -1
  87. package/dist/webdist/assets/{timeline-definition-PNZ67QCA-BnfK2zzg.js → timeline-definition-PNZ67QCA-DloHmMh_.js} +1 -1
  88. package/dist/webdist/assets/{trash-2-DkWlMuY9.js → trash-2-D0azs_Tt.js} +1 -1
  89. package/dist/webdist/assets/{useIsDesktop-DvCa8GTi.js → useIsDesktop-DaHDDrDA.js} +1 -1
  90. package/dist/webdist/assets/{vennDiagram-CIIHVFJN-tPTfe0OZ.js → vennDiagram-CIIHVFJN-Ccv3ZcVV.js} +1 -1
  91. package/dist/webdist/assets/{wardley-L42UT6IY-D7FeB03v.js → wardley-L42UT6IY-DpBTXmML.js} +1 -1
  92. package/dist/webdist/assets/{wardleyDiagram-YWT4CUSO-BDRCgpY_.js → wardleyDiagram-YWT4CUSO-CNQLbvwQ.js} +1 -1
  93. package/dist/webdist/assets/{wrench-BoYGiR8D.js → wrench-B_ypp0gU.js} +1 -1
  94. package/dist/webdist/assets/{xychartDiagram-2RQKCTM6-CVQgje-B.js → xychartDiagram-2RQKCTM6-Dkv0G4Ag.js} +1 -1
  95. package/dist/webdist/index.html +2 -2
  96. package/package.json +1 -1
  97. package/dist/webdist/assets/WorkspaceScreen-DA4l7OyH.js +0 -1
  98. package/dist/webdist/assets/channel-B5rf-0pa.js +0 -1
  99. package/dist/webdist/assets/classDiagram-4FO5ZUOK-B6iRj6y1.js +0 -1
  100. package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-B6iRj6y1.js +0 -1
  101. package/dist/webdist/assets/index-DKAl71q8.css +0 -1
  102. package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-DnzMvaiS.js +0 -1
package/README.md CHANGED
@@ -99,9 +99,10 @@ TurboLLM does the opposite:
99
99
  - **🔀 A gateway that loads models for you.** Name any model in your API request and TurboLLM
100
100
  loads it on demand, keeping your favorites hot in a small pool — so an agent that hops between
101
101
  models just works, with nothing to pre-wire.
102
- - **🔒 Offline-first & private.** No account, no backend, no internet required. Telemetry is
103
- **fully opt-in and off by default** choose Off and nothing but that one-time choice is ever
104
- sent, or opt in and see exactly what leaves your machine, verified in [Privacy](#privacy).
102
+ - **🔒 Offline-first & private.** No account, no backend, no internet required. Telemetry
103
+ **defaults to on** (usage + benchmarks + crash reports) so setup issues and real-hardware speed
104
+ get found change your level any time in Settings, and see exactly what leaves your machine,
105
+ verified in [Privacy](#privacy).
105
106
 
106
107
  ---
107
108
 
@@ -402,8 +403,8 @@ connected); **Remove** undoes it.
402
403
  <br/>
403
404
 
404
405
  - A **~7 MB npm package** on Node — no Electron, no bundled Chromium, no Python.
405
- - **Offline-first** — no account, no backend, no internet required. Telemetry is opt-in and off
406
- by default; see [Privacy](#privacy).
406
+ - **Offline-first** — no account, no backend, no internet required. Telemetry defaults to on,
407
+ changeable any time; see [Privacy](#privacy).
407
408
  - **Windows · macOS · Linux**, with a CPU fallback when there's no GPU.
408
409
 
409
410
  </details>
@@ -589,18 +590,19 @@ TurboLLM is **offline-first**: core local use needs no account, no backend, and
589
590
  choice.** That's not a promise you have to take on faith: the client is source-available, so the
590
591
  claim is checkable against the actual code that ships.
591
592
 
592
- **Telemetry is opt-in, and off by default.** On first run you choose one of three levels, with no
593
- pre-selected option:
593
+ **Telemetry defaults to on** (Usage + benchmarks + crash reports), so setup problems and
594
+ real-hardware speed get found and fixed — verifiable telemetry only helps if enough people are
595
+ actually sending it. Change your level any time from **Settings → Privacy & telemetry**:
594
596
 
595
597
  - **Off** — sends only that one-time choice, nothing else, ever.
596
598
  - **Anonymous usage + benchmarks** — which features you use, your hardware, model names, and
597
599
  measured speed. Never prompts, files, paths, or keys.
598
- - **+ Crash reports** — adds error fingerprints (never your content).
600
+ - **Usage + benchmarks + crash reports** (default) — adds error fingerprints (never your content).
599
601
 
600
602
  You can preview the exact payload before anything is sent, and inspect a running log of every
601
- event this machine has *actually* transmitted, from **Settings Privacy & telemetry**. For
602
- scripted or unattended use, `--no-telemetry` (or `TURBOLLM_TELEMETRY=off`) is a hard, one-way kill
603
- switch, independent of the saved level.
603
+ event this machine has *actually* transmitted, from the same Settings section. For scripted or
604
+ unattended use, `--no-telemetry` (or `TURBOLLM_TELEMETRY=off`) is a hard, one-way kill switch,
605
+ independent of the saved level.
604
606
 
605
607
  ---
606
608
 
@@ -621,13 +623,13 @@ Marks reflect mid-2026; verify the moving rows against each tool's current docs.
621
623
  | Speculative decoding (draft / MTP) | ✅ | ✅ | ◐ env flag | ❌ |
622
624
  | Web UI from any LAN device | ✅ | ❌ | ❌ | ✅ |
623
625
  | **Lightweight** (no Electron / no Python) | ✅ npm | ❌ Electron | ✅ Go | ❌ Python |
624
- | Offline-first · **opt-in, verifiable telemetry** | ✅ verifiable (source-available) | ◐ closed app — not verifiable | ✅ | ✅ |
626
+ | Offline-first · **verifiable telemetry** (on by default) | ✅ verifiable (source-available) | ◐ closed app — not verifiable | ✅ | ✅ |
625
627
 
626
628
  LM Studio and Ollama both added Anthropic `/v1/messages` endpoints in 2026, so the API rows are
627
629
  now parity — Claude Code works against any of them. TurboLLM's durable edges are **any engine
628
630
  including community forks**, **benchmark-based auto-tuning with a VRAM-fit verdict + measured t/s
629
- before you commit**, and **telemetry you can actually verify** — opt-in, off by default, and
630
- checkable against source-available code rather than taken on trust.
631
+ before you commit**, and **telemetry you can actually verify** — on by default, changeable any
632
+ time, and checkable against source-available code rather than taken on trust.
631
633
 
632
634
  Prefer Open WebUI's chat breadth? It works great pointed at TurboLLM's OpenAI endpoint.
633
635
 
package/dist/cli.js CHANGED
@@ -109,7 +109,7 @@ function defaultConfig() {
109
109
  autoMemoryEnabled: false,
110
110
  experimental: { memory: false, cloudDeploy: false }
111
111
  },
112
- telemetry: { level: "unset", machineId: "" },
112
+ telemetry: { level: "full", machineId: "" },
113
113
  apiKeys: [],
114
114
  engines: [],
115
115
  customEngineSources: [],
@@ -223,7 +223,7 @@ function migrate(raw, from) {
223
223
  cfg2[k] = v;
224
224
  }
225
225
  }
226
- if (from < 4 && cfg2.telemetry.level === "off") cfg2.telemetry.level = "unset";
226
+ if (from < 4 && cfg2.telemetry.level === "off") cfg2.telemetry.level = "full";
227
227
  return cfg2;
228
228
  }
229
229
  function splitLaunchArgs(args) {
@@ -404,8 +404,9 @@ function normalize(c) {
404
404
  c.version = SCHEMA_VERSION;
405
405
  }
406
406
  function normalizeTelemetryLevel(level) {
407
- if (level === "unset" || level === "off" || level === "anon" || level === "full") return level;
407
+ if (level === "off" || level === "anon" || level === "full") return level;
408
408
  if (level === "benchmarks" || level === "anonymous") return "anon";
409
+ if (level === void 0 || level === null || level === "unset") return "full";
409
410
  return "off";
410
411
  }
411
412
  function validate(c) {
@@ -8077,7 +8078,11 @@ var BenchRunner = class {
8077
8078
  * see `reportModelLoad`'s own once-only claim — so a re-tune later on
8078
8079
  * cannot overwrite it. */
8079
8080
  reportFirstLoad(outcome, failReason) {
8080
- if (this.telemetry) reportModelLoad(this.store.dir(), this.telemetry, outcome, failReason);
8081
+ if (!this.telemetry) return;
8082
+ if (claimOnce(this.store.dir(), "once:onboarding_step:first_load")) {
8083
+ this.telemetry.emit("onboarding_step", { step: "first_load", outcome });
8084
+ }
8085
+ reportModelLoad(this.store.dir(), this.telemetry, outcome, failReason);
8081
8086
  }
8082
8087
  /** Stop the engine — force-killed immediately when the run is cancelled (the user is actively
8083
8088
  * waiting for it to stop, ADR-220), gracefully otherwise (lets llama-server release resources
@@ -11614,14 +11619,14 @@ function runMcpServer(baseUrl, version2) {
11614
11619
  // src/server.ts
11615
11620
  import { Hono } from "hono";
11616
11621
  import { cors } from "hono/cors";
11617
- import { existsSync as existsSync24, readFileSync as readFileSync17, statSync as statSync6 } from "fs";
11622
+ import { existsSync as existsSync26, readFileSync as readFileSync17, statSync as statSync6 } from "fs";
11618
11623
  import { fileURLToPath } from "url";
11619
11624
  import { dirname as dirname11, join as join33, normalize as normalize2 } from "path";
11620
11625
  import { Agent, setGlobalDispatcher } from "undici";
11621
11626
 
11622
11627
  // src/api/routes.ts
11623
11628
  import { streamSSE as streamSSE2 } from "hono/streaming";
11624
- 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";
11629
+ import { existsSync as existsSync24, mkdirSync as mkdirSync16, readFileSync as readFileSync14, readdirSync as readdirSync10, realpathSync, rmSync as rmSync14, statSync as statSync5, writeFileSync as writeFileSync14 } from "fs";
11625
11630
  import { execFileSync as execFileSync6 } from "child_process";
11626
11631
  import { basename as basename3, dirname as dirname9, join as join29, resolve } from "path";
11627
11632
 
@@ -13639,6 +13644,7 @@ function enabledFeatures() {
13639
13644
  }
13640
13645
 
13641
13646
  // src/terminal/terminal-routes.ts
13647
+ import { existsSync as existsSync21 } from "fs";
13642
13648
  import { createRequire as createRequire2 } from "module";
13643
13649
 
13644
13650
  // src/code/session-auth.ts
@@ -14166,6 +14172,7 @@ var TerminalManager = class {
14166
14172
 
14167
14173
  // src/code/worktree.ts
14168
14174
  import { spawn as spawn5 } from "child_process";
14175
+ import { existsSync as existsSync20 } from "fs";
14169
14176
  import { appendFile, readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
14170
14177
  import { join as join25 } from "path";
14171
14178
  var WORKTREES_SUBDIR = join25(".turbollm", "worktrees");
@@ -14288,6 +14295,7 @@ async function createSessionWorktree(params) {
14288
14295
  return { ok: true, path, branch };
14289
14296
  }
14290
14297
  async function removeSessionWorktree(repoRoot, worktreePath) {
14298
+ if (!existsSync20(worktreePath) || !existsSync20(repoRoot)) return { ok: true };
14291
14299
  const r = await runGit(repoRoot, ["worktree", "remove", worktreePath], 6e4);
14292
14300
  if (r.ok) return { ok: true };
14293
14301
  const err7 = (r.stderr || r.stdout).trim();
@@ -14384,6 +14392,16 @@ function registerTerminalRoutes(app2, d) {
14384
14392
  if (agent === "turbollm") {
14385
14393
  return err2(c, 400, "invalid_input", "This session uses the built-in chat UI, not a terminal agent.");
14386
14394
  }
14395
+ const cwd = agentCwd(run);
14396
+ if (!existsSync21(cwd)) {
14397
+ const isWorktree = !!run.worktreePath && cwd === run.worktreePath;
14398
+ return err2(
14399
+ c,
14400
+ 400,
14401
+ "cwd_missing",
14402
+ isWorktree ? `This session's worktree is gone: ${cwd}. It was probably removed outside TurboLLM (\`git worktree remove\`, or deleting the folder). The session's branch and any commits on it are unaffected \u2014 delete this session and start a new one on the same repo.` : `This session's folder no longer exists: ${cwd}. It may have been moved, renamed or deleted, or it lives on a drive that isn't connected. Reconnect or restore it, or delete this session.`
14403
+ );
14404
+ }
14387
14405
  const b = await body2(c);
14388
14406
  const cols = typeof b.cols === "number" && b.cols > 0 ? Math.floor(b.cols) : 80;
14389
14407
  const rows = typeof b.rows === "number" && b.rows > 0 ? Math.floor(b.rows) : 24;
@@ -14412,7 +14430,7 @@ function registerTerminalRoutes(app2, d) {
14412
14430
  mode,
14413
14431
  firstMessage
14414
14432
  );
14415
- const terminalId = m.create(agentCwd(run), run.id, cols, rows, launchCommand);
14433
+ const terminalId = m.create(cwd, run.id, cols, rows, launchCommand);
14416
14434
  if (!run.terminalLaunchedOnce) d.db.updateAgentRun(run.id, { terminalLaunchedOnce: true });
14417
14435
  return c.json({ terminalId }, 201);
14418
14436
  } catch (e) {
@@ -14938,7 +14956,7 @@ function catalogEngine(id) {
14938
14956
 
14939
14957
  // src/engines/cuda-provision.ts
14940
14958
  import { execFile as execFile11 } from "child_process";
14941
- import { cpSync as cpSync2, existsSync as existsSync20, mkdirSync as mkdirSync14, readdirSync as readdirSync9, rmSync as rmSync13 } from "fs";
14959
+ import { cpSync as cpSync2, existsSync as existsSync22, mkdirSync as mkdirSync14, readdirSync as readdirSync9, rmSync as rmSync13 } from "fs";
14942
14960
  import { tmpdir } from "os";
14943
14961
  import { join as join26 } from "path";
14944
14962
  import { promisify as promisify8 } from "util";
@@ -14997,7 +15015,7 @@ async function provisionCuda(dataDir, onProgress, signal) {
14997
15015
  });
14998
15016
  const root = join26(dataDir, "cuda", version2);
14999
15017
  const binDir = join26(root, "bin");
15000
- if (existsSync20(join26(binDir, "nvcc.exe"))) {
15018
+ if (existsSync22(join26(binDir, "nvcc.exe"))) {
15001
15019
  onProgress({ phase: "assembling", message: `CUDA ${version2} already downloaded \u2014 reusing it.` });
15002
15020
  return { root, binDir, version: version2 };
15003
15021
  }
@@ -15028,12 +15046,12 @@ async function provisionCuda(dataDir, onProgress, signal) {
15028
15046
  mkdirSync14(exDir, { recursive: true });
15029
15047
  onProgress({ phase: "extracting", message: `Extracting ${id}\u2026` });
15030
15048
  await extractArchive(zip, exDir);
15031
- const inner = readdirSync9(exDir).map((n) => join26(exDir, n)).find((p) => existsSync20(join26(p, "include")) || existsSync20(join26(p, "bin")) || existsSync20(join26(p, "lib")));
15049
+ const inner = readdirSync9(exDir).map((n) => join26(exDir, n)).find((p) => existsSync22(join26(p, "include")) || existsSync22(join26(p, "bin")) || existsSync22(join26(p, "lib")));
15032
15050
  const srcRoot = inner ?? join26(exDir, readdirSync9(exDir)[0] ?? "");
15033
15051
  cpSync2(srcRoot, stageRoot, { recursive: true, force: true });
15034
15052
  rmSync13(zip, { force: true });
15035
15053
  }
15036
- if (!existsSync20(join26(stageRoot, "bin", "nvcc.exe"))) {
15054
+ if (!existsSync22(join26(stageRoot, "bin", "nvcc.exe"))) {
15037
15055
  throw new Error("Assembled CUDA toolkit is missing bin/nvcc.exe \u2014 the component set may have changed.");
15038
15056
  }
15039
15057
  rmSync13(root, { recursive: true, force: true });
@@ -15118,7 +15136,7 @@ function recommendEngines(p, engines) {
15118
15136
  }
15119
15137
 
15120
15138
  // src/artifacts/screenshot.ts
15121
- import { existsSync as existsSync21 } from "fs";
15139
+ import { existsSync as existsSync23 } from "fs";
15122
15140
  import { execFileSync as execFileSync5 } from "child_process";
15123
15141
  import { join as join27 } from "path";
15124
15142
  function chromeCandidates() {
@@ -15162,7 +15180,7 @@ function chromeCandidates() {
15162
15180
  function findChrome() {
15163
15181
  for (const p of chromeCandidates()) {
15164
15182
  try {
15165
- if (p && existsSync21(p)) return p;
15183
+ if (p && existsSync23(p)) return p;
15166
15184
  } catch {
15167
15185
  }
15168
15186
  }
@@ -15387,7 +15405,7 @@ function registerApi(app2, d) {
15387
15405
  app2.get("/api/v1/engines", (c) => {
15388
15406
  const { engines, activeEngineId } = d.registry.list();
15389
15407
  const liveKeys = new Set(engines.map((e) => customSourceKey(e)));
15390
- const customDisabled = d.registry.customSources().filter((s) => !liveKeys.has(customSourceKey(s))).map((s) => ({ ...s, binPathExists: existsSync22(s.binPath) }));
15408
+ const customDisabled = d.registry.customSources().filter((s) => !liveKeys.has(customSourceKey(s))).map((s) => ({ ...s, binPathExists: existsSync24(s.binPath) }));
15391
15409
  return c.json({ engines, activeEngineId, customDisabled });
15392
15410
  });
15393
15411
  let provisionAbort = null;
@@ -15418,7 +15436,7 @@ function registerApi(app2, d) {
15418
15436
  };
15419
15437
  });
15420
15438
  const mlxVenvPy = join29(root, "mlx", "venv", process.platform === "win32" ? "Scripts/python.exe" : "bin/python");
15421
- const mlxInstalledOnDisk = existsSync22(mlxVenvPy);
15439
+ const mlxInstalledOnDisk = existsSync24(mlxVenvPy);
15422
15440
  const mlxEngine = regEngines.find((e) => e.kind === "mlx");
15423
15441
  const mlx = {
15424
15442
  supported: process.platform === "darwin",
@@ -15620,17 +15638,17 @@ function registerApi(app2, d) {
15620
15638
  "venv",
15621
15639
  process.platform === "win32" ? "Scripts/python.exe" : "bin/python"
15622
15640
  );
15623
- installed2 = existsSync22(pyPath);
15641
+ installed2 = existsSync24(pyPath);
15624
15642
  enabled = regEngines.some((x) => x.kind === e.kind);
15625
15643
  } else if (e.id === "turboquant") {
15626
15644
  const tqDir = join29(enginesRoot, "turboquant");
15627
- installed2 = existsSync22(tqDir);
15645
+ installed2 = existsSync24(tqDir);
15628
15646
  enabled = regEngines.some((x) => /[\\/]engines[\\/]turboquant[\\/]/.test(x.binPath));
15629
15647
  } else if (e.id === "koboldcpp") {
15630
- installed2 = existsSync22(koboldcppBinPath(enginesRoot));
15648
+ installed2 = existsSync24(koboldcppBinPath(enginesRoot));
15631
15649
  enabled = regEngines.some((x) => x.kind === "koboldcpp");
15632
15650
  } else if (e.id === "llamafile") {
15633
- installed2 = existsSync22(llamafileBinPath(enginesRoot));
15651
+ installed2 = existsSync24(llamafileBinPath(enginesRoot));
15634
15652
  enabled = regEngines.some((x) => x.kind === "llamafile");
15635
15653
  }
15636
15654
  const srcEng = regEngines.find(
@@ -15857,7 +15875,7 @@ function registerApi(app2, d) {
15857
15875
  d.provision.start("turboquant");
15858
15876
  if (upgrade) {
15859
15877
  const tqDir = join29(root, "turboquant");
15860
- if (existsSync22(tqDir)) rmSync14(tqDir, { recursive: true, force: true });
15878
+ if (existsSync24(tqDir)) rmSync14(tqDir, { recursive: true, force: true });
15861
15879
  }
15862
15880
  const bin = await provisionTurboquant(
15863
15881
  root,
@@ -15893,7 +15911,7 @@ function registerApi(app2, d) {
15893
15911
  d.provision.start("koboldcpp");
15894
15912
  if (upgrade) {
15895
15913
  const dir = koboldcppDir(root);
15896
- if (existsSync22(dir)) rmSync14(dir, { recursive: true, force: true });
15914
+ if (existsSync24(dir)) rmSync14(dir, { recursive: true, force: true });
15897
15915
  }
15898
15916
  const rt = await ensureKoboldcpp(root, hasNvidia, (p) => d.provision.progress(p.phase, p.pct, p.part, p.parts));
15899
15917
  const eng = d.registry.addKoboldcpp(`KoboldCpp (${rt.version})`, rt.binPath, rt.version);
@@ -15923,7 +15941,7 @@ function registerApi(app2, d) {
15923
15941
  d.provision.start("llamafile");
15924
15942
  if (upgrade) {
15925
15943
  const dir = llamafileDir(root);
15926
- if (existsSync22(dir)) rmSync14(dir, { recursive: true, force: true });
15944
+ if (existsSync24(dir)) rmSync14(dir, { recursive: true, force: true });
15927
15945
  }
15928
15946
  const rt = await ensureLlamafile(root, (p) => d.provision.progress(p.phase, p.pct, p.part, p.parts));
15929
15947
  const eng = d.registry.addLlamafile(`llamafile (${rt.version})`, rt.binPath, rt.version);
@@ -16025,7 +16043,7 @@ function registerApi(app2, d) {
16025
16043
  if (purge && eng) {
16026
16044
  const enginesRoot = join29(d.store.dir(), "engines");
16027
16045
  const purgeDir = engineInstallDir(eng, enginesRoot);
16028
- if (purgeDir && existsSync22(purgeDir)) {
16046
+ if (purgeDir && existsSync24(purgeDir)) {
16029
16047
  rmSync14(purgeDir, { recursive: true, force: true });
16030
16048
  }
16031
16049
  d.registry.forgetCustomSource(customSourceKey(eng));
@@ -16132,7 +16150,7 @@ function registerApi(app2, d) {
16132
16150
  const raw = (c.req.query("path") ?? "").trim();
16133
16151
  if (!raw) return err3(c, 400, "invalid_input", "path is required.");
16134
16152
  const cwd = resolve(raw);
16135
- if (!existsSync22(cwd) || !statSync5(cwd).isDirectory()) {
16153
+ if (!existsSync24(cwd) || !statSync5(cwd).isDirectory()) {
16136
16154
  return c.json({ isRepo: false, branch: "", branches: [] });
16137
16155
  }
16138
16156
  const git2 = (args) => execFileSync6("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 5e3, windowsHide: true }).trim();
@@ -16255,12 +16273,12 @@ function registerApi(app2, d) {
16255
16273
  const raw = (b.path ?? "").trim();
16256
16274
  if (!raw) return err3(c, 400, "invalid_config_value", "Enter the path to your ComfyUI folder.");
16257
16275
  const root = resolve(raw);
16258
- if (!existsSync22(root) || !statSync5(root).isDirectory()) {
16276
+ if (!existsSync24(root) || !statSync5(root).isDirectory()) {
16259
16277
  return err3(c, 400, "invalid_config_value", "That folder does not exist.");
16260
16278
  }
16261
16279
  let customNodes;
16262
16280
  if (basename3(root).toLowerCase() === "custom_nodes") customNodes = root;
16263
- else if (existsSync22(join29(root, "custom_nodes"))) customNodes = join29(root, "custom_nodes");
16281
+ else if (existsSync24(join29(root, "custom_nodes"))) customNodes = join29(root, "custom_nodes");
16264
16282
  else {
16265
16283
  return err3(c, 400, "invalid_config_value", "No 'custom_nodes' folder here. Point me at your ComfyUI folder or its custom_nodes folder.");
16266
16284
  }
@@ -16281,7 +16299,7 @@ function registerApi(app2, d) {
16281
16299
  });
16282
16300
  app2.post("/api/v1/comfyui/uninstall", (c) => {
16283
16301
  const dir = d.store.snapshot().comfyui.gatePath;
16284
- if (dir && existsSync22(dir)) {
16302
+ if (dir && existsSync24(dir)) {
16285
16303
  try {
16286
16304
  rmSync14(dir, { recursive: true, force: true });
16287
16305
  } catch (e) {
@@ -16316,7 +16334,7 @@ function registerApi(app2, d) {
16316
16334
  });
16317
16335
  while (!aborted) {
16318
16336
  const path = d.manager.logPath();
16319
- if (path && existsSync22(path)) {
16337
+ if (path && existsSync24(path)) {
16320
16338
  const lines = readFileSync14(path, "utf8").split("\n");
16321
16339
  for (; sent < lines.length - 1; sent++) {
16322
16340
  await stream.writeSSE({ event: "line", data: JSON.stringify({ line: lines[sent].replace(/\r$/, "") }) });
@@ -16715,7 +16733,7 @@ function registerApi(app2, d) {
16715
16733
  const b = await body3(c);
16716
16734
  const dir = (b.dir ?? "").trim();
16717
16735
  if (!dir || !/^([a-zA-Z]:[\\/]|[\\/])/.test(dir)) return err3(c, 400, "invalid_config_value", "Path must be absolute.");
16718
- if (!existsSync22(dir)) return err3(c, 400, "invalid_config_value", "That folder does not exist.");
16736
+ if (!existsSync24(dir)) return err3(c, 400, "invalid_config_value", "That folder does not exist.");
16719
16737
  try {
16720
16738
  d.store.update((cfg2) => {
16721
16739
  if (!cfg2.modelDirs.includes(dir)) cfg2.modelDirs.push(dir);
@@ -16951,13 +16969,6 @@ function settingsPayload(d) {
16951
16969
  lanBind: cfg2.daemon.lanBind,
16952
16970
  requireApiKey: cfg2.daemon.requireApiKey,
16953
16971
  telemetryLevel,
16954
- // Whether the user has ever actually answered (ADR-299 Decision 4).
16955
- // `telemetryLevel` above deliberately collapses the first-run 'unset'
16956
- // sentinel to 'off' so every consumer fails safe — but that also makes
16957
- // "never asked" indistinguishable from "chose Off", and the first-run
16958
- // consent card needs exactly that distinction. Hence a separate flag
16959
- // rather than leaking 'unset' into the level enum.
16960
- telemetryDecided: cfg2.telemetry.level !== "unset",
16961
16972
  modelDefaults: cfg2.modelDefaults,
16962
16973
  comfyui: cfg2.comfyui,
16963
16974
  gateway: cfg2.gateway,
@@ -17139,7 +17150,7 @@ function cleanModelName(p) {
17139
17150
  return basename3(p).replace(/\.gguf$/i, "");
17140
17151
  }
17141
17152
  function readTail2(path, n) {
17142
- if (!path || !existsSync22(path)) return [];
17153
+ if (!path || !existsSync24(path)) return [];
17143
17154
  try {
17144
17155
  const lines = readFileSync14(path, "utf8").replace(/[\r\n]+$/, "").split("\n").map((l) => l.replace(/\r$/, ""));
17145
17156
  return lines.length > n ? lines.slice(-n) : lines;
@@ -17153,7 +17164,7 @@ function listWindowsDrives() {
17153
17164
  for (let i = 65; i <= 90; i++) {
17154
17165
  const root = `${String.fromCharCode(i)}:\\`;
17155
17166
  try {
17156
- if (existsSync22(root)) drives.push({ name: root, path: root, isDir: true });
17167
+ if (existsSync24(root)) drives.push({ name: root, path: root, isDir: true });
17157
17168
  } catch {
17158
17169
  }
17159
17170
  }
@@ -19877,7 +19888,7 @@ function subscribeToBuffer(buffer, emitter, opts) {
19877
19888
  }
19878
19889
 
19879
19890
  // src/code/revert.ts
19880
- import { existsSync as existsSync23, readFileSync as readFileSync16, writeFileSync as writeFileSync15 } from "fs";
19891
+ import { existsSync as existsSync25, readFileSync as readFileSync16, writeFileSync as writeFileSync15 } from "fs";
19881
19892
  import { resolve as resolve4 } from "path";
19882
19893
  import { applyPatch, parsePatch, reversePatch } from "diff";
19883
19894
  function revertFileEdits(messages, repoRoot) {
@@ -19900,7 +19911,7 @@ function revertFileEdits(messages, repoRoot) {
19900
19911
  continue;
19901
19912
  }
19902
19913
  const absPath = resolve4(repoRoot, path);
19903
- if (!existsSync23(absPath)) {
19914
+ if (!existsSync25(absPath)) {
19904
19915
  failed.push(path);
19905
19916
  continue;
19906
19917
  }
@@ -20733,6 +20744,7 @@ function registerCodeRoutes(app2, d) {
20733
20744
  // src/gateway/gateway.ts
20734
20745
  import { randomUUID as randomUUID12 } from "crypto";
20735
20746
  import { streamSSE as streamSSE4 } from "hono/streaming";
20747
+ import { createPatch } from "diff";
20736
20748
 
20737
20749
  // src/gateway/anthropic.ts
20738
20750
  import { randomUUID as randomUUID10 } from "crypto";
@@ -20918,11 +20930,12 @@ function mapFromOpenAI(oai, modelName) {
20918
20930
  usage: { input_tokens: usage?.prompt_tokens ?? 0, output_tokens: usage?.completion_tokens ?? 0 }
20919
20931
  };
20920
20932
  }
20921
- async function* streamToAnthropic(oaiStream, modelName, msgId, onUsage, onLive) {
20933
+ async function* streamToAnthropic(oaiStream, modelName, msgId, onUsage, onLive, onToolCalls) {
20922
20934
  let blockIdx = 0;
20923
20935
  let inThinking = false;
20924
20936
  let inText = false;
20925
20937
  let activeToolIdx = -1;
20938
+ const toolAcc = /* @__PURE__ */ new Map();
20926
20939
  let stopReason = "end_turn";
20927
20940
  let outputTokens = 0;
20928
20941
  let inputTokens = 0;
@@ -21014,6 +21027,14 @@ async function* streamToAnthropic(oaiStream, modelName, msgId, onUsage, onLive)
21014
21027
  blockIdx++;
21015
21028
  }
21016
21029
  for (const tc of delta.tool_calls) {
21030
+ let acc = toolAcc.get(tc.index);
21031
+ if (!acc) {
21032
+ acc = { id: "", name: "", args: "" };
21033
+ toolAcc.set(tc.index, acc);
21034
+ }
21035
+ if (!acc.id && tc.id) acc.id = tc.id;
21036
+ if (!acc.name && tc.function?.name) acc.name = tc.function.name;
21037
+ if (tc.function?.arguments) acc.args += tc.function.arguments;
21017
21038
  if (tc.index !== activeToolIdx) {
21018
21039
  if (activeToolIdx >= 0) {
21019
21040
  yield cbStop(blockIdx);
@@ -21071,6 +21092,19 @@ async function* streamToAnthropic(oaiStream, modelName, msgId, onUsage, onLive)
21071
21092
  } catch {
21072
21093
  }
21073
21094
  }
21095
+ if (onToolCalls) {
21096
+ const calls = [];
21097
+ for (const acc of toolAcc.values()) {
21098
+ try {
21099
+ calls.push({ id: acc.id, name: acc.name, input: JSON.parse(acc.args) });
21100
+ } catch {
21101
+ }
21102
+ }
21103
+ try {
21104
+ onToolCalls(calls);
21105
+ } catch {
21106
+ }
21107
+ }
21074
21108
  }
21075
21109
  }
21076
21110
  function sse(event, data) {
@@ -21484,6 +21518,7 @@ function registerGateway(app2, d) {
21484
21518
  }
21485
21519
  const { token: anthropicToken, codeSessionId: anthropicCodeSessionId } = resolveCodeSession(c);
21486
21520
  if (anthropicCodeSessionId) {
21521
+ commitConfirmedCodeToolCalls(d, anthropicCodeSessionId, req);
21487
21522
  const override = sessionAuth.getThinkingBudgetForToken(anthropicToken);
21488
21523
  if (override !== null) {
21489
21524
  req.thinking = override > 0 ? { type: "enabled", budget_tokens: override } : void 0;
@@ -21598,6 +21633,15 @@ function registerGateway(app2, d) {
21598
21633
  d.manager.setLiveGen(live);
21599
21634
  } catch {
21600
21635
  }
21636
+ },
21637
+ // Coding-activity attribution for terminal-agent sessions — see
21638
+ // observeCodeSessionTurn. Nothing to attribute for any other client.
21639
+ (calls) => {
21640
+ if (!anthropicCodeSessionId) return;
21641
+ try {
21642
+ observeCodeSessionTurn(d, anthropicCodeSessionId, calls);
21643
+ } catch {
21644
+ }
21601
21645
  }
21602
21646
  );
21603
21647
  return streamSSE4(c, async (stream) => {
@@ -21616,6 +21660,12 @@ function registerGateway(app2, d) {
21616
21660
  try {
21617
21661
  const oaiRes = await res.json();
21618
21662
  recordOpenAiUsage(d, oaiRes, "anthropic", req.model ?? null, anthropicCodeSessionId, Date.now() - requestStart);
21663
+ if (anthropicCodeSessionId) {
21664
+ try {
21665
+ observeCodeSessionTurn(d, anthropicCodeSessionId, openAiToolCalls(oaiRes));
21666
+ } catch {
21667
+ }
21668
+ }
21619
21669
  return c.json(mapFromOpenAI(oaiRes, modelName));
21620
21670
  } finally {
21621
21671
  d.manager.generationEnd();
@@ -21764,6 +21814,82 @@ function registerGateway(app2, d) {
21764
21814
  return new Response(res.body, { status: res.status, headers: res.headers });
21765
21815
  });
21766
21816
  }
21817
+ var CODE_ACTIVITY_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "multiedit"]);
21818
+ function openAiToolCalls(oai) {
21819
+ const raw = oai.choices?.[0]?.message?.tool_calls;
21820
+ if (!Array.isArray(raw)) return [];
21821
+ const calls = [];
21822
+ for (const tc of raw) {
21823
+ try {
21824
+ calls.push({ id: tc.id ?? "", name: tc.function?.name ?? "", input: JSON.parse(tc.function?.arguments ?? "") });
21825
+ } catch {
21826
+ }
21827
+ }
21828
+ return calls;
21829
+ }
21830
+ var MAX_DIFF_INPUT_CHARS = 64 * 1024;
21831
+ var pendingCodeToolCalls = /* @__PURE__ */ new Map();
21832
+ var MAX_PENDING_PER_SESSION = 256;
21833
+ function observeCodeSessionTurn(d, codeSessionId, calls) {
21834
+ try {
21835
+ const run = d.db.getAgentRun(codeSessionId);
21836
+ if (!run) return;
21837
+ let pending2 = pendingCodeToolCalls.get(codeSessionId);
21838
+ for (const call of calls) {
21839
+ const name = call.name.toLowerCase();
21840
+ if (!CODE_ACTIVITY_TOOLS.has(name)) continue;
21841
+ if (!call.id) continue;
21842
+ const input = call.input ?? {};
21843
+ const path = typeof input.file_path === "string" ? input.file_path : "";
21844
+ if (!path) continue;
21845
+ let record;
21846
+ if (name === "write") {
21847
+ record = { id: call.id, name: "write", args: { path } };
21848
+ } else if (name === "edit") {
21849
+ const oldString = typeof input.old_string === "string" ? input.old_string : "";
21850
+ const newString = typeof input.new_string === "string" ? input.new_string : "";
21851
+ record = oldString.length + newString.length > MAX_DIFF_INPUT_CHARS ? { id: call.id, name: "edit", args: { path } } : { id: call.id, name: "edit", args: { path }, diff: createPatch(path, oldString, newString) };
21852
+ } else {
21853
+ record = { id: call.id, name: "edit", args: { path } };
21854
+ }
21855
+ if (!pending2) {
21856
+ pending2 = /* @__PURE__ */ new Map();
21857
+ pendingCodeToolCalls.set(codeSessionId, pending2);
21858
+ }
21859
+ pending2.set(call.id, record);
21860
+ }
21861
+ while (pending2 && pending2.size > MAX_PENDING_PER_SESSION) {
21862
+ const oldest = pending2.keys().next().value;
21863
+ if (oldest === void 0) break;
21864
+ pending2.delete(oldest);
21865
+ }
21866
+ d.db.updateAgentRun(run.id, { status: "done", endedAt: (/* @__PURE__ */ new Date()).toISOString() });
21867
+ } catch {
21868
+ }
21869
+ }
21870
+ function commitConfirmedCodeToolCalls(d, codeSessionId, req) {
21871
+ try {
21872
+ const pending2 = pendingCodeToolCalls.get(codeSessionId);
21873
+ if (!pending2 || pending2.size === 0) return;
21874
+ const confirmed = [];
21875
+ for (const msg2 of req.messages ?? []) {
21876
+ const raw = msg2.content;
21877
+ if (!Array.isArray(raw)) continue;
21878
+ for (const block of raw) {
21879
+ if (block.type !== "tool_result") continue;
21880
+ const record = pending2.get(block.tool_use_id);
21881
+ if (!record) continue;
21882
+ pending2.delete(block.tool_use_id);
21883
+ if (block.is_error !== true) confirmed.push(record);
21884
+ }
21885
+ }
21886
+ if (confirmed.length === 0) return;
21887
+ const run = d.db.getAgentRun(codeSessionId);
21888
+ if (!run) return;
21889
+ d.db.addMessage(run.convId, "assistant", "", { toolCalls: confirmed });
21890
+ } catch {
21891
+ }
21892
+ }
21767
21893
  function recordOpenAiUsage(d, oai, source, modelKey, codeSessionId = null, durationMs = null) {
21768
21894
  try {
21769
21895
  const usage = oai.usage;
@@ -21924,10 +22050,10 @@ function createApp(d) {
21924
22050
  return c.json({ error: { code: "not_found", message: "Unknown endpoint." } }, 404);
21925
22051
  }
21926
22052
  let file = normalize2(join33(WEB_ROOT, path || "index.html"));
21927
- if (!file.startsWith(WEB_ROOT) || !existsSync24(file) || statSync6(file).isDirectory()) {
22053
+ if (!file.startsWith(WEB_ROOT) || !existsSync26(file) || statSync6(file).isDirectory()) {
21928
22054
  file = join33(WEB_ROOT, "index.html");
21929
22055
  }
21930
- if (!existsSync24(file)) return c.text("web ui not built \u2014 run `npm run build:web`", 500);
22056
+ if (!existsSync26(file)) return c.text("web ui not built \u2014 run `npm run build:web`", 500);
21931
22057
  return new Response(readFileSync17(file), { status: 200, headers: { "Content-Type": contentType(file) } });
21932
22058
  });
21933
22059
  return app2;
@@ -21958,7 +22084,7 @@ import { mkdirSync as mkdirSync18, readFileSync as readFileSync18, readdirSync a
21958
22084
  import { join as join35 } from "path";
21959
22085
 
21960
22086
  // src/tunnel/provision.ts
21961
- import { chmodSync as chmodSync3, existsSync as existsSync25, mkdirSync as mkdirSync17, rmSync as rmSync15 } from "fs";
22087
+ import { chmodSync as chmodSync3, existsSync as existsSync27, mkdirSync as mkdirSync17, rmSync as rmSync15 } from "fs";
21962
22088
  import { join as join34 } from "path";
21963
22089
  var CLOUDFLARED_REPO = "cloudflare/cloudflared";
21964
22090
  function cloudflaredDir(dataDir) {
@@ -21994,7 +22120,7 @@ async function ensureCloudflared(dataDir, onProgress, signal) {
21994
22120
  const binPath = cloudflaredBinPath(dataDir);
21995
22121
  const rel = await latestGithubRelease(CLOUDFLARED_REPO, signal);
21996
22122
  const version2 = rel.tag_name ?? "";
21997
- if (existsSync25(binPath)) return { binPath, version: version2 };
22123
+ if (existsSync27(binPath)) return { binPath, version: version2 };
21998
22124
  const asset = pickCloudflaredAsset(rel.assets ?? []);
21999
22125
  if (!asset) throw new Error("no_release_asset");
22000
22126
  mkdirSync17(dir, { recursive: true });
@@ -22480,7 +22606,13 @@ deps.agentTasks = new AgentTaskState();
22480
22606
  var telemetry = new Emitter({ dataDir: store.dir(), store, version, os: getSysInfo().os });
22481
22607
  deps.telemetry = telemetry;
22482
22608
  telemetry.dailyActive();
22483
- manager.onLoadSettled = (ok, err7) => reportModelLoad(store.dir(), telemetry, ok ? "ok" : "fail", ok ? void 0 : classifyLoadFailure(err7));
22609
+ manager.onLoadSettled = (ok, err7) => {
22610
+ const outcome = ok ? "ok" : "fail";
22611
+ if (claimOnce(store.dir(), "once:onboarding_step:first_load")) {
22612
+ telemetry.emit("onboarding_step", { step: "first_load", outcome });
22613
+ }
22614
+ reportModelLoad(store.dir(), telemetry, outcome, ok ? void 0 : classifyLoadFailure(err7));
22615
+ };
22484
22616
  bench.telemetry = telemetry;
22485
22617
  build.onSettled = (ok) => telemetry.emit("onboarding_step", { step: "engine_build", outcome: ok ? "ok" : "fail" });
22486
22618
  provision.onSettled = (ok) => telemetry.emit("onboarding_step", { step: "engine_install", outcome: ok ? "ok" : "fail" });