diff --git a/package.json b/package.json index abe9431a8f..cb5f33f4d9 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,7 @@ "verify-cordis-api": "tsx scripts/gen-cordis-api.ts --check", "gen-client-catalog": "tsx scripts/gen-client-catalog.ts", "gen-cordis-inspect-catalog": "tsx scripts/gen-cordis-inspect-catalog.ts", + "verify-cordis-inspect-catalog": "tsx scripts/gen-cordis-inspect-catalog.ts --check", "verify-client-catalog": "tsx scripts/gen-client-catalog.ts --check", "verify-export-jsdoc": "tsx scripts/verify-export-jsdoc.ts", "gen-tool-catalog": "tsx scripts/gen-tool-catalog.ts", diff --git a/packages/extensions/cordis-client-runner/src/client/api-catalog.ts b/packages/extensions/cordis-client-runner/src/client/api-catalog.ts index 216652dc05..fce084cf6a 100644 --- a/packages/extensions/cordis-client-runner/src/client/api-catalog.ts +++ b/packages/extensions/cordis-client-runner/src/client/api-catalog.ts @@ -1,7 +1,7 @@ /** - * Generated by scripts/gen-cordis-api.ts — do not edit by hand; run - * `pnpm run gen-cordis-api` to regenerate (freshness-gated by - * `pnpm run verify-cordis-api` in doc-sync). + * Generated by scripts/gen-cordis-inspect-catalog.ts — do not edit by hand; run + * `pnpm run gen-cordis-inspect-catalog` to regenerate (freshness-gated by + * `pnpm run verify-cordis-inspect-catalog` in doc-sync). * * The machine-readable cordis API catalog `cordis_inspect` serves to the * model: harness services (summary + structured public method contracts), @@ -184,7 +184,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ returns: 'completion of the current or newly started refresh.', }, { - signature: 'search( query: string, signal: AbortSignal, ): Promise>', + signature: 'search( query: string, signal: AbortSignal, ): Promise>', description: 'Search the Host\'s visible message-content index. Results stay request-local; the list snapshot remains the metadata authority.', parameters: [{ name: 'query', description: 'non-blank literal phrase.' }, { name: 'signal', description: 'cancellation for a superseded search.' }], returns: 'bounded results, or a business/transport error.', @@ -297,71 +297,83 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ ], }, { - key: 'workspaces', - summary: 'The workspaces-service face injected as `ctx.workspaces`.', - description: 'The workspaces-service face injected as `ctx.workspaces`.', + key: 'uiWorkspace', + summary: 'Workspace archive and directory operations consumed by Client UI domains.', + description: 'Workspace archive and directory operations consumed by Client UI domains.', methods: [ { signature: 'connectWorkspace(workspaceId: WorkspaceId): Promise', - description: 'Connect a Workspace to its reusable or freshly created blank session.', - parameters: [{ name: 'workspaceId', description: 'target workspace.' }], - returns: 'the connected session id.', + description: 'Resolve the reusable or newly created blank Session for a Workspace.', + parameters: [{ name: 'workspaceId', description: 'target Workspace.' }], + returns: 'a Session already addressable through the Session Controller.', }, { signature: 'startSession(workspaceId?: WorkspaceId): void', - description: 'The New Session flow: connect the explicit, current-Session, or recent Workspace and open the resulting session; failures surface on the session list state.', - parameters: [{ name: 'workspaceId', description: 'explicit target; omitted inherits the current Session\'s Workspace before falling back to the recency projection.' }], + description: 'Start a New Session flow and navigate to its Session.', + parameters: [{ name: 'workspaceId', description: 'explicit target; absent inherits the current or most recent Workspace.' }], }, { - signature: 'create(input: { path: string }): Promise', - description: 'Register an existing path as a Workspace.', - parameters: [{ name: 'input', description: 'the Host create payload.' }], - returns: 'the created or idempotently resolved Workspace.', + signature: 'archiveSession(sessionId: SessionId): Promise', + description: 'Archive a Session and clear it when it is the current selection.', + parameters: [{ name: 'sessionId', description: 'Session to archive.' }], }, { signature: 'pickDirectory(): Promise', - description: 'Open the Host\'s native directory picker.', + description: 'Open the Host-native directory picker.', parameters: [], - returns: 'the selected path, or null when the user cancelled.', + returns: 'the selected directory, or null when cancelled.', }, { signature: 'listDirectory(path?: string, signal?: AbortSignal): Promise', - description: 'List one directory level through the Host\'s `browse` capability.', - parameters: [{ name: 'path', description: 'absolute directory to list; absent lists the Host home directory.' }, { name: 'signal', description: 'aborts the wire request (and the Host\'s scan) when the caller supersedes it.' }], - returns: 'the level\'s listing with breadcrumb ancestry.', + description: 'List one Host directory level.', + parameters: [{ name: 'path', description: 'directory path; absent selects the Host home.' }, { name: 'signal', description: 'cancellation for a superseded scan.' }], + returns: 'directory entries and breadcrumb ancestry.', }, { signature: 'createDirectory(path: string, name: string): Promise', - description: 'Create one child directory through the Host\'s `browse` capability.', - parameters: [{ name: 'path', description: 'absolute existing parent directory.' }, { name: 'name', description: 'single non-blank path segment.' }], - returns: 'the created directory\'s absolute path.', + description: 'Create a child directory.', + parameters: [{ name: 'path', description: 'existing parent directory.' }, { name: 'name', description: 'child directory name.' }], + returns: 'created absolute path.', }, { signature: 'openPath(path: string): Promise', - description: 'Open a filesystem path with the Host operating system\'s default application.', - parameters: [{ name: 'path', description: 'absolute or host-resolvable path.' }], + description: 'Open a path with the Host operating system.', + parameters: [{ name: 'path', description: 'absolute or Host-resolvable path.' }], + }, + ], + }, + { + key: 'workspaces', + summary: 'Workspace Controller\'s Client service face.', + description: 'Workspace Controller\'s Client service face.', + methods: [ + { + signature: 'create(input: { path: string }): Promise', + description: 'Register an existing path as a Workspace.', + parameters: [{ name: 'input', description: 'Host create payload.' }], + returns: 'the created or idempotently resolved Workspace.', }, { signature: 'rename(workspaceId: WorkspaceId, title: string): Promise', description: 'Rename a Workspace.', - parameters: [{ name: 'workspaceId', description: 'target workspace.' }, { name: 'title', description: 'the new display title.' }], - returns: 'the updated Workspace view.', + parameters: [{ name: 'workspaceId', description: 'target Workspace.' }, { name: 'title', description: 'new display title.' }], + returns: 'the renamed Workspace.', }, { signature: 'delete(workspaceId: WorkspaceId): Promise', - description: 'Delete a Workspace (its sessions fall back to the unaccounted group).', - parameters: [{ name: 'workspaceId', description: 'target workspace.' }], - }, - { - signature: 'insertSessionBefore(workspaceId: WorkspaceId, sessionId: SessionId, beforeSessionId?: SessionId): Promise', - description: 'Move an accounted session within/into a Workspace\'s ordered list.', - parameters: [{ name: 'workspaceId', description: 'target workspace.' }, { name: 'sessionId', description: 'accounted session to move.' }, { name: 'beforeSessionId', description: 'accounted anchor to insert before; omitted appends.' }], - returns: 'the updated Workspace view.', + description: 'Delete a Workspace registration without deleting Sessions or files.', + parameters: [{ name: 'workspaceId', description: 'target Workspace.' }], }, { signature: 'archiveSession(sessionId: SessionId): Promise', - description: 'Archive a session into the registry-global set (hidden from grouping surfaces; session log and accounting slot remain). Archiving the current session clears the selection into the New Session view state.', - parameters: [{ name: 'sessionId', description: 'session to archive.' }], + description: 'Archive a Session from Workspace grouping surfaces.', + parameters: [{ name: 'sessionId', description: 'Session to archive.' }], + }, + { + signature: 'insertSessionBefore( workspaceId: WorkspaceId, sessionId: SessionId, beforeSessionId?: SessionId, ): Promise', + description: 'Move a Session within one Workspace account.', + parameters: [{ name: 'workspaceId', description: 'owning Workspace.' }, { name: 'sessionId', description: 'Session to move.' }, { name: 'beforeSessionId', description: 'anchor Session; omitted appends.' }], + returns: 'the changed Workspace.', }, ], }, @@ -373,8 +385,8 @@ export const EVENT_API: readonly EventApiEntry[] = [ name: 'connection/reset', mode: 'emit', signature: '\'connection/reset\'(): void', - summary: 'A connection generation was (re-)established.', - description: 'A connection generation was (re-)established. Wire-derived caches must treat their state as stale and repull (commands directory; the queue mirrors reset themselves through the session resync path).', + summary: 'A connection generation was established.', + description: 'A connection generation was established. Wire-derived caches must repull; long-lived streams own their own resume and baseline lifecycle.', parameters: [], }, { @@ -389,9 +401,9 @@ export const EVENT_API: readonly EventApiEntry[] = [ name: 'slots/changed', mode: 'emit', signature: '\'slots/changed\'(key: string): void', - summary: 'A slot\'s definition or registration set changed.', - description: 'A slot\'s definition or registration set changed.', - parameters: [{ name: 'key', description: 'the mutated SlotMap key.' }], + summary: 'A slot declaration or registration set changed.', + description: 'A slot declaration or registration set changed.', + parameters: [{ name: 'key', description: 'mutated SlotMap key.' }], }, { name: 'theme/change', @@ -411,27 +423,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'AgentContext', - declaration: 'export type AgentContext = Omit & {\n readonly remote: TypertClientRemote & TypertRemoteScopeApi<\'agent\'>;\n};', - }, - { - name: 'AssistantBlock', - declaration: 'export type AssistantBlock = {\n kind: \'text\';\n text: string;\n} | {\n kind: \'reasoning\';\n text: string;\n} | {\n kind: \'image\';\n attachment: ImageAttachmentRef;\n} | {\n kind: \'tool-call\';\n callId: string;\n name: string;\n argsRaw: string;\n} | {\n kind: \'other\';\n block: unknown;\n};', - }, - { - name: 'AssistantMessageNode', - declaration: 'export interface AssistantMessageNode {\n kind: \'assistant\';\n seq: number;\n messageId?: MessageId;\n time: number;\n turn: number;\n step: number;\n blocks: readonly AssistantBlock[];\n usage?: unknown;\n provenance?: AssistantProvenanceView;\n requestConfig?: AssistantRequestConfig;\n timing?: AssistantTiming;\n interrupted?: true;\n}', - }, - { - name: 'AssistantProvenanceView', - declaration: 'export interface AssistantProvenanceView {\n provider: string;\n model: string;\n}', - }, - { - name: 'AssistantRequestConfig', - declaration: 'export interface AssistantRequestConfig {\n provider: string;\n model: string;\n purpose?: string;\n thinking?: string;\n reasoningEffort?: string;\n temperature?: number;\n maxTokens?: number;\n stop?: readonly string[];\n}', - }, - { - name: 'AssistantTiming', - declaration: 'export interface AssistantTiming {\n stepStartTime: number | null;\n firstTokenTime: number | null;\n completedTime: number;\n}', + declaration: 'export type AgentContext = Omit & {\n readonly remote: ClientRemote & TypertRemoteScopeApi<\'agent\'>;\n};', }, { name: 'BakedActions', @@ -447,99 +439,55 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'ChainRenderOpts', - declaration: 'export interface ChainRenderOpts {\n fallback?: ReactNode;\n overlay?: boolean;\n}', - }, - { - name: 'ChatConversationViewNode', - declaration: 'export interface ChatConversationViewNode extends ConversationViewNode {\n readonly target: \'chat\';\n readonly anchorSeq: number;\n readonly location: ConversationLocation;\n readonly visibility: \'visible\' | \'hidden\';\n}', - }, - { - name: 'ChatLocationNodeIndex', - declaration: 'export interface ChatLocationNodeIndex {\n getTurn(turn: number): readonly string[];\n getStep(turn: number, step: number): readonly string[];\n}', - }, - { - name: 'ChatNodeStore', - declaration: 'export interface ChatNodeStore {\n get(key: string): ChatConversationViewNode | undefined;\n values(): readonly ChatConversationViewNode[];\n}', - }, - { - name: 'ChatSnapshot', - declaration: 'export interface ChatSnapshot {\n readonly order: readonly string[];\n readonly nodes: ChatNodeStore;\n readonly locations: ChatLocationNodeIndex;\n readonly timeline: ConversationTimelineSnapshot;\n readonly legacy: LegacyConversationSlice;\n}', + declaration: 'export interface ChainRenderOpts {\n fallback?: ReactNode;\n fallbackOnly?: boolean;\n overlay?: boolean;\n}', }, { name: 'ChildrenDecl', declaration: 'export type ChildrenDecl = {\n [P in keyof SlotMap & string]?: SlotSpec;\n};', }, { - name: 'CommandNode', - declaration: 'export interface CommandNode {\n kind: \'command\';\n seq: number;\n time: number;\n commandId: CommandId;\n name: string | null;\n args: string | null;\n outcome: {\n kind: \'success\' | \'error\';\n text?: string;\n sourceEventSeq?: number;\n } | null;\n}', + name: 'ClientConnectionRpc', + declaration: 'export interface ClientConnectionRpc {\n call(channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise>;\n readonly open?: (channel: string, endpoint: string, payload: unknown, signal: AbortSignal) => AsyncIterable;\n}', + }, + { + name: 'ClientRemote', + declaration: 'export interface ClientRemote extends TypertClientRemote {\n $stream(options: RemoteStreamOptions): RemoteStream;\n}', }, { name: 'CommonKeyOf', declaration: 'export type CommonKeyOf = LocaleNamespaceMap extends {\n common: infer C;\n} ? C & string : never;', }, - { - name: 'CompactionSummaryNode', - declaration: 'export interface CompactionSummaryNode {\n kind: \'compaction\';\n seq: number;\n time: number;\n summary: string | null;\n summaryEventSeq: number | null;\n shadowedItemCount: number | null;\n shadowedTokenCount: number | null;\n}', - }, { name: 'ComposedProps', declaration: 'export type ComposedProps, S extends keyof SlotMap & string, H, I extends object, M = never, N = undefined> = PropsRuntime & PropsRenderSlots & PropsStore & InjectFace & MatchedShare & PropsLocale;', }, { - name: 'ComposerPhase', - declaration: 'export type ComposerPhase = \'blank\' | \'engaging\' | \'active\';', + name: 'ConnectionConfig', + declaration: 'export interface ConnectionConfig {\n backoffBaseMs?: number;\n backoffFactor?: number;\n backoffMaxMs?: number;\n generationReadyTimeoutMs?: number;\n}', }, { - name: 'ContextMessageNode', - declaration: 'export interface ContextMessageNode {\n kind: \'context\';\n seq: number;\n time: number;\n content: readonly ContentBlock[];\n source: unknown;\n provenance: ContextProvenanceView;\n form: KnownContextForm | null;\n}', + name: 'ConnectionGenerationSource', + declaration: 'export type ConnectionGenerationSource = (signal: AbortSignal, ready: () => void) => Promise;', }, { - name: 'ContextProvenanceView', - declaration: 'export interface ContextProvenanceView {\n role: ContextRole;\n label: string | null;\n}', + name: 'ConnectionHandle', + declaration: 'export interface ConnectionHandle {\n readonly api: IApiClient;\n readonly isLoopback: boolean;\n readonly hostDescription: HostDescriptionSource;\n readonly rpc: ClientConnectionRpc;\n registerGenerationSource(source: ConnectionGenerationSource): () => void;\n start(sinks: ConnectionSinks, config?: ConnectionConfig): {\n stop(): void;\n };\n}', }, { - name: 'ContextRole', - declaration: 'export type ContextRole = \'inject\' | \'recall\';', + name: 'ConnectionRpcFailure', + declaration: 'export interface ConnectionRpcFailure {\n readonly code: string;\n readonly message: string;\n readonly details: object;\n}', }, { - name: 'ConversationLocation', - declaration: 'export type ConversationLocation = {\n readonly kind: \'session\';\n} | {\n readonly kind: \'turn\';\n readonly turn: TurnLocation;\n} | {\n readonly kind: \'step\';\n readonly turn: TurnLocation;\n readonly step: StepLocation;\n} | {\n readonly kind: \'unresolved\';\n};', + name: 'ConnectionRpcResult', + declaration: 'export type ConnectionRpcResult = {\n readonly ok: true;\n readonly value: T;\n} | {\n readonly ok: false;\n readonly error: ConnectionRpcFailure;\n};', }, { - name: 'ConversationLocationDataStore', - declaration: 'export interface ConversationLocationDataStore {\n get(key: Key): Readonly | undefined;\n}', + name: 'ConnectionSinks', + declaration: 'export interface ConnectionSinks {\n onConnected?: (description: HostDescription) => void;\n onStateChange?: (state: ConnectionState) => void;\n}', }, { - name: 'ConversationNode', - declaration: 'export type ConversationNode = UserMessageNode | AssistantMessageNode | SteeringMessageNode | ContextMessageNode | ModelRetryNode | TurnErrorNode | TurnMaxTokensNode | ToolResultNode | CommandNode | CompactionSummaryNode | UnknownSurfaceNode;', - }, - { - name: 'ConversationSnapshot', - declaration: 'export interface ConversationSnapshot {\n sessionId: SessionId;\n views: ConversationViewSnapshotStore;\n chat: ChatSnapshot;\n nodes: readonly ConversationNode[];\n turnTimings: ReadonlyMap;\n turnEnds: ReadonlyMap;\n partial: PartialAssistant | null;\n runningCalls: readonly RunningToolCall[];\n pending: readonly PendingInteraction[];\n queue: readonly QueuedMessage[];\n running: boolean;\n subagent: {\n address: SubagentAddress;\n parentAvailable: boolean;\n } | null;\n composerPhase: ComposerPhase;\n removed: boolean;\n openState: OpenState;\n openError: RpcError | null;\n hasMore: boolean;\n loadingOlder: boolean;\n promptError: PromptError | null;\n blank: boolean;\n lastAgentError: string | null;\n}', - }, - { - name: 'ConversationStepDataMap', - declaration: 'export interface ConversationStepDataMap {\n}', - }, - { - name: 'ConversationTimelineSnapshot', - declaration: 'export interface ConversationTimelineSnapshot {\n readonly turnOrder: readonly number[];\n readonly turns: ReadonlyMap;\n}', - }, - { - name: 'ConversationTurnDataMap', - declaration: 'export interface ConversationTurnDataMap {\n}', - }, - { - name: 'ConversationViewNode', - declaration: 'export interface ConversationViewNode {\n readonly key: string;\n readonly kind: string;\n readonly id: string;\n readonly target: string;\n readonly data: unknown;\n}', - }, - { - name: 'ConversationViewSnapshotMap', - declaration: 'export interface ConversationViewSnapshotMap {\n}', - }, - { - name: 'ConversationViewSnapshotStore', - declaration: 'export interface ConversationViewSnapshotStore {\n get>(target: Target): ConversationViewSnapshotMap[Target] | undefined;\n}', + name: 'ConnectionState', + declaration: 'export type ConnectionState = \'connected\' | \'reconnecting\';', }, { name: 'EntryKeyOf', @@ -557,9 +505,17 @@ export const TYPE_API: readonly TypeApiEntry[] = [ name: 'HooksSources', declaration: 'export type HooksSources = Record>;', }, + { + name: 'HostDescription', + declaration: 'export type HostDescription = import(\'@deepseek-ai/dsh-host-apiproxy/api\').ResponseValue<\'host.describe\'>;', + }, + { + name: 'HostDescriptionSource', + declaration: 'export interface HostDescriptionSource {\n getSnapshot(): HostDescription | undefined;\n subscribe(listener: () => void): () => void;\n}', + }, { name: 'HostObservable', - declaration: 'export interface HostObservable {\n getSnapshot(): T;\n subscribe(fn: () => void): () => void;\n}', + declaration: 'export type HostObservable = ObservableSnapshot;', }, { name: 'InjectFace', @@ -571,20 +527,12 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'ISession', - declaration: 'export interface ISession {\n readonly sessionId: SessionId;\n readonly projections: ProjectionsFace;\n prompt(content: PromptContentPart[], mode: \'queue\' | \'steer\', signal?: AbortSignal): Promise>;\n readAttachment(attachmentId: AttachmentIdType): Promise>;\n updateQueue(itemId: MessageId, action: QueueAction): Promise>;\n cancel(): Promise>;\n rename(title: string): Promise>;\n loadOlder(): Promise;\n command(line: string): Promise>;\n}', + declaration: 'export interface ISession {\n readonly sessionId: SessionId;\n readonly projections: ProjectionsFace;\n prompt(content: PromptContentPart[], mode: \'queue\' | \'steer\', signal?: AbortSignal): Promise>;\n readAttachment(attachmentId: AttachmentIdType): Promise>;\n updateQueue(itemId: MessageId, action: QueueAction): Promise>;\n cancel(): Promise>;\n rename(title: string): Promise>;\n loadOlder(): Promise;\n command(line: string): Promise>;\n}', }, { name: 'KeyPropsOf', declaration: 'export type KeyPropsOf> = SlotMap[K] extends {\n kind: \'keyed\';\n keyProps: infer P extends object;\n} ? EntryKey extends keyof P ? P[EntryKey] extends object ? P[EntryKey] : never : never : object;', }, - { - name: 'KnownContextForm', - declaration: 'export type KnownContextForm = typeof KNOWN_FORMS[number];', - }, - { - name: 'LegacyConversationSlice', - declaration: 'export interface LegacyConversationSlice {\n readonly nodes: readonly ConversationNode[];\n readonly turnTimings: ReadonlyMap;\n readonly turnEnds: ReadonlyMap;\n readonly partial: PartialAssistant | null;\n readonly runningCalls: readonly RunningToolCall[];\n}', - }, { name: 'LocaleDefinition', declaration: 'export interface LocaleDefinition {\n id: LocaleId;\n label: string;\n}', @@ -617,10 +565,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [ name: 'MatchedShare', declaration: 'export type MatchedShare = E[\'kind\'] extends \'chain\' ? {\n matched: M;\n} : object;', }, - { - name: 'ModelRetryNode', - declaration: 'export type ModelRetryNode = LlmRetryEventData & {\n kind: \'model-retry\';\n seq: number;\n time: number;\n retryState: \'scheduled\' | \'started\' | \'cancelled\';\n};', - }, { name: 'ObservableSnapshot', declaration: 'export interface ObservableSnapshot {\n getSnapshot(): T;\n subscribe(fn: () => void): () => void;\n}', @@ -633,33 +577,17 @@ export const TYPE_API: readonly TypeApiEntry[] = [ name: 'OwnerOf', declaration: 'export type OwnerOf = SlotMap[K] extends {\n owner: infer O extends object;\n} ? O : object;', }, - { - name: 'PartialAssistant', - declaration: 'export interface PartialAssistant {\n turn: number;\n step: number;\n blocks: readonly AssistantBlock[];\n}', - }, - { - name: 'PendingInteraction', - declaration: 'export type PendingInteraction = {\n [K in PendingKind]: PendingWait;\n}[PendingKind];', - }, - { - name: 'PendingKind', - declaration: 'export type PendingKind = keyof PendingPayloads;', - }, - { - name: 'PendingPayloads', - declaration: 'export interface PendingPayloads {\n approval: Omit, \'type\' | \'sessionId\'>;\n question: Omit, \'type\' | \'sessionId\'>;\n}', - }, - { - name: 'PendingWait', - declaration: 'export class PendingWait {\n readonly kind: K;\n readonly key: string;\n readonly sessionId: SessionId;\n readonly payload: PendingPayloads[K];\n constructor(kind: K, rpcId: RpcId, sessionId: SessionId, payload: PendingPayloads[K], respond: (message: ClientResponse) => Promise);\n respond(result: ClientResponse[\'result\']): Promise;\n markSettled(): void;\n}', - }, { name: 'ProjectionsFace', declaration: 'export interface ProjectionsFace {\n faceOf(key: string): ObservableSnapshot;\n}', }, + { + name: 'PromptContentPart', + declaration: 'export type PromptContentPart = {\n readonly type: \'text\';\n readonly text: string;\n} | {\n readonly type: \'image\';\n readonly mediaType: ImageMediaType;\n readonly data: string;\n readonly name?: string;\n};', + }, { name: 'PromptError', - declaration: 'export interface PromptError {\n op: \'send\' | \'stop\';\n error: RpcError;\n}', + declaration: 'export interface PromptError {\n readonly op: \'send\' | \'stop\';\n readonly error: ClientFailure;\n}', }, { name: 'PropsHooks', @@ -686,12 +614,20 @@ export const TYPE_API: readonly TypeApiEntry[] = [ declaration: 'export type PropsStore = H extends StoreHandle ? {\n useStore: SnapshotSelectorHook;\n actions: BakedActions;\n} : object;', }, { - name: 'QueueAction', - declaration: 'export type QueueAction = Parameters[1];', + name: 'RemoteStream', + declaration: 'export class RemoteStream implements AsyncIterable> {\n constructor(private readonly connection: Pick, private readonly options: RemoteStreamOptions);\n get signal(): AbortSignal;\n restart(): void;\n dispose(): Promise;\n [Symbol.asyncIterator](): AsyncIterator>;\n}', }, { - name: 'RunningToolCall', - declaration: 'export interface RunningToolCall {\n callId: string;\n name: string;\n argsRaw: string;\n turn: number;\n step: number;\n time: number;\n callView: ToolCallView | null;\n subCalls: readonly ToolCallBlock[];\n}', + name: 'RemoteStreamCarrierError', + declaration: 'export class RemoteStreamCarrierError extends Error {\n constructor(message: string, options?: ErrorOptions);\n}', + }, + { + name: 'RemoteStreamItem', + declaration: 'export interface RemoteStreamItem {\n readonly generation: number;\n readonly value: Item;\n readonly signal: AbortSignal;\n accept(): void;\n}', + }, + { + name: 'RemoteStreamOptions', + declaration: 'export interface RemoteStreamOptions {\n readonly name: string;\n readonly open: (signal: AbortSignal) => AsyncIterable;\n readonly ended: (accepted: boolean) => Error;\n readonly carrierFailed?: (error: RemoteStreamCarrierError) => void;\n}', }, { name: 'ScopeOf', @@ -699,15 +635,31 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'SessionAreaProps', - declaration: 'export interface SessionAreaProps {\n empty?: (() => ReactNode) | undefined;\n children: (sessionId: SessionIdOf) => ReactNode;\n}', + declaration: 'export interface SessionAreaProps {\n empty?: (() => ReactNode) | undefined;\n children: ReactNode;\n}', }, { name: 'SessionBinding', - declaration: 'export interface SessionBinding {\n readonly sessionId: SessionId;\n readonly session: SessionFace;\n readonly ctx: AgentContext;\n}', + declaration: 'export interface SessionBinding {\n readonly sessionId: SessionId;\n readonly session: SessionFace;\n readonly eventSource: SessionEventSource;\n readonly ctx: AgentContext;\n}', + }, + { + name: 'SessionEventChange', + declaration: 'export type SessionEventChange = {\n readonly kind: \'replace\';\n readonly entries: readonly SessionEventEntry[];\n} | {\n readonly kind: \'prepend\';\n readonly entries: readonly SessionEventEntry[];\n} | {\n readonly kind: \'append\';\n readonly entries: readonly SessionEventEntry[];\n};', + }, + { + name: 'SessionEventEntry', + declaration: 'export interface SessionEventEntry {\n readonly event: SessionWireEvent;\n readonly view?: SessionToolView;\n}', + }, + { + name: 'SessionEventSource', + declaration: 'export type SessionEventSource = ObservableSnapshot;', + }, + { + name: 'SessionEventWindow', + declaration: 'export interface SessionEventWindow {\n readonly entries: readonly SessionEventEntry[];\n readonly hasMore: boolean;\n readonly revision: number;\n readonly change: SessionEventChange;\n}', }, { name: 'SessionFace', - declaration: 'export type SessionFace = ISession & ObservableSnapshot;', + declaration: 'export type SessionFace = ISession & ObservableSnapshot;', }, { name: 'SessionIdOf', @@ -725,10 +677,26 @@ export const TYPE_API: readonly TypeApiEntry[] = [ name: 'SessionSearchResultItem', declaration: 'export interface SessionSearchResultItem {\n sessionId: SessionId;\n snippet: string;\n}', }, + { + name: 'SessionSnapshot', + declaration: 'export interface SessionSnapshot {\n readonly sessionId: SessionId;\n readonly queue: readonly QueuedMessage[];\n readonly running: boolean;\n readonly subagent: {\n readonly address: SubagentAddress;\n readonly parentAvailable: boolean;\n } | null;\n readonly removed: boolean;\n readonly openState: OpenState;\n readonly openError: ClientFailure | null;\n readonly hasMore: boolean;\n readonly loadingOlder: boolean;\n readonly promptError: PromptError | null;\n readonly blank: boolean;\n readonly lastAgentError: string | null;\n readonly promptAttempted: boolean;\n readonly awaitingFirstTurn: boolean;\n}', + }, { name: 'SessionStandardProps', declaration: 'export interface SessionStandardProps {\n}', }, + { + name: 'SessionToolCallView', + declaration: 'export type SessionToolCallView = (Omit & {\n readonly rawInput?: JsonValue;\n}) | TerminalCallView | DiffCallView;', + }, + { + name: 'SessionToolView', + declaration: 'export type SessionToolView = {\n readonly for: \'call\';\n readonly view: SessionToolCallView;\n} | {\n readonly for: \'result\';\n readonly view: ToolResultView;\n};', + }, + { + name: 'SessionWireEvent', + declaration: 'export interface SessionWireEvent {\n readonly type: string;\n readonly seq: number;\n readonly time: number;\n readonly data: JsonValue;\n readonly ignorable?: true;\n readonly sourceEventSeqs?: number[];\n readonly surfaceOp?: SurfaceOp;\n}', + }, { name: 'SlotComponent', declaration: 'export type SlotComponent

= (props: P) => ReactNode;', @@ -773,14 +741,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [ name: 'SnapshotSelectorHook', declaration: 'export type SnapshotSelectorHook = (sel: (s: T) => S, eq?: (a: S, b: S) => boolean) => S;', }, - { - name: 'SteeringMessageNode', - declaration: 'export interface SteeringMessageNode {\n kind: \'steering\';\n messageId: MessageId;\n seq: number;\n time: number;\n content: readonly ContentBlock[];\n source: unknown;\n}', - }, - { - name: 'StepLocation', - declaration: 'export interface StepLocation {\n readonly turn: number;\n readonly step: number;\n readonly start: SessionEvent<\'step/start\'> | undefined;\n readonly end: SessionEvent<\'step/end\'> | undefined;\n readonly status: \'open\' | \'closed\' | \'unknown\';\n readonly data: ConversationLocationDataStore;\n}', - }, { name: 'StoreDecl', declaration: 'export type StoreDecl = StoreHandle | StoreFactory;', @@ -829,14 +789,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [ name: 'ThemeTokens', declaration: 'export type ThemeTokens = Record;', }, - { - name: 'ToolCallBlock', - declaration: 'export type ToolCallBlock = RunningToolCall | ToolResultNode;', - }, - { - name: 'ToolResultNode', - declaration: 'export interface ToolResultNode {\n kind: \'tool-result\';\n seq: number;\n time: number;\n callId: string;\n call: {\n name: string;\n argsRaw: string;\n } | null;\n callTime: number | null;\n content: readonly ContentBlock[];\n isError: boolean;\n error?: {\n name: string;\n code: string;\n };\n meta?: unknown;\n callView: ToolCallView | null;\n resultView: ToolResultView | null;\n subCalls: readonly ToolCallBlock[];\n}', - }, { name: 'Translate', declaration: 'export type Translate = (key: K, params?: Record) => string;', @@ -846,24 +798,8 @@ export const TYPE_API: readonly TypeApiEntry[] = [ declaration: 'export type TranslateNS = Translate>;', }, { - name: 'TurnErrorNode', - declaration: 'export interface TurnErrorNode {\n kind: \'turn-error\';\n seq: number;\n time: number;\n turn: number;\n step: number;\n message: string;\n code?: string;\n}', - }, - { - name: 'TurnLocation', - declaration: 'export interface TurnLocation {\n readonly turn: number;\n readonly start: SessionEvent<\'turn/start\'> | undefined;\n readonly end: SessionEvent<\'turn/end\'> | undefined;\n readonly status: \'open\' | \'closed\' | \'unknown\';\n readonly steps: readonly StepLocation[];\n readonly data: ConversationLocationDataStore;\n}', - }, - { - name: 'TurnMaxTokensNode', - declaration: 'export interface TurnMaxTokensNode {\n kind: \'turn-max-tokens\';\n seq: number;\n time: number;\n turn: number;\n step: number;\n}', - }, - { - name: 'UnknownSurfaceNode', - declaration: 'export interface UnknownSurfaceNode {\n kind: \'unknown\';\n seq: number;\n time: number;\n type: string;\n data: unknown;\n}', - }, - { - name: 'UserMessageNode', - declaration: 'export interface UserMessageNode {\n kind: \'user\';\n seq: number;\n time: number;\n content: readonly ContentBlock[];\n source: unknown;\n}', + name: 'WorkspaceView', + declaration: 'export interface WorkspaceView {\n readonly workspaceId: WorkspaceId;\n readonly path: string;\n readonly title: string;\n readonly sessionIds: readonly SessionId[];\n readonly createdAt: string;\n readonly updatedAt: string;\n}', }, ] @@ -888,7 +824,7 @@ function referencedTypeClosure(seeds: readonly string[]): TypeApiEntry[] { const next: string[] = [] for (const entry of TYPE_API) { if (included.has(entry.name)) continue - const pattern = new RegExp(`\b${entry.name}\b`) + const pattern = new RegExp(`\\b${entry.name}\\b`) if (!frontier.some(text => pattern.test(text))) continue included.add(entry.name) next.push(entry.declaration) diff --git a/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts b/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts index bbf2662b4e..e038b24a0d 100644 --- a/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts +++ b/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts @@ -147,7 +147,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [ ], replaceRisk: 'shadows-shipped-ui', example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.approval.detail\', () => ctx.slots.register(\n { name: \'conversation.approval.detail\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}', - source: 'packages/client/ui-approval/src/client/contract/slots.ts:25', + source: 'packages/client/ui-approval/src/client/contract/slots.ts:37', }, { key: 'conversation.chat.assistant-actions', diff --git a/packages/extensions/cordis-client-runner/tests/api-catalog.client.spec.ts b/packages/extensions/cordis-client-runner/tests/api-catalog.client.spec.ts new file mode 100644 index 0000000000..019d34ac90 --- /dev/null +++ b/packages/extensions/cordis-client-runner/tests/api-catalog.client.spec.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest' +import { EVENT_API, queryServiceApi, SERVICE_API } from '../src/client/api-catalog.ts' + +describe('Client Cordis inspect catalog', () => { + it('publishes the split Workspace Controller and UI navigation services', () => { + expect(SERVICE_API.find(service => service.key === 'workspaces')?.methods.map(method => method.signature)) + .toEqual([ + 'create(input: { path: string }): Promise', + 'rename(workspaceId: WorkspaceId, title: string): Promise', + 'delete(workspaceId: WorkspaceId): Promise', + 'archiveSession(sessionId: SessionId): Promise', + 'insertSessionBefore( workspaceId: WorkspaceId, sessionId: SessionId, beforeSessionId?: SessionId, ): Promise', + ]) + expect(SERVICE_API.find(service => service.key === 'uiWorkspace')?.methods.map(method => method.signature)) + .toEqual([ + 'connectWorkspace(workspaceId: WorkspaceId): Promise', + 'startSession(workspaceId?: WorkspaceId): void', + 'archiveSession(sessionId: SessionId): Promise', + 'pickDirectory(): Promise', + 'listDirectory(path?: string, signal?: AbortSignal): Promise', + 'createDirectory(path: string, name: string): Promise', + 'openPath(path: string): Promise', + ]) + }) + + it('contains one entry per visible Client event', () => { + const names = EVENT_API.map(event => event.name) + expect(new Set(names).size).toBe(names.length) + }) + + it('includes the current referenced type closure for the Sessions service', () => { + const result = queryServiceApi('sessions') as { + referencedTypes: readonly { name: string }[] + } + expect(result.referencedTypes.length).toBeGreaterThan(0) + expect(result.referencedTypes.map(type => type.name)).not.toEqual(expect.arrayContaining([ + 'ConversationSnapshot', + 'PendingInteraction', + 'PendingPayloads', + 'PendingWait', + ])) + }) +}) diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts index bb3e9918ca..9d0fc17eb8 100644 --- a/packages/extensions/tool-cordis/src/api-catalog.ts +++ b/packages/extensions/tool-cordis/src/api-catalog.ts @@ -5660,7 +5660,7 @@ function referencedTypeClosure(seeds: readonly string[]): TypeApiEntry[] { const next: string[] = [] for (const entry of TYPE_API) { if (included.has(entry.name)) continue - const pattern = new RegExp(`\b${entry.name}\b`) + const pattern = new RegExp(`\\b${entry.name}\\b`) if (!frontier.some(text => pattern.test(text))) continue included.add(entry.name) next.push(entry.declaration) diff --git a/packages/typert/generator/src/cordis-catalog.ts b/packages/typert/generator/src/cordis-catalog.ts index 8ce666b96c..ec3065d94a 100644 --- a/packages/typert/generator/src/cordis-catalog.ts +++ b/packages/typert/generator/src/cordis-catalog.ts @@ -826,7 +826,7 @@ function renderRuntimeApi( ' const next: string[] = []', ' for (const entry of TYPE_API) {', ' if (included.has(entry.name)) continue', - ' const pattern = new RegExp(`\\b${entry.name}\\b`)', + ' const pattern = new RegExp(`\\\\b${entry.name}\\\\b`)', ' if (!frontier.some(text => pattern.test(text))) continue', ' included.add(entry.name)', ' next.push(entry.declaration)', diff --git a/scripts/gen-cordis-inspect-catalog.ts b/scripts/gen-cordis-inspect-catalog.ts index 77e6404c4d..24ce8d70d2 100644 --- a/scripts/gen-cordis-inspect-catalog.ts +++ b/scripts/gen-cordis-inspect-catalog.ts @@ -1,6 +1,6 @@ /** Generate model-visible Host/Client Service and Event inspect catalogs. */ -import { mkdirSync, writeFileSync } from 'node:fs' +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { projectCordisCatalog } from '@deepseek-ai/dsh-typert-generator' import type { CordisCatalogModel, ServiceMethodEntry } from '@deepseek-ai/dsh-typert-generator' @@ -15,10 +15,11 @@ const CLIENT_SERVICES: Readonly> = { sessions: ['open', 'openSubagent', 'setSubagentCatalogOpen', 'refreshSubagents', 'search', 'fork', 'scope', 'binding'], slots: ['register', 'inject'], theme: ['getTheme', 'setTheme', 'register', 'overrideTokens'], - workspaces: [ - 'connectWorkspace', 'startSession', 'create', 'pickDirectory', 'listDirectory', 'createDirectory', - 'openPath', 'rename', 'delete', 'insertSessionBefore', 'archiveSession', + uiWorkspace: [ + 'connectWorkspace', 'startSession', 'archiveSession', 'pickDirectory', 'listDirectory', + 'createDirectory', 'openPath', ], + workspaces: ['create', 'rename', 'delete', 'insertSessionBefore', 'archiveSession'], } const CLIENT_EVENTS = new Set([ @@ -33,14 +34,34 @@ function methodName(method: ServiceMethodEntry): string | undefined { } function clientModel(model: CordisCatalogModel): CordisCatalogModel { + const services = Object.entries(CLIENT_SERVICES).map(([key, allowed]) => { + const matches = model.services.filter(service => service.key === key) + if (matches.length !== 1) { + throw new Error(`gen-cordis-inspect-catalog: expected one Client Service ${JSON.stringify(key)}, found ${matches.length}`) + } + const service = matches[0] as (typeof matches)[number] + const names = new Set(allowed) + const methods = service.methods.filter(method => names.has(methodName(method) ?? '')) + const found = new Set(methods.map(method => methodName(method))) + const missing = allowed.filter(name => !found.has(name)) + if (missing.length > 0) { + throw new Error( + `gen-cordis-inspect-catalog: Client Service ${JSON.stringify(key)}` + + ` is missing allowlisted method(s): ${missing.join(', ')}`, + ) + } + return { ...service, methods } + }) + const events = [...CLIENT_EVENTS].map((name) => { + const matches = model.events.filter(event => event.name === name) + if (matches.length !== 1) { + throw new Error(`gen-cordis-inspect-catalog: expected one Client Event ${JSON.stringify(name)}, found ${matches.length}`) + } + return matches[0] as (typeof matches)[number] + }) return { - services: model.services.flatMap((service) => { - const allowed = CLIENT_SERVICES[service.key] - if (allowed === undefined) return [] - const names = new Set(allowed) - return [{ ...service, methods: service.methods.filter(method => names.has(methodName(method) ?? '')) }] - }), - events: model.events.filter(event => CLIENT_EVENTS.has(event.name)), + services, + events, } } @@ -48,7 +69,17 @@ function main(): void { const { projector, model } = projectCordisCatalog(root, CORDIS_CATALOG_POLICY, 'client') const destination = resolve(root, CLIENT_OUT) const source = projector.renderRuntimeApi(clientModel(model)) + .replace('Generated by scripts/gen-cordis-api.ts', 'Generated by scripts/gen-cordis-inspect-catalog.ts') + .replaceAll('pnpm run gen-cordis-api', 'pnpm run gen-cordis-inspect-catalog') + .replaceAll('pnpm run verify-cordis-api', 'pnpm run verify-cordis-inspect-catalog') .replaceAll('@deepseek-ai/dsh-tool-cordis/api-catalog', '@deepseek-ai/dsh-cordis-client-runner/client/api-catalog') + if (process.argv.includes('--check')) { + if (!existsSync(destination) || readFileSync(destination, 'utf8') !== source) { + throw new Error(`gen-cordis-inspect-catalog: ${CLIENT_OUT} is stale; run pnpm run gen-cordis-inspect-catalog`) + } + console.log(`gen-cordis-inspect-catalog: ${CLIENT_OUT} is up to date`) + return + } mkdirSync(dirname(destination), { recursive: true }) writeFileSync(destination, source) console.log(`gen-cordis-inspect-catalog: wrote ${CLIENT_OUT}`) diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index 8b5fd2a879..e24e662153 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -665,6 +665,7 @@ function docSyncLeafGates(options: { pnpmScript('markdown-links', 'verify-md-links', { label: 'markdown links' }), pnpmScript('type-equivalence', 'verify-type-equiv', { label: 'type equivalence' }), pnpmScript('cordis-catalog', 'verify-cordis-catalog', { label: 'cordis catalog' }), + pnpmScript('cordis-inspect-catalog', 'verify-cordis-inspect-catalog', { label: 'Cordis inspect catalog' }), pnpmScript('mermaid', 'verify-mermaid'), pnpmScript('scoped-events', 'verify-scoped-events', { label: 'scoped events' }), pnpmScript('translation-pairing', 'verify-translation-pairing', { label: 'translation pairing' }),