feat: controlBrowser 'search' action + one-sentence voice replies
- Add a one-shot `search` action (site=naver/google/daum/youtube/bing) that navigates the on-screen browser straight to the results page, so a small model can satisfy "search X on Naver" in a single tool call instead of a fragile navigate->type->enter chain. - Sharpen the tool description to steer the router to controlBrowser (not webSearch) for anything that should happen IN the visible browser. - System prompt: answer in one short sentence (voice assistant) — also cuts TTS time. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,32 @@ try {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'search': {
|
||||||
|
// One-shot "search on a site": build the engine's results URL so a small
|
||||||
|
// model doesn't have to chain navigate->type->enter. Visible on screen.
|
||||||
|
const q = String(cmd.query || '').trim();
|
||||||
|
if (!q) throw new Error('search: no query');
|
||||||
|
const site = String(cmd.site || 'google').toLowerCase();
|
||||||
|
const engines = {
|
||||||
|
naver: 'https://search.naver.com/search.naver?query=',
|
||||||
|
google: 'https://www.google.com/search?q=',
|
||||||
|
daum: 'https://search.daum.net/search?q=',
|
||||||
|
youtube: 'https://www.youtube.com/results?search_query=',
|
||||||
|
bing: 'https://www.bing.com/search?q=',
|
||||||
|
};
|
||||||
|
const base = engines[site] || engines.google;
|
||||||
|
const target = base + encodeURIComponent(q);
|
||||||
|
await front(page);
|
||||||
|
if (HAS_XDOTOOL && cmd.human !== false) {
|
||||||
|
try { await human.navigateOmnibox(target); await page.waitForLoadState('domcontentloaded').catch(() => {}); }
|
||||||
|
catch { await page.goto(target, { waitUntil: 'domcontentloaded' }); }
|
||||||
|
} else {
|
||||||
|
await page.goto(target, { waitUntil: 'domcontentloaded' });
|
||||||
|
}
|
||||||
|
out({ ok: true, site: engines[site] ? site : 'google', query: q, url: page.url(), title: await page.title().catch(() => '') });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'back': await front(page); await page.goBack({ waitUntil: 'domcontentloaded' }).catch(() => {}); out({ ok: true, url: page.url() }); break;
|
case 'back': await front(page); await page.goBack({ waitUntil: 'domcontentloaded' }).catch(() => {}); out({ ok: true, url: page.url() }); break;
|
||||||
case 'forward': await front(page); await page.goForward({ waitUntil: 'domcontentloaded' }).catch(() => {}); out({ ok: true, url: page.url() }); break;
|
case 'forward': await front(page); await page.goForward({ waitUntil: 'domcontentloaded' }).catch(() => {}); out({ ok: true, url: page.url() }); break;
|
||||||
case 'refresh': await front(page); await reload(); out({ ok: true, url: page.url() }); break;
|
case 'refresh': await front(page); await reload(); out({ ok: true, url: page.url() }); break;
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ _SYSTEM_PROMPT_TEMPLATE: str = (
|
|||||||
"butler clichés, and never address the user as 'sir', 'madam', 'my liege', or similar. "
|
"butler clichés, and never address the user as 'sir', 'madam', 'my liege', or similar. "
|
||||||
"Never stack multiple jokes in one reply. "
|
"Never stack multiple jokes in one reply. "
|
||||||
"Be concise, conversational, and actionable. "
|
"Be concise, conversational, and actionable. "
|
||||||
|
"This is a spoken voice assistant: answer in ONE short sentence whenever possible "
|
||||||
|
"(two at the very most). No lists, no preamble, no 'is there anything else' offers. "
|
||||||
"Never claim you performed an action — opening a website, navigating the browser, "
|
"Never claim you performed an action — opening a website, navigating the browser, "
|
||||||
"playing or showing something on screen, changing a setting, sending a message — unless a "
|
"playing or showing something on screen, changing a setting, sending a message — unless a "
|
||||||
"tool actually did it this turn and reported success. If you have no tool for what was asked, "
|
"tool actually did it this turn and reported success. If you have no tool for what was asked, "
|
||||||
|
|||||||
@@ -41,12 +41,14 @@ class ControlBrowserTool(Tool):
|
|||||||
@property
|
@property
|
||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return (
|
return (
|
||||||
"Operate the on-screen Chrome shown on the broadcast like a person: open a "
|
"Drive the on-screen Chrome that is shown on the broadcast, like a person would. "
|
||||||
"website/URL, list/open/close/switch tabs, close popups, click an element, "
|
"Use this (NOT webSearch) whenever the user wants something done or shown IN the "
|
||||||
"type text, scroll, or screenshot. Use whenever the user asks to go to a site "
|
"browser on screen: open a website or URL, search on a specific site (action "
|
||||||
"(e.g. 'open Naver'), check what's on screen, or interact with the page. "
|
"'search' with site=naver/google/daum/youtube/bing), go back/forward, refresh, "
|
||||||
"Only available in screen-share mode. Do NOT claim you did any of this unless "
|
"manage tabs (list/new/close/switch), close popups, click, type, scroll, or "
|
||||||
"this tool returns success."
|
"screenshot. webSearch only returns text and shows nothing on screen; this tool "
|
||||||
|
"actually navigates the visible browser. Only available in screen-share mode. "
|
||||||
|
"Never claim you did any of this unless this tool returns success."
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -57,14 +59,16 @@ class ControlBrowserTool(Tool):
|
|||||||
"action": {
|
"action": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"status", "listTabs", "navigate", "back", "forward",
|
"status", "listTabs", "navigate", "search", "back",
|
||||||
"refresh", "newTab", "closeTab", "activateTab",
|
"forward", "refresh", "newTab", "closeTab", "activateTab",
|
||||||
"closePopups", "click", "type", "scroll", "pressKey",
|
"closePopups", "click", "type", "scroll", "pressKey",
|
||||||
"screenshot",
|
"screenshot",
|
||||||
],
|
],
|
||||||
"description": "What to do in the browser.",
|
"description": "What to do in the browser.",
|
||||||
},
|
},
|
||||||
"url": {"type": "string", "description": "Target URL/site for navigate/newTab (e.g. 'naver.com')."},
|
"url": {"type": "string", "description": "Target URL/site for navigate/newTab (e.g. 'naver.com')."},
|
||||||
|
"query": {"type": "string", "description": "Search text for action 'search'."},
|
||||||
|
"site": {"type": "string", "description": "Search site for action 'search': naver, google, daum, youtube, bing."},
|
||||||
"index": {"type": "integer", "description": "Tab index for closeTab/activateTab (from listTabs)."},
|
"index": {"type": "integer", "description": "Tab index for closeTab/activateTab (from listTabs)."},
|
||||||
"selector": {"type": "string", "description": "CSS selector for click/type."},
|
"selector": {"type": "string", "description": "CSS selector for click/type."},
|
||||||
"text": {"type": "string", "description": "Text to type."},
|
"text": {"type": "string", "description": "Text to type."},
|
||||||
@@ -129,6 +133,10 @@ class ControlBrowserTool(Tool):
|
|||||||
def _summarise(action: str, args: Dict[str, Any], data: Dict[str, Any]) -> str:
|
def _summarise(action: str, args: Dict[str, Any], data: Dict[str, Any]) -> str:
|
||||||
if action == "navigate":
|
if action == "navigate":
|
||||||
return f"브라우저에서 {data.get('url', args.get('url'))} 로 이동했습니다."
|
return f"브라우저에서 {data.get('url', args.get('url'))} 로 이동했습니다."
|
||||||
|
if action == "search":
|
||||||
|
return f"{data.get('site', '')}에서 '{data.get('query', args.get('query'))}'를 검색해 화면에 띄웠습니다."
|
||||||
|
if action in ("back", "forward", "refresh"):
|
||||||
|
return f"브라우저: {action} 완료 ({data.get('url', '')})."
|
||||||
if action in ("status", "listTabs"):
|
if action in ("status", "listTabs"):
|
||||||
tabs = data.get("tabs", [])
|
tabs = data.get("tabs", [])
|
||||||
lines = "\n".join(f" [{t['index']}] {t.get('title') or t['url']}" for t in tabs) or " (탭 없음)"
|
lines = "\n".join(f" [{t['index']}] {t.get('title') or t['url']}" for t in tabs) or " (탭 없음)"
|
||||||
|
|||||||
Reference in New Issue
Block a user