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
@@ -9,6 +9,7 @@
*/
import type { Context } from "@deepseek-ai/cordis";
import type { Agent } from "@deepseek-ai/dsh-agent";
import {
BSK_SKILL_DESCRIPTION,
BSK_SKILL_MARKDOWN,
@@ -25,6 +26,11 @@ interface SkillsLike {
}): () => void;
}
/** Structural view of the live-agent registry (absent in bare compositions). */
interface AgentsLike {
list(): Agent[];
}
/**
* Publish the browser skill as an embedded runtime skill. Returns the unregistration
* disposer. A composition without the `skills` service (or with dsh-tool-skill
@@ -43,3 +49,54 @@ export function registerBskSkill(ctx: Context): () => void {
source: "bundled",
});
}
/**
* Install the DSH-specific browser skill into every exact agent scope.
*
* DSH merges skill layers nearest-first (`agent -> preset -> global`). A legacy
* CLI skill installed at `~/.agents/skills/browser-skill` is discovered by the
* preset filesystem provider and therefore shadows a plugin-level registration.
* Registering the embedded skill through `agent.ctx` makes the DSH protocol
* contract authoritative for that agent without touching the shared CLI skill.
*
* New agents are handled at `agent/session-start`, the first supported startup
* injection point and still before the first prompt assembly. Existing agents
* are registered immediately so plugin reloads take effect without recreating
* the conversation. Returns a disposer for all plugin-owned registrations.
*/
export function armAgentScopedBskSkill(ctx: Context): () => void {
const registrations = new Map<Agent, () => void>();
let active = true;
const registerForAgent = (agent: Agent): void => {
if (!active || registrations.has(agent)) return;
registrations.set(agent, registerBskSkill(agent.ctx));
};
let stopSessionStart = () => {};
let stopDisposed = () => {};
if (typeof ctx.on === "function") {
stopSessionStart = ctx.on("agent/session-start", ({ agent }) => {
registerForAgent(agent);
});
stopDisposed = ctx.on("agent/disposed", ({ agent }) => {
// Agent-scoped effects have already unwound at this lifecycle edge. Drop
// the retained disposer without invoking it against a disposed context.
registrations.delete(agent);
});
}
const agents = ctx.get("agents") as AgentsLike | undefined | null;
if (agents != null && typeof agents.list === "function") {
for (const agent of agents.list()) registerForAgent(agent);
}
return () => {
if (!active) return;
active = false;
stopDisposed();
stopSessionStart();
for (const unregister of [...registrations.values()].reverse()) unregister();
registrations.clear();
};
}