@ykurilov/agent-chat-skin-tandem 0.55.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/chunk-6YQULY5S.js +19 -0
  3. package/dist/chunk-6YQULY5S.js.map +1 -0
  4. package/dist/chunk-EUBJQLKB.js +75 -0
  5. package/dist/chunk-EUBJQLKB.js.map +1 -0
  6. package/dist/{chunk-7MGUSS4M.js → chunk-MJZLTQYR.js} +2 -18
  7. package/dist/chunk-MJZLTQYR.js.map +1 -0
  8. package/dist/flows/index.js +432 -326
  9. package/dist/flows/index.js.map +1 -1
  10. package/dist/index.js +473 -499
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp/index.js +423 -0
  13. package/dist/mcp/index.js.map +1 -0
  14. package/dist/sandbox/index.js +4 -2
  15. package/dist/sandbox/index.js.map +1 -1
  16. package/dist/skills/index.js +309 -0
  17. package/dist/skills/index.js.map +1 -0
  18. package/dist/types/flows/flow-header-menu.d.ts +20 -0
  19. package/dist/types/generated-images.d.ts +2 -1
  20. package/dist/types/mcp/index.d.ts +5 -0
  21. package/dist/types/mcp/mcp-add-form.d.ts +5 -0
  22. package/dist/types/mcp/mcp-admin-screen.d.ts +3 -0
  23. package/dist/types/mcp/mcp-edit-form.d.ts +5 -0
  24. package/dist/types/mcp/mcp-form-fields.d.ts +33 -0
  25. package/dist/types/mcp/segmented.d.ts +11 -0
  26. package/dist/types/skills/index.d.ts +3 -0
  27. package/dist/types/skills/skill-editor.d.ts +6 -0
  28. package/dist/types/skills/skill-list.d.ts +3 -0
  29. package/dist/types/skills/skill-loads.d.ts +3 -0
  30. package/dist/types/skills/skill-panel.d.ts +3 -0
  31. package/dist/types/skills/skill-status-badge.d.ts +4 -0
  32. package/dist/types/skills/skill-versions.d.ts +3 -0
  33. package/dist/types/skills/skills-layout.d.ts +3 -0
  34. package/dist/types/skills/skills-not-found.d.ts +2 -0
  35. package/package.json +30 -10
  36. package/dist/chunk-7MGUSS4M.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,64 @@
1
1
  # @ykurilov/agent-chat-skin-tandem
2
2
 
3
+ ## 0.57.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 34074f9: Шапка доски сценария на телефоне: лишние кнопки уходят в меню «⋯», Прогон, Проверить граф и сохранение остаются на виду.
8
+ Новая подпись `labels.header.more`.
9
+ - Updated dependencies [34074f9]
10
+ - Updated dependencies [7c82b87]
11
+ - @ykurilov/agent-chat-flows@0.57.0
12
+ - @ykurilov/agent-chat-mcp@0.57.0
13
+ - @ykurilov/agent-chat-messengers@0.57.0
14
+ - @ykurilov/agent-chat-sandbox@0.57.0
15
+ - @ykurilov/agent-chat-skills@0.57.0
16
+ - @ykurilov/agent-chat-react@0.57.0
17
+
18
+ ## 0.56.0
19
+
20
+ ### Minor Changes
21
+
22
+ - 949d3b2: Навыки агента: новый пакет `@ykurilov/agent-chat-skills` (список включённых навыков в блоке контекста, загрузка указаний
23
+ инструментом `use_skill`, версии, учёт загрузок, API и экран для админа) и скин экрана `@ykurilov/agent-chat-skin-tandem/skills`.
24
+ - 1eadba9: Библиотека готовых картинок: новый пакет `@ykurilov/agent-chat-image-library` с инструментами `find_images` и
25
+ `show_image` поверх каталога приложения. Картинка показывается по ширине ответа с подписью, по нажатию — окно просмотра.
26
+
27
+ - `images` у `AgentChat`, `ChatWorkspace`, `AgentPanel` принимает и список клиентов: `images={[imagesClient, imageLibraryClient]}`.
28
+ Одиночный клиент работает как раньше.
29
+ - Договор скина версии 26: у `GeneratedImages` необязательные `wide` и `caption`. Тандем их рисует. Скин версии 25
30
+ работает без правок: картинка будет плиткой ближайшей обычной пропорции (1:1, 3:2, 2:3, 16:9, 9:16) без подписи.
31
+
32
+ Подробно и как подключить — `docs/image-library.md`.
33
+
34
+ Отказ `show_image`, `show_video` и недоступный каталог в `find_images`, `find_videos` теперь ошибка инструмента (`ImageLibraryError`, `VideoLibraryError` с полем `kind`).
35
+ В ленте строка «ошибка» вместо зелёной галочки, модель получает тот же текст. Код, который читал `{ отказ }` из
36
+ результата `show_video` или `find_videos`, должен смотреть на `state: "output-error"` у части сообщения.
37
+ - 24c313e: Новый пакет `@ykurilov/agent-chat-mcp`: серверы MCP, которые подключает админ приложения.
38
+
39
+ - `createMcpServers({ store, secretKey, isAdmin, proxy?, spending?, limits?, syncIntervalMs? })` → `mcp.tools(model, appTools?)`
40
+ для `agent.tools` (первым; при одинаковом имени побеждает инструмент приложения), `mcp.admin`, `mcp.reload()`, `mcp.sync()`, `mcp.close()`. «Только админы» — по `model.viewer.isAdmin`;
41
+ `isAdmin` (может быть асинхронной) — для экрана. Правка доходит до всех копий сервера по отметке версии в хранилище. Только удалённые серверы (Streamable HTTP, SSE). Инструменты — из
42
+ сохранённого снимка списка: запрос к модели стабилен, упавший сервер не ломает начало запроса. Соединение — на
43
+ проверенный адрес DNS; заголовки прокси — только на `proxy.origin`.
44
+ - `createMcpAdminHandler({ mcp, resolveUser, allowedOrigins })` — адреса `/api/mcp`, не админу 404.
45
+ - Хранилища: `createMemoryMcpStore`, `/sqlite` `createSqliteMcpStore`, `/postgres` `createPostgresMcpStore`; миграции сами.
46
+ - `/react`: `McpAdmin`, `useMcpAdmin`, договор скина `McpSkin` v1 (с формой правки `McpServerCardView.edit`), надписи ru/en. Скин Тандема: `./mcp` → `tandemMcpSkin`.
47
+ - Ядро: штатный вид расхода `mcp_tool`; `agent.viewer` — права человека раз в начале хода, полем `viewer` в
48
+ `contextBlocks` и `AgentToolsModel`. Песочница: права тестового чата — по владельцу чата. web-tools: наружу `dnsHostResolver`.
49
+
50
+ ### Patch Changes
51
+
52
+ - Updated dependencies [949d3b2]
53
+ - Updated dependencies [1eadba9]
54
+ - Updated dependencies [24c313e]
55
+ - @ykurilov/agent-chat-skills@0.56.0
56
+ - @ykurilov/agent-chat-react@0.56.0
57
+ - @ykurilov/agent-chat-mcp@0.56.0
58
+ - @ykurilov/agent-chat-sandbox@0.56.0
59
+ - @ykurilov/agent-chat-flows@0.56.0
60
+ - @ykurilov/agent-chat-messengers@0.56.0
61
+
3
62
  ## 0.55.0
4
63
 
5
64
  ### Minor Changes
@@ -0,0 +1,19 @@
1
+ // src/screen-width.ts
2
+ import { useSyncExternalStore } from "react";
3
+ function useScreenQuery(query, fallback) {
4
+ return useSyncExternalStore(
5
+ (onChange) => {
6
+ if (typeof window.matchMedia !== "function") return () => void 0;
7
+ const media = window.matchMedia(query);
8
+ media.addEventListener("change", onChange);
9
+ return () => media.removeEventListener("change", onChange);
10
+ },
11
+ () => typeof window.matchMedia === "function" ? window.matchMedia(query).matches : fallback,
12
+ () => fallback
13
+ );
14
+ }
15
+
16
+ export {
17
+ useScreenQuery
18
+ };
19
+ //# sourceMappingURL=chunk-6YQULY5S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/screen-width.ts"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\n\n/** Подходит ли экран под медиазапрос; где `matchMedia` нет (проверки в jsdom, серверная отрисовка) — `fallback`. */\nexport function useScreenQuery(query: string, fallback: boolean): boolean {\n return useSyncExternalStore(\n (onChange) => {\n if (typeof window.matchMedia !== \"function\") return () => undefined;\n const media = window.matchMedia(query);\n media.addEventListener(\"change\", onChange);\n return () => media.removeEventListener(\"change\", onChange);\n },\n () => (typeof window.matchMedia === \"function\" ? window.matchMedia(query).matches : fallback),\n () => fallback,\n );\n}\n"],"mappings":";AAAA,SAAS,4BAA4B;AAG9B,SAAS,eAAe,OAAe,UAA4B;AACxE,SAAO;AAAA,IACL,CAAC,aAAa;AACZ,UAAI,OAAO,OAAO,eAAe,WAAY,QAAO,MAAM;AAC1D,YAAM,QAAQ,OAAO,WAAW,KAAK;AACrC,YAAM,iBAAiB,UAAU,QAAQ;AACzC,aAAO,MAAM,MAAM,oBAAoB,UAAU,QAAQ;AAAA,IAC3D;AAAA,IACA,MAAO,OAAO,OAAO,eAAe,aAAa,OAAO,WAAW,KAAK,EAAE,UAAU;AAAA,IACpF,MAAM;AAAA,EACR;AACF;","names":[]}
@@ -0,0 +1,75 @@
1
+ // src/safe-markdown.tsx
2
+ import { Markdown } from "@tandem/ui";
3
+ import { useChatLabels } from "@ykurilov/agent-chat-react";
4
+ import { memo, useEffect } from "react";
5
+
6
+ // src/external-images.ts
7
+ import { fromMarkdown } from "mdast-util-from-markdown";
8
+ import { gfmFromMarkdown } from "mdast-util-gfm";
9
+ import { gfm } from "micromark-extension-gfm";
10
+ function isExternal(url) {
11
+ const cleaned = url.replace(/^[\u0000-\u0020]+/, "").replace(/[\t\n\r]/g, "");
12
+ if (/^data:/i.test(cleaned)) return false;
13
+ return /^[a-z][a-z0-9+.-]*:/i.test(cleaned) || /^[\\/]{2}/.test(cleaned);
14
+ }
15
+ var escapeText = (text) => text.replace(/[\\\x60*_{}[\]()#+\-.!|<>~]/g, "\\$&");
16
+ function withoutExternalImages(markdown, labelWithoutAlt) {
17
+ if (!markdown.includes("![")) return markdown;
18
+ const tree = fromMarkdown(markdown, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] });
19
+ const definitions = /* @__PURE__ */ new Map();
20
+ const images = [];
21
+ const walk = (node, insideLink) => {
22
+ if (node.type === "definition") definitions.set(node.identifier, node.url);
23
+ if (node.type === "image" || node.type === "imageReference") images.push({ node, insideLink });
24
+ if ("children" in node) {
25
+ const childInsideLink = insideLink || node.type === "link" || node.type === "linkReference";
26
+ for (const child of node.children) walk(child, childInsideLink);
27
+ }
28
+ };
29
+ walk(tree, false);
30
+ const edits = [];
31
+ for (const { node, insideLink } of images) {
32
+ const url = node.type === "image" ? node.url : definitions.get(node.identifier);
33
+ const start = node.position?.start.offset;
34
+ const end = node.position?.end.offset;
35
+ if (url === void 0 || !isExternal(url) || start === void 0 || end === void 0) continue;
36
+ const alt = node.alt?.trim() ?? "";
37
+ if (insideLink) {
38
+ edits.push({ start, end, text: escapeText(alt || labelWithoutAlt) });
39
+ } else if (alt) {
40
+ edits.push({ start, end: start + 1, text: "" });
41
+ } else {
42
+ edits.push({ start, end: markdown.indexOf("]", start + 2), text: `[${escapeText(labelWithoutAlt)}` });
43
+ }
44
+ }
45
+ let result = markdown;
46
+ for (const edit of edits.sort((a, b) => b.start - a.start)) result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
47
+ return result;
48
+ }
49
+
50
+ // src/safe-markdown.tsx
51
+ import { jsx } from "react/jsx-runtime";
52
+ function ignoreDisarmedLinkClick(event) {
53
+ const link = event.target?.closest?.(".tandem-markdown a");
54
+ if (link && !link.getAttribute("href")?.trim()) event.preventDefault();
55
+ }
56
+ var LINK_CLICKS = ["click", "auxclick"];
57
+ var mountedCount = 0;
58
+ function useDisarmedLinkGuard() {
59
+ useEffect(() => {
60
+ if (mountedCount++ === 0) for (const type of LINK_CLICKS) document.addEventListener(type, ignoreDisarmedLinkClick, true);
61
+ return () => {
62
+ if (--mountedCount === 0) for (const type of LINK_CLICKS) document.removeEventListener(type, ignoreDisarmedLinkClick, true);
63
+ };
64
+ }, []);
65
+ }
66
+ var SafeMarkdown = memo(function SafeMarkdown2({ markdown }) {
67
+ const labels = useChatLabels();
68
+ useDisarmedLinkGuard();
69
+ return /* @__PURE__ */ jsx(Markdown, { children: withoutExternalImages(markdown, labels.externalImageLink) });
70
+ });
71
+
72
+ export {
73
+ SafeMarkdown
74
+ };
75
+ //# sourceMappingURL=chunk-EUBJQLKB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/safe-markdown.tsx","../src/external-images.ts"],"sourcesContent":["import { Markdown } from \"@tandem/ui\";\nimport { useChatLabels } from \"@ykurilov/agent-chat-react\";\nimport { memo, useEffect } from \"react\";\nimport { withoutExternalImages } from \"./external-images\";\n\n/**\n * Обезвреженная ссылка (`javascript:` и другие опасные адреса) остаётся с пустым адресом: клик по ней перезагрузил бы\n * страницу и оборвал бы чат, средняя кнопка мыши (`auxclick`) открыла бы её же в новой вкладке. Такой клик не делает\n * ничего; обычные ссылки работают как прежде.\n */\nfunction ignoreDisarmedLinkClick(event: MouseEvent) {\n const link = (event.target as Element | null)?.closest?.(\".tandem-markdown a\");\n if (link && !link.getAttribute(\"href\")?.trim()) event.preventDefault();\n}\n\n/**\n * Один слушатель на документ, пока на странице есть текст модели. Не обёртка: лишний узел вокруг `.tandem-markdown`\n * сломал бы отступы блоков, которые задаются селектором `&>.tandem-markdown`.\n */\nconst LINK_CLICKS = [\"click\", \"auxclick\"] as const;\nlet mountedCount = 0;\nfunction useDisarmedLinkGuard() {\n useEffect(() => {\n if (mountedCount++ === 0) for (const type of LINK_CLICKS) document.addEventListener(type, ignoreDisarmedLinkClick, true);\n return () => {\n if (--mountedCount === 0) for (const type of LINK_CLICKS) document.removeEventListener(type, ignoreDisarmedLinkClick, true);\n };\n }, []);\n}\n\n/**\n * Markdown текста модели (ответ, сводка сжатия): картинки с чужих адресов показываются ссылкой, браузер не ходит\n * по ним сам. Весь текст модели в скине рисуется через него, голый `Markdown` из `@tandem/ui` — только здесь.\n */\nexport const SafeMarkdown = memo(function SafeMarkdown({ markdown }: { markdown: string }) {\n const labels = useChatLabels();\n useDisarmedLinkGuard();\n return <Markdown>{withoutExternalImages(markdown, labels.externalImageLink)}</Markdown>;\n});\n","import { fromMarkdown } from \"mdast-util-from-markdown\";\nimport { gfmFromMarkdown } from \"mdast-util-gfm\";\nimport { gfm } from \"micromark-extension-gfm\";\n\n/*\n * Картинка в ответе модели грузится браузером сама. Если модель прочитала страницу с подложенным указанием\n * «вставь картинку https://злодей/p.png?d=<данные>», данные уйдут по адресу картинки без единого клика.\n * Поэтому картинки с чужих адресов показываются ссылкой: человек увидит адрес и откроет его сам, если захочет.\n */\n\ntype Node = ReturnType<typeof fromMarkdown>[\"children\"][number] | ReturnType<typeof fromMarkdown>;\n\n/** Свой адрес (без домена) или `data:` не уводит данные наружу. Остальное — чужой адрес. */\nfunction isExternal(url: string): boolean {\n // Браузер так же выбрасывает пробелы по краям и переводы строк внутри адреса.\n const cleaned = url.replace(/^[\\u0000-\\u0020]+/, \"\").replace(/[\\t\\n\\r]/g, \"\");\n if (/^data:/i.test(cleaned)) return false;\n return /^[a-z][a-z0-9+.-]*:/i.test(cleaned) || /^[\\\\/]{2}/.test(cleaned);\n}\n\n// \\x60 — обратная кавычка.\nconst escapeText = (text: string) => text.replace(/[\\\\\\x60*_{}[\\]()#+\\-.!|<>~]/g, \"\\\\$&\");\n\n/**\n * Markdown, где картинки с чужих адресов заменены ссылками (внутри ссылки — подписью). Картинка без подписи\n * получает подпись `labelWithoutAlt` (экранированную: её знаки разметку не ломают). Прочий текст не меняется.\n */\nexport function withoutExternalImages(markdown: string, labelWithoutAlt: string): string {\n if (!markdown.includes(\"![\")) return markdown;\n const tree = fromMarkdown(markdown, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] });\n\n const definitions = new Map<string, string>();\n const images: Array<{ node: Node & { type: \"image\" | \"imageReference\" }; insideLink: boolean }> = [];\n const walk = (node: Node, insideLink: boolean) => {\n if (node.type === \"definition\") definitions.set(node.identifier, node.url);\n if (node.type === \"image\" || node.type === \"imageReference\") images.push({ node, insideLink });\n if (\"children\" in node) {\n const childInsideLink = insideLink || node.type === \"link\" || node.type === \"linkReference\";\n for (const child of node.children) walk(child as Node, childInsideLink);\n }\n };\n walk(tree, false);\n\n const edits: Array<{ start: number; end: number; text: string }> = [];\n for (const { node, insideLink } of images) {\n const url = node.type === \"image\" ? node.url : definitions.get(node.identifier);\n const start = node.position?.start.offset;\n const end = node.position?.end.offset;\n if (url === undefined || !isExternal(url) || start === undefined || end === undefined) continue;\n const alt = node.alt?.trim() ?? \"\";\n if (insideLink) {\n edits.push({ start, end, text: escapeText(alt || labelWithoutAlt) });\n } else if (alt) {\n // Без «!» та же запись — ссылка с тем же адресом и подписью.\n edits.push({ start, end: start + 1, text: \"\" });\n } else {\n edits.push({ start, end: markdown.indexOf(\"]\", start + 2), text: `[${escapeText(labelWithoutAlt)}` });\n }\n }\n\n let result = markdown;\n for (const edit of edits.sort((a, b) => b.start - a.start)) result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);\n return result;\n}\n"],"mappings":"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","names":["SafeMarkdown"]}
@@ -48,23 +48,7 @@ function TandemSkinLabels({ children }) {
48
48
  return /* @__PURE__ */ jsx(TandemLabelsProvider, { labels, children });
49
49
  }
50
50
 
51
- // src/screen-width.ts
52
- import { useSyncExternalStore } from "react";
53
- function useScreenQuery(query, fallback) {
54
- return useSyncExternalStore(
55
- (onChange) => {
56
- if (typeof window.matchMedia !== "function") return () => void 0;
57
- const media = window.matchMedia(query);
58
- media.addEventListener("change", onChange);
59
- return () => media.removeEventListener("change", onChange);
60
- },
61
- () => typeof window.matchMedia === "function" ? window.matchMedia(query).matches : fallback,
62
- () => fallback
63
- );
64
- }
65
-
66
51
  export {
67
- TandemSkinLabels,
68
- useScreenQuery
52
+ TandemSkinLabels
69
53
  };
70
- //# sourceMappingURL=chunk-7MGUSS4M.js.map
54
+ //# sourceMappingURL=chunk-MJZLTQYR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tandem-labels.tsx"],"sourcesContent":["import {\n englishTandemLabels,\n russianTandemLabels,\n TandemLabelsProvider,\n} from \"@tandem/ui\";\nimport type { TandemLabelsOverrides } from \"@tandem/ui\";\nimport { useChatLabels } from \"@ykurilov/agent-chat-react\";\nimport { useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\n\n/** Словарь Тандема на языке словаря SDK; другой язык — ничего: надписи берутся у `TandemLabelsProvider` приложения. */\nfunction tandemLabelsFor(locale: string | undefined): TandemLabelsOverrides {\n const language = (locale ?? \"ru\").split(\"-\")[0];\n if (language === \"en\") return englishTandemLabels;\n if (language === \"ru\") return russianTandemLabels;\n return {};\n}\n\n/**\n * Надписи дизайн-системы Тандема из словаря SDK: язык — по `labels.locale`, а надписи, которые есть и в SDK (поле\n * ввода, «Отправить», «Стоп», копирование кода), — из словаря SDK, чтобы своя надпись приложения была видна и в Тандеме.\n * Словарь собирается только при смене этих строк: новый объект на каждый рендер сбрасывал бы «Скопировано» и прокрутку\n * таблиц, пока идёт ответ.\n */\nexport function TandemSkinLabels({ children }: { children: ReactNode }) {\n const {\n locale,\n composerPlaceholder,\n composerLabel,\n send,\n stop,\n copyCode,\n copied,\n } = useChatLabels();\n const labels = useMemo<TandemLabelsOverrides>(() => {\n const base = tandemLabelsFor(locale);\n return {\n ...base,\n composer: {\n ...base.composer,\n placeholder: composerPlaceholder,\n message: composerLabel,\n send,\n stop,\n },\n markdown: { ...base.markdown, copyCode, copied },\n };\n }, [\n locale,\n composerPlaceholder,\n composerLabel,\n send,\n stop,\n copyCode,\n copied,\n ]);\n return (\n <TandemLabelsProvider labels={labels}>{children}</TandemLabelsProvider>\n );\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AAkDpB;AA9CJ,SAAS,gBAAgB,QAAmD;AAC1E,QAAM,YAAY,UAAU,MAAM,MAAM,GAAG,EAAE,CAAC;AAC9C,MAAI,aAAa,KAAM,QAAO;AAC9B,MAAI,aAAa,KAAM,QAAO;AAC9B,SAAO,CAAC;AACV;AAQO,SAAS,iBAAiB,EAAE,SAAS,GAA4B;AACtE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,cAAc;AAClB,QAAM,SAAS,QAA+B,MAAM;AAClD,UAAM,OAAO,gBAAgB,MAAM;AACnC,WAAO;AAAA,MACL,GAAG;AAAA,MACH,UAAU;AAAA,QACR,GAAG,KAAK;AAAA,QACR,aAAa;AAAA,QACb,SAAS;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACA,UAAU,EAAE,GAAG,KAAK,UAAU,UAAU,OAAO;AAAA,IACjD;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,SACE,oBAAC,wBAAqB,QAAiB,UAAS;AAEpD;","names":[]}