@i14ks/ccv 0.20.0 → 0.21.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 (25) hide show
  1. package/bundle/ccv.mjs +102 -20
  2. package/package.json +1 -1
  3. package/web/assets/{codemirror-DOvy1hit.js → codemirror-B6QVSqxx.js} +1 -1
  4. package/web/assets/{index-0mOmG1OB.js → index-0l0v5ZjK.js} +1 -1
  5. package/web/assets/{index-ChjkRA1v.js → index-1VuadINO.js} +1 -1
  6. package/web/assets/index-3BSpF-Pn.js +134 -0
  7. package/web/assets/{index-zjOh8oJE.js → index-B8GLobgh.js} +1 -1
  8. package/web/assets/{index-BMWh-izi.js → index-B9qDsSjC.js} +1 -1
  9. package/web/assets/{index-C4uljLPL.js → index-BYERpa5Q.js} +1 -1
  10. package/web/assets/{index-C4IoqL6u.js → index-Bf6VCb0a.js} +1 -1
  11. package/web/assets/{index-x3nfLW_t.js → index-BlPPlrvg.js} +1 -1
  12. package/web/assets/{index-BhAQ1Td-.js → index-BqED4I4f.js} +1 -1
  13. package/web/assets/{index-Dkiy3bA7.js → index-BwuRWAfa.js} +1 -1
  14. package/web/assets/{index-CB5sx8v_.js → index-C5hGXrTC.js} +1 -1
  15. package/web/assets/{index-C37WshJ1.js → index-CCRhGn6u.js} +1 -1
  16. package/web/assets/{index-BUfDT07G.js → index-CTe-duo7.js} +1 -1
  17. package/web/assets/{index-DuclA_NV.js → index-Cgi4XmXk.js} +1 -1
  18. package/web/assets/{index-CIbEaXb2.js → index-CqM8bKzd.js} +1 -1
  19. package/web/assets/{index-DTjRu-SE.js → index-DdFPrUTi.js} +1 -1
  20. package/web/assets/{index-C_10amyS.js → index-DmwQZoSG.js} +1 -1
  21. package/web/assets/{index-Bf9yoJhl.js → index-Zo5hIeFa.js} +1 -1
  22. package/web/assets/{index-R1dnxvPf.js → index-oOWAdIqY.js} +1 -1
  23. package/web/assets/{index-CsP-muVq.js → index-rsg6TYOk.js} +1 -1
  24. package/web/index.html +1 -1
  25. package/web/assets/index-Bt29MBdY.js +0 -134
package/bundle/ccv.mjs CHANGED
@@ -2376,6 +2376,7 @@ var ClaudeSession = class extends EventEmitter {
2376
2376
  #handleMessage(message) {
2377
2377
  if (this.#state === "idle" && (message.type === "assistant" || message.type === "stream_event") && message.parent_tool_use_id === null) {
2378
2378
  this.#setState("running");
2379
+ this.emit("unpromptedTurn");
2379
2380
  }
2380
2381
  switch (message.type) {
2381
2382
  case "system":
@@ -6334,6 +6335,10 @@ var LimitStopState = class {
6334
6335
  return false;
6335
6336
  return resets === null || Math.abs(resets - until) <= SAME_WINDOW_MS;
6336
6337
  }
6338
+ /** До какого момента окно `limit` отмечено сработавшим; 0 — не срабатывало. */
6339
+ until(limit) {
6340
+ return this.#fired[limit] ?? 0;
6341
+ }
6337
6342
  /** Запомнить срабатывание до `until` (момент сброса окна). */
6338
6343
  async mark(limit, until) {
6339
6344
  this.#fired[limit] = until;
@@ -7551,6 +7556,17 @@ var HostServer = class {
7551
7556
  #limitTimer = null;
7552
7557
  /** Идёт остановка у лимита — второй снимок лимитов её не запускает. */
7553
7558
  #limitStopping = false;
7559
+ /** Последний снимок лимитов Claude — им сверяется ход без промпта. */
7560
+ #lastUsage = null;
7561
+ /**
7562
+ * Сессии, которым человек сам написал после срабатывания порога: id
7563
+ * сессии → окно → срок, до которого оно было отмечено сработавшим. Это его
7564
+ * решение продолжать, и ход без промпта такую сессию до сброса того же окна
7565
+ * не останавливает. В памяти: живые сессии рестарт хоста не переживают.
7566
+ */
7567
+ #limitApproved = /* @__PURE__ */ new Map();
7568
+ /** Живые id сессий, у которых сейчас пишется хэндофф у лимита. */
7569
+ #limitHandoffBusy = /* @__PURE__ */ new Set();
7554
7570
  /** Живые id сессий, у которых сейчас пишется ручной хэндофф. */
7555
7571
  #handoffBusy = /* @__PURE__ */ new Set();
7556
7572
  #hostId = randomUUID5();
@@ -8074,9 +8090,11 @@ var HostServer = class {
8074
8090
  return;
8075
8091
  }
8076
8092
  case "prompt.send":
8093
+ this.#approveAfterLimit(frame.sessionId);
8077
8094
  this.#requireSession(frame.sessionId).sendPrompt(frame.text, frame.images);
8078
8095
  return;
8079
8096
  case "prompt.edit":
8097
+ this.#approveAfterLimit(frame.sessionId);
8080
8098
  await this.#requireSession(frame.sessionId).editPrompt(frame.userIndex, frame.text, frame.images);
8081
8099
  return;
8082
8100
  case "permission.respond":
@@ -8637,6 +8655,7 @@ var HostServer = class {
8637
8655
  await this.#closeSide(sessionId);
8638
8656
  await session.close(reason);
8639
8657
  this.#sessions.delete(sessionId);
8658
+ this.#limitApproved.delete(sessionId);
8640
8659
  this.#broadcast({ type: "session.closed", sessionId, reason });
8641
8660
  this.#sendLive();
8642
8661
  return true;
@@ -8722,6 +8741,7 @@ var HostServer = class {
8722
8741
  void busy.refreshUsage();
8723
8742
  }
8724
8743
  async #checkLimit(usage) {
8744
+ this.#lastUsage = usage;
8725
8745
  if (this.#limitStopping)
8726
8746
  return;
8727
8747
  const hit = crossedLimit(usage, this.#settings.current, (limit, resets) => this.#limitStop.fired(limit, resets));
@@ -8737,6 +8757,57 @@ var HostServer = class {
8737
8757
  this.#limitStopping = false;
8738
8758
  }
8739
8759
  }
8760
+ /**
8761
+ * Человек сам пишет в сессию — после сработавшего порога это его решение
8762
+ * продолжать (окно перед отправкой его спросило, см. `limitWarning` в
8763
+ * вебе). Запоминаем, до какого срока сработало каждое окно: подтверждение
8764
+ * действует только до его сброса, новое окно спросит заново.
8765
+ */
8766
+ #approveAfterLimit(sessionId) {
8767
+ const now = Date.now();
8768
+ const approved = {};
8769
+ for (const limit of ["fiveHour", "sevenDay"]) {
8770
+ const until = this.#limitStop.until(limit);
8771
+ if (until > now)
8772
+ approved[limit] = until;
8773
+ }
8774
+ if (Object.keys(approved).length > 0) {
8775
+ this.#limitApproved.set(sessionId, { ...this.#limitApproved.get(sessionId), ...approved });
8776
+ }
8777
+ }
8778
+ /**
8779
+ * Сессия начала ход сама, без промпта: её разбудило уведомление фоновой
8780
+ * задачи, таймер или ответ фонового субагента. Порог в окне уже сработал —
8781
+ * сам он до сброса больше не проверяется, и такой ход дожёг бы окно до
8782
+ * 100 % и оборвался без хэндоффа (15.10 в 19:32 так разбудило MIREA).
8783
+ * Останавливаем его, как остановили бы на пороге, — если только человек
8784
+ * сам не писал в эту сессию после срабатывания (`#approveAfterLimit`).
8785
+ *
8786
+ * Снимок — последний пришедший: в пределах окна процент только растёт, а
8787
+ * сброшенное окно `crossedLimit` отбросит по сроку. Окно, которое ещё не
8788
+ * срабатывало, здесь не трогаем: его поймает обычный `#checkLimit`.
8789
+ */
8790
+ async #checkUnpromptedTurn(session) {
8791
+ if (!this.#lastUsage || this.#limitStopping)
8792
+ return;
8793
+ if (this.#limitHandoffBusy.has(session.id) || this.#handoffBusy.has(session.id))
8794
+ return;
8795
+ if (!this.#claudeUserSessions().includes(session))
8796
+ return;
8797
+ const approved = this.#limitApproved.get(session.id) ?? {};
8798
+ const hit = crossedLimit(this.#lastUsage, this.#settings.current, (limit, resets) => !this.#limitStop.fired(limit, resets) || approved[limit] === this.#limitStop.until(limit));
8799
+ if (!hit)
8800
+ return;
8801
+ const saved = await this.#limitHandoff(session, hit);
8802
+ this.#broadcast({
8803
+ type: "limit.stopped",
8804
+ limit: hit.limit,
8805
+ utilization: hit.window.utilization,
8806
+ resetsAt: hit.window.resetsAt,
8807
+ stopped: 1,
8808
+ handoffs: saved ? 1 : 0
8809
+ });
8810
+ }
8740
8811
  /**
8741
8812
  * Прервать работу у лимита: занятые сессии пишут хэндофф (или он
8742
8813
  * собирается из журнала, если агент не ответил), простаивающие не
@@ -8749,9 +8820,25 @@ var HostServer = class {
8749
8820
  if (sessions.length === 0)
8750
8821
  return;
8751
8822
  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) => {
8823
+ const busy = sessions.filter((s) => !this.#limitHandoffBusy.has(s.id) && (this.#isBusy(s) || now - s.lastActiveAt < LIMIT_RECENT_MS));
8824
+ const results = await Promise.all(busy.map((session) => this.#limitHandoff(session, hit)));
8825
+ this.#broadcast({
8826
+ type: "limit.stopped",
8827
+ limit: hit.limit,
8828
+ utilization: hit.window.utilization,
8829
+ resetsAt: hit.window.resetsAt,
8830
+ stopped: results.length,
8831
+ handoffs: results.filter(Boolean).length
8832
+ });
8833
+ }
8834
+ /**
8835
+ * Хэндофф одной сессии у лимита: ход хэндоффа (или журнал, если агент не
8836
+ * ответил), сохранение и рассылка. `true` — хэндофф сохранён.
8837
+ */
8838
+ async #limitHandoff(session, hit) {
8839
+ this.#limitHandoffBusy.add(session.id);
8840
+ try {
8841
+ const prompt = hit.window.utilization >= 100 ? null : handoffPrompt(hit);
8755
8842
  const text = await session.runHandoff(prompt, HANDOFF_TIMEOUT_MS);
8756
8843
  const summary = session.summary();
8757
8844
  const handoff = {
@@ -8765,23 +8852,15 @@ var HostServer = class {
8765
8852
  source: text ? "agent" : "journal",
8766
8853
  text: text ?? journalHandoff(session.journal.since(0))
8767
8854
  };
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
- });
8855
+ await this.#handoffs.save(handoff);
8856
+ this.#broadcast({ type: "handoff.state", sessionId: handoff.sessionId, handoff });
8857
+ return true;
8858
+ } catch (error) {
8859
+ console.error("[limit-stop]", error instanceof Error ? error.message : String(error));
8860
+ return false;
8861
+ } finally {
8862
+ this.#limitHandoffBusy.delete(session.id);
8863
+ }
8785
8864
  }
8786
8865
  /**
8787
8866
  * «Хэндофф и новая сессия»: тот же ход хэндоффа, что у лимита, только по
@@ -9032,6 +9111,9 @@ var HostServer = class {
9032
9111
  if (session instanceof ClaudeSession)
9033
9112
  void this.#checkLimit(usage);
9034
9113
  });
9114
+ if (session instanceof ClaudeSession) {
9115
+ session.on("unpromptedTurn", () => void this.#checkUnpromptedTurn(session));
9116
+ }
9035
9117
  session.on("context", (context) => {
9036
9118
  this.#broadcast({ type: "context.state", sessionId: session.id, context });
9037
9119
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i14ks/ccv",
3
- "version": "0.20.0",
3
+ "version": "0.21.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-Zo5hIeFa.js";import{E as e}from"./index-891wE-Rj.js";import{t as o}from"./index-3BSpF-Pn.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};