@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.
Files changed (26) hide show
  1. package/bundle/ccv.mjs +257 -20
  2. package/package.json +1 -1
  3. package/web/assets/{codemirror-DOvy1hit.js → codemirror-BVRIW86g.js} +1 -1
  4. package/web/assets/{index-DTjRu-SE.js → index-5z-WKiE2.js} +1 -1
  5. package/web/assets/{index-0mOmG1OB.js → index-A6KnUhy4.js} +1 -1
  6. package/web/assets/{index-C37WshJ1.js → index-B54sB01B.js} +1 -1
  7. package/web/assets/{index-C4uljLPL.js → index-B5EnmuL9.js} +1 -1
  8. package/web/assets/{index-CIbEaXb2.js → index-BOTbHjsh.js} +1 -1
  9. package/web/assets/{index-zjOh8oJE.js → index-BRDMJVs_.js} +1 -1
  10. package/web/assets/{index-ChjkRA1v.js → index-BSbB9pNN.js} +1 -1
  11. package/web/assets/{index-Bf9yoJhl.js → index-BTHa3RWC.js} +1 -1
  12. package/web/assets/{index-R1dnxvPf.js → index-BUtKkXYx.js} +1 -1
  13. package/web/assets/{index-CB5sx8v_.js → index-C4RL1RRN.js} +1 -1
  14. package/web/assets/{index-BMWh-izi.js → index-CEtwcv2R.js} +1 -1
  15. package/web/assets/{index-C_10amyS.js → index-CHk-wT2e.js} +1 -1
  16. package/web/assets/{index-Dkiy3bA7.js → index-CftAsWDp.js} +1 -1
  17. package/web/assets/{index-C4IoqL6u.js → index-Cj3tIh2i.js} +1 -1
  18. package/web/assets/{index-DuclA_NV.js → index-CwbKEdO4.js} +1 -1
  19. package/web/assets/{index-x3nfLW_t.js → index-DcOycRyY.js} +1 -1
  20. package/web/assets/{index-BhAQ1Td-.js → index-DiFzORZa.js} +1 -1
  21. package/web/assets/{index-CsP-muVq.js → index-DqNX5AfU.js} +1 -1
  22. package/web/assets/index-L8M_w2e-.js +134 -0
  23. package/web/assets/{index-BUfDT07G.js → index-iBO43oLF.js} +1 -1
  24. package/web/assets/{index-Dv13T8ko.css → index-xoxa8R94.css} +1 -1
  25. package/web/index.html +2 -2
  26. 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 prompt = hit.window.utilization >= 100 ? null : handoffPrompt(hit);
8754
- const results = await Promise.all(busy.map(async (session) => {
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
- try {
8769
- await this.#handoffs.save(handoff);
8770
- this.#broadcast({ type: "handoff.state", sessionId: handoff.sessionId, handoff });
8771
- return true;
8772
- } catch (error) {
8773
- console.error("[limit-stop]", error instanceof Error ? error.message : String(error));
8774
- return false;
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@i14ks/ccv",
3
- "version": "0.20.0",
3
+ "version": "0.22.0",
4
4
  "license": "UNLICENSED",
5
5
  "description": "GUI для Claude Code: сессии агента в каталогах проектов, дерево файлов и редактор рядом с разговором.",
6
6
  "homepage": "https://claudevisual.com",
@@ -1 +1 @@
1
- import{s as r,H as a}from"./index-Bf9yoJhl.js";import{E as e}from"./index-891wE-Rj.js";import{t as o}from"./index-Bt29MBdY.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};
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};