diff --git a/apps/extension/src/content/BorrowConfirmationOverlay.tsx b/apps/extension/src/content/BorrowConfirmationOverlay.tsx
index 0931834..9258505 100644
--- a/apps/extension/src/content/BorrowConfirmationOverlay.tsx
+++ b/apps/extension/src/content/BorrowConfirmationOverlay.tsx
@@ -67,8 +67,8 @@ function BorrowRequestItem({ request, isModal }: { request: BorrowRequestData; i
const totalSeconds = Math.ceil(request.timeoutMs / 1000);
const [secondsLeft, setSecondsLeft] = useState(totalSeconds);
const [exiting, setExiting] = useState(false);
- const [awaitingAutoAllow, setAwaitingAutoAllow] = useState(false);
- const allowedRef = useRef(false);
+ const [awaitingAutoDeny, setAwaitingAutoDeny] = useState(false);
+ const settledRef = useRef(false);
const onAllowRef = useRef(request.onAllow);
const onDenyRef = useRef(request.onDeny);
onAllowRef.current = request.onAllow;
@@ -76,8 +76,8 @@ function BorrowRequestItem({ request, isModal }: { request: BorrowRequestData; i
useEffect(() => {
if (secondsLeft <= 0) {
- if (!allowedRef.current) {
- setAwaitingAutoAllow(true);
+ if (!settledRef.current) {
+ setAwaitingAutoDeny(true);
}
return;
}
@@ -86,9 +86,9 @@ function BorrowRequestItem({ request, isModal }: { request: BorrowRequestData; i
}, [secondsLeft]);
function triggerAllow() {
- if (allowedRef.current) return;
- allowedRef.current = true;
- setAwaitingAutoAllow(false);
+ if (settledRef.current) return;
+ settledRef.current = true;
+ setAwaitingAutoDeny(false);
setExiting(true);
setTimeout(() => onAllowRef.current(), EXIT_ANIMATION_MS);
}
@@ -98,9 +98,9 @@ function BorrowRequestItem({ request, isModal }: { request: BorrowRequestData; i
}
function handleDeny() {
- if (allowedRef.current) return;
- allowedRef.current = true;
- setAwaitingAutoAllow(false);
+ if (settledRef.current) return;
+ settledRef.current = true;
+ setAwaitingAutoDeny(false);
setExiting(true);
setTimeout(() => onDenyRef.current(), EXIT_ANIMATION_MS);
}
@@ -128,13 +128,13 @@ function BorrowRequestItem({ request, isModal }: { request: BorrowRequestData; i
progress={progress}
seconds={secondsLeft}
onProgressTransitionEnd={(propertyName) => {
- if (!awaitingAutoAllow || secondsLeft !== 0) return;
+ if (!awaitingAutoDeny || secondsLeft !== 0) return;
if (propertyName !== "stroke-dashoffset") return;
- triggerAllow();
+ handleDeny();
}}
/>
- {t("borrowConfirmation.autoAllow", { count: secondsLeft })}
+ {t("borrowConfirmation.autoDeny", { count: secondsLeft })}
@@ -156,9 +156,9 @@ function BorrowRequestItem({ request, isModal }: { request: BorrowRequestData; i
{
- if (!awaitingAutoAllow || secondsLeft !== 0) return;
+ if (!awaitingAutoDeny || secondsLeft !== 0) return;
if (event.propertyName !== "width") return;
- triggerAllow();
+ handleDeny();
}}
/>
diff --git a/apps/extension/src/content/__tests__/BorrowConfirmationOverlay.test.tsx b/apps/extension/src/content/__tests__/BorrowConfirmationOverlay.test.tsx
index 5ac6c2e..04be736 100644
--- a/apps/extension/src/content/__tests__/BorrowConfirmationOverlay.test.tsx
+++ b/apps/extension/src/content/__tests__/BorrowConfirmationOverlay.test.tsx
@@ -12,7 +12,7 @@ describe("BorrowConfirmationOverlay", () => {
vi.useRealTimers();
});
- it("auto-allows after countdown reaches zero and progress transition ends", async () => {
+ it("auto-denies after countdown reaches zero and progress transition ends", async () => {
const onAllow = vi.fn();
const onDeny = vi.fn();
@@ -37,7 +37,7 @@ describe("BorrowConfirmationOverlay", () => {
await vi.advanceTimersByTimeAsync(1000);
});
}
- expect(screen.getByText("0 秒后自动允许")).toBeTruthy();
+ expect(screen.getByText("0 秒后自动拒绝")).toBeTruthy();
await act(async () => {
await vi.advanceTimersByTimeAsync(1000);
});
@@ -54,8 +54,8 @@ describe("BorrowConfirmationOverlay", () => {
await act(async () => {
await vi.advanceTimersByTimeAsync(150);
});
- expect(onAllow).toHaveBeenCalledTimes(1);
- expect(onDeny).not.toHaveBeenCalled();
+ expect(onDeny).toHaveBeenCalledTimes(1);
+ expect(onAllow).not.toHaveBeenCalled();
});
it("only invokes onDeny once when deny is clicked repeatedly", async () => {
diff --git a/apps/extension/src/tools/__tests__/borrow-confirmation.test.ts b/apps/extension/src/tools/__tests__/borrow-confirmation.test.ts
index 3025f40..5c4dbe6 100644
--- a/apps/extension/src/tools/__tests__/borrow-confirmation.test.ts
+++ b/apps/extension/src/tools/__tests__/borrow-confirmation.test.ts
@@ -162,6 +162,22 @@ describe("requestBorrowConfirmation", () => {
expect(await pending).toBe(false);
});
+ it("denies malformed or missing confirmation responses", async () => {
+ tabs.get.mockResolvedValue({ id: 42, title: "Tab" });
+ windows.getLastFocused.mockResolvedValue(
+ userWindowWithActiveTab({ windowId: 11, tabId: 42, url: "https://app.example/" }),
+ );
+
+ for (const response of [undefined, {}, { allowed: true }, { type: "borrow-response" }]) {
+ tabs.sendMessage.mockResolvedValueOnce(response);
+ const pending = requestBorrowConfirmation(42, {
+ deps: { tabs, windows, notifications },
+ });
+ await vi.runAllTimersAsync();
+ expect(await pending).toBe(false);
+ }
+ });
+
it("skips the Agent Window when it is lastFocusedWindow and falls back to a real user window", async () => {
tabs.get.mockResolvedValueOnce({
id: 42,
@@ -250,7 +266,7 @@ describe("requestBorrowConfirmation", () => {
expect(tabs.sendMessage.mock.calls[1]?.[0]).toBe(301);
});
- it("does NOT fail-open immediately when every candidate's sendMessage fails — waits for notification button or timeout", async () => {
+ it("waits for a notification decision and denies when every UI path times out", async () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined);
tabs.get.mockResolvedValueOnce({ id: 42, title: "Tab" });
windows.getLastFocused.mockResolvedValueOnce(
@@ -282,13 +298,13 @@ describe("requestBorrowConfirmation", () => {
expect(tabs.sendMessage).toHaveBeenCalledTimes(2);
// CRITICAL: previously this would already be settled(true). Now we
- // require an explicit user choice (notification button) or timeout.
+ // require an explicit user choice (notification button) or safe timeout.
expect(resolved).toBe(false);
- // Allow the BACKGROUND_TIMEOUT_MS fail-open to fire so the test resolves.
+ // The background timeout must resolve the request without authorizing it.
await vi.advanceTimersByTimeAsync(BACKGROUND_TIMEOUT_MS);
- expect(await pending).toBe(true);
- expect(resolvedValue).toBe(true);
+ expect(await pending).toBe(false);
+ expect(resolvedValue).toBe(false);
const exhaustedWarn = warnSpy.mock.calls.find(
(call) =>
@@ -375,7 +391,7 @@ describe("requestBorrowConfirmation", () => {
expect(await pending).toBe(false);
});
- it("fail-opens when no injectable user window exists at all", async () => {
+ it("denies when no injectable user window exists at all", async () => {
tabs.get.mockResolvedValueOnce({ id: 42, title: "Stranded Tab" });
windows.getLastFocused.mockResolvedValueOnce(
userWindowWithActiveTab({ windowId: 500, tabId: 5001, url: "about:blank" }),
@@ -394,7 +410,7 @@ describe("requestBorrowConfirmation", () => {
});
await vi.runAllTimersAsync();
- expect(await pending).toBe(true);
+ expect(await pending).toBe(false);
expect(tabs.sendMessage).not.toHaveBeenCalled();
// No notification either — there's no actionable target window.
expect(notifications.create).not.toHaveBeenCalled();
@@ -469,7 +485,7 @@ describe("requestBorrowConfirmation", () => {
await pending;
});
- it("fail-opens after background timeout when content script never responds", async () => {
+ it("denies after background timeout when content script never responds", async () => {
tabs.get.mockResolvedValueOnce({ id: 42, title: "Tab" });
windows.getLastFocused.mockResolvedValueOnce(
userWindowWithActiveTab({ windowId: 11, tabId: 42, url: "https://app.example/" }),
@@ -480,10 +496,10 @@ describe("requestBorrowConfirmation", () => {
deps: { tabs, windows, notifications },
});
await vi.advanceTimersByTimeAsync(BACKGROUND_TIMEOUT_MS);
- expect(await pending).toBe(true);
+ expect(await pending).toBe(false);
});
- it("dismisses pending overlay and fail-opens when aborted", async () => {
+ it("dismisses the pending overlay and denies when aborted", async () => {
tabs.get.mockResolvedValueOnce({ id: 42, title: "Borrow Target" });
windows.getLastFocused.mockResolvedValueOnce(
userWindowWithActiveTab({ windowId: 11, tabId: 7, url: "https://app.example/" }),
@@ -500,7 +516,7 @@ describe("requestBorrowConfirmation", () => {
controller.abort();
await vi.runAllTimersAsync();
- expect(await pending).toBe(true);
+ expect(await pending).toBe(false);
expect(tabs.sendMessage).toHaveBeenCalledTimes(2);
const requestMessage = tabs.sendMessage.mock.calls[0][1] as { requestId: string };
const cancelMessage = tabs.sendMessage.mock.calls[1][1] as {
diff --git a/apps/extension/src/tools/borrow-confirmation.ts b/apps/extension/src/tools/borrow-confirmation.ts
index 3270a20..bd72f74 100644
--- a/apps/extension/src/tools/borrow-confirmation.ts
+++ b/apps/extension/src/tools/borrow-confirmation.ts
@@ -114,9 +114,8 @@ const pendingBorrowNotifications = new Map();
*
* This is the *fallback* authorization path that fires when no candidate
* user window could host the in-page overlay (every `chrome.tabs.sendMessage`
- * was rejected because the content script was not present). Without it the
- * background-task fail-open at the end of `tryCandidate` would silently
- * allow the borrow without any user-visible authorization step.
+ * was rejected because the content script was not present). It preserves an
+ * explicit authorization path without weakening the fail-closed timeout.
*/
const pendingBorrowDecisions = new Map void>();
@@ -170,7 +169,7 @@ export function attachBorrowNotificationClickHandler(deps: {
* Allow / Deny buttons on the OS notification can resolve the matching
* pending `requestBorrowConfirmation`. This is the explicit-authorization
* fallback path used when every candidate user window's content script was
- * missing — without it we'd be back to silently fail-opening the borrow.
+ * missing.
*
* Button index convention (matches `BorrowNotificationCopy`):
* - 0 → Allow → `resolve(true)`
@@ -326,8 +325,7 @@ async function listConfirmationCandidates(
if (typeof w.id !== "number") continue;
// Never bounce the overlay back into a session's Agent Window — those
// pages can't host a content script when they're on the about:blank
- // bootstrap URL, so sendMessage would fail-open and silently allow the
- // borrow with no UI shown.
+ // bootstrap URL, so they cannot surface an authorization decision.
if (isAgentWindowId(w.id)) continue;
const activeTab = w.tabs?.find((t) => t.active === true) ?? null;
if (!activeTab || typeof activeTab.id !== "number") continue;
@@ -360,16 +358,14 @@ async function listConfirmationCandidates(
* *explicit-authorization fallback* when every candidate fails sendMessage:
* we no longer silently allow the borrow in that case, the promise stays
* pending until the user clicks a notification button (resolves true/false)
- * or `BACKGROUND_TIMEOUT_MS` fires (last-resort fail-open so the agent
- * doesn't hang forever when even the notification path is broken).
+ * or `BACKGROUND_TIMEOUT_MS` fires and safely denies the request.
*
* Clicking the notification body (vs. its buttons) focuses the chosen user
* window so the overlay becomes visible — the previous behaviour.
*
- * Returns `true` on user-allow (overlay or notification) OR last-resort
- * fail-open (no candidate window at all; abort signal; timeout). Returns
- * `false` only on explicit user-deny (overlay button or notification Deny
- * button).
+ * Returns `true` only on an explicit user allow from the overlay or
+ * notification. Missing UI, aborts, malformed responses, and timeouts all
+ * fail closed and return `false`.
*/
export async function requestBorrowConfirmation(
tabId: number,
@@ -385,12 +381,9 @@ export async function requestBorrowConfirmation(
// The borrow pipeline already fetched this tab moments earlier
// (validateBorrowTarget in tabs.ts), so a failure here is a transient
- // SW/page hiccup, not a missing tab. Do NOT fail-open: silently allowing
- // a borrow with no confirmation UI contradicts the gate's purpose and is
- // not one of the documented fail-open cases (no candidate window; abort;
- // timeout — see this function's doc comment). Fall back to a generic
- // title and still surface the overlay + notification so the user gets a
- // real chance to approve or deny.
+ // SW/page hiccup, not a missing tab. Fall back to a generic title and still
+ // surface the overlay + notification so the user gets a real chance to
+ // approve or deny.
let tabTitle: string;
try {
const tab = await tabsApi.get(tabId);
@@ -405,10 +398,10 @@ export async function requestBorrowConfirmation(
const candidates = await listConfirmationCandidates(windowsApi, isAgentWindowId);
if (candidates.length === 0) {
- console.warn("[bsk borrow] no injectable user window available — proceeding without overlay", {
+ console.warn("[bsk borrow] no injectable user window available — denying borrow", {
tabId,
});
- return true;
+ return false;
}
const requestId = createBorrowRequestId(tabId);
@@ -419,7 +412,7 @@ export async function requestBorrowConfirmation(
// overlay immediately.
const notificationAnchor = candidates[0];
if (!notificationAnchor) {
- return true;
+ return false;
}
return new Promise((resolve) => {
@@ -463,7 +456,7 @@ export async function requestBorrowConfirmation(
});
});
}
- settle(true);
+ settle(false);
};
if (signal?.aborted) {
@@ -473,19 +466,17 @@ export async function requestBorrowConfirmation(
signal?.addEventListener("abort", onAbort, { once: true });
timeout = setTimeout(() => {
- console.info("[bsk borrow] confirmation timed out — proceeding", {
+ console.info("[bsk borrow] confirmation timed out — denying borrow", {
tabId,
messageTabId: activeMessageTabId,
});
- settle(true);
+ settle(false);
}, BACKGROUND_TIMEOUT_MS);
// Surface the OS notification *before* messaging any candidate so the
// user has a parallel signal even if every content script is missing.
// The Allow / Deny buttons let the user authorize explicitly when the
- // in-page overlay never reached them — without those buttons we'd have
- // to fail-open after `tryCandidate` exhausts its candidates, which is
- // exactly the silent-allow bug we're fixing here.
+ // in-page overlay never reached them.
if (notificationsApi) {
pendingBorrowNotifications.set(notificationId, {
windowId: notificationAnchor.windowId,
@@ -499,7 +490,7 @@ export async function requestBorrowConfirmation(
title: copy.title,
message: copy.body(tabTitle),
priority: 2,
- requireInteraction: false,
+ requireInteraction: true,
silent: false,
buttons: [{ title: copy.allowButton }, { title: copy.denyButton }],
})
@@ -523,15 +514,12 @@ export async function requestBorrowConfirmation(
): void => {
if (settled) return;
if (index >= candidates.length) {
- // Every candidate rejected sendMessage. We deliberately do NOT
- // fail-open here anymore: silently allowing a borrow with zero
- // user-visible authorization UI is the bug. Instead we keep the
- // promise pending and rely on:
+ // Every candidate rejected sendMessage. Keep the promise pending and
+ // rely on:
// • the OS notification's Allow / Deny buttons for an explicit
// user choice (preferred), or
- // • the BACKGROUND_TIMEOUT_MS fail-open as the last-resort
- // soft-fail so the agent doesn't hang forever when even the
- // notification API is unavailable.
+ // • the BACKGROUND_TIMEOUT_MS fail-closed result so the agent does
+ // not hang forever when even the notification API is unavailable.
// Keep the diagnostics so real-world frequency is still observable.
console.warn(
"[bsk borrow] every candidate user window failed sendMessage — awaiting notification button or timeout",
@@ -560,7 +548,9 @@ export async function requestBorrowConfirmation(
.sendMessage(candidate.tabId, message)
.then((response) => {
if (settled) return;
- const allowed = (response as BorrowResponseMessage | undefined)?.allowed !== false;
+ const candidateResponse = response as BorrowResponseMessage | undefined;
+ const allowed =
+ candidateResponse?.type === "borrow-response" && candidateResponse.allowed === true;
console.info("[bsk borrow] confirmation response", { tabId, allowed });
settle(allowed);
})
diff --git a/apps/extension/vitest.config.ts b/apps/extension/vitest.config.ts
index 2e1c157..00060bf 100644
--- a/apps/extension/vitest.config.ts
+++ b/apps/extension/vitest.config.ts
@@ -21,9 +21,7 @@ export default defineConfig({
},
define: {
__BSK_EXT_VERSION__: JSON.stringify(pkg.version),
- __BSK_DAEMON_WS_URL__: JSON.stringify(
- process.env.BSK_DAEMON_WS_URL ?? "ws://127.0.0.1:52800",
- ),
+ __BSK_DAEMON_WS_URL__: JSON.stringify(process.env.BSK_DAEMON_WS_URL ?? "ws://127.0.0.1:52800"),
},
resolve: {
alias: {
diff --git a/crates/bsk-cli/tests/browser_list_ordering.rs b/crates/bsk-cli/tests/browser_list_ordering.rs
index 1c5199b..f19f320 100644
--- a/crates/bsk-cli/tests/browser_list_ordering.rs
+++ b/crates/bsk-cli/tests/browser_list_ordering.rs
@@ -16,7 +16,6 @@ use bsk_protocol::system::BrowserStatusEntry;
use bsk_protocol::{ErrorCode, Method};
use rand::Rng;
use serde::Deserialize;
-use tokio::net::TcpListener;
use tokio::sync::mpsc;
fn tempfile_path(prefix: &str) -> PathBuf {
@@ -30,9 +29,7 @@ fn tempfile_path(prefix: &str) -> PathBuf {
}
async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) {
- let probe = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let port = probe.local_addr().unwrap().port();
- drop(probe);
+ let port = 0;
let config = DaemonConfig::new(port);
let sock = tempfile_path("bsk-test-browser-list");
diff --git a/crates/bsk-cli/tests/browser_wait.rs b/crates/bsk-cli/tests/browser_wait.rs
index 632a7e8..16513e0 100644
--- a/crates/bsk-cli/tests/browser_wait.rs
+++ b/crates/bsk-cli/tests/browser_wait.rs
@@ -15,7 +15,6 @@ use bsk_protocol::system::{BrowserListParams, HandshakeParams, HandshakeResult,
use bsk_protocol::{BrowserPeerInfo, Method, RequestFrame, ResponseBody, ResponseFrame};
use futures_util::{SinkExt, StreamExt};
use rand::Rng;
-use tokio::net::TcpListener;
use tokio_tungstenite::tungstenite::handshake::client::generate_key;
use tokio_tungstenite::tungstenite::http::Request;
use tokio_tungstenite::tungstenite::protocol::Message;
@@ -77,9 +76,7 @@ fn tempfile_path(prefix: &str) -> PathBuf {
}
async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) {
- let probe = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let port = probe.local_addr().unwrap().port();
- drop(probe);
+ let port = 0;
let config = DaemonConfig::new(port);
let sock = tempfile_path("bsk-test-browser-wait");
diff --git a/crates/bsk-cli/tests/cancel_forwarding.rs b/crates/bsk-cli/tests/cancel_forwarding.rs
index b9913ca..e8a0aaf 100644
--- a/crates/bsk-cli/tests/cancel_forwarding.rs
+++ b/crates/bsk-cli/tests/cancel_forwarding.rs
@@ -20,7 +20,6 @@ use bsk_protocol::{
use futures_util::{SinkExt, StreamExt};
use rand::Rng;
use serde_json::json;
-use tokio::net::TcpListener;
use tokio_tungstenite::tungstenite::handshake::client::generate_key;
use tokio_tungstenite::tungstenite::http::Request;
use tokio_tungstenite::tungstenite::protocol::Message;
@@ -40,9 +39,7 @@ fn tempfile_path(prefix: &str) -> PathBuf {
}
async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) {
- let probe = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let port = probe.local_addr().unwrap().port();
- drop(probe);
+ let port = 0;
let config = DaemonConfig::new(port);
let sock = tempfile_path("bsk-test-cancel");
diff --git a/crates/bsk-cli/tests/handshake_compat.rs b/crates/bsk-cli/tests/handshake_compat.rs
index 07e14e4..596d35e 100644
--- a/crates/bsk-cli/tests/handshake_compat.rs
+++ b/crates/bsk-cli/tests/handshake_compat.rs
@@ -10,7 +10,6 @@ use bsk_protocol::system::{HandshakeParams, HandshakeResult, StatusResult};
use bsk_protocol::{BrowserPeerInfo, ErrorCode, Method, RequestFrame, ResponseBody, ResponseFrame};
use futures_util::{SinkExt, StreamExt};
use rand::Rng;
-use tokio::net::TcpListener;
use tokio_tungstenite::tungstenite::handshake::client::generate_key;
use tokio_tungstenite::tungstenite::http::Request;
use tokio_tungstenite::tungstenite::protocol::Message;
@@ -28,9 +27,7 @@ fn tempfile_path(prefix: &str) -> PathBuf {
}
async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) {
- let probe = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let port = probe.local_addr().unwrap().port();
- drop(probe);
+ let port = 0;
let config = DaemonConfig::new(port);
let sock = tempfile_path("bsk-test-handshake");
diff --git a/crates/bsk-cli/tests/per_session_queue.rs b/crates/bsk-cli/tests/per_session_queue.rs
index f548ee6..79b5355 100644
--- a/crates/bsk-cli/tests/per_session_queue.rs
+++ b/crates/bsk-cli/tests/per_session_queue.rs
@@ -27,7 +27,6 @@ use futures_util::{SinkExt, StreamExt};
use rand::Rng;
use serde::Deserialize;
use serde_json::json;
-use tokio::net::TcpListener;
use tokio::sync::{Mutex, mpsc};
use tokio_tungstenite::tungstenite::handshake::client::generate_key;
use tokio_tungstenite::tungstenite::http::Request;
@@ -46,9 +45,7 @@ fn tempfile_path(prefix: &str) -> PathBuf {
}
async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) {
- let probe = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let port = probe.local_addr().unwrap().port();
- drop(probe);
+ let port = 0;
let sock = tempfile_path("bsk-test-queue");
let handle = daemon::run(DaemonConfig::new(port), Some(sock.clone()))
.await
diff --git a/crates/bsk-cli/tests/session_user_interrupt.rs b/crates/bsk-cli/tests/session_user_interrupt.rs
index 2960814..b7d6e61 100644
--- a/crates/bsk-cli/tests/session_user_interrupt.rs
+++ b/crates/bsk-cli/tests/session_user_interrupt.rs
@@ -26,7 +26,6 @@ use bsk_protocol::{
use futures_util::{SinkExt, StreamExt};
use rand::Rng;
use serde_json::json;
-use tokio::net::TcpListener;
use tokio_tungstenite::tungstenite::handshake::client::generate_key;
use tokio_tungstenite::tungstenite::http::Request;
use tokio_tungstenite::tungstenite::protocol::Message;
@@ -46,9 +45,7 @@ fn tempfile_path(prefix: &str) -> PathBuf {
}
async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) {
- let probe = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let port = probe.local_addr().unwrap().port();
- drop(probe);
+ let port = 0;
let config = DaemonConfig::new(port);
let sock = tempfile_path("bsk-test-user-interrupt");
diff --git a/crates/bsk-cli/tests/sessions_ipc.rs b/crates/bsk-cli/tests/sessions_ipc.rs
index a686520..99b1535 100644
--- a/crates/bsk-cli/tests/sessions_ipc.rs
+++ b/crates/bsk-cli/tests/sessions_ipc.rs
@@ -15,7 +15,6 @@ use bsk_protocol::tools::{SessionStartParams, SessionStartResult, SessionStopPar
use bsk_protocol::{BrowserPeerInfo, Frame, Method, RequestFrame, ResponseBody, ResponseFrame};
use futures_util::{SinkExt, StreamExt};
use rand::Rng;
-use tokio::net::TcpListener;
use tokio_tungstenite::tungstenite::handshake::client::generate_key;
use tokio_tungstenite::tungstenite::http::Request;
use tokio_tungstenite::tungstenite::protocol::Message;
@@ -39,9 +38,7 @@ async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) {
}
async fn spawn_daemon_with_connect_wait(connect_wait: Duration) -> (daemon::DaemonHandle, PathBuf) {
- let probe = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let port = probe.local_addr().unwrap().port();
- drop(probe);
+ let port = 0;
let config = DaemonConfig::new(port).with_extension_connect_wait(connect_wait);
let sock = tempfile_path("bsk-test-ipc");
diff --git a/crates/bsk-cli/tests/tools_ipc.rs b/crates/bsk-cli/tests/tools_ipc.rs
index 32b7732..2857c62 100644
--- a/crates/bsk-cli/tests/tools_ipc.rs
+++ b/crates/bsk-cli/tests/tools_ipc.rs
@@ -21,7 +21,6 @@ use futures_util::stream::{SplitSink, SplitStream};
use futures_util::{SinkExt, StreamExt};
use rand::Rng;
use serde_json::{Value, json};
-use tokio::net::TcpListener;
use tokio::sync::Mutex;
use tokio_tungstenite::tungstenite::handshake::client::generate_key;
use tokio_tungstenite::tungstenite::http::Request;
@@ -40,9 +39,7 @@ fn tempfile_path(prefix: &str) -> PathBuf {
}
async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) {
- let probe = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let port = probe.local_addr().unwrap().port();
- drop(probe);
+ let port = 0;
let sock = tempfile_path("bsk-test-tools");
let handle = daemon::run(DaemonConfig::new(port), Some(sock.clone()))
.await
diff --git a/crates/bsk-cli/tests/tools_m7_ipc.rs b/crates/bsk-cli/tests/tools_m7_ipc.rs
index acb9481..ee74b5e 100644
--- a/crates/bsk-cli/tests/tools_m7_ipc.rs
+++ b/crates/bsk-cli/tests/tools_m7_ipc.rs
@@ -25,7 +25,6 @@ use futures_util::stream::{SplitSink, SplitStream};
use futures_util::{SinkExt, StreamExt};
use rand::Rng;
use serde_json::{Value, json};
-use tokio::net::TcpListener;
use tokio::sync::Mutex;
use tokio_tungstenite::tungstenite::handshake::client::generate_key;
use tokio_tungstenite::tungstenite::http::Request;
@@ -44,9 +43,7 @@ fn tempfile_path(prefix: &str) -> PathBuf {
}
async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) {
- let probe = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let port = probe.local_addr().unwrap().port();
- drop(probe);
+ let port = 0;
let sock = tempfile_path("bsk-test-tools-m7");
let handle = daemon::run(DaemonConfig::new(port), Some(sock.clone()))
.await
diff --git a/crates/bsk-cli/tests/tools_m8_ipc.rs b/crates/bsk-cli/tests/tools_m8_ipc.rs
index 5308f7b..14e7c72 100644
--- a/crates/bsk-cli/tests/tools_m8_ipc.rs
+++ b/crates/bsk-cli/tests/tools_m8_ipc.rs
@@ -24,7 +24,6 @@ use futures_util::stream::{SplitSink, SplitStream};
use futures_util::{SinkExt, StreamExt};
use rand::Rng;
use serde_json::Value;
-use tokio::net::TcpListener;
use tokio::sync::Mutex;
use tokio_tungstenite::tungstenite::handshake::client::generate_key;
use tokio_tungstenite::tungstenite::http::Request;
@@ -43,9 +42,7 @@ fn tempfile_path(prefix: &str) -> PathBuf {
}
async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) {
- let probe = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let port = probe.local_addr().unwrap().port();
- drop(probe);
+ let port = 0;
let sock = tempfile_path("bsk-test-tools-m8");
let handle = daemon::run(DaemonConfig::new(port), Some(sock.clone()))
.await
diff --git a/crates/bsk-cli/tests/tools_m9_ipc.rs b/crates/bsk-cli/tests/tools_m9_ipc.rs
index 6dd5d33..2f8b46d 100644
--- a/crates/bsk-cli/tests/tools_m9_ipc.rs
+++ b/crates/bsk-cli/tests/tools_m9_ipc.rs
@@ -30,7 +30,6 @@ use rand::Rng;
use serde_json::{Value, json};
#[cfg(unix)]
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
-use tokio::net::TcpListener;
#[cfg(unix)]
use tokio::net::UnixStream;
use tokio::sync::Mutex;
@@ -53,9 +52,7 @@ fn tempfile_path(prefix: &str) -> PathBuf {
}
async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) {
- let probe = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let port = probe.local_addr().unwrap().port();
- drop(probe);
+ let port = 0;
let sock = tempfile_path("bsk-test-tools-m9");
let handle = daemon::run(DaemonConfig::new(port), Some(sock.clone()))
.await
diff --git a/crates/bsk-cli/tests/ws_handshake.rs b/crates/bsk-cli/tests/ws_handshake.rs
index 146ef33..c6e2da1 100644
--- a/crates/bsk-cli/tests/ws_handshake.rs
+++ b/crates/bsk-cli/tests/ws_handshake.rs
@@ -8,7 +8,6 @@ use bsk::daemon::{self, DaemonConfig};
use bsk_protocol::system::{HandshakeParams, HandshakeResult};
use bsk_protocol::{BrowserPeerInfo, Method, RequestFrame, ResponseBody, ResponseFrame};
use futures_util::{SinkExt, StreamExt};
-use tokio::net::TcpListener;
use tokio_tungstenite::tungstenite::handshake::client::generate_key;
use tokio_tungstenite::tungstenite::http::Request;
use tokio_tungstenite::tungstenite::protocol::Message;
@@ -17,9 +16,7 @@ pub const TEST_EXT_ID: &str = "abcdefghijklmnopabcdefghijklmnop"; // 32 chars in
pub async fn spawn_daemon() -> daemon::DaemonHandle {
// Bind to any free TCP port.
- let probe = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let port = probe.local_addr().unwrap().port();
- drop(probe);
+ let port = 0;
let config = DaemonConfig::new(port);
daemon::run(config, None).await.unwrap()
diff --git a/packages/i18n/src/locales/en-US/extension.json b/packages/i18n/src/locales/en-US/extension.json
index afe3810..aa275ae 100644
--- a/packages/i18n/src/locales/en-US/extension.json
+++ b/packages/i18n/src/locales/en-US/extension.json
@@ -52,7 +52,7 @@
"titleInactive": "Agent wants to borrow a tab",
"body": "An AI agent wants to move this tab into its workspace. You can deny or allow the borrow.",
"targetTab": "Target tab:",
- "autoAllow": "Auto-allowing in {{count}}s",
+ "autoDeny": "Request expires in {{count}}s",
"deny": "Deny",
"allow": "Allow",
"notificationTitle": "BrowserSkill: Agent wants to borrow a tab",
diff --git a/packages/i18n/src/locales/zh-CN/extension.json b/packages/i18n/src/locales/zh-CN/extension.json
index a4cefad..b9e6415 100644
--- a/packages/i18n/src/locales/zh-CN/extension.json
+++ b/packages/i18n/src/locales/zh-CN/extension.json
@@ -52,7 +52,7 @@
"titleInactive": "Agent 请求借用标签页",
"body": "AI Agent 希望将此标签页移入工作区。您可以拒绝或允许借用。",
"targetTab": "目标标签页:",
- "autoAllow": "{{count}} 秒后自动允许",
+ "autoDeny": "{{count}} 秒后自动拒绝",
"deny": "拒绝",
"allow": "允许",
"notificationTitle": "BrowserSkill:Agent 请求借用标签页",