feat(mcp): integrate devbench#66
Conversation
Adds console-output reading to the RemoteControl MCP via a gated detour
on RE::ConsoleLog::VPrint -- the universal command-output sink (its caller
Print has 600+ command-handler callers: getav, getpos, etc.; addresses
verified on SE 1.5.97, AE 1.6.1170, and VR).
The detour is OFF by default and a near-no-op (one relaxed atomic load),
because ConsoleLog is flooded during cell load (~678k calls with a heavy
modlist -- an always-on hook crashed under that load). console(action=
'exec', capture='true') opens a window (clears buffer, enables capture);
action='read' returns the captured { seq, frame, text } lines and closes
it -- so callers get just that command's output (help FormIDs, getav/
getpos readouts) with no hot-path overhead and no spam eviction.
Runtime-validated (SSE): with capture='true', `player.getav health`
returned exactly "GetActorValue: Health >> 100.00" (headSeq=1); without
the flag headSeq stayed 1 (gate off, nothing captured); clean qqq exit.
Capture is installed lazily and once from RemoteControl::StartServer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add DevBenchBridge — registers CS's tools into the devbench host over its cross-plugin C-ABI, so devbench can eventually supersede CS's built-in RemoteControl MCP server. Wires the `feature` tool (list/toggle) as the first parity proof; TODOs for set/reset, inspect(shadercache), capture, abtest, and shader-recompile events. GUARDED by DEVBENCH_BRIDGE_ENABLED so CS's CONFIGURE_DEPENDS glob compiles it to an empty Install() — the build is unchanged until the devbench-api vcpkg port is wired (activation steps documented in DevBenchBridge.cpp). XSEPlugin calls the (currently no-op) Install() at kDataLoaded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vendor the devbench-api overlay port into cmake/ports/devbench-api (portfile
fetches the MIT API source from github.com/alandtse/devbench — no source copy)
so CS self-resolves it via the existing ${sourceDir}/cmake/ports overlay. Add
the dep to vcpkg.json, find_package + link DevBench::API, and define
DEVBENCH_BRIDGE_ENABLED so DevBenchBridge registers CS's tools into devbench.
Validated: CS's `feature` tool appears in devbench's registry and is callable
over both MCP and REST (list + toggle round-trip).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR replaces the in-process MCP server architecture with a DevBench host bridge. It introduces a new vcpkg port for devbench-api, integrates it conditionally via CMake options and features, implements a C-ABI-safe bridge registering openshaders tools into DevBench, refactors RemoteControl to a devbench status panel, emits shader events to DevBench, and removes the cpp-mcp submodule. ChangesDevBench Bridge & VCpkg Port Integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
No actionable suggestions for changed features. |
The toggle handler looked up the target via Feature::FindFeatureByShortName, which only matches loaded features — so once a feature was toggled off it could never be found to turn back on (confirmed live: WetnessEffects off → "unknown shortName" on re-enable). Match over the full GetFeatureList() by shortName instead, mirroring the list branch, so toggle works both directions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds devbench integration for Community Shaders tooling and extends the existing RemoteControl console tool with gated console-output capture.
Changes:
- Adds a
DevBenchBridgethat registers a basicfeaturetool with devbench through the devbench C-ABI. - Adds
ConsoleLogCaptureand wires it into the RemoteControlconsoletool for opt-in captured output reads. - Adds a
devbench-apivcpkg overlay port and build-system wiring.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
vcpkg.json |
Adds devbench-api as a dependency. |
src/XSEPlugin.cpp |
Installs the devbench bridge during kDataLoaded. |
src/Features/RemoteControl.cpp |
Installs console capture and adds console read/capture parameters. |
src/DevBenchBridge.h |
Declares the devbench bridge entry point. |
src/DevBenchBridge.cpp |
Implements devbench feature tool registration and handling. |
src/ConsoleLogCapture.h |
Declares gated console-log capture APIs. |
src/ConsoleLogCapture.cpp |
Implements the ConsoleLog::VPrint detour and capture buffer. |
CMakeLists.txt |
Finds and links DevBench::API and defines bridge enablement. |
cmake/ports/devbench-api/vcpkg.json |
Defines the overlay port metadata. |
cmake/ports/devbench-api/README.md |
Documents consuming the devbench API overlay port. |
cmake/ports/devbench-api/portfile.cmake |
Fetches and installs the devbench API source/header/license. |
cmake/ports/devbench-api/devbench-api-config.cmake |
Defines the imported DevBench::API target. |
- FeatureToolHandler: return an explicit error on malformed argument JSON
instead of silently falling back to action='list'.
- toggle: if SKSE's TaskInterface is unavailable, return an error rather than a
misleading {queued:true} (nothing was queued; loaded won't change).
- port README: drop the stale "placeholder REF/SHA512 / local overlay" section —
the portfile is pinned to a concrete commit; document how to bump instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmake/ports/devbench-api/README.md`:
- Around line 36-48: The README's "Interim: local overlay" section incorrectly
says the portfile's REF/SHA512 are placeholders; instead, update that text to
state that portfile.cmake currently pins a specific REF and SHA512 (referencing
the REF/SHA512 entries in portfile.cmake) and clarify the proper consumer
options: either use the pinned port as-is, override via an overlay by pointing
"overlay-ports" to a local checkout, or update the portfile's REF/SHA512 when
publishing a new GitHub release; remove the line suggesting consumers must fill
in values that are already populated.
In `@src/DevBenchBridge.cpp`:
- Around line 25-66: FeatureToolHandler currently risks letting C++ exceptions
escape the C-ABI boundary because only json::parse is guarded; wrap the entire
body of FeatureToolHandler (everything that uses args, calls args.value,
Feature::GetFeatureList, Feature::FindFeatureByShortName,
SKSE::GetTaskInterface, etc.) in a try/catch(...) block, and in the catch
construct a JSON error payload (e.g. { "error":"internal exception" , "details":
"<optional safe message>" }) and call a_write(a_sink, dumped.c_str()) to return
it; ensure the catch does not rethrow and that a_write is always called so no
exceptions propagate across the C ABI.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a1673e95-0733-4553-8188-bf93250808f1
📒 Files selected for processing (12)
CMakeLists.txtcmake/ports/devbench-api/README.mdcmake/ports/devbench-api/devbench-api-config.cmakecmake/ports/devbench-api/portfile.cmakecmake/ports/devbench-api/vcpkg.jsonsrc/ConsoleLogCapture.cppsrc/ConsoleLogCapture.hsrc/DevBenchBridge.cppsrc/DevBenchBridge.hsrc/Features/RemoteControl.cppsrc/XSEPlugin.cppvcpkg.json
Narrow this PR to the devbench bridge only — devbench owns console capture, so drop the CS-side ConsoleLogCapture and revert RemoteControl's console read/capture additions (the embedded server is the path being superseded, not extended). Bridge review fixes: - Contain all exceptions in the C-ABI handler (json::value can throw on non-object input); logic moved to a helper, handler always writes once. - Namespace the tool as "openshaders.feature" — devbench's registry is shared, so a bare "feature" risks collision. - Correct the build-gating comments: the bridge is compiled in but is a runtime no-op when no devbench host is present (not "inert by default"). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reviewers flagged that the devbench-api dependency + DEVBENCH_BRIDGE_ENABLED were wired unconditionally, making the port a mandatory build dep for every build. Add option(DEVBENCH_BRIDGE ON): the fork ships the bridge by default (CI exercises it), but -DDEVBENCH_BRIDGE=OFF drops the dependency entirely and compiles DevBenchBridge.cpp to an empty Install(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The scope-down reverted RemoteControl.cpp to origin/dev's *tip*, which on this (stale) branch pulled in a ShaderCompileStatus.h reference that doesn't exist in the branch tree — a build break. Reset it to the merge-base instead, so the PR makes no change to RemoteControl at all (the embedded server is untouched). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the new DEVBENCH_BRIDGE cache variable (default ON) to CMakeUserPresets.json.template so it's discoverable when copying the template — set it OFF to drop the devbench-api dependency and build without the bridge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…feature
- Bridge emits a namespaced "openshaders.feature.changed" event ({shortName,
enabled}) on the main thread after a toggle applies, so feature changes are
observable on devbench's bus (confirmed live end-to-end). EmitEvent takes any
topic; the openshaders. prefix marks origin in the shared registry.
- Gate the devbench-api dependency behind a "devbench-bridge" vcpkg MANIFEST
FEATURE and select it from the DEVBENCH_BRIDGE option before project() (where
the vcpkg toolchain loads). Previously the dep was unconditional, so
-DDEVBENCH_BRIDGE=OFF still fetched it; now OFF truly drops it. The duplicate
option() declaration is removed (now declared once, pre-project).
Validated live (1.6.1170): openshaders.feature registers, list + reversible
toggle (on->off->on) work over MCP/REST, and the event appears in /api/events.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/DevBenchBridge.cpp (1)
19-72: 💤 Low valueConsider documenting the threading model and
loadedfield semantics.The toggle path directly sets
target->loaded = desiredon the main thread. This works because SKSE's TaskInterface queues the lambda to the main thread where game/feature state is safely accessed.If
loadedstate changes ever require additional setup/teardown (e.g., shader recompilation, resource allocation), this direct assignment may need to invoke a method likeFeature::SetEnabled()instead. Current implementation appears correct for hot-toggle scenarios.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/DevBenchBridge.cpp` around lines 19 - 72, The toggle path in BuildFeatureResult directly mutates Feature::loaded on the main thread via SKSE::GetTaskInterface()->AddTask; document the threading model and semantics of the loaded field (that it must only be touched on the main thread) and, if enabling/disabling requires more than a plain flag flip (teardown/setup, shader recompiles, resource allocation), replace direct assignment target->loaded = desired with a safe API such as Feature::SetEnabled(bool) (or implement SetEnabled) that runs required work on the main thread and emits the same openshaders.feature.changed event via DevBenchAPI::GetDevBenchInterface001()->EmitEvent inside the task lambda; also add a brief comment in BuildFeatureResult near the task->AddTask call describing this threading contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/DevBenchBridge.cpp`:
- Around line 19-72: The toggle path in BuildFeatureResult directly mutates
Feature::loaded on the main thread via SKSE::GetTaskInterface()->AddTask;
document the threading model and semantics of the loaded field (that it must
only be touched on the main thread) and, if enabling/disabling requires more
than a plain flag flip (teardown/setup, shader recompiles, resource allocation),
replace direct assignment target->loaded = desired with a safe API such as
Feature::SetEnabled(bool) (or implement SetEnabled) that runs required work on
the main thread and emits the same openshaders.feature.changed event via
DevBenchAPI::GetDevBenchInterface001()->EmitEvent inside the task lambda; also
add a brief comment in BuildFeatureResult near the task->AddTask call describing
this threading contract.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 01f62260-094b-4fec-a998-7aed453ca076
📒 Files selected for processing (7)
CMakeLists.txtCMakeUserPresets.json.templatecmake/ports/devbench-api/README.mdsrc/DevBenchBridge.cppsrc/DevBenchBridge.hsrc/XSEPlugin.cppvcpkg.json
✅ Files skipped from review due to trivial changes (2)
- CMakeUserPresets.json.template
- cmake/ports/devbench-api/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
- src/XSEPlugin.cpp
- src/DevBenchBridge.h
Per review: spell out that Feature::loaded is a main-thread-only public flag the render pipeline reads per-frame, hot-toggled by direct assignment (mirroring CS's own RemoteControl — there is no SetEnabled), which is why the bridge marshals via TaskInterface. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
alandtse
left a comment
There was a problem hiding this comment.
Consider throughout whether we need to mention Open Shaders or can leave it out of the text/comments. Also check repo standards re comments and code churn.
The RemoteControl feature now installs the devbench bridge from its own DataLoaded() lifecycle hook instead of two places. Previously DevBenchBridge::Install() was called both from RemoteControl::Load() and directly from XSEPlugin's kDataLoaded handler, with a "whichever runs first wins" comment papering over the duplication. That arrangement violated the feature boundary and, worse, the feature's own call was dead: Load() runs during SKSEPluginLoad, before devbench publishes its cross-plugin interface at kPostLoad, so GetDevBenchInterface001() returned null and the feature logged a misleading "devbench not present" every launch. The only call that actually registered the tools was the one XSEPlugin reached in to make. DataLoaded runs after kPostLoad, so the interface is ready and the feature can own its own integration with no reach-in from the plugin entry. Also drops the "Open Shaders" brand from the DEVBENCH_BRIDGE option and vcpkg feature descriptions per review (build-system text needs no product name). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address inline review on the devbench status panel: - Use ThemeManager StatusPalette colors (SuccessColor / Warning) instead of hardcoded ImVec4 literals, so the panel honors the active theme. - Replace the std::chrono steady_clock port-cache throttle with QPC (QueryPerformanceCounter), matching the codebase's disfavoring of <chrono>. - Drop redundant "Open Shaders" / "CS" naming from the panel copy and the file/DataLoaded comments; the panel already sits under the Open Shaders menu and the abbreviation read as upstream Community Shaders. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diff audit against repo standards (minimal churn): - Remove eight Feature overrides that only restated base-class defaults: Reset / LoadSettings / SaveSettings / RestoreDefaultSettings are no-ops in the base; IsInMenu / GetShaderDefineName / HasShaderDefine return the same values the base does; and IsCore() is already true via the feature's CORE marker (FEATURE_CORE_NAMES contains "RemoteControl"). The feature now overrides only what differs: name, category, VR support, summary, DataLoaded, DrawSettings. - Move the nlohmann/json include + alias from the header (no longer uses it, after dropping the json-typed settings overrides) into the .cpp that does. - Drop "CS" from the header comment and the SkyrimVRESL reference from the devbench-api portfile/README — neither is part of this codebase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Features/RemoteControl/DevBenchBridge.cpp (1)
82-87:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCatch non-
std::exceptionfailures in the main-thread trampoline.If
read()throws anything outsidestd::exception, the exception escapes on the game thread and the promise never gets a value, so a handled bridge request degrades into a crash/timeout path.As per coding guidelines, "Include proper resource management and graceful degradation in error handling".Proposed fix
task->AddTask([prom, read = std::move(a_read)]() { try { prom->set_value(read()); } catch (const std::exception& e) { prom->set_value(json{ { "error", "read threw on main thread" }, { "detail", e.what() } }); + } catch (...) { + prom->set_value(json{ { "error", "read threw on main thread" }, { "detail", "unknown error" } }); } });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Features/RemoteControl/DevBenchBridge.cpp` around lines 82 - 87, The trampoline lambda passed to task->AddTask currently only catches std::exception, so non-std exceptions thrown by read() escape and leave prom unset; add a catch(...) handler after the existing catch(const std::exception& e) in the lambda (the closure that captures prom and read) that calls prom->set_value(...) with a json object indicating a non-std exception (e.g., { "error", "read threw non-std exception" } and optionally a generic "detail" string), ensuring the promise is always fulfilled even for non-std throws.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/Features/RemoteControl/DevBenchBridge.cpp`:
- Around line 236-243: The request handler currently queues set/reset mutations
with task->AddTask and only looks up the target via
Feature::FindFeatureByShortName inside that fire-and-forget task, causing
callers to get { "queued": true } even when shortName is invalid or the feature
is unloaded; change the flow to preflight the lookup on the main thread (call
Feature::FindFeatureByShortName before calling task->AddTask) and return an
error JSON when the lookup fails, or alternatively invoke a synchronous helper
that performs the mutation on the main thread and returns success/error JSON
(use the same LoadSettings/apply logic inside that helper) so that set/reset
only acknowledge queued=true when the target feature was validated successfully.
- Around line 369-383: The current use of RunReadOnMainThread around
globals::features::renderDoc allows the helper to time out while the lambda is
still pending, so TriggerCapture/TriggerMultiFrameCapture may run after a
timeout; change this to a pure queued/write path (replace the
RunReadOnMainThread call with the project's queued/write helper) or modify the
helper call so the lambda is only executed when the main thread confirmed
execution will occur (i.e., no timeout). Concretely, ensure the code that
invokes renderDoc->TriggerCapture and renderDoc->TriggerMultiFrameCapture runs
only via the queued/write-on-main-thread API (so the returned status matches
actual execution) and apply the same change to the similar handler that starts
at lines 387-394.
---
Outside diff comments:
In `@src/Features/RemoteControl/DevBenchBridge.cpp`:
- Around line 82-87: The trampoline lambda passed to task->AddTask currently
only catches std::exception, so non-std exceptions thrown by read() escape and
leave prom unset; add a catch(...) handler after the existing catch(const
std::exception& e) in the lambda (the closure that captures prom and read) that
calls prom->set_value(...) with a json object indicating a non-std exception
(e.g., { "error", "read threw non-std exception" } and optionally a generic
"detail" string), ensuring the promise is always fulfilled even for non-std
throws.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a43979d4-5b2d-4300-9ea0-e6428f6f00b8
📒 Files selected for processing (8)
CMakeLists.txtcmake/ports/devbench-api/README.mdcmake/ports/devbench-api/portfile.cmakesrc/Features/RemoteControl.cppsrc/Features/RemoteControl.hsrc/Features/RemoteControl/DevBenchBridge.cppsrc/Features/RemoteControl/DevBenchBridge.hvcpkg.json
💤 Files with no reviewable changes (1)
- src/Features/RemoteControl/DevBenchBridge.h
✅ Files skipped from review due to trivial changes (1)
- cmake/ports/devbench-api/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
- vcpkg.json
- cmake/ports/devbench-api/portfile.cmake
- CMakeLists.txt
Address CodeRabbit review on the devbench bridge:
- set/reset no longer fake-acknowledge. They ran the target lookup inside a
fire-and-forget task, so an invalid/unloaded shortName still returned
queued:true. Now they resolve + apply synchronously on the main thread via
RunOnMainThread and return a real {applied:true} or an error.
- RunReadOnMainThread (renamed RunOnMainThread — it now drives writes too) gains
a cancellation flag: a task that hasn't started by the 5s timeout is skipped,
so a side-effecting body (set/reset apply, RenderDoc/screenshot trigger) can't
run after the handler already reported a timeout.
- Its main-thread trampoline now also catches non-std exceptions, so a stray
throw can't leave the promise unset and hang the handler until timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address Copilot review: the shadercache tool described/returned `clear` as "shaders requeue for recompile", but clear only drops the in-memory maps — with the disk cache enabled, shaders reload from Data/ShaderCache instead of recompiling. Only deleteDisk forces a cold recompile. Reword the descriptor, the clear response note, and the handler comment so MCP/REST clients pick the right action for compile benchmarks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registers Open Shaders' tools into the standalone devbench MCP/REST test bench over its cross-plugin C-ABI. Purely additive and gated.
What's here
DevBenchBridge— registers anopenshaders.featuretool (list / toggle) into devbench viaDevBenchAPI(vendoreddevbench-apivcpkg overlay port →DevBench::API). Namespaced to avoid collisions in devbench's shared registry. All exceptions are contained at the C-ABI boundary.option(DEVBENCH_BRIDGE ON): ships by default (CI exercises it),-DDEVBENCH_BRIDGE=OFFdrops the dependency and compilesDevBenchBridge.cppto an emptyInstall(). The overlay port self-resolves undercmake/ports/devbench-api(portfile fetches the MIT API source — no source copy).Validation
Confirmed live:
openshaders.featureappears in devbench's/api/toolsand is callable over both MCP and REST — atoggleround-trip flipped a CS feature's state in-game.Scope
Bridge only. This does not touch the embedded RemoteControl server or add console capture (devbench owns capture). Further tools (feature set/reset, shader-cache, capture, abtest) and shader-recompile events register the same way as they're added.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Refactor