mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-22 17:44:53 +02:00
fixes: MCP auto-continue lists tool names + history doesn't eat chats + WS strip replayed closes
(1) After MCPActivate, the continuation prompt now enumerates the activated server's callable tool names so the next turn calls them directly instead of burning a turn on tool-discovery guesses. (2) Resume no longer deletes the session file from disk; previously every click on a past chat permanently removed it from history. (3) WS replay skips agent:closed events; replaying them on a fresh client destructively deleted the session being opened.
This commit is contained in:
@@ -4504,7 +4504,12 @@ class AgentManager:
|
||||
session.closed_at = None
|
||||
self.sessions[session_id] = session
|
||||
|
||||
_delete_session_file(session_id)
|
||||
# Do NOT delete the disk file here. The history list (get_history)
|
||||
# reads from disk; deleting on resume meant every click on a past
|
||||
# chat permanently removed it from history on the next restart.
|
||||
# The disk copy stays as the durable record; subsequent turn
|
||||
# completions and close_session calls overwrite it via
|
||||
# _save_session, so memory and disk stay in sync.
|
||||
|
||||
await ws_manager.send_to_session(session_id, "agent:status", {
|
||||
"session_id": session_id,
|
||||
|
||||
@@ -148,6 +148,7 @@ class ConnectionManager:
|
||||
# reconcile_on_startup also marks waiting_approval sessions as
|
||||
# stopped so there's nothing to answer anyway.
|
||||
events = self._filter_stale_approvals(events)
|
||||
events = self._strip_replayed_closes(events)
|
||||
for s in events:
|
||||
try:
|
||||
await websocket.send_text(s)
|
||||
@@ -177,6 +178,30 @@ class ConnectionManager:
|
||||
"current_seq": newest if newest is not None else 0,
|
||||
}
|
||||
|
||||
def _strip_replayed_closes(self, events: list[str]) -> list[str]:
|
||||
"""Drop `agent:closed` events from a replay buffer.
|
||||
|
||||
agent:closed is a transition event ("session JUST closed") whose
|
||||
frontend reducer (closeSessionFromWs) destructively deletes the
|
||||
session from state.sessions. Replaying it on a fresh client (e.g.
|
||||
a user who just clicked the closed chat in history) deletes the
|
||||
session they're trying to open. The current closed state is
|
||||
already conveyed by the REST hydrate (status=stopped, closed_at
|
||||
set) and by the latest agent:status event in the replay, so
|
||||
suppressing the transition replay is non-lossy.
|
||||
"""
|
||||
out: list[str] = []
|
||||
for payload_str in events:
|
||||
try:
|
||||
parsed = json.loads(payload_str)
|
||||
except (ValueError, TypeError):
|
||||
out.append(payload_str)
|
||||
continue
|
||||
if parsed.get("event") == "agent:closed":
|
||||
continue
|
||||
out.append(payload_str)
|
||||
return out
|
||||
|
||||
def _filter_stale_approvals(self, events: list[str]) -> list[str]:
|
||||
"""Return events minus any `agent:approval_request` whose request_id
|
||||
is no longer in pending_futures. JSON parse is per-event but replay
|
||||
|
||||
+29
-2
@@ -667,11 +667,38 @@ async def mcp_meta(action: str, request: Request):
|
||||
# Turns the typical 3-prompt flow ("check email" → MCPActivate
|
||||
# → "do it") into a 1-prompt flow.
|
||||
session.pending_continuation = True
|
||||
# Enumerate the just-activated server's callable tool names so the
|
||||
# continuation turn can call them directly. Without this the model
|
||||
# often burns a turn on tool-discovery guesses (Bash "mcp list",
|
||||
# Ls /toolbox, ToolSearch fallbacks) before landing on the right
|
||||
# mcp__server__action name. Cap at 16 + clip descriptions so the
|
||||
# prompt stays bounded for kitchen-sink servers (google-workspace
|
||||
# exposes ~30 tools). Best-effort; any lookup failure silently
|
||||
# falls back to the same prompt this code shipped with before.
|
||||
tool_hint = ""
|
||||
try:
|
||||
for t in load_all_tools():
|
||||
if _sanitize_server_name(t.name) != server_name:
|
||||
continue
|
||||
descs = (t.tool_permissions or {}).get("_tool_descriptions", {}) or {}
|
||||
if not descs:
|
||||
break
|
||||
lines: list[str] = []
|
||||
for sub_name, desc in list(descs.items())[:16]:
|
||||
short = (desc or "").strip().split("\n", 1)[0][:120]
|
||||
visible = f"mcp__{server_name}__{sub_name}"
|
||||
lines.append(f"- `{visible}`: {short}" if short else f"- `{visible}`")
|
||||
if lines:
|
||||
more = "" if len(descs) <= 16 else f"\n(+ {len(descs) - 16} more; call ToolSearch with the server name for the rest)"
|
||||
tool_hint = "\n\nCallable tools on this server:\n" + "\n".join(lines) + more
|
||||
break
|
||||
except Exception:
|
||||
logger.exception("activate: failed to build tool hint for %s", server_name)
|
||||
session.pending_continuation_prompt = (
|
||||
"[mcp:auto-continue] The MCP server you requested has been "
|
||||
f"activated (`{server_name}`). Continue with the user's original "
|
||||
"request now using the newly-available tools — do NOT ask "
|
||||
"for confirmation."
|
||||
"request now using the newly-available tools; do NOT ask "
|
||||
"for confirmation." + tool_hint
|
||||
)
|
||||
|
||||
return JSONResponse({"status": "activated", "server_name": server_name, "auto_continue": True})
|
||||
|
||||
Reference in New Issue
Block a user