@yadsh/dsh-qa-surface 0.7.0 → 0.7.2

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 (41) hide show
  1. package/README.md +27 -0
  2. package/lib/client/QaSurface.js +6 -3
  3. package/lib/client/QaSurface.js.map +1 -1
  4. package/lib/client/admin/routes.js +18 -2
  5. package/lib/client/admin/routes.js.map +1 -1
  6. package/lib/client/components/QaChangelog.js +36 -0
  7. package/lib/client/components/QaChangelog.js.map +1 -1
  8. package/lib/client/components/QaHeader.js +2 -2
  9. package/lib/client/components/QaHeader.js.map +1 -1
  10. package/lib/client/index.js +15 -0
  11. package/lib/client/index.js.map +1 -1
  12. package/lib/client/session-sources.js +20 -1
  13. package/lib/client/session-sources.js.map +1 -1
  14. package/lib/client/settings-extensions/index.js +1 -0
  15. package/lib/client/settings-extensions/index.js.map +1 -1
  16. package/lib/client/settings-extensions/user-session.js +62 -0
  17. package/lib/client/settings-extensions/user-session.js.map +1 -0
  18. package/lib/client/styles.js +10 -7
  19. package/lib/client/styles.js.map +1 -1
  20. package/lib/client/user-settings/GeneralSettingsPage.js +6 -5
  21. package/lib/client/user-settings/GeneralSettingsPage.js.map +1 -1
  22. package/lib/client/user-settings/SkillToolPicker.js +1 -1
  23. package/lib/client/user-settings/SkillToolPicker.js.map +1 -1
  24. package/lib/client/user-settings/StartersSettingsPage.js +10 -10
  25. package/lib/client/user-settings/StartersSettingsPage.js.map +1 -1
  26. package/lib/client.js +212 -49
  27. package/lib/client.js.map +1 -1
  28. package/lib/enforcement/tool-grants.js +35 -6
  29. package/lib/enforcement/tool-grants.js.map +1 -1
  30. package/lib/provenance/extractors.js +26 -1
  31. package/lib/provenance/extractors.js.map +1 -1
  32. package/lib/secure-session.js +10 -1
  33. package/lib/secure-session.js.map +1 -1
  34. package/lib/types/client/admin/routes.d.ts +10 -0
  35. package/lib/types/client/components/QaHeader.d.ts +11 -1
  36. package/lib/types/client/session-sources.d.ts +9 -0
  37. package/lib/types/client/settings-extensions/index.d.ts +2 -0
  38. package/lib/types/client/settings-extensions/user-session.d.ts +59 -0
  39. package/lib/types/client/user-settings/GeneralSettingsPage.d.ts +5 -4
  40. package/lib/types/enforcement/tool-grants.d.ts +17 -3
  41. package/package.json +3 -3
package/lib/client.js CHANGED
@@ -11238,10 +11238,23 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11238
11238
  this.refreshing = false;
11239
11239
  this.refusal = "";
11240
11240
  }
11241
+ /**
11242
+ * The Host's bundles win the turns they share with the projection, with one
11243
+ * exception: a bundle that collected nothing never erases a turn the
11244
+ * transcript accounts for. Both channels read the same durable tool results,
11245
+ * so a turn the projection filled and the Host reported empty means the
11246
+ * Host's collection missed that turn, not that the answer had no sources —
11247
+ * and without this guard such a turn loses the sources the reader could see
11248
+ * while it ran the moment the Host answers for it.
11249
+ */
11241
11250
  merge(projected) {
11242
11251
  const merged = /* @__PURE__ */ new Map();
11243
11252
  for (const bundle of projected) merged.set(bundle.turn, bundle);
11244
- for (const bundle of this.bundles) merged.set(bundle.turn, bundle);
11253
+ for (const bundle of this.bundles) {
11254
+ const collected = merged.get(bundle.turn);
11255
+ if (collected !== void 0 && carriesNothing(bundle) && !carriesNothing(collected)) continue;
11256
+ merged.set(bundle.turn, bundle);
11257
+ }
11245
11258
  return [...merged.values()].sort((left, right) => left.turn - right.turn);
11246
11259
  }
11247
11260
  /**
@@ -11282,6 +11295,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11282
11295
  }
11283
11296
  }
11284
11297
  };
11298
+ /** True when a bundle holds no source at all, promoted or discovered. */
11299
+ function carriesNothing(bundle) {
11300
+ return bundle.sources.length === 0 && (bundle.discovered?.length ?? 0) === 0;
11301
+ }
11285
11302
  /** Stable text for one refusal, so the same failure is not reported twice. */
11286
11303
  function refusalKey(error) {
11287
11304
  if (typeof error === "string") return error;
@@ -12085,6 +12102,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
12085
12102
  }
12086
12103
  };
12087
12104
  }
12105
+ /**
12106
+ * The Host's web tool opens its result with an envelope — `Fetched <url>
12107
+ * (HTTP <code>)`, then the untrusted-content notice — before the page itself.
12108
+ * Both lines are dropped here, and so is a first line that is the page's own
12109
+ * address: the card already prints the target URL, so a snippet drawn from
12110
+ * either of them would only repeat what the reader can already see.
12111
+ */
12112
+ const WEB_FETCH_ENVELOPE = /^(?:Fetched\s+\S+\s+\(HTTP\s+\d+\)|External web content follows\..*)$/u;
12113
+ function fetchedSnippet(value, uri) {
12114
+ if (value === void 0) return void 0;
12115
+ const target = uri.replace(/\/+$/u, "");
12116
+ return snippet(value.split(/\r?\n/u).filter((line) => {
12117
+ const text = line.trim();
12118
+ if (text === "") return true;
12119
+ if (WEB_FETCH_ENVELOPE.test(text)) return false;
12120
+ return text.replace(/\/+$/u, "") !== target;
12121
+ }).join("\n"));
12122
+ }
12088
12123
  function createWebFetchExtractor(urlOptions = {}) {
12089
12124
  return {
12090
12125
  id: "dsh-web-fetch",
@@ -12100,7 +12135,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
12100
12135
  if (uri === null) return [];
12101
12136
  const explicitTitle = typeof meta?.title === "string" && meta.title.trim() !== "" ? meta.title.trim() : void 0;
12102
12137
  const titleExplicit = explicitTitle !== void 0;
12103
- const sourceSnippet = snippet(firstText(context.result));
12138
+ const sourceSnippet = fetchedSnippet(firstText(context.result), uri);
12104
12139
  return [{
12105
12140
  id: `web:${uri}`,
12106
12141
  kind: "web",
@@ -14862,7 +14897,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14862
14897
  ]
14863
14898
  });
14864
14899
  }
14865
- function QaHeader({ logoUrl, title, viewingSubagent, onCloseSubagent, agentPreset, roleSelector, administration, agentCount, agentsOpen, onToggleAgents, sourcesVisible, sourcesCount, sourcesComplete, sourcesOpen, onOpenSources, fileCount, filesOpen, onOpenFiles, panelLauncher, showReset, resetDisabled, onReset }) {
14900
+ function QaHeader({ logoUrl, title, viewingSubagent, onCloseSubagent, agentPreset, roleSelector, administration, agentCount, agentsOpen, onToggleAgents, sourcesVisible, sourcesCount, sourcesComplete, sourcesOpen, onOpenSources, fileCount, filesOpen, onOpenFiles, settings, panelLauncher, showReset, resetDisabled, onReset }) {
14866
14901
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
14867
14902
  className: "dsh-qa-header",
14868
14903
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -14988,7 +15023,22 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14988
15023
  onClick: administration.onOpen,
14989
15024
  children: "Администрирование"
14990
15025
  }),
14991
- panelLauncher
15026
+ panelLauncher,
15027
+ settings === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
15028
+ type: "button",
15029
+ className: "dsh-qa-header__settings",
15030
+ title: settings.label,
15031
+ onClick: settings.onOpen,
15032
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
15033
+ viewBox: "0 0 16 16",
15034
+ "aria-hidden": "true",
15035
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
15036
+ cx: "8",
15037
+ cy: "8",
15038
+ r: "2.2"
15039
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 1.8v2M8 12.2v2M1.8 8h2M12.2 8h2M3.6 3.6l1.4 1.4M11 11l1.4 1.4M3.6 12.4 5 11M11 5l1.4-1.4" })]
15040
+ }), "Настройки"]
15041
+ })
14992
15042
  ]
14993
15043
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
14994
15044
  className: "dsh-qa-header__tabs",
@@ -16572,8 +16622,35 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16572
16622
  }
16573
16623
  //#endregion
16574
16624
  //#region src/client/components/QaChangelog.tsx
16575
- const QA_VERSION = "0.7.0";
16625
+ const QA_VERSION = "0.7.2";
16576
16626
  const QA_CHANGELOG = [
16627
+ {
16628
+ version: "0.7.2",
16629
+ date: "2026-09-16",
16630
+ sections: [{
16631
+ title: "Исправления",
16632
+ items: [
16633
+ "Чаты снова открываются: сервер отказывал в допуске, потому что набор инструментов роли называл служебный инструмент проверки активации, а такой инструмент агент регистрирует у себя сам — в ограничение его подставить нельзя. Теперь ограничение ставится только на наследуемые инструменты, служебный остаётся доступным, и выдача инструментов навыком больше не теряется молча из-за того же имени.",
16634
+ "Источники, появившиеся во время ответа, больше не исчезают после его завершения. Пустой ответ сервера об источниках хода больше не затирает то, что браузер прочитал из самой истории чата: список источников, видимый во время работы агента, остаётся рядом с ответом и после её окончания.",
16635
+ "В карточке скачанной страницы больше не повторяется её адрес: вместо служебной строки веб-инструмента «Fetched … (HTTP 200)» показывается начало текста самой страницы.",
16636
+ "В разделе «Быстрые сообщения» поля «Название» и «Промпт» выровнены по одной правой границе, а кнопка удаления подсказки больше не выглядит пустым полем ввода.",
16637
+ "Список инструментов в редакторе навыка стал компактнее и выше: длинные описания занимают две строки, а полный текст виден во всплывающей подсказке.",
16638
+ "Из раздела «Общие» убран пункт «Персональные интеграции и внешние системы» из списка будущих возможностей: интеграции уже доступны отдельным разделом настроек.",
16639
+ "Настройки пользователя больше не теряются в деплоях без списка чатов: если боковая панель с чатами скрыта (это поведение по умолчанию), кнопка «Настройки» появляется в шапке рядом с «Файлами», а при включённой панели вход остаётся прежним — по своему имени внизу списка чатов."
16640
+ ]
16641
+ }]
16642
+ },
16643
+ {
16644
+ version: "0.7.1",
16645
+ date: "2026-09-16",
16646
+ sections: [{
16647
+ title: "Исправления",
16648
+ items: ["В консоли администратора открываются все разделы, а не только «Обзор»: «Пользователи», «Саброли», «Общие возможности», «Навыки», «Разговоры», «Очередь разбора», «Обратная связь», «Аналитика» и «Аудит». Раньше переход в любой раздел закрывал консоль и возвращал в чат, а ссылка на конкретный разговор или сообщение вместо разбора открывала обычный чат. Каждый такой переход ещё и оставлял в стенде пустой чат, попадавший в счётчики разговоров; переходы внутри консоли чатов больше не создают."]
16649
+ }, {
16650
+ title: "Прочее",
16651
+ items: ["Внутренняя доработка для плагинов стенда: карточка плагина в настройках DSH теперь может узнать, вошёл ли пользователь в QA, и работать с его подключениями от его имени — на этом подключения к рабочим сервисам показываются и в списке плагинов. Интерфейс QA это не меняет: вход, чаты и настройки работают как раньше, а без входа карточка подключений ничего не показывает."]
16652
+ }]
16653
+ },
16577
16654
  {
16578
16655
  version: "0.7.0",
16579
16656
  date: "2026-09-16",
@@ -16779,7 +16856,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16779
16856
  className: "dsh-qa-changelog__date",
16780
16857
  children: entry.date
16781
16858
  }),
16782
- entry.version === "0.7.0" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16859
+ entry.version === "0.7.2" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16783
16860
  className: "dsh-qa-changelog__current",
16784
16861
  children: "текущая"
16785
16862
  }) : null
@@ -18100,10 +18177,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18100
18177
  //#region src/client/user-settings/GeneralSettingsPage.tsx
18101
18178
  /**
18102
18179
  * Placeholder with a purpose: the section exists so the dialog has a home for
18103
- * the user-scoped preferences that follow (notifications, integrations,
18104
- * privacy), and so the thing it must not become is written down — deployment
18105
- * and chat policy live in the Host settings, not here. It shows the account
18106
- * facts a user can act on today and nothing invented.
18180
+ * the user-scoped preferences that follow (notifications, privacy), and so the
18181
+ * thing it must not become is written down — deployment and chat policy live in
18182
+ * the Host settings, not here. It shows the account facts a user can act on
18183
+ * today and nothing invented, and it names only what is still missing: a
18184
+ * preference that already has its own tab does not belong on this list.
18107
18185
  */
18108
18186
  function QaGeneralSettingsPage(props) {
18109
18187
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -18141,11 +18219,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18141
18219
  title: "Скоро здесь",
18142
18220
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
18143
18221
  className: "dsh-qa-settings__list",
18144
- children: [
18145
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "Настройки уведомлений" }),
18146
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "Персональные интеграции и внешние системы" }),
18147
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "Приватность и хранение данных" })
18148
- ]
18222
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "Настройки уведомлений" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "Приватность и хранение данных" })]
18149
18223
  })
18150
18224
  })
18151
18225
  ]
@@ -18645,6 +18719,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18645
18719
  }),
18646
18720
  tool.description === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
18647
18721
  className: "dsh-qa-toolpicker__description",
18722
+ title: tool.description,
18648
18723
  children: tool.description
18649
18724
  })
18650
18725
  ]
@@ -19262,9 +19337,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19262
19337
  className: "dsh-qa-starters__list",
19263
19338
  children: rows.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19264
19339
  className: "dsh-qa-starters__item",
19265
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19266
- className: "dsh-qa-starters__item-head",
19267
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
19340
+ children: [
19341
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
19268
19342
  className: "dsh-qa-settings__field",
19269
19343
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
19270
19344
  className: "dsh-qa-settings__field-label",
@@ -19282,7 +19356,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19282
19356
  }
19283
19357
  })]
19284
19358
  })
19285
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
19359
+ }),
19360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
19286
19361
  type: "button",
19287
19362
  className: "dsh-qa-starters__remove",
19288
19363
  "aria-label": `Удалить «${row.label.trim() || "без названия"}»`,
@@ -19296,27 +19371,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19296
19371
  "aria-hidden": "true",
19297
19372
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m4 4 8 8m0-8-8 8" })
19298
19373
  })
19299
- })]
19300
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
19301
- className: "dsh-qa-settings__field",
19302
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
19303
- className: "dsh-qa-settings__field-label",
19304
- children: ["Промпт", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
19305
- rows: 2,
19306
- value: row.prompt,
19307
- maxLength: QA_STARTERS_MAX_PROMPT,
19308
- placeholder: "Что уйдёт в чат при нажатии",
19309
- onChange: (event) => {
19310
- const value = event.currentTarget.value;
19311
- setRows((current) => current.map((candidate, at) => at === index ? {
19312
- ...candidate,
19313
- prompt: value
19314
- } : candidate));
19315
- setSaved(false);
19316
- }
19317
- })]
19374
+ }),
19375
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
19376
+ className: "dsh-qa-settings__field",
19377
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
19378
+ className: "dsh-qa-settings__field-label",
19379
+ children: ["Промпт", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
19380
+ rows: 2,
19381
+ value: row.prompt,
19382
+ maxLength: QA_STARTERS_MAX_PROMPT,
19383
+ placeholder: "Что уйдёт в чат при нажатии",
19384
+ onChange: (event) => {
19385
+ const value = event.currentTarget.value;
19386
+ setRows((current) => current.map((candidate, at) => at === index ? {
19387
+ ...candidate,
19388
+ prompt: value
19389
+ } : candidate));
19390
+ setSaved(false);
19391
+ }
19392
+ })]
19393
+ })
19318
19394
  })
19319
- })]
19395
+ ]
19320
19396
  }, index))
19321
19397
  }),
19322
19398
  rows.length >= 12 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
@@ -19967,9 +20043,25 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19967
20043
  }
19968
20044
  //#endregion
19969
20045
  //#region src/client/admin/routes.ts
20046
+ /** The console's own base path under the surface route. */
20047
+ function adminBasePath(routePath) {
20048
+ return `${routePath === "/" ? "" : routePath}/admin`;
20049
+ }
20050
+ /**
20051
+ * Whether a pathname addresses the console at all. Sections and entities each
20052
+ * own a path under the base ({@link adminPath}), and the surface hosting the
20053
+ * console must stay the console while any of them is open, so the test is by
20054
+ * prefix. An equality test against the base alone unmounts the console on the
20055
+ * first navigation inside it, and every deep link lands in the chat instead.
20056
+ */
20057
+ function isAdminPath(pathname, routePath) {
20058
+ const base = adminBasePath(routePath);
20059
+ const trimmed = pathname.replace(/\/+$/u, "");
20060
+ return trimmed === base || trimmed.startsWith(`${base}/`);
20061
+ }
19970
20062
  /** Path segments after `/qa/admin`, without the leading slash. */
19971
20063
  function adminSegments(pathname, routePath) {
19972
- const base = `${routePath === "/" ? "" : routePath}/admin`;
20064
+ const base = adminBasePath(routePath);
19973
20065
  return (pathname.startsWith(base) ? pathname.slice(base.length) : "").split("/").map((segment) => segment.trim()).filter((segment) => segment !== "");
19974
20066
  }
19975
20067
  /**
@@ -20009,7 +20101,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20009
20101
  }
20010
20102
  /** The pathname for one console route; the inverse of {@link parseAdminRoute}. */
20011
20103
  function adminPath(routePath, route, search = "") {
20012
- return `${`${routePath === "/" ? "" : routePath}/admin`}${(() => {
20104
+ return `${adminBasePath(routePath)}${(() => {
20013
20105
  switch (route.page) {
20014
20106
  case "overview": return "";
20015
20107
  case "users": return "/users";
@@ -22799,7 +22891,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
22799
22891
  const accounts = props.accounts;
22800
22892
  const accountsSnapshot = (0, react.useSyncExternalStore)(accounts?.subscribe ?? noopSubscribe$1, accounts?.getSnapshot ?? noopAccountsSnapshot, accounts?.getSnapshot ?? noopAccountsSnapshot);
22801
22893
  const accountsStage = accountsSnapshot.stage;
22802
- const adminRoute = route.pathname.replace(/\/+$/u, "") === `${config.route.path === "/" ? "" : config.route.path}/admin`;
22894
+ const adminRoute = isAdminPath(route.pathname, config.route.path);
22803
22895
  const [access, setAccess] = (0, react.useState)();
22804
22896
  const [selectedSubrole, setSelectedSubrole] = (0, react.useState)(null);
22805
22897
  const [adminPreview, setAdminPreview] = (0, react.useState)(false);
@@ -23277,6 +23369,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
23277
23369
  filesOpen: rail.railOpen && rail.railTab === "files",
23278
23370
  onOpenFiles: () => rail.openTab("files"),
23279
23371
  panelLauncher: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaPanelLauncher, { panels: props.panels }),
23372
+ settings: showSidebar ? void 0 : settingsEntry,
23280
23373
  showReset: showResetButton,
23281
23374
  resetDisabled: controller === void 0 || state.phase === "creating",
23282
23375
  onReset: handleNewChat
@@ -23898,21 +23991,20 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
23898
23991
  .dsh-qa-settings__list{margin:0;padding-left:18px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
23899
23992
  .dsh-qa-settings__list li{margin:2px 0}
23900
23993
  .dsh-qa-starters__list{display:flex;flex-direction:column;gap:10px}
23901
- .dsh-qa-starters__item{display:flex;flex-direction:column;gap:8px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3)}
23902
- .dsh-qa-starters__item-head{display:flex;align-items:flex-end;gap:8px}
23903
- .dsh-qa-starters__item-head .dsh-qa-settings__field{flex:1}
23904
- .dsh-qa-starters__remove{appearance:none;flex:none;width:30px;height:30px;margin-bottom:5px;display:flex;align-items:center;justify-content:center;padding:0;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:0 0;color:var(--dsw-alias-label-tertiary);cursor:pointer}
23905
- .dsh-qa-starters__remove:hover{color:var(--dsh-qa-error);border-color:var(--dsh-alias-label-error)}
23994
+ .dsh-qa-starters__item{display:grid;grid-template-columns:minmax(0,1fr) 28px;column-gap:8px;row-gap:8px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3)}
23995
+ .dsh-qa-starters__item>.dsh-qa-settings__field{grid-column:1}
23996
+ .dsh-qa-starters__remove{appearance:none;grid-column:2;grid-row:1;align-self:end;flex:none;width:28px;height:28px;margin-bottom:6px;display:flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:8px;background:0 0;color:var(--dsw-alias-label-tertiary);cursor:pointer}
23997
+ .dsh-qa-starters__remove:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsh-qa-error)}
23906
23998
  .dsh-qa-starters__remove:focus-visible{outline:2px solid var(--dsh-qa-accent);outline-offset:1px}
23907
23999
  .dsh-qa-starters__remove svg{width:12px;height:12px;fill:none;stroke:currentColor;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round}
23908
- .dsh-qa-toolpicker__list{display:flex;flex-direction:column;gap:2px;max-height:320px;margin:8px 0 0;padding:0;list-style:none;overflow-y:auto}
24000
+ .dsh-qa-toolpicker__list{display:flex;flex-direction:column;gap:2px;max-height:min(52vh,440px);margin:8px 0 0;padding:0;list-style:none;overflow-y:auto}
23909
24001
  .dsh-qa-toolpicker__group{padding:8px 0 3px;color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:600;line-height:16px}
23910
24002
  .dsh-qa-toolpicker__row{margin:0}
23911
24003
  .dsh-qa-toolpicker__label{display:grid;grid-template-columns:16px minmax(0,auto) minmax(0,1fr);align-items:baseline;gap:9px;padding:6px 8px;border-radius:8px;cursor:pointer}
23912
24004
  .dsh-qa-toolpicker__label:hover{background:var(--dsw-alias-interactive-bg-hover)}
23913
24005
  .dsh-qa-toolpicker__label input{width:15px;height:15px;margin:0;accent-color:var(--dsh-qa-brand)}
23914
24006
  .dsh-qa-toolpicker__name{color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:18px}
23915
- .dsh-qa-toolpicker__description{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;min-width:0}
24007
+ .dsh-qa-toolpicker__description{display:-webkit-box;overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;min-width:0;-webkit-box-orient:vertical;-webkit-line-clamp:2}
23916
24008
  .dsh-qa-toolpicker__selected{margin-right:auto;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px}
23917
24009
  @media (max-width:720px){.dsh-qa-modal__panel--settings{height:min(640px,calc(100vh - 48px))}.dsh-qa-settings{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr)}.dsh-qa-settings__nav{flex-direction:row;gap:4px;padding:8px 10px;border-right:0;border-bottom:1px solid var(--dsw-alias-border-l2);overflow-x:auto;overflow-y:hidden}.dsh-qa-settings__tab{white-space:nowrap}.dsh-qa-settings__content{padding:12px 14px 0}.dsh-qa-settings__page{padding-bottom:18px}.dsh-qa-settings__actions{margin:auto -14px -18px;padding:10px 14px}}
23918
24010
  .dsh-qa-surface *{box-sizing:border-box}
@@ -23974,6 +24066,10 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
23974
24066
  .dsh-qa-header__files[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
23975
24067
  .dsh-qa-header__files svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}
23976
24068
  .dsh-qa-header__files:focus-visible{outline:2px solid var(--dsh-qa-accent);outline-offset:2px}
24069
+ .dsh-qa-header__settings{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}
24070
+ .dsh-qa-header__settings:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
24071
+ .dsh-qa-header__settings svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}
24072
+ .dsh-qa-header__settings:focus-visible{outline:2px solid var(--dsh-qa-accent);outline-offset:2px}
23977
24073
  .dsh-qa-role-selector select,.dsh-qa-header__admin{height:30px;padding:4px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;line-height:20px;cursor:pointer}
23978
24074
  .dsh-qa-role-selector select:hover,.dsh-qa-header__admin:hover{border-color:var(--dsw-alias-label-dimmed);color:var(--dsw-alias-label-primary)}
23979
24075
  .dsh-qa-role-selector select:focus-visible,.dsh-qa-header__admin:focus-visible{outline:2px solid var(--dsh-qa-accent);outline-offset:1px}
@@ -26590,6 +26686,64 @@ textarea.qa-card-control{height:auto;padding:8px 12px;line-height:1.5;resize:ver
26590
26686
  }
26591
26687
  };
26592
26688
  //#endregion
26689
+ //#region src/client/settings-extensions/user-session.ts
26690
+ const CHECKING = Object.freeze({
26691
+ stage: "checking",
26692
+ token: null
26693
+ });
26694
+ const ANONYMOUS = Object.freeze({
26695
+ stage: "anonymous",
26696
+ token: null
26697
+ });
26698
+ /**
26699
+ * Publishes the account state of the controller the QA overlay mounts. The
26700
+ * service lives for the whole host page while the controller is rebuilt
26701
+ * whenever the remote wiring re-injects, so {@link attach} swaps the source and
26702
+ * republishes; an unattached mirror stays in `checking`, which is what a card
26703
+ * that mounts before the accounts subsystem sees.
26704
+ */
26705
+ var QaUserSessionMirror = class {
26706
+ controller;
26707
+ unsubscribeSource;
26708
+ snapshot = CHECKING;
26709
+ listeners = /* @__PURE__ */ new Set();
26710
+ /** Bind the controller whose account state this view publishes. */
26711
+ attach(controller) {
26712
+ this.unsubscribeSource?.();
26713
+ this.unsubscribeSource = void 0;
26714
+ this.controller = controller;
26715
+ this.unsubscribeSource = controller?.subscribe(() => {
26716
+ this.publish();
26717
+ });
26718
+ this.publish();
26719
+ }
26720
+ dispose() {
26721
+ this.attach(void 0);
26722
+ this.listeners.clear();
26723
+ }
26724
+ getSnapshot = () => this.snapshot;
26725
+ subscribe = (listener) => {
26726
+ this.listeners.add(listener);
26727
+ return () => this.listeners.delete(listener);
26728
+ };
26729
+ publish() {
26730
+ const next = this.derive();
26731
+ if (next.stage === this.snapshot.stage && next.token === this.snapshot.token) return;
26732
+ this.snapshot = next;
26733
+ for (const listener of [...this.listeners]) listener();
26734
+ }
26735
+ derive() {
26736
+ const state = this.controller?.getSnapshot();
26737
+ const token = this.controller?.token() ?? null;
26738
+ if (state?.stage === "authed" && token !== null) return Object.freeze({
26739
+ stage: "authed",
26740
+ token
26741
+ });
26742
+ if (state === void 0 || state.stage === "checking") return CHECKING;
26743
+ return ANONYMOUS;
26744
+ }
26745
+ };
26746
+ //#endregion
26593
26747
  //#region src/client/index.tsx
26594
26748
  const QA_WELCOME_SLOT_ID = "welcome-notice";
26595
26749
  function qaWelcomeStorageKey(config) {
@@ -26607,6 +26761,7 @@ textarea.qa-card-control{height:auto;padding:8px 12px;line-height:1.5;resize:ver
26607
26761
  async function apply(ctx) {
26608
26762
  const panels = new QaSurfacePanelRegistry();
26609
26763
  const settingsSections = new QaUserSettingsSectionRegistry();
26764
+ const userSession = new QaUserSessionMirror();
26610
26765
  ctx.effect(() => {
26611
26766
  const removeService = ctx.provide("qaSurfacePanels", panels);
26612
26767
  return () => {
@@ -26621,6 +26776,13 @@ textarea.qa-card-control{height:auto;padding:8px 12px;line-height:1.5;resize:ver
26621
26776
  removeService();
26622
26777
  };
26623
26778
  }, "dsh-qa-surface: user settings extension service");
26779
+ ctx.effect(() => {
26780
+ const removeService = ctx.provide("qaUserSession", userSession);
26781
+ return () => {
26782
+ userSession.dispose();
26783
+ removeService();
26784
+ };
26785
+ }, "dsh-qa-surface: user session service");
26624
26786
  const disposeRemote = await ctx.remote.$mount(TYPERT_REMOTE);
26625
26787
  await ctx.inject([
26626
26788
  "remote.qaSurface",
@@ -26700,6 +26862,7 @@ textarea.qa-card-control{height:auto;padding:8px 12px;line-height:1.5;resize:ver
26700
26862
  new QaChatIndex(window.localStorage, qaStorageNamespace(snapshot)).forgetChat(sessionId);
26701
26863
  }
26702
26864
  });
26865
+ userSession.attach(accounts);
26703
26866
  const route = new QaRouteController();
26704
26867
  const settingsScope = ctx.settingsScope.bind({ namespace: QA_SURFACE_SETTINGS_NAMESPACE });
26705
26868
  const config = new QaConfigController(settingsScope, async () => {