@i14ks/ccv 0.22.0 → 0.24.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/README.md +10 -2
  2. package/bundle/ccv.mjs +1455 -251
  3. package/package.json +5 -2
  4. package/web/assets/{codemirror-BVRIW86g.js → codemirror-BABguQzY.js} +1 -1
  5. package/web/assets/{index-BOTbHjsh.js → index-BHEVBqv1.js} +1 -1
  6. package/web/assets/{index-DiFzORZa.js → index-BJQdUP7a.js} +1 -1
  7. package/web/assets/{index-CwbKEdO4.js → index-BQiRpvdM.js} +1 -1
  8. package/web/assets/{index-DcOycRyY.js → index-BRzmWdWn.js} +1 -1
  9. package/web/assets/{index-Cj3tIh2i.js → index-BWAAro79.js} +1 -1
  10. package/web/assets/{index-DqNX5AfU.js → index-BaKf7c69.js} +1 -1
  11. package/web/assets/{index-BRDMJVs_.js → index-BmRYCxGv.js} +1 -1
  12. package/web/assets/{index-CftAsWDp.js → index-BnqCpVA0.js} +1 -1
  13. package/web/assets/{index-CHk-wT2e.js → index-Bqjj9q2W.js} +1 -1
  14. package/web/assets/{index-iBO43oLF.js → index-CIsgyM-R.js} +1 -1
  15. package/web/assets/{index-B54sB01B.js → index-CZppS1t2.js} +1 -1
  16. package/web/assets/{index-5z-WKiE2.js → index-Ck0zkLid.js} +1 -1
  17. package/web/assets/{index-CEtwcv2R.js → index-CxF-ndAb.js} +1 -1
  18. package/web/assets/{index-BUtKkXYx.js → index-DW1Fnc9x.js} +1 -1
  19. package/web/assets/index-DXMZoXqv.js +136 -0
  20. package/web/assets/index-DafZE925.css +1 -0
  21. package/web/assets/{index-BSbB9pNN.js → index-Di0Nghq9.js} +1 -1
  22. package/web/assets/{index-A6KnUhy4.js → index-DnlEeQVW.js} +1 -1
  23. package/web/assets/{index-BTHa3RWC.js → index-DogVeywh.js} +1 -1
  24. package/web/assets/{index-B5EnmuL9.js → index-U9tL3Foh.js} +1 -1
  25. package/web/assets/{index-C4RL1RRN.js → index-dSYnVYDn.js} +1 -1
  26. package/web/index.html +2 -2
  27. package/web/assets/index-L8M_w2e-.js +0 -134
  28. package/web/assets/index-xoxa8R94.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 spawn3 } from "node:child_process";
4
+ import { spawn as spawn4 } from "node:child_process";
5
5
  import { readFile as readFile17 } from "node:fs/promises";
6
- import path24 from "node:path";
6
+ import path25 from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { parseArgs } from "node:util";
9
9
 
@@ -145,10 +145,10 @@ function envDefaults() {
145
145
 
146
146
  // ../host/dist/server.js
147
147
  import { createServer } from "node:http";
148
- import { createReadStream, existsSync as existsSync4, statSync as statSync3 } from "node:fs";
149
- import path23 from "node:path";
150
- import { homedir as homedir6 } from "node:os";
151
- import { randomUUID as randomUUID5 } from "node:crypto";
148
+ import { createReadStream, existsSync as existsSync5, statSync as statSync4 } from "node:fs";
149
+ import path24 from "node:path";
150
+ import { homedir as homedir7 } from "node:os";
151
+ import { randomUUID as randomUUID6 } from "node:crypto";
152
152
  import { WebSocketServer } from "ws";
153
153
  import { renameSession as renameSession2 } from "@anthropic-ai/claude-agent-sdk";
154
154
 
@@ -187,12 +187,12 @@ function hashText(text) {
187
187
  // ../host/dist/workspace-ids.js
188
188
  function workspaceIdRemap(paths, sessionsOf) {
189
189
  const byLegacy = /* @__PURE__ */ new Map();
190
- for (const path25 of paths) {
191
- const id = workspaceIdFromPath(path25);
192
- for (const legacy of formerWorkspaceIdsFromPath(path25)) {
190
+ for (const path26 of paths) {
191
+ const id = workspaceIdFromPath(path26);
192
+ for (const legacy of formerWorkspaceIdsFromPath(path26)) {
193
193
  const group = byLegacy.get(legacy) ?? [];
194
194
  if (!group.some((target) => target.id === id))
195
- group.push({ id, path: path25 });
195
+ group.push({ id, path: path26 });
196
196
  byLegacy.set(legacy, group);
197
197
  }
198
198
  }
@@ -223,7 +223,7 @@ function workspaceIdRemap(paths, sessionsOf) {
223
223
  }
224
224
 
225
225
  // ../host/dist/session.js
226
- import { EventEmitter } from "node:events";
226
+ import { EventEmitter as EventEmitter2 } from "node:events";
227
227
  import { randomUUID } from "node:crypto";
228
228
  import path5 from "node:path";
229
229
  import { query, renameSession } from "@anthropic-ai/claude-agent-sdk";
@@ -787,6 +787,173 @@ var Journal = class {
787
787
  }
788
788
  };
789
789
 
790
+ // ../host/dist/network-guard.js
791
+ import { EventEmitter } from "node:events";
792
+ import { request } from "node:https";
793
+ import { networkInterfaces } from "node:os";
794
+ var TRACE_URL = "https://api.anthropic.com/cdn-cgi/trace";
795
+ var NETWORK_CHECK_MS = 5e3;
796
+ var INTERFACES_POLL_MS = 1e3;
797
+ var CHECK_TIMEOUT_MS = 4e3;
798
+ var RETRY_DELAY_MS = 1e3;
799
+ var NETWORK_FRESH_MS = 3e3;
800
+ var UNCHECKED_REASON = "\u041C\u0430\u0440\u0448\u0440\u0443\u0442 \u0434\u043E Anthropic \u0435\u0449\u0451 \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D";
801
+ var NetworkGuard = class extends EventEmitter {
802
+ #enabled = false;
803
+ #blocked = /* @__PURE__ */ new Set();
804
+ #state = {
805
+ status: "ok",
806
+ country: null,
807
+ reason: null,
808
+ checkedAt: null,
809
+ enabled: false
810
+ };
811
+ #checkTimer = null;
812
+ #interfacesTimer = null;
813
+ #interfaces = "";
814
+ #inFlight = null;
815
+ /** Растёт при каждой смене настроек: ответ проверки, начатой до неё, не применяется. */
816
+ #generation = 0;
817
+ get state() {
818
+ return { ...this.#state };
819
+ }
820
+ /** Почему агентам сейчас нельзя в сеть; `null` — можно. */
821
+ blockedReason() {
822
+ return this.#state.status === "blocked" ? this.#state.reason ?? UNCHECKED_REASON : null;
823
+ }
824
+ /**
825
+ * Применить настройки. Включение начинает с блока до первой проверки,
826
+ * выключение снимает блок сразу.
827
+ */
828
+ configure(enabled, blockedCountries) {
829
+ const countries = new Set(blockedCountries);
830
+ const same = enabled === this.#enabled && countries.size === this.#blocked.size && [...countries].every((c) => this.#blocked.has(c));
831
+ if (same)
832
+ return;
833
+ this.#generation += 1;
834
+ this.#blocked = countries;
835
+ const wasEnabled = this.#enabled;
836
+ this.#enabled = enabled;
837
+ if (!enabled) {
838
+ this.#stopTimers();
839
+ this.#apply({ status: "ok", country: this.#state.country, reason: null, checkedAt: null });
840
+ return;
841
+ }
842
+ if (!wasEnabled) {
843
+ this.#apply({ status: "blocked", country: null, reason: UNCHECKED_REASON, checkedAt: null });
844
+ this.#startTimers();
845
+ }
846
+ void this.check();
847
+ }
848
+ stop() {
849
+ this.#stopTimers();
850
+ }
851
+ /** Проверить сейчас. Параллельные вызовы делят одну проверку. */
852
+ check() {
853
+ if (!this.#enabled)
854
+ return Promise.resolve(this.state);
855
+ this.#inFlight ??= this.#runCheck().finally(() => {
856
+ this.#inFlight = null;
857
+ });
858
+ return this.#inFlight;
859
+ }
860
+ /** Проверить, если последняя проверка старше `NETWORK_FRESH_MS`. */
861
+ async ensureFresh() {
862
+ if (!this.#enabled)
863
+ return this.state;
864
+ const at = this.#state.checkedAt;
865
+ if (at !== null && Date.now() - at < NETWORK_FRESH_MS && !this.#inFlight)
866
+ return this.state;
867
+ return this.check();
868
+ }
869
+ async #runCheck() {
870
+ const generation = this.#generation;
871
+ let result = await traceCountry();
872
+ if (!result.ok && generation === this.#generation) {
873
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
874
+ result = await traceCountry();
875
+ }
876
+ if (generation !== this.#generation || !this.#enabled)
877
+ return this.state;
878
+ const checkedAt = Date.now();
879
+ if (!result.ok) {
880
+ this.#apply({
881
+ status: "blocked",
882
+ country: null,
883
+ reason: `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043C\u0430\u0440\u0448\u0440\u0443\u0442 \u0434\u043E Anthropic: ${result.error}`,
884
+ checkedAt
885
+ });
886
+ } else if (this.#blocked.has(result.country)) {
887
+ this.#apply({
888
+ status: "blocked",
889
+ country: result.country,
890
+ reason: `\u0417\u0430\u043F\u0440\u043E\u0441\u044B \u043A Anthropic \u0438\u0434\u0443\u0442 \u0438\u0437 ${result.country} \u2014 \u043F\u043E\u0445\u043E\u0436\u0435, VPN \u043D\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442`,
891
+ checkedAt
892
+ });
893
+ } else {
894
+ this.#apply({ status: "ok", country: result.country, reason: null, checkedAt });
895
+ }
896
+ return this.state;
897
+ }
898
+ #apply(next) {
899
+ const previous = this.#state;
900
+ this.#state = { ...next, enabled: this.#enabled };
901
+ const changed = previous.status !== next.status || previous.country !== next.country || previous.reason !== next.reason || previous.enabled !== this.#enabled;
902
+ if (changed)
903
+ this.emit("change", this.state);
904
+ }
905
+ #startTimers() {
906
+ this.#stopTimers();
907
+ this.#interfaces = interfacesSignature();
908
+ this.#checkTimer = setInterval(() => void this.check(), NETWORK_CHECK_MS);
909
+ this.#checkTimer.unref();
910
+ this.#interfacesTimer = setInterval(() => {
911
+ const signature = interfacesSignature();
912
+ if (signature === this.#interfaces)
913
+ return;
914
+ this.#interfaces = signature;
915
+ void this.check();
916
+ }, INTERFACES_POLL_MS);
917
+ this.#interfacesTimer.unref();
918
+ }
919
+ #stopTimers() {
920
+ if (this.#checkTimer)
921
+ clearInterval(this.#checkTimer);
922
+ if (this.#interfacesTimer)
923
+ clearInterval(this.#interfacesTimer);
924
+ this.#checkTimer = null;
925
+ this.#interfacesTimer = null;
926
+ }
927
+ };
928
+ var networkGuard = new NetworkGuard();
929
+ function traceCountry() {
930
+ return new Promise((resolve) => {
931
+ const req = request(TRACE_URL, { method: "GET", agent: false, timeout: CHECK_TIMEOUT_MS, headers: { "cache-control": "no-store" } }, (res) => {
932
+ let body = "";
933
+ res.setEncoding("utf8");
934
+ res.on("data", (chunk) => {
935
+ body += chunk;
936
+ if (body.length > 16e3)
937
+ req.destroy(new Error("\u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0434\u043B\u0438\u043D\u043D\u044B\u0439 \u043E\u0442\u0432\u0435\u0442"));
938
+ });
939
+ res.on("end", () => {
940
+ const country = /^loc=([A-Z]{2})$/m.exec(body)?.[1];
941
+ if (res.statusCode === 200 && country)
942
+ resolve({ ok: true, country });
943
+ else
944
+ resolve({ ok: false, error: `\u043D\u0435\u043E\u0436\u0438\u0434\u0430\u043D\u043D\u044B\u0439 \u043E\u0442\u0432\u0435\u0442 (HTTP ${res.statusCode ?? "\u2014"})` });
945
+ });
946
+ res.on("error", (error) => resolve({ ok: false, error: error.message }));
947
+ });
948
+ req.on("timeout", () => req.destroy(new Error("\u043D\u0435\u0442 \u043E\u0442\u0432\u0435\u0442\u0430")));
949
+ req.on("error", (error) => resolve({ ok: false, error: error.message }));
950
+ req.end();
951
+ });
952
+ }
953
+ function interfacesSignature() {
954
+ return Object.entries(networkInterfaces()).map(([name, addresses]) => `${name}:${(addresses ?? []).map((a) => a.address).sort().join(",")}`).sort().join("|");
955
+ }
956
+
790
957
  // ../host/dist/settings.js
791
958
  import { readFile as readFile3 } from "node:fs/promises";
792
959
  import { homedir as homedir2 } from "node:os";
@@ -1176,7 +1343,7 @@ var INTERRUPT_SETTLE_MS = 15e3;
1176
1343
  var TASK_STOP_TIMEOUT_MS = 5e3;
1177
1344
  var TASK_WAKE_MS = 5e3;
1178
1345
  var CONTEXT_MIN_INTERVAL_MS = 5e3;
1179
- var ClaudeSession = class extends EventEmitter {
1346
+ var ClaudeSession = class extends EventEmitter2 {
1180
1347
  id;
1181
1348
  workspaceId;
1182
1349
  cwd;
@@ -1484,6 +1651,8 @@ var ClaudeSession = class extends EventEmitter {
1484
1651
  * умолчанию) — обычное поведение, ничего не меняется.
1485
1652
  */
1486
1653
  #spawn(restarted, resumeAt) {
1654
+ if (this.#refuseOffRoute())
1655
+ return;
1487
1656
  const options2 = {
1488
1657
  cwd: this.cwd,
1489
1658
  // Без этого SDK не читает с диска ничего: ни CLAUDE.md, ни кастомные
@@ -1668,6 +1837,56 @@ var ClaudeSession = class extends EventEmitter {
1668
1837
  this.#query = null;
1669
1838
  this.#resetTasks();
1670
1839
  }
1840
+ /**
1841
+ * Погасить процесс CLI, не закрывая сессию: упал VPN (см. network-guard.ts).
1842
+ *
1843
+ * Идущий ход обрывается вместе с процессом, отложенное переподключение
1844
+ * отменяется, накопленные для повтора промпты забываются — повторить их сам
1845
+ * хост не вправе, решать человеку. Разговор цел: следующий промпт после
1846
+ * возвращения VPN поднимет процесс, продолжающий ту же сессию.
1847
+ */
1848
+ suspend(reason) {
1849
+ if (this.#closing || this.#state === "closed")
1850
+ return;
1851
+ if (this.#reconnectTimer) {
1852
+ clearTimeout(this.#reconnectTimer);
1853
+ this.#reconnectTimer = null;
1854
+ }
1855
+ this.#pendingRetry = [];
1856
+ if (!this.#query) {
1857
+ if (this.#state === "reconnecting")
1858
+ this.#setState("error");
1859
+ return;
1860
+ }
1861
+ const busy = this.#state !== "idle";
1862
+ this.#teardown(reason);
1863
+ this.#inbox = new AsyncQueue();
1864
+ this.#streamMessageId = null;
1865
+ this.#blockCounts.clear();
1866
+ if (busy)
1867
+ this.#lastTurn = "interrupted";
1868
+ this.#record({
1869
+ kind: "error",
1870
+ message: `${reason}. \u041F\u0440\u043E\u0446\u0435\u0441\u0441 Claude \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D${busy ? ", \u0445\u043E\u0434 \u043F\u0440\u0435\u0440\u0432\u0430\u043D" : ""}. \u0420\u0430\u0437\u0433\u043E\u0432\u043E\u0440 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D \u2014 \u043A\u043E\u0433\u0434\u0430 VPN \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F, \u043E\u0442\u043F\u0440\u0430\u0432\u044C\u0442\u0435 \u043F\u0440\u043E\u043C\u043F\u0442, \u0438 \u0441\u0435\u0441\u0441\u0438\u044F \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442.`
1871
+ });
1872
+ this.#setState("error");
1873
+ }
1874
+ /**
1875
+ * Нет VPN — процесс не поднимаем: пишем об этом в ленту и оставляем сессию
1876
+ * без процесса. Следующий промпт попробует снова.
1877
+ */
1878
+ #refuseOffRoute() {
1879
+ const blocked = networkGuard.blockedReason();
1880
+ if (!blocked)
1881
+ return false;
1882
+ this.#pendingRetry = [];
1883
+ this.#record({
1884
+ kind: "error",
1885
+ message: `${blocked}. \u041F\u0440\u043E\u0446\u0435\u0441\u0441 Claude \u043D\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D \u2014 \u043E\u0442\u043F\u0440\u0430\u0432\u044C\u0442\u0435 \u043F\u0440\u043E\u043C\u043F\u0442, \u043A\u043E\u0433\u0434\u0430 VPN \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F.`
1886
+ });
1887
+ this.#setState("error");
1888
+ return true;
1889
+ }
1671
1890
  // -------------------------------------------------------------------------
1672
1891
  // Переподключение
1673
1892
  // -------------------------------------------------------------------------
@@ -1697,6 +1916,8 @@ var ClaudeSession = class extends EventEmitter {
1697
1916
  clearTimeout(this.#reconnectTimer);
1698
1917
  this.#reconnectTimer = null;
1699
1918
  }
1919
+ if (this.#refuseOffRoute())
1920
+ return;
1700
1921
  this.#setState("reconnecting");
1701
1922
  this.#teardown("\u043F\u0435\u0440\u0435\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435");
1702
1923
  this.#inbox = new AsyncQueue();
@@ -1751,6 +1972,9 @@ var ClaudeSession = class extends EventEmitter {
1751
1972
  sendPrompt(text, images, service = false) {
1752
1973
  if (this.#closing)
1753
1974
  throw new Error("session is closed");
1975
+ const blocked = networkGuard.blockedReason();
1976
+ if (blocked)
1977
+ throw new Error(`${blocked}. \u041F\u0440\u043E\u043C\u043F\u0442 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D.`);
1754
1978
  this.#record({ kind: "user.message", text, images });
1755
1979
  if (!service && (!this.#title || this.#titleIsDefault)) {
1756
1980
  this.#title = text.slice(0, 80);
@@ -1788,6 +2012,9 @@ var ClaudeSession = class extends EventEmitter {
1788
2012
  async editPrompt(userIndex, text, images) {
1789
2013
  if (this.#closing)
1790
2014
  throw new Error("session is closed");
2015
+ const blocked = networkGuard.blockedReason();
2016
+ if (blocked)
2017
+ throw new Error(`${blocked}. \u041F\u0440\u0430\u0432\u043A\u0430 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0430.`);
1791
2018
  if (this.#state === "running")
1792
2019
  await this.interrupt();
1793
2020
  const source = this.#claudeSessionId ?? this.#options.resume;
@@ -2905,7 +3132,7 @@ function contentToText2(content) {
2905
3132
  }
2906
3133
 
2907
3134
  // ../host/dist/antigravity-session.js
2908
- import { EventEmitter as EventEmitter2 } from "node:events";
3135
+ import { EventEmitter as EventEmitter3 } from "node:events";
2909
3136
  import { spawn as spawn2, execFile as execFile3 } from "node:child_process";
2910
3137
  import { randomUUID as randomUUID2 } from "node:crypto";
2911
3138
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
@@ -3125,7 +3352,7 @@ function agyContextWindow(model) {
3125
3352
 
3126
3353
  // ../host/dist/antigravity-session.js
3127
3354
  var TRANSIENT = /* @__PURE__ */ new Set(["assistant.delta", "thinking.delta", "state"]);
3128
- var AntigravitySession = class extends EventEmitter2 {
3355
+ var AntigravitySession = class extends EventEmitter3 {
3129
3356
  id;
3130
3357
  workspaceId;
3131
3358
  cwd;
@@ -3265,6 +3492,8 @@ var AntigravitySession = class extends EventEmitter2 {
3265
3492
  }
3266
3493
  this.#capabilities = this.#baseCapabilities();
3267
3494
  this.emit("capabilities");
3495
+ if (this.#refuseOffRoute())
3496
+ return;
3268
3497
  void agyModels(this.#executable).then((models) => {
3269
3498
  if (!this.#capabilities || models.length === 0)
3270
3499
  return;
@@ -3323,6 +3552,8 @@ var AntigravitySession = class extends EventEmitter2 {
3323
3552
  #spawn() {
3324
3553
  if (!this.#executable || this.#closing)
3325
3554
  return;
3555
+ if (this.#refuseOffRoute())
3556
+ return;
3326
3557
  let child;
3327
3558
  try {
3328
3559
  child = spawn2(this.#executable, this.#args(), {
@@ -3420,12 +3651,48 @@ var AntigravitySession = class extends EventEmitter2 {
3420
3651
  reconnect(_reason) {
3421
3652
  this.#restart();
3422
3653
  }
3654
+ /**
3655
+ * Погасить процесс `agy`, не закрывая сессию: упал VPN (см.
3656
+ * network-guard.ts). Следующий промпт после его возвращения поднимет
3657
+ * процесс на том же разговоре.
3658
+ */
3659
+ suspend(reason) {
3660
+ if (this.#closing || this.#state === "closed" || !this.#child)
3661
+ return;
3662
+ const busy = this.#pendingTurns > 0;
3663
+ this.#restartPending = false;
3664
+ this.#kill();
3665
+ this.#closeOpenSteps();
3666
+ this.#pendingTurns = 0;
3667
+ if (busy)
3668
+ this.#lastTurn = "interrupted";
3669
+ this.#record_({
3670
+ kind: "error",
3671
+ message: `${reason}. \u041F\u0440\u043E\u0446\u0435\u0441\u0441 Antigravity \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D${busy ? ", \u0445\u043E\u0434 \u043F\u0440\u0435\u0440\u0432\u0430\u043D" : ""}. \u0420\u0430\u0437\u0433\u043E\u0432\u043E\u0440 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D \u2014 \u043A\u043E\u0433\u0434\u0430 VPN \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F, \u043E\u0442\u043F\u0440\u0430\u0432\u044C\u0442\u0435 \u043F\u0440\u043E\u043C\u043F\u0442, \u0438 \u0441\u0435\u0441\u0441\u0438\u044F \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442.`
3672
+ });
3673
+ this.#setState("error");
3674
+ }
3675
+ /** Нет VPN — процесс не поднимаем, пишем об этом в ленту. */
3676
+ #refuseOffRoute() {
3677
+ const blocked = networkGuard.blockedReason();
3678
+ if (!blocked)
3679
+ return false;
3680
+ this.#record_({
3681
+ kind: "error",
3682
+ message: `${blocked}. \u041F\u0440\u043E\u0446\u0435\u0441\u0441 Antigravity \u043D\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D \u2014 \u043E\u0442\u043F\u0440\u0430\u0432\u044C\u0442\u0435 \u043F\u0440\u043E\u043C\u043F\u0442, \u043A\u043E\u0433\u0434\u0430 VPN \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F.`
3683
+ });
3684
+ this.#setState("error");
3685
+ return true;
3686
+ }
3423
3687
  // -------------------------------------------------------------------------
3424
3688
  // Ввод
3425
3689
  // -------------------------------------------------------------------------
3426
3690
  sendPrompt(text, images) {
3427
3691
  if (this.#closing)
3428
3692
  throw new Error("session is closed");
3693
+ const blocked = networkGuard.blockedReason();
3694
+ if (blocked)
3695
+ throw new Error(`${blocked}. \u041F\u0440\u043E\u043C\u043F\u0442 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D.`);
3429
3696
  const command = /^\/([\w-]+)(?:\s+([\s\S]*))?$/.exec(text.trim());
3430
3697
  if (command && AGY_CLI_COMMANDS.has(command[1]) && (!images || images.length === 0)) {
3431
3698
  void this.#runCliCommand(text, command[1], command[2]?.trim() ?? "");
@@ -3642,7 +3909,7 @@ Attached image${paths.length > 1 ? "s" : ""} (view them with your file viewing t
3642
3909
  }
3643
3910
  /** Квота `agy` (`/usage`) — общая на аккаунт, кэш один на хост. */
3644
3911
  async refreshUsage(force = false) {
3645
- if (!this.#executable)
3912
+ if (!this.#executable || networkGuard.blockedReason())
3646
3913
  return;
3647
3914
  const usage = await agyUsage(this.#executable, force);
3648
3915
  if (!usage)
@@ -3820,148 +4087,995 @@ Attached image${paths.length > 1 ? "s" : ""} (view them with your file viewing t
3820
4087
  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).`
3821
4088
  });
3822
4089
  }
3823
- if (!ok && result.error)
3824
- this.#record_({ kind: "error", message: result.error });
3825
- this.#record_({
4090
+ if (!ok && result.error)
4091
+ this.#record_({ kind: "error", message: result.error });
4092
+ this.#record_({
4093
+ kind: "result",
4094
+ subtype: ok ? "success" : "error",
4095
+ isError: !ok,
4096
+ durationMs: Math.round((result.duration_seconds ?? 0) * 1e3),
4097
+ costUsd: null,
4098
+ usage,
4099
+ contextTokens: this.#contextTokens,
4100
+ text: result.response ?? null
4101
+ });
4102
+ this.#emitContext();
4103
+ void this.refreshUsage(true);
4104
+ this.#lastTurn = ok ? "completed" : "failed";
4105
+ this.#pendingTurns = Math.max(0, this.#pendingTurns - 1);
4106
+ if (this.#pendingTurns > 0)
4107
+ return;
4108
+ if (this.#restartPending)
4109
+ this.#restart();
4110
+ this.#setState("idle");
4111
+ }
4112
+ /** Незакрытые шаги оборванного хода: текст — в ленту, инструменты — с ошибкой. */
4113
+ #closeOpenSteps() {
4114
+ const prefix = this.#record.conversationId ?? this.id;
4115
+ for (const [index, text] of this.#texts) {
4116
+ if (text)
4117
+ this.#record_({ kind: "assistant.text", text, blockId: `${prefix}:${index}` });
4118
+ }
4119
+ for (const index of this.#tools) {
4120
+ this.#record_({
4121
+ kind: "tool.result",
4122
+ toolUseId: `${prefix}:${index}`,
4123
+ content: "\u041F\u0440\u0435\u0440\u0432\u0430\u043D\u043E",
4124
+ isError: true
4125
+ });
4126
+ }
4127
+ this.#texts.clear();
4128
+ this.#thinking.clear();
4129
+ this.#tools.clear();
4130
+ }
4131
+ // -------------------------------------------------------------------------
4132
+ #record_(event) {
4133
+ this.#updatedAt = Date.now();
4134
+ const entry = this.journal.append(event);
4135
+ this.emit("event", entry);
4136
+ if (TRANSIENT.has(event.kind))
4137
+ return;
4138
+ this.#unsaved.push({ ts: entry.ts, event });
4139
+ this.#scheduleFlush();
4140
+ }
4141
+ #scheduleFlush() {
4142
+ if (this.#flushTimer)
4143
+ return;
4144
+ this.#flushTimer = setTimeout(() => this.#flush(), 500);
4145
+ this.#flushTimer.unref?.();
4146
+ }
4147
+ #flush() {
4148
+ if (this.#flushTimer) {
4149
+ clearTimeout(this.#flushTimer);
4150
+ this.#flushTimer = null;
4151
+ }
4152
+ const batch = this.#unsaved;
4153
+ this.#unsaved = [];
4154
+ this.#record.updatedAt = this.#updatedAt;
4155
+ void this.#store.appendEvents(this.id, batch);
4156
+ void this.#store.save(this.#record);
4157
+ }
4158
+ #setState(state) {
4159
+ if (this.#state === state)
4160
+ return;
4161
+ this.#state = state;
4162
+ this.#record_({ kind: "state", state });
4163
+ if (state === "idle" || state === "error" || state === "closed")
4164
+ this.#flush();
4165
+ this.emit("summary", this.summary());
4166
+ }
4167
+ };
4168
+ function toolInput(parameters) {
4169
+ const input = { ...parameters ?? {} };
4170
+ const pick = (...keys) => keys.map((k) => input[k]).find((v) => typeof v === "string");
4171
+ const file = pick("TargetFile", "AbsolutePath", "File", "FilePath", "Path");
4172
+ if (file && input.file_path === void 0)
4173
+ input.file_path = file;
4174
+ const command = pick("CommandLine", "Command");
4175
+ if (command && input.command === void 0)
4176
+ input.command = command;
4177
+ const pattern = pick("Query", "Pattern", "SearchPattern");
4178
+ if (pattern && input.pattern === void 0)
4179
+ input.pattern = pattern;
4180
+ const dir = pick("DirectoryPath", "SearchPath", "SearchDirectory");
4181
+ if (dir && input.path === void 0)
4182
+ input.path = dir;
4183
+ const url2 = pick("Url", "URL");
4184
+ if (url2 && input.url === void 0)
4185
+ input.url = url2;
4186
+ return input;
4187
+ }
4188
+ function formatUsage(usage) {
4189
+ const rows = (usage.buckets ?? []).map((b) => {
4190
+ const left = Math.round(100 - b.window.utilization);
4191
+ const reset = b.window.resetsAt ? new Date(b.window.resetsAt).toLocaleString("ru-RU") : "\u2014";
4192
+ return `| ${b.title || b.label} | ${left}% | ${reset} |`;
4193
+ });
4194
+ if (rows.length === 0)
4195
+ return "\u041A\u0432\u043E\u0442\u0430 Antigravity: \u0434\u0430\u043D\u043D\u044B\u0445 \u043D\u0435\u0442.";
4196
+ 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");
4197
+ }
4198
+ function formatCommandReply(name, response) {
4199
+ const text = response.replace(/\s+$/, "");
4200
+ if (!text)
4201
+ return `/${name}: \u043F\u0443\u0441\u0442\u043E`;
4202
+ const lines = text.split(/\r?\n/);
4203
+ if (lines.every((l) => l.includes(" "))) {
4204
+ return lines.map((l) => {
4205
+ const [head, ...rest] = l.split(" ");
4206
+ return `- **${head}** \u2014 ${rest.join(" ")}`;
4207
+ }).join("\n");
4208
+ }
4209
+ return text;
4210
+ }
4211
+ function agyEffort(effort) {
4212
+ if (!effort)
4213
+ return null;
4214
+ return effort === "xhigh" ? "high" : effort;
4215
+ }
4216
+ function describe2(error) {
4217
+ return error instanceof Error ? error.message : String(error);
4218
+ }
4219
+
4220
+ // ../host/dist/codex-session.js
4221
+ import { EventEmitter as EventEmitter5 } from "node:events";
4222
+ import { randomUUID as randomUUID3 } from "node:crypto";
4223
+
4224
+ // ../host/dist/codex-cli.js
4225
+ import { accessSync, constants as constants2, existsSync as existsSync2, readFileSync, statSync } from "node:fs";
4226
+ import { homedir as homedir4 } from "node:os";
4227
+ import path8 from "node:path";
4228
+ var CODEX_MISSING_HINT = "Codex CLI \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D. \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435: npm install -g @openai/codex, \u0437\u0430\u0442\u0435\u043C \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u0435 codex login. \u0415\u0441\u043B\u0438 CLI \u0443\u0436\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D, \u0437\u0430\u0434\u0430\u0439\u0442\u0435 CCV_CODEX_PATH \u0438 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435 ccv.";
4229
+ function codexLauncher() {
4230
+ const forced = process.env.CCV_CODEX_PATH;
4231
+ if (forced)
4232
+ return launcherFor(path8.resolve(forced));
4233
+ const dirs = (process.env.PATH ?? "").split(path8.delimiter).filter(Boolean);
4234
+ if (process.platform === "win32") {
4235
+ const local = process.env.LOCALAPPDATA ?? path8.join(homedir4(), "AppData", "Local");
4236
+ dirs.push(path8.join(local, "Programs", "OpenAI", "Codex", "bin"));
4237
+ dirs.push(path8.join(process.env.APPDATA ?? path8.join(homedir4(), "AppData", "Roaming"), "npm"));
4238
+ } else {
4239
+ dirs.push(path8.join(homedir4(), ".local", "bin"), "/usr/local/bin", "/opt/homebrew/bin");
4240
+ }
4241
+ for (const dir of dirs) {
4242
+ for (const name of process.platform === "win32" ? ["codex.exe", "codex.cmd", "codex"] : ["codex"]) {
4243
+ const found = launcherFor(path8.join(dir.replace(/^"|"$/g, ""), name));
4244
+ if (found)
4245
+ return found;
4246
+ }
4247
+ }
4248
+ return null;
4249
+ }
4250
+ function launcherFor(file) {
4251
+ try {
4252
+ if (!statSync(file).isFile())
4253
+ return null;
4254
+ if (/\.[cm]?js$/i.test(file))
4255
+ return { file: process.execPath, prefix: [file] };
4256
+ if (process.platform === "win32" && /\.(cmd|bat|ps1)$/i.test(file)) {
4257
+ const root = path8.dirname(file);
4258
+ const candidates = [path8.join(root, "node_modules", "@openai", "codex", "bin", "codex.js")];
4259
+ const shim = readFileSync(file, "utf8");
4260
+ for (const match of shim.matchAll(/(?:%dp0%|%~dp0|\$basedir)[\\/]([^"\r\n]+?\.[cm]?js)/gi)) {
4261
+ candidates.push(path8.resolve(root, match[1]));
4262
+ }
4263
+ const script = candidates.find((candidate) => existsSync2(candidate));
4264
+ return script ? { file: process.execPath, prefix: [script] } : null;
4265
+ }
4266
+ if (process.platform === "win32" && !/\.exe$/i.test(file))
4267
+ return null;
4268
+ accessSync(file, constants2.X_OK);
4269
+ return { file, prefix: [] };
4270
+ } catch {
4271
+ return null;
4272
+ }
4273
+ }
4274
+
4275
+ // ../host/dist/codex-rpc.js
4276
+ import { EventEmitter as EventEmitter4 } from "node:events";
4277
+ import { execFile as execFile4, spawn as spawn3 } from "node:child_process";
4278
+ import { createInterface as createInterface2 } from "node:readline";
4279
+ var CodexRpc = class extends EventEmitter4 {
4280
+ #child;
4281
+ #pending = /* @__PURE__ */ new Map();
4282
+ #nextId = 0;
4283
+ #closed = false;
4284
+ #stderr = "";
4285
+ constructor(launcher, cwd) {
4286
+ super();
4287
+ this.#child = spawn3(launcher.file, [...launcher.prefix, "app-server", "--listen", "stdio://"], {
4288
+ cwd,
4289
+ windowsHide: true,
4290
+ stdio: ["pipe", "pipe", "pipe"],
4291
+ env: { ...process.env, NO_COLOR: "1" }
4292
+ });
4293
+ createInterface2({ input: this.#child.stdout }).on("line", (line) => {
4294
+ let message;
4295
+ try {
4296
+ message = JSON.parse(line);
4297
+ } catch {
4298
+ return;
4299
+ }
4300
+ if (!message || typeof message !== "object")
4301
+ return;
4302
+ if (message.method) {
4303
+ this.emit("message", message);
4304
+ return;
4305
+ }
4306
+ const pending = typeof message.id === "number" ? this.#pending.get(message.id) : void 0;
4307
+ if (!pending)
4308
+ return;
4309
+ this.#pending.delete(message.id);
4310
+ clearTimeout(pending.timer);
4311
+ if (message.error)
4312
+ pending.reject(new Error(message.error.message));
4313
+ else
4314
+ pending.resolve(message.result);
4315
+ });
4316
+ this.#child.stderr.on("data", (chunk) => {
4317
+ this.#stderr = (this.#stderr + chunk.toString()).slice(-4e3);
4318
+ this.emit("stderr", chunk.toString());
4319
+ });
4320
+ this.#child.stdin.on("error", (error) => this.#finish(error));
4321
+ this.#child.on("error", (error) => this.#finish(error));
4322
+ this.#child.on("exit", (code) => this.#finish(new Error(`Codex \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F (${code ?? "signal"}). ${this.#stderr.trim()}`)));
4323
+ }
4324
+ request(method, params = {}) {
4325
+ if (this.#closed)
4326
+ return Promise.reject(new Error("\u0421\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0441 Codex \u0437\u0430\u043A\u0440\u044B\u0442\u043E"));
4327
+ const id = ++this.#nextId;
4328
+ return new Promise((resolve, reject) => {
4329
+ const timer = setTimeout(() => {
4330
+ this.#pending.delete(id);
4331
+ reject(new Error(`Codex \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043D\u0430 ${method} \u0437\u0430 60 \u0441\u0435\u043A\u0443\u043D\u0434`));
4332
+ }, 6e4);
4333
+ this.#pending.set(id, { resolve: (value) => resolve(value), reject, timer });
4334
+ this.write({ id, method, params });
4335
+ });
4336
+ }
4337
+ write(message) {
4338
+ if (!this.#closed)
4339
+ this.#child.stdin.write(JSON.stringify(message) + "\n");
4340
+ }
4341
+ close() {
4342
+ this.#finish(new Error("\u0421\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0441 Codex \u0437\u0430\u043A\u0440\u044B\u0442\u043E"), false);
4343
+ this.#child.stdin.end();
4344
+ const child = this.#child;
4345
+ if (child.exitCode !== null)
4346
+ return;
4347
+ if (process.platform === "win32" && child.pid) {
4348
+ execFile4("taskkill", ["/pid", String(child.pid), "/T", "/F"], { windowsHide: true }, () => {
4349
+ if (child.exitCode === null)
4350
+ child.kill();
4351
+ });
4352
+ } else
4353
+ child.kill("SIGTERM");
4354
+ }
4355
+ #finish(error, notify = true) {
4356
+ if (this.#closed)
4357
+ return;
4358
+ this.#closed = true;
4359
+ for (const pending of this.#pending.values()) {
4360
+ clearTimeout(pending.timer);
4361
+ pending.reject(error);
4362
+ }
4363
+ this.#pending.clear();
4364
+ if (notify)
4365
+ this.emit("exit", error);
4366
+ }
4367
+ };
4368
+
4369
+ // ../host/dist/codex-session.js
4370
+ function codexPolicy(mode) {
4371
+ switch (mode) {
4372
+ case "default":
4373
+ return { approvalPolicy: "untrusted", sandbox: "workspace-write" };
4374
+ case "acceptEdits":
4375
+ return { approvalPolicy: "on-request", sandbox: "workspace-write" };
4376
+ case "plan":
4377
+ return { approvalPolicy: "untrusted", sandbox: "read-only" };
4378
+ case "bypassPermissions":
4379
+ return { approvalPolicy: "never", sandbox: "danger-full-access" };
4380
+ default:
4381
+ throw new Error(`Codex \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0440\u0435\u0436\u0438\u043C \u043F\u0440\u0430\u0432 \xAB${mode}\xBB`);
4382
+ }
4383
+ }
4384
+ function codexUsage(limits) {
4385
+ const window = (value) => value ? {
4386
+ utilization: Math.min(100, Math.max(0, value.usedPercent)),
4387
+ resetsAt: value.resetsAt ? new Date(value.resetsAt * 1e3).toISOString() : ""
4388
+ } : null;
4389
+ return {
4390
+ available: !!(limits.primary || limits.secondary),
4391
+ plan: limits.planType ?? "",
4392
+ fiveHour: window(limits.primary),
4393
+ sevenDay: window(limits.secondary),
4394
+ sevenDayOpus: null,
4395
+ fetchedAt: Date.now()
4396
+ };
4397
+ }
4398
+ var CodexSession = class extends EventEmitter5 {
4399
+ provider = "codex";
4400
+ id;
4401
+ workspaceId;
4402
+ cwd;
4403
+ journal = new Journal();
4404
+ #store;
4405
+ #record;
4406
+ #history = Promise.resolve([]);
4407
+ #rpc = null;
4408
+ #ready = Promise.resolve();
4409
+ #state = "starting";
4410
+ #lastTurn = null;
4411
+ #capabilities = null;
4412
+ #usage = null;
4413
+ #context = null;
4414
+ #turnId = null;
4415
+ #turnStartedAt = 0;
4416
+ #turnUsage = null;
4417
+ #totalUsage = null;
4418
+ #turnUsageBaseline = null;
4419
+ #interruptRequested = false;
4420
+ #busy = false;
4421
+ #startingTurn = false;
4422
+ #generation = 0;
4423
+ #queue = [];
4424
+ #pending = /* @__PURE__ */ new Map();
4425
+ #texts = /* @__PURE__ */ new Map();
4426
+ #tools = /* @__PURE__ */ new Set();
4427
+ #items = /* @__PURE__ */ new Map();
4428
+ #titleIsDefault;
4429
+ #unsaved = [];
4430
+ #flushTimer = null;
4431
+ constructor(options2) {
4432
+ super();
4433
+ this.workspaceId = options2.workspaceId;
4434
+ this.cwd = options2.cwd;
4435
+ this.#store = options2.store;
4436
+ this.#record = options2.record ? { ...options2.record } : {
4437
+ id: randomUUID3(),
4438
+ conversationId: null,
4439
+ workspaceId: options2.workspaceId,
4440
+ cwd: options2.cwd,
4441
+ title: options2.defaultTitle ?? null,
4442
+ model: options2.model ?? null,
4443
+ permissionMode: options2.permissionMode ?? "default",
4444
+ effort: null,
4445
+ createdAt: Date.now(),
4446
+ updatedAt: Date.now()
4447
+ };
4448
+ if (options2.permissionMode)
4449
+ this.#record.permissionMode = options2.permissionMode;
4450
+ if (options2.model)
4451
+ this.#record.model = options2.model;
4452
+ this.id = this.#record.id;
4453
+ this.#context = this.#record.context ?? null;
4454
+ if (!options2.record?.conversationId) {
4455
+ this.#totalUsage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 };
4456
+ }
4457
+ this.#titleIsDefault = !options2.record;
4458
+ }
4459
+ get state() {
4460
+ return this.#state;
4461
+ }
4462
+ get capabilities() {
4463
+ return this.#capabilities;
4464
+ }
4465
+ get claudeSessionId() {
4466
+ return this.id;
4467
+ }
4468
+ get parentSessionId() {
4469
+ return null;
4470
+ }
4471
+ get usage() {
4472
+ return this.#usage;
4473
+ }
4474
+ get context() {
4475
+ return this.#context;
4476
+ }
4477
+ history() {
4478
+ return this.#history;
4479
+ }
4480
+ summary() {
4481
+ return {
4482
+ id: this.id,
4483
+ provider: this.provider,
4484
+ claudeSessionId: this.id,
4485
+ workspaceId: this.workspaceId,
4486
+ cwd: this.cwd,
4487
+ parentSessionId: null,
4488
+ title: this.#record.title,
4489
+ state: this.#state,
4490
+ lastTurn: this.#lastTurn,
4491
+ backgroundWork: false,
4492
+ tasks: [],
4493
+ model: this.#record.model,
4494
+ permissionMode: this.#record.permissionMode,
4495
+ effort: this.#record.effort,
4496
+ ultracode: false,
4497
+ thinking: true,
4498
+ switchModelsOnFlag: false,
4499
+ createdAt: this.#record.createdAt,
4500
+ updatedAt: this.#record.updatedAt,
4501
+ lastSeq: this.journal.lastSeq,
4502
+ totalCostUsd: 0
4503
+ };
4504
+ }
4505
+ start() {
4506
+ this.#history = this.#store.readEvents(this.id).then((entries) => {
4507
+ const pending = /* @__PURE__ */ new Set();
4508
+ for (const { event } of entries) {
4509
+ if (event.kind === "permission.request")
4510
+ pending.add(event.requestId);
4511
+ if (event.kind === "permission.resolved")
4512
+ pending.delete(event.requestId);
4513
+ }
4514
+ for (const requestId of pending)
4515
+ entries.push({ seq: entries.length + 1, ts: Date.now(), event: {
4516
+ kind: "permission.resolved",
4517
+ requestId,
4518
+ decision: "deny",
4519
+ reason: "\u041F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0438\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 Codex \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D"
4520
+ } });
4521
+ return entries;
4522
+ });
4523
+ this.#launch();
4524
+ }
4525
+ #launch() {
4526
+ const generation = ++this.#generation;
4527
+ this.#ready = this.#connect(generation);
4528
+ void this.#ready.catch((error) => {
4529
+ if (generation === this.#generation)
4530
+ this.#fail(error);
4531
+ });
4532
+ }
4533
+ async #connect(generation) {
4534
+ await this.#history;
4535
+ await this.#store.save(this.#record);
4536
+ if (generation !== this.#generation)
4537
+ return;
4538
+ const reason = networkGuard.blockedReason();
4539
+ if (reason)
4540
+ throw new Error(reason);
4541
+ const launcher = codexLauncher();
4542
+ if (!launcher)
4543
+ throw new Error(CODEX_MISSING_HINT);
4544
+ const rpc = new CodexRpc(launcher, this.cwd);
4545
+ this.#rpc = rpc;
4546
+ rpc.on("message", (message) => {
4547
+ if (this.#rpc === rpc)
4548
+ this.#message(message);
4549
+ });
4550
+ rpc.on("stderr", (data) => this.emit("stderr", data));
4551
+ rpc.on("exit", (error) => {
4552
+ if (this.#rpc === rpc)
4553
+ this.#fail(error);
4554
+ });
4555
+ await rpc.request("initialize", { clientInfo: { name: "ccv", title: "Claude Code Visual", version: "0.1.0" } });
4556
+ if (this.#rpc !== rpc)
4557
+ return;
4558
+ rpc.write({ method: "initialized", params: {} });
4559
+ const policy = codexPolicy(this.#record.permissionMode);
4560
+ const result = await rpc.request(this.#record.conversationId ? "thread/resume" : "thread/start", {
4561
+ ...this.#record.conversationId ? { threadId: this.#record.conversationId } : {},
4562
+ cwd: this.cwd,
4563
+ ...this.#record.model ? { model: this.#record.model } : {},
4564
+ approvalPolicy: policy.approvalPolicy,
4565
+ sandbox: policy.sandbox
4566
+ });
4567
+ if (this.#rpc !== rpc)
4568
+ return;
4569
+ this.#record.conversationId = result.thread.id;
4570
+ this.#record.model = result.model;
4571
+ if (!this.#record.effort && result.reasoningEffort)
4572
+ this.#record.effort = result.reasoningEffort;
4573
+ await this.#store.save(this.#record);
4574
+ if (this.#rpc !== rpc)
4575
+ return;
4576
+ this.#capabilities = {
4577
+ cwd: this.cwd,
4578
+ model: result.model,
4579
+ permissionMode: this.#record.permissionMode,
4580
+ slashCommands: [],
4581
+ commands: [],
4582
+ models: [],
4583
+ tools: ["Bash", "apply_patch", "web_search"],
4584
+ agents: [],
4585
+ mcpServers: [],
4586
+ flags: []
4587
+ };
4588
+ this.emit("capabilities");
4589
+ this.#setState("idle");
4590
+ void this.#loadModels(rpc).catch((error) => {
4591
+ if (this.#rpc === rpc)
4592
+ this.#notice(error);
4593
+ });
4594
+ void this.refreshUsage();
4595
+ }
4596
+ async #loadModels(rpc) {
4597
+ const models = [];
4598
+ let cursor = null;
4599
+ do {
4600
+ const page = await rpc.request("model/list", { cursor, limit: 100 });
4601
+ for (const model of page.data) {
4602
+ const levels = (model.supportedReasoningEfforts ?? []).map((e) => e.reasoningEffort).filter((e) => ["low", "medium", "high", "xhigh", "max"].includes(e));
4603
+ models.push({
4604
+ value: model.model,
4605
+ resolvedModel: model.model,
4606
+ displayName: model.displayName,
4607
+ description: model.description,
4608
+ supportsEffort: levels.length > 0,
4609
+ effortLevels: levels,
4610
+ supportsThinking: false,
4611
+ supportsAutoMode: false
4612
+ });
4613
+ }
4614
+ cursor = page.nextCursor;
4615
+ } while (cursor && this.#rpc === rpc);
4616
+ if (this.#capabilities && this.#rpc === rpc) {
4617
+ this.#capabilities.models = models;
4618
+ this.emit("capabilities");
4619
+ }
4620
+ }
4621
+ sendPrompt(text, images) {
4622
+ const reason = networkGuard.blockedReason();
4623
+ if (reason) {
4624
+ this.#notice(new Error(reason));
4625
+ return;
4626
+ }
4627
+ if (this.#state === "closed" || this.#state === "error") {
4628
+ this.#notice(new Error("\u041F\u0435\u0440\u0435\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0441\u0435\u0441\u0441\u0438\u044E Codex \u043F\u0435\u0440\u0435\u0434 \u043E\u0442\u043F\u0440\u0430\u0432\u043A\u043E\u0439"));
4629
+ return;
4630
+ }
4631
+ if (!text.trim() && !images?.length)
4632
+ return;
4633
+ this.#append({ kind: "user.message", text, ...images?.length ? { images } : {} });
4634
+ if (this.#titleIsDefault) {
4635
+ this.#record.title = text.trim().split("\n")[0].slice(0, 100) || "Codex";
4636
+ this.#titleIsDefault = false;
4637
+ this.emit("summary", this.summary());
4638
+ }
4639
+ this.#queue.push({ text, images });
4640
+ void this.#drain();
4641
+ }
4642
+ async #drain() {
4643
+ if (this.#busy || this.#startingTurn || !this.#queue.length)
4644
+ return;
4645
+ const generation = this.#generation;
4646
+ this.#busy = true;
4647
+ this.#startingTurn = true;
4648
+ try {
4649
+ await this.#ready;
4650
+ if (generation !== this.#generation)
4651
+ return;
4652
+ const prompt = this.#queue.shift();
4653
+ if (!prompt || !this.#rpc || this.#state === "closed" || this.#state === "error") {
4654
+ this.#busy = false;
4655
+ return;
4656
+ }
4657
+ const reason = networkGuard.blockedReason();
4658
+ if (reason)
4659
+ throw new Error(reason);
4660
+ this.#turnStartedAt = Date.now();
4661
+ this.#turnUsage = null;
4662
+ this.#turnUsageBaseline = this.#totalUsage;
4663
+ this.#lastTurn = null;
4664
+ this.#setState("running");
4665
+ const policy = codexPolicy(this.#record.permissionMode);
4666
+ const sandboxPolicy = this.#record.permissionMode === "bypassPermissions" ? { type: "dangerFullAccess" } : this.#record.permissionMode === "plan" ? { type: "readOnly", networkAccess: false } : {
4667
+ type: "workspaceWrite",
4668
+ writableRoots: [this.cwd],
4669
+ networkAccess: false,
4670
+ excludeTmpdirEnvVar: false,
4671
+ excludeSlashTmp: false
4672
+ };
4673
+ const result = await this.#rpc.request("turn/start", {
4674
+ threadId: this.#record.conversationId,
4675
+ cwd: this.cwd,
4676
+ input: [{ type: "text", text: prompt.text, text_elements: [] }, ...(prompt.images ?? []).map((image) => ({
4677
+ type: "image",
4678
+ url: `data:${image.mediaType};base64,${image.data}`
4679
+ }))],
4680
+ model: this.#record.model,
4681
+ effort: this.#record.effort,
4682
+ approvalPolicy: policy.approvalPolicy,
4683
+ sandboxPolicy
4684
+ });
4685
+ if (this.#busy)
4686
+ this.#turnId = result.turn.id;
4687
+ if (this.#interruptRequested && this.#turnId)
4688
+ await this.interrupt();
4689
+ } catch (error) {
4690
+ if (generation === this.#generation)
4691
+ this.#fail(error);
4692
+ } finally {
4693
+ if (generation === this.#generation) {
4694
+ this.#startingTurn = false;
4695
+ if (!this.#busy)
4696
+ void this.#drain();
4697
+ }
4698
+ }
4699
+ }
4700
+ #message(message) {
4701
+ const p = message.params ?? {};
4702
+ if (message.id !== void 0 && message.method) {
4703
+ this.#request(message.id, message.method, p);
4704
+ return;
4705
+ }
4706
+ if (p.threadId && p.threadId !== this.#record.conversationId)
4707
+ return;
4708
+ switch (message.method) {
4709
+ case "turn/started": {
4710
+ const turn = p.turn;
4711
+ this.#turnId = turn.id;
4712
+ break;
4713
+ }
4714
+ case "item/agentMessage/delta":
4715
+ case "item/reasoning/summaryTextDelta": {
4716
+ const id = String(p.itemId);
4717
+ const thinking = message.method !== "item/agentMessage/delta";
4718
+ const key = `${id}:${thinking ? "thinking" : "text"}`;
4719
+ const delta = String(p.delta ?? "");
4720
+ const block = this.#texts.get(key) ?? { text: "", thinking };
4721
+ block.text += delta;
4722
+ this.#texts.set(key, block);
4723
+ this.#append({ kind: thinking ? "thinking.delta" : "assistant.delta", blockId: key, text: delta });
4724
+ break;
4725
+ }
4726
+ case "item/started":
4727
+ this.#item(p.item, false);
4728
+ break;
4729
+ case "item/completed":
4730
+ this.#item(p.item, true);
4731
+ break;
4732
+ case "thread/tokenUsage/updated": {
4733
+ const usage = p.tokenUsage;
4734
+ if (usage?.last && usage.modelContextWindow) {
4735
+ this.#context = { tokens: usage.last.totalTokens, maxTokens: usage.modelContextWindow, fetchedAt: Date.now() };
4736
+ this.#record.context = this.#context;
4737
+ this.emit("context", this.#context);
4738
+ }
4739
+ if (usage?.total) {
4740
+ const total = {
4741
+ inputTokens: usage.total.inputTokens,
4742
+ outputTokens: usage.total.outputTokens,
4743
+ cacheReadTokens: usage.total.cachedInputTokens ?? 0,
4744
+ cacheCreationTokens: usage.total.cacheWriteInputTokens ?? 0
4745
+ };
4746
+ if (this.#busy && this.#turnUsageBaseline) {
4747
+ const baseline = this.#turnUsageBaseline;
4748
+ this.#turnUsage = {
4749
+ inputTokens: Math.max(0, total.inputTokens - baseline.inputTokens),
4750
+ outputTokens: Math.max(0, total.outputTokens - baseline.outputTokens),
4751
+ cacheReadTokens: Math.max(0, total.cacheReadTokens - baseline.cacheReadTokens),
4752
+ cacheCreationTokens: Math.max(0, total.cacheCreationTokens - baseline.cacheCreationTokens)
4753
+ };
4754
+ }
4755
+ this.#totalUsage = total;
4756
+ }
4757
+ break;
4758
+ }
4759
+ case "account/rateLimits/updated":
4760
+ if (p.rateLimits)
4761
+ this.#setUsage(p.rateLimits);
4762
+ break;
4763
+ case "serverRequest/resolved": {
4764
+ const requestId = String(p.requestId);
4765
+ if (this.#pending.delete(requestId)) {
4766
+ this.#append({ kind: "permission.resolved", requestId, decision: "deny", reason: "\u0417\u0430\u043F\u0440\u043E\u0441 \u0437\u0430\u043A\u0440\u044B\u0442 Codex" });
4767
+ this.#setState(this.#pending.size ? "waiting_permission" : "running");
4768
+ }
4769
+ break;
4770
+ }
4771
+ case "turn/completed": {
4772
+ const turn = p.turn;
4773
+ this.#lastTurn = turn.status === "completed" ? "completed" : turn.status === "interrupted" ? "interrupted" : "failed";
4774
+ if (turn.error?.message)
4775
+ this.#notice(new Error(turn.error.message));
4776
+ this.#finishTurn(turn.status, turn.durationMs);
4777
+ void this.refreshUsage();
4778
+ break;
4779
+ }
4780
+ case "error": {
4781
+ const error = p.error;
4782
+ this.#notice(new Error(error?.message ?? "\u041E\u0448\u0438\u0431\u043A\u0430 Codex"));
4783
+ break;
4784
+ }
4785
+ }
4786
+ }
4787
+ #item(item, done) {
4788
+ if (!item)
4789
+ return;
4790
+ this.#items.set(item.id, item);
4791
+ if (item.type === "agentMessage" || item.type === "reasoning" || item.type === "plan") {
4792
+ if (!done)
4793
+ return;
4794
+ const thinking = item.type === "reasoning";
4795
+ const key = `${item.id}:${thinking ? "thinking" : "text"}`;
4796
+ const text = item.text ?? item.summary?.join("\n") ?? this.#texts.get(key)?.text ?? "";
4797
+ this.#texts.delete(key);
4798
+ if (text)
4799
+ this.#append({ kind: thinking ? "assistant.thinking" : "assistant.text", text, blockId: key });
4800
+ return;
4801
+ }
4802
+ if (!["commandExecution", "fileChange", "mcpToolCall", "webSearch", "imageView", "collabAgentToolCall"].includes(item.type))
4803
+ return;
4804
+ if (!this.#tools.has(item.id)) {
4805
+ this.#tools.add(item.id);
4806
+ const name = item.type === "commandExecution" ? "Bash" : item.type === "fileChange" ? "apply_patch" : item.type === "mcpToolCall" ? `mcp__${item.server}__${item.tool}` : item.type;
4807
+ const input = item.type === "commandExecution" ? { command: item.command, cwd: item.cwd } : item.type === "fileChange" ? { changes: item.changes, file_path: item.changes?.[0]?.path } : item.arguments ?? item.action ?? { query: item.query };
4808
+ this.#append({ kind: "tool.use", toolUseId: item.id, name, input });
4809
+ }
4810
+ if (done) {
4811
+ this.#tools.delete(item.id);
4812
+ const output = item.aggregatedOutput ?? (item.changes ? item.changes.map((c) => `${c.path}
4813
+ ${c.diff}`).join("\n") : typeof item.result === "string" ? item.result : JSON.stringify(item.error ?? item.result ?? item.action ?? "\u0413\u043E\u0442\u043E\u0432\u043E"));
4814
+ this.#append({
4815
+ kind: "tool.result",
4816
+ toolUseId: item.id,
4817
+ content: output,
4818
+ isError: ["failed", "declined"].includes(item.status ?? "") || item.exitCode != null && item.exitCode !== 0 || !!item.error
4819
+ });
4820
+ }
4821
+ }
4822
+ #request(id, method, params) {
4823
+ const requestId = String(id);
4824
+ const questions = method === "item/tool/requestUserInput" ? params.questions.map((q) => ({ ...q, options: q.options ?? [], multiSelect: false })) : void 0;
4825
+ const known = [
4826
+ "item/commandExecution/requestApproval",
4827
+ "item/fileChange/requestApproval",
4828
+ "item/permissions/requestApproval",
4829
+ "item/tool/requestUserInput"
4830
+ ];
4831
+ if (!known.includes(method)) {
4832
+ this.#rpc?.write({ id, error: { code: -32601, message: `CCV does not support ${method}` } });
4833
+ this.#notice(new Error(`Codex \u0437\u0430\u043F\u0440\u043E\u0441\u0438\u043B \u043D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u043E\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435: ${method}`));
4834
+ return;
4835
+ }
4836
+ this.#pending.set(requestId, { id, method, params, questions });
4837
+ this.#append({
4838
+ kind: "permission.request",
4839
+ requestId,
4840
+ toolUseId: String(params.itemId ?? requestId),
4841
+ toolName: questions ? "AskUserQuestion" : method.includes("commandExecution") ? "Bash" : method.includes("fileChange") ? "apply_patch" : "Codex permissions",
4842
+ input: { ...params, ...this.#items.get(String(params.itemId))?.changes ? { changes: this.#items.get(String(params.itemId)).changes } : {} },
4843
+ ...typeof params.reason === "string" ? { reason: params.reason } : {},
4844
+ ...questions ? { questions } : {}
4845
+ });
4846
+ this.#setState("waiting_permission");
4847
+ }
4848
+ respondPermission(requestId, decision, reason, answers) {
4849
+ const request2 = this.#pending.get(requestId);
4850
+ if (!request2)
4851
+ return;
4852
+ let result = { decision: decision === "allow" ? "accept" : "decline" };
4853
+ if (request2.questions) {
4854
+ result = { answers: Object.fromEntries(request2.questions.map((q) => [q.id, {
4855
+ answers: decision === "allow" ? [answers?.[q.question] ?? ""] : []
4856
+ }])) };
4857
+ } else if (request2.method === "item/permissions/requestApproval") {
4858
+ result = { permissions: decision === "allow" ? request2.params.permissions : {}, scope: "turn" };
4859
+ }
4860
+ this.#rpc?.write({ id: request2.id, result });
4861
+ this.#pending.delete(requestId);
4862
+ this.#append({ kind: "permission.resolved", requestId, decision, reason, answers });
4863
+ this.#setState(this.#pending.size ? "waiting_permission" : "running");
4864
+ }
4865
+ async interrupt() {
4866
+ this.#queue = [];
4867
+ if (!this.#busy)
4868
+ return;
4869
+ this.#interruptRequested = true;
4870
+ if (this.#turnId && this.#rpc) {
4871
+ await this.#rpc.request("turn/interrupt", { threadId: this.#record.conversationId, turnId: this.#turnId });
4872
+ }
4873
+ }
4874
+ #finishTurn(subtype, durationMs) {
4875
+ for (const [blockId, block] of this.#texts) {
4876
+ this.#append({ kind: block.thinking ? "assistant.thinking" : "assistant.text", text: block.text, blockId });
4877
+ }
4878
+ this.#texts.clear();
4879
+ for (const toolUseId of this.#tools) {
4880
+ this.#append({ kind: "tool.result", toolUseId, content: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043F\u0440\u0435\u0440\u0432\u0430\u043D\u043E", isError: true });
4881
+ }
4882
+ this.#tools.clear();
4883
+ this.#items.clear();
4884
+ this.#clearApprovals();
4885
+ this.#append({
3826
4886
  kind: "result",
3827
- subtype: ok ? "success" : "error",
3828
- isError: !ok,
3829
- durationMs: Math.round((result.duration_seconds ?? 0) * 1e3),
4887
+ subtype,
4888
+ isError: this.#lastTurn === "failed",
4889
+ durationMs: durationMs ?? Math.max(0, Date.now() - this.#turnStartedAt),
3830
4890
  costUsd: null,
3831
- usage,
3832
- contextTokens: this.#contextTokens,
3833
- text: result.response ?? null
4891
+ usage: this.#turnUsage,
4892
+ contextTokens: this.#context?.tokens ?? null,
4893
+ text: null
3834
4894
  });
3835
- this.#emitContext();
3836
- void this.refreshUsage(true);
3837
- this.#lastTurn = ok ? "completed" : "failed";
3838
- this.#pendingTurns = Math.max(0, this.#pendingTurns - 1);
3839
- if (this.#pendingTurns > 0)
3840
- return;
3841
- if (this.#restartPending)
3842
- this.#restart();
4895
+ this.#busy = false;
4896
+ this.#turnId = null;
4897
+ this.#interruptRequested = false;
3843
4898
  this.#setState("idle");
4899
+ void this.#drain();
3844
4900
  }
3845
- /** Незакрытые шаги оборванного хода: текст — в ленту, инструменты — с ошибкой. */
3846
- #closeOpenSteps() {
3847
- const prefix = this.#record.conversationId ?? this.id;
3848
- for (const [index, text] of this.#texts) {
3849
- if (text)
3850
- this.#record_({ kind: "assistant.text", text, blockId: `${prefix}:${index}` });
4901
+ async setPermissionMode(mode) {
4902
+ codexPolicy(mode);
4903
+ this.#record.permissionMode = mode;
4904
+ this.#settingsChanged();
4905
+ }
4906
+ async setModel(model) {
4907
+ this.#record.model = model;
4908
+ this.#record.effort = null;
4909
+ this.#settingsChanged();
4910
+ }
4911
+ async setEffort(effort) {
4912
+ const model = this.#capabilities?.models.find((m) => m.value === this.#record.model);
4913
+ if (model && !model.effortLevels.includes(effort))
4914
+ throw new Error("\u041C\u043E\u0434\u0435\u043B\u044C Codex \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u044D\u0442\u043E \u0443\u0441\u0438\u043B\u0438\u0435");
4915
+ this.#record.effort = effort;
4916
+ this.#settingsChanged();
4917
+ }
4918
+ #settingsChanged() {
4919
+ if (this.#capabilities) {
4920
+ this.#capabilities.model = this.#record.model ?? "";
4921
+ this.#capabilities.permissionMode = this.#record.permissionMode;
4922
+ this.emit("capabilities");
3851
4923
  }
3852
- for (const index of this.#tools) {
3853
- this.#record_({
3854
- kind: "tool.result",
3855
- toolUseId: `${prefix}:${index}`,
3856
- content: "\u041F\u0440\u0435\u0440\u0432\u0430\u043D\u043E",
3857
- isError: true
3858
- });
4924
+ void this.#store.save(this.#record);
4925
+ this.emit("summary", this.summary());
4926
+ }
4927
+ setTitle(title) {
4928
+ this.#record.title = title;
4929
+ this.#titleIsDefault = false;
4930
+ this.#settingsChanged();
4931
+ }
4932
+ async refreshUsage(_force = false) {
4933
+ if (!this.#rpc || networkGuard.blockedReason())
4934
+ return;
4935
+ try {
4936
+ const reply = await this.#rpc.request("account/rateLimits/read");
4937
+ this.#setUsage(reply.rateLimits);
4938
+ } catch {
3859
4939
  }
3860
- this.#texts.clear();
3861
- this.#thinking.clear();
3862
- this.#tools.clear();
3863
4940
  }
3864
- // -------------------------------------------------------------------------
3865
- #record_(event) {
3866
- this.#updatedAt = Date.now();
4941
+ #setUsage(limits) {
4942
+ this.#usage = codexUsage(limits);
4943
+ this.emit("usage", this.#usage);
4944
+ }
4945
+ async refreshContext(_force = false) {
4946
+ if (this.#context)
4947
+ this.emit("context", this.#context);
4948
+ }
4949
+ reconnect(_reason) {
4950
+ this.#disconnect();
4951
+ this.#setState("starting");
4952
+ this.#launch();
4953
+ }
4954
+ suspend(reason) {
4955
+ this.#disconnect();
4956
+ this.#notice(new Error(reason));
4957
+ this.#setState("error");
4958
+ }
4959
+ async close(_reason = "closed by client") {
4960
+ this.#disconnect();
4961
+ this.#setState("closed");
4962
+ await this.#flush();
4963
+ }
4964
+ #disconnect() {
4965
+ this.#generation += 1;
4966
+ this.#startingTurn = false;
4967
+ const rpc = this.#rpc;
4968
+ this.#rpc = null;
4969
+ rpc?.close();
4970
+ this.#queue = [];
4971
+ if (this.#busy) {
4972
+ this.#lastTurn = "interrupted";
4973
+ this.#finishTurn("interrupted");
4974
+ }
4975
+ this.#clearApprovals();
4976
+ }
4977
+ #clearApprovals() {
4978
+ for (const requestId of this.#pending.keys()) {
4979
+ this.#append({ kind: "permission.resolved", requestId, decision: "deny", reason: "\u0425\u043E\u0434 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D" });
4980
+ }
4981
+ this.#pending.clear();
4982
+ }
4983
+ #notice(error) {
4984
+ this.#append({ kind: "error", message: error instanceof Error ? error.message : String(error) });
4985
+ }
4986
+ #fail(error) {
4987
+ if (this.#state === "closed")
4988
+ return;
4989
+ this.#queue = [];
4990
+ if (this.#busy) {
4991
+ this.#lastTurn = "failed";
4992
+ this.#finishTurn("failed");
4993
+ }
4994
+ this.#notice(error);
4995
+ const rpc = this.#rpc;
4996
+ this.#rpc = null;
4997
+ rpc?.close();
4998
+ this.#setState("error");
4999
+ }
5000
+ #append(event) {
5001
+ this.#record.updatedAt = Date.now();
3867
5002
  const entry = this.journal.append(event);
3868
5003
  this.emit("event", entry);
3869
- if (TRANSIENT.has(event.kind))
5004
+ if (["assistant.delta", "thinking.delta", "state"].includes(event.kind))
3870
5005
  return;
3871
5006
  this.#unsaved.push({ ts: entry.ts, event });
3872
- this.#scheduleFlush();
5007
+ if (!this.#flushTimer) {
5008
+ this.#flushTimer = setTimeout(() => {
5009
+ void this.#flush();
5010
+ }, 500);
5011
+ this.#flushTimer.unref?.();
5012
+ }
3873
5013
  }
3874
- #scheduleFlush() {
5014
+ async #flush() {
3875
5015
  if (this.#flushTimer)
3876
- return;
3877
- this.#flushTimer = setTimeout(() => this.#flush(), 500);
3878
- this.#flushTimer.unref?.();
3879
- }
3880
- #flush() {
3881
- if (this.#flushTimer) {
3882
5016
  clearTimeout(this.#flushTimer);
3883
- this.#flushTimer = null;
3884
- }
5017
+ this.#flushTimer = null;
3885
5018
  const batch = this.#unsaved;
3886
5019
  this.#unsaved = [];
3887
- this.#record.updatedAt = this.#updatedAt;
3888
- void this.#store.appendEvents(this.id, batch);
3889
- void this.#store.save(this.#record);
5020
+ await this.#store.appendEvents(this.id, batch);
5021
+ await this.#store.save(this.#record);
3890
5022
  }
3891
5023
  #setState(state) {
3892
- if (this.#state === state)
3893
- return;
3894
5024
  this.#state = state;
3895
- this.#record_({ kind: "state", state });
3896
- if (state === "idle" || state === "error" || state === "closed")
3897
- this.#flush();
5025
+ this.#append({ kind: "state", state });
5026
+ if (["idle", "error", "closed"].includes(state))
5027
+ void this.#flush();
3898
5028
  this.emit("summary", this.summary());
3899
5029
  }
3900
- };
3901
- function toolInput(parameters) {
3902
- const input = { ...parameters ?? {} };
3903
- const pick = (...keys) => keys.map((k) => input[k]).find((v) => typeof v === "string");
3904
- const file = pick("TargetFile", "AbsolutePath", "File", "FilePath", "Path");
3905
- if (file && input.file_path === void 0)
3906
- input.file_path = file;
3907
- const command = pick("CommandLine", "Command");
3908
- if (command && input.command === void 0)
3909
- input.command = command;
3910
- const pattern = pick("Query", "Pattern", "SearchPattern");
3911
- if (pattern && input.pattern === void 0)
3912
- input.pattern = pattern;
3913
- const dir = pick("DirectoryPath", "SearchPath", "SearchDirectory");
3914
- if (dir && input.path === void 0)
3915
- input.path = dir;
3916
- const url2 = pick("Url", "URL");
3917
- if (url2 && input.url === void 0)
3918
- input.url = url2;
3919
- return input;
3920
- }
3921
- function formatUsage(usage) {
3922
- const rows = (usage.buckets ?? []).map((b) => {
3923
- const left = Math.round(100 - b.window.utilization);
3924
- const reset = b.window.resetsAt ? new Date(b.window.resetsAt).toLocaleString("ru-RU") : "\u2014";
3925
- return `| ${b.title || b.label} | ${left}% | ${reset} |`;
3926
- });
3927
- if (rows.length === 0)
3928
- return "\u041A\u0432\u043E\u0442\u0430 Antigravity: \u0434\u0430\u043D\u043D\u044B\u0445 \u043D\u0435\u0442.";
3929
- 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");
3930
- }
3931
- function formatCommandReply(name, response) {
3932
- const text = response.replace(/\s+$/, "");
3933
- if (!text)
3934
- return `/${name}: \u043F\u0443\u0441\u0442\u043E`;
3935
- const lines = text.split(/\r?\n/);
3936
- if (lines.every((l) => l.includes(" "))) {
3937
- return lines.map((l) => {
3938
- const [head, ...rest] = l.split(" ");
3939
- return `- **${head}** \u2014 ${rest.join(" ")}`;
3940
- }).join("\n");
5030
+ async editPrompt(_index, _text, _images) {
5031
+ throw new Error("\u0412 \u0441\u0435\u0441\u0441\u0438\u0438 Codex \u043F\u0440\u0430\u0432\u043A\u0430 \u043F\u0440\u043E\u0448\u043B\u044B\u0445 \u0440\u0435\u043F\u043B\u0438\u043A \u043F\u043E\u043A\u0430 \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F");
3941
5032
  }
3942
- return text;
3943
- }
3944
- function agyEffort(effort) {
3945
- if (!effort)
3946
- return null;
3947
- return effort === "xhigh" ? "high" : effort;
3948
- }
3949
- function describe2(error) {
3950
- return error instanceof Error ? error.message : String(error);
3951
- }
5033
+ async setUltracode(_enabled) {
5034
+ }
5035
+ async setThinking(_enabled) {
5036
+ }
5037
+ async setSwitchModelsOnFlag(_enabled) {
5038
+ }
5039
+ async reloadPlugins() {
5040
+ }
5041
+ async mcpServerStatus() {
5042
+ return [];
5043
+ }
5044
+ async addMcpServer(_name, _config) {
5045
+ throw this.#mcpHint();
5046
+ }
5047
+ async removeMcpServer(_name) {
5048
+ throw this.#mcpHint();
5049
+ }
5050
+ async reconnectMcpServer(_name) {
5051
+ throw this.#mcpHint();
5052
+ }
5053
+ async toggleMcpServer(_name, _enabled) {
5054
+ throw this.#mcpHint();
5055
+ }
5056
+ async authenticateMcpServer(_name) {
5057
+ throw this.#mcpHint();
5058
+ }
5059
+ async submitMcpOAuthCallback(_name, _url) {
5060
+ throw this.#mcpHint();
5061
+ }
5062
+ #mcpHint() {
5063
+ return new Error("MCP Codex \u043D\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 codex mcp \u0438\u043B\u0438 ~/.codex/config.toml");
5064
+ }
5065
+ };
3952
5066
 
3953
5067
  // ../host/dist/antigravity-store.js
3954
5068
  import { appendFile, mkdir as mkdir3, readFile as readFile4, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
3955
- import path8 from "node:path";
5069
+ import path9 from "node:path";
3956
5070
  var AntigravityStore = class {
3957
5071
  #dir;
3958
5072
  #file;
3959
5073
  #records = /* @__PURE__ */ new Map();
3960
5074
  #loaded = null;
3961
5075
  #writing = Promise.resolve();
3962
- constructor(dataDir) {
3963
- this.#dir = path8.join(dataDir, "antigravity");
3964
- this.#file = path8.join(this.#dir, "sessions.json");
5076
+ constructor(dataDir, provider = "antigravity") {
5077
+ this.#dir = path9.join(dataDir, provider);
5078
+ this.#file = path9.join(this.#dir, "sessions.json");
3965
5079
  }
3966
5080
  #load() {
3967
5081
  this.#loaded ??= (async () => {
@@ -4058,7 +5172,7 @@ var AntigravityStore = class {
4058
5172
  return entries;
4059
5173
  }
4060
5174
  #eventsFile(id) {
4061
- return path8.join(this.#dir, `${id.replace(/[^\w-]/g, "_")}.jsonl`);
5175
+ return path9.join(this.#dir, `${id.replace(/[^\w-]/g, "_")}.jsonl`);
4062
5176
  }
4063
5177
  #persist() {
4064
5178
  const snapshot = JSON.stringify([...this.#records.values()], null, 2);
@@ -4078,10 +5192,10 @@ var AntigravityStore = class {
4078
5192
  };
4079
5193
 
4080
5194
  // ../host/dist/git.js
4081
- import { execFile as execFile4 } from "node:child_process";
5195
+ import { execFile as execFile5 } from "node:child_process";
4082
5196
  import { mkdtemp, readFile as readFile5, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
4083
5197
  import { tmpdir as tmpdir2 } from "node:os";
4084
- import path9 from "node:path";
5198
+ import path10 from "node:path";
4085
5199
  var TIMEOUT_MS = 2e4;
4086
5200
  var NET_TIMEOUT_MS = 5 * 6e4;
4087
5201
  var MAX_BUFFER = 32 * 1024 * 1024;
@@ -4397,7 +5511,7 @@ var GitRepo = class {
4397
5511
  * важнее всего (см. `checkIgnored`).
4398
5512
  */
4399
5513
  async #withoutRepo(args) {
4400
- const dir = await mkdtemp(path9.join(tmpdir2(), "ccv-git-"));
5514
+ const dir = await mkdtemp(path10.join(tmpdir2(), "ccv-git-"));
4401
5515
  try {
4402
5516
  await this.#run(["init", "--bare", "--quiet", dir]);
4403
5517
  return await this.#soft([`--git-dir=${dir}`, `--work-tree=${this.cwd}`, ...args]);
@@ -4443,7 +5557,7 @@ var GitRepo = class {
4443
5557
  * исключил. Повторы отсеиваются, чтобы файл не рос от каждой публикации.
4444
5558
  */
4445
5559
  async #appendGitignore(paths) {
4446
- const file = path9.join(this.cwd, ".gitignore");
5560
+ const file = path10.join(this.cwd, ".gitignore");
4447
5561
  const existing = await readFile5(file, "utf8").catch(() => "");
4448
5562
  const known = new Set(existing.split(/\r?\n/).map((line) => line.trim()));
4449
5563
  const lines = paths.map(ignorePattern).filter((line) => !known.has(line));
@@ -4468,7 +5582,7 @@ var GitRepo = class {
4468
5582
  #run(args, options2 = {}) {
4469
5583
  const token = options2.token ?? "";
4470
5584
  return new Promise((resolve, reject) => {
4471
- const child = execFile4(
5585
+ const child = execFile5(
4472
5586
  "git",
4473
5587
  // core.quotepath=false — иначе кириллические пути приезжают в виде
4474
5588
  // «\320\277\321\200…» и в панели читаются как мусор.
@@ -4649,9 +5763,9 @@ function parsePatch(raw) {
4649
5763
  body = "";
4650
5764
  }
4651
5765
  total += body.length;
4652
- const path25 = status === "deleted" ? oldPath : newPath || oldPath;
5766
+ const path26 = status === "deleted" ? oldPath : newPath || oldPath;
4653
5767
  files.push({
4654
- path: path25,
5768
+ path: path26,
4655
5769
  oldPath: status === "renamed" ? oldPath : "",
4656
5770
  status,
4657
5771
  added: count(body, "+"),
@@ -4663,10 +5777,10 @@ function parsePatch(raw) {
4663
5777
  return { files, truncated };
4664
5778
  }
4665
5779
  function stripPrefix(value) {
4666
- const path25 = unquote(value.trim());
4667
- if (path25 === "/dev/null")
5780
+ const path26 = unquote(value.trim());
5781
+ if (path26 === "/dev/null")
4668
5782
  return "";
4669
- return path25.replace(/^[ab]\//, "");
5783
+ return path26.replace(/^[ab]\//, "");
4670
5784
  }
4671
5785
  function count(body, mark) {
4672
5786
  let n = 0;
@@ -4678,9 +5792,9 @@ function count(body, mark) {
4678
5792
  }
4679
5793
 
4680
5794
  // ../host/dist/github.js
4681
- import { execFile as execFile5 } from "node:child_process";
5795
+ import { execFile as execFile6 } from "node:child_process";
4682
5796
  import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
4683
- import path10 from "node:path";
5797
+ import path11 from "node:path";
4684
5798
  var API = "https://api.github.com";
4685
5799
  var API_VERSION = "2022-11-28";
4686
5800
  var API_TIMEOUT_MS = 2e4;
@@ -4701,7 +5815,7 @@ var GitHubAccount = class {
4701
5815
  */
4702
5816
  #ghInstalled = null;
4703
5817
  constructor(dataDir) {
4704
- this.#storePath = path10.join(dataDir, "github.json");
5818
+ this.#storePath = path11.join(dataDir, "github.json");
4705
5819
  }
4706
5820
  async load() {
4707
5821
  try {
@@ -4715,7 +5829,7 @@ var GitHubAccount = class {
4715
5829
  /** Сохранить токен или стереть его пустой строкой. */
4716
5830
  async setToken(token) {
4717
5831
  this.#token = token.trim();
4718
- await mkdir4(path10.dirname(this.#storePath), { recursive: true });
5832
+ await mkdir4(path11.dirname(this.#storePath), { recursive: true });
4719
5833
  await writeFile5(this.#storePath, JSON.stringify({ token: this.#token }, null, 2), {
4720
5834
  encoding: "utf8",
4721
5835
  mode: 384
@@ -4867,7 +5981,7 @@ var GitHubAccount = class {
4867
5981
  };
4868
5982
  function gh(args) {
4869
5983
  return new Promise((resolve, reject) => {
4870
- execFile5("gh", args, { timeout: GH_TIMEOUT_MS, windowsHide: true, encoding: "utf8" }, (error, stdout, stderr) => {
5984
+ execFile6("gh", args, { timeout: GH_TIMEOUT_MS, windowsHide: true, encoding: "utf8" }, (error, stdout, stderr) => {
4871
5985
  if (error)
4872
5986
  return reject(new Error(stderr.trim() || error.message));
4873
5987
  resolve(stdout);
@@ -4908,10 +6022,10 @@ function describe4(error) {
4908
6022
  }
4909
6023
 
4910
6024
  // ../host/dist/finder.js
4911
- import { existsSync as existsSync2, statSync } from "node:fs";
6025
+ import { existsSync as existsSync3, statSync as statSync2 } from "node:fs";
4912
6026
  import { readdir as readdir2 } from "node:fs/promises";
4913
6027
  import os2 from "node:os";
4914
- import path11 from "node:path";
6028
+ import path12 from "node:path";
4915
6029
  import { listSessions } from "@anthropic-ai/claude-agent-sdk";
4916
6030
  var CACHE_TTL = 6e4;
4917
6031
  var FIND_LIMIT = 50;
@@ -4950,7 +6064,7 @@ var ProjectFinder = class {
4950
6064
  if (needles.length === 0)
4951
6065
  return { candidates: [], truncated: false };
4952
6066
  const open = new Map(this.registry.list().map((workspace) => [workspace.path.toLowerCase(), workspace]));
4953
- const byName = this.#dirs.filter((dir) => matches(path11.basename(dir.path), needles));
6067
+ const byName = this.#dirs.filter((dir) => matches(path12.basename(dir.path), needles));
4954
6068
  const matched = byName.length > 0 ? byName : this.#dirs.filter((dir) => matches(dir.path, needles));
4955
6069
  const sorted = [...matched].sort((a, b) => {
4956
6070
  const aOpen = open.has(a.path.toLowerCase());
@@ -4961,7 +6075,7 @@ var ProjectFinder = class {
4961
6075
  return a.sessions > 0 ? -1 : 1;
4962
6076
  if (a.lastUsedAt !== b.lastUsedAt)
4963
6077
  return b.lastUsedAt - a.lastUsedAt;
4964
- return path11.basename(a.path).localeCompare(path11.basename(b.path), void 0, {
6078
+ return path12.basename(a.path).localeCompare(path12.basename(b.path), void 0, {
4965
6079
  numeric: true,
4966
6080
  sensitivity: "base"
4967
6081
  });
@@ -4969,7 +6083,7 @@ var ProjectFinder = class {
4969
6083
  return {
4970
6084
  candidates: sorted.slice(0, FIND_LIMIT).map((dir) => ({
4971
6085
  path: dir.path,
4972
- name: path11.basename(dir.path) || dir.path,
6086
+ name: path12.basename(dir.path) || dir.path,
4973
6087
  sessions: dir.sessions,
4974
6088
  lastUsedAt: dir.lastUsedAt || null,
4975
6089
  open: open.has(dir.path.toLowerCase()),
@@ -4993,7 +6107,7 @@ var ProjectFinder = class {
4993
6107
  const candidates = [];
4994
6108
  for (const entry of named) {
4995
6109
  const absolute = entry.path.trim();
4996
- if (!absolute || !path11.isAbsolute(absolute))
6110
+ if (!absolute || !path12.isAbsolute(absolute))
4997
6111
  continue;
4998
6112
  const key = absolute.toLowerCase();
4999
6113
  if (seen.has(key))
@@ -5004,7 +6118,7 @@ var ProjectFinder = class {
5004
6118
  const record = known.get(key);
5005
6119
  candidates.push({
5006
6120
  path: record?.path ?? absolute,
5007
- name: path11.basename(absolute) || absolute,
6121
+ name: path12.basename(absolute) || absolute,
5008
6122
  sessions: record?.sessions ?? 0,
5009
6123
  lastUsedAt: record?.lastUsedAt || null,
5010
6124
  open: open.has(key),
@@ -5061,7 +6175,7 @@ var ProjectFinder = class {
5061
6175
  if (!dirent.isDirectory() || SKIP_NAMES.has(dirent.name) || dirent.name.startsWith(".")) {
5062
6176
  continue;
5063
6177
  }
5064
- const full = path11.join(parent, dirent.name);
6178
+ const full = path12.join(parent, dirent.name);
5065
6179
  const key = full.toLowerCase();
5066
6180
  if (dirs.has(key))
5067
6181
  continue;
@@ -5082,7 +6196,7 @@ var ProjectFinder = class {
5082
6196
  #parents(dirs) {
5083
6197
  const counted = /* @__PURE__ */ new Map();
5084
6198
  for (const dir of dirs) {
5085
- const parent = path11.dirname(dir.path);
6199
+ const parent = path12.dirname(dir.path);
5086
6200
  if (parent === dir.path || !usable(parent))
5087
6201
  continue;
5088
6202
  const key = parent.toLowerCase();
@@ -5096,7 +6210,7 @@ var ProjectFinder = class {
5096
6210
  }
5097
6211
  };
5098
6212
  function usable(dir) {
5099
- return !contains(os2.tmpdir(), dir) && existsSync2(dir);
6213
+ return !contains(os2.tmpdir(), dir) && existsSync3(dir);
5100
6214
  }
5101
6215
  function tokens(query2) {
5102
6216
  return query2.toLowerCase().split(/[^a-z0-9а-яё]+/i).filter(Boolean);
@@ -5107,7 +6221,7 @@ function matches(value, needles) {
5107
6221
  }
5108
6222
  function isDirectory(dir) {
5109
6223
  try {
5110
- return statSync(dir).isDirectory();
6224
+ return statSync2(dir).isDirectory();
5111
6225
  } catch {
5112
6226
  return false;
5113
6227
  }
@@ -5193,15 +6307,15 @@ function fromText(text) {
5193
6307
  }
5194
6308
 
5195
6309
  // ../host/dist/workspaces.js
5196
- import { existsSync as existsSync3, statSync as statSync2 } from "node:fs";
6310
+ import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
5197
6311
  import { mkdir as mkdir5, readFile as readFile7, writeFile as writeFile6 } from "node:fs/promises";
5198
- import path12 from "node:path";
6312
+ import path13 from "node:path";
5199
6313
  import { listSessions as listSessions2 } from "@anthropic-ai/claude-agent-sdk";
5200
6314
  var WorkspaceRegistry = class {
5201
6315
  #entries = [];
5202
6316
  #storePath;
5203
6317
  constructor(dataDir) {
5204
- this.#storePath = path12.join(dataDir, "workspaces.json");
6318
+ this.#storePath = path13.join(dataDir, "workspaces.json");
5205
6319
  }
5206
6320
  async load() {
5207
6321
  try {
@@ -5217,13 +6331,13 @@ var WorkspaceRegistry = class {
5217
6331
  if (isEntry(item))
5218
6332
  return item;
5219
6333
  return null;
5220
- }).filter((e) => e !== null && existsSync3(e.path));
6334
+ }).filter((e) => e !== null && existsSync4(e.path));
5221
6335
  } catch {
5222
6336
  this.#entries = [];
5223
6337
  }
5224
6338
  }
5225
6339
  async #persist() {
5226
- await mkdir5(path12.dirname(this.#storePath), { recursive: true });
6340
+ await mkdir5(path13.dirname(this.#storePath), { recursive: true });
5227
6341
  await writeFile6(this.#storePath, JSON.stringify(this.#entries, null, 2), "utf8");
5228
6342
  }
5229
6343
  /** Активные проекты. */
@@ -5243,8 +6357,8 @@ var WorkspaceRegistry = class {
5243
6357
  return entry ? toInfo(entry) : void 0;
5244
6358
  }
5245
6359
  async open(rawPath) {
5246
- const absolute = path12.resolve(rawPath);
5247
- if (!existsSync3(absolute)) {
6360
+ const absolute = path13.resolve(rawPath);
6361
+ if (!existsSync4(absolute)) {
5248
6362
  throw new Error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${absolute}`);
5249
6363
  }
5250
6364
  const existing = this.#entries.find((e) => e.path === absolute);
@@ -5310,24 +6424,24 @@ var WorkspaceRegistry = class {
5310
6424
  async create(parentRaw, nameRaw, init) {
5311
6425
  const name = nameRaw.trim();
5312
6426
  requireName(name);
5313
- const parent = path12.resolve(parentRaw.trim());
5314
- if (!existsSync3(parent)) {
6427
+ const parent = path13.resolve(parentRaw.trim());
6428
+ if (!existsSync4(parent)) {
5315
6429
  throw new Error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${parent}`);
5316
6430
  }
5317
- if (!statSync2(parent).isDirectory()) {
6431
+ if (!statSync3(parent).isDirectory()) {
5318
6432
  throw new Error(`\u042D\u0442\u043E \u043D\u0435 \u043A\u0430\u0442\u0430\u043B\u043E\u0433: ${parent}`);
5319
6433
  }
5320
- const target = path12.join(parent, name);
5321
- if (existsSync3(target)) {
6434
+ const target = path13.join(parent, name);
6435
+ if (existsSync4(target)) {
5322
6436
  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`);
5323
6437
  }
5324
6438
  await mkdir5(target);
5325
6439
  const warnings = [];
5326
6440
  if (init.claudeMd) {
5327
- await step(warnings, "CLAUDE.md", () => writeFile6(path12.join(target, "CLAUDE.md"), claudeMdStub(name), "utf8"));
6441
+ await step(warnings, "CLAUDE.md", () => writeFile6(path13.join(target, "CLAUDE.md"), claudeMdStub(name), "utf8"));
5328
6442
  }
5329
6443
  if (init.readme) {
5330
- await step(warnings, "README.md", () => writeFile6(path12.join(target, "README.md"), readmeStub(name), "utf8"));
6444
+ await step(warnings, "README.md", () => writeFile6(path13.join(target, "README.md"), readmeStub(name), "utf8"));
5331
6445
  }
5332
6446
  if (init.git) {
5333
6447
  await step(warnings, "git init", () => new GitRepo(target).init());
@@ -5399,8 +6513,8 @@ function toInfo(entry) {
5399
6513
  return {
5400
6514
  id: workspaceIdFromPath(absolute),
5401
6515
  path: absolute,
5402
- name: path12.basename(absolute) || absolute,
5403
- hasClaudeMd: existsSync3(path12.join(absolute, "CLAUDE.md")),
6516
+ name: path13.basename(absolute) || absolute,
6517
+ hasClaudeMd: existsSync4(path13.join(absolute, "CLAUDE.md")),
5404
6518
  ...archivedAt ? { archivedAt } : {}
5405
6519
  };
5406
6520
  }
@@ -5410,13 +6524,13 @@ function isEntry(value) {
5410
6524
 
5411
6525
  // ../host/dist/archive.js
5412
6526
  import { mkdir as mkdir6, readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
5413
- import path13 from "node:path";
6527
+ import path14 from "node:path";
5414
6528
  var SessionArchive = class {
5415
6529
  #archived = /* @__PURE__ */ new Map();
5416
6530
  #restored = /* @__PURE__ */ new Map();
5417
6531
  #storePath;
5418
6532
  constructor(dataDir) {
5419
- this.#storePath = path13.join(dataDir, "session-archive.json");
6533
+ this.#storePath = path14.join(dataDir, "session-archive.json");
5420
6534
  }
5421
6535
  async load() {
5422
6536
  try {
@@ -5528,7 +6642,7 @@ var SessionArchive = class {
5528
6642
  archived: Object.fromEntries(this.#archived),
5529
6643
  restored: Object.fromEntries(this.#restored)
5530
6644
  };
5531
- await mkdir6(path13.dirname(this.#storePath), { recursive: true });
6645
+ await mkdir6(path14.dirname(this.#storePath), { recursive: true });
5532
6646
  await writeFile7(this.#storePath, JSON.stringify(file, null, 2), "utf8");
5533
6647
  }
5534
6648
  };
@@ -5538,7 +6652,7 @@ function isRecord(value) {
5538
6652
 
5539
6653
  // ../host/dist/pins.js
5540
6654
  import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile8 } from "node:fs/promises";
5541
- import path14 from "node:path";
6655
+ import path15 from "node:path";
5542
6656
  var SessionPins = class {
5543
6657
  #pinned = /* @__PURE__ */ new Map();
5544
6658
  /**
@@ -5553,7 +6667,7 @@ var SessionPins = class {
5553
6667
  #moved = /* @__PURE__ */ new Map();
5554
6668
  #storePath;
5555
6669
  constructor(dataDir) {
5556
- this.#storePath = path14.join(dataDir, "session-pins.json");
6670
+ this.#storePath = path15.join(dataDir, "session-pins.json");
5557
6671
  }
5558
6672
  async load() {
5559
6673
  try {
@@ -5710,7 +6824,7 @@ var SessionPins = class {
5710
6824
  pinned: Object.fromEntries(this.#pinned),
5711
6825
  ...this.#moved.size > 0 ? { moved: Object.fromEntries(this.#moved) } : {}
5712
6826
  };
5713
- await mkdir7(path14.dirname(this.#storePath), { recursive: true });
6827
+ await mkdir7(path15.dirname(this.#storePath), { recursive: true });
5714
6828
  await writeFile8(this.#storePath, JSON.stringify(file, null, 2), "utf8");
5715
6829
  }
5716
6830
  };
@@ -5720,12 +6834,12 @@ function isRecord2(value) {
5720
6834
 
5721
6835
  // ../host/dist/order.js
5722
6836
  import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile9 } from "node:fs/promises";
5723
- import path15 from "node:path";
6837
+ import path16 from "node:path";
5724
6838
  var ListOrderStore = class {
5725
6839
  #order = emptyOrder();
5726
6840
  #storePath;
5727
6841
  constructor(dataDir) {
5728
- this.#storePath = path15.join(dataDir, "list-order.json");
6842
+ this.#storePath = path16.join(dataDir, "list-order.json");
5729
6843
  }
5730
6844
  async load() {
5731
6845
  try {
@@ -5801,7 +6915,7 @@ var ListOrderStore = class {
5801
6915
  return changed;
5802
6916
  }
5803
6917
  async #persist() {
5804
- await mkdir8(path15.dirname(this.#storePath), { recursive: true });
6918
+ await mkdir8(path16.dirname(this.#storePath), { recursive: true });
5805
6919
  await writeFile9(this.#storePath, JSON.stringify(this.#order, null, 2), "utf8");
5806
6920
  }
5807
6921
  };
@@ -5832,8 +6946,8 @@ function sanitize(value) {
5832
6946
 
5833
6947
  // ../host/dist/folders.js
5834
6948
  import { mkdir as mkdir9, readFile as readFile11, writeFile as writeFile10 } from "node:fs/promises";
5835
- import path16 from "node:path";
5836
- import { randomUUID as randomUUID3 } from "node:crypto";
6949
+ import path17 from "node:path";
6950
+ import { randomUUID as randomUUID4 } from "node:crypto";
5837
6951
  var COLOR_PATTERN = /^#[0-9a-f]{6}$/i;
5838
6952
  var Folders = class {
5839
6953
  #workspaceFolders = /* @__PURE__ */ new Map();
@@ -5844,7 +6958,7 @@ var Folders = class {
5844
6958
  #pinnedAssignments = /* @__PURE__ */ new Map();
5845
6959
  #storePath;
5846
6960
  constructor(dataDir) {
5847
- this.#storePath = path16.join(dataDir, "folders.json");
6961
+ this.#storePath = path17.join(dataDir, "folders.json");
5848
6962
  }
5849
6963
  async load() {
5850
6964
  try {
@@ -5933,19 +7047,19 @@ var Folders = class {
5933
7047
  };
5934
7048
  }
5935
7049
  async createWorkspaceFolder(name) {
5936
- const id = randomUUID3();
7050
+ const id = randomUUID4();
5937
7051
  this.#workspaceFolders.set(id, { name });
5938
7052
  await this.#persist();
5939
7053
  return id;
5940
7054
  }
5941
7055
  async createSessionFolder(workspaceId, name) {
5942
- const id = randomUUID3();
7056
+ const id = randomUUID4();
5943
7057
  this.#sessionFolders.set(id, { name, workspaceId });
5944
7058
  await this.#persist();
5945
7059
  return id;
5946
7060
  }
5947
7061
  async createPinnedFolder(name) {
5948
- const id = randomUUID3();
7062
+ const id = randomUUID4();
5949
7063
  this.#pinnedFolders.set(id, { name });
5950
7064
  await this.#persist();
5951
7065
  return id;
@@ -6125,7 +7239,7 @@ var Folders = class {
6125
7239
  sessionAssignments: Object.fromEntries(this.#sessionAssignments),
6126
7240
  pinnedAssignments: Object.fromEntries(this.#pinnedAssignments)
6127
7241
  };
6128
- await mkdir9(path16.dirname(this.#storePath), { recursive: true });
7242
+ await mkdir9(path17.dirname(this.#storePath), { recursive: true });
6129
7243
  await writeFile10(this.#storePath, JSON.stringify(file, null, 2), "utf8");
6130
7244
  }
6131
7245
  };
@@ -6148,15 +7262,16 @@ function isPinnedFolder(value) {
6148
7262
 
6149
7263
  // ../host/dist/app-settings.js
6150
7264
  import { mkdir as mkdir10, readFile as readFile12, writeFile as writeFile11 } from "node:fs/promises";
6151
- import { homedir as homedir4 } from "node:os";
6152
- import path17 from "node:path";
7265
+ import { homedir as homedir5 } from "node:os";
7266
+ import path18 from "node:path";
6153
7267
  var DEFAULT_AUTO_ARCHIVE_AFTER_MS = 2 * 24 * 60 * 60 * 1e3;
6154
7268
  var MIN_AUTO_ARCHIVE_AFTER_MS = 60 * 60 * 1e3;
6155
7269
  var DEFAULT_AUTO_STOP_AFTER_MS = 10 * 60 * 1e3;
6156
7270
  var MIN_AUTO_STOP_AFTER_MS = 60 * 1e3;
6157
7271
  var MIN_AUTO_SAVE_AFTER_PAUSE_MS = 500;
7272
+ var DEFAULT_BLOCKED_COUNTRIES = ["RU", "BY", "CN", "IR", "KP", "SY", "CU"];
6158
7273
  var FOLDER_ACCESS = ["all", "projects", "project"];
6159
- var PROVIDERS = ["claude", "antigravity"];
7274
+ var PROVIDERS = ["claude", "antigravity", "codex"];
6160
7275
  var PERMISSION_MODES = [
6161
7276
  "default",
6162
7277
  "acceptEdits",
@@ -6191,13 +7306,17 @@ var DEFAULT_APP_SETTINGS = {
6191
7306
  limitStopFiveHourPct: 95,
6192
7307
  limitStopSevenDayPct: 99,
6193
7308
  // Оба: до появления настройки приложение предлагало обоих агентов.
6194
- enabledProviders: ["claude", "antigravity"]
7309
+ enabledProviders: ["claude", "antigravity", "codex"],
7310
+ // Включено: запрос без VPN может стоить аккаунта, а тому, у кого маршрут и
7311
+ // так из разрешённой страны, защита ничем не мешает.
7312
+ networkGuardEnabled: true,
7313
+ networkGuardBlockedCountries: DEFAULT_BLOCKED_COUNTRIES
6195
7314
  };
6196
7315
  var AppSettingsStore = class {
6197
7316
  #settings = { ...DEFAULT_APP_SETTINGS };
6198
7317
  #storePath;
6199
7318
  constructor(dataDir) {
6200
- this.#storePath = path17.join(dataDir, "app-settings.json");
7319
+ this.#storePath = path18.join(dataDir, "app-settings.json");
6201
7320
  }
6202
7321
  async load() {
6203
7322
  try {
@@ -6221,7 +7340,7 @@ var AppSettingsStore = class {
6221
7340
  async update(patch) {
6222
7341
  const next = sanitize2(patch, this.#settings);
6223
7342
  this.#settings = next;
6224
- await mkdir10(path17.dirname(this.#storePath), { recursive: true });
7343
+ await mkdir10(path18.dirname(this.#storePath), { recursive: true });
6225
7344
  await writeFile11(this.#storePath, JSON.stringify(next, null, 2), "utf8");
6226
7345
  return this.current;
6227
7346
  }
@@ -6276,10 +7395,19 @@ function sanitize2(value, fallback) {
6276
7395
  if (picked.length > 0)
6277
7396
  result.enabledProviders = picked;
6278
7397
  }
7398
+ if (typeof record.networkGuardEnabled === "boolean") {
7399
+ result.networkGuardEnabled = record.networkGuardEnabled;
7400
+ }
7401
+ const countries = record.networkGuardBlockedCountries;
7402
+ if (Array.isArray(countries)) {
7403
+ result.networkGuardBlockedCountries = [
7404
+ ...new Set(countries.filter((c) => typeof c === "string").map((c) => c.trim().toUpperCase()).filter((c) => /^[A-Z]{2}$/.test(c)))
7405
+ ];
7406
+ }
6279
7407
  return result;
6280
7408
  }
6281
7409
  function claudeHome() {
6282
- return path17.join(homedir4(), ".claude");
7410
+ return path18.join(homedir5(), ".claude");
6283
7411
  }
6284
7412
  function writableRoots(mode, cwd, workspacePaths) {
6285
7413
  if (mode === "all")
@@ -6289,7 +7417,7 @@ function writableRoots(mode, cwd, workspacePaths) {
6289
7417
  return [...roots];
6290
7418
  for (const workspace of [cwd, ...workspacePaths]) {
6291
7419
  roots.add(workspace);
6292
- const parent = path17.dirname(workspace);
7420
+ const parent = path18.dirname(workspace);
6293
7421
  if (parent !== workspace)
6294
7422
  roots.add(parent);
6295
7423
  }
@@ -6298,11 +7426,11 @@ function writableRoots(mode, cwd, workspacePaths) {
6298
7426
 
6299
7427
  // ../host/dist/handoff-store.js
6300
7428
  import { mkdir as mkdir11, readFile as readFile13, readdir as readdir3, rm as rm4, writeFile as writeFile12 } from "node:fs/promises";
6301
- import path18 from "node:path";
7429
+ import path19 from "node:path";
6302
7430
  var HandoffStore = class {
6303
7431
  #dir;
6304
7432
  constructor(dataDir) {
6305
- this.#dir = path18.join(dataDir, "handoffs");
7433
+ this.#dir = path19.join(dataDir, "handoffs");
6306
7434
  }
6307
7435
  async get(sessionId) {
6308
7436
  const file = this.#file(sessionId);
@@ -6339,7 +7467,7 @@ var HandoffStore = class {
6339
7467
  #file(sessionId) {
6340
7468
  if (!/^[\w-]{1,128}$/.test(sessionId))
6341
7469
  return null;
6342
- return path18.join(this.#dir, `${sessionId}.json`);
7470
+ return path19.join(this.#dir, `${sessionId}.json`);
6343
7471
  }
6344
7472
  };
6345
7473
  var SAME_WINDOW_MS = 5 * 6e4;
@@ -6347,7 +7475,7 @@ var LimitStopState = class {
6347
7475
  #fired = {};
6348
7476
  #storePath;
6349
7477
  constructor(dataDir) {
6350
- this.#storePath = path18.join(dataDir, "limit-stop.json");
7478
+ this.#storePath = path19.join(dataDir, "limit-stop.json");
6351
7479
  }
6352
7480
  async load() {
6353
7481
  try {
@@ -6383,7 +7511,7 @@ var LimitStopState = class {
6383
7511
  /** Запомнить срабатывание до `until` (момент сброса окна). */
6384
7512
  async mark(limit, until) {
6385
7513
  this.#fired[limit] = until;
6386
- await mkdir11(path18.dirname(this.#storePath), { recursive: true });
7514
+ await mkdir11(path19.dirname(this.#storePath), { recursive: true });
6387
7515
  await writeFile12(this.#storePath, JSON.stringify(this.#fired, null, 2), "utf8");
6388
7516
  }
6389
7517
  };
@@ -6489,8 +7617,8 @@ function capitalize(text) {
6489
7617
 
6490
7618
  // ../host/dist/plugins.js
6491
7619
  import { readFile as readFile14 } from "node:fs/promises";
6492
- import { homedir as homedir5 } from "node:os";
6493
- import path19 from "node:path";
7620
+ import { homedir as homedir6 } from "node:os";
7621
+ import path20 from "node:path";
6494
7622
  var TIMEOUT_MS2 = 12e4;
6495
7623
  var SAFE_ARG = /^[A-Za-z0-9][A-Za-z0-9._@/-]*$/;
6496
7624
  var PluginCli = class {
@@ -6589,10 +7717,10 @@ async function readCatalog(markets, installed) {
6589
7717
  return lists.flat().sort((a, b) => a.name.localeCompare(b.name));
6590
7718
  }
6591
7719
  async function readMarketplace(market, installed) {
6592
- const root = market.installLocation || path19.join(homedir5(), ".claude", "plugins", "marketplaces", market.name);
7720
+ const root = market.installLocation || path20.join(homedir6(), ".claude", "plugins", "marketplaces", market.name);
6593
7721
  let manifest;
6594
7722
  try {
6595
- const text = await readFile14(path19.join(root, ".claude-plugin", "marketplace.json"), "utf8");
7723
+ const text = await readFile14(path20.join(root, ".claude-plugin", "marketplace.json"), "utf8");
6596
7724
  manifest = JSON.parse(text);
6597
7725
  } catch {
6598
7726
  return [];
@@ -6635,7 +7763,7 @@ function authorName(raw) {
6635
7763
  }
6636
7764
 
6637
7765
  // ../host/dist/auth.js
6638
- import { EventEmitter as EventEmitter3 } from "node:events";
7766
+ import { EventEmitter as EventEmitter6 } from "node:events";
6639
7767
  var QUICK_TIMEOUT_MS = 3e4;
6640
7768
  var LOGIN_TIMEOUT_MS = 10 * 6e4;
6641
7769
  var URL_RE = /https:\/\/\S*\/oauth\/authorize\S*/;
@@ -6647,7 +7775,7 @@ var IDLE = {
6647
7775
  output: "",
6648
7776
  error: null
6649
7777
  };
6650
- var AuthCli = class extends EventEmitter3 {
7778
+ var AuthCli = class extends EventEmitter6 {
6651
7779
  #child = null;
6652
7780
  #state = IDLE;
6653
7781
  #timer = null;
@@ -6832,9 +7960,9 @@ function describe5(error) {
6832
7960
  }
6833
7961
 
6834
7962
  // ../host/dist/relay-link.js
6835
- import { EventEmitter as EventEmitter4 } from "node:events";
7963
+ import { EventEmitter as EventEmitter7 } from "node:events";
6836
7964
  import { readFile as readFile15, writeFile as writeFile13, rm as rm5, mkdir as mkdir12 } from "node:fs/promises";
6837
- import path20 from "node:path";
7965
+ import path21 from "node:path";
6838
7966
  import { hostname } from "node:os";
6839
7967
  import WebSocket from "ws";
6840
7968
 
@@ -6851,7 +7979,7 @@ var SILENCE_TIMEOUT_MS = 95e3;
6851
7979
  function jitter(delay2) {
6852
7980
  return Math.round(delay2 * (0.8 + Math.random() * 0.4));
6853
7981
  }
6854
- var RelayLink = class extends EventEmitter4 {
7982
+ var RelayLink = class extends EventEmitter7 {
6855
7983
  #credentials = null;
6856
7984
  #socket = null;
6857
7985
  #retry = RETRY_MIN_MS;
@@ -6889,7 +8017,7 @@ var RelayLink = class extends EventEmitter4 {
6889
8017
  #log;
6890
8018
  constructor(options2) {
6891
8019
  super();
6892
- this.#file = path20.join(options2.dataDir, "relay.json");
8020
+ this.#file = path21.join(options2.dataDir, "relay.json");
6893
8021
  this.#defaultUrl = options2.defaultUrl.replace(/\/+$/, "");
6894
8022
  this.#log = options2.log ?? ((msg) => console.log(`[relay] ${msg}`));
6895
8023
  }
@@ -6983,7 +8111,7 @@ var RelayLink = class extends EventEmitter4 {
6983
8111
  }
6984
8112
  }
6985
8113
  async #write(credentials) {
6986
- await mkdir12(path20.dirname(this.#file), { recursive: true });
8114
+ await mkdir12(path21.dirname(this.#file), { recursive: true });
6987
8115
  await writeFile13(this.#file, `${JSON.stringify(credentials, null, 2)}
6988
8116
  `, {
6989
8117
  encoding: "utf8",
@@ -7239,14 +8367,14 @@ function humanize(error) {
7239
8367
  }
7240
8368
 
7241
8369
  // ../host/dist/terminal.js
7242
- import { EventEmitter as EventEmitter5 } from "node:events";
7243
- import { access as access2, constants as constants2 } from "node:fs/promises";
7244
- import path21 from "node:path";
7245
- import { randomUUID as randomUUID4 } from "node:crypto";
8370
+ import { EventEmitter as EventEmitter8 } from "node:events";
8371
+ import { access as access2, constants as constants3 } from "node:fs/promises";
8372
+ import path22 from "node:path";
8373
+ import { randomUUID as randomUUID5 } from "node:crypto";
7246
8374
  var SCROLLBACK_BYTES = 256 * 1024;
7247
8375
  var FLUSH_MS = 12;
7248
8376
  var MAX_TERMINALS = 24;
7249
- var TerminalHub = class extends EventEmitter5 {
8377
+ var TerminalHub = class extends EventEmitter8 {
7250
8378
  #terminals = /* @__PURE__ */ new Map();
7251
8379
  #module = null;
7252
8380
  list(workspaceId) {
@@ -7279,7 +8407,7 @@ var TerminalHub = class extends EventEmitter5 {
7279
8407
  env: environment()
7280
8408
  });
7281
8409
  const info = {
7282
- id: randomUUID4(),
8410
+ id: randomUUID5(),
7283
8411
  workspaceId: options2.workspaceId,
7284
8412
  title: shell.title,
7285
8413
  shell: shell.file,
@@ -7411,36 +8539,36 @@ async function defaultShell() {
7411
8539
  if (pwsh)
7412
8540
  return { file: pwsh, args: [], title: "pwsh" };
7413
8541
  const system = process.env.SystemRoot ?? "C:\\Windows";
7414
- const powershell = path21.join(system, "System32/WindowsPowerShell/v1.0/powershell.exe");
8542
+ const powershell = path22.join(system, "System32/WindowsPowerShell/v1.0/powershell.exe");
7415
8543
  if (await exists2(powershell))
7416
8544
  return { file: powershell, args: [], title: "powershell" };
7417
8545
  return { file: process.env.COMSPEC ?? "cmd.exe", args: [], title: "cmd" };
7418
8546
  }
7419
8547
  const shell = process.env.SHELL;
7420
8548
  if (shell && await exists2(shell))
7421
- return { file: shell, args: [], title: path21.basename(shell) };
8549
+ return { file: shell, args: [], title: path22.basename(shell) };
7422
8550
  if (await exists2("/bin/bash"))
7423
8551
  return { file: "/bin/bash", args: [], title: "bash" };
7424
8552
  return { file: "/bin/sh", args: [], title: "sh" };
7425
8553
  }
7426
8554
  async function findOnPath(name) {
7427
- const dirs = (process.env.PATH ?? "").split(path21.delimiter).filter(Boolean);
8555
+ const dirs = (process.env.PATH ?? "").split(path22.delimiter).filter(Boolean);
7428
8556
  for (const dir of dirs) {
7429
- const candidate = path21.join(dir, name);
8557
+ const candidate = path22.join(dir, name);
7430
8558
  if (await exists2(candidate))
7431
8559
  return candidate;
7432
8560
  }
7433
8561
  return null;
7434
8562
  }
7435
8563
  function exists2(file) {
7436
- return access2(file, constants2.X_OK).then(() => true, () => false);
8564
+ return access2(file, constants3.X_OK).then(() => true, () => false);
7437
8565
  }
7438
8566
 
7439
8567
  // ../host/dist/mcp-config.js
7440
8568
  import { readFile as readFile16, writeFile as writeFile14, mkdir as mkdir13 } from "node:fs/promises";
7441
- import path22 from "node:path";
8569
+ import path23 from "node:path";
7442
8570
  var MCP_FILE = ".mcp.json";
7443
- var LOCAL_SETTINGS2 = path22.join(".claude", "settings.local.json");
8571
+ var LOCAL_SETTINGS2 = path23.join(".claude", "settings.local.json");
7444
8572
  var queues = /* @__PURE__ */ new Map();
7445
8573
  function serialize(cwd, work) {
7446
8574
  const previous = queues.get(cwd) ?? Promise.resolve();
@@ -7449,7 +8577,7 @@ function serialize(cwd, work) {
7449
8577
  return next;
7450
8578
  }
7451
8579
  async function readProjectServers(cwd) {
7452
- const file = await readJson(path22.join(cwd, MCP_FILE));
8580
+ const file = await readJson(path23.join(cwd, MCP_FILE));
7453
8581
  const servers = file?.mcpServers;
7454
8582
  if (!servers || typeof servers !== "object")
7455
8583
  return {};
@@ -7457,23 +8585,23 @@ async function readProjectServers(cwd) {
7457
8585
  }
7458
8586
  function addProjectServer(cwd, name, config) {
7459
8587
  return serialize(cwd, async () => {
7460
- const file = await readJson(path22.join(cwd, MCP_FILE)) ?? {};
8588
+ const file = await readJson(path23.join(cwd, MCP_FILE)) ?? {};
7461
8589
  const servers = file.mcpServers ?? {};
7462
8590
  servers[name] = config;
7463
8591
  file.mcpServers = servers;
7464
- await writeJson(path22.join(cwd, MCP_FILE), file);
8592
+ await writeJson(path23.join(cwd, MCP_FILE), file);
7465
8593
  await approve(cwd, name);
7466
8594
  });
7467
8595
  }
7468
8596
  function removeProjectServer(cwd, name) {
7469
8597
  return serialize(cwd, async () => {
7470
- const file = await readJson(path22.join(cwd, MCP_FILE));
8598
+ const file = await readJson(path23.join(cwd, MCP_FILE));
7471
8599
  const servers = file?.mcpServers;
7472
8600
  if (!servers || !(name in servers))
7473
8601
  return false;
7474
8602
  delete servers[name];
7475
- await writeJson(path22.join(cwd, MCP_FILE), file);
7476
- const settingsPath = path22.join(cwd, LOCAL_SETTINGS2);
8603
+ await writeJson(path23.join(cwd, MCP_FILE), file);
8604
+ const settingsPath = path23.join(cwd, LOCAL_SETTINGS2);
7477
8605
  const settings = await readJson(settingsPath);
7478
8606
  const enabled = settings?.enabledMcpjsonServers;
7479
8607
  if (settings && Array.isArray(enabled) && enabled.includes(name)) {
@@ -7484,7 +8612,7 @@ function removeProjectServer(cwd, name) {
7484
8612
  });
7485
8613
  }
7486
8614
  async function approve(cwd, name) {
7487
- const file = path22.join(cwd, LOCAL_SETTINGS2);
8615
+ const file = path23.join(cwd, LOCAL_SETTINGS2);
7488
8616
  const settings = await readJson(file) ?? {};
7489
8617
  const enabled = Array.isArray(settings.enabledMcpjsonServers) ? settings.enabledMcpjsonServers : [];
7490
8618
  if (!enabled.includes(name))
@@ -7530,13 +8658,13 @@ async function readJson(file) {
7530
8658
  if (error.code === "ENOENT")
7531
8659
  return null;
7532
8660
  if (error instanceof SyntaxError) {
7533
- 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}`);
8661
+ throw new Error(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u0442\u044C ${path23.basename(file)}: ${error.message}`);
7534
8662
  }
7535
8663
  throw error;
7536
8664
  }
7537
8665
  }
7538
8666
  async function writeJson(file, data) {
7539
- await mkdir13(path22.dirname(file), { recursive: true });
8667
+ await mkdir13(path23.dirname(file), { recursive: true });
7540
8668
  await writeFile14(file, `${JSON.stringify(data, null, 2)}
7541
8669
  `, "utf8");
7542
8670
  }
@@ -7555,6 +8683,8 @@ var BUSY_STATES2 = /* @__PURE__ */ new Set([
7555
8683
  "reconnecting"
7556
8684
  ]);
7557
8685
  var HostServer = class {
8686
+ #httpServer = null;
8687
+ #webSockets = null;
7558
8688
  #options;
7559
8689
  #workspaces;
7560
8690
  #archive;
@@ -7567,6 +8697,7 @@ var HostServer = class {
7567
8697
  /** Опросы статуса для идущих OAuth-входов MCP, ключ — сессия и сервер. */
7568
8698
  #mcpAuthWatch = /* @__PURE__ */ new Map();
7569
8699
  #antigravity;
8700
+ #codex;
7570
8701
  /** Боковые сессии («вопрос на полях») по id родителя — не больше одной на разговор. */
7571
8702
  #sides = /* @__PURE__ */ new Map();
7572
8703
  /**
@@ -7614,7 +8745,7 @@ var HostServer = class {
7614
8745
  #limitHandoffBusy = /* @__PURE__ */ new Set();
7615
8746
  /** Живые id сессий, у которых сейчас пишется ручной хэндофф. */
7616
8747
  #handoffBusy = /* @__PURE__ */ new Set();
7617
- #hostId = randomUUID5();
8748
+ #hostId = randomUUID6();
7618
8749
  #plugins = new PluginCli();
7619
8750
  #auth = new AuthCli();
7620
8751
  #relay;
@@ -7638,6 +8769,7 @@ var HostServer = class {
7638
8769
  this.#folders = new Folders(options2.dataDir);
7639
8770
  this.#settings = new AppSettingsStore(options2.dataDir);
7640
8771
  this.#antigravity = new AntigravityStore(options2.dataDir);
8772
+ this.#codex = new AntigravityStore(options2.dataDir, "codex");
7641
8773
  this.#handoffs = new HandoffStore(options2.dataDir);
7642
8774
  this.#limitStop = new LimitStopState(options2.dataDir);
7643
8775
  this.#finder = new ProjectFinder(this.#workspaces);
@@ -7664,9 +8796,11 @@ var HostServer = class {
7664
8796
  const id = workspaceIdFromPath(dir);
7665
8797
  const onDisk = await this.#workspaces.sessionsOnDisk(id);
7666
8798
  const antigravity = await this.#antigravity.list(id);
7667
- return [...onDisk.map((s) => s.sessionId), ...antigravity.map((r) => r.id)];
8799
+ const codex = await this.#codex.list(id);
8800
+ return [...onDisk.map((s) => s.sessionId), ...antigravity.map((r) => r.id), ...codex.map((r) => r.id)];
7668
8801
  });
7669
8802
  await this.#antigravity.remapWorkspaceIds(remap);
8803
+ await this.#codex.remapWorkspaceIds(remap);
7670
8804
  await this.#archive.remapWorkspaceIds(remap);
7671
8805
  await this.#pins.remapWorkspaceIds(remap);
7672
8806
  await this.#order.remapWorkspaceIds(remap);
@@ -7680,6 +8814,7 @@ var HostServer = class {
7680
8814
  await this.#folders.load();
7681
8815
  await this.#carryOverWorkspaceIds();
7682
8816
  await this.#settings.load();
8817
+ this.#wireNetworkGuard();
7683
8818
  await this.#limitStop.load();
7684
8819
  await this.#github.load();
7685
8820
  await warmClaudeLauncher();
@@ -7700,6 +8835,8 @@ var HostServer = class {
7700
8835
  resolve();
7701
8836
  });
7702
8837
  });
8838
+ this.#httpServer = httpServer;
8839
+ this.#webSockets = wss;
7703
8840
  await this.#relay.start().catch((error) => {
7704
8841
  console.error("[relay]", error instanceof Error ? error.message : String(error));
7705
8842
  });
@@ -7708,7 +8845,25 @@ var HostServer = class {
7708
8845
  this.#limitTimer = setInterval(() => this.#sweepUsage(), LIMIT_SWEEP_MS);
7709
8846
  this.#limitTimer.unref();
7710
8847
  }
8848
+ /**
8849
+ * Защита от запросов без VPN (см. network-guard.ts): при блоке гасим
8850
+ * процессы всех живых сессий — идущий ход шлёт запрос за запросом, и
8851
+ * дожидаться его конца нельзя.
8852
+ */
8853
+ #wireNetworkGuard() {
8854
+ networkGuard.on("change", (state) => {
8855
+ this.#broadcast({ type: "network.state", state });
8856
+ if (state.status !== "blocked")
8857
+ return;
8858
+ const reason = state.reason ?? "VPN \u043D\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442";
8859
+ for (const session of this.#sessions.values())
8860
+ session.suspend(reason);
8861
+ });
8862
+ const { networkGuardEnabled, networkGuardBlockedCountries } = this.#settings.current;
8863
+ networkGuard.configure(networkGuardEnabled, networkGuardBlockedCountries);
8864
+ }
7711
8865
  async shutdown() {
8866
+ networkGuard.stop();
7712
8867
  if (this.#autoStopTimer)
7713
8868
  clearInterval(this.#autoStopTimer);
7714
8869
  this.#autoStopTimer = null;
@@ -7720,6 +8875,16 @@ var HostServer = class {
7720
8875
  await this.#relay.close();
7721
8876
  await Promise.all([...this.#sessions.values()].map((s) => s.close("host shutdown")));
7722
8877
  this.#sessions.clear();
8878
+ const wss = this.#webSockets;
8879
+ const http = this.#httpServer;
8880
+ this.#webSockets = null;
8881
+ this.#httpServer = null;
8882
+ for (const socket of wss?.clients ?? [])
8883
+ socket.terminate();
8884
+ await Promise.all([
8885
+ ...wss ? [new Promise((resolve) => wss.close(() => resolve()))] : [],
8886
+ ...http ? [new Promise((resolve) => http.close(() => resolve()))] : []
8887
+ ]);
7723
8888
  }
7724
8889
  // -------------------------------------------------------------------------
7725
8890
  // WebSocket
@@ -7876,6 +9041,7 @@ var HostServer = class {
7876
9041
  this.#sendLive(socket);
7877
9042
  this.#auth.status().then((status) => this.#send(socket, { type: "auth.status", status })).catch(() => void 0);
7878
9043
  this.#send(socket, { type: "relay.state", state: this.#relay.state });
9044
+ this.#send(socket, { type: "network.state", state: networkGuard.state });
7879
9045
  return;
7880
9046
  case "ping":
7881
9047
  this.#send(socket, { type: "pong", nonce: frame.nonce });
@@ -7921,6 +9087,7 @@ var HostServer = class {
7921
9087
  return;
7922
9088
  }
7923
9089
  case "workspace.find.agent":
9090
+ await networkGuard.ensureFresh();
7924
9091
  this.#findWithAgent(frame.query);
7925
9092
  return;
7926
9093
  case "workspace.roots":
@@ -8135,10 +9302,12 @@ var HostServer = class {
8135
9302
  return;
8136
9303
  }
8137
9304
  case "prompt.send":
9305
+ await networkGuard.ensureFresh();
8138
9306
  this.#approveAfterLimit(frame.sessionId);
8139
9307
  this.#requireSession(frame.sessionId).sendPrompt(frame.text, frame.images);
8140
9308
  return;
8141
9309
  case "prompt.edit":
9310
+ await networkGuard.ensureFresh();
8142
9311
  this.#approveAfterLimit(frame.sessionId);
8143
9312
  await this.#requireSession(frame.sessionId).editPrompt(frame.userIndex, frame.text, frame.images);
8144
9313
  return;
@@ -8146,6 +9315,7 @@ var HostServer = class {
8146
9315
  this.#requireSession(frame.sessionId).respondPermission(frame.requestId, frame.decision, frame.reason, frame.answers, frame.annotations, frame.plan);
8147
9316
  return;
8148
9317
  case "side.ask":
9318
+ await networkGuard.ensureFresh();
8149
9319
  this.#ensureSide(this.#requireSession(frame.sessionId)).sendPrompt(frame.text);
8150
9320
  return;
8151
9321
  // Поднять форк, ничего не спрашивая: клиенту нужен его id, чтобы
@@ -8560,6 +9730,7 @@ var HostServer = class {
8560
9730
  await this.#sendSessionList(workspace.id);
8561
9731
  }
8562
9732
  }
9733
+ networkGuard.configure(settings.networkGuardEnabled, settings.networkGuardBlockedCountries);
8563
9734
  if (before.terminalEnabled && !settings.terminalEnabled) {
8564
9735
  for (const terminal of this.#terminals.list()) {
8565
9736
  this.#terminals.close(terminal.id);
@@ -8572,9 +9743,13 @@ var HostServer = class {
8572
9743
  this.#send(socket, { type: "auth.status", status: await this.#auth.status() });
8573
9744
  this.#send(socket, { type: "auth.login", state: this.#auth.loginState });
8574
9745
  return;
8575
- case "auth.login":
9746
+ case "auth.login": {
9747
+ const blocked = (await networkGuard.ensureFresh()).status === "blocked";
9748
+ if (blocked)
9749
+ throw new Error(`${networkGuard.blockedReason()}. \u0412\u0445\u043E\u0434 \u043E\u0442\u043B\u043E\u0436\u0435\u043D.`);
8576
9750
  await this.#auth.login(frame.mode, frame.switchAccount === true);
8577
9751
  return;
9752
+ }
8578
9753
  case "auth.code":
8579
9754
  this.#auth.submitCode(frame.code);
8580
9755
  return;
@@ -9109,7 +10284,31 @@ var HostServer = class {
9109
10284
  throw new Error(`\u0412\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${frame.workspaceId}`);
9110
10285
  const provider = frame.provider ?? "claude";
9111
10286
  if (!frame.resume && !this.#settings.current.enabledProviders.includes(provider)) {
9112
- throw new Error(`${provider === "antigravity" ? "Antigravity" : "Claude"} \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445`);
10287
+ throw new Error(`${provider === "antigravity" ? "Antigravity" : provider === "codex" ? "Codex" : "Claude"} \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445`);
10288
+ }
10289
+ const codexRecord = frame.resume ? await this.#codex.find(frame.resume) : null;
10290
+ if (codexRecord || !frame.resume && provider === "codex") {
10291
+ if (frame.fork)
10292
+ throw new Error("\u0412\u0435\u0442\u0432\u043B\u0435\u043D\u0438\u0435 \u0441\u0435\u0441\u0441\u0438\u0439 Codex \u043F\u043E\u043A\u0430 \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F");
10293
+ if (codexRecord && codexRecord.workspaceId !== workspace.id)
10294
+ throw new Error("\u0421\u0435\u0441\u0441\u0438\u044F Codex \u043F\u0440\u0438\u043D\u0430\u0434\u043B\u0435\u0436\u0438\u0442 \u0434\u0440\u0443\u0433\u043E\u043C\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0443");
10295
+ if (codexRecord)
10296
+ await this.#archive.unarchive([codexRecord.id]);
10297
+ const session2 = new CodexSession({
10298
+ workspaceId: workspace.id,
10299
+ cwd: codexRecord?.cwd ?? workspace.path,
10300
+ store: this.#codex,
10301
+ ...codexRecord ? { record: codexRecord } : {},
10302
+ ...frame.permissionMode ? { permissionMode: frame.permissionMode } : {},
10303
+ ...frame.model ? { model: frame.model } : {},
10304
+ defaultTitle: workspace.name
10305
+ });
10306
+ this.#wire(session2);
10307
+ this.#sessions.set(session2.id, session2);
10308
+ session2.start();
10309
+ this.#sendLive();
10310
+ this.#send(socket, { type: "session.summary", session: session2.summary() });
10311
+ return;
9113
10312
  }
9114
10313
  const agyRecord = frame.resume ? await this.#antigravity.find(frame.resume) : null;
9115
10314
  if (agyRecord || !frame.resume && frame.provider === "antigravity") {
@@ -9273,7 +10472,7 @@ var HostServer = class {
9273
10472
  this.#broadcast({ type: "context.state", sessionId: session.id, context });
9274
10473
  });
9275
10474
  session.on("stderr", (data) => {
9276
- const tag = session instanceof AntigravitySession ? "agy" : "claude";
10475
+ const tag = session instanceof AntigravitySession ? "agy" : session instanceof CodexSession ? "codex" : "claude";
9277
10476
  process.stderr.write(`[${tag} ${session.id.slice(0, 8)}] ${data}`);
9278
10477
  });
9279
10478
  }
@@ -9293,7 +10492,7 @@ var HostServer = class {
9293
10492
  * разговора пока и не было.
9294
10493
  */
9295
10494
  #ensureSide(parent) {
9296
- if (parent instanceof AntigravitySession) {
10495
+ if (!(parent instanceof ClaudeSession)) {
9297
10496
  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");
9298
10497
  }
9299
10498
  const existing = this.#sides.get(parent.id);
@@ -9351,7 +10550,7 @@ var HostServer = class {
9351
10550
  */
9352
10551
  #roots() {
9353
10552
  const roots = this.#finder.roots();
9354
- return roots.length > 0 ? roots : [homedir6()];
10553
+ return roots.length > 0 ? roots : [homedir7()];
9355
10554
  }
9356
10555
  /**
9357
10556
  * Где сессии этого каталога разрешено писать без вопроса.
@@ -9593,7 +10792,7 @@ var HostServer = class {
9593
10792
  live.setTitle(title);
9594
10793
  return;
9595
10794
  }
9596
- if (await this.#antigravity.rename(sessionId, title)) {
10795
+ if (await this.#antigravity.rename(sessionId, title) || await this.#codex.rename(sessionId, title)) {
9597
10796
  if (workspaceId)
9598
10797
  await this.#sendSessionList(workspaceId);
9599
10798
  return;
@@ -9650,6 +10849,11 @@ var HostServer = class {
9650
10849
  continue;
9651
10850
  historical.push(antigravitySummary(record));
9652
10851
  }
10852
+ for (const record of await this.#codex.list(workspaceId)) {
10853
+ if (liveIds.has(record.id))
10854
+ continue;
10855
+ historical.push({ ...antigravitySummary(record), provider: "codex", switchModelsOnFlag: false });
10856
+ }
9653
10857
  const known = /* @__PURE__ */ new Set([...liveIds, ...historical.map((s) => s.id)]);
9654
10858
  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);
9655
10859
  await this.#pins.sweep(workspaceId, known);
@@ -9696,15 +10900,15 @@ var HostServer = class {
9696
10900
  }
9697
10901
  #serveStatic(req, res) {
9698
10902
  const webRoot = this.#options.webRoot;
9699
- if (!webRoot || !existsSync4(webRoot)) {
10903
+ if (!webRoot || !existsSync5(webRoot)) {
9700
10904
  res.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
9701
10905
  res.end("CCV host is running. UI is served by the Vite dev server.\n");
9702
10906
  return;
9703
10907
  }
9704
10908
  const url2 = new URL(req.url ?? "/", "http://localhost");
9705
- const requested = path23.join(webRoot, path23.normalize(url2.pathname));
9706
- const target = requested.startsWith(webRoot) && existsSync4(requested) && statSync3(requested).isFile() ? requested : path23.join(webRoot, "index.html");
9707
- if (!existsSync4(target)) {
10909
+ const requested = path24.join(webRoot, path24.normalize(url2.pathname));
10910
+ const target = requested.startsWith(webRoot) && existsSync5(requested) && statSync4(requested).isFile() ? requested : path24.join(webRoot, "index.html");
10911
+ if (!existsSync5(target)) {
9708
10912
  res.writeHead(404).end("not found");
9709
10913
  return;
9710
10914
  }
@@ -9729,7 +10933,7 @@ function contentType(file) {
9729
10933
  ".svg": "image/svg+xml",
9730
10934
  ".woff2": "font/woff2"
9731
10935
  };
9732
- return types[path23.extname(file)] ?? "application/octet-stream";
10936
+ return types[path24.extname(file)] ?? "application/octet-stream";
9733
10937
  }
9734
10938
  function antigravitySummary(record) {
9735
10939
  return {
@@ -9758,9 +10962,9 @@ function antigravitySummary(record) {
9758
10962
  }
9759
10963
 
9760
10964
  // dist/ccv.js
9761
- var HERE = path24.dirname(fileURLToPath(import.meta.url));
9762
- var PACKAGE_ROOT = path24.resolve(HERE, "..");
9763
- 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.
10965
+ var HERE = path25.dirname(fileURLToPath(import.meta.url));
10966
+ var PACKAGE_ROOT = path25.resolve(HERE, "..");
10967
+ var USAGE = `ccv \u2014 Claude Code Visual: \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043A Claude Code, Codex \u0438 Antigravity \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435.
9764
10968
 
9765
10969
  \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435:
9766
10970
  ccv [\u043E\u043F\u0446\u0438\u0438]
@@ -9769,12 +10973,12 @@ var USAGE = `ccv \u2014 Claude Code Visual: \u043B\u043E\u043A\u0430\u043B\u044C
9769
10973
  -p, --port <\u043F\u043E\u0440\u0442> \u043F\u043E\u0440\u0442 \u0445\u043E\u0441\u0442\u0430 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E 8787)
9770
10974
  --host <\u0430\u0434\u0440\u0435\u0441> \u0430\u0434\u0440\u0435\u0441 \u043F\u0440\u043E\u0441\u043B\u0443\u0448\u0438\u0432\u0430\u043D\u0438\u044F (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E 127.0.0.1)
9771
10975
  --no-open \u043D\u0435 \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0442\u044C \u0431\u0440\u0430\u0443\u0437\u0435\u0440
9772
- --claude \u0447\u0435\u043C \u0431\u0443\u0434\u0443\u0442 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C\u0441\u044F \u0441\u0435\u0441\u0441\u0438\u0438, \u0438 \u0432\u044B\u0439\u0442\u0438
10976
+ --claude \u0447\u0435\u043C \u0431\u0443\u0434\u0443\u0442 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C\u0441\u044F \u0441\u0435\u0441\u0441\u0438\u0438 Claude, \u0438 \u0432\u044B\u0439\u0442\u0438
9773
10977
  -v, --version \u0432\u0435\u0440\u0441\u0438\u044F
9774
10978
  -h, --help \u044D\u0442\u0430 \u0441\u043F\u0440\u0430\u0432\u043A\u0430
9775
10979
 
9776
10980
  \u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435: CCV_PORT, CCV_HOST, CCV_DATA_DIR, CCV_WEB_ROOT, CCV_RELAY_URL,
9777
- CCV_CLAUDE_PATH, CCV_BUNDLED_CLAUDE.
10981
+ CCV_CLAUDE_PATH, CCV_BUNDLED_CLAUDE, CCV_CODEX_PATH, CCV_AGY_PATH.
9778
10982
  \u0424\u043B\u0430\u0433 \u0432\u0430\u0436\u043D\u0435\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0439 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F, \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F \u0432\u0430\u0436\u043D\u0435\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E.`;
9779
10983
  var options = {
9780
10984
  port: { type: "string", short: "p" },
@@ -9826,7 +11030,7 @@ var server = new HostServer({
9826
11030
  dataDir: defaults.dataDir,
9827
11031
  // UI лежит в пакете рядом с бандлом — это и есть разница между установленной
9828
11032
  // утилитой и запуском из репозитория, где сборка живёт в `apps/web/dist`.
9829
- webRoot: process.env.CCV_WEB_ROOT ?? path24.join(PACKAGE_ROOT, "web"),
11033
+ webRoot: process.env.CCV_WEB_ROOT ?? path25.join(PACKAGE_ROOT, "web"),
9830
11034
  version,
9831
11035
  relayUrl: defaults.relayUrl
9832
11036
  });
@@ -9855,7 +11059,7 @@ for (const signal of ["SIGINT", "SIGTERM"]) {
9855
11059
  }
9856
11060
  async function packageVersion() {
9857
11061
  try {
9858
- const text = await readFile17(path24.join(PACKAGE_ROOT, "package.json"), "utf8");
11062
+ const text = await readFile17(path25.join(PACKAGE_ROOT, "package.json"), "utf8");
9859
11063
  return JSON.parse(text).version ?? "0.0.0";
9860
11064
  } catch {
9861
11065
  return "0.0.0";
@@ -9899,7 +11103,7 @@ function openBrowser(target) {
9899
11103
  return;
9900
11104
  }
9901
11105
  const command = process.platform === "win32" ? { file: "cmd", args: ["/c", "start", "", target] } : process.platform === "darwin" ? { file: "open", args: [target] } : { file: "xdg-open", args: [target] };
9902
- const child = spawn3(command.file, command.args, {
11106
+ const child = spawn4(command.file, command.args, {
9903
11107
  stdio: "ignore",
9904
11108
  detached: true,
9905
11109
  windowsHide: true