Bladeren bron

fix(client): gate the inspect catalog

imccyu 1 maand geleden
bovenliggende
commit
a050b3d4f1

+ 1 - 0
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",

+ 141 - 205
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<RpcResult<{ items: SessionSearchResultItem[]; hasMore: boolean }>>',
+        signature: 'search( query: string, signal: AbortSignal, ): Promise<ClientResult<{ items: SessionSearchResultItem[]; hasMore: boolean }>>',
         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<SessionId>',
-        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<WorkspaceView>',
-        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<void>',
+        description: 'Archive a Session and clear it when it is the current selection.',
+        parameters: [{ name: 'sessionId', description: 'Session to archive.' }],
       },
       {
         signature: 'pickDirectory(): Promise<string | null>',
-        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<DirectoryListing>',
-        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<string>',
-        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<void>',
-        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<WorkspaceView>',
+        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<WorkspaceView>',
         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<void>',
-        description: 'Delete a Workspace (its sessions fall back to the unaccounted group).',
-        parameters: [{ name: 'workspaceId', description: 'target workspace.' }],
+        description: 'Delete a Workspace registration without deleting Sessions or files.',
+        parameters: [{ name: 'workspaceId', description: 'target Workspace.' }],
       },
       {
-        signature: 'insertSessionBefore(workspaceId: WorkspaceId, sessionId: SessionId, beforeSessionId?: SessionId): Promise<WorkspaceView>',
-        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.',
+        signature: 'archiveSession(sessionId: SessionId): Promise<void>',
+        description: 'Archive a Session from Workspace grouping surfaces.',
+        parameters: [{ name: 'sessionId', description: 'Session to archive.' }],
       },
       {
-        signature: 'archiveSession(sessionId: SessionId): Promise<void>',
-        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.' }],
+        signature: 'insertSessionBefore( workspaceId: WorkspaceId, sessionId: SessionId, beforeSessionId?: SessionId, ): Promise<WorkspaceView>',
+        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<Context, \'remote\'> & {\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<Context, \'remote\'> & {\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<SlotMap[P]>;\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<ConnectionRpcResult<unknown>>;\n    readonly open?: (channel: string, endpoint: string, payload: unknown, signal: AbortSignal) => AsyncIterable<unknown>;\n}',
   },
   {
-    name: 'CommonKeyOf',
-    declaration: 'export type CommonKeyOf = LocaleNamespaceMap extends {\n    common: infer C;\n} ? C & string : never;',
+    name: 'ClientRemote',
+    declaration: 'export interface ClientRemote extends TypertClientRemote {\n    $stream<Item>(options: RemoteStreamOptions<Item>): RemoteStream<Item>;\n}',
   },
   {
-    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: 'CommonKeyOf',
+    declaration: 'export type CommonKeyOf = LocaleNamespaceMap extends {\n    common: infer C;\n} ? C & string : never;',
   },
   {
     name: 'ComposedProps',
     declaration: 'export type ComposedProps<K extends keyof SlotMap & string, EntryKey extends EntryKeyOf<K>, S extends keyof SlotMap & string, H, I extends object, M = never, N = undefined> = PropsRuntime<K, EntryKey> & PropsRenderSlots<S> & PropsStore<H> & InjectFace<I> & MatchedShare<SlotMap[K], M> & PropsLocale<N>;',
   },
   {
-    name: 'ComposerPhase',
-    declaration: 'export type ComposerPhase = \'blank\' | \'engaging\' | \'active\';',
-  },
-  {
-    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: 'ContextProvenanceView',
-    declaration: 'export interface ContextProvenanceView {\n    role: ContextRole;\n    label: string | null;\n}',
-  },
-  {
-    name: 'ContextRole',
-    declaration: 'export type ContextRole = \'inject\' | \'recall\';',
-  },
-  {
-    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: 'ConversationLocationDataStore',
-    declaration: 'export interface ConversationLocationDataStore<DataMap extends object> {\n    get<Key extends keyof DataMap & string>(key: Key): Readonly<DataMap[Key]> | undefined;\n}',
+    name: 'ConnectionConfig',
+    declaration: 'export interface ConnectionConfig {\n    backoffBaseMs?: number;\n    backoffFactor?: number;\n    backoffMaxMs?: number;\n    generationReadyTimeoutMs?: number;\n}',
   },
   {
-    name: 'ConversationNode',
-    declaration: 'export type ConversationNode = UserMessageNode | AssistantMessageNode | SteeringMessageNode | ContextMessageNode | ModelRetryNode | TurnErrorNode | TurnMaxTokensNode | ToolResultNode | CommandNode | CompactionSummaryNode | UnknownSurfaceNode;',
+    name: 'ConnectionGenerationSource',
+    declaration: 'export type ConnectionGenerationSource = (signal: AbortSignal, ready: () => void) => Promise<void>;',
   },
   {
-    name: 'ConversationSnapshot',
-    declaration: 'export interface ConversationSnapshot {\n    sessionId: SessionId;\n    views: ConversationViewSnapshotStore;\n    chat: ChatSnapshot;\n    nodes: readonly ConversationNode[];\n    turnTimings: ReadonlyMap<number, {\n        readonly startTime: number;\n        readonly endTime?: number;\n    }>;\n    turnEnds: ReadonlyMap<number, number>;\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: '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: 'ConversationStepDataMap',
-    declaration: 'export interface ConversationStepDataMap {\n}',
+    name: 'ConnectionRpcFailure',
+    declaration: 'export interface ConnectionRpcFailure {\n    readonly code: string;\n    readonly message: string;\n    readonly details: object;\n}',
   },
   {
-    name: 'ConversationTimelineSnapshot',
-    declaration: 'export interface ConversationTimelineSnapshot {\n    readonly turnOrder: readonly number[];\n    readonly turns: ReadonlyMap<number, TurnLocation>;\n}',
+    name: 'ConnectionRpcResult',
+    declaration: 'export type ConnectionRpcResult<T> = {\n    readonly ok: true;\n    readonly value: T;\n} | {\n    readonly ok: false;\n    readonly error: ConnectionRpcFailure;\n};',
   },
   {
-    name: 'ConversationTurnDataMap',
-    declaration: 'export interface ConversationTurnDataMap {\n}',
+    name: 'ConnectionSinks',
+    declaration: 'export interface ConnectionSinks {\n    onConnected?: (description: HostDescription) => void;\n    onStateChange?: (state: ConnectionState) => void;\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 extends Extract<keyof ConversationViewSnapshotMap, string>>(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<string, HostObservable<unknown>>;',
   },
+  {
+    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<T> {\n    getSnapshot(): T;\n    subscribe(fn: () => void): () => void;\n}',
+    declaration: 'export type HostObservable<T> = ObservableSnapshot<T>;',
   },
   {
     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<RpcResult<{\n        accepted: true;\n    }>>;\n    readAttachment(attachmentId: AttachmentIdType): Promise<RpcResult<{\n        attachment: ImageAttachmentRef;\n        data: Uint8Array;\n    }>>;\n    updateQueue(itemId: MessageId, action: QueueAction): Promise<RpcResult<{\n        accepted: true;\n    }>>;\n    cancel(): Promise<RpcResult<{\n        accepted: true;\n    }>>;\n    rename(title: string): Promise<RpcResult<{\n        title: string;\n        seq: number;\n    }>>;\n    loadOlder(): Promise<void>;\n    command(line: string): Promise<RemoteResult<{\n        matched: boolean;\n    }>>;\n}',
+    declaration: 'export interface ISession {\n    readonly sessionId: SessionId;\n    readonly projections: ProjectionsFace;\n    prompt(content: PromptContentPart[], mode: \'queue\' | \'steer\', signal?: AbortSignal): Promise<ClientResult<{\n        accepted: true;\n    }>>;\n    readAttachment(attachmentId: AttachmentIdType): Promise<ClientResult<{\n        attachment: ImageAttachmentRef;\n        data: Uint8Array;\n    }>>;\n    updateQueue(itemId: MessageId, action: QueueAction): Promise<ClientResult<{\n        accepted: true;\n    }>>;\n    cancel(): Promise<ClientResult<{\n        accepted: true;\n    }>>;\n    rename(title: string): Promise<ClientResult<{\n        title: string;\n        seq: number;\n    }>>;\n    loadOlder(): Promise<void>;\n    command(line: string): Promise<RemoteResult<{\n        matched: boolean;\n    }>>;\n}',
   },
   {
     name: 'KeyPropsOf',
     declaration: 'export type KeyPropsOf<K extends keyof SlotMap & string, EntryKey extends EntryKeyOf<K>> = 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<number, {\n        readonly startTime: number;\n        readonly endTime?: number;\n    }>;\n    readonly turnEnds: ReadonlyMap<number, number>;\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 extends SlotEntryDef, M> = 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<T> {\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<K extends keyof SlotMap & string> = 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<K>;\n}[PendingKind];',
-  },
-  {
-    name: 'PendingKind',
-    declaration: 'export type PendingKind = keyof PendingPayloads;',
-  },
-  {
-    name: 'PendingPayloads',
-    declaration: 'export interface PendingPayloads {\n    approval: Omit<Extract<MuxFrame, {\n        type: \'approval/requested\';\n    }>, \'type\' | \'sessionId\'>;\n    question: Omit<Extract<MuxFrame, {\n        type: \'question/requested\';\n    }>, \'type\' | \'sessionId\'>;\n}',
-  },
-  {
-    name: 'PendingWait',
-    declaration: 'export class PendingWait<K extends PendingKind = PendingKind> {\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<RpcReceipt>);\n    respond(result: ClientResponse[\'result\']): Promise<RpcReceipt>;\n    markSettled(): void;\n}',
-  },
   {
     name: 'ProjectionsFace',
     declaration: 'export interface ProjectionsFace {\n    faceOf(key: string): ObservableSnapshot<unknown>;\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> = H extends StoreHandle<infer T, infer A> ? {\n    useStore: SnapshotSelectorHook<T>;\n    actions: BakedActions<T, A>;\n} : object;',
   },
   {
-    name: 'QueueAction',
-    declaration: 'export type QueueAction = Parameters<SessionFace[\'updateQueue\']>[1];',
+    name: 'RemoteStream',
+    declaration: 'export class RemoteStream<Item> implements AsyncIterable<RemoteStreamItem<Item>> {\n    constructor(private readonly connection: Pick<ConnectionHandle, \'hostDescription\'>, private readonly options: RemoteStreamOptions<Item>);\n    get signal(): AbortSignal;\n    restart(): void;\n    dispose(): Promise<void>;\n    [Symbol.asyncIterator](): AsyncIterator<RemoteStreamItem<Item>>;\n}',
+  },
+  {
+    name: 'RemoteStreamCarrierError',
+    declaration: 'export class RemoteStreamCarrierError extends Error {\n    constructor(message: string, options?: ErrorOptions);\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: 'RemoteStreamItem',
+    declaration: 'export interface RemoteStreamItem<Item> {\n    readonly generation: number;\n    readonly value: Item;\n    readonly signal: AbortSignal;\n    accept(): void;\n}',
+  },
+  {
+    name: 'RemoteStreamOptions',
+    declaration: 'export interface RemoteStreamOptions<Item> {\n    readonly name: string;\n    readonly open: (signal: AbortSignal) => AsyncIterable<Item>;\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<SessionEventWindow>;',
+  },
+  {
+    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<ConversationSnapshot>;',
+    declaration: 'export type SessionFace = ISession & ObservableSnapshot<SessionSnapshot>;',
   },
   {
     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<GenericCallView, \'rawInput\'> & {\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<P> = (props: P) => ReactNode;',
@@ -773,14 +741,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [
     name: 'SnapshotSelectorHook',
     declaration: 'export type SnapshotSelectorHook<T> = <S>(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<ConversationStepDataMap>;\n}',
-  },
   {
     name: 'StoreDecl',
     declaration: 'export type StoreDecl = StoreHandle<any, any> | StoreFactory;',
@@ -829,14 +789,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [
     name: 'ThemeTokens',
     declaration: 'export type ThemeTokens = Record<string, string>;',
   },
-  {
-    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<K extends string = string> = (key: K, params?: Record<string, unknown>) => string;',
@@ -846,24 +798,8 @@ export const TYPE_API: readonly TypeApiEntry[] = [
     declaration: 'export type TranslateNS<N extends keyof LocaleNamespaceMap & string> = Translate<LocaleKeysOf<N>>;',
   },
   {
-    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<ConversationTurnDataMap>;\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)

+ 1 - 1
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',

+ 43 - 0
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<WorkspaceView>',
+        'rename(workspaceId: WorkspaceId, title: string): Promise<WorkspaceView>',
+        'delete(workspaceId: WorkspaceId): Promise<void>',
+        'archiveSession(sessionId: SessionId): Promise<void>',
+        'insertSessionBefore( workspaceId: WorkspaceId, sessionId: SessionId, beforeSessionId?: SessionId, ): Promise<WorkspaceView>',
+      ])
+    expect(SERVICE_API.find(service => service.key === 'uiWorkspace')?.methods.map(method => method.signature))
+      .toEqual([
+        'connectWorkspace(workspaceId: WorkspaceId): Promise<SessionId>',
+        'startSession(workspaceId?: WorkspaceId): void',
+        'archiveSession(sessionId: SessionId): Promise<void>',
+        'pickDirectory(): Promise<string | null>',
+        'listDirectory(path?: string, signal?: AbortSignal): Promise<DirectoryListing>',
+        'createDirectory(path: string, name: string): Promise<string>',
+        'openPath(path: string): Promise<void>',
+      ])
+  })
+
+  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',
+    ]))
+  })
+})

+ 1 - 1
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)

+ 1 - 1
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)',

+ 42 - 11
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<Record<string, readonly string[]>> = {
   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}`)

+ 1 - 0
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' }),