Files
deepseek-harness/packages/cordis/tool-cordis/tests/inspect.spec.ts
imccyu ee1da1ce5b feat(cordis): @deepseek-ai/dsh-tool-cordis — inspect/mount/unmount over the live runtime
New top-level packages/cordis/ group with the self-referential toolset:
cordis_inspect (services / plugin tree / tools / dynamic mounts / api / events,
the api section intersecting the generated catalog with the live service store),
cordis_mount (model-written code evaluated in a node:vm sandbox, mounted under
one cordis-dynamic group fiber as dyn-<n>), cordis_unmount (awaited disposal to
quiescence). Boundary mechanisms: dual-realm instanceof, JSON realm
normalization of dynamic tool results, marker-guarded registration, SchemaSpec
teaching errors, parse failures surfaced with the offending line + caret and a
line-scoped TypeScript hint, and the unmount-first recipe on tool-name
collisions. Config: vmTimeoutMs (schemastery, default 5000). Design record:
docs/rfc/implemented/feature/2026-07-08-self-referential-cordis-toolset.md.

The tool-catalog boot manifest, its regenerated output, and the pinned
tool-name list land here rather than with the other repo registration: the
completeness guard globs packages/*/tool-* and fails the generator (and the
core/tools spec) the moment the package directory exists.
2026-07-09 13:57:03 +08:00

124 lines
5.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { Context, Fiber } from 'cordis'
import { FiberState } from '../src/fiber-state.ts'
import { describeApi, describeEvents, describePluginTree, describeServices } from '../src/inspect.ts'
import { call, LISTENER_CODE, setup, text } from './helpers.ts'
/**
* The `cordis_inspect` sections: rendered against the real runtime through the
* tool, plus direct renderer calls for the states a minimal harness cannot
* reach (empty service store, uid-less fibers, a fully-live catalog).
*/
describe('cordis_inspect', () => {
it('reports all six sections by default', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_inspect', {})
expect(result.isError).toBe(false)
const report = text(result)
for (const heading of ['services', 'plugins', 'tools', 'dynamic', 'api', 'events']) {
expect(report).toContain(`## ${heading}`)
}
// The services section sees the real providers; the tree shows the dynamic
// group under this plugin; the tools section lists the cordis tools.
expect(report).toContain('- tools (provided by ToolRegistry)')
expect(report).toMatch(/tool-cordis \[active\]/)
expect(report).toMatch(/cordis-dynamic \[active\]/)
expect(report).toContain('- cordis_mount')
expect(report).toContain('(no dynamic plugins mounted)')
})
it('limits the report to one section via `what`', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_inspect', { what: 'tools' })
const report = text(result)
expect(report).toContain('## tools')
expect(report).not.toContain('## services')
expect(report).not.toContain('## plugins')
})
it('shows a mount in the dynamic section and as an annotated child of the group in the tree', async () => {
const ctx = await setup()
await call(ctx, 'cordis_mount', { code: LISTENER_CODE })
const report = text(await call(ctx, 'cordis_inspect', {}))
expect(report).toContain('- dyn-1: change-logger [active]')
expect(report).toMatch(/dyn-1: change-logger \[active\]/)
})
it('renders the api section from the generated catalog intersected with the LIVE runtime', async () => {
const ctx = await setup()
const report = text(await call(ctx, 'cordis_inspect', { what: 'api' }))
// Live catalogued services render summary + signatures.
expect(report).toContain('- tools — ')
expect(report).toContain('register(definition: ToolDefinition)')
expect(report).toContain('- systemPrompt — ')
// Catalogued services with no live provider are listed tersely.
expect(report).toMatch(/not running \(loadable services with no live provider\): .*bash/)
// The type shapes the LIVE signatures reference follow (closure over the
// generated TYPE_API — a consumer can see field types, not just names).
expect(report).toContain('type shapes (referenced by the signatures above')
expect(report).toContain('export interface ToolExecution')
// A type only reachable through a NOT-live service (e.g. bash) is scoped out.
expect(report).not.toContain('export interface BashRunResult')
// The inherited ctx surface closes the section.
expect(report).toContain('inherited ctx API:')
expect(report).toContain('- ctx.effect — ')
})
it('renders the events section with mode badges, signatures, and the waterfall caution', async () => {
const ctx = await setup()
const report = text(await call(ctx, 'cordis_inspect', { what: 'events' }))
expect(report).toContain('- tools/change [emit]')
expect(report).toContain('- tools/pre-execute [waterfall]')
expect(report).toMatch(/'agent\/status'\(/)
expect(report).toContain('returning without next() vetoes the chain')
})
})
describe('inspect renderers (direct)', () => {
it('describeServices reports an empty store as such, and labels a non-active provider', () => {
const empty = { reflect: { store: {} } } as unknown as Context
expect(describeServices(empty)).toEqual(['(no services provided)'])
const pendingFiber = { state: FiberState.PENDING, name: 'half-loaded' } as unknown as Fiber
const store: Record<symbol, unknown> = {}
store[Symbol('impl')] = { name: 'thing', fiber: pendingFiber }
const ctx = { reflect: { store } } as unknown as Context
expect(describeServices(ctx)).toEqual(['- thing (provided by half-loaded, pending)'])
})
it('describePluginTree sorts uid-less fibers last and renders sibling branches', () => {
// The parent fiber is OUTSIDE the registry set, so all three are roots.
const rootFiber = { uid: 0, name: 'root' } as unknown as Fiber
const fiber = (uid: number | null, name: string): Fiber =>
({ uid, name, state: FiberState.ACTIVE, parent: { fiber: rootFiber } }) as unknown as Fiber
const a = fiber(2, 'beta')
const b = fiber(1, 'alpha')
const c = fiber(null, 'rootless')
const d = fiber(null, 'rootless-too')
const ctx = { registry: { values: () => [{ fibers: [a, b, c, d] }] } } as unknown as Context
expect(describePluginTree(ctx, () => undefined)).toEqual([
'root',
'├─ alpha [active]',
'├─ beta [active]',
'├─ rootless [active]',
'└─ rootless-too [active]',
])
})
it('describeApi omits the not-running line and type shapes when nothing applies', async () => {
const ctx = await setup()
const lines = describeApi(ctx, [{ key: 'tools', summary: 'The registry.', methods: ['register(x): void'] }], [], [])
expect(lines[0]).toBe('- tools — The registry.')
expect(lines[1]).toBe(' register(x): void')
expect(lines.join('\n')).not.toContain('not running')
expect(lines.join('\n')).not.toContain('type shapes')
})
it('describeEvents renders an empty catalog as just the waterfall caution', () => {
expect(describeEvents([])).toEqual([
'waterfall listeners receive a trailing next() and MUST call it to delegate — returning without next() vetoes the chain.',
])
})
})