@yadsh/dsh-qa-surface 0.15.3 → 0.15.4

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 (47) hide show
  1. package/README.md +49 -8
  2. package/lib/approvals.js +42 -1
  3. package/lib/approvals.js.map +1 -1
  4. package/lib/client/QaSessionController.js +103 -3
  5. package/lib/client/QaSessionController.js.map +1 -1
  6. package/lib/client/QaSurface.js +6 -1
  7. package/lib/client/QaSurface.js.map +1 -1
  8. package/lib/client/QaTranscriptAdapter.js +33 -3
  9. package/lib/client/QaTranscriptAdapter.js.map +1 -1
  10. package/lib/client/components/QaChangelog.js +18 -0
  11. package/lib/client/components/QaChangelog.js.map +1 -1
  12. package/lib/client/components/QaComposer.js +3 -0
  13. package/lib/client/components/QaComposer.js.map +1 -1
  14. package/lib/client/components/QaHeader.js +29 -3
  15. package/lib/client/components/QaHeader.js.map +1 -1
  16. package/lib/client/components/QaRightRail.js +20 -1
  17. package/lib/client/components/QaRightRail.js.map +1 -1
  18. package/lib/client/components/QaSidebar.js +12 -2
  19. package/lib/client/components/QaSidebar.js.map +1 -1
  20. package/lib/client/components/QaSourcesPanel.js +1 -1
  21. package/lib/client/components/QaSourcesPanel.js.map +1 -1
  22. package/lib/client/components/QaWorkGroup.js +13 -7
  23. package/lib/client/components/QaWorkGroup.js.map +1 -1
  24. package/lib/client/components/QaWorkspaceBrowser.js +5 -1
  25. package/lib/client/components/QaWorkspaceBrowser.js.map +1 -1
  26. package/lib/client/settings/styles.js +16 -10
  27. package/lib/client/settings/styles.js.map +1 -1
  28. package/lib/client/slash/SlashPalette.js +3 -1
  29. package/lib/client/slash/SlashPalette.js.map +1 -1
  30. package/lib/client/styles.js +31 -15
  31. package/lib/client/styles.js.map +1 -1
  32. package/lib/client.js +259 -77
  33. package/lib/client.js.map +1 -1
  34. package/lib/qa-tools/file-delete-gate.js +35 -10
  35. package/lib/qa-tools/file-delete-gate.js.map +1 -1
  36. package/lib/qa-tools/file-delete.js +1 -1
  37. package/lib/qa-tools/file-delete.js.map +1 -1
  38. package/lib/types/approvals.d.ts +18 -1
  39. package/lib/types/client/QaSessionController.d.ts +23 -0
  40. package/lib/types/client/components/QaHeader.d.ts +10 -1
  41. package/lib/types/client/components/QaRightRail.d.ts +7 -1
  42. package/lib/types/client/components/QaSidebar.d.ts +5 -0
  43. package/lib/types/client/components/QaWorkGroup.d.ts +5 -2
  44. package/lib/types/client/settings/styles.d.ts +6 -0
  45. package/lib/types/qa-tools/file-delete-gate.d.ts +24 -7
  46. package/lib/types/types.d.ts +5 -2
  47. package/package.json +4 -4
package/lib/client.js CHANGED
@@ -16342,6 +16342,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16342
16342
  */
16343
16343
  const QA_REGENERATE_MARKER = "Перегенерируй свой предыдущий ответ — дай новый вариант, не повторяя предыдущий.";
16344
16344
  /**
16345
+ * Terminal row of a turn a person stopped. "Готово" claims a complete answer,
16346
+ * and a stopped turn commits a prefix instead, so the row says both what
16347
+ * happened and what the reader is holding.
16348
+ */
16349
+ const QA_TURN_STOPPED_COPY = "Ход остановлен: ответ неполный. Отправь запрос ещё раз, чтобы получить ответ целиком.";
16350
+ /**
16345
16351
  * Durable image references carried by user content. The core ContentBlock
16346
16352
  * vocabulary is not installed client-side, so the shape is matched
16347
16353
  * structurally against the host contract ({type:"image", attachment:{...}}).
@@ -16431,6 +16437,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16431
16437
  }
16432
16438
  function collectAssistant(node, turns, toolHeads, showReasoning, workspaceRoot) {
16433
16439
  const turn = getTurn(turns, node.turn);
16440
+ if (node.interrupted === true) turn.stoppedAt = node.seq;
16434
16441
  const text = visibleAssistantText(node.blocks, workspaceRoot);
16435
16442
  if (text !== "") turn.text.push({
16436
16443
  id: `assistant:${node.messageId ?? node.seq}`,
@@ -16538,6 +16545,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16538
16545
  function emitTurn(legacy, running, turn, output, erroredCode) {
16539
16546
  const timing = legacy.turnTimings.get(turn.turn);
16540
16547
  const completed = timing?.endTime !== void 0 || legacy.turnEnds.has(turn.turn);
16548
+ const stoppedAt = erroredCode === void 0 ? turn.stoppedAt : void 0;
16541
16549
  const sortedText = [...turn.text].sort((left, right) => left.order - right.order);
16542
16550
  const partial = sortedText.find((message) => message.status === "streaming");
16543
16551
  const committed = sortedText.filter((message) => message.status === "committed");
@@ -16562,7 +16570,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16562
16570
  id: `work:${turn.turn}`,
16563
16571
  role: "work",
16564
16572
  turn: turn.turn,
16565
- status: erroredCode !== void 0 ? "error" : completed || !running ? "complete" : "running",
16573
+ status: erroredCode !== void 0 ? "error" : stoppedAt !== void 0 ? "stopped" : completed || !running ? "complete" : "running",
16566
16574
  ...timing?.startTime === void 0 ? {} : { startedAt: timing.startTime },
16567
16575
  ...timing?.endTime === void 0 ? {} : { endedAt: timing.endTime },
16568
16576
  items
@@ -16595,6 +16603,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16595
16603
  ...turn.artifacts.length === 0 ? {} : { artifacts: turn.artifacts }
16596
16604
  }
16597
16605
  });
16606
+ if (stoppedAt !== void 0) output.push({
16607
+ order: stoppedAt + .5,
16608
+ message: {
16609
+ id: `turn-stopped:${turn.turn}`,
16610
+ role: "system",
16611
+ text: QA_TURN_STOPPED_COPY,
16612
+ status: "info"
16613
+ }
16614
+ });
16598
16615
  }
16599
16616
  /** Project end-user messages plus optional operator-approved work detail. */
16600
16617
  function projectTranscript(snapshot, options = {}) {
@@ -16926,6 +16943,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16926
16943
  */
16927
16944
  const SLASH_CATALOG_STALE_MS = 3e3;
16928
16945
  /**
16946
+ * How long a refused retain waits for its row to come into this browser's
16947
+ * Session catalog. Short on purpose, and shorter than the adoption timeout: the
16948
+ * row arrives either as a Host push or as the answer of the one pull asked for
16949
+ * here, so it is a round-trip away — while a whole `timeoutMs` of patience
16950
+ * would stack a second window onto the waits that follow and hold a chat that
16951
+ * never opens on screen for two of them. A row that misses this window loses
16952
+ * the chat, and the plate's «Повторить» re-runs the adoption on a catalog that
16953
+ * has had the pull.
16954
+ */
16955
+ const CATALOG_ROW_PATIENCE_MS = 5e3;
16956
+ /**
16929
16957
  * Spacing of the parked-request poll. A request is answered by a person, so a
16930
16958
  * second of latency is invisible; the poll only runs while a turn runs.
16931
16959
  */
@@ -17995,7 +18023,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17995
18023
  */
17996
18024
  async bind(id, options) {
17997
18025
  const { operation, report = true, attest = true, track = true, allowCompatibilityReadOnly = false } = options;
17998
- const reference = this.sessions.retain(id, { source: QA_SURFACE_SESSION_SOURCE });
18026
+ const retainAttemptedAt = Date.now();
18027
+ let reference;
18028
+ try {
18029
+ reference = this.sessions.retain(id, { source: QA_SURFACE_SESSION_SOURCE });
18030
+ } catch (error) {
18031
+ if (this.catalogLists(id)) throw error;
18032
+ if (!await this.awaitCatalogRow(id, retainAttemptedAt, operation, error)) return false;
18033
+ reference = this.sessions.retain(id, { source: QA_SURFACE_SESSION_SOURCE });
18034
+ }
17999
18035
  let binding = this.sessions.binding(id);
18000
18036
  try {
18001
18037
  if (binding === void 0) {
@@ -18063,6 +18099,53 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18063
18099
  return true;
18064
18100
  }
18065
18101
  /**
18102
+ * Whether this browser's Session catalog names one id. The Host answers a
18103
+ * retain from exactly this set, so a refusal of a listed session is a refusal
18104
+ * of a session it knows — never the race this adoption can wait out.
18105
+ */
18106
+ catalogLists(id) {
18107
+ return Object.hasOwn(this.sessions.list.getSnapshot().byId, id);
18108
+ }
18109
+ /**
18110
+ * Wait for this browser's Session catalog to come to know one id, after
18111
+ * {@link bind} was refused a reference to it, and answer whether the chat on
18112
+ * screen still belongs to the adoption that asked (`false` — it moved on, and
18113
+ * nothing is retained). Only an id the catalog does not name reaches here: a
18114
+ * refusal of a session it already lists is not this race and goes back as it
18115
+ * came, so there is always a row to wait for. A refusal is only final once
18116
+ * that row has had its chance: this is the one window where the race is
18117
+ * repairable rather than reportable, so the failure the caller reads stays the
18118
+ * Host's own refusal, not a timeout nobody asked for.
18119
+ *
18120
+ * Both timestamps are logged because the race is intermittent and the stand
18121
+ * keeps no other trace of it: the server never sees a retain, so its journal
18122
+ * cannot say which chat lost the create, and the next round needs the id and
18123
+ * the two moments to line this up with its own observations.
18124
+ */
18125
+ async awaitCatalogRow(id, retainAttemptedAt, operation, failure) {
18126
+ if (this.disposed || operation !== this.generation) return false;
18127
+ console.warn("dsh-qa-surface: sessions.retain raced the session catalog", {
18128
+ sessionId: id,
18129
+ retainAttemptedAt: new Date(retainAttemptedAt).toISOString()
18130
+ });
18131
+ this.sessions.refresh().catch((cause) => {
18132
+ console.warn("dsh-qa-surface: session catalog refresh refused", {
18133
+ sessionId: id,
18134
+ cause: cause instanceof Error ? cause.message : String(cause)
18135
+ });
18136
+ });
18137
+ try {
18138
+ await waitFor(this.sessions.list, (snapshot) => Object.hasOwn(snapshot.byId, id), Math.min(this.timeoutMs, CATALOG_ROW_PATIENCE_MS));
18139
+ } catch {
18140
+ throw failure;
18141
+ }
18142
+ console.warn("dsh-qa-surface: session catalog row landed", {
18143
+ sessionId: id,
18144
+ waitedMs: Date.now() - retainAttemptedAt
18145
+ });
18146
+ return !this.disposed && operation === this.generation;
18147
+ }
18148
+ /**
18066
18149
  * Give up on an adoption the chat on screen has outgrown, and answer whether
18067
18150
  * that is what happened (`true` — the caller has nothing left to publish).
18068
18151
  *
@@ -19524,6 +19607,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19524
19607
  }
19525
19608
  //#endregion
19526
19609
  //#region src/client/slash/SlashPalette.tsx
19610
+ /** Whether a Host-supplied description is written in the surface's language. */
19611
+ const isCyrillic = /[А-Яа-яЁё]/u;
19527
19612
  const KIND_LABEL = Object.freeze({
19528
19613
  skill: "Навык",
19529
19614
  command: "Команда"
@@ -19599,6 +19684,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19599
19684
  }), props.showDescriptions && entry.description !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
19600
19685
  className: "dsh-qa-slash__description",
19601
19686
  "data-testid": "qa-slash-row-description",
19687
+ lang: isCyrillic.test(entry.description) ? "ru" : "en",
19602
19688
  children: entry.description
19603
19689
  }) : null]
19604
19690
  }, entry.id);
@@ -19918,6 +20004,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19918
20004
  if (await props.onSend(text, attachments, pick?.id ?? null)) {
19919
20005
  setDraft("");
19920
20006
  props.onAttachmentsChange([]);
20007
+ setAttachmentError(null);
19921
20008
  }
19922
20009
  } finally {
19923
20010
  setSubmitting(false);
@@ -20491,7 +20578,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20491
20578
  ]
20492
20579
  });
20493
20580
  }
20494
- function QaHeader({ logoUrl, title, viewingSubagent, onCloseSubagent, roleSelector, themeSwitcher, administration, agentCount, agentsOpen, onToggleAgents, sourcesVisible, sourcesCount, sourcesComplete, sourcesOpen, onOpenSources, fileCount, filesEnabled, filesOpen, onOpenFiles, settings, history, panelLauncher, showReset, resetDisabled, onReset }) {
20581
+ function QaHeader({ logoUrl, title, viewingSubagent, onCloseSubagent, roleSelector, themeSwitcher, administration, agentCount, agentsOpen, onToggleAgents, sourcesVisible, sourcesCount, sourcesComplete, sourcesOpen, onOpenSources, fileCount, filesEnabled, filesOpen, onOpenFiles, settings, history, panelLauncher, showReset, resetDisabled, onReset, onBackToChat }) {
20495
20582
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
20496
20583
  className: "dsh-qa-header",
20497
20584
  "data-testid": "qa-surface-header",
@@ -20548,9 +20635,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20548
20635
  type: "button",
20549
20636
  className: "dsh-qa-header__agents",
20550
20637
  "data-testid": "qa-surface-header-agents",
20551
- disabled: agentCount === 0,
20638
+ "aria-disabled": agentCount === 0 || void 0,
20552
20639
  "aria-expanded": agentsOpen,
20553
- onClick: onToggleAgents,
20640
+ ...agentCount === 0 ? { "aria-label": "Агенты: субагентов в этом чате пока нет" } : {},
20641
+ title: agentCount === 0 ? "Субагентов в этом чате пока нет" : "Показать субагентов этого чата",
20642
+ onClick: () => {
20643
+ if (agentCount === 0) return;
20644
+ onToggleAgents();
20645
+ },
20554
20646
  children: [
20555
20647
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
20556
20648
  viewBox: "0 0 16 16",
@@ -20571,8 +20663,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20571
20663
  type: "button",
20572
20664
  className: "dsh-qa-header__sources",
20573
20665
  "data-testid": "qa-surface-header-sources",
20574
- disabled: sourcesCount === 0 && sourcesComplete,
20575
20666
  "aria-expanded": sourcesOpen,
20667
+ ...sourcesCount === 0 && sourcesComplete ? { "aria-label": "Источники: этот ответ обошёлся без источников" } : {},
20668
+ title: sourcesCount === 0 && sourcesComplete ? "Этот ответ обошёлся без источников" : "Показать источники этого ответа",
20576
20669
  onClick: onOpenSources,
20577
20670
  children: [
20578
20671
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
@@ -20592,9 +20685,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20592
20685
  type: "button",
20593
20686
  className: "dsh-qa-header__files",
20594
20687
  "data-testid": "qa-surface-header-files",
20595
- disabled: !filesEnabled,
20688
+ "aria-disabled": !filesEnabled || void 0,
20596
20689
  "aria-expanded": filesOpen,
20597
- onClick: onOpenFiles,
20690
+ ...!filesEnabled ? { "aria-label": "Файлы: в этом чате нет ни вложений, ни читаемого рабочего каталога" } : {},
20691
+ title: !filesEnabled ? "В этом чате нет ни вложений, ни читаемого рабочего каталога" : "Показать файлы этого чата",
20692
+ onClick: () => {
20693
+ if (!filesEnabled) return;
20694
+ onOpenFiles();
20695
+ },
20598
20696
  children: [
20599
20697
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
20600
20698
  viewBox: "0 0 16 16",
@@ -20647,8 +20745,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20647
20745
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20648
20746
  className: "dsh-qa-header__tabs",
20649
20747
  "data-testid": "qa-surface-header-tabs",
20650
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20748
+ children: onBackToChat === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20749
+ "aria-current": "page",
20750
+ children: "Чат"
20751
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
20752
+ type: "button",
20753
+ className: "dsh-qa-header__tab",
20754
+ "data-testid": "qa-surface-header-chat-tab",
20651
20755
  "aria-current": "page",
20756
+ "aria-label": "Вернуться к чату",
20757
+ title: "Закрыть панель и вернуться к чату",
20758
+ onClick: onBackToChat,
20652
20759
  children: "Чат"
20653
20760
  })
20654
20761
  })]
@@ -50182,6 +50289,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
50182
50289
  }
50183
50290
  //#endregion
50184
50291
  //#region src/client/components/QaWorkGroup.tsx
50292
+ /** Whether one turn is over: a settled group opens collapsed and stays closed. */
50293
+ function isSettled(status) {
50294
+ return status === "complete" || status === "error" || status === "stopped";
50295
+ }
50185
50296
  function Chevron({ open }) {
50186
50297
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
50187
50298
  className: "dsh-qa-work__chevron",
@@ -50356,15 +50467,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
50356
50467
  const now = useNow(status === "running" && startedAt !== void 0);
50357
50468
  const previousStatus = (0, react.useRef)(status);
50358
50469
  (0, react.useEffect)(() => {
50359
- if (previousStatus.current === "running" && (status === "complete" || status === "error")) setOpen(false);
50360
- else if (previousStatus.current === "complete" || previousStatus.current === "error") {
50470
+ if (previousStatus.current === "running" && isSettled(status)) setOpen(false);
50471
+ else if (isSettled(previousStatus.current)) {
50361
50472
  if (status === "running") setOpen(true);
50362
50473
  }
50363
50474
  previousStatus.current = status;
50364
50475
  }, [status]);
50365
50476
  const elapsed = (endedAt ?? now) - (startedAt ?? now);
50366
50477
  const duration = startedAt === void 0 ? null : formatWorkDuration(elapsed);
50367
- const label = status === "running" ? `${thinkingPhrase(elapsed, thinkingPhrases)}${duration === null ? "" : ` (${duration})`}` : status === "error" ? duration === null ? "Ход прерван" : `Прервано за ${duration}` : duration === null ? "Ход работы" : `Готово за ${duration}`;
50478
+ const label = status === "running" ? `${thinkingPhrase(elapsed, thinkingPhrases)}${duration === null ? "" : ` (${duration})`}` : status === "error" ? duration === null ? "Ход прерван" : `Прервано за ${duration}` : status === "stopped" ? duration === null ? "Ход остановлен" : `Остановлено на ${duration}` : duration === null ? "Ход работы" : `Готово за ${duration}`;
50368
50479
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
50369
50480
  className: "dsh-qa-work",
50370
50481
  "data-testid": "qa-surface-work-group",
@@ -52961,8 +53072,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
52961
53072
  }
52962
53073
  //#endregion
52963
53074
  //#region src/client/components/QaChangelog.tsx
52964
- const QA_VERSION = "0.15.3";
53075
+ const QA_VERSION = "0.15.4";
52965
53076
  const QA_CHANGELOG = [
53077
+ {
53078
+ version: "0.15.4",
53079
+ date: "2026-10-08",
53080
+ sections: [{
53081
+ title: "Исправления",
53082
+ items: [
53083
+ "Мелкие контролы поверхности стали объяснять себя, а основной текст — читаться с 16 px. Чипы «Агенты», «Источники» и «Файлы» при пустом списке просто глушились: серая кнопка выпадала из обхода Tab, не отвечала на наведение и не говорила, что именно кончилось; теперь она остаётся доступной, а причина пустоты читается в её имени и подсказке. «Чат» в шапке выглядел выбранной вкладкой, но не нажимался — когда поверх разговора открыта панель, это кнопка, которая закрывает панель и возвращает к чату, а пока ничего не открыто, он остаётся подписью. На телефоне панель, занимающая весь экран, имела один выход — крестик 26 px: у него и зона попадания размером с палец, и имя, называющее закрываемую вкладку, а Escape закрывает панель, где бы ни стоял курсор; действия у сообщения на этой ширине такие же. Ответ ассистента больше не мельчает ни относительно собственного вопроса, ни на узком экране, и карточки входа и настроек переехали на тот же кегль — ниже 16 px остались только служебные подписи. Заголовок беседы в истории, который не влезал целиком, обрывается многоточием, полное имя даёт подсказка, а поиск ищет по всему имени; третий быстрый вопрос на узкой ширине больше не отсекается — чипы переносятся. Красная строка об отказе слишком большого файла уходит вместе с отправленным сообщением, а не висит над пустым полем; «Рабочий каталог» в панели «Файлы» напечатан один раз; карточка «Ожидает одобрения» называет причину по-русски, а описания команд в палитре по `/` объявляют свой язык, чтобы перевод не звучал как ошибка.",
53084
+ "Кнопка «Источники» в шапке больше не блокируется пустым списком, и пустой список объясняет себя сам. Ход, ответивший по памяти или по тому, что уже было в этом разговоре, в список ничего не добавляет: чтения памяти не узнаёт ни один экстрактор, и это замысел, а не поломка. Прежний интерфейс молчал дважды — под ответом не было чипа, а кнопка стояла заблокированной без счётчика и без причины, и вместе с ней закрывалась единственная поверхность, способная объяснить пустоту. Теперь кнопка жива всегда, когда её показывает развёртывание, а панель на пустом завершённом ходе перечисляет, какие материалы в неё попадают, и говорит, что пустой список — не сбой сбора. Пока ход ещё идёт или делегированный запуск остался должен свои источники, объяснение молчит: об этом уже говорит строка о недоступных источниках, и два противоречащих текста на одном экране не встречаются.",
53085
+ "Остановленный ход больше не притворяется завершённым. Кнопка «стоп» обрывала ответ, а под ходом читалось «Готово за 2 с»: «готово» обещает целый ответ, и человек, остановивший ход, чтобы переформулировать вопрос, видел успех вместо того, что сделал сам. Теперь у остановки свой итог — строка «Остановлено на N с» и плашка о том, что ответ неполный и как получить его целиком. Всё, что успело прийти, остаётся обычным сообщением: его можно скопировать и оценить, так что остановка ничего уже написанного не съедает. У сбоя провайдера по-прежнему свой итог — остановка его не подменяет.",
53086
+ "Палитра слэш-команд в чате, который ещё не начался, называет те же навыки, что и в чате, где уже шёл ответ. Пока за разговором не стоял живой агент, список строился по одному общему allow-list'у развёртывания и не сужался ролью: в него попадал навык, который в этом чате вызвать нельзя — имя подсвечивалось, Enter подставлял его в строку, а в ответ приходил отказ, — и наоборот, доступный навык мог не показаться. Теперь роль учитывается при обоих чтениях, а список, который палитра показывает, и список, которым проверяется введённая строка, выводятся из одного правила, поэтому предложение в палитре и возможность его вызвать больше не могут разойтись.",
53087
+ "Делегированный вызов больше не ставит ход на паузу, которую никто не может снять. Проверяющий, вызванный из чата, не умеет спрашивать человека — харнесс отказывает таким запросам, — но заявка на подтверждение вставала карточкой над композером родителя и держала весь ход: на стенде так ждало то двенадцать минут, то больше часа, пока в основном чате шёл только таймер «думаю». Теперь запрос на подтверждение от дочерней сессии отклоняется сразу там, где он поднят, с объяснением, что дочь спрашивать не может, — модель получает это как находку, а оператор не получает зависший ход. Отдельно закрыто то, что привело к заявке: проверяющему ответа выдаются только инструменты чтения и поиска, а имена удаления файла, записи файлов и запуска команд исключены из его набора — и списком по умолчанию, и проверкой на сборке дочерней сессии.",
53088
+ "Запрос на одобрение, который никто не увидел, перестаёт владеть ходом навсегда: карточка, оставшаяся без ответа, гаснет по истечении ожидания и означает отказ, как и явный отказ оператора. Ничего при этом не разрешается автоматически: ни один путь не одобряет вызов без человека.",
53089
+ "Первый вопрос, заданный в новый черновик, больше не открывается плашкой «Не удалось начать чат.» над разговором, который стенд на самом деле создал. Список чатов браузер держит у себя, и право на сессию хост выдаёт по этому списку: идентификатор, которого в нём ещё нет, отвергается строкой `sessions.retain: unknown session`. Разговор же рождается на сервере — через собственный Remote стенда, — и ответ «создано» успевал дойти до браузера раньше, чем строка нового разговора в его список попала. Первая попытка закрепиться за сессией приходилась ровно на это окно, поэтому экран докладывал об отказе и молчал, что чат тем временем уже стоит в боковом списке строкой, в которую никто не вошёл. Теперь поверхность сначала ждёт, пока каталог браузера назовёт этот идентификатор, и сама просит свежий список, а не ждёт, что успеет прийти раньше — собственный кадр сервера или его очередная выгрузка. Ждёт недолго, на одну выгрузку: строка приходит пушем или ответом на этот же запрос, а целый таймаут ожидания лег бы вторым слоем поверх тех, что идут следом, и чат, которому не открыться, держал бы экран вдвое дольше. Если строка так и не пришла, в журнале остаётся отказ самого хоста, а не «истекло ожидание», которого никто не заказывал; отказ хоста для чата, который список уже называет, остаётся самим собой — поверхность не ждёт строку, которая на месте, и не рисует гонку там, где её не было. Отказ самой выгрузки тоже слышен: чат, потерянный в таком виде, читается как медленный каталог, а на деле это нечитаемый каталог. Случается это не на каждом входе, поэтому обе половины гонки пишутся в журнал браузера: отказ удержания — с его моментом, и пришедшая следом строка каталога — с тем, сколько пришлось ждать. Раунд, который чат всё-таки потерял, сможет сказать, на какой стороне создания он его потерял. Тот же порядок и для чата, который поверхность возвращает при открытии: он тоже сначала ждёт, пока список его назовёт, и только потом закрепляется за сессией."
53090
+ ]
53091
+ }]
53092
+ },
52966
53093
  {
52967
53094
  version: "0.15.3",
52968
53095
  date: "2026-10-08",
@@ -53521,7 +53648,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
53521
53648
  className: "dsh-qa-changelog__date",
53522
53649
  children: entry.date
53523
53650
  }),
53524
- entry.version === "0.15.3" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53651
+ entry.version === "0.15.4" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53525
53652
  className: "dsh-qa-changelog__current",
53526
53653
  children: "текущая"
53527
53654
  }) : null
@@ -53819,12 +53946,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
53819
53946
  const summary = byId[id];
53820
53947
  if (summary === void 0 || isDelegatedSession(summary)) continue;
53821
53948
  const ownerName = ownerNameOf?.(id);
53949
+ const label = summary.blank ? "Новый чат" : summary.displayTitle;
53950
+ const full = summary.title;
53951
+ const clipped = !summary.blank && full !== void 0 && full.length > label.length;
53822
53952
  rows.push({
53823
53953
  id,
53824
- title: summary.blank ? "Новый чат" : summary.displayTitle,
53954
+ title: clipped ? `${label.trimEnd()}…` : label,
53825
53955
  running: summary.running,
53826
53956
  active: id === activeId,
53827
53957
  meta: relativeTime(summary.updatedAt, now),
53958
+ ...clipped && full !== void 0 ? { fullName: full } : {},
53828
53959
  updatedAt: summary.updatedAt,
53829
53960
  ...ownerName === void 0 ? {} : { ownerName }
53830
53961
  });
@@ -53907,7 +54038,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
53907
54038
  });
53908
54039
  }
53909
54040
  function rowMatches(row, query) {
53910
- return row.title.toLowerCase().includes(query) || row.ownerName?.toLowerCase().includes(query) === true;
54041
+ return row.title.toLowerCase().includes(query) || (row.fullName?.toLowerCase().includes(query) ?? false) || row.ownerName?.toLowerCase().includes(query) === true;
53911
54042
  }
53912
54043
  /**
53913
54044
  * How each chat is named by the controls acting on it, keyed by id. A title is
@@ -54014,6 +54145,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54014
54145
  className: "dsh-qa-sidebar__item-main",
54015
54146
  "data-testid": "qa-surface-sidebar-item-open",
54016
54147
  "aria-current": row.active ? "true" : void 0,
54148
+ title: row.fullName ?? row.title,
54017
54149
  onClick: () => {
54018
54150
  props.onSwitch(row.id);
54019
54151
  closeDrawer();
@@ -54811,22 +54943,23 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54811
54943
  });
54812
54944
  }
54813
54945
  const crumbs = crumbsOf(directory);
54946
+ const trail = directory === "" ? crumbs.slice(1) : crumbs;
54814
54947
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
54815
54948
  className: "dsh-qa-ws",
54816
54949
  "data-testid": "qa-surface-workspace",
54817
54950
  children: [
54818
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
54951
+ trail.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
54819
54952
  className: "dsh-qa-ws__crumbs",
54820
54953
  "data-testid": "qa-surface-workspace-crumbs",
54821
54954
  "aria-label": "Путь в рабочем каталоге",
54822
- children: crumbs.map((crumb, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [index > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
54955
+ children: trail.map((crumb, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [index > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
54823
54956
  "aria-hidden": "true",
54824
54957
  children: "/"
54825
54958
  }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
54826
54959
  type: "button",
54827
54960
  className: "dsh-qa-ws__crumb",
54828
54961
  "data-testid": "qa-surface-workspace-crumb",
54829
- "aria-current": index === crumbs.length - 1 ? "true" : void 0,
54962
+ "aria-current": index === trail.length - 1 ? "true" : void 0,
54830
54963
  onClick: () => setDirectory(crumb.path),
54831
54964
  children: crumb.label
54832
54965
  })] }, crumb.path))
@@ -54983,9 +55116,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54983
55116
  * The chat's right rail: a fixed column whose top edge is the tab strip,
54984
55117
  * mirroring the Harness right Sidebar's chrome. Tabs are plain chips with
54985
55118
  * counts; the strip's last control collapses the whole column.
55119
+ *
55120
+ * On the phone layout the column is the whole surface, so the strip's control
55121
+ * is the only way back and Escape has to answer from anywhere on the page —
55122
+ * the focus may well still sit in the composer behind the panel. A dialog above
55123
+ * the rail keeps the key: the innermost thing on the page is what the reader
55124
+ * means to dismiss.
54986
55125
  */
54987
55126
  function QaRightRail({ tabs, activeTab, onTabSelect, onClose }) {
54988
55127
  const active = tabs.find((tab) => tab.id === activeTab) ?? tabs[0];
55128
+ (0, react.useEffect)(() => {
55129
+ const onKey = (event) => {
55130
+ if (event.key !== "Escape" || event.defaultPrevented) return;
55131
+ if (isQaModalOpen()) return;
55132
+ onClose();
55133
+ };
55134
+ window.addEventListener("keydown", onKey);
55135
+ return () => window.removeEventListener("keydown", onKey);
55136
+ }, [onClose]);
54989
55137
  if (active === void 0) return null;
54990
55138
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
54991
55139
  className: "dsh-qa-panel",
@@ -55011,8 +55159,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
55011
55159
  type: "button",
55012
55160
  className: "dsh-qa-panel__close",
55013
55161
  "data-testid": "qa-surface-rail-close",
55014
- "aria-label": "Закрыть панель",
55015
- title: "Закрыть",
55162
+ "aria-label": `Закрыть панель «${active.title}» и вернуться к чату`,
55163
+ title: `Закрыть панель «${active.title}»`,
55016
55164
  onClick: onClose,
55017
55165
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
55018
55166
  viewBox: "0 0 16 16",
@@ -55316,39 +55464,47 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
55316
55464
  }) : null, detail === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
55317
55465
  className: "dsh-qa-sources__list",
55318
55466
  "data-testid": "qa-sources-list",
55319
- children: [!complete ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
55320
- className: "dsh-qa-sources__incomplete",
55321
- "data-testid": "qa-sources-incomplete",
55322
- children: [
55323
- "Некоторые источники делегированных запусков недоступны (",
55324
- incompleteOrigins?.length ?? 1,
55325
- ")."
55326
- ]
55327
- }) : null, groups.map((group) => {
55328
- const visible = expanded.has(group.key) ? group.sources : group.sources.slice(0, display.maxInitiallyVisiblePerGroup);
55329
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
55330
- className: "dsh-qa-sources__group",
55331
- "data-testid": `qa-sources-group-${group.key}`,
55467
+ children: [
55468
+ !complete ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
55469
+ className: "dsh-qa-sources__incomplete",
55470
+ "data-testid": "qa-sources-incomplete",
55332
55471
  children: [
55333
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", { children: [group.label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
55334
- "data-testid": "qa-sources-group-count",
55335
- children: group.sources.length
55336
- })] }),
55337
- visible.map((source) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaSourceCard, {
55338
- source,
55339
- showOriginBadges: display.showOriginBadges,
55340
- onOpen: () => setDetailId(source.id)
55341
- }, source.id)),
55342
- visible.length < group.sources.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
55343
- type: "button",
55344
- className: "dsh-qa-sources__more",
55345
- "data-testid": "qa-sources-more",
55346
- onClick: () => setExpanded(/* @__PURE__ */ new Set([...expanded, group.key])),
55347
- children: ["Показать ещё ", group.sources.length - visible.length]
55348
- }) : null
55472
+ "Некоторые источники делегированных запусков недоступны (",
55473
+ incompleteOrigins?.length ?? 1,
55474
+ ")."
55349
55475
  ]
55350
- }, group.key);
55351
- })]
55476
+ }) : null,
55477
+ complete && sources.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
55478
+ className: "dsh-qa-sources__empty",
55479
+ "data-testid": "qa-sources-empty",
55480
+ children: "Здесь появляются материалы, которые ход читал или открывал сам: файл рабочего каталога, веб-страница, выдача веб-поиска, ответ Jira, Confluence или базы знаний. Ответ, собранный по памяти или по тому, что уже было в этом разговоре, сюда ничего не добавляет — поэтому пустой список после завершённого хода означает именно это, а не сбой сбора."
55481
+ }) : null,
55482
+ groups.map((group) => {
55483
+ const visible = expanded.has(group.key) ? group.sources : group.sources.slice(0, display.maxInitiallyVisiblePerGroup);
55484
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
55485
+ className: "dsh-qa-sources__group",
55486
+ "data-testid": `qa-sources-group-${group.key}`,
55487
+ children: [
55488
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", { children: [group.label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
55489
+ "data-testid": "qa-sources-group-count",
55490
+ children: group.sources.length
55491
+ })] }),
55492
+ visible.map((source) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaSourceCard, {
55493
+ source,
55494
+ showOriginBadges: display.showOriginBadges,
55495
+ onOpen: () => setDetailId(source.id)
55496
+ }, source.id)),
55497
+ visible.length < group.sources.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
55498
+ type: "button",
55499
+ className: "dsh-qa-sources__more",
55500
+ "data-testid": "qa-sources-more",
55501
+ onClick: () => setExpanded(/* @__PURE__ */ new Set([...expanded, group.key])),
55502
+ children: ["Показать ещё ", group.sources.length - visible.length]
55503
+ }) : null
55504
+ ]
55505
+ }, group.key);
55506
+ })
55507
+ ]
55352
55508
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaSourceDetail, {
55353
55509
  source: detail,
55354
55510
  sessionId,
@@ -64658,6 +64814,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
64658
64814
  config.ui.showHeader ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaHeader, {
64659
64815
  logoUrl: config.branding.logoUrl,
64660
64816
  title: conversationTitle,
64817
+ onBackToChat: rail.railOpen || agentsOpen ? () => {
64818
+ rail.close();
64819
+ setAgentsOpen(false);
64820
+ } : void 0,
64661
64821
  viewingSubagent: state.viewingSubagent !== null,
64662
64822
  onCloseSubagent: handleCloseSubagent,
64663
64823
  roleSelector: access === void 0 || selectedSubrole === null || previewing ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaRoleSelector, {
@@ -65304,17 +65464,17 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
65304
65464
  .dsh-qa-auth__logo{width:32px;height:32px;fill:none;stroke:var(--dsh-qa-brand);stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round}
65305
65465
  .dsh-qa-auth__brand h1{margin:0;color:var(--dsw-alias-label-primary);font-size:19px;font-weight:600;line-height:26px}
65306
65466
  .dsh-qa-auth__tabs{display:flex;gap:4px;padding:3px;border-radius:9px;background:var(--dsw-alias-bg-module-platform)}
65307
- .dsh-qa-auth__tab{appearance:none;flex:1;padding:6px 0;border:0;border-radius:7px;background:0 0;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;font-weight:500;line-height:18px;cursor:pointer;transition:background .12s,color .12s}
65467
+ .dsh-qa-auth__tab{appearance:none;flex:1;padding:6px 0;border:0;border-radius:7px;background:0 0;color:var(--dsw-alias-label-secondary);font:inherit;font-size:16px;font-weight:500;line-height:22px;cursor:pointer;transition:background .12s,color .12s}
65308
65468
  .dsh-qa-auth__tab:hover{color:var(--dsw-alias-label-primary)}
65309
65469
  .dsh-qa-auth__tab--active{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
65310
65470
  .dsh-qa-auth__tab:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsh-qa-accent));outline-offset:-2px}
65311
65471
  .dsh-qa-auth__field{display:flex;flex-direction:column;gap:5px}
65312
- .dsh-qa-auth__field span{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:16px}
65313
- .dsh-qa-auth__field input{appearance:none;width:100%;padding:9px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:20px}
65472
+ .dsh-qa-auth__field span{color:var(--dsw-alias-label-secondary);font-size:16px;line-height:22px}
65473
+ .dsh-qa-auth__field input{appearance:none;width:100%;padding:9px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:inherit;font-size:16px;line-height:22px}
65314
65474
  .dsh-qa-auth__field input:focus{outline:none;border-color:var(--dsh-qa-brand)}
65315
65475
  .dsh-qa-auth__field input:disabled{opacity:.55}
65316
- .dsh-qa-auth__error{margin:0;color:var(--dsh-qa-error);font-size:12px;line-height:17px}
65317
- .dsh-qa-auth__submit{appearance:none;margin-top:2px;padding:10px 0;border:0;border-radius:9px;background:var(--dsh-qa-brand);color:var(--dsh-qa-accent-contrast);font:inherit;font-size:14px;font-weight:600;line-height:20px;cursor:pointer;transition:background .12s}
65476
+ .dsh-qa-auth__error{margin:0;color:var(--dsh-qa-error);font-size:16px;line-height:22px}
65477
+ .dsh-qa-auth__submit{appearance:none;margin-top:2px;padding:10px 0;border:0;border-radius:9px;background:var(--dsh-qa-brand);color:var(--dsh-qa-accent-contrast);font:inherit;font-size:16px;font-weight:600;line-height:22px;cursor:pointer;transition:background .12s}
65318
65478
  .dsh-qa-auth__submit:hover{background:var(--dsh-qa-accent-hover)}
65319
65479
  .dsh-qa-auth__submit:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsh-qa-accent));outline-offset:2px}
65320
65480
  .dsh-qa-auth__submit:disabled{opacity:.6;cursor:default}
@@ -65476,8 +65636,8 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
65476
65636
  .dsh-qa-header h1{max-width:min(42vw,420px);min-width:0;overflow:hidden;margin:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}
65477
65637
  .dsh-qa-header__reset{padding:5px 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;cursor:pointer}
65478
65638
  .dsh-qa-header__agents{display:inline-flex;align-items:center;gap:6px;flex:none;padding:5px 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;line-height:20px;cursor:pointer}
65479
- .dsh-qa-header__agents:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65480
- .dsh-qa-header__agents:disabled{opacity:.45;cursor:default}
65639
+ .dsh-qa-header__agents:hover:not(:disabled):not([aria-disabled="true"]){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65640
+ .dsh-qa-header__agents:disabled,.dsh-qa-header__agents[aria-disabled="true"]{opacity:.45;cursor:default}
65481
65641
  .dsh-qa-header__agents[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65482
65642
  .dsh-qa-header__agents svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}
65483
65643
  .dsh-qa-header__agents:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsh-qa-accent));outline-offset:2px}
@@ -65512,13 +65672,12 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
65512
65672
  .dsh-qa-agents__open{flex:none;color:var(--dsh-qa-accent);font-size:12px;font-weight:500;line-height:18px}
65513
65673
  .dsh-qa-header__sources{display:inline-flex;align-items:center;gap:6px;flex:none;padding:5px 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;line-height:20px;cursor:pointer}
65514
65674
  .dsh-qa-header__sources:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65515
- .dsh-qa-header__sources:disabled{opacity:.45;cursor:default}
65516
65675
  .dsh-qa-header__sources[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65517
65676
  .dsh-qa-header__sources svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}
65518
65677
  .dsh-qa-header__sources:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsh-qa-accent));outline-offset:2px}
65519
65678
  .dsh-qa-header__files{display:inline-flex;align-items:center;gap:6px;flex:none;padding:5px 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;line-height:20px;cursor:pointer}
65520
- .dsh-qa-header__files:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65521
- .dsh-qa-header__files:disabled{opacity:.45;cursor:default}
65679
+ .dsh-qa-header__files:hover:not(:disabled):not([aria-disabled="true"]){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65680
+ .dsh-qa-header__files:disabled,.dsh-qa-header__files[aria-disabled="true"]{opacity:.45;cursor:default}
65522
65681
  .dsh-qa-header__files[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65523
65682
  .dsh-qa-header__files svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}
65524
65683
  .dsh-qa-header__files:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsh-qa-accent));outline-offset:2px}
@@ -65589,8 +65748,11 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
65589
65748
  .dsh-qa-header__reset:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
65590
65749
  .dsh-qa-header__reset:disabled{opacity:.45;cursor:default}
65591
65750
  .dsh-qa-header__tabs{position:relative;z-index:1;display:flex;gap:32px;margin-top:4px}
65592
- .dsh-qa-header__tabs span{position:relative;padding:0 0 11px;color:var(--dsh-qa-accent);font-size:13px;font-weight:500;line-height:16px}
65593
- .dsh-qa-header__tabs span::after{content:"";position:absolute;right:0;bottom:1px;left:0;height:2px;border-radius:2px;background:var(--dsh-qa-accent)}
65751
+ .dsh-qa-header__tabs span,.dsh-qa-header__tab{position:relative;padding:0 0 11px;color:var(--dsh-qa-accent);font-size:13px;font-weight:500;line-height:16px}
65752
+ .dsh-qa-header__tab{appearance:none;background:0 0;border:0;cursor:pointer;font-family:inherit}
65753
+ .dsh-qa-header__tab:hover{text-decoration:underline}
65754
+ .dsh-qa-header__tab:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsh-qa-accent));outline-offset:2px}
65755
+ .dsh-qa-header__tabs span::after,.dsh-qa-header__tab::after{content:"";position:absolute;right:0;bottom:1px;left:0;height:2px;border-radius:2px;background:var(--dsh-qa-accent)}
65594
65756
  .dsh-qa-extension-panel__resizer{position:relative;z-index:2;flex:0 0 5px;width:5px;touch-action:none;cursor:col-resize;background:var(--dsw-alias-border-l2)}
65595
65757
  .dsh-qa-extension-panel__resizer:hover,.dsh-qa-extension-panel__resizer:focus-visible{background:var(--dsh-qa-accent);outline:0}
65596
65758
  .dsh-qa-extension-panel{display:flex;flex-direction:column;min-width:0;min-height:0;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary)}
@@ -65695,6 +65857,7 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
65695
65857
  span.dsh-qa-files__thumb{display:inline-block}
65696
65858
  img.dsh-qa-files__thumb{object-fit:cover;background:var(--dsw-alias-bg-layer-3)}
65697
65859
  .dsh-qa-sources__list{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:0 10px 12px}
65860
+ .dsh-qa-sources__empty{margin:4px 2px 12px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px}
65698
65861
  .dsh-qa-sources__group{margin:0 0 14px}
65699
65862
  .dsh-qa-sources__group h3{display:flex;align-items:center;gap:7px;margin:4px 4px 8px;color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;line-height:18px}
65700
65863
  .dsh-qa-sources__group h3 span{display:inline-grid;place-items:center;min-width:19px;height:18px;padding:0 5px;border-radius:999px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-tertiary);font-size:10px}
@@ -65753,7 +65916,20 @@ a.dsh-qa-sources__title:hover{text-decoration:underline}
65753
65916
  .dsh-qa-message--assistant{align-items:flex-start;margin-right:auto}
65754
65917
  .dsh-qa-message--work{align-items:flex-start;margin:-4px 0 14px}
65755
65918
  .dsh-qa-message--system{align-items:center;margin:4px auto 20px}
65756
- .dsh-qa-message__content{max-width:100%;white-space:pre-wrap;overflow-wrap:anywhere;color:var(--dsw-alias-label-primary);font-size:15px;line-height:1.62}
65919
+ /*
65920
+ The type scale of this surface: what the reader is asked to read is 16px, and
65921
+ nothing that carries a decision sits below it — the assistant's answer, the
65922
+ operator's own draft and the composer's status line all match the 16px the
65923
+ user bubble is drawn at, so a reply never reads smaller than the question.
65924
+
65925
+ Deliberate exceptions, all of them chrome rather than content: the header row
65926
+ and the tab marker (13px), the panel tab chips and their counts (13px/10px),
65927
+ message meta and timestamps (11-12px), the quick-question chips (13px),
65928
+ system notices and turn lines (13px), code and workspace file names
65929
+ (monospace, 11-13px), the admin console's tables and badges (10-12px), the
65930
+ composer's key hint (11px) and the welcome lede (14px).
65931
+ */
65932
+ .dsh-qa-message__content{max-width:100%;white-space:pre-wrap;overflow-wrap:anywhere;color:var(--dsw-alias-label-primary);font-size:16px;line-height:1.62}
65757
65933
  .dsh-qa-message--user .dsh-qa-message__content{max-width:min(525px,82%);padding:10px 16px;border-radius:22px;background:var(--dsw-specific-bubble);font-size:16px;line-height:24px}
65758
65934
  .dsh-qa-message[data-status="pending"] .dsh-qa-message__content{opacity:.78}
65759
65935
  .dsh-qa-message__pending{display:flex;align-items:center;gap:6px;margin-top:6px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
@@ -65826,7 +66002,7 @@ a.dsh-qa-srcref:hover,button.dsh-qa-srcref:hover{border-color:var(--dsw-alias-la
65826
66002
  properties, so a font-size preference or a light/dark switch moves both
65827
66003
  surfaces together. A container that needs a denser scale (the source
65828
66004
  preview, the work-item text) overrides the ladder for its own subtree. */
65829
- .dsh-qa-md{min-width:0;white-space:normal;overflow-wrap:anywhere;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-markdown-base-font-family,inherit);font-size:var(--dsw-font-markdown-base-font-size,14px);font-weight:400;line-height:var(--dsw-font-markdown-base-line-height,24px)}
66005
+ .dsh-qa-md{min-width:0;white-space:normal;overflow-wrap:anywhere;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-markdown-base-font-family,inherit);font-size:var(--dsw-font-markdown-base-font-size,16px);font-weight:400;line-height:var(--dsw-font-markdown-base-line-height,24px)}
65830
66006
  .dsh-qa-md>:first-child{margin-top:0}
65831
66007
  .dsh-qa-md>:last-child{margin-bottom:0}
65832
66008
  .dsh-qa-md p{margin:16px 0}
@@ -66115,7 +66291,7 @@ a.dsh-qa-srcref:hover,button.dsh-qa-srcref:hover{border-color:var(--dsw-alias-la
66115
66291
  row — title, role, palette, action cluster — is wider than the viewport with
66116
66292
  every child refusing to shrink, so it wraps instead of pushing «Настройки»
66117
66293
  past the clipped edge. */
66118
- @media (max-width:600px){.dsh-qa-sidebar{display:none}.dsh-qa-sidebar[data-qa-drawer="open"]{display:flex;position:fixed;inset:0 auto 0 0;z-index:24;width:min(320px,86vw);max-width:100%;border-right:1px solid var(--dsw-alias-border-l2);box-shadow:0 18px 44px rgba(0,0,0,.28);padding-bottom:env(safe-area-inset-bottom)}.dsh-qa-sidebar__scrim{display:block;position:fixed;inset:0;z-index:22;background:var(--dsh-qa-overlay)}.dsh-qa-sidebar__resize{display:none}.dsh-qa-sidebar__collapse,.dsh-qa-sidebar__expand{width:44px;height:44px}.dsh-qa-sidebar__new,.dsh-qa-sidebar__item-main{min-height:44px}.dsh-qa-header__history{display:inline-flex}.dsh-qa-panel,.dsh-qa-agents{position:absolute;inset:0;z-index:6;width:100%;border-left:0}.dsh-qa-header__inner{padding-left:16px;padding-right:16px}.dsh-qa-header h1{max-width:48vw}.dsh-qa-header__title-row{flex-wrap:wrap;row-gap:6px}.dsh-qa-width-handle{display:none}.dsh-qa-transcript__inner{--dsh-qa-bleed:0px;max-width:100%;padding:16px 16px 28px}.dsh-qa-footer__inner{max-width:100%}.dsh-qa-message{margin-bottom:16px}.dsh-qa-message--user .dsh-qa-message__content{max-width:88%;font-size:15px}.dsh-qa-message__content{font-size:14px}.dsh-qa-footer{padding-left:10px;padding-right:10px;padding-bottom:max(8px,env(safe-area-inset-bottom))}.dsh-qa-composer{min-height:84px;padding-top:12px}.dsh-qa-composer__hint{font-size:0}.dsh-qa-composer__hint::after{content:"Enter: отправить";font-size:11px}}
66294
+ @media (max-width:600px){.dsh-qa-sidebar{display:none}.dsh-qa-sidebar[data-qa-drawer="open"]{display:flex;position:fixed;inset:0 auto 0 0;z-index:24;width:min(320px,86vw);max-width:100%;border-right:1px solid var(--dsw-alias-border-l2);box-shadow:0 18px 44px rgba(0,0,0,.28);padding-bottom:env(safe-area-inset-bottom)}.dsh-qa-sidebar__scrim{display:block;position:fixed;inset:0;z-index:22;background:var(--dsh-qa-overlay)}.dsh-qa-sidebar__resize{display:none}.dsh-qa-sidebar__collapse,.dsh-qa-sidebar__expand{width:44px;height:44px}.dsh-qa-sidebar__new,.dsh-qa-sidebar__item-main{min-height:44px}.dsh-qa-header__history{display:inline-flex}.dsh-qa-panel,.dsh-qa-agents{position:absolute;inset:0;z-index:6;width:100%;border-left:0}.dsh-qa-panel__close{width:44px;height:44px}.dsh-qa-panel__tab{min-height:44px}.dsh-qa-header__inner{padding-left:16px;padding-right:16px}.dsh-qa-header h1{max-width:48vw}.dsh-qa-header__title-row{flex-wrap:wrap;row-gap:6px}.dsh-qa-width-handle{display:none}.dsh-qa-transcript__inner{--dsh-qa-bleed:0px;max-width:100%;padding:16px 16px 28px}.dsh-qa-footer__inner{max-width:100%}.dsh-qa-message{margin-bottom:16px}.dsh-qa-message__actions{height:44px}.dsh-qa-message__actions button{width:44px;height:44px}.dsh-qa-message--user .dsh-qa-message__content{max-width:88%}.dsh-qa-footer{padding-left:10px;padding-right:10px;padding-bottom:max(8px,env(safe-area-inset-bottom))}.dsh-qa-composer{min-height:84px;padding-top:12px}.dsh-qa-quick-questions{flex-wrap:wrap;overflow-x:visible}.dsh-qa-composer__hint{font-size:0}.dsh-qa-composer__hint::after{content:"Enter: отправить";font-size:11px}}
66119
66295
  @media (max-height:480px) and (orientation:landscape){.dsh-qa-header__inner{padding-top:6px}.dsh-qa-header__tabs{display:none}.dsh-qa-transcript__inner{padding-top:12px}.dsh-qa-footer{padding-top:12px}.dsh-qa-composer{min-height:72px;gap:4px;padding-top:8px}}
66120
66296
  @media (prefers-reduced-motion:reduce){.dsh-qa-message__cursor,.dsh-qa-message__pending-spinner,.dsh-qa-work__spinner,.dsh-qa-work-item__spinner,.dsh-qa-command__spinner{animation:none}.dsh-qa-work__chevron{transition:none}.dsh-qa-rail__frame,.dsh-qa-rail__pos,.dsh-qa-rail__mark::before,.dsh-qa-rail__mark--busy::before,.dsh-qa-rail__preview{transition:none;animation:none}}
66121
66297
  ${QA_ADMIN_CONSOLE_STYLES}
@@ -68892,13 +69068,19 @@ ${QA_ROOT_STYLES}`;
68892
69068
  * pair rather than a hard-coded outline: the Host's `focus.css` suppresses an
68893
69069
  * outline under pointer modality at a specificity our rule cannot win, so a
68894
69070
  * hard-coded ring is one that goes missing rather than one that degrades.
69071
+ *
69072
+ * The type scale is the surface's: text the operator reads in order to decide —
69073
+ * section titles, field labels, the values in the controls, the toggle copy —
69074
+ * is 16px, a control's own label and an outcome line are 14px, and only chrome
69075
+ * stays small on purpose: the «Изменено» badge (9px), the status chips (11px),
69076
+ * the monospace data rows (11px) and the footer note (10px).
68895
69077
  */
68896
69078
  const QA_SETTINGS_STYLES = `.qa-card-body,.qa-card-body *{box-sizing:border-box}
68897
69079
  .qa-card-body{display:grid;gap:18px;color:var(--dsw-alias-label-primary)}
68898
69080
  .qa-card-section{display:grid;gap:12px}
68899
69081
  .qa-card-section__title{display:flex;justify-content:space-between;align-items:center;gap:12px}
68900
- .qa-card-section__title h3{font-size:13px;margin:0;display:flex;align-items:center;gap:7px}
68901
- .qa-card-muted{color:var(--dsw-alias-label-tertiary);font-size:12px;margin:0;line-height:1.5}
69082
+ .qa-card-section__title h3{font-size:16px;margin:0;display:flex;align-items:center;gap:7px}
69083
+ .qa-card-muted{color:var(--dsw-alias-label-tertiary);font-size:16px;margin:0;line-height:1.5}
68902
69084
  .qa-card-modified{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-module-platform);border-radius:999px;padding:1px 7px}
68903
69085
  .qa-card-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:0}
68904
69086
  /* One field per row, like the first-party plugin cards: two columns drifted
@@ -68907,22 +69089,22 @@ ${QA_ROOT_STYLES}`;
68907
69089
  .qa-card-grid>*,.qa-card-section>.qa-card-field{padding:12px 0}
68908
69090
  .qa-card-grid>*+*,.qa-card-section>.qa-card-field:not(:first-child){border-top:.5px solid var(--dsw-alias-border-l2)}
68909
69091
  .qa-card-field{display:grid;gap:6px}
68910
- .qa-card-field>span{font-size:13px;color:var(--dsw-alias-label-primary);font-weight:500}
68911
- .qa-card-control{width:100%;height:34px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);padding:0 12px;font:inherit;font-size:13px;line-height:1.5;outline:none}
69092
+ .qa-card-field>span{font-size:16px;color:var(--dsw-alias-label-primary);font-weight:500}
69093
+ .qa-card-control{width:100%;height:34px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);padding:0 12px;font:inherit;font-size:16px;line-height:1.5;outline:none}
68912
69094
  textarea.qa-card-control{height:auto;padding:8px 12px;line-height:1.5;resize:vertical}
68913
69095
  .qa-card-control:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));outline-offset:-1px}
68914
69096
  .qa-card-control:disabled{cursor:default;opacity:.45}
68915
69097
  .qa-card-toggle-row{display:flex;align-items:center;justify-content:space-between;gap:12px}
68916
69098
  .qa-card-toggle-copy{display:grid;gap:2px;min-width:0}
68917
- .qa-card-toggle-copy strong{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary)}
68918
- .qa-card-toggle-copy span{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
69099
+ .qa-card-toggle-copy strong{font-size:16px;font-weight:500;color:var(--dsw-alias-label-primary)}
69100
+ .qa-card-toggle-copy span{font-size:16px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
68919
69101
  .qa-card-toggle{appearance:none;width:34px;height:19px;border-radius:999px;background:var(--dsw-alias-label-dimmed);position:relative;cursor:pointer;transition:.18s;flex:none}
68920
69102
  .qa-card-toggle:after{content:'';position:absolute;top:3px;left:3px;width:13px;height:13px;border-radius:50%;background:var(--dsw-alias-bg-layer-3);transition:.18s}
68921
69103
  .qa-card-toggle:checked{background:var(--dsw-alias-brand-primary)}
68922
69104
  .qa-card-toggle:checked:after{transform:translateX(15px)}
68923
69105
  .qa-card-toggle:disabled{cursor:default;opacity:.45}
68924
69106
  .qa-card-toggle:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));outline-offset:2px}
68925
- .qa-card-btn{height:34px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);padding:0 12px;font:inherit;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap}
69107
+ .qa-card-btn{height:34px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);padding:0 12px;font:inherit;font-size:14px;font-weight:600;cursor:pointer;white-space:nowrap}
68926
69108
  .qa-card-btn:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-interactive-bg-hover)}
68927
69109
  .qa-card-btn:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));outline-offset:-1px}
68928
69110
  .qa-card-btn:disabled{cursor:default;opacity:.45}
@@ -68931,11 +69113,11 @@ textarea.qa-card-control{height:auto;padding:8px 12px;line-height:1.5;resize:ver
68931
69113
  .qa-card-chip{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:3px 10px;font-size:11px;color:var(--dsw-alias-label-secondary)}
68932
69114
  .qa-card-chip b{font-weight:600;color:var(--dsw-alias-label-primary)}
68933
69115
  .qa-card-chip.off{color:var(--dsw-alias-label-tertiary)}
68934
- .qa-card-notice{padding:9px 11px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);border:1px solid var(--dsw-alias-border-l2);font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary)}
69116
+ .qa-card-notice{padding:9px 11px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);border:1px solid var(--dsw-alias-border-l2);font-size:14px;line-height:1.5;color:var(--dsw-alias-label-secondary)}
68935
69117
  .qa-card-notice.warn{border-color:var(--dsw-alias-label-dimmed);color:var(--dsw-alias-label-primary)}
68936
- .qa-card-error{padding:9px 11px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary, #b3261e) 8%, transparent);color:var(--dsw-alias-state-error-primary, #b3261e);font-size:11px;line-height:1.5}
69118
+ .qa-card-error{padding:9px 11px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary, #b3261e) 8%, transparent);color:var(--dsw-alias-state-error-primary, #b3261e);font-size:14px;line-height:1.5}
68937
69119
  .qa-card-advanced{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:0 12px}
68938
- .qa-card-advanced summary{cursor:pointer;padding:11px 0;font-size:12px;font-weight:600}
69120
+ .qa-card-advanced summary{cursor:pointer;padding:11px 0;font-size:16px;font-weight:600}
68939
69121
  .qa-card-advanced summary:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));outline-offset:-1px}
68940
69122
  .qa-card-advanced-content{padding:2px 0 13px;display:grid;gap:10px}
68941
69123
  .qa-card-rows{display:grid;gap:6px}