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:
BB-fat
2026-08-12 19:58:53 +08:00
committed by GitHub
co-authored by alectimison-maker
parent 0ce497f017
commit f443cfae74
17 changed files with 235 additions and 43 deletions
@@ -44,3 +44,53 @@ describe("chromeAgentWindowApi.ensureActiveTab", () => {
expect(update).not.toHaveBeenCalled();
});
});
describe("chromeAgentWindowApi.create", () => {
const create = vi.fn();
beforeEach(() => {
vi.stubGlobal("chrome", {
windows: { create },
});
create.mockReset();
create.mockResolvedValue({ id: 100 });
});
afterEach(() => {
vi.unstubAllGlobals();
});
it("focuses Agent Windows by default", async () => {
await chromeAgentWindowApi.create(AGENT_WINDOW_HOME);
expect(create).toHaveBeenCalledWith({
type: "normal",
focused: true,
url: AGENT_WINDOW_HOME,
});
});
it("can create an Agent Window without stealing focus", async () => {
await chromeAgentWindowApi.create(AGENT_WINDOW_HOME, { focused: false });
expect(create).toHaveBeenCalledWith({
type: "normal",
focused: false,
url: AGENT_WINDOW_HOME,
});
});
it("passes an optional window size through the options object", async () => {
await chromeAgentWindowApi.create(AGENT_WINDOW_HOME, {
size: { width: 1280, height: 800 },
});
expect(create).toHaveBeenCalledWith({
type: "normal",
focused: true,
url: AGENT_WINDOW_HOME,
width: 1280,
height: 800,
});
});
});
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import type { AgentWindowApi } from "../agent-window";
import type { AgentWindowApi, AgentWindowCreateOptions } from "../agent-window";
import { SessionManager } from "../manager";
function fakeAgentWindow(): AgentWindowApi & {
@@ -8,7 +8,7 @@ function fakeAgentWindow(): AgentWindowApi & {
ensureActiveTabMock: ReturnType<typeof vi.fn>;
} {
let nextId = 100;
const createMock = vi.fn(async (_url: string) => {
const createMock = vi.fn(async (_url: string, _opts?: AgentWindowCreateOptions) => {
const id = nextId++;
return id;
});
@@ -30,7 +30,7 @@ describe("SessionManager", () => {
const sm = new SessionManager({ agentWindow: aw, now: () => 1700000000000 });
const ctx = await sm.start("aa11");
expect(aw.createMock).toHaveBeenCalledOnce();
expect(aw.createMock).toHaveBeenCalledWith("about:blank");
expect(aw.createMock).toHaveBeenCalledWith("about:blank", {});
expect(aw.ensureActiveTabMock).toHaveBeenCalledOnce();
expect(aw.ensureActiveTabMock).toHaveBeenCalledWith(100, "about:blank");
expect(ctx.sessionId).toBe("aa11");
@@ -42,11 +42,22 @@ describe("SessionManager", () => {
it("forwards an optional window size when starting a session", async () => {
const aw = fakeAgentWindow();
const sm = new SessionManager({ agentWindow: aw });
const ctx = await sm.start("aa11", { width: 1280, height: 800 });
expect(aw.createMock).toHaveBeenCalledWith("about:blank", { width: 1280, height: 800 });
const ctx = await sm.start("aa11", { size: { width: 1280, height: 800 } });
expect(aw.createMock).toHaveBeenCalledWith("about:blank", {
size: { width: 1280, height: 800 },
});
expect(ctx.agentWindowId).toBe(100);
});
it("forwards an explicit unfocused start to the Agent Window", async () => {
const aw = fakeAgentWindow();
const sm = new SessionManager({ agentWindow: aw });
await sm.start("aa11", { focused: false });
expect(aw.createMock).toHaveBeenCalledWith("about:blank", { focused: false });
});
it("indexes the session by sessionId and agent window id", async () => {
const aw = fakeAgentWindow();
const sm = new SessionManager({ agentWindow: aw });