mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
feat: add bsk network read-only command
Add `bsk network` as a sibling of the `console` command from #7, reading the buffered network responses / failures for a tab. Fills the remaining half of the request in #2 (console landed in #7; network did not). Mirrors the console command's conventions exactly: bsk network --session <id> [--tab-id N] [--since C] [--limit N] [--max-text-chars N] The extension enables `Network` on attach (best-effort) and buffers `responseReceived` / `loadingFailed` events per tab with a monotonic sequence, correlating them to `requestWillBeSent` for method/URL. Reads are cursor-paginated (`since` -> `next_since`) and bounded (`limit`, `max_text_chars`) for agent-context safety, same as console. Entries carry the real HTTP status code (response) or the CDP failure reason (failure) — things `performance.getEntriesByType('resource')` can't give. Classified read-only (pass-through under the pending-interrupt gate); defaults to the Agent Window's active tab. Wires bsk-protocol (Method + params/result/entry structs + schema), bsk-cli (network subcommand + daemon dispatch allow-list) and the extension (handler + dispatcher route), with unit / parse / IPC tests paralleling the console command. Refs #2.
This commit is contained in:
@@ -18,6 +18,7 @@ pub mod install_skill;
|
||||
pub mod interaction;
|
||||
pub mod logs;
|
||||
pub mod navigate;
|
||||
pub mod network;
|
||||
pub mod render_error;
|
||||
pub mod screenshot;
|
||||
pub mod session;
|
||||
@@ -37,6 +38,7 @@ use crate::cli::human_loop::RequestHelpArgs;
|
||||
use crate::cli::install_skill::InstallSkillArgs;
|
||||
use crate::cli::interaction::{ClickArgs, FillArgs, PressArgs, SelectArgs};
|
||||
use crate::cli::navigate::{NavigateCommand, NavigateHistoryArgs, ReloadArgs};
|
||||
use crate::cli::network::NetworkArgs;
|
||||
use crate::cli::screenshot::ScreenshotArgs;
|
||||
use crate::cli::session::SessionCmd;
|
||||
use crate::cli::snapshot::SnapshotArgs;
|
||||
@@ -120,6 +122,9 @@ pub enum Command {
|
||||
/// Read buffered console/log/exception messages.
|
||||
Console(ConsoleArgs),
|
||||
|
||||
/// Read buffered network responses / failures.
|
||||
Network(NetworkArgs),
|
||||
|
||||
/// Dump raw HTML for a tab or a snapshot ref.
|
||||
#[command(name = "get-html")]
|
||||
GetHtml(GetHtmlArgs),
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
//! `bsk network` — read buffered page network responses / failures.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Context;
|
||||
use bsk_protocol::Method;
|
||||
use bsk_protocol::tools::{NetworkEntry, NetworkParams, NetworkResult};
|
||||
use clap::Args;
|
||||
|
||||
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 NetworkArgs {
|
||||
/// Session id (must be active).
|
||||
#[arg(long)]
|
||||
pub session: String,
|
||||
|
||||
/// Target tab. Defaults to the Agent Window's active tab.
|
||||
#[arg(long = "tab-id")]
|
||||
pub tab_id: Option<i64>,
|
||||
|
||||
/// Return entries with sequence greater than this cursor.
|
||||
#[arg(long)]
|
||||
pub since: Option<u64>,
|
||||
|
||||
/// Maximum number of entries to return. Defaults to 50; extension caps at 200.
|
||||
#[arg(long, value_parser = clap::value_parser!(u32).range(1..))]
|
||||
pub limit: Option<u32>,
|
||||
|
||||
/// Maximum characters per URL / error text. Defaults to 1000; extension caps at 4096.
|
||||
#[arg(long = "max-text-chars", value_parser = clap::value_parser!(u32).range(1..))]
|
||||
pub max_text_chars: Option<u32>,
|
||||
}
|
||||
|
||||
pub fn dispatch(args: NetworkArgs, format: Format) -> Result<(), CliError> {
|
||||
let info = ensure_daemon().context("ensure daemon is running")?;
|
||||
run(info.sock_path, args, format)
|
||||
}
|
||||
|
||||
fn run(sock: PathBuf, args: NetworkArgs, format: Format) -> Result<(), CliError> {
|
||||
let params = NetworkParams {
|
||||
session_id: args.session,
|
||||
tab_id: args.tab_id,
|
||||
since: args.since,
|
||||
limit: args.limit,
|
||||
max_text_chars: args.max_text_chars,
|
||||
};
|
||||
let reply: NetworkResult = call(sock, params)?;
|
||||
render(&reply, format)
|
||||
}
|
||||
|
||||
fn call(sock: PathBuf, params: NetworkParams) -> Result<NetworkResult, CliError> {
|
||||
crate::cli::business_rpc::call::<NetworkParams, NetworkResult>(
|
||||
sock,
|
||||
"network",
|
||||
Method::ToolNetwork,
|
||||
Some(params),
|
||||
TOOL_IPC_TIMEOUT,
|
||||
)
|
||||
}
|
||||
|
||||
fn render(reply: &NetworkResult, format: Format) -> Result<(), CliError> {
|
||||
match format {
|
||||
Format::Json => {
|
||||
let json = serde_json::to_string_pretty(reply)
|
||||
.map_err(|e| CliError::Local(anyhow::anyhow!(e)))?;
|
||||
println!("{json}");
|
||||
}
|
||||
Format::Human => {
|
||||
if reply.entries.is_empty() {
|
||||
println!("(no network activity captured)");
|
||||
} else {
|
||||
for entry in &reply.entries {
|
||||
println!("{}", render_entry(entry));
|
||||
}
|
||||
}
|
||||
if reply.truncated {
|
||||
eprintln!(
|
||||
"warning: network output truncated (next_since={}). Use --since / --limit / --max-text-chars to request a different slice.",
|
||||
reply.next_since
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_entry(entry: &NetworkEntry) -> String {
|
||||
let method = entry.method.as_deref().unwrap_or("?");
|
||||
match entry.kind {
|
||||
bsk_protocol::tools::NetworkEntryKind::Failure => {
|
||||
let err = entry.error_text.as_deref().unwrap_or("failed");
|
||||
format!("#{} FAILED {method} {} — {err}", entry.sequence, entry.url)
|
||||
}
|
||||
bsk_protocol::tools::NetworkEntryKind::Response => {
|
||||
let status = entry
|
||||
.status
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| "?".to_string());
|
||||
format!("#{} {status} {method} {}", entry.sequence, entry.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user