website: fix nine review findings (generator coverage, loader facts, mode semantics)

Generator (all four structural gaps):
- harness service pages now render public properties/accessors, not just
  methods (ctx.codeRuntime.language/isolation were missing);
- the class page merges the same-named interface half, so ctx.root/baseUrl/
  events/logger/reflect/registry appear on Context (vendor root JSDoc gains
  prose alongside @experimental);
- Pick<…> heritage on a Context merge resolves to the picked class members,
  giving ctx.effect a documented signature on the Fiber page;
- {@link} tags normalize to code spans; merge sections get their own h2 so
  reflect members no longer nest under 'Static members'.

verify-website-yaml: reject the unloadable 'group:' pseudo-name (tree.import
only special-cases 'cordis:'; no builtin is registered here) and recurse into
@cordisjs/plugin-group nested entry lists instead.

Prose corrected against loader/cordis source: service.md isolation example
uses the real group plugin + group: true + the required isolate map;
config.md documents concurrent entry startup (Promise.all; order via inject)
and the real hmr defaults (root ['.'], base/ignored/debounce); events.md
fixes emit (synchronous, not parallel), bail (null/false also delegate), and
serial (stops at the first bail value).
This commit is contained in:
lintianle
2026-07-16 21:15:44 +08:00
parent 4c49677469
commit fcd9d8c391
16 changed files with 230 additions and 42 deletions

View File

@@ -57,6 +57,66 @@ Plugins loaded under the returned context see `config` merged into the service's
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/vendor/cordis/src/context.ts#L139)
### ctx.root
```ts website-api
root: this
```
The root context of the application (every child context shares it). @experimental
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/vendor/cordis/src/context.ts#L22)
### ctx.baseUrl
```ts website-api
baseUrl?: string
```
Base URL used to resolve relative plugin/module specifiers, if the runtime sets one.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/vendor/cordis/src/context.ts#L24)
### ctx.events
```ts website-api
events: EventsService
```
The event bus. Its methods are also mixed onto `ctx` (`ctx.on`, `ctx.emit`, ...).
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/vendor/cordis/src/context.ts#L26)
### ctx.logger
```ts website-api
logger: LoggerService
```
The logging service. Call `ctx.logger(name)` for a named logger.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/vendor/cordis/src/context.ts#L28)
### ctx.reflect
```ts website-api
reflect: ReflectService
```
The reflection layer backing the context proxy (`ctx.get`, `ctx.provide`, ...).
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/vendor/cordis/src/context.ts#L30)
### ctx.registry
```ts website-api
registry: RegistryService
```
The plugin registry. Its methods are mixed onto `ctx` (`ctx.plugin`, `ctx.inject`).
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/vendor/cordis/src/context.ts#L32)
## Static members
### Context.effect
@@ -114,6 +174,8 @@ Works across realms and across multiple copies of cordis, because the brand is k
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/vendor/cordis/src/context.ts#L61)
## Service store and mixins
### ctx.get(name, strict?)
```ts website-api

View File

@@ -4,6 +4,23 @@
A fiber is one loaded plugin instance: its lifecycle state, validated config, and registered effects. `ctx.fiber` is the current fiber; `ctx.effect()` delegates to it.
### ctx.effect(execute, label?)
```ts website-api
effect(execute: () => SyncEffect, label?: string): Disposable<Promise<void>>
effect(execute: () => Effect, label?: string): AsyncDisposable<Promise<void>>
```
Register a cleanup-aware effect on this fiber.
`execute` runs immediately; the disposers it produces are collected and run (in reverse order) either when the returned disposer is called or when the fiber unloads, whichever comes first. Calling the disposer twice is a no-op. Throws `CordisError('INACTIVE_EFFECT')` if the fiber is already disposed, and `TypeError` if `execute` returns an invalid shape.
- `execute` — the effect body; see `Effect` for accepted shapes.
- `label` — effect label shown in `getEffects()` diagnostics.
**Returns** a disposer that tears the effect down and settles once done.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/vendor/cordis/src/fiber.ts#L363)
### ctx.fiber
```ts website-api
@@ -14,6 +31,8 @@ The fiber (plugin runtime instance) that owns this context.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/vendor/cordis/src/fiber.ts#L11)
## The Fiber class
Runtime instance of one plugin application.
A fiber tracks dependency state, validated config, lifecycle effects, and cleanup for the plugin context returned by `ctx.plugin()`.
@@ -121,7 +140,7 @@ effect(execute: () => Effect, label?: string): AsyncDisposable<Promise<void>>
Register a cleanup-aware effect on this fiber.
`execute` runs immediately; the disposers it produces are collected and run (in reverse order) either when the returned disposer is called or when the fiber unloads, whichever comes first. Calling the disposer twice is a no-op. Throws `CordisError('INACTIVE_EFFECT')` if the fiber is already disposed, and `TypeError` if `execute` returns an invalid shape.
- `execute` — the effect body; see {@link Effect} for accepted shapes.
- `execute` — the effect body; see `Effect` for accepted shapes.
- `label` — effect label shown in `getEffects()` diagnostics.
**Returns** a disposer that tears the effect down and settles once done.
@@ -136,7 +155,7 @@ getEffects()
Return metadata for currently registered effects.
**Returns** one {@link EffectMeta} tree per labeled live effect.
**Returns** one `EffectMeta` tree per labeled live effect.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/vendor/cordis/src/fiber.ts#L436)

View File

@@ -16,7 +16,7 @@ setFactory(factory: AgentFactory): () => void
Register the agent-creation factory (the loop calls this on construction, effect-scoped). Throws if a factory is already registered. Returns the disposer; on dispose the factory slot is cleared.
- `factory` — the loop-owned factory {@link create}/{@link resume} delegate to.
- `factory` — the loop-owned factory `create`/`resume` delegate to.
**Returns** the disposer that clears the factory slot.

View File

@@ -23,7 +23,7 @@ Resolve a caller's BashExecRequest into a fully-specified BashExecSpec, applying
- `request` — the caller's request; omitted fields get this implementation's defaults, capped fields are clamped.
**Returns** the fully-specified spec to hand to {@link run}/{@link start}.
**Returns** the fully-specified spec to hand to `run`/`start`.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/bash/bash/src/index.ts#L84)
@@ -35,7 +35,7 @@ abstract run(spec: BashExecSpec): Promise<BashRunResult>
Run a command in the foreground; resolves when it finishes.
- `spec` — a resolved spec from {@link resolve}, never a raw request.
- `spec` — a resolved spec from `resolve`, never a raw request.
**Returns** the outcome; nonzero exits, timeout kills, and abort kills resolve with a descriptive result rather than reject.
@@ -49,9 +49,9 @@ abstract start(spec: BashExecSpec): BashTask
Start a background task and return its handle immediately.
- `spec` — a resolved spec from {@link resolve}, never a raw request.
- `spec` — a resolved spec from `resolve`, never a raw request.
**Returns** the live task handle; completion fires {@link onTaskDone}.
**Returns** the live task handle; completion fires `onTaskDone`.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/bash/bash/src/index.ts#L99)

View File

@@ -13,6 +13,26 @@ Semantics every implementation must honor:
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/code-runtime/code-runtime/src/index.ts#L59)
### ctx.codeRuntime.language
```ts website-api
abstract readonly language: string
```
The source language run expects `program` to be written in, as a lowercase identifier. Informational, not gating — a consumer that generates language-specific presentation (typed SDK stubs, usage instructions) switches on it and fails loud on a language it cannot present. Well-known value: `'typescript'`.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/code-runtime/code-runtime/src/index.ts#L67)
### ctx.codeRuntime.isolation
```ts website-api
abstract readonly isolation: string
```
The execution substrate, as a lowercase identifier. Informational, not gating — a descriptor so deployments and diagnostics can tell backends apart, not a security claim. Well-known values: `'worker-thread'`, `'process'`, `'container'`.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/code-runtime/code-runtime/src/index.ts#L75)
### ctx.codeRuntime.run(request)
```ts website-api

View File

@@ -381,7 +381,7 @@ A subagent run started — emitted after the provider is resolved and its capabi
Waterfall around prompt assembly — mutate or extend the PromptAssembly (sections + tools + variables) before it is rendered. Bound to the SystemPrompt service; call `next()` to delegate.
- `assembly` — the assembly built from the registered sections, tool providers, and variable providers; listeners may mutate it or return a replacement.
- `context` — the per-assembly {@link AssembleContext} the caller passed to {@link SystemPrompt.assemble} (e.g. which agent the prompt is for), so a listener can filter or extend per agent.
- `context` — the per-assembly `AssembleContext` the caller passed to `SystemPrompt.assemble` (e.g. which agent the prompt is for), so a listener can filter or extend per agent.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/system-prompt/src/index.ts#L38)
@@ -497,7 +497,7 @@ One `agent()` call started a child run. Paired with Events['workflow/agent-end']
A workflow run settled (any stop reason). Fired when WorkflowRun.result resolves. Paired with Events['workflow/start'].
- `info` — the run's identity snapshot.
- `result` — the outcome data (stop reason, error, agent count) — deliberately WITHOUT the result value (see {@link WorkflowResultInfo}).
- `result` — the outcome data (stop reason, error, agent count) — deliberately WITHOUT the result value (see `WorkflowResultInfo`).
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/workflow/workflow/src/index.ts#L106)

View File

@@ -72,7 +72,7 @@ Stream the whole regular text file as decoded text chunks (same text semantics a
- `target` — the resolved target to read.
- `signal` — aborts the stream, including between chunks.
**Returns** the chunk iterable, decoded and validated like {@link readText}.
**Returns** the chunk iterable, decoded and validated like `readText`.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/fs/fs/src/index.ts#L221)

View File

@@ -49,7 +49,7 @@ enter(session: Session): () => void
Enter a prepared session into the store: wire `onAppend` → `session/event` and add it to the store. Returns the DETACH disposer (`onAppend = undefined` + store removal). Does NOT emit `session/created` — the caller yields this disposer inside its effect and THEN calls announce, so a throwing `session/created` listener rolls the attach back instead of leaking it.
Re-checks the id for a duplicate: `prepare` and `enter` are public cross-package primitives and a caller may interleave arbitrary work (or another create) between them, so a stale prepared session must NOT overwrite a live store entry of the same id — its detach disposer would later delete the REAL session. The create convenience and the agent factory call the two back-to-back so they never trip this, but the public seam cannot assume that.
- `session` — a {@link prepare}d session not yet in the store.
- `session` — a `prepare`d session not yet in the store.
**Returns** the detach disposer (`onAppend = undefined` + store removal).

View File

@@ -30,7 +30,7 @@ tools(provider: () => ToolSchema[]): () => void
Contribute a tool-schema provider that is evaluated at each assembly call (so it can reflect the live registry state). The provider is removed when the calling fiber is disposed. A provider must not return a schema named TOOL_ORDER_REST; that name is reserved for Config.toolOrder's rest entry and rejects the assembly. Emits `system-prompt/change`.
- `provider` — evaluated at every {@link assemble} for fresh schemas.
- `provider` — evaluated at every `assemble` for fresh schemas.
**Returns** the disposer that removes the provider.
@@ -45,7 +45,7 @@ variable(name: string, provider: (context: AssembleContext) => string | undefine
Contribute a named prompt variable, referenced from section text as `{{name}}`. The provider is evaluated at each assembly with that assembly's AssembleContext; returning `undefined` means "no value for this assembly" (a section referencing it then fails to render — a deployment must not claim facts it does not have). Throws on a name that does not match `[a-z][a-z0-9_]*` (it could never be referenced) or is already registered. Removed when the calling fiber is disposed; emits `system-prompt/change` on register/unregister.
- `name` — the reference name (matches `[a-z][a-z0-9_]*`).
- `provider` — evaluated at every {@link assemble} for the value.
- `provider` — evaluated at every `assemble` for the value.
**Returns** the disposer that removes the variable.
@@ -59,7 +59,7 @@ async assemble(context: AssembleContext = {}): Promise<PromptAssembly>
Assemble the current prompt for one caller: section texts are resolved against `context` and sorted by order, tools collected from all providers and put in the canonical model-facing order (Config.toolOrder, or lexicographic name order when unconfigured — provider registration order is a plugin-load artifact and never reaches the assembly; a configured order naming a tool no provider contributed rejects the assembly), and every registered variable resolved against `context` into `assembly.variables`. Tool schemas are deep-cloned because adapters and request waterfalls may mutate schema objects. Runs through the `system-prompt/assemble` waterfall, giving listeners the opportunity to mutate or replace the assembly before it reaches the model — like the sections' `order` sort, tool canonicalization happens on the initial assembly, and a listener owns the determinism of whatever it emits. Await the result before reading the assembly values — waterfall listeners may be async. Interpolation happens later, in renderPrompt.
- `context` — what this assembly is for (defaults to an empty context; see {@link AssembleContext}).
- `context` — what this assembly is for (defaults to an empty context; see `AssembleContext`).
**Returns** the assembly after the waterfall has run.

View File

@@ -45,7 +45,7 @@ Cordis 提供多种事件触发模式,适用于不同场景:
### emit — 广播
所有监听器并行执行,不关心返回值
同步依次调用所有监听器,不等待、不关心返回值(监听器如果是 async其 Promise 被忽略)
```ts
import type { Context } from 'cordis'
@@ -71,7 +71,7 @@ ctx.on('my-plugin/turn-end', (agentId, turnIndex) => {
### bail — 短路
依次调用监听器,第一个返回非 `undefined` 值的结果作为最终值
同步依次调用监听器,第一个返回**`undefined`/`null`/`false`** 值的监听器终止链并作为最终值(返回 `undefined`/`null`/`false` 则继续下一个)
```ts
import type { Context } from 'cordis'
@@ -99,7 +99,7 @@ ctx.on('some-check', (input) => {
### serial — 顺序执行
所有监听器按注册顺序依次执行(异步安全)
按注册顺序逐个 `await` 监听器,遇到第一个 bail 值(非 `undefined`/`null`/`false`)即停止并返回它;全部返回空值则执行到底。相当于 `bail` 的异步版
```ts
import type { Context } from 'cordis'

View File

@@ -132,11 +132,14 @@ export function apply(ctx: Context) {
## 服务隔离
`cordis.yml` 支持服务隔离——同一个服务可以有多个实例,不同插件组看到不同实例:
`cordis.yml` 支持服务隔离——同一个服务可以有多个实例,不同插件组看到不同实例。用 `@cordisjs/plugin-group` 建组(`group: true` 标记组条目),并在组上声明 `isolate`,把该服务隔离进组内作用域
```yaml
- id: group-a
name: 'group:'
name: '@cordisjs/plugin-group'
group: true
isolate:
bash: true
config:
- name: '@deepseek-ai/dsh-bash-local'
config:
@@ -144,7 +147,10 @@ export function apply(ctx: Context) {
- name: './src/plugin-a.ts'
- id: group-b
name: 'group:'
name: '@cordisjs/plugin-group'
group: true
isolate:
bash: true
config:
- name: '@deepseek-ai/dsh-bash-local'
config:
@@ -152,7 +158,7 @@ export function apply(ctx: Context) {
- name: './src/plugin-b.ts'
```
`plugin-a``plugin-b` 各自看到自己组内的 bash 实例,互不影响。
`plugin-a``plugin-b` 各自看到自己组内的 bash 实例,互不影响。`isolate: { bash: true }` 是必需的:不隔离的话,两个组在同一作用域注册同名服务,第二个会直接报重复注册错误。
## Harness 内置服务一览

View File

@@ -332,7 +332,12 @@ config:
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `root` | string[] | **必填** | 监听文件变更的目录列表 |
| `root` | string[] | `['.']` | 监听文件变更的目录列表 |
| `base` | string | — | 解析 `root` 的基准目录(默认取配置文件所在目录) |
| `ignored` | string[] | `['**/node_modules', '**/.*', 'cache', 'data']` | 忽略的 glob 列表 |
| `debounce` | number | `100` | 变更合并窗口(毫秒) |
其余字段透传给 chokidar`Config` 继承 `ChokidarOptions`)。
::: tip
hmr 仅用于开发环境。它需要 `node --expose-internals` 启动参数,`demo:*` 脚本已自动添加。
@@ -342,7 +347,9 @@ hmr 仅用于开发环境。它需要 `node --expose-internals` 启动参数,`
## 加载顺序
`cordis.yml`顺序就是加载顺序。推荐:
`cordis.yml`条目是**并发启动**的loader 对全部条目 `Promise.all`),文件顺序不决定加载顺序。真正的先后关系由依赖协调:插件声明的 `inject` 服务就绪之前,插件不会启动;服务出现后自动继续。所以**不要依赖书写顺序传递时序**——需要"先有 A 再有 B"就让 B `inject` A 提供的服务。
文件顺序只是给人读的。推荐按角色分组书写:
1. **hmr** — 热替换(仅开发时需要)
2. **LLM 适配器** — 模型后端