mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
fix(dsh-plugin-browserskill): strict session ownership on a shared daemon
Review hardening for daemon-sharing deployments (other agents, terminals, or dsh instances on the same bsk daemon): - Ownership split: only sessions created by browser_session_start are 'owned'. Explicitly referenced foreign sessions are still tracked for the current-session pointer, but browser_session_stop refuses them and unload cleanup stops exactly the owned set — no path can stop a session this plugin did not create. - Start race: the capacity check is now a synchronous reserveStart/completeStart/abandonStart protocol, so two concurrent starts can never both pass the cap (check-and-reserve is atomic on the event loop); a rejected or failed start never leaks a session. - Stale handles: dispose already swallowed per-stop errors; the owned set is computed at dispose time so an externally stopped session is a no-op, not a failure. Tests (+7): reservation race (two concurrent starts, cap 1 -> exactly one spawn), foreign-stop refusal (no stop command reaches the daemon), reference adoption never becoming owned, resolveForStop pointer stability, and an apply-level dispose test proving only owned sessions are stopped while stale stops are tolerated. e2e: a manually started foreign session survived the plugin's full lifecycle while both plugin sessions were cleaned up.
This commit is contained in:
@@ -162,19 +162,23 @@ export function registerTools(deps: ToolDeps): void {
|
||||
if ((args.width === undefined) !== (args.height === undefined)) {
|
||||
throw new Error("width and height must be given together");
|
||||
}
|
||||
// Check the cap BEFORE spawning so a rejected start never leaks a session.
|
||||
registry.assertCapacity();
|
||||
// Reserve the slot BEFORE spawning: check-and-reserve is synchronous,
|
||||
// so concurrent starts can never both pass the cap and leak a session.
|
||||
registry.reserveStart();
|
||||
const startArgs = ["session", "start"];
|
||||
if (args.width !== undefined && args.height !== undefined) {
|
||||
startArgs.push("--width", String(args.width), "--height", String(args.height));
|
||||
}
|
||||
if (args.noFocus === true) startArgs.push("--no-focus");
|
||||
if (args.browser !== undefined) startArgs.push("--browser", args.browser);
|
||||
const reply = (await runBsk(deps, exec, startArgs, "session start")) as {
|
||||
session_id: string;
|
||||
browser_instance_id: string;
|
||||
};
|
||||
registry.add({
|
||||
let reply: { session_id: string; browser_instance_id: string };
|
||||
try {
|
||||
reply = (await runBsk(deps, exec, startArgs, "session start")) as typeof reply;
|
||||
} catch (error) {
|
||||
registry.abandonStart();
|
||||
throw error;
|
||||
}
|
||||
registry.completeStart({
|
||||
sessionId: reply.session_id,
|
||||
browserInstanceId: reply.browser_instance_id,
|
||||
startedAtMs: Date.now(),
|
||||
@@ -230,7 +234,8 @@ export function registerTools(deps: ToolDeps): void {
|
||||
name: "browser_session_stop",
|
||||
description:
|
||||
"Stop a browser session and close its Agent Window. Stops the given session, or the " +
|
||||
"current session when `session` is omitted.",
|
||||
"current session when `session` is omitted. Only sessions created by browser_session_start " +
|
||||
"can be stopped — sessions owned by other programs sharing the bsk daemon are refused.",
|
||||
parameters: { session: SESSION_PARAM },
|
||||
output: {
|
||||
schema: {
|
||||
@@ -243,7 +248,7 @@ export function registerTools(deps: ToolDeps): void {
|
||||
],
|
||||
},
|
||||
async execute(args, exec) {
|
||||
const sessionId = registry.resolve(args.session, "browser_session_stop");
|
||||
const sessionId = registry.resolveForStop(args.session);
|
||||
await runBsk(deps, exec, ["session", "stop", sessionId], "session stop");
|
||||
registry.remove(sessionId);
|
||||
return { stopped: sessionId };
|
||||
|
||||
Reference in New Issue
Block a user