From 93abbd5c491870d750244b5eda6b93b3ed3b8028 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Sun, 23 Aug 2026 16:57:23 -0400 Subject: [PATCH 1/3] Bump Version --- cecli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cecli/__init__.py b/cecli/__init__.py index f92d0cbc15d..19645dbc949 100644 --- a/cecli/__init__.py +++ b/cecli/__init__.py @@ -1,6 +1,6 @@ from packaging import version -__version__ = "1.3.0.dev" +__version__ = "1.3.1.dev" safe_version = __version__ try: From c4dd9f240b5076552eabf941403388afe972aa90 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Sun, 23 Aug 2026 16:58:21 -0400 Subject: [PATCH 2/3] Extreme bias against emdashes as a delimiter --- cecli/helpers/hashpos/hashpos.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cecli/helpers/hashpos/hashpos.py b/cecli/helpers/hashpos/hashpos.py index 0190c8df418..bd5a150fa98 100644 --- a/cecli/helpers/hashpos/hashpos.py +++ b/cecli/helpers/hashpos/hashpos.py @@ -74,10 +74,8 @@ class HashPos: rf"^({UNIQUE_HASH_DELIMITER}|{HASH_DELIMITER}[{_B1024_REGEX_SET}]{{4}}{HASH_DELIMITER})$" ) - # Loose prefix for robust stripping: Matches a tilde-wrapped 4-char string containing non-ASCII - _LOOSE_PREFIX_RE = re.compile( - rf"^[{HASH_DELIMITER}]?(?=.{{0,3}}[^\x00-\x7f]).{{4}}{HASH_DELIMITER}" - ) + # Loose prefix for robust stripping: Matches a emdash-wrapped 4-char string containing non-ASCII + _LOOSE_PREFIX_RE = re.compile(rf"^[{HASH_DELIMITER}]?\S{{0,4}}{HASH_DELIMITER}") def __init__(self, source_text: str = ""): self.lines = source_text.splitlines() From 70c188dc9c502a55cebf11d815050d4cac0bb08e Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Sun, 23 Aug 2026 16:59:48 -0400 Subject: [PATCH 3/3] Fix MCP server inheritance between parent and child agents --- cecli/coders/base_coder.py | 17 ++++++++++++++ cecli/mcp/manager.py | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/cecli/coders/base_coder.py b/cecli/coders/base_coder.py index c14608dcbf7..052886e1f47 100755 --- a/cecli/coders/base_coder.py +++ b/cecli/coders/base_coder.py @@ -354,6 +354,23 @@ async def create( if from_coder.tui: res.tui = from_coder.tui + # Sub-agents get a dedicated, independent MCP manager so they + # can rebuild a custom tool list (their own LocalServer tools / + # filters) and be disconnected independently from the parent. + # The parent's server configs are copied into a fresh manager + # whose connections are created on this loop; the Local server + # is left to initialize_mcp_tools() so it is recreated from the + # sub-agent's filters. + if ( + from_coder.mcp_manager + and res.uuid + and res.parent_uuid + and res.parent_uuid != res.uuid + ): + res.mcp_manager = await from_coder.mcp_manager.spawn_child( + io=IOProxy.unwrap(res.io) + ) + if res.mcp_manager: # When switching to a non-agent coder, disconnect the "Local" MCP server # (which provides agent-only tools like tool calling and file editing) diff --git a/cecli/mcp/manager.py b/cecli/mcp/manager.py index af3e2eaa0a0..9f3cb429208 100644 --- a/cecli/mcp/manager.py +++ b/cecli/mcp/manager.py @@ -387,6 +387,46 @@ async def _connect(server: McpServer) -> tuple[McpServer, bool, bool]: tool_desc = tool.get("function", {}).get("description", "").split("\n")[0] self.io.tool_output(f" - {tool_name}: {tool_desc}") + async def spawn_child(self, io=None) -> "McpServerManager": + """Create a new, independent manager for a sub-agent. + + Rebuilds fresh McpServer instances from this manager's server configs + so the sub-agent sees the same full set of servers it can include or + exclude (via registered_servers), but with its own connections that + can be torn down together with the sub-agent instead of being shared + with the parent. The "Local" server is deliberately left out so + AgentCoder.initialize_mcp_tools() can create and connect its own + instance and rebuild the tool list from the sub-agent's own filters + (the parent may exclude tools a child opts into). + """ + server_io = io if io is not None else self.io + servers = [ + _recreate_server(server, io=server_io, verbose=self.verbose) + for server in self._servers + if not isinstance(server, LocalServer) + ] + + child = McpServerManager(servers=servers, io=server_io, verbose=self.verbose) + child._connection_loop = asyncio.get_running_loop() + + # Mirror the parent's connected set, but with independent connections so + # the child can be disconnected without affecting the parent. + connected_names = { + server.name + for server in self._servers + if server in self._connected_servers or server.is_connected + } + for server in servers: + if server.name in connected_names: + try: + await child.connect_server(server.name) + except Exception as exc: + child._log_warning( + f"Failed to connect MCP server {server.name} for sub-agent: {exc}" + ) + + return child + def get_local_tool_schemas(): """Returns the JSON schemas for all local tools using the tool registry.""" @@ -449,3 +489,11 @@ def _mcp_tools_to_openai_tools(tools): } for tool in tools ] + + +def _recreate_server(server, io=None, verbose=False): + """Rebuild a fresh McpServer of the same type/config as an existing one.""" + import copy + + config = copy.deepcopy(server.config) + return type(server)(config, io=io, verbose=verbose)