Первая версия: инструменты и цикл для DeepSeek

DeepSeek через веб-чат видит только то, что человек положил в вопрос. Здесь
у него появляется обратное направление: он возвращает блок-запрос, программа
исполняет его и досылает результат следующим сообщением в тот же чат. Обычный
агентский цикл, только транспорт — текст.

Инструменты (только чтение, только внутри рабочей папки):
  === NEED: путь, путь ===   прислать файлы целиком
  === LIST: **/*.mjs ===     список файлов по маске
  === FIND: текст ===        где встречается, с номерами строк

Запуска команд намеренно нет: исполнять строки из ответа, пришедшего из
интернета, без белого списка и подтверждения нельзя.

Мост ollama_orchestro/ask.mjs не импортируется и не копируется — он разбирает
process.argv и исполняет команду на верхнем уровне, поэтому import немедленно
запустил бы её. Вызывается подпроцессом теми флагами, которые у него уже есть:
шаг 1 без --new (правка первого сообщения), шаги 2..N с --new, иначе DeepSeek
не помнит предыдущий шаг. Взамен orchestro не меняется ни на строку, а его
правки прилетают сюда даром.

Два урока, каждый стоил живого прогона и вписан в код:
- инструкция в начале сообщения не работает. 22 КБ файла между приставкой и
  вопросом — и блоки-запросы игнорируются. Помогает дословное напоминание
  вплотную к вопросу;
- дерево папки надо давать сразу. Без него DeepSeek угадывает имена файлов и
  тратит два круга по минуте на LIST.

Есть веб-пульт (start-deepwork.cmd, бросить папку): файлы галочками, журнал
шагов по ходу, запись правок с копией в backup/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Alex Cube
2026-08-11 16:31:33 +03:00
co-authored by Claude Opus 5
commit 97eefb57c7
7 changed files with 1008 additions and 0 deletions
+170
View File
@@ -0,0 +1,170 @@
# deepwork
Цикл с инструментами поверх моста `ollama_orchestro/ask.mjs`.
DeepSeek через веб-чат видит только то, что положили ему в вопрос. Здесь у него
появляется обратное направление: он возвращает блок-запрос, программа его
исполняет и досылает результат следующим сообщением в тот же чат. Обычный
агентский цикл, только транспорт — текст.
## Как пользоваться
Обычный путь — **пульт**: бросить папку проекта на `start-deepwork.cmd`.
Откроется страница: файлы галочками, поле вопроса, ход работы построчно,
кнопка «Записать» у каждого пришедшего файла.
Пульт открывается в браузере ПО УМОЛЧАНИЮ, а не в отладочном: там он занял бы
вкладку, и мост перестал бы видеть DeepSeek. Порт 8787 — рядом с пультом
orchestro (8777), оба могут работать разом.
Тот же цикл из консоли:
```
cd c:\мой\проект
node c:\#Projects\deepwork\deepwork.mjs "вопрос" --files a.js,b.js --apply
node c:\#Projects\deepwork\deepwork.mjs ui тот же пульт вручную
```
Работает в текущей папке: её файлы показывает, в неё же пишет правки.
## Инструменты
Только чтение и только внутри текущей папки:
| Блок в ответе | Что делает |
|---|---|
| `=== NEED: путь, путь ===` | прислать файлы целиком |
| `=== LIST: **/*.mjs ===` | список файлов по маске |
| `=== FIND: текст ===` | где встречается текст, с номерами строк |
Запуска команд (`RUN`) нет намеренно: исполнять строки из ответа, пришедшего
из интернета, без белого списка и подтверждения нельзя.
Лимиты на результат шага: `NEED` — 200 000 символов суммарно, `FIND` — 100
попаданий, `LIST` — 300 путей. Обрезанное помечается прямо в тексте.
## Как устроено
Мост **не импортируется и не копируется**. `ask.mjs` разбирает `process.argv`
и исполняет команду на верхнем уровне — `import` немедленно запустил бы её.
Поэтому мост вызывается подпроцессом теми флагами, которые у него уже есть:
```
node ask.mjs ask --chat think --file step1.txt --out ans1.txt --quiet
node ask.mjs ask --chat think --new --file step2.txt --out ans2.txt --quiet
```
Шаг 1 идёт без `--new`: мост правит первое сообщение, и чат остаётся из одного
обмена. Шаги 2..N — с `--new`, обычной отправкой: только так DeepSeek помнит
предыдущий шаг. Цена — переподключение к браузеру на каждом шаге, 1–2 секунды.
Взамен `ollama_orchestro` не меняется ни на строку, а его правки прилетают сюда
даром.
Чат берётся тот же, что у orchestro (`think` из его `chats.json`). Своя роль не
заводится: `chats.json` принадлежит мосту.
Цикл останавливается, когда запросов в ответе нет, когда кончились шаги или
когда пришёл тот же самый запрос второй раз подряд (петля).
## Настройка
`config.json` (в репозиторий не попадает, образец — `config.example.json`):
```json
{ "bridge": "C:/#Projects/ollama_orchestro/ask.mjs", "chat": "think", "steps": 4 }
```
Без файла мост ищется рядом: `../ollama_orchestro/ask.mjs`.
## Проверка
```
node deepwork.mjs --selftest внутренние проверки, ничего не отправляют
node deepwork.mjs --check мост на месте, вкладка жива
```
Браузер должен быть запущен `start-browser.cmd` из `ollama_orchestro`
с отладочным портом 9333.
## Что проверено
- `--selftest`: разбор запросов, отказ пути за пределы папки, маски `*` и `**`,
снятие тройных кавычек и подписи языка. Строка вида `=== NEED: … ===`
**внутри** блока `=== FILE: ===` за запрос не принимается — иначе правка
файла с описанием формата запускала бы запрос сама.
- Цикл целиком на заглушке вместо моста: приставка едет в первом сообщении,
второй вызов уходит с `--new`, результаты `NEED` и `FIND` доезжают,
отступы в записанном файле сохраняются, прежняя версия уходит в `backup/`.
- Пропавший файл в `NEED` возвращает «файла нет» и цикл не роняет.
- `--steps 1` отдаёт ответ с пометкой «шаги кончились», а не зависает.
- Битый `config.json` — понятная ошибка, а не молчаливый откат к умолчаниям.
### Живые прогоны на DeepSeek, 11.08.2026
Задача одна и та же: из папки `ollama_orchestro` спросить, где решается, что
ответ дописан до конца, дав в вопросе **только** `ui.html` — то есть заведомо
не тот файл. Правильный ответ (`waitForAnswer` в `ask.mjs`) достижим только
через инструменты.
| Прогон | Шагов | Что вышло |
|---|---|---|
| 1. приставка только в начале сообщения | 1 | **запросов нет.** «Точный код в серверном файле» и три догадки |
| 2. + напоминание вплотную к вопросу | 4 | нашёл, но 2 круга ушли на угадывание имён файлов |
| 3. + дерево папки в первом сообщении | **2** | запросил `ask.mjs` сразу, ответил по существу |
Два вывода, оба стоили живого прогона:
- **Инструкция в начале сообщения не работает.** Между приставкой и вопросом
легли 22 КБ файла, и DeepSeek инструменты просто не заметил. Помогло
дословное повторение перед самим вопросом (`TAIL_REMINDER`).
- **Дерево папки надо давать сразу.** Без него DeepSeek угадывал имена
(`server.js`, `bridge.js` — таких в проекте нет), потом просил `LIST *.js`
(проект на `.mjs`, пусто), потом `LIST **/*`. Дерево — 600 символов против
двух кругов по минуте.
### Пульт, 11.08.2026
Сквозной прогон через страницу: вопрос без единого приложенного файла —
«какая функция решает, что ответ дописан?». 36 секунд, три шага. DeepSeek
запросил пятью блоками `FIND` сразу (`waitForAnswer`, `streaming`, `done`,
`stop_reason`, `finish_reason`), затем `NEED: ask.mjs`, и ответил точно:
`waitForAnswer`, строка 675. Журнал шагов обновлялся на странице по ходу.
## Обрезанные ответы: причина найдена и устранена в мосте (11.08.2026)
В третьем прогоне ответ оборвался посреди блока кода на `// Бы`. Оказалось,
это не «конец определён раньше времени» и не кнопка `Continue`.
**Браузер работал в фоне.** У неактивного окна страница не отрисовывается, а
DeepSeek рисует блоки кода лениво: пока блок не нарисован, в DOM от него
лежат первые две строки, а остального нет вовсе — его не достать ни
`innerText`, ни `textContent`. Прокрутка не помогает (проверено проходом по
всему ответу шагами: ни одного лишнего символа).
Помогает единственное — заставить браузер отрисовать страницу, и через CDP
это делает `Page.captureScreenshot`. Замеры на живых ответах:
| | до отрисовки | после |
|---|---|---|
| ответ про TaskQueue | 2068 | 24 362 |
| ответ про EventBus | 3136 | 30 817 |
| ответ про Router (уже с правкой) | 258 | **32 045** |
Правка в `ollama_orchestro/ask.mjs`: `revealAnswer()` перед съёмом текста
делает дешёвый скриншот (jpeg, качество 1) и перечитывает ответ, до трёх раз,
пока текст растёт. Картинка выбрасывается.
Вдобавок `browser-launch.ps1` запускает браузер развёрнутым и с запретом
фонового режима (`--disable-backgrounding-occluded-windows`,
`--disable-renderer-backgrounding`, `--disable-background-timer-throttling`,
`--disable-features=CalculateNativeWinOcclusion`). Это не замена скриншоту:
свёрнутое окно Windows не рисует вообще.
Вторая причина обрезания, найденная там же, — кнопка **Continue**: DeepSeek
режет длинный ответ по своему лимиту и предлагает дописать, а генерация при
этом честно завершается. Теперь `waitForAnswer` жмёт её и ждёт дальше;
продолжение уходит в тот же блок ответа. Сквозная проверка — ответ на 47 414
символов с целыми кодом и тестами.
Обе правки в `ollama_orchestro`, deepwork получает их даром — мост вызывается
подпроцессом.