feat: play the Nth YouTube result in browseAndPlay via an index arg
Some checks failed
Release / semantic-release (push) Successful in 22s
tests / Unit tests (Linux, Python 3.11) (push) Failing after 5m16s
Release / build-linux (push) Failing after 7m10s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled

agents/llm.md promises "play the Nth video from the top", but browseAndPlay
only ever clicked the first result. Add an optional 1-based index argument
(default 1, backward-compatible) threaded to the Node helper, which now clicks
the Nth a#video-title and clamps to the number of results returned so asking
beyond the list plays the last available video instead of failing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
javis-bot
2026-06-23 15:33:45 +09:00
parent 5ee47827f3
commit 140fc56f18
4 changed files with 134 additions and 19 deletions

View File

@@ -30,8 +30,10 @@ class BrowseAndPlayTool(Tool):
def description(self) -> str:
return (
"Play a song / music video / clip on the shared screen by searching YouTube "
"and playing the first result. Use when the user asks you to play or watch "
"something. Only available in screen-share mode."
"and playing a result. Use when the user asks you to play or watch "
"something. Plays the first result by default; pass 'index' to play the "
"Nth result from the top of the search list (e.g. 'play the 3rd video' -> "
"index=3). Only available in screen-share mode."
)
@property
@@ -42,7 +44,16 @@ class BrowseAndPlayTool(Tool):
"query": {
"type": "string",
"description": "What to play, e.g. 'IU Good Day' or 'lofi hip hop'.",
}
},
"index": {
"type": "integer",
"description": (
"1-based position of the video to play in the search results, "
"counted from the top of the list. Defaults to 1 (first result). "
"Use for 'play the Nth video' / 'play the second one'."
),
"minimum": 1,
},
},
"required": ["query"],
}
@@ -55,18 +66,25 @@ class BrowseAndPlayTool(Tool):
reply_text="화면 공유 모드(STREAM_BROWSER=true)에서만 영상을 재생할 수 있습니다.",
)
query = ""
index = 1
if args and isinstance(args, dict):
query = str(args.get("query", "")).strip()
try:
index = int(args.get("index", 1) or 1)
except (TypeError, ValueError):
index = 1
if index < 1:
index = 1
if not query:
return ToolExecutionResult(success=False, reply_text="재생할 내용을 알려주세요.")
if not _NODE_SCRIPT.exists():
return ToolExecutionResult(success=False, reply_text="브라우저 재생 도구를 찾을 수 없습니다.")
context.user_print(f"▶️ 화면에서 '{query}' 재생 중…")
debug_log(f" ▶️ browseAndPlay '{query}'", "tools")
context.user_print(f"▶️ 화면에서 '{query}' 재생 중… (#{index})")
debug_log(f" ▶️ browseAndPlay '{query}' index={index}", "tools")
try:
proc = subprocess.run(
["node", str(_NODE_SCRIPT), query, "youtube"],
["node", str(_NODE_SCRIPT), query, "youtube", str(index)],
capture_output=True,
text=True,
timeout=40,

View File

@@ -6,16 +6,24 @@ video, or clip.
### Behaviour
- Public schema is a single required `query` string (what to play).
- Public schema is a required `query` string (what to play) plus an optional
`index` integer (1-based position in the search results, counted from the top
of the list). `index` defaults to `1` (first result), so existing callers and
"play X" requests are unchanged; "play the 3rd video" / "play the second one"
map to `index=3` / `index=2`.
- **Mode-gated**: only acts when `STREAM_BROWSER` is true (`cfg.stream_browser`).
In voice-only mode (false) there is no screen to show, so it returns a short
message and does nothing.
- Drives the on-screen Chrome by subprocessing the Node CDP helper
`bot/scripts/stream-test/browse-search.mjs <query> youtube`, which searches
YouTube and plays the first result on display `:1`. The broadcast captures
that display, so the playback is what viewers see.
- Returns `success` with the played video's title, or a failure message if the
helper/Chrome is unavailable. It does NOT make an LLM call.
`bot/scripts/stream-test/browse-search.mjs <query> youtube <index>`, which
searches YouTube and plays the chosen result on display `:1`. The broadcast
captures that display, so the playback is what viewers see.
- The helper clicks the `index`-th `a#video-title` in the results list. The
index is clamped to the number of results actually returned, so asking for a
position beyond the list plays the last available result rather than failing.
- Returns `success` with the played video's title (and the resolved `index`), or
a failure message if the helper/Chrome is unavailable. It does NOT make an LLM
call.
### Principles