fix(dsh-plugin): stabilize skill loading and browser sessions

Register bundled browser instructions at agent scope, prioritize foreground commands over thumbnail capture, and unify cancellation and session cleanup.
This commit is contained in:
drakezhang
2026-08-27 19:59:20 +08:00
parent 554861d6b1
commit 1a70d2200e
10 changed files with 501 additions and 92 deletions
@@ -1,7 +1,14 @@
import type { ChildProcess } from "node:child_process";
import { EventEmitter } from "node:events";
import { describe, expect, it } from "vitest";
import { type BskRunResult, createBskRunner, isCommandNotFound, parseBskJson } from "../src/runner";
import {
type BskRunResult,
createBskRunner,
isCommandNotFound,
isSessionBusyResult,
parseBskJson,
runWithSessionBusyRetry,
} from "../src/runner";
/** Minimal fake ChildProcess driven by the test. */
class FakeChild extends EventEmitter {
@@ -63,7 +70,7 @@ describe("createBskRunner", () => {
controller.abort();
const result = await promise;
expect(result.aborted).toBe(true);
expect(child.killedWith).toContain("SIGTERM");
expect(child.killedWith).toContain("SIGINT");
});
it("kills the child on timeout", async () => {
@@ -80,7 +87,7 @@ describe("createBskRunner", () => {
const promise = runner.run(["session", "list"]);
runner.killAll();
const result = await promise;
expect(child.killedWith).toContain("SIGTERM");
expect(child.killedWith).toContain("SIGINT");
expect(result.code).toBeNull();
});
});
@@ -140,3 +147,51 @@ describe("isCommandNotFound", () => {
expect(isCommandNotFound(new Error("other"))).toBe(false);
});
});
describe("session busy reconciliation", () => {
const busy: BskRunResult = {
code: 4,
stdout: JSON.stringify({
code: "timeout",
message: "session already has an unfinished command",
data: { reason: "session_busy" },
}),
stderr: "",
timedOut: false,
aborted: false,
};
const ok: BskRunResult = {
code: 0,
stdout: "{}",
stderr: "",
timedOut: false,
aborted: false,
};
it("recognizes only the structured session_busy reason", () => {
expect(isSessionBusyResult(busy)).toBe(true);
expect(isSessionBusyResult({ ...busy, stdout: '{"code":"timeout"}' })).toBe(false);
expect(isSessionBusyResult(ok)).toBe(false);
});
it("retries one transient busy result and returns the settled response", async () => {
const replies = [busy, ok];
let calls = 0;
const result = await runWithSessionBusyRetry(async () => {
calls += 1;
return replies.shift() ?? ok;
});
expect(result).toBe(ok);
expect(calls).toBe(2);
});
it("does not loop on a persistent busy result", async () => {
let calls = 0;
const result = await runWithSessionBusyRetry(async () => {
calls += 1;
return busy;
});
expect(result).toBe(busy);
expect(calls).toBe(2);
});
});