mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
feat: support Agent Window sizing (session start --width/--height, window resize) (#52)
- `bsk session start --width <px> --height <px>` opens the Agent Window with the given outer size (chrome.windows.create) - new `bsk window resize --session <id> --width <px> --height <px>` resizes the Agent Window at runtime (chrome.windows.update) - both validated on CLI, daemon and extension sides (100..=7680 CSS px) - protocol: new tool.window_resize method (BrowserMutation) and optional width/height on tool.session_start params; schemas regenerated
This commit is contained in:
@@ -30,6 +30,7 @@ pub mod status;
|
||||
pub mod tab;
|
||||
pub mod update;
|
||||
pub mod waits;
|
||||
pub mod window;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
@@ -50,6 +51,7 @@ use crate::cli::snapshot::SnapshotArgs;
|
||||
use crate::cli::tab::TabCmd;
|
||||
use crate::cli::update::UpdateArgs;
|
||||
use crate::cli::waits::{WaitForNavigationArgs, WaitMsArgs};
|
||||
use crate::cli::window::WindowCmd;
|
||||
|
||||
/// Tool calls wait slightly longer than the daemon's 30s tool timeout so
|
||||
/// callers receive the structured daemon timeout instead of dropping the
|
||||
@@ -118,6 +120,9 @@ pub enum Command {
|
||||
/// Tab management commands.
|
||||
Tab(TabCmd),
|
||||
|
||||
/// Agent Window management commands.
|
||||
Window(WindowCmd),
|
||||
|
||||
/// Capture a PNG of the active tab or a snapshot ref element.
|
||||
Screenshot(ScreenshotArgs),
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ fn dispatch_start(args: RecordStartArgs, format: Format) -> Result<(), CliError>
|
||||
}
|
||||
|
||||
let info = ensure_daemon().context("ensure daemon is running")?;
|
||||
let session = start_session(info.sock_path.clone(), args.browser)?;
|
||||
let session = start_session(info.sock_path.clone(), args.browser, None, None)?;
|
||||
|
||||
let start_params = RecordStartParams {
|
||||
session_id: session.session_id.clone(),
|
||||
|
||||
@@ -54,6 +54,30 @@ pub struct SessionStartArgs {
|
||||
/// are connected).
|
||||
#[arg(long)]
|
||||
pub browser: Option<String>,
|
||||
|
||||
/// Agent Window outer width in CSS pixels (100..=7680). Both
|
||||
/// `--width` and `--height` must be given to take effect.
|
||||
#[arg(long, value_parser = window_size)]
|
||||
pub width: Option<u32>,
|
||||
|
||||
/// Agent Window outer height in CSS pixels (100..=7680). Both
|
||||
/// `--width` and `--height` must be given to take effect.
|
||||
#[arg(long, value_parser = window_size)]
|
||||
pub height: Option<u32>,
|
||||
}
|
||||
|
||||
/// Parse a `--width` / `--height` Agent Window dimension (CSS pixels).
|
||||
fn window_size(s: &str) -> Result<u32, String> {
|
||||
let value: u32 = s
|
||||
.parse()
|
||||
.map_err(|_| format!("invalid window dimension {s:?}: expected a positive integer"))?;
|
||||
if (100..=7680).contains(&value) {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(format!(
|
||||
"window dimension {value} out of range (100..=7680)"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Args)]
|
||||
@@ -71,6 +95,10 @@ pub struct SessionStopArgs {
|
||||
struct StartParams {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
browser_instance_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
width: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
height: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -124,6 +152,11 @@ pub fn dispatch(cmd: SessionCmd, format: Format) -> Result<(), CliError> {
|
||||
}
|
||||
|
||||
fn run_start(sock: PathBuf, args: SessionStartArgs, format: Format) -> Result<(), CliError> {
|
||||
if args.width.is_some() != args.height.is_some() {
|
||||
return Err(CliError::Local(anyhow::anyhow!(
|
||||
"--width and --height must be given together"
|
||||
)));
|
||||
}
|
||||
// The daemon may hold `session.start` open for up to
|
||||
// `EXTENSION_CONNECT_WAIT` while a just-woken service worker
|
||||
// reconnects. Let the user know we are waiting rather than hung —
|
||||
@@ -140,7 +173,7 @@ fn run_start(sock: PathBuf, args: SessionStartArgs, format: Format) -> Result<()
|
||||
}
|
||||
});
|
||||
}
|
||||
let result = start_session(sock, args.browser);
|
||||
let result = start_session(sock, args.browser, args.width, args.height);
|
||||
waited.store(true, Ordering::SeqCst);
|
||||
match result {
|
||||
Ok(reply) => match format {
|
||||
@@ -165,12 +198,19 @@ fn run_start(sock: PathBuf, args: SessionStartArgs, format: Format) -> Result<()
|
||||
}
|
||||
|
||||
/// Start a session and open the Agent Window. Used by `session start` and `record start`.
|
||||
pub fn start_session(sock: PathBuf, browser: Option<String>) -> Result<StartReply, CliError> {
|
||||
pub fn start_session(
|
||||
sock: PathBuf,
|
||||
browser: Option<String>,
|
||||
width: Option<u32>,
|
||||
height: Option<u32>,
|
||||
) -> Result<StartReply, CliError> {
|
||||
call(
|
||||
sock,
|
||||
Method::SessionStart,
|
||||
Some(StartParams {
|
||||
browser_instance_id: browser,
|
||||
width,
|
||||
height,
|
||||
}),
|
||||
SESSION_START_IPC_TIMEOUT,
|
||||
)
|
||||
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
//! `bsk window …` subcommands — Agent Window management.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Context;
|
||||
use bsk_protocol::Method;
|
||||
use bsk_protocol::tools::{WindowResizeParams, WindowResizeResult};
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
use crate::cli::TOOL_IPC_TIMEOUT;
|
||||
use crate::cli::ensure_daemon::ensure_daemon;
|
||||
use crate::cli::error::{CliError, Format};
|
||||
|
||||
#[derive(Debug, Clone, Args)]
|
||||
pub struct WindowCmd {
|
||||
#[command(subcommand)]
|
||||
pub sub: WindowSub,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Subcommand)]
|
||||
pub enum WindowSub {
|
||||
/// Resize the session's Agent Window.
|
||||
Resize(WindowResizeArgs),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Args)]
|
||||
pub struct WindowResizeArgs {
|
||||
/// Session id (must be active).
|
||||
#[arg(long)]
|
||||
pub session: String,
|
||||
|
||||
/// New Agent Window outer width in CSS pixels (100..=7680).
|
||||
#[arg(long, value_parser = window_size)]
|
||||
pub width: u32,
|
||||
|
||||
/// New Agent Window outer height in CSS pixels (100..=7680).
|
||||
#[arg(long, value_parser = window_size)]
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
/// Parse a `--width` / `--height` Agent Window dimension (CSS pixels).
|
||||
fn window_size(s: &str) -> Result<u32, String> {
|
||||
let value: u32 = s
|
||||
.parse()
|
||||
.map_err(|_| format!("invalid window dimension {s:?}: expected a positive integer"))?;
|
||||
if (100..=7680).contains(&value) {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(format!(
|
||||
"window dimension {value} out of range (100..=7680)"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch(cmd: WindowCmd, format: Format) -> Result<(), CliError> {
|
||||
let info = ensure_daemon().context("ensure daemon is running")?;
|
||||
match cmd.sub {
|
||||
WindowSub::Resize(args) => run_resize(info.sock_path, args, format),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_resize(sock: PathBuf, args: WindowResizeArgs, format: Format) -> Result<(), CliError> {
|
||||
let params = WindowResizeParams {
|
||||
session_id: args.session,
|
||||
width: args.width,
|
||||
height: args.height,
|
||||
};
|
||||
let reply: WindowResizeResult =
|
||||
ipc_call("window-resize-1", Method::ToolWindowResize, sock, params)?;
|
||||
match format {
|
||||
Format::Json => {
|
||||
let json = serde_json::to_string_pretty(&reply)
|
||||
.map_err(|e| CliError::Local(anyhow::anyhow!(e)))?;
|
||||
println!("{json}");
|
||||
}
|
||||
Format::Human => {
|
||||
println!(
|
||||
"resized window_id={} to {}x{}",
|
||||
reply.window_id, reply.width, reply.height
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ipc_call<P, R>(
|
||||
rpc_id_prefix: &'static str,
|
||||
method: Method,
|
||||
sock: PathBuf,
|
||||
params: P,
|
||||
) -> Result<R, CliError>
|
||||
where
|
||||
P: serde::Serialize + Send + 'static,
|
||||
R: serde::de::DeserializeOwned + Send + 'static,
|
||||
{
|
||||
crate::cli::business_rpc::call::<P, R>(
|
||||
sock,
|
||||
rpc_id_prefix,
|
||||
method,
|
||||
Some(params),
|
||||
TOOL_IPC_TIMEOUT,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user