@ykurilov/agent-chat-sandbox 0.80.0 → 0.81.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 +24 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @ykurilov/agent-chat-sandbox
|
|
2
2
|
|
|
3
|
+
## 0.81.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 038b6e2: Исправления аудита клиентской зоны. `useAgentChat` без `key` при смене чата больше не переносит в новый чат дела прежнего: «Стоп», ответ на подтверждение или виджет, передачу поправок и сорвавшуюся голосовую замену; новый чат не остаётся «занятым», повторное открытие того же чата начинается с чистого состояния. Песочница не зовёт хуки приложения, которые пишут данные (`onStateChanged`, `onTurnFinished`, `onModelChosen`), ни в песочном чате, ни в копии чата человека; `newChatGreeting` и `modelMenu` решаются для настоящего админа. Вложения: удаление человеком и чистка брошенных больше не убирают файл, который отправляемое сообщение уже отметило; `confirmUserParts` отказывает `AttachmentRejectedError` (`attachment_not_found` — файла нет или отметка не записана, `attachment_unavailable` — сбой хранилища), а не отмечает молча.
|
|
8
|
+
|
|
9
|
+
### Как обновить приложение
|
|
10
|
+
|
|
11
|
+
Обновите пакеты SDK одной версией. Со встроенными хранилищами вложений (`createDiskAttachmentStorage`, `createMemoryAttachmentStorage`) ничего менять не нужно. Своё хранилище (`AttachmentStorage`, например S3) должно получить метод `removeUnattached(id): Promise<boolean>` — удалить файл, только если он не прикреплён, атомарно с `markAttached` того же файла; `removeUnattachedOlderThan` должен следовать тому же правилу. Без метода `createAttachments` бросает ошибку при запуске. Проверьте хранилище обновлённым `describeAttachmentStorageContract`. Если песочница полагалась на `onStateChanged`, `onTurnFinished` или `onModelChosen` в песочных чатах — в песочнице они больше не зовутся. [Подробности](https://github.com/ykurilov/agent-chat-sdk/blob/main/docs/audit-client.md).
|
|
12
|
+
- Updated dependencies [038b6e2]
|
|
13
|
+
- Updated dependencies [58204d2]
|
|
14
|
+
- @ykurilov/agent-chat-react@0.81.0
|
|
15
|
+
- @ykurilov/agent-chat-core@0.81.0
|
|
16
|
+
- @ykurilov/agent-chat-server@0.81.0
|
|
17
|
+
|
|
18
|
+
## 0.80.1
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [d59ae4e]
|
|
23
|
+
- @ykurilov/agent-chat-core@0.80.1
|
|
24
|
+
- @ykurilov/agent-chat-server@0.80.1
|
|
25
|
+
- @ykurilov/agent-chat-react@0.80.1
|
|
26
|
+
|
|
3
27
|
## 0.80.0
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -381,6 +381,14 @@ function createSandboxHandler(options) {
|
|
|
381
381
|
subjectChats: void 0,
|
|
382
382
|
// Данные нового чата тоже: они про человека приложения, а в песочнице чат создаёт админ.
|
|
383
383
|
newChatData: void 0,
|
|
384
|
+
// Хуки, что пишут в данные приложения, тоже: копия состояния, итог хода и выбор модели легли бы на `sandbox:<админ>`,
|
|
385
|
+
// а в копии чата человека — поверх его данных (у этих хуков нет `staff`, отличить копию приложению нечем).
|
|
386
|
+
onStateChanged: void 0,
|
|
387
|
+
onTurnFinished: void 0,
|
|
388
|
+
onModelChosen: void 0,
|
|
389
|
+
// Решения, что только читают, — для настоящего админа, как у инструментов: песочного владельца приложение не знает.
|
|
390
|
+
...chat.newChatGreeting && { newChatGreeting: forRealAdmin(chat.newChatGreeting) },
|
|
391
|
+
...chat.modelMenu && { modelMenu: forRealAdmin(chat.modelMenu) },
|
|
384
392
|
// Итог хода приложению — только из копии чата человека, с его объектом и `staff` (см. `staffCopies.afterTurn`).
|
|
385
393
|
afterTurn: chat.afterTurn && staffCopies.afterTurn(chat.afterTurn),
|
|
386
394
|
memory: chat.memory && readOnlyMemory(chat.memory, staffCopies),
|
|
@@ -421,6 +429,9 @@ function createSandboxHandler(options) {
|
|
|
421
429
|
}
|
|
422
430
|
};
|
|
423
431
|
}
|
|
432
|
+
function forRealAdmin(decide) {
|
|
433
|
+
return (input) => decide({ ...input, userId: realOwnerOf(input.userId) });
|
|
434
|
+
}
|
|
424
435
|
function createSandboxRoutes({ service, journal, people, staffCopies, appInstructions, logError, readHistory }) {
|
|
425
436
|
async function settingsResponse(chatId) {
|
|
426
437
|
const settings = await journal.getSettings(chatId);
|
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, 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":"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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 { realOwnerOf, 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 // Хуки, что пишут в данные приложения, тоже: копия состояния, итог хода и выбор модели легли бы на `sandbox:<админ>`,\n // а в копии чата человека — поверх его данных (у этих хуков нет `staff`, отличить копию приложению нечем).\n onStateChanged: undefined,\n onTurnFinished: undefined,\n onModelChosen: undefined,\n // Решения, что только читают, — для настоящего админа, как у инструментов: песочного владельца приложение не знает.\n ...(chat.newChatGreeting && { newChatGreeting: forRealAdmin(chat.newChatGreeting) }),\n ...(chat.modelMenu && { modelMenu: forRealAdmin(chat.modelMenu) }),\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\n/** Решение приложения о человеке (`{ userId }`) получает настоящего админа вместо песочного владельца. */\nfunction forRealAdmin<Input extends { userId: string }, Result>(decide: (input: Input) => Result): (input: Input) => Result {\n return (input) => decide({ ...input, userId: realOwnerOf(input.userId) });\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":"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","names":["refusal","notFound"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ykurilov/agent-chat-sandbox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.81.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-core": "0.
|
|
50
|
-
"@ykurilov/agent-chat-server": "0.
|
|
49
|
+
"@ykurilov/agent-chat-core": "0.81.0",
|
|
50
|
+
"@ykurilov/agent-chat-server": "0.81.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.
|
|
56
|
+
"@ykurilov/agent-chat-react": "^0.81.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-
|
|
89
|
-
"@ykurilov/agent-chat-
|
|
90
|
-
"@ykurilov/agent-chat-memory": "0.
|
|
91
|
-
"@ykurilov/agent-chat-react": "0.
|
|
88
|
+
"@ykurilov/agent-chat-attachments": "0.81.0",
|
|
89
|
+
"@ykurilov/agent-chat-images": "0.81.0",
|
|
90
|
+
"@ykurilov/agent-chat-memory": "0.81.0",
|
|
91
|
+
"@ykurilov/agent-chat-react": "0.81.0"
|
|
92
92
|
},
|
|
93
93
|
"scripts": {
|
|
94
94
|
"build": "node ../../scripts/build-package.mjs",
|