# dsh-tool-docx [English](README.md) | [中文](README.zh.md) | Русский Дорожная карта: [English](ROADMAP.md) · [中文](ROADMAP.zh.md) · [Русский](ROADMAP.ru.md) Ориентированные на модель инструменты Microsoft Word (`.docx`) для [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): `docx_read` извлекает документ как Markdown или структурированные JSON-блоки, `docx_create` генерирует новый `.docx` из Markdown, а `docx_edit` заменяет содержимое документа из Markdown, сохраняя его свойства title/author/created. `.docx` — это ZIP-архив XML-частей, поэтому каждый инструмент читает весь пакет через ограниченный примитив `ctx.fs.readBytes` и записывает пакеты через бинарный сервис записи плагина `fsBinary` (или через `ctx.fs` хоста, если он нативно предоставляет `writeBytes`) — те же атомарные, ограждённые песочницей мутации, которые используют текстовые инструменты, при этом собственная файловая система хоста никогда не заменяется. Этот репозиторий — **автономное распространение** плагина. Плагин — **оригинальная работа, написанная для DeepSeek Harness**: независимый плагин, разработанный этим проектом, изначально в локальном checkout `deepseek-harness` (harness — его среда выполнения), а не копия плагина из общего репозитория. Он подключается к сервисам harness `tools`, `fs` и `systemPrompt` и самостоятельно собирается и тестируется против опубликованных пакетов `@deepseek-ai/*`, поэтому его можно установить в любой checkout harness. ## Требования - Хост DeepSeek Harness (линия `0.1.0-rc.7`), чей слой файловой системы предоставляет **примитив чтения** `fs.readBytes` — опубликован в `@deepseek-ai/dsh-fs` начиная с `0.1.0-rc.7`. **Сторона записи** (`writeBytes`) отсутствует во всех опубликованных релизах `dsh-fs`, поэтому бандл монтирует [бинарные fs-провайдеры](#бинарные-fs-провайдеры) плагина: отдельный сервис `fsBinary`, реализующий `writeBytes` (с ограждением для песочных хостов), не затрагивая `ctx.fs`. Без какого-либо бинарного writer'а — ни сервиса `fsBinary`, ни нативного `ctx.fs.writeBytes` — `docx_read` по-прежнему работает, а `docx_create`/`docx_edit` завершаются типизированной ошибкой `DOCX_HOST_FS_UNSUPPORTED` с указанием исправления. - Harness предоставляет peer-сервисы (линия `0.1.0-rc.7`): `cordis`, `dsh-tools`, `dsh-fs`, `dsh-llm`, `dsh-sandbox`, `dsh-sandbox-policy`, `dsh-system-prompt`, `dsh-invariants`, `dsh-user-approval`, `dsh-session`. ## Установка Официальный путь установки — собственный менеджер плагинов harness: одна команда устанавливает пакет в профиль, а лаунчер профиля автоматически активирует слой [`cordis.patch.yml`](cordis.patch.yml) бандла (пакет объявляет `dsh.bundle.patch`): ```sh dsh plugin --profile web add dsh-tool-docx ``` `dsh plugin` запускает pnpm внутри каталога профиля и сверяет `dsh.profile.bundles` с установленным состоянием, поэтому больше ничего не нужно — ни записей `allowBuilds` (пакет поставляется с готовым `lib/` и без build-скриптов), ни ручного редактирования `cordis.patch.yml`, ни overlay через `--patch`. После этого перезапустите harness. - **Обновление:** `dsh plugin --profile web update dsh-tool-docx`, либо повторный `add` с новым тегом. - **Удаление:** `dsh plugin --profile web remove dsh-tool-docx`. - **Локальная разработка:** `dsh plugin --profile web add ../dsh-tool-docx` (относительные spec привязываются к каталогу вызова) или `dsh plugin --profile web add link:../dsh-tool-docx`. > Опубликован в [npm](https://www.npmjs.com/package/dsh-tool-docx) под именем `dsh-tool-docx` (конвенция экосистемы `dsh-tool-*`), независимо от scope `@deepseek-ai`. ## Инструменты | Инструмент | Назначение | |---|---| | `docx_read(file_path, format?, max_chars?)` | Извлекает тело документа как Markdown (по умолчанию) или структурированные JSON-блоки плюс `docProps`. При наличии сервиса `attachments` встроенные изображения извлекаются из `word/media/*` и возвращаются как durable-блоки изображений вместе с текстом. Испускает `fs/observed`. | | `docx_create(file_path, markdown, title?, author?)` | Генерирует новый `.docx` из Markdown. Встраивает изображения, на которые ссылаются в Markdown (`![alt](path)`), читая их из файловой системы. Защищён `createIfAbsent`: существующий файл никогда не перезаписывается вслепую. | | `docx_edit(file_path, markdown)` | Читает текущий документ (проверяя, что это docx), сохраняет `docProps` и встроенные изображения, перегенерирует тело из полного Markdown и записывает обратно с защитой версии (`DOCX_STALE` при конкурентном изменении). Новые ссылки на изображения в Markdown разрешаются из файловой системы. | Все три инструмента разрешают относительные пути относительно рабочей директории сессии вызывающего агента, перед изменением запускают водопад `fs/write-intent` (плагин политики наблюдения может предоставить свой intent) и фиксируют `fs/observed` по завершении — поэтому ограждение песочницы, поля эскалации и политика «читать перед записью» применяются к мутациям docx точно так же, как к `write`/`edit`. ## Конфигурация | Поле | По умолчанию | Значение | |---|---|---| | `maxDocxBytes` | 64 MiB | Включительный байтовый лимит на весь файл `.docx` (чтение + распаковка ZIP). | | `maxMarkdownChars` | 1 000 000 | Включительный лимит символов на входной Markdown для create/edit. | | `maxReadChars` | 200 000 | Включительный лимит символов на Markdown, возвращаемый `docx_read`. | ## Бинарные fs-провайдеры Строка `fs-sandbox` базового бандла продолжает предоставлять `ctx.fs` **без изменений**. Бандл добавляет рядом docx-инструменты и один из бинарных провайдеров, который регистрирует бинарный примитив `writeBytes` как **отдельный сервис `fsBinary`** — файловая система хоста никогда не заменяется, поэтому этот плагин не может сломать запуск harness; в худшем случае без провайдера изменяющие инструменты сообщают `DOCX_HOST_FS_UNSUPPORTED`: - **`dsh-tool-docx/fs-binary-sandbox-plugin`** (монтируется бандлом; рекомендуется для песочных хостов) — регистрирует `fsBinary.writeBytes`, ограждённый **тем же по-вызовным политическим забором**, что и каждая мутация песочницы: containment `workspace-write`, отказ в `read-only`, пропуск в `danger-full-access`, `FS_SANDBOX_DENIED` при отказе (на уровне инструмента отображается в `DOCX_SANDBOX_DENIED`). - **`dsh-tool-docx/fs-binary-local-plugin`** — регистрирует `fsBinary.writeBytes` **без забора**, для минимальных окружений (тесты, headless-скрипты) или там, где хост уже ограждает поверх провайдера. Чтобы смонтировать его вместо песочного, переопределите строку бандла в своём `cordis.patch.yml` профиля: ```yaml - id: fs-binary-sandbox disabled: true - insert: - id: fs-binary-local name: dsh-tool-docx/fs-binary-local-plugin ``` Оба используют тот же поток probe → intent-охрана (`createIfAbsent` / `replaceIfVersion`) → атомарная публикация, что и в seam harness: приватный owner-only staging-каталог, fsync, затем атомарная публикация (для `createIfAbsent` — hard-link без замены), с сериализацией по таргету. Первая версия опускает Win32 DACL-церемонию harness — заменённый файл наследует owner-only ACL временного файла. Для хостов, которые намеренно хотят смонтировать полный бэкенд **как `ctx.fs`** (заменив `fs-sandbox`), пакет также поставляет классы-провайдеры `dsh-tool-docx/fs-binary-sandbox` и `dsh-tool-docx/fs-binary-local`; рецепт замены строки по-прежнему применим: ```yaml - id: fs-sandbox disabled: true - insert: - id: fs-binary-sandbox name: dsh-tool-docx/fs-binary-sandbox - id: tool-docx name: dsh-tool-docx ``` ## Заметки по дизайну - **Извлечение** (`src/docx/extract.ts`) обходит `word/document.xml` с помощью `fast-xml-parser`: заголовки (`Heading1`–`Heading6`, `Title`), run-ы жирный/курсив/зачёркнутый, вложенные списки через `word/numbering.xml` (маркированный и нумерованный), таблицы с разделителями-пайпами (объединённые ячейки — приблизительно), внешние гиперссылки через `word/_rels/document.xml.rels` и встроенные изображения как считаемые плейсхолдеры. Неподдерживаемые конструкции деградируют до предупреждений, но никогда до ошибок. - **Генерация** (`src/docx/generate.ts`) рендерит блочную модель библиотекой `docx`: ATX-заголовки, стилизованные строчные run-ы, маркированная/нумерованная нумерация 9 уровней, таблицы с пайпами и внешние гиперссылки `[text](url)`. `parseMarkdown` (src/markdown.ts) принимает то подмножество, которое выдаёт экстрактор, поэтому циклы «прочитать → изменить → записать» стабильны. - **Лимиты применяются на стыке (seam), а не в инструменте** — байтовый лимит всего файла уходит в `ctx.fs.readBytes` (`FS_TOO_LARGE` отображается в `DOCX_TOO_LARGE`), а ZIP-ридер применяет тот же лимит к общему несжатому объёму, поэтому сжатая бомба не может распаковаться без ограничений. - **Паритет песочницы** — `src/sandbox.ts` повторяет API эскалации `dsh-tool-fs` (`sandbox_permissions`/`justification` анонсируются только под ограничивающим бэкендом, отображение маркеров отказа); извлечение общего контроллера — отложенная работа (см. ниже). - **Контракт файловой системы хоста** — `src/fs-binary.ts` объявляет бинарный контракт, нужный инструментам, и разрешает writer во время вызова: сервис `fsBinary`, когда он смонтирован, иначе `ctx.fs` хоста, нативно предоставляющий `writeBytes`. Без какого-либо бинарного writer'а он выбрасывает `DOCX_HOST_FS_UNSUPPORTED` с указанием исправления вместо загадочного `fs.writeBytes is not a function`; для `docx_read` нужен только опубликованный `ctx.fs.readBytes`. ## Опыт модели ### Системный промпт #### Что видит модель Раздел `tool:docx-read` ниже регистрируется один раз при применении плагина: ##### Раздел-руководство по docx ```markdown MS Word .docx files are binary (ZIP+XML) and the read tool cannot read them. Use docx_read to extract a document as Markdown (default) or structured JSON blocks, docx_create to generate a new .docx from Markdown, and docx_edit to replace a document's content from Markdown while preserving its title/author/created properties. Legacy .doc is not supported — convert it to .docx first. ``` #### Влияние на токены Фиксированная стоимость руководства на каждый запрос, пока плагин подключён; раздел не зависит от ограничений инструментов по scope. #### Влияние на KV-кэш Префиксно-стабилен, пока текст руководства не меняется. Жизненный цикл плагина или изменение текста могут инвалидировать переиспользование с первого изменённого раздела промпта. ### Схемы инструментов #### Что видит модель Сгенерированные схемы `docx_read`, `docx_create` и `docx_edit` — параметры и канонические результаты в сводке таблицы [Инструменты](#инструменты). Байтовые/символьные лимиты — это настройки развёртывания, а не аргументы модели; поля эскалации появляются только под ограничивающим файловым бэкендом. #### Влияние на токены Фиксированная стоимость схем на каждый запрос для каждого подключённого инструмента; отключение в конфигурации удаляет схемы и руководство вместе, а ограничение по scope удаляет только схему. #### Влияние на KV-кэш Префиксно-стабилен, пока определения и видимость не меняются. Включение в конфигурации, жизненный цикл плагина или ограничения по scope могут инвалидировать переиспользование с первого изменённого токена схемы. ### Результат чтения #### Что видит модель Успешный `docx_read` рендерит извлечённый Markdown (или форматированные JSON-блоки). При наличии сервиса `attachments` и изображений в документе результат инструмента также содержит блоки контента `{ type: 'image', attachment }` — модель видит изображения напрямую, без отдельного вызова `read_image`. При усечении добавляется `\n… (truncated)`; ошибки — типизированные сообщения, например `file not found: `, `the document is encrypted (password-protected); decryption is not supported` или подсказка для legacy `legacy .doc format is not supported — convert the document to .docx first`. #### Влияние на токены Зависящие от данных результаты ограничены `maxReadChars` (или `max_chars` из вызова) и пересылаются до компактификации. #### Влияние на KV-кэш Только добавление; вновь видимое содержимое следует за переиспользуемым префиксом запроса и не инвалидирует существующие записи KV-кэша. ### Результат создания/редактирования #### Что видит модель Успешное создание/редактирование рендерит короткий конверт ``/`docx` с размером в байтах — никогда тело документа. Предупреждения об приближениях (изображения, объединённые ячейки, блоки кода) переносятся в канонический массив `warnings` и рендерятся как обычный текст. #### Влияние на токены Только сохранённые аргументы вызова (включая полный Markdown-ввод) и короткий результат добавляют токены; байты сгенерированного пакета никогда не попадают в журнал сессии. #### Влияние на KV-кэш Только добавление; вновь видимое содержимое следует за переиспользуемым префиксом запроса и не инвалидирует существующие записи KV-кэша. ### Ошибки аргументов #### Что видит модель Пустой `file_path` становится `Error: file_path must be a non-empty string`; markdown сверх лимита ввода становится `Error: markdown exceeds the -character limit`. #### Влияние на токены Только неудачный вызов добавляет эти сохранённые токены. #### Влияние на KV-кэш Только добавление; вновь видимое содержимое следует за переиспользуемым префиксом запроса и не инвалидирует существующие записи KV-кэша. ## Известные ограничения и отложенная работа - **Legacy `.doc` (OLE) не поддерживается** — бинарный OLE требует конвертации через LibreOffice или Word COM; инструменты завершаются с `DOCX_LEGACY_DOC` и подсказкой сначала сконвертировать в `.docx`. - **Цикл «прочитать → изменить» перегенерирует документ** — стили, настройки страницы, колонтитулы и разрывы разделов не сохраняются; редактирование пересобирает тело со стилями по умолчанию, сохраняя только title/author/created и встроенные изображения. Точность вёрстки не является целью цикла. - **Объединённые ячейки таблиц — приблизительно** — `gridSpan`/`vMerge` деградируют до обычных ячеек пайп-таблиц с предупреждением; сноски, концевые сноски, текстовые поля и разрывы страниц отбрасываются (с предупреждениями). - **Контроллер песочницы дублирует `dsh-tool-fs`** — извлечение общего `FsSandboxController` — отложенная работа; до тех пор две копии должны поддерживаться в синхронизации. - **Подмножество Markdown-ввода** — цитаты, горизонтальные линии и вложенные ограждения не представлены; они деградируют до абзацев с предупреждением (ограждённый код становится абзацами в стиле кода). - **Структурированные коды ошибок при запуске из исходников (tsx)** — когда harness запускается из исходников (`node --import tsx/esm`, dev-лаунчер), healed-junction `$DSH_HOME/profiles/node_modules` разрешается как второй инстанс модуля peer-пакетов Service Definition, поэтому ошибки плагина не проходят проверку `instanceof HarnessError` хоста, и `error.info.code` опускается из результатов инструментов. Сообщение для модели — включая маркеры `[sandbox: …]` и подсказки эскалации — не затрагивается, а запуск из собранных bin (обычный Node) использует один инстанс и сохраняет коды. ## Разработка ```sh pnpm install pnpm typecheck # tsc по src/ pnpm build # tsc → lib/types + tsdown → lib/index.js, lib/invariant.js pnpm test # vitest: юнит-тесты конвертации + потребительские тесты на фейковой fs pnpm pack # собрать npm-тарбол (files: lib/index.js, lib/invariant.js, lib/types/**/*.d.ts) ``` Структура: - `src/docx/` — извлечение ZIP/XML и генерация библиотекой `docx`; - `src/tools/` — три регистрации инструментов; - `src/fs-binary.ts` — контракт бинарной файловой системы и разрешение writer'а во время вызова (`fsBinary`-сервис или нативный `ctx.fs.writeBytes`); - `src/fs-binary-local.ts`, `src/fs-binary-sandbox.ts`, `src/fsio-bytes.ts`, `src/path-contains.ts` — классы бинарных fs-провайдеров, атомарный writer и хелперы containment; - `src/fs-binary-sandbox-plugin.ts`, `src/fs-binary-local-plugin.ts` — namespace-плагины, регистрирующие сервис `fsBinary` (монтирование по умолчанию из бандла); - `tests/` — тесты циклов конвертации, тесты провайдера и потребительские тесты против опубликованного сервиса `ToolRuntime` (экспортируется с `dsh-tools@0.1.0-rc.7`). ## Отношение к deepseek-harness Этот плагин — **оригинальный, независимый проект, написанный для DeepSeek Harness**: он подключается к публичным сервисам harness (`tools`, `fs`, `systemPrompt`) и разрабатывается в локальном checkout `deepseek-harness` для тестирования против harness. Он не является копией плагина из общего репозитория `deepseek-harness` и не является его частью; этот репозиторий — канонический канал распространения. Две заметки по реализации: 1. `src/fs-binary.ts` (контракт хоста + разрешение writer'а) — бинарный контракт `readBytes`/`writeBytes` — часть дизайна этого плагина. `readBytes` опубликован в линии релизов `dsh-fs` harness начиная с `0.1.0-rc.7`; `writeBytes` — нет, поэтому [бинарные fs-провайдеры](#бинарные-fs-провайдеры) поставляют его как отдельный сервис `fsBinary`, а не патчат хост; 2. `tests/` работает против опубликованного сервиса `ToolRuntime` (экспортируется с `dsh-tools@0.1.0-rc.7`), поэтому потребительские тесты проходят через реальный конвейер реестра, а не локальный дублёр. Плагин полностью разрабатывается и поддерживается в этом репозитории против опубликованных пакетов `@deepseek-ai/*`; checkout harness — лишь среда выполнения для интеграционной проверки и не содержит копии этого плагина. ## Лицензия MIT © 2026 BroBFG. Части `src/sandbox.ts`, паттерн атомарной записи в `src/fsio-bytes.ts` и логика containment в `src/path-contains.ts` являются производными от [deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) (MIT, Copyright (c) 2026 DeepSeek) — см. [LICENSE](LICENSE).