From 41d6e12f0d349511dfef4e1f007361842680d076 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 28 Apr 2026 10:45:36 +0900 Subject: [PATCH] feat(team-mode): add member session resolution and routing logic --- .../team-mode/member-session-resolution.ts | 63 +++++++++++++++++ .../team-mode/member-session-routing.ts | 69 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 src/features/team-mode/member-session-resolution.ts create mode 100644 src/features/team-mode/member-session-routing.ts diff --git a/src/features/team-mode/member-session-resolution.ts b/src/features/team-mode/member-session-resolution.ts new file mode 100644 index 000000000..798d21ff0 --- /dev/null +++ b/src/features/team-mode/member-session-resolution.ts @@ -0,0 +1,63 @@ +import type { TeamModeConfig } from "../../config/schema/team-mode" +import { log } from "../../shared/logger" +import { lookupTeamSession } from "./team-session-registry" +import { listActiveTeams, loadRuntimeState } from "./team-state-store/store" + +export type ResolvedMemberSession = { + teamRunId: string + memberName: string +} + +export async function findResolvedMemberSession( + sessionID: string, + config: TeamModeConfig, + logContext: string, +): Promise { + const registryEntry = lookupTeamSession(sessionID) + if (registryEntry?.role === "member") { + try { + const runtimeState = await loadRuntimeState(registryEntry.teamRunId, config) + const memberEntry = runtimeState.members.find( + (member) => member.name === registryEntry.memberName + && (member.sessionId === undefined || member.sessionId === sessionID), + ) + + if (memberEntry !== undefined) { + return { + teamRunId: runtimeState.teamRunId, + memberName: memberEntry.name, + } + } + } catch (error) { + log(`${logContext} registry lookup failed`, { + event: `${logContext}-registry-error`, + teamRunId: registryEntry.teamRunId, + sessionID, + error: error instanceof Error ? error.message : String(error), + }) + } + } + + const activeTeams = await listActiveTeams(config) + for (const activeTeam of activeTeams) { + try { + const runtimeState = await loadRuntimeState(activeTeam.teamRunId, config) + const memberEntry = runtimeState.members.find((member) => member.sessionId === sessionID) + if (memberEntry !== undefined) { + return { + teamRunId: runtimeState.teamRunId, + memberName: memberEntry.name, + } + } + } catch (error) { + log(`${logContext} skipped runtime`, { + event: `${logContext}-runtime-error`, + teamRunId: activeTeam.teamRunId, + sessionID, + error: error instanceof Error ? error.message : String(error), + }) + } + } + + return null +} diff --git a/src/features/team-mode/member-session-routing.ts b/src/features/team-mode/member-session-routing.ts new file mode 100644 index 000000000..af2ae8f88 --- /dev/null +++ b/src/features/team-mode/member-session-routing.ts @@ -0,0 +1,69 @@ +import { stripAgentListSortPrefix } from "../../shared/agent-display-names" +import { resolveRegisteredAgentName } from "../claude-code-session-state" +import { applySessionPromptParams } from "../../shared/session-prompt-params-helpers" +import { SessionCategoryRegistry } from "../../shared/session-category-registry" +import type { RuntimeStateMember } from "./types" + +type PromptGenerationModel = { + reasoningEffort?: string + temperature?: number + top_p?: number + maxTokens?: number + thinking?: { type: "enabled" | "disabled"; budgetTokens?: number } +} + +export type TeamMemberPromptBody = { + parts: Array<{ type: "text"; text: string }> + agent?: string + model?: { providerID: string; modelID: string } + variant?: string + temperature?: number + topP?: number + maxOutputTokens?: number + options?: Record +} + +function buildPromptGenerationParams(model: PromptGenerationModel | undefined): Omit { + if (!model) { + return {} + } + + const promptOptions: Record = { + ...(model.reasoningEffort ? { reasoningEffort: model.reasoningEffort } : {}), + ...(model.thinking ? { thinking: model.thinking } : {}), + } + + return { + ...(model.temperature !== undefined ? { temperature: model.temperature } : {}), + ...(model.top_p !== undefined ? { topP: model.top_p } : {}), + ...(model.maxTokens !== undefined ? { maxOutputTokens: model.maxTokens } : {}), + ...(Object.keys(promptOptions).length > 0 ? { options: promptOptions } : {}), + } +} + +export function applyMemberSessionRouting(sessionID: string, member: RuntimeStateMember): void { + if (member.category) { + SessionCategoryRegistry.register(sessionID, member.category) + } + + applySessionPromptParams(sessionID, member.model) +} + +export function buildMemberPromptBody(member: RuntimeStateMember, text: string): TeamMemberPromptBody { + const normalizedAgent = member.subagent_type ? stripAgentListSortPrefix(member.subagent_type) : undefined + const launchAgent = resolveRegisteredAgentName(normalizedAgent) ?? normalizedAgent + const model = member.model + ? { + providerID: member.model.providerID, + modelID: member.model.modelID, + } + : undefined + + return { + ...(launchAgent ? { agent: launchAgent } : {}), + ...(model ? { model } : {}), + ...(member.model?.variant ? { variant: member.model.variant } : {}), + ...buildPromptGenerationParams(member.model), + parts: [{ type: "text", text }], + } +}