feat(codex): plexum-host MCP exposure + real consume mirror
Three-part wiring so codex agents get full dynamic-tool consume
support:
1. mcp-host subcommand in the plugin binary (routes argv[1] to
mcpbridge.Run). Reads PLEXUM_MCP_SOCKET / PLEXUM_MCP_AGENT_ID
from env, baked into codex's `mcp add --env` registration.
2. EnsureCodexMCPRegistered registers a per-agent stable server
name (plexum-host-<sanitised>) lazily on first turn. Stable
per-agent socket path via StableSocketPath so codex's
registration entry and the per-turn bridge listener agree.
3. Post-turn ParseRolloutToolCalls + EmitCodexToolCalls walks the
matching rollout-*-<thread_id>.jsonl and emits canonical events
for every function_call + function_call_output pair using
codex's REAL call_X ids. dynamic.jsonl now has block_ids that
match codex's session, so consume mirror has real targets.
4. MutateCodexSession rewrites those response_item entries on
consume: function_call.arguments → "{}" (heavy), output → marker.
E2E verified: codex resume sees "...(tool called)" instead of
the original output.
This commit is contained in:
76
internal/runner/mcp_register.go
Normal file
76
internal/runner/mcp_register.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// MCP server registration. codex's `mcp add` writes into the user's
|
||||
// global config; we use a per-agent stable name so multiple Plexum
|
||||
// agents using codex don't collide. Registration is idempotent and
|
||||
// performed lazily before the first turn that needs it.
|
||||
|
||||
package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var registered sync.Map // agentID → bool (registration done this process)
|
||||
|
||||
// EnsureCodexMCPRegistered makes sure codex's user config has an MCP
|
||||
// server entry for this agent named plexum-host-<sanitized>. Returns
|
||||
// the entry's tool-name namespace prefix so the caller can build
|
||||
// req.Tools wire shapes if needed.
|
||||
//
|
||||
// The socket path baked into the env is StableSocketPath(agentID) —
|
||||
// the same value the per-turn bridge listens on.
|
||||
func EnsureCodexMCPRegistered(ctx context.Context, codexBinary, pluginBinary, agentID string) (string, error) {
|
||||
name := codexServerName(agentID)
|
||||
if _, ok := registered.Load(name); ok {
|
||||
return name, nil
|
||||
}
|
||||
// Remove first so we don't get "already exists"; ignore error.
|
||||
_ = exec.CommandContext(ctx, codexBinary, "mcp", "remove", name).Run()
|
||||
|
||||
args := []string{
|
||||
"mcp", "add",
|
||||
"--env", "PLEXUM_MCP_SOCKET=" + StableSocketPath(agentID),
|
||||
"--env", "PLEXUM_MCP_AGENT_ID=" + agentID,
|
||||
name, "--", pluginBinary, "mcp-host",
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, codexBinary, args...)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return "", fmt.Errorf("codex mcp add: %w: %s", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
registered.Store(name, true)
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// StableSocketPath returns the per-agent unix socket path the bridge
|
||||
// listens on AND codex's mcp-host subprocess dials into. Keeping it
|
||||
// stable lets us register codex's MCP server once and reuse it.
|
||||
func StableSocketPath(agentID string) string {
|
||||
return filepath.Join(os.TempDir(), "plexum-codex-mcp-"+sanitize(agentID)+".sock")
|
||||
}
|
||||
|
||||
// codexServerName produces the per-agent MCP server name codex stores
|
||||
// in its global config. Names need to be filesystem-safe; we just
|
||||
// reuse the sanitised agent id.
|
||||
func codexServerName(agentID string) string {
|
||||
return "plexum-host-" + sanitize(agentID)
|
||||
}
|
||||
|
||||
func sanitize(s string) string {
|
||||
out := make([]byte, 0, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
switch {
|
||||
case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z',
|
||||
c >= '0' && c <= '9', c == '-', c == '_':
|
||||
out = append(out, c)
|
||||
default:
|
||||
out = append(out, '_')
|
||||
}
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
Reference in New Issue
Block a user