Files
BrowserSkill/packages/dsh-plugin-browserskill/tests/sessions.test.ts
T
BB-fat 703fc64399 feat(packages): add dsh-plugin-browserskill — DeepSeek Harness tools for bsk
Add a new monorepo package that registers BrowserSkill (bsk) browser
automation as model-visible tools in DeepSeek Harness (dsh):

- 11 tools mapping to bsk CLI --json commands: session start/stop/list,
  navigate, snapshot, observe, click, fill, press, screenshot, emulate
- multi-session support: optional session arg with a current-session
  pointer, session id echoed in every result, configurable concurrency
  cap (default 5), and dispose-time cleanup of plugin-started sessions
- cancellation: exec.signal aborts kill the underlying bsk child process
- terminal-style UI cards (command line as the call card, output as the
  result card); screenshots inline the PNG through the host attachment
  store when the model route accepts image input, else return the path
- config: bskPath / defaultTimeoutMs / maxSessions via Schemastery schema
- install guidance when the bsk binary is missing; bsk JSON error
  envelopes (code/message/hint) surfaced to the model
- unit tests mock the bsk runner (no real browser required); the root
  lint script now also runs the package's typecheck + tests so the
  existing CI frontend job covers the new package (a dedicated CI job
  diff is provided in the PR; the pushing token lacks workflow scope)
2026-08-13 15:04:52 +00:00

56 lines
2.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { SessionRegistry } from "../src/sessions";
describe("SessionRegistry", () => {
it("tracks the current session across add and remove", () => {
const registry = new SessionRegistry(5);
expect(registry.current()).toBeUndefined();
registry.add({ sessionId: "s1", startedAtMs: 1 });
expect(registry.current()).toBe("s1");
registry.add({ sessionId: "s2", startedAtMs: 2 });
expect(registry.current()).toBe("s2");
registry.remove("s2");
expect(registry.current()).toBe("s1");
registry.remove("s1");
expect(registry.current()).toBeUndefined();
});
it("enforces the concurrency cap", () => {
const registry = new SessionRegistry(2);
registry.add({ sessionId: "s1", startedAtMs: 1 });
registry.add({ sessionId: "s2", startedAtMs: 2 });
expect(() => registry.add({ sessionId: "s3", startedAtMs: 3 })).toThrow(/session limit/);
registry.remove("s1");
registry.add({ sessionId: "s3", startedAtMs: 3 });
expect(registry.size()).toBe(2);
});
it("resolve prefers the explicit session and adopts unknown ids", () => {
const registry = new SessionRegistry(5);
registry.add({ sessionId: "s1", startedAtMs: 1 });
expect(registry.resolve("s9", "tool")).toBe("s9");
expect(registry.current()).toBe("s9");
expect(registry.list().map((s) => s.sessionId)).toContain("s9");
});
it("resolve falls back to the current session", () => {
const registry = new SessionRegistry(5);
registry.add({ sessionId: "s1", startedAtMs: 1 });
expect(registry.resolve(undefined, "tool")).toBe("s1");
});
it("resolve throws actionable guidance when no session exists", () => {
const registry = new SessionRegistry(5);
expect(() => registry.resolve(undefined, "browser_click")).toThrow(/browser_session_start/);
});
it("touch refreshes recency so fallback picks the most recent survivor", () => {
const registry = new SessionRegistry(5);
registry.add({ sessionId: "s1", startedAtMs: 1 });
registry.add({ sessionId: "s2", startedAtMs: 2 });
registry.touch("s1");
registry.remove("s1");
expect(registry.current()).toBe("s2");
});
});