@i14ks/ccv 0.14.0 → 0.15.0

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 (28) hide show
  1. package/bundle/ccv.mjs +1734 -194
  2. package/package.json +1 -1
  3. package/web/assets/antigravity-logo-DRKQga2U.png +0 -0
  4. package/web/assets/{codemirror-DfnUEAQ1.js → codemirror-wcb7BToF.js} +1 -1
  5. package/web/assets/{index-UUIF3jgc.js → index-B1iGktMS.js} +1 -1
  6. package/web/assets/{index-C_0hLH-6.js → index-B7uOCyzk.js} +1 -1
  7. package/web/assets/{index-BrhFysJH.js → index-B8ioctog.js} +1 -1
  8. package/web/assets/index-BKeYZb3W.css +1 -0
  9. package/web/assets/{index-C33PhVuR.js → index-BebgK8qS.js} +1 -1
  10. package/web/assets/{index-DdCEnSCp.js → index-BgYZP6B5.js} +1 -1
  11. package/web/assets/{index-BFyhFly9.js → index-BoWGjyq1.js} +1 -1
  12. package/web/assets/{index-Aa3M3Mcp.js → index-Bug5lJuD.js} +1 -1
  13. package/web/assets/{index-6WrTm4Cl.js → index-CKo4-duU.js} +1 -1
  14. package/web/assets/{index-CE-6gz9W.js → index-CLUF7ja0.js} +1 -1
  15. package/web/assets/{index-BUVVNNln.js → index-D8i-20tT.js} +1 -1
  16. package/web/assets/{index-DdWyOJPD.js → index-DaeBIEG7.js} +1 -1
  17. package/web/assets/index-DfzFdzpd.js +134 -0
  18. package/web/assets/{index-DNvUBsh8.js → index-DzuxHBqN.js} +1 -1
  19. package/web/assets/{index-B4QS6XYB.js → index-H1KO3hWw.js} +1 -1
  20. package/web/assets/{index-DLoVg97c.js → index-ZyET8YfL.js} +1 -1
  21. package/web/assets/{index-Cu9yVjDd.js → index-ggJz6TAQ.js} +1 -1
  22. package/web/assets/{index-CReKaug6.js → index-jRANpi-W.js} +1 -1
  23. package/web/assets/{index-BSHub4Y3.js → index-qJpQucwj.js} +1 -1
  24. package/web/assets/{index-BBJ7uyxo.js → index-s31Rk-Wi.js} +1 -1
  25. package/web/assets/{index-1NZdgXKt.js → index-tW3KsNuy.js} +1 -1
  26. package/web/index.html +2 -2
  27. package/web/assets/index-i3oOZBX4.js +0 -133
  28. package/web/assets/index-ihyDKGfF.css +0 -1
package/bundle/ccv.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // dist/ccv.js
4
- import { spawn as spawn2 } from "node:child_process";
5
- import { readFile as readFile15 } from "node:fs/promises";
6
- import path20 from "node:path";
4
+ import { spawn as spawn3 } from "node:child_process";
5
+ import { readFile as readFile17 } from "node:fs/promises";
6
+ import path24 from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { parseArgs } from "node:util";
9
9
 
@@ -146,9 +146,9 @@ function envDefaults() {
146
146
  // ../host/dist/server.js
147
147
  import { createServer } from "node:http";
148
148
  import { createReadStream, existsSync as existsSync4, statSync as statSync3 } from "node:fs";
149
- import path19 from "node:path";
149
+ import path23 from "node:path";
150
150
  import { homedir as homedir6 } from "node:os";
151
- import { randomUUID as randomUUID4 } from "node:crypto";
151
+ import { randomUUID as randomUUID5 } from "node:crypto";
152
152
  import { WebSocketServer } from "ws";
153
153
  import { renameSession as renameSession2 } from "@anthropic-ai/claude-agent-sdk";
154
154
 
@@ -164,8 +164,6 @@ function workspaceIdFromPath(absolutePath) {
164
164
  // ../host/dist/session.js
165
165
  import { EventEmitter } from "node:events";
166
166
  import { randomUUID } from "node:crypto";
167
- import { existsSync, writeFileSync } from "node:fs";
168
- import { homedir as homedir3 } from "node:os";
169
167
  import path5 from "node:path";
170
168
  import { query, renameSession } from "@anthropic-ai/claude-agent-sdk";
171
169
 
@@ -1111,7 +1109,9 @@ function previewAnnotations(questions, answers) {
1111
1109
  return annotations;
1112
1110
  }
1113
1111
  var USAGE_MIN_INTERVAL_MS = 2e4;
1114
- var USAGE_TURN_INTERVAL_MS = 12e4;
1112
+ var HANDOFF_TOOL_DENIAL = "\u041B\u0438\u043C\u0438\u0442 \u043F\u043B\u0430\u043D\u0430 \u043F\u043E\u0447\u0442\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D, \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B \u043E\u0442\u043A\u043B\u044E\u0447\u0435\u043D\u044B. \u041E\u0442\u0432\u0435\u0442\u044C \u0445\u044D\u043D\u0434\u043E\u0444\u0444\u043E\u043C \u0442\u0435\u043A\u0441\u0442\u043E\u043C, \u0431\u0435\u0437 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432.";
1113
+ var USAGE_TURN_INTERVAL_MS = 3e4;
1114
+ var INTERRUPT_SETTLE_MS = 15e3;
1115
1115
  var CONTEXT_MIN_INTERVAL_MS = 5e3;
1116
1116
  var ClaudeSession = class extends EventEmitter {
1117
1117
  id;
@@ -1201,6 +1201,11 @@ var ClaudeSession = class extends EventEmitter {
1201
1201
  #titleIsDefault = false;
1202
1202
  /** Настоящий id сессии Claude Code — известен только после system/init. */
1203
1203
  #claudeSessionId = null;
1204
+ /**
1205
+ * Идёт ход хэндоффа (`runHandoff`): любой вызов инструмента отклоняется —
1206
+ * и хуком, и `canUseTool`, в любом режиме прав.
1207
+ */
1208
+ #handoffMode = false;
1204
1209
  /**
1205
1210
  * Имя, которое пользователь задал, но записать на диск ещё не удалось.
1206
1211
  *
@@ -1329,6 +1334,7 @@ var ClaudeSession = class extends EventEmitter {
1329
1334
  summary() {
1330
1335
  return {
1331
1336
  id: this.id,
1337
+ provider: "claude",
1332
1338
  claudeSessionId: this.#claudeSessionId,
1333
1339
  workspaceId: this.workspaceId,
1334
1340
  cwd: this.cwd,
@@ -1407,7 +1413,10 @@ var ClaudeSession = class extends EventEmitter {
1407
1413
  {
1408
1414
  matcher: WRITE_TOOLS,
1409
1415
  hooks: [(input) => this.#guardWrite(input)]
1410
- }
1416
+ },
1417
+ // Без matcher — на все инструменты, включая MCP и инструменты
1418
+ // субагентов: в ходе хэндоффа отклоняется всё.
1419
+ { hooks: [async () => this.#guardHandoff()] }
1411
1420
  ]
1412
1421
  },
1413
1422
  stderr: (data) => this.emit("stderr", data)
@@ -1637,11 +1646,11 @@ var ClaudeSession = class extends EventEmitter {
1637
1646
  // -------------------------------------------------------------------------
1638
1647
  // Ввод от пользователя
1639
1648
  // -------------------------------------------------------------------------
1640
- sendPrompt(text, images) {
1649
+ sendPrompt(text, images, service = false) {
1641
1650
  if (this.#closing)
1642
1651
  throw new Error("session is closed");
1643
1652
  this.#record({ kind: "user.message", text, images });
1644
- if (!this.#title || this.#titleIsDefault) {
1653
+ if (!service && (!this.#title || this.#titleIsDefault)) {
1645
1654
  this.#title = text.slice(0, 80);
1646
1655
  this.#titleIsDefault = false;
1647
1656
  }
@@ -1773,6 +1782,49 @@ var ClaudeSession = class extends EventEmitter {
1773
1782
  this.#interruptRequested = true;
1774
1783
  await this.#query?.interrupt();
1775
1784
  }
1785
+ /**
1786
+ * Ход хэндоффа у лимита: прервать текущий ход (вместе с субагентами), затем
1787
+ * отправить служебный `prompt` с запретом на любые инструменты и вернуть
1788
+ * текст ответа. `null` — хода не вышло: ошибка, пустой ответ или ход не
1789
+ * кончился за `timeoutMs`; тогда хэндофф собирают из журнала.
1790
+ */
1791
+ async runHandoff(prompt, timeoutMs) {
1792
+ if (this.#closing)
1793
+ return null;
1794
+ this.#handoffMode = true;
1795
+ try {
1796
+ if (this.#state === "running" || this.#state === "waiting_permission") {
1797
+ const settled = this.#nextTurnEnd(INTERRUPT_SETTLE_MS);
1798
+ await this.interrupt().catch(() => void 0);
1799
+ await settled;
1800
+ }
1801
+ if (this.#closing || !this.#query || this.#state !== "idle")
1802
+ return null;
1803
+ const reply = this.#nextTurnEnd(timeoutMs);
1804
+ this.sendPrompt(prompt, void 0, true);
1805
+ const turn = await reply;
1806
+ const text = turn?.text?.trim();
1807
+ return turn && !turn.isError && text ? text : null;
1808
+ } catch {
1809
+ return null;
1810
+ } finally {
1811
+ this.#handoffMode = false;
1812
+ }
1813
+ }
1814
+ /** Ближайший конец хода (`result`) или `null`, если не дождались. */
1815
+ #nextTurnEnd(timeoutMs) {
1816
+ return new Promise((resolve) => {
1817
+ const onEnd = (turn) => {
1818
+ clearTimeout(timer);
1819
+ resolve(turn);
1820
+ };
1821
+ const timer = setTimeout(() => {
1822
+ this.off("turnEnd", onEnd);
1823
+ resolve(null);
1824
+ }, timeoutMs);
1825
+ this.once("turnEnd", onEnd);
1826
+ });
1827
+ }
1776
1828
  /**
1777
1829
  * Смена режима прав на лету.
1778
1830
  *
@@ -1867,7 +1919,6 @@ var ClaudeSession = class extends EventEmitter {
1867
1919
  const raw = await q.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET();
1868
1920
  this.#usage = toUsageState(raw);
1869
1921
  this.emit("usage", this.#usage);
1870
- writeFinisherSnapshot(this.#usage, this.#claudeSessionId, this.cwd);
1871
1922
  } catch {
1872
1923
  } finally {
1873
1924
  this.#usageAt = Date.now();
@@ -2036,6 +2087,18 @@ var ClaudeSession = class extends EventEmitter {
2036
2087
  }
2037
2088
  };
2038
2089
  };
2090
+ #guardHandoff() {
2091
+ if (!this.#handoffMode)
2092
+ return { continue: true };
2093
+ return {
2094
+ continue: true,
2095
+ hookSpecificOutput: {
2096
+ hookEventName: "PreToolUse",
2097
+ permissionDecision: "deny",
2098
+ permissionDecisionReason: HANDOFF_TOOL_DENIAL
2099
+ }
2100
+ };
2101
+ }
2039
2102
  /**
2040
2103
  * Мост между `canUseTool` из SDK и решением человека в UI.
2041
2104
  *
@@ -2053,6 +2116,9 @@ var ClaudeSession = class extends EventEmitter {
2053
2116
  * это и происходило, пока вопросы показывались обычной карточкой прав.
2054
2117
  */
2055
2118
  #canUseTool = (toolName, input, { signal, decisionReason, toolUseID }) => {
2119
+ if (this.#handoffMode) {
2120
+ return Promise.resolve({ behavior: "deny", message: HANDOFF_TOOL_DENIAL });
2121
+ }
2056
2122
  if (this.#options.readOnly) {
2057
2123
  this.#record({
2058
2124
  kind: "error",
@@ -2202,6 +2268,10 @@ var ClaudeSession = class extends EventEmitter {
2202
2268
  contextTokens: this.#contextTokens,
2203
2269
  text: "result" in message ? message.result : null
2204
2270
  });
2271
+ this.emit("turnEnd", {
2272
+ text: "result" in message ? message.result : null,
2273
+ isError: message.is_error
2274
+ });
2205
2275
  if (this.#options.outputFormat) {
2206
2276
  this.emit("structured", {
2207
2277
  data: "structured_output" in message ? message.structured_output : void 0,
@@ -2427,60 +2497,1178 @@ function toUsageState(raw) {
2427
2497
  fetchedAt: Date.now()
2428
2498
  };
2429
2499
  }
2430
- function writeFinisherSnapshot(usage, sessionId, cwd) {
2431
- if (!usage.available)
2432
- return;
2433
- const dir = path5.join(homedir3(), ".claude", "finisher");
2434
- if (!existsSync(dir))
2435
- return;
2436
- const snapshot = {
2437
- updated_at: Date.now(),
2438
- session_id: sessionId,
2439
- cwd,
2440
- rate_limits: {
2441
- five_hour: usage.fiveHour?.utilization ?? null,
2442
- seven_day: usage.sevenDay?.utilization ?? null,
2443
- spend_limit: null
2500
+ function toUsageWindow(raw) {
2501
+ if (!raw || typeof raw.utilization !== "number")
2502
+ return null;
2503
+ return {
2504
+ // Доля окна и так приходит в 0–100, но полоса шире дорожки — это уже не
2505
+ // «почти всё израсходовано», а сломанная вёрстка.
2506
+ utilization: Math.min(100, Math.max(0, raw.utilization)),
2507
+ resetsAt: typeof raw.resets_at === "string" ? raw.resets_at : ""
2508
+ };
2509
+ }
2510
+ function contentToText2(content) {
2511
+ if (typeof content === "string")
2512
+ return content;
2513
+ if (!Array.isArray(content))
2514
+ return "";
2515
+ return content.map((block) => {
2516
+ if (typeof block === "string")
2517
+ return block;
2518
+ if (block && typeof block === "object" && "text" in block) {
2519
+ return String(block.text);
2520
+ }
2521
+ if (block && typeof block === "object" && "type" in block) {
2522
+ return `[${String(block.type)}]`;
2523
+ }
2524
+ return "";
2525
+ }).join("");
2526
+ }
2527
+
2528
+ // ../host/dist/antigravity-session.js
2529
+ import { EventEmitter as EventEmitter2 } from "node:events";
2530
+ import { spawn as spawn2, execFile as execFile3 } from "node:child_process";
2531
+ import { randomUUID as randomUUID2 } from "node:crypto";
2532
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
2533
+ import { tmpdir } from "node:os";
2534
+ import path7 from "node:path";
2535
+ import { createInterface } from "node:readline";
2536
+
2537
+ // ../host/dist/antigravity-cli.js
2538
+ import { execFile as execFile2 } from "node:child_process";
2539
+ import { existsSync } from "node:fs";
2540
+ import { homedir as homedir3 } from "node:os";
2541
+ import path6 from "node:path";
2542
+ function agyExecutable() {
2543
+ const forced = process.env.CCV_AGY_PATH;
2544
+ if (forced && existsSync(forced))
2545
+ return forced;
2546
+ const names2 = process.platform === "win32" ? ["agy.exe", "agy"] : ["agy"];
2547
+ const dirs = (process.env.PATH ?? "").split(path6.delimiter).filter(Boolean);
2548
+ if (process.platform === "win32") {
2549
+ const local = process.env.LOCALAPPDATA ?? path6.join(homedir3(), "AppData", "Local");
2550
+ dirs.push(path6.join(local, "agy", "bin"));
2551
+ } else {
2552
+ dirs.push(path6.join(homedir3(), ".local", "bin"), "/usr/local/bin", "/opt/homebrew/bin");
2553
+ }
2554
+ for (const dir of dirs) {
2555
+ for (const name of names2) {
2556
+ const candidate = path6.join(dir, name);
2557
+ if (existsSync(candidate))
2558
+ return candidate;
2559
+ }
2560
+ }
2561
+ return null;
2562
+ }
2563
+ var AGY_MISSING_HINT = "Antigravity CLI (agy) \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D. \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 \u0435\u0433\u043E \u0441 https://antigravity.google \u0438 \u0432\u043E\u0439\u0434\u0438\u0442\u0435, \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0432 `agy` \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435, \u2014 \u0438\u043B\u0438 \u0443\u043A\u0430\u0436\u0438\u0442\u0435 \u043F\u0443\u0442\u044C \u0432 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0439 CCV_AGY_PATH.";
2564
+ var modelsCache = null;
2565
+ function agyModels(file) {
2566
+ if (modelsCache)
2567
+ return modelsCache;
2568
+ const pending = new Promise((resolve) => {
2569
+ execFile2(file, ["models"], { timeout: 3e4, windowsHide: true }, (error, stdout) => {
2570
+ if (error) {
2571
+ resolve([]);
2572
+ return;
2573
+ }
2574
+ const models = [];
2575
+ for (const line of stdout.split(/\r?\n/)) {
2576
+ const [value, displayName] = line.split(" ");
2577
+ if (!value || !displayName || /\s/.test(value))
2578
+ continue;
2579
+ models.push({
2580
+ value,
2581
+ resolvedModel: value,
2582
+ displayName: displayName.trim(),
2583
+ description: "",
2584
+ supportsEffort: false,
2585
+ effortLevels: [],
2586
+ supportsThinking: false,
2587
+ supportsAutoMode: false
2588
+ });
2589
+ }
2590
+ resolve(models);
2591
+ });
2592
+ });
2593
+ modelsCache = pending;
2594
+ void pending.then((models) => {
2595
+ if (models.length === 0)
2596
+ modelsCache = null;
2597
+ });
2598
+ return pending;
2599
+ }
2600
+ function agyCommand(file, command, cwd) {
2601
+ return new Promise((resolve) => {
2602
+ execFile2(file, ["--output-format", "json", "-p", command], {
2603
+ cwd,
2604
+ timeout: 6e4,
2605
+ windowsHide: true,
2606
+ maxBuffer: 8 * 1024 * 1024,
2607
+ env: { ...process.env, NO_COLOR: "1" }
2608
+ }, (error, stdout) => {
2609
+ const line = stdout.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.startsWith("{")).pop();
2610
+ if (line) {
2611
+ try {
2612
+ resolve(JSON.parse(line));
2613
+ return;
2614
+ } catch {
2615
+ }
2616
+ }
2617
+ resolve({ status: "ERROR", error: error ? error.message : "agy \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B" });
2618
+ });
2619
+ });
2620
+ }
2621
+ var USAGE_TTL_MS = 3e4;
2622
+ var usageCache = null;
2623
+ var usageInFlight = null;
2624
+ var usageError = null;
2625
+ function agyUsageError() {
2626
+ return usageError;
2627
+ }
2628
+ function agyUsage(file, force = false) {
2629
+ if (!force && usageCache && Date.now() - usageCache.at < USAGE_TTL_MS) {
2630
+ return Promise.resolve(usageCache.value);
2631
+ }
2632
+ if (usageInFlight)
2633
+ return usageInFlight;
2634
+ usageInFlight = agyCommand(file, "/usage").then((reply) => {
2635
+ const value = parseUsage(reply);
2636
+ if (value)
2637
+ usageCache = { at: Date.now(), value };
2638
+ usageError = value ? null : reply.error || "agy \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u043A\u0432\u043E\u0442\u0443";
2639
+ return value;
2640
+ }).finally(() => {
2641
+ usageInFlight = null;
2642
+ });
2643
+ return usageInFlight;
2644
+ }
2645
+ var PERIOD_LABELS = {
2646
+ weekly: "\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u044B\u0439 \u043B\u0438\u043C\u0438\u0442",
2647
+ daily: "\u0434\u043D\u0435\u0432\u043D\u043E\u0439 \u043B\u0438\u043C\u0438\u0442",
2648
+ monthly: "\u043C\u0435\u0441\u044F\u0447\u043D\u044B\u0439 \u043B\u0438\u043C\u0438\u0442"
2649
+ };
2650
+ function parseUsage(reply) {
2651
+ if (reply.status !== "SUCCESS")
2652
+ return null;
2653
+ const data = reply.command?.data;
2654
+ if (!data || !Array.isArray(data.groups))
2655
+ return null;
2656
+ const buckets = [];
2657
+ for (const group of data.groups) {
2658
+ const groupName = group.name ?? "";
2659
+ for (const bucket of group.buckets ?? []) {
2660
+ if (typeof bucket.remaining_fraction !== "number")
2661
+ continue;
2662
+ const used = Math.min(100, Math.max(0, (1 - bucket.remaining_fraction) * 100));
2663
+ buckets.push({
2664
+ id: bucket.id ?? `${groupName}:${bucket.name ?? ""}`,
2665
+ label: shortGroupLabel(groupName),
2666
+ // Имя окна у `agy` — «Weekly Limit Remaining», а полоса показывает
2667
+ // израсходованное: подпись с «Remaining» читалась бы наоборот.
2668
+ title: [groupName, PERIOD_LABELS[bucket.window ?? ""] ?? bucket.name].filter(Boolean).join(" \u2014 "),
2669
+ description: group.description ?? "",
2670
+ period: bucket.window ?? "",
2671
+ window: { utilization: used, resetsAt: bucket.reset_time ?? "" }
2672
+ });
2673
+ }
2674
+ }
2675
+ return {
2676
+ available: buckets.length > 0,
2677
+ plan: "",
2678
+ fiveHour: null,
2679
+ sevenDay: null,
2680
+ sevenDayOpus: null,
2681
+ buckets,
2682
+ fetchedAt: Date.now()
2683
+ };
2684
+ }
2685
+ function shortGroupLabel(name) {
2686
+ const short = name.replace(/\s+models?$/i, "").replace(/\s+and\s+/gi, "/").trim();
2687
+ return short || name;
2688
+ }
2689
+ var INTERACTIVE_COMMANDS = /* @__PURE__ */ new Set(["config", "hooks", "permissions", "context"]);
2690
+ var AGY_CLI_COMMANDS = /* @__PURE__ */ new Set([
2691
+ "help",
2692
+ "usage",
2693
+ "quota",
2694
+ "skills",
2695
+ "agents",
2696
+ "changelog",
2697
+ "credits",
2698
+ "model",
2699
+ "effort"
2700
+ ]);
2701
+ var COMMAND_ARGS = { model: "[\u043C\u043E\u0434\u0435\u043B\u044C]", effort: "[low|medium|high|max]" };
2702
+ var catalogCache = null;
2703
+ function agyCommandCatalog(file, cwd) {
2704
+ if (catalogCache)
2705
+ return catalogCache;
2706
+ const pending = Promise.all([agyCommand(file, "/help", cwd), agyCommand(file, "/skills", cwd)]).then(([help, skills]) => {
2707
+ const commands = [];
2708
+ const helpData = help.command?.data;
2709
+ for (const c of helpData?.commands ?? []) {
2710
+ if (!c.name || INTERACTIVE_COMMANDS.has(c.name))
2711
+ continue;
2712
+ commands.push({
2713
+ name: c.name,
2714
+ description: c.description ?? "",
2715
+ argumentHint: COMMAND_ARGS[c.name] ?? "",
2716
+ aliases: c.aliases ?? []
2717
+ });
2718
+ }
2719
+ const skillsData = skills.command?.data;
2720
+ for (const s of skillsData?.skills ?? []) {
2721
+ if (!s.name || commands.some((c) => c.name === s.name))
2722
+ continue;
2723
+ commands.push({ name: s.name, description: s.description ?? "", argumentHint: "", aliases: [] });
2724
+ }
2725
+ return commands;
2726
+ });
2727
+ catalogCache = pending;
2728
+ void pending.then((commands) => {
2729
+ if (commands.length === 0)
2730
+ catalogCache = null;
2731
+ });
2732
+ return pending;
2733
+ }
2734
+ function agyContextWindow(model) {
2735
+ if (!model)
2736
+ return null;
2737
+ const id = model.toLowerCase();
2738
+ if (id.startsWith("gemini-"))
2739
+ return 1048576;
2740
+ if (id.startsWith("claude-"))
2741
+ return 2e5;
2742
+ if (id.startsWith("gpt-oss"))
2743
+ return 131072;
2744
+ return null;
2745
+ }
2746
+
2747
+ // ../host/dist/antigravity-session.js
2748
+ var TRANSIENT = /* @__PURE__ */ new Set(["assistant.delta", "thinking.delta", "state"]);
2749
+ var AntigravitySession = class extends EventEmitter2 {
2750
+ id;
2751
+ workspaceId;
2752
+ cwd;
2753
+ journal = new Journal();
2754
+ provider = "antigravity";
2755
+ #store;
2756
+ #record;
2757
+ #history = Promise.resolve([]);
2758
+ #child = null;
2759
+ #executable = null;
2760
+ #state = "starting";
2761
+ #lastTurn = null;
2762
+ #capabilities = null;
2763
+ #titleIsDefault = false;
2764
+ #closing = false;
2765
+ /** Ходы, отправленные процессу и ещё не закрытые его `result`. */
2766
+ #pendingTurns = 0;
2767
+ /** Перезапуск (смена модели/режима), отложенный до конца хода. */
2768
+ #restartPending = false;
2769
+ #interrupting = false;
2770
+ /** Накопленный текст шагов-ответов: на `DONE` уходит одним `assistant.text`. */
2771
+ #texts = /* @__PURE__ */ new Map();
2772
+ #thinking = /* @__PURE__ */ new Map();
2773
+ /** Шаги-инструменты, о которых уже сказано `tool.use`. */
2774
+ #tools = /* @__PURE__ */ new Set();
2775
+ #contextTokens = null;
2776
+ /** Модель, на которой `agy` реально работает (`init.model`) — с учётом его умолчания. */
2777
+ #activeModel = null;
2778
+ #usage = null;
2779
+ #context = null;
2780
+ #commands = [];
2781
+ #stderrTail = "";
2782
+ #unsaved = [];
2783
+ #flushTimer = null;
2784
+ #updatedAt = Date.now();
2785
+ constructor(options2) {
2786
+ super();
2787
+ this.#store = options2.store;
2788
+ this.workspaceId = options2.workspaceId;
2789
+ this.cwd = options2.cwd;
2790
+ const now = Date.now();
2791
+ this.#record = options2.record ? { ...options2.record } : {
2792
+ id: randomUUID2(),
2793
+ conversationId: null,
2794
+ workspaceId: options2.workspaceId,
2795
+ cwd: options2.cwd,
2796
+ title: options2.defaultTitle ?? null,
2797
+ model: options2.model ?? null,
2798
+ permissionMode: options2.permissionMode ?? "default",
2799
+ effort: null,
2800
+ createdAt: now,
2801
+ updatedAt: now
2802
+ };
2803
+ if (!options2.record)
2804
+ this.#titleIsDefault = options2.defaultTitle !== void 0;
2805
+ if (options2.record && options2.permissionMode) {
2806
+ this.#record.permissionMode = options2.permissionMode;
2807
+ }
2808
+ this.id = this.#record.id;
2809
+ this.#updatedAt = this.#record.updatedAt;
2810
+ }
2811
+ // -------------------------------------------------------------------------
2812
+ // Публичное состояние — та же форма, что у ClaudeSession
2813
+ // -------------------------------------------------------------------------
2814
+ get state() {
2815
+ return this.#state;
2816
+ }
2817
+ get capabilities() {
2818
+ return this.#capabilities;
2819
+ }
2820
+ /** Постоянный id сессии: под ним она в архиве, закреплённых и папках. */
2821
+ get claudeSessionId() {
2822
+ return this.id;
2823
+ }
2824
+ get parentSessionId() {
2825
+ return null;
2826
+ }
2827
+ get usage() {
2828
+ return this.#usage;
2829
+ }
2830
+ get context() {
2831
+ return this.#context;
2832
+ }
2833
+ history() {
2834
+ return this.#history;
2835
+ }
2836
+ summary() {
2837
+ return {
2838
+ id: this.id,
2839
+ provider: "antigravity",
2840
+ claudeSessionId: this.id,
2841
+ workspaceId: this.workspaceId,
2842
+ cwd: this.cwd,
2843
+ parentSessionId: null,
2844
+ title: this.#record.title,
2845
+ state: this.#state,
2846
+ lastTurn: this.#lastTurn,
2847
+ backgroundWork: false,
2848
+ model: this.#record.model,
2849
+ permissionMode: this.#record.permissionMode,
2850
+ effort: this.#record.effort,
2851
+ ultracode: false,
2852
+ thinking: true,
2853
+ switchModelsOnFlag: true,
2854
+ createdAt: this.#record.createdAt,
2855
+ updatedAt: this.#updatedAt,
2856
+ lastSeq: this.journal.lastSeq,
2857
+ totalCostUsd: 0
2858
+ };
2859
+ }
2860
+ // -------------------------------------------------------------------------
2861
+ // Жизненный цикл
2862
+ // -------------------------------------------------------------------------
2863
+ start() {
2864
+ if (this.#record.conversationId) {
2865
+ this.#history = this.#store.readEvents(this.id);
2866
+ void this.#history.then((entries) => {
2867
+ if (this.#contextTokens !== null)
2868
+ return;
2869
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
2870
+ const event = entries[i].event;
2871
+ if (event.kind === "result" && event.contextTokens !== null) {
2872
+ this.#contextTokens = event.contextTokens;
2873
+ this.#emitContext();
2874
+ return;
2875
+ }
2876
+ }
2877
+ });
2878
+ }
2879
+ void this.#store.save(this.#record);
2880
+ this.#executable = agyExecutable();
2881
+ if (!this.#executable) {
2882
+ this.#record_({ kind: "error", message: AGY_MISSING_HINT });
2883
+ this.#setState("error");
2884
+ return;
2885
+ }
2886
+ this.#capabilities = this.#baseCapabilities();
2887
+ this.emit("capabilities");
2888
+ void agyModels(this.#executable).then((models) => {
2889
+ if (!this.#capabilities || models.length === 0)
2890
+ return;
2891
+ this.#capabilities = { ...this.#capabilities, models };
2892
+ this.emit("capabilities");
2893
+ });
2894
+ void agyCommandCatalog(this.#executable, this.cwd).then((commands) => {
2895
+ if (!this.#capabilities || commands.length === 0)
2896
+ return;
2897
+ this.#commands = commands;
2898
+ this.#capabilities = this.#baseCapabilities();
2899
+ this.emit("capabilities");
2900
+ });
2901
+ void this.refreshUsage();
2902
+ this.#spawn();
2903
+ }
2904
+ #baseCapabilities() {
2905
+ return {
2906
+ cwd: this.cwd,
2907
+ model: this.#record.model ?? this.#activeModel ?? "",
2908
+ permissionMode: this.#record.permissionMode,
2909
+ slashCommands: this.#commands.map((c) => c.name),
2910
+ commands: this.#commands,
2911
+ models: this.#capabilities?.models ?? [],
2912
+ tools: this.#capabilities?.tools ?? [],
2913
+ agents: [],
2914
+ mcpServers: [],
2915
+ flags: []
2916
+ };
2917
+ }
2918
+ #args() {
2919
+ const args = ["--input-format", "stream-json", "--output-format", "stream-json"];
2920
+ switch (this.#record.permissionMode) {
2921
+ case "acceptEdits":
2922
+ args.push("--mode", "accept-edits");
2923
+ break;
2924
+ case "plan":
2925
+ args.push("--mode", "plan");
2926
+ break;
2927
+ case "bypassPermissions":
2928
+ args.push("--dangerously-skip-permissions");
2929
+ break;
2930
+ default:
2931
+ break;
2932
+ }
2933
+ if (this.#record.model)
2934
+ args.push("--model", this.#record.model);
2935
+ const effort = agyEffort(this.#record.effort);
2936
+ if (effort)
2937
+ args.push("--effort", effort);
2938
+ if (this.#record.conversationId)
2939
+ args.push("--conversation", this.#record.conversationId);
2940
+ args.push("-p=");
2941
+ return args;
2942
+ }
2943
+ #spawn() {
2944
+ if (!this.#executable || this.#closing)
2945
+ return;
2946
+ let child;
2947
+ try {
2948
+ child = spawn2(this.#executable, this.#args(), {
2949
+ cwd: this.cwd,
2950
+ windowsHide: true,
2951
+ env: { ...process.env, NO_COLOR: "1" }
2952
+ });
2953
+ } catch (error) {
2954
+ this.#record_({ kind: "error", message: `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C agy: ${describe2(error)}` });
2955
+ this.#setState("error");
2956
+ return;
2957
+ }
2958
+ this.#child = child;
2959
+ this.#stderrTail = "";
2960
+ this.#texts.clear();
2961
+ this.#thinking.clear();
2962
+ this.#tools.clear();
2963
+ const lines = createInterface({ input: child.stdout });
2964
+ lines.on("line", (line) => {
2965
+ if (this.#child !== child)
2966
+ return;
2967
+ this.#handleLine(line);
2968
+ });
2969
+ child.stderr.setEncoding("utf8");
2970
+ child.stderr.on("data", (data) => {
2971
+ this.#stderrTail = (this.#stderrTail + data).slice(-4e3);
2972
+ this.emit("stderr", data);
2973
+ });
2974
+ child.stdin.on("error", () => void 0);
2975
+ child.on("error", (error) => {
2976
+ if (this.#child !== child)
2977
+ return;
2978
+ this.#record_({ kind: "error", message: `agy: ${describe2(error)}` });
2979
+ });
2980
+ child.on("exit", (code) => {
2981
+ if (this.#child !== child)
2982
+ return;
2983
+ this.#child = null;
2984
+ this.#onExit(code);
2985
+ });
2986
+ }
2987
+ /** Процесс ушёл сам — не по нашей просьбе. */
2988
+ #onExit(code) {
2989
+ if (this.#closing)
2990
+ return;
2991
+ const busy = this.#pendingTurns > 0;
2992
+ this.#pendingTurns = 0;
2993
+ if (code === 0 && !busy) {
2994
+ this.#setState("idle");
2995
+ return;
2996
+ }
2997
+ const tail = this.#stderrTail.trim().split(/\r?\n/).slice(-3).join("\n");
2998
+ this.#lastTurn = busy ? "failed" : this.#lastTurn;
2999
+ this.#record_({
3000
+ kind: "error",
3001
+ message: `Antigravity CLI \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F (\u043A\u043E\u0434 ${code ?? "\u2014"})${tail ? `: ${tail}` : ""}`
3002
+ });
3003
+ this.#setState("error");
3004
+ }
3005
+ async close(_reason = "closed by client") {
3006
+ this.#closing = true;
3007
+ this.#kill();
3008
+ this.#flush();
3009
+ this.#setState("closed");
3010
+ }
3011
+ #kill() {
3012
+ const child = this.#child;
3013
+ this.#child = null;
3014
+ if (!child || child.exitCode !== null)
3015
+ return;
3016
+ try {
3017
+ child.stdin.end();
3018
+ } catch {
3019
+ }
3020
+ if (process.platform === "win32" && child.pid) {
3021
+ execFile3("taskkill", ["/pid", String(child.pid), "/T", "/F"], { windowsHide: true }, () => {
3022
+ if (child.exitCode === null)
3023
+ child.kill();
3024
+ });
3025
+ } else {
3026
+ child.kill("SIGTERM");
3027
+ }
3028
+ }
3029
+ /** Новый процесс на том же разговоре — смена настроек, ручная починка. */
3030
+ #restart() {
3031
+ if (this.#closing)
3032
+ return;
3033
+ this.#restartPending = false;
3034
+ this.#kill();
3035
+ this.#pendingTurns = 0;
3036
+ this.#spawn();
3037
+ if (this.#state !== "error" && this.#state !== "closed")
3038
+ this.#setState("idle");
3039
+ }
3040
+ reconnect(_reason) {
3041
+ this.#restart();
3042
+ }
3043
+ // -------------------------------------------------------------------------
3044
+ // Ввод
3045
+ // -------------------------------------------------------------------------
3046
+ sendPrompt(text, images) {
3047
+ if (this.#closing)
3048
+ throw new Error("session is closed");
3049
+ const command = /^\/([\w-]+)(?:\s+([\s\S]*))?$/.exec(text.trim());
3050
+ if (command && AGY_CLI_COMMANDS.has(command[1]) && (!images || images.length === 0)) {
3051
+ void this.#runCliCommand(text, command[1], command[2]?.trim() ?? "");
3052
+ return;
3053
+ }
3054
+ this.#record_({ kind: "user.message", text, images });
3055
+ if (!this.#record.title || this.#titleIsDefault) {
3056
+ this.#record.title = text.slice(0, 80);
3057
+ this.#titleIsDefault = false;
3058
+ }
3059
+ this.#lastTurn = null;
3060
+ if (!this.#child)
3061
+ this.#spawn();
3062
+ if (!this.#child)
3063
+ return;
3064
+ this.#pendingTurns += 1;
3065
+ this.#setState("running");
3066
+ const child = this.#child;
3067
+ void this.#composePrompt(text, images).then((prompt) => {
3068
+ if (this.#child !== child)
3069
+ return;
3070
+ const line = JSON.stringify({
3071
+ event: "user",
3072
+ message: { role: "user", content: [{ type: "text", text: prompt }] }
3073
+ });
3074
+ child.stdin.write(line + "\n");
3075
+ });
3076
+ }
3077
+ /**
3078
+ * Встроенная команда `agy` — отдельным вызовом, без хода модели.
3079
+ *
3080
+ * `/model` и `/effort` с аргументом меняют настройки сессии ровно как
3081
+ * дропдауны (своё значение у `agy -p` прожило бы один процесс), без
3082
+ * аргумента — называют текущее. `/usage` заодно обновляет шкалу в шапке.
3083
+ * Остальное — как ответил сам `agy`. Посреди хода команда отвечает в ленту,
3084
+ * но ни состояния, ни `result` не трогает: ход закроет его собственный.
3085
+ */
3086
+ async #runCliCommand(text, name, arg) {
3087
+ this.#record_({ kind: "user.message", text });
3088
+ const turnRunning = this.#pendingTurns > 0;
3089
+ if (!turnRunning) {
3090
+ this.#lastTurn = null;
3091
+ this.#setState("running");
3092
+ }
3093
+ const started = Date.now();
3094
+ let reply = null;
3095
+ let error = null;
3096
+ try {
3097
+ if (!this.#executable) {
3098
+ error = AGY_MISSING_HINT;
3099
+ } else if (name === "model") {
3100
+ ({ reply, error } = await this.#modelCommand(arg));
3101
+ } else if (name === "effort") {
3102
+ ({ reply, error } = await this.#effortCommand(arg));
3103
+ } else if (name === "usage" || name === "quota") {
3104
+ const usage = await agyUsage(this.#executable, true);
3105
+ if (usage) {
3106
+ this.#usage = usage;
3107
+ this.emit("usage", usage);
3108
+ reply = formatUsage(usage);
3109
+ } else {
3110
+ error = `agy \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u043A\u0432\u043E\u0442\u0443 (\`/usage\`): ${agyUsageError() ?? "\u043D\u0435\u0442 \u043E\u0442\u0432\u0435\u0442\u0430"}`;
3111
+ }
3112
+ } else {
3113
+ const result = await agyCommand(this.#executable, `/${name}${arg ? ` ${arg}` : ""}`, this.cwd);
3114
+ if (result.status === "SUCCESS")
3115
+ reply = formatCommandReply(name, result.response ?? "");
3116
+ else
3117
+ error = result.error || `/${name} \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u043E\u0448\u0438\u0431\u043A\u043E\u0439`;
3118
+ }
3119
+ } catch (e) {
3120
+ error = describe2(e);
3121
+ }
3122
+ if (this.#closing)
3123
+ return;
3124
+ if (reply)
3125
+ this.#record_({ kind: "assistant.text", text: reply, blockId: `cmd:${randomUUID2()}` });
3126
+ if (error)
3127
+ this.#record_({ kind: "error", message: error });
3128
+ if (turnRunning || this.#pendingTurns > 0)
3129
+ return;
3130
+ this.#record_({
3131
+ kind: "result",
3132
+ subtype: error ? "error" : "success",
3133
+ isError: Boolean(error),
3134
+ durationMs: Date.now() - started,
3135
+ costUsd: null,
3136
+ usage: null,
3137
+ contextTokens: this.#contextTokens,
3138
+ text: reply
3139
+ });
3140
+ this.#lastTurn = error ? "failed" : "completed";
3141
+ if (this.#state === "running")
3142
+ this.#setState("idle");
3143
+ else
3144
+ this.emit("summary", this.summary());
3145
+ }
3146
+ async #modelCommand(arg) {
3147
+ const models = this.#capabilities?.models ?? [];
3148
+ const label = (id2) => id2 ? models.find((m) => m.value === id2)?.displayName ?? id2 : "\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E agy";
3149
+ if (!arg) {
3150
+ const current = this.#record.model ?? this.#activeModel;
3151
+ return { reply: `\u041C\u043E\u0434\u0435\u043B\u044C: **${label(current)}**${current ? ` (\`${current}\`)` : ""}`, error: null };
3152
+ }
3153
+ const wanted = arg.toLowerCase();
3154
+ const match = models.find((m) => m.value.toLowerCase() === wanted) ?? models.find((m) => m.displayName.toLowerCase() === wanted);
3155
+ if (models.length > 0 && !match) {
3156
+ return {
3157
+ reply: null,
3158
+ error: `\u041D\u0435\u0442 \u043C\u043E\u0434\u0435\u043B\u0438 \xAB${arg}\xBB. \u0414\u043E\u0441\u0442\u0443\u043F\u043D\u044B: ${models.map((m) => m.value).join(", ")}`
3159
+ };
3160
+ }
3161
+ const id = match?.value ?? arg;
3162
+ await this.setModel(id);
3163
+ return { reply: `\u041C\u043E\u0434\u0435\u043B\u044C \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0435\u043D\u0430: **${label(id)}** (\`${id}\`)`, error: null };
3164
+ }
3165
+ async #effortCommand(arg) {
3166
+ const levels = ["low", "medium", "high", "max"];
3167
+ if (!arg) {
3168
+ return { reply: `\u0423\u0441\u0438\u043B\u0438\u0435: **${this.#record.effort ?? "\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u043C\u043E\u0434\u0435\u043B\u0438"}**`, error: null };
3169
+ }
3170
+ const level = arg.toLowerCase();
3171
+ if (!levels.includes(level)) {
3172
+ return { reply: null, error: `\u0423\u0440\u043E\u0432\u0435\u043D\u044C \u0443\u0441\u0438\u043B\u0438\u044F \u2014 \u043E\u0434\u0438\u043D \u0438\u0437: ${levels.join(", ")}` };
3173
+ }
3174
+ await this.setEffort(level);
3175
+ return { reply: `\u0423\u0441\u0438\u043B\u0438\u0435: **${level}**`, error: null };
3176
+ }
3177
+ /** Картинки — файлами во временном каталоге, а в текст — их пути. */
3178
+ async #composePrompt(text, images) {
3179
+ if (!images || images.length === 0)
3180
+ return text;
3181
+ const dir = path7.join(tmpdir(), "ccv-antigravity", this.id);
3182
+ await mkdir2(dir, { recursive: true });
3183
+ const paths = [];
3184
+ for (const image of images) {
3185
+ const ext = image.mediaType.split("/")[1] === "jpeg" ? "jpg" : image.mediaType.split("/")[1];
3186
+ const file = path7.join(dir, `${randomUUID2()}.${ext}`);
3187
+ await writeFile2(file, Buffer.from(image.data, "base64"));
3188
+ paths.push(file);
3189
+ }
3190
+ return `${text}
3191
+
3192
+ Attached image${paths.length > 1 ? "s" : ""} (view them with your file viewing tool):
3193
+ ` + paths.map((p) => `- ${p}`).join("\n");
3194
+ }
3195
+ async editPrompt(_userIndex, _text, _images) {
3196
+ throw new Error("\u0412 \u0441\u0435\u0441\u0441\u0438\u0438 Antigravity \u043F\u0440\u0430\u0432\u043A\u0430 \u043F\u0440\u043E\u0448\u043B\u044B\u0445 \u0440\u0435\u043F\u043B\u0438\u043A \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F");
3197
+ }
3198
+ respondPermission(_requestId, _decision, _reason, _answers, _annotations, _plan) {
3199
+ }
3200
+ async interrupt() {
3201
+ if (this.#pendingTurns === 0)
3202
+ return;
3203
+ this.#interrupting = true;
3204
+ this.#kill();
3205
+ this.#closeOpenSteps();
3206
+ this.#pendingTurns = 0;
3207
+ this.#lastTurn = "interrupted";
3208
+ this.#record_({
3209
+ kind: "result",
3210
+ subtype: "interrupted",
3211
+ isError: false,
3212
+ durationMs: 0,
3213
+ costUsd: null,
3214
+ usage: null,
3215
+ contextTokens: this.#contextTokens,
3216
+ text: null
3217
+ });
3218
+ this.#interrupting = false;
3219
+ this.#spawn();
3220
+ if (this.#state !== "error")
3221
+ this.#setState("idle");
3222
+ }
3223
+ async setPermissionMode(mode) {
3224
+ if (this.#record.permissionMode === mode)
3225
+ return;
3226
+ this.#record.permissionMode = mode;
3227
+ this.#settingsChanged();
3228
+ }
3229
+ async setModel(model) {
3230
+ if (this.#record.model === model)
3231
+ return;
3232
+ this.#record.model = model;
3233
+ this.#settingsChanged();
3234
+ }
3235
+ async setEffort(effort) {
3236
+ if (this.#record.effort === effort)
3237
+ return;
3238
+ this.#record.effort = effort;
3239
+ this.#settingsChanged();
3240
+ }
3241
+ /** Флаги запуска поменялись: новый процесс — сейчас или после хода. */
3242
+ #settingsChanged() {
3243
+ if (this.#capabilities) {
3244
+ this.#capabilities = this.#baseCapabilities();
3245
+ this.emit("capabilities");
3246
+ }
3247
+ this.emit("summary", this.summary());
3248
+ void this.#store.save(this.#record);
3249
+ if (!this.#executable || this.#state === "closed")
3250
+ return;
3251
+ if (this.#pendingTurns > 0)
3252
+ this.#restartPending = true;
3253
+ else
3254
+ this.#restart();
3255
+ }
3256
+ // Claude-only управление: у agy этого нет — молча ничего не делаем.
3257
+ async setUltracode(_enabled) {
3258
+ }
3259
+ async setThinking(_enabled) {
3260
+ }
3261
+ async setSwitchModelsOnFlag(_enabled) {
3262
+ }
3263
+ /** Квота `agy` (`/usage`) — общая на аккаунт, кэш один на хост. */
3264
+ async refreshUsage(force = false) {
3265
+ if (!this.#executable)
3266
+ return;
3267
+ const usage = await agyUsage(this.#executable, force);
3268
+ if (!usage)
3269
+ return;
3270
+ this.#usage = usage;
3271
+ this.emit("usage", usage);
3272
+ }
3273
+ async refreshContext(_force = false) {
3274
+ this.#emitContext();
3275
+ }
3276
+ /**
3277
+ * Снимок окна контекста: занятое — из `usage` последнего шага модели,
3278
+ * всего — по модели из таблицы. Чего-то из двух нет — молчим: полоса
3279
+ * останется со своим умолчанием и числом из `result`.
3280
+ */
3281
+ #emitContext() {
3282
+ const maxTokens = agyContextWindow(this.#record.model ?? this.#activeModel);
3283
+ if (this.#contextTokens === null || maxTokens === null)
3284
+ return;
3285
+ this.#context = { tokens: this.#contextTokens, maxTokens, fetchedAt: Date.now() };
3286
+ this.emit("context", this.#context);
3287
+ }
3288
+ async reloadPlugins() {
3289
+ }
3290
+ async mcpServerStatus() {
3291
+ return [];
3292
+ }
3293
+ async addMcpServer(_name, _config) {
3294
+ throw new Error("MCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044B Antigravity \u043D\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u044E\u0442\u0441\u044F \u0432 \u0441\u0430\u043C\u043E\u043C agy (`agy mcp`)");
3295
+ }
3296
+ async removeMcpServer(_name) {
3297
+ return false;
3298
+ }
3299
+ setTitle(title) {
3300
+ this.#record.title = title;
3301
+ this.#titleIsDefault = false;
3302
+ void this.#store.save(this.#record);
3303
+ this.emit("summary", this.summary());
3304
+ }
3305
+ // -------------------------------------------------------------------------
3306
+ // Поток agy
3307
+ // -------------------------------------------------------------------------
3308
+ #handleLine(line) {
3309
+ const trimmed = line.trim();
3310
+ if (!trimmed.startsWith("{"))
3311
+ return;
3312
+ let message;
3313
+ try {
3314
+ message = JSON.parse(trimmed);
3315
+ } catch {
3316
+ return;
3317
+ }
3318
+ switch (message.event) {
3319
+ case "init": {
3320
+ const conversationId = typeof message.conversation_id === "string" ? message.conversation_id : null;
3321
+ if (conversationId && conversationId !== this.#record.conversationId) {
3322
+ this.#record.conversationId = conversationId;
3323
+ void this.#store.save(this.#record);
3324
+ }
3325
+ const init = message.init;
3326
+ if (typeof init?.model === "string" && init.model && init.model !== this.#activeModel) {
3327
+ this.#activeModel = init.model;
3328
+ if (this.#capabilities) {
3329
+ this.#capabilities = this.#baseCapabilities();
3330
+ this.emit("capabilities");
3331
+ }
3332
+ this.#emitContext();
3333
+ }
3334
+ if (this.#capabilities && Array.isArray(init?.tools)) {
3335
+ this.#capabilities = {
3336
+ ...this.#capabilities,
3337
+ tools: init.tools.filter((t) => typeof t === "string")
3338
+ };
3339
+ this.emit("capabilities");
3340
+ }
3341
+ if (this.#state === "starting")
3342
+ this.#setState("idle");
3343
+ return;
3344
+ }
3345
+ case "step_update":
3346
+ this.#handleStep(message.step_update);
3347
+ return;
3348
+ case "result":
3349
+ this.#handleResult(message.result);
3350
+ return;
3351
+ default:
3352
+ return;
3353
+ }
3354
+ }
3355
+ #handleStep(step2) {
3356
+ if (!step2 || typeof step2.step_index !== "number")
3357
+ return;
3358
+ const index = step2.step_index;
3359
+ const blockId = `${this.#record.conversationId ?? this.id}:${index}`;
3360
+ if (step2.step_type === "user_input")
3361
+ return;
3362
+ if (step2.step_type === "tool" || step2.tool_info || step2.tool_name) {
3363
+ const name = step2.tool_name ?? step2.tool_info?.name ?? "tool";
3364
+ if (!this.#tools.has(index)) {
3365
+ this.#tools.add(index);
3366
+ this.#record_({
3367
+ kind: "tool.use",
3368
+ toolUseId: blockId,
3369
+ name,
3370
+ input: toolInput(step2.tool_info?.parameters)
3371
+ });
3372
+ }
3373
+ if (step2.state === "DONE" || step2.state === "ERROR") {
3374
+ const error = step2.tool_info?.error?.message;
3375
+ this.#record_({
3376
+ kind: "tool.result",
3377
+ toolUseId: blockId,
3378
+ content: error ?? step2.tool_info?.output ?? "",
3379
+ isError: step2.state === "ERROR" || Boolean(error)
3380
+ });
3381
+ this.#tools.delete(index);
3382
+ }
3383
+ return;
3384
+ }
3385
+ if (step2.thinking_delta) {
3386
+ this.#thinking.set(index, (this.#thinking.get(index) ?? "") + step2.thinking_delta);
3387
+ this.#record_({ kind: "thinking.delta", text: step2.thinking_delta, blockId: `${blockId}:t` });
3388
+ }
3389
+ if (step2.text_delta) {
3390
+ this.#texts.set(index, (this.#texts.get(index) ?? "") + step2.text_delta);
3391
+ this.#record_({ kind: "assistant.delta", text: step2.text_delta, blockId });
3392
+ }
3393
+ if (step2.usage && typeof step2.usage.input_tokens === "number") {
3394
+ this.#contextTokens = step2.usage.input_tokens + (step2.usage.output_tokens ?? 0);
3395
+ }
3396
+ if (step2.state === "DONE" || step2.state === "ERROR") {
3397
+ const thinking = this.#thinking.get(index);
3398
+ if (thinking) {
3399
+ this.#record_({ kind: "assistant.thinking", text: thinking, blockId: `${blockId}:t` });
3400
+ this.#thinking.delete(index);
3401
+ }
3402
+ const text = this.#texts.get(index);
3403
+ if (text) {
3404
+ this.#record_({ kind: "assistant.text", text: text.replace(/\n+$/, ""), blockId });
3405
+ this.#texts.delete(index);
3406
+ }
3407
+ if (step2.state === "ERROR") {
3408
+ const message = typeof step2.error === "string" ? step2.error : step2.error?.message;
3409
+ this.#record_({ kind: "error", message: message || `\u0428\u0430\u0433 ${step2.step_type ?? ""} \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u043E\u0448\u0438\u0431\u043A\u043E\u0439` });
3410
+ }
3411
+ }
3412
+ }
3413
+ #handleResult(result) {
3414
+ if (!result || this.#interrupting)
3415
+ return;
3416
+ this.#closeOpenSteps();
3417
+ const ok = result.status === "SUCCESS";
3418
+ const usage = result.usage ? {
3419
+ inputTokens: result.usage.input_tokens ?? 0,
3420
+ outputTokens: (result.usage.output_tokens ?? 0) + (result.usage.thinking_tokens ?? 0),
3421
+ cacheReadTokens: result.usage.cache_read_tokens ?? 0,
3422
+ cacheCreationTokens: 0
3423
+ } : null;
3424
+ const denied = (result.denied_actions ?? []).map((a) => a.display_name ?? a.action).filter((a) => Boolean(a));
3425
+ if (denied.length > 0) {
3426
+ this.#record_({
3427
+ kind: "error",
3428
+ message: `Antigravity \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B \u0431\u0435\u0437 \u0432\u043E\u043F\u0440\u043E\u0441\u0430: ${[...new Set(denied)].join(", ")}. \u0412 headless-\u0440\u0435\u0436\u0438\u043C\u0435 agy \u043D\u0435 \u0443\u043C\u0435\u0435\u0442 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435: \u043F\u0440\u0430\u0432\u043A\u0438 \u0444\u0430\u0439\u043B\u043E\u0432 \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0435\u0442 \u0440\u0435\u0436\u0438\u043C acceptEdits, \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0438 \u0432\u0441\u0451 \u043E\u0441\u0442\u0430\u043B\u044C\u043D\u043E\u0435 \u2014 bypassPermissions. \u041E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u0435 \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u043C\u043E\u0436\u043D\u043E \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C \u043F\u0440\u0430\u0432\u0438\u043B\u043E\u043C \u0432 permissions.allow \u0444\u0430\u0439\u043B\u0430 ~/.gemini/antigravity-cli/settings.json, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 command(npm test).`
3429
+ });
2444
3430
  }
2445
- };
2446
- try {
2447
- writeFileSync(path5.join(dir, "usage-cache.json"), JSON.stringify(snapshot, null, 2));
2448
- } catch {
3431
+ if (!ok && result.error)
3432
+ this.#record_({ kind: "error", message: result.error });
3433
+ this.#record_({
3434
+ kind: "result",
3435
+ subtype: ok ? "success" : "error",
3436
+ isError: !ok,
3437
+ durationMs: Math.round((result.duration_seconds ?? 0) * 1e3),
3438
+ costUsd: null,
3439
+ usage,
3440
+ contextTokens: this.#contextTokens,
3441
+ text: result.response ?? null
3442
+ });
3443
+ this.#emitContext();
3444
+ void this.refreshUsage(true);
3445
+ this.#lastTurn = ok ? "completed" : "failed";
3446
+ this.#pendingTurns = Math.max(0, this.#pendingTurns - 1);
3447
+ if (this.#pendingTurns > 0)
3448
+ return;
3449
+ if (this.#restartPending)
3450
+ this.#restart();
3451
+ this.#setState("idle");
3452
+ }
3453
+ /** Незакрытые шаги оборванного хода: текст — в ленту, инструменты — с ошибкой. */
3454
+ #closeOpenSteps() {
3455
+ const prefix = this.#record.conversationId ?? this.id;
3456
+ for (const [index, text] of this.#texts) {
3457
+ if (text)
3458
+ this.#record_({ kind: "assistant.text", text, blockId: `${prefix}:${index}` });
3459
+ }
3460
+ for (const index of this.#tools) {
3461
+ this.#record_({
3462
+ kind: "tool.result",
3463
+ toolUseId: `${prefix}:${index}`,
3464
+ content: "\u041F\u0440\u0435\u0440\u0432\u0430\u043D\u043E",
3465
+ isError: true
3466
+ });
3467
+ }
3468
+ this.#texts.clear();
3469
+ this.#thinking.clear();
3470
+ this.#tools.clear();
3471
+ }
3472
+ // -------------------------------------------------------------------------
3473
+ #record_(event) {
3474
+ this.#updatedAt = Date.now();
3475
+ const entry = this.journal.append(event);
3476
+ this.emit("event", entry);
3477
+ if (TRANSIENT.has(event.kind))
3478
+ return;
3479
+ this.#unsaved.push({ ts: entry.ts, event });
3480
+ this.#scheduleFlush();
2449
3481
  }
3482
+ #scheduleFlush() {
3483
+ if (this.#flushTimer)
3484
+ return;
3485
+ this.#flushTimer = setTimeout(() => this.#flush(), 500);
3486
+ this.#flushTimer.unref?.();
3487
+ }
3488
+ #flush() {
3489
+ if (this.#flushTimer) {
3490
+ clearTimeout(this.#flushTimer);
3491
+ this.#flushTimer = null;
3492
+ }
3493
+ const batch = this.#unsaved;
3494
+ this.#unsaved = [];
3495
+ this.#record.updatedAt = this.#updatedAt;
3496
+ void this.#store.appendEvents(this.id, batch);
3497
+ void this.#store.save(this.#record);
3498
+ }
3499
+ #setState(state) {
3500
+ if (this.#state === state)
3501
+ return;
3502
+ this.#state = state;
3503
+ this.#record_({ kind: "state", state });
3504
+ if (state === "idle" || state === "error" || state === "closed")
3505
+ this.#flush();
3506
+ this.emit("summary", this.summary());
3507
+ }
3508
+ };
3509
+ function toolInput(parameters) {
3510
+ const input = { ...parameters ?? {} };
3511
+ const pick = (...keys) => keys.map((k) => input[k]).find((v) => typeof v === "string");
3512
+ const file = pick("TargetFile", "AbsolutePath", "File", "FilePath", "Path");
3513
+ if (file && input.file_path === void 0)
3514
+ input.file_path = file;
3515
+ const command = pick("CommandLine", "Command");
3516
+ if (command && input.command === void 0)
3517
+ input.command = command;
3518
+ const pattern = pick("Query", "Pattern", "SearchPattern");
3519
+ if (pattern && input.pattern === void 0)
3520
+ input.pattern = pattern;
3521
+ const dir = pick("DirectoryPath", "SearchPath", "SearchDirectory");
3522
+ if (dir && input.path === void 0)
3523
+ input.path = dir;
3524
+ const url2 = pick("Url", "URL");
3525
+ if (url2 && input.url === void 0)
3526
+ input.url = url2;
3527
+ return input;
3528
+ }
3529
+ function formatUsage(usage) {
3530
+ const rows = (usage.buckets ?? []).map((b) => {
3531
+ const left = Math.round(100 - b.window.utilization);
3532
+ const reset = b.window.resetsAt ? new Date(b.window.resetsAt).toLocaleString("ru-RU") : "\u2014";
3533
+ return `| ${b.title || b.label} | ${left}% | ${reset} |`;
3534
+ });
3535
+ if (rows.length === 0)
3536
+ return "\u041A\u0432\u043E\u0442\u0430 Antigravity: \u0434\u0430\u043D\u043D\u044B\u0445 \u043D\u0435\u0442.";
3537
+ return ["**\u041A\u0432\u043E\u0442\u0430 Antigravity**", "", "| \u0413\u0440\u0443\u043F\u043F\u0430 | \u041E\u0441\u0442\u0430\u043B\u043E\u0441\u044C | \u041E\u0431\u043D\u0443\u043B\u0438\u0442\u0441\u044F |", "|---|---|---|", ...rows].join("\n");
2450
3538
  }
2451
- function toUsageWindow(raw) {
2452
- if (!raw || typeof raw.utilization !== "number")
3539
+ function formatCommandReply(name, response) {
3540
+ const text = response.replace(/\s+$/, "");
3541
+ if (!text)
3542
+ return `/${name}: \u043F\u0443\u0441\u0442\u043E`;
3543
+ const lines = text.split(/\r?\n/);
3544
+ if (lines.every((l) => l.includes(" "))) {
3545
+ return lines.map((l) => {
3546
+ const [head, ...rest] = l.split(" ");
3547
+ return `- **${head}** \u2014 ${rest.join(" ")}`;
3548
+ }).join("\n");
3549
+ }
3550
+ return text;
3551
+ }
3552
+ function agyEffort(effort) {
3553
+ if (!effort)
2453
3554
  return null;
2454
- return {
2455
- // Доля окна и так приходит в 0–100, но полоса шире дорожки — это уже не
2456
- // «почти всё израсходовано», а сломанная вёрстка.
2457
- utilization: Math.min(100, Math.max(0, raw.utilization)),
2458
- resetsAt: typeof raw.resets_at === "string" ? raw.resets_at : ""
2459
- };
3555
+ return effort === "xhigh" ? "high" : effort;
2460
3556
  }
2461
- function contentToText2(content) {
2462
- if (typeof content === "string")
2463
- return content;
2464
- if (!Array.isArray(content))
2465
- return "";
2466
- return content.map((block) => {
2467
- if (typeof block === "string")
2468
- return block;
2469
- if (block && typeof block === "object" && "text" in block) {
2470
- return String(block.text);
3557
+ function describe2(error) {
3558
+ return error instanceof Error ? error.message : String(error);
3559
+ }
3560
+
3561
+ // ../host/dist/antigravity-store.js
3562
+ import { appendFile, mkdir as mkdir3, readFile as readFile4, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
3563
+ import path8 from "node:path";
3564
+ var AntigravityStore = class {
3565
+ #dir;
3566
+ #file;
3567
+ #records = /* @__PURE__ */ new Map();
3568
+ #loaded = null;
3569
+ #writing = Promise.resolve();
3570
+ constructor(dataDir) {
3571
+ this.#dir = path8.join(dataDir, "antigravity");
3572
+ this.#file = path8.join(this.#dir, "sessions.json");
3573
+ }
3574
+ #load() {
3575
+ this.#loaded ??= (async () => {
3576
+ try {
3577
+ const raw = JSON.parse(await readFile4(this.#file, "utf8"));
3578
+ for (const record of Array.isArray(raw) ? raw : []) {
3579
+ if (record && typeof record.id === "string")
3580
+ this.#records.set(record.id, record);
3581
+ }
3582
+ } catch {
3583
+ }
3584
+ })();
3585
+ return this.#loaded;
3586
+ }
3587
+ async find(id) {
3588
+ await this.#load();
3589
+ return this.#records.get(id) ?? null;
3590
+ }
3591
+ async list(workspaceId) {
3592
+ await this.#load();
3593
+ return [...this.#records.values()].filter((r) => r.workspaceId === workspaceId);
3594
+ }
3595
+ async save(record) {
3596
+ await this.#load();
3597
+ this.#records.set(record.id, { ...record });
3598
+ await this.#persist();
3599
+ }
3600
+ async rename(id, title) {
3601
+ await this.#load();
3602
+ const record = this.#records.get(id);
3603
+ if (!record)
3604
+ return false;
3605
+ record.title = title;
3606
+ await this.#persist();
3607
+ return true;
3608
+ }
3609
+ async remove(id) {
3610
+ await this.#load();
3611
+ if (!this.#records.delete(id))
3612
+ return;
3613
+ await this.#persist();
3614
+ await rm2(this.#eventsFile(id), { force: true });
3615
+ }
3616
+ /** Дописать события в ленту сессии. Порядок записей сохраняется. */
3617
+ appendEvents(id, events) {
3618
+ if (events.length === 0)
3619
+ return this.#writing;
3620
+ const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
3621
+ return this.#enqueue(async () => {
3622
+ await mkdir3(this.#dir, { recursive: true });
3623
+ await appendFile(this.#eventsFile(id), lines, "utf8");
3624
+ });
3625
+ }
3626
+ /** Лента сессии как история: `seq` — позиция, от 1 по порядку. */
3627
+ async readEvents(id) {
3628
+ await this.#writing;
3629
+ let raw;
3630
+ try {
3631
+ raw = await readFile4(this.#eventsFile(id), "utf8");
3632
+ } catch {
3633
+ return [];
2471
3634
  }
2472
- if (block && typeof block === "object" && "type" in block) {
2473
- return `[${String(block.type)}]`;
3635
+ const entries = [];
3636
+ for (const line of raw.split("\n")) {
3637
+ if (!line.trim())
3638
+ continue;
3639
+ try {
3640
+ const parsed = JSON.parse(line);
3641
+ entries.push({ seq: entries.length + 1, ts: parsed.ts, event: parsed.event });
3642
+ } catch {
3643
+ }
2474
3644
  }
2475
- return "";
2476
- }).join("");
2477
- }
3645
+ return entries;
3646
+ }
3647
+ #eventsFile(id) {
3648
+ return path8.join(this.#dir, `${id.replace(/[^\w-]/g, "_")}.jsonl`);
3649
+ }
3650
+ #persist() {
3651
+ const snapshot = JSON.stringify([...this.#records.values()], null, 2);
3652
+ return this.#enqueue(async () => {
3653
+ await mkdir3(this.#dir, { recursive: true });
3654
+ await writeFile3(this.#file, snapshot, "utf8");
3655
+ });
3656
+ }
3657
+ /** Записи на диск — строго по очереди: две параллельные испортили бы файл. */
3658
+ #enqueue(task) {
3659
+ const next = this.#writing.then(task, task);
3660
+ this.#writing = next.catch((error) => {
3661
+ console.error("[antigravity-store]", error instanceof Error ? error.message : error);
3662
+ });
3663
+ return this.#writing;
3664
+ }
3665
+ };
2478
3666
 
2479
3667
  // ../host/dist/git.js
2480
- import { execFile as execFile2 } from "node:child_process";
2481
- import { mkdtemp, readFile as readFile4, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
2482
- import { tmpdir } from "node:os";
2483
- import path6 from "node:path";
3668
+ import { execFile as execFile4 } from "node:child_process";
3669
+ import { mkdtemp, readFile as readFile5, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
3670
+ import { tmpdir as tmpdir2 } from "node:os";
3671
+ import path9 from "node:path";
2484
3672
  var TIMEOUT_MS = 2e4;
2485
3673
  var NET_TIMEOUT_MS = 5 * 6e4;
2486
3674
  var MAX_BUFFER = 32 * 1024 * 1024;
@@ -2526,7 +3714,7 @@ var GitRepo = class {
2526
3714
  await this.#run(["rev-parse", "--is-inside-work-tree"]);
2527
3715
  } catch (error) {
2528
3716
  const runnable2 = await this.#run(["--version"]).then(() => true, () => false);
2529
- return { ...blank, message: describe2(error), noRepo: runnable2 };
3717
+ return { ...blank, message: describe3(error), noRepo: runnable2 };
2530
3718
  }
2531
3719
  const branchRaw = await this.#soft(["rev-parse", "--abbrev-ref", "HEAD"]);
2532
3720
  const detached = branchRaw === "HEAD" || branchRaw === "";
@@ -2796,12 +3984,12 @@ var GitRepo = class {
2796
3984
  * важнее всего (см. `checkIgnored`).
2797
3985
  */
2798
3986
  async #withoutRepo(args) {
2799
- const dir = await mkdtemp(path6.join(tmpdir(), "ccv-git-"));
3987
+ const dir = await mkdtemp(path9.join(tmpdir2(), "ccv-git-"));
2800
3988
  try {
2801
3989
  await this.#run(["init", "--bare", "--quiet", dir]);
2802
3990
  return await this.#soft([`--git-dir=${dir}`, `--work-tree=${this.cwd}`, ...args]);
2803
3991
  } finally {
2804
- await rm2(dir, { recursive: true, force: true }).catch(() => void 0);
3992
+ await rm3(dir, { recursive: true, force: true }).catch(() => void 0);
2805
3993
  }
2806
3994
  }
2807
3995
  /**
@@ -2842,14 +4030,14 @@ var GitRepo = class {
2842
4030
  * исключил. Повторы отсеиваются, чтобы файл не рос от каждой публикации.
2843
4031
  */
2844
4032
  async #appendGitignore(paths) {
2845
- const file = path6.join(this.cwd, ".gitignore");
2846
- const existing = await readFile4(file, "utf8").catch(() => "");
4033
+ const file = path9.join(this.cwd, ".gitignore");
4034
+ const existing = await readFile5(file, "utf8").catch(() => "");
2847
4035
  const known = new Set(existing.split(/\r?\n/).map((line) => line.trim()));
2848
4036
  const lines = paths.map(ignorePattern).filter((line) => !known.has(line));
2849
4037
  if (lines.length === 0)
2850
4038
  return;
2851
4039
  const prefix = existing === "" || existing.endsWith("\n") ? "" : "\n";
2852
- await writeFile2(file, `${existing}${prefix}${lines.join("\n")}
4040
+ await writeFile4(file, `${existing}${prefix}${lines.join("\n")}
2853
4041
  `, "utf8");
2854
4042
  }
2855
4043
  /** Команда, чей провал — обычное дело (нет upstream, нет коммитов, detached). */
@@ -2867,7 +4055,7 @@ var GitRepo = class {
2867
4055
  #run(args, options2 = {}) {
2868
4056
  const token = options2.token ?? "";
2869
4057
  return new Promise((resolve, reject) => {
2870
- const child = execFile2(
4058
+ const child = execFile4(
2871
4059
  "git",
2872
4060
  // core.quotepath=false — иначе кириллические пути приезжают в виде
2873
4061
  // «\320\277\321\200…» и в панели читаются как мусор.
@@ -2938,7 +4126,7 @@ function requirePath(value) {
2938
4126
  function ignorePattern(relPath) {
2939
4127
  return `/${relPath.replace(/[\\*?[\]]/g, (char) => `\\${char}`)}`;
2940
4128
  }
2941
- function describe2(error) {
4129
+ function describe3(error) {
2942
4130
  return error instanceof Error ? error.message : String(error);
2943
4131
  }
2944
4132
  function parseStatus(raw) {
@@ -3048,9 +4236,9 @@ function parsePatch(raw) {
3048
4236
  body = "";
3049
4237
  }
3050
4238
  total += body.length;
3051
- const path21 = status === "deleted" ? oldPath : newPath || oldPath;
4239
+ const path25 = status === "deleted" ? oldPath : newPath || oldPath;
3052
4240
  files.push({
3053
- path: path21,
4241
+ path: path25,
3054
4242
  oldPath: status === "renamed" ? oldPath : "",
3055
4243
  status,
3056
4244
  added: count(body, "+"),
@@ -3062,10 +4250,10 @@ function parsePatch(raw) {
3062
4250
  return { files, truncated };
3063
4251
  }
3064
4252
  function stripPrefix(value) {
3065
- const path21 = unquote(value.trim());
3066
- if (path21 === "/dev/null")
4253
+ const path25 = unquote(value.trim());
4254
+ if (path25 === "/dev/null")
3067
4255
  return "";
3068
- return path21.replace(/^[ab]\//, "");
4256
+ return path25.replace(/^[ab]\//, "");
3069
4257
  }
3070
4258
  function count(body, mark) {
3071
4259
  let n = 0;
@@ -3077,9 +4265,9 @@ function count(body, mark) {
3077
4265
  }
3078
4266
 
3079
4267
  // ../host/dist/github.js
3080
- import { execFile as execFile3 } from "node:child_process";
3081
- import { mkdir as mkdir2, readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
3082
- import path7 from "node:path";
4268
+ import { execFile as execFile5 } from "node:child_process";
4269
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
4270
+ import path10 from "node:path";
3083
4271
  var API = "https://api.github.com";
3084
4272
  var API_VERSION = "2022-11-28";
3085
4273
  var API_TIMEOUT_MS = 2e4;
@@ -3100,11 +4288,11 @@ var GitHubAccount = class {
3100
4288
  */
3101
4289
  #ghInstalled = null;
3102
4290
  constructor(dataDir) {
3103
- this.#storePath = path7.join(dataDir, "github.json");
4291
+ this.#storePath = path10.join(dataDir, "github.json");
3104
4292
  }
3105
4293
  async load() {
3106
4294
  try {
3107
- const raw = JSON.parse(await readFile5(this.#storePath, "utf8"));
4295
+ const raw = JSON.parse(await readFile6(this.#storePath, "utf8"));
3108
4296
  const token = raw.token;
3109
4297
  this.#token = typeof token === "string" ? token.trim() : "";
3110
4298
  } catch {
@@ -3114,8 +4302,8 @@ var GitHubAccount = class {
3114
4302
  /** Сохранить токен или стереть его пустой строкой. */
3115
4303
  async setToken(token) {
3116
4304
  this.#token = token.trim();
3117
- await mkdir2(path7.dirname(this.#storePath), { recursive: true });
3118
- await writeFile3(this.#storePath, JSON.stringify({ token: this.#token }, null, 2), {
4305
+ await mkdir4(path10.dirname(this.#storePath), { recursive: true });
4306
+ await writeFile5(this.#storePath, JSON.stringify({ token: this.#token }, null, 2), {
3119
4307
  encoding: "utf8",
3120
4308
  mode: 384
3121
4309
  });
@@ -3142,7 +4330,7 @@ var GitHubAccount = class {
3142
4330
  const credential = await this.credential();
3143
4331
  return { ...base, connected: true, source: credential.source, login: credential.login };
3144
4332
  } catch (error) {
3145
- return { ...base, message: describe3(error) };
4333
+ return { ...base, message: describe4(error) };
3146
4334
  }
3147
4335
  }
3148
4336
  /**
@@ -3240,7 +4428,7 @@ var GitHubAccount = class {
3240
4428
  signal: AbortSignal.timeout(API_TIMEOUT_MS)
3241
4429
  });
3242
4430
  } catch (error) {
3243
- throw new Error(`GitHub \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${describe3(error)}`);
4431
+ throw new Error(`GitHub \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${describe4(error)}`);
3244
4432
  }
3245
4433
  const text = await response.text();
3246
4434
  if (!response.ok)
@@ -3266,7 +4454,7 @@ var GitHubAccount = class {
3266
4454
  };
3267
4455
  function gh(args) {
3268
4456
  return new Promise((resolve, reject) => {
3269
- execFile3("gh", args, { timeout: GH_TIMEOUT_MS, windowsHide: true, encoding: "utf8" }, (error, stdout, stderr) => {
4457
+ execFile5("gh", args, { timeout: GH_TIMEOUT_MS, windowsHide: true, encoding: "utf8" }, (error, stdout, stderr) => {
3270
4458
  if (error)
3271
4459
  return reject(new Error(stderr.trim() || error.message));
3272
4460
  resolve(stdout);
@@ -3302,7 +4490,7 @@ function apiError(status, body) {
3302
4490
  function str(value) {
3303
4491
  return typeof value === "string" ? value : "";
3304
4492
  }
3305
- function describe3(error) {
4493
+ function describe4(error) {
3306
4494
  return error instanceof Error ? error.message : String(error);
3307
4495
  }
3308
4496
 
@@ -3310,7 +4498,7 @@ function describe3(error) {
3310
4498
  import { existsSync as existsSync2, statSync } from "node:fs";
3311
4499
  import { readdir as readdir2 } from "node:fs/promises";
3312
4500
  import os2 from "node:os";
3313
- import path8 from "node:path";
4501
+ import path11 from "node:path";
3314
4502
  import { listSessions } from "@anthropic-ai/claude-agent-sdk";
3315
4503
  var CACHE_TTL = 6e4;
3316
4504
  var FIND_LIMIT = 50;
@@ -3349,7 +4537,7 @@ var ProjectFinder = class {
3349
4537
  if (needles.length === 0)
3350
4538
  return { candidates: [], truncated: false };
3351
4539
  const open = new Map(this.registry.list().map((workspace) => [workspace.path.toLowerCase(), workspace]));
3352
- const byName = this.#dirs.filter((dir) => matches(path8.basename(dir.path), needles));
4540
+ const byName = this.#dirs.filter((dir) => matches(path11.basename(dir.path), needles));
3353
4541
  const matched = byName.length > 0 ? byName : this.#dirs.filter((dir) => matches(dir.path, needles));
3354
4542
  const sorted = [...matched].sort((a, b) => {
3355
4543
  const aOpen = open.has(a.path.toLowerCase());
@@ -3360,7 +4548,7 @@ var ProjectFinder = class {
3360
4548
  return a.sessions > 0 ? -1 : 1;
3361
4549
  if (a.lastUsedAt !== b.lastUsedAt)
3362
4550
  return b.lastUsedAt - a.lastUsedAt;
3363
- return path8.basename(a.path).localeCompare(path8.basename(b.path), void 0, {
4551
+ return path11.basename(a.path).localeCompare(path11.basename(b.path), void 0, {
3364
4552
  numeric: true,
3365
4553
  sensitivity: "base"
3366
4554
  });
@@ -3368,7 +4556,7 @@ var ProjectFinder = class {
3368
4556
  return {
3369
4557
  candidates: sorted.slice(0, FIND_LIMIT).map((dir) => ({
3370
4558
  path: dir.path,
3371
- name: path8.basename(dir.path) || dir.path,
4559
+ name: path11.basename(dir.path) || dir.path,
3372
4560
  sessions: dir.sessions,
3373
4561
  lastUsedAt: dir.lastUsedAt || null,
3374
4562
  open: open.has(dir.path.toLowerCase()),
@@ -3392,7 +4580,7 @@ var ProjectFinder = class {
3392
4580
  const candidates = [];
3393
4581
  for (const entry of named) {
3394
4582
  const absolute = entry.path.trim();
3395
- if (!absolute || !path8.isAbsolute(absolute))
4583
+ if (!absolute || !path11.isAbsolute(absolute))
3396
4584
  continue;
3397
4585
  const key = absolute.toLowerCase();
3398
4586
  if (seen.has(key))
@@ -3403,7 +4591,7 @@ var ProjectFinder = class {
3403
4591
  const record = known.get(key);
3404
4592
  candidates.push({
3405
4593
  path: record?.path ?? absolute,
3406
- name: path8.basename(absolute) || absolute,
4594
+ name: path11.basename(absolute) || absolute,
3407
4595
  sessions: record?.sessions ?? 0,
3408
4596
  lastUsedAt: record?.lastUsedAt || null,
3409
4597
  open: open.has(key),
@@ -3460,7 +4648,7 @@ var ProjectFinder = class {
3460
4648
  if (!dirent.isDirectory() || SKIP_NAMES.has(dirent.name) || dirent.name.startsWith(".")) {
3461
4649
  continue;
3462
4650
  }
3463
- const full = path8.join(parent, dirent.name);
4651
+ const full = path11.join(parent, dirent.name);
3464
4652
  const key = full.toLowerCase();
3465
4653
  if (dirs.has(key))
3466
4654
  continue;
@@ -3481,7 +4669,7 @@ var ProjectFinder = class {
3481
4669
  #parents(dirs) {
3482
4670
  const counted = /* @__PURE__ */ new Map();
3483
4671
  for (const dir of dirs) {
3484
- const parent = path8.dirname(dir.path);
4672
+ const parent = path11.dirname(dir.path);
3485
4673
  if (parent === dir.path || !usable(parent))
3486
4674
  continue;
3487
4675
  const key = parent.toLowerCase();
@@ -3593,18 +4781,18 @@ function fromText(text) {
3593
4781
 
3594
4782
  // ../host/dist/workspaces.js
3595
4783
  import { existsSync as existsSync3, statSync as statSync2 } from "node:fs";
3596
- import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
3597
- import path9 from "node:path";
4784
+ import { mkdir as mkdir5, readFile as readFile7, writeFile as writeFile6 } from "node:fs/promises";
4785
+ import path12 from "node:path";
3598
4786
  import { listSessions as listSessions2 } from "@anthropic-ai/claude-agent-sdk";
3599
4787
  var WorkspaceRegistry = class {
3600
4788
  #entries = [];
3601
4789
  #storePath;
3602
4790
  constructor(dataDir) {
3603
- this.#storePath = path9.join(dataDir, "workspaces.json");
4791
+ this.#storePath = path12.join(dataDir, "workspaces.json");
3604
4792
  }
3605
4793
  async load() {
3606
4794
  try {
3607
- const raw = await readFile6(this.#storePath, "utf8");
4795
+ const raw = await readFile7(this.#storePath, "utf8");
3608
4796
  const parsed = JSON.parse(raw);
3609
4797
  if (!Array.isArray(parsed)) {
3610
4798
  this.#entries = [];
@@ -3622,8 +4810,8 @@ var WorkspaceRegistry = class {
3622
4810
  }
3623
4811
  }
3624
4812
  async #persist() {
3625
- await mkdir3(path9.dirname(this.#storePath), { recursive: true });
3626
- await writeFile4(this.#storePath, JSON.stringify(this.#entries, null, 2), "utf8");
4813
+ await mkdir5(path12.dirname(this.#storePath), { recursive: true });
4814
+ await writeFile6(this.#storePath, JSON.stringify(this.#entries, null, 2), "utf8");
3627
4815
  }
3628
4816
  /** Активные проекты. */
3629
4817
  list() {
@@ -3638,7 +4826,7 @@ var WorkspaceRegistry = class {
3638
4826
  return entry ? toInfo(entry) : void 0;
3639
4827
  }
3640
4828
  async open(rawPath) {
3641
- const absolute = path9.resolve(rawPath);
4829
+ const absolute = path12.resolve(rawPath);
3642
4830
  if (!existsSync3(absolute)) {
3643
4831
  throw new Error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${absolute}`);
3644
4832
  }
@@ -3705,24 +4893,24 @@ var WorkspaceRegistry = class {
3705
4893
  async create(parentRaw, nameRaw, init) {
3706
4894
  const name = nameRaw.trim();
3707
4895
  requireName(name);
3708
- const parent = path9.resolve(parentRaw.trim());
4896
+ const parent = path12.resolve(parentRaw.trim());
3709
4897
  if (!existsSync3(parent)) {
3710
4898
  throw new Error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${parent}`);
3711
4899
  }
3712
4900
  if (!statSync2(parent).isDirectory()) {
3713
4901
  throw new Error(`\u042D\u0442\u043E \u043D\u0435 \u043A\u0430\u0442\u0430\u043B\u043E\u0433: ${parent}`);
3714
4902
  }
3715
- const target = path9.join(parent, name);
4903
+ const target = path12.join(parent, name);
3716
4904
  if (existsSync3(target)) {
3717
4905
  throw new Error(`\u0423\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442: ${target} \u2014 \u043E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 \u0435\u0433\u043E \u043A\u0430\u043A \u043F\u0440\u043E\u0435\u043A\u0442`);
3718
4906
  }
3719
- await mkdir3(target);
4907
+ await mkdir5(target);
3720
4908
  const warnings = [];
3721
4909
  if (init.claudeMd) {
3722
- await step(warnings, "CLAUDE.md", () => writeFile4(path9.join(target, "CLAUDE.md"), claudeMdStub(name), "utf8"));
4910
+ await step(warnings, "CLAUDE.md", () => writeFile6(path12.join(target, "CLAUDE.md"), claudeMdStub(name), "utf8"));
3723
4911
  }
3724
4912
  if (init.readme) {
3725
- await step(warnings, "README.md", () => writeFile4(path9.join(target, "README.md"), readmeStub(name), "utf8"));
4913
+ await step(warnings, "README.md", () => writeFile6(path12.join(target, "README.md"), readmeStub(name), "utf8"));
3726
4914
  }
3727
4915
  if (init.git) {
3728
4916
  await step(warnings, "git init", () => new GitRepo(target).init());
@@ -3794,8 +4982,8 @@ function toInfo(entry) {
3794
4982
  return {
3795
4983
  id: workspaceIdFromPath(absolute),
3796
4984
  path: absolute,
3797
- name: path9.basename(absolute) || absolute,
3798
- hasClaudeMd: existsSync3(path9.join(absolute, "CLAUDE.md")),
4985
+ name: path12.basename(absolute) || absolute,
4986
+ hasClaudeMd: existsSync3(path12.join(absolute, "CLAUDE.md")),
3799
4987
  ...archivedAt ? { archivedAt } : {}
3800
4988
  };
3801
4989
  }
@@ -3804,18 +4992,18 @@ function isEntry(value) {
3804
4992
  }
3805
4993
 
3806
4994
  // ../host/dist/archive.js
3807
- import { mkdir as mkdir4, readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
3808
- import path10 from "node:path";
4995
+ import { mkdir as mkdir6, readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
4996
+ import path13 from "node:path";
3809
4997
  var SessionArchive = class {
3810
4998
  #archived = /* @__PURE__ */ new Map();
3811
4999
  #restored = /* @__PURE__ */ new Map();
3812
5000
  #storePath;
3813
5001
  constructor(dataDir) {
3814
- this.#storePath = path10.join(dataDir, "session-archive.json");
5002
+ this.#storePath = path13.join(dataDir, "session-archive.json");
3815
5003
  }
3816
5004
  async load() {
3817
5005
  try {
3818
- const raw = await readFile7(this.#storePath, "utf8");
5006
+ const raw = await readFile8(this.#storePath, "utf8");
3819
5007
  const parsed = JSON.parse(raw);
3820
5008
  this.#archived = new Map(Object.entries(parsed.archived ?? {}).filter(([, r]) => isRecord(r)));
3821
5009
  this.#restored = new Map(Object.entries(parsed.restored ?? {}).filter((entry) => typeof entry[1] === "number"));
@@ -3904,8 +5092,8 @@ var SessionArchive = class {
3904
5092
  archived: Object.fromEntries(this.#archived),
3905
5093
  restored: Object.fromEntries(this.#restored)
3906
5094
  };
3907
- await mkdir4(path10.dirname(this.#storePath), { recursive: true });
3908
- await writeFile5(this.#storePath, JSON.stringify(file, null, 2), "utf8");
5095
+ await mkdir6(path13.dirname(this.#storePath), { recursive: true });
5096
+ await writeFile7(this.#storePath, JSON.stringify(file, null, 2), "utf8");
3909
5097
  }
3910
5098
  };
3911
5099
  function isRecord(value) {
@@ -3913,17 +5101,17 @@ function isRecord(value) {
3913
5101
  }
3914
5102
 
3915
5103
  // ../host/dist/pins.js
3916
- import { mkdir as mkdir5, readFile as readFile8, writeFile as writeFile6 } from "node:fs/promises";
3917
- import path11 from "node:path";
5104
+ import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile8 } from "node:fs/promises";
5105
+ import path14 from "node:path";
3918
5106
  var SessionPins = class {
3919
5107
  #pinned = /* @__PURE__ */ new Map();
3920
5108
  #storePath;
3921
5109
  constructor(dataDir) {
3922
- this.#storePath = path11.join(dataDir, "session-pins.json");
5110
+ this.#storePath = path14.join(dataDir, "session-pins.json");
3923
5111
  }
3924
5112
  async load() {
3925
5113
  try {
3926
- const raw = await readFile8(this.#storePath, "utf8");
5114
+ const raw = await readFile9(this.#storePath, "utf8");
3927
5115
  const parsed = JSON.parse(raw);
3928
5116
  this.#pinned = new Map(Object.entries(parsed.pinned ?? {}).filter(([, r]) => isRecord2(r)));
3929
5117
  } catch {
@@ -4003,8 +5191,8 @@ var SessionPins = class {
4003
5191
  }
4004
5192
  async #persist() {
4005
5193
  const file = { pinned: Object.fromEntries(this.#pinned) };
4006
- await mkdir5(path11.dirname(this.#storePath), { recursive: true });
4007
- await writeFile6(this.#storePath, JSON.stringify(file, null, 2), "utf8");
5194
+ await mkdir7(path14.dirname(this.#storePath), { recursive: true });
5195
+ await writeFile8(this.#storePath, JSON.stringify(file, null, 2), "utf8");
4008
5196
  }
4009
5197
  };
4010
5198
  function isRecord2(value) {
@@ -4012,17 +5200,17 @@ function isRecord2(value) {
4012
5200
  }
4013
5201
 
4014
5202
  // ../host/dist/order.js
4015
- import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
4016
- import path12 from "node:path";
5203
+ import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile9 } from "node:fs/promises";
5204
+ import path15 from "node:path";
4017
5205
  var ListOrderStore = class {
4018
5206
  #order = emptyOrder();
4019
5207
  #storePath;
4020
5208
  constructor(dataDir) {
4021
- this.#storePath = path12.join(dataDir, "list-order.json");
5209
+ this.#storePath = path15.join(dataDir, "list-order.json");
4022
5210
  }
4023
5211
  async load() {
4024
5212
  try {
4025
- const raw = await readFile9(this.#storePath, "utf8");
5213
+ const raw = await readFile10(this.#storePath, "utf8");
4026
5214
  this.#order = sanitize(JSON.parse(raw));
4027
5215
  } catch {
4028
5216
  this.#order = emptyOrder();
@@ -4052,8 +5240,8 @@ var ListOrderStore = class {
4052
5240
  } else {
4053
5241
  this.#order[scope] = clean ?? [];
4054
5242
  }
4055
- await mkdir6(path12.dirname(this.#storePath), { recursive: true });
4056
- await writeFile7(this.#storePath, JSON.stringify(this.#order, null, 2), "utf8");
5243
+ await mkdir8(path15.dirname(this.#storePath), { recursive: true });
5244
+ await writeFile9(this.#storePath, JSON.stringify(this.#order, null, 2), "utf8");
4057
5245
  }
4058
5246
  };
4059
5247
  function emptyOrder() {
@@ -4082,9 +5270,9 @@ function sanitize(value) {
4082
5270
  }
4083
5271
 
4084
5272
  // ../host/dist/folders.js
4085
- import { mkdir as mkdir7, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
4086
- import path13 from "node:path";
4087
- import { randomUUID as randomUUID2 } from "node:crypto";
5273
+ import { mkdir as mkdir9, readFile as readFile11, writeFile as writeFile10 } from "node:fs/promises";
5274
+ import path16 from "node:path";
5275
+ import { randomUUID as randomUUID3 } from "node:crypto";
4088
5276
  var Folders = class {
4089
5277
  #workspaceFolders = /* @__PURE__ */ new Map();
4090
5278
  #sessionFolders = /* @__PURE__ */ new Map();
@@ -4094,11 +5282,11 @@ var Folders = class {
4094
5282
  #pinnedAssignments = /* @__PURE__ */ new Map();
4095
5283
  #storePath;
4096
5284
  constructor(dataDir) {
4097
- this.#storePath = path13.join(dataDir, "folders.json");
5285
+ this.#storePath = path16.join(dataDir, "folders.json");
4098
5286
  }
4099
5287
  async load() {
4100
5288
  try {
4101
- const raw = await readFile10(this.#storePath, "utf8");
5289
+ const raw = await readFile11(this.#storePath, "utf8");
4102
5290
  const parsed = JSON.parse(raw);
4103
5291
  this.#workspaceFolders = new Map(Object.entries(parsed.workspaceFolders ?? {}).filter(([, r]) => isWorkspaceFolder(r)));
4104
5292
  this.#sessionFolders = new Map(Object.entries(parsed.sessionFolders ?? {}).filter(([, r]) => isSessionFolder(r)));
@@ -4128,19 +5316,19 @@ var Folders = class {
4128
5316
  };
4129
5317
  }
4130
5318
  async createWorkspaceFolder(name) {
4131
- const id = randomUUID2();
5319
+ const id = randomUUID3();
4132
5320
  this.#workspaceFolders.set(id, { name });
4133
5321
  await this.#persist();
4134
5322
  return id;
4135
5323
  }
4136
5324
  async createSessionFolder(workspaceId, name) {
4137
- const id = randomUUID2();
5325
+ const id = randomUUID3();
4138
5326
  this.#sessionFolders.set(id, { name, workspaceId });
4139
5327
  await this.#persist();
4140
5328
  return id;
4141
5329
  }
4142
5330
  async createPinnedFolder(name) {
4143
- const id = randomUUID2();
5331
+ const id = randomUUID3();
4144
5332
  this.#pinnedFolders.set(id, { name });
4145
5333
  await this.#persist();
4146
5334
  return id;
@@ -4281,8 +5469,8 @@ var Folders = class {
4281
5469
  sessionAssignments: Object.fromEntries(this.#sessionAssignments),
4282
5470
  pinnedAssignments: Object.fromEntries(this.#pinnedAssignments)
4283
5471
  };
4284
- await mkdir7(path13.dirname(this.#storePath), { recursive: true });
4285
- await writeFile8(this.#storePath, JSON.stringify(file, null, 2), "utf8");
5472
+ await mkdir9(path16.dirname(this.#storePath), { recursive: true });
5473
+ await writeFile10(this.#storePath, JSON.stringify(file, null, 2), "utf8");
4286
5474
  }
4287
5475
  };
4288
5476
  function isWorkspaceFolder(value) {
@@ -4296,9 +5484,9 @@ function isPinnedFolder(value) {
4296
5484
  }
4297
5485
 
4298
5486
  // ../host/dist/app-settings.js
4299
- import { mkdir as mkdir8, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
5487
+ import { mkdir as mkdir10, readFile as readFile12, writeFile as writeFile11 } from "node:fs/promises";
4300
5488
  import { homedir as homedir4 } from "node:os";
4301
- import path14 from "node:path";
5489
+ import path17 from "node:path";
4302
5490
  var DEFAULT_AUTO_ARCHIVE_AFTER_MS = 2 * 24 * 60 * 60 * 1e3;
4303
5491
  var MIN_AUTO_ARCHIVE_AFTER_MS = 60 * 60 * 1e3;
4304
5492
  var DEFAULT_AUTO_STOP_AFTER_MS = 10 * 60 * 1e3;
@@ -4332,17 +5520,22 @@ var DEFAULT_APP_SETTINGS = {
4332
5520
  // машине что угодно.
4333
5521
  terminalEnabled: false,
4334
5522
  // Выключено по умолчанию: списки идут в том порядке, в каком их расставили.
4335
- liveSessionsFirst: false
5523
+ liveSessionsFirst: false,
5524
+ // Пятичасовое окно останавливаем с запасом: хэндофф — это ещё один ход, и
5525
+ // ему нужно место. Недельное — у самого края: до сброса там может быть
5526
+ // несколько дней, и терять их из-за пары процентов жалко.
5527
+ limitStopFiveHourPct: 95,
5528
+ limitStopSevenDayPct: 99
4336
5529
  };
4337
5530
  var AppSettingsStore = class {
4338
5531
  #settings = { ...DEFAULT_APP_SETTINGS };
4339
5532
  #storePath;
4340
5533
  constructor(dataDir) {
4341
- this.#storePath = path14.join(dataDir, "app-settings.json");
5534
+ this.#storePath = path17.join(dataDir, "app-settings.json");
4342
5535
  }
4343
5536
  async load() {
4344
5537
  try {
4345
- const raw = await readFile11(this.#storePath, "utf8");
5538
+ const raw = await readFile12(this.#storePath, "utf8");
4346
5539
  this.#settings = sanitize2(JSON.parse(raw), DEFAULT_APP_SETTINGS);
4347
5540
  } catch {
4348
5541
  this.#settings = { ...DEFAULT_APP_SETTINGS };
@@ -4362,8 +5555,8 @@ var AppSettingsStore = class {
4362
5555
  async update(patch) {
4363
5556
  const next = sanitize2(patch, this.#settings);
4364
5557
  this.#settings = next;
4365
- await mkdir8(path14.dirname(this.#storePath), { recursive: true });
4366
- await writeFile9(this.#storePath, JSON.stringify(next, null, 2), "utf8");
5558
+ await mkdir10(path17.dirname(this.#storePath), { recursive: true });
5559
+ await writeFile11(this.#storePath, JSON.stringify(next, null, 2), "utf8");
4367
5560
  return this.current;
4368
5561
  }
4369
5562
  };
@@ -4405,10 +5598,16 @@ function sanitize2(value, fallback) {
4405
5598
  if (typeof record.liveSessionsFirst === "boolean") {
4406
5599
  result.liveSessionsFirst = record.liveSessionsFirst;
4407
5600
  }
5601
+ for (const key of ["limitStopFiveHourPct", "limitStopSevenDayPct"]) {
5602
+ const pct = record[key];
5603
+ if (typeof pct === "number" && Number.isFinite(pct)) {
5604
+ result[key] = pct <= 0 ? 0 : Math.min(Math.max(Math.round(pct), 1), 100);
5605
+ }
5606
+ }
4408
5607
  return result;
4409
5608
  }
4410
5609
  function claudeHome() {
4411
- return path14.join(homedir4(), ".claude");
5610
+ return path17.join(homedir4(), ".claude");
4412
5611
  }
4413
5612
  function writableRoots(mode, cwd, workspacePaths) {
4414
5613
  if (mode === "all")
@@ -4418,17 +5617,189 @@ function writableRoots(mode, cwd, workspacePaths) {
4418
5617
  return [...roots];
4419
5618
  for (const workspace of [cwd, ...workspacePaths]) {
4420
5619
  roots.add(workspace);
4421
- const parent = path14.dirname(workspace);
5620
+ const parent = path17.dirname(workspace);
4422
5621
  if (parent !== workspace)
4423
5622
  roots.add(parent);
4424
5623
  }
4425
5624
  return [...roots];
4426
5625
  }
4427
5626
 
5627
+ // ../host/dist/handoff-store.js
5628
+ import { mkdir as mkdir11, readFile as readFile13, readdir as readdir3, rm as rm4, writeFile as writeFile12 } from "node:fs/promises";
5629
+ import path18 from "node:path";
5630
+ var HandoffStore = class {
5631
+ #dir;
5632
+ constructor(dataDir) {
5633
+ this.#dir = path18.join(dataDir, "handoffs");
5634
+ }
5635
+ async get(sessionId) {
5636
+ const file = this.#file(sessionId);
5637
+ if (!file)
5638
+ return null;
5639
+ try {
5640
+ const parsed = JSON.parse(await readFile13(file, "utf8"));
5641
+ return typeof parsed.text === "string" ? parsed : null;
5642
+ } catch {
5643
+ return null;
5644
+ }
5645
+ }
5646
+ async save(handoff) {
5647
+ const file = this.#file(handoff.sessionId);
5648
+ if (!file)
5649
+ return;
5650
+ await mkdir11(this.#dir, { recursive: true });
5651
+ await writeFile12(file, JSON.stringify(handoff, null, 2), "utf8");
5652
+ }
5653
+ async remove(sessionId) {
5654
+ const file = this.#file(sessionId);
5655
+ if (file)
5656
+ await rm4(file, { force: true });
5657
+ }
5658
+ /** id всех сохранённых хэндоффов. */
5659
+ async list() {
5660
+ try {
5661
+ return (await readdir3(this.#dir)).filter((name) => name.endsWith(".json")).map((name) => name.slice(0, -".json".length));
5662
+ } catch {
5663
+ return [];
5664
+ }
5665
+ }
5666
+ /** Путь файла; `null` — id пришёл от клиента и на имя файла не годится. */
5667
+ #file(sessionId) {
5668
+ if (!/^[\w-]{1,128}$/.test(sessionId))
5669
+ return null;
5670
+ return path18.join(this.#dir, `${sessionId}.json`);
5671
+ }
5672
+ };
5673
+ var LimitStopState = class {
5674
+ #fired = {};
5675
+ #storePath;
5676
+ constructor(dataDir) {
5677
+ this.#storePath = path18.join(dataDir, "limit-stop.json");
5678
+ }
5679
+ async load() {
5680
+ try {
5681
+ const parsed = JSON.parse(await readFile13(this.#storePath, "utf8"));
5682
+ this.#fired = {};
5683
+ for (const key of ["fiveHour", "sevenDay"]) {
5684
+ const until = parsed[key];
5685
+ if (typeof until === "number" && Number.isFinite(until))
5686
+ this.#fired[key] = until;
5687
+ }
5688
+ } catch {
5689
+ this.#fired = {};
5690
+ }
5691
+ }
5692
+ /** Окно `limit` уже сработало и ещё не сбросилось. */
5693
+ fired(limit, now = Date.now()) {
5694
+ return (this.#fired[limit] ?? 0) > now;
5695
+ }
5696
+ /** Запомнить срабатывание до `until` (момент сброса окна). */
5697
+ async mark(limit, until) {
5698
+ this.#fired[limit] = until;
5699
+ await mkdir11(path18.dirname(this.#storePath), { recursive: true });
5700
+ await writeFile12(this.#storePath, JSON.stringify(this.#fired, null, 2), "utf8");
5701
+ }
5702
+ };
5703
+
5704
+ // ../host/dist/limit-guard.js
5705
+ var HANDOFF_TIMEOUT_MS = 3 * 6e4;
5706
+ var LIMIT_SWEEP_MS = 6e4;
5707
+ var WINDOW_SPAN_MS = {
5708
+ fiveHour: 5 * 60 * 6e4,
5709
+ sevenDay: 7 * 24 * 60 * 6e4
5710
+ };
5711
+ var LIMIT_LABEL = {
5712
+ fiveHour: "5-\u0447\u0430\u0441\u043E\u0432\u043E\u0439 \u043B\u0438\u043C\u0438\u0442",
5713
+ sevenDay: "\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u044B\u0439 \u043B\u0438\u043C\u0438\u0442"
5714
+ };
5715
+ function crossedLimit(usage, settings, fired, now = Date.now()) {
5716
+ if (!usage.available)
5717
+ return null;
5718
+ const checks = [
5719
+ ["fiveHour", usage.fiveHour, settings.limitStopFiveHourPct],
5720
+ ["sevenDay", usage.sevenDay, settings.limitStopSevenDayPct]
5721
+ ];
5722
+ for (const [limit, window, threshold] of checks) {
5723
+ if (!window || threshold <= 0 || window.utilization < threshold)
5724
+ continue;
5725
+ if (fired(limit))
5726
+ continue;
5727
+ const resets = Date.parse(window.resetsAt);
5728
+ const until = Number.isFinite(resets) && resets > now ? resets : now + WINDOW_SPAN_MS[limit];
5729
+ return { limit, window, until };
5730
+ }
5731
+ return null;
5732
+ }
5733
+ function handoffPrompt(hit) {
5734
+ return [
5735
+ `[ccv] ${capitalize(LIMIT_LABEL[hit.limit])} \u043F\u043E\u0447\u0442\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D (${Math.round(hit.window.utilization)} %), \u0440\u0430\u0431\u043E\u0442\u0430 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430.`,
5736
+ "\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B \u0432 \u044D\u0442\u043E\u043C \u0445\u043E\u0434\u0435 \u043E\u0442\u043A\u043B\u044E\u0447\u0435\u043D\u044B \u2014 \u043D\u0435 \u0432\u044B\u0437\u044B\u0432\u0430\u0439 \u0438\u0445.",
5737
+ "\u041E\u0442\u0432\u0435\u0442\u044C \u043E\u0434\u043D\u0438\u043C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435\u043C \u2014 \u0445\u044D\u043D\u0434\u043E\u0444\u0444\u043E\u043C, \u0441 \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u0440\u0430\u0431\u043E\u0442\u0443 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442 \u0432 \u043D\u043E\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438.",
5738
+ "Markdown, \u0440\u0430\u0437\u0434\u0435\u043B\u044B: \xAB## \u0426\u0435\u043B\u044C\xBB, \xAB## \u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435\xBB (\u0447\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u043D\u043E, \u0447\u0442\u043E \u043D\u0435\u0442, \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E),",
5739
+ "\xAB## \u0422\u0440\u043E\u043D\u0443\u0442\u044B\u0435 \u0444\u0430\u0439\u043B\u044B\xBB, \xAB## \u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B\xBB, \xAB## \u0421\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0448\u0430\u0433\xBB.",
5740
+ "\u041A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E: \u043F\u0443\u0442\u0438, \u043A\u043E\u043C\u0430\u043D\u0434\u044B, \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F. \u0411\u0435\u0437 \u0432\u0441\u0442\u0443\u043F\u043B\u0435\u043D\u0438\u0439."
5741
+ ].join("\n");
5742
+ }
5743
+ var EDIT_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
5744
+ function journalHandoff(entries) {
5745
+ const prompts = [];
5746
+ let lastText = "";
5747
+ let todos = null;
5748
+ const files = /* @__PURE__ */ new Set();
5749
+ for (const { event } of entries) {
5750
+ if (event.kind === "user.message") {
5751
+ if (!event.text.startsWith("[ccv]"))
5752
+ prompts.push(event.text);
5753
+ } else if (event.kind === "assistant.text") {
5754
+ if (event.text.trim())
5755
+ lastText = event.text;
5756
+ } else if (event.kind === "tool.use") {
5757
+ const input = event.input ?? {};
5758
+ if (EDIT_TOOLS.has(event.name)) {
5759
+ const target = input.file_path ?? input.notebook_path;
5760
+ if (typeof target === "string")
5761
+ files.add(target);
5762
+ } else if (event.name === "TodoWrite" && Array.isArray(input.todos)) {
5763
+ todos = input.todos.flatMap((todo) => {
5764
+ const t = todo;
5765
+ return typeof t.content === "string" ? [{ content: t.content, status: String(t.status ?? "") }] : [];
5766
+ });
5767
+ }
5768
+ }
5769
+ }
5770
+ const sections = [
5771
+ "_\u0425\u044D\u043D\u0434\u043E\u0444\u0444 \u0441\u043E\u0431\u0440\u0430\u043D \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u0438\u0437 \u0436\u0443\u0440\u043D\u0430\u043B\u0430 \u0441\u0435\u0441\u0441\u0438\u0438: \u0430\u0433\u0435\u043D\u0442 \u043D\u0435 \u0443\u0441\u043F\u0435\u043B \u043D\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0435\u0433\u043E \u0441\u0430\u043C._"
5772
+ ];
5773
+ if (prompts.length > 0) {
5774
+ sections.push("## \u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0435 \u0437\u0430\u043F\u0440\u043E\u0441\u044B\n\n" + prompts.slice(-3).map((text) => quote(clip2(text, 1500))).join("\n\n"));
5775
+ }
5776
+ if (todos && todos.length > 0) {
5777
+ const mark = (status) => status === "completed" ? "[x]" : status === "in_progress" ? "[~]" : "[ ]";
5778
+ sections.push("## \u0417\u0430\u0434\u0430\u0447\u0438\n\n" + todos.map((t) => `- ${mark(t.status)} ${t.content}`).join("\n"));
5779
+ }
5780
+ if (files.size > 0) {
5781
+ sections.push("## \u0422\u0440\u043E\u043D\u0443\u0442\u044B\u0435 \u0444\u0430\u0439\u043B\u044B\n\n" + [...files].map((f) => `- \`${f}\``).join("\n"));
5782
+ }
5783
+ if (lastText)
5784
+ sections.push("## \u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u043E\u0442\u0432\u0435\u0442 \u0430\u0433\u0435\u043D\u0442\u0430\n\n" + clip2(lastText, 4e3));
5785
+ if (sections.length === 1)
5786
+ sections.push("\u0412 \u0436\u0443\u0440\u043D\u0430\u043B\u0435 \u044D\u0442\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0448\u043B\u043E\u0441\u044C.");
5787
+ return sections.join("\n\n");
5788
+ }
5789
+ function clip2(text, max) {
5790
+ return text.length > max ? `${text.slice(0, max)}\u2026` : text;
5791
+ }
5792
+ function quote(text) {
5793
+ return text.split("\n").map((line) => `> ${line}`).join("\n");
5794
+ }
5795
+ function capitalize(text) {
5796
+ return text.charAt(0).toUpperCase() + text.slice(1);
5797
+ }
5798
+
4428
5799
  // ../host/dist/plugins.js
4429
- import { readFile as readFile12 } from "node:fs/promises";
5800
+ import { readFile as readFile14 } from "node:fs/promises";
4430
5801
  import { homedir as homedir5 } from "node:os";
4431
- import path15 from "node:path";
5802
+ import path19 from "node:path";
4432
5803
  var TIMEOUT_MS2 = 12e4;
4433
5804
  var SAFE_ARG = /^[A-Za-z0-9][A-Za-z0-9._@/-]*$/;
4434
5805
  var PluginCli = class {
@@ -4527,10 +5898,10 @@ async function readCatalog(markets, installed) {
4527
5898
  return lists.flat().sort((a, b) => a.name.localeCompare(b.name));
4528
5899
  }
4529
5900
  async function readMarketplace(market, installed) {
4530
- const root = market.installLocation || path15.join(homedir5(), ".claude", "plugins", "marketplaces", market.name);
5901
+ const root = market.installLocation || path19.join(homedir5(), ".claude", "plugins", "marketplaces", market.name);
4531
5902
  let manifest;
4532
5903
  try {
4533
- const text = await readFile12(path15.join(root, ".claude-plugin", "marketplace.json"), "utf8");
5904
+ const text = await readFile14(path19.join(root, ".claude-plugin", "marketplace.json"), "utf8");
4534
5905
  manifest = JSON.parse(text);
4535
5906
  } catch {
4536
5907
  return [];
@@ -4573,7 +5944,7 @@ function authorName(raw) {
4573
5944
  }
4574
5945
 
4575
5946
  // ../host/dist/auth.js
4576
- import { EventEmitter as EventEmitter2 } from "node:events";
5947
+ import { EventEmitter as EventEmitter3 } from "node:events";
4577
5948
  var QUICK_TIMEOUT_MS = 3e4;
4578
5949
  var LOGIN_TIMEOUT_MS = 10 * 6e4;
4579
5950
  var URL_RE = /https:\/\/\S*\/oauth\/authorize\S*/;
@@ -4585,7 +5956,7 @@ var IDLE = {
4585
5956
  output: "",
4586
5957
  error: null
4587
5958
  };
4588
- var AuthCli = class extends EventEmitter2 {
5959
+ var AuthCli = class extends EventEmitter3 {
4589
5960
  #child = null;
4590
5961
  #state = IDLE;
4591
5962
  #timer = null;
@@ -4617,7 +5988,7 @@ var AuthCli = class extends EventEmitter2 {
4617
5988
  if (this.#child)
4618
5989
  throw new Error("\u0412\u0445\u043E\u0434 \u0443\u0436\u0435 \u0438\u0434\u0451\u0442 \u2014 \u0434\u043E\u0436\u0434\u0438\u0442\u0435\u0441\u044C \u0438\u043B\u0438 \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u0435 \u0435\u0433\u043E");
4619
5990
  if (switchAccount) {
4620
- const out = await run(["auth", "logout"], QUICK_TIMEOUT_MS).catch(describe4);
5991
+ const out = await run(["auth", "logout"], QUICK_TIMEOUT_MS).catch(describe5);
4621
5992
  this.#push({ ...IDLE, active: true, output: `${out}
4622
5993
  ` });
4623
5994
  this.emit("changed");
@@ -4637,7 +6008,7 @@ var AuthCli = class extends EventEmitter2 {
4637
6008
  child.stdout?.on("data", (chunk) => this.#absorb(chunk.toString()));
4638
6009
  child.stderr?.on("data", (chunk) => this.#absorb(chunk.toString()));
4639
6010
  child.on("error", (error) => {
4640
- this.#fail(error.code === "ENOENT" ? "\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D claude \u0432 PATH \u2014 \u0432\u0445\u043E\u0434 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D" : describe4(error));
6011
+ this.#fail(error.code === "ENOENT" ? "\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D claude \u0432 PATH \u2014 \u0432\u0445\u043E\u0434 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D" : describe5(error));
4641
6012
  });
4642
6013
  child.on("close", (code) => {
4643
6014
  if (this.#child !== child)
@@ -4765,14 +6136,14 @@ function parseStatus2(raw) {
4765
6136
  function str2(value) {
4766
6137
  return typeof value === "string" ? value : "";
4767
6138
  }
4768
- function describe4(error) {
6139
+ function describe5(error) {
4769
6140
  return error instanceof Error ? error.message : String(error);
4770
6141
  }
4771
6142
 
4772
6143
  // ../host/dist/relay-link.js
4773
- import { EventEmitter as EventEmitter3 } from "node:events";
4774
- import { readFile as readFile13, writeFile as writeFile10, rm as rm3, mkdir as mkdir9 } from "node:fs/promises";
4775
- import path16 from "node:path";
6144
+ import { EventEmitter as EventEmitter4 } from "node:events";
6145
+ import { readFile as readFile15, writeFile as writeFile13, rm as rm5, mkdir as mkdir12 } from "node:fs/promises";
6146
+ import path20 from "node:path";
4776
6147
  import { hostname } from "node:os";
4777
6148
  import WebSocket from "ws";
4778
6149
 
@@ -4789,7 +6160,7 @@ var SILENCE_TIMEOUT_MS = 95e3;
4789
6160
  function jitter(delay) {
4790
6161
  return Math.round(delay * (0.8 + Math.random() * 0.4));
4791
6162
  }
4792
- var RelayLink = class extends EventEmitter3 {
6163
+ var RelayLink = class extends EventEmitter4 {
4793
6164
  #credentials = null;
4794
6165
  #socket = null;
4795
6166
  #retry = RETRY_MIN_MS;
@@ -4827,7 +6198,7 @@ var RelayLink = class extends EventEmitter3 {
4827
6198
  #log;
4828
6199
  constructor(options2) {
4829
6200
  super();
4830
- this.#file = path16.join(options2.dataDir, "relay.json");
6201
+ this.#file = path20.join(options2.dataDir, "relay.json");
4831
6202
  this.#defaultUrl = options2.defaultUrl.replace(/\/+$/, "");
4832
6203
  this.#log = options2.log ?? ((msg) => console.log(`[relay] ${msg}`));
4833
6204
  }
@@ -4908,7 +6279,7 @@ var RelayLink = class extends EventEmitter3 {
4908
6279
  }
4909
6280
  async #read() {
4910
6281
  try {
4911
- const raw = await readFile13(this.#file, "utf8");
6282
+ const raw = await readFile15(this.#file, "utf8");
4912
6283
  const parsed = JSON.parse(raw);
4913
6284
  if (typeof parsed !== "object" || parsed === null)
4914
6285
  return null;
@@ -4921,8 +6292,8 @@ var RelayLink = class extends EventEmitter3 {
4921
6292
  }
4922
6293
  }
4923
6294
  async #write(credentials) {
4924
- await mkdir9(path16.dirname(this.#file), { recursive: true });
4925
- await writeFile10(this.#file, `${JSON.stringify(credentials, null, 2)}
6295
+ await mkdir12(path20.dirname(this.#file), { recursive: true });
6296
+ await writeFile13(this.#file, `${JSON.stringify(credentials, null, 2)}
4926
6297
  `, {
4927
6298
  encoding: "utf8",
4928
6299
  mode: 384
@@ -4946,7 +6317,7 @@ var RelayLink = class extends EventEmitter3 {
4946
6317
  body: JSON.stringify({ code, name: machine, platform: process.platform })
4947
6318
  });
4948
6319
  } catch (error) {
4949
- throw new Error(`\u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043D\u0438\u043B\u0438\u0441\u044C \u0434\u043E ${url2}: ${describe5(error)}`);
6320
+ throw new Error(`\u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043D\u0438\u043B\u0438\u0441\u044C \u0434\u043E ${url2}: ${describe6(error)}`);
4950
6321
  }
4951
6322
  const data = await response.json().catch(() => ({}));
4952
6323
  if (!response.ok || !data.token || !data.hostId) {
@@ -4974,7 +6345,7 @@ var RelayLink = class extends EventEmitter3 {
4974
6345
  this.#error = null;
4975
6346
  this.#clients.clear();
4976
6347
  this.#shell = { enabled: false, pending: false, email: null, confirmedAt: null };
4977
- await rm3(this.#file, { force: true });
6348
+ await rm5(this.#file, { force: true });
4978
6349
  this.#changed();
4979
6350
  this.#log("\u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u0435 \u0441\u043D\u044F\u0442\u043E");
4980
6351
  }
@@ -5165,11 +6536,11 @@ var RelayLink = class extends EventEmitter3 {
5165
6536
  socket.send(JSON.stringify(frame));
5166
6537
  }
5167
6538
  };
5168
- function describe5(error) {
6539
+ function describe6(error) {
5169
6540
  return error instanceof Error ? error.message : String(error);
5170
6541
  }
5171
6542
  function humanize(error) {
5172
- const message = describe5(error);
6543
+ const message = describe6(error);
5173
6544
  if (/handshake has timed out/i.test(message)) {
5174
6545
  return `relay \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043D\u0430 \u0440\u0443\u043A\u043E\u043F\u043E\u0436\u0430\u0442\u0438\u0435 \u0437\u0430 ${Math.round(HANDSHAKE_TIMEOUT_MS / 1e3)} \u0441 \u2014 \u043F\u0440\u043E\u0431\u0443\u0435\u043C \u0441\u043D\u043E\u0432\u0430`;
5175
6546
  }
@@ -5177,14 +6548,14 @@ function humanize(error) {
5177
6548
  }
5178
6549
 
5179
6550
  // ../host/dist/terminal.js
5180
- import { EventEmitter as EventEmitter4 } from "node:events";
6551
+ import { EventEmitter as EventEmitter5 } from "node:events";
5181
6552
  import { access as access2, constants as constants2 } from "node:fs/promises";
5182
- import path17 from "node:path";
5183
- import { randomUUID as randomUUID3 } from "node:crypto";
6553
+ import path21 from "node:path";
6554
+ import { randomUUID as randomUUID4 } from "node:crypto";
5184
6555
  var SCROLLBACK_BYTES = 256 * 1024;
5185
6556
  var FLUSH_MS = 12;
5186
6557
  var MAX_TERMINALS = 24;
5187
- var TerminalHub = class extends EventEmitter4 {
6558
+ var TerminalHub = class extends EventEmitter5 {
5188
6559
  #terminals = /* @__PURE__ */ new Map();
5189
6560
  #module = null;
5190
6561
  list(workspaceId) {
@@ -5217,7 +6588,7 @@ var TerminalHub = class extends EventEmitter4 {
5217
6588
  env: environment()
5218
6589
  });
5219
6590
  const info = {
5220
- id: randomUUID3(),
6591
+ id: randomUUID4(),
5221
6592
  workspaceId: options2.workspaceId,
5222
6593
  title: shell.title,
5223
6594
  shell: shell.file,
@@ -5349,22 +6720,22 @@ async function defaultShell() {
5349
6720
  if (pwsh)
5350
6721
  return { file: pwsh, args: [], title: "pwsh" };
5351
6722
  const system = process.env.SystemRoot ?? "C:\\Windows";
5352
- const powershell = path17.join(system, "System32/WindowsPowerShell/v1.0/powershell.exe");
6723
+ const powershell = path21.join(system, "System32/WindowsPowerShell/v1.0/powershell.exe");
5353
6724
  if (await exists2(powershell))
5354
6725
  return { file: powershell, args: [], title: "powershell" };
5355
6726
  return { file: process.env.COMSPEC ?? "cmd.exe", args: [], title: "cmd" };
5356
6727
  }
5357
6728
  const shell = process.env.SHELL;
5358
6729
  if (shell && await exists2(shell))
5359
- return { file: shell, args: [], title: path17.basename(shell) };
6730
+ return { file: shell, args: [], title: path21.basename(shell) };
5360
6731
  if (await exists2("/bin/bash"))
5361
6732
  return { file: "/bin/bash", args: [], title: "bash" };
5362
6733
  return { file: "/bin/sh", args: [], title: "sh" };
5363
6734
  }
5364
6735
  async function findOnPath(name) {
5365
- const dirs = (process.env.PATH ?? "").split(path17.delimiter).filter(Boolean);
6736
+ const dirs = (process.env.PATH ?? "").split(path21.delimiter).filter(Boolean);
5366
6737
  for (const dir of dirs) {
5367
- const candidate = path17.join(dir, name);
6738
+ const candidate = path21.join(dir, name);
5368
6739
  if (await exists2(candidate))
5369
6740
  return candidate;
5370
6741
  }
@@ -5375,10 +6746,10 @@ function exists2(file) {
5375
6746
  }
5376
6747
 
5377
6748
  // ../host/dist/mcp-config.js
5378
- import { readFile as readFile14, writeFile as writeFile11, mkdir as mkdir10 } from "node:fs/promises";
5379
- import path18 from "node:path";
6749
+ import { readFile as readFile16, writeFile as writeFile14, mkdir as mkdir13 } from "node:fs/promises";
6750
+ import path22 from "node:path";
5380
6751
  var MCP_FILE = ".mcp.json";
5381
- var LOCAL_SETTINGS2 = path18.join(".claude", "settings.local.json");
6752
+ var LOCAL_SETTINGS2 = path22.join(".claude", "settings.local.json");
5382
6753
  var queues = /* @__PURE__ */ new Map();
5383
6754
  function serialize(cwd, work) {
5384
6755
  const previous = queues.get(cwd) ?? Promise.resolve();
@@ -5387,7 +6758,7 @@ function serialize(cwd, work) {
5387
6758
  return next;
5388
6759
  }
5389
6760
  async function readProjectServers(cwd) {
5390
- const file = await readJson(path18.join(cwd, MCP_FILE));
6761
+ const file = await readJson(path22.join(cwd, MCP_FILE));
5391
6762
  const servers = file?.mcpServers;
5392
6763
  if (!servers || typeof servers !== "object")
5393
6764
  return {};
@@ -5395,23 +6766,23 @@ async function readProjectServers(cwd) {
5395
6766
  }
5396
6767
  function addProjectServer(cwd, name, config) {
5397
6768
  return serialize(cwd, async () => {
5398
- const file = await readJson(path18.join(cwd, MCP_FILE)) ?? {};
6769
+ const file = await readJson(path22.join(cwd, MCP_FILE)) ?? {};
5399
6770
  const servers = file.mcpServers ?? {};
5400
6771
  servers[name] = config;
5401
6772
  file.mcpServers = servers;
5402
- await writeJson(path18.join(cwd, MCP_FILE), file);
6773
+ await writeJson(path22.join(cwd, MCP_FILE), file);
5403
6774
  await approve(cwd, name);
5404
6775
  });
5405
6776
  }
5406
6777
  function removeProjectServer(cwd, name) {
5407
6778
  return serialize(cwd, async () => {
5408
- const file = await readJson(path18.join(cwd, MCP_FILE));
6779
+ const file = await readJson(path22.join(cwd, MCP_FILE));
5409
6780
  const servers = file?.mcpServers;
5410
6781
  if (!servers || !(name in servers))
5411
6782
  return false;
5412
6783
  delete servers[name];
5413
- await writeJson(path18.join(cwd, MCP_FILE), file);
5414
- const settingsPath = path18.join(cwd, LOCAL_SETTINGS2);
6784
+ await writeJson(path22.join(cwd, MCP_FILE), file);
6785
+ const settingsPath = path22.join(cwd, LOCAL_SETTINGS2);
5415
6786
  const settings = await readJson(settingsPath);
5416
6787
  const enabled = settings?.enabledMcpjsonServers;
5417
6788
  if (settings && Array.isArray(enabled) && enabled.includes(name)) {
@@ -5422,7 +6793,7 @@ function removeProjectServer(cwd, name) {
5422
6793
  });
5423
6794
  }
5424
6795
  async function approve(cwd, name) {
5425
- const file = path18.join(cwd, LOCAL_SETTINGS2);
6796
+ const file = path22.join(cwd, LOCAL_SETTINGS2);
5426
6797
  const settings = await readJson(file) ?? {};
5427
6798
  const enabled = Array.isArray(settings.enabledMcpjsonServers) ? settings.enabledMcpjsonServers : [];
5428
6799
  if (!enabled.includes(name))
@@ -5461,21 +6832,21 @@ function describeConfig(config) {
5461
6832
  }
5462
6833
  async function readJson(file) {
5463
6834
  try {
5464
- const raw = await readFile14(file, "utf8");
6835
+ const raw = await readFile16(file, "utf8");
5465
6836
  const parsed = JSON.parse(raw);
5466
6837
  return parsed && typeof parsed === "object" ? parsed : null;
5467
6838
  } catch (error) {
5468
6839
  if (error.code === "ENOENT")
5469
6840
  return null;
5470
6841
  if (error instanceof SyntaxError) {
5471
- throw new Error(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u0442\u044C ${path18.basename(file)}: ${error.message}`);
6842
+ throw new Error(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u0442\u044C ${path22.basename(file)}: ${error.message}`);
5472
6843
  }
5473
6844
  throw error;
5474
6845
  }
5475
6846
  }
5476
6847
  async function writeJson(file, data) {
5477
- await mkdir10(path18.dirname(file), { recursive: true });
5478
- await writeFile11(file, `${JSON.stringify(data, null, 2)}
6848
+ await mkdir13(path22.dirname(file), { recursive: true });
6849
+ await writeFile14(file, `${JSON.stringify(data, null, 2)}
5479
6850
  `, "utf8");
5480
6851
  }
5481
6852
 
@@ -5500,6 +6871,7 @@ var HostServer = class {
5500
6871
  #settings;
5501
6872
  #finder;
5502
6873
  #sessions = /* @__PURE__ */ new Map();
6874
+ #antigravity;
5503
6875
  /** Боковые сессии («вопрос на полях») по id родителя — не больше одной на разговор. */
5504
6876
  #sides = /* @__PURE__ */ new Map();
5505
6877
  /**
@@ -5529,7 +6901,12 @@ var HostServer = class {
5529
6901
  */
5530
6902
  #draftTouchedAt = /* @__PURE__ */ new Map();
5531
6903
  #autoStopTimer = null;
5532
- #hostId = randomUUID4();
6904
+ #handoffs;
6905
+ #limitStop;
6906
+ #limitTimer = null;
6907
+ /** Идёт остановка у лимита — второй снимок лимитов её не запускает. */
6908
+ #limitStopping = false;
6909
+ #hostId = randomUUID5();
5533
6910
  #plugins = new PluginCli();
5534
6911
  #auth = new AuthCli();
5535
6912
  #relay;
@@ -5552,6 +6929,9 @@ var HostServer = class {
5552
6929
  this.#order = new ListOrderStore(options2.dataDir);
5553
6930
  this.#folders = new Folders(options2.dataDir);
5554
6931
  this.#settings = new AppSettingsStore(options2.dataDir);
6932
+ this.#antigravity = new AntigravityStore(options2.dataDir);
6933
+ this.#handoffs = new HandoffStore(options2.dataDir);
6934
+ this.#limitStop = new LimitStopState(options2.dataDir);
5555
6935
  this.#finder = new ProjectFinder(this.#workspaces);
5556
6936
  this.#relay = new RelayLink({ dataDir: options2.dataDir, defaultUrl: options2.relayUrl });
5557
6937
  this.#github = new GitHubAccount(options2.dataDir);
@@ -5571,6 +6951,7 @@ var HostServer = class {
5571
6951
  await this.#order.load();
5572
6952
  await this.#folders.load();
5573
6953
  await this.#settings.load();
6954
+ await this.#limitStop.load();
5574
6955
  await this.#github.load();
5575
6956
  await warmClaudeLauncher();
5576
6957
  void this.#finder.warm();
@@ -5595,11 +6976,16 @@ var HostServer = class {
5595
6976
  });
5596
6977
  this.#autoStopTimer = setInterval(() => void this.#stopIdleSessions(), AUTO_STOP_SWEEP_MS);
5597
6978
  this.#autoStopTimer.unref();
6979
+ this.#limitTimer = setInterval(() => this.#sweepUsage(), LIMIT_SWEEP_MS);
6980
+ this.#limitTimer.unref();
5598
6981
  }
5599
6982
  async shutdown() {
5600
6983
  if (this.#autoStopTimer)
5601
6984
  clearInterval(this.#autoStopTimer);
5602
6985
  this.#autoStopTimer = null;
6986
+ if (this.#limitTimer)
6987
+ clearInterval(this.#limitTimer);
6988
+ this.#limitTimer = null;
5603
6989
  this.#auth.cancel();
5604
6990
  this.#terminals.closeAll();
5605
6991
  await this.#relay.close();
@@ -6338,6 +7724,17 @@ var HostServer = class {
6338
7724
  case "settings.get":
6339
7725
  this.#send(socket, { type: "settings.state", settings: this.#settings.current });
6340
7726
  return;
7727
+ case "handoff.get":
7728
+ this.#send(socket, {
7729
+ type: "handoff.state",
7730
+ sessionId: frame.sessionId,
7731
+ handoff: await this.#handoffs.get(frame.sessionId)
7732
+ });
7733
+ return;
7734
+ case "handoff.dismiss":
7735
+ await this.#handoffs.remove(frame.sessionId);
7736
+ this.#broadcast({ type: "handoff.state", sessionId: frame.sessionId, handoff: null });
7737
+ return;
6341
7738
  case "settings.update": {
6342
7739
  const before = this.#settings.current;
6343
7740
  const settings = await this.#settings.update(frame.settings);
@@ -6600,6 +7997,88 @@ var HostServer = class {
6600
7997
  this.#draftTouchedAt.delete(session.id);
6601
7998
  }
6602
7999
  }
8000
+ // -------------------------------------------------------------------------
8001
+ // Остановка у лимита плана
8002
+ // -------------------------------------------------------------------------
8003
+ /** Пользовательские сессии Claude — те, что останавливаются у лимита. */
8004
+ #claudeUserSessions() {
8005
+ return [...this.#sessions.values()].filter((s) => s instanceof ClaudeSession && s !== this.#finderSession && Boolean(s.workspaceId) && !s.summary().parentSessionId);
8006
+ }
8007
+ #isBusy(session) {
8008
+ const summary = session.summary();
8009
+ return BUSY_STATES.has(summary.state) || summary.backgroundWork;
8010
+ }
8011
+ /**
8012
+ * Перечитать лимиты, пока хоть одна сессия занята. Сама сессия делает это
8013
+ * по результатам инструментов, а долгий шаг модели без инструментов иначе
8014
+ * прошёл бы мимо порога. Лимиты общие на аккаунт — спросить одну хватит.
8015
+ */
8016
+ #sweepUsage() {
8017
+ const busy = this.#claudeUserSessions().find((s) => this.#isBusy(s));
8018
+ if (busy)
8019
+ void busy.refreshUsage();
8020
+ }
8021
+ async #checkLimit(usage) {
8022
+ if (this.#limitStopping)
8023
+ return;
8024
+ const hit = crossedLimit(usage, this.#settings.current, (limit) => this.#limitStop.fired(limit));
8025
+ if (!hit)
8026
+ return;
8027
+ this.#limitStopping = true;
8028
+ try {
8029
+ await this.#limitStop.mark(hit.limit, hit.until);
8030
+ await this.#stopAtLimit(hit);
8031
+ } catch (error) {
8032
+ console.error("[limit-stop]", error instanceof Error ? error.message : String(error));
8033
+ } finally {
8034
+ this.#limitStopping = false;
8035
+ }
8036
+ }
8037
+ /**
8038
+ * Остановить все живые сессии Claude: занятые — после хода хэндоффа (или с
8039
+ * хэндоффом из журнала, если агент не ответил), простаивающие — сразу.
8040
+ */
8041
+ async #stopAtLimit(hit) {
8042
+ const sessions = this.#claudeUserSessions();
8043
+ if (sessions.length === 0)
8044
+ return;
8045
+ const prompt = handoffPrompt(hit);
8046
+ const results = await Promise.all(sessions.map(async (session) => {
8047
+ let saved = false;
8048
+ if (this.#isBusy(session)) {
8049
+ const text = await session.runHandoff(prompt, HANDOFF_TIMEOUT_MS);
8050
+ const summary = session.summary();
8051
+ const handoff = {
8052
+ sessionId: summary.claudeSessionId ?? session.id,
8053
+ workspaceId: session.workspaceId,
8054
+ title: summary.title ?? "",
8055
+ limit: hit.limit,
8056
+ utilization: hit.window.utilization,
8057
+ resetsAt: hit.window.resetsAt,
8058
+ stoppedAt: Date.now(),
8059
+ source: text ? "agent" : "journal",
8060
+ text: text ?? journalHandoff(session.journal.since(0))
8061
+ };
8062
+ try {
8063
+ await this.#handoffs.save(handoff);
8064
+ saved = true;
8065
+ this.#broadcast({ type: "handoff.state", sessionId: handoff.sessionId, handoff });
8066
+ } catch (error) {
8067
+ console.error("[limit-stop]", error instanceof Error ? error.message : String(error));
8068
+ }
8069
+ }
8070
+ await this.#dropSession(session.id, "limit").catch(() => false);
8071
+ return saved;
8072
+ }));
8073
+ this.#broadcast({
8074
+ type: "limit.stopped",
8075
+ limit: hit.limit,
8076
+ utilization: hit.window.utilization,
8077
+ resetsAt: hit.window.resetsAt,
8078
+ stopped: results.length,
8079
+ handoffs: results.filter(Boolean).length
8080
+ });
8081
+ }
6603
8082
  /**
6604
8083
  * Живые сессии всех проектов — вкладке запущенных.
6605
8084
  *
@@ -6654,6 +8133,26 @@ var HostServer = class {
6654
8133
  const workspace = this.#workspaces.find(frame.workspaceId);
6655
8134
  if (!workspace)
6656
8135
  throw new Error(`\u0412\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${frame.workspaceId}`);
8136
+ const agyRecord = frame.resume ? await this.#antigravity.find(frame.resume) : null;
8137
+ if (agyRecord || !frame.resume && frame.provider === "antigravity") {
8138
+ if (agyRecord)
8139
+ await this.#archive.unarchive([agyRecord.id]);
8140
+ const session2 = new AntigravitySession({
8141
+ workspaceId: workspace.id,
8142
+ cwd: agyRecord?.cwd ?? workspace.path,
8143
+ store: this.#antigravity,
8144
+ ...agyRecord ? { record: agyRecord } : {},
8145
+ ...frame.permissionMode ? { permissionMode: frame.permissionMode } : {},
8146
+ ...frame.model ? { model: frame.model } : {},
8147
+ defaultTitle: workspace.name
8148
+ });
8149
+ this.#wire(session2);
8150
+ this.#sessions.set(session2.id, session2);
8151
+ session2.start();
8152
+ this.#sendLive();
8153
+ this.#send(socket, { type: "session.summary", session: session2.summary() });
8154
+ return;
8155
+ }
6657
8156
  const resumed = frame.resume && !frame.fork ? (await this.#workspaces.sessionsOnDisk(workspace.id)).find((info) => info.sessionId === frame.resume) : void 0;
6658
8157
  const resumedTitle = resumed ? diskTitle(resumed) : null;
6659
8158
  if (frame.resume)
@@ -6710,12 +8209,15 @@ var HostServer = class {
6710
8209
  });
6711
8210
  session.on("usage", (usage) => {
6712
8211
  this.#broadcast({ type: "usage.state", sessionId: session.id, usage });
8212
+ if (session instanceof ClaudeSession)
8213
+ void this.#checkLimit(usage);
6713
8214
  });
6714
8215
  session.on("context", (context) => {
6715
8216
  this.#broadcast({ type: "context.state", sessionId: session.id, context });
6716
8217
  });
6717
8218
  session.on("stderr", (data) => {
6718
- process.stderr.write(`[claude ${session.id.slice(0, 8)}] ${data}`);
8219
+ const tag = session instanceof AntigravitySession ? "agy" : "claude";
8220
+ process.stderr.write(`[${tag} ${session.id.slice(0, 8)}] ${data}`);
6719
8221
  });
6720
8222
  }
6721
8223
  // -------------------------------------------------------------------------
@@ -6734,6 +8236,9 @@ var HostServer = class {
6734
8236
  * разговора пока и не было.
6735
8237
  */
6736
8238
  #ensureSide(parent) {
8239
+ if (parent instanceof AntigravitySession) {
8240
+ throw new Error("\u0412\u043E\u043F\u0440\u043E\u0441 \u043D\u0430 \u043F\u043E\u043B\u044F\u0445 \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0441\u0435\u0441\u0441\u0438\u044F\u0445 Claude");
8241
+ }
6737
8242
  const existing = this.#sides.get(parent.id);
6738
8243
  if (existing && existing.state !== "closed" && existing.state !== "error") {
6739
8244
  return existing;
@@ -7031,6 +8536,11 @@ var HostServer = class {
7031
8536
  live.setTitle(title);
7032
8537
  return;
7033
8538
  }
8539
+ if (await this.#antigravity.rename(sessionId, title)) {
8540
+ if (workspaceId)
8541
+ await this.#sendSessionList(workspaceId);
8542
+ return;
8543
+ }
7034
8544
  const dir = workspaceId ? this.#workspaces.find(workspaceId)?.path : void 0;
7035
8545
  await renameSession2(sessionId, title, dir ? { dir } : void 0);
7036
8546
  if (!workspaceId)
@@ -7052,6 +8562,7 @@ var HostServer = class {
7052
8562
  const workspacePath = this.#workspaces.find(workspaceId)?.path ?? "";
7053
8563
  const historical = onDisk.filter((info) => !liveIds.has(info.sessionId) && info.customTitle !== SIDE_TITLE && info.customTitle !== FINDER_TITLE).map((info) => ({
7054
8564
  id: info.sessionId,
8565
+ provider: "claude",
7055
8566
  claudeSessionId: info.sessionId,
7056
8567
  workspaceId,
7057
8568
  cwd: info.cwd ?? workspacePath,
@@ -7076,6 +8587,11 @@ var HostServer = class {
7076
8587
  lastSeq: 0,
7077
8588
  totalCostUsd: 0
7078
8589
  }));
8590
+ for (const record of await this.#antigravity.list(workspaceId)) {
8591
+ if (liveIds.has(record.id))
8592
+ continue;
8593
+ historical.push(antigravitySummary(record));
8594
+ }
7079
8595
  const known = /* @__PURE__ */ new Set([...liveIds, ...historical.map((s) => s.id)]);
7080
8596
  await this.#archive.sweep(workspaceId, historical.filter((s) => !this.#pins.has(s.id)).map((s) => ({ id: s.id, updatedAt: s.updatedAt })), known, this.#settings.current.autoArchiveAfterMs);
7081
8597
  await this.#pins.sweep(workspaceId, known);
@@ -7128,8 +8644,8 @@ var HostServer = class {
7128
8644
  return;
7129
8645
  }
7130
8646
  const url2 = new URL(req.url ?? "/", "http://localhost");
7131
- const requested = path19.join(webRoot, path19.normalize(url2.pathname));
7132
- const target = requested.startsWith(webRoot) && existsSync4(requested) && statSync3(requested).isFile() ? requested : path19.join(webRoot, "index.html");
8647
+ const requested = path23.join(webRoot, path23.normalize(url2.pathname));
8648
+ const target = requested.startsWith(webRoot) && existsSync4(requested) && statSync3(requested).isFile() ? requested : path23.join(webRoot, "index.html");
7133
8649
  if (!existsSync4(target)) {
7134
8650
  res.writeHead(404).end("not found");
7135
8651
  return;
@@ -7155,12 +8671,36 @@ function contentType(file) {
7155
8671
  ".svg": "image/svg+xml",
7156
8672
  ".woff2": "font/woff2"
7157
8673
  };
7158
- return types[path19.extname(file)] ?? "application/octet-stream";
8674
+ return types[path23.extname(file)] ?? "application/octet-stream";
8675
+ }
8676
+ function antigravitySummary(record) {
8677
+ return {
8678
+ id: record.id,
8679
+ provider: "antigravity",
8680
+ claudeSessionId: record.id,
8681
+ workspaceId: record.workspaceId,
8682
+ cwd: record.cwd,
8683
+ parentSessionId: null,
8684
+ title: record.title,
8685
+ state: "closed",
8686
+ lastTurn: null,
8687
+ backgroundWork: false,
8688
+ model: record.model,
8689
+ permissionMode: record.permissionMode,
8690
+ effort: record.effort,
8691
+ ultracode: false,
8692
+ thinking: true,
8693
+ switchModelsOnFlag: true,
8694
+ createdAt: record.createdAt,
8695
+ updatedAt: record.updatedAt,
8696
+ lastSeq: 0,
8697
+ totalCostUsd: 0
8698
+ };
7159
8699
  }
7160
8700
 
7161
8701
  // dist/ccv.js
7162
- var HERE = path20.dirname(fileURLToPath(import.meta.url));
7163
- var PACKAGE_ROOT = path20.resolve(HERE, "..");
8702
+ var HERE = path24.dirname(fileURLToPath(import.meta.url));
8703
+ var PACKAGE_ROOT = path24.resolve(HERE, "..");
7164
8704
  var USAGE = `ccv \u2014 Claude Code Visual: \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0445\u043E\u0441\u0442 Claude Code \u0441 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043E\u043C \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435.
7165
8705
 
7166
8706
  \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435:
@@ -7227,7 +8767,7 @@ var server = new HostServer({
7227
8767
  dataDir: defaults.dataDir,
7228
8768
  // UI лежит в пакете рядом с бандлом — это и есть разница между установленной
7229
8769
  // утилитой и запуском из репозитория, где сборка живёт в `apps/web/dist`.
7230
- webRoot: process.env.CCV_WEB_ROOT ?? path20.join(PACKAGE_ROOT, "web"),
8770
+ webRoot: process.env.CCV_WEB_ROOT ?? path24.join(PACKAGE_ROOT, "web"),
7231
8771
  version,
7232
8772
  relayUrl: defaults.relayUrl
7233
8773
  });
@@ -7256,7 +8796,7 @@ for (const signal of ["SIGINT", "SIGTERM"]) {
7256
8796
  }
7257
8797
  async function packageVersion() {
7258
8798
  try {
7259
- const text = await readFile15(path20.join(PACKAGE_ROOT, "package.json"), "utf8");
8799
+ const text = await readFile17(path24.join(PACKAGE_ROOT, "package.json"), "utf8");
7260
8800
  return JSON.parse(text).version ?? "0.0.0";
7261
8801
  } catch {
7262
8802
  return "0.0.0";
@@ -7300,7 +8840,7 @@ function openBrowser(target) {
7300
8840
  return;
7301
8841
  }
7302
8842
  const command = process.platform === "win32" ? { file: "cmd", args: ["/c", "start", "", target] } : process.platform === "darwin" ? { file: "open", args: [target] } : { file: "xdg-open", args: [target] };
7303
- const child = spawn2(command.file, command.args, {
8843
+ const child = spawn3(command.file, command.args, {
7304
8844
  stdio: "ignore",
7305
8845
  detached: true,
7306
8846
  windowsHide: true