@ykurilov/agent-chat-sandbox 0.73.0 → 0.75.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @ykurilov/agent-chat-sandbox
2
2
 
3
+ ## 0.75.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [dfb9869]
8
+ - @ykurilov/agent-chat-react@0.75.0
9
+ - @ykurilov/agent-chat-core@0.75.0
10
+ - @ykurilov/agent-chat-server@0.75.0
11
+
12
+ ## 0.74.0
13
+
14
+ ### Minor Changes
15
+
16
+ - 3140c17: Одуванчик получил композицию «Студия» и отдельный вход /sandbox с оформлением песочницы по умолчанию. Чат и журнал прокручиваются независимо; приветствие появляется только в загруженном пустом чате. В договорах скинов добавлены необязательные empty и busy, прежние скины совместимы. История песочницы сохраняет признаки транспорта, поэтому чат без фона не опрашивает несуществующий /tasks.
17
+
18
+ ### Как обновить приложение
19
+
20
+ Обновить все пакеты SDK одной версией. Для песочницы импортировать AgentSandbox из @ykurilov/agent-chat-skin-oduvanchik/sandbox, убрать прежние явные скины Тандема и подключить один CSS Одуванчика. У контейнера задать высоту и min-height: 0 у flex/grid-предков. Для сохранения Тандема оставить его импорты, явные скины и CSS. Сервер, доступ и данные не меняются. Подробности: https://github.com/ykurilov/agent-chat-sdk/blob/main/skins/oduvanchik/README.md
21
+
22
+ ### Patch Changes
23
+
24
+ - Updated dependencies [3140c17]
25
+ - @ykurilov/agent-chat-react@0.74.0
26
+ - @ykurilov/agent-chat-core@0.74.0
27
+ - @ykurilov/agent-chat-server@0.74.0
28
+
3
29
  ## 0.73.0
4
30
 
5
31
  ### Patch Changes
package/dist/index.js CHANGED
@@ -393,7 +393,7 @@ function createSandboxHandler(options) {
393
393
  });
394
394
  if (chat.env) journal.addSecrets?.(secretsFromEnv(chat.env));
395
395
  const people = options.people ? createPeopleRoutes({ service, storage: chat.storage, attachments: chat.attachments, people: options.people }) : null;
396
- const routes = createSandboxRoutes({ service, journal, people, staffCopies, appInstructions: chat.agent.instructions, logError });
396
+ const routes = createSandboxRoutes({ service, journal, people, staffCopies, appInstructions: chat.agent.instructions, logError, readHistory: (request) => inner.fetch(request) });
397
397
  startRetention(journal, options.retentionDays ?? DEFAULT_RETENTION_DAYS, logError);
398
398
  return {
399
399
  basePath,
@@ -411,9 +411,9 @@ function createSandboxHandler(options) {
411
411
  if (!user || !await options.isAdmin(user, request)) return notFound2();
412
412
  if (!isSafeMethod(request.method) && !allowsOrigin(request)) return errorResponse(403, "forbidden_origin");
413
413
  const owner = sandboxOwnerOf(user.id);
414
+ ownerOfRequest.set(request, owner);
414
415
  const own = await routes.handle(request, segments, { adminId: user.id, owner });
415
416
  if (own) return own;
416
- ownerOfRequest.set(request, owner);
417
417
  return await inner.fetch(request);
418
418
  } catch (error) {
419
419
  return errorToResponse(error, request, logError);
@@ -421,7 +421,7 @@ function createSandboxHandler(options) {
421
421
  }
422
422
  };
423
423
  }
424
- function createSandboxRoutes({ service, journal, people, staffCopies, appInstructions, logError }) {
424
+ function createSandboxRoutes({ service, journal, people, staffCopies, appInstructions, logError, readHistory }) {
425
425
  async function settingsResponse(chatId) {
426
426
  const settings = await journal.getSettings(chatId);
427
427
  const body = {
@@ -495,8 +495,12 @@ function createSandboxRoutes({ service, journal, people, staffCopies, appInstruc
495
495
  }
496
496
  if (first !== void 0 && first !== "models" && second === void 0) {
497
497
  if (method === "GET") {
498
- const history = await service.getHistory(caller.owner, first);
499
- return Response.json({ ...history, answerEdits: await answerEdits(first) });
498
+ const response = await readHistory(request);
499
+ if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) return response;
500
+ const history = await response.json();
501
+ const headers = new Headers(response.headers);
502
+ headers.delete("content-length");
503
+ return Response.json({ ...history, answerEdits: await answerEdits(first) }, { status: response.status, headers });
500
504
  }
501
505
  if (method === "DELETE") {
502
506
  await service.deleteChat(caller.owner, first);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sandbox-handler.ts","../src/sandbox-owner.ts","../src/people/copy-person-chat.ts","../src/people/staff-copies.ts","../src/people/people-routes.ts","../src/sandbox-tools.ts","../src/read-only-memory.ts","../src/journal/memory-journal.ts"],"sourcesContent":["import {\n AnswerEditError,\n answerText,\n ChatClosedError,\n ChatNotFoundError,\n createRequestOriginCheck,\n isSafeMethod,\n originRefusalHint,\n} from \"@ykurilov/agent-chat-core\";\nimport type { AgentViewerInput, AgentViewerSource, AnswerEdit, AnswerEditErrorKind, ChatService, StoredMessage } from \"@ykurilov/agent-chat-core\";\nimport { allowedUser, createChatServer } from \"@ykurilov/agent-chat-server\";\nimport type { ChatHandler } from \"@ykurilov/agent-chat-server\";\nimport { secretsFromEnv } from \"./journal/hide-secrets\";\nimport { createPeopleRoutes } from \"./people/people-routes\";\nimport { createStaffCopies } from \"./people/staff-copies\";\nimport { readOnlyMemory } from \"./read-only-memory\";\nimport { sandboxOwnerOf } from \"./sandbox-owner\";\nimport { sandboxTools } from \"./sandbox-tools\";\nimport type {\n AnswerEditMark,\n AnswerEditResponse,\n JournalListResponse,\n SandboxHandlerOptions,\n SandboxHistoryExtras,\n SandboxJournal,\n SandboxSettingsResponse,\n} from \"./types\";\n\nexport { isSandboxOwner } from \"./sandbox-owner\";\n\n/**\n * Инструменты, чей результат правка ответа не меняет: файлы картинок и видео лежат отдельно от текста ответа.\n * Имена строками, без зависимости от пакетов картинок и видео; приложение добавляет свои через `chat.lockedToolOutputs`.\n */\nexport const SANDBOX_LOCKED_TOOL_OUTPUTS: readonly string[] = [\"generate_images\", \"show_video\"];\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\nconst DEFAULT_RETENTION_DAYS = 30;\nconst MAX_INSTRUCTIONS_CHARS = 100_000;\n/** Тело запроса песочницы больше этого не читается: инструкция до 100 000 знаков и результаты инструментов до 64 КБ. */\nconst MAX_BODY_BYTES = 1024 * 1024;\n\nconst answerEditStatus: Record<AnswerEditErrorKind, number> = {\n chat_not_found: 404,\n message_not_found: 404,\n not_an_answer: 400,\n answer_busy: 409,\n not_on_current_branch: 409,\n empty_text: 400,\n invalid_tool_output: 400,\n nothing_changed: 400,\n};\n\n/**\n * Песочница агента для админа: тот же чат приложения, но чаты личные (`sandbox:<id админа>`), каждый ход пишется\n * в журнал, инструкцию можно править на чат, ответ агента — исправить. Раздел «Чаты людей» (`{base}/people`): чаты\n * человека только на чтение и их копия в песочницу; в копии память человека только на чтение, инструменты получают\n * его id и `model.staff`. Не админ получает 404 на любом адресе.\n */\nexport function createSandboxHandler(options: SandboxHandlerOptions): ChatHandler {\n const { chat, journal } = options;\n const basePath = (options.basePath ?? \"/api/sandbox\").replace(/\\/+$/, \"\");\n const logError = chat.logError ?? ((message: string) => console.error(message));\n const allowsOrigin = createRequestOriginCheck({\n allowedOrigins: chat.allowedOrigins,\n onRefused: (refusal) => logError(`Песочница: ${originRefusalHint(refusal)}`),\n });\n // Владелец уже проверенного запроса: внутренний обработчик не зовёт `resolveUser` приложения второй раз.\n const ownerOfRequest = new WeakMap<Request, string>();\n const staffCopies = createStaffCopies();\n const { handler: inner, service } = createChatServer({\n ...chat,\n basePath,\n resolveUser: async (request) => {\n const owner = ownerOfRequest.get(request);\n return owner ? { id: owner } : null;\n },\n agent: {\n ...chat.agent,\n ...(chat.agent.tools && { tools: sandboxTools(chat.agent.tools, options.chatOwnedTools, staffCopies.toolsModel) }),\n // Права — по тому же владельцу, что видят инструменты: настоящий админ или человек копии.\n ...(chat.agent.viewer && { viewer: viewerOfSandbox(chat.agent.viewer, staffCopies.viewerInput) }),\n },\n // Лимиты приложения в песочницу не переходят: только свои, по умолчанию без лимита.\n limits: options.limits,\n // Проверка приложения перед ходом тоже: она ждёт человека приложения, а в песочнице ходит `sandbox:<админ>`.\n beforeTurn: undefined,\n turnNotStarted: undefined,\n // Права приложения тоже: в песочнице ходит админ, ему доступно всё.\n chatCapabilities: undefined,\n // И запрет новых чатов: админ заводит песочные чаты, сколько нужно для теста.\n beforeCreateChat: undefined,\n // Чаты объектов приложения тоже: его `subjectChats` ждёт человека, а получил бы `sandbox:<админ>`. Адреса `/subject` нет.\n subjectChats: undefined,\n // Данные нового чата тоже: они про человека приложения, а в песочнице чат создаёт админ.\n newChatData: undefined,\n // Итог хода приложению — только из копии чата человека, с его объектом и `staff` (см. `staffCopies.afterTurn`).\n afterTurn: chat.afterTurn && staffCopies.afterTurn(chat.afterTurn),\n memory: chat.memory && readOnlyMemory(chat.memory, staffCopies),\n // Копии чатов людей — чаты объектов (`staff-copy:…`): в списке песочницы они видны.\n chatList: { subjectChats: true },\n staffOfChat: staffCopies.staffOf,\n turnObserver: { modelCall: (event) => journal.enqueueModelCall(event), toolCall: (event) => journal.enqueueToolCall(event) },\n chatInstructions: async (sandboxChat) => (await journal.getSettings(sandboxChat.id))?.instructions ?? null,\n lockedToolOutputs: [...new Set([...SANDBOX_LOCKED_TOOL_OUTPUTS, ...(chat.lockedToolOutputs ?? [])])],\n });\n // Ключи провайдеров из окружения чата прячутся в журнале, даже если журнал создан без `env`.\n if (chat.env) journal.addSecrets?.(secretsFromEnv(chat.env));\n // Без опции `people` раздела нет: адреса `/people…` — 404, как до него.\n const people = options.people ? createPeopleRoutes({ service, storage: chat.storage, attachments: chat.attachments, people: options.people }) : null;\n const routes = createSandboxRoutes({ service, journal, people, staffCopies, appInstructions: chat.agent.instructions, logError });\n startRetention(journal, options.retentionDays ?? DEFAULT_RETENTION_DAYS, logError);\n\n return {\n basePath,\n recoverInterruptedAnswers: () => inner.recoverInterruptedAnswers(),\n async flush(flushOptions) {\n await inner.flush(flushOptions);\n await journal.flush();\n },\n async fetch(request) {\n const segments = pathSegments(new URL(request.url).pathname, basePath);\n if (!segments) return notFound();\n try {\n const resolved = await chat.resolveUser(request);\n // Отказ приложения в доступе — тоже 404: не-админ не должен узнать, что песочница есть, и не получает слов приложения.\n const user = allowedUser(resolved);\n if (!user || !(await options.isAdmin(user, request))) return notFound();\n // Источник проверяется после админа: не-админ всегда получает 404, как будто песочницы нет.\n if (!isSafeMethod(request.method) && !allowsOrigin(request)) return errorResponse(403, \"forbidden_origin\");\n const owner = sandboxOwnerOf(user.id);\n const own = await routes.handle(request, segments, { adminId: user.id, owner });\n if (own) return own;\n ownerOfRequest.set(request, owner);\n return await inner.fetch(request);\n } catch (error) {\n return errorToResponse(error, request, logError);\n }\n },\n };\n}\n\ninterface RouteInput {\n service: ChatService;\n journal: SandboxJournal;\n people: ReturnType<typeof createPeopleRoutes> | null;\n staffCopies: ReturnType<typeof createStaffCopies>;\n appInstructions: string;\n logError: (message: string) => void;\n}\n\ntype MessagePart = StoredMessage[\"parts\"][number];\n\ninterface Caller {\n adminId: string;\n owner: string;\n}\n\n/** Адреса песочницы поверх чата: журнал, настройки, правка ответа, история с отметками правок, удаление с журналом, чаты людей. */\nfunction createSandboxRoutes({ service, journal, people, staffCopies, appInstructions, logError }: RouteInput) {\n async function settingsResponse(chatId: string): Promise<Response> {\n const settings = await journal.getSettings(chatId);\n const body: SandboxSettingsResponse = {\n instructions: settings?.instructions ?? null,\n appInstructions,\n updatedAt: settings?.updatedAt.toISOString() ?? null,\n };\n return Response.json(body);\n }\n\n async function answerEdits(chatId: string): Promise<SandboxHistoryExtras[\"answerEdits\"]> {\n const records = await journal.listEditedAnswers(chatId);\n return Object.fromEntries(records.map(({ messageId, textEdited, editedToolCallIds }) => [messageId, { textEdited, editedToolCallIds }]));\n }\n\n async function editAnswer(request: Request, chatId: string, messageId: string, caller: Caller): Promise<Response> {\n const edit = parseAnswerEdit(await readJson(request));\n if (!edit) return errorResponse(400, \"invalid\");\n const { messages } = await service.getHistory(caller.owner, chatId);\n const replacement = await service.editAnswer(caller.owner, chatId, messageId, edit);\n // Правка прошла — значит, ответ был на текущей ветке и есть в истории.\n const source = messages.find((message) => message.id === messageId)!;\n const earlier = (await journal.listEditedAnswers(chatId)).find((record) => record.messageId === messageId);\n const mark = editMark(source.parts as MessagePart[], edit, earlier);\n // Ответ уже заменён: сбой журнала не должен превращать успех в 500, отметка просто пропадёт.\n try {\n await journal.markAnswerEdited({ messageId: replacement.id, sourceMessageId: messageId, chatId, editedBy: caller.adminId, editedAt: new Date(), ...mark });\n } catch (error) {\n logError(`Песочница: отметка правки ответа не записана: ${describe(error)}`);\n }\n const body: AnswerEditResponse = { messageId: replacement.id, mark };\n return Response.json(body);\n }\n\n /** Ответ, если адрес песочницы свой; `null` — адрес чата, его обслуживает внутренний обработчик. */\n async function handle(request: Request, segments: string[], caller: Caller): Promise<Response | null> {\n const method = request.method.toUpperCase();\n const [first, second, third, fourth, ...rest] = segments;\n\n // Экран памяти в песочнице закрыт: память админа там только для чтения.\n if (first === \"memory\") return notFound();\n\n if (first === \"people\") return people ? people.handle(request, segments.slice(1), caller.owner) : notFound();\n\n if (first === \"journal\") {\n if (method !== \"GET\" || third === undefined || fourth !== undefined) return notFound();\n if (second === \"calls\") {\n const details = await journal.getCall(third);\n return details && details.call.ownerId === caller.owner ? Response.json(details) : notFound();\n }\n if (second === \"blobs\") {\n const blob = await journal.getBlob(third, caller.owner);\n return blob ? Response.json(blob) : notFound();\n }\n return notFound();\n }\n\n if (first === \"chats\") {\n if (second === undefined) return notFound();\n await service.assertOwnChat(caller.owner, second);\n if (third === \"journal\" && fourth === undefined && method === \"GET\") {\n const messageId = new URL(request.url).searchParams.get(\"messageId\");\n const entries = await journal.listForChat(second, messageId ? { messageId } : undefined);\n return Response.json({ entries } as unknown as JournalListResponse);\n }\n if (third === \"settings\" && fourth === undefined) {\n if (method === \"GET\") return settingsResponse(second);\n if (method !== \"PUT\") return notFound();\n const instructions = parseInstructions(await readJson(request));\n if (instructions === \"invalid\") return errorResponse(400, \"invalid\");\n await journal.setSettings({ chatId: second, instructions, updatedBy: caller.adminId, updatedAt: new Date() });\n return settingsResponse(second);\n }\n if (third === \"messages\" && fourth !== undefined && rest.length === 1 && rest[0] === \"answer-edit\" && method === \"POST\") {\n return editAnswer(request, second, fourth, caller);\n }\n // Закрытие в песочнице настоящее (так видно, что оно работает); открыть чат снова — чтобы продолжить тест.\n if (third === \"reopen\" && fourth === undefined && method === \"POST\") {\n await service.reopenChat({ ownerId: caller.owner, chatId: second });\n return new Response(null, { status: 204 });\n }\n return notFound();\n }\n\n // Идентификаторы чатов — UUID, поэтому пути `models`, `chats`, `journal`, `memory` и `people` с чатом не пересекаются.\n if (first !== undefined && first !== \"models\" && second === undefined) {\n if (method === \"GET\") {\n const history = await service.getHistory(caller.owner, first);\n return Response.json({ ...history, answerEdits: await answerEdits(first) });\n }\n if (method === \"DELETE\") {\n await service.deleteChat(caller.owner, first);\n staffCopies.forget(first);\n // Чат уже удалён: журнал, который не вычистился сейчас, дочистит суточная чистка.\n try {\n await journal.deleteChat(first);\n } catch (error) {\n logError(`Песочница: журнал удалённого чата не вычищен: ${describe(error)}`);\n }\n return new Response(null, { status: 204 });\n }\n }\n return null;\n }\n\n return { handle };\n}\n\n/** Отметки правки: текст изменён, результаты каких вызовов заменены; прежние отметки исправленного ответа сохраняются. */\nfunction editMark(sourceParts: MessagePart[], edit: AnswerEdit, earlier: AnswerEditMark | undefined): AnswerEditMark {\n const outputs = new Map(\n sourceParts.flatMap((part) => {\n const call = part as { toolCallId?: unknown; state?: unknown; output?: unknown };\n return typeof call.toolCallId === \"string\" && call.state === \"output-available\" ? [[call.toolCallId, JSON.stringify(call.output)] as const] : [];\n }),\n );\n const changedCalls = Object.entries(edit.toolOutputs ?? {})\n .filter(([toolCallId, output]) => outputs.get(toolCallId) !== JSON.stringify(output))\n .map(([toolCallId]) => toolCallId);\n return {\n textEdited: (earlier?.textEdited ?? false) || edit.text !== answerText(sourceParts),\n editedToolCallIds: [...new Set([...(earlier?.editedToolCallIds ?? []), ...changedCalls])],\n };\n}\n\n/** Чистка журнала старше срока: сразу и раз в сутки. Таймер не держит процесс. */\nfunction startRetention(journal: SandboxJournal, retentionDays: number, logError: (message: string) => void): void {\n const prune = () => {\n journal.deleteOlderThan(new Date(Date.now() - retentionDays * DAY_MS)).catch((error: unknown) => {\n logError(`Песочница: чистка журнала не удалась: ${describe(error)}`);\n });\n };\n prune();\n const timer = setInterval(prune, DAY_MS) as { unref?: () => void };\n timer.unref?.();\n}\n\nfunction parseAnswerEdit(body: unknown): AnswerEdit | null {\n if (!isPlainObject(body) || typeof body.text !== \"string\") return null;\n if (body.toolOutputs === undefined) return { text: body.text };\n return isPlainObject(body.toolOutputs) ? { text: body.text, toolOutputs: body.toolOutputs } : null;\n}\n\nfunction parseInstructions(body: unknown): string | null | \"invalid\" {\n if (!isPlainObject(body) || !Object.hasOwn(body, \"instructions\")) return \"invalid\";\n const { instructions } = body;\n if (instructions === null) return null;\n return typeof instructions === \"string\" && instructions.length <= MAX_INSTRUCTIONS_CHARS ? instructions : \"invalid\";\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nclass BodyTooLargeError extends Error {}\n\n/** Тело JSON; не JSON — `undefined` (ответ 400), длиннее предела — 413. Больше предела не читается ни байта сверх него. */\nasync function readJson(request: Request): Promise<unknown> {\n const declared = Number(request.headers.get(\"content-length\"));\n if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {\n await request.body?.cancel().catch(() => {});\n throw new BodyTooLargeError();\n }\n const text = await readLimitedText(request);\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Тело потоком: после предела чтение обрывается, поток закрывается. */\nasync function readLimitedText(request: Request): Promise<string> {\n if (!request.body) return \"\";\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > MAX_BODY_BYTES) {\n await reader.cancel().catch(() => {});\n throw new BodyTooLargeError();\n }\n chunks.push(value);\n }\n return Buffer.concat(chunks).toString(\"utf8\");\n}\n\nfunction pathSegments(pathname: string, basePath: string): string[] | null {\n if (pathname !== basePath && !pathname.startsWith(`${basePath}/`)) return null;\n try {\n return pathname\n .slice(basePath.length)\n .split(\"/\")\n .filter((segment) => segment.length > 0)\n .map((segment) => decodeURIComponent(segment));\n } catch {\n // Битая кодировка (например, `%E0`): такого адреса нет.\n return null;\n }\n}\n\n/** Ответ «нет такого адреса»: и не-админу, и на чужой чат — без слова о песочнице. */\nfunction notFound(): Response {\n return errorResponse(404, \"not_found\");\n}\n\nfunction errorResponse(status: number, kind: string): Response {\n return Response.json({ error: { kind } }, { status });\n}\n\nfunction errorToResponse(error: unknown, request: Request, logError: (message: string) => void): Response {\n if (error instanceof ChatNotFoundError) return notFound();\n if (error instanceof AnswerEditError) return error.kind === \"chat_not_found\" ? notFound() : errorResponse(answerEditStatus[error.kind], error.kind);\n if (error instanceof BodyTooLargeError) return errorResponse(413, \"too_large\");\n if (error instanceof ChatClosedError) return Response.json({ error: { kind: \"chat_closed\", code: error.code, message: error.message } }, { status: 409 });\n logError(`Песочница: сбой на ${request.method} ${new URL(request.url).pathname}: ${describe(error)}`);\n return errorResponse(500, \"other\");\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? `${error.name}: ${error.message}` : String(error);\n}\n\n/** `agent.viewer` приложения в песочнице: вход с настоящим владельцем (см. `staffCopies.viewerInput`). */\nfunction viewerOfSandbox(viewer: AgentViewerSource, input: (input: AgentViewerInput) => AgentViewerInput): AgentViewerSource {\n return (turn) => viewer(input(turn));\n}\n","/** Песочные чаты админа записаны на отдельного владельца: боевой список, лимиты и траты их не видят. */\nconst SANDBOX_OWNER_PREFIX = \"sandbox:\";\n\n/** Владелец песочных чатов админа. */\nexport function sandboxOwnerOf(adminId: string): string {\n return SANDBOX_OWNER_PREFIX + adminId;\n}\n\n/** Чат или трата из песочницы: отчёты приложения отделяют их по владельцу. */\nexport function isSandboxOwner(ownerId: string): boolean {\n return ownerId.startsWith(SANDBOX_OWNER_PREFIX);\n}\n\n/** Настоящий id админа за песочным владельцем; обычный владелец остаётся как есть. */\nexport function realOwnerOf(ownerId: string): string {\n return isSandboxOwner(ownerId) ? ownerId.slice(SANDBOX_OWNER_PREFIX.length) : ownerId;\n}\n","import { chatDataOfInput, DuplicateChatSubjectError, IMPORT_MAX_IMAGE_BYTES, IMPORT_MAX_IMAGES, IMPORT_MAX_MESSAGES } from \"@ykurilov/agent-chat-core\";\nimport type { AgentChatMessage, ChatService, ChatStorage, ImportChatInput } from \"@ykurilov/agent-chat-core\";\nimport type { ChatAttachments } from \"@ykurilov/agent-chat-server\";\nimport { parseStaffCopyKey, staffCopyKey, staffCopySubjectData } from \"./staff-copies\";\n\nconst TITLE_MAX_LENGTH = 100;\nconst COPY_TITLE_PREFIX = \"Копия: \";\n/**\n * Сколько раз взять следующий номер, если такой же номер одновременно заняли другие запросы. Каждая попытка берёт\n * номер не меньше прошлого + 1, поэтому N одновременных копий укладываются в N попыток.\n */\nconst COPY_NUMBER_ATTEMPTS = 20;\n\ntype ImportedPart = ImportChatInput[\"messages\"][number][\"parts\"][number];\ntype MessagePart = AgentChatMessage[\"parts\"][number];\n\nexport interface CopyPersonChatInput {\n service: ChatService;\n storage: ChatStorage;\n attachments: ChatAttachments | undefined;\n personId: string;\n chatId: string;\n /** Песочный владелец админа: копия записывается на него. */\n owner: string;\n /** Запрос админа: из него собирается чтение файла человека. */\n request: Request;\n}\n\n/** Копию сделать нечем: в чате человека нет сообщений. */\nexport class EmptyPersonChatError extends Error {\n constructor(chatId: string) {\n super(`В чате ${chatId} нет сообщений`);\n this.name = \"EmptyPersonChatError\";\n }\n}\n\n/**\n * Копия текущей ветки чата человека в песочнице админа (`service.importChat`): даты как есть, ответы законченные,\n * текст и картинки. Картинки — через вложения (`importFile`), без них — текстовой пометкой; прочие файлы, вызовы\n * инструментов и рассуждения не копируются. Исходный чат (ключ объекта и данные) едет в данные копии.\n * Чат человека только читается.\n * @throws ChatNotFoundError чата нет или он не этого человека.\n * @throws EmptyPersonChatError в чате нет сообщений.\n */\nexport async function copyPersonChat(input: CopyPersonChatInput): Promise<{ chatId: string }> {\n const { service, storage, personId, chatId, owner } = input;\n const history = await service.peekHistory(personId, chatId);\n const createdAtOf = new Map((await storage.listMessages(chatId)).map((message) => [message.id, message.createdAt]));\n const images = { count: 0, bytes: 0 };\n const messages: Array<ImportChatInput[\"messages\"][number]> = [];\n for (const message of history.messages.slice(-IMPORT_MAX_MESSAGES)) {\n const createdAt = createdAtOf.get(message.id);\n if ((message.role !== \"user\" && message.role !== \"assistant\") || !createdAt) continue;\n const parts = await copiedParts(message.parts, input, images);\n if (parts.length > 0) messages.push({ role: message.role, createdAt, parts });\n }\n if (messages.length === 0) throw new EmptyPersonChatError(chatId);\n\n const { modelId, providerId, reasoning } = history.chat;\n // История отдаёт чат без данных: ключ объекта и данные исходного чата берутся из хранилища и едут в копию.\n const source = await storage.getChat(chatId);\n const sourceData = source ? chatDataOfInput(source) : undefined;\n const subjectData = staffCopySubjectData({\n ...(source?.subjectKey !== undefined && { key: source.subjectKey }),\n ...(sourceData !== undefined && { data: sourceData }),\n });\n const title = Array.from(`${COPY_TITLE_PREFIX}${history.chat.title ?? \"\"}`.trim()).slice(0, TITLE_MAX_LENGTH).join(\"\");\n let copyNumber = 0;\n for (let attempt = 1; ; attempt++) {\n copyNumber = Math.max(copyNumber + 1, await nextCopyNumber(service, owner, personId, chatId));\n try {\n return await service.importChat({\n ownerId: owner,\n chat: {\n title,\n subjectKey: staffCopyKey(personId, chatId, copyNumber),\n ...(subjectData && { subjectData }),\n ...(modelId && providerId && { model: { modelId, providerId, ...(reasoning && { reasoning }) } }),\n },\n messages,\n });\n } catch (error) {\n if (!(error instanceof DuplicateChatSubjectError) || attempt >= COPY_NUMBER_ATTEMPTS) throw error;\n }\n }\n}\n\n/**\n * Номер следующей копии этого чата человека у админа: на 1 больше самого большого. Считаются только ключи ровно этого\n * человека и чата: у человека `x:y` начало ключа копии такое же, как у чата `y` человека `x`.\n */\nasync function nextCopyNumber(service: ChatService, owner: string, personId: string, chatId: string): Promise<number> {\n const numbers = (await service.listChats(owner, { subjects: \"only\" })).flatMap((chat) => {\n const copy = parseStaffCopyKey(chat.subjectKey);\n return copy?.personId === personId && copy.chatId === chatId ? [copy.copyNumber] : [];\n });\n return Math.max(0, ...numbers) + 1;\n}\n\nasync function copiedParts(parts: readonly MessagePart[], input: CopyPersonChatInput, images: { count: number; bytes: number }): Promise<ImportedPart[]> {\n const copied: ImportedPart[] = [];\n for (const part of parts) {\n if (part.type === \"text\") {\n if (part.text.length > 0) copied.push({ type: \"text\", text: part.text });\n } else if (part.type === \"file\") {\n copied.push(await copiedFile(part, input, images));\n }\n }\n return copied;\n}\n\n/** Картинка — байтами через вложения (в пределах импорта); не вышло или не картинка — пометкой с именем. */\nasync function copiedFile(\n part: Extract<MessagePart, { type: \"file\" }>,\n input: CopyPersonChatInput,\n images: { count: number; bytes: number },\n): Promise<ImportedPart> {\n const name = part.filename ?? \"файл\";\n const isImage = part.mediaType.startsWith(\"image/\");\n const note: ImportedPart = { type: \"text\", text: isImage ? `[картинка: ${name}]` : `[файл: ${name}]` };\n const fileId = part.url.startsWith(\"attachment:\") ? part.url.slice(\"attachment:\".length) : null;\n if (!isImage || !fileId || !input.attachments?.importFile || images.count >= IMPORT_MAX_IMAGES) return note;\n const response = await input.attachments.handle({\n request: new Request(input.request.url, { method: \"GET\", headers: input.request.headers }),\n userId: input.personId,\n chatId: input.chatId,\n segments: [fileId],\n });\n if (!response.ok) return note;\n const bytes = new Uint8Array(await response.arrayBuffer());\n const mediaType = response.headers.get(\"content-type\")?.split(\";\")[0]?.trim() ?? part.mediaType;\n if (!mediaType.startsWith(\"image/\") || bytes.length === 0 || images.bytes + bytes.length > IMPORT_MAX_IMAGE_BYTES) return note;\n images.count += 1;\n images.bytes += bytes.length;\n return { type: \"image\", bytes, mediaType, filename: name };\n}\n","import type { AfterTurnHandler, AgentToolsModel, AgentViewerInput, Chat, ChatSubject, StaffActor } from \"@ykurilov/agent-chat-core\";\nimport { isSandboxOwner, realOwnerOf } from \"../sandbox-owner\";\n\nconst STAFF_COPY_PREFIX = \"staff-copy:\";\n/** Поле в `subjectData` копии: объект исходного чата человека (ключ и данные). */\nconst ORIGINAL_SUBJECT_FIELD = \"staffCopyOf\";\n/** Сколько копий помнить: копия запоминается заново перед каждым своим ходом, поэтому забыть давнюю не страшно. */\nexport const STAFF_COPIES_REMEMBERED = 1000;\n\n/** Ключ копии чата человека в песочнице: `staff-copy:<человек>:<чат>:<n>`, `n` — номер копии с 1. */\nexport function staffCopyKey(personId: string, chatId: string, copyNumber: number): string {\n return `${staffCopyKeyPrefix(personId, chatId)}${copyNumber}`;\n}\n\n/** Начало ключей всех копий одного чата человека. */\nexport function staffCopyKeyPrefix(personId: string, chatId: string): string {\n return `${STAFF_COPY_PREFIX}${personId}:${chatId}:`;\n}\n\n/** Человек, чат и номер копии по ключу; не ключ копии — `null`. Id человека может содержать `:`, чат и номер — нет. */\nexport function parseStaffCopyKey(key: string | undefined): { personId: string; chatId: string; copyNumber: number } | null {\n if (!key?.startsWith(STAFF_COPY_PREFIX)) return null;\n const rest = key.slice(STAFF_COPY_PREFIX.length).split(\":\");\n if (rest.length < 3) return null;\n const copyNumber = Number(rest.pop());\n const chatId = rest.pop()!;\n const personId = rest.join(\":\");\n if (!Number.isInteger(copyNumber) || copyNumber < 1 || !chatId || !personId) return null;\n return { personId, chatId, copyNumber };\n}\n\n/** Исходный чат человека: ключ объекта (если это чат объекта) и данные чата (если есть). */\nexport interface OriginalChat {\n key?: string;\n data?: Record<string, unknown>;\n}\n\n/** Данные копии: исходный чат лежит под служебным полем; у чата без ключа и без данных переносить нечего. */\nexport function staffCopySubjectData(original: OriginalChat | undefined): Record<string, unknown> | undefined {\n return original && (original.key !== undefined || original.data !== undefined) ? { [ORIGINAL_SUBJECT_FIELD]: original } : undefined;\n}\n\n/** Человек и исходный чат, если `subject` — ключ копии в песочном чате; иначе `null`. */\nfunction copyOfSubject(ownerId: string, subject: ChatSubject | undefined): { personId: string; original: ChatSubject | undefined; data: Record<string, unknown> | undefined } | null {\n const copy = isSandboxOwner(ownerId) ? parseStaffCopyKey(subject?.key) : null;\n if (!copy) return null;\n const stored = subject?.data?.[ORIGINAL_SUBJECT_FIELD] as OriginalChat | undefined;\n const data = isRecord(stored?.data) ? stored.data : undefined;\n const original = typeof stored?.key === \"string\" ? { key: stored.key, ...(data && { data }) } : undefined;\n return { personId: copy.personId, original, data };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Ключ копии наружу не уходит: вместо него объект исходного чата человека (если у него был ключ). Данные чата копии —\n * служебная обёртка, поэтому `chat.data` и `chatData` заменяются данными исходного чата, с ключом или без.\n */\nfunction withOriginalChat<T extends { subject?: ChatSubject; chat?: { data?: Record<string, unknown> }; chatData?: Record<string, unknown> }>(\n value: T,\n copy: { original: ChatSubject | undefined; data: Record<string, unknown> | undefined },\n): T {\n const { subject: _copyKey, chat: _copyChat, chatData: _copyData, ...rest } = value;\n return {\n ...rest,\n ...(copy.original && { subject: copy.original }),\n ...(\"chat\" in value && { chat: { ...(copy.data && { data: copy.data }) } }),\n ...(\"chatData\" in value && copy.data && { chatData: copy.data }),\n } as T;\n}\n\n/**\n * Копии чатов людей в песочнице: ядро спрашивает сотрудника хода (`staffOfChat`) перед каждым ходом, и в этот момент\n * копия запоминает, чей это чат. Инструменты и память хода копии читают человека отсюда: их функции синхронны и\n * знают только id чата.\n */\nexport function createStaffCopies() {\n const personOfChat = new Map<string, string>();\n\n return {\n /** Сотрудник хода: админ песочницы, если чат — копия чата человека; иначе `null`. */\n staffOf(chat: Chat): StaffActor | null {\n const copy = isSandboxOwner(chat.ownerId) ? parseStaffCopyKey(chat.subjectKey) : null;\n if (!copy) {\n personOfChat.delete(chat.id);\n return null;\n }\n // Самая свежая — в конце: при переполнении забывается та, что дольше всех не ходила.\n personOfChat.delete(chat.id);\n personOfChat.set(chat.id, copy.personId);\n if (personOfChat.size > STAFF_COPIES_REMEMBERED) personOfChat.delete(personOfChat.keys().next().value!);\n return { id: realOwnerOf(chat.ownerId) };\n },\n\n /** Человек, чей чат скопирован в этот песочный чат; не копия — `undefined`. */\n personOf(chatId: string): string | undefined {\n return personOfChat.get(chatId);\n },\n\n /**\n * Модель хода для инструментов приложения. В копии — id человека, `staff` админа и объект исходного чата\n * человека (`subject`), в том числе вне хода (сжатие, фоновый помощник): данные человека инструменты только\n * читают. В обычном песочном чате — настоящий id админа, остальное как поставило ядро.\n */\n toolsModel(model: AgentToolsModel): AgentToolsModel {\n const copy = copyOfSubject(model.ownerId, model.subject);\n if (!copy) return { ...model, ownerId: realOwnerOf(model.ownerId) };\n return withOriginalChat({ ...model, ownerId: copy.personId, staff: model.staff ?? { id: realOwnerOf(model.ownerId) } }, copy);\n },\n\n /**\n * Вход `agent.viewer` приложения — как у инструментов: права по владельцу чата. В копии — человек, `userId` и\n * `staff` — админ; в обычном песочном чате — настоящий id админа.\n */\n viewerInput(input: AgentViewerInput): AgentViewerInput {\n const copy = copyOfSubject(input.ownerId, input.subject);\n if (!copy) return { ...input, ownerId: realOwnerOf(input.ownerId), userId: realOwnerOf(input.userId) };\n const staff = input.staff ?? { id: realOwnerOf(input.ownerId) };\n return withOriginalChat({ ...input, ownerId: copy.personId, userId: staff.id, staff }, copy);\n },\n\n /**\n * Хук итога хода приложения в песочнице: зовётся только в копии чата человека — с id человека, `staff` и `actorId`\n * админа и объектом исходного чата, в том числе при доделке после перезапуска. При `turn.staff` в данные человека\n * не писать. В обычном песочном чате не зовётся, как и `beforeTurn`: там ходит не человек приложения.\n */\n afterTurn(handler: AfterTurnHandler): AfterTurnHandler {\n return (result) => {\n const copy = copyOfSubject(result.ownerId, result.subject);\n if (!copy) return;\n const staff = result.staff ?? { id: realOwnerOf(result.ownerId) };\n return handler(withOriginalChat({ ...result, ownerId: copy.personId, staff, actorId: staff.id }, copy));\n };\n },\n\n /** Чат удалён: забыть его. */\n forget(chatId: string): void {\n personOfChat.delete(chatId);\n },\n };\n}\n\nexport type StaffCopies = ReturnType<typeof createStaffCopies>;\n","import type { ChatService, ChatStorage } from \"@ykurilov/agent-chat-core\";\nimport type { ChatAttachments } from \"@ykurilov/agent-chat-server\";\nimport { isSandboxOwner } from \"../sandbox-owner\";\nimport type { PeopleSearchResponse, Person, PersonChatsResponse, SandboxPeople, StaffCopyResponse } from \"../types\";\nimport { copyPersonChat, EmptyPersonChatError } from \"./copy-person-chat\";\n\nconst PERSON_ID_MAX_LENGTH = 128;\nconst QUERY_MAX_LENGTH = 200;\n\nexport interface PeopleRoutesInput {\n /** Служба песочницы: то же хранилище, что у боевого чата, поэтому чаты людей читаются по их id. */\n service: ChatService;\n storage: ChatStorage;\n attachments: ChatAttachments | undefined;\n /** `true` — поиска нет, человека вводят по id. */\n people: SandboxPeople | true;\n}\n\n/**\n * Раздел «Чаты людей» под `{base}/people`: поиск человека, его чаты, история и файлы только на чтение и копия чата\n * в песочницу админа. Админ уже проверен. Записи в чат человека здесь нет ни одной: всё, кроме перечисленного, — 404.\n *\n * - `GET /people?query=` — люди (`PeopleSearchResponse`);\n * - `GET /people/:personId/chats` — чаты человека с чатами объектов (`PersonChatsResponse`);\n * - `GET /people/:personId/models` — меню моделей (экрану чата на чтение);\n * - `GET /people/:personId/:chatId` — история, как её видит человек, без данных объекта;\n * - `GET /people/:personId/:chatId/attachments/:fileId` — файл вложения;\n * - `POST /people/:personId/:chatId/staff-copy` — копия текущей ветки (`StaffCopyResponse`, 201).\n */\nexport function createPeopleRoutes({ service, storage, attachments, people }: PeopleRoutesInput) {\n async function search(request: Request): Promise<Response> {\n const query = (new URL(request.url).searchParams.get(\"query\") ?? \"\").trim().slice(0, QUERY_MAX_LENGTH);\n const finder = people === true ? null : people;\n const found = finder && query.length > 0 ? await finder.find(query) : [];\n const body: PeopleSearchResponse = { search: finder !== null, people: found.flatMap(publicPerson) };\n return Response.json(body);\n }\n\n /** `segments` — путь после `people`; `owner` — песочный владелец админа. */\n async function handle(request: Request, segments: string[], owner: string): Promise<Response> {\n const method = request.method.toUpperCase();\n const [personId, second, third, fourth, ...rest] = segments;\n if (personId === undefined) return method === \"GET\" ? search(request) : notFound();\n // Песочные владельцы — не люди: чужая песочница через этот раздел не читается.\n if (!isPersonId(personId) || second === undefined || rest.length > 0) return notFound();\n\n if (third === undefined && method === \"GET\") {\n if (second === \"chats\") {\n const body: PersonChatsResponse = { chats: await service.listChats(personId, { subjects: \"include\" }) };\n return Response.json(body);\n }\n if (second === \"models\") return Response.json(await service.getModelMenu());\n return Response.json(await service.peekHistory(personId, second));\n }\n if (third === \"attachments\" && fourth !== undefined && method === \"GET\" && attachments) {\n await service.assertOwnChat(personId, second);\n return attachments.handle({ request, userId: personId, chatId: second, segments: [fourth] });\n }\n if (third === \"staff-copy\" && fourth === undefined && method === \"POST\") {\n try {\n const { chatId } = await copyPersonChat({ service, storage, attachments, personId, chatId: second, owner, request });\n const body: StaffCopyResponse = { id: chatId };\n return Response.json(body, { status: 201 });\n } catch (error) {\n if (error instanceof EmptyPersonChatError) return Response.json({ error: { kind: \"empty_chat\" } }, { status: 400 });\n throw error;\n }\n }\n return notFound();\n }\n\n return { handle };\n}\n\nfunction isPersonId(id: string): boolean {\n return id.length > 0 && id.length <= PERSON_ID_MAX_LENGTH && !isSandboxOwner(id);\n}\n\n/** Только поля договора: лишнее, что вернуло приложение, наружу не идёт. */\nfunction publicPerson(person: Person): Person[] {\n if (typeof person?.id !== \"string\" || !isPersonId(person.id)) return [];\n return [\n {\n id: person.id,\n ...(typeof person.name === \"string\" && { name: person.name }),\n ...(typeof person.hint === \"string\" && { hint: person.hint }),\n },\n ];\n}\n\nfunction notFound(): Response {\n return Response.json({ error: { kind: \"not_found\" } }, { status: 404 });\n}\n","import type { AgentToolsModel, AgentToolsSource } from \"@ykurilov/agent-chat-core\";\nimport { realOwnerOf } from \"./sandbox-owner\";\n\n/**\n * Инструменты, чьи файлы и траты принадлежат песочному чату: картинка ложится во вложения песочного владельца\n * (её открывает только этот админ), расход — на него же, а не в боевой лимит.\n */\nexport const SANDBOX_CHAT_OWNED_TOOLS: readonly string[] = [\"generate_images\"];\n\n/** Модель хода для инструментов приложения: настоящий id админа, чат — песочный. */\nexport function withRealOwner(model: AgentToolsModel): AgentToolsModel {\n return { ...model, ownerId: realOwnerOf(model.ownerId) };\n}\n\n/**\n * Инструменты приложения в песочнице: функция получает настоящий `ownerId` админа (в копии чата человека — id\n * человека, см. `toolsModel`), а инструменты из `chatOwned` — песочного владельца. Описания и схемы те же, поэтому\n * запрос к модели побайтно как у боевого чата. Набор-объект от владельца не зависит.\n */\nexport function sandboxTools(\n tools: AgentToolsSource | undefined,\n chatOwned: readonly string[] = SANDBOX_CHAT_OWNED_TOOLS,\n toolsModel: (model: AgentToolsModel) => AgentToolsModel = withRealOwner,\n): AgentToolsSource | undefined {\n if (typeof tools !== \"function\") return tools;\n return (model) => {\n const real = tools(toolsModel(model));\n const owned = chatOwned.filter((name) => name in real);\n if (owned.length === 0) return real;\n const ofSandbox = tools(model);\n return { ...real, ...Object.fromEntries(owned.flatMap((name) => (ofSandbox[name] ? [[name, ofSandbox[name]]] : []))) };\n };\n}\n","import type { AgentToolsModel } from \"@ykurilov/agent-chat-core\";\nimport type { ChatHandlerOptions } from \"@ykurilov/agent-chat-server\";\nimport { realOwnerOf } from \"./sandbox-owner\";\nimport { withRealOwner } from \"./sandbox-tools\";\n\ntype ChatMemory = NonNullable<ChatHandlerOptions[\"memory\"]>;\ntype ToolSet = ReturnType<ChatMemory[\"tools\"]>;\n\nconst READ_ONLY_MESSAGE = \"в песочнице память только для чтения\";\nconst refusal = { kind: \"invalid\" as const, message: READ_ONLY_MESSAGE };\n\n/** Чья память у хода: по умолчанию — настоящего id админа за песочным владельцем. */\nexport interface MemoryOwner {\n /** Модель хода для инструментов памяти (с нужным `ownerId`). */\n toolsModel(model: AgentToolsModel): AgentToolsModel;\n /** Человек, чья память у чата; нет — память админа. */\n personOf(chatId: string): string | undefined;\n}\n\nconst adminMemory: MemoryOwner = { toolsModel: withRealOwner, personOf: () => undefined };\n\n/**\n * Боевая память только на чтение: админа, а в копии чата человека — этого человека (`owner`). Блок памяти и\n * инструменты — с этим id; у инструментов те же имя, описание и схема (начало запроса кэшируется так же), но всё,\n * кроме `search`, отвечает агенту отказом. Оптимизации после ответа (`afterAnswer`) нет; адреса экрана памяти\n * песочница закрывает сама.\n */\nexport function readOnlyMemory(memory: ChatMemory, owner: MemoryOwner = adminMemory): ChatMemory {\n return {\n instructions: memory.instructions,\n toolNames: memory.toolNames,\n tools: (model) => readOnlyTools(memory.tools(owner.toolsModel(model))),\n snapshot: (input) => memory.snapshot({ ...input, userId: owner.personOf(input.chatId) ?? realOwnerOf(input.userId) }),\n ...(memory.blocks && {\n blocks: (input: Parameters<NonNullable<ChatMemory[\"blocks\"]>>[0]) => memory.blocks!({ ...input, userId: owner.personOf(input.chatId) ?? realOwnerOf(input.userId) }),\n }),\n list: (ownerId) => memory.list(realOwnerOf(ownerId)),\n create: async () => ({ ok: false, error: refusal }),\n update: async () => ({ ok: false, error: refusal }),\n remove: async () => ({ ok: false, error: refusal }),\n clear: async () => 0,\n };\n}\n\nfunction readOnlyTools(tools: ToolSet): ToolSet {\n return Object.fromEntries(\n Object.entries(tools).map(([name, tool]) => {\n const execute = tool.execute;\n if (!execute) return [name, tool];\n return [name, { ...tool, execute: (input: unknown, options: unknown) => (isSearch(input) ? execute(input, options as never) : { error: refusal }) }];\n }),\n ) as ToolSet;\n}\n\nfunction isSearch(input: unknown): boolean {\n return typeof input === \"object\" && input !== null && (input as { action?: unknown }).action === \"search\";\n}\n","import type { AnswerEditRecord, SandboxChatSettings, SandboxJournal } from \"../types\";\nimport { createSandboxJournal, type JournalOptions } from \"./journal\";\nimport type { BlobRow, JournalStore, ModelCallRow, ToolCallRow } from \"./journal-store\";\n\n/** Хранилище журнала в памяти процесса: для тестов и приложений без базы. */\nexport function createMemoryJournalStore(): JournalStore & { blobCount(): number } {\n const modelCalls = new Map<string, ModelCallRow>();\n const toolCalls = new Map<string, ToolCallRow>();\n const blobs = new Map<string, BlobRow>();\n const settings = new Map<string, SandboxChatSettings>();\n const edits = new Map<string, AnswerEditRecord>();\n\n function addBlobs(rows: BlobRow[]): void {\n for (const row of rows) if (!blobs.has(row.hash)) blobs.set(row.hash, row);\n }\n\n function referencedHashes(): Set<string> {\n const hashes = new Set<string>();\n for (const row of modelCalls.values()) for (const hash of [row.instructionsHash, ...row.messageHashes, row.toolsHash, row.settingsHash, row.responseHash]) hashes.add(hash);\n for (const row of toolCalls.values()) {\n hashes.add(row.inputHash);\n if (row.outputHash) hashes.add(row.outputHash);\n }\n return hashes;\n }\n\n function dropUnreferencedBlobs(): void {\n const used = referencedHashes();\n for (const hash of blobs.keys()) if (!used.has(hash)) blobs.delete(hash);\n }\n\n return {\n blobCount: () => blobs.size,\n async saveModelCall(row, rowBlobs) {\n addBlobs(rowBlobs);\n if (!modelCalls.has(row.id)) modelCalls.set(row.id, row);\n },\n async saveToolCall(row, rowBlobs) {\n addBlobs(rowBlobs);\n if (!toolCalls.has(row.id)) toolCalls.set(row.id, row);\n },\n async listModelCalls(chatId) {\n return [...modelCalls.values()].filter((row) => row.chatId === chatId);\n },\n async listToolCalls(chatId) {\n return [...toolCalls.values()].filter((row) => row.chatId === chatId);\n },\n async findModelCall(id) {\n return modelCalls.get(id) ?? null;\n },\n async findToolCall(id) {\n return toolCalls.get(id) ?? null;\n },\n async loadBlobs(hashes) {\n return hashes.flatMap((hash) => blobs.get(hash) ?? []);\n },\n async loadBlobForOwner(hash, ownerId) {\n const blob = blobs.get(hash);\n if (!blob) return null;\n const owned = [...modelCalls.values()].some(\n (row) => row.ownerId === ownerId && (row.instructionsHash === hash || row.toolsHash === hash || row.settingsHash === hash || row.responseHash === hash || row.messageHashes.includes(hash)),\n ) || [...toolCalls.values()].some((row) => row.ownerId === ownerId && (row.inputHash === hash || row.outputHash === hash));\n return owned ? blob : null;\n },\n async getSettings(chatId) {\n return settings.get(chatId) ?? null;\n },\n async setSettings(value) {\n settings.set(value.chatId, value);\n },\n async saveAnswerEdit(record) {\n edits.set(record.messageId, record);\n },\n async listAnswerEdits(chatId) {\n return [...edits.values()].filter((record) => record.chatId === chatId).sort((a, b) => a.editedAt.getTime() - b.editedAt.getTime());\n },\n async deleteChat(chatId) {\n for (const [id, row] of modelCalls) if (row.chatId === chatId) modelCalls.delete(id);\n for (const [id, row] of toolCalls) if (row.chatId === chatId) toolCalls.delete(id);\n for (const [id, record] of edits) if (record.chatId === chatId) edits.delete(id);\n settings.delete(chatId);\n dropUnreferencedBlobs();\n },\n async deleteOlderThan(before) {\n let removed = 0;\n for (const [id, row] of modelCalls) if (row.startedAt < before) (modelCalls.delete(id), (removed += 1));\n for (const [id, row] of toolCalls) if (row.startedAt < before) (toolCalls.delete(id), (removed += 1));\n dropUnreferencedBlobs();\n return removed;\n },\n async close() {},\n };\n}\n\nexport function createMemorySandboxJournal(options: JournalOptions = {}): SandboxJournal {\n return createSandboxJournal(createMemoryJournalStore(), options);\n}\n"],"mappings":"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","names":["refusal","notFound"]}
1
+ {"version":3,"sources":["../src/sandbox-handler.ts","../src/sandbox-owner.ts","../src/people/copy-person-chat.ts","../src/people/staff-copies.ts","../src/people/people-routes.ts","../src/sandbox-tools.ts","../src/read-only-memory.ts","../src/journal/memory-journal.ts"],"sourcesContent":["import {\n AnswerEditError,\n answerText,\n ChatClosedError,\n ChatNotFoundError,\n createRequestOriginCheck,\n isSafeMethod,\n originRefusalHint,\n} from \"@ykurilov/agent-chat-core\";\nimport type { AgentViewerInput, AgentViewerSource, AnswerEdit, AnswerEditErrorKind, ChatService, StoredMessage } from \"@ykurilov/agent-chat-core\";\nimport { allowedUser, createChatServer } from \"@ykurilov/agent-chat-server\";\nimport type { ChatHandler } from \"@ykurilov/agent-chat-server\";\nimport { secretsFromEnv } from \"./journal/hide-secrets\";\nimport { createPeopleRoutes } from \"./people/people-routes\";\nimport { createStaffCopies } from \"./people/staff-copies\";\nimport { readOnlyMemory } from \"./read-only-memory\";\nimport { sandboxOwnerOf } from \"./sandbox-owner\";\nimport { sandboxTools } from \"./sandbox-tools\";\nimport type {\n AnswerEditMark,\n AnswerEditResponse,\n JournalListResponse,\n SandboxHandlerOptions,\n SandboxHistoryExtras,\n SandboxJournal,\n SandboxSettingsResponse,\n} from \"./types\";\n\nexport { isSandboxOwner } from \"./sandbox-owner\";\n\n/**\n * Инструменты, чей результат правка ответа не меняет: файлы картинок и видео лежат отдельно от текста ответа.\n * Имена строками, без зависимости от пакетов картинок и видео; приложение добавляет свои через `chat.lockedToolOutputs`.\n */\nexport const SANDBOX_LOCKED_TOOL_OUTPUTS: readonly string[] = [\"generate_images\", \"show_video\"];\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\nconst DEFAULT_RETENTION_DAYS = 30;\nconst MAX_INSTRUCTIONS_CHARS = 100_000;\n/** Тело запроса песочницы больше этого не читается: инструкция до 100 000 знаков и результаты инструментов до 64 КБ. */\nconst MAX_BODY_BYTES = 1024 * 1024;\n\nconst answerEditStatus: Record<AnswerEditErrorKind, number> = {\n chat_not_found: 404,\n message_not_found: 404,\n not_an_answer: 400,\n answer_busy: 409,\n not_on_current_branch: 409,\n empty_text: 400,\n invalid_tool_output: 400,\n nothing_changed: 400,\n};\n\n/**\n * Песочница агента для админа: тот же чат приложения, но чаты личные (`sandbox:<id админа>`), каждый ход пишется\n * в журнал, инструкцию можно править на чат, ответ агента — исправить. Раздел «Чаты людей» (`{base}/people`): чаты\n * человека только на чтение и их копия в песочницу; в копии память человека только на чтение, инструменты получают\n * его id и `model.staff`. Не админ получает 404 на любом адресе.\n */\nexport function createSandboxHandler(options: SandboxHandlerOptions): ChatHandler {\n const { chat, journal } = options;\n const basePath = (options.basePath ?? \"/api/sandbox\").replace(/\\/+$/, \"\");\n const logError = chat.logError ?? ((message: string) => console.error(message));\n const allowsOrigin = createRequestOriginCheck({\n allowedOrigins: chat.allowedOrigins,\n onRefused: (refusal) => logError(`Песочница: ${originRefusalHint(refusal)}`),\n });\n // Владелец уже проверенного запроса: внутренний обработчик не зовёт `resolveUser` приложения второй раз.\n const ownerOfRequest = new WeakMap<Request, string>();\n const staffCopies = createStaffCopies();\n const { handler: inner, service } = createChatServer({\n ...chat,\n basePath,\n resolveUser: async (request) => {\n const owner = ownerOfRequest.get(request);\n return owner ? { id: owner } : null;\n },\n agent: {\n ...chat.agent,\n ...(chat.agent.tools && { tools: sandboxTools(chat.agent.tools, options.chatOwnedTools, staffCopies.toolsModel) }),\n // Права — по тому же владельцу, что видят инструменты: настоящий админ или человек копии.\n ...(chat.agent.viewer && { viewer: viewerOfSandbox(chat.agent.viewer, staffCopies.viewerInput) }),\n },\n // Лимиты приложения в песочницу не переходят: только свои, по умолчанию без лимита.\n limits: options.limits,\n // Проверка приложения перед ходом тоже: она ждёт человека приложения, а в песочнице ходит `sandbox:<админ>`.\n beforeTurn: undefined,\n turnNotStarted: undefined,\n // Права приложения тоже: в песочнице ходит админ, ему доступно всё.\n chatCapabilities: undefined,\n // И запрет новых чатов: админ заводит песочные чаты, сколько нужно для теста.\n beforeCreateChat: undefined,\n // Чаты объектов приложения тоже: его `subjectChats` ждёт человека, а получил бы `sandbox:<админ>`. Адреса `/subject` нет.\n subjectChats: undefined,\n // Данные нового чата тоже: они про человека приложения, а в песочнице чат создаёт админ.\n newChatData: undefined,\n // Итог хода приложению — только из копии чата человека, с его объектом и `staff` (см. `staffCopies.afterTurn`).\n afterTurn: chat.afterTurn && staffCopies.afterTurn(chat.afterTurn),\n memory: chat.memory && readOnlyMemory(chat.memory, staffCopies),\n // Копии чатов людей — чаты объектов (`staff-copy:…`): в списке песочницы они видны.\n chatList: { subjectChats: true },\n staffOfChat: staffCopies.staffOf,\n turnObserver: { modelCall: (event) => journal.enqueueModelCall(event), toolCall: (event) => journal.enqueueToolCall(event) },\n chatInstructions: async (sandboxChat) => (await journal.getSettings(sandboxChat.id))?.instructions ?? null,\n lockedToolOutputs: [...new Set([...SANDBOX_LOCKED_TOOL_OUTPUTS, ...(chat.lockedToolOutputs ?? [])])],\n });\n // Ключи провайдеров из окружения чата прячутся в журнале, даже если журнал создан без `env`.\n if (chat.env) journal.addSecrets?.(secretsFromEnv(chat.env));\n // Без опции `people` раздела нет: адреса `/people…` — 404, как до него.\n const people = options.people ? createPeopleRoutes({ service, storage: chat.storage, attachments: chat.attachments, people: options.people }) : null;\n const routes = createSandboxRoutes({ service, journal, people, staffCopies, appInstructions: chat.agent.instructions, logError, readHistory: (request) => inner.fetch(request) });\n startRetention(journal, options.retentionDays ?? DEFAULT_RETENTION_DAYS, logError);\n\n return {\n basePath,\n recoverInterruptedAnswers: () => inner.recoverInterruptedAnswers(),\n async flush(flushOptions) {\n await inner.flush(flushOptions);\n await journal.flush();\n },\n async fetch(request) {\n const segments = pathSegments(new URL(request.url).pathname, basePath);\n if (!segments) return notFound();\n try {\n const resolved = await chat.resolveUser(request);\n // Отказ приложения в доступе — тоже 404: не-админ не должен узнать, что песочница есть, и не получает слов приложения.\n const user = allowedUser(resolved);\n if (!user || !(await options.isAdmin(user, request))) return notFound();\n // Источник проверяется после админа: не-админ всегда получает 404, как будто песочницы нет.\n if (!isSafeMethod(request.method) && !allowsOrigin(request)) return errorResponse(403, \"forbidden_origin\");\n const owner = sandboxOwnerOf(user.id);\n ownerOfRequest.set(request, owner);\n const own = await routes.handle(request, segments, { adminId: user.id, owner });\n if (own) return own;\n return await inner.fetch(request);\n } catch (error) {\n return errorToResponse(error, request, logError);\n }\n },\n };\n}\n\ninterface RouteInput {\n readHistory: (request: Request) => Promise<Response>;\n service: ChatService;\n journal: SandboxJournal;\n people: ReturnType<typeof createPeopleRoutes> | null;\n staffCopies: ReturnType<typeof createStaffCopies>;\n appInstructions: string;\n logError: (message: string) => void;\n}\n\ntype MessagePart = StoredMessage[\"parts\"][number];\n\ninterface Caller {\n adminId: string;\n owner: string;\n}\n\n/** Адреса песочницы поверх чата: журнал, настройки, правка ответа, история с отметками правок, удаление с журналом, чаты людей. */\nfunction createSandboxRoutes({ service, journal, people, staffCopies, appInstructions, logError, readHistory }: RouteInput) {\n async function settingsResponse(chatId: string): Promise<Response> {\n const settings = await journal.getSettings(chatId);\n const body: SandboxSettingsResponse = {\n instructions: settings?.instructions ?? null,\n appInstructions,\n updatedAt: settings?.updatedAt.toISOString() ?? null,\n };\n return Response.json(body);\n }\n\n async function answerEdits(chatId: string): Promise<SandboxHistoryExtras[\"answerEdits\"]> {\n const records = await journal.listEditedAnswers(chatId);\n return Object.fromEntries(records.map(({ messageId, textEdited, editedToolCallIds }) => [messageId, { textEdited, editedToolCallIds }]));\n }\n\n async function editAnswer(request: Request, chatId: string, messageId: string, caller: Caller): Promise<Response> {\n const edit = parseAnswerEdit(await readJson(request));\n if (!edit) return errorResponse(400, \"invalid\");\n const { messages } = await service.getHistory(caller.owner, chatId);\n const replacement = await service.editAnswer(caller.owner, chatId, messageId, edit);\n // Правка прошла — значит, ответ был на текущей ветке и есть в истории.\n const source = messages.find((message) => message.id === messageId)!;\n const earlier = (await journal.listEditedAnswers(chatId)).find((record) => record.messageId === messageId);\n const mark = editMark(source.parts as MessagePart[], edit, earlier);\n // Ответ уже заменён: сбой журнала не должен превращать успех в 500, отметка просто пропадёт.\n try {\n await journal.markAnswerEdited({ messageId: replacement.id, sourceMessageId: messageId, chatId, editedBy: caller.adminId, editedAt: new Date(), ...mark });\n } catch (error) {\n logError(`Песочница: отметка правки ответа не записана: ${describe(error)}`);\n }\n const body: AnswerEditResponse = { messageId: replacement.id, mark };\n return Response.json(body);\n }\n\n /** Ответ, если адрес песочницы свой; `null` — адрес чата, его обслуживает внутренний обработчик. */\n async function handle(request: Request, segments: string[], caller: Caller): Promise<Response | null> {\n const method = request.method.toUpperCase();\n const [first, second, third, fourth, ...rest] = segments;\n\n // Экран памяти в песочнице закрыт: память админа там только для чтения.\n if (first === \"memory\") return notFound();\n\n if (first === \"people\") return people ? people.handle(request, segments.slice(1), caller.owner) : notFound();\n\n if (first === \"journal\") {\n if (method !== \"GET\" || third === undefined || fourth !== undefined) return notFound();\n if (second === \"calls\") {\n const details = await journal.getCall(third);\n return details && details.call.ownerId === caller.owner ? Response.json(details) : notFound();\n }\n if (second === \"blobs\") {\n const blob = await journal.getBlob(third, caller.owner);\n return blob ? Response.json(blob) : notFound();\n }\n return notFound();\n }\n\n if (first === \"chats\") {\n if (second === undefined) return notFound();\n await service.assertOwnChat(caller.owner, second);\n if (third === \"journal\" && fourth === undefined && method === \"GET\") {\n const messageId = new URL(request.url).searchParams.get(\"messageId\");\n const entries = await journal.listForChat(second, messageId ? { messageId } : undefined);\n return Response.json({ entries } as unknown as JournalListResponse);\n }\n if (third === \"settings\" && fourth === undefined) {\n if (method === \"GET\") return settingsResponse(second);\n if (method !== \"PUT\") return notFound();\n const instructions = parseInstructions(await readJson(request));\n if (instructions === \"invalid\") return errorResponse(400, \"invalid\");\n await journal.setSettings({ chatId: second, instructions, updatedBy: caller.adminId, updatedAt: new Date() });\n return settingsResponse(second);\n }\n if (third === \"messages\" && fourth !== undefined && rest.length === 1 && rest[0] === \"answer-edit\" && method === \"POST\") {\n return editAnswer(request, second, fourth, caller);\n }\n // Закрытие в песочнице настоящее (так видно, что оно работает); открыть чат снова — чтобы продолжить тест.\n if (third === \"reopen\" && fourth === undefined && method === \"POST\") {\n await service.reopenChat({ ownerId: caller.owner, chatId: second });\n return new Response(null, { status: 204 });\n }\n return notFound();\n }\n\n // Идентификаторы чатов — UUID, поэтому пути `models`, `chats`, `journal`, `memory` и `people` с чатом не пересекаются.\n if (first !== undefined && first !== \"models\" && second === undefined) {\n if (method === \"GET\") {\n // Preserve transport metadata (including backgroundTasks:false), then add sandbox marks.\n const response = await readHistory(request);\n if (!response.ok || !response.headers.get(\"content-type\")?.includes(\"application/json\")) return response;\n const history = await response.json();\n const headers = new Headers(response.headers);\n // The augmented JSON has a different byte length; retain all other transport headers/status.\n headers.delete(\"content-length\");\n return Response.json({ ...history, answerEdits: await answerEdits(first) }, { status: response.status, headers });\n }\n if (method === \"DELETE\") {\n await service.deleteChat(caller.owner, first);\n staffCopies.forget(first);\n // Чат уже удалён: журнал, который не вычистился сейчас, дочистит суточная чистка.\n try {\n await journal.deleteChat(first);\n } catch (error) {\n logError(`Песочница: журнал удалённого чата не вычищен: ${describe(error)}`);\n }\n return new Response(null, { status: 204 });\n }\n }\n return null;\n }\n\n return { handle };\n}\n\n/** Отметки правки: текст изменён, результаты каких вызовов заменены; прежние отметки исправленного ответа сохраняются. */\nfunction editMark(sourceParts: MessagePart[], edit: AnswerEdit, earlier: AnswerEditMark | undefined): AnswerEditMark {\n const outputs = new Map(\n sourceParts.flatMap((part) => {\n const call = part as { toolCallId?: unknown; state?: unknown; output?: unknown };\n return typeof call.toolCallId === \"string\" && call.state === \"output-available\" ? [[call.toolCallId, JSON.stringify(call.output)] as const] : [];\n }),\n );\n const changedCalls = Object.entries(edit.toolOutputs ?? {})\n .filter(([toolCallId, output]) => outputs.get(toolCallId) !== JSON.stringify(output))\n .map(([toolCallId]) => toolCallId);\n return {\n textEdited: (earlier?.textEdited ?? false) || edit.text !== answerText(sourceParts),\n editedToolCallIds: [...new Set([...(earlier?.editedToolCallIds ?? []), ...changedCalls])],\n };\n}\n\n/** Чистка журнала старше срока: сразу и раз в сутки. Таймер не держит процесс. */\nfunction startRetention(journal: SandboxJournal, retentionDays: number, logError: (message: string) => void): void {\n const prune = () => {\n journal.deleteOlderThan(new Date(Date.now() - retentionDays * DAY_MS)).catch((error: unknown) => {\n logError(`Песочница: чистка журнала не удалась: ${describe(error)}`);\n });\n };\n prune();\n const timer = setInterval(prune, DAY_MS) as { unref?: () => void };\n timer.unref?.();\n}\n\nfunction parseAnswerEdit(body: unknown): AnswerEdit | null {\n if (!isPlainObject(body) || typeof body.text !== \"string\") return null;\n if (body.toolOutputs === undefined) return { text: body.text };\n return isPlainObject(body.toolOutputs) ? { text: body.text, toolOutputs: body.toolOutputs } : null;\n}\n\nfunction parseInstructions(body: unknown): string | null | \"invalid\" {\n if (!isPlainObject(body) || !Object.hasOwn(body, \"instructions\")) return \"invalid\";\n const { instructions } = body;\n if (instructions === null) return null;\n return typeof instructions === \"string\" && instructions.length <= MAX_INSTRUCTIONS_CHARS ? instructions : \"invalid\";\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nclass BodyTooLargeError extends Error {}\n\n/** Тело JSON; не JSON — `undefined` (ответ 400), длиннее предела — 413. Больше предела не читается ни байта сверх него. */\nasync function readJson(request: Request): Promise<unknown> {\n const declared = Number(request.headers.get(\"content-length\"));\n if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {\n await request.body?.cancel().catch(() => {});\n throw new BodyTooLargeError();\n }\n const text = await readLimitedText(request);\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Тело потоком: после предела чтение обрывается, поток закрывается. */\nasync function readLimitedText(request: Request): Promise<string> {\n if (!request.body) return \"\";\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > MAX_BODY_BYTES) {\n await reader.cancel().catch(() => {});\n throw new BodyTooLargeError();\n }\n chunks.push(value);\n }\n return Buffer.concat(chunks).toString(\"utf8\");\n}\n\nfunction pathSegments(pathname: string, basePath: string): string[] | null {\n if (pathname !== basePath && !pathname.startsWith(`${basePath}/`)) return null;\n try {\n return pathname\n .slice(basePath.length)\n .split(\"/\")\n .filter((segment) => segment.length > 0)\n .map((segment) => decodeURIComponent(segment));\n } catch {\n // Битая кодировка (например, `%E0`): такого адреса нет.\n return null;\n }\n}\n\n/** Ответ «нет такого адреса»: и не-админу, и на чужой чат — без слова о песочнице. */\nfunction notFound(): Response {\n return errorResponse(404, \"not_found\");\n}\n\nfunction errorResponse(status: number, kind: string): Response {\n return Response.json({ error: { kind } }, { status });\n}\n\nfunction errorToResponse(error: unknown, request: Request, logError: (message: string) => void): Response {\n if (error instanceof ChatNotFoundError) return notFound();\n if (error instanceof AnswerEditError) return error.kind === \"chat_not_found\" ? notFound() : errorResponse(answerEditStatus[error.kind], error.kind);\n if (error instanceof BodyTooLargeError) return errorResponse(413, \"too_large\");\n if (error instanceof ChatClosedError) return Response.json({ error: { kind: \"chat_closed\", code: error.code, message: error.message } }, { status: 409 });\n logError(`Песочница: сбой на ${request.method} ${new URL(request.url).pathname}: ${describe(error)}`);\n return errorResponse(500, \"other\");\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? `${error.name}: ${error.message}` : String(error);\n}\n\n/** `agent.viewer` приложения в песочнице: вход с настоящим владельцем (см. `staffCopies.viewerInput`). */\nfunction viewerOfSandbox(viewer: AgentViewerSource, input: (input: AgentViewerInput) => AgentViewerInput): AgentViewerSource {\n return (turn) => viewer(input(turn));\n}\n","/** Песочные чаты админа записаны на отдельного владельца: боевой список, лимиты и траты их не видят. */\nconst SANDBOX_OWNER_PREFIX = \"sandbox:\";\n\n/** Владелец песочных чатов админа. */\nexport function sandboxOwnerOf(adminId: string): string {\n return SANDBOX_OWNER_PREFIX + adminId;\n}\n\n/** Чат или трата из песочницы: отчёты приложения отделяют их по владельцу. */\nexport function isSandboxOwner(ownerId: string): boolean {\n return ownerId.startsWith(SANDBOX_OWNER_PREFIX);\n}\n\n/** Настоящий id админа за песочным владельцем; обычный владелец остаётся как есть. */\nexport function realOwnerOf(ownerId: string): string {\n return isSandboxOwner(ownerId) ? ownerId.slice(SANDBOX_OWNER_PREFIX.length) : ownerId;\n}\n","import { chatDataOfInput, DuplicateChatSubjectError, IMPORT_MAX_IMAGE_BYTES, IMPORT_MAX_IMAGES, IMPORT_MAX_MESSAGES } from \"@ykurilov/agent-chat-core\";\nimport type { AgentChatMessage, ChatService, ChatStorage, ImportChatInput } from \"@ykurilov/agent-chat-core\";\nimport type { ChatAttachments } from \"@ykurilov/agent-chat-server\";\nimport { parseStaffCopyKey, staffCopyKey, staffCopySubjectData } from \"./staff-copies\";\n\nconst TITLE_MAX_LENGTH = 100;\nconst COPY_TITLE_PREFIX = \"Копия: \";\n/**\n * Сколько раз взять следующий номер, если такой же номер одновременно заняли другие запросы. Каждая попытка берёт\n * номер не меньше прошлого + 1, поэтому N одновременных копий укладываются в N попыток.\n */\nconst COPY_NUMBER_ATTEMPTS = 20;\n\ntype ImportedPart = ImportChatInput[\"messages\"][number][\"parts\"][number];\ntype MessagePart = AgentChatMessage[\"parts\"][number];\n\nexport interface CopyPersonChatInput {\n service: ChatService;\n storage: ChatStorage;\n attachments: ChatAttachments | undefined;\n personId: string;\n chatId: string;\n /** Песочный владелец админа: копия записывается на него. */\n owner: string;\n /** Запрос админа: из него собирается чтение файла человека. */\n request: Request;\n}\n\n/** Копию сделать нечем: в чате человека нет сообщений. */\nexport class EmptyPersonChatError extends Error {\n constructor(chatId: string) {\n super(`В чате ${chatId} нет сообщений`);\n this.name = \"EmptyPersonChatError\";\n }\n}\n\n/**\n * Копия текущей ветки чата человека в песочнице админа (`service.importChat`): даты как есть, ответы законченные,\n * текст и картинки. Картинки — через вложения (`importFile`), без них — текстовой пометкой; прочие файлы, вызовы\n * инструментов и рассуждения не копируются. Исходный чат (ключ объекта и данные) едет в данные копии.\n * Чат человека только читается.\n * @throws ChatNotFoundError чата нет или он не этого человека.\n * @throws EmptyPersonChatError в чате нет сообщений.\n */\nexport async function copyPersonChat(input: CopyPersonChatInput): Promise<{ chatId: string }> {\n const { service, storage, personId, chatId, owner } = input;\n const history = await service.peekHistory(personId, chatId);\n const createdAtOf = new Map((await storage.listMessages(chatId)).map((message) => [message.id, message.createdAt]));\n const images = { count: 0, bytes: 0 };\n const messages: Array<ImportChatInput[\"messages\"][number]> = [];\n for (const message of history.messages.slice(-IMPORT_MAX_MESSAGES)) {\n const createdAt = createdAtOf.get(message.id);\n if ((message.role !== \"user\" && message.role !== \"assistant\") || !createdAt) continue;\n const parts = await copiedParts(message.parts, input, images);\n if (parts.length > 0) messages.push({ role: message.role, createdAt, parts });\n }\n if (messages.length === 0) throw new EmptyPersonChatError(chatId);\n\n const { modelId, providerId, reasoning } = history.chat;\n // История отдаёт чат без данных: ключ объекта и данные исходного чата берутся из хранилища и едут в копию.\n const source = await storage.getChat(chatId);\n const sourceData = source ? chatDataOfInput(source) : undefined;\n const subjectData = staffCopySubjectData({\n ...(source?.subjectKey !== undefined && { key: source.subjectKey }),\n ...(sourceData !== undefined && { data: sourceData }),\n });\n const title = Array.from(`${COPY_TITLE_PREFIX}${history.chat.title ?? \"\"}`.trim()).slice(0, TITLE_MAX_LENGTH).join(\"\");\n let copyNumber = 0;\n for (let attempt = 1; ; attempt++) {\n copyNumber = Math.max(copyNumber + 1, await nextCopyNumber(service, owner, personId, chatId));\n try {\n return await service.importChat({\n ownerId: owner,\n chat: {\n title,\n subjectKey: staffCopyKey(personId, chatId, copyNumber),\n ...(subjectData && { subjectData }),\n ...(modelId && providerId && { model: { modelId, providerId, ...(reasoning && { reasoning }) } }),\n },\n messages,\n });\n } catch (error) {\n if (!(error instanceof DuplicateChatSubjectError) || attempt >= COPY_NUMBER_ATTEMPTS) throw error;\n }\n }\n}\n\n/**\n * Номер следующей копии этого чата человека у админа: на 1 больше самого большого. Считаются только ключи ровно этого\n * человека и чата: у человека `x:y` начало ключа копии такое же, как у чата `y` человека `x`.\n */\nasync function nextCopyNumber(service: ChatService, owner: string, personId: string, chatId: string): Promise<number> {\n const numbers = (await service.listChats(owner, { subjects: \"only\" })).flatMap((chat) => {\n const copy = parseStaffCopyKey(chat.subjectKey);\n return copy?.personId === personId && copy.chatId === chatId ? [copy.copyNumber] : [];\n });\n return Math.max(0, ...numbers) + 1;\n}\n\nasync function copiedParts(parts: readonly MessagePart[], input: CopyPersonChatInput, images: { count: number; bytes: number }): Promise<ImportedPart[]> {\n const copied: ImportedPart[] = [];\n for (const part of parts) {\n if (part.type === \"text\") {\n if (part.text.length > 0) copied.push({ type: \"text\", text: part.text });\n } else if (part.type === \"file\") {\n copied.push(await copiedFile(part, input, images));\n }\n }\n return copied;\n}\n\n/** Картинка — байтами через вложения (в пределах импорта); не вышло или не картинка — пометкой с именем. */\nasync function copiedFile(\n part: Extract<MessagePart, { type: \"file\" }>,\n input: CopyPersonChatInput,\n images: { count: number; bytes: number },\n): Promise<ImportedPart> {\n const name = part.filename ?? \"файл\";\n const isImage = part.mediaType.startsWith(\"image/\");\n const note: ImportedPart = { type: \"text\", text: isImage ? `[картинка: ${name}]` : `[файл: ${name}]` };\n const fileId = part.url.startsWith(\"attachment:\") ? part.url.slice(\"attachment:\".length) : null;\n if (!isImage || !fileId || !input.attachments?.importFile || images.count >= IMPORT_MAX_IMAGES) return note;\n const response = await input.attachments.handle({\n request: new Request(input.request.url, { method: \"GET\", headers: input.request.headers }),\n userId: input.personId,\n chatId: input.chatId,\n segments: [fileId],\n });\n if (!response.ok) return note;\n const bytes = new Uint8Array(await response.arrayBuffer());\n const mediaType = response.headers.get(\"content-type\")?.split(\";\")[0]?.trim() ?? part.mediaType;\n if (!mediaType.startsWith(\"image/\") || bytes.length === 0 || images.bytes + bytes.length > IMPORT_MAX_IMAGE_BYTES) return note;\n images.count += 1;\n images.bytes += bytes.length;\n return { type: \"image\", bytes, mediaType, filename: name };\n}\n","import type { AfterTurnHandler, AgentToolsModel, AgentViewerInput, Chat, ChatSubject, StaffActor } from \"@ykurilov/agent-chat-core\";\nimport { isSandboxOwner, realOwnerOf } from \"../sandbox-owner\";\n\nconst STAFF_COPY_PREFIX = \"staff-copy:\";\n/** Поле в `subjectData` копии: объект исходного чата человека (ключ и данные). */\nconst ORIGINAL_SUBJECT_FIELD = \"staffCopyOf\";\n/** Сколько копий помнить: копия запоминается заново перед каждым своим ходом, поэтому забыть давнюю не страшно. */\nexport const STAFF_COPIES_REMEMBERED = 1000;\n\n/** Ключ копии чата человека в песочнице: `staff-copy:<человек>:<чат>:<n>`, `n` — номер копии с 1. */\nexport function staffCopyKey(personId: string, chatId: string, copyNumber: number): string {\n return `${staffCopyKeyPrefix(personId, chatId)}${copyNumber}`;\n}\n\n/** Начало ключей всех копий одного чата человека. */\nexport function staffCopyKeyPrefix(personId: string, chatId: string): string {\n return `${STAFF_COPY_PREFIX}${personId}:${chatId}:`;\n}\n\n/** Человек, чат и номер копии по ключу; не ключ копии — `null`. Id человека может содержать `:`, чат и номер — нет. */\nexport function parseStaffCopyKey(key: string | undefined): { personId: string; chatId: string; copyNumber: number } | null {\n if (!key?.startsWith(STAFF_COPY_PREFIX)) return null;\n const rest = key.slice(STAFF_COPY_PREFIX.length).split(\":\");\n if (rest.length < 3) return null;\n const copyNumber = Number(rest.pop());\n const chatId = rest.pop()!;\n const personId = rest.join(\":\");\n if (!Number.isInteger(copyNumber) || copyNumber < 1 || !chatId || !personId) return null;\n return { personId, chatId, copyNumber };\n}\n\n/** Исходный чат человека: ключ объекта (если это чат объекта) и данные чата (если есть). */\nexport interface OriginalChat {\n key?: string;\n data?: Record<string, unknown>;\n}\n\n/** Данные копии: исходный чат лежит под служебным полем; у чата без ключа и без данных переносить нечего. */\nexport function staffCopySubjectData(original: OriginalChat | undefined): Record<string, unknown> | undefined {\n return original && (original.key !== undefined || original.data !== undefined) ? { [ORIGINAL_SUBJECT_FIELD]: original } : undefined;\n}\n\n/** Человек и исходный чат, если `subject` — ключ копии в песочном чате; иначе `null`. */\nfunction copyOfSubject(ownerId: string, subject: ChatSubject | undefined): { personId: string; original: ChatSubject | undefined; data: Record<string, unknown> | undefined } | null {\n const copy = isSandboxOwner(ownerId) ? parseStaffCopyKey(subject?.key) : null;\n if (!copy) return null;\n const stored = subject?.data?.[ORIGINAL_SUBJECT_FIELD] as OriginalChat | undefined;\n const data = isRecord(stored?.data) ? stored.data : undefined;\n const original = typeof stored?.key === \"string\" ? { key: stored.key, ...(data && { data }) } : undefined;\n return { personId: copy.personId, original, data };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Ключ копии наружу не уходит: вместо него объект исходного чата человека (если у него был ключ). Данные чата копии —\n * служебная обёртка, поэтому `chat.data` и `chatData` заменяются данными исходного чата, с ключом или без.\n */\nfunction withOriginalChat<T extends { subject?: ChatSubject; chat?: { data?: Record<string, unknown> }; chatData?: Record<string, unknown> }>(\n value: T,\n copy: { original: ChatSubject | undefined; data: Record<string, unknown> | undefined },\n): T {\n const { subject: _copyKey, chat: _copyChat, chatData: _copyData, ...rest } = value;\n return {\n ...rest,\n ...(copy.original && { subject: copy.original }),\n ...(\"chat\" in value && { chat: { ...(copy.data && { data: copy.data }) } }),\n ...(\"chatData\" in value && copy.data && { chatData: copy.data }),\n } as T;\n}\n\n/**\n * Копии чатов людей в песочнице: ядро спрашивает сотрудника хода (`staffOfChat`) перед каждым ходом, и в этот момент\n * копия запоминает, чей это чат. Инструменты и память хода копии читают человека отсюда: их функции синхронны и\n * знают только id чата.\n */\nexport function createStaffCopies() {\n const personOfChat = new Map<string, string>();\n\n return {\n /** Сотрудник хода: админ песочницы, если чат — копия чата человека; иначе `null`. */\n staffOf(chat: Chat): StaffActor | null {\n const copy = isSandboxOwner(chat.ownerId) ? parseStaffCopyKey(chat.subjectKey) : null;\n if (!copy) {\n personOfChat.delete(chat.id);\n return null;\n }\n // Самая свежая — в конце: при переполнении забывается та, что дольше всех не ходила.\n personOfChat.delete(chat.id);\n personOfChat.set(chat.id, copy.personId);\n if (personOfChat.size > STAFF_COPIES_REMEMBERED) personOfChat.delete(personOfChat.keys().next().value!);\n return { id: realOwnerOf(chat.ownerId) };\n },\n\n /** Человек, чей чат скопирован в этот песочный чат; не копия — `undefined`. */\n personOf(chatId: string): string | undefined {\n return personOfChat.get(chatId);\n },\n\n /**\n * Модель хода для инструментов приложения. В копии — id человека, `staff` админа и объект исходного чата\n * человека (`subject`), в том числе вне хода (сжатие, фоновый помощник): данные человека инструменты только\n * читают. В обычном песочном чате — настоящий id админа, остальное как поставило ядро.\n */\n toolsModel(model: AgentToolsModel): AgentToolsModel {\n const copy = copyOfSubject(model.ownerId, model.subject);\n if (!copy) return { ...model, ownerId: realOwnerOf(model.ownerId) };\n return withOriginalChat({ ...model, ownerId: copy.personId, staff: model.staff ?? { id: realOwnerOf(model.ownerId) } }, copy);\n },\n\n /**\n * Вход `agent.viewer` приложения — как у инструментов: права по владельцу чата. В копии — человек, `userId` и\n * `staff` — админ; в обычном песочном чате — настоящий id админа.\n */\n viewerInput(input: AgentViewerInput): AgentViewerInput {\n const copy = copyOfSubject(input.ownerId, input.subject);\n if (!copy) return { ...input, ownerId: realOwnerOf(input.ownerId), userId: realOwnerOf(input.userId) };\n const staff = input.staff ?? { id: realOwnerOf(input.ownerId) };\n return withOriginalChat({ ...input, ownerId: copy.personId, userId: staff.id, staff }, copy);\n },\n\n /**\n * Хук итога хода приложения в песочнице: зовётся только в копии чата человека — с id человека, `staff` и `actorId`\n * админа и объектом исходного чата, в том числе при доделке после перезапуска. При `turn.staff` в данные человека\n * не писать. В обычном песочном чате не зовётся, как и `beforeTurn`: там ходит не человек приложения.\n */\n afterTurn(handler: AfterTurnHandler): AfterTurnHandler {\n return (result) => {\n const copy = copyOfSubject(result.ownerId, result.subject);\n if (!copy) return;\n const staff = result.staff ?? { id: realOwnerOf(result.ownerId) };\n return handler(withOriginalChat({ ...result, ownerId: copy.personId, staff, actorId: staff.id }, copy));\n };\n },\n\n /** Чат удалён: забыть его. */\n forget(chatId: string): void {\n personOfChat.delete(chatId);\n },\n };\n}\n\nexport type StaffCopies = ReturnType<typeof createStaffCopies>;\n","import type { ChatService, ChatStorage } from \"@ykurilov/agent-chat-core\";\nimport type { ChatAttachments } from \"@ykurilov/agent-chat-server\";\nimport { isSandboxOwner } from \"../sandbox-owner\";\nimport type { PeopleSearchResponse, Person, PersonChatsResponse, SandboxPeople, StaffCopyResponse } from \"../types\";\nimport { copyPersonChat, EmptyPersonChatError } from \"./copy-person-chat\";\n\nconst PERSON_ID_MAX_LENGTH = 128;\nconst QUERY_MAX_LENGTH = 200;\n\nexport interface PeopleRoutesInput {\n /** Служба песочницы: то же хранилище, что у боевого чата, поэтому чаты людей читаются по их id. */\n service: ChatService;\n storage: ChatStorage;\n attachments: ChatAttachments | undefined;\n /** `true` — поиска нет, человека вводят по id. */\n people: SandboxPeople | true;\n}\n\n/**\n * Раздел «Чаты людей» под `{base}/people`: поиск человека, его чаты, история и файлы только на чтение и копия чата\n * в песочницу админа. Админ уже проверен. Записи в чат человека здесь нет ни одной: всё, кроме перечисленного, — 404.\n *\n * - `GET /people?query=` — люди (`PeopleSearchResponse`);\n * - `GET /people/:personId/chats` — чаты человека с чатами объектов (`PersonChatsResponse`);\n * - `GET /people/:personId/models` — меню моделей (экрану чата на чтение);\n * - `GET /people/:personId/:chatId` — история, как её видит человек, без данных объекта;\n * - `GET /people/:personId/:chatId/attachments/:fileId` — файл вложения;\n * - `POST /people/:personId/:chatId/staff-copy` — копия текущей ветки (`StaffCopyResponse`, 201).\n */\nexport function createPeopleRoutes({ service, storage, attachments, people }: PeopleRoutesInput) {\n async function search(request: Request): Promise<Response> {\n const query = (new URL(request.url).searchParams.get(\"query\") ?? \"\").trim().slice(0, QUERY_MAX_LENGTH);\n const finder = people === true ? null : people;\n const found = finder && query.length > 0 ? await finder.find(query) : [];\n const body: PeopleSearchResponse = { search: finder !== null, people: found.flatMap(publicPerson) };\n return Response.json(body);\n }\n\n /** `segments` — путь после `people`; `owner` — песочный владелец админа. */\n async function handle(request: Request, segments: string[], owner: string): Promise<Response> {\n const method = request.method.toUpperCase();\n const [personId, second, third, fourth, ...rest] = segments;\n if (personId === undefined) return method === \"GET\" ? search(request) : notFound();\n // Песочные владельцы — не люди: чужая песочница через этот раздел не читается.\n if (!isPersonId(personId) || second === undefined || rest.length > 0) return notFound();\n\n if (third === undefined && method === \"GET\") {\n if (second === \"chats\") {\n const body: PersonChatsResponse = { chats: await service.listChats(personId, { subjects: \"include\" }) };\n return Response.json(body);\n }\n if (second === \"models\") return Response.json(await service.getModelMenu());\n return Response.json(await service.peekHistory(personId, second));\n }\n if (third === \"attachments\" && fourth !== undefined && method === \"GET\" && attachments) {\n await service.assertOwnChat(personId, second);\n return attachments.handle({ request, userId: personId, chatId: second, segments: [fourth] });\n }\n if (third === \"staff-copy\" && fourth === undefined && method === \"POST\") {\n try {\n const { chatId } = await copyPersonChat({ service, storage, attachments, personId, chatId: second, owner, request });\n const body: StaffCopyResponse = { id: chatId };\n return Response.json(body, { status: 201 });\n } catch (error) {\n if (error instanceof EmptyPersonChatError) return Response.json({ error: { kind: \"empty_chat\" } }, { status: 400 });\n throw error;\n }\n }\n return notFound();\n }\n\n return { handle };\n}\n\nfunction isPersonId(id: string): boolean {\n return id.length > 0 && id.length <= PERSON_ID_MAX_LENGTH && !isSandboxOwner(id);\n}\n\n/** Только поля договора: лишнее, что вернуло приложение, наружу не идёт. */\nfunction publicPerson(person: Person): Person[] {\n if (typeof person?.id !== \"string\" || !isPersonId(person.id)) return [];\n return [\n {\n id: person.id,\n ...(typeof person.name === \"string\" && { name: person.name }),\n ...(typeof person.hint === \"string\" && { hint: person.hint }),\n },\n ];\n}\n\nfunction notFound(): Response {\n return Response.json({ error: { kind: \"not_found\" } }, { status: 404 });\n}\n","import type { AgentToolsModel, AgentToolsSource } from \"@ykurilov/agent-chat-core\";\nimport { realOwnerOf } from \"./sandbox-owner\";\n\n/**\n * Инструменты, чьи файлы и траты принадлежат песочному чату: картинка ложится во вложения песочного владельца\n * (её открывает только этот админ), расход — на него же, а не в боевой лимит.\n */\nexport const SANDBOX_CHAT_OWNED_TOOLS: readonly string[] = [\"generate_images\"];\n\n/** Модель хода для инструментов приложения: настоящий id админа, чат — песочный. */\nexport function withRealOwner(model: AgentToolsModel): AgentToolsModel {\n return { ...model, ownerId: realOwnerOf(model.ownerId) };\n}\n\n/**\n * Инструменты приложения в песочнице: функция получает настоящий `ownerId` админа (в копии чата человека — id\n * человека, см. `toolsModel`), а инструменты из `chatOwned` — песочного владельца. Описания и схемы те же, поэтому\n * запрос к модели побайтно как у боевого чата. Набор-объект от владельца не зависит.\n */\nexport function sandboxTools(\n tools: AgentToolsSource | undefined,\n chatOwned: readonly string[] = SANDBOX_CHAT_OWNED_TOOLS,\n toolsModel: (model: AgentToolsModel) => AgentToolsModel = withRealOwner,\n): AgentToolsSource | undefined {\n if (typeof tools !== \"function\") return tools;\n return (model) => {\n const real = tools(toolsModel(model));\n const owned = chatOwned.filter((name) => name in real);\n if (owned.length === 0) return real;\n const ofSandbox = tools(model);\n return { ...real, ...Object.fromEntries(owned.flatMap((name) => (ofSandbox[name] ? [[name, ofSandbox[name]]] : []))) };\n };\n}\n","import type { AgentToolsModel } from \"@ykurilov/agent-chat-core\";\nimport type { ChatHandlerOptions } from \"@ykurilov/agent-chat-server\";\nimport { realOwnerOf } from \"./sandbox-owner\";\nimport { withRealOwner } from \"./sandbox-tools\";\n\ntype ChatMemory = NonNullable<ChatHandlerOptions[\"memory\"]>;\ntype ToolSet = ReturnType<ChatMemory[\"tools\"]>;\n\nconst READ_ONLY_MESSAGE = \"в песочнице память только для чтения\";\nconst refusal = { kind: \"invalid\" as const, message: READ_ONLY_MESSAGE };\n\n/** Чья память у хода: по умолчанию — настоящего id админа за песочным владельцем. */\nexport interface MemoryOwner {\n /** Модель хода для инструментов памяти (с нужным `ownerId`). */\n toolsModel(model: AgentToolsModel): AgentToolsModel;\n /** Человек, чья память у чата; нет — память админа. */\n personOf(chatId: string): string | undefined;\n}\n\nconst adminMemory: MemoryOwner = { toolsModel: withRealOwner, personOf: () => undefined };\n\n/**\n * Боевая память только на чтение: админа, а в копии чата человека — этого человека (`owner`). Блок памяти и\n * инструменты — с этим id; у инструментов те же имя, описание и схема (начало запроса кэшируется так же), но всё,\n * кроме `search`, отвечает агенту отказом. Оптимизации после ответа (`afterAnswer`) нет; адреса экрана памяти\n * песочница закрывает сама.\n */\nexport function readOnlyMemory(memory: ChatMemory, owner: MemoryOwner = adminMemory): ChatMemory {\n return {\n instructions: memory.instructions,\n toolNames: memory.toolNames,\n tools: (model) => readOnlyTools(memory.tools(owner.toolsModel(model))),\n snapshot: (input) => memory.snapshot({ ...input, userId: owner.personOf(input.chatId) ?? realOwnerOf(input.userId) }),\n ...(memory.blocks && {\n blocks: (input: Parameters<NonNullable<ChatMemory[\"blocks\"]>>[0]) => memory.blocks!({ ...input, userId: owner.personOf(input.chatId) ?? realOwnerOf(input.userId) }),\n }),\n list: (ownerId) => memory.list(realOwnerOf(ownerId)),\n create: async () => ({ ok: false, error: refusal }),\n update: async () => ({ ok: false, error: refusal }),\n remove: async () => ({ ok: false, error: refusal }),\n clear: async () => 0,\n };\n}\n\nfunction readOnlyTools(tools: ToolSet): ToolSet {\n return Object.fromEntries(\n Object.entries(tools).map(([name, tool]) => {\n const execute = tool.execute;\n if (!execute) return [name, tool];\n return [name, { ...tool, execute: (input: unknown, options: unknown) => (isSearch(input) ? execute(input, options as never) : { error: refusal }) }];\n }),\n ) as ToolSet;\n}\n\nfunction isSearch(input: unknown): boolean {\n return typeof input === \"object\" && input !== null && (input as { action?: unknown }).action === \"search\";\n}\n","import type { AnswerEditRecord, SandboxChatSettings, SandboxJournal } from \"../types\";\nimport { createSandboxJournal, type JournalOptions } from \"./journal\";\nimport type { BlobRow, JournalStore, ModelCallRow, ToolCallRow } from \"./journal-store\";\n\n/** Хранилище журнала в памяти процесса: для тестов и приложений без базы. */\nexport function createMemoryJournalStore(): JournalStore & { blobCount(): number } {\n const modelCalls = new Map<string, ModelCallRow>();\n const toolCalls = new Map<string, ToolCallRow>();\n const blobs = new Map<string, BlobRow>();\n const settings = new Map<string, SandboxChatSettings>();\n const edits = new Map<string, AnswerEditRecord>();\n\n function addBlobs(rows: BlobRow[]): void {\n for (const row of rows) if (!blobs.has(row.hash)) blobs.set(row.hash, row);\n }\n\n function referencedHashes(): Set<string> {\n const hashes = new Set<string>();\n for (const row of modelCalls.values()) for (const hash of [row.instructionsHash, ...row.messageHashes, row.toolsHash, row.settingsHash, row.responseHash]) hashes.add(hash);\n for (const row of toolCalls.values()) {\n hashes.add(row.inputHash);\n if (row.outputHash) hashes.add(row.outputHash);\n }\n return hashes;\n }\n\n function dropUnreferencedBlobs(): void {\n const used = referencedHashes();\n for (const hash of blobs.keys()) if (!used.has(hash)) blobs.delete(hash);\n }\n\n return {\n blobCount: () => blobs.size,\n async saveModelCall(row, rowBlobs) {\n addBlobs(rowBlobs);\n if (!modelCalls.has(row.id)) modelCalls.set(row.id, row);\n },\n async saveToolCall(row, rowBlobs) {\n addBlobs(rowBlobs);\n if (!toolCalls.has(row.id)) toolCalls.set(row.id, row);\n },\n async listModelCalls(chatId) {\n return [...modelCalls.values()].filter((row) => row.chatId === chatId);\n },\n async listToolCalls(chatId) {\n return [...toolCalls.values()].filter((row) => row.chatId === chatId);\n },\n async findModelCall(id) {\n return modelCalls.get(id) ?? null;\n },\n async findToolCall(id) {\n return toolCalls.get(id) ?? null;\n },\n async loadBlobs(hashes) {\n return hashes.flatMap((hash) => blobs.get(hash) ?? []);\n },\n async loadBlobForOwner(hash, ownerId) {\n const blob = blobs.get(hash);\n if (!blob) return null;\n const owned = [...modelCalls.values()].some(\n (row) => row.ownerId === ownerId && (row.instructionsHash === hash || row.toolsHash === hash || row.settingsHash === hash || row.responseHash === hash || row.messageHashes.includes(hash)),\n ) || [...toolCalls.values()].some((row) => row.ownerId === ownerId && (row.inputHash === hash || row.outputHash === hash));\n return owned ? blob : null;\n },\n async getSettings(chatId) {\n return settings.get(chatId) ?? null;\n },\n async setSettings(value) {\n settings.set(value.chatId, value);\n },\n async saveAnswerEdit(record) {\n edits.set(record.messageId, record);\n },\n async listAnswerEdits(chatId) {\n return [...edits.values()].filter((record) => record.chatId === chatId).sort((a, b) => a.editedAt.getTime() - b.editedAt.getTime());\n },\n async deleteChat(chatId) {\n for (const [id, row] of modelCalls) if (row.chatId === chatId) modelCalls.delete(id);\n for (const [id, row] of toolCalls) if (row.chatId === chatId) toolCalls.delete(id);\n for (const [id, record] of edits) if (record.chatId === chatId) edits.delete(id);\n settings.delete(chatId);\n dropUnreferencedBlobs();\n },\n async deleteOlderThan(before) {\n let removed = 0;\n for (const [id, row] of modelCalls) if (row.startedAt < before) (modelCalls.delete(id), (removed += 1));\n for (const [id, row] of toolCalls) if (row.startedAt < before) (toolCalls.delete(id), (removed += 1));\n dropUnreferencedBlobs();\n return removed;\n },\n async close() {},\n };\n}\n\nexport function createMemorySandboxJournal(options: JournalOptions = {}): SandboxJournal {\n return createSandboxJournal(createMemoryJournalStore(), options);\n}\n"],"mappings":";;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,aAAa,wBAAwB;;;ACT9C,IAAM,uBAAuB;AAGtB,SAAS,eAAe,SAAyB;AACtD,SAAO,uBAAuB;AAChC;AAGO,SAAS,eAAe,SAA0B;AACvD,SAAO,QAAQ,WAAW,oBAAoB;AAChD;AAGO,SAAS,YAAY,SAAyB;AACnD,SAAO,eAAe,OAAO,IAAI,QAAQ,MAAM,qBAAqB,MAAM,IAAI;AAChF;;;AChBA,SAAS,iBAAiB,2BAA2B,wBAAwB,mBAAmB,2BAA2B;;;ACG3H,IAAM,oBAAoB;AAE1B,IAAM,yBAAyB;AAExB,IAAM,0BAA0B;AAGhC,SAAS,aAAa,UAAkB,QAAgB,YAA4B;AACzF,SAAO,GAAG,mBAAmB,UAAU,MAAM,CAAC,GAAG,UAAU;AAC7D;AAGO,SAAS,mBAAmB,UAAkB,QAAwB;AAC3E,SAAO,GAAG,iBAAiB,GAAG,QAAQ,IAAI,MAAM;AAClD;AAGO,SAAS,kBAAkB,KAA0F;AAC1H,MAAI,CAAC,KAAK,WAAW,iBAAiB,EAAG,QAAO;AAChD,QAAM,OAAO,IAAI,MAAM,kBAAkB,MAAM,EAAE,MAAM,GAAG;AAC1D,MAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,QAAM,aAAa,OAAO,KAAK,IAAI,CAAC;AACpC,QAAM,SAAS,KAAK,IAAI;AACxB,QAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,MAAI,CAAC,OAAO,UAAU,UAAU,KAAK,aAAa,KAAK,CAAC,UAAU,CAAC,SAAU,QAAO;AACpF,SAAO,EAAE,UAAU,QAAQ,WAAW;AACxC;AASO,SAAS,qBAAqB,UAAyE;AAC5G,SAAO,aAAa,SAAS,QAAQ,UAAa,SAAS,SAAS,UAAa,EAAE,CAAC,sBAAsB,GAAG,SAAS,IAAI;AAC5H;AAGA,SAAS,cAAc,SAAiB,SAA6I;AACnL,QAAM,OAAO,eAAe,OAAO,IAAI,kBAAkB,SAAS,GAAG,IAAI;AACzE,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,SAAS,SAAS,OAAO,sBAAsB;AACrD,QAAM,OAAO,SAAS,QAAQ,IAAI,IAAI,OAAO,OAAO;AACpD,QAAM,WAAW,OAAO,QAAQ,QAAQ,WAAW,EAAE,KAAK,OAAO,KAAK,GAAI,QAAQ,EAAE,KAAK,EAAG,IAAI;AAChG,SAAO,EAAE,UAAU,KAAK,UAAU,UAAU,KAAK;AACnD;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAMA,SAAS,iBACP,OACA,MACG;AACH,QAAM,EAAE,SAAS,UAAU,MAAM,WAAW,UAAU,WAAW,GAAG,KAAK,IAAI;AAC7E,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAI,KAAK,YAAY,EAAE,SAAS,KAAK,SAAS;AAAA,IAC9C,GAAI,UAAU,SAAS,EAAE,MAAM,EAAE,GAAI,KAAK,QAAQ,EAAE,MAAM,KAAK,KAAK,EAAG,EAAE;AAAA,IACzE,GAAI,cAAc,SAAS,KAAK,QAAQ,EAAE,UAAU,KAAK,KAAK;AAAA,EAChE;AACF;AAOO,SAAS,oBAAoB;AAClC,QAAM,eAAe,oBAAI,IAAoB;AAE7C,SAAO;AAAA;AAAA,IAEL,QAAQ,MAA+B;AACrC,YAAM,OAAO,eAAe,KAAK,OAAO,IAAI,kBAAkB,KAAK,UAAU,IAAI;AACjF,UAAI,CAAC,MAAM;AACT,qBAAa,OAAO,KAAK,EAAE;AAC3B,eAAO;AAAA,MACT;AAEA,mBAAa,OAAO,KAAK,EAAE;AAC3B,mBAAa,IAAI,KAAK,IAAI,KAAK,QAAQ;AACvC,UAAI,aAAa,OAAO,wBAAyB,cAAa,OAAO,aAAa,KAAK,EAAE,KAAK,EAAE,KAAM;AACtG,aAAO,EAAE,IAAI,YAAY,KAAK,OAAO,EAAE;AAAA,IACzC;AAAA;AAAA,IAGA,SAAS,QAAoC;AAC3C,aAAO,aAAa,IAAI,MAAM;AAAA,IAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,WAAW,OAAyC;AAClD,YAAM,OAAO,cAAc,MAAM,SAAS,MAAM,OAAO;AACvD,UAAI,CAAC,KAAM,QAAO,EAAE,GAAG,OAAO,SAAS,YAAY,MAAM,OAAO,EAAE;AAClE,aAAO,iBAAiB,EAAE,GAAG,OAAO,SAAS,KAAK,UAAU,OAAO,MAAM,SAAS,EAAE,IAAI,YAAY,MAAM,OAAO,EAAE,EAAE,GAAG,IAAI;AAAA,IAC9H;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,YAAY,OAA2C;AACrD,YAAM,OAAO,cAAc,MAAM,SAAS,MAAM,OAAO;AACvD,UAAI,CAAC,KAAM,QAAO,EAAE,GAAG,OAAO,SAAS,YAAY,MAAM,OAAO,GAAG,QAAQ,YAAY,MAAM,MAAM,EAAE;AACrG,YAAM,QAAQ,MAAM,SAAS,EAAE,IAAI,YAAY,MAAM,OAAO,EAAE;AAC9D,aAAO,iBAAiB,EAAE,GAAG,OAAO,SAAS,KAAK,UAAU,QAAQ,MAAM,IAAI,MAAM,GAAG,IAAI;AAAA,IAC7F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,UAAU,SAA6C;AACrD,aAAO,CAAC,WAAW;AACjB,cAAM,OAAO,cAAc,OAAO,SAAS,OAAO,OAAO;AACzD,YAAI,CAAC,KAAM;AACX,cAAM,QAAQ,OAAO,SAAS,EAAE,IAAI,YAAY,OAAO,OAAO,EAAE;AAChE,eAAO,QAAQ,iBAAiB,EAAE,GAAG,QAAQ,SAAS,KAAK,UAAU,OAAO,SAAS,MAAM,GAAG,GAAG,IAAI,CAAC;AAAA,MACxG;AAAA,IACF;AAAA;AAAA,IAGA,OAAO,QAAsB;AAC3B,mBAAa,OAAO,MAAM;AAAA,IAC5B;AAAA,EACF;AACF;;;ADzIA,IAAM,mBAAmB;AACzB,IAAM,oBAAoB;AAK1B,IAAM,uBAAuB;AAkBtB,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAC9C,YAAY,QAAgB;AAC1B,UAAM,mCAAU,MAAM,4EAAgB;AACtC,SAAK,OAAO;AAAA,EACd;AACF;AAUA,eAAsB,eAAe,OAAyD;AAC5F,QAAM,EAAE,SAAS,SAAS,UAAU,QAAQ,MAAM,IAAI;AACtD,QAAM,UAAU,MAAM,QAAQ,YAAY,UAAU,MAAM;AAC1D,QAAM,cAAc,IAAI,KAAK,MAAM,QAAQ,aAAa,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,IAAI,QAAQ,SAAS,CAAC,CAAC;AAClH,QAAM,SAAS,EAAE,OAAO,GAAG,OAAO,EAAE;AACpC,QAAM,WAAuD,CAAC;AAC9D,aAAW,WAAW,QAAQ,SAAS,MAAM,CAAC,mBAAmB,GAAG;AAClE,UAAM,YAAY,YAAY,IAAI,QAAQ,EAAE;AAC5C,QAAK,QAAQ,SAAS,UAAU,QAAQ,SAAS,eAAgB,CAAC,UAAW;AAC7E,UAAM,QAAQ,MAAM,YAAY,QAAQ,OAAO,OAAO,MAAM;AAC5D,QAAI,MAAM,SAAS,EAAG,UAAS,KAAK,EAAE,MAAM,QAAQ,MAAM,WAAW,MAAM,CAAC;AAAA,EAC9E;AACA,MAAI,SAAS,WAAW,EAAG,OAAM,IAAI,qBAAqB,MAAM;AAEhE,QAAM,EAAE,SAAS,YAAY,UAAU,IAAI,QAAQ;AAEnD,QAAM,SAAS,MAAM,QAAQ,QAAQ,MAAM;AAC3C,QAAM,aAAa,SAAS,gBAAgB,MAAM,IAAI;AACtD,QAAM,cAAc,qBAAqB;AAAA,IACvC,GAAI,QAAQ,eAAe,UAAa,EAAE,KAAK,OAAO,WAAW;AAAA,IACjE,GAAI,eAAe,UAAa,EAAE,MAAM,WAAW;AAAA,EACrD,CAAC;AACD,QAAM,QAAQ,MAAM,KAAK,GAAG,iBAAiB,GAAG,QAAQ,KAAK,SAAS,EAAE,GAAG,KAAK,CAAC,EAAE,MAAM,GAAG,gBAAgB,EAAE,KAAK,EAAE;AACrH,MAAI,aAAa;AACjB,WAAS,UAAU,KAAK,WAAW;AACjC,iBAAa,KAAK,IAAI,aAAa,GAAG,MAAM,eAAe,SAAS,OAAO,UAAU,MAAM,CAAC;AAC5F,QAAI;AACF,aAAO,MAAM,QAAQ,WAAW;AAAA,QAC9B,SAAS;AAAA,QACT,MAAM;AAAA,UACJ;AAAA,UACA,YAAY,aAAa,UAAU,QAAQ,UAAU;AAAA,UACrD,GAAI,eAAe,EAAE,YAAY;AAAA,UACjC,GAAI,WAAW,cAAc,EAAE,OAAO,EAAE,SAAS,YAAY,GAAI,aAAa,EAAE,UAAU,EAAG,EAAE;AAAA,QACjG;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,EAAE,iBAAiB,8BAA8B,WAAW,qBAAsB,OAAM;AAAA,IAC9F;AAAA,EACF;AACF;AAMA,eAAe,eAAe,SAAsB,OAAe,UAAkB,QAAiC;AACpH,QAAM,WAAW,MAAM,QAAQ,UAAU,OAAO,EAAE,UAAU,OAAO,CAAC,GAAG,QAAQ,CAAC,SAAS;AACvF,UAAM,OAAO,kBAAkB,KAAK,UAAU;AAC9C,WAAO,MAAM,aAAa,YAAY,KAAK,WAAW,SAAS,CAAC,KAAK,UAAU,IAAI,CAAC;AAAA,EACtF,CAAC;AACD,SAAO,KAAK,IAAI,GAAG,GAAG,OAAO,IAAI;AACnC;AAEA,eAAe,YAAY,OAA+B,OAA4B,QAAmE;AACvJ,QAAM,SAAyB,CAAC;AAChC,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,SAAS,QAAQ;AACxB,UAAI,KAAK,KAAK,SAAS,EAAG,QAAO,KAAK,EAAE,MAAM,QAAQ,MAAM,KAAK,KAAK,CAAC;AAAA,IACzE,WAAW,KAAK,SAAS,QAAQ;AAC/B,aAAO,KAAK,MAAM,WAAW,MAAM,OAAO,MAAM,CAAC;AAAA,IACnD;AAAA,EACF;AACA,SAAO;AACT;AAGA,eAAe,WACb,MACA,OACA,QACuB;AACvB,QAAM,OAAO,KAAK,YAAY;AAC9B,QAAM,UAAU,KAAK,UAAU,WAAW,QAAQ;AAClD,QAAM,OAAqB,EAAE,MAAM,QAAQ,MAAM,UAAU,sDAAc,IAAI,MAAM,8BAAU,IAAI,IAAI;AACrG,QAAM,SAAS,KAAK,IAAI,WAAW,aAAa,IAAI,KAAK,IAAI,MAAM,cAAc,MAAM,IAAI;AAC3F,MAAI,CAAC,WAAW,CAAC,UAAU,CAAC,MAAM,aAAa,cAAc,OAAO,SAAS,kBAAmB,QAAO;AACvG,QAAM,WAAW,MAAM,MAAM,YAAY,OAAO;AAAA,IAC9C,SAAS,IAAI,QAAQ,MAAM,QAAQ,KAAK,EAAE,QAAQ,OAAO,SAAS,MAAM,QAAQ,QAAQ,CAAC;AAAA,IACzF,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,UAAU,CAAC,MAAM;AAAA,EACnB,CAAC;AACD,MAAI,CAAC,SAAS,GAAI,QAAO;AACzB,QAAM,QAAQ,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC;AACzD,QAAM,YAAY,SAAS,QAAQ,IAAI,cAAc,GAAG,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,KAAK,KAAK;AACtF,MAAI,CAAC,UAAU,WAAW,QAAQ,KAAK,MAAM,WAAW,KAAK,OAAO,QAAQ,MAAM,SAAS,uBAAwB,QAAO;AAC1H,SAAO,SAAS;AAChB,SAAO,SAAS,MAAM;AACtB,SAAO,EAAE,MAAM,SAAS,OAAO,WAAW,UAAU,KAAK;AAC3D;;;AEjIA,IAAM,uBAAuB;AAC7B,IAAM,mBAAmB;AAsBlB,SAAS,mBAAmB,EAAE,SAAS,SAAS,aAAa,OAAO,GAAsB;AAC/F,iBAAe,OAAO,SAAqC;AACzD,UAAM,SAAS,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,IAAI,OAAO,KAAK,IAAI,KAAK,EAAE,MAAM,GAAG,gBAAgB;AACrG,UAAM,SAAS,WAAW,OAAO,OAAO;AACxC,UAAM,QAAQ,UAAU,MAAM,SAAS,IAAI,MAAM,OAAO,KAAK,KAAK,IAAI,CAAC;AACvE,UAAM,OAA6B,EAAE,QAAQ,WAAW,MAAM,QAAQ,MAAM,QAAQ,YAAY,EAAE;AAClG,WAAO,SAAS,KAAK,IAAI;AAAA,EAC3B;AAGA,iBAAe,OAAO,SAAkB,UAAoB,OAAkC;AAC5F,UAAM,SAAS,QAAQ,OAAO,YAAY;AAC1C,UAAM,CAAC,UAAU,QAAQ,OAAO,QAAQ,GAAG,IAAI,IAAI;AACnD,QAAI,aAAa,OAAW,QAAO,WAAW,QAAQ,OAAO,OAAO,IAAI,SAAS;AAEjF,QAAI,CAAC,WAAW,QAAQ,KAAK,WAAW,UAAa,KAAK,SAAS,EAAG,QAAO,SAAS;AAEtF,QAAI,UAAU,UAAa,WAAW,OAAO;AAC3C,UAAI,WAAW,SAAS;AACtB,cAAM,OAA4B,EAAE,OAAO,MAAM,QAAQ,UAAU,UAAU,EAAE,UAAU,UAAU,CAAC,EAAE;AACtG,eAAO,SAAS,KAAK,IAAI;AAAA,MAC3B;AACA,UAAI,WAAW,SAAU,QAAO,SAAS,KAAK,MAAM,QAAQ,aAAa,CAAC;AAC1E,aAAO,SAAS,KAAK,MAAM,QAAQ,YAAY,UAAU,MAAM,CAAC;AAAA,IAClE;AACA,QAAI,UAAU,iBAAiB,WAAW,UAAa,WAAW,SAAS,aAAa;AACtF,YAAM,QAAQ,cAAc,UAAU,MAAM;AAC5C,aAAO,YAAY,OAAO,EAAE,SAAS,QAAQ,UAAU,QAAQ,QAAQ,UAAU,CAAC,MAAM,EAAE,CAAC;AAAA,IAC7F;AACA,QAAI,UAAU,gBAAgB,WAAW,UAAa,WAAW,QAAQ;AACvE,UAAI;AACF,cAAM,EAAE,OAAO,IAAI,MAAM,eAAe,EAAE,SAAS,SAAS,aAAa,UAAU,QAAQ,QAAQ,OAAO,QAAQ,CAAC;AACnH,cAAM,OAA0B,EAAE,IAAI,OAAO;AAC7C,eAAO,SAAS,KAAK,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC5C,SAAS,OAAO;AACd,YAAI,iBAAiB,qBAAsB,QAAO,SAAS,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClH,cAAM;AAAA,MACR;AAAA,IACF;AACA,WAAO,SAAS;AAAA,EAClB;AAEA,SAAO,EAAE,OAAO;AAClB;AAEA,SAAS,WAAW,IAAqB;AACvC,SAAO,GAAG,SAAS,KAAK,GAAG,UAAU,wBAAwB,CAAC,eAAe,EAAE;AACjF;AAGA,SAAS,aAAa,QAA0B;AAC9C,MAAI,OAAO,QAAQ,OAAO,YAAY,CAAC,WAAW,OAAO,EAAE,EAAG,QAAO,CAAC;AACtE,SAAO;AAAA,IACL;AAAA,MACE,IAAI,OAAO;AAAA,MACX,GAAI,OAAO,OAAO,SAAS,YAAY,EAAE,MAAM,OAAO,KAAK;AAAA,MAC3D,GAAI,OAAO,OAAO,SAAS,YAAY,EAAE,MAAM,OAAO,KAAK;AAAA,IAC7D;AAAA,EACF;AACF;AAEA,SAAS,WAAqB;AAC5B,SAAO,SAAS,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AACxE;;;ACrFO,IAAM,2BAA8C,CAAC,iBAAiB;AAGtE,SAAS,cAAc,OAAyC;AACrE,SAAO,EAAE,GAAG,OAAO,SAAS,YAAY,MAAM,OAAO,EAAE;AACzD;AAOO,SAAS,aACd,OACA,YAA+B,0BAC/B,aAA0D,eAC5B;AAC9B,MAAI,OAAO,UAAU,WAAY,QAAO;AACxC,SAAO,CAAC,UAAU;AAChB,UAAM,OAAO,MAAM,WAAW,KAAK,CAAC;AACpC,UAAM,QAAQ,UAAU,OAAO,CAAC,SAAS,QAAQ,IAAI;AACrD,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,YAAY,MAAM,KAAK;AAC7B,WAAO,EAAE,GAAG,MAAM,GAAG,OAAO,YAAY,MAAM,QAAQ,CAAC,SAAU,UAAU,IAAI,IAAI,CAAC,CAAC,MAAM,UAAU,IAAI,CAAC,CAAC,IAAI,CAAC,CAAE,CAAC,EAAE;AAAA,EACvH;AACF;;;ACxBA,IAAM,oBAAoB;AAC1B,IAAM,UAAU,EAAE,MAAM,WAAoB,SAAS,kBAAkB;AAUvE,IAAM,cAA2B,EAAE,YAAY,eAAe,UAAU,MAAM,OAAU;AAQjF,SAAS,eAAe,QAAoB,QAAqB,aAAyB;AAC/F,SAAO;AAAA,IACL,cAAc,OAAO;AAAA,IACrB,WAAW,OAAO;AAAA,IAClB,OAAO,CAAC,UAAU,cAAc,OAAO,MAAM,MAAM,WAAW,KAAK,CAAC,CAAC;AAAA,IACrE,UAAU,CAAC,UAAU,OAAO,SAAS,EAAE,GAAG,OAAO,QAAQ,MAAM,SAAS,MAAM,MAAM,KAAK,YAAY,MAAM,MAAM,EAAE,CAAC;AAAA,IACpH,GAAI,OAAO,UAAU;AAAA,MACnB,QAAQ,CAAC,UAA4D,OAAO,OAAQ,EAAE,GAAG,OAAO,QAAQ,MAAM,SAAS,MAAM,MAAM,KAAK,YAAY,MAAM,MAAM,EAAE,CAAC;AAAA,IACrK;AAAA,IACA,MAAM,CAAC,YAAY,OAAO,KAAK,YAAY,OAAO,CAAC;AAAA,IACnD,QAAQ,aAAa,EAAE,IAAI,OAAO,OAAO,QAAQ;AAAA,IACjD,QAAQ,aAAa,EAAE,IAAI,OAAO,OAAO,QAAQ;AAAA,IACjD,QAAQ,aAAa,EAAE,IAAI,OAAO,OAAO,QAAQ;AAAA,IACjD,OAAO,YAAY;AAAA,EACrB;AACF;AAEA,SAAS,cAAc,OAAyB;AAC9C,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM;AAC1C,YAAM,UAAU,KAAK;AACrB,UAAI,CAAC,QAAS,QAAO,CAAC,MAAM,IAAI;AAChC,aAAO,CAAC,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,OAAgB,YAAsB,SAAS,KAAK,IAAI,QAAQ,OAAO,OAAgB,IAAI,EAAE,OAAO,QAAQ,EAAG,CAAC;AAAA,IACrJ,CAAC;AAAA,EACH;AACF;AAEA,SAAS,SAAS,OAAyB;AACzC,SAAO,OAAO,UAAU,YAAY,UAAU,QAAS,MAA+B,WAAW;AACnG;;;ANtBO,IAAM,8BAAiD,CAAC,mBAAmB,YAAY;AAE9F,IAAM,SAAS,KAAK,KAAK,KAAK;AAC9B,IAAM,yBAAyB;AAC/B,IAAM,yBAAyB;AAE/B,IAAM,iBAAiB,OAAO;AAE9B,IAAM,mBAAwD;AAAA,EAC5D,gBAAgB;AAAA,EAChB,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,aAAa;AAAA,EACb,uBAAuB;AAAA,EACvB,YAAY;AAAA,EACZ,qBAAqB;AAAA,EACrB,iBAAiB;AACnB;AAQO,SAAS,qBAAqB,SAA6C;AAChF,QAAM,EAAE,MAAM,QAAQ,IAAI;AAC1B,QAAM,YAAY,QAAQ,YAAY,gBAAgB,QAAQ,QAAQ,EAAE;AACxE,QAAM,WAAW,KAAK,aAAa,CAAC,YAAoB,QAAQ,MAAM,OAAO;AAC7E,QAAM,eAAe,yBAAyB;AAAA,IAC5C,gBAAgB,KAAK;AAAA,IACrB,WAAW,CAACA,aAAY,SAAS,2DAAc,kBAAkBA,QAAO,CAAC,EAAE;AAAA,EAC7E,CAAC;AAED,QAAM,iBAAiB,oBAAI,QAAyB;AACpD,QAAM,cAAc,kBAAkB;AACtC,QAAM,EAAE,SAAS,OAAO,QAAQ,IAAI,iBAAiB;AAAA,IACnD,GAAG;AAAA,IACH;AAAA,IACA,aAAa,OAAO,YAAY;AAC9B,YAAM,QAAQ,eAAe,IAAI,OAAO;AACxC,aAAO,QAAQ,EAAE,IAAI,MAAM,IAAI;AAAA,IACjC;AAAA,IACA,OAAO;AAAA,MACL,GAAG,KAAK;AAAA,MACR,GAAI,KAAK,MAAM,SAAS,EAAE,OAAO,aAAa,KAAK,MAAM,OAAO,QAAQ,gBAAgB,YAAY,UAAU,EAAE;AAAA;AAAA,MAEhH,GAAI,KAAK,MAAM,UAAU,EAAE,QAAQ,gBAAgB,KAAK,MAAM,QAAQ,YAAY,WAAW,EAAE;AAAA,IACjG;AAAA;AAAA,IAEA,QAAQ,QAAQ;AAAA;AAAA,IAEhB,YAAY;AAAA,IACZ,gBAAgB;AAAA;AAAA,IAEhB,kBAAkB;AAAA;AAAA,IAElB,kBAAkB;AAAA;AAAA,IAElB,cAAc;AAAA;AAAA,IAEd,aAAa;AAAA;AAAA,IAEb,WAAW,KAAK,aAAa,YAAY,UAAU,KAAK,SAAS;AAAA,IACjE,QAAQ,KAAK,UAAU,eAAe,KAAK,QAAQ,WAAW;AAAA;AAAA,IAE9D,UAAU,EAAE,cAAc,KAAK;AAAA,IAC/B,aAAa,YAAY;AAAA,IACzB,cAAc,EAAE,WAAW,CAAC,UAAU,QAAQ,iBAAiB,KAAK,GAAG,UAAU,CAAC,UAAU,QAAQ,gBAAgB,KAAK,EAAE;AAAA,IAC3H,kBAAkB,OAAO,iBAAiB,MAAM,QAAQ,YAAY,YAAY,EAAE,IAAI,gBAAgB;AAAA,IACtG,mBAAmB,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,6BAA6B,GAAI,KAAK,qBAAqB,CAAC,CAAE,CAAC,CAAC;AAAA,EACrG,CAAC;AAED,MAAI,KAAK,IAAK,SAAQ,aAAa,eAAe,KAAK,GAAG,CAAC;AAE3D,QAAM,SAAS,QAAQ,SAAS,mBAAmB,EAAE,SAAS,SAAS,KAAK,SAAS,aAAa,KAAK,aAAa,QAAQ,QAAQ,OAAO,CAAC,IAAI;AAChJ,QAAM,SAAS,oBAAoB,EAAE,SAAS,SAAS,QAAQ,aAAa,iBAAiB,KAAK,MAAM,cAAc,UAAU,aAAa,CAAC,YAAY,MAAM,MAAM,OAAO,EAAE,CAAC;AAChL,iBAAe,SAAS,QAAQ,iBAAiB,wBAAwB,QAAQ;AAEjF,SAAO;AAAA,IACL;AAAA,IACA,2BAA2B,MAAM,MAAM,0BAA0B;AAAA,IACjE,MAAM,MAAM,cAAc;AACxB,YAAM,MAAM,MAAM,YAAY;AAC9B,YAAM,QAAQ,MAAM;AAAA,IACtB;AAAA,IACA,MAAM,MAAM,SAAS;AACnB,YAAM,WAAW,aAAa,IAAI,IAAI,QAAQ,GAAG,EAAE,UAAU,QAAQ;AACrE,UAAI,CAAC,SAAU,QAAOC,UAAS;AAC/B,UAAI;AACF,cAAM,WAAW,MAAM,KAAK,YAAY,OAAO;AAE/C,cAAM,OAAO,YAAY,QAAQ;AACjC,YAAI,CAAC,QAAQ,CAAE,MAAM,QAAQ,QAAQ,MAAM,OAAO,EAAI,QAAOA,UAAS;AAEtE,YAAI,CAAC,aAAa,QAAQ,MAAM,KAAK,CAAC,aAAa,OAAO,EAAG,QAAO,cAAc,KAAK,kBAAkB;AACzG,cAAM,QAAQ,eAAe,KAAK,EAAE;AACpC,uBAAe,IAAI,SAAS,KAAK;AACjC,cAAM,MAAM,MAAM,OAAO,OAAO,SAAS,UAAU,EAAE,SAAS,KAAK,IAAI,MAAM,CAAC;AAC9E,YAAI,IAAK,QAAO;AAChB,eAAO,MAAM,MAAM,MAAM,OAAO;AAAA,MAClC,SAAS,OAAO;AACd,eAAO,gBAAgB,OAAO,SAAS,QAAQ;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AACF;AAoBA,SAAS,oBAAoB,EAAE,SAAS,SAAS,QAAQ,aAAa,iBAAiB,UAAU,YAAY,GAAe;AAC1H,iBAAe,iBAAiB,QAAmC;AACjE,UAAM,WAAW,MAAM,QAAQ,YAAY,MAAM;AACjD,UAAM,OAAgC;AAAA,MACpC,cAAc,UAAU,gBAAgB;AAAA,MACxC;AAAA,MACA,WAAW,UAAU,UAAU,YAAY,KAAK;AAAA,IAClD;AACA,WAAO,SAAS,KAAK,IAAI;AAAA,EAC3B;AAEA,iBAAe,YAAY,QAA8D;AACvF,UAAM,UAAU,MAAM,QAAQ,kBAAkB,MAAM;AACtD,WAAO,OAAO,YAAY,QAAQ,IAAI,CAAC,EAAE,WAAW,YAAY,kBAAkB,MAAM,CAAC,WAAW,EAAE,YAAY,kBAAkB,CAAC,CAAC,CAAC;AAAA,EACzI;AAEA,iBAAe,WAAW,SAAkB,QAAgB,WAAmB,QAAmC;AAChH,UAAM,OAAO,gBAAgB,MAAM,SAAS,OAAO,CAAC;AACpD,QAAI,CAAC,KAAM,QAAO,cAAc,KAAK,SAAS;AAC9C,UAAM,EAAE,SAAS,IAAI,MAAM,QAAQ,WAAW,OAAO,OAAO,MAAM;AAClE,UAAM,cAAc,MAAM,QAAQ,WAAW,OAAO,OAAO,QAAQ,WAAW,IAAI;AAElF,UAAM,SAAS,SAAS,KAAK,CAAC,YAAY,QAAQ,OAAO,SAAS;AAClE,UAAM,WAAW,MAAM,QAAQ,kBAAkB,MAAM,GAAG,KAAK,CAAC,WAAW,OAAO,cAAc,SAAS;AACzG,UAAM,OAAO,SAAS,OAAO,OAAwB,MAAM,OAAO;AAElE,QAAI;AACF,YAAM,QAAQ,iBAAiB,EAAE,WAAW,YAAY,IAAI,iBAAiB,WAAW,QAAQ,UAAU,OAAO,SAAS,UAAU,oBAAI,KAAK,GAAG,GAAG,KAAK,CAAC;AAAA,IAC3J,SAAS,OAAO;AACd,eAAS,+OAAiD,SAAS,KAAK,CAAC,EAAE;AAAA,IAC7E;AACA,UAAM,OAA2B,EAAE,WAAW,YAAY,IAAI,KAAK;AACnE,WAAO,SAAS,KAAK,IAAI;AAAA,EAC3B;AAGA,iBAAe,OAAO,SAAkB,UAAoB,QAA0C;AACpG,UAAM,SAAS,QAAQ,OAAO,YAAY;AAC1C,UAAM,CAAC,OAAO,QAAQ,OAAO,QAAQ,GAAG,IAAI,IAAI;AAGhD,QAAI,UAAU,SAAU,QAAOA,UAAS;AAExC,QAAI,UAAU,SAAU,QAAO,SAAS,OAAO,OAAO,SAAS,SAAS,MAAM,CAAC,GAAG,OAAO,KAAK,IAAIA,UAAS;AAE3G,QAAI,UAAU,WAAW;AACvB,UAAI,WAAW,SAAS,UAAU,UAAa,WAAW,OAAW,QAAOA,UAAS;AACrF,UAAI,WAAW,SAAS;AACtB,cAAM,UAAU,MAAM,QAAQ,QAAQ,KAAK;AAC3C,eAAO,WAAW,QAAQ,KAAK,YAAY,OAAO,QAAQ,SAAS,KAAK,OAAO,IAAIA,UAAS;AAAA,MAC9F;AACA,UAAI,WAAW,SAAS;AACtB,cAAM,OAAO,MAAM,QAAQ,QAAQ,OAAO,OAAO,KAAK;AACtD,eAAO,OAAO,SAAS,KAAK,IAAI,IAAIA,UAAS;AAAA,MAC/C;AACA,aAAOA,UAAS;AAAA,IAClB;AAEA,QAAI,UAAU,SAAS;AACrB,UAAI,WAAW,OAAW,QAAOA,UAAS;AAC1C,YAAM,QAAQ,cAAc,OAAO,OAAO,MAAM;AAChD,UAAI,UAAU,aAAa,WAAW,UAAa,WAAW,OAAO;AACnE,cAAM,YAAY,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,IAAI,WAAW;AACnE,cAAM,UAAU,MAAM,QAAQ,YAAY,QAAQ,YAAY,EAAE,UAAU,IAAI,MAAS;AACvF,eAAO,SAAS,KAAK,EAAE,QAAQ,CAAmC;AAAA,MACpE;AACA,UAAI,UAAU,cAAc,WAAW,QAAW;AAChD,YAAI,WAAW,MAAO,QAAO,iBAAiB,MAAM;AACpD,YAAI,WAAW,MAAO,QAAOA,UAAS;AACtC,cAAM,eAAe,kBAAkB,MAAM,SAAS,OAAO,CAAC;AAC9D,YAAI,iBAAiB,UAAW,QAAO,cAAc,KAAK,SAAS;AACnE,cAAM,QAAQ,YAAY,EAAE,QAAQ,QAAQ,cAAc,WAAW,OAAO,SAAS,WAAW,oBAAI,KAAK,EAAE,CAAC;AAC5G,eAAO,iBAAiB,MAAM;AAAA,MAChC;AACA,UAAI,UAAU,cAAc,WAAW,UAAa,KAAK,WAAW,KAAK,KAAK,CAAC,MAAM,iBAAiB,WAAW,QAAQ;AACvH,eAAO,WAAW,SAAS,QAAQ,QAAQ,MAAM;AAAA,MACnD;AAEA,UAAI,UAAU,YAAY,WAAW,UAAa,WAAW,QAAQ;AACnE,cAAM,QAAQ,WAAW,EAAE,SAAS,OAAO,OAAO,QAAQ,OAAO,CAAC;AAClE,eAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC3C;AACA,aAAOA,UAAS;AAAA,IAClB;AAGA,QAAI,UAAU,UAAa,UAAU,YAAY,WAAW,QAAW;AACrE,UAAI,WAAW,OAAO;AAEpB,cAAM,WAAW,MAAM,YAAY,OAAO;AAC1C,YAAI,CAAC,SAAS,MAAM,CAAC,SAAS,QAAQ,IAAI,cAAc,GAAG,SAAS,kBAAkB,EAAG,QAAO;AAChG,cAAM,UAAU,MAAM,SAAS,KAAK;AACpC,cAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;AAE5C,gBAAQ,OAAO,gBAAgB;AAC/B,eAAO,SAAS,KAAK,EAAE,GAAG,SAAS,aAAa,MAAM,YAAY,KAAK,EAAE,GAAG,EAAE,QAAQ,SAAS,QAAQ,QAAQ,CAAC;AAAA,MAClH;AACA,UAAI,WAAW,UAAU;AACvB,cAAM,QAAQ,WAAW,OAAO,OAAO,KAAK;AAC5C,oBAAY,OAAO,KAAK;AAExB,YAAI;AACF,gBAAM,QAAQ,WAAW,KAAK;AAAA,QAChC,SAAS,OAAO;AACd,mBAAS,+OAAiD,SAAS,KAAK,CAAC,EAAE;AAAA,QAC7E;AACA,eAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC3C;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,OAAO;AAClB;AAGA,SAAS,SAAS,aAA4B,MAAkB,SAAqD;AACnH,QAAM,UAAU,IAAI;AAAA,IAClB,YAAY,QAAQ,CAAC,SAAS;AAC5B,YAAM,OAAO;AACb,aAAO,OAAO,KAAK,eAAe,YAAY,KAAK,UAAU,qBAAqB,CAAC,CAAC,KAAK,YAAY,KAAK,UAAU,KAAK,MAAM,CAAC,CAAU,IAAI,CAAC;AAAA,IACjJ,CAAC;AAAA,EACH;AACA,QAAM,eAAe,OAAO,QAAQ,KAAK,eAAe,CAAC,CAAC,EACvD,OAAO,CAAC,CAAC,YAAY,MAAM,MAAM,QAAQ,IAAI,UAAU,MAAM,KAAK,UAAU,MAAM,CAAC,EACnF,IAAI,CAAC,CAAC,UAAU,MAAM,UAAU;AACnC,SAAO;AAAA,IACL,aAAa,SAAS,cAAc,UAAU,KAAK,SAAS,WAAW,WAAW;AAAA,IAClF,mBAAmB,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAI,SAAS,qBAAqB,CAAC,GAAI,GAAG,YAAY,CAAC,CAAC;AAAA,EAC1F;AACF;AAGA,SAAS,eAAe,SAAyB,eAAuB,UAA2C;AACjH,QAAM,QAAQ,MAAM;AAClB,YAAQ,gBAAgB,IAAI,KAAK,KAAK,IAAI,IAAI,gBAAgB,MAAM,CAAC,EAAE,MAAM,CAAC,UAAmB;AAC/F,eAAS,oMAAyC,SAAS,KAAK,CAAC,EAAE;AAAA,IACrE,CAAC;AAAA,EACH;AACA,QAAM;AACN,QAAM,QAAQ,YAAY,OAAO,MAAM;AACvC,QAAM,QAAQ;AAChB;AAEA,SAAS,gBAAgB,MAAkC;AACzD,MAAI,CAAC,cAAc,IAAI,KAAK,OAAO,KAAK,SAAS,SAAU,QAAO;AAClE,MAAI,KAAK,gBAAgB,OAAW,QAAO,EAAE,MAAM,KAAK,KAAK;AAC7D,SAAO,cAAc,KAAK,WAAW,IAAI,EAAE,MAAM,KAAK,MAAM,aAAa,KAAK,YAAY,IAAI;AAChG;AAEA,SAAS,kBAAkB,MAA0C;AACnE,MAAI,CAAC,cAAc,IAAI,KAAK,CAAC,OAAO,OAAO,MAAM,cAAc,EAAG,QAAO;AACzE,QAAM,EAAE,aAAa,IAAI;AACzB,MAAI,iBAAiB,KAAM,QAAO;AAClC,SAAO,OAAO,iBAAiB,YAAY,aAAa,UAAU,yBAAyB,eAAe;AAC5G;AAEA,SAAS,cAAc,OAAkD;AACvE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,IAAM,oBAAN,cAAgC,MAAM;AAAC;AAGvC,eAAe,SAAS,SAAoC;AAC1D,QAAM,WAAW,OAAO,QAAQ,QAAQ,IAAI,gBAAgB,CAAC;AAC7D,MAAI,OAAO,SAAS,QAAQ,KAAK,WAAW,gBAAgB;AAC1D,UAAM,QAAQ,MAAM,OAAO,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC3C,UAAM,IAAI,kBAAkB;AAAA,EAC9B;AACA,QAAM,OAAO,MAAM,gBAAgB,OAAO;AAC1C,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAe,gBAAgB,SAAmC;AAChE,MAAI,CAAC,QAAQ,KAAM,QAAO;AAC1B,QAAM,SAAS,QAAQ,KAAK,UAAU;AACtC,QAAM,SAAuB,CAAC;AAC9B,MAAI,QAAQ;AACZ,aAAS;AACP,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,QAAI,KAAM;AACV,aAAS,MAAM;AACf,QAAI,QAAQ,gBAAgB;AAC1B,YAAM,OAAO,OAAO,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACpC,YAAM,IAAI,kBAAkB;AAAA,IAC9B;AACA,WAAO,KAAK,KAAK;AAAA,EACnB;AACA,SAAO,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AAC9C;AAEA,SAAS,aAAa,UAAkB,UAAmC;AACzE,MAAI,aAAa,YAAY,CAAC,SAAS,WAAW,GAAG,QAAQ,GAAG,EAAG,QAAO;AAC1E,MAAI;AACF,WAAO,SACJ,MAAM,SAAS,MAAM,EACrB,MAAM,GAAG,EACT,OAAO,CAAC,YAAY,QAAQ,SAAS,CAAC,EACtC,IAAI,CAAC,YAAY,mBAAmB,OAAO,CAAC;AAAA,EACjD,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AAGA,SAASA,YAAqB;AAC5B,SAAO,cAAc,KAAK,WAAW;AACvC;AAEA,SAAS,cAAc,QAAgB,MAAwB;AAC7D,SAAO,SAAS,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC;AACtD;AAEA,SAAS,gBAAgB,OAAgB,SAAkB,UAA+C;AACxG,MAAI,iBAAiB,kBAAmB,QAAOA,UAAS;AACxD,MAAI,iBAAiB,gBAAiB,QAAO,MAAM,SAAS,mBAAmBA,UAAS,IAAI,cAAc,iBAAiB,MAAM,IAAI,GAAG,MAAM,IAAI;AAClJ,MAAI,iBAAiB,kBAAmB,QAAO,cAAc,KAAK,WAAW;AAC7E,MAAI,iBAAiB,gBAAiB,QAAO,SAAS,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,MAAM,MAAM,MAAM,SAAS,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AACxJ,WAAS,iGAAsB,QAAQ,MAAM,IAAI,IAAI,IAAI,QAAQ,GAAG,EAAE,QAAQ,KAAK,SAAS,KAAK,CAAC,EAAE;AACpG,SAAO,cAAc,KAAK,OAAO;AACnC;AAEA,SAAS,SAAS,OAAwB;AACxC,SAAO,iBAAiB,QAAQ,GAAG,MAAM,IAAI,KAAK,MAAM,OAAO,KAAK,OAAO,KAAK;AAClF;AAGA,SAAS,gBAAgB,QAA2B,OAAyE;AAC3H,SAAO,CAAC,SAAS,OAAO,MAAM,IAAI,CAAC;AACrC;;;AOvYO,SAAS,2BAAmE;AACjF,QAAM,aAAa,oBAAI,IAA0B;AACjD,QAAM,YAAY,oBAAI,IAAyB;AAC/C,QAAM,QAAQ,oBAAI,IAAqB;AACvC,QAAM,WAAW,oBAAI,IAAiC;AACtD,QAAM,QAAQ,oBAAI,IAA8B;AAEhD,WAAS,SAAS,MAAuB;AACvC,eAAW,OAAO,KAAM,KAAI,CAAC,MAAM,IAAI,IAAI,IAAI,EAAG,OAAM,IAAI,IAAI,MAAM,GAAG;AAAA,EAC3E;AAEA,WAAS,mBAAgC;AACvC,UAAM,SAAS,oBAAI,IAAY;AAC/B,eAAW,OAAO,WAAW,OAAO,EAAG,YAAW,QAAQ,CAAC,IAAI,kBAAkB,GAAG,IAAI,eAAe,IAAI,WAAW,IAAI,cAAc,IAAI,YAAY,EAAG,QAAO,IAAI,IAAI;AAC1K,eAAW,OAAO,UAAU,OAAO,GAAG;AACpC,aAAO,IAAI,IAAI,SAAS;AACxB,UAAI,IAAI,WAAY,QAAO,IAAI,IAAI,UAAU;AAAA,IAC/C;AACA,WAAO;AAAA,EACT;AAEA,WAAS,wBAA8B;AACrC,UAAM,OAAO,iBAAiB;AAC9B,eAAW,QAAQ,MAAM,KAAK,EAAG,KAAI,CAAC,KAAK,IAAI,IAAI,EAAG,OAAM,OAAO,IAAI;AAAA,EACzE;AAEA,SAAO;AAAA,IACL,WAAW,MAAM,MAAM;AAAA,IACvB,MAAM,cAAc,KAAK,UAAU;AACjC,eAAS,QAAQ;AACjB,UAAI,CAAC,WAAW,IAAI,IAAI,EAAE,EAAG,YAAW,IAAI,IAAI,IAAI,GAAG;AAAA,IACzD;AAAA,IACA,MAAM,aAAa,KAAK,UAAU;AAChC,eAAS,QAAQ;AACjB,UAAI,CAAC,UAAU,IAAI,IAAI,EAAE,EAAG,WAAU,IAAI,IAAI,IAAI,GAAG;AAAA,IACvD;AAAA,IACA,MAAM,eAAe,QAAQ;AAC3B,aAAO,CAAC,GAAG,WAAW,OAAO,CAAC,EAAE,OAAO,CAAC,QAAQ,IAAI,WAAW,MAAM;AAAA,IACvE;AAAA,IACA,MAAM,cAAc,QAAQ;AAC1B,aAAO,CAAC,GAAG,UAAU,OAAO,CAAC,EAAE,OAAO,CAAC,QAAQ,IAAI,WAAW,MAAM;AAAA,IACtE;AAAA,IACA,MAAM,cAAc,IAAI;AACtB,aAAO,WAAW,IAAI,EAAE,KAAK;AAAA,IAC/B;AAAA,IACA,MAAM,aAAa,IAAI;AACrB,aAAO,UAAU,IAAI,EAAE,KAAK;AAAA,IAC9B;AAAA,IACA,MAAM,UAAU,QAAQ;AACtB,aAAO,OAAO,QAAQ,CAAC,SAAS,MAAM,IAAI,IAAI,KAAK,CAAC,CAAC;AAAA,IACvD;AAAA,IACA,MAAM,iBAAiB,MAAM,SAAS;AACpC,YAAM,OAAO,MAAM,IAAI,IAAI;AAC3B,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,QAAQ,CAAC,GAAG,WAAW,OAAO,CAAC,EAAE;AAAA,QACrC,CAAC,QAAQ,IAAI,YAAY,YAAY,IAAI,qBAAqB,QAAQ,IAAI,cAAc,QAAQ,IAAI,iBAAiB,QAAQ,IAAI,iBAAiB,QAAQ,IAAI,cAAc,SAAS,IAAI;AAAA,MAC3L,KAAK,CAAC,GAAG,UAAU,OAAO,CAAC,EAAE,KAAK,CAAC,QAAQ,IAAI,YAAY,YAAY,IAAI,cAAc,QAAQ,IAAI,eAAe,KAAK;AACzH,aAAO,QAAQ,OAAO;AAAA,IACxB;AAAA,IACA,MAAM,YAAY,QAAQ;AACxB,aAAO,SAAS,IAAI,MAAM,KAAK;AAAA,IACjC;AAAA,IACA,MAAM,YAAY,OAAO;AACvB,eAAS,IAAI,MAAM,QAAQ,KAAK;AAAA,IAClC;AAAA,IACA,MAAM,eAAe,QAAQ;AAC3B,YAAM,IAAI,OAAO,WAAW,MAAM;AAAA,IACpC;AAAA,IACA,MAAM,gBAAgB,QAAQ;AAC5B,aAAO,CAAC,GAAG,MAAM,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,OAAO,WAAW,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,QAAQ,IAAI,EAAE,SAAS,QAAQ,CAAC;AAAA,IACpI;AAAA,IACA,MAAM,WAAW,QAAQ;AACvB,iBAAW,CAAC,IAAI,GAAG,KAAK,WAAY,KAAI,IAAI,WAAW,OAAQ,YAAW,OAAO,EAAE;AACnF,iBAAW,CAAC,IAAI,GAAG,KAAK,UAAW,KAAI,IAAI,WAAW,OAAQ,WAAU,OAAO,EAAE;AACjF,iBAAW,CAAC,IAAI,MAAM,KAAK,MAAO,KAAI,OAAO,WAAW,OAAQ,OAAM,OAAO,EAAE;AAC/E,eAAS,OAAO,MAAM;AACtB,4BAAsB;AAAA,IACxB;AAAA,IACA,MAAM,gBAAgB,QAAQ;AAC5B,UAAI,UAAU;AACd,iBAAW,CAAC,IAAI,GAAG,KAAK,WAAY,KAAI,IAAI,YAAY,OAAQ,CAAC,WAAW,OAAO,EAAE,GAAI,WAAW;AACpG,iBAAW,CAAC,IAAI,GAAG,KAAK,UAAW,KAAI,IAAI,YAAY,OAAQ,CAAC,UAAU,OAAO,EAAE,GAAI,WAAW;AAClG,4BAAsB;AACtB,aAAO;AAAA,IACT;AAAA,IACA,MAAM,QAAQ;AAAA,IAAC;AAAA,EACjB;AACF;AAEO,SAAS,2BAA2B,UAA0B,CAAC,GAAmB;AACvF,SAAO,qBAAqB,yBAAyB,GAAG,OAAO;AACjE;","names":["refusal","notFound"]}
package/dist/react.js CHANGED
@@ -487,7 +487,7 @@ function messageToolDetailsView(part) {
487
487
 
488
488
  // src/react/journal-panel.tsx
489
489
  import { jsx as jsx4 } from "react/jsx-runtime";
490
- function JournalPanel({
490
+ function useJournalPanel({
491
491
  skin,
492
492
  client,
493
493
  chatId,
@@ -503,6 +503,7 @@ function JournalPanel({
503
503
  const request = useRef(0);
504
504
  const reload = useCallback(() => {
505
505
  const ticket = ++request.current;
506
+ if (chatId === null) return;
506
507
  client.journal(chatId).then(
507
508
  (entries) => ticket === request.current && setLoad({ state: "ready", entries }),
508
509
  () => ticket === request.current && setLoad((current) => ({ ...current, state: "failed" }))
@@ -514,6 +515,9 @@ function JournalPanel({
514
515
  setOpenIds(/* @__PURE__ */ new Set());
515
516
  setDetails({});
516
517
  reload();
518
+ return () => {
519
+ request.current++;
520
+ };
517
521
  }, [reload]);
518
522
  const wasBusy = useRef(busy);
519
523
  useEffect2(() => {
@@ -611,6 +615,7 @@ function JournalPanel({
611
615
  return /* @__PURE__ */ jsx4(
612
616
  skin.JournalList,
613
617
  {
618
+ busy,
614
619
  state,
615
620
  rows,
616
621
  ...load.state === "failed" && { onRetry: reload },
@@ -977,10 +982,12 @@ function AgentSandbox({ basePath = DEFAULT_SANDBOX_BASE_PATH, skin, sandboxSkin,
977
982
  },
978
983
  [chatId, client, edits, sandboxSkin]
979
984
  );
985
+ const journal = useJournalPanel({ skin: sandboxSkin, client, chatId, messages: feed.messages, busy: feed.busy, edits });
980
986
  const Sections = sandboxSkin.SandboxSections ?? PlainSandboxSections;
981
987
  const sandboxView = /* @__PURE__ */ jsx7(
982
988
  sandboxSkin.SandboxLayout,
983
989
  {
990
+ busy: feed.busy,
984
991
  header: chatId !== null && /* @__PURE__ */ jsxs3(Fragment2, { children: [
985
992
  /* @__PURE__ */ jsx7(InstructionHeader, { skin: sandboxSkin, client, chatId }, chatId),
986
993
  chatClosed && /* @__PURE__ */ jsx7(ChatReopenControl, { skin: sandboxSkin, client, chatId, onReopened: onChatReopened }, chatId)
@@ -999,7 +1006,7 @@ function AgentSandbox({ basePath = DEFAULT_SANDBOX_BASE_PATH, skin, sandboxSkin,
999
1006
  ...answerEditing && { answerEditing }
1000
1007
  }
1001
1008
  ),
1002
- journal: chatId !== null && /* @__PURE__ */ jsx7(JournalPanel, { skin: sandboxSkin, client, chatId, messages: feed.messages, busy: feed.busy, edits }),
1009
+ journal: chatId !== null && journal,
1003
1010
  journalOpen,
1004
1011
  onJournalOpenChange: setJournalOpen
1005
1012
  }
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react/agent-sandbox.tsx","../src/react/answer-editor-host.tsx","../src/react/answer-edit-draft.ts","../src/react/sandbox-labels.ts","../src/react/chat-reopen.tsx","../src/react/instruction-header.tsx","../src/react/journal-panel.tsx","../src/react/journal-view.ts","../src/react/people-chats-panel.tsx","../src/react/read-only-chat-skin.tsx","../src/react/sandbox-client.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { ChatWorkspace } from \"@ykurilov/agent-chat-react\";\nimport type { AgentChatMessage, AgentChatProps, ChatAccess, ChatListSkin, ChatSkin, ChatWorkspaceProps } from \"@ykurilov/agent-chat-react\";\nimport type { AnswerEditMark } from \"../types\";\nimport { AnswerEditorHost } from \"./answer-editor-host\";\nimport { ChatReopenControl } from \"./chat-reopen\";\nimport { InstructionHeader } from \"./instruction-header\";\nimport { JournalPanel } from \"./journal-panel\";\nimport { PeopleChatsPanel } from \"./people-chats-panel\";\nimport type { ReadOnlyChatProps } from \"./people-chats-panel\";\nimport { createSandboxClient, DEFAULT_SANDBOX_BASE_PATH } from \"./sandbox-client\";\nimport { usePeopleChatsLabels } from \"./sandbox-labels\";\nimport type { SandboxSection, SandboxSkin } from \"./sandbox-skin\";\n\ntype AnswerEditing = NonNullable<AgentChatProps[\"answerEditing\"]>;\ntype Feed = { messages: AgentChatMessage[]; busy: boolean };\n\nexport interface AgentSandboxProps {\n /** Адрес обработчика песочницы; по умолчанию `/api/sandbox`. */\n basePath?: string;\n skin: ChatSkin;\n sandboxSkin: SandboxSkin;\n chatListSkin: ChatListSkin;\n /**\n * Те же возможности чата, что у боевой страницы: вложения, картинки, видео, схемы, виджеты, голос. Без них агент\n * в песочнице отвечает, но файлы и картинки в ленте не показываются. Остальным экран песочницы управляет сам.\n */\n chatProps?: Omit<ChatWorkspaceProps, \"api\" | \"skin\" | \"initialChatId\" | \"onChatChange\" | \"onFeedChange\" | \"answerEditing\" | \"reloadKey\">;\n /**\n * Раздел «Чаты людей» и переключатель разделов. Ставить, только если у обработчика песочницы есть опция `people`;\n * нет — экран песочницы прежний, без полосы разделов.\n */\n people?: boolean;\n}\n\n/**\n * Песочница для админа: песочные чаты списком, чат с правкой ответов, инструкция чата и журнал обращений к модели\n * и вызовов инструментов рядом. С `people` второй раздел — «Чаты людей»: чат человека на чтение и «Проверить в копии»; копия\n * открывается здесь же как обычный песочный чат. Всё видимое рисуют скины.\n */\nexport function AgentSandbox({ basePath = DEFAULT_SANDBOX_BASE_PATH, skin, sandboxSkin, chatListSkin, chatProps, people = false }: AgentSandboxProps) {\n const client = useMemo(() => createSandboxClient({ basePath }), [basePath]);\n const workspaceSkin = useMemo(() => ({ ...skin, ...chatListSkin }), [skin, chatListSkin]);\n const [chatId, setChatId] = useState<string | null>(null);\n const [feed, setFeed] = useState<Feed>({ messages: [], busy: false });\n const [edits, setEdits] = useState<ReadonlyMap<string, AnswerEditMark>>(new Map());\n const [journalOpen, setJournalOpen] = useState(false);\n /** Открытый песочный чат закрыт инструментом: админу нужна кнопка «Открыть чат снова». */\n const [chatClosed, setChatClosed] = useState(false);\n /** Растёт после «Открыть чат снова»: экран чата перечитывает историю и снова принимает ходы. */\n const [reloadKey, setReloadKey] = useState(0);\n const passedAccessChange = chatProps?.onChatAccessChange;\n const onChatAccessChange = useCallback(\n (access: ChatAccess) => {\n setChatClosed(access.closed !== null);\n passedAccessChange?.(access);\n },\n [passedAccessChange],\n );\n const onChatReopened = useCallback(() => {\n setChatClosed(false);\n setReloadKey((key) => key + 1);\n }, []);\n const [section, setSection] = useState<SandboxSection>(\"sandbox\");\n /** Только что созданная копия чата человека: песочный раздел открывается на ней, а не на запомненном чате. */\n const [openedCopyId, setOpenedCopyId] = useState<string | null>(null);\n const changeSection = useCallback((next: SandboxSection) => {\n setOpenedCopyId(null);\n setSection(next);\n }, []);\n const readOnlyChatProps = useMemo<ReadOnlyChatProps>(() => readOnlyPropsOf(chatProps), [chatProps]);\n const onCopied = useCallback((copyId: string) => {\n setOpenedCopyId(copyId);\n setSection(\"sandbox\");\n }, []);\n\n /** Права нового чата приходят с его историей; пока их нет, закрытость прежнего чата не переносится. Первый чат не в счёт: его права могли прийти раньше. */\n const previousChatId = useRef<string | null>(null);\n useEffect(() => {\n if (previousChatId.current !== null && previousChatId.current !== chatId) setChatClosed(false);\n previousChatId.current = chatId;\n }, [chatId]);\n\n useEffect(() => {\n setEdits(new Map());\n if (chatId === null) return;\n let current = true;\n client.answerEdits(chatId).then(\n (loaded) => current && setEdits(loaded),\n () => undefined,\n );\n return () => {\n current = false;\n };\n }, [client, chatId]);\n\n // Пока идёт ответ, куски потока журналу не нужны: экран не перерисовывается на каждый кусок.\n const onFeedChange = useCallback((next: Feed) => setFeed((current) => (current.busy && next.busy ? current : next)), []);\n\n const answerEditing = useMemo<AnswerEditing | undefined>(\n () =>\n chatId === null\n ? undefined\n : {\n edits,\n renderEditor: ({ message, onSaved, onCancel }) => (\n <AnswerEditorHost\n key={message.id}\n skin={sandboxSkin}\n client={client}\n chatId={chatId}\n message={message}\n onSaved={(newMessageId, mark) => {\n setEdits((current) => new Map(current).set(newMessageId, mark));\n onSaved(newMessageId);\n }}\n onCancel={onCancel}\n />\n ),\n },\n [chatId, client, edits, sandboxSkin],\n );\n\n const Sections = sandboxSkin.SandboxSections ?? PlainSandboxSections;\n const sandboxView = (\n <sandboxSkin.SandboxLayout\n header={\n chatId !== null && (\n <>\n <InstructionHeader key={chatId} skin={sandboxSkin} client={client} chatId={chatId} />\n {chatClosed && <ChatReopenControl key={chatId} skin={sandboxSkin} client={client} chatId={chatId} onReopened={onChatReopened} />}\n </>\n )\n }\n chat={\n <ChatWorkspace\n {...chatProps}\n {...(openedCopyId !== null && { initialChatId: openedCopyId })}\n api={basePath}\n skin={workspaceSkin}\n onChatChange={setChatId}\n onFeedChange={onFeedChange}\n onChatAccessChange={onChatAccessChange}\n reloadKey={reloadKey}\n {...(answerEditing && { answerEditing })}\n />\n }\n journal={\n chatId !== null && <JournalPanel skin={sandboxSkin} client={client} chatId={chatId} messages={feed.messages} busy={feed.busy} edits={edits} />\n }\n journalOpen={journalOpen}\n onJournalOpenChange={setJournalOpen}\n />\n );\n if (!people) return sandboxView;\n return (\n <Sections section={section} onSectionChange={changeSection}>\n {section === \"people\" ? (\n <PeopleChatsPanel client={client} skin={skin} sandboxSkin={sandboxSkin} chatProps={readOnlyChatProps} onCopied={onCopied} />\n ) : (\n sandboxView\n )}\n </Sections>\n );\n}\n\nfunction readOnlyPropsOf(chatProps: AgentSandboxProps[\"chatProps\"]): ReadOnlyChatProps {\n if (!chatProps) return {};\n const { attachments, widgets, diagrams, images, videos, liveness } = chatProps;\n return {\n ...(attachments && { attachments }),\n ...(widgets && { widgets }),\n ...(diagrams && { diagrams }),\n ...(images && { images }),\n ...(videos && { videos }),\n ...(liveness && { liveness }),\n };\n}\n\n/** Простой переключатель разделов для скина без `SandboxSections`. */\nfunction PlainSandboxSections({ section, onSectionChange, children }: { section: SandboxSection; onSectionChange: (section: SandboxSection) => void; children: ReactNode }) {\n const labels = usePeopleChatsLabels();\n return (\n <>\n <nav aria-label={labels.sections}>\n <button type=\"button\" aria-pressed={section === \"sandbox\"} onClick={() => onSectionChange(\"sandbox\")}>\n {labels.sandboxSection}\n </button>\n <button type=\"button\" aria-pressed={section === \"people\"} onClick={() => onSectionChange(\"people\")}>\n {labels.peopleSection}\n </button>\n </nav>\n {children}\n </>\n );\n}\n","import { useState } from \"react\";\nimport type { AgentChatMessage } from \"@ykurilov/agent-chat-react\";\nimport type { AnswerEditMark } from \"../types\";\nimport { answerEditDraft, answerEditRequest, checkAnswerEditDraft } from \"./answer-edit-draft\";\nimport type { AnswerEditDraft } from \"./answer-edit-draft\";\nimport type { SandboxClient } from \"./sandbox-client\";\nimport { useSandboxLabels } from \"./sandbox-labels\";\nimport type { SandboxSkin } from \"./sandbox-skin\";\n\n/** Редактор ответа на месте ответа: черновик текста и JSON результатов, проверка, отправка только изменённого. */\nexport function AnswerEditorHost({\n skin,\n client,\n chatId,\n message,\n onSaved,\n onCancel,\n}: {\n skin: SandboxSkin;\n client: SandboxClient;\n chatId: string;\n message: AgentChatMessage;\n onSaved: (newMessageId: string, mark: AnswerEditMark) => void;\n onCancel: () => void;\n}) {\n const labels = useSandboxLabels();\n const [draft, setDraft] = useState<AnswerEditDraft>(() => answerEditDraft(message));\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | undefined>();\n const check = checkAnswerEditDraft(draft);\n\n const save = async () => {\n if (!check.canSave || saving) return;\n setSaving(true);\n setError(undefined);\n try {\n const saved = await client.editAnswer(chatId, message.id, answerEditRequest(draft));\n onSaved(saved.messageId, saved.mark);\n } catch {\n setError(labels.answerEditFailed);\n setSaving(false);\n }\n };\n\n return (\n <skin.AnswerEditor\n text={draft.text}\n onTextChange={(text) => setDraft((current) => ({ ...current, text }))}\n {...(check.textError && { textError: labels.answerTextEmpty })}\n toolOutputs={draft.toolOutputs.map((output) => {\n const outputError = check.outputErrors[output.toolCallId];\n return {\n toolCallId: output.toolCallId,\n toolName: output.toolName,\n ...(output.title !== undefined && { title: output.title }),\n value: output.value,\n onValueChange: (value: string) =>\n setDraft((current) => ({\n ...current,\n toolOutputs: current.toolOutputs.map((item) => (item.toolCallId === output.toolCallId ? { ...item, value } : item)),\n })),\n ...(outputError && { error: outputError === \"invalid\" ? labels.toolOutputInvalidJson : labels.toolOutputTooLarge }),\n edited: output.value !== output.original,\n };\n })}\n onSave={() => void save()}\n onCancel={onCancel}\n canSave={check.canSave && !saving}\n saving={saving}\n {...(error && { error })}\n />\n );\n}\n","import type { AgentChatMessage } from \"@ykurilov/agent-chat-react\";\nimport type { AnswerEditRequest } from \"../types\";\n\n/** Предел нового результата инструмента (FR-014a). */\nexport const MAX_TOOL_OUTPUT_BYTES = 64 * 1024;\n\n/** Вызовы, результат которых не правится: картинки, видео, виджеты и подсказки — у них свой вид, а не JSON. */\nconst NOT_EDITABLE_TOOLS = new Set([\"generate_images\", \"show_video\", \"ask_choice\", \"ask_multi_choice\", \"ask_rating\", \"ask_form\", \"suggest_replies\"]);\n\ntype MessagePart = AgentChatMessage[\"parts\"][number];\n\n/** Часть-вызов инструмента: `tool-<имя>` или `dynamic-tool` (как `isToolUIPart` и `getToolName` из `ai`; пакет от `ai` не зависит). */\nexport interface ToolCallPart {\n toolCallId: string;\n toolName: string;\n state: string;\n input?: unknown;\n output?: unknown;\n errorText?: string;\n title?: string;\n}\n\nexport function toolCallOf(part: MessagePart): ToolCallPart | null {\n const record = part as { type: string; toolName?: unknown; toolCallId?: unknown };\n if (typeof record.toolCallId !== \"string\") return null;\n if (record.type === \"dynamic-tool\" && typeof record.toolName === \"string\") return part as unknown as ToolCallPart;\n if (record.type.startsWith(\"tool-\")) return { ...(part as unknown as ToolCallPart), toolName: record.type.slice(\"tool-\".length) };\n return null;\n}\n\nexport interface ToolOutputDraft {\n toolCallId: string;\n toolName: string;\n title?: string;\n /** Исходный результат JSON-текстом; у вызова с ошибкой — пусто. */\n original: string;\n value: string;\n}\n\nexport interface AnswerEditDraft {\n originalText: string;\n text: string;\n toolOutputs: ToolOutputDraft[];\n}\n\nexport type ToolOutputError = \"invalid\" | \"too_large\";\n\nexport interface AnswerEditCheck {\n textError: \"empty\" | undefined;\n outputErrors: Record<string, ToolOutputError>;\n /** Текст не пуст, все правленые JSON верны и хоть что-то изменилось. */\n canSave: boolean;\n}\n\n/** Черновик из ответа: текст частей через пустую строку и результаты обычных вызовов, у которых результат уже есть. */\nexport function answerEditDraft(message: AgentChatMessage): AnswerEditDraft {\n const text = message.parts.flatMap((part) => (part.type === \"text\" ? [part.text] : [])).join(\"\\n\\n\");\n const toolOutputs = message.parts.flatMap((part): ToolOutputDraft[] => {\n const call = toolCallOf(part);\n if (!call || (call.state !== \"output-available\" && call.state !== \"output-error\") || NOT_EDITABLE_TOOLS.has(call.toolName)) return [];\n const original = call.state === \"output-available\" ? (JSON.stringify(call.output, null, 2) ?? \"\") : \"\";\n const title = typeof call.title === \"string\" ? call.title : undefined;\n return [{ toolCallId: call.toolCallId, toolName: call.toolName, ...(title !== undefined && { title }), original, value: original }];\n });\n return { originalText: text, text, toolOutputs };\n}\n\nconst isEdited = (output: ToolOutputDraft) => output.value !== output.original;\n\nfunction toolOutputError(value: string): ToolOutputError | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch {\n return \"invalid\";\n }\n return new TextEncoder().encode(JSON.stringify(parsed)).length > MAX_TOOL_OUTPUT_BYTES ? \"too_large\" : undefined;\n}\n\nexport function checkAnswerEditDraft(draft: AnswerEditDraft): AnswerEditCheck {\n const textError = draft.text.trim().length === 0 ? \"empty\" : undefined;\n const outputErrors: Record<string, ToolOutputError> = {};\n for (const output of draft.toolOutputs.filter(isEdited)) {\n const error = toolOutputError(output.value);\n if (error) outputErrors[output.toolCallId] = error;\n }\n const changed = draft.text !== draft.originalText || draft.toolOutputs.some(isEdited);\n return { textError, outputErrors, canSave: !textError && Object.keys(outputErrors).length === 0 && changed };\n}\n\n/** Тело запроса: текст и только изменённые результаты. Черновик должен пройти `checkAnswerEditDraft`. */\nexport function answerEditRequest(draft: AnswerEditDraft): AnswerEditRequest {\n const edited = draft.toolOutputs.filter(isEdited);\n if (edited.length === 0) return { text: draft.text };\n return { text: draft.text, toolOutputs: Object.fromEntries(edited.map((output) => [output.toolCallId, JSON.parse(output.value) as unknown])) };\n}\n","import { createContext, createElement, useContext } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { ModelCallPurpose } from \"@ykurilov/agent-chat-core\";\n\n/** Надписи песочницы. Надписи чата внутри песочницы — обычный `ChatLabels`. */\nexport interface SandboxLabels {\n title: string;\n chatPanel: string;\n journalPanel: string;\n /** Подпись выпадающего списка ходов внутри журнала. */\n turnPicker: string;\n openJournal: string;\n closeJournal: string;\n journalLoading: string;\n journalFailed: string;\n journalEmpty: string;\n retry: string;\n wholeChat: string;\n /** Подпись хода в выборе над журналом: `index` считается с 1. */\n turnLabel: (index: number) => string;\n purposes: Record<ModelCallPurpose, string>;\n modelCallStatus: Record<\"complete\" | \"error\" | \"aborted\", string>;\n attempt: (attempt: number) => string;\n durationMs: (ms: number) => string;\n firstChunk: (ms: number) => string;\n tokens: (input: number, cached: number, output: number) => string;\n costUnknown: string;\n instructionsChanged: string;\n outputEdited: string;\n fromMessage: string;\n truncated: string;\n instructions: string;\n messages: string;\n newMessage: string;\n tools: string;\n settings: string;\n response: string;\n reasoning: string;\n toolCalls: string;\n finishReason: string;\n error: string;\n toolInput: string;\n toolOutput: string;\n noOutput: string;\n instructionEditor: string;\n /** Подпись самого поля текста инструкции (кнопка раскрытия подписана `instructionEditor`). */\n instructionText: string;\n appInstructions: string;\n instructionsEdited: string;\n instructionsSave: string;\n instructionsSaving: string;\n instructionsReset: string;\n instructionsTooLong: (maxLength: number) => string;\n instructionsSaveFailed: string;\n answerEditor: string;\n answerText: string;\n answerTextEmpty: string;\n toolOutputJson: (toolName: string) => string;\n toolOutputInvalidJson: string;\n toolOutputTooLarge: string;\n answerEditSave: string;\n answerEditSaving: string;\n answerEditCancel: string;\n answerEditFailed: string;\n /** Раздел «Чаты людей»; нет в словаре — русские надписи `russianPeopleChatsLabels`. */\n peopleChats?: PeopleChatsLabels;\n /** Кнопка «Открыть чат снова»; нет в словаре — русские надписи `russianChatReopenLabels`. */\n chatReopen?: ChatReopenLabels;\n}\n\n/** Надписи кнопки «Открыть чат снова» (чат, закрытый инструментом, в песочнице). */\nexport interface ChatReopenLabels {\n reopen: string;\n reopening: string;\n failed: string;\n}\n\nexport const russianChatReopenLabels: ChatReopenLabels = {\n reopen: \"Открыть чат снова\",\n reopening: \"Открываем…\",\n failed: \"Чат не открылся\",\n};\n\nexport const englishChatReopenLabels: ChatReopenLabels = {\n reopen: \"Reopen chat\",\n reopening: \"Reopening…\",\n failed: \"The chat was not reopened\",\n};\n\n/** Надписи раздела «Чаты людей» и переключателя разделов песочницы. */\nexport interface PeopleChatsLabels {\n /** Подпись переключателя разделов. */\n sections: string;\n sandboxSection: string;\n peopleSection: string;\n /** Поле поиска, когда приложение дало поиск людей. */\n searchPerson: string;\n /** Поле id человека, когда поиска нет. */\n personId: string;\n search: string;\n openPerson: string;\n searching: string;\n searchFailed: string;\n nobodyFound: string;\n chatsLoading: string;\n chatsFailed: string;\n noChats: string;\n untitledChat: string;\n /** Отметка чата объекта (урок, заказ). */\n subjectChat: string;\n chooseChat: string;\n /** Отметка над лентой: чат человека открыт только на чтение. */\n readOnly: string;\n copy: string;\n copying: string;\n copyFailed: string;\n emptyChat: string;\n}\n\nexport const russianPeopleChatsLabels: PeopleChatsLabels = {\n sections: \"Разделы песочницы\",\n sandboxSection: \"Песочница\",\n peopleSection: \"Чаты людей\",\n searchPerson: \"Найти человека\",\n personId: \"Id человека\",\n search: \"Найти\",\n openPerson: \"Открыть\",\n searching: \"Ищем…\",\n searchFailed: \"Поиск не удался\",\n nobodyFound: \"Никого не нашли\",\n chatsLoading: \"Чаты загружаются…\",\n chatsFailed: \"Чаты не загрузились\",\n noChats: \"Чатов нет\",\n untitledChat: \"Без названия\",\n subjectChat: \"Чат объекта\",\n chooseChat: \"Выберите чат\",\n readOnly: \"Только чтение: чат человека не меняется\",\n copy: \"Проверить в копии\",\n copying: \"Копируем…\",\n copyFailed: \"Копия не создалась\",\n emptyChat: \"В чате нет сообщений\",\n};\n\nexport const englishPeopleChatsLabels: PeopleChatsLabels = {\n sections: \"Sandbox sections\",\n sandboxSection: \"Sandbox\",\n peopleSection: \"People's chats\",\n searchPerson: \"Find a person\",\n personId: \"Person id\",\n search: \"Search\",\n openPerson: \"Open\",\n searching: \"Searching…\",\n searchFailed: \"Search failed\",\n nobodyFound: \"Nobody found\",\n chatsLoading: \"Loading chats…\",\n chatsFailed: \"Chats failed to load\",\n noChats: \"No chats\",\n untitledChat: \"Untitled\",\n subjectChat: \"Object chat\",\n chooseChat: \"Choose a chat\",\n readOnly: \"Read only: the person's chat does not change\",\n copy: \"Check in a copy\",\n copying: \"Copying…\",\n copyFailed: \"Copy was not created\",\n emptyChat: \"The chat has no messages\",\n};\n\nexport const russianSandboxLabels: SandboxLabels = {\n title: \"Песочница\",\n chatPanel: \"Чат\",\n journalPanel: \"Журнал\",\n turnPicker: \"Выбор хода\",\n openJournal: \"Открыть журнал\",\n closeJournal: \"Закрыть журнал\",\n journalLoading: \"Журнал загружается…\",\n journalFailed: \"Журнал не загрузился\",\n journalEmpty: \"Записей пока нет\",\n retry: \"Повторить\",\n wholeChat: \"Весь чат\",\n turnLabel: (index) => `Ответ ${index}`,\n purposes: { answer: \"Ответ\", compaction: \"Сжатие истории\", memory: \"Память\", title: \"Название чата\", helper: \"Помощник\", other: \"Другое\" },\n modelCallStatus: { complete: \"Готово\", error: \"Ошибка\", aborted: \"Прервано\" },\n attempt: (attempt) => `Попытка ${attempt}`,\n durationMs: (ms) => `${(ms / 1000).toFixed(1)} с`,\n firstChunk: (ms) => `Первый кусок через ${(ms / 1000).toFixed(1)} с`,\n tokens: (input, cached, output) => `Вход ${input} (из кэша ${cached}) · выход ${output}`,\n costUnknown: \"Цена неизвестна\",\n instructionsChanged: \"Инструкция изменилась\",\n outputEdited: \"Результат изменён\",\n fromMessage: \"Из сообщения\",\n truncated: \"Обрезано\",\n instructions: \"Инструкция\",\n messages: \"Сообщения\",\n newMessage: \"Новое\",\n tools: \"Инструменты\",\n settings: \"Настройки\",\n response: \"Ответ\",\n reasoning: \"Рассуждение\",\n toolCalls: \"Вызовы инструментов\",\n finishReason: \"Причина остановки\",\n error: \"Ошибка\",\n toolInput: \"Вход\",\n toolOutput: \"Результат\",\n noOutput: \"Результата нет\",\n instructionEditor: \"Инструкция чата\",\n instructionText: \"Текст инструкции\",\n appInstructions: \"Боевая инструкция\",\n instructionsEdited: \"Инструкция изменена\",\n instructionsSave: \"Сохранить\",\n instructionsSaving: \"Сохраняем…\",\n instructionsReset: \"Вернуть боевую\",\n instructionsTooLong: (maxLength) => `Не длиннее ${maxLength} знаков`,\n instructionsSaveFailed: \"Инструкция не сохранилась\",\n answerEditor: \"Правка ответа\",\n answerText: \"Текст ответа\",\n answerTextEmpty: \"Текст ответа не может быть пустым\",\n toolOutputJson: (toolName) => `Результат ${toolName}`,\n toolOutputInvalidJson: \"Неверный JSON\",\n toolOutputTooLarge: \"Больше 64 КБ\",\n answerEditSave: \"Сохранить\",\n answerEditSaving: \"Сохраняем…\",\n answerEditCancel: \"Отмена\",\n answerEditFailed: \"Правка не сохранилась\",\n peopleChats: russianPeopleChatsLabels,\n chatReopen: russianChatReopenLabels,\n};\n\nexport const englishSandboxLabels: SandboxLabels = {\n title: \"Sandbox\",\n chatPanel: \"Chat\",\n journalPanel: \"Journal\",\n turnPicker: \"Choose turn\",\n openJournal: \"Open journal\",\n closeJournal: \"Close journal\",\n journalLoading: \"Loading journal…\",\n journalFailed: \"Journal failed to load\",\n journalEmpty: \"No entries yet\",\n retry: \"Retry\",\n wholeChat: \"Whole chat\",\n turnLabel: (index) => `Answer ${index}`,\n purposes: { answer: \"Answer\", compaction: \"History compaction\", memory: \"Memory\", title: \"Chat title\", helper: \"Helper\", other: \"Other\" },\n modelCallStatus: { complete: \"Done\", error: \"Error\", aborted: \"Aborted\" },\n attempt: (attempt) => `Attempt ${attempt}`,\n durationMs: (ms) => `${(ms / 1000).toFixed(1)} s`,\n firstChunk: (ms) => `First chunk after ${(ms / 1000).toFixed(1)} s`,\n tokens: (input, cached, output) => `Input ${input} (cached ${cached}) · output ${output}`,\n costUnknown: \"Cost unknown\",\n instructionsChanged: \"Instructions changed\",\n outputEdited: \"Output edited\",\n fromMessage: \"From message\",\n truncated: \"Truncated\",\n instructions: \"Instructions\",\n messages: \"Messages\",\n newMessage: \"New\",\n tools: \"Tools\",\n settings: \"Settings\",\n response: \"Response\",\n reasoning: \"Reasoning\",\n toolCalls: \"Tool calls\",\n finishReason: \"Finish reason\",\n error: \"Error\",\n toolInput: \"Input\",\n toolOutput: \"Output\",\n noOutput: \"No output\",\n instructionEditor: \"Chat instructions\",\n instructionText: \"Instructions text\",\n appInstructions: \"App instructions\",\n instructionsEdited: \"Instructions edited\",\n instructionsSave: \"Save\",\n instructionsSaving: \"Saving…\",\n instructionsReset: \"Restore app instructions\",\n instructionsTooLong: (maxLength) => `At most ${maxLength} characters`,\n instructionsSaveFailed: \"Instructions were not saved\",\n answerEditor: \"Edit answer\",\n answerText: \"Answer text\",\n answerTextEmpty: \"Answer text cannot be empty\",\n toolOutputJson: (toolName) => `${toolName} output`,\n toolOutputInvalidJson: \"Invalid JSON\",\n toolOutputTooLarge: \"Larger than 64 KB\",\n answerEditSave: \"Save\",\n answerEditSaving: \"Saving…\",\n answerEditCancel: \"Cancel\",\n answerEditFailed: \"Edit was not saved\",\n peopleChats: englishPeopleChatsLabels,\n chatReopen: englishChatReopenLabels,\n};\n\nconst SandboxLabelsContext = createContext<SandboxLabels>(russianSandboxLabels);\n\n/** Свой словарь песочницы; без провайдера — русский. */\nexport function SandboxLabelsProvider({ labels, children }: { labels: SandboxLabels; children?: ReactNode }) {\n return createElement(SandboxLabelsContext.Provider, { value: labels }, children);\n}\n\nexport function useSandboxLabels(): SandboxLabels {\n return useContext(SandboxLabelsContext);\n}\n\n/** Надписи раздела «Чаты людей»: из словаря песочницы, а в старом словаре без них — русские. */\nexport function usePeopleChatsLabels(): PeopleChatsLabels {\n return useSandboxLabels().peopleChats ?? russianPeopleChatsLabels;\n}\n\n/** Надписи кнопки «Открыть чат снова»: из словаря песочницы, а в старом словаре без них — русские. */\nexport function useChatReopenLabels(): ChatReopenLabels {\n return useSandboxLabels().chatReopen ?? russianChatReopenLabels;\n}\n","import { useState } from \"react\";\nimport type { SandboxClient } from \"./sandbox-client\";\nimport { useChatReopenLabels } from \"./sandbox-labels\";\nimport type { SandboxSkin } from \"./sandbox-skin\";\n\n/** «Открыть чат снова» у закрытого чата песочницы: запрос на сервер, а что показать, решает скин (или простая кнопка). */\nexport function ChatReopenControl({ skin, client, chatId, onReopened }: { skin: SandboxSkin; client: SandboxClient; chatId: string; onReopened: () => void }) {\n const labels = useChatReopenLabels();\n const [pending, setPending] = useState(false);\n const [failed, setFailed] = useState(false);\n\n const reopen = async () => {\n setPending(true);\n setFailed(false);\n try {\n await client.reopenChat(chatId);\n onReopened();\n } catch {\n setFailed(true);\n } finally {\n setPending(false);\n }\n };\n\n const error = failed ? labels.failed : undefined;\n if (skin.ChatReopen) return <skin.ChatReopen onReopen={() => void reopen()} pending={pending} {...(error && { error })} />;\n return (\n <div>\n <button type=\"button\" disabled={pending} onClick={() => void reopen()}>\n {pending ? labels.reopening : labels.reopen}\n </button>\n {error && <p role=\"alert\">{error}</p>}\n </div>\n );\n}\n","import { useEffect, useState } from \"react\";\nimport type { SandboxSettingsResponse } from \"../types\";\nimport type { SandboxClient } from \"./sandbox-client\";\nimport { useSandboxLabels } from \"./sandbox-labels\";\nimport type { SandboxSkin } from \"./sandbox-skin\";\n\n/** Предел длины инструкции песочного чата (как у сервера). */\nexport const MAX_INSTRUCTIONS_LENGTH = 100_000;\n\n/** Правка инструкции открытого песочного чата; боевая рядом для сравнения. */\nexport function InstructionHeader({ skin, client, chatId }: { skin: SandboxSkin; client: SandboxClient; chatId: string }) {\n const labels = useSandboxLabels();\n const [settings, setSettings] = useState<SandboxSettingsResponse | null>(null);\n const [value, setValue] = useState(\"\");\n const [open, setOpen] = useState(false);\n const [saving, setSaving] = useState(false);\n const [saveFailed, setSaveFailed] = useState(false);\n\n useEffect(() => {\n let current = true;\n setSettings(null);\n client.settings(chatId).then(\n (loaded) => {\n if (!current) return;\n setSettings(loaded);\n setValue(loaded.instructions ?? loaded.appInstructions);\n },\n () => undefined,\n );\n return () => {\n current = false;\n };\n }, [client, chatId]);\n\n if (!settings) return null;\n const saved = settings.instructions ?? settings.appInstructions;\n const dirty = value !== saved;\n const tooLong = value.length > MAX_INSTRUCTIONS_LENGTH;\n\n const save = async (instructions: string | null) => {\n setSaving(true);\n setSaveFailed(false);\n try {\n const next = await client.saveSettings(chatId, instructions);\n setSettings(next);\n setValue(next.instructions ?? next.appInstructions);\n } catch {\n setSaveFailed(true);\n } finally {\n setSaving(false);\n }\n };\n\n const error = tooLong ? labels.instructionsTooLong(MAX_INSTRUCTIONS_LENGTH) : saveFailed ? labels.instructionsSaveFailed : undefined;\n return (\n <skin.InstructionEditor\n value={value}\n onValueChange={setValue}\n appInstructions={settings.appInstructions}\n edited={settings.instructions !== null}\n dirty={dirty}\n onSave={() => void save(value)}\n canSave={dirty && !tooLong && !saving}\n saving={saving}\n {...(settings.instructions !== null && !saving && { onReset: () => void save(null) })}\n open={open}\n onOpenChange={setOpen}\n {...(error && { error })}\n maxLength={MAX_INSTRUCTIONS_LENGTH}\n />\n );\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { AgentChatMessage } from \"@ykurilov/agent-chat-react\";\nimport type { AnswerEditMark, JournalListResponse } from \"../types\";\nimport {\n formatCost,\n formatTime,\n journalRows,\n journalTurns,\n messageToolDetailsView,\n modelCallDetailsView,\n previousModelCall,\n toolCallDetailsView,\n} from \"./journal-view\";\nimport type { JournalRowSource } from \"./journal-view\";\nimport type { SandboxClient } from \"./sandbox-client\";\nimport { useSandboxLabels } from \"./sandbox-labels\";\nimport type { JournalRowView, ModelCallDetailsView, SandboxSkin, ToolCallDetailsView } from \"./sandbox-skin\";\n\ntype Entries = JournalListResponse[\"entries\"];\ntype Loaded<View> = { state: \"loading\" } | { state: \"failed\" } | { state: \"ready\"; view: View };\n\n/**\n * Журнал открытого чата: обращения к модели и вызовы инструментов хода или всего чата. Записи грузятся при открытии\n * чата и перечитываются после конца каждого хода; подробности строки — по нажатию, один раз.\n */\nexport function JournalPanel({\n skin,\n client,\n chatId,\n messages,\n busy,\n edits,\n}: {\n skin: SandboxSkin;\n client: SandboxClient;\n chatId: string;\n /** Сообщения текущей ветки. */\n messages: AgentChatMessage[];\n busy: boolean;\n edits: ReadonlyMap<string, AnswerEditMark>;\n}) {\n const labels = useSandboxLabels();\n const [load, setLoad] = useState<{ state: \"loading\" | \"ready\" | \"failed\"; entries: Entries }>({ state: \"loading\", entries: [] });\n const [turnId, setTurnId] = useState<string | null>(null);\n const [openIds, setOpenIds] = useState<ReadonlySet<string>>(new Set());\n const [details, setDetails] = useState<Record<string, Loaded<ModelCallDetailsView | ToolCallDetailsView>>>({});\n const request = useRef(0);\n\n const reload = useCallback(() => {\n const ticket = ++request.current;\n client.journal(chatId).then(\n (entries) => ticket === request.current && setLoad({ state: \"ready\", entries }),\n () => ticket === request.current && setLoad((current) => ({ ...current, state: \"failed\" })),\n );\n }, [client, chatId]);\n\n useEffect(() => {\n setLoad({ state: \"loading\", entries: [] });\n setTurnId(null);\n setOpenIds(new Set());\n setDetails({});\n reload();\n }, [reload]);\n\n // Ход закончился: журнал уже записал его обращения и вызовы.\n const wasBusy = useRef(busy);\n useEffect(() => {\n if (wasBusy.current && !busy) reload();\n wasBusy.current = busy;\n }, [busy, reload]);\n\n const turns = journalTurns(messages, labels);\n const currentTurnId = turns.some((turn) => turn.id === turnId) ? turnId : null;\n const sources = journalRows(load.entries, messages, edits, currentTurnId);\n\n const loadDetails = async (row: Extract<JournalRowSource, { kind: \"model\" | \"tool\" }>) => {\n setDetails((current) => ({ ...current, [row.id]: { state: \"loading\" } }));\n try {\n let view: ModelCallDetailsView | ToolCallDetailsView;\n if (row.kind === \"model\") {\n const previous = previousModelCall(load.entries, row.id);\n const [call, previousCall] = await Promise.all([client.call(row.id), previous ? client.call(previous.id) : null]);\n if (call.kind !== \"model\") throw new Error(\"journal call kind mismatch\");\n const previousHashes = previousCall?.kind === \"model\" ? new Set(previousCall.messages.map((blob) => blob.hash)) : null;\n view = modelCallDetailsView(call, previousHashes);\n } else {\n const call = await client.call(row.id);\n if (call.kind !== \"tool\") throw new Error(\"journal call kind mismatch\");\n view = toolCallDetailsView(call);\n }\n setDetails((current) => ({ ...current, [row.id]: { state: \"ready\", view } }));\n } catch {\n setDetails((current) => ({ ...current, [row.id]: { state: \"failed\" } }));\n }\n };\n\n const openChange = (row: JournalRowSource) => (open: boolean) => {\n setOpenIds((current) => {\n const next = new Set(current);\n if (open) next.add(row.id);\n else next.delete(row.id);\n return next;\n });\n if (open && row.kind !== \"message-tool\" && (details[row.id] === undefined || details[row.id]?.state === \"failed\")) void loadDetails(row);\n };\n\n const detailsOf = (row: JournalRowSource) => {\n if (row.kind === \"message-tool\") return <skin.ToolCallDetails {...messageToolDetailsView(row.part)} />;\n const loaded = details[row.id] ?? { state: \"loading\" };\n const view =\n loaded.state === \"ready\" ? loaded.view : loaded.state === \"failed\" ? { state: \"failed\" as const, onRetry: () => void loadDetails(row) } : { state: \"loading\" as const };\n return row.kind === \"model\" ? <skin.ModelCallDetails {...(view as ModelCallDetailsView)} /> : <skin.ToolCallDetails {...(view as ToolCallDetailsView)} />;\n };\n\n const rows = sources.map((row): JournalRowView => {\n const open = openIds.has(row.id);\n const common = { id: row.id, open, onOpenChange: openChange(row), ...(open && { details: detailsOf(row) }) };\n if (row.kind === \"model\") {\n const { entry } = row;\n const cost = formatCost(entry.costUsd);\n return {\n ...common,\n kind: \"model\",\n purpose: entry.purpose,\n attempt: entry.attempt,\n modelId: entry.modelId,\n status: entry.status,\n time: formatTime(entry.startedAt),\n durationMs: entry.durationMs,\n firstChunkMs: entry.firstChunkMs,\n inputTokens: entry.usage?.inputTokens ?? null,\n cachedInputTokens: entry.usage?.cachedInputTokens ?? null,\n outputTokens: entry.usage?.outputTokens ?? null,\n ...(cost !== undefined && { cost }),\n instructionsChanged: entry.instructionsChanged,\n };\n }\n const title = row.part?.title;\n if (row.kind === \"tool\") {\n return {\n ...common,\n kind: \"tool\",\n toolName: row.entry.toolName,\n ...(title !== undefined && { title }),\n source: \"server\",\n status: row.entry.error === null ? \"complete\" : \"error\",\n time: formatTime(row.entry.startedAt),\n durationMs: row.entry.durationMs,\n outputEdited: row.outputEdited,\n };\n }\n return {\n ...common,\n kind: \"tool\",\n toolName: row.part.toolName,\n ...(title !== undefined && { title }),\n source: \"message\",\n status: row.part.state === \"output-error\" ? \"error\" : \"complete\",\n time: \"\",\n durationMs: null,\n outputEdited: row.outputEdited,\n };\n });\n\n const state = load.state === \"ready\" && rows.length === 0 ? \"empty\" : load.state;\n return (\n <skin.JournalList\n state={state}\n rows={rows}\n {...(load.state === \"failed\" && { onRetry: reload })}\n turns={turns}\n currentTurnId={currentTurnId}\n onTurnChange={setTurnId}\n />\n );\n}\n","import type { AgentChatMessage } from \"@ykurilov/agent-chat-react\";\nimport type { AnswerEditMark, JournalBlob, JournalCallResponse, JournalListResponse, Serialized } from \"../types\";\nimport { toolCallOf } from \"./answer-edit-draft\";\nimport type { ToolCallPart } from \"./answer-edit-draft\";\nimport type { SandboxLabels } from \"./sandbox-labels\";\nimport type { JournalTurnView, ModelCallDetailsView, ToolCallDetailsView } from \"./sandbox-skin\";\n\ntype Entry = JournalListResponse[\"entries\"][number];\nexport type ModelEntry = Extract<Entry, { kind: \"model\" }>;\nexport type ToolEntry = Extract<Entry, { kind: \"tool\" }>;\ntype ModelCallResponse = Extract<JournalCallResponse, { kind: \"model\" }>;\ntype ToolCallResponse = Extract<JournalCallResponse, { kind: \"tool\" }>;\n\n/** Строка журнала до вида: запись сервера или вызов без `execute`, взятый из частей сообщения. */\nexport type JournalRowSource =\n | { kind: \"model\"; id: string; entry: ModelEntry }\n | { kind: \"tool\"; id: string; entry: ToolEntry; part: ToolCallPart | null; outputEdited: boolean }\n | { kind: \"message-tool\"; id: string; messageId: string; part: ToolCallPart; outputEdited: boolean };\n\n/** «Весь чат» и по ходу на каждый ответ агента текущей ветки. */\nexport function journalTurns(messages: AgentChatMessage[], labels: SandboxLabels): JournalTurnView[] {\n const answers = messages.filter((message) => message.role === \"assistant\");\n return [{ id: null, label: labels.wholeChat }, ...answers.map((message, index) => ({ id: message.id, label: labels.turnLabel(index + 1) }))];\n}\n\n/**\n * Строки хода (`turnId`) или всего чата (`null`) по времени. Вызовы, которых нет в журнале (виджеты, браузер,\n * подтверждения, ответы с правкой), берутся из частей сообщения и встают после последней записи своего ответа.\n */\nexport function journalRows(\n entries: Entry[],\n messages: AgentChatMessage[],\n edits: ReadonlyMap<string, AnswerEditMark>,\n turnId: string | null,\n): JournalRowSource[] {\n const answers = messages.filter((message) => message.role === \"assistant\" && (turnId === null || message.id === turnId));\n const shown = turnId === null ? entries : entries.filter((entry) => entry.messageId === turnId);\n const parts = new Map(answers.flatMap((message) => message.parts.flatMap((part) => toolCallOf(part) ?? [])).map((part) => [part.toolCallId, part]));\n const edited = (messageId: string, toolCallId: string) => edits.get(messageId)?.editedToolCallIds.includes(toolCallId) ?? false;\n const rows: JournalRowSource[] = shown.map((entry) =>\n entry.kind === \"model\"\n ? { kind: \"model\", id: entry.id, entry }\n : { kind: \"tool\", id: entry.id, entry, part: parts.get(entry.toolCallId) ?? null, outputEdited: edited(entry.messageId, entry.toolCallId) },\n );\n const logged = new Set(shown.flatMap((entry) => (entry.kind === \"tool\" ? [entry.toolCallId] : [])));\n for (const message of answers) {\n const fromMessage: JournalRowSource[] = message.parts.flatMap((part) => {\n const call = toolCallOf(part);\n if (!call || logged.has(call.toolCallId) || (call.state !== \"output-available\" && call.state !== \"output-error\")) return [];\n return [{ kind: \"message-tool\", id: `message:${call.toolCallId}`, messageId: message.id, part: call, outputEdited: edited(message.id, call.toolCallId) }];\n });\n if (fromMessage.length === 0) continue;\n const last = rows.findLastIndex((row) => (row.kind === \"message-tool\" ? row.messageId : row.entry.messageId) === message.id);\n rows.splice(last < 0 ? rows.length : last + 1, 0, ...fromMessage);\n }\n return rows;\n}\n\n/** Прошлое обращение того же назначения: относительно него подсвечиваются новые сообщения. */\nexport function previousModelCall(entries: Entry[], callId: string): ModelEntry | null {\n const index = entries.findIndex((entry) => entry.id === callId);\n const call = entries[index];\n if (!call || call.kind !== \"model\") return null;\n for (let i = index - 1; i >= 0; i--) {\n const entry = entries[i]!;\n if (entry.kind === \"model\" && entry.purpose === call.purpose) return entry;\n }\n return null;\n}\n\nexport function formatTime(iso: string): string {\n return new Date(iso).toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\", second: \"2-digit\", hour12: false });\n}\n\nexport function formatCost(costUsd: number | null): string | undefined {\n return costUsd === null ? undefined : `$${costUsd.toFixed(4)}`;\n}\n\n/** Значение как текст для чтения: строка как есть, остальное — отформатированный JSON. */\nexport function readable(value: unknown): string {\n return typeof value === \"string\" ? value : (JSON.stringify(value, null, 2) ?? \"\");\n}\n\n/** Подробности обращения; `previousHashes` — сообщения прошлого обращения (`null` — прошлого нет, все сообщения новые). */\nexport function modelCallDetailsView(details: ModelCallResponse, previousHashes: ReadonlySet<string> | null): Extract<ModelCallDetailsView, { state: \"ready\" }> {\n const response = (details.response.content ?? {}) as { text?: string; reasoning?: string; toolCalls?: Array<{ toolCallId: string; toolName: string; input: unknown }>; finishReason?: string | null };\n const tools = Array.isArray(details.tools.content) ? (details.tools.content as Array<{ name: string; description?: string; inputSchema: unknown }>) : [];\n return {\n state: \"ready\",\n instructions: readable(details.instructions.content),\n messages: details.messages.map((blob) => messageView(blob, previousHashes)),\n tools: tools.map((tool) => ({ name: tool.name, ...(tool.description !== undefined && { description: tool.description }), inputSchema: readable(tool.inputSchema) })),\n settings: readable(details.settings.content),\n response: {\n text: response.text ?? \"\",\n reasoning: response.reasoning ?? \"\",\n toolCalls: (response.toolCalls ?? []).map((call) => ({ toolCallId: call.toolCallId, toolName: call.toolName, input: readable(call.input) })),\n finishReason: response.finishReason ?? null,\n },\n error: details.call.error,\n };\n}\n\nfunction messageView(blob: Serialized<JournalBlob>, previousHashes: ReadonlySet<string> | null) {\n const message = blob.content as { role?: unknown; content?: unknown } | null;\n const role = typeof message?.role === \"string\" ? message.role : \"\";\n return {\n role,\n content: readable(message && \"content\" in message ? message.content : blob.content),\n isNew: previousHashes === null || !previousHashes.has(blob.hash),\n truncated: blob.truncated,\n };\n}\n\nexport function toolCallDetailsView(details: ToolCallResponse): Extract<ToolCallDetailsView, { state: \"ready\" }> {\n return {\n state: \"ready\",\n input: readable(details.input.content),\n output: details.output ? readable(details.output.content) : null,\n error: details.call.error,\n truncated: details.input.truncated || (details.output?.truncated ?? false),\n };\n}\n\n/** Вызов без записи в журнале: вход и результат из части сообщения. */\nexport function messageToolDetailsView(part: ToolCallPart): Extract<ToolCallDetailsView, { state: \"ready\" }> {\n return {\n state: \"ready\",\n input: readable(part.input),\n output: part.state === \"output-available\" ? readable(part.output) : null,\n error: part.state === \"output-error\" ? (part.errorText ?? null) : null,\n truncated: false,\n };\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { AgentChat } from \"@ykurilov/agent-chat-react\";\nimport type { AgentChatProps, ChatSkin } from \"@ykurilov/agent-chat-react\";\nimport type { Person } from \"../types\";\nimport { readOnlyChatSkin } from \"./read-only-chat-skin\";\nimport { SandboxRequestError } from \"./sandbox-client\";\nimport type { SandboxClient } from \"./sandbox-client\";\nimport { usePeopleChatsLabels, useSandboxLabels } from \"./sandbox-labels\";\nimport type { PeopleChatsView, SandboxLoadState, SandboxSkin } from \"./sandbox-skin\";\n\ntype ChatItems = Awaited<ReturnType<SandboxClient[\"personChats\"]>>;\n/** Что из возможностей чата нужно ленте на чтение: показ файлов, картинок, видео, схем и виджетов. */\nexport type ReadOnlyChatProps = Pick<AgentChatProps, \"attachments\" | \"widgets\" | \"diagrams\" | \"images\" | \"videos\" | \"liveness\">;\n\nexport interface PeopleChatsPanelProps {\n client: SandboxClient;\n skin: ChatSkin;\n sandboxSkin: SandboxSkin;\n chatProps: ReadOnlyChatProps;\n /** Копия создана: открыть её как песочный чат. */\n onCopied: (chatId: string) => void;\n}\n\n/**\n * Раздел «Чаты людей»: найти человека (или ввести его id, если приложение не дало поиск), открыть его чат только на\n * чтение и сделать копию в песочнице. Рисует `sandboxSkin.PeopleChats`, без него — простой вид.\n */\nexport function PeopleChatsPanel({ client, skin, sandboxSkin, chatProps, onCopied }: PeopleChatsPanelProps) {\n const labels = usePeopleChatsLabels();\n const [searchable, setSearchable] = useState(true);\n const [query, setQuery] = useState(\"\");\n const [peopleState, setPeopleState] = useState<PeopleChatsView[\"peopleState\"]>(\"idle\");\n const [people, setPeople] = useState<Person[]>([]);\n const [person, setPerson] = useState<Person | null>(null);\n const [chatsState, setChatsState] = useState<SandboxLoadState | \"empty\">(\"loading\");\n const [chats, setChats] = useState<ChatItems>([]);\n const [chatId, setChatId] = useState<string | null>(null);\n const [copying, setCopying] = useState(false);\n const [copyError, setCopyError] = useState<string | undefined>();\n const searchRequest = useRef(0);\n const chatsRequest = useRef(0);\n\n // Пустой запрос ничего не ищет, но говорит, дало ли приложение поиск.\n useEffect(() => {\n let current = true;\n client.searchPeople(\"\").then(\n (response) => current && setSearchable(response.search),\n () => undefined,\n );\n return () => {\n current = false;\n };\n }, [client]);\n\n const loadChats = useCallback(\n (personId: string) => {\n const request = ++chatsRequest.current;\n setChatsState(\"loading\");\n setChats([]);\n client.personChats(personId).then(\n (loaded) => {\n if (request !== chatsRequest.current) return;\n setChats(loaded);\n setChatsState(loaded.length > 0 ? \"ready\" : \"empty\");\n },\n () => request === chatsRequest.current && setChatsState(\"failed\"),\n );\n },\n [client],\n );\n\n const selectPerson = useCallback(\n (selected: Person) => {\n setPerson(selected);\n setChatId(null);\n setCopyError(undefined);\n loadChats(selected.id);\n },\n [loadChats],\n );\n\n const onSearch = useCallback(() => {\n const text = query.trim();\n if (text.length === 0) return;\n if (!searchable) {\n selectPerson({ id: text });\n return;\n }\n const request = ++searchRequest.current;\n setPeopleState(\"loading\");\n client.searchPeople(text).then(\n (response) => {\n if (request !== searchRequest.current) return;\n setPeople(response.people);\n setPeopleState(response.people.length > 0 ? \"ready\" : \"empty\");\n },\n () => request === searchRequest.current && setPeopleState(\"failed\"),\n );\n }, [client, query, searchable, selectPerson]);\n\n const onCopy = useCallback(() => {\n if (!person || chatId === null || copying) return;\n setCopying(true);\n setCopyError(undefined);\n client.copyPersonChat(person.id, chatId).then(\n (copyId) => {\n setCopying(false);\n onCopied(copyId);\n },\n (error: unknown) => {\n setCopying(false);\n setCopyError(error instanceof SandboxRequestError && error.kind === \"empty_chat\" ? labels.emptyChat : labels.copyFailed);\n },\n );\n }, [chatId, client, copying, labels, onCopied, person]);\n\n const chatSkin = useMemo(() => readOnlyChatSkin(skin), [skin]);\n const view: PeopleChatsView = {\n searchable,\n query,\n onQueryChange: setQuery,\n onSearch,\n peopleState,\n people: people.map((found) => ({ ...found, selected: found.id === person?.id, onSelect: () => selectPerson(found) })),\n ...(person && { person: { id: person.id, ...(person.name !== undefined && { name: person.name }) } }),\n chatsState,\n chats: chats.map((chat) => ({\n id: chat.id,\n title: chat.title,\n updatedAt: chat.updatedAt,\n subject: chat.subjectKey !== undefined,\n selected: chat.id === chatId,\n onOpen: () => {\n setChatId(chat.id);\n setCopyError(undefined);\n },\n })),\n onChatsRetry: () => person && loadChats(person.id),\n ...(person &&\n chatId !== null && {\n chat: <AgentChat key={`${person.id}:${chatId}`} {...chatProps} api={client.personChatApi(person.id)} chatId={chatId} skin={chatSkin} />,\n onCopy,\n }),\n copying,\n ...(copyError !== undefined && { copyError }),\n };\n const View = sandboxSkin.PeopleChats ?? PlainPeopleChats;\n return <View {...view} />;\n}\n\n/** Простой вид раздела для скина без `PeopleChats`. */\nfunction PlainPeopleChats(view: PeopleChatsView) {\n const labels = usePeopleChatsLabels();\n const retryLabel = useSandboxLabels().retry;\n const fieldLabel = view.searchable ? labels.searchPerson : labels.personId;\n return (\n <section aria-label={labels.peopleSection} data-people-chats=\"\">\n <form\n role=\"search\"\n onSubmit={(event) => {\n event.preventDefault();\n view.onSearch();\n }}\n >\n <input aria-label={fieldLabel} placeholder={fieldLabel} value={view.query} onChange={(event) => view.onQueryChange(event.target.value)} />\n <button type=\"submit\">{view.searchable ? labels.search : labels.openPerson}</button>\n </form>\n {view.peopleState === \"loading\" && <p>{labels.searching}</p>}\n {view.peopleState === \"failed\" && <p role=\"alert\">{labels.searchFailed}</p>}\n {view.peopleState === \"empty\" && <p>{labels.nobodyFound}</p>}\n {view.people.length > 0 && (\n <ul aria-label={labels.searchPerson}>\n {view.people.map((found) => (\n <li key={found.id}>\n <button type=\"button\" aria-pressed={found.selected} onClick={found.onSelect}>\n {found.name ?? found.id}\n </button>\n {found.hint && <span> {found.hint}</span>}\n </li>\n ))}\n </ul>\n )}\n {view.person && (\n <>\n <h2>{view.person.name ?? view.person.id}</h2>\n {view.chatsState === \"loading\" && <p>{labels.chatsLoading}</p>}\n {view.chatsState === \"failed\" && (\n <p role=\"alert\">\n {labels.chatsFailed}{\" \"}\n <button type=\"button\" onClick={view.onChatsRetry}>\n {retryLabel}\n </button>\n </p>\n )}\n {view.chatsState === \"empty\" && <p>{labels.noChats}</p>}\n <ul aria-label={labels.peopleSection}>\n {view.chats.map((chat) => (\n <li key={chat.id}>\n <button type=\"button\" aria-current={chat.selected || undefined} onClick={chat.onOpen}>\n {chat.title ?? labels.untitledChat}\n </button>\n {chat.subject && <span> · {labels.subjectChat}</span>}\n </li>\n ))}\n </ul>\n </>\n )}\n {view.chat ? (\n <div>\n <p>{labels.readOnly}</p>\n {view.onCopy && (\n <button type=\"button\" disabled={view.copying} onClick={view.onCopy}>\n {view.copying ? labels.copying : labels.copy}\n </button>\n )}\n {view.copyError && <p role=\"alert\">{view.copyError}</p>}\n {view.chat}\n </div>\n ) : (\n view.person && view.chatsState === \"ready\" && <p>{labels.chooseChat}</p>\n )}\n </section>\n );\n}\n","import type { ChatSkin, ToolApprovalView, WidgetView } from \"@ykurilov/agent-chat-react\";\n\nconst readOnlySkins = new WeakMap<ChatSkin, ChatSkin>();\n\n/**\n * Скин чата только на чтение (чат человека в песочнице): без поля ввода, без правки, повтора, нового ответа,\n * переключения веток и кнопок-подсказок. Решать за человека тоже нельзя: ждущие подтверждения без кнопок, задачи без\n * «Отменить», виджеты не нажимаются, свой виджет приложения — запасным видом. Рисуют те же компоненты скина. Обёртка\n * одна на скин: компоненты не пересоздаются при перерисовке.\n */\nexport function readOnlyChatSkin(skin: ChatSkin): ChatSkin {\n const known = readOnlySkins.get(skin);\n if (known) return known;\n const { SuggestedReplies: _suggestedReplies, ...rest } = skin;\n const { Widget, HelperCallRow, AgentNoticeView, TaskCard } = skin;\n const wrapped: ChatSkin = {\n ...rest,\n Layout: ({ composer: _composer, ...props }) => <skin.Layout {...props} composer={null} />,\n UserMessage: ({ onEdit: _onEdit, ...props }) => <skin.UserMessage {...props} />,\n AgentMessage: ({ onRetry: _onRetry, onRegenerate: _onRegenerate, onEdit: _onEdit, ...props }) => <skin.AgentMessage {...props} />,\n BranchSwitcher: ({ onPrevious: _onPrevious, onNext: _onNext, ...props }) => <skin.BranchSwitcher {...props} />,\n ToolCallRow: ({ approval, ...props }) => <skin.ToolCallRow {...props} {...decided(approval)} />,\n ...(HelperCallRow && { HelperCallRow: ({ approval, ...props }) => <HelperCallRow {...props} {...decided(approval)} /> }),\n ...(AgentNoticeView && { AgentNoticeView: ({ approval, ...props }) => <AgentNoticeView {...props} {...decided(approval)} /> }),\n ...(TaskCard && { TaskCard: ({ onCancel: _onCancel, ...props }) => <TaskCard {...props} /> }),\n ...(Widget && { Widget: (props) => <Widget {...inactiveWidget(props)} /> }),\n };\n readOnlySkins.set(skin, wrapped);\n return wrapped;\n}\n\n/** Итог решения остаётся пометкой; ждущее решение — без кнопок. */\nfunction decided(approval: ToolApprovalView | undefined): { approval?: ToolApprovalView } {\n return approval && approval.state !== \"requested\" ? { approval } : {};\n}\n\nfunction inactiveWidget(view: WidgetView): WidgetView {\n const base = { ...view, active: false };\n switch (base.kind) {\n case \"choice\": {\n const { onChoose: _onChoose, ...widget } = base;\n return widget;\n }\n case \"multi_choice\":\n case \"form\": {\n const { onSubmit: _onSubmit, ...widget } = base;\n return widget;\n }\n case \"rating\": {\n const { onRate: _onRate, ...widget } = base;\n return widget;\n }\n case \"custom\": {\n const { content: _content, ...widget } = base;\n return widget;\n }\n }\n}\n","import type {\n AnswerEditMark,\n AnswerEditRequest,\n AnswerEditResponse,\n JournalBlobResponse,\n JournalCallResponse,\n JournalListResponse,\n PeopleSearchResponse,\n PersonChatsResponse,\n SandboxHistoryExtras,\n SandboxSettingsRequest,\n SandboxSettingsResponse,\n StaffCopyResponse,\n} from \"../types\";\n\ntype Fetch = typeof globalThis.fetch;\n\nexport const DEFAULT_SANDBOX_BASE_PATH = \"/api/sandbox\";\n\n/** Сервер ответил отказом: `status` — код ответа, `kind` — вид ошибки из тела (`answer_busy`, `empty_text`…), если он есть. */\nexport class SandboxRequestError extends Error {\n constructor(\n readonly status: number,\n readonly kind: string | null,\n action: string,\n ) {\n super(`Sandbox request failed (${action}): HTTP ${status}${kind ? ` ${kind}` : \"\"}`);\n this.name = \"SandboxRequestError\";\n }\n}\n\n/** Запросы экрана песочницы к адресам из plan.md, «Адреса песочницы». */\nexport interface SandboxClient {\n /** Обращения и вызовы всего чата; с `messageId` — одного хода. */\n journal(chatId: string, messageId?: string): Promise<JournalListResponse[\"entries\"]>;\n call(callId: string): Promise<JournalCallResponse>;\n blob(hash: string): Promise<JournalBlobResponse>;\n settings(chatId: string): Promise<SandboxSettingsResponse>;\n /** `null` — вернуть боевую инструкцию. */\n saveSettings(chatId: string, instructions: string | null): Promise<SandboxSettingsResponse>;\n editAnswer(chatId: string, messageId: string, edit: AnswerEditRequest): Promise<AnswerEditResponse>;\n /** Отметки правок по id ответа — из дополнения к истории чата. */\n answerEdits(chatId: string): Promise<Map<string, AnswerEditMark>>;\n /** Поиск человека; `search: false` — приложение поиск не дало, id вводится вручную. */\n searchPeople(query: string): Promise<PeopleSearchResponse>;\n personChats(personId: string): Promise<PersonChatsResponse[\"chats\"]>;\n /** Копия текущей ветки чата человека в песочнице; ответ — id песочного чата. */\n copyPersonChat(personId: string, chatId: string): Promise<string>;\n /** Открывает закрытый инструментом чат снова: ходы идут дальше (`POST …/chats/{id}/reopen`, 204). */\n reopenChat(chatId: string): Promise<void>;\n /** Адрес чатов человека только на чтение: его отдают `AgentChat` как `api`. */\n personChatApi(personId: string): string;\n}\n\nexport function createSandboxClient({ basePath = DEFAULT_SANDBOX_BASE_PATH, fetch: fetchImpl = (...args) => globalThis.fetch(...args) }: { basePath?: string; fetch?: Fetch } = {}): SandboxClient {\n const base = basePath.replace(/\\/+$/, \"\");\n const segment = encodeURIComponent;\n const chat = (chatId: string) => `${base}/chats/${segment(chatId)}`;\n\n async function json<T>(action: string, url: string, init?: RequestInit): Promise<T> {\n const response = await fetchImpl(url, init);\n if (!response.ok) throw new SandboxRequestError(response.status, await errorKindOf(response), action);\n return (await response.json()) as T;\n }\n const send = (method: string, body: unknown): RequestInit => ({\n method,\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n return {\n journal: async (chatId, messageId) =>\n (\n await json<JournalListResponse>(\n \"journal\",\n `${chat(chatId)}/journal${messageId === undefined ? \"\" : `?messageId=${segment(messageId)}`}`,\n )\n ).entries,\n call: (callId) => json(\"call\", `${base}/journal/calls/${segment(callId)}`),\n blob: (hash) => json(\"blob\", `${base}/journal/blobs/${segment(hash)}`),\n settings: (chatId) => json(\"settings\", `${chat(chatId)}/settings`),\n saveSettings: (chatId, instructions) =>\n json(\"save-settings\", `${chat(chatId)}/settings`, send(\"PUT\", { instructions } satisfies SandboxSettingsRequest)),\n editAnswer: (chatId, messageId, edit) =>\n json(\"answer-edit\", `${chat(chatId)}/messages/${segment(messageId)}/answer-edit`, send(\"POST\", edit)),\n answerEdits: async (chatId) => {\n const history = await json<Partial<SandboxHistoryExtras>>(\"history\", `${base}/${segment(chatId)}`);\n return new Map(Object.entries(history.answerEdits ?? {}));\n },\n searchPeople: (query) => json(\"people\", `${base}/people?query=${segment(query)}`),\n personChats: async (personId) => (await json<PersonChatsResponse>(\"person-chats\", `${base}/people/${segment(personId)}/chats`)).chats,\n copyPersonChat: async (personId, chatId) =>\n (await json<StaffCopyResponse>(\"staff-copy\", `${base}/people/${segment(personId)}/${segment(chatId)}/staff-copy`, { method: \"POST\" })).id,\n reopenChat: async (chatId) => {\n const response = await fetchImpl(`${chat(chatId)}/reopen`, { method: \"POST\" });\n if (!response.ok) throw new SandboxRequestError(response.status, await errorKindOf(response), \"reopen\");\n },\n personChatApi: (personId) => `${base}/people/${segment(personId)}`,\n };\n}\n\nasync function errorKindOf(response: Response): Promise<string | null> {\n try {\n const body = (await response.json()) as { error?: { kind?: unknown } };\n return typeof body.error?.kind === \"string\" ? body.error.kind : null;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["useCallback","useEffect","useMemo","useRef","useState","useState","jsx","useState","useState","jsx","useState","useEffect","useState","jsx","useState","useEffect","useCallback","useEffect","useRef","useState","jsx","jsx","jsxs","useState","useRef","useEffect","useCallback","Fragment","jsx","jsxs","useMemo","useState","useCallback","useRef","useEffect"]}
1
+ {"version":3,"sources":["../src/react/agent-sandbox.tsx","../src/react/answer-editor-host.tsx","../src/react/answer-edit-draft.ts","../src/react/sandbox-labels.ts","../src/react/chat-reopen.tsx","../src/react/instruction-header.tsx","../src/react/journal-panel.tsx","../src/react/journal-view.ts","../src/react/people-chats-panel.tsx","../src/react/read-only-chat-skin.tsx","../src/react/sandbox-client.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { ChatWorkspace } from \"@ykurilov/agent-chat-react\";\nimport type { AgentChatMessage, AgentChatProps, ChatAccess, ChatListSkin, ChatSkin, ChatWorkspaceProps } from \"@ykurilov/agent-chat-react\";\nimport type { AnswerEditMark } from \"../types\";\nimport { AnswerEditorHost } from \"./answer-editor-host\";\nimport { ChatReopenControl } from \"./chat-reopen\";\nimport { InstructionHeader } from \"./instruction-header\";\nimport { useJournalPanel } from \"./journal-panel\";\nimport { PeopleChatsPanel } from \"./people-chats-panel\";\nimport type { ReadOnlyChatProps } from \"./people-chats-panel\";\nimport { createSandboxClient, DEFAULT_SANDBOX_BASE_PATH } from \"./sandbox-client\";\nimport { usePeopleChatsLabels } from \"./sandbox-labels\";\nimport type { SandboxSection, SandboxSkin } from \"./sandbox-skin\";\n\ntype AnswerEditing = NonNullable<AgentChatProps[\"answerEditing\"]>;\ntype Feed = { messages: AgentChatMessage[]; busy: boolean };\n\nexport interface AgentSandboxProps {\n /** Адрес обработчика песочницы; по умолчанию `/api/sandbox`. */\n basePath?: string;\n skin: ChatSkin;\n sandboxSkin: SandboxSkin;\n chatListSkin: ChatListSkin;\n /**\n * Те же возможности чата, что у боевой страницы: вложения, картинки, видео, схемы, виджеты, голос. Без них агент\n * в песочнице отвечает, но файлы и картинки в ленте не показываются. Остальным экран песочницы управляет сам.\n */\n chatProps?: Omit<ChatWorkspaceProps, \"api\" | \"skin\" | \"initialChatId\" | \"onChatChange\" | \"onFeedChange\" | \"answerEditing\" | \"reloadKey\">;\n /**\n * Раздел «Чаты людей» и переключатель разделов. Ставить, только если у обработчика песочницы есть опция `people`;\n * нет — экран песочницы прежний, без полосы разделов.\n */\n people?: boolean;\n}\n\n/**\n * Песочница для админа: песочные чаты списком, чат с правкой ответов, инструкция чата и журнал обращений к модели\n * и вызовов инструментов рядом. С `people` второй раздел — «Чаты людей»: чат человека на чтение и «Проверить в копии»; копия\n * открывается здесь же как обычный песочный чат. Всё видимое рисуют скины.\n */\nexport function AgentSandbox({ basePath = DEFAULT_SANDBOX_BASE_PATH, skin, sandboxSkin, chatListSkin, chatProps, people = false }: AgentSandboxProps) {\n const client = useMemo(() => createSandboxClient({ basePath }), [basePath]);\n const workspaceSkin = useMemo(() => ({ ...skin, ...chatListSkin }), [skin, chatListSkin]);\n const [chatId, setChatId] = useState<string | null>(null);\n const [feed, setFeed] = useState<Feed>({ messages: [], busy: false });\n const [edits, setEdits] = useState<ReadonlyMap<string, AnswerEditMark>>(new Map());\n const [journalOpen, setJournalOpen] = useState(false);\n /** Открытый песочный чат закрыт инструментом: админу нужна кнопка «Открыть чат снова». */\n const [chatClosed, setChatClosed] = useState(false);\n /** Растёт после «Открыть чат снова»: экран чата перечитывает историю и снова принимает ходы. */\n const [reloadKey, setReloadKey] = useState(0);\n const passedAccessChange = chatProps?.onChatAccessChange;\n const onChatAccessChange = useCallback(\n (access: ChatAccess) => {\n setChatClosed(access.closed !== null);\n passedAccessChange?.(access);\n },\n [passedAccessChange],\n );\n const onChatReopened = useCallback(() => {\n setChatClosed(false);\n setReloadKey((key) => key + 1);\n }, []);\n const [section, setSection] = useState<SandboxSection>(\"sandbox\");\n /** Только что созданная копия чата человека: песочный раздел открывается на ней, а не на запомненном чате. */\n const [openedCopyId, setOpenedCopyId] = useState<string | null>(null);\n const changeSection = useCallback((next: SandboxSection) => {\n setOpenedCopyId(null);\n setSection(next);\n }, []);\n const readOnlyChatProps = useMemo<ReadOnlyChatProps>(() => readOnlyPropsOf(chatProps), [chatProps]);\n const onCopied = useCallback((copyId: string) => {\n setOpenedCopyId(copyId);\n setSection(\"sandbox\");\n }, []);\n\n /** Права нового чата приходят с его историей; пока их нет, закрытость прежнего чата не переносится. Первый чат не в счёт: его права могли прийти раньше. */\n const previousChatId = useRef<string | null>(null);\n useEffect(() => {\n if (previousChatId.current !== null && previousChatId.current !== chatId) setChatClosed(false);\n previousChatId.current = chatId;\n }, [chatId]);\n\n useEffect(() => {\n setEdits(new Map());\n if (chatId === null) return;\n let current = true;\n client.answerEdits(chatId).then(\n (loaded) => current && setEdits(loaded),\n () => undefined,\n );\n return () => {\n current = false;\n };\n }, [client, chatId]);\n\n // Пока идёт ответ, куски потока журналу не нужны: экран не перерисовывается на каждый кусок.\n const onFeedChange = useCallback((next: Feed) => setFeed((current) => (current.busy && next.busy ? current : next)), []);\n\n const answerEditing = useMemo<AnswerEditing | undefined>(\n () =>\n chatId === null\n ? undefined\n : {\n edits,\n renderEditor: ({ message, onSaved, onCancel }) => (\n <AnswerEditorHost\n key={message.id}\n skin={sandboxSkin}\n client={client}\n chatId={chatId}\n message={message}\n onSaved={(newMessageId, mark) => {\n setEdits((current) => new Map(current).set(newMessageId, mark));\n onSaved(newMessageId);\n }}\n onCancel={onCancel}\n />\n ),\n },\n [chatId, client, edits, sandboxSkin],\n );\n\n // State belongs above the layout: a responsive inspector may remount its renderer.\n const journal = useJournalPanel({ skin: sandboxSkin, client, chatId, messages: feed.messages, busy: feed.busy, edits });\n const Sections = sandboxSkin.SandboxSections ?? PlainSandboxSections;\n const sandboxView = (\n <sandboxSkin.SandboxLayout\n busy={feed.busy}\n header={\n chatId !== null && (\n <>\n <InstructionHeader key={chatId} skin={sandboxSkin} client={client} chatId={chatId} />\n {chatClosed && <ChatReopenControl key={chatId} skin={sandboxSkin} client={client} chatId={chatId} onReopened={onChatReopened} />}\n </>\n )\n }\n chat={\n <ChatWorkspace\n {...chatProps}\n {...(openedCopyId !== null && { initialChatId: openedCopyId })}\n api={basePath}\n skin={workspaceSkin}\n onChatChange={setChatId}\n onFeedChange={onFeedChange}\n onChatAccessChange={onChatAccessChange}\n reloadKey={reloadKey}\n {...(answerEditing && { answerEditing })}\n />\n }\n journal={\n chatId !== null && journal\n }\n journalOpen={journalOpen}\n onJournalOpenChange={setJournalOpen}\n />\n );\n if (!people) return sandboxView;\n return (\n <Sections section={section} onSectionChange={changeSection}>\n {section === \"people\" ? (\n <PeopleChatsPanel client={client} skin={skin} sandboxSkin={sandboxSkin} chatProps={readOnlyChatProps} onCopied={onCopied} />\n ) : (\n sandboxView\n )}\n </Sections>\n );\n}\n\nfunction readOnlyPropsOf(chatProps: AgentSandboxProps[\"chatProps\"]): ReadOnlyChatProps {\n if (!chatProps) return {};\n const { attachments, widgets, diagrams, images, videos, liveness } = chatProps;\n return {\n ...(attachments && { attachments }),\n ...(widgets && { widgets }),\n ...(diagrams && { diagrams }),\n ...(images && { images }),\n ...(videos && { videos }),\n ...(liveness && { liveness }),\n };\n}\n\n/** Простой переключатель разделов для скина без `SandboxSections`. */\nfunction PlainSandboxSections({ section, onSectionChange, children }: { section: SandboxSection; onSectionChange: (section: SandboxSection) => void; children: ReactNode }) {\n const labels = usePeopleChatsLabels();\n return (\n <>\n <nav aria-label={labels.sections}>\n <button type=\"button\" aria-pressed={section === \"sandbox\"} onClick={() => onSectionChange(\"sandbox\")}>\n {labels.sandboxSection}\n </button>\n <button type=\"button\" aria-pressed={section === \"people\"} onClick={() => onSectionChange(\"people\")}>\n {labels.peopleSection}\n </button>\n </nav>\n {children}\n </>\n );\n}\n","import { useState } from \"react\";\nimport type { AgentChatMessage } from \"@ykurilov/agent-chat-react\";\nimport type { AnswerEditMark } from \"../types\";\nimport { answerEditDraft, answerEditRequest, checkAnswerEditDraft } from \"./answer-edit-draft\";\nimport type { AnswerEditDraft } from \"./answer-edit-draft\";\nimport type { SandboxClient } from \"./sandbox-client\";\nimport { useSandboxLabels } from \"./sandbox-labels\";\nimport type { SandboxSkin } from \"./sandbox-skin\";\n\n/** Редактор ответа на месте ответа: черновик текста и JSON результатов, проверка, отправка только изменённого. */\nexport function AnswerEditorHost({\n skin,\n client,\n chatId,\n message,\n onSaved,\n onCancel,\n}: {\n skin: SandboxSkin;\n client: SandboxClient;\n chatId: string;\n message: AgentChatMessage;\n onSaved: (newMessageId: string, mark: AnswerEditMark) => void;\n onCancel: () => void;\n}) {\n const labels = useSandboxLabels();\n const [draft, setDraft] = useState<AnswerEditDraft>(() => answerEditDraft(message));\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | undefined>();\n const check = checkAnswerEditDraft(draft);\n\n const save = async () => {\n if (!check.canSave || saving) return;\n setSaving(true);\n setError(undefined);\n try {\n const saved = await client.editAnswer(chatId, message.id, answerEditRequest(draft));\n onSaved(saved.messageId, saved.mark);\n } catch {\n setError(labels.answerEditFailed);\n setSaving(false);\n }\n };\n\n return (\n <skin.AnswerEditor\n text={draft.text}\n onTextChange={(text) => setDraft((current) => ({ ...current, text }))}\n {...(check.textError && { textError: labels.answerTextEmpty })}\n toolOutputs={draft.toolOutputs.map((output) => {\n const outputError = check.outputErrors[output.toolCallId];\n return {\n toolCallId: output.toolCallId,\n toolName: output.toolName,\n ...(output.title !== undefined && { title: output.title }),\n value: output.value,\n onValueChange: (value: string) =>\n setDraft((current) => ({\n ...current,\n toolOutputs: current.toolOutputs.map((item) => (item.toolCallId === output.toolCallId ? { ...item, value } : item)),\n })),\n ...(outputError && { error: outputError === \"invalid\" ? labels.toolOutputInvalidJson : labels.toolOutputTooLarge }),\n edited: output.value !== output.original,\n };\n })}\n onSave={() => void save()}\n onCancel={onCancel}\n canSave={check.canSave && !saving}\n saving={saving}\n {...(error && { error })}\n />\n );\n}\n","import type { AgentChatMessage } from \"@ykurilov/agent-chat-react\";\nimport type { AnswerEditRequest } from \"../types\";\n\n/** Предел нового результата инструмента (FR-014a). */\nexport const MAX_TOOL_OUTPUT_BYTES = 64 * 1024;\n\n/** Вызовы, результат которых не правится: картинки, видео, виджеты и подсказки — у них свой вид, а не JSON. */\nconst NOT_EDITABLE_TOOLS = new Set([\"generate_images\", \"show_video\", \"ask_choice\", \"ask_multi_choice\", \"ask_rating\", \"ask_form\", \"suggest_replies\"]);\n\ntype MessagePart = AgentChatMessage[\"parts\"][number];\n\n/** Часть-вызов инструмента: `tool-<имя>` или `dynamic-tool` (как `isToolUIPart` и `getToolName` из `ai`; пакет от `ai` не зависит). */\nexport interface ToolCallPart {\n toolCallId: string;\n toolName: string;\n state: string;\n input?: unknown;\n output?: unknown;\n errorText?: string;\n title?: string;\n}\n\nexport function toolCallOf(part: MessagePart): ToolCallPart | null {\n const record = part as { type: string; toolName?: unknown; toolCallId?: unknown };\n if (typeof record.toolCallId !== \"string\") return null;\n if (record.type === \"dynamic-tool\" && typeof record.toolName === \"string\") return part as unknown as ToolCallPart;\n if (record.type.startsWith(\"tool-\")) return { ...(part as unknown as ToolCallPart), toolName: record.type.slice(\"tool-\".length) };\n return null;\n}\n\nexport interface ToolOutputDraft {\n toolCallId: string;\n toolName: string;\n title?: string;\n /** Исходный результат JSON-текстом; у вызова с ошибкой — пусто. */\n original: string;\n value: string;\n}\n\nexport interface AnswerEditDraft {\n originalText: string;\n text: string;\n toolOutputs: ToolOutputDraft[];\n}\n\nexport type ToolOutputError = \"invalid\" | \"too_large\";\n\nexport interface AnswerEditCheck {\n textError: \"empty\" | undefined;\n outputErrors: Record<string, ToolOutputError>;\n /** Текст не пуст, все правленые JSON верны и хоть что-то изменилось. */\n canSave: boolean;\n}\n\n/** Черновик из ответа: текст частей через пустую строку и результаты обычных вызовов, у которых результат уже есть. */\nexport function answerEditDraft(message: AgentChatMessage): AnswerEditDraft {\n const text = message.parts.flatMap((part) => (part.type === \"text\" ? [part.text] : [])).join(\"\\n\\n\");\n const toolOutputs = message.parts.flatMap((part): ToolOutputDraft[] => {\n const call = toolCallOf(part);\n if (!call || (call.state !== \"output-available\" && call.state !== \"output-error\") || NOT_EDITABLE_TOOLS.has(call.toolName)) return [];\n const original = call.state === \"output-available\" ? (JSON.stringify(call.output, null, 2) ?? \"\") : \"\";\n const title = typeof call.title === \"string\" ? call.title : undefined;\n return [{ toolCallId: call.toolCallId, toolName: call.toolName, ...(title !== undefined && { title }), original, value: original }];\n });\n return { originalText: text, text, toolOutputs };\n}\n\nconst isEdited = (output: ToolOutputDraft) => output.value !== output.original;\n\nfunction toolOutputError(value: string): ToolOutputError | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch {\n return \"invalid\";\n }\n return new TextEncoder().encode(JSON.stringify(parsed)).length > MAX_TOOL_OUTPUT_BYTES ? \"too_large\" : undefined;\n}\n\nexport function checkAnswerEditDraft(draft: AnswerEditDraft): AnswerEditCheck {\n const textError = draft.text.trim().length === 0 ? \"empty\" : undefined;\n const outputErrors: Record<string, ToolOutputError> = {};\n for (const output of draft.toolOutputs.filter(isEdited)) {\n const error = toolOutputError(output.value);\n if (error) outputErrors[output.toolCallId] = error;\n }\n const changed = draft.text !== draft.originalText || draft.toolOutputs.some(isEdited);\n return { textError, outputErrors, canSave: !textError && Object.keys(outputErrors).length === 0 && changed };\n}\n\n/** Тело запроса: текст и только изменённые результаты. Черновик должен пройти `checkAnswerEditDraft`. */\nexport function answerEditRequest(draft: AnswerEditDraft): AnswerEditRequest {\n const edited = draft.toolOutputs.filter(isEdited);\n if (edited.length === 0) return { text: draft.text };\n return { text: draft.text, toolOutputs: Object.fromEntries(edited.map((output) => [output.toolCallId, JSON.parse(output.value) as unknown])) };\n}\n","import { createContext, createElement, useContext } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { ModelCallPurpose } from \"@ykurilov/agent-chat-core\";\n\n/** Надписи песочницы. Надписи чата внутри песочницы — обычный `ChatLabels`. */\nexport interface SandboxLabels {\n title: string;\n chatPanel: string;\n journalPanel: string;\n /** Подпись выпадающего списка ходов внутри журнала. */\n turnPicker: string;\n openJournal: string;\n closeJournal: string;\n journalLoading: string;\n journalFailed: string;\n journalEmpty: string;\n retry: string;\n wholeChat: string;\n /** Подпись хода в выборе над журналом: `index` считается с 1. */\n turnLabel: (index: number) => string;\n purposes: Record<ModelCallPurpose, string>;\n modelCallStatus: Record<\"complete\" | \"error\" | \"aborted\", string>;\n attempt: (attempt: number) => string;\n durationMs: (ms: number) => string;\n firstChunk: (ms: number) => string;\n tokens: (input: number, cached: number, output: number) => string;\n costUnknown: string;\n instructionsChanged: string;\n outputEdited: string;\n fromMessage: string;\n truncated: string;\n instructions: string;\n messages: string;\n newMessage: string;\n tools: string;\n settings: string;\n response: string;\n reasoning: string;\n toolCalls: string;\n finishReason: string;\n error: string;\n toolInput: string;\n toolOutput: string;\n noOutput: string;\n instructionEditor: string;\n /** Подпись самого поля текста инструкции (кнопка раскрытия подписана `instructionEditor`). */\n instructionText: string;\n appInstructions: string;\n instructionsEdited: string;\n instructionsSave: string;\n instructionsSaving: string;\n instructionsReset: string;\n instructionsTooLong: (maxLength: number) => string;\n instructionsSaveFailed: string;\n answerEditor: string;\n answerText: string;\n answerTextEmpty: string;\n toolOutputJson: (toolName: string) => string;\n toolOutputInvalidJson: string;\n toolOutputTooLarge: string;\n answerEditSave: string;\n answerEditSaving: string;\n answerEditCancel: string;\n answerEditFailed: string;\n /** Раздел «Чаты людей»; нет в словаре — русские надписи `russianPeopleChatsLabels`. */\n peopleChats?: PeopleChatsLabels;\n /** Кнопка «Открыть чат снова»; нет в словаре — русские надписи `russianChatReopenLabels`. */\n chatReopen?: ChatReopenLabels;\n}\n\n/** Надписи кнопки «Открыть чат снова» (чат, закрытый инструментом, в песочнице). */\nexport interface ChatReopenLabels {\n reopen: string;\n reopening: string;\n failed: string;\n}\n\nexport const russianChatReopenLabels: ChatReopenLabels = {\n reopen: \"Открыть чат снова\",\n reopening: \"Открываем…\",\n failed: \"Чат не открылся\",\n};\n\nexport const englishChatReopenLabels: ChatReopenLabels = {\n reopen: \"Reopen chat\",\n reopening: \"Reopening…\",\n failed: \"The chat was not reopened\",\n};\n\n/** Надписи раздела «Чаты людей» и переключателя разделов песочницы. */\nexport interface PeopleChatsLabels {\n /** Подпись переключателя разделов. */\n sections: string;\n sandboxSection: string;\n peopleSection: string;\n /** Поле поиска, когда приложение дало поиск людей. */\n searchPerson: string;\n /** Поле id человека, когда поиска нет. */\n personId: string;\n search: string;\n openPerson: string;\n searching: string;\n searchFailed: string;\n nobodyFound: string;\n chatsLoading: string;\n chatsFailed: string;\n noChats: string;\n untitledChat: string;\n /** Отметка чата объекта (урок, заказ). */\n subjectChat: string;\n chooseChat: string;\n /** Отметка над лентой: чат человека открыт только на чтение. */\n readOnly: string;\n copy: string;\n copying: string;\n copyFailed: string;\n emptyChat: string;\n}\n\nexport const russianPeopleChatsLabels: PeopleChatsLabels = {\n sections: \"Разделы песочницы\",\n sandboxSection: \"Песочница\",\n peopleSection: \"Чаты людей\",\n searchPerson: \"Найти человека\",\n personId: \"Id человека\",\n search: \"Найти\",\n openPerson: \"Открыть\",\n searching: \"Ищем…\",\n searchFailed: \"Поиск не удался\",\n nobodyFound: \"Никого не нашли\",\n chatsLoading: \"Чаты загружаются…\",\n chatsFailed: \"Чаты не загрузились\",\n noChats: \"Чатов нет\",\n untitledChat: \"Без названия\",\n subjectChat: \"Чат объекта\",\n chooseChat: \"Выберите чат\",\n readOnly: \"Только чтение: чат человека не меняется\",\n copy: \"Проверить в копии\",\n copying: \"Копируем…\",\n copyFailed: \"Копия не создалась\",\n emptyChat: \"В чате нет сообщений\",\n};\n\nexport const englishPeopleChatsLabels: PeopleChatsLabels = {\n sections: \"Sandbox sections\",\n sandboxSection: \"Sandbox\",\n peopleSection: \"People's chats\",\n searchPerson: \"Find a person\",\n personId: \"Person id\",\n search: \"Search\",\n openPerson: \"Open\",\n searching: \"Searching…\",\n searchFailed: \"Search failed\",\n nobodyFound: \"Nobody found\",\n chatsLoading: \"Loading chats…\",\n chatsFailed: \"Chats failed to load\",\n noChats: \"No chats\",\n untitledChat: \"Untitled\",\n subjectChat: \"Object chat\",\n chooseChat: \"Choose a chat\",\n readOnly: \"Read only: the person's chat does not change\",\n copy: \"Check in a copy\",\n copying: \"Copying…\",\n copyFailed: \"Copy was not created\",\n emptyChat: \"The chat has no messages\",\n};\n\nexport const russianSandboxLabels: SandboxLabels = {\n title: \"Песочница\",\n chatPanel: \"Чат\",\n journalPanel: \"Журнал\",\n turnPicker: \"Выбор хода\",\n openJournal: \"Открыть журнал\",\n closeJournal: \"Закрыть журнал\",\n journalLoading: \"Журнал загружается…\",\n journalFailed: \"Журнал не загрузился\",\n journalEmpty: \"Записей пока нет\",\n retry: \"Повторить\",\n wholeChat: \"Весь чат\",\n turnLabel: (index) => `Ответ ${index}`,\n purposes: { answer: \"Ответ\", compaction: \"Сжатие истории\", memory: \"Память\", title: \"Название чата\", helper: \"Помощник\", other: \"Другое\" },\n modelCallStatus: { complete: \"Готово\", error: \"Ошибка\", aborted: \"Прервано\" },\n attempt: (attempt) => `Попытка ${attempt}`,\n durationMs: (ms) => `${(ms / 1000).toFixed(1)} с`,\n firstChunk: (ms) => `Первый кусок через ${(ms / 1000).toFixed(1)} с`,\n tokens: (input, cached, output) => `Вход ${input} (из кэша ${cached}) · выход ${output}`,\n costUnknown: \"Цена неизвестна\",\n instructionsChanged: \"Инструкция изменилась\",\n outputEdited: \"Результат изменён\",\n fromMessage: \"Из сообщения\",\n truncated: \"Обрезано\",\n instructions: \"Инструкция\",\n messages: \"Сообщения\",\n newMessage: \"Новое\",\n tools: \"Инструменты\",\n settings: \"Настройки\",\n response: \"Ответ\",\n reasoning: \"Рассуждение\",\n toolCalls: \"Вызовы инструментов\",\n finishReason: \"Причина остановки\",\n error: \"Ошибка\",\n toolInput: \"Вход\",\n toolOutput: \"Результат\",\n noOutput: \"Результата нет\",\n instructionEditor: \"Инструкция чата\",\n instructionText: \"Текст инструкции\",\n appInstructions: \"Боевая инструкция\",\n instructionsEdited: \"Инструкция изменена\",\n instructionsSave: \"Сохранить\",\n instructionsSaving: \"Сохраняем…\",\n instructionsReset: \"Вернуть боевую\",\n instructionsTooLong: (maxLength) => `Не длиннее ${maxLength} знаков`,\n instructionsSaveFailed: \"Инструкция не сохранилась\",\n answerEditor: \"Правка ответа\",\n answerText: \"Текст ответа\",\n answerTextEmpty: \"Текст ответа не может быть пустым\",\n toolOutputJson: (toolName) => `Результат ${toolName}`,\n toolOutputInvalidJson: \"Неверный JSON\",\n toolOutputTooLarge: \"Больше 64 КБ\",\n answerEditSave: \"Сохранить\",\n answerEditSaving: \"Сохраняем…\",\n answerEditCancel: \"Отмена\",\n answerEditFailed: \"Правка не сохранилась\",\n peopleChats: russianPeopleChatsLabels,\n chatReopen: russianChatReopenLabels,\n};\n\nexport const englishSandboxLabels: SandboxLabels = {\n title: \"Sandbox\",\n chatPanel: \"Chat\",\n journalPanel: \"Journal\",\n turnPicker: \"Choose turn\",\n openJournal: \"Open journal\",\n closeJournal: \"Close journal\",\n journalLoading: \"Loading journal…\",\n journalFailed: \"Journal failed to load\",\n journalEmpty: \"No entries yet\",\n retry: \"Retry\",\n wholeChat: \"Whole chat\",\n turnLabel: (index) => `Answer ${index}`,\n purposes: { answer: \"Answer\", compaction: \"History compaction\", memory: \"Memory\", title: \"Chat title\", helper: \"Helper\", other: \"Other\" },\n modelCallStatus: { complete: \"Done\", error: \"Error\", aborted: \"Aborted\" },\n attempt: (attempt) => `Attempt ${attempt}`,\n durationMs: (ms) => `${(ms / 1000).toFixed(1)} s`,\n firstChunk: (ms) => `First chunk after ${(ms / 1000).toFixed(1)} s`,\n tokens: (input, cached, output) => `Input ${input} (cached ${cached}) · output ${output}`,\n costUnknown: \"Cost unknown\",\n instructionsChanged: \"Instructions changed\",\n outputEdited: \"Output edited\",\n fromMessage: \"From message\",\n truncated: \"Truncated\",\n instructions: \"Instructions\",\n messages: \"Messages\",\n newMessage: \"New\",\n tools: \"Tools\",\n settings: \"Settings\",\n response: \"Response\",\n reasoning: \"Reasoning\",\n toolCalls: \"Tool calls\",\n finishReason: \"Finish reason\",\n error: \"Error\",\n toolInput: \"Input\",\n toolOutput: \"Output\",\n noOutput: \"No output\",\n instructionEditor: \"Chat instructions\",\n instructionText: \"Instructions text\",\n appInstructions: \"App instructions\",\n instructionsEdited: \"Instructions edited\",\n instructionsSave: \"Save\",\n instructionsSaving: \"Saving…\",\n instructionsReset: \"Restore app instructions\",\n instructionsTooLong: (maxLength) => `At most ${maxLength} characters`,\n instructionsSaveFailed: \"Instructions were not saved\",\n answerEditor: \"Edit answer\",\n answerText: \"Answer text\",\n answerTextEmpty: \"Answer text cannot be empty\",\n toolOutputJson: (toolName) => `${toolName} output`,\n toolOutputInvalidJson: \"Invalid JSON\",\n toolOutputTooLarge: \"Larger than 64 KB\",\n answerEditSave: \"Save\",\n answerEditSaving: \"Saving…\",\n answerEditCancel: \"Cancel\",\n answerEditFailed: \"Edit was not saved\",\n peopleChats: englishPeopleChatsLabels,\n chatReopen: englishChatReopenLabels,\n};\n\nconst SandboxLabelsContext = createContext<SandboxLabels>(russianSandboxLabels);\n\n/** Свой словарь песочницы; без провайдера — русский. */\nexport function SandboxLabelsProvider({ labels, children }: { labels: SandboxLabels; children?: ReactNode }) {\n return createElement(SandboxLabelsContext.Provider, { value: labels }, children);\n}\n\nexport function useSandboxLabels(): SandboxLabels {\n return useContext(SandboxLabelsContext);\n}\n\n/** Надписи раздела «Чаты людей»: из словаря песочницы, а в старом словаре без них — русские. */\nexport function usePeopleChatsLabels(): PeopleChatsLabels {\n return useSandboxLabels().peopleChats ?? russianPeopleChatsLabels;\n}\n\n/** Надписи кнопки «Открыть чат снова»: из словаря песочницы, а в старом словаре без них — русские. */\nexport function useChatReopenLabels(): ChatReopenLabels {\n return useSandboxLabels().chatReopen ?? russianChatReopenLabels;\n}\n","import { useState } from \"react\";\nimport type { SandboxClient } from \"./sandbox-client\";\nimport { useChatReopenLabels } from \"./sandbox-labels\";\nimport type { SandboxSkin } from \"./sandbox-skin\";\n\n/** «Открыть чат снова» у закрытого чата песочницы: запрос на сервер, а что показать, решает скин (или простая кнопка). */\nexport function ChatReopenControl({ skin, client, chatId, onReopened }: { skin: SandboxSkin; client: SandboxClient; chatId: string; onReopened: () => void }) {\n const labels = useChatReopenLabels();\n const [pending, setPending] = useState(false);\n const [failed, setFailed] = useState(false);\n\n const reopen = async () => {\n setPending(true);\n setFailed(false);\n try {\n await client.reopenChat(chatId);\n onReopened();\n } catch {\n setFailed(true);\n } finally {\n setPending(false);\n }\n };\n\n const error = failed ? labels.failed : undefined;\n if (skin.ChatReopen) return <skin.ChatReopen onReopen={() => void reopen()} pending={pending} {...(error && { error })} />;\n return (\n <div>\n <button type=\"button\" disabled={pending} onClick={() => void reopen()}>\n {pending ? labels.reopening : labels.reopen}\n </button>\n {error && <p role=\"alert\">{error}</p>}\n </div>\n );\n}\n","import { useEffect, useState } from \"react\";\nimport type { SandboxSettingsResponse } from \"../types\";\nimport type { SandboxClient } from \"./sandbox-client\";\nimport { useSandboxLabels } from \"./sandbox-labels\";\nimport type { SandboxSkin } from \"./sandbox-skin\";\n\n/** Предел длины инструкции песочного чата (как у сервера). */\nexport const MAX_INSTRUCTIONS_LENGTH = 100_000;\n\n/** Правка инструкции открытого песочного чата; боевая рядом для сравнения. */\nexport function InstructionHeader({ skin, client, chatId }: { skin: SandboxSkin; client: SandboxClient; chatId: string }) {\n const labels = useSandboxLabels();\n const [settings, setSettings] = useState<SandboxSettingsResponse | null>(null);\n const [value, setValue] = useState(\"\");\n const [open, setOpen] = useState(false);\n const [saving, setSaving] = useState(false);\n const [saveFailed, setSaveFailed] = useState(false);\n\n useEffect(() => {\n let current = true;\n setSettings(null);\n client.settings(chatId).then(\n (loaded) => {\n if (!current) return;\n setSettings(loaded);\n setValue(loaded.instructions ?? loaded.appInstructions);\n },\n () => undefined,\n );\n return () => {\n current = false;\n };\n }, [client, chatId]);\n\n if (!settings) return null;\n const saved = settings.instructions ?? settings.appInstructions;\n const dirty = value !== saved;\n const tooLong = value.length > MAX_INSTRUCTIONS_LENGTH;\n\n const save = async (instructions: string | null) => {\n setSaving(true);\n setSaveFailed(false);\n try {\n const next = await client.saveSettings(chatId, instructions);\n setSettings(next);\n setValue(next.instructions ?? next.appInstructions);\n } catch {\n setSaveFailed(true);\n } finally {\n setSaving(false);\n }\n };\n\n const error = tooLong ? labels.instructionsTooLong(MAX_INSTRUCTIONS_LENGTH) : saveFailed ? labels.instructionsSaveFailed : undefined;\n return (\n <skin.InstructionEditor\n value={value}\n onValueChange={setValue}\n appInstructions={settings.appInstructions}\n edited={settings.instructions !== null}\n dirty={dirty}\n onSave={() => void save(value)}\n canSave={dirty && !tooLong && !saving}\n saving={saving}\n {...(settings.instructions !== null && !saving && { onReset: () => void save(null) })}\n open={open}\n onOpenChange={setOpen}\n {...(error && { error })}\n maxLength={MAX_INSTRUCTIONS_LENGTH}\n />\n );\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { AgentChatMessage } from \"@ykurilov/agent-chat-react\";\nimport type { AnswerEditMark, JournalListResponse } from \"../types\";\nimport {\n formatCost,\n formatTime,\n journalRows,\n journalTurns,\n messageToolDetailsView,\n modelCallDetailsView,\n previousModelCall,\n toolCallDetailsView,\n} from \"./journal-view\";\nimport type { JournalRowSource } from \"./journal-view\";\nimport type { SandboxClient } from \"./sandbox-client\";\nimport { useSandboxLabels } from \"./sandbox-labels\";\nimport type { JournalRowView, ModelCallDetailsView, SandboxSkin, ToolCallDetailsView } from \"./sandbox-skin\";\n\ntype Entries = JournalListResponse[\"entries\"];\ntype Loaded<View> = { state: \"loading\" } | { state: \"failed\" } | { state: \"ready\"; view: View };\n\n/**\n * Журнал открытого чата: обращения к модели и вызовы инструментов хода или всего чата. Записи грузятся при открытии\n * чата и перечитываются после конца каждого хода; подробности строки — по нажатию, один раз.\n */\nexport function useJournalPanel({\n skin,\n client,\n chatId,\n messages,\n busy,\n edits,\n}: {\n skin: SandboxSkin;\n client: SandboxClient;\n chatId: string | null;\n /** Сообщения текущей ветки. */\n messages: AgentChatMessage[];\n busy: boolean;\n edits: ReadonlyMap<string, AnswerEditMark>;\n}) {\n const labels = useSandboxLabels();\n const [load, setLoad] = useState<{ state: \"loading\" | \"ready\" | \"failed\"; entries: Entries }>({ state: \"loading\", entries: [] });\n const [turnId, setTurnId] = useState<string | null>(null);\n const [openIds, setOpenIds] = useState<ReadonlySet<string>>(new Set());\n const [details, setDetails] = useState<Record<string, Loaded<ModelCallDetailsView | ToolCallDetailsView>>>({});\n const request = useRef(0);\n\n const reload = useCallback(() => {\n const ticket = ++request.current;\n if (chatId === null) return;\n client.journal(chatId).then(\n (entries) => ticket === request.current && setLoad({ state: \"ready\", entries }),\n () => ticket === request.current && setLoad((current) => ({ ...current, state: \"failed\" })),\n );\n }, [client, chatId]);\n\n useEffect(() => {\n setLoad({ state: \"loading\", entries: [] });\n setTurnId(null);\n setOpenIds(new Set());\n setDetails({});\n reload();\n return () => { request.current++; };\n }, [reload]);\n\n // Ход закончился: журнал уже записал его обращения и вызовы.\n const wasBusy = useRef(busy);\n useEffect(() => {\n if (wasBusy.current && !busy) reload();\n wasBusy.current = busy;\n }, [busy, reload]);\n\n const turns = journalTurns(messages, labels);\n const currentTurnId = turns.some((turn) => turn.id === turnId) ? turnId : null;\n const sources = journalRows(load.entries, messages, edits, currentTurnId);\n\n const loadDetails = async (row: Extract<JournalRowSource, { kind: \"model\" | \"tool\" }>) => {\n setDetails((current) => ({ ...current, [row.id]: { state: \"loading\" } }));\n try {\n let view: ModelCallDetailsView | ToolCallDetailsView;\n if (row.kind === \"model\") {\n const previous = previousModelCall(load.entries, row.id);\n const [call, previousCall] = await Promise.all([client.call(row.id), previous ? client.call(previous.id) : null]);\n if (call.kind !== \"model\") throw new Error(\"journal call kind mismatch\");\n const previousHashes = previousCall?.kind === \"model\" ? new Set(previousCall.messages.map((blob) => blob.hash)) : null;\n view = modelCallDetailsView(call, previousHashes);\n } else {\n const call = await client.call(row.id);\n if (call.kind !== \"tool\") throw new Error(\"journal call kind mismatch\");\n view = toolCallDetailsView(call);\n }\n setDetails((current) => ({ ...current, [row.id]: { state: \"ready\", view } }));\n } catch {\n setDetails((current) => ({ ...current, [row.id]: { state: \"failed\" } }));\n }\n };\n\n const openChange = (row: JournalRowSource) => (open: boolean) => {\n setOpenIds((current) => {\n const next = new Set(current);\n if (open) next.add(row.id);\n else next.delete(row.id);\n return next;\n });\n if (open && row.kind !== \"message-tool\" && (details[row.id] === undefined || details[row.id]?.state === \"failed\")) void loadDetails(row);\n };\n\n const detailsOf = (row: JournalRowSource) => {\n if (row.kind === \"message-tool\") return <skin.ToolCallDetails {...messageToolDetailsView(row.part)} />;\n const loaded = details[row.id] ?? { state: \"loading\" };\n const view =\n loaded.state === \"ready\" ? loaded.view : loaded.state === \"failed\" ? { state: \"failed\" as const, onRetry: () => void loadDetails(row) } : { state: \"loading\" as const };\n return row.kind === \"model\" ? <skin.ModelCallDetails {...(view as ModelCallDetailsView)} /> : <skin.ToolCallDetails {...(view as ToolCallDetailsView)} />;\n };\n\n const rows = sources.map((row): JournalRowView => {\n const open = openIds.has(row.id);\n const common = { id: row.id, open, onOpenChange: openChange(row), ...(open && { details: detailsOf(row) }) };\n if (row.kind === \"model\") {\n const { entry } = row;\n const cost = formatCost(entry.costUsd);\n return {\n ...common,\n kind: \"model\",\n purpose: entry.purpose,\n attempt: entry.attempt,\n modelId: entry.modelId,\n status: entry.status,\n time: formatTime(entry.startedAt),\n durationMs: entry.durationMs,\n firstChunkMs: entry.firstChunkMs,\n inputTokens: entry.usage?.inputTokens ?? null,\n cachedInputTokens: entry.usage?.cachedInputTokens ?? null,\n outputTokens: entry.usage?.outputTokens ?? null,\n ...(cost !== undefined && { cost }),\n instructionsChanged: entry.instructionsChanged,\n };\n }\n const title = row.part?.title;\n if (row.kind === \"tool\") {\n return {\n ...common,\n kind: \"tool\",\n toolName: row.entry.toolName,\n ...(title !== undefined && { title }),\n source: \"server\",\n status: row.entry.error === null ? \"complete\" : \"error\",\n time: formatTime(row.entry.startedAt),\n durationMs: row.entry.durationMs,\n outputEdited: row.outputEdited,\n };\n }\n return {\n ...common,\n kind: \"tool\",\n toolName: row.part.toolName,\n ...(title !== undefined && { title }),\n source: \"message\",\n status: row.part.state === \"output-error\" ? \"error\" : \"complete\",\n time: \"\",\n durationMs: null,\n outputEdited: row.outputEdited,\n };\n });\n\n const state = load.state === \"ready\" && rows.length === 0 ? \"empty\" : load.state;\n return (\n <skin.JournalList\n busy={busy}\n state={state}\n rows={rows}\n {...(load.state === \"failed\" && { onRetry: reload })}\n turns={turns}\n currentTurnId={currentTurnId}\n onTurnChange={setTurnId}\n />\n );\n}\n","import type { AgentChatMessage } from \"@ykurilov/agent-chat-react\";\nimport type { AnswerEditMark, JournalBlob, JournalCallResponse, JournalListResponse, Serialized } from \"../types\";\nimport { toolCallOf } from \"./answer-edit-draft\";\nimport type { ToolCallPart } from \"./answer-edit-draft\";\nimport type { SandboxLabels } from \"./sandbox-labels\";\nimport type { JournalTurnView, ModelCallDetailsView, ToolCallDetailsView } from \"./sandbox-skin\";\n\ntype Entry = JournalListResponse[\"entries\"][number];\nexport type ModelEntry = Extract<Entry, { kind: \"model\" }>;\nexport type ToolEntry = Extract<Entry, { kind: \"tool\" }>;\ntype ModelCallResponse = Extract<JournalCallResponse, { kind: \"model\" }>;\ntype ToolCallResponse = Extract<JournalCallResponse, { kind: \"tool\" }>;\n\n/** Строка журнала до вида: запись сервера или вызов без `execute`, взятый из частей сообщения. */\nexport type JournalRowSource =\n | { kind: \"model\"; id: string; entry: ModelEntry }\n | { kind: \"tool\"; id: string; entry: ToolEntry; part: ToolCallPart | null; outputEdited: boolean }\n | { kind: \"message-tool\"; id: string; messageId: string; part: ToolCallPart; outputEdited: boolean };\n\n/** «Весь чат» и по ходу на каждый ответ агента текущей ветки. */\nexport function journalTurns(messages: AgentChatMessage[], labels: SandboxLabels): JournalTurnView[] {\n const answers = messages.filter((message) => message.role === \"assistant\");\n return [{ id: null, label: labels.wholeChat }, ...answers.map((message, index) => ({ id: message.id, label: labels.turnLabel(index + 1) }))];\n}\n\n/**\n * Строки хода (`turnId`) или всего чата (`null`) по времени. Вызовы, которых нет в журнале (виджеты, браузер,\n * подтверждения, ответы с правкой), берутся из частей сообщения и встают после последней записи своего ответа.\n */\nexport function journalRows(\n entries: Entry[],\n messages: AgentChatMessage[],\n edits: ReadonlyMap<string, AnswerEditMark>,\n turnId: string | null,\n): JournalRowSource[] {\n const answers = messages.filter((message) => message.role === \"assistant\" && (turnId === null || message.id === turnId));\n const shown = turnId === null ? entries : entries.filter((entry) => entry.messageId === turnId);\n const parts = new Map(answers.flatMap((message) => message.parts.flatMap((part) => toolCallOf(part) ?? [])).map((part) => [part.toolCallId, part]));\n const edited = (messageId: string, toolCallId: string) => edits.get(messageId)?.editedToolCallIds.includes(toolCallId) ?? false;\n const rows: JournalRowSource[] = shown.map((entry) =>\n entry.kind === \"model\"\n ? { kind: \"model\", id: entry.id, entry }\n : { kind: \"tool\", id: entry.id, entry, part: parts.get(entry.toolCallId) ?? null, outputEdited: edited(entry.messageId, entry.toolCallId) },\n );\n const logged = new Set(shown.flatMap((entry) => (entry.kind === \"tool\" ? [entry.toolCallId] : [])));\n for (const message of answers) {\n const fromMessage: JournalRowSource[] = message.parts.flatMap((part) => {\n const call = toolCallOf(part);\n if (!call || logged.has(call.toolCallId) || (call.state !== \"output-available\" && call.state !== \"output-error\")) return [];\n return [{ kind: \"message-tool\", id: `message:${call.toolCallId}`, messageId: message.id, part: call, outputEdited: edited(message.id, call.toolCallId) }];\n });\n if (fromMessage.length === 0) continue;\n const last = rows.findLastIndex((row) => (row.kind === \"message-tool\" ? row.messageId : row.entry.messageId) === message.id);\n rows.splice(last < 0 ? rows.length : last + 1, 0, ...fromMessage);\n }\n return rows;\n}\n\n/** Прошлое обращение того же назначения: относительно него подсвечиваются новые сообщения. */\nexport function previousModelCall(entries: Entry[], callId: string): ModelEntry | null {\n const index = entries.findIndex((entry) => entry.id === callId);\n const call = entries[index];\n if (!call || call.kind !== \"model\") return null;\n for (let i = index - 1; i >= 0; i--) {\n const entry = entries[i]!;\n if (entry.kind === \"model\" && entry.purpose === call.purpose) return entry;\n }\n return null;\n}\n\nexport function formatTime(iso: string): string {\n return new Date(iso).toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\", second: \"2-digit\", hour12: false });\n}\n\nexport function formatCost(costUsd: number | null): string | undefined {\n return costUsd === null ? undefined : `$${costUsd.toFixed(4)}`;\n}\n\n/** Значение как текст для чтения: строка как есть, остальное — отформатированный JSON. */\nexport function readable(value: unknown): string {\n return typeof value === \"string\" ? value : (JSON.stringify(value, null, 2) ?? \"\");\n}\n\n/** Подробности обращения; `previousHashes` — сообщения прошлого обращения (`null` — прошлого нет, все сообщения новые). */\nexport function modelCallDetailsView(details: ModelCallResponse, previousHashes: ReadonlySet<string> | null): Extract<ModelCallDetailsView, { state: \"ready\" }> {\n const response = (details.response.content ?? {}) as { text?: string; reasoning?: string; toolCalls?: Array<{ toolCallId: string; toolName: string; input: unknown }>; finishReason?: string | null };\n const tools = Array.isArray(details.tools.content) ? (details.tools.content as Array<{ name: string; description?: string; inputSchema: unknown }>) : [];\n return {\n state: \"ready\",\n instructions: readable(details.instructions.content),\n messages: details.messages.map((blob) => messageView(blob, previousHashes)),\n tools: tools.map((tool) => ({ name: tool.name, ...(tool.description !== undefined && { description: tool.description }), inputSchema: readable(tool.inputSchema) })),\n settings: readable(details.settings.content),\n response: {\n text: response.text ?? \"\",\n reasoning: response.reasoning ?? \"\",\n toolCalls: (response.toolCalls ?? []).map((call) => ({ toolCallId: call.toolCallId, toolName: call.toolName, input: readable(call.input) })),\n finishReason: response.finishReason ?? null,\n },\n error: details.call.error,\n };\n}\n\nfunction messageView(blob: Serialized<JournalBlob>, previousHashes: ReadonlySet<string> | null) {\n const message = blob.content as { role?: unknown; content?: unknown } | null;\n const role = typeof message?.role === \"string\" ? message.role : \"\";\n return {\n role,\n content: readable(message && \"content\" in message ? message.content : blob.content),\n isNew: previousHashes === null || !previousHashes.has(blob.hash),\n truncated: blob.truncated,\n };\n}\n\nexport function toolCallDetailsView(details: ToolCallResponse): Extract<ToolCallDetailsView, { state: \"ready\" }> {\n return {\n state: \"ready\",\n input: readable(details.input.content),\n output: details.output ? readable(details.output.content) : null,\n error: details.call.error,\n truncated: details.input.truncated || (details.output?.truncated ?? false),\n };\n}\n\n/** Вызов без записи в журнале: вход и результат из части сообщения. */\nexport function messageToolDetailsView(part: ToolCallPart): Extract<ToolCallDetailsView, { state: \"ready\" }> {\n return {\n state: \"ready\",\n input: readable(part.input),\n output: part.state === \"output-available\" ? readable(part.output) : null,\n error: part.state === \"output-error\" ? (part.errorText ?? null) : null,\n truncated: false,\n };\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { AgentChat } from \"@ykurilov/agent-chat-react\";\nimport type { AgentChatProps, ChatSkin } from \"@ykurilov/agent-chat-react\";\nimport type { Person } from \"../types\";\nimport { readOnlyChatSkin } from \"./read-only-chat-skin\";\nimport { SandboxRequestError } from \"./sandbox-client\";\nimport type { SandboxClient } from \"./sandbox-client\";\nimport { usePeopleChatsLabels, useSandboxLabels } from \"./sandbox-labels\";\nimport type { PeopleChatsView, SandboxLoadState, SandboxSkin } from \"./sandbox-skin\";\n\ntype ChatItems = Awaited<ReturnType<SandboxClient[\"personChats\"]>>;\n/** Что из возможностей чата нужно ленте на чтение: показ файлов, картинок, видео, схем и виджетов. */\nexport type ReadOnlyChatProps = Pick<AgentChatProps, \"attachments\" | \"widgets\" | \"diagrams\" | \"images\" | \"videos\" | \"liveness\">;\n\nexport interface PeopleChatsPanelProps {\n client: SandboxClient;\n skin: ChatSkin;\n sandboxSkin: SandboxSkin;\n chatProps: ReadOnlyChatProps;\n /** Копия создана: открыть её как песочный чат. */\n onCopied: (chatId: string) => void;\n}\n\n/**\n * Раздел «Чаты людей»: найти человека (или ввести его id, если приложение не дало поиск), открыть его чат только на\n * чтение и сделать копию в песочнице. Рисует `sandboxSkin.PeopleChats`, без него — простой вид.\n */\nexport function PeopleChatsPanel({ client, skin, sandboxSkin, chatProps, onCopied }: PeopleChatsPanelProps) {\n const labels = usePeopleChatsLabels();\n const [searchable, setSearchable] = useState(true);\n const [query, setQuery] = useState(\"\");\n const [peopleState, setPeopleState] = useState<PeopleChatsView[\"peopleState\"]>(\"idle\");\n const [people, setPeople] = useState<Person[]>([]);\n const [person, setPerson] = useState<Person | null>(null);\n const [chatsState, setChatsState] = useState<SandboxLoadState | \"empty\">(\"loading\");\n const [chats, setChats] = useState<ChatItems>([]);\n const [chatId, setChatId] = useState<string | null>(null);\n const [copying, setCopying] = useState(false);\n const [copyError, setCopyError] = useState<string | undefined>();\n const searchRequest = useRef(0);\n const chatsRequest = useRef(0);\n\n // Пустой запрос ничего не ищет, но говорит, дало ли приложение поиск.\n useEffect(() => {\n let current = true;\n client.searchPeople(\"\").then(\n (response) => current && setSearchable(response.search),\n () => undefined,\n );\n return () => {\n current = false;\n };\n }, [client]);\n\n const loadChats = useCallback(\n (personId: string) => {\n const request = ++chatsRequest.current;\n setChatsState(\"loading\");\n setChats([]);\n client.personChats(personId).then(\n (loaded) => {\n if (request !== chatsRequest.current) return;\n setChats(loaded);\n setChatsState(loaded.length > 0 ? \"ready\" : \"empty\");\n },\n () => request === chatsRequest.current && setChatsState(\"failed\"),\n );\n },\n [client],\n );\n\n const selectPerson = useCallback(\n (selected: Person) => {\n setPerson(selected);\n setChatId(null);\n setCopyError(undefined);\n loadChats(selected.id);\n },\n [loadChats],\n );\n\n const onSearch = useCallback(() => {\n const text = query.trim();\n if (text.length === 0) return;\n if (!searchable) {\n selectPerson({ id: text });\n return;\n }\n const request = ++searchRequest.current;\n setPeopleState(\"loading\");\n client.searchPeople(text).then(\n (response) => {\n if (request !== searchRequest.current) return;\n setPeople(response.people);\n setPeopleState(response.people.length > 0 ? \"ready\" : \"empty\");\n },\n () => request === searchRequest.current && setPeopleState(\"failed\"),\n );\n }, [client, query, searchable, selectPerson]);\n\n const onCopy = useCallback(() => {\n if (!person || chatId === null || copying) return;\n setCopying(true);\n setCopyError(undefined);\n client.copyPersonChat(person.id, chatId).then(\n (copyId) => {\n setCopying(false);\n onCopied(copyId);\n },\n (error: unknown) => {\n setCopying(false);\n setCopyError(error instanceof SandboxRequestError && error.kind === \"empty_chat\" ? labels.emptyChat : labels.copyFailed);\n },\n );\n }, [chatId, client, copying, labels, onCopied, person]);\n\n const chatSkin = useMemo(() => readOnlyChatSkin(skin), [skin]);\n const view: PeopleChatsView = {\n searchable,\n query,\n onQueryChange: setQuery,\n onSearch,\n peopleState,\n people: people.map((found) => ({ ...found, selected: found.id === person?.id, onSelect: () => selectPerson(found) })),\n ...(person && { person: { id: person.id, ...(person.name !== undefined && { name: person.name }) } }),\n chatsState,\n chats: chats.map((chat) => ({\n id: chat.id,\n title: chat.title,\n updatedAt: chat.updatedAt,\n subject: chat.subjectKey !== undefined,\n selected: chat.id === chatId,\n onOpen: () => {\n setChatId(chat.id);\n setCopyError(undefined);\n },\n })),\n onChatsRetry: () => person && loadChats(person.id),\n ...(person &&\n chatId !== null && {\n chat: <AgentChat key={`${person.id}:${chatId}`} {...chatProps} api={client.personChatApi(person.id)} chatId={chatId} skin={chatSkin} />,\n onCopy,\n }),\n copying,\n ...(copyError !== undefined && { copyError }),\n };\n const View = sandboxSkin.PeopleChats ?? PlainPeopleChats;\n return <View {...view} />;\n}\n\n/** Простой вид раздела для скина без `PeopleChats`. */\nfunction PlainPeopleChats(view: PeopleChatsView) {\n const labels = usePeopleChatsLabels();\n const retryLabel = useSandboxLabels().retry;\n const fieldLabel = view.searchable ? labels.searchPerson : labels.personId;\n return (\n <section aria-label={labels.peopleSection} data-people-chats=\"\">\n <form\n role=\"search\"\n onSubmit={(event) => {\n event.preventDefault();\n view.onSearch();\n }}\n >\n <input aria-label={fieldLabel} placeholder={fieldLabel} value={view.query} onChange={(event) => view.onQueryChange(event.target.value)} />\n <button type=\"submit\">{view.searchable ? labels.search : labels.openPerson}</button>\n </form>\n {view.peopleState === \"loading\" && <p>{labels.searching}</p>}\n {view.peopleState === \"failed\" && <p role=\"alert\">{labels.searchFailed}</p>}\n {view.peopleState === \"empty\" && <p>{labels.nobodyFound}</p>}\n {view.people.length > 0 && (\n <ul aria-label={labels.searchPerson}>\n {view.people.map((found) => (\n <li key={found.id}>\n <button type=\"button\" aria-pressed={found.selected} onClick={found.onSelect}>\n {found.name ?? found.id}\n </button>\n {found.hint && <span> {found.hint}</span>}\n </li>\n ))}\n </ul>\n )}\n {view.person && (\n <>\n <h2>{view.person.name ?? view.person.id}</h2>\n {view.chatsState === \"loading\" && <p>{labels.chatsLoading}</p>}\n {view.chatsState === \"failed\" && (\n <p role=\"alert\">\n {labels.chatsFailed}{\" \"}\n <button type=\"button\" onClick={view.onChatsRetry}>\n {retryLabel}\n </button>\n </p>\n )}\n {view.chatsState === \"empty\" && <p>{labels.noChats}</p>}\n <ul aria-label={labels.peopleSection}>\n {view.chats.map((chat) => (\n <li key={chat.id}>\n <button type=\"button\" aria-current={chat.selected || undefined} onClick={chat.onOpen}>\n {chat.title ?? labels.untitledChat}\n </button>\n {chat.subject && <span> · {labels.subjectChat}</span>}\n </li>\n ))}\n </ul>\n </>\n )}\n {view.chat ? (\n <div>\n <p>{labels.readOnly}</p>\n {view.onCopy && (\n <button type=\"button\" disabled={view.copying} onClick={view.onCopy}>\n {view.copying ? labels.copying : labels.copy}\n </button>\n )}\n {view.copyError && <p role=\"alert\">{view.copyError}</p>}\n {view.chat}\n </div>\n ) : (\n view.person && view.chatsState === \"ready\" && <p>{labels.chooseChat}</p>\n )}\n </section>\n );\n}\n","import type { ChatSkin, ToolApprovalView, WidgetView } from \"@ykurilov/agent-chat-react\";\n\nconst readOnlySkins = new WeakMap<ChatSkin, ChatSkin>();\n\n/**\n * Скин чата только на чтение (чат человека в песочнице): без поля ввода, без правки, повтора, нового ответа,\n * переключения веток и кнопок-подсказок. Решать за человека тоже нельзя: ждущие подтверждения без кнопок, задачи без\n * «Отменить», виджеты не нажимаются, свой виджет приложения — запасным видом. Рисуют те же компоненты скина. Обёртка\n * одна на скин: компоненты не пересоздаются при перерисовке.\n */\nexport function readOnlyChatSkin(skin: ChatSkin): ChatSkin {\n const known = readOnlySkins.get(skin);\n if (known) return known;\n const { SuggestedReplies: _suggestedReplies, ...rest } = skin;\n const { Widget, HelperCallRow, AgentNoticeView, TaskCard } = skin;\n const wrapped: ChatSkin = {\n ...rest,\n Layout: ({ composer: _composer, ...props }) => <skin.Layout {...props} composer={null} />,\n UserMessage: ({ onEdit: _onEdit, ...props }) => <skin.UserMessage {...props} />,\n AgentMessage: ({ onRetry: _onRetry, onRegenerate: _onRegenerate, onEdit: _onEdit, ...props }) => <skin.AgentMessage {...props} />,\n BranchSwitcher: ({ onPrevious: _onPrevious, onNext: _onNext, ...props }) => <skin.BranchSwitcher {...props} />,\n ToolCallRow: ({ approval, ...props }) => <skin.ToolCallRow {...props} {...decided(approval)} />,\n ...(HelperCallRow && { HelperCallRow: ({ approval, ...props }) => <HelperCallRow {...props} {...decided(approval)} /> }),\n ...(AgentNoticeView && { AgentNoticeView: ({ approval, ...props }) => <AgentNoticeView {...props} {...decided(approval)} /> }),\n ...(TaskCard && { TaskCard: ({ onCancel: _onCancel, ...props }) => <TaskCard {...props} /> }),\n ...(Widget && { Widget: (props) => <Widget {...inactiveWidget(props)} /> }),\n };\n readOnlySkins.set(skin, wrapped);\n return wrapped;\n}\n\n/** Итог решения остаётся пометкой; ждущее решение — без кнопок. */\nfunction decided(approval: ToolApprovalView | undefined): { approval?: ToolApprovalView } {\n return approval && approval.state !== \"requested\" ? { approval } : {};\n}\n\nfunction inactiveWidget(view: WidgetView): WidgetView {\n const base = { ...view, active: false };\n switch (base.kind) {\n case \"choice\": {\n const { onChoose: _onChoose, ...widget } = base;\n return widget;\n }\n case \"multi_choice\":\n case \"form\": {\n const { onSubmit: _onSubmit, ...widget } = base;\n return widget;\n }\n case \"rating\": {\n const { onRate: _onRate, ...widget } = base;\n return widget;\n }\n case \"custom\": {\n const { content: _content, ...widget } = base;\n return widget;\n }\n }\n}\n","import type {\n AnswerEditMark,\n AnswerEditRequest,\n AnswerEditResponse,\n JournalBlobResponse,\n JournalCallResponse,\n JournalListResponse,\n PeopleSearchResponse,\n PersonChatsResponse,\n SandboxHistoryExtras,\n SandboxSettingsRequest,\n SandboxSettingsResponse,\n StaffCopyResponse,\n} from \"../types\";\n\ntype Fetch = typeof globalThis.fetch;\n\nexport const DEFAULT_SANDBOX_BASE_PATH = \"/api/sandbox\";\n\n/** Сервер ответил отказом: `status` — код ответа, `kind` — вид ошибки из тела (`answer_busy`, `empty_text`…), если он есть. */\nexport class SandboxRequestError extends Error {\n constructor(\n readonly status: number,\n readonly kind: string | null,\n action: string,\n ) {\n super(`Sandbox request failed (${action}): HTTP ${status}${kind ? ` ${kind}` : \"\"}`);\n this.name = \"SandboxRequestError\";\n }\n}\n\n/** Запросы экрана песочницы к адресам из plan.md, «Адреса песочницы». */\nexport interface SandboxClient {\n /** Обращения и вызовы всего чата; с `messageId` — одного хода. */\n journal(chatId: string, messageId?: string): Promise<JournalListResponse[\"entries\"]>;\n call(callId: string): Promise<JournalCallResponse>;\n blob(hash: string): Promise<JournalBlobResponse>;\n settings(chatId: string): Promise<SandboxSettingsResponse>;\n /** `null` — вернуть боевую инструкцию. */\n saveSettings(chatId: string, instructions: string | null): Promise<SandboxSettingsResponse>;\n editAnswer(chatId: string, messageId: string, edit: AnswerEditRequest): Promise<AnswerEditResponse>;\n /** Отметки правок по id ответа — из дополнения к истории чата. */\n answerEdits(chatId: string): Promise<Map<string, AnswerEditMark>>;\n /** Поиск человека; `search: false` — приложение поиск не дало, id вводится вручную. */\n searchPeople(query: string): Promise<PeopleSearchResponse>;\n personChats(personId: string): Promise<PersonChatsResponse[\"chats\"]>;\n /** Копия текущей ветки чата человека в песочнице; ответ — id песочного чата. */\n copyPersonChat(personId: string, chatId: string): Promise<string>;\n /** Открывает закрытый инструментом чат снова: ходы идут дальше (`POST …/chats/{id}/reopen`, 204). */\n reopenChat(chatId: string): Promise<void>;\n /** Адрес чатов человека только на чтение: его отдают `AgentChat` как `api`. */\n personChatApi(personId: string): string;\n}\n\nexport function createSandboxClient({ basePath = DEFAULT_SANDBOX_BASE_PATH, fetch: fetchImpl = (...args) => globalThis.fetch(...args) }: { basePath?: string; fetch?: Fetch } = {}): SandboxClient {\n const base = basePath.replace(/\\/+$/, \"\");\n const segment = encodeURIComponent;\n const chat = (chatId: string) => `${base}/chats/${segment(chatId)}`;\n\n async function json<T>(action: string, url: string, init?: RequestInit): Promise<T> {\n const response = await fetchImpl(url, init);\n if (!response.ok) throw new SandboxRequestError(response.status, await errorKindOf(response), action);\n return (await response.json()) as T;\n }\n const send = (method: string, body: unknown): RequestInit => ({\n method,\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n return {\n journal: async (chatId, messageId) =>\n (\n await json<JournalListResponse>(\n \"journal\",\n `${chat(chatId)}/journal${messageId === undefined ? \"\" : `?messageId=${segment(messageId)}`}`,\n )\n ).entries,\n call: (callId) => json(\"call\", `${base}/journal/calls/${segment(callId)}`),\n blob: (hash) => json(\"blob\", `${base}/journal/blobs/${segment(hash)}`),\n settings: (chatId) => json(\"settings\", `${chat(chatId)}/settings`),\n saveSettings: (chatId, instructions) =>\n json(\"save-settings\", `${chat(chatId)}/settings`, send(\"PUT\", { instructions } satisfies SandboxSettingsRequest)),\n editAnswer: (chatId, messageId, edit) =>\n json(\"answer-edit\", `${chat(chatId)}/messages/${segment(messageId)}/answer-edit`, send(\"POST\", edit)),\n answerEdits: async (chatId) => {\n const history = await json<Partial<SandboxHistoryExtras>>(\"history\", `${base}/${segment(chatId)}`);\n return new Map(Object.entries(history.answerEdits ?? {}));\n },\n searchPeople: (query) => json(\"people\", `${base}/people?query=${segment(query)}`),\n personChats: async (personId) => (await json<PersonChatsResponse>(\"person-chats\", `${base}/people/${segment(personId)}/chats`)).chats,\n copyPersonChat: async (personId, chatId) =>\n (await json<StaffCopyResponse>(\"staff-copy\", `${base}/people/${segment(personId)}/${segment(chatId)}/staff-copy`, { method: \"POST\" })).id,\n reopenChat: async (chatId) => {\n const response = await fetchImpl(`${chat(chatId)}/reopen`, { method: \"POST\" });\n if (!response.ok) throw new SandboxRequestError(response.status, await errorKindOf(response), \"reopen\");\n },\n personChatApi: (personId) => `${base}/people/${segment(personId)}`,\n };\n}\n\nasync function errorKindOf(response: Response): Promise<string | null> {\n try {\n const body = (await response.json()) as { error?: { kind?: unknown } };\n return typeof body.error?.kind === \"string\" ? body.error.kind : null;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["useCallback","useEffect","useMemo","useRef","useState","useState","jsx","useState","useState","jsx","useState","useEffect","useState","jsx","useState","useEffect","useCallback","useEffect","useRef","useState","jsx","jsx","jsxs","useState","useRef","useEffect","useCallback","Fragment","jsx","jsxs","useMemo","useState","useCallback","useRef","useEffect"]}
@@ -6,10 +6,10 @@ import type { SandboxSkin } from "./sandbox-skin.js";
6
6
  * Журнал открытого чата: обращения к модели и вызовы инструментов хода или всего чата. Записи грузятся при открытии
7
7
  * чата и перечитываются после конца каждого хода; подробности строки — по нажатию, один раз.
8
8
  */
9
- export declare function JournalPanel({ skin, client, chatId, messages, busy, edits, }: {
9
+ export declare function useJournalPanel({ skin, client, chatId, messages, busy, edits, }: {
10
10
  skin: SandboxSkin;
11
11
  client: SandboxClient;
12
- chatId: string;
12
+ chatId: string | null;
13
13
  /** Сообщения текущей ветки. */
14
14
  messages: AgentChatMessage[];
15
15
  busy: boolean;
@@ -178,6 +178,8 @@ export interface SandboxSkin {
178
178
  * `journalOpen`). У панелей `aria-label` из `labels.chatPanel` и `labels.journalPanel`.
179
179
  */
180
180
  SandboxLayout: ComponentType<{
181
+ /** Идёт ход агента; реальные записи журнала приходят отдельно. */
182
+ busy?: boolean;
181
183
  /** Над чатом: редактор инструкции и прочее управление чатом. */
182
184
  header: ReactNode;
183
185
  /** `ChatWorkspace` со списком песочных чатов. */
@@ -188,6 +190,8 @@ export interface SandboxSkin {
188
190
  }>;
189
191
  /** Список обращений и вызовов хода или всего чата. */
190
192
  JournalList: ComponentType<{
193
+ /** Идёт ход, журнал перечитывается после его завершения. */
194
+ busy?: boolean;
191
195
  /** `empty` — записей нет (`labels.journalEmpty`). */
192
196
  state: SandboxLoadState | "empty";
193
197
  rows: JournalRowView[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ykurilov/agent-chat-sandbox",
3
- "version": "0.73.0",
3
+ "version": "0.75.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -46,14 +46,14 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "drizzle-orm": "^0.45.3",
49
- "@ykurilov/agent-chat-server": "0.73.0",
50
- "@ykurilov/agent-chat-core": "0.73.0"
49
+ "@ykurilov/agent-chat-core": "0.75.0",
50
+ "@ykurilov/agent-chat-server": "0.75.0"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "better-sqlite3": "^13.0.3",
54
54
  "pg": "^8.23.0",
55
55
  "react": "^19.3.0",
56
- "@ykurilov/agent-chat-react": "^0.73.0"
56
+ "@ykurilov/agent-chat-react": "^0.75.0"
57
57
  },
58
58
  "peerDependenciesMeta": {
59
59
  "better-sqlite3": {
@@ -85,10 +85,10 @@
85
85
  "pg": "^8.23.0",
86
86
  "react": "^19.3.0",
87
87
  "react-dom": "^19.3.0",
88
- "@ykurilov/agent-chat-images": "0.73.0",
89
- "@ykurilov/agent-chat-memory": "0.73.0",
90
- "@ykurilov/agent-chat-attachments": "0.73.0",
91
- "@ykurilov/agent-chat-react": "0.73.0"
88
+ "@ykurilov/agent-chat-attachments": "0.75.0",
89
+ "@ykurilov/agent-chat-images": "0.75.0",
90
+ "@ykurilov/agent-chat-memory": "0.75.0",
91
+ "@ykurilov/agent-chat-react": "0.75.0"
92
92
  },
93
93
  "scripts": {
94
94
  "build": "node ../../scripts/build-package.mjs",