fix(dsh-plugin-browserskill): serialize per-session commands (observation vs tool calls)

The daemon accepts only one unfinished command per session; the
thumbnail loop racing a model tool call produced 'session already has
an unfinished command' failures on both sides (found by the Phase 1-C
e2e: navigate/snapshot/click all errored while captures kept running).
Add a KeyedExecutor: every command for a session — tool calls and
observation captures alike — runs FIFO; queued tasks reject early on
abort, running tasks keep the runner's signal-driven kill.
This commit is contained in:
BB-fat
2026-08-14 06:41:49 +00:00
parent 2955260d11
commit bc5d9610a3
6 changed files with 93 additions and 9 deletions
@@ -14,6 +14,7 @@ import {
ObservationService,
} from "../src/observation";
import { registerObservationRoutes } from "../src/observation-http";
import { KeyedExecutor } from "../src/queue";
import type { BskRunner, BskRunOptions, BskRunResult } from "../src/runner";
import { SessionRegistry } from "../src/sessions";
@@ -128,6 +129,7 @@ function setup(opts: {
ctx: fakeCtx(attachments),
runner,
registry,
queue: new KeyedExecutor(),
options: OPTIONS,
scheduler,
});
@@ -4,6 +4,7 @@ import { join } from "node:path";
import type { ToolDefinition, ToolRunContext } from "@deepseek-ai/dsh-tools";
import { describe, expect, it } from "vitest";
import { ObservationService } from "../src/observation";
import { KeyedExecutor } from "../src/queue";
import type { BskRunner, BskRunOptions, BskRunResult } from "../src/runner";
import { SessionRegistry } from "../src/sessions";
import { type PluginConfig, registerTools } from "../src/tools";
@@ -23,6 +24,7 @@ function disabledObservation(deps: { ctx: unknown; runner: BskRunner; registry:
ctx: deps.ctx as never,
runner: deps.runner,
registry: deps.registry,
queue: new KeyedExecutor(),
options: { enabled: false, thumbnailIntervalMs: 1500, idleIntervalMs: 8000 },
});
}
@@ -121,6 +123,7 @@ function setup(
registry,
config,
observation: disabledObservation({ ctx, runner: runner as BskRunner, registry }),
queue: new KeyedExecutor(),
});
return { tools, calls, registry };
}
@@ -576,6 +579,7 @@ describe("error and cancellation semantics", () => {
registry,
config: CONFIG,
observation: disabledObservation({ ctx, runner, registry }),
queue: new KeyedExecutor(),
});
const start = tools.get("browser_session_start");
await expect(start?.execute({}, makeExec())).rejects.toThrow(/BrowserSkill must be installed/);