@ykurilov/agent-chat-react 0.68.0 → 0.69.1

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.
package/dist/index.js CHANGED
@@ -11,10 +11,12 @@ import {
11
11
  englishLabels,
12
12
  englishWorkLabels,
13
13
  englishWorkViewLabels,
14
+ guessStepIcon,
14
15
  russianLabels,
15
16
  splitAnswerFences,
16
17
  splitMermaidBlocks,
17
18
  splitVoiceTags,
19
+ stepIconOf,
18
20
  stripVoiceTags,
19
21
  useAgentChat,
20
22
  useChatLabels,
@@ -27,7 +29,7 @@ import {
27
29
  voiceAnswerIds,
28
30
  voiceErrorText,
29
31
  withoutResetSteps
30
- } from "./chunk-JLGBVDAR.js";
32
+ } from "./chunk-Z7X5D4OF.js";
31
33
 
32
34
  // src/chat-list/chat-workspace.tsx
33
35
  import { useCallback, useEffect, useRef, useState } from "react";
@@ -484,10 +486,12 @@ export {
484
486
  englishLabels,
485
487
  englishWorkLabels,
486
488
  englishWorkViewLabels,
489
+ guessStepIcon,
487
490
  russianLabels,
488
491
  splitAnswerFences,
489
492
  splitMermaidBlocks,
490
493
  splitVoiceTags,
494
+ stepIconOf,
491
495
  stripVoiceTags,
492
496
  useAgentChat,
493
497
  useChatLabels,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/chat-list/chat-workspace.tsx","../src/memory/agent-memory.tsx","../src/memory/memory-tree.ts","../src/memory/memory-types.ts","../src/memory/use-memory-notes.ts","../src/memory/memory-client.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { AgentChat } from \"../agent-chat\";\nimport type { AgentChatProps } from \"../agent-chat\";\nimport type { ChatAccess } from \"../chat-access\";\nimport { useChatLabels } from \"../labels\";\nimport type { ChatSkin } from \"../skin-contract\";\nimport type { ChatListSkin } from \"./chat-list-skin\";\nimport { useChatList } from \"./use-chat-list\";\nimport { activeChatTitle, useChatListView } from \"./use-chat-list-view\";\nimport { useWorkspaceListSync } from \"./use-workspace-list-sync\";\n\nexport interface ChatWorkspaceProps extends Omit<AgentChatProps, \"chatId\" | \"subject\" | \"skin\" | \"onBusyChange\" | \"onChatUnavailable\" | \"initialDraft\"> {\n /** Скин чата плюс скин списка (например, `tandemSkin`). */\n skin: ChatSkin & ChatListSkin;\n /** Какой чат открыть первым, например из адреса страницы. Главнее запомненного; чата нет — «Чат не найден». */\n initialChatId?: string;\n /** Открытый чат сменился (выбор, создание, удаление): например, чтобы поменять адрес страницы. */\n onChatChange?: (chatId: string | null) => void;\n}\n\n/**\n * Чат со списком чатов рядом: выбрать, создать, переименовать, удалить. Всё видимое рисует скин. У открытого чата\n * запрещено право `chatList` — списка нет, только чат: он не показывается и не обновляется, пока права открытого чата неизвестны\n * (до первой истории) и если запрещён.\n */\nexport function ChatWorkspace({ skin, initialChatId, onChatChange, onChatAccessChange, ...chatProps }: ChatWorkspaceProps) {\n const labels = useChatLabels();\n const { api } = chatProps;\n const list = useChatList({ api, ...(initialChatId !== undefined && { initialChatId }), ...(chatProps.onTurnRefused && { onCreateRefused: chatProps.onTurnRefused }) });\n const [listOpen, setListOpen] = useState(false);\n const [activeBusy, setActiveBusy] = useState(false);\n const { activeChatId } = list;\n /**\n * Список показан только когда известно, что он разрешён: права приходят с первой историей открытого чата, и список,\n * который тут же спрятали бы (`chatList: false`), не должен мигать. Выбор другого чата список не прячет: права нового\n * чата приходят позже, а список в это время остаётся на месте. Не открылся чат или сам список — список виден: из него\n * выбирают другой чат.\n */\n const [listAllowed, setListAllowed] = useState(false);\n const reportAccess = useRef(onChatAccessChange);\n reportAccess.current = onChatAccessChange;\n const onActiveAccessChange = useCallback((access: ChatAccess) => {\n setListAllowed(access.capabilities.chatList);\n reportAccess.current?.(access);\n }, []);\n const onActiveChatUnavailable = useCallback(() => setListAllowed(true), []);\n const listShown = listAllowed || (activeChatId === null && list.status !== \"loading\");\n\n const reportChange = useRef(onChatChange);\n reportChange.current = onChatChange;\n useEffect(() => {\n reportChange.current?.(activeChatId);\n }, [activeChatId]);\n\n // Список скрыт — его не запрашивают: ни при смене занятости, ни ради названия чата.\n useWorkspaceListSync(list, activeBusy, listShown);\n const closeList = useCallback(() => setListOpen(false), []);\n const listView = useChatListView(list, closeList);\n\n return (\n <skin.ChatWorkspace\n activeTitle={activeChatTitle(list, labels)}\n listOpen={listOpen && listShown}\n onListOpenChange={setListOpen}\n {...(listShown && { list: <skin.ChatList {...listView} /> })}\n >\n {activeChatId !== null && (\n <AgentChat key={activeChatId} {...chatProps} chatId={activeChatId} skin={skin} onBusyChange={setActiveBusy} onChatAccessChange={onActiveAccessChange} onChatUnavailable={onActiveChatUnavailable} />\n )}\n </skin.ChatWorkspace>\n );\n}\n","import { useCallback, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useChatLabels } from \"../labels\";\nimport type { ChatLabels } from \"../labels\";\nimport type { MemoryConfirmTarget, MemoryNoteEditorErrors, MemorySkin } from \"./memory-skin\";\nimport { buildMemoryTree } from \"./memory-tree\";\nimport type { MemoryTreeGroup } from \"./memory-tree\";\nimport { memoryNoteLimits } from \"./memory-types\";\nimport type { MemoryActionResult, MemoryApi, MemoryFailure, MemoryNote, MemoryNoteInput } from \"./memory-types\";\nimport { useMemoryNotes } from \"./use-memory-notes\";\n\nexport interface AgentMemoryProps {\n /** Обмен с сервером: `createMemoryApi({ api: \"/api/chat\" })`. Создайте один раз — при смене объекта список грузится заново. */\n api: MemoryApi;\n /** Скин экрана памяти (договор 11), например `tandemMemorySkin`. */\n skin: MemorySkin;\n}\n\ninterface Draft {\n /** `null` — новая запись. */\n id: string | null;\n path: string;\n content: string;\n}\n\ntype Confirm = { kind: \"note\"; id: string; content: string } | { kind: \"all\" };\n\nconst squeeze = (text: string) => text.replace(/\\s+/g, \" \").trim();\n\n/** Форма черновика → ввод для сервера либо ошибки полей. Пределы те же, что у пакета памяти. */\nfunction readDraft(draft: Draft, labels: ChatLabels): { input: MemoryNoteInput } | { errors: MemoryNoteEditorErrors } {\n const errors: MemoryNoteEditorErrors = {};\n const categories = draft.path.split(\"/\").map(squeeze).filter((part) => part !== \"\");\n if (categories.length === 0) errors.path = labels.memoryFieldErrors.pathRequired;\n else if (categories.length > memoryNoteLimits.maxCategories) errors.path = labels.memoryFieldErrors.pathTooMany;\n else if (categories.some((part) => part.length > memoryNoteLimits.maxCategoryChars)) errors.path = labels.memoryFieldErrors.pathTooLong;\n const content = squeeze(draft.content);\n if (content === \"\") errors.content = labels.memoryFieldErrors.contentRequired;\n else if (content.length > memoryNoteLimits.maxContentChars) errors.content = labels.memoryFieldErrors.contentTooLong;\n return errors.path || errors.content ? { errors } : { input: { categories, content } };\n}\n\n/**\n * Экран памяти агента: записи по разделам, добавление, правка, удаление, «Стереть всю память» и блок «Объединено» с кнопкой «Вернуть как было».\n * Всё видимое рисует скин; здесь — состояние, черновики и проверка ввода.\n */\nexport function AgentMemory({ api, skin }: AgentMemoryProps) {\n const labels = useChatLabels();\n const memory = useMemoryNotes(api);\n const { notes, edit: editNote, add: addNote, remove: removeNote, clear: clearNotes, revertMerge } = memory;\n const [draft, setDraft] = useState<Draft | null>(null);\n const [fieldErrors, setFieldErrors] = useState<MemoryNoteEditorErrors>({});\n const [confirm, setConfirm] = useState<Confirm | null>(null);\n const [confirmError, setConfirmError] = useState<string | undefined>();\n const [notice, setNotice] = useState<string | undefined>();\n const [pending, setPending] = useState(false);\n\n // Правка записи, которой уже нет в списке (её удалили в другом месте), закрывается сама.\n const editorOpen = draft !== null && (draft.id === null || notes.some((item) => item.id === draft.id));\n const failureText = (failure: MemoryFailure) => labels.memoryErrors[failure];\n\n const run = useCallback(async (action: () => Promise<MemoryActionResult>): Promise<MemoryActionResult> => {\n setPending(true);\n try {\n return await action();\n } finally {\n setPending(false);\n }\n }, []);\n\n const openAdd = () => {\n setNotice(undefined);\n setFieldErrors({});\n setDraft({ id: null, path: \"\", content: \"\" });\n };\n const openEdit = (note: MemoryNote) => {\n setNotice(undefined);\n setFieldErrors({});\n setDraft({ id: note.id, path: note.categories.join(\" / \"), content: note.content });\n };\n const closeEditor = () => {\n setDraft(null);\n setFieldErrors({});\n };\n const changeDraft = (change: Partial<Pick<Draft, \"path\" | \"content\">>) => {\n setDraft((current) => current && { ...current, ...change });\n setFieldErrors({});\n };\n\n const save = async () => {\n if (!draft) return;\n const read = readDraft(draft, labels);\n if (\"errors\" in read) {\n setFieldErrors(read.errors);\n return;\n }\n setFieldErrors({});\n const result = await run(() => (draft.id === null ? addNote(read.input) : editNote(draft.id, read.input)));\n if (result.ok) {\n setDraft(null);\n } else if (result.failure === \"not_found\" && draft.id !== null) {\n // Записи нет: форму закрываем, причину говорим на экране.\n setDraft(null);\n setNotice(failureText(\"not_found\"));\n } else {\n setFieldErrors({ form: failureText(result.failure) });\n }\n };\n\n const revert = async (runId: string) => {\n setNotice(undefined);\n const result = await run(() => revertMerge(runId));\n if (result.ok) return;\n const { failure } = result;\n setNotice(failure === \"limit_reached\" || failure === \"duplicate\" ? labels.memoryRevertErrors[failure] : failureText(failure));\n };\n\n const openConfirm = (next: Confirm) => {\n setNotice(undefined);\n setConfirmError(undefined);\n setConfirm(next);\n };\n const closeConfirm = () => {\n setConfirm(null);\n setConfirmError(undefined);\n };\n const confirmAction = async () => {\n if (!confirm) return;\n const result = await run(() => (confirm.kind === \"note\" ? removeNote(confirm.id) : clearNotes()));\n if (result.ok) {\n closeConfirm();\n } else if (result.failure === \"stale_version\" || result.failure === \"not_found\") {\n // Запись уже другая или её нет: список обновлён, человек решает заново.\n closeConfirm();\n setNotice(failureText(result.failure));\n } else {\n setConfirmError(failureText(result.failure));\n }\n };\n\n const confirmTarget: MemoryConfirmTarget | null =\n confirm === null ? null : confirm.kind === \"note\" ? { kind: \"note\", content: confirm.content } : { kind: \"all\", total: notes.length };\n\n const canChange = !pending;\n const renderEditor = (mode: \"create\" | \"edit\"): ReactNode =>\n draft && (\n <skin.MemoryNoteEditor\n mode={mode}\n path={draft.path}\n content={draft.content}\n onPathChange={(path) => changeDraft({ path })}\n onContentChange={(content) => changeDraft({ content })}\n onSave={() => void save()}\n onCancel={closeEditor}\n canSave={draft.path.trim() !== \"\" && draft.content.trim() !== \"\"}\n pending={pending}\n errors={fieldErrors}\n />\n );\n\n const renderNote = (note: MemoryNote): ReactNode =>\n editorOpen && draft?.id === note.id ? (\n <div key={note.id}>{renderEditor(\"edit\")}</div>\n ) : (\n <skin.MemoryNoteRow\n key={note.id}\n content={note.content}\n {...(canChange && { onEdit: () => openEdit(note), onDelete: () => openConfirm({ kind: \"note\", id: note.id, content: note.content }) })}\n />\n );\n\n const renderGroup = (group: MemoryTreeGroup, depth: number): ReactNode => (\n <skin.MemoryGroup key={group.key} name={group.name} depth={depth} count={group.count}>\n {group.notes.map(renderNote)}\n {group.groups.map((inner) => renderGroup(inner, depth + 1))}\n </skin.MemoryGroup>\n );\n\n const renderMerges = (): ReactNode =>\n memory.merges.map((merge) => (\n <skin.MemoryMerge\n key={merge.runId}\n finishedAt={merge.finishedAt}\n groups={merge.groups.map((item) => ({ before: item.originals.map((original) => original.content), after: item.result.content }))}\n revertible={merge.revertible}\n {...(merge.revertible && canChange && { onRevert: () => void revert(merge.runId) })}\n />\n ));\n\n const usable = memory.state === \"ready\" || memory.state === \"empty\";\n return (\n <skin.MemoryScreen\n status={memory.state}\n {...(memory.counts && { counts: memory.counts })}\n {...(usable && !editorOpen && { onAdd: openAdd })}\n {...(memory.state === \"ready\" && canChange && { onClearAll: () => openConfirm({ kind: \"all\" }) })}\n {...((memory.state === \"error\") && { onRetry: memory.reload })}\n {...(notice && { error: notice })}\n {...(editorOpen && draft?.id === null && { newNoteEditor: renderEditor(\"create\") })}\n {...(memory.state === \"ready\" && memory.merges.length > 0 && { merges: renderMerges() })}\n {...(confirmTarget && {\n confirm: (\n <skin.MemoryConfirm\n target={confirmTarget}\n pending={pending}\n {...(confirmError && { error: confirmError })}\n onConfirm={() => void confirmAction()}\n onCancel={closeConfirm}\n />\n ),\n })}\n >\n {memory.state === \"ready\" && buildMemoryTree(notes).map((group) => renderGroup(group, 0))}\n </skin.MemoryScreen>\n );\n}\n","import type { MemoryNote } from \"./memory-types\";\n\n/** Раздел дерева: записи, лежащие прямо в нём, и вложенные разделы. */\nexport interface MemoryTreeGroup {\n name: string;\n /** Путь от верхнего раздела, имена в нижнем регистре: годится в ключ. */\n key: string;\n notes: MemoryNote[];\n groups: MemoryTreeGroup[];\n /** Записей в разделе вместе с вложенными. */\n count: number;\n}\n\n/**\n * Раскладывает записи по разделам: запись с разделами `[\"О себе\", \"Работа\"]` лежит в «Работа» внутри «О себе».\n * Разделы, различающиеся только регистром, сливаются (имя берётся у первой записи); разделы идут по алфавиту,\n * записи — в порядке списка.\n */\nexport function buildMemoryTree(notes: readonly MemoryNote[]): MemoryTreeGroup[] {\n const roots: MemoryTreeGroup[] = [];\n for (const note of notes) {\n let level = roots;\n let key = \"\";\n note.categories.forEach((name, index) => {\n key = `${key}/${name.trim().toLowerCase()}`;\n let group = level.find((candidate) => candidate.key === key);\n if (!group) {\n group = { name: name.trim(), key, notes: [], groups: [], count: 0 };\n level.push(group);\n }\n group.count += 1;\n if (index === note.categories.length - 1) group.notes.push(note);\n level = group.groups;\n });\n }\n return sortGroups(roots);\n}\n\nfunction sortGroups(groups: MemoryTreeGroup[]): MemoryTreeGroup[] {\n for (const group of groups) sortGroups(group.groups);\n return groups.sort((a, b) => a.name.localeCompare(b.name, \"ru\"));\n}\n","/** Запись памяти, как её отдаёт сервер (`GET {api}/memory`). Владельца в ответах нет. */\nexport interface MemoryNote {\n id: string;\n /** Раздел записи от общего к частному: от 1 до 3 уровней. */\n categories: string[];\n content: string;\n /** Замок: правка и удаление называют версию, которую человек видел. */\n version: number;\n createdAt: string;\n updatedAt: string;\n}\n\n/** То, что человек вводит: разделы и текст. */\nexport interface MemoryNoteInput {\n categories: string[];\n content: string;\n}\n\nexport interface MemoryCounts {\n /** Записей всего. */\n total: number;\n /** Предел записей. */\n limit: number;\n /** Сколько записей видит агент в каждом запросе (остальные он находит поиском). */\n shown: number;\n}\n\n/** Одна склейка повторов: `originals` («было») заменены записью `result` («стало»). */\nexport interface MemoryMergeGroup {\n result: MemoryNote;\n originals: MemoryNote[];\n}\n\n/** Слияние одного запуска оптимизации (`GET {api}/memory/merges`), новейшие первыми. */\nexport interface MemoryMerge {\n runId: string;\n /** Время в формате ISO 8601. */\n finishedAt: string;\n /** Можно ли «Вернуть как было»: результаты склеек с тех пор не менялись. */\n revertible: boolean;\n groups: MemoryMergeGroup[];\n}\n\n/**\n * Обмен с сервером памяти. Каждый метод при отказе бросает `MemoryRequestError`\n * (обрыв связи — обычная ошибка `fetch`). `createMemoryApi` строит его по адресам `{api}/memory`.\n */\nexport interface MemoryApi {\n list(): Promise<{ notes: MemoryNote[]; counts: MemoryCounts }>;\n create(input: MemoryNoteInput): Promise<MemoryNote>;\n update(id: string, version: number, input: MemoryNoteInput): Promise<MemoryNote>;\n remove(id: string, version: number): Promise<void>;\n /** Стирает всю память человека; возвращает, сколько записей удалено. */\n clear(): Promise<number>;\n /** Склейки повторов, которые можно показать и вернуть. */\n listMerges(): Promise<MemoryMerge[]>;\n /** Возвращает слияние как было. Отказ 409: `conflict` — результат с тех пор менялся; `limit_reached` — памяти не хватит места; `duplicate` — запись, что вернулась бы, уже добавлена заново. */\n revertMerge(runId: string): Promise<void>;\n}\n\n/** Что пошло не так с действием: коды сервера (`conflict` — слияние вернуть нельзя) плюс `unavailable` (памяти нет) и `other` (сбой, обрыв связи). */\nexport type MemoryFailure = \"not_found\" | \"stale_version\" | \"limit_reached\" | \"duplicate\" | \"invalid\" | \"conflict\" | \"unavailable\" | \"other\";\n\n/** Итог действия. Отказ — не исключение: скину нужна надпись, а не стек. */\nexport type MemoryActionResult = { ok: true } | { ok: false; failure: MemoryFailure };\n\n/**\n * Состояние экрана: `loading` — грузится; `ready` — записи есть; `empty` — записей нет; `unavailable` — сервер ответил\n * 404 (память в приложении не включена); `error` — список не загрузился.\n */\nexport type MemoryStatus = \"loading\" | \"ready\" | \"empty\" | \"unavailable\" | \"error\";\n\n/** Форма записи (как у пакета памяти): до 3 разделов по 60 знаков, текст до 500. Экран проверяет её до отправки. */\nexport const memoryNoteLimits = { maxCategories: 3, maxCategoryChars: 60, maxContentChars: 500 } as const;\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { MemoryRequestError } from \"./memory-client\";\nimport type { MemoryActionResult, MemoryApi, MemoryCounts, MemoryFailure, MemoryMerge, MemoryNote, MemoryNoteInput, MemoryStatus } from \"./memory-types\";\n\nexport interface MemoryNotesState {\n state: MemoryStatus;\n /** Записи в порядке сервера: новейшие первыми. */\n notes: MemoryNote[];\n /** `null`, пока список не загружен. */\n counts: MemoryCounts | null;\n /** Склейки повторов, новейшие первыми; пусто, если их нет или сервер их не отдаёт. */\n merges: MemoryMerge[];\n /** Как закончилось последнее действие, если отказом; следующее действие сбрасывает. */\n error: MemoryFailure | null;\n add(input: MemoryNoteInput): Promise<MemoryActionResult>;\n /** Уходит версия записи из списка. Конфликт версий: список перезагружен, вызывающий держит свои значения и может повторить. */\n edit(id: string, input: MemoryNoteInput): Promise<MemoryActionResult>;\n remove(id: string): Promise<MemoryActionResult>;\n clear(): Promise<MemoryActionResult>;\n /** «Вернуть как было»: оригиналы возвращаются вместо результата, список записей и склеек загружается заново. */\n revertMerge(runId: string): Promise<MemoryActionResult>;\n /** Загружает список заново: после отказа — с экраном загрузки, иначе тихо. */\n reload(): void;\n}\n\nconst knownKinds = new Set<string>([\"not_found\", \"stale_version\", \"limit_reached\", \"duplicate\", \"invalid\", \"conflict\"]);\n\n/** Отказ действия по ответу сервера. 404 без `not_found` в теле — адреса памяти нет совсем. */\nfunction failureOf(error: unknown): MemoryFailure {\n if (!(error instanceof MemoryRequestError)) return \"other\";\n if (error.kind !== undefined && knownKinds.has(error.kind)) return error.kind as MemoryFailure;\n return error.status === 404 ? \"unavailable\" : \"other\";\n}\n\n/** Состояние экрана памяти: загрузка, добавление, правка, удаление, стирание. Ничего не рисует. */\nexport function useMemoryNotes(api: MemoryApi): MemoryNotesState {\n const [status, setStatus] = useState<MemoryStatus>(\"loading\");\n const [notes, setNotes] = useState<MemoryNote[]>([]);\n const [counts, setCounts] = useState<MemoryCounts | null>(null);\n const [merges, setMerges] = useState<MemoryMerge[]>([]);\n const [error, setError] = useState<MemoryFailure | null>(null);\n\n const apiRef = useRef(api);\n apiRef.current = api;\n const notesRef = useRef(notes);\n notesRef.current = notes;\n const statusRef = useRef(status);\n statusRef.current = status;\n const countsRef = useRef(counts);\n countsRef.current = counts;\n /** Номер последнего запроса списка: ответ более раннего не затирает свежий. */\n const listRequest = useRef(0);\n const alive = useRef(true);\n useEffect(() => {\n alive.current = true;\n return () => {\n alive.current = false;\n };\n }, []);\n\n const show = useCallback((next: MemoryNote[], nextCounts: MemoryCounts | null) => {\n notesRef.current = next;\n setNotes(next);\n setCounts(nextCounts);\n setStatus(next.length === 0 ? \"empty\" : \"ready\");\n }, []);\n\n /** `quiet`: список уже на экране, экран загрузки не нужен, отказ не ломает то, что показано. */\n const load = useCallback(\n async (quiet: boolean) => {\n const request = ++listRequest.current;\n if (!quiet) setStatus(\"loading\");\n try {\n // Склейки — дополнение к списку: их сбой (или сервер без склеек) записи не ломает, прежние склейки остаются.\n const [loaded, loadedMerges] = await Promise.all([apiRef.current.list(), apiRef.current.listMerges().catch(() => null)]);\n if (!alive.current || request !== listRequest.current) return;\n show(loaded.notes, loaded.counts);\n if (loadedMerges) setMerges(loadedMerges);\n } catch (failure) {\n if (!alive.current || request !== listRequest.current || quiet) return;\n setStatus(failureOf(failure) === \"unavailable\" ? \"unavailable\" : \"error\");\n }\n },\n [show],\n );\n\n useEffect(() => {\n void load(false);\n }, [api, load]);\n\n const reload = useCallback(() => {\n void load(statusRef.current === \"ready\" || statusRef.current === \"empty\");\n }, [load]);\n\n /** Действие с отказом: его причина остаётся в `error`; при конфликте список заново. */\n const refuse = useCallback(\n async (failure: MemoryFailure): Promise<MemoryActionResult> => {\n if (failure === \"stale_version\" || failure === \"not_found\" || failure === \"conflict\") await load(true);\n if (alive.current) setError(failure);\n return { ok: false, failure };\n },\n [load],\n );\n\n const succeed = useCallback(\n (next: MemoryNote[]): MemoryActionResult => {\n show(next, countsRef.current === null ? null : { ...countsRef.current, total: next.length });\n // Счётчик «показано агенту» считает сервер: тихо спрашиваем его после изменения.\n void load(true);\n return { ok: true };\n },\n [load, show],\n );\n\n const add = useCallback(\n async (input: MemoryNoteInput): Promise<MemoryActionResult> => {\n setError(null);\n try {\n const created = await apiRef.current.create(input);\n return alive.current ? succeed([created, ...notesRef.current]) : { ok: true };\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n },\n [refuse, succeed],\n );\n\n const edit = useCallback(\n async (id: string, input: MemoryNoteInput): Promise<MemoryActionResult> => {\n setError(null);\n const current = notesRef.current.find((item) => item.id === id);\n if (!current) return refuse(\"not_found\");\n try {\n const saved = await apiRef.current.update(id, current.version, input);\n return alive.current ? succeed([saved, ...notesRef.current.filter((item) => item.id !== id)]) : { ok: true };\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n },\n [refuse, succeed],\n );\n\n const remove = useCallback(\n async (id: string): Promise<MemoryActionResult> => {\n setError(null);\n const current = notesRef.current.find((item) => item.id === id);\n if (!current) return refuse(\"not_found\");\n try {\n await apiRef.current.remove(id, current.version);\n } catch (failure) {\n const kind = failureOf(failure);\n // Записи уже нет — человек этого и хотел.\n if (kind !== \"not_found\") return refuse(kind);\n }\n return alive.current ? succeed(notesRef.current.filter((item) => item.id !== id)) : { ok: true };\n },\n [refuse, succeed],\n );\n\n const clear = useCallback(async (): Promise<MemoryActionResult> => {\n setError(null);\n try {\n await apiRef.current.clear();\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n return alive.current ? succeed([]) : { ok: true };\n }, [refuse, succeed]);\n\n const revertMerge = useCallback(\n async (runId: string): Promise<MemoryActionResult> => {\n setError(null);\n try {\n await apiRef.current.revertMerge(runId);\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n if (!alive.current) return { ok: true };\n await load(true);\n return { ok: true };\n },\n [load, refuse],\n );\n\n return { state: status, notes, counts, merges, error, add, edit, remove, clear, revertMerge, reload };\n}\n","import type { MemoryApi, MemoryCounts, MemoryMerge, MemoryNote, MemoryNoteInput } from \"./memory-types\";\n\ntype Fetch = typeof globalThis.fetch;\n\n/** Сервер ответил отказом; `status` — код ответа, `kind` — `error.kind` из тела, если оно было (404 без тела — памяти нет). */\nexport class MemoryRequestError extends Error {\n constructor(\n readonly status: number,\n readonly kind: string | undefined,\n action: string,\n ) {\n super(`Memory request failed (${action}): HTTP ${status}${kind ? ` ${kind}` : \"\"}`);\n this.name = \"MemoryRequestError\";\n }\n}\n\nconst jsonBody = (body: unknown): RequestInit => ({\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n});\n\nasync function readKind(response: Response): Promise<string | undefined> {\n try {\n const body = (await response.json()) as { error?: { kind?: unknown } };\n return typeof body.error?.kind === \"string\" ? body.error.kind : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Клиент адресов памяти сервера: `GET/POST {api}/memory`, `PATCH/DELETE {api}/memory/{id}`, `DELETE {api}/memory`,\n * склейки повторов — `GET {api}/memory/merges` и `POST {api}/memory/merges/{runId}/revert`.\n * `api` — тот же адрес обработчика чата, например `/api/chat`. Вызывайте один раз (например, в `useMemo`):\n * экран загружает список заново при смене объекта.\n */\nexport function createMemoryApi({ api, fetch: customFetch }: { api: string; fetch?: Fetch }): MemoryApi {\n const base = `${api.replace(/\\/+$/, \"\")}/memory`;\n const noteUrl = (id: string) => `${base}/${encodeURIComponent(id)}`;\n\n async function request(action: string, url: string, init?: RequestInit): Promise<Response> {\n const response = await (customFetch ?? globalThis.fetch)(url, init);\n if (!response.ok) throw new MemoryRequestError(response.status, await readKind(response), action);\n return response;\n }\n\n return {\n async list() {\n const response = await request(\"list\", base);\n return (await response.json()) as { notes: MemoryNote[]; counts: MemoryCounts };\n },\n async create(input: MemoryNoteInput) {\n const response = await request(\"create\", base, { method: \"POST\", ...jsonBody(input) });\n return ((await response.json()) as { note: MemoryNote }).note;\n },\n async update(id, version, input) {\n const response = await request(\"update\", noteUrl(id), { method: \"PATCH\", ...jsonBody({ ...input, version }) });\n return ((await response.json()) as { note: MemoryNote }).note;\n },\n async remove(id, version) {\n await request(\"remove\", `${noteUrl(id)}?version=${version}`, { method: \"DELETE\" });\n },\n async clear() {\n const response = await request(\"clear\", base, { method: \"DELETE\" });\n return ((await response.json()) as { deleted: number }).deleted;\n },\n async listMerges() {\n const response = await request(\"listMerges\", `${base}/merges`);\n return ((await response.json()) as { merges?: MemoryMerge[] }).merges ?? [];\n },\n async revertMerge(runId) {\n await request(\"revertMerge\", `${base}/merges/${encodeURIComponent(runId)}/revert`, { method: \"POST\" });\n },\n };\n}\n"],"mappings":"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","names":["useCallback","useState","useCallback","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","jsx","useState","useCallback"]}
1
+ {"version":3,"sources":["../src/chat-list/chat-workspace.tsx","../src/memory/agent-memory.tsx","../src/memory/memory-tree.ts","../src/memory/memory-types.ts","../src/memory/use-memory-notes.ts","../src/memory/memory-client.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { AgentChat } from \"../agent-chat\";\nimport type { AgentChatProps } from \"../agent-chat\";\nimport type { ChatAccess } from \"../chat-access\";\nimport { useChatLabels } from \"../labels\";\nimport type { ChatSkin } from \"../skin-contract\";\nimport type { ChatListSkin } from \"./chat-list-skin\";\nimport { useChatList } from \"./use-chat-list\";\nimport { activeChatTitle, useChatListView } from \"./use-chat-list-view\";\nimport { useWorkspaceListSync } from \"./use-workspace-list-sync\";\n\nexport interface ChatWorkspaceProps extends Omit<AgentChatProps, \"chatId\" | \"subject\" | \"skin\" | \"onBusyChange\" | \"onChatUnavailable\" | \"initialDraft\"> {\n /** Скин чата плюс скин списка (например, `tandemSkin`). */\n skin: ChatSkin & ChatListSkin;\n /** Какой чат открыть первым, например из адреса страницы. Главнее запомненного; чата нет — «Чат не найден». */\n initialChatId?: string;\n /** Открытый чат сменился (выбор, создание, удаление): например, чтобы поменять адрес страницы. */\n onChatChange?: (chatId: string | null) => void;\n}\n\n/**\n * Чат со списком чатов рядом: выбрать, создать, переименовать, удалить. Всё видимое рисует скин. У открытого чата\n * запрещено право `chatList` — списка нет, только чат: он не показывается и не обновляется, пока права открытого чата неизвестны\n * (до первой истории) и если запрещён.\n */\nexport function ChatWorkspace({ skin, initialChatId, onChatChange, onChatAccessChange, ...chatProps }: ChatWorkspaceProps) {\n const labels = useChatLabels();\n const { api } = chatProps;\n const list = useChatList({ api, ...(initialChatId !== undefined && { initialChatId }), ...(chatProps.onTurnRefused && { onCreateRefused: chatProps.onTurnRefused }) });\n const [listOpen, setListOpen] = useState(false);\n const [activeBusy, setActiveBusy] = useState(false);\n const { activeChatId } = list;\n /**\n * Список показан только когда известно, что он разрешён: права приходят с первой историей открытого чата, и список,\n * который тут же спрятали бы (`chatList: false`), не должен мигать. Выбор другого чата список не прячет: права нового\n * чата приходят позже, а список в это время остаётся на месте. Не открылся чат или сам список — список виден: из него\n * выбирают другой чат.\n */\n const [listAllowed, setListAllowed] = useState(false);\n const reportAccess = useRef(onChatAccessChange);\n reportAccess.current = onChatAccessChange;\n const onActiveAccessChange = useCallback((access: ChatAccess) => {\n setListAllowed(access.capabilities.chatList);\n reportAccess.current?.(access);\n }, []);\n const onActiveChatUnavailable = useCallback(() => setListAllowed(true), []);\n const listShown = listAllowed || (activeChatId === null && list.status !== \"loading\");\n\n const reportChange = useRef(onChatChange);\n reportChange.current = onChatChange;\n useEffect(() => {\n reportChange.current?.(activeChatId);\n }, [activeChatId]);\n\n // Список скрыт — его не запрашивают: ни при смене занятости, ни ради названия чата.\n useWorkspaceListSync(list, activeBusy, listShown);\n const closeList = useCallback(() => setListOpen(false), []);\n const listView = useChatListView(list, closeList);\n\n return (\n <skin.ChatWorkspace\n activeTitle={activeChatTitle(list, labels)}\n listOpen={listOpen && listShown}\n onListOpenChange={setListOpen}\n {...(listShown && { list: <skin.ChatList {...listView} /> })}\n >\n {activeChatId !== null && (\n <AgentChat key={activeChatId} {...chatProps} chatId={activeChatId} skin={skin} onBusyChange={setActiveBusy} onChatAccessChange={onActiveAccessChange} onChatUnavailable={onActiveChatUnavailable} />\n )}\n </skin.ChatWorkspace>\n );\n}\n","import { useCallback, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useChatLabels } from \"../labels\";\nimport type { ChatLabels } from \"../labels\";\nimport type { MemoryConfirmTarget, MemoryNoteEditorErrors, MemorySkin } from \"./memory-skin\";\nimport { buildMemoryTree } from \"./memory-tree\";\nimport type { MemoryTreeGroup } from \"./memory-tree\";\nimport { memoryNoteLimits } from \"./memory-types\";\nimport type { MemoryActionResult, MemoryApi, MemoryFailure, MemoryNote, MemoryNoteInput } from \"./memory-types\";\nimport { useMemoryNotes } from \"./use-memory-notes\";\n\nexport interface AgentMemoryProps {\n /** Обмен с сервером: `createMemoryApi({ api: \"/api/chat\" })`. Создайте один раз — при смене объекта список грузится заново. */\n api: MemoryApi;\n /** Скин экрана памяти (договор 11), например `tandemMemorySkin`. */\n skin: MemorySkin;\n}\n\ninterface Draft {\n /** `null` — новая запись. */\n id: string | null;\n path: string;\n content: string;\n}\n\ntype Confirm = { kind: \"note\"; id: string; content: string } | { kind: \"all\" };\n\nconst squeeze = (text: string) => text.replace(/\\s+/g, \" \").trim();\n\n/** Форма черновика → ввод для сервера либо ошибки полей. Пределы те же, что у пакета памяти. */\nfunction readDraft(draft: Draft, labels: ChatLabels): { input: MemoryNoteInput } | { errors: MemoryNoteEditorErrors } {\n const errors: MemoryNoteEditorErrors = {};\n const categories = draft.path.split(\"/\").map(squeeze).filter((part) => part !== \"\");\n if (categories.length === 0) errors.path = labels.memoryFieldErrors.pathRequired;\n else if (categories.length > memoryNoteLimits.maxCategories) errors.path = labels.memoryFieldErrors.pathTooMany;\n else if (categories.some((part) => part.length > memoryNoteLimits.maxCategoryChars)) errors.path = labels.memoryFieldErrors.pathTooLong;\n const content = squeeze(draft.content);\n if (content === \"\") errors.content = labels.memoryFieldErrors.contentRequired;\n else if (content.length > memoryNoteLimits.maxContentChars) errors.content = labels.memoryFieldErrors.contentTooLong;\n return errors.path || errors.content ? { errors } : { input: { categories, content } };\n}\n\n/**\n * Экран памяти агента: записи по разделам, добавление, правка, удаление, «Стереть всю память» и блок «Объединено» с кнопкой «Вернуть как было».\n * Всё видимое рисует скин; здесь — состояние, черновики и проверка ввода.\n */\nexport function AgentMemory({ api, skin }: AgentMemoryProps) {\n const labels = useChatLabels();\n const memory = useMemoryNotes(api);\n const { notes, edit: editNote, add: addNote, remove: removeNote, clear: clearNotes, revertMerge } = memory;\n const [draft, setDraft] = useState<Draft | null>(null);\n const [fieldErrors, setFieldErrors] = useState<MemoryNoteEditorErrors>({});\n const [confirm, setConfirm] = useState<Confirm | null>(null);\n const [confirmError, setConfirmError] = useState<string | undefined>();\n const [notice, setNotice] = useState<string | undefined>();\n const [pending, setPending] = useState(false);\n\n // Правка записи, которой уже нет в списке (её удалили в другом месте), закрывается сама.\n const editorOpen = draft !== null && (draft.id === null || notes.some((item) => item.id === draft.id));\n const failureText = (failure: MemoryFailure) => labels.memoryErrors[failure];\n\n const run = useCallback(async (action: () => Promise<MemoryActionResult>): Promise<MemoryActionResult> => {\n setPending(true);\n try {\n return await action();\n } finally {\n setPending(false);\n }\n }, []);\n\n const openAdd = () => {\n setNotice(undefined);\n setFieldErrors({});\n setDraft({ id: null, path: \"\", content: \"\" });\n };\n const openEdit = (note: MemoryNote) => {\n setNotice(undefined);\n setFieldErrors({});\n setDraft({ id: note.id, path: note.categories.join(\" / \"), content: note.content });\n };\n const closeEditor = () => {\n setDraft(null);\n setFieldErrors({});\n };\n const changeDraft = (change: Partial<Pick<Draft, \"path\" | \"content\">>) => {\n setDraft((current) => current && { ...current, ...change });\n setFieldErrors({});\n };\n\n const save = async () => {\n if (!draft) return;\n const read = readDraft(draft, labels);\n if (\"errors\" in read) {\n setFieldErrors(read.errors);\n return;\n }\n setFieldErrors({});\n const result = await run(() => (draft.id === null ? addNote(read.input) : editNote(draft.id, read.input)));\n if (result.ok) {\n setDraft(null);\n } else if (result.failure === \"not_found\" && draft.id !== null) {\n // Записи нет: форму закрываем, причину говорим на экране.\n setDraft(null);\n setNotice(failureText(\"not_found\"));\n } else {\n setFieldErrors({ form: failureText(result.failure) });\n }\n };\n\n const revert = async (runId: string) => {\n setNotice(undefined);\n const result = await run(() => revertMerge(runId));\n if (result.ok) return;\n const { failure } = result;\n setNotice(failure === \"limit_reached\" || failure === \"duplicate\" ? labels.memoryRevertErrors[failure] : failureText(failure));\n };\n\n const openConfirm = (next: Confirm) => {\n setNotice(undefined);\n setConfirmError(undefined);\n setConfirm(next);\n };\n const closeConfirm = () => {\n setConfirm(null);\n setConfirmError(undefined);\n };\n const confirmAction = async () => {\n if (!confirm) return;\n const result = await run(() => (confirm.kind === \"note\" ? removeNote(confirm.id) : clearNotes()));\n if (result.ok) {\n closeConfirm();\n } else if (result.failure === \"stale_version\" || result.failure === \"not_found\") {\n // Запись уже другая или её нет: список обновлён, человек решает заново.\n closeConfirm();\n setNotice(failureText(result.failure));\n } else {\n setConfirmError(failureText(result.failure));\n }\n };\n\n const confirmTarget: MemoryConfirmTarget | null =\n confirm === null ? null : confirm.kind === \"note\" ? { kind: \"note\", content: confirm.content } : { kind: \"all\", total: notes.length };\n\n const canChange = !pending;\n const renderEditor = (mode: \"create\" | \"edit\"): ReactNode =>\n draft && (\n <skin.MemoryNoteEditor\n mode={mode}\n path={draft.path}\n content={draft.content}\n onPathChange={(path) => changeDraft({ path })}\n onContentChange={(content) => changeDraft({ content })}\n onSave={() => void save()}\n onCancel={closeEditor}\n canSave={draft.path.trim() !== \"\" && draft.content.trim() !== \"\"}\n pending={pending}\n errors={fieldErrors}\n />\n );\n\n const renderNote = (note: MemoryNote): ReactNode =>\n editorOpen && draft?.id === note.id ? (\n <div key={note.id}>{renderEditor(\"edit\")}</div>\n ) : (\n <skin.MemoryNoteRow\n key={note.id}\n content={note.content}\n {...(canChange && { onEdit: () => openEdit(note), onDelete: () => openConfirm({ kind: \"note\", id: note.id, content: note.content }) })}\n />\n );\n\n const renderGroup = (group: MemoryTreeGroup, depth: number): ReactNode => (\n <skin.MemoryGroup key={group.key} name={group.name} depth={depth} count={group.count}>\n {group.notes.map(renderNote)}\n {group.groups.map((inner) => renderGroup(inner, depth + 1))}\n </skin.MemoryGroup>\n );\n\n const renderMerges = (): ReactNode =>\n memory.merges.map((merge) => (\n <skin.MemoryMerge\n key={merge.runId}\n finishedAt={merge.finishedAt}\n groups={merge.groups.map((item) => ({ before: item.originals.map((original) => original.content), after: item.result.content }))}\n revertible={merge.revertible}\n {...(merge.revertible && canChange && { onRevert: () => void revert(merge.runId) })}\n />\n ));\n\n const usable = memory.state === \"ready\" || memory.state === \"empty\";\n return (\n <skin.MemoryScreen\n status={memory.state}\n {...(memory.counts && { counts: memory.counts })}\n {...(usable && !editorOpen && { onAdd: openAdd })}\n {...(memory.state === \"ready\" && canChange && { onClearAll: () => openConfirm({ kind: \"all\" }) })}\n {...((memory.state === \"error\") && { onRetry: memory.reload })}\n {...(notice && { error: notice })}\n {...(editorOpen && draft?.id === null && { newNoteEditor: renderEditor(\"create\") })}\n {...(memory.state === \"ready\" && memory.merges.length > 0 && { merges: renderMerges() })}\n {...(confirmTarget && {\n confirm: (\n <skin.MemoryConfirm\n target={confirmTarget}\n pending={pending}\n {...(confirmError && { error: confirmError })}\n onConfirm={() => void confirmAction()}\n onCancel={closeConfirm}\n />\n ),\n })}\n >\n {memory.state === \"ready\" && buildMemoryTree(notes).map((group) => renderGroup(group, 0))}\n </skin.MemoryScreen>\n );\n}\n","import type { MemoryNote } from \"./memory-types\";\n\n/** Раздел дерева: записи, лежащие прямо в нём, и вложенные разделы. */\nexport interface MemoryTreeGroup {\n name: string;\n /** Путь от верхнего раздела, имена в нижнем регистре: годится в ключ. */\n key: string;\n notes: MemoryNote[];\n groups: MemoryTreeGroup[];\n /** Записей в разделе вместе с вложенными. */\n count: number;\n}\n\n/**\n * Раскладывает записи по разделам: запись с разделами `[\"О себе\", \"Работа\"]` лежит в «Работа» внутри «О себе».\n * Разделы, различающиеся только регистром, сливаются (имя берётся у первой записи); разделы идут по алфавиту,\n * записи — в порядке списка.\n */\nexport function buildMemoryTree(notes: readonly MemoryNote[]): MemoryTreeGroup[] {\n const roots: MemoryTreeGroup[] = [];\n for (const note of notes) {\n let level = roots;\n let key = \"\";\n note.categories.forEach((name, index) => {\n key = `${key}/${name.trim().toLowerCase()}`;\n let group = level.find((candidate) => candidate.key === key);\n if (!group) {\n group = { name: name.trim(), key, notes: [], groups: [], count: 0 };\n level.push(group);\n }\n group.count += 1;\n if (index === note.categories.length - 1) group.notes.push(note);\n level = group.groups;\n });\n }\n return sortGroups(roots);\n}\n\nfunction sortGroups(groups: MemoryTreeGroup[]): MemoryTreeGroup[] {\n for (const group of groups) sortGroups(group.groups);\n return groups.sort((a, b) => a.name.localeCompare(b.name, \"ru\"));\n}\n","/** Запись памяти, как её отдаёт сервер (`GET {api}/memory`). Владельца в ответах нет. */\nexport interface MemoryNote {\n id: string;\n /** Раздел записи от общего к частному: от 1 до 3 уровней. */\n categories: string[];\n content: string;\n /** Замок: правка и удаление называют версию, которую человек видел. */\n version: number;\n createdAt: string;\n updatedAt: string;\n}\n\n/** То, что человек вводит: разделы и текст. */\nexport interface MemoryNoteInput {\n categories: string[];\n content: string;\n}\n\nexport interface MemoryCounts {\n /** Записей всего. */\n total: number;\n /** Предел записей. */\n limit: number;\n /** Сколько записей видит агент в каждом запросе (остальные он находит поиском). */\n shown: number;\n}\n\n/** Одна склейка повторов: `originals` («было») заменены записью `result` («стало»). */\nexport interface MemoryMergeGroup {\n result: MemoryNote;\n originals: MemoryNote[];\n}\n\n/** Слияние одного запуска оптимизации (`GET {api}/memory/merges`), новейшие первыми. */\nexport interface MemoryMerge {\n runId: string;\n /** Время в формате ISO 8601. */\n finishedAt: string;\n /** Можно ли «Вернуть как было»: результаты склеек с тех пор не менялись. */\n revertible: boolean;\n groups: MemoryMergeGroup[];\n}\n\n/**\n * Обмен с сервером памяти. Каждый метод при отказе бросает `MemoryRequestError`\n * (обрыв связи — обычная ошибка `fetch`). `createMemoryApi` строит его по адресам `{api}/memory`.\n */\nexport interface MemoryApi {\n list(): Promise<{ notes: MemoryNote[]; counts: MemoryCounts }>;\n create(input: MemoryNoteInput): Promise<MemoryNote>;\n update(id: string, version: number, input: MemoryNoteInput): Promise<MemoryNote>;\n remove(id: string, version: number): Promise<void>;\n /** Стирает всю память человека; возвращает, сколько записей удалено. */\n clear(): Promise<number>;\n /** Склейки повторов, которые можно показать и вернуть. */\n listMerges(): Promise<MemoryMerge[]>;\n /** Возвращает слияние как было. Отказ 409: `conflict` — результат с тех пор менялся; `limit_reached` — памяти не хватит места; `duplicate` — запись, что вернулась бы, уже добавлена заново. */\n revertMerge(runId: string): Promise<void>;\n}\n\n/** Что пошло не так с действием: коды сервера (`conflict` — слияние вернуть нельзя) плюс `unavailable` (памяти нет) и `other` (сбой, обрыв связи). */\nexport type MemoryFailure = \"not_found\" | \"stale_version\" | \"limit_reached\" | \"duplicate\" | \"invalid\" | \"conflict\" | \"unavailable\" | \"other\";\n\n/** Итог действия. Отказ — не исключение: скину нужна надпись, а не стек. */\nexport type MemoryActionResult = { ok: true } | { ok: false; failure: MemoryFailure };\n\n/**\n * Состояние экрана: `loading` — грузится; `ready` — записи есть; `empty` — записей нет; `unavailable` — сервер ответил\n * 404 (память в приложении не включена); `error` — список не загрузился.\n */\nexport type MemoryStatus = \"loading\" | \"ready\" | \"empty\" | \"unavailable\" | \"error\";\n\n/** Форма записи (как у пакета памяти): до 3 разделов по 60 знаков, текст до 500. Экран проверяет её до отправки. */\nexport const memoryNoteLimits = { maxCategories: 3, maxCategoryChars: 60, maxContentChars: 500 } as const;\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { MemoryRequestError } from \"./memory-client\";\nimport type { MemoryActionResult, MemoryApi, MemoryCounts, MemoryFailure, MemoryMerge, MemoryNote, MemoryNoteInput, MemoryStatus } from \"./memory-types\";\n\nexport interface MemoryNotesState {\n state: MemoryStatus;\n /** Записи в порядке сервера: новейшие первыми. */\n notes: MemoryNote[];\n /** `null`, пока список не загружен. */\n counts: MemoryCounts | null;\n /** Склейки повторов, новейшие первыми; пусто, если их нет или сервер их не отдаёт. */\n merges: MemoryMerge[];\n /** Как закончилось последнее действие, если отказом; следующее действие сбрасывает. */\n error: MemoryFailure | null;\n add(input: MemoryNoteInput): Promise<MemoryActionResult>;\n /** Уходит версия записи из списка. Конфликт версий: список перезагружен, вызывающий держит свои значения и может повторить. */\n edit(id: string, input: MemoryNoteInput): Promise<MemoryActionResult>;\n remove(id: string): Promise<MemoryActionResult>;\n clear(): Promise<MemoryActionResult>;\n /** «Вернуть как было»: оригиналы возвращаются вместо результата, список записей и склеек загружается заново. */\n revertMerge(runId: string): Promise<MemoryActionResult>;\n /** Загружает список заново: после отказа — с экраном загрузки, иначе тихо. */\n reload(): void;\n}\n\nconst knownKinds = new Set<string>([\"not_found\", \"stale_version\", \"limit_reached\", \"duplicate\", \"invalid\", \"conflict\"]);\n\n/** Отказ действия по ответу сервера. 404 без `not_found` в теле — адреса памяти нет совсем. */\nfunction failureOf(error: unknown): MemoryFailure {\n if (!(error instanceof MemoryRequestError)) return \"other\";\n if (error.kind !== undefined && knownKinds.has(error.kind)) return error.kind as MemoryFailure;\n return error.status === 404 ? \"unavailable\" : \"other\";\n}\n\n/** Состояние экрана памяти: загрузка, добавление, правка, удаление, стирание. Ничего не рисует. */\nexport function useMemoryNotes(api: MemoryApi): MemoryNotesState {\n const [status, setStatus] = useState<MemoryStatus>(\"loading\");\n const [notes, setNotes] = useState<MemoryNote[]>([]);\n const [counts, setCounts] = useState<MemoryCounts | null>(null);\n const [merges, setMerges] = useState<MemoryMerge[]>([]);\n const [error, setError] = useState<MemoryFailure | null>(null);\n\n const apiRef = useRef(api);\n apiRef.current = api;\n const notesRef = useRef(notes);\n notesRef.current = notes;\n const statusRef = useRef(status);\n statusRef.current = status;\n const countsRef = useRef(counts);\n countsRef.current = counts;\n /** Номер последнего запроса списка: ответ более раннего не затирает свежий. */\n const listRequest = useRef(0);\n const alive = useRef(true);\n useEffect(() => {\n alive.current = true;\n return () => {\n alive.current = false;\n };\n }, []);\n\n const show = useCallback((next: MemoryNote[], nextCounts: MemoryCounts | null) => {\n notesRef.current = next;\n setNotes(next);\n setCounts(nextCounts);\n setStatus(next.length === 0 ? \"empty\" : \"ready\");\n }, []);\n\n /** `quiet`: список уже на экране, экран загрузки не нужен, отказ не ломает то, что показано. */\n const load = useCallback(\n async (quiet: boolean) => {\n const request = ++listRequest.current;\n if (!quiet) setStatus(\"loading\");\n try {\n // Склейки — дополнение к списку: их сбой (или сервер без склеек) записи не ломает, прежние склейки остаются.\n const [loaded, loadedMerges] = await Promise.all([apiRef.current.list(), apiRef.current.listMerges().catch(() => null)]);\n if (!alive.current || request !== listRequest.current) return;\n show(loaded.notes, loaded.counts);\n if (loadedMerges) setMerges(loadedMerges);\n } catch (failure) {\n if (!alive.current || request !== listRequest.current || quiet) return;\n setStatus(failureOf(failure) === \"unavailable\" ? \"unavailable\" : \"error\");\n }\n },\n [show],\n );\n\n useEffect(() => {\n void load(false);\n }, [api, load]);\n\n const reload = useCallback(() => {\n void load(statusRef.current === \"ready\" || statusRef.current === \"empty\");\n }, [load]);\n\n /** Действие с отказом: его причина остаётся в `error`; при конфликте список заново. */\n const refuse = useCallback(\n async (failure: MemoryFailure): Promise<MemoryActionResult> => {\n if (failure === \"stale_version\" || failure === \"not_found\" || failure === \"conflict\") await load(true);\n if (alive.current) setError(failure);\n return { ok: false, failure };\n },\n [load],\n );\n\n const succeed = useCallback(\n (next: MemoryNote[]): MemoryActionResult => {\n show(next, countsRef.current === null ? null : { ...countsRef.current, total: next.length });\n // Счётчик «показано агенту» считает сервер: тихо спрашиваем его после изменения.\n void load(true);\n return { ok: true };\n },\n [load, show],\n );\n\n const add = useCallback(\n async (input: MemoryNoteInput): Promise<MemoryActionResult> => {\n setError(null);\n try {\n const created = await apiRef.current.create(input);\n return alive.current ? succeed([created, ...notesRef.current]) : { ok: true };\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n },\n [refuse, succeed],\n );\n\n const edit = useCallback(\n async (id: string, input: MemoryNoteInput): Promise<MemoryActionResult> => {\n setError(null);\n const current = notesRef.current.find((item) => item.id === id);\n if (!current) return refuse(\"not_found\");\n try {\n const saved = await apiRef.current.update(id, current.version, input);\n return alive.current ? succeed([saved, ...notesRef.current.filter((item) => item.id !== id)]) : { ok: true };\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n },\n [refuse, succeed],\n );\n\n const remove = useCallback(\n async (id: string): Promise<MemoryActionResult> => {\n setError(null);\n const current = notesRef.current.find((item) => item.id === id);\n if (!current) return refuse(\"not_found\");\n try {\n await apiRef.current.remove(id, current.version);\n } catch (failure) {\n const kind = failureOf(failure);\n // Записи уже нет — человек этого и хотел.\n if (kind !== \"not_found\") return refuse(kind);\n }\n return alive.current ? succeed(notesRef.current.filter((item) => item.id !== id)) : { ok: true };\n },\n [refuse, succeed],\n );\n\n const clear = useCallback(async (): Promise<MemoryActionResult> => {\n setError(null);\n try {\n await apiRef.current.clear();\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n return alive.current ? succeed([]) : { ok: true };\n }, [refuse, succeed]);\n\n const revertMerge = useCallback(\n async (runId: string): Promise<MemoryActionResult> => {\n setError(null);\n try {\n await apiRef.current.revertMerge(runId);\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n if (!alive.current) return { ok: true };\n await load(true);\n return { ok: true };\n },\n [load, refuse],\n );\n\n return { state: status, notes, counts, merges, error, add, edit, remove, clear, revertMerge, reload };\n}\n","import type { MemoryApi, MemoryCounts, MemoryMerge, MemoryNote, MemoryNoteInput } from \"./memory-types\";\n\ntype Fetch = typeof globalThis.fetch;\n\n/** Сервер ответил отказом; `status` — код ответа, `kind` — `error.kind` из тела, если оно было (404 без тела — памяти нет). */\nexport class MemoryRequestError extends Error {\n constructor(\n readonly status: number,\n readonly kind: string | undefined,\n action: string,\n ) {\n super(`Memory request failed (${action}): HTTP ${status}${kind ? ` ${kind}` : \"\"}`);\n this.name = \"MemoryRequestError\";\n }\n}\n\nconst jsonBody = (body: unknown): RequestInit => ({\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n});\n\nasync function readKind(response: Response): Promise<string | undefined> {\n try {\n const body = (await response.json()) as { error?: { kind?: unknown } };\n return typeof body.error?.kind === \"string\" ? body.error.kind : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Клиент адресов памяти сервера: `GET/POST {api}/memory`, `PATCH/DELETE {api}/memory/{id}`, `DELETE {api}/memory`,\n * склейки повторов — `GET {api}/memory/merges` и `POST {api}/memory/merges/{runId}/revert`.\n * `api` — тот же адрес обработчика чата, например `/api/chat`. Вызывайте один раз (например, в `useMemo`):\n * экран загружает список заново при смене объекта.\n */\nexport function createMemoryApi({ api, fetch: customFetch }: { api: string; fetch?: Fetch }): MemoryApi {\n const base = `${api.replace(/\\/+$/, \"\")}/memory`;\n const noteUrl = (id: string) => `${base}/${encodeURIComponent(id)}`;\n\n async function request(action: string, url: string, init?: RequestInit): Promise<Response> {\n const response = await (customFetch ?? globalThis.fetch)(url, init);\n if (!response.ok) throw new MemoryRequestError(response.status, await readKind(response), action);\n return response;\n }\n\n return {\n async list() {\n const response = await request(\"list\", base);\n return (await response.json()) as { notes: MemoryNote[]; counts: MemoryCounts };\n },\n async create(input: MemoryNoteInput) {\n const response = await request(\"create\", base, { method: \"POST\", ...jsonBody(input) });\n return ((await response.json()) as { note: MemoryNote }).note;\n },\n async update(id, version, input) {\n const response = await request(\"update\", noteUrl(id), { method: \"PATCH\", ...jsonBody({ ...input, version }) });\n return ((await response.json()) as { note: MemoryNote }).note;\n },\n async remove(id, version) {\n await request(\"remove\", `${noteUrl(id)}?version=${version}`, { method: \"DELETE\" });\n },\n async clear() {\n const response = await request(\"clear\", base, { method: \"DELETE\" });\n return ((await response.json()) as { deleted: number }).deleted;\n },\n async listMerges() {\n const response = await request(\"listMerges\", `${base}/merges`);\n return ((await response.json()) as { merges?: MemoryMerge[] }).merges ?? [];\n },\n async revertMerge(runId) {\n await request(\"revertMerge\", `${base}/merges/${encodeURIComponent(runId)}/revert`, { method: \"POST\" });\n },\n };\n}\n"],"mappings":"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","names":["useCallback","useState","useCallback","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","jsx","useState","useCallback"]}
package/dist/panel.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  useChatList,
7
7
  useChatListView,
8
8
  useWorkspaceListSync
9
- } from "./chunk-JLGBVDAR.js";
9
+ } from "./chunk-Z7X5D4OF.js";
10
10
 
11
11
  // src/agent-panel/agent-panel-provider.tsx
12
12
  import { useCallback as useCallback2, useMemo, useRef, useState, useSyncExternalStore as useSyncExternalStore2 } from "react";
@@ -67,6 +67,8 @@ export interface ChatMessageMetadata {
67
67
  usage?: TokenUsage;
68
68
  /** Длительность рассуждений по порядковому номеру части рассуждения в сообщении. */
69
69
  reasoningDurationsMs?: Record<string, number>;
70
+ /** Заголовки раздумий по тому же номеру («Продумываю правила игры»): есть, если сервер включил `thoughtTitles`. */
71
+ thoughtTitles?: Record<string, string>;
70
72
  status?: AnswerStatus;
71
73
  error?: ChatError;
72
74
  /** Есть, если озвучку ответа оборвало перебивание. */
@@ -22,6 +22,8 @@ export { useThinkingStatus } from "./thinking-status/thinking-status.js";
22
22
  export type { ThinkingStatus } from "./thinking-status/thinking-status.js";
23
23
  export type { ChatLabels, DiagramLabels, HelperLabels, LiveDemoLabels, ImageLabels, MessengerStatusLabels, NoticeLabels, ResolvedChatLabels, TaskLabels, VideoLabels, ViewerLabels, VoiceDialogLabels, VoiceErrorLabels, VoiceSpeechLabels, WidgetLabels, WorkViewLabels, } from "./labels.js";
24
24
  export type { StepCall, StepPhrase, StepPhrases, StepPhraseText, WorkView } from "./work-view/step-phrases.js";
25
+ export { guessStepIcon, stepIconOf } from "./work-view/step-icons.js";
26
+ export type { StepIcon } from "./work-view/step-icons.js";
25
27
  export type { SpendingInfo, SpendingPeriod, SpendingRefusalReason, SpendingScope, SpendingView, SpendingWindowInfo } from "./spending-status.js";
26
28
  export type { AgentNoticeViewKind, AgentNoticeViewProps, AgentVideoView, ChatOriginView, ChatSkin, CustomWidgetProps, DiagramColors, DiagramView, FormFieldView, LiveDemoColors, LiveDemoView, GeneratedImagesView, HelperCallStatus, HelperCallView, HelperStepView, MediaViewerProps, MessageActionView, ModelChoiceView, StaffMessageView, TaskCardView, TaskStatusView, ToolApprovalView, ToolCallState, VoiceInputView, WidgetView, WorkStepsView, } from "./skin-contract.js";
27
29
  export type { AgentAppBridge, AppDataChange, BrowserCall, BrowserCallResult, ScreenPlace } from "./app-bridge/app-bridge.js";
@@ -162,6 +162,10 @@ export interface WorkViewLabels {
162
162
  stepRepeated: (phrase: string, count: number) => string;
163
163
  /** Модель думает: «Думаю». */
164
164
  thinking: string;
165
+ /** Раздумье без заголовка в списке шагов (договор 28): «Думал 4 сек»; времени нет — «Подумал». */
166
+ thought: (durationMs: number | null) => string;
167
+ /** Скрытые значки длинного ряда (договор 28): «+3». */
168
+ moreSteps: (count: number) => string;
165
169
  /** Живая строка: «Рисую календарь…». */
166
170
  live: (phrase: string) => string;
167
171
  /** Свёрнутые шаги: «Сделано 3 шага»; `count` — число вызовов, вместе с повторами. */
@@ -3,6 +3,7 @@ import type { ComposerAttachments, MessageAttachmentsView } from "./attachments-
3
3
  import type { AnswerStatus, ApprovalDecision, ErrorNoticeKind } from "./chat-types.js";
4
4
  import type { MediaViewerItem } from "./media-viewer/media-viewer-types.js";
5
5
  import type { VoiceDialogView } from "./voice-dialog.js";
6
+ import type { StepIcon } from "./work-view/step-icons.js";
6
7
  /** Состояние вызова инструмента в ленте (FR-010). */
7
8
  export type ToolCallState = "running" | "done" | "error";
8
9
  /**
@@ -364,6 +365,8 @@ export interface MessageActionView {
364
365
  export type WorkStepsView = {
365
366
  state: "live";
366
367
  phrase: string;
368
+ /** Версия 28: значок текущего шага (`StepIcon`). Нет — скин до 28 рисует строку без значка. */
369
+ icon?: StepIcon;
367
370
  } | {
368
371
  state: "done";
369
372
  steps: Array<{
@@ -373,6 +376,19 @@ export type WorkStepsView = {
373
376
  count: number;
374
377
  open: boolean;
375
378
  onOpenChange: (open: boolean) => void;
379
+ /**
380
+ * Версия 28: все шаги группы со значками — вызовы и раздумья (заголовок раздумья или «Думал N сек»), одинаковые
381
+ * подряд схлопнуты. Скин 28 рисует раскрытый список по ним, `steps` (только вызовы) — для скинов 23–27.
382
+ */
383
+ items?: Array<{
384
+ icon: StepIcon;
385
+ phrase: string;
386
+ failed: boolean;
387
+ }>;
388
+ /** Версия 28: ряд значков свёрнутой строки, по значку на шаг, не больше 8. */
389
+ icons?: StepIcon[];
390
+ /** Версия 28: сколько значков не влезло в ряд — подпись `labels.workView.moreSteps(n)` («+3»); 0 — все видны. */
391
+ moreIcons?: number;
376
392
  };
377
393
  /** Реплика живого сотрудника человеку (версия 25, `ChatService.appendStaffMessage`): сказано от имени компании, не агентом. */
378
394
  export interface StaffMessageView {
@@ -382,7 +398,10 @@ export interface StaffMessageView {
382
398
  staffName?: string;
383
399
  }
384
400
  /**
385
- * Договор скина, версия 27 (в версии 27 добавилось только необязательное `ServiceLine` — служебная строка пометки
401
+ * Договор скина, версия 28 (в версии 28 добавились только необязательные значки шагов и заголовки раздумий:
402
+ * `WorkStepsView.live.icon`, `WorkStepsView.done.items`, `icons`, `moreIcons` и `Reasoning.title`, docs/skin-v28.md;
403
+ * скин версии 27 работает без правок: шаги без значков, раздумья — «Думал N сек»).
404
+ * Версия 27 (в версии 27 добавилось только необязательное `ServiceLine` — служебная строка пометки
386
405
  * приложения, право чата `appNotices` и `noticeLines`, docs/skin-v27.md; скин версии 26 работает без правок: пометок
387
406
  * не видно).
388
407
  * Версия 26 (в версии 26 добавились только необязательные `GeneratedImagesView.wide` и `caption` у готовой
@@ -536,6 +555,11 @@ export interface ChatSkin {
536
555
  streaming: boolean;
537
556
  /** `null`, пока рассуждение идёт. */
538
557
  durationMs: number | null;
558
+ /**
559
+ * Версия 28: заголовок раздумья («Продумываю правила игры», `createChatService({ thoughtTitles })`) — показать
560
+ * вместо «Думал N сек». Нет — прежняя подпись.
561
+ */
562
+ title?: string;
539
563
  open: boolean;
540
564
  onOpenChange: (open: boolean) => void;
541
565
  }>;
@@ -0,0 +1,16 @@
1
+ import type { StepCall, StepPhrases, StepPhraseText } from "./step-phrases.js";
2
+ /**
3
+ * Значок шага в ленте работы (договор скина 28): вид шага, а не картинка — рисует скин. Свой вид приложения — строкой;
4
+ * незнакомый скин рисует общим значком (`tool`).
5
+ */
6
+ export type StepIcon = "thinking" | "read" | "edit" | "search" | "web" | "skill" | "mcp" | "image" | "video" | "check" | "snapshot" | "run" | "memory" | "helper" | "task" | "tool" | (string & {});
7
+ /** Значок по имени инструмента: пакеты SDK, помощники `helper_*`, MCP `<префикс>__<имя>`, слова имени; иначе `tool`. */
8
+ export declare function guessStepIcon(toolName: string): StepIcon;
9
+ /**
10
+ * Значок шага: значок приложения рядом с фразой (`stepPhrases`, `icon`), запасной слоя поведения (`fallback`), иначе
11
+ * по имени (`guessStepIcon`). Ошибка функции фраз — угадывание, чат не ломается.
12
+ */
13
+ export declare function stepIconOf(call: StepCall, { phrases, fallback }?: {
14
+ phrases?: StepPhrases | undefined;
15
+ fallback?: Pick<StepPhraseText, "icon"> | undefined;
16
+ }): StepIcon;
@@ -1,5 +1,6 @@
1
1
  import type { WorkViewLabels } from "../labels.js";
2
2
  import type { ToolCallState } from "../skin-contract.js";
3
+ import type { StepIcon } from "./step-icons.js";
3
4
  /**
4
5
  * Вид работы агента в ленте. `detailed` — мысли и строки инструментов с входом и выходом, как до 0.45.
5
6
  * `simple` — пока ход идёт, одна живая строка с текущим шагом; после хода шаги свёрнуты в «Сделано N шагов».
@@ -12,11 +13,15 @@ export interface StepCall {
12
13
  input?: unknown;
13
14
  output?: unknown;
14
15
  }
15
- /** Фразы шага: пока идёт («Делаю календарь»), готово («Сделал календарь»), не вышло (необязательно). */
16
+ /**
17
+ * Фразы шага: пока идёт («Делаю календарь»), готово («Сделал календарь»), не вышло (необязательно). `icon` — значок
18
+ * шага (договор скина 28, `StepIcon`); нет — угадывается по имени инструмента.
19
+ */
16
20
  export interface StepPhraseText {
17
21
  running: string;
18
22
  done: string;
19
23
  failed?: string;
24
+ icon?: StepIcon;
20
25
  }
21
26
  /** Фразы шага готовые или по вызову. Нет ответа (`undefined`) — общая фраза. */
22
27
  export type StepPhrase = StepPhraseText | ((call: StepCall) => Partial<StepPhraseText> | undefined);
@@ -1,8 +1,14 @@
1
1
  import type { WorkViewLabels } from "../labels.js";
2
+ import type { StepIcon } from "./step-icons.js";
2
3
  import type { StepCall, StepPhrases, StepPhraseText } from "./step-phrases.js";
3
- /** Шаг работы: размышление модели или вызов инструмента (`title` — название от приложения, `fallback` — запасные фразы). */
4
+ /**
5
+ * Шаг работы: размышление модели (`title` — заголовок раздумья от сервера, `durationMs` — сколько думала, `null` — ещё
6
+ * думает) или вызов инструмента (`title` — название от приложения, `fallback` — запасные фразы).
7
+ */
4
8
  export type WorkStep = {
5
9
  kind: "thinking";
10
+ title?: string;
11
+ durationMs?: number | null;
6
12
  } | {
7
13
  kind: "tool";
8
14
  call: StepCall;
@@ -27,6 +33,12 @@ export interface WorkStepLine {
27
33
  phrase: string;
28
34
  failed: boolean;
29
35
  }
36
+ /** Строка списка шагов со значком (договор 28): и вызовы, и раздумья. */
37
+ export interface WorkStepItem extends WorkStepLine {
38
+ icon: StepIcon;
39
+ }
40
+ /** Больше значков в свёрнутом ряду не рисуется: остальные — «+N». */
41
+ export declare const STEP_ICON_ROW_MAX = 8;
30
42
  /** Раскладка ответа в простом виде: видимые части, живая строка последней группы, свёрнутые группы. */
31
43
  export type WorkLayoutItem<Node> = {
32
44
  kind: "shown";
@@ -35,17 +47,26 @@ export type WorkLayoutItem<Node> = {
35
47
  kind: "live";
36
48
  key: string;
37
49
  phrase: string;
50
+ icon: StepIcon;
38
51
  } | {
39
52
  kind: "done";
40
53
  key: string;
41
54
  steps: WorkStepLine[];
42
55
  count: number;
56
+ items: WorkStepItem[];
57
+ icons: StepIcon[];
58
+ moreIcons: number;
43
59
  };
44
60
  interface LayoutOptions {
45
61
  /** Ответ ещё пишется: последняя группа шагов — живая строка. */
46
62
  streaming: boolean;
47
63
  phrases?: StepPhrases | undefined;
48
64
  labels: WorkViewLabels;
65
+ /**
66
+ * Раздумья — тоже шаги списка и ряда значков (договор 28). Экран включает, когда у ответа есть заголовки раздумий
67
+ * (`thoughtTitles`): без них в списке только вызовы, как до 0.69.
68
+ */
69
+ thoughts?: boolean;
49
70
  }
50
71
  /**
51
72
  * Простой вид: подряд идущие шаги (размышления и вызовы) — одна группа; пустой текст между ними группу не рвёт и
@@ -53,5 +74,5 @@ interface LayoutOptions {
53
74
  * Остальные — свёрнутые «Сделано N шагов» с понятными фразами; одинаковые фразы подряд — одна строка «… ×N», группа из
54
75
  * одних размышлений не показывается. Вызов, оставшийся «идёт» в законченном ответе («Стоп», обрыв), — «не закончил».
55
76
  */
56
- export declare function workLayoutOf<Node>(entries: AnswerEntry<Node>[], { streaming, phrases, labels }: LayoutOptions): WorkLayoutItem<Node>[];
77
+ export declare function workLayoutOf<Node>(entries: AnswerEntry<Node>[], { streaming, phrases, labels, thoughts }: LayoutOptions): WorkLayoutItem<Node>[];
57
78
  export {};
@@ -2,7 +2,7 @@ import type { ExpandedParts } from "../expanded-parts.js";
2
2
  import type { ChatSkin } from "../skin-contract.js";
3
3
  import type { WorkLayoutItem } from "./work-layout.js";
4
4
  /**
5
- * Живая строка или свёрнутые шаги простого вида. Раскрытие — в хранилище чата под своим ключом (`…:work`), отдельно от
5
+ * Живая строка или свёрнутые шаги простого вида; скин 28 получает значки и список со раздумьями. Раскрытие — в хранилище чата под своим ключом (`…:work`), отдельно от
6
6
  * раскрытия мыслей и строк подробного вида. Скин без `WorkSteps` (до версии 23) получает строку `ToolCallRow`: фраза
7
7
  * в `title`, входа нет, а результат — список понятных фраз текстом, по строке на шаг. Ни кода, ни путей.
8
8
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ykurilov/agent-chat-react",
3
- "version": "0.68.0",
3
+ "version": "0.69.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,