Skip to content

feat(app): workspace-action MCP tools on the live bridge (F-mcp-orchestration, #194)#219

Merged
bastien-gallay merged 2 commits into
mainfrom
worktree-194-mcp-orchestration
Jul 25, 2026
Merged

feat(app): workspace-action MCP tools on the live bridge (F-mcp-orchestration, #194)#219
bastien-gallay merged 2 commits into
mainfrom
worktree-194-mcp-orchestration

Conversation

@bastien-gallay

Copy link
Copy Markdown
Collaborator

What

The action rung of the MCP control surface, after the read-only
perception rung (#212). Six mutating tools, each a thin wrapper over an
existing core::App event — the orchestration surface never bypasses the
state machine (#90's own constraint):

MCP tool core Event
open_session(project, kind) LaunchSession (via the shell's own launch path)
split_pane(direction[, pane]) [FocusPane +] SplitFocused
focus_pane(session) FocusPane
rename_tab(tab, title) RenameTab
close_pane([pane]) CloseFocusedPane (lone pane → core collapses to close_tab)
run_in_session(session, text) TerminalInput (no wait)

How

core is untouched — all mutation is already expressed as events. The seam
is app-side:

  • shell/bridge.rsRequest::Act(Action) + Reply::Acted(ActionOutcome { focused, error }). Action has the six variants; targets are the stable
    string handle list_sessions/snapshot already report.
  • shell.rsMessage::Bridge branches: actions → perform_action
    (needs &mut), the two read requests → respond (unchanged).
  • shell/orchestrate.rs (new) — perform_action + one act_* per action,
    sharing retarget/resolve/applied. It resolves the handle, applies the
    event(s) through App::apply, performs the effects via the one executor, and
    reports the resulting focused handle — so an agent gets act→observe in
    one round trip. Mirrors shell/launch.rs.
  • mcp/handler.rs — the six #[tool] methods + arg DTOs. A rejection
    becomes invalid_params; success returns { focused_handle }.

An unknown handle / out-of-range tab is rejected before any state is
touched
; run never misfires into a wrong terminal. Every call stays
tokio::timeout-bounded and apply-and-read (Q7, from #192).

Tests

23 new (all green):

  • perform_action behaviour (11) — open focuses a new pane; split targets
    a pane and rejects an unknown one; focus/run reject an unknown handle without
    touching state; run writes the bytes; rename relabels the tab; close kills a
    lone-pane tab.
  • tool shaping (12) — each tool maps its args to the right Action,
    defaults kind/direction, parses handles, relays a rejection as
    invalid_params, reports the resulting focus, and surfaces a bridge failure
    as an error (not a hang).

Full suite green (234 app); fmt · clippy · machete · deny ·
check-deps · check-arch · markdownlint all pass. ROADMAP entry ticked.

Closes #194.

…stration)

The action rung after the read-only perception rung (#212): six mutating
MCP tools — open_session, split_pane, focus_pane, rename_tab, close_pane,
run_in_session — each a thin wrapper over an existing core::App event, so
the orchestration surface never bypasses the state machine (#90's own
constraint).

core is untouched; all mutation is already expressed as events. The seam
is app-side: the bridge grows an Act(Action) request and an ActionOutcome
reply, and the shell adapter — which owns core::App and the one effect
executor — resolves the stable handle, applies the event(s), performs the
effects, and reports the resulting focused handle, so an agent gets
act→observe in one round trip. An unknown handle or out-of-range tab is
rejected before any state is touched (surfaced as invalid_params); every
call stays tokio::timeout-bounded and apply-and-read (Q7). Handles are
strings everywhere, matching list_sessions/snapshot.

New shell/orchestrate.rs owns perform_action + one act_* per action,
mirroring shell/launch.rs. 23 tests: perform_action behaviour and tool
arg-mapping / rejection / bridge-failure shaping.
A pane handle from a non-active tab resolved (the session registry is
workspace-global) but `Event::FocusPane` only searches the active tab and
discards its result, so the follow-up focus-relative event acted on the
*active* tab's focused pane while the tool reported success — close_pane
killed the wrong terminal, split_pane split beside the wrong one, and
focus_pane reported a focus move that never happened.

Core grows `Workspace::reveal_pane_of` + `Event::RevealPane`: activate the
owning tab, then focus the leaf, restoring the previous tab if the leaf
turns out to be unreachable. `FocusPane` keeps its click-to-focus meaning —
a click cannot land outside the active tab. The orchestration seam resolves
through `tab_of` rather than the tab-blind registry, so a handle no open
pane hosts is rejected instead of silently retargeting whatever holds focus.

Also drops a bare issue number from a doc-comment (AGENTS.md).
@bastien-gallay
bastien-gallay merged commit 30f110b into main Jul 25, 2026
17 checks passed
@bastien-gallay
bastien-gallay deleted the worktree-194-mcp-orchestration branch July 25, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

F-mcp-orchestration: open_session / split / focus / rename / run_in_session tools

1 participant