mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
[eric] hardening: legacy pre-collapse tool ids canonicalize to core, and the docked mini gets a load listener plus a 1s belt so a silent layout shift can't strand it
This commit is contained in:
@@ -208,12 +208,13 @@ def resolve_policy_slot(tool_name: str, tools: list[ToolDefinition]) -> PolicySl
|
||||
differently. That divergence was the bug behind 'Always approve' acting like a
|
||||
one-time accept: writes landed under the raw mcp__server__action name while the
|
||||
gate read the parsed inner action, so the next call never saw the policy."""
|
||||
# 1.7.5 sessions/logs may still carry the pre-collapse server names; they are the same builtin tools, so any legacy prefix canonicalizes to core before slot resolution.
|
||||
tool_name = re.sub(
|
||||
r"^mcp__openswarm-(?:browser-agent|invoke-agent|spawn-agent|skill|ui|schedule|web|mcp-meta|settings-meta|apps)__",
|
||||
"mcp__openswarm-core__", tool_name)
|
||||
bm = re.match(r"mcp__openswarm-core__(.+)", tool_name)
|
||||
if bm:
|
||||
return PolicySlot("builtin", bm.group(1), None)
|
||||
im = re.match(r"mcp__openswarm-core__(.+)", tool_name)
|
||||
if im:
|
||||
return PolicySlot("builtin", im.group(1), None)
|
||||
m = re.match(r"mcp__([^_]+(?:-[^_]+)*)__(.+)", tool_name)
|
||||
if m:
|
||||
server_slug, action = m.group(1), m.group(2)
|
||||
|
||||
@@ -136,3 +136,15 @@ def test_mcp_policy_survives_a_real_tool_file_reload(tmp_path, monkeypatch):
|
||||
rslot = tl.resolve_policy_slot(name, tools2)
|
||||
got = next(t for t in tools2 if t.id == rslot.key)
|
||||
assert got.tool_permissions.get(rslot.action) == "always_allow"
|
||||
|
||||
|
||||
def test_legacy_precollapse_ids_canonicalize_to_core():
|
||||
# A 1.7.5 session/log can still name the old per-server ids; they must land in the SAME builtin slot as their openswarm-core successors, or a remembered approval silently stops matching after the upgrade.
|
||||
for legacy, inner in (
|
||||
("mcp__openswarm-browser-agent__BrowserAgent", "BrowserAgent"),
|
||||
("mcp__openswarm-web__WebSearch", "WebSearch"),
|
||||
("mcp__openswarm-settings-meta__SettingsWrite", "SettingsWrite"),
|
||||
("mcp__openswarm-schedule__ScheduleWorkflow", "ScheduleWorkflow"),
|
||||
):
|
||||
assert resolve_policy_slot(legacy, []) == resolve_policy_slot(f"mcp__openswarm-core__{inner}", [])
|
||||
assert resolve_policy_slot(legacy, []).key == inner
|
||||
|
||||
@@ -271,8 +271,11 @@ const BrowserCard: React.FC<Props> = ({
|
||||
ro.observe(slot);
|
||||
if (slot.parentElement) ro.observe(slot.parentElement);
|
||||
scrollHost?.removeEventListener('scroll', onScroll);
|
||||
scrollHost?.removeEventListener('load', measure, true);
|
||||
scrollHost = slot.closest('[data-chat-transcript]');
|
||||
scrollHost?.addEventListener('scroll', onScroll, { passive: true });
|
||||
// An image/iframe finishing load ABOVE the slot shifts it with no scroll, resize, or mutation; load doesn't bubble but it does capture.
|
||||
scrollHost?.addEventListener('load', measure, true);
|
||||
// Streaming appends move/remount the slot without resizing or scrolling anything observed; the mutation observer is what keeps the mini glued during a live turn.
|
||||
mo.disconnect();
|
||||
if (scrollHost) mo.observe(scrollHost, { childList: true, subtree: true });
|
||||
@@ -317,6 +320,9 @@ const BrowserCard: React.FC<Props> = ({
|
||||
if ((e as CustomEvent).detail?.id === dockedTo) measure();
|
||||
};
|
||||
window.addEventListener('openswarm:browser-slot-mounted', onSlotMounted);
|
||||
// A 1s belt bounds any move-without-event gap nobody has named yet. Only alive while a mini is
|
||||
// docked, so it costs one layout read per second during active docking instead of 6.7/sec forever.
|
||||
const belt = window.setInterval(measure, 1000);
|
||||
return () => {
|
||||
ro.disconnect();
|
||||
mo.disconnect();
|
||||
@@ -324,9 +330,11 @@ const BrowserCard: React.FC<Props> = ({
|
||||
window.removeEventListener('openswarm:canvas-pan-changed', measure);
|
||||
document.removeEventListener('visibilitychange', measure);
|
||||
window.removeEventListener('openswarm:browser-slot-mounted', onSlotMounted);
|
||||
scrollHost?.removeEventListener('load', measure, true);
|
||||
scrollHost?.removeEventListener('scroll', onScroll);
|
||||
if (scrollRaf) cancelAnimationFrame(scrollRaf);
|
||||
timers.forEach((tm) => window.clearTimeout(tm));
|
||||
window.clearInterval(belt);
|
||||
};
|
||||
// dockParentCard x/y/w/h are re-measure triggers: the slot's client rect moves with the chat card.
|
||||
}, [dockedTo, dockParentExpanded, dockParentTiled, dockParentCard?.x, dockParentCard?.y, dockParentCard?.width, dockParentCard?.height, getCanvasState, dockParentCard]);
|
||||
|
||||
Reference in New Issue
Block a user