[eric] dashboard: agent clicks in its own browser no longer steal selection (browser chased the cursor mid-drag, spawn-beside re-anchored); also block Task, the 2.1.122 name of the builtin sub-agent tool

This commit is contained in:
ciregenz
2026-07-15 17:30:22 -07:00
parent 128869f0e6
commit 82d4acd6dd
3 changed files with 18 additions and 4 deletions
@@ -229,8 +229,9 @@ class RunOptions(AgentManagerProtocol):
# The claude_code preset auto-attaches the user's claude.ai- connected partner MCPs (`mcp__claude_ai_*`). Those bypass our MCPActivate gate, don't share OAuth state with the OpenSwarm Gmail/Calendar/Drive connectors the user actually configured here, and confuse the model into picking the partner shim instead of our vetted server. Hard-block them at the SDK layer so the model can't even attempt the call.
options_kwargs["disallowed_tools"] = [
"mcp__claude_ai_*",
# The CLI's built-in Agent tool is replaced by our SpawnAgent MCP (prompt + run_in_background only); its subagent types resolve to models router setups can't serve.
# The CLI's built-in sub-agent tool is replaced by our SpawnAgent MCP (prompt + run_in_background only); it is named Task on CLI 2.1.122 (Agent on older builds, kept for safety), and its subagent types resolve to models router setups can't serve.
"Agent",
"Task",
]
if session.cwd:
+5 -3
View File
@@ -70,14 +70,16 @@ def test_spawn_agent_unknown_parent_raises() -> None:
asyncio.run(agent_manager.spawn_agent(prompt="x", parent_session_id="nope-" + "0" * 28))
def test_builtin_agent_tool_stays_blocked() -> None:
# The CLI's Agent tool must not be offered: out of the catalog AND hard-blocked at the SDK layer.
def test_builtin_subagent_tools_stay_blocked() -> None:
# The CLI's built-in sub-agent tool (Task on 2.1.122, Agent on older builds) must not be offered: out of the catalog AND hard-blocked at the SDK layer.
from backend.apps.agents.manager.prompt.tool_catalog import FULL_TOOLS
assert "Agent" not in FULL_TOOLS
assert "Task" not in FULL_TOOLS
import inspect
from backend.apps.agents.manager.run import RunOptions
src = inspect.getsource(RunOptions)
assert '"Agent",' in src.split('disallowed_tools"] = [')[1][:300]
block = src.split('disallowed_tools"] = [')[1][:400]
assert '"Agent",' in block and '"Task",' in block
def test_spawn_server_schema_is_prompt_plus_background_only() -> None:
@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useRef, type Dispatch, type SetStateAction } from 'react';
import { report } from '@/shared/serviceClient';
import { useAppDispatch } from '@/shared/hooks';
import { store } from '@/shared/state/store';
import { collapseSession, expandSession } from '@/shared/state/agentsSlice';
import { bringToFront } from '@/shared/state/dashboardLayoutSlice';
import type { CardType, useDashboardSelection } from '../state/useDashboardSelection';
@@ -96,6 +97,16 @@ export function useDashboardInteractions({
const onGuestSelect = (e: Event) => {
const browserId = (e as CustomEvent).detail?.browserId;
if (typeof browserId !== 'string' || !browserId) return;
// Mid-drag/marquee a selection change joins the card to the multi-drag (the browser visibly chased the cursor); the shield class is up for exactly that window.
if (document.body.classList.contains('dashboard-marquee-active')) return;
// The guest preload fires app-clicked for the AGENT's clicks too; a working agent driving its own page must not steal selection (it also re-anchored spawn-beside onto its browser).
const st = store.getState();
const working = (s?: { status?: string }) => !!s && (s.status === 'running' || s.status === 'waiting_approval');
const glow = st.dashboardLayout.glowingBrowserCards[browserId];
const agentDriven =
Object.values(st.agents.sessions).some((s) => s.browser_id === browserId && working(s)) ||
(!!glow && !glow.fading && working(st.agents.sessions[glow.sourceId]));
if (agentDriven) return;
selection.selectCard(browserId, 'browser', false);
dispatch(bringToFront({ id: browserId, type: 'browser' }));
};