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
@@ -15,6 +15,7 @@ import { readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { Context } from "@deepseek-ai/cordis";
import type { KeyedExecutor } from "./queue";
import type { BskRunner } from "./runner";
import type { SessionRegistry } from "./sessions";
@@ -98,6 +99,7 @@ export class ObservationService {
ctx: Context;
runner: BskRunner;
registry: SessionRegistry;
queue: KeyedExecutor;
options: ObservationOptions;
scheduler?: ObservationScheduler;
},
@@ -287,9 +289,11 @@ export class ObservationService {
this.captureInFlight.add(sessionId);
try {
const outPath = join(tmpdir(), `bsk-obs-${sessionId}-${this.scheduler.now()}.png`);
const result = await this.deps.runner.run(
["screenshot", "--session", sessionId, "--out", outPath],
{ timeoutMs: 15_000, tag: `observation:${sessionId}` },
const result = await this.deps.queue.run(sessionId, () =>
this.deps.runner.run(["screenshot", "--session", sessionId, "--out", outPath], {
timeoutMs: 15_000,
tag: `observation:${sessionId}`,
}),
);
if (result.code !== 0) {
let code: string | undefined;