@i14ks/ccv 0.21.0 → 0.23.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.
- package/bundle/ccv.mjs +478 -13
- package/package.json +1 -1
- package/web/assets/{codemirror-B6QVSqxx.js → codemirror-BdKpW4CC.js} +1 -1
- package/web/assets/{index-CqM8bKzd.js → index-B2BGvdag.js} +1 -1
- package/web/assets/index-BCLbqlhA.js +134 -0
- package/web/assets/{index-CCRhGn6u.js → index-BrkwvvWk.js} +1 -1
- package/web/assets/{index-BlPPlrvg.js → index-BroMLvyS.js} +1 -1
- package/web/assets/{index-rsg6TYOk.js → index-C59b0_Vk.js} +1 -1
- package/web/assets/index-C6Y6TadO.css +1 -0
- package/web/assets/{index-B8GLobgh.js → index-CRsndRFc.js} +1 -1
- package/web/assets/{index-0l0v5ZjK.js → index-CWTFMuFd.js} +1 -1
- package/web/assets/{index-BYERpa5Q.js → index-CZuUjzhP.js} +1 -1
- package/web/assets/{index-DdFPrUTi.js → index-ChyP1fTD.js} +1 -1
- package/web/assets/{index-1VuadINO.js → index-D6DYiY6f.js} +1 -1
- package/web/assets/{index-BqED4I4f.js → index-D83he8x-.js} +1 -1
- package/web/assets/{index-oOWAdIqY.js → index-DPrfFlZq.js} +1 -1
- package/web/assets/{index-C5hGXrTC.js → index-DXHZO0ym.js} +1 -1
- package/web/assets/{index-Cgi4XmXk.js → index-Dd7rh20x.js} +1 -1
- package/web/assets/{index-BwuRWAfa.js → index-DqnCWDLh.js} +1 -1
- package/web/assets/{index-Bf6VCb0a.js → index-Si6no75_.js} +1 -1
- package/web/assets/{index-CTe-duo7.js → index-UxKgaDrj.js} +1 -1
- package/web/assets/{index-B9qDsSjC.js → index-YCUybqa_.js} +1 -1
- package/web/assets/{index-Zo5hIeFa.js → index-wsSNo9PM.js} +1 -1
- package/web/assets/{index-DmwQZoSG.js → index-yXtesgZ4.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-3BSpF-Pn.js +0 -134
- package/web/assets/index-Dv13T8ko.css +0 -1
package/bundle/ccv.mjs
CHANGED
|
@@ -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
|
|
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;
|
|
@@ -2154,6 +2381,41 @@ var ClaudeSession = class extends EventEmitter {
|
|
|
2154
2381
|
throw new Error("\u0421\u0435\u0441\u0441\u0438\u044F \u0435\u0449\u0451 \u043D\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u0430");
|
|
2155
2382
|
await this.#query.reconnectMcpServer(name);
|
|
2156
2383
|
}
|
|
2384
|
+
/**
|
|
2385
|
+
* Начать OAuth-вход сервера в `needs-auth`.
|
|
2386
|
+
*
|
|
2387
|
+
* `mcpAuthenticate` есть в `Query`, но не в `sdk.d.ts` — отсюда приведение.
|
|
2388
|
+
* CLI сам слушает колбэк на `localhost` и сам переподключает сервер после
|
|
2389
|
+
* входа; нам отдаёт только ссылку. Без `redirectUri` — тогда CLI выбирает
|
|
2390
|
+
* локальный колбэк, единственный, что работает без своего домена.
|
|
2391
|
+
*/
|
|
2392
|
+
async authenticateMcpServer(name) {
|
|
2393
|
+
if (!this.#query)
|
|
2394
|
+
throw new Error("\u0421\u0435\u0441\u0441\u0438\u044F \u0435\u0449\u0451 \u043D\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u0430");
|
|
2395
|
+
const q = this.#query;
|
|
2396
|
+
if (typeof q.mcpAuthenticate !== "function") {
|
|
2397
|
+
throw new Error("\u042D\u0442\u0430 \u0432\u0435\u0440\u0441\u0438\u044F Agent SDK \u043D\u0435 \u0443\u043C\u0435\u0435\u0442 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u0442\u044C MCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044B");
|
|
2398
|
+
}
|
|
2399
|
+
const response = await q.mcpAuthenticate(name) ?? {};
|
|
2400
|
+
return {
|
|
2401
|
+
url: typeof response.authUrl === "string" ? response.authUrl : "",
|
|
2402
|
+
requiresUserAction: response.requiresUserAction === true,
|
|
2403
|
+
callbackExpected: response.callbackExpected === true
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
/**
|
|
2407
|
+
* Отдать CLI адрес редиректа, вставленный руками, — когда браузер не на этой
|
|
2408
|
+
* машине и до `localhost` хоста не достучался. Резолвится после входа.
|
|
2409
|
+
*/
|
|
2410
|
+
async submitMcpOAuthCallback(name, callbackUrl) {
|
|
2411
|
+
if (!this.#query)
|
|
2412
|
+
throw new Error("\u0421\u0435\u0441\u0441\u0438\u044F \u0435\u0449\u0451 \u043D\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u0430");
|
|
2413
|
+
const q = this.#query;
|
|
2414
|
+
if (typeof q.mcpSubmitOAuthCallbackUrl !== "function") {
|
|
2415
|
+
throw new Error("\u042D\u0442\u0430 \u0432\u0435\u0440\u0441\u0438\u044F Agent SDK \u043D\u0435 \u0443\u043C\u0435\u0435\u0442 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u0442\u044C MCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044B");
|
|
2416
|
+
}
|
|
2417
|
+
await q.mcpSubmitOAuthCallbackUrl(name, callbackUrl);
|
|
2418
|
+
}
|
|
2157
2419
|
/** Включить или выключить сервер — тот же обход слэш-команды, что выше. */
|
|
2158
2420
|
async toggleMcpServer(name, enabled) {
|
|
2159
2421
|
if (!this.#query)
|
|
@@ -2870,7 +3132,7 @@ function contentToText2(content) {
|
|
|
2870
3132
|
}
|
|
2871
3133
|
|
|
2872
3134
|
// ../host/dist/antigravity-session.js
|
|
2873
|
-
import { EventEmitter as
|
|
3135
|
+
import { EventEmitter as EventEmitter3 } from "node:events";
|
|
2874
3136
|
import { spawn as spawn2, execFile as execFile3 } from "node:child_process";
|
|
2875
3137
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2876
3138
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
@@ -3090,7 +3352,7 @@ function agyContextWindow(model) {
|
|
|
3090
3352
|
|
|
3091
3353
|
// ../host/dist/antigravity-session.js
|
|
3092
3354
|
var TRANSIENT = /* @__PURE__ */ new Set(["assistant.delta", "thinking.delta", "state"]);
|
|
3093
|
-
var AntigravitySession = class extends
|
|
3355
|
+
var AntigravitySession = class extends EventEmitter3 {
|
|
3094
3356
|
id;
|
|
3095
3357
|
workspaceId;
|
|
3096
3358
|
cwd;
|
|
@@ -3230,6 +3492,8 @@ var AntigravitySession = class extends EventEmitter2 {
|
|
|
3230
3492
|
}
|
|
3231
3493
|
this.#capabilities = this.#baseCapabilities();
|
|
3232
3494
|
this.emit("capabilities");
|
|
3495
|
+
if (this.#refuseOffRoute())
|
|
3496
|
+
return;
|
|
3233
3497
|
void agyModels(this.#executable).then((models) => {
|
|
3234
3498
|
if (!this.#capabilities || models.length === 0)
|
|
3235
3499
|
return;
|
|
@@ -3288,6 +3552,8 @@ var AntigravitySession = class extends EventEmitter2 {
|
|
|
3288
3552
|
#spawn() {
|
|
3289
3553
|
if (!this.#executable || this.#closing)
|
|
3290
3554
|
return;
|
|
3555
|
+
if (this.#refuseOffRoute())
|
|
3556
|
+
return;
|
|
3291
3557
|
let child;
|
|
3292
3558
|
try {
|
|
3293
3559
|
child = spawn2(this.#executable, this.#args(), {
|
|
@@ -3385,12 +3651,48 @@ var AntigravitySession = class extends EventEmitter2 {
|
|
|
3385
3651
|
reconnect(_reason) {
|
|
3386
3652
|
this.#restart();
|
|
3387
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
|
+
}
|
|
3388
3687
|
// -------------------------------------------------------------------------
|
|
3389
3688
|
// Ввод
|
|
3390
3689
|
// -------------------------------------------------------------------------
|
|
3391
3690
|
sendPrompt(text, images) {
|
|
3392
3691
|
if (this.#closing)
|
|
3393
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.`);
|
|
3394
3696
|
const command = /^\/([\w-]+)(?:\s+([\s\S]*))?$/.exec(text.trim());
|
|
3395
3697
|
if (command && AGY_CLI_COMMANDS.has(command[1]) && (!images || images.length === 0)) {
|
|
3396
3698
|
void this.#runCliCommand(text, command[1], command[2]?.trim() ?? "");
|
|
@@ -3607,7 +3909,7 @@ Attached image${paths.length > 1 ? "s" : ""} (view them with your file viewing t
|
|
|
3607
3909
|
}
|
|
3608
3910
|
/** Квота `agy` (`/usage`) — общая на аккаунт, кэш один на хост. */
|
|
3609
3911
|
async refreshUsage(force = false) {
|
|
3610
|
-
if (!this.#executable)
|
|
3912
|
+
if (!this.#executable || networkGuard.blockedReason())
|
|
3611
3913
|
return;
|
|
3612
3914
|
const usage = await agyUsage(this.#executable, force);
|
|
3613
3915
|
if (!usage)
|
|
@@ -3647,6 +3949,12 @@ Attached image${paths.length > 1 ? "s" : ""} (view them with your file viewing t
|
|
|
3647
3949
|
async toggleMcpServer(_name, _enabled) {
|
|
3648
3950
|
throw new Error("MCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044B Antigravity \u043D\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u044E\u0442\u0441\u044F \u0432 \u0441\u0430\u043C\u043E\u043C agy (`agy mcp`)");
|
|
3649
3951
|
}
|
|
3952
|
+
async authenticateMcpServer(_name) {
|
|
3953
|
+
throw new Error("MCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044B Antigravity \u043D\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u044E\u0442\u0441\u044F \u0432 \u0441\u0430\u043C\u043E\u043C agy (`agy mcp`)");
|
|
3954
|
+
}
|
|
3955
|
+
async submitMcpOAuthCallback(_name, _callbackUrl) {
|
|
3956
|
+
throw new Error("MCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044B Antigravity \u043D\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u044E\u0442\u0441\u044F \u0432 \u0441\u0430\u043C\u043E\u043C agy (`agy mcp`)");
|
|
3957
|
+
}
|
|
3650
3958
|
setTitle(title) {
|
|
3651
3959
|
this.#record.title = title;
|
|
3652
3960
|
this.#titleIsDefault = false;
|
|
@@ -6114,6 +6422,7 @@ var MIN_AUTO_ARCHIVE_AFTER_MS = 60 * 60 * 1e3;
|
|
|
6114
6422
|
var DEFAULT_AUTO_STOP_AFTER_MS = 10 * 60 * 1e3;
|
|
6115
6423
|
var MIN_AUTO_STOP_AFTER_MS = 60 * 1e3;
|
|
6116
6424
|
var MIN_AUTO_SAVE_AFTER_PAUSE_MS = 500;
|
|
6425
|
+
var DEFAULT_BLOCKED_COUNTRIES = ["RU", "BY", "CN", "IR", "KP", "SY", "CU"];
|
|
6117
6426
|
var FOLDER_ACCESS = ["all", "projects", "project"];
|
|
6118
6427
|
var PROVIDERS = ["claude", "antigravity"];
|
|
6119
6428
|
var PERMISSION_MODES = [
|
|
@@ -6150,7 +6459,11 @@ var DEFAULT_APP_SETTINGS = {
|
|
|
6150
6459
|
limitStopFiveHourPct: 95,
|
|
6151
6460
|
limitStopSevenDayPct: 99,
|
|
6152
6461
|
// Оба: до появления настройки приложение предлагало обоих агентов.
|
|
6153
|
-
enabledProviders: ["claude", "antigravity"]
|
|
6462
|
+
enabledProviders: ["claude", "antigravity"],
|
|
6463
|
+
// Включено: запрос без VPN может стоить аккаунта, а тому, у кого маршрут и
|
|
6464
|
+
// так из разрешённой страны, защита ничем не мешает.
|
|
6465
|
+
networkGuardEnabled: true,
|
|
6466
|
+
networkGuardBlockedCountries: DEFAULT_BLOCKED_COUNTRIES
|
|
6154
6467
|
};
|
|
6155
6468
|
var AppSettingsStore = class {
|
|
6156
6469
|
#settings = { ...DEFAULT_APP_SETTINGS };
|
|
@@ -6235,6 +6548,15 @@ function sanitize2(value, fallback) {
|
|
|
6235
6548
|
if (picked.length > 0)
|
|
6236
6549
|
result.enabledProviders = picked;
|
|
6237
6550
|
}
|
|
6551
|
+
if (typeof record.networkGuardEnabled === "boolean") {
|
|
6552
|
+
result.networkGuardEnabled = record.networkGuardEnabled;
|
|
6553
|
+
}
|
|
6554
|
+
const countries = record.networkGuardBlockedCountries;
|
|
6555
|
+
if (Array.isArray(countries)) {
|
|
6556
|
+
result.networkGuardBlockedCountries = [
|
|
6557
|
+
...new Set(countries.filter((c) => typeof c === "string").map((c) => c.trim().toUpperCase()).filter((c) => /^[A-Z]{2}$/.test(c)))
|
|
6558
|
+
];
|
|
6559
|
+
}
|
|
6238
6560
|
return result;
|
|
6239
6561
|
}
|
|
6240
6562
|
function claudeHome() {
|
|
@@ -6594,7 +6916,7 @@ function authorName(raw) {
|
|
|
6594
6916
|
}
|
|
6595
6917
|
|
|
6596
6918
|
// ../host/dist/auth.js
|
|
6597
|
-
import { EventEmitter as
|
|
6919
|
+
import { EventEmitter as EventEmitter4 } from "node:events";
|
|
6598
6920
|
var QUICK_TIMEOUT_MS = 3e4;
|
|
6599
6921
|
var LOGIN_TIMEOUT_MS = 10 * 6e4;
|
|
6600
6922
|
var URL_RE = /https:\/\/\S*\/oauth\/authorize\S*/;
|
|
@@ -6606,7 +6928,7 @@ var IDLE = {
|
|
|
6606
6928
|
output: "",
|
|
6607
6929
|
error: null
|
|
6608
6930
|
};
|
|
6609
|
-
var AuthCli = class extends
|
|
6931
|
+
var AuthCli = class extends EventEmitter4 {
|
|
6610
6932
|
#child = null;
|
|
6611
6933
|
#state = IDLE;
|
|
6612
6934
|
#timer = null;
|
|
@@ -6791,7 +7113,7 @@ function describe5(error) {
|
|
|
6791
7113
|
}
|
|
6792
7114
|
|
|
6793
7115
|
// ../host/dist/relay-link.js
|
|
6794
|
-
import { EventEmitter as
|
|
7116
|
+
import { EventEmitter as EventEmitter5 } from "node:events";
|
|
6795
7117
|
import { readFile as readFile15, writeFile as writeFile13, rm as rm5, mkdir as mkdir12 } from "node:fs/promises";
|
|
6796
7118
|
import path20 from "node:path";
|
|
6797
7119
|
import { hostname } from "node:os";
|
|
@@ -6810,7 +7132,7 @@ var SILENCE_TIMEOUT_MS = 95e3;
|
|
|
6810
7132
|
function jitter(delay2) {
|
|
6811
7133
|
return Math.round(delay2 * (0.8 + Math.random() * 0.4));
|
|
6812
7134
|
}
|
|
6813
|
-
var RelayLink = class extends
|
|
7135
|
+
var RelayLink = class extends EventEmitter5 {
|
|
6814
7136
|
#credentials = null;
|
|
6815
7137
|
#socket = null;
|
|
6816
7138
|
#retry = RETRY_MIN_MS;
|
|
@@ -7198,14 +7520,14 @@ function humanize(error) {
|
|
|
7198
7520
|
}
|
|
7199
7521
|
|
|
7200
7522
|
// ../host/dist/terminal.js
|
|
7201
|
-
import { EventEmitter as
|
|
7523
|
+
import { EventEmitter as EventEmitter6 } from "node:events";
|
|
7202
7524
|
import { access as access2, constants as constants2 } from "node:fs/promises";
|
|
7203
7525
|
import path21 from "node:path";
|
|
7204
7526
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
7205
7527
|
var SCROLLBACK_BYTES = 256 * 1024;
|
|
7206
7528
|
var FLUSH_MS = 12;
|
|
7207
7529
|
var MAX_TERMINALS = 24;
|
|
7208
|
-
var TerminalHub = class extends
|
|
7530
|
+
var TerminalHub = class extends EventEmitter6 {
|
|
7209
7531
|
#terminals = /* @__PURE__ */ new Map();
|
|
7210
7532
|
#module = null;
|
|
7211
7533
|
list(workspaceId) {
|
|
@@ -7506,6 +7828,8 @@ var FINDER_TITLE = "\xB7 \u043F\u043E\u0438\u0441\u043A \u043F\u0440\u043E\u0435
|
|
|
7506
7828
|
var HISTORY_PAGE = 250;
|
|
7507
7829
|
var HISTORY_PAGE_MAX = 500;
|
|
7508
7830
|
var AUTO_STOP_SWEEP_MS = 30 * 1e3;
|
|
7831
|
+
var MCP_AUTH_POLL_MS = 2 * 1e3;
|
|
7832
|
+
var MCP_AUTH_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
7509
7833
|
var BUSY_STATES2 = /* @__PURE__ */ new Set([
|
|
7510
7834
|
"running",
|
|
7511
7835
|
"waiting_permission",
|
|
@@ -7521,6 +7845,8 @@ var HostServer = class {
|
|
|
7521
7845
|
#settings;
|
|
7522
7846
|
#finder;
|
|
7523
7847
|
#sessions = /* @__PURE__ */ new Map();
|
|
7848
|
+
/** Опросы статуса для идущих OAuth-входов MCP, ключ — сессия и сервер. */
|
|
7849
|
+
#mcpAuthWatch = /* @__PURE__ */ new Map();
|
|
7524
7850
|
#antigravity;
|
|
7525
7851
|
/** Боковые сессии («вопрос на полях») по id родителя — не больше одной на разговор. */
|
|
7526
7852
|
#sides = /* @__PURE__ */ new Map();
|
|
@@ -7635,6 +7961,7 @@ var HostServer = class {
|
|
|
7635
7961
|
await this.#folders.load();
|
|
7636
7962
|
await this.#carryOverWorkspaceIds();
|
|
7637
7963
|
await this.#settings.load();
|
|
7964
|
+
this.#wireNetworkGuard();
|
|
7638
7965
|
await this.#limitStop.load();
|
|
7639
7966
|
await this.#github.load();
|
|
7640
7967
|
await warmClaudeLauncher();
|
|
@@ -7663,7 +7990,25 @@ var HostServer = class {
|
|
|
7663
7990
|
this.#limitTimer = setInterval(() => this.#sweepUsage(), LIMIT_SWEEP_MS);
|
|
7664
7991
|
this.#limitTimer.unref();
|
|
7665
7992
|
}
|
|
7993
|
+
/**
|
|
7994
|
+
* Защита от запросов без VPN (см. network-guard.ts): при блоке гасим
|
|
7995
|
+
* процессы всех живых сессий — идущий ход шлёт запрос за запросом, и
|
|
7996
|
+
* дожидаться его конца нельзя.
|
|
7997
|
+
*/
|
|
7998
|
+
#wireNetworkGuard() {
|
|
7999
|
+
networkGuard.on("change", (state) => {
|
|
8000
|
+
this.#broadcast({ type: "network.state", state });
|
|
8001
|
+
if (state.status !== "blocked")
|
|
8002
|
+
return;
|
|
8003
|
+
const reason = state.reason ?? "VPN \u043D\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442";
|
|
8004
|
+
for (const session of this.#sessions.values())
|
|
8005
|
+
session.suspend(reason);
|
|
8006
|
+
});
|
|
8007
|
+
const { networkGuardEnabled, networkGuardBlockedCountries } = this.#settings.current;
|
|
8008
|
+
networkGuard.configure(networkGuardEnabled, networkGuardBlockedCountries);
|
|
8009
|
+
}
|
|
7666
8010
|
async shutdown() {
|
|
8011
|
+
networkGuard.stop();
|
|
7667
8012
|
if (this.#autoStopTimer)
|
|
7668
8013
|
clearInterval(this.#autoStopTimer);
|
|
7669
8014
|
this.#autoStopTimer = null;
|
|
@@ -7831,6 +8176,7 @@ var HostServer = class {
|
|
|
7831
8176
|
this.#sendLive(socket);
|
|
7832
8177
|
this.#auth.status().then((status) => this.#send(socket, { type: "auth.status", status })).catch(() => void 0);
|
|
7833
8178
|
this.#send(socket, { type: "relay.state", state: this.#relay.state });
|
|
8179
|
+
this.#send(socket, { type: "network.state", state: networkGuard.state });
|
|
7834
8180
|
return;
|
|
7835
8181
|
case "ping":
|
|
7836
8182
|
this.#send(socket, { type: "pong", nonce: frame.nonce });
|
|
@@ -7876,6 +8222,7 @@ var HostServer = class {
|
|
|
7876
8222
|
return;
|
|
7877
8223
|
}
|
|
7878
8224
|
case "workspace.find.agent":
|
|
8225
|
+
await networkGuard.ensureFresh();
|
|
7879
8226
|
this.#findWithAgent(frame.query);
|
|
7880
8227
|
return;
|
|
7881
8228
|
case "workspace.roots":
|
|
@@ -8090,10 +8437,12 @@ var HostServer = class {
|
|
|
8090
8437
|
return;
|
|
8091
8438
|
}
|
|
8092
8439
|
case "prompt.send":
|
|
8440
|
+
await networkGuard.ensureFresh();
|
|
8093
8441
|
this.#approveAfterLimit(frame.sessionId);
|
|
8094
8442
|
this.#requireSession(frame.sessionId).sendPrompt(frame.text, frame.images);
|
|
8095
8443
|
return;
|
|
8096
8444
|
case "prompt.edit":
|
|
8445
|
+
await networkGuard.ensureFresh();
|
|
8097
8446
|
this.#approveAfterLimit(frame.sessionId);
|
|
8098
8447
|
await this.#requireSession(frame.sessionId).editPrompt(frame.userIndex, frame.text, frame.images);
|
|
8099
8448
|
return;
|
|
@@ -8101,6 +8450,7 @@ var HostServer = class {
|
|
|
8101
8450
|
this.#requireSession(frame.sessionId).respondPermission(frame.requestId, frame.decision, frame.reason, frame.answers, frame.annotations, frame.plan);
|
|
8102
8451
|
return;
|
|
8103
8452
|
case "side.ask":
|
|
8453
|
+
await networkGuard.ensureFresh();
|
|
8104
8454
|
this.#ensureSide(this.#requireSession(frame.sessionId)).sendPrompt(frame.text);
|
|
8105
8455
|
return;
|
|
8106
8456
|
// Поднять форк, ничего не спрашивая: клиенту нужен его id, чтобы
|
|
@@ -8182,6 +8532,56 @@ var HostServer = class {
|
|
|
8182
8532
|
await this.#sendMcpList(frame.sessionId);
|
|
8183
8533
|
return;
|
|
8184
8534
|
}
|
|
8535
|
+
case "mcp.auth": {
|
|
8536
|
+
const session = this.#requireSession(frame.sessionId);
|
|
8537
|
+
const { name } = frame;
|
|
8538
|
+
const base = { name, url: "", callbackExpected: false, done: false };
|
|
8539
|
+
try {
|
|
8540
|
+
const start = await session.authenticateMcpServer(name);
|
|
8541
|
+
this.#broadcast({
|
|
8542
|
+
type: "mcp.auth",
|
|
8543
|
+
sessionId: frame.sessionId,
|
|
8544
|
+
state: {
|
|
8545
|
+
...base,
|
|
8546
|
+
active: true,
|
|
8547
|
+
url: start.requiresUserAction ? start.url : "",
|
|
8548
|
+
callbackExpected: start.callbackExpected,
|
|
8549
|
+
error: null
|
|
8550
|
+
}
|
|
8551
|
+
});
|
|
8552
|
+
this.#watchMcpAuth(frame.sessionId, name);
|
|
8553
|
+
} catch (error) {
|
|
8554
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8555
|
+
this.#broadcast({
|
|
8556
|
+
type: "mcp.auth",
|
|
8557
|
+
sessionId: frame.sessionId,
|
|
8558
|
+
state: { ...base, active: false, error: message }
|
|
8559
|
+
});
|
|
8560
|
+
this.#reportOperation(frame.sessionId, "mcp", false, message);
|
|
8561
|
+
}
|
|
8562
|
+
return;
|
|
8563
|
+
}
|
|
8564
|
+
case "mcp.auth_callback": {
|
|
8565
|
+
const session = this.#requireSession(frame.sessionId);
|
|
8566
|
+
try {
|
|
8567
|
+
await session.submitMcpOAuthCallback(frame.name, frame.callbackUrl.trim());
|
|
8568
|
+
} catch (error) {
|
|
8569
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8570
|
+
this.#broadcast({
|
|
8571
|
+
type: "mcp.auth",
|
|
8572
|
+
sessionId: frame.sessionId,
|
|
8573
|
+
state: {
|
|
8574
|
+
name: frame.name,
|
|
8575
|
+
active: true,
|
|
8576
|
+
url: "",
|
|
8577
|
+
callbackExpected: true,
|
|
8578
|
+
done: false,
|
|
8579
|
+
error: message
|
|
8580
|
+
}
|
|
8581
|
+
});
|
|
8582
|
+
}
|
|
8583
|
+
return;
|
|
8584
|
+
}
|
|
8185
8585
|
case "plugin.list":
|
|
8186
8586
|
await this.#sendPluginList(frame.sessionId);
|
|
8187
8587
|
return;
|
|
@@ -8465,6 +8865,7 @@ var HostServer = class {
|
|
|
8465
8865
|
await this.#sendSessionList(workspace.id);
|
|
8466
8866
|
}
|
|
8467
8867
|
}
|
|
8868
|
+
networkGuard.configure(settings.networkGuardEnabled, settings.networkGuardBlockedCountries);
|
|
8468
8869
|
if (before.terminalEnabled && !settings.terminalEnabled) {
|
|
8469
8870
|
for (const terminal of this.#terminals.list()) {
|
|
8470
8871
|
this.#terminals.close(terminal.id);
|
|
@@ -8477,9 +8878,13 @@ var HostServer = class {
|
|
|
8477
8878
|
this.#send(socket, { type: "auth.status", status: await this.#auth.status() });
|
|
8478
8879
|
this.#send(socket, { type: "auth.login", state: this.#auth.loginState });
|
|
8479
8880
|
return;
|
|
8480
|
-
case "auth.login":
|
|
8881
|
+
case "auth.login": {
|
|
8882
|
+
const blocked = (await networkGuard.ensureFresh()).status === "blocked";
|
|
8883
|
+
if (blocked)
|
|
8884
|
+
throw new Error(`${networkGuard.blockedReason()}. \u0412\u0445\u043E\u0434 \u043E\u0442\u043B\u043E\u0436\u0435\u043D.`);
|
|
8481
8885
|
await this.#auth.login(frame.mode, frame.switchAccount === true);
|
|
8482
8886
|
return;
|
|
8887
|
+
}
|
|
8483
8888
|
case "auth.code":
|
|
8484
8889
|
this.#auth.submitCode(frame.code);
|
|
8485
8890
|
return;
|
|
@@ -8629,6 +9034,66 @@ var HostServer = class {
|
|
|
8629
9034
|
this.#reportOperation(sessionId, "plugin", ok, message);
|
|
8630
9035
|
await this.#sendPluginList(sessionId);
|
|
8631
9036
|
}
|
|
9037
|
+
/**
|
|
9038
|
+
* Дождаться, пока сервер выйдет из `needs-auth`.
|
|
9039
|
+
*
|
|
9040
|
+
* О конце входа CLI не сообщает ничем: после колбэка он молча меняет токены
|
|
9041
|
+
* и переподключает сервер у себя. Остаётся спрашивать статус, пока не
|
|
9042
|
+
* изменится, — недолго, потому что вход человек либо проходит за минуты,
|
|
9043
|
+
* либо бросает.
|
|
9044
|
+
*/
|
|
9045
|
+
#watchMcpAuth(sessionId, name) {
|
|
9046
|
+
const key = `${sessionId}/${name}`;
|
|
9047
|
+
clearInterval(this.#mcpAuthWatch.get(key));
|
|
9048
|
+
const startedAt = Date.now();
|
|
9049
|
+
const finish = () => {
|
|
9050
|
+
clearInterval(this.#mcpAuthWatch.get(key));
|
|
9051
|
+
this.#mcpAuthWatch.delete(key);
|
|
9052
|
+
};
|
|
9053
|
+
const timer = setInterval(() => {
|
|
9054
|
+
void (async () => {
|
|
9055
|
+
const session = this.#sessions.get(sessionId);
|
|
9056
|
+
if (!session)
|
|
9057
|
+
return finish();
|
|
9058
|
+
if (Date.now() - startedAt > MCP_AUTH_TIMEOUT_MS) {
|
|
9059
|
+
finish();
|
|
9060
|
+
this.#broadcast({
|
|
9061
|
+
type: "mcp.auth",
|
|
9062
|
+
sessionId,
|
|
9063
|
+
state: {
|
|
9064
|
+
name,
|
|
9065
|
+
active: false,
|
|
9066
|
+
url: "",
|
|
9067
|
+
callbackExpected: false,
|
|
9068
|
+
done: false,
|
|
9069
|
+
error: "\u0412\u0445\u043E\u0434 \u043D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D \u2014 \u0438\u0441\u0442\u0435\u043A\u043B\u043E \u0432\u0440\u0435\u043C\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F"
|
|
9070
|
+
}
|
|
9071
|
+
});
|
|
9072
|
+
return;
|
|
9073
|
+
}
|
|
9074
|
+
const status = (await session.mcpServerStatus()).find((s) => s.name === name)?.status;
|
|
9075
|
+
if (status === "needs-auth" || status === "pending")
|
|
9076
|
+
return;
|
|
9077
|
+
finish();
|
|
9078
|
+
const ok = status === "connected";
|
|
9079
|
+
this.#broadcast({
|
|
9080
|
+
type: "mcp.auth",
|
|
9081
|
+
sessionId,
|
|
9082
|
+
state: {
|
|
9083
|
+
name,
|
|
9084
|
+
active: false,
|
|
9085
|
+
url: "",
|
|
9086
|
+
callbackExpected: false,
|
|
9087
|
+
done: ok,
|
|
9088
|
+
error: ok ? null : `\u0421\u0435\u0440\u0432\u0435\u0440 \xAB${name}\xBB \u043F\u043E\u0441\u043B\u0435 \u0432\u0445\u043E\u0434\u0430 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 ${status ?? "\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E"}`
|
|
9089
|
+
}
|
|
9090
|
+
});
|
|
9091
|
+
this.#reportOperation(sessionId, "mcp", ok, ok ? `\u0421\u0435\u0440\u0432\u0435\u0440 \xAB${name}\xBB \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u043D \u0438 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D` : `\u0421\u0435\u0440\u0432\u0435\u0440 \xAB${name}\xBB \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u043B\u0441\u044F \u043F\u043E\u0441\u043B\u0435 \u0432\u0445\u043E\u0434\u0430`);
|
|
9092
|
+
await this.#sendMcpList(sessionId);
|
|
9093
|
+
})().catch(() => finish());
|
|
9094
|
+
}, MCP_AUTH_POLL_MS);
|
|
9095
|
+
this.#mcpAuthWatch.set(key, timer);
|
|
9096
|
+
}
|
|
8632
9097
|
#reportOperation(sessionId, scope, ok, message) {
|
|
8633
9098
|
this.#broadcast({ type: "operation.result", sessionId, result: { scope, ok, message } });
|
|
8634
9099
|
}
|