mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
[eric] app-use: strip pierre's transient [app-agent] trace scaffolding + write-only controls cache (no speculative scaffolding)
This commit is contained in:
@@ -151,22 +151,6 @@ def p_app_output_id(browser_id: str) -> str | None:
|
||||
return browser_id[4:] if browser_id.startswith("app:") else None
|
||||
|
||||
|
||||
def p_app_workspace_dir(browser_id: str) -> str | None:
|
||||
"""Resolve the on-disk workspace folder for an `app:<output_id>` target."""
|
||||
oid = p_app_output_id(browser_id)
|
||||
if not oid:
|
||||
return None
|
||||
try:
|
||||
from backend.apps.outputs.workspace_io import load_output
|
||||
from backend.config.paths import OUTPUTS_WORKSPACE_DIR
|
||||
out = load_output(oid)
|
||||
if not out or not getattr(out, "workspace_id", None):
|
||||
return None
|
||||
return os.path.join(OUTPUTS_WORKSPACE_DIR, out.workspace_id)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def render_app_controls(describe_value: object) -> tuple[str, str] | None:
|
||||
"""From a decoded describe() value build (rules_md, controls_md). Returns
|
||||
None when the value is not a ready, usable describe."""
|
||||
@@ -194,29 +178,6 @@ def render_app_controls(describe_value: object) -> tuple[str, str] | None:
|
||||
return rules_md, controls_md
|
||||
|
||||
|
||||
def p_persist_app_controls(browser_id: str, describe_value: object) -> None:
|
||||
"""Cache rules.md + controls.md into the app workspace so the agent reads them
|
||||
up front and only re-describes when controls change. Best-effort; written
|
||||
under .openswarm/ so they stay out of the app's own file tree."""
|
||||
rendered = render_app_controls(describe_value)
|
||||
if not rendered:
|
||||
return
|
||||
folder = p_app_workspace_dir(browser_id)
|
||||
if not folder or not os.path.isdir(folder):
|
||||
return
|
||||
rules_md, controls_md = rendered
|
||||
try:
|
||||
cache_dir = os.path.join(folder, ".openswarm")
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
with open(os.path.join(cache_dir, "controls.md"), "w", encoding="utf-8") as f:
|
||||
f.write(controls_md)
|
||||
if rules_md:
|
||||
with open(os.path.join(cache_dir, "rules.md"), "w", encoding="utf-8") as f:
|
||||
f.write(rules_md)
|
||||
except Exception:
|
||||
logger.debug("[app-agent] failed to persist controls cache", exc_info=True)
|
||||
|
||||
|
||||
# Single-tool names -> the sub-action type they map to, so one summarizer covers
|
||||
# both BrowserPressKey({key}) and a batch's {"type":"press_key","params":{key}}.
|
||||
P_SINGLE_ACTION_TYPE = {
|
||||
@@ -286,10 +247,6 @@ async def execute_browser_tool(
|
||||
tool_name: str, tool_input: dict, browser_id: str, tab_id: str = "",
|
||||
) -> dict:
|
||||
"""Execute a browser tool via ws_manager directly (no MCP/HTTP round-trip)."""
|
||||
# [app-agent] step trace: only for app targets / bridge tools so normal
|
||||
# browser-agent runs stay quiet. Greppable prefix; remove when done.
|
||||
p_trace = browser_id.startswith("app:") or tool_name in APP_BRIDGE_TOOLS
|
||||
|
||||
# One greppable line naming the actual buttons/keys/selectors this call drives,
|
||||
# so a run reads as "key:ArrowRight x5" rather than an opaque tool name. Fires
|
||||
# for action tools only (reads stay quiet) and ungated so web runs get it too.
|
||||
@@ -306,12 +263,7 @@ async def execute_browser_tool(
|
||||
|
||||
async def p_eval_once() -> dict:
|
||||
rid = uuid4().hex
|
||||
if p_trace:
|
||||
logger.info(f"[app-agent] DISPATCH {tool_name} -> {browser_id} (req {rid[:8]}) js={expr[:120]}")
|
||||
r = await ws_manager.send_browser_command(rid, action, browser_id, params, tab_id=tab_id)
|
||||
if p_trace:
|
||||
logger.info(f"[app-agent] RESULT {tool_name} <- {browser_id}: {json.dumps(r)[:300]}")
|
||||
return r
|
||||
return await ws_manager.send_browser_command(rid, action, browser_id, params, tab_id=tab_id)
|
||||
|
||||
result = await p_eval_once()
|
||||
# Reads poll for the bridge to come up (app still mounting on turn 1).
|
||||
@@ -334,8 +286,6 @@ async def execute_browser_tool(
|
||||
p_bridge_known_absent.discard(browser_id)
|
||||
else:
|
||||
p_bridge_known_absent.add(browser_id)
|
||||
if tool_name == "AppDescribe":
|
||||
p_persist_app_controls(browser_id, parse_bridge_result(result))
|
||||
return result
|
||||
|
||||
action = ACTION_MAP.get(tool_name)
|
||||
@@ -344,13 +294,9 @@ async def execute_browser_tool(
|
||||
|
||||
params = {k: v for k, v in tool_input.items()}
|
||||
request_id = uuid4().hex
|
||||
if p_trace:
|
||||
logger.info(f"[app-agent] DISPATCH {tool_name}/{action} -> {browser_id} (req {request_id[:8]})")
|
||||
result = await ws_manager.send_browser_command(
|
||||
request_id, action, browser_id, params, tab_id=tab_id,
|
||||
)
|
||||
if p_trace:
|
||||
logger.info(f"[app-agent] RESULT {tool_name} <- {browser_id}: {json.dumps(result)[:300]}")
|
||||
return result
|
||||
|
||||
|
||||
@@ -604,9 +550,6 @@ async def run_browser_agent(
|
||||
|
||||
p_browser_perms = load_builtin_permissions()
|
||||
|
||||
if app_mode:
|
||||
logger.info(f"[app-agent] START loop: browser_id={browser_id} task={task[:140]!r}")
|
||||
|
||||
session_id = uuid4().hex
|
||||
cancel_event = asyncio.Event()
|
||||
session = AgentSession(
|
||||
|
||||
@@ -485,14 +485,6 @@ async def browser_agent_run(request: Request):
|
||||
if not tasks:
|
||||
return JSONResponse({"error": "tasks array is required"}, status_code=400)
|
||||
|
||||
# [app-agent] step trace: log app-mode dispatches arriving at the route.
|
||||
for p_t in tasks:
|
||||
if p_t.get("app_mode") or str(p_t.get("browser_id", "")).startswith("app:"):
|
||||
logger.info(
|
||||
f"[app-agent] ROUTE /run: browser_id={p_t.get('browser_id')!r} "
|
||||
f"app_mode={p_t.get('app_mode')} task={str(p_t.get('task',''))[:120]!r}"
|
||||
)
|
||||
|
||||
results = await run_browser_agents(
|
||||
tasks=tasks,
|
||||
model=model,
|
||||
|
||||
@@ -203,7 +203,6 @@ def test_app_describe_polls_until_bridge_ready(monkeypatch):
|
||||
|
||||
monkeypatch.setattr(BA.ws_manager, "send_browser_command", p_send, raising=False)
|
||||
monkeypatch.setattr(BA.asyncio, "sleep", p_no_sleep, raising=True)
|
||||
monkeypatch.setattr(BA, "p_persist_app_controls", lambda *a, **k: None, raising=True)
|
||||
|
||||
out = asyncio.run(BA.execute_browser_tool("AppDescribe", {}, "app:abc"))
|
||||
assert calls["n"] == 3 # polled twice, succeeded on the third
|
||||
@@ -225,19 +224,3 @@ def test_app_invoke_does_not_poll(monkeypatch):
|
||||
|
||||
asyncio.run(BA.execute_browser_tool("AppInvoke", {"name": "flap"}, "app:abc"))
|
||||
assert calls["n"] == 1 # single shot, no readiness wait
|
||||
|
||||
|
||||
def test_app_describe_persists_controls_cache(monkeypatch, tmp_path):
|
||||
ready = {"rules": "Keep the bird airborne.", "controls": [{"name": "flap", "keys": "Space"}], "__rev": 1}
|
||||
|
||||
async def p_send(request_id, action, browser_id, params, tab_id=""):
|
||||
return {"text": json.dumps(ready)}
|
||||
|
||||
monkeypatch.setattr(BA.ws_manager, "send_browser_command", p_send, raising=False)
|
||||
monkeypatch.setattr(BA, "p_app_workspace_dir", lambda bid: str(tmp_path), raising=True)
|
||||
|
||||
asyncio.run(BA.execute_browser_tool("AppDescribe", {}, "app:abc"))
|
||||
controls = (tmp_path / ".openswarm" / "controls.md").read_text()
|
||||
rules = (tmp_path / ".openswarm" / "rules.md").read_text()
|
||||
assert "- `flap`" in controls and "[Space]" in controls
|
||||
assert "Keep the bird airborne." in rules
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getWebview, registeredKeys, type BrowserWebview } from './browserRegistry';
|
||||
import { getWebview, type BrowserWebview } from './browserRegistry';
|
||||
import { store } from './state/store';
|
||||
import { resumeBrowserCard } from './state/dashboardLayoutSlice';
|
||||
import { dashboardWs } from './ws/WebSocketManager';
|
||||
@@ -1339,13 +1339,8 @@ async function handleReplayRoute(wv: BrowserWebview, params: Record<string, any>
|
||||
async function handleEvaluate(wv: BrowserWebview, params: Record<string, any>): Promise<Record<string, any>> {
|
||||
const expression = params.expression as string;
|
||||
if (!expression) return { error: 'expression parameter is required' };
|
||||
// [app-agent] step trace: surface what the app's bridge actually returned.
|
||||
const _bridgeCall = expression.includes('OPENSWARM_APP');
|
||||
try {
|
||||
const result = await wv.executeJavaScript(expression);
|
||||
if (_bridgeCall) {
|
||||
console.log(`[app-agent] BRIDGE eval -> ${typeof result === 'string' ? result : JSON.stringify(result)}`);
|
||||
}
|
||||
const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
||||
// evaluate is the agent's main read path; sample routes here too (XHRs have fired by now) so the backend can surface the fast network tier once.
|
||||
const routes_available = await countSafeRoutes(wv);
|
||||
@@ -1405,27 +1400,14 @@ async function runBrowserCommand(
|
||||
request_id: string, action: string, browser_id: string, tab_id: string | undefined,
|
||||
params: Record<string, any>,
|
||||
) {
|
||||
// [app-agent] step trace: only for app targets so browser-agent runs stay quiet.
|
||||
const _appTrace = String(browser_id || '').startsWith('app:');
|
||||
if (_appTrace) {
|
||||
console.log(`[app-agent] CMD recv: action=${action} browser_id=${browser_id} tab_id=${tab_id ?? ''} registered=[${registeredKeys().join(', ')}]`);
|
||||
}
|
||||
const wv = await awaitWebview(browser_id, tab_id || undefined);
|
||||
if (!wv) {
|
||||
if (_appTrace) {
|
||||
console.warn(`[app-agent] LOOKUP MISS: no webview for '${browser_id}' (registered keys: [${registeredKeys().join(', ')}]) -> the app card isn't mounted on the active dashboard`);
|
||||
}
|
||||
dashboardWs.send('browser:result', {
|
||||
request_id,
|
||||
error: `Browser card '${browser_id}'${tab_id ? ` tab '${tab_id}'` : ''} not found or not an Electron webview`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (_appTrace) {
|
||||
let _url = '';
|
||||
try { _url = wv.getURL(); } catch (_e) {}
|
||||
console.log(`[app-agent] LOOKUP HIT: webview for '${browser_id}' found, url=${_url} loading=${(() => { try { return wv.isLoading(); } catch { return '?'; } })()}`);
|
||||
}
|
||||
|
||||
const detail = params.url || params.selector || params.expression || undefined;
|
||||
setActivity(browser_id, { action: action as BrowserAction, detail });
|
||||
|
||||
@@ -54,11 +54,6 @@ export function setActiveTab(browserId: string, tabId: string): void {
|
||||
activeTabMap.set(browserId, tabId);
|
||||
}
|
||||
|
||||
// [app-agent] diagnostic: list currently-registered keys ("browserId:tabId").
|
||||
export function registeredKeys(): string[] {
|
||||
return Array.from(registry.keys());
|
||||
}
|
||||
|
||||
export function getWebview(browserId: string, tabId?: string): BrowserWebview | undefined {
|
||||
const resolvedTabId = tabId || activeTabMap.get(browserId);
|
||||
if (!resolvedTabId) return undefined;
|
||||
|
||||
Reference in New Issue
Block a user