Skip to content

Subprocess Wiring

BxpProcessClient is the single entry point for all binary calls. Every call goes through the in-process FFI bridge (bxp-gui-bridge.{dll,so,dylib}) on every platform — there is no dart:io Process.start path. The bridge offers two call shapes: in-process inspect / eval, and a native-code bxp-cli subprocess proxy (both detailed below).

Transport paths

The bridge is the single backend on every platform — there is no Process.start path. Every entry point is a bridge_* call, and the whole C-ABI surface is below: in-proc runs inside the GUI process against bxp-core, proxy spawns bxp-cli and drains its pipes in native code, and lifecycle covers handles, buffers and the version probe.

Export Served Purpose
bridge_version() lifecycle NUL-terminated semver string, matching build.zig.zon. The GUI's library-probe uses it to refuse a mismatched build.
bridge_run(argv, out) proxy One-shot spawn: run bxp-cli, drain stdout and stderr into the caller's buffer, return the exit code. Used for the --version probe; the native drain sidesteps the Dart pipe truncation of dart-lang/sdk#1727.
bridge_run_streaming(argv, callbacks) proxy Streaming spawn: per-batch stdout / stderr callbacks plus an exit callback. Carries the bxp-cli --trace BXTB frame stream behind every dry-run and full run.
bridge_cancel(handle) lifecycle Cooperative cancel for a streaming handle. The child leads its own process group, so this reaches its grandchildren too.
bridge_ack(handle) lifecycle Backpressure acknowledgement — releases one queue permit, so a slow Dart consumer cannot be outrun by a fast producer.
bridge_free(ptr, len) lifecycle Returns a response buffer to the bridge allocator. Every buffer the bridge hands out is freed through here, never by Dart.
bridge_eval_expr(expr, row, out) in-proc Parse and evaluate one expression, returning the value or {error, offset, length}. Drives the expression editor's per-keystroke validation, which is why it must not pay a ~50 ms spawn.
bridge_eval_expr_trace(expr, row, out) in-proc The same with a per-call NDJSON trace plus a terminal sentinel — the ExprPlayground's step-through view.
bridge_inspect(request, out) in-proc The stateless inspect ops behind one JSON request envelope: docs, config, list_templates, fetch_template, eval_batch. Result JSON comes back in the out buffer.
bridge_verify_minisign(file, sig, pubkey) in-proc Verifies a minisign signature over a file — the release SHA256SUMS — against a base64 public key. Ed25519 + Blake2b-512 through the zig-libs minisign module, no heap allocation, no Dart crypto dependency; returns 0 for authentic and non-zero to refuse.

The table is generated from bxp-gui-bridge/src/ops.zig, which a compile-time check holds to the library's actual pub export fns in both directions — so an export cannot be added, renamed or removed without this page following.

The bridge is implemented as a Zig shared library that links the bxp-core/inspect + expr modules directly (in-proc paths) and spawns the bxp-cli subprocess (proxy paths). For the two-cause rationale behind the in-proc / proxy split see internals/index.md — "Why the bridge exists". The C-ABI surface and Debug→ReleaseSafe rewrite landmine live in bxp-gui-bridge/CLAUDE.md. The proxy path's pipe drain is hardened against several subprocess-reaping hazards — the Dart VM's own child reaper closing fds mid-read, an inherited SIGCHLD=SIG_IGN, and ECHILD on wait — by joining the stream readers before reaping; see that file for the ordering.

Mandatory on every platform. Library probe failure at startup is fatal (synthetic error surfaced through the normal startup gate, which also parses the docs catalog). There is no subprocess fallback — Windows can't use Process.start (dart-lang/sdk#1727) and the Linux/macOS Process.start route was removed when the bridge became the single backend.

Reloading bridge changes. dlopen mmaps the file at process start, so editing a .so/.dylib and mcp__dart__hot_reload does NOT pick it up. After zig build in bxp-gui-bridge/, fully stop and relaunch the Flutter app.


Binary resolution

Resolved in this order:

  1. Env override$BXP_CLI_PATH. If set and non-empty, used absolutely (missing file → fatal error, no fallthrough). ($BXP_EXAMPLES_PATH is the analogous override for locating the bundled bxp-cli.examples.json template catalog.)
  2. Bundle sibling<name> next to the Flutter executable inside the app bundle.
  3. Dev-tree fallback — walks up from the exe dir until it finds a bxp-gui/ segment, then looks for <monorepo-root>/<name>/zig-out/bin/<name>. This makes flutter run -d linux work without copying binaries after a bundle wipe.

Client methods

Method Backend call Notes
validateConfig(path) bridge_inspect {config} Returns annotated JSON with $err_*/$warn_* siblings
getDocs() bridge_inspect {docs} Cached at startup; drives FnDoc tooltips + SchemaGate
listTemplates(path) bridge_inspect {list_templates} {templates:[…]}List<TemplateInfo> (id + io shape)
validateExpr(text) bridge_eval_expr Returns {error, offset, length} on failure
traceExpr(text, …) bridge_eval_expr_trace NDJSON stream of per-call values
runWithBtrace(...) bridge_run_streamingbxp-cli --trace=bin BXTB frame stream → in-store reader
getVersion(name) bridge_runbxp-cli --version Writes to stdout

Linux dev-tree gotcha

The Linux CMake config copies the bxp-gui-bridge library (and bxp-cli) into the bundle at build time. After rebuilding the bridge, either run a clean Flutter build or rely on the dev-tree fallback (option 3 above) which reads directly from bxp-gui-bridge/zig-out/.