import { ResponseError } from "vscode-jsonrpc"; import type WebSocket from "ws"; import type { CharacterCard, CharacterSummary } from "../../config/character-card.js"; import type { GroupChatState } from "../../data/group-chat-state.js"; import type { ClientMessage } from "../../protocol/messages.js"; import { ERROR_CHARACTER_UNAVAILABLE, ERROR_CODE_CHARACTER_UNAVAILABLE } from "../../shared/messages.js"; type ClaimCharacterMessage = Extract; /** 连接上下文窄接口(creator-runtime 的 ConnectionContext 结构子集)。 */ interface ClaimConnectionLike { sessionId: string | null; online: boolean; reservedCharacterId: string | null; } interface ClaimPipelineDependencies { state: GroupChatState; characters: ReadonlyMap; isCharacterAvailable: (characterId: string) => boolean; /** 预留超时定时器(runtime 方法注入:超时释放 + close,跨消息编排归 runtime)。 */ startReadyTimer: (socket: WebSocket, connection: ClaimConnectionLike) => void; toCharacterSummaryMessage: (character: CharacterSummary) => { character_id: string; name: string; description: string; }; } /** * claim_character 门面(短流程:粒度约束不建管线)。阶段: * validate(会话归属/在线/重复预留/角色存在可用)→ commit 预留 → 定时器 + 响应。 * 只显式读写 runtime 会话状态(决策 7),不缓存跨消息状态。 */ export class ClaimPipeline { constructor(private readonly deps: ClaimPipelineDependencies) {} run( socket: WebSocket, connection: ClaimConnectionLike, message: ClaimCharacterMessage, ): { character: { character_id: string; name: string; description: string; path: string }; } { const character = this.deps.characters.get(message.params.character_id); if ( connection.sessionId === null || connection.online || connection.reservedCharacterId !== null || !character || !this.deps.isCharacterAvailable(message.params.character_id) ) { throw new ResponseError(ERROR_CODE_CHARACTER_UNAVAILABLE, ERROR_CHARACTER_UNAVAILABLE); } this.deps.state.characterReservations.set(character.characterId, connection.sessionId); connection.reservedCharacterId = character.characterId; this.deps.startReadyTimer(socket, connection); return { character: { ...this.deps.toCharacterSummaryMessage(character), path: character.path, }, }; } }