From fcd9d8c391e5eef98933ec3a77db69c7f9402faf Mon Sep 17 00:00:00 2001 From: lintianle Date: Thu, 16 Jul 2026 21:15:44 +0800 Subject: [PATCH] website: fix nine review findings (generator coverage, loader facts, mode semantics) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- scripts/gen-website-api.ts | 99 ++++++++++++++++++---- scripts/verify-website-yaml.ts | 9 +- vendor/README.md | 2 +- vendor/cordis/src/context.ts | 2 +- website/zh-CN/api/cordis/context.md | 62 ++++++++++++++ website/zh-CN/api/cordis/fiber.md | 23 ++++- website/zh-CN/api/harness/agents.md | 2 +- website/zh-CN/api/harness/bash.md | 8 +- website/zh-CN/api/harness/code-runtime.md | 20 +++++ website/zh-CN/api/harness/events.md | 4 +- website/zh-CN/api/harness/fs.md | 2 +- website/zh-CN/api/harness/sessions.md | 2 +- website/zh-CN/api/harness/system-prompt.md | 6 +- website/zh-CN/develop/framework/events.md | 6 +- website/zh-CN/develop/framework/service.md | 14 ++- website/zh-CN/guide/config.md | 11 ++- 16 files changed, 230 insertions(+), 42 deletions(-) diff --git a/scripts/gen-website-api.ts b/scripts/gen-website-api.ts index 2d74f075e6..cb7ddfd8da 100644 --- a/scripts/gen-website-api.ts +++ b/scripts/gen-website-api.ts @@ -70,8 +70,8 @@ interface MemberDoc { /** A cordis-page section: which declarations it renders. */ type Section = - | { kind: 'class'; file: string; symbol: string; prefix?: string } - | { kind: 'context-merge'; file: string } + | { kind: 'class'; file: string; symbol: string; prefix?: string; heading?: string } + | { kind: 'context-merge'; file: string; heading?: string } | { kind: 'decl'; file: string; symbol: string } /** One generated cordis page. */ @@ -95,7 +95,7 @@ const CORDIS_PAGES: CordisPage[] = [ intro: 'The context is the core cordis object: every service, event, and lifecycle API is reached through `ctx`. Event methods (`ctx.on`, `ctx.emit`, …) are documented on [Events](./events.md); `ctx.effect` and `ctx.fiber` on [Fiber](./fiber.md); `ctx.plugin` and `ctx.inject` on [Registry](./registry.md).', sections: [ { kind: 'class', file: 'vendor/cordis/src/context.ts', symbol: 'Context', prefix: 'ctx.' }, - { kind: 'context-merge', file: 'vendor/cordis/src/reflect.ts' }, + { kind: 'context-merge', file: 'vendor/cordis/src/reflect.ts', heading: 'Service store and mixins' }, ], }, { @@ -114,7 +114,7 @@ const CORDIS_PAGES: CordisPage[] = [ intro: '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.', sections: [ { kind: 'context-merge', file: 'vendor/cordis/src/fiber.ts' }, - { kind: 'class', file: 'vendor/cordis/src/fiber.ts', symbol: 'Fiber' }, + { kind: 'class', file: 'vendor/cordis/src/fiber.ts', symbol: 'Fiber', heading: 'The Fiber class' }, { kind: 'decl', file: 'vendor/cordis/src/fiber.ts', symbol: 'Effect' }, { kind: 'decl', file: 'vendor/cordis/src/fiber.ts', symbol: 'Disposable' }, { kind: 'decl', file: 'vendor/cordis/src/fiber.ts', symbol: 'EffectMeta' }, @@ -265,14 +265,53 @@ function memberDoc( } } -/** Members of the `interface Context` merge in `rel`, overloads grouped. */ +/** Resolve an `extends Pick` heritage clause on the Context + * merge to the named members of `Class` declared in the same file — the fiber + * merge (`interface Context extends Pick`) is the motivating + * case: without this, `ctx.effect` had no documented signature anywhere. */ +function heritageMembers( + stmt: ts.InterfaceDeclaration, + sf: ts.SourceFile, + groups: Map, +): void { + for (const clause of stmt.heritageClauses ?? []) { + for (const type of clause.types) { + if (!ts.isIdentifier(type.expression) || type.expression.text !== 'Pick') continue + const [target, keys] = type.typeArguments ?? [] + if (!target || !keys || !ts.isTypeReferenceNode(target)) continue + const targetName = target.typeName.getText(sf) + const cls = sf.statements.find( + (s): s is ts.ClassDeclaration => ts.isClassDeclaration(s) && s.name?.text === targetName, + ) + if (!cls) continue + const picked = new Set() + const collect = (node: ts.TypeNode): void => { + if (ts.isLiteralTypeNode(node) && ts.isStringLiteral(node.literal)) picked.add(node.literal.text) + if (ts.isUnionTypeNode(node)) node.types.forEach(collect) + } + collect(keys) + for (const member of cls.members) { + if (!ts.isMethodDeclaration(member)) continue + const name = member.name.getText(sf) + if (!picked.has(name)) continue + const group = groups.get(name) ?? [] + group.push(member) + groups.set(name, group) + } + } + } +} + +/** Members of the `interface Context` merge in `rel`, overloads grouped; + * `Pick<…>` heritage resolved to the picked class members. */ function contextMergeMembers(rel: string, violations: string[]): MemberDoc[] { const { sf } = load(rel) const body = moduleBody(sf) if (!body) throw new Error(`gen-website-api: ${rel} has no context module merge`) - const groups = new Map() + const groups = new Map() for (const stmt of body.statements) { if (!ts.isInterfaceDeclaration(stmt) || stmt.name.text !== 'Context') continue + heritageMembers(stmt, sf, groups) for (const member of stmt.members) { if (!ts.isMethodSignature(member) && !ts.isPropertySignature(member)) continue if (ts.isComputedPropertyName(member.name)) continue @@ -286,7 +325,10 @@ function contextMergeMembers(rel: string, violations: string[]): MemberDoc[] { memberDoc(`ctx.${name} (${rel})`, name, group, rel, violations)) } -/** Instance + static members of one class, as two rendered lists. */ +/** Instance + static members of one class, as two rendered lists. The class's + * same-named top-level interface half (declaration merging — vendor Context + * declares `root`/`events`/`logger`/… on the interface) is folded into the + * instance list, so neither half of a merged symbol goes undocumented. */ function classMembers(rel: string, className: string, violations: string[]): { doc: string instance: MemberDoc[] @@ -300,7 +342,8 @@ function classMembers(rel: string, className: string, violations: string[]): { if (!cls) throw new Error(`gen-website-api: class ${className} not found in ${rel}`) const clsDoc = parseJsDoc(rawJsDoc(text, cls)).doc if (!clsDoc) violations.push(`class ${className} (${pointer(rel, sf, cls)}) has no JSDoc.`) - const instance = new Map() + type Renderable = ts.MethodDeclaration | ts.PropertyDeclaration | ts.GetAccessorDeclaration | ts.PropertySignature + const instance = new Map() const statics = new Map() for (const member of cls.members) { const renderable = ts.isMethodDeclaration(member) || ts.isPropertyDeclaration(member) || ts.isGetAccessorDeclaration(member) @@ -316,7 +359,17 @@ function classMembers(rel: string, className: string, violations: string[]): { statics.set(name, group) } } - type Renderable = ts.MethodDeclaration | ts.PropertyDeclaration | ts.GetAccessorDeclaration + const iface = sf.statements.find( + (s): s is ts.InterfaceDeclaration => ts.isInterfaceDeclaration(s) && s.name.text === className, + ) + for (const member of iface?.members ?? []) { + if (!ts.isPropertySignature(member)) continue + if (ts.isComputedPropertyName(member.name)) continue + const name = member.name.getText(sf) + const group = instance.get(name) ?? [] + group.push(member) + instance.set(name, group) + } const toDocs = (groups: Map, prefix: string): MemberDoc[] => [...groups.entries()].map(([name, group]) => memberDoc(`${prefix}${name} (${rel})`, name, group, rel, violations)) @@ -417,9 +470,12 @@ function collectHarnessServices(violations: string[]): HarnessService[] { const abstract = cls.modifiers?.some(m => m.kind === ts.SyntaxKind.AbstractKeyword) ?? false const clsDoc = parseJsDoc(rawJsDoc(text, cls)).doc if (!clsDoc) violations.push(`service ctx.${key} (${pointer(rel, sf, cls)}): class ${type} has no JSDoc.`) - const groups = new Map() + const groups = new Map() for (const member of cls.members) { - if (!ts.isMethodDeclaration(member)) continue + // Public properties are API too: ctx.codeRuntime.language/isolation + // are readonly descriptors consumers key presentation off. + const renderable = ts.isMethodDeclaration(member) || ts.isPropertyDeclaration(member) || ts.isGetAccessorDeclaration(member) + if (!renderable) continue if (!isPublicInstance(member)) continue const name = member.name.getText(sf) const group = groups.get(name) ?? [] @@ -492,9 +548,19 @@ function sourceLink(source: string): string { return `[Source](${GITHUB}/${file}#L${line})` } -/** Render prose paragraphs (one per line of `doc`). */ +/** Normalize JSDoc inline `{@link X}` / `{@link X|label}` / `{@link X label}` + * tags to plain Markdown code spans — left verbatim they leak into the built + * page as literal `{@link …}` text. */ +function unlink(text: string): string { + return text.replace(/\{@link\s+([^}|\s]+)\s*(?:[|\s]\s*([^}]*))?\}/g, (_m, target: string, label?: string) => { + const name = label?.trim() + return name && name !== '' ? name : `\`${target}\`` + }) +} + +/** Render prose paragraphs (one per line of `doc`), JSDoc links normalized. */ function prose(doc: string): string[] { - return doc.split('\n').filter(l => l.trim() !== '') + return unlink(doc).split('\n').filter(l => l.trim() !== '') } /** Render one member section at heading depth 3. */ @@ -507,10 +573,10 @@ function renderMember(prefix: string, m: MemberDoc): string[] { lines.push('```', '') lines.push(...prose(m.doc), '') if (m.params.length > 0) { - for (const p of m.params) lines.push(`- \`${p.name}\` — ${p.text}`) + for (const p of m.params) lines.push(`- \`${p.name}\` — ${unlink(p.text)}`) lines.push('') } - if (m.returns) lines.push(`**Returns** ${m.returns}`, '') + if (m.returns) lines.push(`**Returns** ${unlink(m.returns)}`, '') lines.push(sourceLink(m.source), '') return lines } @@ -519,6 +585,7 @@ function renderMember(prefix: string, m: MemberDoc): string[] { function renderCordisPage(page: CordisPage, violations: string[]): string { const lines: string[] = [BANNER, '', `# ${page.title}`, '', page.intro, ''] for (const section of page.sections) { + if (section.kind !== 'decl' && section.heading) lines.push(`## ${section.heading}`, '') if (section.kind === 'context-merge') { for (const m of contextMergeMembers(section.file, violations)) { lines.push(...renderMember('ctx.', m)) @@ -577,7 +644,7 @@ function renderEventsPage(events: HarnessEvent[]): string { lines.push('```' + FENCE, e.signature, '```', '') lines.push(...prose(e.doc), '') if (e.params.length > 0) { - for (const p of e.params) lines.push(`- \`${p.name}\` — ${p.text}`) + for (const p of e.params) lines.push(`- \`${p.name}\` — ${unlink(p.text)}`) lines.push('') } lines.push(sourceLink(e.source), '') diff --git a/scripts/verify-website-yaml.ts b/scripts/verify-website-yaml.ts index 809af247e2..0f46b6a5aa 100644 --- a/scripts/verify-website-yaml.ts +++ b/scripts/verify-website-yaml.ts @@ -201,8 +201,15 @@ function checkEntryList( } // Illustrative local plugin — nothing on disk to check against. if (name.startsWith('./') || name.startsWith('../')) return - // Loader built-in group: its config is a nested entry list. + // A `group:`-style pseudo-name is NOT loadable: tree.import() only + // special-cases the `cordis:` prefix, and nothing in this repo registers + // loader builtins — reject it and point at the real group plugin. if (name.startsWith('group:')) { + flag(`${at}: \`${name}\` is not loadable (no loader builtin is registered); use \`@cordisjs/plugin-group\` with \`group: true\``) + return + } + // The vendored group plugin: its config is a nested entry list. + if (name === '@cordisjs/plugin-group') { if (Array.isArray(entry['config'])) checkEntryList(entry['config'], known, block, violations) return } diff --git a/vendor/README.md b/vendor/README.md index 8487ab1086..1523b481a3 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -35,7 +35,7 @@ Keep this log exhaustive — every divergence from upstream must be listed. 3. **All `tsconfig.json` files**: regenerated to extend the repo-root `tsconfig.base.json`, emit TypeScript intermediates to `lib/types`, and declare project references. 4. **Vendored TypeScript source internal specifiers**: changed local relative imports/exports from upstream's specifier shape to explicit `.ts` specifiers so TypeScript rewrites emitted JS to `.js` while declarations keep explicit, NodeNext-safe `.ts` specifiers. This includes `loader/src/config/isolate.ts` using `declare module './entry.ts'`. 5. **`schemastery/tsdown.config.ts` and `logger-console/tsdown.config.ts`**: ours, not upstream files — per-package build-shape overrides (dual ESM+CJS output; separate node/browser entries) for the repo-root tsdown build. They read the JS emitted under `lib/types` and then write the publish runtime entries under `lib/`. Like the regenerated tsconfigs, they are not part of the upstream sync surface. -6. **`cordis/src/*.ts` JSDoc enrichment**: added `@param`/`@returns` tags and contract documentation (disposal semantics, waterfall veto, bail conditions, error cases) across the public plugin-author surface — `Context`, `EventsService`, `Fiber`, `RegistryService`, `ReflectService`, `Service`, `LoggerService` and their `declare module './context.ts'` overloads. Comment-only; no code changes. Motivation: the website API-reference generator renders these docs and hard-errors on undocumented members. Retire this entry when the enrichment is upstreamed to the fork. +6. **`cordis/src/*.ts` JSDoc enrichment**: added `@param`/`@returns` tags and contract documentation (disposal semantics, waterfall veto, bail conditions, error cases) across the public plugin-author surface — `Context` (class, statics, and the `Context` interface properties incl. `root`), `EventsService`, `Fiber`, `RegistryService`, `ReflectService`, `Service`, `LoggerService` and their `declare module './context.ts'` overloads. Comment-only; no code changes. Motivation: the website API-reference generator renders these docs and hard-errors on undocumented members. Retire this entry when the enrichment is upstreamed to the fork. ## Sync procedure diff --git a/vendor/cordis/src/context.ts b/vendor/cordis/src/context.ts index b34b575cb9..514b1e062a 100644 --- a/vendor/cordis/src/context.ts +++ b/vendor/cordis/src/context.ts @@ -18,7 +18,7 @@ export interface Context { [symbols.isolate]: Dict /** Intercept map: service name → config merged into that service's per-plugin config. */ [symbols.intercept]: Dict - /** @experimental */ + /** The root context of the application (every child context shares it). @experimental */ root: this /** Base URL used to resolve relative plugin/module specifiers, if the runtime sets one. */ baseUrl?: string diff --git a/website/zh-CN/api/cordis/context.md b/website/zh-CN/api/cordis/context.md index f8ef268f03..cc08f4e28a 100644 --- a/website/zh-CN/api/cordis/context.md +++ b/website/zh-CN/api/cordis/context.md @@ -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 diff --git a/website/zh-CN/api/cordis/fiber.md b/website/zh-CN/api/cordis/fiber.md index 360986350b..76e0da6101 100644 --- a/website/zh-CN/api/cordis/fiber.md +++ b/website/zh-CN/api/cordis/fiber.md @@ -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> +effect(execute: () => Effect, label?: string): AsyncDisposable> +``` + +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> 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) diff --git a/website/zh-CN/api/harness/agents.md b/website/zh-CN/api/harness/agents.md index 9ce78bcf14..86b6a3d7d1 100644 --- a/website/zh-CN/api/harness/agents.md +++ b/website/zh-CN/api/harness/agents.md @@ -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. diff --git a/website/zh-CN/api/harness/bash.md b/website/zh-CN/api/harness/bash.md index a18d5e5a0d..9044573f13 100644 --- a/website/zh-CN/api/harness/bash.md +++ b/website/zh-CN/api/harness/bash.md @@ -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 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) diff --git a/website/zh-CN/api/harness/code-runtime.md b/website/zh-CN/api/harness/code-runtime.md index 9ce642b1ab..226647765d 100644 --- a/website/zh-CN/api/harness/code-runtime.md +++ b/website/zh-CN/api/harness/code-runtime.md @@ -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 diff --git a/website/zh-CN/api/harness/events.md b/website/zh-CN/api/harness/events.md index 9aa3d3b791..15bbacfd57 100644 --- a/website/zh-CN/api/harness/events.md +++ b/website/zh-CN/api/harness/events.md @@ -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) diff --git a/website/zh-CN/api/harness/fs.md b/website/zh-CN/api/harness/fs.md index 911bda4db2..2e3749902c 100644 --- a/website/zh-CN/api/harness/fs.md +++ b/website/zh-CN/api/harness/fs.md @@ -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) diff --git a/website/zh-CN/api/harness/sessions.md b/website/zh-CN/api/harness/sessions.md index d0df73e846..78c1b682c2 100644 --- a/website/zh-CN/api/harness/sessions.md +++ b/website/zh-CN/api/harness/sessions.md @@ -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). diff --git a/website/zh-CN/api/harness/system-prompt.md b/website/zh-CN/api/harness/system-prompt.md index 2016285739..5f9bf613c3 100644 --- a/website/zh-CN/api/harness/system-prompt.md +++ b/website/zh-CN/api/harness/system-prompt.md @@ -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 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. diff --git a/website/zh-CN/develop/framework/events.md b/website/zh-CN/develop/framework/events.md index d2ddcf17a2..d043dc0cb8 100644 --- a/website/zh-CN/develop/framework/events.md +++ b/website/zh-CN/develop/framework/events.md @@ -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' diff --git a/website/zh-CN/develop/framework/service.md b/website/zh-CN/develop/framework/service.md index 7508d02675..b935c905df 100644 --- a/website/zh-CN/develop/framework/service.md +++ b/website/zh-CN/develop/framework/service.md @@ -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 内置服务一览 diff --git a/website/zh-CN/guide/config.md b/website/zh-CN/guide/config.md index 3f194edc79..4b496b66d7 100644 --- a/website/zh-CN/guide/config.md +++ b/website/zh-CN/guide/config.md @@ -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 适配器** — 模型后端