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
@@ -1,7 +1,8 @@
/** Internal React bindings for the renderer host and active session cell. */
/** Internal React bindings for the renderer host and active session provide bundle. */
import { createContext, useContext, type ReactNode } from 'react'
import type {
HostObservable, SessionCell, SlotRendererHost, SnapshotSelectorHook,
HostObservable, MaybeSnapshotSelectorHook, SessionMaybeProvideInfo, SessionProvideInfo,
SlotRendererHost, SnapshotSelectorHook,
} from '@deepseek-ai/dsh-client-ui-slots'
import { bindSnapshotSelector } from './bind.ts'
@@ -27,17 +28,24 @@ export function useHost(): SlotRendererHost {
return host
}
const BindingContext = createContext<SessionCell | null>(null)
const BindingContext = createContext<SessionMaybeProvideInfo | null>(null)
/** Read the current-session-optional bundle supplied at the root. */
export function useSessionMaybeProvideInfo(): SessionMaybeProvideInfo {
const info = useContext(BindingContext)
if (!info) throw new SlotAssemblyError('session-aware slot rendered outside the root binding provider')
return info
}
/**
* Read the enclosing session cell; throws outside a SessionProvider subtree
* (session slots must not render without a session).
* @returns the enclosing cell.
* Read the enclosing session provide bundle; throws outside a SessionProvider
* subtree (session slots must not render without a session).
* @returns the enclosing bundle.
*/
export function useSessionCell(): SessionCell {
const cell = useContext(BindingContext)
if (!cell) throw new SlotAssemblyError('session slot rendered outside SessionProvider')
return cell
export function useSessionProvideInfo(): SessionProvideInfo {
const info = useSessionMaybeProvideInfo()
if (info.sessionId === undefined) throw new SlotAssemblyError('strict session slot rendered without a session')
return info as SessionProvideInfo
}
/**
@@ -57,6 +65,36 @@ export function observableHook<T>(source: HostObservable<T>): SnapshotSelectorHo
}
const hookCache = new WeakMap<object, unknown>()
const absentSource: HostObservable<undefined> = {
getSnapshot: () => undefined,
subscribe: () => () => {},
}
/** Bind a source that disappears with the current session to an optional selector hook. */
export function maybeObservableHook<T>(source: HostObservable<T> | undefined): MaybeSnapshotSelectorHook<T> {
if (source !== undefined) return observableHook(source)
return useAbsentSnapshot as MaybeSnapshotSelectorHook<T>
}
function useAbsentSnapshot<S>(_selector: (snapshot: never) => S, _equal?: (a: S, b: S) => boolean): S | undefined {
return observableHook(absentSource)(() => undefined)
}
/**
* Root-level binding provider. It follows current selection without a key, so
* session-maybe entries retain their React identity while the context value
* moves between absent and definite session bundles.
*/
export function SessionMaybeProvider({ children }: { children: ReactNode }) {
const host = useHost()
const id = observableHook(host.sessions.current)((s) => s)
return (
<BindingContext.Provider value={host.sessions.maybeProvideInfo(id)}>
{children}
</BindingContext.Provider>
)
}
/** SessionProvider surface: render-prop body plus the no-session branch. */
export interface SessionProviderProps {
/** No-session body (also covers a current id whose session cannot be resolved). */
@@ -75,10 +113,10 @@ export interface SessionProviderProps {
export function SessionProvider({ empty, children }: SessionProviderProps) {
const host = useHost()
const id = observableHook(host.sessions.current)((s) => s)
const cell = id === undefined ? undefined : host.sessions.cell(id)
if (id === undefined || cell === undefined) return <>{empty?.() ?? null}</>
const info = id === undefined ? undefined : host.sessions.provideInfo(id)
if (id === undefined || info === undefined) return <>{empty?.() ?? null}</>
return (
<BindingContext.Provider value={cell} key={id}>
<BindingContext.Provider value={info} key={id}>
{children(id)}
</BindingContext.Provider>
)