Files
deepseek-harness/packages/experimental/client-ui-agent-team/src/client/mount.ts
T

102 lines
3.7 KiB
TypeScript

/** Source-safe Agent Teams browser registration and Remote mount lifecycle. */
import type {
TeamMemberView as TeamRosterMember,
TeamView,
} from '@deepseek-ai/dsh-experimental-agent-team/client'
import type {} from '@deepseek-ai/dsh-experimental-agent-team/remote'
import type { Context as ClientContext } from '@deepseek-ai/cordis'
import type {} from '@deepseek-ai/dsh-api-remotes/client'
import type {} from '@deepseek-ai/dsh-api-session-controller/client'
import type { SessionId } from '@deepseek-ai/dsh-session/types'
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
import type {} from '@deepseek-ai/dsh-client-locale/client'
import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
import type { TypertRemoteContribution } from '@deepseek-ai/dsh-typert-protocol'
import {
TeamAction, type TeamActionInjected, type TeamActionResult, type TeamTaskActionResult,
} from './TeamAction.tsx'
import { en, NS, zh, type TeamKey } from './locales.ts'
declare module '@deepseek-ai/dsh-client-ui-slots' {
interface LocaleNamespaceMap {
/** Agent Teams roster and task-board copy. */
'agent-team': TeamKey
}
}
/** Required browser services for RPC, navigation, slots, and localized copy. */
export const inject = ['sessions', 'remote', 'slots', 'locale']
function registerUi(ctx: ClientContext): void {
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'client-ui-agent-team: dictionaries')
const sessions = ctx.sessions
const leadSessionId = (sessionId: SessionId): SessionId => {
const address = sessions.binding(sessionId)?.session.getSnapshot().subagent?.address
return address?.parentSessionId ?? sessionId
}
const actions: TeamActionInjected = {
async load(sessionId): Promise<TeamActionResult<TeamView>> {
return await ctx.remote.agentTeams.view(leadSessionId(sessionId))
},
async createTask(sessionId, input): Promise<TeamTaskActionResult> {
return await ctx.remote.agentTeams.createTask(leadSessionId(sessionId), input)
},
async updateTask(sessionId, input) {
const { owner, ...rest } = input
return await ctx.remote.agentTeams.updateTask(leadSessionId(sessionId), {
...rest,
...owner === undefined ? {} : { owner },
})
},
async openTeammate(sessionId: SessionId, member: TeamRosterMember): Promise<void> {
if (member.role !== 'teammate') return
const parentSessionId = leadSessionId(sessionId)
await sessions.refreshSubagents(parentSessionId)
if (sessions.list.getSnapshot().current !== sessionId) return
sessions.openSubagent({
parentSessionId,
childSessionId: member.id,
mode: 'continuable',
})
},
}
ctx.slots.inject(
'conversation.session.header.actions',
() => ctx.slots.register({
name: 'conversation.session.header.actions',
id: 'agent-team',
order: 20,
locale: NS,
inject: () => actions,
}, TeamAction),
)
}
/**
* Mount one generated Team Remote contribution, then register its browser UI.
* @param ctx - Client Context carrying navigation, locale, slot, and Remote services.
* @param contribution - generated Team descriptors selected by the browser entry.
* @returns disposer for both the UI registrations and Remote namespace.
*/
export async function mountAgentTeamUi(
ctx: ClientContext,
contribution: TypertRemoteContribution,
): Promise<() => Promise<void>> {
const disposeRemote = await ctx.remote.$mount(contribution)
const ui = ctx.inject(['sessions', 'remote.agentTeams', 'slots', 'locale'], registerUi)
try {
await ui
} catch (error) {
await ui.dispose()
await disposeRemote()
throw error
}
return async () => {
await ui.dispose()
await disposeRemote()
}
}