feat(tool-cordis): flatten the inspect plugins section to a capability list

The tree SHAPE was the wrong surface for the model: what it needs from
cordis_inspect is what services, plugins, and capabilities are loaded, not the
fiber hierarchy. The plugins section is now a flat name + lifecycle-state list
from ctx.registry (deterministically sorted, one line per instance); the ASCII
tree renderer, the parent→child rebuild, and the dyn-id tree annotation are
deleted — dynamic mounts keep their own richer dynamic section (id, state,
provides, waits). Net -49 lines; RFC and READMEs state the flat-list contract.
This commit is contained in:
imccyu
2026-07-08 21:01:49 +08:00
parent a500c791f7
commit ea66641b84
8 changed files with 47 additions and 96 deletions
+8 -17
View File
@@ -2,8 +2,8 @@
* The self-referential cordis toolset: three model-facing tools that let the
* agent inspect and MODIFY the live cordis runtime it is running inside.
*
* - `cordis_inspect` — read-only: provided services, the plugin fiber tree
* (rendered as an ASCII tree), registered tools, the dynamic mounts, and the
* - `cordis_inspect` — read-only: provided services, the flat plugin list
* with lifecycle states, registered tools, the dynamic mounts, and the
* catalog-backed `api` / `events` references.
* - `cordis_mount` — evaluate model-written code in a `node:vm` sandbox; the
* code returns a cordis plugin, which is mounted as a child of a dedicated
@@ -14,8 +14,8 @@
* `harness.registerTool`, services via `ctx.provide`) is an effect on the
* dynamic fiber, so unmounting — or disposing this plugin itself (HMR) — cleans
* it all up through the ordinary cordis lifecycle. The group fiber exists
* exactly so the dynamic mounts form ONE subtree: visible as a unit in the
* inspect tree and disposed as a unit with this plugin. Design home:
* exactly so the dynamic mounts form ONE subtree, disposed as a unit with
* this plugin. Design home:
* docs/rfc/implemented/feature/2026-07-08-self-referential-cordis-toolset.md.
*
* The vm sandbox guards against ACCIDENTAL global pollution only — it is not a
@@ -31,12 +31,12 @@
* @module @deepseek-ai/dsh-tool-cordis
*/
import type { Context, Fiber } from 'cordis'
import type { Context } from 'cordis'
import z from 'schemastery'
import { defineTool } from '@deepseek-ai/dsh-tools'
import { STATE_LABELS } from './fiber-state.ts'
import { isPlugin, pluginName } from './guard.ts'
import { describeApi, describeDynamic, describeEvents, describePluginTree, describeServices, describeTools } from './inspect.ts'
import { describeApi, describeDynamic, describeEvents, describePlugins, describeServices, describeTools } from './inspect.ts'
import { missingServices, mountDynamic } from './mount.ts'
import type { DynamicMount } from './mount.ts'
import { presentInspectCall, presentMountCall, presentUnmountCall } from './present.ts'
@@ -79,21 +79,12 @@ export function apply(ctx: Context, config: Config): void {
const mounts = new Map<string, DynamicMount>()
let nextId = 1
/** The dynamic-mount id for a fiber, when that fiber is a tracked mount. */
function mountIdOf(fiber: Fiber): string | undefined {
for (const [id, mount] of mounts) {
if (mount.fiber === fiber) return id
}
return undefined
}
ctx.tools.register(defineTool({
name: 'cordis_inspect',
description:
'Inspect the live cordis runtime that is running THIS agent. Read-only. '
+ 'Sections: `services` (every provided ctx service and the plugin fiber that owns it), '
+ '`plugins` (the whole plugin fiber tree with lifecycle states, as an ASCII tree — '
+ 'dynamic mounts appear under the `cordis-dynamic` group with their ids), '
+ '`plugins` (a flat list of the loaded plugins with their lifecycle states), '
+ '`tools` (the model-facing tools currently registered, i.e. what you can call), '
+ '`dynamic` (plugins you mounted via cordis_mount: id, name, state, provided services, awaited services), '
+ '`api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), '
@@ -109,7 +100,7 @@ export function apply(ctx: Context, config: Config): void {
execute(args): Promise<{ type: 'text'; text: string }[]> {
const sections: [heading: string, body: () => string[]][] = [
['services', () => describeServices(ctx)],
['plugins', () => describePluginTree(ctx, mountIdOf)],
['plugins', () => describePlugins(ctx)],
['tools', () => describeTools(ctx)],
['dynamic', () => describeDynamic(ctx, mounts)],
['api', () => describeApi(ctx)],