mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
Initial public release
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import type { Transport } from "@/transport/transport";
|
||||
import type { EventFrame } from "@/transport/types";
|
||||
import type { SessionManager } from "./manager";
|
||||
|
||||
/**
|
||||
* Listener interface that mirrors `chrome.windows.onRemoved` so vitest
|
||||
* can drive the handler without a real Chrome runtime.
|
||||
*/
|
||||
export interface WindowRemovedListener {
|
||||
addListener(cb: (windowId: number) => void): void;
|
||||
removeListener(cb: (windowId: number) => void): void;
|
||||
}
|
||||
|
||||
export interface SessionEventHandlerOptions {
|
||||
manager: SessionManager;
|
||||
transport: Transport;
|
||||
windowEvents?: WindowRemovedListener;
|
||||
cdp?: {
|
||||
detachSession(sessionId: string): Promise<void>;
|
||||
};
|
||||
/**
|
||||
* Invoked after a user-closed Agent Window has been removed from the
|
||||
* SessionManager. Lets the caller refresh side caches such as the
|
||||
* `chrome.storage.session` "sessions live" flag (review M4/M5 I3).
|
||||
*/
|
||||
onSessionsChanged?: () => void;
|
||||
}
|
||||
|
||||
function chromeWindowEvents(): WindowRemovedListener {
|
||||
return {
|
||||
addListener: (cb) => chrome.windows.onRemoved.addListener(cb),
|
||||
removeListener: (cb) => chrome.windows.onRemoved.removeListener(cb),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch for the user closing an Agent Window. When that happens we:
|
||||
* 1. Drop the local SessionContext (without trying to close the window
|
||||
* again — it's already gone).
|
||||
* 2. Emit a `session.window_closed` event to the daemon so it can
|
||||
* remove the session from its registry too.
|
||||
*
|
||||
* Returns a disposer that detaches the listener (used in tests).
|
||||
*/
|
||||
export function attachSessionEventHandler(options: SessionEventHandlerOptions): {
|
||||
dispose: () => void;
|
||||
} {
|
||||
const { manager, transport, onSessionsChanged } = options;
|
||||
const events = options.windowEvents ?? chromeWindowEvents();
|
||||
|
||||
const onRemoved = (windowId: number): void => {
|
||||
const ctx = manager.findByWindowId(windowId);
|
||||
if (!ctx) return;
|
||||
const returnFailures = Array.from(ctx.borrowedTabs.keys()).map((tabId) => ({
|
||||
tab_id: tabId,
|
||||
code: "cdp_failed",
|
||||
message: "Agent Window was closed before borrowed tab could be returned",
|
||||
}));
|
||||
if (returnFailures.length > 0) {
|
||||
console.warn(
|
||||
`[bh] Agent Window ${windowId} closed with borrowed tabs that could not be returned`,
|
||||
returnFailures,
|
||||
);
|
||||
}
|
||||
const detach = options.cdp
|
||||
? options.cdp.detachSession(ctx.sessionId).catch((err) => {
|
||||
console.debug("[bh] session-event cdp detach failed", err);
|
||||
})
|
||||
: Promise.resolve();
|
||||
void detach
|
||||
.then(() => manager.stop(ctx.sessionId, { dropOnly: true }))
|
||||
.then(() => {
|
||||
onSessionsChanged?.();
|
||||
const event: EventFrame = {
|
||||
event: "session.window_closed",
|
||||
payload: {
|
||||
session_id: ctx.sessionId,
|
||||
reason: "user_closed_window",
|
||||
...(returnFailures.length > 0 ? { return_failures: returnFailures } : {}),
|
||||
},
|
||||
};
|
||||
try {
|
||||
transport.send(event);
|
||||
} catch (err) {
|
||||
console.warn("[bh] could not push session.window_closed event", err);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn("[bh] session-event handler failed", err);
|
||||
});
|
||||
};
|
||||
|
||||
events.addListener(onRemoved);
|
||||
return {
|
||||
dispose: () => events.removeListener(onRemoved),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user