Files
tai/taicpp/docs/wiring/mplex.md
T
2026-10-25 07:20:48 +01:00

4.5 KiB

Wiring: mplex and uiflow (hooks for the integrator)

Everything below is applied outside src/mplex and src/plexer/plexer_flow.*, which the mplex port does not own. Nothing here changes behavior of the ported classes.

Build

Add src/mplex/mplex.mk to the module list (the top Makefile already has -include src/*/*.mk, so the file is picked up as is). It appends every mplex source and src/plexer/plexer_flow.cpp to SRCS. ALL_SRCS = $(shell find src -name '*.cpp') already compiles them into build/detz and build/detz_test; there is no duplicate main in src/ (the standalone binary tests/mplex_cli_main.cpp is compiled only by the $(BUILD)/mplex_cli rule inside mplex.mk).

Includes: the headers use flat names prefixed mplex_ / plexer_flow.h; the Makefile's -I for every src/* directory is enough. Link needs only libc (no sqlite, no curl).

Extra targets defined in mplex.mk: make build/mplex_cli, make build/mplex_flow_test, make mplex-flow-test, make mplex-e2e.

--mplex [COMMAND...]

tai.py declares --mplex with nargs=argparse.REMAINDER, default=None: everything after the flag belongs to mplex, bare --mplex means a new session. src/cli/cli.cpp:130 already parses this into o.mplex (std::optional<std::vector<std::string>>, set to an empty vector for bare --mplex).

Dispatch (tai.py 40125-40128): after --version, --cap*, --serve-mcp, --update-models, --demo* and the theme check, before --plexer-wrap and --tui:

#include "mplex_client.h"
...
if (opts.mplex.has_value()) {
  std::string home = Env("TAI_HOME").empty() ? HomeDir() + "/.tai" : Env("TAI_HOME");
  return detz::mplex::MplexCli::main(home, *opts.mplex);
}

MplexCli::main returns the process exit code and writes its own diagnostics to stderr, exactly like the Python (sessions should be nested with care, unset TAI_PLEXER_ACTIVE to force, open terminal failed: not a terminal, no server running on <sock>, ...).

Argument quirk to keep in step with tai.py: argparse's nargs=REMAINDER swallows a literal -- inside the mplex words (tai.py: --mplex send-keys -X -- cursor-up loses the --), while tests/mplex_cli_main.cpp passes argv through untouched. The src/cli parser must drop/keep -- exactly as argparse.REMAINDER does (verify against python3 tai.py --mplex ls -- x).

The version format variable is detz::kVersion when the build puts the generated version.hpp on the include path (the Makefile's -I$(GEN_INC) does), 1.44.2 otherwise.

--plexer-wrap

tai.py 40129-40133: same home computation, profile = args.profile or "default":

if (opts.plexer_wrap) {
  std::string home = ...;  // same as above
  detz::mplex::NativePlexerBackend backend(home, opts.profile.empty() ? "default" : opts.profile);
  return backend.run();
}

NativePlexerBackend::run() starts or joins the server and attaches this terminal to a fresh session (MplexCli::main(home, {"new-session"})).

Shell hook text and detection

detz::mplex::PLEXER_BASHRC_BLOCK, FISH_PLEXER_BLOCK, PLEXER_BASHRC_MARK_BEGIN/END and plexer_should_wrap(env) (header mplex_client.h) are byte-identical to the Python constants. The installer (--install) must write these strings, not copy its own.

Agent tools (plexer_send, plexer_read, plexer_type, plexer_status, plexer_show)

Use detz::plexer::PlexerControllerFactory::detect() from plexer_flow.h (native when TAI_PLEXER_SOCKET is set, tmux when TMUX_PANE is set, null otherwise, which hides the tools). plexer_show is validate_flow_spec(spec) followed by PlexerFlow(app).run(*controller); its result JSON is the return value. The existing skeleton detz::plexer::Controller/Native/Tmux in src/plexer/plexer.h is superseded by PlexerController and friends and can be deleted by the foundation owner once the tool handlers call the new classes (both can coexist: names differ).

Server process

MplexServer::ensure_running(home) double-forks a detached daemon that runs the select loop in the same binary, so detz needs no extra executable. Its stdout/stderr go to <TAI_HOME>/plexer/server.log. The daemon inherits the environment; TERM, SHELL and TAI_HOME behave as in tai.py.

Verified

See the report of the porting run: tests/mplex_flow_test.cpp (spec/flow corpus against tai.py), tests/mplex_e2e.py (seven scenarios run through a pty against both tai.py --mplex and build/mplex_cli --mplex, observations compared), tests/mplex_tmux_oracle.py (tmux 3.6 oracle for the tmux controller).