Транспорт: был дочерний процесс 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>
12 KiB
deepwork
Цикл с инструментами поверх сервиса 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):
{ "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 получает их даром — мост вызывается
подпроцессом.