Addresses review findings on the dockerized stack: - Container Chrome search was dead: add --remote-debugging-port + a non-default --user-data-dir (Chrome 136+ refuses CDP on the default profile), add the playwright dep (browse-search.mjs connectOverCDP) with browser download skipped, and connect to 127.0.0.1 not "localhost" (container localhost -> ::1 while Chrome binds IPv4). Verified: browse-search returns real results. - Broadcast toggle reliability: always offer setBroadcast in screen-share mode (the embedding/keyword router dropped it for non-English utterances) and make its description force a tool call. "방송 꺼줘"->stop now 5/5; no false triggers. - Stop the broadcast on voice leave (no orphaned stream). - Security: bind VNC/noVNC to loopback by default (VNC_BIND override) and the bridge to the container loopback (BRIDGE_HOST=127.0.0.1), not published. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
78 lines
3.0 KiB
Python
78 lines
3.0 KiB
Python
"""Start or stop the live screen-share broadcast (Go-Live) on request.
|
|
|
|
The brain cannot drive the Discord broadcast itself — the bot owns it. This
|
|
tool records a directive ("start"/"stop") in the per-turn state; the bridge
|
|
returns it to the bot, which performs the actual start/stop. Only meaningful in
|
|
screen-share mode (``STREAM_BROWSER`` true); when broadcasting is disabled the
|
|
tool refuses so the user is told it cannot be turned on.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Dict, Any, Optional
|
|
|
|
from ..base import Tool, ToolContext
|
|
from ..types import ToolExecutionResult
|
|
from ...debug import debug_log
|
|
|
|
|
|
class SetBroadcastTool(Tool):
|
|
"""Turn the live screen-share broadcast on or off."""
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
return "setBroadcast"
|
|
|
|
@property
|
|
def description(self) -> str:
|
|
return (
|
|
"Control the live screen-share broadcast (Go-Live). You MUST call this "
|
|
"tool — do not just reply in words — whenever the user asks to turn the "
|
|
"broadcast or screen ON or OFF: start/begin/turn on/show your screen/go "
|
|
"live -> action='start'; stop/end/turn off/hide your screen/stop "
|
|
"streaming -> action='stop'. Saying you did it without calling this tool "
|
|
"does nothing. Only available in screen-share mode."
|
|
)
|
|
|
|
@property
|
|
def inputSchema(self) -> Dict[str, Any]:
|
|
return {
|
|
"type": "object",
|
|
"properties": {
|
|
"action": {
|
|
"type": "string",
|
|
"enum": ["start", "stop"],
|
|
"description": "'start' to begin the broadcast, 'stop' to end it.",
|
|
}
|
|
},
|
|
"required": ["action"],
|
|
}
|
|
|
|
def run(self, args: Optional[Dict[str, Any]], context: ToolContext) -> ToolExecutionResult:
|
|
cfg = context.cfg
|
|
if not getattr(cfg, "stream_browser", True):
|
|
# Broadcast capability is disabled (STREAM_BROWSER=false): refuse so
|
|
# the user is clearly told it can't be turned on in this mode.
|
|
return ToolExecutionResult(
|
|
success=False,
|
|
reply_text="방송(화면 공유) 기능이 꺼져 있어 켤 수 없습니다 (STREAM_BROWSER=false).",
|
|
)
|
|
|
|
action = ""
|
|
if args and isinstance(args, dict):
|
|
action = str(args.get("action", "")).strip().lower()
|
|
if action not in ("start", "stop"):
|
|
return ToolExecutionResult(
|
|
success=False,
|
|
reply_text="Specify action='start' or action='stop'.",
|
|
)
|
|
|
|
# Record the directive for the bridge to return to the bot.
|
|
from ...reply.turn_state import request_broadcast
|
|
request_broadcast(action)
|
|
debug_log(f" 📡 setBroadcast requested: {action}", "stream")
|
|
return ToolExecutionResult(
|
|
success=True,
|
|
reply_text="방송을 시작합니다." if action == "start" else "방송을 종료합니다.",
|
|
)
|