mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
feat(session): support unfocused agent windows (--no-focus) (#87)
`bsk session start --no-focus` opens the Agent Window in the background without stealing focus. The flag threads focused=false through CLI -> daemon IPC -> tool.session_start -> chrome.windows.create; an omitted `focused` keeps the extension default (true) so older clients are unaffected. Reworked onto current main so it composes with #52 window sizing: AgentWindowApi.create / SessionManager.start (extension), start_session (CLI) and the daemon's session start now take options objects/structs ({ size, focused }) instead of colliding positional params. Co-authored-by: alectimison-maker <alec.timison@gmail.com>
This commit is contained in:
co-authored by
alectimison-maker
parent
0ce497f017
commit
f443cfae74
@@ -17,7 +17,7 @@ use crate::cli::business_rpc;
|
||||
use crate::cli::ensure_daemon::ensure_daemon;
|
||||
use crate::cli::error::{CliError, Format};
|
||||
use crate::cli::record_state;
|
||||
use crate::cli::session::{start_session, stop_session};
|
||||
use crate::cli::session::{SessionStartOptions, start_session, stop_session};
|
||||
|
||||
/// Max wait for the user to click 结束 in the browser (24 hours).
|
||||
const RECORD_AWAIT_TIMEOUT_MS: u32 = 86_400_000;
|
||||
@@ -85,7 +85,13 @@ 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, None, None)?;
|
||||
let session = start_session(
|
||||
info.sock_path.clone(),
|
||||
SessionStartOptions {
|
||||
browser: args.browser,
|
||||
..SessionStartOptions::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let start_params = RecordStartParams {
|
||||
session_id: session.session_id.clone(),
|
||||
|
||||
@@ -64,6 +64,10 @@ pub struct SessionStartArgs {
|
||||
/// `--width` and `--height` must be given to take effect.
|
||||
#[arg(long, value_parser = window_size)]
|
||||
pub height: Option<u32>,
|
||||
|
||||
/// Open the Agent Window in the background without stealing focus.
|
||||
#[arg(long)]
|
||||
pub no_focus: bool,
|
||||
}
|
||||
|
||||
/// Parse a `--width` / `--height` Agent Window dimension (CSS pixels).
|
||||
@@ -99,6 +103,8 @@ struct StartParams {
|
||||
width: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
height: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
focused: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -173,7 +179,15 @@ fn run_start(sock: PathBuf, args: SessionStartArgs, format: Format) -> Result<()
|
||||
}
|
||||
});
|
||||
}
|
||||
let result = start_session(sock, args.browser, args.width, args.height);
|
||||
let result = start_session(
|
||||
sock,
|
||||
SessionStartOptions {
|
||||
browser: args.browser,
|
||||
width: args.width,
|
||||
height: args.height,
|
||||
focused: args.no_focus.then_some(false),
|
||||
},
|
||||
);
|
||||
waited.store(true, Ordering::SeqCst);
|
||||
match result {
|
||||
Ok(reply) => match format {
|
||||
@@ -197,20 +211,27 @@ fn run_start(sock: PathBuf, args: SessionStartArgs, format: Format) -> Result<()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Options for [`start_session`]: browser selection plus Agent Window
|
||||
/// creation hints. `None` fields keep the extension-side defaults
|
||||
/// (focused window, browser-chosen size).
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct SessionStartOptions {
|
||||
pub browser: Option<String>,
|
||||
pub width: Option<u32>,
|
||||
pub height: Option<u32>,
|
||||
pub focused: Option<bool>,
|
||||
}
|
||||
|
||||
/// Start a session and open the Agent Window. Used by `session start` and `record start`.
|
||||
pub fn start_session(
|
||||
sock: PathBuf,
|
||||
browser: Option<String>,
|
||||
width: Option<u32>,
|
||||
height: Option<u32>,
|
||||
) -> Result<StartReply, CliError> {
|
||||
pub fn start_session(sock: PathBuf, opts: SessionStartOptions) -> Result<StartReply, CliError> {
|
||||
call(
|
||||
sock,
|
||||
Method::SessionStart,
|
||||
Some(StartParams {
|
||||
browser_instance_id: browser,
|
||||
width,
|
||||
height,
|
||||
browser_instance_id: opts.browser,
|
||||
width: opts.width,
|
||||
height: opts.height,
|
||||
focused: opts.focused,
|
||||
}),
|
||||
SESSION_START_IPC_TIMEOUT,
|
||||
)
|
||||
|
||||
@@ -42,8 +42,8 @@ use tracing::{debug, warn};
|
||||
use super::abort::AbortRegistry;
|
||||
use super::queue::{DEFAULT_TOOL_TIMEOUT, DispatchError};
|
||||
use super::sessions::{
|
||||
SessionId, StartSessionError, StopSessionError, snapshot_status_entries, start_session,
|
||||
stop_session,
|
||||
AgentWindowOptions, SessionId, StartSessionError, StopSessionError, snapshot_status_entries,
|
||||
start_session, stop_session,
|
||||
};
|
||||
use super::state::{DAEMON_VERSION, DaemonState, PROTOCOL_VERSION};
|
||||
|
||||
@@ -545,6 +545,8 @@ struct CliSessionStartParams {
|
||||
pub width: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub height: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub focused: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -638,6 +640,7 @@ async fn handle_session_start(state: &Arc<DaemonState>, params: Value) -> Result
|
||||
browser_instance_id: None,
|
||||
width: None,
|
||||
height: None,
|
||||
focused: None,
|
||||
}
|
||||
} else {
|
||||
serde_json::from_value(params).map_err(|err| RpcError {
|
||||
@@ -664,7 +667,10 @@ async fn handle_session_start(state: &Arc<DaemonState>, params: Value) -> Result
|
||||
&state.sessions,
|
||||
&state.tool_queues,
|
||||
params.browser_instance_id.as_deref(),
|
||||
window_size,
|
||||
AgentWindowOptions {
|
||||
size: window_size,
|
||||
focused: params.focused,
|
||||
},
|
||||
state.config.extension_connect_wait,
|
||||
DEFAULT_RPC_TIMEOUT,
|
||||
)
|
||||
|
||||
@@ -390,6 +390,17 @@ pub enum StopSessionError {
|
||||
/// of live sessions.
|
||||
const SESSION_ID_MAX_RESERVE_ATTEMPTS: u32 = 64;
|
||||
|
||||
/// Agent Window creation hints forwarded to the extension on
|
||||
/// `tool.session_start`. `None` fields keep the extension-side defaults
|
||||
/// (focused window, browser-chosen size).
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct AgentWindowOptions {
|
||||
/// Optional outer size as `(width, height)` CSS pixels.
|
||||
pub size: Option<(u32, u32)>,
|
||||
/// Optional focus hint (`None` = extension default: focused).
|
||||
pub focused: Option<bool>,
|
||||
}
|
||||
|
||||
/// Ask the chosen browser to create a fresh Agent Window for a brand-new
|
||||
/// session id, registering the result on success.
|
||||
///
|
||||
@@ -401,8 +412,7 @@ pub async fn start_session(
|
||||
sessions: &Arc<SessionRegistry>,
|
||||
queues: &Arc<ToolQueueRegistry>,
|
||||
requested: Option<&str>,
|
||||
// Optional Agent Window outer size as `(width, height)` CSS pixels.
|
||||
window_size: Option<(u32, u32)>,
|
||||
window: AgentWindowOptions,
|
||||
connect_wait: Duration,
|
||||
timeout_dur: Duration,
|
||||
) -> Result<Session, StartSessionError> {
|
||||
@@ -429,8 +439,9 @@ pub async fn start_session(
|
||||
let params = SessionStartParams {
|
||||
session_id: session_id.0.clone(),
|
||||
browser_instance_id: Some(client.id.0.clone()),
|
||||
width: window_size.map(|(width, _)| width),
|
||||
height: window_size.map(|(_, height)| height),
|
||||
width: window.size.map(|(width, _)| width),
|
||||
height: window.size.map(|(_, height)| height),
|
||||
focused: window.focused,
|
||||
};
|
||||
let rpc_id = next_rpc_id("sess-start");
|
||||
let request = RequestFrame {
|
||||
|
||||
Reference in New Issue
Block a user