Перейти к основному содержимому

Node Inspect Debugger

Отладка Node.js через --inspect + Chrome DevTools Protocol CLI.

Метаданные навыка​

ИсточникВстроенный (устанавливается по умолчанию)
Путьskills/software-development/node-inspect-debugger
Версия1.0.0
АвторVibeOS
ЛицензияMIT
Платформыlinux, macos, windows
Тегиdebugging, nodejs, node-inspect, cdp, breakpoints, ui-tui
Связанные навыкиsystematic-debugging, python-debugpy, debugging-vibeos-tui-commands

Справочник: полный SKILL.md​

к сведению

Ниже приведено полное описание навыка, которое VibeOS загружает при его активации. Это те инструкции, которые видит агент, когда навык активен.

Node.js Inspect Debugger

Обзор​

Когда console.log недостаточно, управляйте встроенным инспектором V8 в Node программно из терминала. Вы получаете реальные точки останова, шаг с заходом/через/выходом, просмотр стека вызовов, дампы локальных/замыкающих областей видимости и вычисление произвольных выражений в приостановленном фрейме.

Два инструмента, выбирайте:

  • node inspect — встроенный, без установки, REPL в CLI. Лучше всего для быстрых проверок.
  • ndb / CDP через chrome-remote-interface — скриптуемый из Node/Python; лучше всего, когда нужно автоматизировать много точек останова, собрать состояние между запусками или отлаживать неинтерактивно из цикла агента.

Предпочитайте node inspect в первую очередь. Он всегда доступен, и REPL работает быстро.

Когда использовать​

  • Тест Node падает, и нужно увидеть промежуточное состояние
  • ui-tui падает или ведёт себя неправильно, и нужно проверить состояние React/Ink до рендеринга
  • Дочерние процессы tui_gateway (_SlashWorker, PTY bridge workers) работают некорректно
  • Нужно проверить значение в замыкании, до которого console.log не может добраться без патчинга
  • Производительность: подключиться к работающему процессу, чтобы захватить CPU-профиль или снимок кучи

Не используйте для: того, что console.log решает меньше чем за минуту. Отладка с точками останова более ресурсоёмка; используйте её, когда выгода реальна.

Краткий справочник: REPL node inspect​

Запуск с остановкой на первой строке:

node inspect path/to/script.js
# или с tsx
node --inspect-brk $(which tsx) path/to/script.ts

Приглашение debug> принимает:

КомандаДействие
c или contпродолжить
n или nextшаг через
s или stepшаг с заходом
o или outшаг с выходом
pauseприостановить выполняющийся код
sb('file.js', 42)установить точку останова в file.js, строка 42
sb(42)установить точку останова на строке 42 текущего файла
sb('functionName')остановиться при вызове функции
cb('file.js', 42)удалить точку останова
breakpointsсписок всех точек останова
btbacktrace (стек вызовов)
list(5)показать 5 строк исходного кода вокруг текущей позиции
watch('expr')вычислять выражение при каждой остановке
watchersпоказать отслеживаемые выражения
replвойти в REPL в текущей области видимости (Ctrl+C для выхода из REPL)
exec exprвычислить выражение один раз
restartперезапустить скрипт
killзавершить скрипт
.exitвыйти из отладчика

В подрежиме repl: введите любое JS-выражение, включая доступ к локальным переменным и переменным замыкания. Ctrl+C возвращает в debug>.

Подключение к работающему процессу​

Когда процесс уже запущен (например, долгоживущий dev-сервер или шлюз TUI):

# 1. Отправить SIGUSR1, чтобы включить инспектор в существующем процессе
kill -SIGUSR1 <pid>
# Node выведет: Debugger listening on ws://127.0.0.1:9229/<uuid>

# 2. Подключить отладчик CLI
node inspect -p <pid>
# или по URL
node inspect ws://127.0.0.1:9229/<uuid>

Чтобы запустить процесс с инспектором с самого начала:

node --inspect script.js           # слушать на 127.0.0.1:9229, продолжить выполнение
node --inspect-brk script.js # слушать И остановиться на первой строке
node --inspect=0.0.0.0:9230 script.js # пользовательский хост:порт

Для TypeScript через tsx:

node --inspect-brk --import tsx script.ts
# или старая версия tsx
node --inspect-brk -r tsx/cjs script.ts

Программный CDP (скриптинг из терминала)​

Когда нужно автоматизировать — установить много точек останова, захватить состояние области видимости, написать скрипт для воспроизведения — используйте chrome-remote-interface:

npm i -g chrome-remote-interface        # или локально в проекте
# Запустите вашу цель:
node --inspect-brk=9229 target.js &

Скрипт-драйвер (сохраните как /tmp/cdp-debug.js):

const CDP = require('chrome-remote-interface');

(async () => {
const client = await CDP({ port: 9229 });
const { Debugger, Runtime } = client;

Debugger.paused(async ({ callFrames, reason }) => {
const top = callFrames[0];
console.log(`PAUSED: ${reason} @ ${top.url}:${top.location.lineNumber + 1}`);

// Обход областей видимости для локальных переменных
for (const scope of top.scopeChain) {
if (scope.type === 'local' || scope.type === 'closure') {
const { result } = await Runtime.getProperties({
objectId: scope.object.objectId,
ownProperties: true,
});
for (const p of result) {
console.log(` ${scope.type}.${p.name} =`, p.value?.value ?? p.value?.description);
}
}
}

// Вычислить выражение в приостановленном фрейме
const { result } = await Debugger.evaluateOnCallFrame({
callFrameId: top.callFrameId,
expression: 'typeof state !== "undefined" ? JSON.stringify(state) : "n/a"',
});
console.log('state =', result.value ?? result.description);

await Debugger.resume();
});

await Runtime.enable();
await Debugger.enable();

// Установить точку останова по URL-регулярному выражению + строка
await Debugger.setBreakpointByUrl({
urlRegex: '.*app\\.tsx$',
lineNumber: 119, // с нуля
columnNumber: 0,
});

await Runtime.runIfWaitingForDebugger();
})();

Запустите:

node /tmp/cdp-debug.js

Примечание для VibeOS: chrome-remote-interface НЕТ в ui-tui/package.json. Установите его во временную директорию, если не хотите засорять проект:

mkdir -p /tmp/cdp-tools && cd /tmp/cdp-tools && npm i chrome-remote-interface
NODE_PATH=/tmp/cdp-tools/node_modules node /tmp/cdp-debug.js

Отладка VibeOS ui-tui​

TUI построен на Ink + tsx. Два распространённых сценария:

Отладка одного компонента Ink в режиме разработки​

В ui-tui/package.json есть npm run dev (tsx --watch). Добавьте --inspect-brk, запустив tsx напрямую:

cd /home/bb/vibeos-agent/ui-tui
npm run build # собрать dist/ один раз, чтобы транспиляция не требовалась при первой загрузке
node --inspect-brk dist/entry.js
# В другом терминале:
node inspect -p <pid node>

Затем внутри debug>:

sb('dist/app.js', 220)     # или там, где подозреваемый рендер
cont

Когда остановится, repl → проверьте props, ссылки на состояние, значения обработчиков useInput и т.д.

Отладка работающего vibeos --tui​

TUI запускает Node из Python CLI. Самый простой путь:

# 1. Запустить TUI
vibeos --tui &
TUI_PID=$(pgrep -f 'ui-tui/dist/entry' | head -1)

# 2. Включить инспектор на этом PID Node
kill -SIGUSR1 "$TUI_PID"

# 3. Найти WS URL
curl -s http://127.0.0.1:9229/json/list | jq -r '.[0].webSocketDebuggerUrl'

# 4. Подключиться
node inspect ws://127.0.0.1:9229/<uuid>

Взаимодействие с TUI (ввод в его окне) продолжает выполнение; ваш отладчик может остановить его на точке останова по любому sb(...).

Отладка _SlashWorker / PTY дочерних процессов​

Это Python, а не Node — используйте навык python-debugpy для них. Только части на Node (Ink UI, клиент tui_gateway, тесты tsx-run в ui-tui/) используют этот навык.

Запуск тестов Vitest под отладчиком​

cd /home/bb/vibeos-agent/ui-tui
# Запустить один тестовый файл с остановкой на входе
node --inspect-brk ./node_modules/vitest/vitest.mjs run --no-file-parallelism src/app/foo.test.tsx

В другом терминале: node inspect -p &lt;pid&gt;, затем sb('src/app/foo.tsx', 42), cont.

Используйте --no-file-parallelism (vitest) или --runInBand (jest), чтобы существовал только один воркер — отладка пула болезненна.

Снимки кучи и CPU-профили (неинтерактивные)​

В драйвере CDP выше замените Debugger на HeapProfiler / Profiler:

// CPU-профиль на 5 секунд
await client.Profiler.enable();
await client.Profiler.start();
await new Promise(r => setTimeout(r, 5000));
const { profile } = await client.Profiler.stop();
require('fs').writeFileSync('/tmp/cpu.cpuprofile', JSON.stringify(profile));
// Откройте /tmp/cpu.cpuprofile в Chrome DevTools → вкладка Performance
// Снимок кучи
await client.HeapProfiler.enable();
const chunks = [];
client.HeapProfiler.addHeapSnapshotChunk(({ chunk }) => chunks.push(chunk));
await client.HeapProfiler.takeHeapSnapshot({ reportProgress: false });
require('fs').writeFileSync('/tmp/heap.heapsnapshot', chunks.join(''));

Частые ошибки​

  1. Неверные номера строк в исходном TS-коде. Точки останова срабатывают на скомпилированном JS, а не на .ts. Либо (а) ставьте точки останова в собранном dist/*.js, либо (б) включите sourcemaps (node --enable-source-maps) и используйте sb('src/app.tsx', N) — но только с CDP-клиентами, которые поддерживают sourcemaps. CLI node inspect не поддерживает.

  2. --inspect vs --inspect-brk. --inspect запускает инспектор, но не останавливается; ваш скрипт может проскочить мимо первой точки останова, если вы подключитесь слишком поздно. Используйте --inspect-brk, когда нужно установить точки останова до выполнения любого кода.

  3. Конфликты портов. По умолчанию используется 9229. Если несколько процессов Node используют инспектор, передайте --inspect=0 (случайный порт) и прочитайте фактический URL из /json/list:

    curl -s http://127.0.0.1:9229/json/list   # список всех целей для инспектирования на хосте
  4. Дочерние процессы. --inspect на родительском процессе НЕ инспектирует его дочерние процессы. Используйте NODE_OPTIONS='--inspect-brk' node parent.js, чтобы распространить на каждый дочерний процесс; учтите, что всем нужны уникальные порты (Node автоматически увеличивает порт, когда наследуется NODE_OPTIONS='--inspect').

  5. Фоновые завершения. Если вы нажмёте Ctrl+C в node inspect, пока цель приостановлена, цель останется приостановленной. Либо сначала выполните cont, либо явно завершите цель через kill.

  6. Запуск node inspect через терминал агента. Это PTY-совместимый REPL. В VibeOS запускайте его с terminal(pty=true) или background=true + process(action='submit', data='...'). Режим переднего плана без PTY подойдёт для одноразовых команд, но не для интерактивного пошагового выполнения.

  7. Безопасность. --inspect=0.0.0.0:9229 открывает возможность произвольного выполнения кода. Всегда привязывайтесь к 127.0.0.1 (по умолчанию), если только у вас не изолированная сеть.

Контрольный список проверки​

После настройки сессии отладки проверьте:

  • curl -s http://127.0.0.1:9229/json/list возвращает именно ту цель, которую вы ожидаете
  • Первая точка останова действительно срабатывает (если нет, вы, вероятно, пропустили --inspect-brk или подключились после завершения выполнения)
  • Просмотр исходного кода на паузе показывает правильный файл (несоответствие = проблема с sourcemaps, см. ошибку 1)
  • exec process.pid в repl возвращает PID, к которому вы хотели подключиться

Одноразовые рецепты​

«Почему эта переменная undefined на строке X?»

node --inspect-brk script.js &
node inspect -p $!
# debug>
sb('script.js', X)
cont
# остановлено. Теперь:
repl
> myVariable
> Object.keys(this)

«Какой путь вызова в эту функцию?»

debug> sb('suspectFn')
debug> cont
# остановлено на входе
debug> bt

«Эта асинхронная цепочка зависает — где?»

# Запустите с --inspect (без -brk), дайте выполниться до зависания, затем:
debug> pause
debug> bt
# Теперь вы видите застрявший фрейм