Expose owned C breakpoints through shared MCP session
This commit is contained in:
+3
-2
@@ -280,12 +280,13 @@ Quick wins:
|
|||||||
## Прочий backlog
|
## Прочий backlog
|
||||||
|
|
||||||
- [ ] **Довести MCP C-уровня до полного безопасного контракта.** Первый
|
- [ ] **Довести MCP C-уровня до полного безопасного контракта.** Первый
|
||||||
stdio-адаптер с 26 инструментами использует общую sdbg-сессию и
|
stdio-адаптер с 27 инструментами использует общую sdbg-сессию и
|
||||||
owner ID личных точек. Полный план и матрица всех 30 raw-возможностей —
|
owner ID личных точек. Полный план и матрица всех 30 raw-возможностей —
|
||||||
[mcp-convergence-plan.md](mcp-convergence-plan.md).
|
[mcp-convergence-plan.md](mcp-convergence-plan.md).
|
||||||
control lease для DAP/MCP и cleanup точек по истечении heartbeat
|
control lease для DAP/MCP и cleanup точек по истечении heartbeat
|
||||||
проверены живым прогоном. Привязка RPC к session/build/generation и
|
проверены живым прогоном. Привязка RPC к session/build/generation и
|
||||||
экранные API и одиночный `press_key` уже добавлены. Далее очередь
|
экранные API, одиночный `press_key` и читающий список C-точек уже
|
||||||
|
добавлены. Далее очередь
|
||||||
строкового ввода, управляемые поля портов и мышь, затем доказанные
|
строкового ввода, управляемые поля портов и мышь, затем доказанные
|
||||||
watchpoints, запись typed values, disassemble_src и batch-загрузка
|
watchpoints, запись typed values, disassemble_src и batch-загрузка
|
||||||
точек. Raw `mame_mcp.py` с `-plugin mamebridge` не подключать к тому же
|
точек. Raw `mame_mcp.py` с `-plugin mamebridge` не подключать к тому же
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ stdio MCP-адаптер C-уровня. Эксклюзивное владени
|
|||||||
проверяет сигнатуру entry и запускает session server. Закрытие DAP
|
проверяет сигнатуру entry и запускает session server. Закрытие DAP
|
||||||
завершает только созданные им server/MAME, удаляет временный каталог и
|
завершает только созданные им server/MAME, удаляет временный каталог и
|
||||||
свой Unix socket (с проверкой inode, чтобы не удалить новый сеанс).
|
свой Unix socket (с проверкой inode, чтобы не удалить новый сеанс).
|
||||||
- `sdbg_mcp.py` предоставляет 26 инструментов через официальный MCP SDK
|
- `sdbg_mcp.py` предоставляет 27 инструментов через официальный MCP SDK
|
||||||
2.x поверх того же session server: статус, C-позиция, регистры, logical
|
2.x поверх того же session server: статус, C-позиция, регистры, logical
|
||||||
memory, переменные, события/логи, личные точки и команды исполнения.
|
memory, переменные, события/логи, личные точки и команды исполнения.
|
||||||
MCP-точки имеют owner ID и не могут удалить точки VS Code; при обычном
|
MCP-точки имеют owner ID и не могут удалить точки VS Code; при обычном
|
||||||
@@ -136,7 +136,7 @@ stdio MCP-адаптер C-уровня. Эксклюзивное владени
|
|||||||
шага после клавиши.
|
шага после клавиши.
|
||||||
Повторный запуск на том же фиксированном socket после прежнего stale-файла
|
Повторный запуск на том же фиксированном socket после прежнего stale-файла
|
||||||
прошёл; после Stop путь удалён.
|
прошёл; после Stop путь удалён.
|
||||||
В автономном режиме доступны 28 инструментов: официальный MCP-клиент
|
В автономном режиме доступны 29 инструментов: официальный MCP-клиент
|
||||||
запустил `hello`, ввёл `x` через публичный `press_key` во время `getchar()`
|
запустил `hello`, ввёл `x` через публичный `press_key` во время `getchar()`
|
||||||
и остановился на следующей C-строке. Остальные raw-возможности ввода
|
и остановился на следующей C-строке. Остальные raw-возможности ввода
|
||||||
перечислены в [матрице](mcp-capability-matrix.md).
|
перечислены в [матрице](mcp-capability-matrix.md).
|
||||||
@@ -294,6 +294,7 @@ commit `b0c4527c2edb1ee177fd09b3c412b65b385bf35b`:
|
|||||||
| RPC identity и экран C-MCP, 2026-09-17 | Запросы после handshake привязаны к session/build; мутации сверяют generation. 25 C-инструментов: в живом DAP+MCP-прогоне проверены raw program/logical memory, VRAM/share, порты, пиксели и PNG; при running `hello` MCP снял кадр с `Press any key to exit...`, затем `x` завершил шаг через `getchar()` |
|
| RPC identity и экран C-MCP, 2026-09-17 | Запросы после handshake привязаны к session/build; мутации сверяют generation. 25 C-инструментов: в живом DAP+MCP-прогоне проверены raw program/logical memory, VRAM/share, порты, пиксели и PNG; при running `hello` MCP снял кадр с `Press any key to exit...`, затем `x` завершил шаг через `getchar()` |
|
||||||
| Автономный MCP, 2026-09-17 | `start_session` ответил за 5–7 мс, затем официальный MCP-клиент дождался DSS→`hello.exe`→`main`; DAP attach увидел тот же PC, disconnect не завершил MAME. Повторный start отклонён, stop удалил socket и процесс MAME. Codex/Claude UI ещё не проверены |
|
| Автономный MCP, 2026-09-17 | `start_session` ответил за 5–7 мс, затем официальный MCP-клиент дождался DSS→`hello.exe`→`main`; DAP attach увидел тот же PC, disconnect не завершил MAME. Повторный start отклонён, stop удалил socket и процесс MAME. Codex/Claude UI ещё не проверены |
|
||||||
| Публичный MCP `press_key`, 2026-09-17 | В автономном видимом MAME официальный MCP-клиент поставил C-точку перед `getchar()`, продолжил `hello`, ввёл `x` через `press_key` и остановился на следующей строке 63. В автономном режиме доступны 28 инструментов; строковый ввод и мышь остаются в плане |
|
| Публичный MCP `press_key`, 2026-09-17 | В автономном видимом MAME официальный MCP-клиент поставил C-точку перед `getchar()`, продолжил `hello`, ввёл `x` через `press_key` и остановился на следующей строке 63. В автономном режиме доступны 28 инструментов; строковый ввод и мышь остаются в плане |
|
||||||
|
| Список C-точек MCP, 2026-09-17 | В автономном видимом MAME MCP увидел собственную точку перед `getchar()` с owner и адресом; `list_breakpoints` также отдаёт guard-условие банка из bridge, если оно есть. Всего 29 автономных инструментов; вручную созданные debugger-точки пока не перечисляются |
|
||||||
| F10 через `getchar()`, 2026-09-15 | Асинхронный `next` ответил за 21,49 мс; при ручном `x` в окне MAME `hello.c:62` → `:63`, возврат `DE.low=0x78`; обе клавиатуры включены в изолированном cfg |
|
| F10 через `getchar()`, 2026-09-15 | Асинхронный `next` ответил за 21,49 мс; при ручном `x` в окне MAME `hello.c:62` → `:63`, возврат `DE.low=0x78`; обе клавиатуры включены в изолированном cfg |
|
||||||
| VS Code TaskProvider Build, 2026-09-15 | Одиннадцать Node-проверок: выбор Makefile рядом с пакетом/в `build/`, команда make с Python shim, SDCC matcher, успешный и неуспешный код задачи, pyenv вне workspace; реальный make в `tests/hello` прошёл, VS Code показал Build → MAME/DSS → `main` |
|
| VS Code TaskProvider Build, 2026-09-15 | Одиннадцать Node-проверок: выбор Makefile рядом с пакетом/в `build/`, команда make с Python shim, SDCC matcher, успешный и неуспешный код задачи, pyenv вне workspace; реальный make в `tests/hello` прошёл, VS Code показал Build → MAME/DSS → `main` |
|
||||||
| Ошибочная debug-сборка, 2026-09-15 | Изолированный `fault.c` вернул код 2 от make, `file:1: error 20` сохранился, Python traceback удалён; MAME не участвует |
|
| Ошибочная debug-сборка, 2026-09-15 | Изолированный `fault.c` вернул код 2 от make, `file:1: error 20` сохранился, Python traceback удалён; MAME не участвует |
|
||||||
|
|||||||
@@ -536,7 +536,7 @@ console_log и загрузка набора точек. Возвращать bu
|
|||||||
в арбитраж, а не обходят его.
|
в арбитраж, а не обходят его.
|
||||||
|
|
||||||
Первый stdio MCP-адаптер уже работает через общий session server и отдаёт
|
Первый stdio MCP-адаптер уже работает через общий session server и отдаёт
|
||||||
26 проверенных C-инструментов (28 при автономном start/stop). У личных MCP-точек owner ID; удаление чужой
|
27 проверенных C-инструментов (29 при автономном start/stop). У личных MCP-точек owner ID; удаление чужой
|
||||||
DAP-точки отклоняется. Совместный живой прогон подтвердил чтение состояния,
|
DAP-точки отклоняется. Совместный живой прогон подтвердил чтение состояния,
|
||||||
точки и последующее срабатывание DAP-точки. Добавлен control lease для
|
точки и последующее срабатывание DAP-точки. Добавлен control lease для
|
||||||
команд CPU и ввода; передача MCP→DAP и cleanup точки владельца без heartbeat
|
команд CPU и ввода; передача MCP→DAP и cleanup точки владельца без heartbeat
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
| `write_memory` | — | Нужны lease, проверка адреса/банка и инвалидация C-карты |
|
| `write_memory` | — | Нужны lease, проверка адреса/банка и инвалидация C-карты |
|
||||||
| `set_breakpoint` | `set_line_breakpoint`, `set_function_breakpoint` | Только проверенные C-адреса; произвольный raw-адрес и условие ещё не покрыты |
|
| `set_breakpoint` | `set_line_breakpoint`, `set_function_breakpoint` | Только проверенные C-адреса; произвольный raw-адрес и условие ещё не покрыты |
|
||||||
| `clear_breakpoint` | `clear_breakpoint`, `clear_my_breakpoints` | Только логические ID текущего владельца, не MAME ID |
|
| `clear_breakpoint` | `clear_breakpoint`, `clear_my_breakpoints` | Только логические ID текущего владельца, не MAME ID |
|
||||||
| `list_breakpoints` | — | Нужен структурированный список с owner и bank guard |
|
| `list_breakpoints` | `list_breakpoints` | Проверено на `hello`: логические C-точки всех владельцев, owner, адреса/банк и фактическое guard-условие только для чтения. Точки из родного окна MAME сюда не входят |
|
||||||
| `set_watchpoint` | — | Нужны owner, space/access, bank guard и проверка PC при попадании |
|
| `set_watchpoint` | — | Нужны owner, space/access, bank guard и проверка PC при попадании |
|
||||||
| `clear_watchpoint` | — | Зависит от owner-модели watchpoint |
|
| `clear_watchpoint` | — | Зависит от owner-модели watchpoint |
|
||||||
| `step` | `step_instruction` | Один машинный шаг проверен; счётчик `count` ещё не поддержан |
|
| `step` | `step_instruction` | Один машинный шаг проверен; счётчик `count` ещё не поддержан |
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
с `--waitkey --emulated-key --screen-mcp-python` проверяет экран работающего
|
с `--waitkey --emulated-key --screen-mcp-python` проверяет экран работающего
|
||||||
`hello` во время `getchar()` и последующий ввод. Автономный
|
`hello` во время `getchar()` и последующий ввод. Автономный
|
||||||
`run_managed_mcp_probe.py` через официальный MCP-клиент проверяет
|
`run_managed_mcp_probe.py` через официальный MCP-клиент проверяет
|
||||||
`press_key("x")` и остановку на строке после `getchar()`.
|
`list_breakpoints`, `press_key("x")` и остановку на строке после `getchar()`.
|
||||||
`run_raw_mcp_hello_probe.py`
|
`run_raw_mcp_hello_probe.py`
|
||||||
проверяет отдельный raw plugin на том же приложении. Несколько процессов MAME
|
проверяет отдельный raw plugin на том же приложении. Несколько процессов MAME
|
||||||
с MCP одновременно не проверялись.
|
с MCP одновременно не проверялись.
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
`MAME.HT/plugins/mamebridge` вместе с `src/mame_mcp.py` публикует 30
|
`MAME.HT/plugins/mamebridge` вместе с `src/mame_mcp.py` публикует 30
|
||||||
raw-инструментов. Удалённый на этапе А `src/mame_bridge.lua` использовал тот
|
raw-инструментов. Удалённый на этапе А `src/mame_bridge.lua` использовал тот
|
||||||
же Python frontend, но обрабатывал только 16 команд и не отвечал при hard-stop.
|
же Python frontend, но обрабатывал только 16 команд и не отвечал при hard-stop.
|
||||||
`toolchain/sdbg_mcp.py` публикует 26 C-инструментов через общую с DAP сессию
|
`toolchain/sdbg_mcp.py` публикует 27 C-инструментов через общую с DAP сессию
|
||||||
(28 в автономном режиме с командами start/stop).
|
(29 в автономном режиме с командами start/stop).
|
||||||
**Цель — покрыть возможности всех 30 raw-инструментов в C-сессии, а не
|
**Цель — покрыть возможности всех 30 raw-инструментов в C-сессии, а не
|
||||||
механически скопировать имена и небезопасную семантику.** Полный raw MCP
|
механически скопировать имена и небезопасную семантику.** Полный raw MCP
|
||||||
остаётся отдельным режимом для задач без C-пакета и других машин MAME.
|
остаётся отдельным режимом для задач без C-пакета и других машин MAME.
|
||||||
@@ -148,6 +148,10 @@ pass-through и особенности адресов перечислены п
|
|||||||
официальный MCP-клиент ввёл `x` в ожидающий `getchar()` и попал на следующую
|
официальный MCP-клиент ввёл `x` в ожидающий `getchar()` и попал на следующую
|
||||||
C-строку. Очередь строки, другие имена физических клавиш и мышь ещё не
|
C-строку. Очередь строки, другие имена физических клавиш и мышь ещё не
|
||||||
реализованы; поэтому строка raw `press_key` покрыта частично.
|
реализованы; поэтому строка raw `press_key` покрыта частично.
|
||||||
|
Читающий `list_breakpoints` теперь показывает логические C-точки всех
|
||||||
|
владельцев, адреса и полученные от MAME условия банковской страницы;
|
||||||
|
удалять чужие точки он не позволяет. Живой MCP-прогон увидел личную точку
|
||||||
|
перед `getchar()`. Точки, вручную созданные в родном debugger, пока вне списка.
|
||||||
|
|
||||||
## Г. Сделать запуск пригодным для Codex и Claude без VS Code
|
## Г. Сделать запуск пригодным для Codex и Claude без VS Code
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -77,7 +77,8 @@ MCP-сервером MAME и удаляет socket. Закрытие самог
|
|||||||
`read_memory`, `read_program_memory`, `list_ports`, `list_shares`, `read_share`, `read_vram`,
|
`read_memory`, `read_program_memory`, `list_ports`, `list_shares`, `read_share`, `read_vram`,
|
||||||
`read_screen_pixels`, `screenshot`, `press_key`, `list_variables`, `read_variable`, `recent_events`,
|
`read_screen_pixels`, `screenshot`, `press_key`, `list_variables`, `read_variable`, `recent_events`,
|
||||||
`mame_console_tail`, `set_line_breakpoint`, `set_function_breakpoint`,
|
`mame_console_tail`, `set_line_breakpoint`, `set_function_breakpoint`,
|
||||||
`clear_breakpoint`, `clear_my_breakpoints`, `claim_control`, `release_control`,
|
`list_breakpoints`, `clear_breakpoint`, `clear_my_breakpoints`,
|
||||||
|
`claim_control`, `release_control`,
|
||||||
`continue_execution`,
|
`continue_execution`,
|
||||||
`pause_execution`, `step_instruction`, `step_source`. Ответы SDK 2.x содержат
|
`pause_execution`, `step_instruction`, `step_source`. Ответы SDK 2.x содержат
|
||||||
`structuredContent`. `step_source` принимает `into`, `over` или `out` и
|
`structuredContent`. `step_source` принимает `into`, `over` или `out` и
|
||||||
@@ -85,7 +86,7 @@ MCP-сервером MAME и удаляет socket. Закрытие самог
|
|||||||
`recent_events` с курсором `last`. Логи `SDBG_LOG` приходят как события
|
`recent_events` с курсором `last`. Логи `SDBG_LOG` приходят как события
|
||||||
`output`; журнал ограничен 1024 событиями и сообщает `first`/`last`/`lost`.
|
`output`; журнал ограничен 1024 событиями и сообщает `first`/`last`/`lost`.
|
||||||
|
|
||||||
Число 26 в attach-режиме (28 в автономном с `start_session`/`stop_session`)
|
Число 27 в attach-режиме (29 в автономном с `start_session`/`stop_session`)
|
||||||
не означает полного переноса более нового `-plugin mamebridge`:
|
не означает полного переноса более нового `-plugin mamebridge`:
|
||||||
его `src/mame_mcp.py` публикует 30 raw-инструментов, ориентированных на
|
его `src/mame_mcp.py` публикует 30 raw-инструментов, ориентированных на
|
||||||
машину MAME. Здесь инструменты сгруппированы по операциям C-сессии, а
|
машину MAME. Здесь инструменты сгруппированы по операциям C-сессии, а
|
||||||
@@ -139,6 +140,13 @@ MCP-прогон ввёл `x` в ожидающий `getchar()` и дошёл д
|
|||||||
его. Набор строки, произвольные имена клавиш, мышь и порты ввода ещё не
|
его. Набор строки, произвольные имена клавиш, мышь и порты ввода ещё не
|
||||||
публичны в C-MCP.
|
публичны в C-MCP.
|
||||||
|
|
||||||
|
`list_breakpoints` возвращает логические ID, владельца (`mcp:…`, `dap` или
|
||||||
|
`build`), вид C-точки, адреса и фактические условия банковской страницы для
|
||||||
|
всех точек общей сессии. Это чтение доступно и при running CPU. Точки,
|
||||||
|
поставленные вручную в родном debugger MAME, в этот список не входят.
|
||||||
|
Инструмент не даёт удалить чужую точку: `clear_breakpoint` по-прежнему
|
||||||
|
проверяет owner.
|
||||||
|
|
||||||
После первого `session_status` клиент отправляет session ID и build ID в
|
После первого `session_status` клиент отправляет session ID и build ID в
|
||||||
каждом RPC; команды, меняющие CPU/точки, дополнительно сверяют generation.
|
каждом RPC; команды, меняющие CPU/точки, дополнительно сверяют generation.
|
||||||
При замене сеанса или устаревшей generation команда отклоняется без
|
При замене сеанса или устаревшей generation команда отклоняется без
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ launcher задаёт этому процессу `SDL_NO_SIGNAL_HANDLERS=1`, е
|
|||||||
фиксированный `"socket": "/tmp/sprinter-sdbg-hello.sock"` и подключите
|
фиксированный `"socket": "/tmp/sprinter-sdbg-hello.sock"` и подключите
|
||||||
`toolchain/sdbg_mcp.py` после остановки в `main`. MCP использует тот же
|
`toolchain/sdbg_mcp.py` после остановки в `main`. MCP использует тот же
|
||||||
session server; его личные точки не заменяют точки VS Code. Порядок запуска,
|
session server; его личные точки не заменяют точки VS Code. Порядок запуска,
|
||||||
26 доступных инструментов и ограничения совместного управления описаны в
|
27 доступных инструментов и ограничения совместного управления описаны в
|
||||||
[руководстве по MCP](sdbg-mcp.md).
|
[руководстве по MCP](sdbg-mcp.md).
|
||||||
|
|
||||||
Команда палитры `Sprinter: Build Active Project` собирает приложение по
|
Команда палитры `Sprinter: Build Active Project` собирает приложение по
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ async def probe() -> None:
|
|||||||
|
|
||||||
names = {tool.name for tool in (await client.list_tools()).tools}
|
names = {tool.name for tool in (await client.list_tools()).tools}
|
||||||
if not {'start_session', 'stop_session', 'where', 'read_variable',
|
if not {'start_session', 'stop_session', 'where', 'read_variable',
|
||||||
'press_key'} <= names:
|
'press_key', 'list_breakpoints'} <= names:
|
||||||
raise RuntimeError('Нет инструментов автономного запуска')
|
raise RuntimeError('Нет инструментов автономного запуска')
|
||||||
initial = await call('session_status')
|
initial = await call('session_status')
|
||||||
if initial['phase'] != 'idle':
|
if initial['phase'] != 'idle':
|
||||||
@@ -99,6 +99,12 @@ async def probe() -> None:
|
|||||||
raise RuntimeError('DAP disconnect завершил MCP-owned MAME')
|
raise RuntimeError('DAP disconnect завершил MCP-owned MAME')
|
||||||
source = str(ROOT / 'tests/hello/hello.c')
|
source = str(ROOT / 'tests/hello/hello.c')
|
||||||
before = await call('set_line_breakpoint', {'file': source, 'line': 62})
|
before = await call('set_line_breakpoint', {'file': source, 'line': 62})
|
||||||
|
points = await call('list_breakpoints')
|
||||||
|
active = [item for item in points['breakpoints']
|
||||||
|
if item['id'] == before['id']]
|
||||||
|
if len(active) != 1 or not active[0]['owner'].startswith('mcp:') or \
|
||||||
|
not any(loc.get('line') == 62 for loc in active[0]['locations']):
|
||||||
|
raise RuntimeError('Личная точка не отражена в списке: ' + repr(points))
|
||||||
await call('continue_execution')
|
await call('continue_execution')
|
||||||
deadline = time.monotonic() + 10
|
deadline = time.monotonic() + 10
|
||||||
while time.monotonic() < deadline:
|
while time.monotonic() < deadline:
|
||||||
@@ -115,6 +121,13 @@ async def probe() -> None:
|
|||||||
after = await call('set_line_breakpoint', {'file': source, 'line': 63})
|
after = await call('set_line_breakpoint', {'file': source, 'line': 63})
|
||||||
await call('continue_execution')
|
await call('continue_execution')
|
||||||
await asyncio.sleep(.4)
|
await asyncio.sleep(.4)
|
||||||
|
running = await call('session_status')
|
||||||
|
if not running['running']:
|
||||||
|
raise RuntimeError('CPU не ожидает getchar перед вводом')
|
||||||
|
while_running = await call('list_breakpoints')
|
||||||
|
if not any(item['id'] == after['id']
|
||||||
|
for item in while_running['breakpoints']):
|
||||||
|
raise RuntimeError('Не прочитана точка при running CPU')
|
||||||
key = await call('press_key', {'key': 'x', 'frames': 3})
|
key = await call('press_key', {'key': 'x', 'frames': 3})
|
||||||
deadline = time.monotonic() + 10
|
deadline = time.monotonic() + 10
|
||||||
while time.monotonic() < deadline:
|
while time.monotonic() < deadline:
|
||||||
|
|||||||
@@ -225,6 +225,12 @@ class ServerTests(unittest.TestCase):
|
|||||||
mine = controller.call('break_line',
|
mine = controller.call('break_line',
|
||||||
{'file': '/src/main.c', 'line': 3, 'owner': 'mcp:a'})
|
{'file': '/src/main.c', 'line': 3, 'owner': 'mcp:a'})
|
||||||
other = controller.call('break_function', {'name': 'main', 'owner': 'mcp:b'})
|
other = controller.call('break_function', {'name': 'main', 'owner': 'mcp:b'})
|
||||||
|
controller.breakpoint_info[mine['id']]['guard_conditions'] = ['PG3 == 0x12']
|
||||||
|
listed = controller.call('list_breakpoints', {})['breakpoints']
|
||||||
|
self.assertEqual([(item['id'], item['owner']) for item in listed],
|
||||||
|
[(mine['id'], 'mcp:a'), (other['id'], 'mcp:b')])
|
||||||
|
self.assertEqual(listed[0]['locations'][0]['link_address'], 0x8100)
|
||||||
|
self.assertEqual(listed[0]['locations'][0]['bank_guard'], 'PG3 == 0x12')
|
||||||
self.assertTrue(controller._has_stop_breakpoint(session.where()))
|
self.assertTrue(controller._has_stop_breakpoint(session.where()))
|
||||||
with self.assertRaisesRegex(SessionError, 'не принадлежит'):
|
with self.assertRaisesRegex(SessionError, 'не принадлежит'):
|
||||||
controller.call('clear_breakpoint', {'id': other['id'], 'owner': 'mcp:a'})
|
controller.call('clear_breakpoint', {'id': other['id'], 'owner': 'mcp:a'})
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ class McpSession:
|
|||||||
def screenshot(self):
|
def screenshot(self):
|
||||||
return self.call('screenshot')
|
return self.call('screenshot')
|
||||||
|
|
||||||
|
def list_breakpoints(self):
|
||||||
|
return self.call('list_breakpoints')
|
||||||
|
|
||||||
def press_key(self, key: str, frames: int = 3):
|
def press_key(self, key: str, frames: int = 3):
|
||||||
"""Один физический PC-key с отпусканием даже при stop/ошибке."""
|
"""Один физический PC-key с отпусканием даже при stop/ошибке."""
|
||||||
from mame_interactive import SHIFT_KEY, resolve
|
from mame_interactive import SHIFT_KEY, resolve
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class SessionController:
|
|||||||
'kind': 'log', 'message': macro['message'],
|
'kind': 'log', 'message': macro['message'],
|
||||||
'condition': macro['condition'], 'module': macro['module'],
|
'condition': macro['condition'], 'module': macro['module'],
|
||||||
'tag': macro['tag'], 'locations': item['locations'], 'hits': 0,
|
'tag': macro['tag'], 'locations': item['locations'], 'hits': 0,
|
||||||
|
'owner': 'build', 'guard_conditions': item['conditions'],
|
||||||
}
|
}
|
||||||
if created:
|
if created:
|
||||||
self.session.activate_breakpoints()
|
self.session.activate_breakpoints()
|
||||||
@@ -233,6 +234,21 @@ class SessionController:
|
|||||||
'event_sequence': self.sequence, 'session_id': self.session_id,
|
'event_sequence': self.sequence, 'session_id': self.session_id,
|
||||||
'control_owner': self.control_owner if
|
'control_owner': self.control_owner if
|
||||||
self.control_deadline > time.monotonic() else None}
|
self.control_deadline > time.monotonic() else None}
|
||||||
|
if method == 'list_breakpoints':
|
||||||
|
breakpoints = []
|
||||||
|
for identifier, info in sorted(self.breakpoint_info.items()):
|
||||||
|
guards = info.get('guard_conditions', [])
|
||||||
|
locations = []
|
||||||
|
for index, location in enumerate(info['locations']):
|
||||||
|
locations.append({**location,
|
||||||
|
'bank_guard': guards[index] if index < len(guards)
|
||||||
|
else ''})
|
||||||
|
breakpoints.append({'id': identifier,
|
||||||
|
'owner': info.get('owner', 'dap'),
|
||||||
|
'kind': info['kind'], 'hits': info['hits'],
|
||||||
|
'locations': locations,
|
||||||
|
'tag': info.get('tag')})
|
||||||
|
return {'breakpoints': breakpoints}
|
||||||
if method == 'snapshot':
|
if method == 'snapshot':
|
||||||
return self.session.bridge.request('snapshot')
|
return self.session.bridge.request('snapshot')
|
||||||
if method == 'mame_console_tail':
|
if method == 'mame_console_tail':
|
||||||
@@ -322,7 +338,8 @@ class SessionController:
|
|||||||
item = self.session.break_line(arguments['file'], int(arguments['line']))
|
item = self.session.break_line(arguments['file'], int(arguments['line']))
|
||||||
self.breakpoint_info[item['id']] = {
|
self.breakpoint_info[item['id']] = {
|
||||||
'kind': 'stop', 'message': None, 'locations': item['locations'],
|
'kind': 'stop', 'message': None, 'locations': item['locations'],
|
||||||
'hits': 0, 'owner': owner}
|
'hits': 0, 'owner': owner,
|
||||||
|
'guard_conditions': item['conditions']}
|
||||||
return item
|
return item
|
||||||
if method == 'break_function':
|
if method == 'break_function':
|
||||||
owner = self._owner(arguments)
|
owner = self._owner(arguments)
|
||||||
@@ -330,7 +347,8 @@ class SessionController:
|
|||||||
item = self.session.break_function(arguments['name'])
|
item = self.session.break_function(arguments['name'])
|
||||||
self.breakpoint_info[item['id']] = {
|
self.breakpoint_info[item['id']] = {
|
||||||
'kind': 'stop', 'message': None, 'locations': item['locations'],
|
'kind': 'stop', 'message': None, 'locations': item['locations'],
|
||||||
'hits': 0, 'owner': owner}
|
'hits': 0, 'owner': owner,
|
||||||
|
'guard_conditions': item['conditions']}
|
||||||
return item
|
return item
|
||||||
if method == 'clear_breakpoint':
|
if method == 'clear_breakpoint':
|
||||||
identifier = int(arguments['id'])
|
identifier = int(arguments['id'])
|
||||||
@@ -480,6 +498,7 @@ class SessionController:
|
|||||||
self.breakpoint_info[item['id']] = {
|
self.breakpoint_info[item['id']] = {
|
||||||
'kind': kind, 'message': specification['logMessage'],
|
'kind': kind, 'message': specification['logMessage'],
|
||||||
'locations': item['locations'], 'hits': 0,
|
'locations': item['locations'], 'hits': 0,
|
||||||
|
'owner': 'dap', 'guard_conditions': item['conditions'],
|
||||||
}
|
}
|
||||||
results.append({'line': line, 'verified': True,
|
results.append({'line': line, 'verified': True,
|
||||||
'logMessage': specification['logMessage'], **item})
|
'logMessage': specification['logMessage'], **item})
|
||||||
@@ -513,7 +532,9 @@ class SessionController:
|
|||||||
item = self.session.break_function(name, enabled=False)
|
item = self.session.break_function(name, enabled=False)
|
||||||
created.append(item['id'])
|
created.append(item['id'])
|
||||||
self.breakpoint_info[item['id']] = {
|
self.breakpoint_info[item['id']] = {
|
||||||
'kind': 'stop', 'message': None, 'locations': item['locations'], 'hits': 0}
|
'kind': 'stop', 'message': None, 'locations': item['locations'],
|
||||||
|
'hits': 0, 'owner': 'dap',
|
||||||
|
'guard_conditions': item['conditions']}
|
||||||
results.append({'name': name, 'verified': True, **item})
|
results.append({'name': name, 'verified': True, **item})
|
||||||
except BaseException:
|
except BaseException:
|
||||||
for identifier in created:
|
for identifier in created:
|
||||||
|
|||||||
@@ -143,6 +143,11 @@ def make_server(client: McpSession | ManagedMcpSession,
|
|||||||
"""Поставить личную точку по имени C-функции."""
|
"""Поставить личную точку по имени C-функции."""
|
||||||
return client.break_function(name)
|
return client.break_function(name)
|
||||||
|
|
||||||
|
@tool(annotations=reading)
|
||||||
|
def list_breakpoints() -> dict[str, Any]:
|
||||||
|
"""Читать C-точки всех владельцев с логическими ID и условиями банка."""
|
||||||
|
return client.list_breakpoints()
|
||||||
|
|
||||||
@tool()
|
@tool()
|
||||||
def clear_breakpoint(identifier: int) -> dict[str, Any]:
|
def clear_breakpoint(identifier: int) -> dict[str, Any]:
|
||||||
"""Удалить свою логическую точку по ID; чужие точки отклоняются."""
|
"""Удалить свою логическую точку по ID; чужие точки отклоняются."""
|
||||||
|
|||||||
Reference in New Issue
Block a user