mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 12:17:45 +02:00
[eric] mcp: list activated server's tools in auto-continue prompt
This commit is contained in:
@@ -4614,7 +4614,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,
|
||||
|
||||
@@ -103,6 +103,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)
|
||||
@@ -130,6 +131,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
|
||||
|
||||
@@ -132,6 +132,7 @@ async def execute(wf: Workflow, triggered_by: str = "schedule", scheduled_for: O
|
||||
"last_run_id": run.id,
|
||||
})
|
||||
|
||||
session = None
|
||||
try:
|
||||
steps = [s.text for s in wf.steps if s.text and s.text.strip()]
|
||||
if not steps:
|
||||
@@ -207,6 +208,16 @@ async def execute(wf: Workflow, triggered_by: str = "schedule", scheduled_for: O
|
||||
"last_run_at": run.finished_at,
|
||||
})
|
||||
finally:
|
||||
# Close the workflow's agent session so closed_at is set and the
|
||||
# run shows up in chat history (get_history sorts by closed_at;
|
||||
# sessions with closed_at=None sort to the bottom and fall off
|
||||
# the first page). close_session also drops in-memory state and
|
||||
# persists the final snapshot to disk.
|
||||
if session is not None:
|
||||
try:
|
||||
await agent_manager.close_session(session.id)
|
||||
except Exception:
|
||||
logger.exception("close_session failed for workflow run %s", run.id)
|
||||
async with _running_lock:
|
||||
_running.pop(wf.id, None)
|
||||
|
||||
|
||||
+28
-1
@@ -517,11 +517,38 @@ async def mcp_meta(action: str, request: Request):
|
||||
|
||||
# Auto-continue: read at turn boundary in _run_agent_loop (race-free); collapses the typical 3-prompt flow into 1.
|
||||
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."
|
||||
"for confirmation." + tool_hint
|
||||
)
|
||||
|
||||
return JSONResponse({"status": "activated", "server_name": server_name, "auto_continue": True})
|
||||
|
||||
Reference in New Issue
Block a user