@i14ks/ccv 0.20.0 → 0.22.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 +257 -20
- package/package.json +1 -1
- package/web/assets/{codemirror-DOvy1hit.js → codemirror-BVRIW86g.js} +1 -1
- package/web/assets/{index-DTjRu-SE.js → index-5z-WKiE2.js} +1 -1
- package/web/assets/{index-0mOmG1OB.js → index-A6KnUhy4.js} +1 -1
- package/web/assets/{index-C37WshJ1.js → index-B54sB01B.js} +1 -1
- package/web/assets/{index-C4uljLPL.js → index-B5EnmuL9.js} +1 -1
- package/web/assets/{index-CIbEaXb2.js → index-BOTbHjsh.js} +1 -1
- package/web/assets/{index-zjOh8oJE.js → index-BRDMJVs_.js} +1 -1
- package/web/assets/{index-ChjkRA1v.js → index-BSbB9pNN.js} +1 -1
- package/web/assets/{index-Bf9yoJhl.js → index-BTHa3RWC.js} +1 -1
- package/web/assets/{index-R1dnxvPf.js → index-BUtKkXYx.js} +1 -1
- package/web/assets/{index-CB5sx8v_.js → index-C4RL1RRN.js} +1 -1
- package/web/assets/{index-BMWh-izi.js → index-CEtwcv2R.js} +1 -1
- package/web/assets/{index-C_10amyS.js → index-CHk-wT2e.js} +1 -1
- package/web/assets/{index-Dkiy3bA7.js → index-CftAsWDp.js} +1 -1
- package/web/assets/{index-C4IoqL6u.js → index-Cj3tIh2i.js} +1 -1
- package/web/assets/{index-DuclA_NV.js → index-CwbKEdO4.js} +1 -1
- package/web/assets/{index-x3nfLW_t.js → index-DcOycRyY.js} +1 -1
- package/web/assets/{index-BhAQ1Td-.js → index-DiFzORZa.js} +1 -1
- package/web/assets/{index-CsP-muVq.js → index-DqNX5AfU.js} +1 -1
- package/web/assets/index-L8M_w2e-.js +134 -0
- package/web/assets/{index-BUfDT07G.js → index-iBO43oLF.js} +1 -1
- package/web/assets/{index-Dv13T8ko.css → index-xoxa8R94.css} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-Bt29MBdY.js +0 -134
package/bundle/ccv.mjs
CHANGED
|
@@ -2154,6 +2154,41 @@ var ClaudeSession = class extends EventEmitter {
|
|
|
2154
2154
|
throw new Error("\u0421\u0435\u0441\u0441\u0438\u044F \u0435\u0449\u0451 \u043D\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u0430");
|
|
2155
2155
|
await this.#query.reconnectMcpServer(name);
|
|
2156
2156
|
}
|
|
2157
|
+
/**
|
|
2158
|
+
* Начать OAuth-вход сервера в `needs-auth`.
|
|
2159
|
+
*
|
|
2160
|
+
* `mcpAuthenticate` есть в `Query`, но не в `sdk.d.ts` — отсюда приведение.
|
|
2161
|
+
* CLI сам слушает колбэк на `localhost` и сам переподключает сервер после
|
|
2162
|
+
* входа; нам отдаёт только ссылку. Без `redirectUri` — тогда CLI выбирает
|
|
2163
|
+
* локальный колбэк, единственный, что работает без своего домена.
|
|
2164
|
+
*/
|
|
2165
|
+
async authenticateMcpServer(name) {
|
|
2166
|
+
if (!this.#query)
|
|
2167
|
+
throw new Error("\u0421\u0435\u0441\u0441\u0438\u044F \u0435\u0449\u0451 \u043D\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u0430");
|
|
2168
|
+
const q = this.#query;
|
|
2169
|
+
if (typeof q.mcpAuthenticate !== "function") {
|
|
2170
|
+
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");
|
|
2171
|
+
}
|
|
2172
|
+
const response = await q.mcpAuthenticate(name) ?? {};
|
|
2173
|
+
return {
|
|
2174
|
+
url: typeof response.authUrl === "string" ? response.authUrl : "",
|
|
2175
|
+
requiresUserAction: response.requiresUserAction === true,
|
|
2176
|
+
callbackExpected: response.callbackExpected === true
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
/**
|
|
2180
|
+
* Отдать CLI адрес редиректа, вставленный руками, — когда браузер не на этой
|
|
2181
|
+
* машине и до `localhost` хоста не достучался. Резолвится после входа.
|
|
2182
|
+
*/
|
|
2183
|
+
async submitMcpOAuthCallback(name, callbackUrl) {
|
|
2184
|
+
if (!this.#query)
|
|
2185
|
+
throw new Error("\u0421\u0435\u0441\u0441\u0438\u044F \u0435\u0449\u0451 \u043D\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u0430");
|
|
2186
|
+
const q = this.#query;
|
|
2187
|
+
if (typeof q.mcpSubmitOAuthCallbackUrl !== "function") {
|
|
2188
|
+
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");
|
|
2189
|
+
}
|
|
2190
|
+
await q.mcpSubmitOAuthCallbackUrl(name, callbackUrl);
|
|
2191
|
+
}
|
|
2157
2192
|
/** Включить или выключить сервер — тот же обход слэш-команды, что выше. */
|
|
2158
2193
|
async toggleMcpServer(name, enabled) {
|
|
2159
2194
|
if (!this.#query)
|
|
@@ -2376,6 +2411,7 @@ var ClaudeSession = class extends EventEmitter {
|
|
|
2376
2411
|
#handleMessage(message) {
|
|
2377
2412
|
if (this.#state === "idle" && (message.type === "assistant" || message.type === "stream_event") && message.parent_tool_use_id === null) {
|
|
2378
2413
|
this.#setState("running");
|
|
2414
|
+
this.emit("unpromptedTurn");
|
|
2379
2415
|
}
|
|
2380
2416
|
switch (message.type) {
|
|
2381
2417
|
case "system":
|
|
@@ -3646,6 +3682,12 @@ Attached image${paths.length > 1 ? "s" : ""} (view them with your file viewing t
|
|
|
3646
3682
|
async toggleMcpServer(_name, _enabled) {
|
|
3647
3683
|
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`)");
|
|
3648
3684
|
}
|
|
3685
|
+
async authenticateMcpServer(_name) {
|
|
3686
|
+
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`)");
|
|
3687
|
+
}
|
|
3688
|
+
async submitMcpOAuthCallback(_name, _callbackUrl) {
|
|
3689
|
+
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`)");
|
|
3690
|
+
}
|
|
3649
3691
|
setTitle(title) {
|
|
3650
3692
|
this.#record.title = title;
|
|
3651
3693
|
this.#titleIsDefault = false;
|
|
@@ -6334,6 +6376,10 @@ var LimitStopState = class {
|
|
|
6334
6376
|
return false;
|
|
6335
6377
|
return resets === null || Math.abs(resets - until) <= SAME_WINDOW_MS;
|
|
6336
6378
|
}
|
|
6379
|
+
/** До какого момента окно `limit` отмечено сработавшим; 0 — не срабатывало. */
|
|
6380
|
+
until(limit) {
|
|
6381
|
+
return this.#fired[limit] ?? 0;
|
|
6382
|
+
}
|
|
6337
6383
|
/** Запомнить срабатывание до `until` (момент сброса окна). */
|
|
6338
6384
|
async mark(limit, until) {
|
|
6339
6385
|
this.#fired[limit] = until;
|
|
@@ -7501,6 +7547,8 @@ var FINDER_TITLE = "\xB7 \u043F\u043E\u0438\u0441\u043A \u043F\u0440\u043E\u0435
|
|
|
7501
7547
|
var HISTORY_PAGE = 250;
|
|
7502
7548
|
var HISTORY_PAGE_MAX = 500;
|
|
7503
7549
|
var AUTO_STOP_SWEEP_MS = 30 * 1e3;
|
|
7550
|
+
var MCP_AUTH_POLL_MS = 2 * 1e3;
|
|
7551
|
+
var MCP_AUTH_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
7504
7552
|
var BUSY_STATES2 = /* @__PURE__ */ new Set([
|
|
7505
7553
|
"running",
|
|
7506
7554
|
"waiting_permission",
|
|
@@ -7516,6 +7564,8 @@ var HostServer = class {
|
|
|
7516
7564
|
#settings;
|
|
7517
7565
|
#finder;
|
|
7518
7566
|
#sessions = /* @__PURE__ */ new Map();
|
|
7567
|
+
/** Опросы статуса для идущих OAuth-входов MCP, ключ — сессия и сервер. */
|
|
7568
|
+
#mcpAuthWatch = /* @__PURE__ */ new Map();
|
|
7519
7569
|
#antigravity;
|
|
7520
7570
|
/** Боковые сессии («вопрос на полях») по id родителя — не больше одной на разговор. */
|
|
7521
7571
|
#sides = /* @__PURE__ */ new Map();
|
|
@@ -7551,6 +7601,17 @@ var HostServer = class {
|
|
|
7551
7601
|
#limitTimer = null;
|
|
7552
7602
|
/** Идёт остановка у лимита — второй снимок лимитов её не запускает. */
|
|
7553
7603
|
#limitStopping = false;
|
|
7604
|
+
/** Последний снимок лимитов Claude — им сверяется ход без промпта. */
|
|
7605
|
+
#lastUsage = null;
|
|
7606
|
+
/**
|
|
7607
|
+
* Сессии, которым человек сам написал после срабатывания порога: id
|
|
7608
|
+
* сессии → окно → срок, до которого оно было отмечено сработавшим. Это его
|
|
7609
|
+
* решение продолжать, и ход без промпта такую сессию до сброса того же окна
|
|
7610
|
+
* не останавливает. В памяти: живые сессии рестарт хоста не переживают.
|
|
7611
|
+
*/
|
|
7612
|
+
#limitApproved = /* @__PURE__ */ new Map();
|
|
7613
|
+
/** Живые id сессий, у которых сейчас пишется хэндофф у лимита. */
|
|
7614
|
+
#limitHandoffBusy = /* @__PURE__ */ new Set();
|
|
7554
7615
|
/** Живые id сессий, у которых сейчас пишется ручной хэндофф. */
|
|
7555
7616
|
#handoffBusy = /* @__PURE__ */ new Set();
|
|
7556
7617
|
#hostId = randomUUID5();
|
|
@@ -8074,9 +8135,11 @@ var HostServer = class {
|
|
|
8074
8135
|
return;
|
|
8075
8136
|
}
|
|
8076
8137
|
case "prompt.send":
|
|
8138
|
+
this.#approveAfterLimit(frame.sessionId);
|
|
8077
8139
|
this.#requireSession(frame.sessionId).sendPrompt(frame.text, frame.images);
|
|
8078
8140
|
return;
|
|
8079
8141
|
case "prompt.edit":
|
|
8142
|
+
this.#approveAfterLimit(frame.sessionId);
|
|
8080
8143
|
await this.#requireSession(frame.sessionId).editPrompt(frame.userIndex, frame.text, frame.images);
|
|
8081
8144
|
return;
|
|
8082
8145
|
case "permission.respond":
|
|
@@ -8164,6 +8227,56 @@ var HostServer = class {
|
|
|
8164
8227
|
await this.#sendMcpList(frame.sessionId);
|
|
8165
8228
|
return;
|
|
8166
8229
|
}
|
|
8230
|
+
case "mcp.auth": {
|
|
8231
|
+
const session = this.#requireSession(frame.sessionId);
|
|
8232
|
+
const { name } = frame;
|
|
8233
|
+
const base = { name, url: "", callbackExpected: false, done: false };
|
|
8234
|
+
try {
|
|
8235
|
+
const start = await session.authenticateMcpServer(name);
|
|
8236
|
+
this.#broadcast({
|
|
8237
|
+
type: "mcp.auth",
|
|
8238
|
+
sessionId: frame.sessionId,
|
|
8239
|
+
state: {
|
|
8240
|
+
...base,
|
|
8241
|
+
active: true,
|
|
8242
|
+
url: start.requiresUserAction ? start.url : "",
|
|
8243
|
+
callbackExpected: start.callbackExpected,
|
|
8244
|
+
error: null
|
|
8245
|
+
}
|
|
8246
|
+
});
|
|
8247
|
+
this.#watchMcpAuth(frame.sessionId, name);
|
|
8248
|
+
} catch (error) {
|
|
8249
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8250
|
+
this.#broadcast({
|
|
8251
|
+
type: "mcp.auth",
|
|
8252
|
+
sessionId: frame.sessionId,
|
|
8253
|
+
state: { ...base, active: false, error: message }
|
|
8254
|
+
});
|
|
8255
|
+
this.#reportOperation(frame.sessionId, "mcp", false, message);
|
|
8256
|
+
}
|
|
8257
|
+
return;
|
|
8258
|
+
}
|
|
8259
|
+
case "mcp.auth_callback": {
|
|
8260
|
+
const session = this.#requireSession(frame.sessionId);
|
|
8261
|
+
try {
|
|
8262
|
+
await session.submitMcpOAuthCallback(frame.name, frame.callbackUrl.trim());
|
|
8263
|
+
} catch (error) {
|
|
8264
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8265
|
+
this.#broadcast({
|
|
8266
|
+
type: "mcp.auth",
|
|
8267
|
+
sessionId: frame.sessionId,
|
|
8268
|
+
state: {
|
|
8269
|
+
name: frame.name,
|
|
8270
|
+
active: true,
|
|
8271
|
+
url: "",
|
|
8272
|
+
callbackExpected: true,
|
|
8273
|
+
done: false,
|
|
8274
|
+
error: message
|
|
8275
|
+
}
|
|
8276
|
+
});
|
|
8277
|
+
}
|
|
8278
|
+
return;
|
|
8279
|
+
}
|
|
8167
8280
|
case "plugin.list":
|
|
8168
8281
|
await this.#sendPluginList(frame.sessionId);
|
|
8169
8282
|
return;
|
|
@@ -8611,6 +8724,66 @@ var HostServer = class {
|
|
|
8611
8724
|
this.#reportOperation(sessionId, "plugin", ok, message);
|
|
8612
8725
|
await this.#sendPluginList(sessionId);
|
|
8613
8726
|
}
|
|
8727
|
+
/**
|
|
8728
|
+
* Дождаться, пока сервер выйдет из `needs-auth`.
|
|
8729
|
+
*
|
|
8730
|
+
* О конце входа CLI не сообщает ничем: после колбэка он молча меняет токены
|
|
8731
|
+
* и переподключает сервер у себя. Остаётся спрашивать статус, пока не
|
|
8732
|
+
* изменится, — недолго, потому что вход человек либо проходит за минуты,
|
|
8733
|
+
* либо бросает.
|
|
8734
|
+
*/
|
|
8735
|
+
#watchMcpAuth(sessionId, name) {
|
|
8736
|
+
const key = `${sessionId}/${name}`;
|
|
8737
|
+
clearInterval(this.#mcpAuthWatch.get(key));
|
|
8738
|
+
const startedAt = Date.now();
|
|
8739
|
+
const finish = () => {
|
|
8740
|
+
clearInterval(this.#mcpAuthWatch.get(key));
|
|
8741
|
+
this.#mcpAuthWatch.delete(key);
|
|
8742
|
+
};
|
|
8743
|
+
const timer = setInterval(() => {
|
|
8744
|
+
void (async () => {
|
|
8745
|
+
const session = this.#sessions.get(sessionId);
|
|
8746
|
+
if (!session)
|
|
8747
|
+
return finish();
|
|
8748
|
+
if (Date.now() - startedAt > MCP_AUTH_TIMEOUT_MS) {
|
|
8749
|
+
finish();
|
|
8750
|
+
this.#broadcast({
|
|
8751
|
+
type: "mcp.auth",
|
|
8752
|
+
sessionId,
|
|
8753
|
+
state: {
|
|
8754
|
+
name,
|
|
8755
|
+
active: false,
|
|
8756
|
+
url: "",
|
|
8757
|
+
callbackExpected: false,
|
|
8758
|
+
done: false,
|
|
8759
|
+
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"
|
|
8760
|
+
}
|
|
8761
|
+
});
|
|
8762
|
+
return;
|
|
8763
|
+
}
|
|
8764
|
+
const status = (await session.mcpServerStatus()).find((s) => s.name === name)?.status;
|
|
8765
|
+
if (status === "needs-auth" || status === "pending")
|
|
8766
|
+
return;
|
|
8767
|
+
finish();
|
|
8768
|
+
const ok = status === "connected";
|
|
8769
|
+
this.#broadcast({
|
|
8770
|
+
type: "mcp.auth",
|
|
8771
|
+
sessionId,
|
|
8772
|
+
state: {
|
|
8773
|
+
name,
|
|
8774
|
+
active: false,
|
|
8775
|
+
url: "",
|
|
8776
|
+
callbackExpected: false,
|
|
8777
|
+
done: ok,
|
|
8778
|
+
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"}`
|
|
8779
|
+
}
|
|
8780
|
+
});
|
|
8781
|
+
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`);
|
|
8782
|
+
await this.#sendMcpList(sessionId);
|
|
8783
|
+
})().catch(() => finish());
|
|
8784
|
+
}, MCP_AUTH_POLL_MS);
|
|
8785
|
+
this.#mcpAuthWatch.set(key, timer);
|
|
8786
|
+
}
|
|
8614
8787
|
#reportOperation(sessionId, scope, ok, message) {
|
|
8615
8788
|
this.#broadcast({ type: "operation.result", sessionId, result: { scope, ok, message } });
|
|
8616
8789
|
}
|
|
@@ -8637,6 +8810,7 @@ var HostServer = class {
|
|
|
8637
8810
|
await this.#closeSide(sessionId);
|
|
8638
8811
|
await session.close(reason);
|
|
8639
8812
|
this.#sessions.delete(sessionId);
|
|
8813
|
+
this.#limitApproved.delete(sessionId);
|
|
8640
8814
|
this.#broadcast({ type: "session.closed", sessionId, reason });
|
|
8641
8815
|
this.#sendLive();
|
|
8642
8816
|
return true;
|
|
@@ -8722,6 +8896,7 @@ var HostServer = class {
|
|
|
8722
8896
|
void busy.refreshUsage();
|
|
8723
8897
|
}
|
|
8724
8898
|
async #checkLimit(usage) {
|
|
8899
|
+
this.#lastUsage = usage;
|
|
8725
8900
|
if (this.#limitStopping)
|
|
8726
8901
|
return;
|
|
8727
8902
|
const hit = crossedLimit(usage, this.#settings.current, (limit, resets) => this.#limitStop.fired(limit, resets));
|
|
@@ -8737,6 +8912,57 @@ var HostServer = class {
|
|
|
8737
8912
|
this.#limitStopping = false;
|
|
8738
8913
|
}
|
|
8739
8914
|
}
|
|
8915
|
+
/**
|
|
8916
|
+
* Человек сам пишет в сессию — после сработавшего порога это его решение
|
|
8917
|
+
* продолжать (окно перед отправкой его спросило, см. `limitWarning` в
|
|
8918
|
+
* вебе). Запоминаем, до какого срока сработало каждое окно: подтверждение
|
|
8919
|
+
* действует только до его сброса, новое окно спросит заново.
|
|
8920
|
+
*/
|
|
8921
|
+
#approveAfterLimit(sessionId) {
|
|
8922
|
+
const now = Date.now();
|
|
8923
|
+
const approved = {};
|
|
8924
|
+
for (const limit of ["fiveHour", "sevenDay"]) {
|
|
8925
|
+
const until = this.#limitStop.until(limit);
|
|
8926
|
+
if (until > now)
|
|
8927
|
+
approved[limit] = until;
|
|
8928
|
+
}
|
|
8929
|
+
if (Object.keys(approved).length > 0) {
|
|
8930
|
+
this.#limitApproved.set(sessionId, { ...this.#limitApproved.get(sessionId), ...approved });
|
|
8931
|
+
}
|
|
8932
|
+
}
|
|
8933
|
+
/**
|
|
8934
|
+
* Сессия начала ход сама, без промпта: её разбудило уведомление фоновой
|
|
8935
|
+
* задачи, таймер или ответ фонового субагента. Порог в окне уже сработал —
|
|
8936
|
+
* сам он до сброса больше не проверяется, и такой ход дожёг бы окно до
|
|
8937
|
+
* 100 % и оборвался без хэндоффа (15.10 в 19:32 так разбудило MIREA).
|
|
8938
|
+
* Останавливаем его, как остановили бы на пороге, — если только человек
|
|
8939
|
+
* сам не писал в эту сессию после срабатывания (`#approveAfterLimit`).
|
|
8940
|
+
*
|
|
8941
|
+
* Снимок — последний пришедший: в пределах окна процент только растёт, а
|
|
8942
|
+
* сброшенное окно `crossedLimit` отбросит по сроку. Окно, которое ещё не
|
|
8943
|
+
* срабатывало, здесь не трогаем: его поймает обычный `#checkLimit`.
|
|
8944
|
+
*/
|
|
8945
|
+
async #checkUnpromptedTurn(session) {
|
|
8946
|
+
if (!this.#lastUsage || this.#limitStopping)
|
|
8947
|
+
return;
|
|
8948
|
+
if (this.#limitHandoffBusy.has(session.id) || this.#handoffBusy.has(session.id))
|
|
8949
|
+
return;
|
|
8950
|
+
if (!this.#claudeUserSessions().includes(session))
|
|
8951
|
+
return;
|
|
8952
|
+
const approved = this.#limitApproved.get(session.id) ?? {};
|
|
8953
|
+
const hit = crossedLimit(this.#lastUsage, this.#settings.current, (limit, resets) => !this.#limitStop.fired(limit, resets) || approved[limit] === this.#limitStop.until(limit));
|
|
8954
|
+
if (!hit)
|
|
8955
|
+
return;
|
|
8956
|
+
const saved = await this.#limitHandoff(session, hit);
|
|
8957
|
+
this.#broadcast({
|
|
8958
|
+
type: "limit.stopped",
|
|
8959
|
+
limit: hit.limit,
|
|
8960
|
+
utilization: hit.window.utilization,
|
|
8961
|
+
resetsAt: hit.window.resetsAt,
|
|
8962
|
+
stopped: 1,
|
|
8963
|
+
handoffs: saved ? 1 : 0
|
|
8964
|
+
});
|
|
8965
|
+
}
|
|
8740
8966
|
/**
|
|
8741
8967
|
* Прервать работу у лимита: занятые сессии пишут хэндофф (или он
|
|
8742
8968
|
* собирается из журнала, если агент не ответил), простаивающие не
|
|
@@ -8749,9 +8975,25 @@ var HostServer = class {
|
|
|
8749
8975
|
if (sessions.length === 0)
|
|
8750
8976
|
return;
|
|
8751
8977
|
const now = Date.now();
|
|
8752
|
-
const busy = sessions.filter((s) => this.#isBusy(s) || now - s.lastActiveAt < LIMIT_RECENT_MS);
|
|
8753
|
-
const
|
|
8754
|
-
|
|
8978
|
+
const busy = sessions.filter((s) => !this.#limitHandoffBusy.has(s.id) && (this.#isBusy(s) || now - s.lastActiveAt < LIMIT_RECENT_MS));
|
|
8979
|
+
const results = await Promise.all(busy.map((session) => this.#limitHandoff(session, hit)));
|
|
8980
|
+
this.#broadcast({
|
|
8981
|
+
type: "limit.stopped",
|
|
8982
|
+
limit: hit.limit,
|
|
8983
|
+
utilization: hit.window.utilization,
|
|
8984
|
+
resetsAt: hit.window.resetsAt,
|
|
8985
|
+
stopped: results.length,
|
|
8986
|
+
handoffs: results.filter(Boolean).length
|
|
8987
|
+
});
|
|
8988
|
+
}
|
|
8989
|
+
/**
|
|
8990
|
+
* Хэндофф одной сессии у лимита: ход хэндоффа (или журнал, если агент не
|
|
8991
|
+
* ответил), сохранение и рассылка. `true` — хэндофф сохранён.
|
|
8992
|
+
*/
|
|
8993
|
+
async #limitHandoff(session, hit) {
|
|
8994
|
+
this.#limitHandoffBusy.add(session.id);
|
|
8995
|
+
try {
|
|
8996
|
+
const prompt = hit.window.utilization >= 100 ? null : handoffPrompt(hit);
|
|
8755
8997
|
const text = await session.runHandoff(prompt, HANDOFF_TIMEOUT_MS);
|
|
8756
8998
|
const summary = session.summary();
|
|
8757
8999
|
const handoff = {
|
|
@@ -8765,23 +9007,15 @@ var HostServer = class {
|
|
|
8765
9007
|
source: text ? "agent" : "journal",
|
|
8766
9008
|
text: text ?? journalHandoff(session.journal.since(0))
|
|
8767
9009
|
};
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
}
|
|
8777
|
-
this.#broadcast({
|
|
8778
|
-
type: "limit.stopped",
|
|
8779
|
-
limit: hit.limit,
|
|
8780
|
-
utilization: hit.window.utilization,
|
|
8781
|
-
resetsAt: hit.window.resetsAt,
|
|
8782
|
-
stopped: results.length,
|
|
8783
|
-
handoffs: results.filter(Boolean).length
|
|
8784
|
-
});
|
|
9010
|
+
await this.#handoffs.save(handoff);
|
|
9011
|
+
this.#broadcast({ type: "handoff.state", sessionId: handoff.sessionId, handoff });
|
|
9012
|
+
return true;
|
|
9013
|
+
} catch (error) {
|
|
9014
|
+
console.error("[limit-stop]", error instanceof Error ? error.message : String(error));
|
|
9015
|
+
return false;
|
|
9016
|
+
} finally {
|
|
9017
|
+
this.#limitHandoffBusy.delete(session.id);
|
|
9018
|
+
}
|
|
8785
9019
|
}
|
|
8786
9020
|
/**
|
|
8787
9021
|
* «Хэндофф и новая сессия»: тот же ход хэндоффа, что у лимита, только по
|
|
@@ -9032,6 +9266,9 @@ var HostServer = class {
|
|
|
9032
9266
|
if (session instanceof ClaudeSession)
|
|
9033
9267
|
void this.#checkLimit(usage);
|
|
9034
9268
|
});
|
|
9269
|
+
if (session instanceof ClaudeSession) {
|
|
9270
|
+
session.on("unpromptedTurn", () => void this.#checkUnpromptedTurn(session));
|
|
9271
|
+
}
|
|
9035
9272
|
session.on("context", (context) => {
|
|
9036
9273
|
this.#broadcast({ type: "context.state", sessionId: session.id, context });
|
|
9037
9274
|
});
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{s as r,H as a}from"./index-
|
|
1
|
+
import{s as r,H as a}from"./index-BTHa3RWC.js";import{E as e}from"./index-891wE-Rj.js";import{t as o}from"./index-L8M_w2e-.js";import"./index-BHot-lnj.js";const m=e.theme({"&":{height:"100%",color:"var(--text)",backgroundColor:"var(--bg)",fontSize:"13px"},".cm-scroller":{fontFamily:"var(--mono)",lineHeight:"1.6"},".cm-content":{caretColor:"var(--accent)"},".cm-cursor, .cm-dropCursor":{borderLeftColor:"var(--accent)"},"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":{backgroundColor:"var(--cm-selection)"},".cm-gutters":{backgroundColor:"var(--bg)",color:"var(--muted)",borderRight:"1px solid var(--border-soft)"},".cm-activeLineGutter":{backgroundColor:"var(--surface-2)",color:"var(--text-2)"},".cm-activeLine":{backgroundColor:"var(--cm-active)"},".cm-selectionMatch":{backgroundColor:"var(--cm-match)"},"&.cm-focused .cm-matchingBracket":{backgroundColor:"var(--cm-match)",outline:"1px solid var(--accent-line)"},".cm-foldPlaceholder":{backgroundColor:"var(--surface-2)",border:"1px solid var(--border)",color:"var(--muted)"},".cm-panels":{backgroundColor:"var(--surface)",color:"var(--text)",border:"1px solid var(--border)"},".cm-panels input, .cm-panels button, .cm-panels select":{fontFamily:"var(--sans)",fontSize:"12px",background:"var(--surface-2)",color:"var(--text)",border:"1px solid var(--border)",borderRadius:"6px",padding:"3px 6px"},".cm-searchMatch":{backgroundColor:"var(--cm-match)"},".cm-searchMatch.cm-searchMatch-selected":{backgroundColor:"var(--accent-soft)"},".cm-tooltip":{background:"var(--surface)",border:"1px solid var(--border)",borderRadius:"8px",boxShadow:"var(--shadow-md)",color:"var(--text)"},".cm-tooltip-autocomplete ul li[aria-selected]":{background:"var(--surface-2)",color:"var(--text)"}}),i=r(a.define([{tag:[o.keyword,o.modifier,o.controlKeyword,o.moduleKeyword],color:"var(--cm-key)"},{tag:[o.name,o.deleted,o.character,o.macroName],color:"var(--text)"},{tag:[o.propertyName],color:"var(--cm-prop)"},{tag:[o.function(o.variableName),o.labelName],color:"var(--cm-fn)"},{tag:[o.color,o.constant(o.name),o.standard(o.name),o.bool],color:"var(--cm-const)"},{tag:[o.definition(o.name),o.separator],color:"var(--text)"},{tag:[o.typeName,o.className,o.changed,o.annotation,o.self,o.namespace],color:"var(--cm-type)"},{tag:[o.number,o.integer,o.float],color:"var(--cm-num)"},{tag:[o.string,o.special(o.string),o.regexp],color:"var(--cm-str)"},{tag:[o.operator,o.operatorKeyword,o.escape],color:"var(--cm-op)"},{tag:[o.meta,o.comment],color:"var(--cm-comment)",fontStyle:"italic"},{tag:o.strong,fontWeight:"650"},{tag:o.emphasis,fontStyle:"italic"},{tag:o.strikethrough,textDecoration:"line-through"},{tag:o.link,color:"var(--cm-str)",textDecoration:"underline"},{tag:o.heading,fontWeight:"650",color:"var(--cm-key)"},{tag:o.invalid,color:"var(--danger)"}]));export{i as editorHighlight,m as editorTheme};
|