@yadsh/dsh-qa-surface 0.15.3 → 0.15.5

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 +38 -1
  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 +268 -78
  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 +3 -3
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,32 @@ 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.5";
52965
53076
  const QA_CHANGELOG = [
53077
+ {
53078
+ version: "0.15.5",
53079
+ date: "2026-10-08",
53080
+ sections: [{
53081
+ title: "Исправления",
53082
+ items: ["История версий больше не рисует заголовок раздела, под которым нет ни одного пункта. Текст уже выпущенных версий остаётся ровно тем, что было опубликовано: пустой раздел не вычёркивается из данных, а просто не показывается — заголовок без содержимого читался как чейнджлог, у которого потеряли пункты."]
53083
+ }]
53084
+ },
53085
+ {
53086
+ version: "0.15.4",
53087
+ date: "2026-10-08",
53088
+ sections: [{
53089
+ title: "Исправления",
53090
+ items: [
53091
+ "Мелкие контролы поверхности стали объяснять себя, а основной текст — читаться с 16 px. Чипы «Агенты», «Источники» и «Файлы» при пустом списке просто глушились: серая кнопка выпадала из обхода Tab, не отвечала на наведение и не говорила, что именно кончилось; теперь она остаётся доступной, а причина пустоты читается в её имени и подсказке. «Чат» в шапке выглядел выбранной вкладкой, но не нажимался — когда поверх разговора открыта панель, это кнопка, которая закрывает панель и возвращает к чату, а пока ничего не открыто, он остаётся подписью. На телефоне панель, занимающая весь экран, имела один выход — крестик 26 px: у него и зона попадания размером с палец, и имя, называющее закрываемую вкладку, а Escape закрывает панель, где бы ни стоял курсор; действия у сообщения на этой ширине такие же. Ответ ассистента больше не мельчает ни относительно собственного вопроса, ни на узком экране, и карточки входа и настроек переехали на тот же кегль — ниже 16 px остались только служебные подписи. Заголовок беседы в истории, который не влезал целиком, обрывается многоточием, полное имя даёт подсказка, а поиск ищет по всему имени; третий быстрый вопрос на узкой ширине больше не отсекается — чипы переносятся. Красная строка об отказе слишком большого файла уходит вместе с отправленным сообщением, а не висит над пустым полем; «Рабочий каталог» в панели «Файлы» напечатан один раз; карточка «Ожидает одобрения» называет причину по-русски, а описания команд в палитре по `/` объявляют свой язык, чтобы перевод не звучал как ошибка.",
53092
+ "Кнопка «Источники» в шапке больше не блокируется пустым списком, и пустой список объясняет себя сам. Ход, ответивший по памяти или по тому, что уже было в этом разговоре, в список ничего не добавляет: чтения памяти не узнаёт ни один экстрактор, и это замысел, а не поломка. Прежний интерфейс молчал дважды — под ответом не было чипа, а кнопка стояла заблокированной без счётчика и без причины, и вместе с ней закрывалась единственная поверхность, способная объяснить пустоту. Теперь кнопка жива всегда, когда её показывает развёртывание, а панель на пустом завершённом ходе перечисляет, какие материалы в неё попадают, и говорит, что пустой список — не сбой сбора. Пока ход ещё идёт или делегированный запуск остался должен свои источники, объяснение молчит: об этом уже говорит строка о недоступных источниках, и два противоречащих текста на одном экране не встречаются.",
53093
+ "Остановленный ход больше не притворяется завершённым. Кнопка «стоп» обрывала ответ, а под ходом читалось «Готово за 2 с»: «готово» обещает целый ответ, и человек, остановивший ход, чтобы переформулировать вопрос, видел успех вместо того, что сделал сам. Теперь у остановки свой итог — строка «Остановлено на N с» и плашка о том, что ответ неполный и как получить его целиком. Всё, что успело прийти, остаётся обычным сообщением: его можно скопировать и оценить, так что остановка ничего уже написанного не съедает. У сбоя провайдера по-прежнему свой итог — остановка его не подменяет.",
53094
+ "Палитра слэш-команд в чате, который ещё не начался, называет те же навыки, что и в чате, где уже шёл ответ. Пока за разговором не стоял живой агент, список строился по одному общему allow-list'у развёртывания и не сужался ролью: в него попадал навык, который в этом чате вызвать нельзя — имя подсвечивалось, Enter подставлял его в строку, а в ответ приходил отказ, — и наоборот, доступный навык мог не показаться. Теперь роль учитывается при обоих чтениях, а список, который палитра показывает, и список, которым проверяется введённая строка, выводятся из одного правила, поэтому предложение в палитре и возможность его вызвать больше не могут разойтись.",
53095
+ "Делегированный вызов больше не ставит ход на паузу, которую никто не может снять. Проверяющий, вызванный из чата, не умеет спрашивать человека — харнесс отказывает таким запросам, — но заявка на подтверждение вставала карточкой над композером родителя и держала весь ход: на стенде так ждало то двенадцать минут, то больше часа, пока в основном чате шёл только таймер «думаю». Теперь запрос на подтверждение от дочерней сессии отклоняется сразу там, где он поднят, с объяснением, что дочь спрашивать не может, — модель получает это как находку, а оператор не получает зависший ход. Отдельно закрыто то, что привело к заявке: проверяющему ответа выдаются только инструменты чтения и поиска, а имена удаления файла, записи файлов и запуска команд исключены из его набора — и списком по умолчанию, и проверкой на сборке дочерней сессии.",
53096
+ "Запрос на одобрение, который никто не увидел, перестаёт владеть ходом навсегда: карточка, оставшаяся без ответа, гаснет по истечении ожидания и означает отказ, как и явный отказ оператора. Ничего при этом не разрешается автоматически: ни один путь не одобряет вызов без человека.",
53097
+ "Первый вопрос, заданный в новый черновик, больше не открывается плашкой «Не удалось начать чат.» над разговором, который стенд на самом деле создал. Список чатов браузер держит у себя, и право на сессию хост выдаёт по этому списку: идентификатор, которого в нём ещё нет, отвергается строкой `sessions.retain: unknown session`. Разговор же рождается на сервере — через собственный Remote стенда, — и ответ «создано» успевал дойти до браузера раньше, чем строка нового разговора в его список попала. Первая попытка закрепиться за сессией приходилась ровно на это окно, поэтому экран докладывал об отказе и молчал, что чат тем временем уже стоит в боковом списке строкой, в которую никто не вошёл. Теперь поверхность сначала ждёт, пока каталог браузера назовёт этот идентификатор, и сама просит свежий список, а не ждёт, что успеет прийти раньше — собственный кадр сервера или его очередная выгрузка. Ждёт недолго, на одну выгрузку: строка приходит пушем или ответом на этот же запрос, а целый таймаут ожидания лег бы вторым слоем поверх тех, что идут следом, и чат, которому не открыться, держал бы экран вдвое дольше. Если строка так и не пришла, в журнале остаётся отказ самого хоста, а не «истекло ожидание», которого никто не заказывал; отказ хоста для чата, который список уже называет, остаётся самим собой — поверхность не ждёт строку, которая на месте, и не рисует гонку там, где её не было. Отказ самой выгрузки тоже слышен: чат, потерянный в таком виде, читается как медленный каталог, а на деле это нечитаемый каталог. Случается это не на каждом входе, поэтому обе половины гонки пишутся в журнал браузера: отказ удержания — с его моментом, и пришедшая следом строка каталога — с тем, сколько пришлось ждать. Раунд, который чат всё-таки потерял, сможет сказать, на какой стороне создания он его потерял. Тот же порядок и для чата, который поверхность возвращает при открытии: он тоже сначала ждёт, пока список его назовёт, и только потом закрепляется за сессией."
53098
+ ]
53099
+ }]
53100
+ },
52966
53101
  {
52967
53102
  version: "0.15.3",
52968
53103
  date: "2026-10-08",
@@ -53521,12 +53656,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
53521
53656
  className: "dsh-qa-changelog__date",
53522
53657
  children: entry.date
53523
53658
  }),
53524
- entry.version === "0.15.3" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53659
+ entry.version === "0.15.5" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53525
53660
  className: "dsh-qa-changelog__current",
53526
53661
  children: "текущая"
53527
53662
  }) : null
53528
53663
  ]
53529
- }), entry.sections.map((section, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
53664
+ }), entry.sections.filter((section) => section.items.some((item) => item.trim() !== "")).map((section, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
53530
53665
  className: "dsh-qa-changelog__section",
53531
53666
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
53532
53667
  className: "dsh-qa-changelog__section-title",
@@ -53819,12 +53954,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
53819
53954
  const summary = byId[id];
53820
53955
  if (summary === void 0 || isDelegatedSession(summary)) continue;
53821
53956
  const ownerName = ownerNameOf?.(id);
53957
+ const label = summary.blank ? "Новый чат" : summary.displayTitle;
53958
+ const full = summary.title;
53959
+ const clipped = !summary.blank && full !== void 0 && full.length > label.length;
53822
53960
  rows.push({
53823
53961
  id,
53824
- title: summary.blank ? "Новый чат" : summary.displayTitle,
53962
+ title: clipped ? `${label.trimEnd()}…` : label,
53825
53963
  running: summary.running,
53826
53964
  active: id === activeId,
53827
53965
  meta: relativeTime(summary.updatedAt, now),
53966
+ ...clipped && full !== void 0 ? { fullName: full } : {},
53828
53967
  updatedAt: summary.updatedAt,
53829
53968
  ...ownerName === void 0 ? {} : { ownerName }
53830
53969
  });
@@ -53907,7 +54046,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
53907
54046
  });
53908
54047
  }
53909
54048
  function rowMatches(row, query) {
53910
- return row.title.toLowerCase().includes(query) || row.ownerName?.toLowerCase().includes(query) === true;
54049
+ return row.title.toLowerCase().includes(query) || (row.fullName?.toLowerCase().includes(query) ?? false) || row.ownerName?.toLowerCase().includes(query) === true;
53911
54050
  }
53912
54051
  /**
53913
54052
  * How each chat is named by the controls acting on it, keyed by id. A title is
@@ -54014,6 +54153,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54014
54153
  className: "dsh-qa-sidebar__item-main",
54015
54154
  "data-testid": "qa-surface-sidebar-item-open",
54016
54155
  "aria-current": row.active ? "true" : void 0,
54156
+ title: row.fullName ?? row.title,
54017
54157
  onClick: () => {
54018
54158
  props.onSwitch(row.id);
54019
54159
  closeDrawer();
@@ -54811,22 +54951,23 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54811
54951
  });
54812
54952
  }
54813
54953
  const crumbs = crumbsOf(directory);
54954
+ const trail = directory === "" ? crumbs.slice(1) : crumbs;
54814
54955
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
54815
54956
  className: "dsh-qa-ws",
54816
54957
  "data-testid": "qa-surface-workspace",
54817
54958
  children: [
54818
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
54959
+ trail.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
54819
54960
  className: "dsh-qa-ws__crumbs",
54820
54961
  "data-testid": "qa-surface-workspace-crumbs",
54821
54962
  "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", {
54963
+ children: trail.map((crumb, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [index > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
54823
54964
  "aria-hidden": "true",
54824
54965
  children: "/"
54825
54966
  }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
54826
54967
  type: "button",
54827
54968
  className: "dsh-qa-ws__crumb",
54828
54969
  "data-testid": "qa-surface-workspace-crumb",
54829
- "aria-current": index === crumbs.length - 1 ? "true" : void 0,
54970
+ "aria-current": index === trail.length - 1 ? "true" : void 0,
54830
54971
  onClick: () => setDirectory(crumb.path),
54831
54972
  children: crumb.label
54832
54973
  })] }, crumb.path))
@@ -54983,9 +55124,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54983
55124
  * The chat's right rail: a fixed column whose top edge is the tab strip,
54984
55125
  * mirroring the Harness right Sidebar's chrome. Tabs are plain chips with
54985
55126
  * counts; the strip's last control collapses the whole column.
55127
+ *
55128
+ * On the phone layout the column is the whole surface, so the strip's control
55129
+ * is the only way back and Escape has to answer from anywhere on the page —
55130
+ * the focus may well still sit in the composer behind the panel. A dialog above
55131
+ * the rail keeps the key: the innermost thing on the page is what the reader
55132
+ * means to dismiss.
54986
55133
  */
54987
55134
  function QaRightRail({ tabs, activeTab, onTabSelect, onClose }) {
54988
55135
  const active = tabs.find((tab) => tab.id === activeTab) ?? tabs[0];
55136
+ (0, react.useEffect)(() => {
55137
+ const onKey = (event) => {
55138
+ if (event.key !== "Escape" || event.defaultPrevented) return;
55139
+ if (isQaModalOpen()) return;
55140
+ onClose();
55141
+ };
55142
+ window.addEventListener("keydown", onKey);
55143
+ return () => window.removeEventListener("keydown", onKey);
55144
+ }, [onClose]);
54989
55145
  if (active === void 0) return null;
54990
55146
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
54991
55147
  className: "dsh-qa-panel",
@@ -55011,8 +55167,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
55011
55167
  type: "button",
55012
55168
  className: "dsh-qa-panel__close",
55013
55169
  "data-testid": "qa-surface-rail-close",
55014
- "aria-label": "Закрыть панель",
55015
- title: "Закрыть",
55170
+ "aria-label": `Закрыть панель «${active.title}» и вернуться к чату`,
55171
+ title: `Закрыть панель «${active.title}»`,
55016
55172
  onClick: onClose,
55017
55173
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
55018
55174
  viewBox: "0 0 16 16",
@@ -55316,39 +55472,47 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
55316
55472
  }) : null, detail === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
55317
55473
  className: "dsh-qa-sources__list",
55318
55474
  "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}`,
55475
+ children: [
55476
+ !complete ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
55477
+ className: "dsh-qa-sources__incomplete",
55478
+ "data-testid": "qa-sources-incomplete",
55332
55479
  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
55480
+ "Некоторые источники делегированных запусков недоступны (",
55481
+ incompleteOrigins?.length ?? 1,
55482
+ ")."
55349
55483
  ]
55350
- }, group.key);
55351
- })]
55484
+ }) : null,
55485
+ complete && sources.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
55486
+ className: "dsh-qa-sources__empty",
55487
+ "data-testid": "qa-sources-empty",
55488
+ children: "Здесь появляются материалы, которые ход читал или открывал сам: файл рабочего каталога, веб-страница, выдача веб-поиска, ответ Jira, Confluence или базы знаний. Ответ, собранный по памяти или по тому, что уже было в этом разговоре, сюда ничего не добавляет — поэтому пустой список после завершённого хода означает именно это, а не сбой сбора."
55489
+ }) : null,
55490
+ groups.map((group) => {
55491
+ const visible = expanded.has(group.key) ? group.sources : group.sources.slice(0, display.maxInitiallyVisiblePerGroup);
55492
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
55493
+ className: "dsh-qa-sources__group",
55494
+ "data-testid": `qa-sources-group-${group.key}`,
55495
+ children: [
55496
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", { children: [group.label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
55497
+ "data-testid": "qa-sources-group-count",
55498
+ children: group.sources.length
55499
+ })] }),
55500
+ visible.map((source) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaSourceCard, {
55501
+ source,
55502
+ showOriginBadges: display.showOriginBadges,
55503
+ onOpen: () => setDetailId(source.id)
55504
+ }, source.id)),
55505
+ visible.length < group.sources.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
55506
+ type: "button",
55507
+ className: "dsh-qa-sources__more",
55508
+ "data-testid": "qa-sources-more",
55509
+ onClick: () => setExpanded(/* @__PURE__ */ new Set([...expanded, group.key])),
55510
+ children: ["Показать ещё ", group.sources.length - visible.length]
55511
+ }) : null
55512
+ ]
55513
+ }, group.key);
55514
+ })
55515
+ ]
55352
55516
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaSourceDetail, {
55353
55517
  source: detail,
55354
55518
  sessionId,
@@ -64658,6 +64822,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
64658
64822
  config.ui.showHeader ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaHeader, {
64659
64823
  logoUrl: config.branding.logoUrl,
64660
64824
  title: conversationTitle,
64825
+ onBackToChat: rail.railOpen || agentsOpen ? () => {
64826
+ rail.close();
64827
+ setAgentsOpen(false);
64828
+ } : void 0,
64661
64829
  viewingSubagent: state.viewingSubagent !== null,
64662
64830
  onCloseSubagent: handleCloseSubagent,
64663
64831
  roleSelector: access === void 0 || selectedSubrole === null || previewing ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaRoleSelector, {
@@ -65304,17 +65472,17 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
65304
65472
  .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
65473
  .dsh-qa-auth__brand h1{margin:0;color:var(--dsw-alias-label-primary);font-size:19px;font-weight:600;line-height:26px}
65306
65474
  .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}
65475
+ .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
65476
  .dsh-qa-auth__tab:hover{color:var(--dsw-alias-label-primary)}
65309
65477
  .dsh-qa-auth__tab--active{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
65310
65478
  .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
65479
  .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}
65480
+ .dsh-qa-auth__field span{color:var(--dsw-alias-label-secondary);font-size:16px;line-height:22px}
65481
+ .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
65482
  .dsh-qa-auth__field input:focus{outline:none;border-color:var(--dsh-qa-brand)}
65315
65483
  .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}
65484
+ .dsh-qa-auth__error{margin:0;color:var(--dsh-qa-error);font-size:16px;line-height:22px}
65485
+ .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
65486
  .dsh-qa-auth__submit:hover{background:var(--dsh-qa-accent-hover)}
65319
65487
  .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
65488
  .dsh-qa-auth__submit:disabled{opacity:.6;cursor:default}
@@ -65476,8 +65644,8 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
65476
65644
  .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
65645
  .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
65646
  .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}
65647
+ .dsh-qa-header__agents:hover:not(:disabled):not([aria-disabled="true"]){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65648
+ .dsh-qa-header__agents:disabled,.dsh-qa-header__agents[aria-disabled="true"]{opacity:.45;cursor:default}
65481
65649
  .dsh-qa-header__agents[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65482
65650
  .dsh-qa-header__agents svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}
65483
65651
  .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 +65680,12 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
65512
65680
  .dsh-qa-agents__open{flex:none;color:var(--dsh-qa-accent);font-size:12px;font-weight:500;line-height:18px}
65513
65681
  .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
65682
  .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
65683
  .dsh-qa-header__sources[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65517
65684
  .dsh-qa-header__sources svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}
65518
65685
  .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
65686
  .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}
65687
+ .dsh-qa-header__files:hover:not(:disabled):not([aria-disabled="true"]){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65688
+ .dsh-qa-header__files:disabled,.dsh-qa-header__files[aria-disabled="true"]{opacity:.45;cursor:default}
65522
65689
  .dsh-qa-header__files[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
65523
65690
  .dsh-qa-header__files svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}
65524
65691
  .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 +65756,11 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
65589
65756
  .dsh-qa-header__reset:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
65590
65757
  .dsh-qa-header__reset:disabled{opacity:.45;cursor:default}
65591
65758
  .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)}
65759
+ .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}
65760
+ .dsh-qa-header__tab{appearance:none;background:0 0;border:0;cursor:pointer;font-family:inherit}
65761
+ .dsh-qa-header__tab:hover{text-decoration:underline}
65762
+ .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}
65763
+ .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
65764
  .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
65765
  .dsh-qa-extension-panel__resizer:hover,.dsh-qa-extension-panel__resizer:focus-visible{background:var(--dsh-qa-accent);outline:0}
65596
65766
  .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 +65865,7 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
65695
65865
  span.dsh-qa-files__thumb{display:inline-block}
65696
65866
  img.dsh-qa-files__thumb{object-fit:cover;background:var(--dsw-alias-bg-layer-3)}
65697
65867
  .dsh-qa-sources__list{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:0 10px 12px}
65868
+ .dsh-qa-sources__empty{margin:4px 2px 12px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px}
65698
65869
  .dsh-qa-sources__group{margin:0 0 14px}
65699
65870
  .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
65871
  .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 +65924,20 @@ a.dsh-qa-sources__title:hover{text-decoration:underline}
65753
65924
  .dsh-qa-message--assistant{align-items:flex-start;margin-right:auto}
65754
65925
  .dsh-qa-message--work{align-items:flex-start;margin:-4px 0 14px}
65755
65926
  .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}
65927
+ /*
65928
+ The type scale of this surface: what the reader is asked to read is 16px, and
65929
+ nothing that carries a decision sits below it — the assistant's answer, the
65930
+ operator's own draft and the composer's status line all match the 16px the
65931
+ user bubble is drawn at, so a reply never reads smaller than the question.
65932
+
65933
+ Deliberate exceptions, all of them chrome rather than content: the header row
65934
+ and the tab marker (13px), the panel tab chips and their counts (13px/10px),
65935
+ message meta and timestamps (11-12px), the quick-question chips (13px),
65936
+ system notices and turn lines (13px), code and workspace file names
65937
+ (monospace, 11-13px), the admin console's tables and badges (10-12px), the
65938
+ composer's key hint (11px) and the welcome lede (14px).
65939
+ */
65940
+ .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
65941
  .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
65942
  .dsh-qa-message[data-status="pending"] .dsh-qa-message__content{opacity:.78}
65759
65943
  .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 +66010,7 @@ a.dsh-qa-srcref:hover,button.dsh-qa-srcref:hover{border-color:var(--dsw-alias-la
65826
66010
  properties, so a font-size preference or a light/dark switch moves both
65827
66011
  surfaces together. A container that needs a denser scale (the source
65828
66012
  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)}
66013
+ .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
66014
  .dsh-qa-md>:first-child{margin-top:0}
65831
66015
  .dsh-qa-md>:last-child{margin-bottom:0}
65832
66016
  .dsh-qa-md p{margin:16px 0}
@@ -66115,7 +66299,7 @@ a.dsh-qa-srcref:hover,button.dsh-qa-srcref:hover{border-color:var(--dsw-alias-la
66115
66299
  row — title, role, palette, action cluster — is wider than the viewport with
66116
66300
  every child refusing to shrink, so it wraps instead of pushing «Настройки»
66117
66301
  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}}
66302
+ @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
66303
  @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
66304
  @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
66305
  ${QA_ADMIN_CONSOLE_STYLES}
@@ -68892,13 +69076,19 @@ ${QA_ROOT_STYLES}`;
68892
69076
  * pair rather than a hard-coded outline: the Host's `focus.css` suppresses an
68893
69077
  * outline under pointer modality at a specificity our rule cannot win, so a
68894
69078
  * hard-coded ring is one that goes missing rather than one that degrades.
69079
+ *
69080
+ * The type scale is the surface's: text the operator reads in order to decide —
69081
+ * section titles, field labels, the values in the controls, the toggle copy —
69082
+ * is 16px, a control's own label and an outcome line are 14px, and only chrome
69083
+ * stays small on purpose: the «Изменено» badge (9px), the status chips (11px),
69084
+ * the monospace data rows (11px) and the footer note (10px).
68895
69085
  */
68896
69086
  const QA_SETTINGS_STYLES = `.qa-card-body,.qa-card-body *{box-sizing:border-box}
68897
69087
  .qa-card-body{display:grid;gap:18px;color:var(--dsw-alias-label-primary)}
68898
69088
  .qa-card-section{display:grid;gap:12px}
68899
69089
  .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}
69090
+ .qa-card-section__title h3{font-size:16px;margin:0;display:flex;align-items:center;gap:7px}
69091
+ .qa-card-muted{color:var(--dsw-alias-label-tertiary);font-size:16px;margin:0;line-height:1.5}
68902
69092
  .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
69093
  .qa-card-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:0}
68904
69094
  /* One field per row, like the first-party plugin cards: two columns drifted
@@ -68907,22 +69097,22 @@ ${QA_ROOT_STYLES}`;
68907
69097
  .qa-card-grid>*,.qa-card-section>.qa-card-field{padding:12px 0}
68908
69098
  .qa-card-grid>*+*,.qa-card-section>.qa-card-field:not(:first-child){border-top:.5px solid var(--dsw-alias-border-l2)}
68909
69099
  .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}
69100
+ .qa-card-field>span{font-size:16px;color:var(--dsw-alias-label-primary);font-weight:500}
69101
+ .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
69102
  textarea.qa-card-control{height:auto;padding:8px 12px;line-height:1.5;resize:vertical}
68913
69103
  .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
69104
  .qa-card-control:disabled{cursor:default;opacity:.45}
68915
69105
  .qa-card-toggle-row{display:flex;align-items:center;justify-content:space-between;gap:12px}
68916
69106
  .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)}
69107
+ .qa-card-toggle-copy strong{font-size:16px;font-weight:500;color:var(--dsw-alias-label-primary)}
69108
+ .qa-card-toggle-copy span{font-size:16px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
68919
69109
  .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
69110
  .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
69111
  .qa-card-toggle:checked{background:var(--dsw-alias-brand-primary)}
68922
69112
  .qa-card-toggle:checked:after{transform:translateX(15px)}
68923
69113
  .qa-card-toggle:disabled{cursor:default;opacity:.45}
68924
69114
  .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}
69115
+ .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
69116
  .qa-card-btn:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-interactive-bg-hover)}
68927
69117
  .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
69118
  .qa-card-btn:disabled{cursor:default;opacity:.45}
@@ -68931,11 +69121,11 @@ textarea.qa-card-control{height:auto;padding:8px 12px;line-height:1.5;resize:ver
68931
69121
  .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
69122
  .qa-card-chip b{font-weight:600;color:var(--dsw-alias-label-primary)}
68933
69123
  .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)}
69124
+ .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
69125
  .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}
69126
+ .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
69127
  .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}
69128
+ .qa-card-advanced summary{cursor:pointer;padding:11px 0;font-size:16px;font-weight:600}
68939
69129
  .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
69130
  .qa-card-advanced-content{padding:2px 0 13px;display:grid;gap:10px}
68941
69131
  .qa-card-rows{display:grid;gap:6px}