/** Cordis dynamic-plugin cards, inventory panel, business-view host, and `@pluginId` source. */ import type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client' import type {} from '@deepseek-ai/dsh-client-ui-tool/client' import type {} from '@deepseek-ai/dsh-client-locale/client' import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client' import type {} from '@deepseek-ai/dsh-api-remotes/client' import type { InputTriggerService, InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client' import type {} from './events.ts' import { CordisDefineRow } from './CordisDefineRow.tsx' import { CordisRunRow } from './CordisRunRow.tsx' import { CordisPanel } from './CordisPanel.tsx' import { createCordisInventory } from './inventory.ts' import { CordisRunCardRegistry } from './run-card-index.ts' import type { CordisDynamicPort } from './dynamic-port.ts' import type { CordisCardFace, CordisPanelFace, CordisRunCardFace } from './slots.ts' import { en, NS, zh } from './locales.ts' export type { CordisCardFace, CordisPanelFace, CordisRunCardFace, CordisToolViewOwnerProps } from './slots.ts' export type { CordisActionResult, CordisDynamicPort, CordisInventoryRow } from './dynamic-port.ts' export type { CordisDefineRowProps } from './CordisDefineRow.tsx' export type { CordisRunRowProps } from './CordisRunRow.tsx' export type { CordisRunCardPointer, CordisRunCardStore, CordisToolViewKey, } from './run-card-index.ts' export type { ApprovalRequestId, CordisDynamicPackageId, CordisDynamicPluginId, CordisDynamicPluginRunId, DynamicCordisInventoryRow, DynamicCordisPackage, DynamicCordisRetracted, } from './events.ts' export type { CordisKey } from './locales.ts' /** Required services for the two Tool cards, panel, Remote lifecycle, and Slash source. */ export const inject = [ 'slots', 'locale', 'slash', 'remote', 'remote.dynamicCordisRunner', 'dynamicCordisRunner', ] /** Mount every Cordis browser surface over the shared Host inventory. */ export function apply(ctx: ClientContext): void { ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-cordis: dictionaries') const port: CordisDynamicPort = { stop: async (sessionId, pluginId) => { const answered = await ctx.remote.dynamicCordisRunner.stopFromPanel(sessionId, pluginId) if (!answered.ok) return { ok: false, message: `${answered.error.code}: ${answered.error.message}` } if (answered.value.ok || answered.value.reason === 'not-running') return { ok: true } return { ok: false, message: answered.value.message } }, remove: async (sessionId, pluginId) => { const answered = await ctx.remote.dynamicCordisRunner.undefineFromPanel(sessionId, pluginId) if (!answered.ok) return { ok: false, message: `${answered.error.code}: ${answered.error.message}` } return answered.value.ok ? { ok: true } : { ok: false, message: answered.value.message } }, inventory: async () => { const answered = await ctx.remote.dynamicCordisRunner.inventory() if (!answered.ok) throw new Error(`${answered.error.code}: ${answered.error.message}`) return answered.value }, } const inventory = createCordisInventory(port, (error) => { console.error('[ui-cordis] reading the Cordis inventory failed:', error) }) const runner = ctx.dynamicCordisRunner const loaded = { getSnapshot: () => runner.getSnapshot(), subscribe: (fn: () => void) => runner.subscribe(fn) } const runCards = new CordisRunCardRegistry() ctx.effect(() => inventory.subscribe(() => { const snapshot = inventory.getSnapshot() if (snapshot.read) runner.reconcileApprovals(snapshot.rows) }), 'ui-cordis: reconcile pending approvals') ctx.remote.$on('cordis/dynamic-package', () => { inventory.refresh() }) ctx.remote.$on('cordis/dynamic-retract', () => { inventory.refresh() }) ctx.remote.$on('cordis/request-run', (request) => { if (!inventory.getSnapshot().rows.some(row => row.pluginId === request.pluginId)) inventory.refresh() }) ctx.remote.$on('cordis/request-run-resolved', () => { inventory.refresh() }) ctx.on('connection/reset', () => { inventory.reset() inventory.refresh() }) ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({ name: 'sidebar.footer.action', id: 'cordis-panel', locale: NS, inject: (): CordisPanelFace => ({ hooks: { inventory, activeRuns: runner.activeRuns, runErrors: runner.lastRunError, loaded, renderFailures: runner.renderFailures, }, onApprove: (requestId, approveFutureVersions) => runner.approve(requestId, approveFutureVersions), onDecline: requestId => runner.decline(requestId), onRun: request => runner.startUserRun(request), onStop: async (sessionId, pluginId) => { const result = await port.stop(sessionId, pluginId) inventory.refresh() return result }, onRemove: async (sessionId, pluginId) => { const result = await port.remove(sessionId, pluginId) if (result.ok) inventory.retire(pluginId) inventory.refresh() return result }, onRefresh: () => { inventory.refresh() }, }), }, CordisPanel)) const cardFace = (): CordisCardFace => ({ hooks: { inventory, loaded } }) ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({ name: 'tool.call.toolview', key: 'cordis_define', locale: NS, inject: cardFace, }, CordisDefineRow)) ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({ name: 'tool.call.toolview', key: 'cordis_run', locale: NS, children: { 'tool.view.cordis': { kind: 'keyed', scope: 'session' } }, inject: (sessionId: SessionId): CordisRunCardFace => { const store = runCards.forSession(sessionId) return { hooks: { inventory, loaded, runCards: store, activeRuns: runner.activeRuns }, onObserveRunCard: (pointer) => { store.observe(pointer) }, } }, }, CordisRunRow)) const rowsOf = (sessionId: SessionId, query: string) => inventory.getSnapshot().rows .filter(row => row.agentId === sessionId && String(row.pluginId).includes(query)) const source: InputTriggerSource = { trigger: '@', name: 'cordis', order: 1, candidates(session, { query }) { const rows = rowsOf(session.sessionId, query) return Promise.resolve(rows.map((row) => { const packageId = row.nextPackageId ?? row.currentPackageId ?? row.packages.at(-1)?.packageId const pkg = packageId === undefined ? undefined : row.packages.find(candidate => candidate.packageId === packageId) return { name: String(row.pluginId), ...pkg === undefined ? {} : { description: pkg.purpose }, } })) }, warm() { inventory.refresh() }, lexicon(session) { return rowsOf(session.sessionId, '').map(row => String(row.pluginId)) }, subscribeLexicon(_session, listener) { return inventory.subscribe(listener) }, onPick({ candidate }) { return { text: `@${candidate.name} ` } }, } const slash = ctx.get('slash') as InputTriggerService ctx.effect(() => slash.registerSource(source), 'ui-cordis: @pluginId source') inventory.refresh() }