feat: slash system / input service / agent scope

This commit is contained in:
imccyu
2026-07-27 03:17:52 +08:00
parent f3a4833dbf
commit a27be43ac1
210 changed files with 15969 additions and 2213 deletions

View File

@@ -0,0 +1,58 @@
/**
* Subagent reference plugin, browser half: registers the '@' source —
* candidates filtered from the session list snapshot's running children
* (zero RPC; the list rides the plugin's root-context sessions service, the
* scoped session comes from the per-call projection), pick inserts the
* literal `@label ` text (decision 21: the draft carries plain text, chip
* visuals are derived by scanning against the source lexicon, and the
* prompt ships the same literal). Consumption semantics stay with future
* business work (design ledger). No adjudication hooks: subagent
* references never enter command adjudication.
*/
import type { ClientContext, SessionsService } from '@deepseek-ai/dsh-client-runtime/client'
import type { ClientSessionContext, SlashServiceContract, SlashSource } from '@deepseek-ai/dsh-client-ui-slash/client'
/** Required services: the slash registry + the session list face the source closes over. */
export const inject = ['slash', 'sessions']
/**
* Client plugin body: register the '@' subagent source over the root session list.
* @param ctx - client root context.
*/
export function apply(ctx: ClientContext): void {
const sessions = ctx.get('sessions') as SessionsService
// Child labels live on the session list (parentId lineage + displayTitle),
// not the conversation snapshot — the list store is the zero-RPC candidate feed.
const childLabels = (session: ClientSessionContext, query: string): string[] => {
const { byId } = sessions.list.getSnapshot()
return Object.values(byId)
.filter((child) => child.parentId === session.sessionId && child.running && child.displayTitle.includes(query))
.map((child) => child.displayTitle)
}
const source: SlashSource = {
trigger: '@',
name: 'subagent',
candidates(session, { query }) {
return Promise.resolve(childLabels(session, query).map((name) => ({ name })))
},
lexicon(session) {
// The list snapshot is always warm — the full running-children roster.
return childLabels(session, '')
},
onPick({ candidate }) {
// Decision 21: plain-text reference — the literal lands in the draft
// and ships to the model verbatim (trailing space closes the token).
// Legacy path (decision 21), retained for the removal cut, no longer reached:
// return { insert: { source: 'subagent', ref: candidate.name, label: candidate.name, clipboardText: `@${candidate.name}` } }
return { text: `@${candidate.name} ` }
},
codec: {
clipboardText: (ref) => `@${ref}`,
// TODO: serialize returns the raw label until the '@' consumption
// feature defines a model representation (design ledger).
serialize: (ref) => Promise.resolve(`@${ref}`),
},
}
const slash = ctx.get('slash') as SlashServiceContract
ctx.effect(() => slash.registerSource(source), 'ui-subagent: @ source')
}

View File

@@ -0,0 +1,6 @@
declare module '*.module.css' {
const classes: Record<string, string>
export default classes
}
declare module '*.css'

View File

@@ -0,0 +1,9 @@
/**
* Subagent reference plugin, node half. Pure UI plugin: the empty apply
* exists so the plugin appears in the host cordis.yml / Loader; the browser
* half ships via exports["./client"], discovered through the package.json
* dshClient declaration.
*/
/** Host plugin body — no host-side behavior for this source plugin. */
export function apply(): void {}

View File

@@ -0,0 +1,31 @@
/**
* Package-owned invariant companion for `@deepseek-ai/dsh-client-ui-subagent`.
* @module @deepseek-ai/dsh-client-ui-subagent/invariant
*/
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-client-ui-subagent'
/** Cordis companion plugin name. */
export const name = 'client-ui-subagent-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/**
* No runtime invariant: a single slash-source registration whose disposal is
* proven by the HMR-safety spec — it emits no cordis events and owns no
* cross-plugin mutable state.
*/
const install: InvariantInstaller = () => {}
/**
* Register this package's invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/
export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
/* jscpd:ignore-end */