feat(dsh-plugin): better-sidebar 集成、会话关停与归档清理、悬浮窗修复 (#130)

* better-sidebar 集成:observation 视图迁入 better-sidebar 载体,按会话作用域展示 bsk 会话
* 会话关停与归档清理:支持会话 dispose 与归档自动清理
* 悬浮窗修复:悬浮 overlay 拖拽/缩放/PiP 弹出等问题修复
* 修复 Document PiP 空白窗口:载体卸载(关 tab、切会话、插件 HMR)时主动 close() PiP 窗口,避免残留空白窗口
This commit is contained in:
BB-fat
2026-08-25 10:33:46 +08:00
committed by GitHub
parent 4b6cdde168
commit a004291848
19 changed files with 1583 additions and 98 deletions
@@ -24,6 +24,13 @@ export interface TrackedSession {
export class SessionRegistry {
private readonly sessions = new Map<string, TrackedSession>();
private currentId: string | undefined;
/**
* bsk session id → the DSH session ids that may clean it up: the agent
* session that started it plus every ancestor along the seed lineage, so
* archiving a conversation at ANY level of the chain reaps its browsers
* (see archive-cleanup.ts).
*/
private readonly dshOwners = new Map<string, ReadonlySet<string>>();
/**
* Slots reserved by in-flight starts. reserveStart/completeStart/abandonStart
* run synchronously around the async spawn, so concurrent starts can never
@@ -72,12 +79,37 @@ export class SessionRegistry {
/** Forget a session; falls back to the most recent remaining one. */
remove(sessionId: string): void {
this.sessions.delete(sessionId);
this.dshOwners.delete(sessionId);
if (this.currentId === sessionId) {
const rest = [...this.sessions.values()];
this.currentId = rest.length > 0 ? rest[rest.length - 1].sessionId : undefined;
}
}
/**
* Record which DSH conversation(s) a freshly started bsk session belongs
* to (the starting agent's session plus its ancestors). No-op without ids
* — e.g. a start whose caller carried no agent identity.
*/
trackOwner(sessionId: string, dshSessionIds: readonly string[]): void {
if (dshSessionIds.length === 0 || !this.sessions.has(sessionId)) return;
this.dshOwners.set(sessionId, new Set(dshSessionIds));
}
/** bsk session ids owned by the given DSH conversation (or its descendants). */
ownedByDsh(dshSessionId: string): string[] {
const owned: string[] = [];
for (const [sessionId, owners] of this.dshOwners) {
if (owners.has(dshSessionId)) owned.push(sessionId);
}
return owned;
}
/** The DSH conversation ids owning one bsk session (empty when untracked). */
dshOwnersOf(sessionId: string): string[] {
return [...(this.dshOwners.get(sessionId) ?? [])];
}
/** Mark an owned session as most recently used (recency order refresh). */
private touch(sessionId: string): void {
const existing = this.sessions.get(sessionId);