Транспорт: был дочерний процесс ask.mjs из ollama_orchestro с обменом через временные файлы, стал POST /api/chat к сервису deepseek-api. Он держит вкладку браузера и отдаёт наружу протокол Ollama, поэтому здесь про браузер больше не знают: ушли проверка существования моста, временные файлы шагов вместе с флагом --keep-temp и четыре осиротевших импорта. Главное не в транспорте. Раньше шаги 2..N уходили с флагом --new, и ход разговора помнил сам чат DeepSeek. Сервис памяти не имеет принципиально: каждый запрос он кладёт в чат целиком, затирая прошлый. Поэтому стенограмму ведём здесь — growTranscript подклеивает к отправленному тексту прошлый ответ модели между метками === ТВОЙ ПРЕДЫДУЩИЙ ОТВЕТ === и === END ===, и следующим шагом уезжает всё сразу. Так надёжнее, чем было: видно в одном месте, что именно уедет модели, а чужой потребитель, вклинившийся между нашими шагами, ничего не портит — мы всё приносим заново. Плата — вопрос растёт на длину прошлого ответа. --check теперь различает три состояния: сервис отвечает и браузер поднят; сервис жив, но браузера ещё нет; сервис не запущен. Плашка в ui.html тоже. Настройки: bridge/chat/cdpPort заменены на deepseekHost и deepseekModel. Проверено живым многошаговым прогоном: шаг 1 — 2094 символа, ответ NEED: deepwork.mjs; шаг 2 — 28 447 символов стенограммы, ответ дословно верный и по вопросу из ПЕРВОГО сообщения, то есть память шагов работает. Два шага за 17 с. --selftest пройден, --check зелёный. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
184 lines
12 KiB
Markdown
184 lines
12 KiB
Markdown
# deepwork
|
|
|
|
Цикл с инструментами поверх сервиса [deepseek-api](https://git.08h.ru/alex/deepseek-api).
|
|
|
|
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 путей. Обрезанное помечается прямо в тексте.
|
|
|
|
## Как устроено
|
|
|
|
У веб-версии DeepSeek нет API — только вкладка в браузере. Разговором с ней
|
|
занимается отдельный сервис `deepseek-api`, а нам показывает протокол Ollama.
|
|
Здесь про браузер не знают вовсе: обычный `POST /api/chat`.
|
|
|
|
**Разговор помним мы, а не DeepSeek.** Сервис без памяти: каждый запрос он
|
|
кладёт в чат целиком, затирая прошлый. Поэтому стенограмма растёт здесь —
|
|
`growTranscript` подклеивает к отправленному тексту прошлый ответ модели
|
|
(между метками `=== ТВОЙ ПРЕДЫДУЩИЙ ОТВЕТ ===` и `=== END ===`) и результаты
|
|
запросов, и следующим шагом уезжает всё сразу.
|
|
|
|
Раньше эту роль играл флаг `--new` у моста, и помнил всё сам чат. Своя
|
|
стенограмма надёжнее: видно в одном месте, что именно уедет модели, а чужой
|
|
потребитель, вклинившийся между нашими шагами, ничего не портит — мы всё
|
|
приносим заново. Плата — вопрос растёт на длину прошлого ответа; при `steps: 4`
|
|
это единицы килобайт против 22 КБ первого сообщения.
|
|
|
|
Цикл останавливается, когда запросов в ответе нет, когда кончились шаги или
|
|
когда пришёл тот же самый запрос второй раз подряд (петля).
|
|
|
|
## Настройка
|
|
|
|
`config.json` (в репозиторий не попадает, образец — `config.example.json`):
|
|
|
|
```json
|
|
{ "deepseekHost": "http://localhost:11500", "deepseekModel": "deepseek:think", "steps": 4 }
|
|
```
|
|
|
|
Без файла берётся `http://localhost:11500`.
|
|
|
|
## Проверка
|
|
|
|
```
|
|
node deepwork.mjs --selftest внутренние проверки, ничего не отправляют
|
|
node deepwork.mjs --check сервис на месте, вкладка жива
|
|
```
|
|
|
|
`--check` различает три состояния: сервис отвечает и браузер поднят; сервис
|
|
жив, но браузера ещё нет (встанет на первом же вопросе); сервис не запущен.
|
|
|
|
Запускается сервис своим `start.cmd` в папке `deepseek-api` — браузер он
|
|
поднимает сам.
|
|
|
|
## Переезд на deepseek-api (26.08.2026)
|
|
|
|
Транспорт сменился: был дочерний процесс `ask.mjs` из `ollama_orchestro`
|
|
с обменом через временные файлы, стал `POST /api/chat` к сервису
|
|
`deepseek-api`. Заодно ушли временные файлы шагов (и флаг `--keep-temp`),
|
|
проверка существования моста и запуск браузера — всё это теперь не наша забота.
|
|
|
|
Память шагов переехала из чата DeepSeek в `growTranscript` (см. «Как устроено»).
|
|
Проверено живым прогоном: шаг 1 — 2094 символа, ответ `NEED: deepwork.mjs`;
|
|
шаг 2 — 28 447 символов стенограммы, ответ дословно верный и по вопросу
|
|
из ПЕРВОГО сообщения. Два шага за 17 с.
|
|
|
|
## Что проверено
|
|
|
|
- `--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 получает их даром — мост вызывается
|
|
подпроцессом.
|