docs: rebalance prose cleanup and add trimming skill

This commit is contained in:
Tianyi Cui
2026-07-13 23:27:00 +08:00
parent fcdc318dda
commit 148046b9c8
392 changed files with 2801 additions and 1754 deletions

View File

@@ -12,7 +12,7 @@ Exact model-facing schemas: [the generated tool catalog](../../../docs/tool-cata
## Trust stance
The sandbox isolates globals but is not a security boundary. Node globals are absent or redirect to Cordis services, and writes to `globalThis` stay local, but host-realm helpers and the privileged context make escape possible. Mounted plugins receive a façade without framework internals, yet its allowed services affect the live runtime. Treat this toolset like bash access.
The sandbox isolates globals but is not a security boundary. Node globals are absent or redirect to Cordis services such as `ctx.fs`, `ctx.web`, and `ctx.bash`, and writes to `globalThis` stay local, but host-realm helpers make escape possible. Mounted plugins receive a façade without framework internals, yet its allowed services affect the live runtime. Treat this toolset like bash access; see the [design and trust stance](../../../docs/rfc/implemented/feature/2026-07-08-self-referential-cordis-toolset.md).
## Config

View File

@@ -84,7 +84,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
{
key: 'bash',
summary: 'Abstract bash execution service.',
summary: 'Registers one `ctx.bash` implementation.',
methods: [
'abstract resolve(request: BashExecRequest): BashExecSpec',
'abstract run(spec: BashExecSpec): Promise<BashRunResult>',
@@ -99,7 +99,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
{
key: 'codeRuntime',
summary: 'Abstract code-execution service.',
summary: 'Registers one `ctx.codeRuntime` implementation.',
methods: [
'abstract run(request: CodeRunRequest): Promise<CodeRunResult>',
],
@@ -114,7 +114,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
{
key: 'fs',
summary: 'Abstract filesystem provider service.',
summary: 'Abstract filesystem provider.',
methods: [
'abstract resolve(path: string, opts?: { cwd?: string }): Promise<FsTarget>',
'abstract stat(target: FsTarget, signal?: AbortSignal): Promise<FsInfo | undefined>',
@@ -240,7 +240,7 @@ export const EVENT_API: readonly EventApiEntry[] = [
name: 'agent/created',
mode: 'emit',
signature: '\'agent/created\'(this: Scoped<Agent>, agent: Agent): void',
summary: 'A fully configured agent and its session were published.',
summary: 'A fully configured agent and live session were published.',
},
{
name: 'agent/disposed',
@@ -324,19 +324,19 @@ export const EVENT_API: readonly EventApiEntry[] = [
name: 'fs/edit-intent',
mode: 'waterfall',
signature: '\'fs/edit-intent\'(target: FsTarget, actor: object | undefined, next: () => { version: FsVersion } | undefined | Promise<{ version: FsVersion } | undefined>): Promise<{ version: FsVersion } | undefined>',
summary: 'Single-slot decision: produce the optional version guard for the next FileSystem.editText.',
summary: 'Single-slot decision for the next FileSystem.editText.',
},
{
name: 'fs/observed',
mode: 'emit',
signature: '\'fs/observed\'(target: FsTarget, version: FsVersion, actor: object | undefined): void',
summary: 'Record that an actor observed a target at a version, after a successful read/write/edit.',
summary: 'Record a successful observation.',
},
{
name: 'fs/write-intent',
mode: 'waterfall',
signature: '\'fs/write-intent\'(target: FsTarget, actor: object | undefined, next: () => FsWriteIntent | undefined | Promise<FsWriteIntent | undefined>): Promise<FsWriteIntent | undefined>',
summary: 'Single-slot decision: produce the write intent for the next FileSystem.writeText.',
summary: 'Single-slot decision for the next FileSystem.writeText.',
},
{
name: 'llm/stream',
@@ -348,25 +348,25 @@ export const EVENT_API: readonly EventApiEntry[] = [
name: 'session/created',
mode: 'emit',
signature: '\'session/created\'(this: Scoped<Session>, session: Session): void',
summary: 'Emitted after session publication.',
summary: 'Creation announcement during session publication.',
},
{
name: 'session/disposed',
mode: 'emit',
signature: '\'session/disposed\'(this: Scoped<Session>, session: Session): void',
summary: 'Emitted once when an announced session leaves the store, including publication rollback.',
summary: 'Emitted once when an announced session leaves the store, including publication rollback, but never for an entry whose creation announcement did not begin.',
},
{
name: 'session/event',
mode: 'emit',
signature: '\'session/event\'(this: Scoped<Session>, session: Session, event: SessionEvent): void',
summary: 'Post-commit append feed.',
summary: 'Post-commit, fire-and-forget append feed.',
},
{
name: 'session/flush',
mode: 'parallel',
signature: '\'session/flush\'(this: Scoped<Session>, session: Session): Promise<void> | void',
summary: 'Awaited parallel durability checkpoint; dispatch through SessionStore.flush.',
summary: 'Awaited parallel durability checkpoint: every listener runs and the caller awaits all of them, with no waterfall veto.',
},
{
name: 'skill/provider-added',

View File

@@ -1,7 +1,7 @@
/**
* Runtime mirror of the cordis `FiberState` const enum plus human-readable labels, shared by
* the mount lifecycle (state reporting) and the inspect renderers (plugin-list and mount-table
* labels).
* Runtime mirror and labels for Cordis's `FiberState` const enum. A const enum has no runtime
* object to import, so these values mirror the pinned vendored definition while retaining its
* type.
* @module @deepseek-ai/dsh-tool-cordis/fiber-state
*/

View File

@@ -3,7 +3,12 @@
* normalization + validation with teaching errors, the marker-guarded `harness.defineTool` /
* `harness.registerTool` pair, the SANDBOX CONTEXT FAÇADE a mounted plugin's `apply` receives
* in place of the real `ctx`, and the plugin-shape helpers the mount lifecycle narrows sandbox
* return values with.
* return values with. The façade is a whitelist of lifecycle-safe verbs and declared services;
* framework internals and context-valued service returns are denied.
*
* VM-realm schemas are rebuilt as host objects, and tool results are JSON-round-tripped and
* shape-checked before session logging. Common JSON-Schema spellings are normalized when they
* have one meaning; invalid vocabulary fails during registration with a teaching error.
* @module @deepseek-ai/dsh-tool-cordis/guard
*/
@@ -61,7 +66,7 @@ function normalizeSchemaProp(value: unknown, path: string, forceRequired = false
}
// On an object property a JSON-Schema-style `required` ARRAY names required
// children (handled by the nested unwrap below); everywhere else `required`
// must be a boolean, and `false` simply reads as optional.
// must be a boolean, and `false` means optional.
const nestedRequiredArray = type === 'object' && Array.isArray(value.required)
if (value.required !== undefined && typeof value.required !== 'boolean' && !nestedRequiredArray) {
throw new Error(`harness.defineTool ${path}.required must be a boolean when present`)
@@ -157,8 +162,8 @@ function assertExecuteReturn(value: unknown): ToolExecuteReturn {
* The `harness.defineTool` handed into the sandbox: the real DSL, with `parameters` normalized
* into a fresh host-realm SchemaSpec (JSON-Schema wrapper unwrapped, `integer` mapped,
* `required: false` dropped) and the tool's `execute` return normalized into the host realm
* via a JSON round-trip (see the module doc).
*
* via a JSON round-trip. Non-JSON or wrong-shape output fails that call instead of poisoning
* the session log.
* @param options - the standard `defineTool` options; `parameters` may be the SchemaSpec DSL or a JSON-Schema-style wrapper.
* @returns the marker-tagged definition `harness.registerTool` (and the guarded `ctx.tools.register`) accepts.
*/
@@ -266,7 +271,8 @@ function declaredInjects(ctx: Context): Set<string> {
}
/**
* The sandbox context façade handed to a mounted plugin's `apply` in place of the real `ctx`.
* Whitelist context for mounted plugins: lifecycle-safe verbs, guarded tools, and only declared
* injected services. Framework plumbing is denied, and service methods cannot return a Context.
*/
function sandboxContext(ctx: Context): Context {
const tools = sandboxTools(ctx)

View File

@@ -1,6 +1,9 @@
/**
* The self-referential cordis toolset: three model-facing tools that let the agent inspect and
* MODIFY the live cordis runtime it is running inside.
* Self-referential runtime tools: inspect live services/plugins/tools, mount a returned plugin
* under an owned dynamic fiber, and unmount it to quiescence. Registrations are fiber effects,
* so plugin disposal removes the entire dynamic subtree. The VM and context façade prevent
* accidental misuse, not hostile code: an allowed service such as `ctx.bash` reaches the real
* runtime. Named exports preserve loader injection metadata.
* @module @deepseek-ai/dsh-tool-cordis
*/

View File

@@ -127,7 +127,9 @@ function typeClosure(seeds: string[], types: readonly TypeApiEntry[]): TypeApiEn
}
/**
* Render the generated service catalog against the live runtime.
* Render the generated catalog against the live runtime: live catalogued services with methods,
* uncatalogued live services with owners, absent loadable services, referenced type shapes, and
* inherited Context APIs.
* @param ctx - the runtime to intersect the catalog with.
* @param api - generated service entries, replaceable in tests.
* @param inherited - inherited `ctx` entries, replaceable in tests.

View File

@@ -21,8 +21,8 @@ export interface DynamicMount {
}
/**
* Mount a plugin under the group fiber and settle it.
*
* Await the group, mount and settle one guarded child, and dispose it before rethrowing any
* startup failure so a failed mount never lingers. A valid unresolved inject may remain pending.
* @param group - the `cordis-dynamic` group fiber every mount hangs under.
* @param plugin - the plugin the sandbox returned; wrapped with the registration guard before mounting.
* @returns the settled child fiber (possibly pending on unsatisfied `inject`).

View File

@@ -2,7 +2,9 @@
* The `node:vm` sandbox `cordis_mount` code evaluates in: a fresh realm whose globals are a
* tagged write-through console, the `harness` registration helpers, the encoding primitives a
* bare vm context lacks, and callable traps over the Node APIs the sandbox deliberately
* withholds.
* withholds. Traps steer filesystem, network, process, and timer work to `ctx.fs`, `ctx.web`,
* `ctx.bash`, and Cordis timers. This keeps cooperative mounts inspectable and disposable but
* is not containment: host-realm helper functions remain an escape route.
* @module @deepseek-ai/dsh-tool-cordis/sandbox
*/
@@ -23,8 +25,8 @@ function taggedConsole(id: string): Record<'log' | 'info' | 'warn' | 'error' | '
}
/**
* Per-sandbox prelude: give the vm realm's own constructors a `Symbol.hasInstance` that checks
* BOTH realms.
* Patch only VM constructors so `instanceof` accepts both VM values and host values passed as
* arguments, events, or service results; host intrinsics remain untouched.
*/
const DUAL_REALM_INSTANCEOF_PRELUDE = `
(hostIntrinsics) => {
@@ -137,8 +139,8 @@ export function syntaxErrorContext(error: Error): string {
/**
* Evaluate mount code as the body of an async function inside the sandbox. `vmTimeoutMs` only
* bounds the SYNCHRONOUS portion; an async body escapes it — acceptable under the module's
* trust stance.
*
* trust stance. Parse errors include the offending line and a TypeScript-removal or bracket-
* balance hint.
* @param sandbox - the contextified object from {@link createSandbox}.
* @param code - the model-written function body; must `return` a plugin.
* @param id - the mount id, used as the vm filename (`cordis-mount-<id>.js`).

View File

@@ -48,7 +48,7 @@ describe('cordis_mount', () => {
})
it('normalizes a self-made tool\'s result into the host realm, so the session log accepts it', async () => {
// Normalize vm-realm results into host JSON before session validation.
// VM-realm objects fail the session prototype-identity check; normalize them into host JSON.
const ctx = await setup()
await call(ctx, 'cordis_mount', { code: REVERSE_TOOL_CODE })
const reversed = await call(ctx, 'reverse_text', { text: 'harness' })
@@ -89,9 +89,8 @@ describe('cordis_mount', () => {
['object-form blocks missing the type tag', 'return { content: [{ text: \'hi\' }] }', '{"content":[{"text":"hi"}]}'],
['undefined — a forgotten return', 'return undefined', 'undefined'],
])('rejects an execute return of %s as that one call\'s teaching error', async (_label, returnStatement, preview) => {
// The failure this prevents: the registry trusts the return shape (postExecute spreads
// result.content), so an unvalidated { content: 'ok' } would enter the session log as
// ['o','k'] and silently corrupt the next model request.
// The registry spreads result.content, so { content: 'ok' } would become ['o','k']; reject
// it as this call's error before it corrupts the next request.
const ctx = await setup()
await call(ctx, 'cordis_mount', {
code: `
@@ -143,8 +142,8 @@ describe('cordis_mount', () => {
})
it('accepts a JSON-Schema-style parameters wrapper and normalizes it to the DSL', async () => {
// The dialect models write by strong prior: the { type:'object', properties, required: […]
// } wrapper, `type: 'integer'`, and `required: false`.
// These common JSON-Schema spellings each have one DSL meaning, so normalize rather than
// consume another model turn with a rejection.
const ctx = await setup()
const result = await call(ctx, 'cordis_mount', {
code: `

View File

@@ -2,13 +2,10 @@ import { describe, expect, it } from 'vitest'
import { call, setup, text } from './helpers.ts'
/**
* The sandbox context façade is a whitelist, not a pass-through proxy: mount
* code reaches only the registration/eventing verbs, the timer helpers, a
* guarded `tools`, and its injected services. Every framework-plumbing member
* that could hand back an UNGUARDED context — through which a plugin could
* `ctx.<escape>.tools.register({…})` to bypass the marker check and host-realm
* normalization — is denied. These are the regression guards for that escape
* class (the review finding on the original pass-through proxy).
* The sandbox context façade is a whitelist, not a pass-through proxy. Mounted code reaches only
* registration/eventing verbs, timer helpers, guarded tools, and injected services. Framework
* members that expose an unguarded context are denied because they could bypass marker checks and
* host-realm normalization; these tests pin that escape class.
*/
/** Mount a plugin whose `apply` touches one framework member, and report the error text. */
@@ -77,7 +74,8 @@ describe('sandbox context façade — escape surface is closed', () => {
it('denies a service whose method returns a Context (the .ctx escape), registering nothing', async () => {
// A cordis Service instance carries `.ctx` (a real Context), so
// `ctx.systemPrompt.ctx.root.tools.register(…)` would be a fresh unguarded handle.
// `ctx.systemPrompt.ctx.root.tools.register(…)` would escape the façade; service-return
// guards reject that Context before the registration lands.
const ctx = await setup()
const result = await call(ctx, 'cordis_mount', {
code: `
@@ -189,9 +187,8 @@ describe('sandbox context façade — inject gate on services', () => {
})
it('a cross-mount consumer must declare the provider — the undeclared path is refused, not left as a zombie tool', async () => {
// The finding's scenario: a consumer registers a tool built on a provider's service WITHOUT
// declaring inject. cordis would then never park the consumer when the provider unmounts,
// leaving a tool that fails only at execution.
// Without declared inject, Cordis cannot park the consumer when its provider unmounts. The
// façade refuses access up front instead of leaving a zombie tool.
const ctx = await setup()
await call(ctx, 'cordis_mount', {
code: 'return { name: \'greeter-provider\', apply(ctx) { ctx.provide(\'greeter\', { greet: (n) => \'hi \' + n }) } }',