fix: test/docs

This commit is contained in:
imccyu
2026-08-13 00:19:15 +08:00
parent ffcc8e299b
commit 978e573605
33 changed files with 1307 additions and 136 deletions

View File

@@ -16,7 +16,7 @@ This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verifie
- `ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin` — Low-level service-store access and binding. ([`vendor/cordis/src/reflect.ts:7`](../../vendor/cordis/src/reflect.ts))
- `ctx.extend / ctx.isolate / ctx.intercept` — Derive a child context (scoped services / isolation / interception). ([`vendor/cordis/src/context.ts:42`](../../vendor/cordis/src/context.ts))
- `ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger` — Ambient handles onto the running context graph. ([`vendor/cordis/src/context.ts:16`](../../vendor/cordis/src/context.ts))
- `ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)` — Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick). ([`vendor/timer/src/index.ts:4`](../../vendor/timer/src/index.ts))
- `ctx.timer (+ interval / timeout / throttle / debounce)` — Disposable timer helpers. The `timer` key is provided at runtime; the four supported helpers are mixed onto ctx directly (declared via Pick). ([`vendor/timer/src/index.ts:4`](../../vendor/timer/src/index.ts))
- `ctx.loader` — The config Loader that booted the app (present under the loader). ([`vendor/loader/src/index.ts:30`](../../vendor/loader/src/index.ts))
- `ctx.hmr` — The hot-module-reload watcher (present under the hmr plugin). ([`vendor/hmr/src/index.ts:15`](../../vendor/hmr/src/index.ts))

View File

@@ -163,3 +163,40 @@ interface LspService {
```
`LspProviderId` is the seam's branded id (`Branded<'LspProviderId'>` from [dsh-brand](../../packages/util/brand)); `LspError` extends `HarnessError` with stable codes such as `LSP_INVALID_PROVIDER`, `LSP_CONFLICT`, `LSP_UNAVAILABLE`, `LSP_DISPOSED`, `LSP_UNSUPPORTED_OPERATION`, and `LSP_MALFORMED_RESPONSE`, which callers route on instead of parsing `message`.
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
<a id="cordis-surface"></a>
## Cordis API
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxlsp--lspservice"></a>
### `ctx.lsp` — `LspService`
The LSP capability seam (`ctx.lsp`). Owns provider registration/selection and normalized query execution; exposes exactly the four operations and no protocol escape hatch.
```ts cordis-catalog
/**
* Register a provider, atomically reserving its id and every normalized extension. Any conflict
* or invalid input publishes nothing and throws `LspError`; the returned disposer releases all
* reservations. Disposed with the calling fiber.
* @param provider - the backend to register.
* @returns a synchronous disposer releasing the id and all extension reservations.
*/
registerProvider(provider: LspProvider): () => void
/**
* Select a provider by the file's extension and run one query. Selection is per-query and
* order-independent; no match throws `LspError` `LSP_UNAVAILABLE`.
* @param request - the normalized query.
* @param signal - optional cancellation forwarded to the selected provider.
* @returns the normalized, closed-union result.
*/
query(request: LspQueryRequest, signal?: AbortSignal): Promise<LspQueryResult>
```
Source: [`packages/lsp/lsp/src/types.ts:113`](../../packages/lsp/lsp/src/types.ts)
<!-- END GENERATED cordis-surface -->

View File

@@ -163,3 +163,40 @@ interface LspService {
```
`LspProviderId` 是该 seam 的品牌化 id来自 [dsh-brand](../../packages/util/brand) 的 `Branded<'LspProviderId'>``LspError` 扩展 `HarnessError`,提供 `LSP_INVALID_PROVIDER`、`LSP_CONFLICT`、`LSP_UNAVAILABLE`、`LSP_DISPOSED`、`LSP_UNSUPPORTED_OPERATION` 和 `LSP_MALFORMED_RESPONSE` 等稳定错误码,调用方应按错误码路由,而不是解析 `message`。
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
<a id="cordis-surface"></a>
## Cordis API
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxlsp--lspservice"></a>
### `ctx.lsp` — `LspService`
The LSP capability seam (`ctx.lsp`). Owns provider registration/selection and normalized query execution; exposes exactly the four operations and no protocol escape hatch.
```ts cordis-catalog
/**
* Register a provider, atomically reserving its id and every normalized extension. Any conflict
* or invalid input publishes nothing and throws `LspError`; the returned disposer releases all
* reservations. Disposed with the calling fiber.
* @param provider - the backend to register.
* @returns a synchronous disposer releasing the id and all extension reservations.
*/
registerProvider(provider: LspProvider): () => void
/**
* Select a provider by the file's extension and run one query. Selection is per-query and
* order-independent; no match throws `LspError` `LSP_UNAVAILABLE`.
* @param request - the normalized query.
* @param signal - optional cancellation forwarded to the selected provider.
* @returns the normalized, closed-union result.
*/
query(request: LspQueryRequest, signal?: AbortSignal): Promise<LspQueryResult>
```
Source: [`packages/lsp/lsp/src/types.ts:113`](../../packages/lsp/lsp/src/types.ts)
<!-- END GENERATED cordis-surface -->

View File

@@ -0,0 +1,364 @@
# Runtime self-modification
English | [中文](self-modification.zh.md)
The self-modification subsystem lets an agent define versioned Cordis packages, run their host and browser halves, and query approved runtime metadata before writing code. Package lifecycle and sandbox behavior belong to the [`packages/self-modification`](../../packages/self-modification/README.md) package group.
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
<a id="cordis-surface"></a>
## Cordis API
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxcordisinspect--cordisinspectregistryservice"></a>
### `ctx.cordisInspect` — `CordisInspectRegistryService`
Registry and cross-page router behind the two model-facing inspect tools.
```ts cordis-catalog
/**
* Register one Host provider.
* @param registration - manifest and local query handler.
* @returns idempotent disposer.
*/
register(registration: HostCordisInspectProviderRegistration): () => void
/**
* Replace the mirrored Client provider directory.
* @param providers - complete Client manifest snapshot.
*/
syncClientManifest(providers: readonly CordisInspectProviderManifest[]): void
/**
* Return the complete known Host and Client provider directory.
* @returns Host providers followed by the Client providers.
*/
list(): CordisInspectProviderView[]
/**
* Execute one provider query on its owning platform.
* @param platform - Host or Client runtime.
* @param providerId - provider selected from {@link list}.
* @param methodName - declared method name.
* @param input - optional lossless JSON input.
* @param agent - requesting Agent and scope.
* @param signal - tool-call cancellation.
* @returns provider JSON data.
*/
async query( platform: CordisInspectPlatform, providerId: string, methodName: string, input: JsonValue | undefined, agent: Agent, signal: AbortSignal, ): Promise<JsonValue>
/**
* Accept the first valid Client response for a pending query.
* @param agent - Agent whose Session owns the query.
* @param requestId - Pending Client query identity.
* @param resolution - Client provider result or failure.
* @returns whether this response settled the still-pending query.
*/
resolveClientQuery( agent: Agent, requestId: CordisInspectRequestId, resolution: CordisInspectQueryResolution, ): CordisInspectResolveAck
```
Types: [Agent](core.md)
Source: [`packages/self-modification/cordis-host-runner/src/inspect-registry.ts:46`](../../packages/self-modification/cordis-host-runner/src/inspect-registry.ts)
<a id="ctxdynamiccordisrunner--dynamiccordisrunnerservice"></a>
### `ctx.dynamicCordisRunner` — `DynamicCordisRunnerService`
Dynamic Plugin registry and Host-half lifecycle.
```ts cordis-catalog
/**
* Define a new Plugin's first Package or append a Package to an existing Plugin.
* @param request - Session ownership, Plugin selection, metadata, and source code.
* @returns Host-minted Plugin and Package identities with declared-half metadata.
*/
define(request: DynamicCordisDefineRequest): DynamicCordisDefineReceipt
/**
* Remove a Plugin, its active run, and all immutable Packages.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to remove.
* @returns Whether removal succeeded and whether it stopped an active run.
*/
async undefine(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisUndefineReceipt>
/**
* Remove a Plugin from the user panel and queue the resulting state change for the model's next step.
* @param agent - Agent whose Session owns the Plugin and receives the context.
* @param pluginId - Stable Plugin identity to remove.
* @returns Whether removal succeeded and whether it stopped an active run.
*/
@Remote('undefineFromPanel') async undefineFromPanel(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisUndefineReceipt>
/**
* Start or update one Package for a model tool call. An unauthorized Client
* Package waits for approval; Plugin-wide authorization covers later versions.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to activate.
* @param packageId - Immutable Package version to activate.
* @param mode - Whether to run the current version or switch versions.
* @param signal - Tool-call cancellation signal while the activation request is being created.
* @returns The successful activation identity or an actionable refusal.
*/
async run( agent: Agent, pluginId: CordisDynamicPluginId, packageId: CordisDynamicPackageId, mode: CordisDynamicRunMode, signal?: AbortSignal, ): Promise<DynamicCordisRunResponse>
/**
* Start Host code for an approved request or a direct panel gesture.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to activate.
* @param packageId - Immutable Package version to activate.
* @param mode - Whether to run the current version or switch versions.
* @param requestId - Model-driven request identity, or null for a direct user gesture.
* @param approveFutureVersions - Whether this approval covers later Packages of the same Plugin.
* @returns The exact Host activation or a failure message.
*/
@Remote('runHostHalf') async runHostHalf( agent: Agent, pluginId: CordisDynamicPluginId, packageId: CordisDynamicPackageId, mode: CordisDynamicRunMode, requestId: ApprovalRequestId | null, approveFutureVersions: boolean, ): Promise<DynamicCordisHostHalfResult>
/**
* Fetch Client code for the exact active run.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to read.
* @param pluginRunId - Exact active run authorized to receive source.
* @returns Client source and its Plugin, Package, and run identities.
*/
@Remote('getClientCode') getClientCode( agent: Agent, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, ): DynamicCordisClientSource
/**
* Resolve one model-driven Client activation request.
* @param requestId - Request identity to settle once.
* @param resolution - Browser refusal or exact Client activation result.
* @returns Whether the still-pending request accepted this resolution.
*/
@Remote('resolveRequestRun') async resolveRequestRun( requestId: ApprovalRequestId, resolution: DynamicCordisRunResolution, ): Promise<DynamicCordisResolveAck>
/**
* Settle a direct panel run after this page loaded or failed its Client half.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity being settled.
* @param resolution - Exact Client activation result from the acting page.
* @returns The committed activation or its failure.
*/
@Remote('settleUserRun') async settleUserRun( agent: Agent, pluginId: CordisDynamicPluginId, resolution: DynamicCordisRunResolution, ): Promise<DynamicCordisRunResponse>
/**
* Stop the active run while retaining every Package version.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to stop.
* @returns Success or the reason no run was stopped.
*/
async stop(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisStopResponse>
/**
* Stop a Plugin from the user panel and queue the resulting state change for the model's next step.
* @param agent - Agent whose Session owns the Plugin and receives the context.
* @param pluginId - Stable Plugin identity to stop.
* @returns Success or the reason no run was stopped.
*/
@Remote('stopFromPanel') async stopFromPanel(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisStopResponse>
/**
* Replace the Host mirror of the Client inspect provider directory.
* @param providers - complete Client provider manifest.
* @returns null after accepting the manifest.
*/
@Remote('syncInspectManifest') syncInspectManifest(providers: readonly CordisInspectProviderManifest[]): null
/**
* Claim one pending Client inspect query with its live result.
* @param agent - Session that owns the query.
* @param requestId - exact pending query identity.
* @param resolution - provider result or structured refusal.
* @returns whether this answer won the query.
*/
@Remote('resolveInspectQuery') resolveInspectQuery( agent: Agent, requestId: CordisInspectRequestId, resolution: CordisInspectQueryResolution, ): CordisInspectResolveAck
/**
* Frame-wide inventory, grouped as one row per stable Plugin.
* @returns Source-free metadata for every process-local Plugin.
*/
@Remote('inventory') inventory(): DynamicCordisInventoryRow[]
/**
* Read one Session's Host-rich state for inspection and result rendering.
* @param agent - Agent whose Session selects visible Plugins.
* @returns Plugin versions, active runs, Host fibers, and render failures.
*/
snapshot(agent: Agent): DynamicCordisSnapshotRow[]
/**
* Read source-free context for an explicit `@pluginId` user gesture.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity referenced by the user.
* @returns The preferred modification base, or undefined when unavailable.
*/
reference(agent: Agent, pluginId: CordisDynamicPluginId): DynamicCordisReference | undefined
/**
* List source-free Plugin summaries owned by one Session.
* @param agent - Agent whose Session selects visible Plugins.
* @returns one summary per Plugin in creation order.
*/
listPlugins(agent: Agent): DynamicCordisPluginInspection[]
/**
* Inspect one Plugin without returning Package source.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - stable Plugin identity.
* @returns version pointers, latest run, and all Package summaries.
*/
inspectPlugin(agent: Agent, pluginId: CordisDynamicPluginId): DynamicCordisPluginInspection
/**
* Read one exact immutable Package and its Host and Client source.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity that owns the Package.
* @param packageId - Exact immutable Package identity to inspect.
* @returns Package metadata, source, and the Plugin's lifecycle pointers.
*/
inspectPackage( agent: Agent, pluginId: CordisDynamicPluginId, packageId: CordisDynamicPackageId, ): DynamicCordisPackageInspection
/**
* Record a post-load render failure for the exact active run.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity that rendered.
* @param pluginRunId - Exact active run that produced the failure.
* @param failure - Slot, message, and entry-retirement result.
* @returns Null after recording or ignoring a stale report.
*/
@Remote('reportRenderFailure') async reportRenderFailure( agent: Agent, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, failure: DynamicCordisRenderFailure, ): Promise<null>
/**
* Report a Client guard rejection that happened after the Package completed activation.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity whose Client code was rejected.
* @param pluginRunId - Exact active run that produced the rejection.
* @param failure - Original guard message and stack.
* @returns Null after reporting or ignoring a stale/startup failure.
*/
@Remote('reportClientGuardFailure') async reportClientGuardFailure( agent: Agent, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, failure: CordisErrorDetails, ): Promise<null>
/**
* Invoke an active Host method while rejecting stale Client runs.
* @param pluginId - Stable Plugin identity that owns the method.
* @param pluginRunId - Exact active run authorizing the call.
* @param method - Registered Host handler name.
* @param args - JSON argument delivered to the handler.
* @returns The JSON result or a typed invocation failure.
*/
@Remote('invoke') async invoke( pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, method: string, args: JsonValue, ): Promise<DynamicCordisInvokeResult>
```
Types: [Agent](core.md)
Source: [`packages/self-modification/cordis-host-runner/src/index.ts:124`](../../packages/self-modification/cordis-host-runner/src/index.ts)
<a id="cordis-events"></a>
### `cordis/*` events
<a id="cordisdynamic-package--emit"></a>
#### `cordis/dynamic-package` — emit
One exact Plugin/Package activation is now live in the Host.
```ts cordis-catalog
/**
* One exact Plugin/Package activation is now live in the Host.
* @param pkg - stable plugin, immutable package, run identity, and label.
* @mode emit
*/
'cordis/dynamic-package'(pkg: DynamicCordisPackage): void
```
Source: [`packages/self-modification/cordis-host-runner/src/types.ts:379`](../../packages/self-modification/cordis-host-runner/src/types.ts)
<a id="cordisdynamic-retract--emit"></a>
#### `cordis/dynamic-retract` — emit
One exact activation was withdrawn.
```ts cordis-catalog
/**
* One exact activation was withdrawn.
* @param retracted - plugin, package, and run identity.
* @mode emit
*/
'cordis/dynamic-retract'(retracted: DynamicCordisRetracted): void
```
Source: [`packages/self-modification/cordis-host-runner/src/types.ts:385`](../../packages/self-modification/cordis-host-runner/src/types.ts)
<a id="cordisinspect-query--emit"></a>
#### `cordis/inspect-query` — emit
Request a live read-only query from the Client inspect registry.
```ts cordis-catalog
/**
* Request a live read-only query from the Client inspect registry.
* @param request - correlation, Session, provider, method, and JSON input.
* @mode emit
*/
'cordis/inspect-query'(request: CordisInspectQueryRequest): void
```
Source: [`packages/self-modification/cordis-host-runner/src/types.ts:391`](../../packages/self-modification/cordis-host-runner/src/types.ts)
<a id="cordisinspect-query-resolved--emit"></a>
#### `cordis/inspect-query-resolved` — emit
Notify every Client that an inspect query has settled or been cancelled.
```ts cordis-catalog
/**
* Notify every Client that an inspect query has settled or been cancelled.
* @param resolved - exact query identity that is no longer answerable.
* @mode emit
*/
'cordis/inspect-query-resolved'(resolved: CordisInspectQueryResolved): void
```
Source: [`packages/self-modification/cordis-host-runner/src/types.ts:397`](../../packages/self-modification/cordis-host-runner/src/types.ts)
<a id="cordisrequest-run--emit"></a>
#### `cordis/request-run` — emit
A Client-bearing activation needs a browser page, and may require a user decision.
```ts cordis-catalog
/**
* A Client-bearing activation needs a browser page, and may require a user decision.
* @param request - correlation identity, owner, target version, mode, and approval requirement.
* @mode emit
*/
'cordis/request-run'(request: DynamicCordisRunRequest): void
```
Source: [`packages/self-modification/cordis-host-runner/src/types.ts:367`](../../packages/self-modification/cordis-host-runner/src/types.ts)
<a id="cordisrequest-run-resolved--emit"></a>
#### `cordis/request-run-resolved` — emit
A pending Client activation request left the answerable state.
```ts cordis-catalog
/**
* A pending Client activation request left the answerable state.
* @param resolved - request identity and outcome.
* @mode emit
*/
'cordis/request-run-resolved'(resolved: DynamicCordisRequestResolved): void
```
Source: [`packages/self-modification/cordis-host-runner/src/types.ts:373`](../../packages/self-modification/cordis-host-runner/src/types.ts)
<!-- END GENERATED cordis-surface -->

View File

@@ -0,0 +1,364 @@
# 运行时自修改
[English](self-modification.md) | 中文
self-modification 子系统允许 agent智能体定义带版本的 Cordis 包、运行其 host 与浏览器两半,并在编写代码前查询获准公开的运行时元数据。包生命周期与沙箱行为由 [`packages/self-modification`](../../packages/self-modification/README.md) 包组说明。
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
<a id="cordis-surface"></a>
## Cordis API
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxcordisinspect--cordisinspectregistryservice"></a>
### `ctx.cordisInspect` — `CordisInspectRegistryService`
Registry and cross-page router behind the two model-facing inspect tools.
```ts cordis-catalog
/**
* Register one Host provider.
* @param registration - manifest and local query handler.
* @returns idempotent disposer.
*/
register(registration: HostCordisInspectProviderRegistration): () => void
/**
* Replace the mirrored Client provider directory.
* @param providers - complete Client manifest snapshot.
*/
syncClientManifest(providers: readonly CordisInspectProviderManifest[]): void
/**
* Return the complete known Host and Client provider directory.
* @returns Host providers followed by the Client providers.
*/
list(): CordisInspectProviderView[]
/**
* Execute one provider query on its owning platform.
* @param platform - Host or Client runtime.
* @param providerId - provider selected from {@link list}.
* @param methodName - declared method name.
* @param input - optional lossless JSON input.
* @param agent - requesting Agent and scope.
* @param signal - tool-call cancellation.
* @returns provider JSON data.
*/
async query( platform: CordisInspectPlatform, providerId: string, methodName: string, input: JsonValue | undefined, agent: Agent, signal: AbortSignal, ): Promise<JsonValue>
/**
* Accept the first valid Client response for a pending query.
* @param agent - Agent whose Session owns the query.
* @param requestId - Pending Client query identity.
* @param resolution - Client provider result or failure.
* @returns whether this response settled the still-pending query.
*/
resolveClientQuery( agent: Agent, requestId: CordisInspectRequestId, resolution: CordisInspectQueryResolution, ): CordisInspectResolveAck
```
Types: [Agent](core.md)
Source: [`packages/self-modification/cordis-host-runner/src/inspect-registry.ts:46`](../../packages/self-modification/cordis-host-runner/src/inspect-registry.ts)
<a id="ctxdynamiccordisrunner--dynamiccordisrunnerservice"></a>
### `ctx.dynamicCordisRunner` — `DynamicCordisRunnerService`
Dynamic Plugin registry and Host-half lifecycle.
```ts cordis-catalog
/**
* Define a new Plugin's first Package or append a Package to an existing Plugin.
* @param request - Session ownership, Plugin selection, metadata, and source code.
* @returns Host-minted Plugin and Package identities with declared-half metadata.
*/
define(request: DynamicCordisDefineRequest): DynamicCordisDefineReceipt
/**
* Remove a Plugin, its active run, and all immutable Packages.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to remove.
* @returns Whether removal succeeded and whether it stopped an active run.
*/
async undefine(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisUndefineReceipt>
/**
* Remove a Plugin from the user panel and queue the resulting state change for the model's next step.
* @param agent - Agent whose Session owns the Plugin and receives the context.
* @param pluginId - Stable Plugin identity to remove.
* @returns Whether removal succeeded and whether it stopped an active run.
*/
@Remote('undefineFromPanel') async undefineFromPanel(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisUndefineReceipt>
/**
* Start or update one Package for a model tool call. An unauthorized Client
* Package waits for approval; Plugin-wide authorization covers later versions.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to activate.
* @param packageId - Immutable Package version to activate.
* @param mode - Whether to run the current version or switch versions.
* @param signal - Tool-call cancellation signal while the activation request is being created.
* @returns The successful activation identity or an actionable refusal.
*/
async run( agent: Agent, pluginId: CordisDynamicPluginId, packageId: CordisDynamicPackageId, mode: CordisDynamicRunMode, signal?: AbortSignal, ): Promise<DynamicCordisRunResponse>
/**
* Start Host code for an approved request or a direct panel gesture.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to activate.
* @param packageId - Immutable Package version to activate.
* @param mode - Whether to run the current version or switch versions.
* @param requestId - Model-driven request identity, or null for a direct user gesture.
* @param approveFutureVersions - Whether this approval covers later Packages of the same Plugin.
* @returns The exact Host activation or a failure message.
*/
@Remote('runHostHalf') async runHostHalf( agent: Agent, pluginId: CordisDynamicPluginId, packageId: CordisDynamicPackageId, mode: CordisDynamicRunMode, requestId: ApprovalRequestId | null, approveFutureVersions: boolean, ): Promise<DynamicCordisHostHalfResult>
/**
* Fetch Client code for the exact active run.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to read.
* @param pluginRunId - Exact active run authorized to receive source.
* @returns Client source and its Plugin, Package, and run identities.
*/
@Remote('getClientCode') getClientCode( agent: Agent, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, ): DynamicCordisClientSource
/**
* Resolve one model-driven Client activation request.
* @param requestId - Request identity to settle once.
* @param resolution - Browser refusal or exact Client activation result.
* @returns Whether the still-pending request accepted this resolution.
*/
@Remote('resolveRequestRun') async resolveRequestRun( requestId: ApprovalRequestId, resolution: DynamicCordisRunResolution, ): Promise<DynamicCordisResolveAck>
/**
* Settle a direct panel run after this page loaded or failed its Client half.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity being settled.
* @param resolution - Exact Client activation result from the acting page.
* @returns The committed activation or its failure.
*/
@Remote('settleUserRun') async settleUserRun( agent: Agent, pluginId: CordisDynamicPluginId, resolution: DynamicCordisRunResolution, ): Promise<DynamicCordisRunResponse>
/**
* Stop the active run while retaining every Package version.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to stop.
* @returns Success or the reason no run was stopped.
*/
async stop(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisStopResponse>
/**
* Stop a Plugin from the user panel and queue the resulting state change for the model's next step.
* @param agent - Agent whose Session owns the Plugin and receives the context.
* @param pluginId - Stable Plugin identity to stop.
* @returns Success or the reason no run was stopped.
*/
@Remote('stopFromPanel') async stopFromPanel(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisStopResponse>
/**
* Replace the Host mirror of the Client inspect provider directory.
* @param providers - complete Client provider manifest.
* @returns null after accepting the manifest.
*/
@Remote('syncInspectManifest') syncInspectManifest(providers: readonly CordisInspectProviderManifest[]): null
/**
* Claim one pending Client inspect query with its live result.
* @param agent - Session that owns the query.
* @param requestId - exact pending query identity.
* @param resolution - provider result or structured refusal.
* @returns whether this answer won the query.
*/
@Remote('resolveInspectQuery') resolveInspectQuery( agent: Agent, requestId: CordisInspectRequestId, resolution: CordisInspectQueryResolution, ): CordisInspectResolveAck
/**
* Frame-wide inventory, grouped as one row per stable Plugin.
* @returns Source-free metadata for every process-local Plugin.
*/
@Remote('inventory') inventory(): DynamicCordisInventoryRow[]
/**
* Read one Session's Host-rich state for inspection and result rendering.
* @param agent - Agent whose Session selects visible Plugins.
* @returns Plugin versions, active runs, Host fibers, and render failures.
*/
snapshot(agent: Agent): DynamicCordisSnapshotRow[]
/**
* Read source-free context for an explicit `@pluginId` user gesture.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity referenced by the user.
* @returns The preferred modification base, or undefined when unavailable.
*/
reference(agent: Agent, pluginId: CordisDynamicPluginId): DynamicCordisReference | undefined
/**
* List source-free Plugin summaries owned by one Session.
* @param agent - Agent whose Session selects visible Plugins.
* @returns one summary per Plugin in creation order.
*/
listPlugins(agent: Agent): DynamicCordisPluginInspection[]
/**
* Inspect one Plugin without returning Package source.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - stable Plugin identity.
* @returns version pointers, latest run, and all Package summaries.
*/
inspectPlugin(agent: Agent, pluginId: CordisDynamicPluginId): DynamicCordisPluginInspection
/**
* Read one exact immutable Package and its Host and Client source.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity that owns the Package.
* @param packageId - Exact immutable Package identity to inspect.
* @returns Package metadata, source, and the Plugin's lifecycle pointers.
*/
inspectPackage( agent: Agent, pluginId: CordisDynamicPluginId, packageId: CordisDynamicPackageId, ): DynamicCordisPackageInspection
/**
* Record a post-load render failure for the exact active run.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity that rendered.
* @param pluginRunId - Exact active run that produced the failure.
* @param failure - Slot, message, and entry-retirement result.
* @returns Null after recording or ignoring a stale report.
*/
@Remote('reportRenderFailure') async reportRenderFailure( agent: Agent, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, failure: DynamicCordisRenderFailure, ): Promise<null>
/**
* Report a Client guard rejection that happened after the Package completed activation.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity whose Client code was rejected.
* @param pluginRunId - Exact active run that produced the rejection.
* @param failure - Original guard message and stack.
* @returns Null after reporting or ignoring a stale/startup failure.
*/
@Remote('reportClientGuardFailure') async reportClientGuardFailure( agent: Agent, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, failure: CordisErrorDetails, ): Promise<null>
/**
* Invoke an active Host method while rejecting stale Client runs.
* @param pluginId - Stable Plugin identity that owns the method.
* @param pluginRunId - Exact active run authorizing the call.
* @param method - Registered Host handler name.
* @param args - JSON argument delivered to the handler.
* @returns The JSON result or a typed invocation failure.
*/
@Remote('invoke') async invoke( pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, method: string, args: JsonValue, ): Promise<DynamicCordisInvokeResult>
```
Types: [Agent](core.md)
Source: [`packages/self-modification/cordis-host-runner/src/index.ts:124`](../../packages/self-modification/cordis-host-runner/src/index.ts)
<a id="cordis-events"></a>
### `cordis/*` events
<a id="cordisdynamic-package--emit"></a>
#### `cordis/dynamic-package` — emit
One exact Plugin/Package activation is now live in the Host.
```ts cordis-catalog
/**
* One exact Plugin/Package activation is now live in the Host.
* @param pkg - stable plugin, immutable package, run identity, and label.
* @mode emit
*/
'cordis/dynamic-package'(pkg: DynamicCordisPackage): void
```
Source: [`packages/self-modification/cordis-host-runner/src/types.ts:379`](../../packages/self-modification/cordis-host-runner/src/types.ts)
<a id="cordisdynamic-retract--emit"></a>
#### `cordis/dynamic-retract` — emit
One exact activation was withdrawn.
```ts cordis-catalog
/**
* One exact activation was withdrawn.
* @param retracted - plugin, package, and run identity.
* @mode emit
*/
'cordis/dynamic-retract'(retracted: DynamicCordisRetracted): void
```
Source: [`packages/self-modification/cordis-host-runner/src/types.ts:385`](../../packages/self-modification/cordis-host-runner/src/types.ts)
<a id="cordisinspect-query--emit"></a>
#### `cordis/inspect-query` — emit
Request a live read-only query from the Client inspect registry.
```ts cordis-catalog
/**
* Request a live read-only query from the Client inspect registry.
* @param request - correlation, Session, provider, method, and JSON input.
* @mode emit
*/
'cordis/inspect-query'(request: CordisInspectQueryRequest): void
```
Source: [`packages/self-modification/cordis-host-runner/src/types.ts:391`](../../packages/self-modification/cordis-host-runner/src/types.ts)
<a id="cordisinspect-query-resolved--emit"></a>
#### `cordis/inspect-query-resolved` — emit
Notify every Client that an inspect query has settled or been cancelled.
```ts cordis-catalog
/**
* Notify every Client that an inspect query has settled or been cancelled.
* @param resolved - exact query identity that is no longer answerable.
* @mode emit
*/
'cordis/inspect-query-resolved'(resolved: CordisInspectQueryResolved): void
```
Source: [`packages/self-modification/cordis-host-runner/src/types.ts:397`](../../packages/self-modification/cordis-host-runner/src/types.ts)
<a id="cordisrequest-run--emit"></a>
#### `cordis/request-run` — emit
A Client-bearing activation needs a browser page, and may require a user decision.
```ts cordis-catalog
/**
* A Client-bearing activation needs a browser page, and may require a user decision.
* @param request - correlation identity, owner, target version, mode, and approval requirement.
* @mode emit
*/
'cordis/request-run'(request: DynamicCordisRunRequest): void
```
Source: [`packages/self-modification/cordis-host-runner/src/types.ts:367`](../../packages/self-modification/cordis-host-runner/src/types.ts)
<a id="cordisrequest-run-resolved--emit"></a>
#### `cordis/request-run-resolved` — emit
A pending Client activation request left the answerable state.
```ts cordis-catalog
/**
* A pending Client activation request left the answerable state.
* @param resolved - request identity and outcome.
* @mode emit
*/
'cordis/request-run-resolved'(resolved: DynamicCordisRequestResolved): void
```
Source: [`packages/self-modification/cordis-host-runner/src/types.ts:373`](../../packages/self-modification/cordis-host-runner/src/types.ts)
<!-- END GENERATED cordis-surface -->

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/typert.md
typert.md: 19d517f85ebd9372252608124fef4f513b1462a0
typert.zh.md: ee69f6fa7d99cb44f8e7f07bd4cd82fc70c1fa18
typert.md: 863ab9821bbf3681ae43df817cc04018e275390c
typert.zh.md: 4883b4be51a09d8e63001284e42bd7e60497bf1d

View File

@@ -233,6 +233,23 @@ interface TypertClientRemote extends TypertRemoteNamespaceMap {
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxapiproxy--apiproxy"></a>
### `ctx.apiProxy` — `ApiProxy`
Root interface of the unified API. New client-request domain = one new file pair + one field here + one map row.
```ts cordis-catalog
/**
* Response entry for server requests; not a domain method.
* @param message - Client response carrying the server request's rpcId.
* @returns Transport receipt for the response delivery.
*/
respond(message: ClientResponse): Promise<RpcReceipt>
```
Source: [`packages/host/apiproxy/src/api/index.ts:22`](../../packages/host/apiproxy/src/api/index.ts)
<a id="ctxtypert--typertregistry"></a>
### `ctx.typert` — `TypertRegistry`

View File

@@ -233,6 +233,23 @@ interface TypertClientRemote extends TypertRemoteNamespaceMap {
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxapiproxy--apiproxy"></a>
### `ctx.apiProxy` — `ApiProxy`
Root interface of the unified API. New client-request domain = one new file pair + one field here + one map row.
```ts cordis-catalog
/**
* Response entry for server requests; not a domain method.
* @param message - Client response carrying the server request's rpcId.
* @returns Transport receipt for the response delivery.
*/
respond(message: ClientResponse): Promise<RpcReceipt>
```
Source: [`packages/host/apiproxy/src/api/index.ts:22`](../../packages/host/apiproxy/src/api/index.ts)
<a id="ctxtypert--typertregistry"></a>
### `ctx.typert` — `TypertRegistry`

View File

@@ -41,6 +41,8 @@
- insert:
- id: code-runtime
name: '@deepseek-ai/dsh-code-runtime-worker-thread'
- id: cordis-host-runner
name: '@deepseek-ai/dsh-cordis-host-runner'
- id: tool-cordis
name: '@deepseek-ai/dsh-tool-cordis'
- id: llm-replay

View File

@@ -28,5 +28,7 @@
- insert:
- id: code-runtime
name: '@deepseek-ai/dsh-code-runtime-worker-thread'
- id: cordis-host-runner
name: '@deepseek-ai/dsh-cordis-host-runner'
- id: tool-cordis
name: '@deepseek-ai/dsh-tool-cordis'

View File

@@ -10,4 +10,3 @@ Model-facing tools over the live cordis runtime the agent itself runs inside: in
| [`cordis-host-runner/`](cordis-host-runner/README.md) | Definition registry, the `node:vm` sandbox for host halves, and the request-run round trip | provides `ctx.dynamicCordisRunner` |
| [`cordis-client-runner/`](cordis-client-runner/README.md) | Browser half of a dual-half package: evaluates the definition into a live browser plugin and answers the run request | client face; provides the browser `ctx.dynamicCordisRunner` |
| [`ui-cordis/`](ui-cordis/README.md) | Browser surfaces: the frame-wide panel that operates every definition, and the read-only define card | client face; registers slots |
| [`repository-plugin/`](repository-plugin/README.md) | Repository skill and MCP composition | registers a Loader builtin |

View File

@@ -10,4 +10,3 @@ agent 修改自身运行时:检查已加载的插件与服务接口、定义
| [`cordis-host-runner/`](cordis-host-runner/README.md) | 定义注册表、host 半的 `node:vm` 沙箱,以及 request-run 往返 | 提供 `ctx.dynamicCordisRunner` |
| [`cordis-client-runner/`](cordis-client-runner/README.md) | 双半包的浏览器半:把定义求值成活的浏览器插件,并应答运行请求 | client 面;提供浏览器侧 `ctx.dynamicCordisRunner` |
| [`ui-cordis/`](ui-cordis/README.md) | 浏览器面:操作全部定义的全局面板,与只读的 define 卡片 | client 面;注册 slot |
| [`repository-plugin/`](repository-plugin/README.md) | 通过 DSH 自有子 Plugin 准备并挂载静态 repository skills 与通用 `.mcp.json` server | 注册一个 Loader builtin |

View File

@@ -122,36 +122,36 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
{
signature: 'subscribe(fn: () => void): () => void',
description: 'LocaleFace subscribe: notified on every snapshot change (locale switch or dictionary registration — registrations bump the revision so already rendered outlets pick up late-arriving dictionaries).',
parameters: [{"name":"fn","description":"change callback."}],
parameters: [{ name: 'fn', description: 'change callback.' }],
returns: 'unsubscribe.',
},
{
signature: 'setLocale(id: string): void',
description: 'Switch the active locale — the only user preference write entry.',
parameters: [{"name":"id","description":"a registered locale id; unknown ids throw."}],
parameters: [{ name: 'id', description: 'a registered locale id; unknown ids throw.' }],
},
{
signature: 'register<N extends keyof LocaleNamespaceMap & string>(ns: N, dicts: Record<LocaleId, LocaleDictOf<N>>): () => void',
description: 'Register a declared namespace\'s dictionaries, all locales in one call — the typed form: each dictionary is checked against the namespace\'s LocaleNamespaceMap key union (a missing or extra key is a compile error), and every shipped locale is required (bilingual balance enforced at registration). Duplicate (ns, locale) throws (single occupant; a namespace\'s texts have one owner). Registration bumps the revision so mounted outlets pick up late-arriving dictionaries.',
parameters: [{"name":"ns","description":"a namespace merged into LocaleNamespaceMap."},{"name":"dicts","description":"complete dictionaries keyed by locale id."}],
parameters: [{ name: 'ns', description: 'a namespace merged into LocaleNamespaceMap.' }, { name: 'dicts', description: 'complete dictionaries keyed by locale id.' }],
returns: 'disposer removing every locale registered by this call (idempotent).',
},
{
signature: 'register(ns: string, locale: string, dict: LocaleDict): () => void',
description: 'Single-locale untyped form for namespaces outside the merge table (dynamic composition, tests).',
parameters: [{"name":"ns","description":"namespace."},{"name":"locale","description":"locale tag."},{"name":"dict","description":"dictionary."}],
parameters: [{ name: 'ns', description: 'namespace.' }, { name: 'locale', description: 'locale tag.' }, { name: 'dict', description: 'dictionary.' }],
returns: 'disposer (idempotent).',
},
{
signature: 'bind<N extends keyof LocaleNamespaceMap & string>(ns: N): TranslateNS<N>',
description: 'Bind a declared namespace to a translate function typed to its dictionary key union (plus the shared common vocabulary) — the same key domain the framework-injected `t` seat carries. The returned reference is stable per namespace (repeat binds return the same function), so it can ride inject surfaces without breaking memoization.',
parameters: [{"name":"ns","description":"a namespace merged into LocaleNamespaceMap."}],
parameters: [{ name: 'ns', description: 'a namespace merged into LocaleNamespaceMap.' }],
returns: 'the typed translate function (reads the active locale at call time).',
},
{
signature: 'bind(ns: string): Translate',
description: 'Untyped form for namespaces outside the merge table (dynamic composition, tests).',
parameters: [{"name":"ns","description":"namespace."}],
parameters: [{ name: 'ns', description: 'namespace.' }],
returns: 'the translate function.',
},
],
@@ -164,47 +164,47 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
{
signature: 'open(id: SessionId): void',
description: 'Select a session as current.',
parameters: [{"name":"id","description":"session id (must exist in the list; unknown ids fail loud)."}],
parameters: [{ name: 'id', description: 'session id (must exist in the list; unknown ids fail loud).' }],
},
{
signature: 'openSubagent(address: SubagentAddress): void',
description: 'Open a healthy catalog child through its exact direct-parent address.',
parameters: [{"name":"address","description":"catalog-derived parent and child ids."}],
parameters: [{ name: 'address', description: 'catalog-derived parent and child ids.' }],
},
{
signature: 'setSubagentCatalogOpen(parentSessionId: SessionId, open: boolean): void',
description: 'Mark whether a catalog menu is consuming live membership updates.',
parameters: [{"name":"parentSessionId","description":"catalog owner."},{"name":"open","description":"current menu state."}],
parameters: [{ name: 'parentSessionId', description: 'catalog owner.' }, { name: 'open', description: 'current menu state.' }],
},
{
signature: 'refreshSubagents(parentSessionId: SessionId): Promise<void>',
description: 'Refresh one direct-child catalog.',
parameters: [{"name":"parentSessionId","description":"catalog owner."}],
parameters: [{ name: 'parentSessionId', description: 'catalog owner.' }],
returns: 'completion of the current or newly started refresh.',
},
{
signature: 'search( query: string, signal: AbortSignal, ): Promise<RpcResult<{ 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."}],
parameters: [{ name: 'query', description: 'non-blank literal phrase.' }, { name: 'signal', description: 'cancellation for a superseded search.' }],
returns: 'bounded results, or a business/transport error.',
},
{
signature: 'fork(opts: { sessionId: SessionId; atSeq?: number; increaseTitle?: boolean }): Promise<SessionId>',
description: 'Fork a session from a completed-turn prefix of the source; on resolution the child is in the list store and `open()` can target it.',
parameters: [{"name":"opts","description":"source session id, the optional event seq anchoring the cut (the boundary is the first turn/end at or after it; an in-log anchor in an open turn is unavailable rather than clipped backward), and whether to increment an inherited durable title before resolving."}],
parameters: [{ name: 'opts', description: 'source session id, the optional event seq anchoring the cut (the boundary is the first turn/end at or after it; an in-log anchor in an open turn is unavailable rather than clipped backward), and whether to increment an inherited durable title before resolving.' }],
returns: 'the child session id.',
throws: ["when the fork fails, or when a requested child-title rename fails after creation."],
throws: ['when the fork fails, or when a requested child-title rename fails after creation.'],
},
{
signature: 'scope(id: SessionId): AgentContext | undefined',
description: 'Resolve an Agent-scoped context view (use-and-discard).',
parameters: [{"name":"id","description":"session id."}],
parameters: [{ name: 'id', description: 'session id.' }],
returns: 'scoped ctx, or undefined for a session neither listed nor already scoped.',
},
{
signature: 'binding(id: SessionId): SessionBinding | undefined',
description: 'Resolve the stable session binding (scope-addressed assembly feed).',
parameters: [{"name":"id","description":"session id."}],
parameters: [{ name: 'id', description: 'session id.' }],
returns: 'binding, or undefined for a session neither listed nor already scoped.',
},
],
@@ -222,9 +222,9 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
{
signature: 'inject(key: keyof SlotMap & string, callback: () => SlotInjectionEffect): () => void',
description: 'Install an effect for each declaration lifetime of a slot. The callback runs synchronously when the declaration already exists; otherwise it runs inside the declaring `register()` call after the declaration is committed. Collapse disposes the effect and a later declaration runs it again. Callback effects are synchronous disposers; iterable effects install transactionally and dispose in reverse order. The controller belongs to the caller\'s fiber, so plugin unload cancels a pending wait and removes any active contribution.',
parameters: [{"name":"key","description":"declared SlotMap key to depend on."},{"name":"callback","description":"creates one disposer or an iterable of disposers."}],
parameters: [{ name: 'key', description: 'declared SlotMap key to depend on.' }, { name: 'callback', description: 'creates one disposer or an iterable of disposers.' }],
returns: 'idempotent disposer for the wait and active effect.',
throws: ["callback setup failures synchronously when the slot is already declared."],
throws: ['callback setup failures synchronously when the slot is already declared.'],
},
],
},
@@ -242,18 +242,18 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
{
signature: 'setTheme(id: string): void',
description: 'Switch the theme preference — the only user preference write entry. Built-in preferences are written through the settings scope and every accepted value emits `theme/change`.',
parameters: [{"name":"id","description":"a registered theme id or `system`; unknown ids throw."}],
parameters: [{ name: 'id', description: 'a registered theme id or `system`; unknown ids throw.' }],
},
{
signature: 'register(definition: ThemeDefinition): () => void',
description: 'Register a theme. Duplicate id throws (single occupant per id; the built-in pair counts; `system` is a preference, not a registrable id).',
parameters: [{"name":"definition","description":"theme id, colorScheme, and alias-token overrides."}],
parameters: [{ name: 'definition', description: 'theme id, colorScheme, and alias-token overrides.' }],
returns: 'disposer. Disposing the theme backing the active preference resets the preference to the default so the UI never keeps tokens of an unregistered theme.',
},
{
signature: 'overrideTokens(source: string, tokens: ThemeTokenOverrides): () => void',
description: 'Stack a token override layer on top of the active theme — the token-level analogue of slot shading: the base theme stays untouched, layers compose in seq order with later layers winning per-token, and removing a layer restores whatever it covered. Calling again with the same source replaces that source\'s whole layer and restacks it on top (effect re-registration semantics). Emits `theme/change` with the recomposed snapshot.',
parameters: [{"name":"source","description":"layer identity; one layer per source (dynamic packages pass their package id — the façade pins it, so it also names the layer's origin for inspection)."},{"name":"tokens","description":"token-name → `{ light, dark }` value pairs. Validated at runtime (model-authored callers reach this boundary with untyped JS); a bare string value throws a teaching error."}],
parameters: [{ name: 'source', description: 'layer identity; one layer per source (dynamic packages pass their package id — the façade pins it, so it also names the layer\'s origin for inspection).' }, { name: 'tokens', description: 'token-name → `{ light, dark }` value pairs. Validated at runtime (model-authored callers reach this boundary with untyped JS); a bare string value throws a teaching error.' }],
returns: 'disposer removing exactly the layer this call created; a no-op once the source has re-overridden (the newer layer is not torn down).',
},
],
@@ -303,18 +303,18 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
{
signature: 'connectWorkspace(workspaceId: WorkspaceId): Promise<SessionId>',
description: 'Connect a Workspace to its reusable or freshly created blank session.',
parameters: [{"name":"workspaceId","description":"target workspace."}],
parameters: [{ name: 'workspaceId', description: 'target workspace.' }],
returns: 'the connected session id.',
},
{
signature: 'startSession(workspaceId?: WorkspaceId): void',
description: 'The New Session flow: connect the target (or recent) Workspace and open the resulting session; failures surface on the session list state.',
parameters: [{"name":"workspaceId","description":"explicit target; omitted uses the recency projection."}],
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.' }],
},
{
signature: 'create(input: { path: string }): Promise<WorkspaceView>',
description: 'Register an existing path as a Workspace.',
parameters: [{"name":"input","description":"the Host create payload."}],
parameters: [{ name: 'input', description: 'the Host create payload.' }],
returns: 'the created or idempotently resolved Workspace.',
},
{
@@ -326,41 +326,41 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
{
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."}],
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.',
},
{
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."}],
parameters: [{ name: 'path', description: 'absolute existing parent directory.' }, { name: 'name', description: 'single non-blank path segment.' }],
returns: 'the created directory\'s 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."}],
parameters: [{ name: 'path', description: 'absolute or host-resolvable path.' }],
},
{
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."}],
parameters: [{ name: 'workspaceId', description: 'target workspace.' }, { name: 'title', description: 'the new display title.' }],
returns: 'the updated Workspace view.',
},
{
signature: 'delete(workspaceId: WorkspaceId): Promise<void>',
description: 'Delete a Workspace (its sessions fall back to the unaccounted group).',
parameters: [{"name":"workspaceId","description":"target workspace."}],
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."}],
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 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."}],
parameters: [{ name: 'sessionId', description: 'session to archive.' }],
},
],
},
@@ -382,7 +382,7 @@ export const EVENT_API: readonly EventApiEntry[] = [
signature: '\'locale/change\'(snapshot: LocaleSnapshot): void',
summary: 'The active locale switched.',
description: 'The active locale switched. Dictionary registrations do NOT emit this event (listeners may re-register slots in response, and boot registers one namespace per package); continuous render refresh rides the LocaleFace revision instead.',
parameters: [{"name":"snapshot","description":"Current immutable locale snapshot."}],
parameters: [{ name: 'snapshot', description: 'Current immutable locale snapshot.' }],
},
{
name: 'slots/changed',
@@ -390,7 +390,7 @@ export const EVENT_API: readonly EventApiEntry[] = [
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."}],
parameters: [{ name: 'key', description: 'the mutated SlotMap key.' }],
},
{
name: 'theme/change',
@@ -398,7 +398,7 @@ export const EVENT_API: readonly EventApiEntry[] = [
signature: '\'theme/change\'(snapshot: ThemeSnapshot): void',
summary: 'Theme state changed (preference switched, registry updated, or the OS color scheme changed while the preference is `system`).',
description: 'Theme state changed (preference switched, registry updated, or the OS color scheme changed while the preference is `system`).',
parameters: [{"name":"snapshot","description":"Current immutable theme snapshot."}],
parameters: [{ name: 'snapshot', description: 'Current immutable theme snapshot.' }],
},
]
@@ -418,7 +418,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'AssistantMessageNode',
declaration: 'export interface AssistantMessageNode {\n kind: \'assistant\';\n seq: number;\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}',
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',
@@ -510,7 +510,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'ConversationNode',
declaration: 'export type ConversationNode = UserMessageNode | AssistantMessageNode | SteeringMessageNode | ContextMessageNode | ModelRetryNode | TurnErrorNode | ToolResultNode | CommandNode | CompactionSummaryNode | UnknownSurfaceNode;',
declaration: 'export type ConversationNode = UserMessageNode | AssistantMessageNode | SteeringMessageNode | ContextMessageNode | ModelRetryNode | TurnErrorNode | TurnMaxTokensNode | ToolResultNode | CommandNode | CompactionSummaryNode | UnknownSurfaceNode;',
},
{
name: 'ConversationSnapshot',
@@ -852,6 +852,10 @@ export const TYPE_API: readonly TypeApiEntry[] = [
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}',

View File

@@ -140,7 +140,11 @@ declare module '@deepseek-ai/cordis' {
}
}
/** Provide the registry as a normal Client service. */
/**
* Provide the registry as a normal Client service.
* @param ctx - Client Cordis context receiving the service.
* @param registry - page-local inspect registry to publish.
*/
export function provideClientCordisInspect(ctx: Context, registry: ClientCordisInspectRegistry): void {
ctx.provide('cordisInspect', registry)
}

View File

@@ -142,7 +142,10 @@ export class CordisRunOrchestrator {
subscribe: fn => this.observe(fn),
}
/** Register a Client activation request, starting it immediately when the Plugin is already authorized. */
/**
* Register a Client activation request, starting it immediately when the Plugin is already authorized.
* @param request - forwarded approval and activation metadata.
*/
open(request: CordisRunRequest): void {
this.requests.set(request.requestId, request)
if (!request.requiresApproval) {
@@ -238,7 +241,10 @@ export class CordisRunOrchestrator {
if (changed) this.commit()
}
/** Close an approval settled by another page or by cancellation. */
/**
* Close an approval settled by another page or by cancellation.
* @param requestId - approval request that can no longer be answered here.
*/
close(requestId: ApprovalRequestId): void {
const request = this.requests.get(requestId)
if (request === undefined) return
@@ -250,7 +256,11 @@ export class CordisRunOrchestrator {
this.commit()
}
/** Approve and execute one still-open model request. */
/**
* Approve and execute one still-open model request.
* @param requestId - approval request to execute.
* @param approveFutureVersions - whether this approval covers later Packages for the same Plugin.
*/
approve(requestId: ApprovalRequestId, approveFutureVersions: boolean): Promise<void> {
const request = this.requests.get(requestId)
if (request === undefined || !request.requiresApproval) return Promise.resolve()
@@ -265,7 +275,10 @@ export class CordisRunOrchestrator {
})
}
/** Reject one still-open model request without executing either half. */
/**
* Reject one still-open model request without executing either half.
* @param requestId - approval request to reject.
*/
async decline(requestId: ApprovalRequestId): Promise<void> {
const request = this.requests.get(requestId)
if (request === undefined || !request.requiresApproval) return
@@ -277,7 +290,10 @@ export class CordisRunOrchestrator {
await this.answer(requestId, { ok: false, reason: 'rejected' })
}
/** Execute a direct panel run; the user gesture itself authorizes it. */
/**
* Execute a direct panel run; the user gesture itself authorizes it.
* @param request - exact Package activation selected by the user.
*/
startUserRun(request: CordisUserRunRequest): Promise<void> {
return this.orchestrate(request)
}

View File

@@ -67,7 +67,11 @@ export const CLIENT_BUILTIN_INSPECTION: readonly JsonValue[] = [
},
]
/** Construct the first-party Client provider registrations. */
/**
* Construct the first-party Client provider registrations.
* @param ctx - Client context used for live Service-backed queries.
* @returns registrations for static catalogs and live Client capabilities.
*/
export function clientInspectProviders(ctx: Context): ClientCordisInspectProviderRegistration[] {
return [
registration(

View File

@@ -110,6 +110,58 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation\', () => ctx.slots.register(\n { name: \'conversation\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-layout/src/client/index.ts:62',
},
{
key: 'conversation.chat.assistant-actions',
kind: 'list',
scope: 'session',
summary: 'Action strip attached to one finalized assistant message, rendered inside that message\'s IconActions row.',
doc: 'Action strip attached to one finalized assistant message, rendered\ninside that message\'s IconActions row. The chat entry owns the render\nsite and passes the addressed message identity; contributors add\nper-message actions without importing the conversation implementation.\nEntries render by ascending `order`.',
registerOptions: [
{
name: 'id',
requirement: 'required',
type: 'string',
doc: 'Your cell key. Use an id of your own: a fresh id is added beside the shipped entries, while reusing a shipped id puts you in THAT cell and replaces it. Owners that filter by id address you by it.',
},
{
name: 'order',
requirement: 'optional',
type: 'number',
doc: 'Position among the entries, ascending (default 0).',
},
{
name: 'label',
requirement: 'optional',
type: 'string | (() => string)',
doc: 'Display text where the owner projects one (nav rows, tabs). A thunk is re-read on every projection, so localized text follows the active locale without re-registering.',
},
],
ownerProps: [
'/**\n * Owner currency of the assistant-message action strip: the durable identity\n * of the one finalized message the contributed actions address. Only finalized\n * messages reach this slot, so the id is always present.\n */\nexport interface AssistantActionOwnerProps {\n /** Stable identity carried from the `assistant/message` event. */\n messageId: MessageId\n}',
],
ownerPropsReferences: [
'MessageId',
],
standardProps: [
'useSessions: SnapshotSelectorHook<SessionListState>',
'useWorkspaces: SnapshotSelectorHook<import(\'./workspaces/service.ts\').WorkspaceListState>',
'useSession: SnapshotSelectorHook<ConversationSnapshot>',
'sessionId: SessionId',
'useProjection: UseProjection',
'useInput: SnapshotSelectorHook<InputState>',
'inputActions: InputActions',
],
keyDomain: '',
hookContext: '',
slotInject: '',
declaredBy: 'an entry in \'conversation.chat.node\' (client-ui-conversation), so it exists while that entry is mounted',
occupants: [
'client-ui-feedback FeedbackActions id \'feedback\'',
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.chat.assistant-actions\', () => ctx.slots.register(\n { name: \'conversation.chat.assistant-actions\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:109',
},
{
key: 'conversation.chat.commandview',
kind: 'keyed',
@@ -147,7 +199,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
occupants: [],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.chat.commandview\', () => ctx.slots.register(\n { name: \'conversation.chat.commandview\', key: \'<one key the owner dispatches>\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:88',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:94',
},
{
key: 'conversation.chat.node',
@@ -180,7 +232,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
'useInput: SnapshotSelectorHook<InputState>',
'inputActions: InputActions',
],
keyDomain: 'fixed by the owner\'s key table { [Kind in ChatNodeKind]: { node: ChatNode<Kind> } }, already taken: assistant-step, command, command-input, compaction, context, manual-compaction, model-retry, steering, tool-call, turn-error, turn-tail, unknown, user, workflow-run',
keyDomain: 'fixed by the owner\'s key table { [Kind in ChatNodeKind]: { node: ChatNode<Kind> } }, already taken: assistant-step, command, command-input, compaction, context, manual-compaction, model-retry, steering, tool-call, turn-error, turn-max-tokens, turn-tail, unknown, user, workflow-run',
hookContext: 'string',
slotInject: 'ChatNodeTurnDataInjected',
declaredBy: 'an entry in \'conversation.view\' (client-ui-conversation), so it exists while that entry is mounted',
@@ -194,6 +246,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
'client-ui-conversation CompactionNodeView key \'compaction\'',
'client-ui-conversation RetryNodeView key \'model-retry\'',
'client-ui-conversation TurnErrorNodeView key \'turn-error\'',
'client-ui-conversation TurnMaxTokensNodeView key \'turn-max-tokens\'',
'client-ui-conversation TurnTailNodeView key \'turn-tail\'',
'client-ui-conversation UnknownNodeView key \'unknown\'',
'client-ui-goal GoalCommandInputView key \'command-input\'',
@@ -202,7 +255,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.chat.node\', () => ctx.slots.register(\n { name: \'conversation.chat.node\', key: \'<one key the owner dispatches>\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:72',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:78',
},
{
key: 'conversation.chat.turnTail',
@@ -242,7 +295,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.chat.turnTail\', () => ctx.slots.register(\n { name: \'conversation.chat.turnTail\', select: owner => null },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:95',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:101',
},
{
key: 'conversation.composer',
@@ -285,7 +338,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.composer\', () => ctx.slots.register(\n { name: \'conversation.composer\', select: owner => null },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:114',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:132',
},
{
key: 'conversation.composer.bar',
@@ -318,7 +371,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.composer.bar\', () => ctx.slots.register(\n { name: \'conversation.composer.bar\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:183',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:201',
},
{
key: 'conversation.composer.dock',
@@ -371,7 +424,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.composer.dock\', () => ctx.slots.register(\n { name: \'conversation.composer.dock\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:152',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:170',
},
{
key: 'conversation.details.tool',
@@ -402,7 +455,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.details.tool\', () => ctx.slots.register(\n { name: \'conversation.details.tool\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:106',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:124',
},
{
key: 'conversation.hero.agentPreset',
@@ -428,7 +481,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.hero.agentPreset\', () => ctx.slots.register(\n { name: \'conversation.hero.agentPreset\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:127',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:145',
},
{
key: 'conversation.hero.workspace',
@@ -456,7 +509,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.hero.workspace\', () => ctx.slots.register(\n { name: \'conversation.hero.workspace\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:121',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:139',
},
{
key: 'conversation.hero.workspace.directoryFlow',
@@ -538,7 +591,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.dock\', () => ctx.slots.register(\n { name: \'conversation.input.dock\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:143',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:161',
},
{
key: 'conversation.input.left',
@@ -589,7 +642,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
occupants: [],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.left\', () => ctx.slots.register(\n { name: \'conversation.input.left\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:161',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:179',
},
{
key: 'conversation.input.model',
@@ -620,7 +673,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.input.model\', () => ctx.slots.register(\n { name: \'conversation.input.model\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:203',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:221',
},
{
key: 'conversation.input.overlay',
@@ -700,7 +753,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.input.plan\', () => ctx.slots.register(\n { name: \'conversation.input.plan\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:193',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:211',
},
{
key: 'conversation.input.right',
@@ -751,7 +804,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
occupants: [],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.right\', () => ctx.slots.register(\n { name: \'conversation.input.right\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:169',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:187',
},
{
key: 'conversation.session',
@@ -780,7 +833,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.session\', () => ctx.slots.register(\n { name: \'conversation.session\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:43',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:44',
},
{
key: 'conversation.session.header',
@@ -809,7 +862,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.session.header\', () => ctx.slots.register(\n { name: \'conversation.session.header\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:51',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:52',
},
{
key: 'conversation.session.header.actions',
@@ -861,7 +914,57 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.session.header.actions\', () => ctx.slots.register(\n { name: \'conversation.session.header.actions\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:62',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:63',
},
{
key: 'conversation.session.header.utilities',
kind: 'list',
scope: 'session',
summary: 'Right-aligned Session utilities kept outside the title-adjacent action group, so an optional utility cannot reorder session context or lineage.',
doc: 'Right-aligned Session utilities kept outside the title-adjacent action\ngroup, so an optional utility cannot reorder session context or lineage.',
registerOptions: [
{
name: 'id',
requirement: 'required',
type: 'string',
doc: 'Your cell key. Use an id of your own: a fresh id is added beside the shipped entries, while reusing a shipped id puts you in THAT cell and replaces it. Owners that filter by id address you by it.',
},
{
name: 'order',
requirement: 'optional',
type: 'number',
doc: 'Position among the entries, ascending (default 0).',
},
{
name: 'label',
requirement: 'optional',
type: 'string | (() => string)',
doc: 'Display text where the owner projects one (nav rows, tabs). A thunk is re-read on every projection, so localized text follows the active locale without re-registering.',
},
],
ownerProps: [
'/** Header actions derive their state from the standard session/global kit. */\nexport interface ConversationHeaderActionOwnerProps {}',
],
ownerPropsReferences: [],
standardProps: [
'useSessions: SnapshotSelectorHook<SessionListState>',
'useWorkspaces: SnapshotSelectorHook<import(\'./workspaces/service.ts\').WorkspaceListState>',
'useSession: SnapshotSelectorHook<ConversationSnapshot>',
'sessionId: SessionId',
'useProjection: UseProjection',
'useInput: SnapshotSelectorHook<InputState>',
'inputActions: InputActions',
],
keyDomain: '',
hookContext: '',
slotInject: '',
declaredBy: 'an entry in \'conversation.session.header\' (client-ui-conversation), so it exists while that entry is mounted',
occupants: [
'session-export SessionExportHeader id \'session-export\'',
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.session.header.utilities\', () => ctx.slots.register(\n { name: \'conversation.session.header.utilities\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:68',
},
{
key: 'conversation.view',
@@ -912,7 +1015,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.view\', () => ctx.slots.register(\n { name: \'conversation.view\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:70',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:76',
},
{
key: 'details',
@@ -1089,7 +1192,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'settings.general.item\', () => ctx.slots.register(\n { name: \'settings.general.item\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-settings/src/client/contract/slots.ts:81',
source: 'packages/client/ui-settings/src/client/contract/slots.ts:90',
},
{
key: 'settings.header',
@@ -1161,7 +1264,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'settings.onboarding\', () => ctx.slots.register(\n { name: \'settings.onboarding\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-settings/src/client/contract/slots.ts:66',
source: 'packages/client/ui-settings/src/client/contract/slots.ts:75',
},
{
key: 'settings.plugin.item',
@@ -1200,7 +1303,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
keyDomain: '',
hookContext: '',
slotInject: '',
declaredBy: 'an entry in \'settings.section\' (client-ui-plugin-config), so it exists while that entry is mounted',
declaredBy: 'an entry in \'settings.plugins.tab\' (client-ui-plugin-config), so it exists while that entry is mounted',
occupants: [
'client-ui-plugin-config BashCard id \'bash\'',
'client-ui-plugin-config AgentLoopCard id \'agent-loop\'',
@@ -1210,6 +1313,52 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'settings.plugin.item\', () => ctx.slots.register(\n { name: \'settings.plugin.item\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-plugin-config/src/client/slot-contract.ts:16',
},
{
key: 'settings.plugins.tab',
kind: 'list',
scope: 'root',
summary: 'One page inside the Plugins settings section.',
doc: 'One page inside the Plugins settings section. The section owner renders\nlocalized entry labels as tabs and mounts each contribution inside its\ncorresponding tab panel. Options: `id` (tab key), `order` (tab order),\nand `label` (registrant-localized tab text). Declared at runtime by the\nfeature that owns the Plugins section; the type lives here so inventory\nand configuration plugins collaborate without depending on one another.',
registerOptions: [
{
name: 'id',
requirement: 'required',
type: 'string',
doc: 'Your cell key. Use an id of your own: a fresh id is added beside the shipped entries, while reusing a shipped id puts you in THAT cell and replaces it. Owners that filter by id address you by it.',
},
{
name: 'order',
requirement: 'optional',
type: 'number',
doc: 'Position among the entries, ascending (default 0).',
},
{
name: 'label',
requirement: 'optional',
type: 'string | (() => string)',
doc: 'Display text where the owner projects one (nav rows, tabs). A thunk is re-read on every projection, so localized text follows the active locale without re-registering.',
},
],
ownerProps: [
'/** Owner share of a Plugins tab (the section supplies nothing). */\nexport interface SettingsPluginsTabOwnerProps {\n /** Marker field: tab owner props are intentionally empty. */\n children?: never\n}',
],
ownerPropsReferences: [],
standardProps: [
'useSessions: SnapshotSelectorHook<SessionListState>',
'useWorkspaces: SnapshotSelectorHook<import(\'./workspaces/service.ts\').WorkspaceListState>',
],
keyDomain: '',
hookContext: '',
slotInject: '',
declaredBy: 'an entry in \'settings.section\' (client-ui-plugin-config), so it exists while that entry is mounted',
occupants: [
'client-ui-plugin-config ConfigurablePluginsTab id \'configurable\'',
'client-ui-plugins PluginSettingsSection id \'all\'',
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'settings.plugins.tab\', () => ctx.slots.register(\n { name: \'settings.plugins.tab\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-settings/src/client/contract/slots.ts:62',
},
{
key: 'settings.section',
kind: 'list',

View File

@@ -45,9 +45,7 @@
"lib/typert.host.js",
"lib/typert.host.d.ts",
"lib/typert.remote-client.js",
"lib/typert.remote-client.d.ts",
"lib/typert.remote-client.d.ts.map",
"src"
"lib/typert.remote-client.d.ts"
],
"license": "BSD-3-Clause",
"dependencies": {

View File

@@ -41,22 +41,38 @@ export { CordisInspectRegistryService } from './inspect-registry.ts'
export type { HostCordisInspectProviderRegistration } from './inspect-registry.ts'
export { HOST_BUILTIN_INSPECTION } from './sandbox.ts'
/** Brand a Host-minted Plugin ID. */
/**
* Brand a Host-minted Plugin ID.
* @param id - opaque identifier minted by the Host registry.
* @returns the branded Plugin identifier.
*/
export function CordisDynamicPluginId(id: string): CordisDynamicPluginId {
return id as CordisDynamicPluginId
}
/** Brand a Host-minted Package ID. */
/**
* Brand a Host-minted Package ID.
* @param id - opaque identifier minted by the Host registry.
* @returns the branded Package identifier.
*/
export function CordisDynamicPackageId(id: string): CordisDynamicPackageId {
return id as CordisDynamicPackageId
}
/** Brand a Host-minted Plugin Run ID. */
/**
* Brand a Host-minted Plugin Run ID.
* @param id - opaque identifier minted by the Host registry.
* @returns the branded Plugin Run identifier.
*/
export function CordisDynamicPluginRunId(id: string): CordisDynamicPluginRunId {
return id as CordisDynamicPluginRunId
}
/** Brand a Host-minted approval request ID. */
/**
* Brand a Host-minted approval request ID.
* @param id - opaque identifier minted by the Host registry.
* @returns the branded approval request identifier.
*/
export function ApprovalRequestId(id: string): ApprovalRequestId {
return id as ApprovalRequestId
}

View File

@@ -146,7 +146,11 @@ export class DynamicCordisRegistry {
private nextRun = 1
private nextApproval = 1
/** Mint a semantic plugin ID without reusing a prior suffix. */
/**
* Mint a semantic plugin ID without reusing a prior suffix.
* @param prefix - validated lowercase semantic prefix proposed by the model.
* @returns a process-unique Plugin ID.
*/
mintPluginId(prefix: string): string {
let id: CordisDynamicPluginId
do id = `${prefix}-${this.nextPlugin++}` as CordisDynamicPluginId
@@ -154,69 +158,115 @@ export class DynamicCordisRegistry {
return id
}
/** Mint an immutable package ID. */
/**
* Mint an immutable package ID.
* @returns a process-unique Package ID.
*/
mintPackageId(): string {
return `pkg-${this.nextPackage++}`
}
/** Mint an activation ID. */
/**
* Mint an activation ID.
* @returns a process-unique Plugin Run ID.
*/
mintPluginRunId(): string {
return `run-${this.nextRun++}`
}
/** Mint an approval ID. */
/**
* Mint an approval ID.
* @returns a process-unique approval request ID.
*/
mintApprovalRequestId(): string {
return `approval-${this.nextApproval++}`
}
/** Add one stable plugin. */
/**
* Add one stable plugin.
* @param plugin - Plugin record to retain under its stable ID.
*/
add(plugin: DynamicCordisPlugin): void {
this.plugins.set(plugin.pluginId, plugin)
}
/** Read one plugin. */
/**
* Read one plugin.
* @param id - stable Plugin ID.
* @returns the Plugin record, or `undefined` when absent.
*/
get(id: CordisDynamicPluginId): DynamicCordisPlugin | undefined {
return this.plugins.get(id)
}
/** Delete one plugin and all package versions. */
/**
* Delete one plugin and all package versions.
* @param id - stable Plugin ID to remove.
* @returns whether a Plugin record was removed.
*/
delete(id: CordisDynamicPluginId): boolean {
return this.plugins.delete(id)
}
/** All plugins in creation order. */
/**
* Read all plugins in creation order.
* @returns a snapshot of every Plugin record.
*/
all(): DynamicCordisPlugin[] {
return [...this.plugins.values()]
}
/** One session's plugins in creation order. */
/**
* Read one session's plugins in creation order.
* @param sessionId - owning session to filter by.
* @returns a snapshot of matching Plugin records.
*/
ofSession(sessionId: SessionId): DynamicCordisPlugin[] {
return this.all().filter(plugin => plugin.sessionId === sessionId)
}
/** Publish one pending approval. */
/**
* Publish one pending approval.
* @param id - approval request ID.
* @param pending - resolver and Plugin metadata retained until settlement.
*/
armRequest(id: ApprovalRequestId, pending: DynamicCordisPendingRequest): void {
this.pendingRequests.set(id, pending)
}
/** Read one pending approval without claiming it. */
/**
* Read one pending approval without claiming it.
* @param id - approval request ID.
* @returns the pending request, or `undefined` when absent.
*/
peekRequest(id: ApprovalRequestId): DynamicCordisPendingRequest | undefined {
return this.pendingRequests.get(id)
}
/** Claim one pending approval; first answer wins. */
/**
* Claim one pending approval; first answer wins.
* @param id - approval request ID.
* @returns the claimed request, or `undefined` when already settled.
*/
claimRequest(id: ApprovalRequestId): DynamicCordisPendingRequest | undefined {
const pending = this.pendingRequests.get(id)
if (pending !== undefined) this.pendingRequests.delete(id)
return pending
}
/** Cancel one pending approval. */
/**
* Cancel one pending approval.
* @param id - approval request ID to remove.
*/
disarmRequest(id: ApprovalRequestId): void {
this.pendingRequests.delete(id)
}
/** Pending approval for one plugin, if any. */
/**
* Find a pending approval for one Plugin.
* @param pluginId - stable Plugin ID.
* @returns its approval request ID, or `undefined` when none is pending.
*/
pendingRequestFor(pluginId: CordisDynamicPluginId): ApprovalRequestId | undefined {
for (const [requestId, request] of this.pendingRequests) {
if (request.pluginId === pluginId) return requestId

View File

@@ -2,23 +2,38 @@
import type { GenericCallView } from '@deepseek-ai/dsh-tools'
/** Render a runtime-inspection call. */
/**
* Render a runtime-inspection call.
* @param args - requested runtime category and optional member name.
* @returns replay-safe generic call presentation.
*/
export function presentRuntimeInspectCall(args: { what?: string; name?: string }): GenericCallView {
const target = args.name === undefined ? args.what : `${args.what}: ${args.name}`
return { card: 'generic', kind: 'read', title: target === undefined ? 'Inspect Cordis runtime' : `Inspect Cordis runtime: ${target}` }
}
/** Render provider-directory inspection. */
/**
* Render provider-directory inspection.
* @returns replay-safe generic call presentation.
*/
export function presentInspectListCall(): GenericCallView {
return { card: 'generic', kind: 'read', title: 'List Cordis Inspect Providers' }
}
/** Render one provider query. */
/**
* Render one provider query.
* @param args - target platform, provider, and method.
* @returns replay-safe generic call presentation.
*/
export function presentInspectQueryCall(args: { platform: string; provider: string; method: string }): GenericCallView {
return { card: 'generic', kind: 'read', title: `Query Cordis ${args.platform} ${args.provider}.${args.method}` }
}
/** Render layered self-inspection. */
/**
* Render layered self-inspection.
* @param args - optional Plugin and Package identity.
* @returns replay-safe generic call presentation.
*/
export function presentInspectSelfCall(args: { pluginId?: string; packageId?: string }): GenericCallView {
const target = args.pluginId === undefined
? 'dynamic Cordis Plugins'
@@ -26,12 +41,20 @@ export function presentInspectSelfCall(args: { pluginId?: string; packageId?: st
return { card: 'generic', kind: 'read', title: `Inspect ${target}` }
}
/** Render an immutable Package source-inspection call. */
/**
* Render an immutable Package source-inspection call.
* @param args - exact Plugin and Package identity.
* @returns replay-safe generic call presentation.
*/
export function presentPackageInspectCall(args: { pluginId: string; packageId: string }): GenericCallView {
return { card: 'generic', kind: 'read', title: `Inspect Cordis Package ${args.pluginId}/${args.packageId}` }
}
/** Render a new or appended Package definition. */
/**
* Render a new or appended Package definition.
* @param args - target Plugin, Package metadata, and source halves.
* @returns replay-safe generic call presentation with source in raw input.
*/
export function presentDefineCall(args: {
plugin: { kind: 'new'; idPrefix: string } | { kind: 'existing'; pluginId: string }
name: string
@@ -47,12 +70,20 @@ export function presentDefineCall(args: {
}
}
/** Render Plugin removal. */
/**
* Render Plugin removal.
* @param args - Plugin identity to remove.
* @returns replay-safe generic call presentation.
*/
export function presentUndefineCall(args: { pluginId: string }): GenericCallView {
return { card: 'generic', kind: 'delete', title: `Remove dynamic Plugin ${args.pluginId}` }
}
/** Render one exact Package activation. */
/**
* Render one exact Package activation.
* @param args - Plugin, Package, and activation mode.
* @returns replay-safe generic call presentation.
*/
export function presentRunCall(args: { pluginId: string; packageId: string; mode: 'run' | 'update' }): GenericCallView {
return {
card: 'generic',
@@ -61,7 +92,11 @@ export function presentRunCall(args: { pluginId: string; packageId: string; mode
}
}
/** Render Plugin stop. */
/**
* Render Plugin stop.
* @param args - Plugin identity to stop.
* @returns replay-safe generic call presentation.
*/
export function presentStopCall(args: { pluginId: string }): GenericCallView {
return { card: 'generic', kind: 'execute', title: `Stop dynamic Plugin ${args.pluginId}` }
}

View File

@@ -18,7 +18,11 @@ const EVENT_OUTPUT = {
} as const
const HOST_EVENTS = EVENT_API.filter(event => !event.name.startsWith('cordis/'))
/** Construct Host providers over generated Catalogs, evaluator declarations, and live Tool scope. */
/**
* Construct Host providers over generated Catalogs, evaluator declarations, and live Tool scope.
* @param ctx - Host context used for Agent-scoped live Tool queries.
* @returns registrations for static catalogs and live Host capabilities.
*/
export function hostInspectProviders(ctx: Context): HostCordisInspectProviderRegistration[] {
return [
registration(

View File

@@ -70,18 +70,15 @@
"@deepseek-ai/dsh-cordis-client-runner": "workspace:^",
"@deepseek-ai/dsh-client-locale": "workspace:^",
"@deepseek-ai/dsh-client-runtime": "workspace:^",
"@deepseek-ai/dsh-client-test-runtime": "workspace:^",
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
"@deepseek-ai/dsh-client-ui-sidebar": "workspace:^",
"@deepseek-ai/dsh-client-ui-slash": "workspace:^",
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
"@deepseek-ai/dsh-client-ui-tool": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@testing-library/react": "^16.1.0",
"@types/react": "~18.3.1",
"@deepseek-ai/cordis": "workspace:^",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react": "^18.2.0"
},
"files": [
"lib/index.js",

View File

@@ -41,17 +41,17 @@ export function CordisRunRow({
const loaded = useLoaded(snapshot => snapshot)
const latest = useRunCards(snapshot => snapshot)
const activeRuns = useActiveRuns(snapshot => snapshot)
const successful = card.state === 'ok'
const key = card.state === 'ok'
&& card.pluginId !== null
&& card.packageId !== null
&& card.pluginRunId !== null
&& card.seq !== null
const key = successful ? cordisToolViewKey(card.pluginId!, card.packageId!) : null
? cordisToolViewKey(card.pluginId, card.packageId)
: null
useEffect(() => {
if (!successful || key === null) return
onObserveRunCard({ key, callId, seq: card.seq!, pluginRunId: card.pluginRunId! })
}, [callId, card.pluginRunId, card.seq, key, onObserveRunCard, successful])
if (key === null || card.seq === null || card.pluginRunId === null) return
onObserveRunCard({ key, callId, seq: card.seq, pluginRunId: card.pluginRunId })
}, [callId, card.pluginRunId, card.seq, key, onObserveRunCard])
const row = card.pluginId === null
? undefined
@@ -72,15 +72,15 @@ export function CordisRunRow({
? 'superseded'
: awaitingApproval
? 'awaiting-approval'
: attempt?.status === 'failed'
? 'failed'
: row !== undefined && card.packageId !== null
? cordisVisibleStatus(row, card.packageId, loaded)
: 'idle'
: attempt?.status === 'failed'
? 'failed'
: row !== undefined && card.packageId !== null
? cordisVisibleStatus(row, card.packageId, loaded)
: 'idle'
const status = t(READING_LABELS[reading])
const summary = card.errorSummary
?? (card.pluginId === null ? callId : `${card.pluginId}${card.packageId === null ? '' : ` · ${card.packageId}`}`)
const showBusiness = successful && reading === 'running' && key !== null
const showBusiness = reading === 'running' && key !== null
return (
<div
@@ -116,13 +116,13 @@ export function CordisRunRow({
<div className={css.message}>{attempt.error.message}</div>
)}
{showBusiness && card.pluginId !== null && card.packageId !== null && card.pluginRunId !== null && (
<div className={css.business} data-cordis-business-view={key ?? undefined}>
<div className={css.business} data-cordis-business-view={key}>
{renderSlot('tool.view.cordis', {
pluginId: card.pluginId,
packageId: card.packageId,
pluginRunId: card.pluginRunId,
}, {
entryKey: key ?? undefined,
entryKey: key,
fallback: card.output === null ? null : <pre className={css.output}>{card.output}</pre>,
})}
</div>

View File

@@ -79,7 +79,11 @@ function metaObject(block: Block): Record<string, unknown> | null {
return block.meta as Record<string, unknown>
}
/** Derive one Define card from its frozen call/result slice. */
/**
* Derive one Define card from its frozen call/result slice.
* @param block - active or settled tool-call block.
* @returns normalized Define card fields.
*/
export function cordisDefineCard(block: Block): CordisDefineCard {
const settled = 'kind' in block
const argsRaw = (settled ? block.call?.argsRaw : block.argsRaw) ?? ''
@@ -102,7 +106,11 @@ export function cordisDefineCard(block: Block): CordisDefineCard {
}
}
/** Derive one Run card and its successful activation metadata. */
/**
* Derive one Run card and its successful activation metadata.
* @param block - active or settled tool-call block.
* @returns normalized Run card fields.
*/
export function cordisRunCard(block: Block): CordisRunCard {
const settled = 'kind' in block
const argsRaw = (settled ? block.call?.argsRaw : block.argsRaw) ?? ''

View File

@@ -126,7 +126,7 @@ export function apply(ctx: ClientContext): void {
const store = runCards.forSession(sessionId)
return {
hooks: { inventory, loaded, runCards: store, activeRuns: runner.activeRuns },
onObserveRunCard: pointer => { store.observe(pointer) },
onObserveRunCard: (pointer) => { store.observe(pointer) },
}
},
}, CordisRunRow))
@@ -139,7 +139,7 @@ export function apply(ctx: ClientContext): void {
order: 1,
candidates(session, { query }) {
const rows = rowsOf(session.sessionId, query)
return Promise.resolve(rows.map(row => {
return Promise.resolve(rows.map((row) => {
const packageId = row.nextPackageId ?? row.currentPackageId ?? row.packages.at(-1)?.packageId
const pkg = packageId === undefined ? undefined : row.packages.find(candidate => candidate.packageId === packageId)
return {

View File

@@ -2,6 +2,7 @@
export const NS = 'cordis'
/** Simplified Chinese Cordis UI messages. */
export const zh = {
'row.defineTitle': '注册 Cordis 插件',
'row.runTitle': '运行 Cordis 插件',
@@ -52,6 +53,7 @@ export const zh = {
'body.copied': '已复制',
} satisfies Record<string, string>
/** Translation keys owned by the Cordis UI namespace. */
export type CordisKey = keyof typeof zh
declare module '@deepseek-ai/dsh-client-ui-slots' {
@@ -61,6 +63,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
}
}
/** English Cordis UI messages. */
export const en = {
'row.defineTitle': 'Define Cordis Plugin',
'row.runTitle': 'Run Cordis Plugin',

View File

@@ -47,7 +47,11 @@ function createStore(): CordisRunCardStore {
export class CordisRunCardRegistry {
private readonly sessions = new Map<SessionId, CordisRunCardStore>()
/** Return the persistent page-local Store for a session. */
/**
* Return the persistent page-local Store for a session.
* @param sessionId - session whose cards share supersession state.
* @returns the page-local Store retained for that session.
*/
forSession(sessionId: SessionId): CordisRunCardStore {
let store = this.sessions.get(sessionId)
if (store === undefined) {
@@ -58,7 +62,12 @@ export class CordisRunCardRegistry {
}
}
/** Build the Package business-view key shared by registrations and Run cards. */
/**
* Build the Package business-view key shared by registrations and Run cards.
* @param pluginId - stable Plugin identity.
* @param packageId - immutable Package identity.
* @returns the shared business-view key.
*/
export function cordisToolViewKey(
pluginId: CordisDynamicPluginId,
packageId: CordisDynamicPackageId,

View File

@@ -8,8 +8,16 @@ import type {
/** The three product-visible lifecycle readings. */
export type CordisVisibleStatus = 'idle' | 'client-pending' | 'running'
/** Locate one immutable Package inside a Plugin row. */
export function packageOf(row: DynamicCordisInventoryRow, packageId: CordisDynamicPackageId) {
/**
* Locate one immutable Package inside a Plugin row.
* @param row - owning Plugin inventory row.
* @param packageId - immutable Package identity to locate.
* @returns the matching Package metadata, or `undefined` when absent.
*/
export function packageOf(
row: DynamicCordisInventoryRow,
packageId: CordisDynamicPackageId,
): DynamicCordisInventoryRow['packages'][number] | undefined {
return row.packages.find(pkg => pkg.packageId === packageId)
}

View File

@@ -74,6 +74,8 @@ export interface EventEntry {
/** One public service method and the source contract attached to it. */
export interface ServiceMethodEntry {
/** Compiler member category; policy-supplied methods may omit it. */
kind?: 'method' | 'property'
/** Public method signature (body stripped). */
signature: string
/** Original method JSDoc, dedented from its containing class. */
@@ -293,7 +295,7 @@ export class CordisCatalogProjector {
if (parsed.deprecated) continue
if (member.kind === 'property') {
if (member.jsDoc === undefined) continue
methods.push({ signature: member.text, jsDoc: member.jsDoc })
methods.push({ kind: 'property', signature: member.text, jsDoc: member.jsDoc })
continue
}
if (member.kind !== 'method') continue
@@ -301,7 +303,7 @@ export class CordisCatalogProjector {
if (this.face.face === 'host') {
checkTypeLinks(where, signatureTypeNames(this.renderer, member.signature), this.policy, typeLinkViolations)
}
methods.push({ signature: member.text, jsDoc: member.jsDoc ?? '' })
methods.push({ kind: 'method', signature: member.text, jsDoc: member.jsDoc ?? '' })
if (member.jsDoc === undefined) {
violations.push(`${where} has no JSDoc.`)
continue
@@ -357,6 +359,7 @@ export class CordisCatalogProjector {
* Analyze the host project once and return both the model and its projection.
* @param scanRoot - workspace root containing `tsconfig.host.json`.
* @param policy - caller-owned type classifications and inherited Cordis data.
* @param targetFace - Host or Client Typert face to project.
* @returns the configured projector and its validated catalog model.
*/
export function projectCordisCatalog(scanRoot: string, policy: CordisCatalogPolicy, targetFace: TypertFace = 'host'): {
@@ -617,6 +620,19 @@ function quote(value: string): string {
return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'").replaceAll('\n', '\\n')}'`
}
/** Render a compact TypeScript string-array literal. */
function quoteList(values: readonly string[]): string {
return `[${values.map(quote).join(', ')}]`
}
/** Render structured parameter documentation as a compact TypeScript literal. */
function renderParameters(parameters: ReadonlyMap<string, string>): string {
const values = [...parameters].map(([name, description]) => (
`{ name: ${quote(name)}, description: ${quote(description)} }`
))
return `[${values.join(', ')}]`
}
/** Resolve and sort the word-bounded transitive type closure referenced by seed text. */
function referencedTypes(
seeds: readonly string[],
@@ -752,9 +768,9 @@ function renderRuntimeApi(
lines.push(' {')
lines.push(` signature: ${quote(method.signature)},`)
lines.push(` description: ${quote(contract.doc)},`)
lines.push(` parameters: ${JSON.stringify([...contract.params].map(([name, description]) => ({ name, description })))},`)
lines.push(` parameters: ${renderParameters(contract.params)},`)
if (contract.returns !== null) lines.push(` returns: ${quote(contract.returns)},`)
if (contract.throws.length > 0) lines.push(` throws: ${JSON.stringify(contract.throws)},`)
if (contract.throws.length > 0) lines.push(` throws: ${quoteList(contract.throws)},`)
lines.push(' },')
}
lines.push(' ],')
@@ -775,7 +791,7 @@ function renderRuntimeApi(
lines.push(` signature: ${quote(event.signature)},`)
lines.push(` summary: ${quote(firstSentence(event.doc))},`)
lines.push(` description: ${quote(event.doc)},`)
lines.push(` parameters: ${JSON.stringify([...contract.params].map(([name, description]) => ({ name, description })))},`)
lines.push(` parameters: ${renderParameters(contract.params)},`)
lines.push(' },')
}
lines.push(
@@ -949,14 +965,15 @@ function renderService(s: ServiceEntry, onPage: string, linkedTypePages: Readonl
const kind = s.abstract ? ' (abstract seam)' : ''
const out = [...anchorFor(`ctx.${s.key}${s.type}${kind}`), `### \`ctx.${s.key}\`\`${s.type}\`${kind}`, '']
if (s.doc) out.push(s.doc, '')
if (s.methods.length) {
const declarations = s.methods.flatMap((method, index) => [
const methods = s.methods.filter(member => member.kind !== 'property')
if (methods.length) {
const declarations = methods.flatMap((method, index) => [
...(index > 0 ? [''] : []),
method.jsDoc,
method.signature,
])
out.push('```' + FENCE, ...declarations, '```', '')
const links = typeLinks(s.methods.map(method => method.signature).join('\n'), onPage, linkedTypePages)
const links = typeLinks(methods.map(method => method.signature).join('\n'), onPage, linkedTypePages)
if (links) out.push(links, '')
}
out.push(`Source: [\`${s.source}\`](../../${s.source.split(':')[0]})`, '')

View File

@@ -27,6 +27,7 @@
"@deepseek-ai/dsh-compaction": "workspace:^",
"@deepseek-ai/dsh-compaction-basic": "workspace:^",
"@deepseek-ai/dsh-compaction-tool-result-pruner": "workspace:^",
"@deepseek-ai/dsh-cordis-host-runner": "workspace:^",
"@deepseek-ai/dsh-credentials": "workspace:^",
"@deepseek-ai/dsh-launch-environment": "workspace:^",
"@deepseek-ai/dsh-fs": "workspace:^",

View File

@@ -63,6 +63,7 @@ export const SERVICE_PAGE: Record<string, string> = {
codeRuntime: 'code-runtime.md',
commands: 'commands.md',
compaction: 'compaction.md',
cordisInspect: 'self-modification.md',
credentials: 'credentials.md',
directoryPicker: 'workspace.md',
dynamicCordisRunner: 'self-modification.md',
@@ -149,6 +150,7 @@ export const SERVICE_WALK_EXEMPTIONS: Record<string, string> = {
remote: 'client-side interface-typed gateway accessor (ClientRemote) — packages/api/gateway/README.md owns the API',
sessionLogDownload: 'client-side browser download controller — packages/session-query/session-log-download/README.md owns the API',
inputTriggers: 'client-side interface-typed browser service — packages/client/ui-input-trigger/README.md owns the API',
timer: 'client-side dynamic-package timer service — packages/extensions/cordis-client-runner/README.md owns the API',
slots: 'client-side interface-typed browser service — packages/client/runtime/README.md owns the API',
theme: 'client-side interface-typed browser service — packages/client/ui-theme/README.md owns the API',
workspaces: 'client-side interface-typed browser service — packages/client/runtime/README.md owns the API',
@@ -584,7 +586,7 @@ export const CORDIS_CATALOG_POLICY: CordisCatalogPolicy = {
linkedTypePages: LINK_MAP,
foundationTypeNames: FOUNDATION_TYPE_NAMES,
typeLinkExemptions: TYPE_LINK_EXEMPTIONS,
runtimeServiceExclusions: new Set(['dynamicCordisRunner']),
runtimeServiceExclusions: new Set(['cordisInspect', 'dynamicCordisRunner']),
runtimeServices: [{
key: 'timer',
type: 'TimerService',

View File

@@ -555,6 +555,14 @@ const SERVICE_ROLES: ServiceRole[] = [
consumers: ['tool-cordis'],
note: 'Owns the in-memory definition registry, the vm sandbox for host halves, and the request-run round trip; browser pages reach the same service over the wire through its remote namespace.',
},
{
key: 'cordisInspect',
pkg: 'cordis-host-runner',
title: 'Dynamic Cordis inspect registry',
mode: 'core',
consumers: ['tool-cordis'],
note: 'Registers host inspect providers, mirrors the client provider manifest, and routes client queries through the dynamic Cordis transport.',
},
]
function generatedHeader(title: string): string[] {