Merge remote-tracking branch 'origin/master' into docs/readme-human-polish-2

# Conflicts:
#	apps/cli/README.i18n.yaml
#	apps/cli/README.zh.md
#	packages/core/tools/README.i18n.yaml
#	packages/core/tools/README.zh.md
#	python/sdk/README.i18n.yaml
#	python/sdk/README.zh.md
#	scripts/snapshots/translation-prompt-v4/request-response.expected.json
This commit is contained in:
j-xiang
2026-08-13 10:44:52 +08:00
3512 changed files with 51728 additions and 30967 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ const root = resolve(import.meta.dirname, '..')
/** The closure manifest whose dependencies define the executable. */
const DEPLOY_ROOT_PACKAGE = 'dsh-jsonrpc-agent-pkg'
/** The closed-runtime app entry inside the deployed closure. */
const ENTRY_BIN = 'node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/packaged-bin.js'
const ENTRY_BIN = 'node_modules/@deepseek-ai/dsh-sdk-jsonrpc-demo/lib/packaged-bin.js'
const OUTPUT_BASENAME = 'dsh-jsonrpc-agent-pkg'
/** Default Node major; SEA mode requires at least Node 22. */
const DEFAULT_NODE_RANGE = 'node24'
+5 -5
View File
@@ -7,12 +7,12 @@
import { existsSync, readdirSync, readFileSync } from 'node:fs'
import { join, relative, resolve } from 'node:path'
import { hasTypeRTRemoteNavigation, isForbiddenPublicationFile } from './publication-payload.ts'
import { hasTypertRemoteNavigation, isForbiddenPublicationFile } from './publication-payload.ts'
import { collectProjectReferenceFaceViolations } from './project-reference-faces.ts'
const root = resolve(import.meta.dirname, '..')
// vendor/* is single-level; packages/<group>/<pkg> nests one level deeper
// (the group dirs — core/llm/bash/… — are pure containers with no manifest).
// (the group dirs — core/llm/shell/… — are pure containers with no manifest).
const workspaceGlobs = [
{ dir: 'vendor', depth: 1 },
{ dir: 'packages', depth: 2 },
@@ -55,7 +55,7 @@ const appPackageFiles: Readonly<Record<string, readonly string[]>> = {
'@deepseek-ai/dsh': ['lib/*.js', 'config'],
// The Web build emits sourcemaps for browser debugging; publishing them is
// what the payload policy forbids, so the bundle ships without them.
'@deepseek-ai/dsh-frontend': ['dist', '!dist/**/*.map'],
'@deepseek-ai/dsh-web-frontend': ['dist', '!dist/**/*.map'],
}
/** The subset of package.json fields this constraint check cares about. */
@@ -138,7 +138,7 @@ const packageFileExtras: Readonly<Record<string, readonly string[]>> = {
'@deepseek-ai/dsh-client-ui-theme': ['lib/styles'],
// The Python runtime uses a distinct closed-resolution bin; the public CLI
// keeps config-owned bare-package resolution through lib/bin.js.
'@deepseek-ai/dsh-jsonrpc-demo': ['lib/packaged-bin.js'],
'@deepseek-ai/dsh-sdk-jsonrpc-demo': ['lib/packaged-bin.js'],
// The argv-prefix runner entry ships beside the lib as its own bundle;
// sandbox-local resolves it through the package's ./runner export. tsdown
// also shares its generated FFI code through a hashed runtime chunk.
@@ -185,7 +185,7 @@ function expectedDshPackageFiles(manifest: PackageManifest): readonly string[] {
...hasExportPair(manifest, './client/typert', './lib/typert.client.d.ts', './lib/typert.client.js')
? ['lib/typert.client.js', 'lib/typert.client.d.ts']
: [],
...hasTypeRTRemoteNavigation(manifest)
...hasTypertRemoteNavigation(manifest)
? ['lib/typert.remote-client.js', 'lib/typert.remote-client.d.ts']
: [],
]
+3 -3
View File
@@ -154,9 +154,9 @@ describe('CI workflow', () => {
it('keeps supported LSP source under native Windows coverage', () => {
const config = readFileSync(resolve(root, 'vitest.config.ts'), 'utf8')
expect(config).not.toContain('packages/lsp/lsp-local/src/connection.ts')
expect(config).not.toContain('packages/lsp/lsp-local/src/index.ts')
expect(config).not.toContain('packages/lsp/lsp-local/src/instance.ts')
expect(config).not.toContain('packages/lsp/lsp-stdio/src/connection.ts')
expect(config).not.toContain('packages/lsp/lsp-stdio/src/index.ts')
expect(config).not.toContain('packages/lsp/lsp-stdio/src/instance.ts')
})
it('requires one release-shaped Python runtime target on every pull request', () => {
+7 -4
View File
@@ -9,10 +9,13 @@ import { describe, expect, it } from 'vitest'
const root = fileURLToPath(new URL('..', import.meta.url))
function clientCssDeclarations(): string[] {
const clientRoot = resolve(root, 'packages/client')
return readdirSync(clientRoot, { withFileTypes: true })
.filter(entry => entry.isDirectory())
.map(entry => resolve(clientRoot, entry.name, 'src/css-modules.d.ts'))
const clientGroups = ['client', 'extensions']
return clientGroups.flatMap((group) => {
const clientRoot = resolve(root, 'packages', group)
return readdirSync(clientRoot, { withFileTypes: true })
.filter(entry => entry.isDirectory())
.map(entry => resolve(clientRoot, entry.name, 'src/css-modules.d.ts'))
})
.filter(existsSync)
.map(file => file.replaceAll(sep, '/'))
.sort()
+3 -3
View File
@@ -4,14 +4,14 @@ import { builtDeclarationPath } from './doc-typecheck-paths.ts'
describe('builtDeclarationPath', () => {
it('maps package source directories and exact entry files to built declarations', () => {
expect(builtDeclarationPath('./packages/*/*/src')).toBe('./packages/*/*/lib/types')
expect(builtDeclarationPath('./packages/support/invariants/src/index.ts'))
.toBe('./packages/support/invariants/lib/types/index.d.ts')
expect(builtDeclarationPath('./packages/runtime-diagnostics/invariants/src/index.ts'))
.toBe('./packages/runtime-diagnostics/invariants/lib/types/index.d.ts')
expect(builtDeclarationPath('./packages/core/session/src/invariant.ts'))
.toBe('./packages/core/session/lib/types/invariant.d.ts')
})
it('rejects aliases without a supported source target', () => {
expect(() => builtDeclarationPath('./packages/support/invariants/source/index.ts'))
expect(() => builtDeclarationPath('./packages/runtime-diagnostics/invariants/source/index.ts'))
.toThrow('cannot map workspace source path')
})
})
+214
View File
@@ -0,0 +1,214 @@
/**
* The client slot catalog's judgement, proven on hand-built inputs: the
* contract checks that must reject an unteachable slot, and the projection
* facts a registrant depends on (who occupies a seat, what replacing it costs,
* which owner has to be mounted). Run against the real workspace, the
* generator's own `--check` covers freshness; these cases pin the rules that
* make a stale or undocumented contract fail loudly instead of shipping.
*/
import { describe, expect, it } from 'vitest'
import { collectSlotEntries, oversizedSlotReports, resolveSlotEntries, validateSlotContracts } from './gen-client-catalog.ts'
import type { SlotDeclaration, SlotRegistration, TypeDeclaration } from './slot-walk.ts'
/** A declaration with every field the catalog needs, overridable per case. */
function declaration(over: Partial<SlotDeclaration> = {}): SlotDeclaration {
return {
key: 'demo.seat',
kind: 'single',
scope: 'root',
jsDoc: '/** A seat. Registering here replaces the shipped entry. */',
package: '@deepseek-ai/dsh-client-demo',
source: 'packages/client/demo/src/client/contract/slots.ts:1',
...over,
}
}
/** A registration into `demo.seat`, overridable per case. */
function registration(over: Partial<SlotRegistration> = {}): SlotRegistration {
return {
key: 'demo.seat',
package: '@deepseek-ai/dsh-client-demo',
component: 'DemoSeat',
children: [],
source: 'packages/client/demo/src/client/index.ts:10',
...over,
}
}
/** An exported owner-props declaration the catalog can resolve. */
const OWNER_TYPES = new Map<string, TypeDeclaration>([
['DemoOwnerProps', {
name: 'DemoOwnerProps',
text: '/** Owner share. */\nexport interface DemoOwnerProps {\n /** Column width. */\n width: number\n}',
source: 'packages/client/demo/src/client/contract/slots.ts:20',
}],
])
describe('client slot contract validation', () => {
it('accepts a documented slot whose owner props resolve', () => {
expect(validateSlotContracts(
[declaration({ ownerType: 'DemoOwnerProps' })],
[registration()],
OWNER_TYPES,
)).toEqual([])
})
it('rejects a slot with no registrant-facing prose, naming the writing template', () => {
const problems = validateSlotContracts([declaration({ jsDoc: '' })], [], new Map())
expect(problems).toHaveLength(1)
expect(problems[0]).toContain('has no JSDoc prose')
expect(problems[0]).toContain('ui-settings')
})
it.each([
['kind', { kind: 'whatever' }],
['scope', { scope: 'whatever' }],
])('rejects a slot whose %s is not one of the contract literals', (field, over) => {
const problems = validateSlotContracts([declaration(over)], [], new Map())
expect(problems).toHaveLength(1)
expect(problems[0]).toContain(`no literal '${field}'`)
})
it('rejects owner props no exported declaration provides', () => {
const problems = validateSlotContracts([declaration({ ownerType: 'MissingProps' })], [], new Map())
expect(problems).toHaveLength(1)
expect(problems[0]).toContain('MissingProps')
})
it('rejects the same key declared twice, because a merge would hide one contract', () => {
const problems = validateSlotContracts(
[declaration(), declaration({ source: 'packages/client/other/src/client/slots.ts:3' })],
[],
new Map(),
)
expect(problems).toHaveLength(1)
expect(problems[0]).toContain('is also declared at')
})
it('rejects a registration into an undeclared slot as a scan blind spot', () => {
const problems = validateSlotContracts([declaration()], [registration({ key: 'ghost.seat' })], new Map())
expect(problems).toHaveLength(1)
expect(problems[0]).toContain('blind spot')
})
it('rejects a children declaration for a slot no merge types', () => {
const problems = validateSlotContracts([declaration()], [registration({ children: ['ghost.child'] })], new Map())
expect(problems).toHaveLength(1)
expect(problems[0]).toContain("child slot 'ghost.child'")
})
})
describe('client slot projection', () => {
const kits = new Map<string, readonly string[]>([['root', ['useSessions: Hook']]])
it('warns that a single seat with a shipped occupant is replaced, not shared', () => {
const [entry] = resolveSlotEntries([declaration()], [registration()], OWNER_TYPES, kits)
expect(entry?.replaceRisk).toBe('shadows-shipped-ui')
expect(entry?.occupants).toEqual(['client-demo DemoSeat'])
})
it('treats a list seat as additive even when shipped entries exist', () => {
const [entry] = resolveSlotEntries(
[declaration({ kind: 'list' })],
[registration({ id: 'shipped' })],
OWNER_TYPES,
kits,
)
expect(entry?.replaceRisk).toBe('none')
expect(entry?.occupants).toEqual(["client-demo DemoSeat id 'shipped'"])
expect(entry?.registerOptions.map(option => option.name)).toEqual(['id', 'order', 'label'])
})
it('names the entry whose mount makes a child seat exist', () => {
const parent = registration({ key: 'demo.parent', children: ['demo.seat'] })
const entries = resolveSlotEntries(
[declaration(), declaration({ key: 'demo.parent' })],
[parent],
OWNER_TYPES,
kits,
)
expect(entries.find(entry => entry.key === 'demo.seat')?.declaredBy)
.toContain("an entry in 'demo.parent' (client-demo)")
expect(entries.find(entry => entry.key === 'demo.parent')?.declaredBy)
.toContain('built in')
})
it('reports an open keyed domain and the keys already taken', () => {
const [entry] = resolveSlotEntries(
[declaration({ kind: 'keyed' })],
[registration({ entryKey: 'bash' }), registration({ entryKey: 'read' })],
OWNER_TYPES,
kits,
)
expect(entry?.keyDomain).toContain('open: any string')
expect(entry?.keyDomain).toContain('already taken: bash, read')
})
it('carries owner-props documentation into the entry, not just the type name', () => {
const [entry] = resolveSlotEntries([declaration({ ownerType: 'DemoOwnerProps' })], [], OWNER_TYPES, kits)
expect(entry?.ownerProps.join('\n')).toContain('Column width.')
})
it('expands owner props one level and only names the shapes they reference', () => {
// Transitive expansion once dragged the whole session model into four
// seats; a registrant needs the fields, not the graph behind them.
const types = new Map(OWNER_TYPES)
types.set('Zone', {
name: 'Zone',
text: 'export interface Zone {\n session: BigSnapshot\n}',
source: 'packages/client/demo/src/client/contract/slots.ts:30',
})
types.set('BigSnapshot', {
name: 'BigSnapshot',
text: 'export interface BigSnapshot {\n turns: number\n}',
source: 'packages/client/demo/src/client/snapshot.ts:1',
})
const [entry] = resolveSlotEntries([declaration({ ownerType: 'Zone' })], [], types, kits)
expect(entry?.ownerProps.join('\n')).toContain('export interface Zone')
expect(entry?.ownerProps.join('\n')).not.toContain('export interface BigSnapshot')
expect(entry?.ownerPropsReferences).toEqual(['BigSnapshot'])
})
it('offers a runnable registration whose options match the cardinality', () => {
const [entry] = resolveSlotEntries([declaration({ kind: 'list' })], [], OWNER_TYPES, kits)
expect(entry?.example).toContain("ctx.slots.inject('demo.seat'")
expect(entry?.example).toContain("id: 'my-entry'")
})
})
describe('the per-slot report budget', () => {
it('rejects a slot whose report a model could not finish reading', () => {
// Truncation already bounds one declaration, so the remaining runaway is
// prose: a contract that grew into a manual costs exactly what narrowing to
// one slot was supposed to save.
const manual = ['/**', ...Array.from({ length: 150 }, (_, i) => ` * Paragraph ${String(i)} about this seat.`), ' */']
const entries = resolveSlotEntries([declaration({ jsDoc: manual.join('\n') })], [], OWNER_TYPES, new Map())
const problems = oversizedSlotReports(entries)
expect(problems).toHaveLength(1)
expect(problems[0]).toContain("slot 'demo.seat'")
expect(problems[0]).toContain('tighten')
})
it('passes a slot whose report stays within the budget', () => {
const entries = resolveSlotEntries([declaration({ ownerType: 'DemoOwnerProps' })], [], OWNER_TYPES, new Map())
expect(oversizedSlotReports(entries)).toEqual([])
})
})
describe('the real workspace surface', () => {
it('collects every declared slot with a teachable contract', { timeout: 30_000 }, () => {
const entries = collectSlotEntries(process.cwd())
expect(entries.length).toBeGreaterThan(30)
for (const entry of entries) {
expect(entry.summary, `${entry.key} has no summary`).not.toBe('')
expect(['single', 'list', 'keyed', 'chain']).toContain(entry.kind)
expect(['root', 'session', 'session-maybe']).toContain(entry.scope)
}
// The frame root is the canonical trap: occupied by the shipped app frame,
// so a dynamic package registering there replaces the whole UI.
const root = entries.find(entry => entry.key === 'root')
expect(root?.replaceRisk).toBe('shadows-shipped-ui')
expect(root?.occupants.join(' ')).toContain('AppFrame')
})
})
+558
View File
@@ -0,0 +1,558 @@
/**
* Generate the model-facing client slot catalog consumed by `cordis_inspect
* what:"client"`. A dynamic package's browser half can only contribute UI
* through `ctx.slots.register`, and every fact it needs to do that safely —
* which keys exist, what each register call must pass, what the component
* receives, who already occupies the seat, and when the seat exists at all —
* is decided at compile time by the shipped web bundle. This generator reads
* those facts lexically (no type-checker program) and emits them as a data
* module inside `tool-cordis`, so the host-side toolset teaches the browser
* surface without importing a single client runtime module.
*
* `--check` verifies the committed artifact is fresh.
*/
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import {
declaredTypes,
indexExportedTypes,
referencedTypeNames,
scanSlotFiles,
slotDeclarations,
slotRegistrations,
standardKitMembers,
} from './slot-walk.ts'
import type { ScannedFile, SlotDeclaration, SlotRegistration, TypeDeclaration } from './slot-walk.ts'
const root = resolve(import.meta.dirname, '..')
const OUT = 'packages/extensions/cordis-client-runner/src/client/slot-catalog.ts'
/** Source globs: every workspace package's sources, `.tsx` included (a contract may live in one). */
const SOURCE_GLOBS = ['packages/*/*/src/**/*.ts', 'packages/*/*/src/**/*.tsx']
/** Slot cardinalities the contract allows. */
const KINDS = ['single', 'list', 'keyed', 'chain'] as const
/** Slot data scopes the contract allows. */
const SCOPES = ['root', 'session', 'session-maybe'] as const
/** Declarations longer than this render truncated; the full shape stays in source. */
const MAX_DECL_CHARS = 1200
/**
* Line budget for ONE slot's expanded report. The whole point of narrowing to a
* single slot is to spend less context, so a report a model cannot finish
* reading is a defect rather than a detail. Today's widest slot renders 60
* lines, so this leaves room to document a slot properly while catching the two
* ways a report runs away: an owner share that hands down a subsystem instead of
* a share, and prose that grew into a manual.
*/
const MAX_ENTRY_LINES = 120
/** One register-call option as the catalog teaches it. */
interface OptionDoc {
readonly name: string
readonly requirement: 'required' | 'optional'
readonly type: string
readonly doc: string
}
/**
* Register options per cardinality, curated from `KindOptions` in
* `packages/client/ui-slots/src/index.ts` — the authority for what a register
* call may pass. Curated rather than projected because the authority is a
* conditional type keyed on the slot's kind: it has no per-kind declaration a
* lexical scan could read, and its own JSDoc addresses the compiler, not a
* registrant. `verify-client-catalog` pins the authority's text so a change
* there forces this table to be revisited.
*/
const REGISTER_OPTIONS: Readonly<Record<(typeof KINDS)[number], readonly OptionDoc[]>> = {
single: [],
list: [
{ name: 'id', requirement: 'required', type: 'string', doc: 'Your cell key. Use an id of your own: a fresh id is added beside the shipped entries, while reusing a shipped id puts you in THAT cell and replaces it. Owners that filter by id address you by it.' },
{ name: 'order', requirement: 'optional', type: 'number', doc: 'Position among the entries, ascending (default 0).' },
{ name: 'label', requirement: 'optional', type: 'string | (() => string)', doc: 'Display text where the owner projects one (nav rows, tabs). A thunk is re-read on every projection, so localized text follows the active locale without re-registering.' },
],
keyed: [
{ name: 'key', requirement: 'required', type: 'string', doc: 'Your cell key: the entry renders where the owner dispatches this exact key. Registering an already-occupied key replaces that occupant.' },
],
chain: [
{ name: 'select', requirement: 'required', type: '(owner) => unknown | null', doc: 'Pure routing selector. Entries are tried in ascending order; the first non-null result wins and arrives as the component\'s `matched` prop. All-null falls through to the owner\'s fallback.' },
],
}
/** The one register option a dynamic package must NOT pass, and why. */
const PRIORITY_NOTE = 'Do NOT pass `priority`: the browser-half facade assigns one automatically, and it is LOWER than every shipped entry — in a single or keyed cell that means your entry is the one that renders.'
/** Cross-cutting rules a registrant needs once, not per slot. */
const CLIENT_NOTES: readonly string[] = [
'Contribute UI only through `ctx.slots.register(options, Component)`; declare `inject: [\'slots\']` in your returned plugin (object form) or the seat is withheld.',
'Wrap every registration in `ctx.slots.inject(key, () => ctx.slots.register(...))`. A slot exists only while the entry that declared it is mounted, and registering into an undeclared slot throws; `inject` runs your registration when the declaration is (or becomes) live and re-runs it if the owner remounts.',
PRIORITY_NOTE,
'You cannot `import` anything, so the design-system components are out of reach: build markup with `React.createElement` and ship CSS through `styles.insert(css)`. Use the theme CSS variables (`var(--dsw-alias-bg-layer-1)`, `var(--dsw-alias-label-primary)`, …) instead of literal colors, or your contribution breaks in the other color scheme.',
'Every component receives the framework hook seats listed under `framework props` for its scope; a selector hook is called with a selector, e.g. `useSessions(state => state.current)`.',
'This catalog is the COMPILE-TIME contract of the shipped web bundle, not a snapshot of one page: a key is registrable only where the owner that declares it is mounted. A failed registration surfaces in the browser-half load report — read it back with `cordis_inspect what:"temporary"`.',
]
/** Standard-kit interface that applies to each scope, beyond the global one. */
const SCOPE_KIT: Readonly<Record<(typeof SCOPES)[number], string | undefined>> = {
'root': undefined,
'session': 'SessionStandardProps',
'session-maybe': 'SessionMaybeStandardProps',
}
/** One resolved catalog entry, ready to render. */
export interface SlotEntry {
readonly key: string
readonly kind: string
readonly scope: string
readonly summary: string
readonly doc: string
readonly registerOptions: readonly OptionDoc[]
readonly ownerProps: readonly string[]
readonly ownerPropsReferences: readonly string[]
readonly standardProps: readonly string[]
readonly keyDomain: string
readonly hookContext: string
readonly slotInject: string
readonly declaredBy: string
readonly occupants: readonly string[]
readonly replaceRisk: string
readonly example: string
readonly source: string
}
/**
* Read the workspace and resolve every catalog entry, failing loud on a
* contract the catalog cannot teach.
* @param scanRoot - repository root to scan.
* @returns the entries, sorted by key.
* @throws when any declared slot is unteachable or the scan contradicts itself.
*/
export function collectSlotEntries(scanRoot: string): SlotEntry[] {
const files = scanSlotFiles(scanRoot, SOURCE_GLOBS)
const declarations = files.flatMap(file => slotDeclarations(file))
const registrations = files.flatMap(file => slotRegistrations(file))
const types = indexExportedTypes(scanRoot, SOURCE_GLOBS)
const problems = validateSlotContracts(declarations, registrations, types)
if (problems.length > 0) {
throw new Error(`gen-client-catalog: ${String(problems.length)} contract violation(s):\n${problems.map(problem => ` ${problem}`).join('\n')}`)
}
const entries = resolveSlotEntries(declarations, registrations, types, standardKits(files))
const oversized = oversizedSlotReports(entries)
if (oversized.length > 0) {
throw new Error(`gen-client-catalog: ${String(oversized.length)} slot(s) exceed the per-slot report budget `
+ `of ${String(MAX_ENTRY_LINES)} lines:\n${oversized.map(problem => ` ${problem}`).join('\n')}`)
}
return entries
}
/**
* Slots whose expanded report exceeds {@link MAX_ENTRY_LINES}. Separated from
* the scan so the budget is provable on one hand-built entry.
* @param entries - resolved catalog entries.
* @returns one message per over-budget slot, empty when every report is readable.
*/
export function oversizedSlotReports(entries: readonly SlotEntry[]): string[] {
return entries
.filter(entry => entryLines(entry) > MAX_ENTRY_LINES)
.map(entry => `slot '${entry.key}' (${entry.source}) reports ${String(entryLines(entry))} lines. `
+ 'Narrow the owner share it passes down (a slot hands a registrant a share, not a subsystem) or tighten '
+ 'its prose, so asking about one slot stays cheaper than asking about all of them.')
}
/** Line count of one entry's variable-length content, the proxy for its rendered report. */
function entryLines(entry: SlotEntry): number {
const blocks = [entry.doc, entry.example, ...entry.ownerProps, ...entry.registerOptions.map(option => option.doc)]
return blocks.reduce((total, block) => total + block.split('\n').length, 0)
+ entry.standardProps.length + entry.ownerPropsReferences.length + entry.occupants.length
}
/**
* Fail-closed contract checks: an unteachable slot must break the gate rather
* than ship an entry a model cannot act on. Pure, so every rejection is
* provable without scanning the workspace.
* @param declarations - every declared slot.
* @param registrations - every registration call site.
* @param types - exported type index the owner-props reference resolves against.
* @returns one message per violation, empty when the surface is teachable.
*/
export function validateSlotContracts(
declarations: readonly SlotDeclaration[],
registrations: readonly SlotRegistration[],
types: ReadonlyMap<string, TypeDeclaration>,
): string[] {
const problems: string[] = []
const byKey = new Map<string, SlotDeclaration>()
for (const declaration of declarations) {
const where = `slot '${declaration.key}' (${declaration.source})`
const previous = byKey.get(declaration.key)
if (previous !== undefined) {
problems.push(`${where} is also declared at ${previous.source}; SlotMap merges duplicates silently, so the catalog cannot tell which documentation wins.`)
continue
}
byKey.set(declaration.key, declaration)
if (!(KINDS as readonly string[]).includes(declaration.kind)) {
problems.push(`${where} has no literal 'kind'; the catalog derives the register options from it, so it must be one of ${KINDS.join('/')}.`)
}
if (!(SCOPES as readonly string[]).includes(declaration.scope)) {
problems.push(`${where} has no literal 'scope'; the catalog derives the framework props from it, so it must be one of ${SCOPES.join('/')}.`)
}
if (docProse(declaration.jsDoc) === '') {
problems.push(`${where} has no JSDoc prose. Write it from the REGISTRANT's side: what to pass, what the component receives, whom a registration replaces, and what absence looks like (packages/client/ui-settings/src/client/contract/slots.ts is the template).`)
}
if (declaration.ownerType !== undefined
&& /^[A-Za-z_$][\w$]*$/.test(declaration.ownerType)
&& !types.has(declaration.ownerType)) {
problems.push(`${where} names owner props '${declaration.ownerType}' that no exported declaration provides; export the interface so the catalog can show what the component receives.`)
}
}
for (const registration of registrations) {
if (!byKey.has(registration.key)) {
problems.push(`registration into '${registration.key}' (${registration.source}) targets a slot no SlotMap merge declares; either the scan has a blind spot or the registration is dead.`)
}
for (const child of registration.children) {
if (!byKey.has(child)) {
problems.push(`registration at ${registration.source} declares child slot '${child}' that no SlotMap merge types.`)
}
}
}
return problems
}
/**
* Project validated declarations into catalog entries: cardinality decides the
* register options, scope decides the framework props, and the registration
* call sites decide who already sits in the seat and which owner's mount makes
* it exist. Pure, so the projection facts are provable without a workspace.
* @param declarations - validated slot declarations.
* @param registrations - every registration call site.
* @param types - exported type index for owner-props expansion.
* @param kits - framework prop seats per scope.
* @returns the entries, sorted by key.
*/
export function resolveSlotEntries(
declarations: readonly SlotDeclaration[],
registrations: readonly SlotRegistration[],
types: ReadonlyMap<string, TypeDeclaration>,
kits: ReadonlyMap<string, readonly string[]>,
): SlotEntry[] {
const declaredBy = new Map<string, SlotRegistration>()
for (const registration of registrations) {
for (const child of registration.children) {
if (!declaredBy.has(child)) declaredBy.set(child, registration)
}
}
return declarations
.map(declaration => entryOf(declaration, registrations, declaredBy.get(declaration.key), types, kits))
.sort((left, right) => left.key.localeCompare(right.key))
}
/** The framework prop seats per scope, read from the merged standard-kit interfaces. */
function standardKits(files: readonly ScannedFile[]): ReadonlyMap<string, readonly string[]> {
const global = standardKitMembers(files, 'GlobalStandardProps')
const kits = new Map<string, readonly string[]>()
for (const scope of SCOPES) {
const extra = SCOPE_KIT[scope]
kits.set(scope, [...global, ...extra === undefined ? [] : standardKitMembers(files, extra)])
}
return kits
}
/** Resolve one declaration into its catalog entry. */
function entryOf(
declaration: SlotDeclaration,
registrations: readonly SlotRegistration[],
declaredBy: SlotRegistration | undefined,
types: ReadonlyMap<string, TypeDeclaration>,
kits: ReadonlyMap<string, readonly string[]>,
): SlotEntry {
const occupants = registrations.filter(registration => registration.key === declaration.key)
const cellOccupied = occupants.some(occupant =>
declaration.kind === 'single' || occupant.entryKey !== undefined)
const doc = docProse(declaration.jsDoc)
const owner = ownerShapes(declaration.ownerType, types)
return {
key: declaration.key,
kind: declaration.kind,
scope: declaration.scope,
summary: firstSentence(doc),
doc,
registerOptions: REGISTER_OPTIONS[declaration.kind as (typeof KINDS)[number]],
ownerProps: owner.declarations.map(type => truncate(type.text)),
ownerPropsReferences: owner.references,
standardProps: kits.get(declaration.scope) ?? [],
keyDomain: keyDomainOf(declaration, occupants),
hookContext: declaration.hookContext ?? '',
slotInject: declaration.injectType ?? '',
declaredBy: declaredBy === undefined
? 'the runtime itself (built in; always present)'
: `an entry in '${declaredBy.key}' (${shortPackage(declaredBy.package)}), so it exists while that entry is mounted`,
occupants: occupants.map(occupant => [
shortPackage(occupant.package),
occupant.component,
...occupant.id === undefined ? [] : [`id '${occupant.id}'`],
...occupant.entryKey === undefined ? [] : [`key '${occupant.entryKey}'`],
].join(' ')),
replaceRisk: cellOccupied && (declaration.kind === 'single' || declaration.kind === 'keyed')
? 'shadows-shipped-ui'
: 'none',
example: exampleOf(declaration),
source: declaration.source,
}
}
/**
* The owner-props contract at ONE level: the owner declaration(s) themselves,
* plus the names of the shapes their fields reference. Expanding transitively
* pulled the whole session model into four seats (one report exceeded 2400
* lines), which defeats the purpose of narrowing to a single slot — a registrant
* needs the fields and their documented meaning, not the type graph behind them.
*/
function ownerShapes(
ownerType: string | undefined,
types: ReadonlyMap<string, TypeDeclaration>,
): { declarations: TypeDeclaration[]; references: string[] } {
if (ownerType === undefined) return { declarations: [], references: [] }
const declarations = declaredTypes(referencedTypeNames([ownerType], types), types)
const own = new Set(declarations.map(declaration => declaration.name))
const references = referencedTypeNames(declarations.map(declaration => declaration.text), types)
.filter(name => !own.has(name))
return { declarations, references }
}
/** How a keyed slot's key domain is constrained, '' for the other kinds. */
function keyDomainOf(declaration: SlotDeclaration, occupants: readonly SlotRegistration[]): string {
if (declaration.kind !== 'keyed') return ''
const taken = [...new Set(occupants.flatMap(occupant => occupant.entryKey === undefined ? [] : [occupant.entryKey]))].sort()
const shipped = taken.length === 0 ? 'none are taken yet' : `already taken: ${taken.join(', ')}`
return declaration.keyProps === undefined
? `open: any string the owner dispatches (no compile-time key set), ${shipped}`
: `fixed by the owner's key table ${declaration.keyProps}, ${shipped}`
}
/** A runnable minimal registration for one slot, per cardinality. */
function exampleOf(declaration: SlotDeclaration): string {
const options = [`name: '${declaration.key}'`, ...KIND_EXAMPLE[declaration.kind] ?? []].join(', ')
return [
'return {',
" inject: ['slots'],",
' apply(ctx) {',
` ctx.slots.inject('${declaration.key}', () => ctx.slots.register(`,
` { ${options} },`,
" () => React.createElement('div', null, 'hello'),",
' ))',
' },',
'}',
].join('\n')
}
/** Extra example options per cardinality. */
const KIND_EXAMPLE: Readonly<Record<string, readonly string[]>> = {
single: [],
list: ["id: 'my-entry'", 'order: 100', "label: 'My entry'"],
keyed: ["key: '<one key the owner dispatches>'"],
chain: ['select: owner => null'],
}
/** Drop the `@deepseek-ai/dsh-` prefix so rows stay readable. */
function shortPackage(name: string): string {
return name.replace('@deepseek-ai/dsh-', '')
}
/** Truncate an over-long declaration, naming the truncation. */
function truncate(text: string): string {
return text.length > MAX_DECL_CHARS
? `${text.slice(0, MAX_DECL_CHARS)} /* …truncated — full shape in source */`
: text
}
/** JSDoc prose: comment markers and block tags removed, paragraphs kept. */
function docProse(jsDoc: string): string {
const lines = jsDoc.replace(/^\/\*\*/, '').replace(/\*\/$/, '').split('\n')
.map(line => line.replace(/^\s*\*?\s?/, '').replace(/\s+$/, ''))
const kept: string[] = []
for (const line of lines) {
if (line.trimStart().startsWith('@')) break
kept.push(line)
}
return kept.join('\n').replace(/\{@link\s+([^}]+)\}/g, '$1').replace(/\n{3,}/g, '\n\n').trim()
}
/** First sentence of a prose block, for the compact listing. */
function firstSentence(doc: string): string {
const flat = doc.replace(/\s+/g, ' ').trim()
const match = /^(.*?[.!?])(?:\s|$)/.exec(flat)
return (match?.[1] ?? flat).trim()
}
/** Render one value as a single-quoted TypeScript literal. */
function quote(value: string): string {
return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'").replaceAll('\n', '\\n')}'`
}
/** Render a readonly string-array literal. */
function list(values: readonly string[], indent: string): string {
if (values.length === 0) return '[]'
return ['[', ...values.map(value => `${indent} ${quote(value)},`), `${indent}]`].join('\n')
}
/**
* Render the generated data module.
* @param entries - resolved catalog entries.
* @returns the module source.
*/
export function renderClientCatalog(entries: readonly SlotEntry[]): string {
const lines: string[] = [
'/**',
' * Generated by scripts/gen-client-catalog.ts — do not edit by hand; run',
' * `pnpm run gen-client-catalog` to regenerate (freshness-gated by',
' * `pnpm run verify-client-catalog` in doc-sync).',
' *',
' * The compile-time contract of the shipped web bundle\'s slot surface, as',
' * `cordis_inspect what:"client"` serves it to the model: every SlotMap key a',
' * browser half can register into, what that register call must pass, what the',
' * component receives, who already occupies the seat, and which owner has to be',
' * mounted for the seat to exist. Data only — this module is the one legitimate',
' * meeting point of the two planes, so it carries strings, never client imports.',
' *',
' * @module @deepseek-ai/dsh-cordis-client-runner/client/slot-catalog',
' */',
'',
'/* jscpd:ignore-start */',
'/** One option a register call passes for a given slot cardinality. */',
'export interface ClientSlotOption {',
' /** Option name as written in the register options object. */',
' name: string',
' /** Whether the cardinality requires it. */',
' requirement: string',
' /** Accepted type, in source spelling. */',
' type: string',
' /** What it does, from the registrant\'s side. */',
' doc: string',
'}',
'',
'/** One browser-half slot a dynamic package can contribute UI into. */',
'export interface ClientSlotEntry {',
' /** SlotMap key passed as the register call\'s `name`. */',
' key: string',
' /** Cardinality: `single`, `list`, `keyed`, or `chain`. */',
' kind: string',
' /** Data scope: `root`, `session`, or `session-maybe`. */',
' scope: string',
' /** First sentence of the contract prose. */',
' summary: string',
' /** Full contract prose from the SlotMap declaration. */',
' doc: string',
' /** Options this cardinality accepts (beyond `name`). */',
' registerOptions: readonly ClientSlotOption[]',
' /** Declarations of the props the owner passes down, with their own documentation. */',
' ownerProps: readonly string[]',
' /** Names of the shapes those props reference; deliberately not expanded here. */',
' ownerPropsReferences: readonly string[]',
' /** Framework-supplied component props for this scope. */',
' standardProps: readonly string[]',
' /** For keyed slots: how the key set is constrained and which keys are taken. */',
' keyDomain: string',
' /** Opaque per-render-site context passed to slot-level hooks, when the slot declares one. */',
' hookContext: string',
' /** Slot-level inject face every entry receives, when the slot declares one. */',
' slotInject: string',
' /** Which mounted entry makes this slot exist. */',
' declaredBy: string',
' /** Entries the shipped composition already registered here. */',
' occupants: readonly string[]',
' /** `shadows-shipped-ui` when registering here replaces shipped UI; `none` when additive. */',
' replaceRisk: string',
' /** A minimal browser half that registers into this slot. */',
' example: string',
' /** Source pointer of the contract declaration. */',
' source: string',
'}',
'',
'/** Rules that apply to every browser-half contribution, in reading order. */',
'export const CLIENT_NOTES: readonly string[] = [',
...CLIENT_NOTES.map(note => ` ${quote(note)},`),
']',
'',
'/** Every slot the shipped web bundle declares, sorted by key. */',
// The entries below repeat by nature: seats of one cardinality share their
// register options and framework props verbatim, and that sameness is the
// contract a registrant reads, not a refactor waiting to happen. Clone
// detection is told so here rather than through a config exception, which is
// how this repository marks duplication that belongs to its subject.
'// Seats of one cardinality repeat their register options and framework props',
'// verbatim; that sameness IS the contract a registrant reads, so clone',
'// detection is told to skip the data rather than the file.',
'export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [',
]
for (const entry of entries) {
lines.push(' {')
lines.push(` key: ${quote(entry.key)},`)
lines.push(` kind: ${quote(entry.kind)},`)
lines.push(` scope: ${quote(entry.scope)},`)
lines.push(` summary: ${quote(entry.summary)},`)
lines.push(` doc: ${quote(entry.doc)},`)
if (entry.registerOptions.length === 0) {
lines.push(' registerOptions: [],')
} else {
lines.push(' registerOptions: [')
for (const option of entry.registerOptions) {
lines.push(' {')
lines.push(` name: ${quote(option.name)},`)
lines.push(` requirement: ${quote(option.requirement)},`)
lines.push(` type: ${quote(option.type)},`)
lines.push(` doc: ${quote(option.doc)},`)
lines.push(' },')
}
lines.push(' ],')
}
lines.push(` ownerProps: ${list(entry.ownerProps, ' ')},`)
lines.push(` ownerPropsReferences: ${list(entry.ownerPropsReferences, ' ')},`)
lines.push(` standardProps: ${list(entry.standardProps, ' ')},`)
lines.push(` keyDomain: ${quote(entry.keyDomain)},`)
lines.push(` hookContext: ${quote(entry.hookContext)},`)
lines.push(` slotInject: ${quote(entry.slotInject)},`)
lines.push(` declaredBy: ${quote(entry.declaredBy)},`)
lines.push(` occupants: ${list(entry.occupants, ' ')},`)
lines.push(` replaceRisk: ${quote(entry.replaceRisk)},`)
lines.push(` example: ${quote(entry.example)},`)
lines.push(` source: ${quote(entry.source)},`)
lines.push(' },')
}
lines.push(']', '/* jscpd:ignore-end */', '')
return lines.join('\n')
}
/**
* CLI entry: regenerate the catalog, or with `--check` fail when it is stale.
* @returns nothing; writes the artifact or reports freshness through the process.
*/
export function main(): void {
const content = renderClientCatalog(collectSlotEntries(root))
const destination = resolve(root, OUT)
if (process.argv.includes('--check')) {
let committed: string | null = null
try {
committed = readFileSync(destination, 'utf8')
} catch {
// Only ENOENT (never generated) is expected here, and its remedy is the
// same as a stale artifact's: regenerate.
committed = null
}
if (committed === content) {
console.log(`gen-client-catalog: ${OUT} is up to date.`)
process.exit(0)
}
console.error(`gen-client-catalog: stale — ${OUT}. Run \`pnpm run gen-client-catalog\` and commit the result.`)
process.exit(1)
}
mkdirSync(dirname(destination), { recursive: true })
writeFileSync(destination, content)
console.log(`gen-client-catalog: wrote ${OUT}.`)
}
// Run only when invoked as a script, not when imported by a test.
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) {
main()
}
+171 -78
View File
@@ -8,6 +8,13 @@
* projection enforces event modes, JSDoc parameter/return completeness, and
* signature type-link coverage; the inherited (vendor) tier renders to
* `docs/cordis-api/inherited.md`. `--check` verifies every generated artifact.
*
* Generated regions embed `file:line` source pointers, so inserting lines ABOVE a
* recorded symbol makes the committed output stale even though nothing about the
* symbol changed. Regenerate after editing any file this projection records — the
* failure otherwise surfaces as the "reproduces every committed catalog artifact
* byte for byte" test failing, which reads like a snapshot regression rather than
* a missing regeneration.
*/
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
@@ -32,7 +39,7 @@ import {
const root = resolve(import.meta.dirname, '..')
const SUBSYSTEMS_DIR = 'docs/subsystems'
const OUT_INHERITED = 'docs/cordis-api/inherited.md'
const OUT_RUNTIME_API = 'packages/self-modification/tool-cordis/src/api-catalog.ts'
const OUT_RUNTIME_API = 'packages/extensions/tool-cordis/src/api-catalog.ts'
export { REGION_BEGIN, REGION_END }
@@ -47,31 +54,35 @@ export const SERVICE_PAGE: Record<string, string> = {
agentDefaultModel: 'core.md',
agentPresets: 'core.md',
agents: 'core.md',
apiProxy: 'typert.md',
approval: 'approval.md',
attachments: 'attachment.md',
bash: 'bash.md',
bashEnv: 'bash.md',
clientModuleHost: 'client-modules.md',
shell: 'shell.md',
shellEnv: 'shell.md',
clientModules: 'client-modules.md',
codeRuntime: 'code-runtime.md',
commands: 'commands.md',
compact: 'compaction.md',
compaction: 'compaction.md',
cordisInspect: 'extensions.md',
credentials: 'credentials.md',
directoryPicker: 'workspace.md',
dynamicCordisRunner: 'extensions.md',
e2b: 'subprocess.md',
fs: 'filesystem.md',
goals: 'goal.md',
httpServer: 'http-server.md',
webServer: 'web-server.md',
invariants: 'invariants.md',
llm: 'llm-streaming.md',
lsp: 'lsp.md',
messageFeedback: 'feedback.md',
permission: 'permission.md',
permissionPresets: 'permission-presets.md',
planMode: 'plan.md',
pty: 'pty.md',
terminals: 'terminal.md',
sandbox: 'sandbox.md',
sandboxPolicy: 'sandbox.md',
sessionPersistence: 'persistence.md',
sessionQuery: 'session-query.md',
sessionReferences: 'session-reference.md',
sessionReferenceResolver: 'session-reference.md',
sessionProjectionCache: 'session-projection.md',
sessionProjections: 'session-projection.md',
sessions: 'session.md',
@@ -84,17 +95,17 @@ export const SERVICE_PAGE: Record<string, string> = {
subagents: 'subagent.md',
subprocess: 'subprocess.md',
systemPrompt: 'system-prompt.md',
tasks: 'tasks.md',
telemetry: 'telemetry.md',
jobs: 'jobs.md',
sessionTelemetry: 'session-telemetry.md',
tokenMeter: 'token-meter.md',
toolResultPrune: 'compaction.md',
toolResultPruner: 'compaction.md',
tools: 'tools.md',
typert: 'typert.md',
typertGateway: 'typert.md',
userInteraction: 'user-interaction.md',
userQuestions: 'user-questions.md',
web: 'web.md',
workflows: 'workflow.md',
workspace: 'workspace.md',
workflowEngine: 'workflow.md',
workspaceRegistry: 'workspace.md',
}
/**
@@ -105,10 +116,15 @@ export const SERVICE_PAGE: Record<string, string> = {
* `index.ts` files with a same-named service class — so a new service can
* never silently join this blind spot: it either enters {@link SERVICE_PAGE}
* or names itself here. Client-face keys (the projection analyzes the host
* face only) name the package README that owns their API.
* TODO(cordis-catalog-interface-services): the interface-typed and
* non-index-declared entries would all render once the projection resolves a
* Context key through its declaring file's imports to the class declaration.
* face only) name the package README that owns their surface.
*
* Two categories remain, and neither is a projection gap a scanning rule could
* close. An OPTIONAL key (`key?: X`) is a value the launcher or boot code
* installs before the tree mounts, which the analyzer skips by rule because no
* plugin provides it and `inject` cannot reach it. A client-face key belongs to
* the browser Context, which this host-face program never sees; the browser
* surface has its own generated catalog (`scripts/gen-client-catalog.ts`, served
* to a model as `cordis_runtime_inspect what:"client"`).
*/
export const SERVICE_WALK_EXEMPTIONS: Record<string, string> = {
agent: 'not a service: the DX accessor field on Agent.ctx (root accessor defaulting to undefined) — docs/subsystems/core.md owns the Agent handle',
@@ -117,24 +133,23 @@ export const SERVICE_WALK_EXEMPTIONS: Record<string, string> = {
configuredAgentIdentities: 'not a service: launcher-provided boot-context value (ConfiguredAgentIdentities | undefined) — packages/core/agent-loop/README.md owns this launcher contract',
launcherSessionQueryPath: 'not a service: launcher-provided boot-context value (string | undefined) — packages/session-query/session-query-sqlite/README.md owns this launcher contract',
dshHomePath: 'not a service: boot-provided root accessor function (typeof dshHomePath | undefined) for Loader !!js config expressions — packages/boot/app-boot/README.md owns the boot contract',
launcherEnvironment: 'not a service: launcher-provided root accessor value (EnvironmentSnapshot | undefined) — packages/util/environment/README.md owns this launcher contract',
lsp: 'interface-typed (LspService); implementing class Lsp is not the declared type name — packages/lsp/lsp/README.md owns the API',
apiProxy: 'interface-typed (ApiProxy) with the class in api-proxy.ts, not index.ts — packages/host/apiproxy/README.md owns the API',
launchEnvironment: 'not a service: launcher-provided root accessor value (LaunchEnvironmentSnapshot | undefined) — packages/util/launch-environment/README.md owns this launcher contract',
connection: 'interface-typed (HostConnectionHandle); implementing class HostConnectionService is declared in rpc-host.ts — packages/client/connection/README.md owns the API',
appShell: 'client-side interface-typed browser service — packages/client/web/README.md owns the API',
connection: 'client-side interface-typed browser service — packages/client/connection/README.md owns the API',
settingsScope: 'client-side settings-namespace transport service — packages/client/ui-settings/README.md owns the API',
chatFileMentions: 'client-side slot-contract accessor (ChatFileMentions) — packages/client/ui-conversation/README.md owns the API',
command: 'client-side interface-typed browser service — packages/client/ui-command/README.md owns the API',
commandUi: 'client-side interface-typed browser service — packages/client/ui-commands/README.md owns the API',
conversation: 'client-side interface-typed browser service — packages/client/ui-conversation/README.md owns the API',
conversationEvents: 'client-side interface-typed registry — packages/client/runtime/README.md owns the API',
conversationViews: 'client-side interface-typed registry — packages/client/runtime/README.md owns the API',
layout: 'client-side interface-typed browser service — packages/client/ui-layout/README.md owns the API',
locale: 'client-side interface-typed browser service — packages/client/locale/README.md owns the API',
models: 'client-side interface-typed browser service — packages/client/ui-model/README.md owns the API',
modelDirectories: 'client-side interface-typed browser service — packages/client/ui-model-selection/README.md owns the API',
modules: 'client-side interface-typed browser service — packages/client/modules/README.md owns the API',
remote: 'client-side interface-typed gateway accessor (ClientRemote) — packages/api/gateway/README.md owns the API',
sessionExport: 'client-side browser download controller — packages/session-query/session-export/README.md owns the API',
slash: 'client-side interface-typed browser service — packages/client/ui-slash/README.md owns the API',
sessionLogDownload: 'client-side browser download controller — packages/session-query/session-log-export/README.md owns the API',
inputTriggers: 'client-side interface-typed browser service — packages/client/ui-input-trigger/README.md owns the API',
timer: 'client-side dynamic-package timer service — packages/extensions/cordis-client-runner/README.md owns the API',
slots: 'client-side interface-typed browser service — packages/client/runtime/README.md owns the API',
theme: 'client-side interface-typed browser service — packages/client/ui-theme/README.md owns the API',
workspaces: 'client-side interface-typed browser service — packages/client/runtime/README.md owns the API',
@@ -153,6 +168,7 @@ export const EVENT_SCOPE_PAGE: Record<string, string> = {
'agent-preset': 'core.md',
'approval': 'approval.md',
'commands': 'commands.md',
'cordis': 'extensions.md',
'credentials': 'credentials.md',
'domain': 'storage.md',
'fs': 'filesystem.md',
@@ -163,7 +179,7 @@ export const EVENT_SCOPE_PAGE: Record<string, string> = {
'skills': 'skills.md',
'subagent': 'subagent.md',
'system-prompt': 'system-prompt.md',
'telemetry': 'telemetry.md',
'session-telemetry': 'session-telemetry.md',
'tools': 'tools.md',
'workflow': 'workflow.md',
}
@@ -179,13 +195,13 @@ export const EVENT_SCOPE_PAGE: Record<string, string> = {
* exemption cannot mask another declaration in that scope.
*/
export const EVENT_WALK_EXEMPTIONS: Record<string, string> = {
'command/executed': 'client-face local command acknowledgment — packages/client/ui-command/README.md owns the API',
'command/executed': 'client-face local command acknowledgment — packages/client/ui-commands/README.md owns the API',
'connection/reset': 'client-face transport signal — packages/client/runtime/README.md owns the API',
'locale/change': 'client-face locale switch signal — packages/client/locale/README.md owns the API',
'slash/input-begin-command': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the API',
'slash/input-consume-token': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the API',
'slash/input-insert-reference': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the API',
'slash/input-insert-text': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the API',
'slash/input-begin-command': 'client-face slash-input protocol — packages/client/ui-input-trigger/README.md owns the API',
'slash/input-consume-token': 'client-face slash-input protocol — packages/client/ui-input-trigger/README.md owns the API',
'slash/input-insert-reference': 'client-face slash-input protocol — packages/client/ui-input-trigger/README.md owns the API',
'slash/input-insert-text': 'client-face slash-input protocol — packages/client/ui-input-trigger/README.md owns the API',
'slots/changed': 'client-face slot invalidation signal — packages/client/runtime/README.md owns the API',
'theme/change': 'client-face theme switch signal — packages/client/ui-theme/README.md owns the API',
}
@@ -266,10 +282,10 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
ImageAttachmentRef: 'attachment.md',
SaveImageAttachment: 'attachment.md',
StoredImageAttachment: 'attachment.md',
BashExecRequest: 'bash.md',
BashExecSpec: 'bash.md',
BashProcess: 'bash.md',
BashRunResult: 'bash.md',
ShellExecRequest: 'shell.md',
ShellExecSpec: 'shell.md',
ShellProcess: 'shell.md',
ShellRunResult: 'shell.md',
DshEnvironment: 'subprocess.md',
SubprocessHandle: 'subprocess.md',
SubprocessOutcome: 'subprocess.md',
@@ -290,7 +306,7 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
FsInfo: 'filesystem.md',
FsObservation: 'filesystem.md',
FsPathInfo: 'filesystem.md',
FsPolicyExec: 'filesystem.md',
FsObservationActor: 'filesystem.md',
FsTarget: 'filesystem.md',
FsVersion: 'filesystem.md',
FsWriteIntent: 'filesystem.md',
@@ -306,9 +322,13 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
CommandDescriptor: 'commands.md',
CommandId: 'commands.md',
CommandResult: 'commands.md',
CommandSurface: 'commands.md',
LspProvider: 'lsp.md',
LspQueryRequest: 'lsp.md',
LspQueryResult: 'lsp.md',
LlmAdapter: 'llm-streaming.md',
PreparedLlmCall: 'llm-streaming.md',
LlmService: 'llm-streaming.md',
LlmRuntime: 'llm-streaming.md',
StreamChunk: 'llm-streaming.md',
SkillProviderControl: 'skills.md',
CreateSessionOptions: 'persistence.md',
@@ -323,17 +343,17 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
SandboxExecutionPolicy: 'sandbox.md',
SandboxMode: 'sandbox.md',
SandboxPolicy: 'sandbox.md',
PtyBackend: 'pty.md',
PtyReadRequest: 'pty.md',
PtyReadResult: 'pty.md',
PtySendOperation: 'pty.md',
PtySendRequest: 'pty.md',
PtySessionId: 'pty.md',
PtySessionSnapshot: 'pty.md',
PtySignal: 'pty.md',
PtySignalResult: 'pty.md',
PtySpawnRequest: 'pty.md',
PtySpawnResult: 'pty.md',
TerminalBackend: 'terminal.md',
TerminalReadRequest: 'terminal.md',
TerminalReadResult: 'terminal.md',
TerminalSendOperation: 'terminal.md',
TerminalSendRequest: 'terminal.md',
TerminalSessionId: 'terminal.md',
TerminalSessionSnapshot: 'terminal.md',
TerminalSignal: 'terminal.md',
TerminalSignalResult: 'terminal.md',
TerminalSpawnRequest: 'terminal.md',
TerminalSpawnResult: 'terminal.md',
SandboxPolicyRequest: 'sandbox.md',
ScopeKey: 'scope.md',
Scoped: 'scope.md',
@@ -389,19 +409,19 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
SubagentReportMessageSource: 'subagent.md',
SubagentReportOptions: 'subagent.md',
SubagentRun: 'subagent.md',
SubagentService: 'subagent.md',
SubagentRuntime: 'subagent.md',
SubagentStartRequest: 'subagent.md',
AssembleContext: 'system-prompt.md',
PromptContext: 'system-prompt.md',
PromptSection: 'system-prompt.md',
SystemPrompt: 'system-prompt.md',
ToolProviderResult: 'system-prompt.md',
TaskDoneListener: 'tasks.md',
TaskId: 'tasks.md',
TaskRead: 'tasks.md',
TaskSnapshot: 'tasks.md',
TaskStart: 'tasks.md',
TasksChangedListener: 'tasks.md',
JobDoneListener: 'jobs.md',
JobId: 'jobs.md',
JobRead: 'jobs.md',
JobSnapshot: 'jobs.md',
JobStart: 'jobs.md',
JobsChangedListener: 'jobs.md',
TokenMeasurement: 'token-meter.md',
CodeDispatchLog: 'tools.md',
PostToolDecision: 'tools.md',
@@ -415,7 +435,7 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
ToolExecutionToken: 'tools.md',
ToolGuard: 'tools.md',
ToolPresentationMode: 'tools.md',
ToolRegistry: 'tools.md',
ToolRuntime: 'tools.md',
ToolRestriction: 'tools.md',
ToolSchema: 'tools.md',
SettingsNamespace: 'settings.md',
@@ -428,9 +448,9 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
CredentialRef: 'credentials.md',
CredentialInfo: 'credentials.md',
ResolvedCredential: 'credentials.md',
AskUserQuestionAnswer: 'user-interaction.md',
AskUserQuestionRequest: 'user-interaction.md',
UserInteractionProvider: 'user-interaction.md',
AskUserQuestionAnswer: 'user-questions.md',
AskUserQuestionRequest: 'user-questions.md',
UserQuestionProvider: 'user-questions.md',
WebFetchProvider: 'web.md',
WebFetchRequest: 'web.md',
WebFetchResult: 'web.md',
@@ -438,10 +458,10 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
WebSearchRequest: 'web.md',
WebSearchResult: 'web.md',
WorkflowRun: 'workflow.md',
PresetOption: 'permission.md',
PresetSpec: 'permission.md',
PresetOption: 'permission-presets.md',
PresetSpec: 'permission-presets.md',
InvariantInstaller: 'invariants.md',
WebRoute: 'http-server.md',
WebRoute: 'web-server.md',
StorageBackend: 'storage.md',
StorageForms: 'storage.md',
Domain: 'storage.md',
@@ -451,7 +471,7 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
Workspace: 'workspace.md',
WorkspaceId: 'workspace.md',
WebBootGraph: 'client-modules.md',
TelemetryRecord: 'telemetry.md',
SessionTelemetryRecord: 'session-telemetry.md',
WorkflowRunInfo: 'workflow.md',
WorkflowStartRequest: 'workflow.md',
ProjectionDefinition: 'session-projection.md',
@@ -486,32 +506,71 @@ export const FOUNDATION_TYPE_NAMES: ReadonlySet<string> = new Set([
/** Project types deliberately documented outside the subsystems catalog. */
export const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
z: 'schemastery schema constructor is owned by vendor/schemastery (vendored upstream)',
BeginCommandRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
InsertReferenceRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
ConsumeTokenRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
InsertTextRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
BeginCommandRequest: 'event-local request contract is owned by packages/client/ui-input-trigger/src/types.ts',
InsertReferenceRequest: 'event-local request contract is owned by packages/client/ui-input-trigger/src/types.ts',
ConsumeTokenRequest: 'event-local request contract is owned by packages/client/ui-input-trigger/src/types.ts',
InsertTextRequest: 'event-local request contract is owned by packages/client/ui-input-trigger/src/types.ts',
AgentHandle: 'agent ownership handle is owned by packages/core/agent/README.md',
AgentPreset: 'discovered preset record is owned by packages/preset/agent-presets/README.md',
PresetMetadata: 'preset display text is owned by packages/preset/agent-presets/README.md',
BashEnvContributor: 'service-local extension type is owned by packages/bash/tool-bash/src/index.ts',
BashEnvVariableInfo: 'service-local metadata type is owned by packages/bash/tool-bash/src/index.ts',
CompactAgentContext: 'compaction service input is owned by packages/compact/compact/src/index.ts',
ManualCompactAgentContext: 'manual compaction service input is owned by packages/compact/compact/src/index.ts',
BashEnvContributor: 'service-local extension type is owned by packages/shell/tool-bash/src/index.ts',
BashEnvVariableInfo: 'service-local metadata type is owned by packages/shell/tool-bash/src/index.ts',
CompactionAgentContext: 'compaction service input is owned by packages/compaction/compaction/src/index.ts',
ManualCompactAgentContext: 'manual compaction service input is owned by packages/compaction/compaction/src/index.ts',
ClientResponse: 'wire response message is owned by packages/host/apiproxy/src/api/rpc.ts',
ApprovalRequestId: 'dynamic Plugin approval identity is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisErrorDetails: 'Cordis runtime error payload is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisInspectPlatform: 'Cordis inspect platform identity is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisInspectProviderManifest: 'Cordis inspect provider manifest is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisInspectProviderView: 'Cordis inspect provider view is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisInspectQueryRequest: 'Cordis inspect transport payload is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisInspectQueryResolution: 'Cordis inspect query result is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisInspectQueryResolved: 'Cordis inspect transport payload is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisInspectRequestId: 'Cordis inspect request identity is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisInspectResolveAck: 'Cordis inspect resolution acknowledgement is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisDynamicPackageId: 'dynamic Package identity is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisDynamicPluginId: 'dynamic Plugin identity is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisDynamicPluginRunId: 'dynamic Plugin run identity is owned by packages/extensions/cordis-host-runner/src/types.ts',
CordisDynamicRunMode: 'dynamic Plugin activation mode is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisClientSource: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisDefineReceipt: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisDefineRequest: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisHostHalfResult: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisInventoryRow: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisInvokeResult: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisPackageInspection: 'dynamic Package source inspection is owned by packages/extensions/cordis-host-runner/src/registry.ts',
DynamicCordisPluginInspection: 'dynamic Plugin inspection is owned by packages/extensions/cordis-host-runner/src/registry.ts',
DynamicCordisRequestResolved: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisRetracted: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisRunRequest: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisPackage: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisReference: 'dynamic Plugin reference is owned by packages/extensions/cordis-host-runner/src/registry.ts',
DynamicCordisRenderFailure: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisResolveAck: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisRunResolution: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisRunResponse: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisSnapshotRow: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisStopResponse: 'dynamic Plugin stop result is owned by packages/extensions/cordis-host-runner/src/types.ts',
DynamicCordisUndefineReceipt: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
HostCordisInspectProviderRegistration: 'Host inspect provider registration is owned by packages/extensions/cordis-host-runner/src/inspect-registry.ts',
DomainImpl: 'domain implementation contract is owned by packages/storage/storage-domain/README.md',
CommandExecution: 'executor return contract is owned by packages/interaction/commands/src/index.ts',
'z.core.JSONSchema.BaseSchema': 'zod projection output is owned by the zod v4 API',
'z.core.ToJSONSchemaParams': 'zod projection parameters are owned by the zod v4 API',
TypeRTDisposer: 'TypeRT lifecycle contract is owned by packages/typert/type-meta/README.md',
TypertDisposer: 'Typert lifecycle contract is owned by packages/typert/protocol/README.md',
InvokeRemoteRequest: 'gateway invocation contract is owned by packages/api/gateway/README.md',
LocaleDict: 'service-local dictionary fields are owned by packages/client/i18n/src/index.ts',
ThemeTokens: 'service-local token dictionary is owned by packages/client/ui-theme/src/index.ts',
Translate: 'service-local bound translator is owned by packages/client/i18n/src/index.ts',
WebUpgradeRoute:
'upgrade route registration contract is owned by packages/host/webserver/src/index.ts',
InvariantRegistration: 'service-local lifecycle handle is owned by packages/support/invariants/README.md',
KnobState: 'projection unit state fields are owned by packages/interaction/permission/README.md',
PermissionSelect: 'permissions projection payload is owned by packages/interaction/permission/src/types.ts',
InvariantRegistration: 'service-local lifecycle handle is owned by packages/runtime-diagnostics/invariants/README.md',
JsonValue: 'JSON value union is owned by packages/core/session/src/json.ts',
KnobState: 'projection unit state fields are owned by packages/interaction/permission-presets/README.md',
PermissionSelect: 'permissions projection payload is owned by packages/interaction/permission-presets/src/types.ts',
PromptAssembly: 'assembly result is owned by packages/core/system-prompt/README.md',
RequestRunId: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
RpcReceipt: 'carrier-layer receipt is owned by packages/host/apiproxy/src/api/rpc.ts',
Sandbox: 'external E2B SDK handle is owned by packages/e2b/e2b/README.md',
SessionForkSource: 'service-local fork input is owned by packages/core/session/src/index.ts',
SubagentRunEndInfo: 'event payload contract is owned by packages/subagent/subagent/src/types.ts',
@@ -526,6 +585,40 @@ export const CORDIS_CATALOG_POLICY: CordisCatalogPolicy = {
linkedTypePages: LINK_MAP,
foundationTypeNames: FOUNDATION_TYPE_NAMES,
typeLinkExemptions: TYPE_LINK_EXEMPTIONS,
runtimeServiceExclusions: new Set(['cordisInspect', 'dynamicCordisRunner']),
runtimeServices: [{
key: 'timer',
type: 'TimerService',
abstract: false,
doc: 'Disposable timer helpers mixed into Cordis contexts.',
source: 'vendor/timer/src/index.ts:12',
methods: [
{
signature: 'timeout(callback: () => void, delay: number): () => void',
jsDoc: '/** Run a callback once and return its disposer. */',
},
{
signature: 'timeout(delay: number): Promise<void>',
jsDoc: '/** Resolve after a delay; disposal rejects the pending promise. */',
},
{
signature: 'interval(callback: () => void, delay: number): () => void',
jsDoc: '/** Run a callback repeatedly and return its disposer. */',
},
{
signature: 'interval<R = any>(delay: number): AsyncIterableIterator<void, R, void>',
jsDoc: '/** Return an async iterator of timer ticks. */',
},
{
signature: 'throttle<F extends (...args: any[]) => void>(callback: F, delay: number, noTrailing?: boolean): F & { dispose: () => void }',
jsDoc: '/** Return a throttled function whose timer is disposed with the current fiber. */',
},
{
signature: 'debounce<F extends (...args: any[]) => void>(callback: F, delay: number): F & { dispose: () => void }',
jsDoc: '/** Return a debounced function whose timer is disposed with the current fiber. */',
},
],
}],
inheritedEvents: [
{ name: 'internal/plugin', summary: 'A plugin fiber was created.', source: 'vendor/cordis/src/events.ts:328' },
{ name: 'internal/status', summary: 'A fiber changed lifecycle state.', source: 'vendor/cordis/src/events.ts:330' },
@@ -551,7 +644,7 @@ export const CORDIS_CATALOG_POLICY: CordisCatalogPolicy = {
{ name: 'ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin', summary: 'Low-level service-store access and binding.', source: 'vendor/cordis/src/reflect.ts:7' },
{ name: 'ctx.extend / ctx.isolate / ctx.intercept', summary: 'Derive a child context (scoped services / isolation / interception).', source: 'vendor/cordis/src/context.ts:42' },
{ name: 'ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger', summary: 'Ambient handles onto the running context graph.', source: 'vendor/cordis/src/context.ts:16' },
{ name: 'ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)', summary: 'Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick).', source: 'vendor/timer/src/index.ts:4' },
{ name: 'ctx.timer (+ interval / timeout / throttle / debounce)', summary: 'Disposable timer helpers. The `timer` key is provided at runtime; the four supported helpers are mixed onto ctx directly (declared via Pick).', source: 'vendor/timer/src/index.ts:4' },
{ name: 'ctx.loader', summary: 'The config Loader that booted the app (present under the loader).', source: 'vendor/loader/src/index.ts:30' },
{ name: 'ctx.hmr', summary: 'The hot-module-reload watcher (present under the hmr plugin).', source: 'vendor/hmr/src/index.ts:15' },
],
+57
View File
@@ -0,0 +1,57 @@
/** Generate model-visible Host/Client Service and Event inspect catalogs. */
import { mkdirSync, writeFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { projectCordisCatalog } from '@deepseek-ai/dsh-typert-generator'
import type { CordisCatalogModel, ServiceMethodEntry } from '@deepseek-ai/dsh-typert-generator'
import { CORDIS_CATALOG_POLICY } from './gen-cordis-catalog.ts'
const root = resolve(import.meta.dirname, '..')
const CLIENT_OUT = 'packages/extensions/cordis-client-runner/src/client/api-catalog.ts'
const CLIENT_SERVICES: Readonly<Record<string, readonly string[]>> = {
layout: ['toggleSidebar', 'openDetails', 'closeDetails'],
locale: ['getLocale', 'getSnapshot', 'subscribe', 'setLocale', 'register', 'bind'],
sessions: ['open', 'openSubagent', 'setSubagentCatalogOpen', 'refreshSubagents', 'search', 'fork', 'scope', 'binding'],
slots: ['register', 'inject'],
theme: ['getTheme', 'setTheme', 'register', 'overrideTokens'],
workspaces: [
'connectWorkspace', 'startSession', 'create', 'pickDirectory', 'listDirectory', 'createDirectory',
'openPath', 'rename', 'delete', 'insertSessionBefore', 'archiveSession',
],
}
const CLIENT_EVENTS = new Set([
'connection/reset',
'locale/change',
'slots/changed',
'theme/change',
])
function methodName(method: ServiceMethodEntry): string | undefined {
return /^(?:declare\s+)?(?:readonly\s+)?(?:async\s+)?([A-Za-z_$][\w$]*)/.exec(method.signature)?.[1]
}
function clientModel(model: CordisCatalogModel): CordisCatalogModel {
return {
services: model.services.flatMap((service) => {
const allowed = CLIENT_SERVICES[service.key]
if (allowed === undefined) return []
const names = new Set(allowed)
return [{ ...service, methods: service.methods.filter(method => names.has(methodName(method) ?? '')) }]
}),
events: model.events.filter(event => CLIENT_EVENTS.has(event.name)),
}
}
function main(): void {
const { projector, model } = projectCordisCatalog(root, CORDIS_CATALOG_POLICY, 'client')
const destination = resolve(root, CLIENT_OUT)
const source = projector.renderRuntimeApi(clientModel(model))
.replaceAll('@deepseek-ai/dsh-tool-cordis/api-catalog', '@deepseek-ai/dsh-cordis-client-runner/client/api-catalog')
mkdirSync(dirname(destination), { recursive: true })
writeFileSync(destination, source)
console.log(`gen-cordis-inspect-catalog: wrote ${CLIENT_OUT}`)
}
main()
+84 -51
View File
@@ -111,7 +111,7 @@ const SERVICE_ROLES: ServiceRole[] = [
title: 'LLM adapter registry',
mode: 'seam',
implementations: ['llm-deepseek', 'llm-pi-ai', 'llm-replay'],
consumers: ['agent-loop', 'compact-basic'],
consumers: ['agent-loop', 'compaction-basic'],
note: 'Adapters register provider implementations; the loop and compaction call the provider-neutral stream service.',
},
{
@@ -119,15 +119,15 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'token-meter',
title: 'Replay token measurement',
mode: 'core',
consumers: ['compact-basic'],
consumers: ['compaction-basic'],
note: 'Owns isolated per-session replay folds; pressure consumers share immutable revisioned measurements.',
},
{
key: 'toolResultPrune',
pkg: 'compact-tool-result-prune',
key: 'toolResultPruner',
pkg: 'compaction-tool-result-pruner',
title: 'Model-free tool-result pruning',
mode: 'core',
consumers: ['compact-basic'],
consumers: ['compaction-basic'],
note: 'Rewrites oversized current tool results through replayable single-node surface replacements before summary compaction.',
},
{
@@ -157,7 +157,7 @@ const SERVICE_ROLES: ServiceRole[] = [
{
key: 'typertGateway',
pkg: 'api-gateway',
title: 'TypeRT Host invocation gateway',
title: 'Typert Host invocation gateway',
mode: 'core',
note: 'Associates generated Remote descriptors with live Cordis services, resolves registered identities, and exposes unary calls through the shared Connection RPC carrier.',
},
@@ -167,7 +167,7 @@ const SERVICE_ROLES: ServiceRole[] = [
title: 'Durable session persistence seam',
mode: 'seam',
implementations: ['session-persistence-jsonl', 'session-persistence-sqlite'],
consumers: ['agent-loop', 'tool-bash', 'hooks-claude', 'hooks-codex', 'session-query', 'session-query-sqlite', 'message-feedback'],
consumers: ['agent-loop', 'tool-bash', 'hooks-claude-code', 'hooks-codex', 'session-query', 'session-query-sqlite', 'message-feedback'],
note: 'Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time.',
},
{
@@ -175,7 +175,7 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'settings',
title: 'User-settings seam',
mode: 'seam',
implementations: ['settings-local'],
implementations: ['settings-file'],
consumers: ['llm-deepseek', 'llm-pi-ai', 'apiproxy'],
note: 'Plugins register namespace schemas and resolve layered values; providers store the raw document. The LLM adapters register their entry config as the composition base under the user section; the web gateway serves redacted layered descriptors and writes the user layer.',
},
@@ -189,7 +189,7 @@ const SERVICE_ROLES: ServiceRole[] = [
note: 'Configuration carries references to secrets; providers own the values. Consumers resolve per operation, so a rotated credential reaches the very next request; the web gateway exposes value-free views and write-only storage.',
},
{
key: 'telemetry',
key: 'sessionTelemetry',
pkg: 'session-telemetry',
title: 'Session telemetry seam',
mode: 'seam',
@@ -222,7 +222,7 @@ const SERVICE_ROLES: ServiceRole[] = [
note: 'Owns local per-assistant-message feedback, lifecycle and target validation, per-item compare-and-set, and the Host unary Remote contract without entering Session history or telemetry.',
},
{
key: 'workspace',
key: 'workspaceRegistry',
pkg: 'workspace',
title: 'Workspace entity registry',
mode: 'core',
@@ -239,7 +239,7 @@ const SERVICE_ROLES: ServiceRole[] = [
note: 'The interface supplies exact reads, filters, and traces; its concrete backend adds full-text reconciliation, ranking, snippets, and cursor generations, while the model consumer owns workspace authority and cursor-free rendering.',
},
{
key: 'sessionReferences',
key: 'sessionReferenceResolver',
pkg: 'session-reference',
title: 'Cross-session snapshot preparation',
mode: 'core',
@@ -250,7 +250,7 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'session-title',
title: 'Log-backed session titles',
mode: 'seam',
implementations: ['session-title-first-message-llm', 'session-title-all-messages-llm'],
implementations: ['session-title-first-prompt-llm', 'session-title-all-prompts-llm'],
note: 'Owns the deterministic fallback, latest-title fold, and sole optional asynchronous provider registration.',
},
{
@@ -258,7 +258,7 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'system-prompt',
title: 'System prompt assembly registry',
mode: 'core',
consumers: ['agent-loop', 'tools', 'tool-fs', 'tool-pty', 'tool-web'],
consumers: ['agent-loop', 'tools', 'tool-fs', 'tool-terminal', 'tool-web'],
note: 'Collects prompt sections and model-facing tool schemas for each step.',
},
{
@@ -266,12 +266,12 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'tools',
title: 'Tool registry and guarded execution pipeline',
mode: 'core',
consumers: ['agent-loop', 'tool-ask-user', 'tool-bash', 'tool-cordis', 'tool-fs', 'tool-pty', 'tool-skill', 'tool-subagent', 'tool-todo', 'tool-web'],
consumers: ['agent-loop', 'tool-ask-user', 'tool-bash', 'tool-cordis', 'tool-fs', 'tool-terminal', 'tool-skill', 'tool-subagent', 'tool-todo', 'tool-web'],
note: 'Registers capabilities, owns Code Mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation.',
},
{
key: 'userInteraction',
pkg: 'user-interaction',
key: 'userQuestions',
pkg: 'user-questions',
title: 'Human question/answer seam',
mode: 'seam',
consumers: ['tool-ask-user'],
@@ -319,7 +319,7 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'skill',
title: 'Skill provider registry',
mode: 'seam',
implementations: ['skill-badge', 'skill-local'],
implementations: ['skill-badge', 'skill-filesystem'],
consumers: ['tool-skill'],
note: 'Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies.',
},
@@ -368,34 +368,34 @@ const SERVICE_ROLES: ServiceRole[] = [
title: 'Subprocess seam',
mode: 'seam',
implementations: ['subprocess-local', 'subprocess-e2b'],
consumers: ['bash-local', 'bash-sandbox', 'pty-local', 'lsp-local', 'subagent-acp', 'subagent-codex', 'subagent-claude-code'],
consumers: ['bash-local', 'bash-sandbox', 'terminal-bash', 'lsp-stdio', 'subagent-acp', 'subagent-codex', 'subagent-claude-code'],
note: 'The bash executors, the PTY shell backend, the LSP host, and the out-of-process ACP, Codex, and Claude Code subagent backends spawn through ctx.subprocess; the service owns process coordinates, tree/session lifetime, stdio dispositions, terminal mechanics, and kill escalation.',
},
{
key: 'bash',
pkg: 'bash',
key: 'shell',
pkg: 'shell',
title: 'Bash executor seam',
mode: 'seam',
implementations: ['bash-local', 'bash-sandbox', 'pwsh-local'],
consumers: ['tool-bash', 'tool-pwsh', 'hooks-claude', 'hooks-codex'],
consumers: ['tool-bash', 'tool-pwsh', 'hooks-claude-code', 'hooks-codex'],
note: 'The model-facing shell tools and hook bridges consume this seam; sandboxed, remote, or PowerShell executors replace bash-local without touching them.',
},
{
key: 'bashEnv',
pkg: 'bash-env',
key: 'shellEnv',
pkg: 'shell-env',
title: 'Managed bash environment registry',
mode: 'core',
consumers: ['tool-bash', 'tool-pwsh'],
note: 'Plugins declare effect-scoped DSH_* facts; each shell tool collects one trusted snapshot per execution and its executor rebuilds the namespace.',
},
{
key: 'pty',
pkg: 'pty',
key: 'terminals',
pkg: 'terminal',
title: 'Persistent PTY session registry',
mode: 'seam',
implementations: ['pty-local'],
consumers: ['tool-pty'],
note: 'The registry owns exact-Agent session identity and cleanup; backends own terminal mechanics, while tool-pty exposes the owner-scoped model tools.',
implementations: ['terminal-bash'],
consumers: ['tool-terminal'],
note: 'The registry owns exact-Agent session identity and cleanup; backends own terminal mechanics, while tool-terminal exposes the owner-scoped model tools.',
},
{
key: 'sandbox',
@@ -403,7 +403,7 @@ const SERVICE_ROLES: ServiceRole[] = [
title: 'Process-sandbox seam',
mode: 'seam',
implementations: ['sandbox-local'],
consumers: ['bash-sandbox', 'pty-local'],
consumers: ['bash-sandbox', 'terminal-bash'],
note: 'Consumers hand over the exact argv they are about to spawn; same-world backends wrap it under a per-call policy and report enforcement.',
},
{
@@ -412,7 +412,7 @@ const SERVICE_ROLES: ServiceRole[] = [
title: 'Sandbox policy home',
mode: 'core',
implementations: [],
consumers: ['bash-sandbox', 'fs-sandbox', 'pty-local'],
consumers: ['bash-sandbox', 'fs-sandbox', 'terminal-bash'],
note: 'The one home for the deployment default mode + workspace root; only the sandboxed executor and provider read the service (the tool layers use the pure `sandbox/mode` fold it also exports). Both enforcing families read it so bash and fs cannot confine to different roots.',
},
{
@@ -425,8 +425,8 @@ const SERVICE_ROLES: ServiceRole[] = [
note: 'One-shot permission decisions dispatched over the `approval/request` waterfall; answerers are listeners (the ACP bridge for its own agents), absence fails closed to `unavailable`.',
},
{
key: 'permission',
pkg: 'permission',
key: 'permissionPresets',
pkg: 'permission-presets',
title: 'Permission presets',
mode: 'core',
implementations: [],
@@ -448,16 +448,16 @@ const SERVICE_ROLES: ServiceRole[] = [
mode: 'seam',
implementations: ['fs-local', 'fs-sandbox', 'fs-e2b'],
consumers: ['tool-fs'],
companions: ['fs-policy'],
note: 'tool-fs executes read/write/edit through ctx.fs; fs-sandbox fences mutations by the shared sandbox mode; fs-policy contributes observed-state checks through the fs/* event gate.',
companions: ['fs-observation-policy'],
note: 'tool-fs executes read/write/edit through ctx.fs; fs-sandbox fences mutations by the shared sandbox mode; fs-observation-policy contributes observed-state checks through the fs/* event gate.',
},
{
key: 'compact',
pkg: 'compact',
key: 'compaction',
pkg: 'compaction',
title: 'Compaction seam',
mode: 'seam',
implementations: ['compact-basic'],
consumers: ['compact-basic'],
implementations: ['compaction-basic'],
consumers: ['compaction-basic'],
note: 'The basic backend consumes post-step pressure and request-error recovery events; there is no model-facing compact tool.',
},
{
@@ -465,25 +465,25 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'subagent',
title: 'Subagent provider and continuation service',
mode: 'seam',
implementations: ['subagent-spawn', 'subagent-fork', 'subagent-acp', 'subagent-codex', 'subagent-claude-code', 'subagent-dsh-sdk'],
implementations: ['subagent-spawn-in-process', 'subagent-fork-in-process', 'subagent-acp', 'subagent-codex', 'subagent-claude-code', 'subagent-dsh-sdk'],
consumers: ['tool-subagent', 'tool-subagent-control', 'tool-ralph'],
note: 'Providers implement transports; the service also owns optional Activation-based continuation orchestration, tool-subagent selects one-shot or continuable delegation, tool-subagent-control delivers follow-ups, and tool-ralph requires one fresh structured-output route.',
},
{
key: 'tasks',
pkg: 'tasks',
title: 'Background task registry',
key: 'jobs',
pkg: 'jobs',
title: 'Background job registry',
mode: 'seam',
implementations: ['tasks-local'],
consumers: ['tool-bash', 'tool-pty', 'tool-subagent', 'tool-tasks'],
note: 'Producers (background bash, PTY sends, and subagent delegations) register running work; tool-tasks is the model-facing controller that reads, lists, and kills it; tasks-local is the process-local registry.',
implementations: ['jobs-local'],
consumers: ['tool-bash', 'tool-terminal', 'tool-subagent', 'tool-jobs'],
note: 'Producers (background bash, PTY sends, and subagent delegations) register running work; tool-jobs is the model-facing controller that reads, lists, and kills it; jobs-local is the process-local registry.',
},
{
key: 'web',
pkg: 'web',
title: 'Web access provider registry',
mode: 'seam',
implementations: ['web-search-exa', 'web-search-perplexity', 'web-search-deepseek', 'web-fetch-local'],
implementations: ['web-search-exa', 'web-search-perplexity', 'web-search-deepseek', 'web-fetch-http'],
consumers: ['tool-web'],
note: 'Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names.',
},
@@ -506,7 +506,7 @@ const SERVICE_ROLES: ServiceRole[] = [
note: 'Discriminated interaction capability: the native backend opens one OS chooser on the host display, the browse backend serves listing/creation primitives for the in-app browser; dual-face backends fill ui-workspace directory-flow slots from their browser halves (no wire advertisement).',
},
{
key: 'httpServer',
key: 'webServer',
pkg: 'webserver',
title: 'HTTP route registration',
mode: 'core',
@@ -514,7 +514,7 @@ const SERVICE_ROLES: ServiceRole[] = [
note: 'Plain node:http carrier: named-route registry, index transform taps, and the static dist fallback; web-transport plugins register their own routes.',
},
{
key: 'clientModuleHost',
key: 'clientModules',
pkg: 'modules',
title: 'Client plugin graph host',
mode: 'core',
@@ -522,14 +522,47 @@ const SERVICE_ROLES: ServiceRole[] = [
note: 'Composes the __DSH_BOOT__ entry graph from an incremental dsh.client scan, serves plugin bundles, and notifies rebuilt/graph-changed subscribers.',
},
{
key: 'workflows',
key: 'workflowEngine',
pkg: 'workflow',
title: 'Workflow script engine',
mode: 'seam',
implementations: ['workflow-workerthread'],
implementations: ['workflow-worker-thread'],
consumers: ['tool-workflow', 'tool-ralph'],
note: 'One engine per context, as in bash, with no named-provider registry; the general workflow and fixed Ralph consumers start runs whose agent() calls fan out through ctx.subagents.',
},
{
key: 'lsp',
pkg: 'lsp',
title: 'Language-server navigation seam',
mode: 'seam',
implementations: ['lsp-local'],
consumers: ['tool-lsp'],
note: 'Provider registration and selection plus normalized query execution over exactly four operations; the seam offers no protocol escape hatch, so a backend translates into the normalized request and result.',
},
{
key: 'apiProxy',
pkg: 'apiproxy',
title: 'Host API dispatch',
mode: 'core',
consumers: ['connection'],
note: 'The transport-agnostic host gateway face: it dispatches browser API calls, and each open host stream subscribes to the events it forwards rather than being pushed to through a broadcast verb.',
},
{
key: 'dynamicCordisRunner',
pkg: 'cordis-host-runner',
title: 'Dynamic Cordis package host runner',
mode: 'core',
consumers: ['tool-cordis'],
note: 'Owns the in-memory definition registry, the vm sandbox for host halves, and the request-run round trip; browser pages reach the same service over the wire through its remote namespace.',
},
{
key: 'cordisInspect',
pkg: 'cordis-host-runner',
title: 'Dynamic Cordis inspect registry',
mode: 'core',
consumers: ['tool-cordis'],
note: 'Registers host inspect providers, mirrors the client provider manifest, and routes client queries through the dynamic Cordis transport.',
},
]
function generatedHeader(title: string): string[] {
@@ -1265,7 +1298,7 @@ function renderLifecycle(): string {
'',
'The `assistant/message` event records every successful provider call, including content-less and `max-tokens` finishes. Empty content stays out of derived history, while the durable event keeps usage and `sourceEventSeqs` listing the exact `assistant/chunk` events, including an explicit empty list.',
'',
'`dsh-compact-basic` uses `agent/pre-step` for pressure before request derivation and `agent/request-error` only for canonical context overflow. Once either trigger qualifies, optional tool-result pruning runs before summary selection. Recovery works between the closed failed step and failed turn close, and opens a fresh retry turn only when pruning or summarization advances the surface replacement generation; otherwise the original request error remains authoritative.',
'`dsh-compaction-basic` uses `agent/pre-step` for pressure before request derivation and `agent/request-error` only for canonical context overflow. Once either trigger qualifies, optional tool-result pruning runs before summary selection. Recovery works between the closed failed step and failed turn close, and opens a fresh retry turn only when pruning or summarization advances the surface replacement generation; otherwise the original request error remains authoritative.',
'',
'The returned `agent/pre-step` decision is authoritative; listeners wrapping `next()` preserve downstream messages unless replacement is intentional. Steering and injected context pass through the same waterfall after a later claim operation takes their next-step batch.',
'',
+2 -2
View File
@@ -46,8 +46,8 @@ describe('tierExternalDeps', () => {
const { manifests, names } = workspace({
// Root tooling and test infrastructure never ship, whichever section declares them.
'package.json': { dependencies: { 'root-runtime-looking': '^1' }, devDependencies: { 'lint-tool': '^1' } },
'packages/support/loader-smoke/package.json': { name: '@deepseek-ai/dsh-loader-smoke', dependencies: { 'smoke-helper': '^1' } },
'packages/client/test-runtime/package.json': { name: '@deepseek-ai/dsh-client-test-runtime', dependencies: { 'test-lib': '^1' } },
'packages/test-support/loader-smoke/package.json': { name: '@deepseek-ai/dsh-loader-smoke', dependencies: { 'smoke-helper': '^1' } },
'packages/test-support/client-runtime/package.json': { name: '@deepseek-ai/dsh-client-test-runtime', dependencies: { 'test-lib': '^1' } },
'website/package.json': { devDependencies: { 'site-tool': '^1' } },
// A plugin package's runtime dependency ships even when no app mounts it by default.
'packages/mcp/mcp-client/package.json': { name: '@deepseek-ai/dsh-mcp-client', dependencies: { 'protocol-sdk': '^1' }, devDependencies: { 'protocol-fixture-server': '^1' } },
+2 -2
View File
@@ -32,8 +32,8 @@ const ALL_KINDS = ['dependencies', 'devDependencies', 'optionalDependencies', 'p
*/
const DEV_ONLY_AREAS = [
'package.json',
'packages/support/',
'packages/client/test-runtime/',
'packages/test-support/',
'packages/test-support/client-runtime/',
'website/',
'examples/',
'native/',
+97 -71
View File
@@ -15,51 +15,52 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
import { createScope } from '@deepseek-ai/dsh-scope'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
import SessionQuerySqlite from '@deepseek-ai/dsh-session-query-sqlite'
import SqliteSessionQueryEngine from '@deepseek-ai/dsh-session-query-sqlite'
import GoalService from '@deepseek-ai/dsh-goal'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools'
import ToolRuntime, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools'
import LocalBashExecutor from '@deepseek-ai/dsh-bash-local'
import * as BashEnvPlugin from '@deepseek-ai/dsh-bash-env'
import * as BashEnvPlugin from '@deepseek-ai/dsh-shell-env'
import { PwshLocalExecutor } from '@deepseek-ai/dsh-pwsh-local'
import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local'
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
import { AttachmentStore } from '@deepseek-ai/dsh-attachment'
import type { ImageAttachmentLimits, ImageAttachmentRef, SaveImageAttachment, StoredImageAttachment } from '@deepseek-ai/dsh-attachment'
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
import PlanModeService from '@deepseek-ai/dsh-plan-mode'
import WebService from '@deepseek-ai/dsh-web'
import UserQuestionService from '@deepseek-ai/dsh-user-questions'
import PlanModeController from '@deepseek-ai/dsh-plan-mode'
import WebRuntime from '@deepseek-ai/dsh-web'
import * as WebSearchExa from '@deepseek-ai/dsh-web-search-exa'
import * as WebFetchLocal from '@deepseek-ai/dsh-web-fetch-local'
import SubagentService from '@deepseek-ai/dsh-subagent'
import * as WebFetchLocal from '@deepseek-ai/dsh-web-fetch-http'
import SubagentRuntime from '@deepseek-ai/dsh-subagent'
import type { SubagentProvider, SubagentReportDelivery } from '@deepseek-ai/dsh-subagent'
import * as ToolSubagentControl from '@deepseek-ai/dsh-tool-subagent-control'
import * as ToolSubagentListAgents from '@deepseek-ai/dsh-tool-subagent-control/list-agents'
import * as ToolSubagentReport from '@deepseek-ai/dsh-tool-subagent-report'
import SkillService from '@deepseek-ai/dsh-skill'
import * as SkillLocal from '@deepseek-ai/dsh-skill-local'
import LocalTaskService from '@deepseek-ai/dsh-tasks-local'
import SkillRegistry from '@deepseek-ai/dsh-skill'
import * as SkillFileSystem from '@deepseek-ai/dsh-skill-filesystem'
import LocalJobRegistry from '@deepseek-ai/dsh-jobs-local'
import * as ToolAskUser from '@deepseek-ai/dsh-tool-ask-user'
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import * as ToolPwsh from '@deepseek-ai/dsh-tool-pwsh'
import * as ToolBashPersistent from '@deepseek-ai/dsh-tool-bash-persistent'
import CordisHostRunner from '@deepseek-ai/dsh-cordis-host-runner'
import * as ToolCordis from '@deepseek-ai/dsh-tool-cordis'
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
import * as ToolFsSearch from '@deepseek-ai/dsh-tool-fs-search'
import * as ToolStrReplaceEditor from '@deepseek-ai/dsh-tool-str-replace-editor'
import PtyService from '@deepseek-ai/dsh-pty'
import * as ToolPty from '@deepseek-ai/dsh-tool-pty'
import TerminalSessionService from '@deepseek-ai/dsh-terminal'
import * as ToolPty from '@deepseek-ai/dsh-tool-terminal'
import * as ToolGoal from '@deepseek-ai/dsh-tool-goal'
import * as ToolSchedule from '@deepseek-ai/dsh-tool-schedule'
import * as ToolSchedule from '@deepseek-ai/dsh-schedule'
import Lsp from '@deepseek-ai/dsh-lsp'
import * as ToolLsp from '@deepseek-ai/dsh-tool-lsp'
import * as ToolSkill from '@deepseek-ai/dsh-tool-skill'
import * as ToolSessionQuery from '@deepseek-ai/dsh-tool-session-query'
import * as ToolTasks from '@deepseek-ai/dsh-tool-tasks'
import * as ToolTasks from '@deepseek-ai/dsh-tool-jobs'
import * as ToolTodo from '@deepseek-ai/dsh-tool-todo'
import * as ToolSubagent from '@deepseek-ai/dsh-tool-subagent'
import * as ToolWeb from '@deepseek-ai/dsh-tool-web'
import VmWorkflowEngine from '@deepseek-ai/dsh-workflow-workerthread'
import VmWorkflowEngine from '@deepseek-ai/dsh-workflow-worker-thread'
import * as ToolRalph from '@deepseek-ai/dsh-tool-ralph'
import * as ToolWorkflow from '@deepseek-ai/dsh-tool-workflow'
@@ -135,7 +136,7 @@ async function mountCatalogChildScope(
* prompt and registry; each recipe supplies only package-specific seams and
* config, while `dir` participates in the completeness check.
*/
interface ToolPackage {
export interface ToolPackage {
/** The npm package name, used as the catalog section heading. */
pkg: string
/** The `packages/<group>/<dir>` leaf name — matched by the completeness guard. */
@@ -158,7 +159,7 @@ interface ToolPackage {
/** Agent-like scope key whose tool view is catalogued instead of the global view. */
scope?: (ctx: Context) => Agent
/**
* Config for the caller's `ToolRegistry` mount. The registry itself ships a
* Config for the caller's `ToolRuntime` mount. The registry itself ships a
* model-facing tool (`run_code`, registered under a non-native `mode`), so
* ITS catalog entry boots the registry in the mode that exposes it;
* every other entry uses the default (native) registry.
@@ -184,10 +185,10 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-ask-user',
dir: 'tool-ask-user',
source: 'packages/interaction/tool-ask-user/src/index.ts',
requires: ['ctx.tools', 'ctx.userInteraction'],
requires: ['ctx.tools', 'ctx.userQuestions'],
writes: ['tool/call', 'tool/result after a UI/provider answers the question'],
async mount(ctx) {
await ctx.plugin(UserInteractionService)
await ctx.plugin(UserQuestionService)
await ctx.plugin(ToolAskUser)
},
note:
@@ -211,67 +212,68 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-plan-mode',
dir: 'plan-mode',
source: 'packages/plan/plan-mode/src/index.ts',
requires: ['ctx.tools', 'ctx.systemPrompt', 'ctx.userInteraction (execution time, opportunistic)'],
requires: ['ctx.tools', 'ctx.systemPrompt', 'ctx.userQuestions (execution time, opportunistic)'],
writes: ['tool/call', 'plan/mode inactive on an approved review', 'tool/result'],
async mount(ctx) {
await ctx.plugin(PlanModeService, { section: 'Tool catalog schema harvest.' })
await ctx.plugin(PlanModeController, { section: 'Tool catalog schema harvest.' })
},
note:
'exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-interaction seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary.',
'exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-questions seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary.',
},
{
pkg: '@deepseek-ai/dsh-tool-bash',
dir: 'tool-bash',
source: 'packages/bash/tool-bash/src/index.ts',
requires: ['ctx.tools', 'ctx.bash', 'ctx.systemPrompt', 'ctx.bashEnv', 'ctx.tasks at call time for run_in_background'],
source: 'packages/shell/tool-bash/src/index.ts',
requires: ['ctx.tools', 'ctx.shell', 'ctx.systemPrompt', 'ctx.shellEnv', 'ctx.jobs at call time for run_in_background'],
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalSubprocessRuntime)
await ctx.plugin(BashEnvPlugin)
await ctx.plugin(LocalBashExecutor)
await ctx.plugin(ToolBash)
},
note:
'The bash tool is the model-facing consumer of the bash executor seam. A `run_in_background` run registers with the generic `ctx.tasks` runtime and is collected/stopped through the `task_*` tools from `@deepseek-ai/dsh-tool-tasks`; the `enableRunInBackground` config (default true) removes the parameter entirely when disabled.',
'The bash tool is the model-facing consumer of the bash executor seam. A `run_in_background` run registers with the generic `ctx.jobs` runtime and is collected/stopped through the `job_*` tools from `@deepseek-ai/dsh-tool-jobs`; the `enableRunInBackground` config (default true) removes the parameter entirely when disabled.',
},
{
pkg: '@deepseek-ai/dsh-tool-pwsh',
dir: 'tool-pwsh',
source: 'packages/bash/tool-pwsh/src/index.ts',
requires: ['ctx.tools', 'ctx.bash', 'ctx.systemPrompt', 'ctx.bashEnv', 'ctx.tasks at call time for run_in_background'],
source: 'packages/shell/tool-pwsh/src/index.ts',
requires: ['ctx.tools', 'ctx.shell', 'ctx.systemPrompt', 'ctx.shellEnv', 'ctx.jobs at call time for run_in_background'],
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
// The pwsh tool consumes the bash executor seam; the schema harvest
// mounts the pwsh-local implementation so the inject resolves without
// executing anything (registration never spawns a process).
await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalSubprocessRuntime)
await ctx.plugin(BashEnvPlugin)
await ctx.plugin(PwshLocalExecutor)
await ctx.plugin(ToolPwsh)
},
note:
'The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as `@deepseek-ai/dsh-pwsh-local` backs `ctx.bash`); it mirrors the bash tool call-for-call minus sandbox controls — `run_in_background` runs register with the generic `ctx.tasks` runtime and are collected/stopped through the `task_*` tools, and the managed `DSH_*` environment comes from `@deepseek-ai/dsh-bash-env`. Each call runs in a fresh process (no persistent PTY session), with native `C:\\...` paths and `$env:NAME` variables.',
'The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as `@deepseek-ai/dsh-pwsh-local` backs `ctx.shell`); it mirrors the bash tool call-for-call minus sandbox controls — `run_in_background` runs register with the generic `ctx.jobs` runtime and are collected/stopped through the `job_*` tools, and the managed `DSH_*` environment comes from `@deepseek-ai/dsh-shell-env`. Each call runs in a fresh process (no persistent PTY session), with native `C:\\...` paths and `$env:NAME` variables.',
},
{
pkg: '@deepseek-ai/dsh-tool-cordis',
dir: 'tool-cordis',
source: 'packages/self-modification/tool-cordis/src/index.ts',
requires: ['ctx.tools'],
writes: ['tool/call', 'tool/result', 'process-local temporary Plugin lifecycle'],
source: 'packages/extensions/tool-cordis/src/index.ts',
requires: ['ctx.tools', 'ctx.dynamicCordisRunner'],
writes: ['tool/call', 'tool/result', 'process-local dynamic package lifecycle'],
async mount(ctx) {
await ctx.plugin(CordisHostRunner)
await ctx.plugin(ToolCordis)
},
note:
'Not in any shipped tree (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes.',
'Not in any shipped tree (a deliberate opt-in — dynamic package code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). The toolset injects `ctx.dynamicCordisRunner` from `@deepseek-ai/dsh-cordis-host-runner`, which owns the definition registry and the vm sandbox; a composition missing it never activates the tools. A running package may register ADDITIONAL model-visible tools until it is stopped, undefined, or DSH restarts; a full changed request header logs those tool-set changes.',
},
{
pkg: '@deepseek-ai/dsh-tool-bash-persistent',
dir: 'tool-bash-persistent',
source: 'packages/pty/tool-bash-persistent/src/index.ts',
requires: ['ctx.tools', 'ctx.pty', 'an owning Agent at execution time'],
source: 'packages/shell/tool-bash-persistent/src/index.ts',
requires: ['ctx.tools', 'ctx.terminals', 'an owning Agent at execution time'],
writes: ['tool/call', 'PTY shell state', 'tool/result'],
async mount(ctx) {
await ctx.plugin(PtyService)
await ctx.plugin(TerminalSessionService)
await ctx.plugin(ToolBashPersistent)
},
note:
@@ -305,7 +307,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
await ctx.plugin(ToolFs)
},
note:
'The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. `read_image` is not registered without `ctx.attachments`; its schema is route-independent, and execution refuses unless the exact routed model declares image input.',
'The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-observation-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. `read_image` is not registered without `ctx.attachments`; its schema is route-independent, and execution refuses unless the exact routed model declares image input.',
},
{
pkg: '@deepseek-ai/dsh-tool-fs-search',
@@ -319,24 +321,24 @@ const TOOL_PACKAGES: ToolPackage[] = [
// spawns, so the real local service is inert here. `ctx.spillStore` is
// optional (read via ctx.get) and does not affect the schemas, so no
// spill backend is mounted.
await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalSubprocessRuntime)
await ctx.plugin(ToolFsSearch, { sampleOverCapGlobResults: true })
},
note:
'glob and grep are unconditional discovery tools that spawn the packaged ripgrep binary (`@vscode/ripgrep`) through ctx.subprocess as ordinary foreground calls (never background tasks) — no host `rg` install and no shell layer. The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments.',
'glob and grep are unconditional discovery tools that spawn the packaged ripgrep binary (`@vscode/ripgrep`) through ctx.subprocess as ordinary foreground calls (never background jobs) — no host `rg` install and no shell layer. The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments.',
},
{
pkg: '@deepseek-ai/dsh-tool-pty',
dir: 'tool-pty',
source: 'packages/pty/tool-pty/src/index.ts',
requires: ['ctx.tools', 'ctx.pty', 'ctx.systemPrompt', 'ctx.tasks at call time for run_in_background'],
pkg: '@deepseek-ai/dsh-tool-terminal',
dir: 'tool-terminal',
source: 'packages/terminal/tool-terminal/src/index.ts',
requires: ['ctx.tools', 'ctx.terminals', 'ctx.systemPrompt', 'ctx.jobs at call time for run_in_background'],
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
await ctx.plugin(PtyService)
await ctx.plugin(TerminalSessionService)
await ctx.plugin(ToolPty)
},
note:
'The six terminal tools are opt-in and complement one-shot bash/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.tasks`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema.',
'The six terminal tools are opt-in and complement one-shot shell/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.jobs`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema.',
},
{
pkg: '@deepseek-ai/dsh-tool-goal',
@@ -353,9 +355,9 @@ const TOOL_PACKAGES: ToolPackage[] = [
'create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds.',
},
{
pkg: '@deepseek-ai/dsh-tool-schedule',
dir: 'tool-schedule',
source: 'packages/schedule/tool-schedule/src/tools.ts',
pkg: '@deepseek-ai/dsh-schedule',
dir: 'schedule',
source: 'packages/schedule/schedule/src/tools.ts',
requires: ['ctx.tools', 'ctx.sessions', 'Session persistence', 'a future live root Agent'],
writes: ['tool/call', 'schedule/change create or delete', 'tool/result'],
async mount(ctx) {
@@ -385,16 +387,16 @@ const TOOL_PACKAGES: ToolPackage[] = [
await ctx.plugin(ToolLsp)
},
note:
'The lsp tool keeps provider selection and language-server subprocesses behind ctx.lsp, so its model-visible schema stays stable across providers. Requires a registered provider (e.g. `@deepseek-ai/dsh-lsp-local`) at runtime; without one, a query returns the structured `LSP_UNAVAILABLE` error rather than changing the schema.',
'The lsp tool keeps provider selection and language-server subprocesses behind ctx.lsp, so its model-visible schema stays stable across providers. Requires a registered provider (e.g. `@deepseek-ai/dsh-lsp-stdio`) at runtime; without one, a query returns the structured `LSP_UNAVAILABLE` error rather than changing the schema.',
},
{
pkg: '@deepseek-ai/dsh-tool-ralph',
dir: 'tool-ralph',
source: 'packages/workflow/tool-ralph/src/index.ts',
requires: ['ctx.tools', 'ctx.workflows', 'ctx.subagents', 'ctx.systemPrompt', 'a calling Agent (exec.agent parents every fresh round)'],
requires: ['ctx.tools', 'ctx.workflowEngine', 'ctx.subagents', 'ctx.systemPrompt', 'a calling Agent (exec.agent parents every fresh round)'],
writes: ['tool/call', 'tool/result', 'workflow and child session events during execution'],
async mount(ctx) {
await ctx.plugin(SubagentService)
await ctx.plugin(SubagentRuntime)
registerCatalogSubagentProvider(ctx, 'mock')
await ctx.plugin(VmWorkflowEngine, { provider: 'mock' })
await ctx.plugin(ToolRalph, { subagentProvider: 'mock' })
@@ -410,8 +412,8 @@ const TOOL_PACKAGES: ToolPackage[] = [
writes: ['tool/call', 'tool/result', 'user/message replacement catalogs via agent.inject()'],
async mount(ctx) {
await ctx.plugin(AgentRegistry)
await ctx.plugin(SkillService)
await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
await ctx.plugin(SkillFileSystem, {
dshHome: resolve(root, '.tmp/tool-catalog/.dsh'),
agentsHome: resolve(root, '.tmp/tool-catalog/.agents'),
})
@@ -426,7 +428,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
await ctx.plugin(SessionStore)
await ctx.plugin(SessionQuerySqlite, { path: ':memory:' })
await ctx.plugin(SqliteSessionQueryEngine, { path: ':memory:' })
await ctx.plugin(ToolSessionQuery)
},
note:
@@ -440,7 +442,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
writes: ['tool/call', 'tool/result', 'child session events through the chosen provider'],
shippedNames: ['subagent', 'subagent_fork'],
async mount(ctx) {
await ctx.plugin(SubagentService)
await ctx.plugin(SubagentRuntime)
registerCatalogSubagentProvider(ctx, 'mock')
await ctx.plugin(ToolSubagent, { provider: 'mock' })
},
@@ -458,8 +460,8 @@ const TOOL_PACKAGES: ToolPackage[] = [
requires: ['ctx.tools', 'ctx.subagents', 'ctx.agents and ctx.sessionProjections (list_agents only)'],
writes: ['tool/call', 'tool/result', 'child session events through ctx.subagents'],
async mount(ctx) {
await ctx.plugin(SubagentService)
await ctx.plugin(LocalTaskService)
await ctx.plugin(SubagentRuntime)
await ctx.plugin(LocalJobRegistry)
await ctx.plugin(AgentRegistry)
await ctx.plugin(SessionStore)
await ctx.plugin(SessionProjectionRegistry)
@@ -477,7 +479,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
writes: ['tool/call', 'tool/result', 'a user-role message in the direct parent session'],
async mount(ctx) {
await ctx.plugin(AgentRegistry)
await ctx.plugin(SubagentService)
await ctx.plugin(SubagentRuntime)
const { reportDelivery } = ToolSubagentReport.Config({}) as { reportDelivery: SubagentReportDelivery }
await mountCatalogChildScope(ctx, (childCtx) => {
ToolSubagentReport.installReportTool(childCtx, ctx, reportDelivery)
@@ -491,17 +493,17 @@ const TOOL_PACKAGES: ToolPackage[] = [
+ '`send_message` tool is installed independently.',
},
{
pkg: '@deepseek-ai/dsh-tool-tasks',
dir: 'tool-tasks',
source: 'packages/tasks/tool-tasks/src/index.ts',
requires: ['ctx.tools', 'ctx.tasks', 'ctx.systemPrompt'],
pkg: '@deepseek-ai/dsh-tool-jobs',
dir: 'tool-jobs',
source: 'packages/jobs/tool-jobs/src/index.ts',
requires: ['ctx.tools', 'ctx.jobs', 'ctx.systemPrompt'],
writes: ['tool/call', 'tool/result', 'user/message via agent.inject() for background completion notices'],
async mount(ctx) {
await ctx.plugin(LocalTaskService)
await ctx.plugin(LocalJobRegistry)
await ctx.plugin(ToolTasks)
},
note:
'The kind-agnostic background-task controller: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers\' `ctx.tasks.start()`.',
'The kind-agnostic background-job controller: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers\' `ctx.jobs.start()`.',
},
{
pkg: '@deepseek-ai/dsh-tool-todo',
@@ -519,13 +521,13 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-workflow',
dir: 'tool-workflow',
source: 'packages/workflow/tool-workflow/src/index.ts',
requires: ['ctx.tools', 'ctx.workflows', 'ctx.systemPrompt', 'a calling Agent (exec.agent parents the script children)'],
requires: ['ctx.tools', 'ctx.workflowEngine', 'ctx.systemPrompt', 'a calling Agent (exec.agent parents the script children)'],
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
// The tool injects `workflows`; boot the vm engine over a scripted
// subagent provider to satisfy it. The schema does not depend on which
// provider backs the engine.
await ctx.plugin(SubagentService)
await ctx.plugin(SubagentRuntime)
registerCatalogSubagentProvider(ctx, 'mock')
await ctx.plugin(VmWorkflowEngine, { provider: 'mock' })
await ctx.plugin(ToolWorkflow)
@@ -540,7 +542,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
async mount(ctx) {
// Mount search and fetch providers so both tools register. Their schemas
// do not depend on provider identity or availability.
await ctx.plugin(WebService)
await ctx.plugin(WebRuntime)
await ctx.plugin(WebSearchExa)
await ctx.plugin(WebFetchLocal)
await ctx.plugin(ToolWeb)
@@ -587,6 +589,29 @@ export function assertManifestComplete(packages: ToolPackage[] = TOOL_PACKAGES,
}
}
/**
* Assert one manifest entry actually registered a tool.
*
* A tool package that boots without registering anything is a broken boot, not
* an empty catalog section. The usual cause is an `inject` the entry's `mount`
* does not satisfy: cordis leaves the plugin PENDING, every step here still
* succeeds, and the generator writes a catalog missing that package's tools —
* with the freshness gate green on it, because the omission is now what the
* generator produces. {@link assertManifestComplete} cannot see this: the
* package IS listed, it just contributed nothing.
* @param entry - the manifest entry that was booted.
* @param harvested - how many schemas its boot registered.
* @throws when the boot registered no tool at all.
*/
export function assertToolsHarvested(entry: ToolPackage, harvested: number): void {
if (harvested > 0) return
throw new Error(
`gen-tool-catalog: ${entry.pkg} booted without registering a single tool. `
+ 'Its plugin is most likely PENDING on a service this manifest entry does not mount — '
+ `compare the plugin's inject with mount() and requires: ${entry.requires.join(', ')}.`,
)
}
/**
* Boot each tool package on a fresh Context and harvest its model-facing
* schemas. A fresh Context per package keeps attribution clean (each entry's
@@ -603,9 +628,10 @@ export async function collectToolCatalog(packages: ToolPackage[] = TOOL_PACKAGES
// fiber) — the repo's "dispose must reach quiescence" rule.
try {
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry, entry.toolsConfig ?? {})
await ctx.plugin(ToolRuntime, entry.toolsConfig ?? {})
await entry.mount(ctx)
const schemas = ctx.tools.schemas(entry.scope?.(ctx)).sort((a, b) => a.name.localeCompare(b.name))
assertToolsHarvested(entry, schemas.length)
catalog.push({
pkg: entry.pkg,
sources: Object.fromEntries(schemas.map(schema => [
+2 -2
View File
@@ -50,8 +50,8 @@ describe('Oxlint executable contract', () => {
const suffix = randomUUID()
const configPath = await writeContractConfig(suffix)
const probes = [
['host package source', 'packages/fs/fs-policy/src', 'packages/fs/fs-policy/tsconfig.json'],
['host package test', 'packages/fs/fs-policy/tests', 'tsconfig.host.json'],
['host package source', 'packages/fs/fs-observation-policy/src', 'packages/fs/fs-observation-policy/tsconfig.json'],
['host package test', 'packages/fs/fs-observation-policy/tests', 'tsconfig.host.json'],
['client package source', 'packages/client/ui-primitives/src', 'packages/client/ui-primitives/tsconfig.json'],
// A test under packages/client states its face in the filename, so the
// probe carries the Client suffix to reach the Client aggregate.
+2 -2
View File
@@ -57,7 +57,7 @@ function fixture(options: {
}
writeFileSync(join(dir, 'package.json'), `${JSON.stringify(manifest, null, 2)}\n`)
writeFileSync(join(dir, 'tsconfig.json'), `${JSON.stringify({
references: options.invariantReference === false ? [] : [{ path: '../../support/invariants' }],
references: options.invariantReference === false ? [] : [{ path: '../../runtime-diagnostics/invariants' }],
}, null, 2)}\n`)
writeFileSync(join(dir, 'src/invariant.ts'), options.source ?? handwrittenInvariant(packageName))
writeFileSync(
@@ -80,7 +80,7 @@ describe('package invariant gate', () => {
references: [{ path: './tsconfig.host.json' }],
}, null, 2)}\n`)
writeFileSync(join(dir, 'tsconfig.host.json'), `${JSON.stringify({
references: [{ path: '../../support/invariants' }],
references: [{ path: '../../runtime-diagnostics/invariants' }],
}, null, 2)}\n`)
expect(collectPackageInvariantViolations(root)).toEqual([])
+2 -2
View File
@@ -123,7 +123,7 @@ function checkBuild(
addViolation(
violations,
tsconfigPath,
'TypeScript project references must include ../../support/invariants',
'TypeScript project references must include ../../runtime-diagnostics/invariants',
)
}
@@ -137,7 +137,7 @@ function checkBuild(
function projectReferencesInvariants(root: string, ownerDir: string, entryPath: string): boolean {
const ownerRoot = resolve(root, ownerDir)
const target = resolve(root, 'packages/support/invariants')
const target = resolve(root, 'packages/runtime-diagnostics/invariants')
const pending = [resolve(root, entryPath)]
const visited = new Set<string>()
while (pending.length > 0) {
+3 -3
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import {
hasTypeRTRemoteNavigation,
hasTypertRemoteNavigation,
isForbiddenPublicationFile,
validateTarballPayload,
} from './publication-payload.ts'
@@ -68,7 +68,7 @@ describe('publication payload policy', () => {
})
it('recognizes only the canonical Host-for-Client export pair', () => {
expect(hasTypeRTRemoteNavigation({
expect(hasTypertRemoteNavigation({
exports: {
'./remote': {
types: './lib/typert.remote-client.d.ts',
@@ -76,6 +76,6 @@ describe('publication payload policy', () => {
},
},
})).toBe(true)
expect(hasTypeRTRemoteNavigation({ exports: { './remote': './lib/remote.js' } })).toBe(false)
expect(hasTypertRemoteNavigation({ exports: { './remote': './lib/remote.js' } })).toBe(false)
})
})
+1 -1
View File
@@ -5,7 +5,7 @@
* @param manifest - parsed package manifest to inspect.
* @returns whether the canonical `./remote` export pair is present.
*/
export function hasTypeRTRemoteNavigation(manifest: unknown): boolean {
export function hasTypertRemoteNavigation(manifest: unknown): boolean {
if (manifest === null || typeof manifest !== 'object' || Array.isArray(manifest)) return false
const exportsField = (manifest as Record<string, unknown>).exports
if (exportsField === null || typeof exportsField !== 'object' || Array.isArray(exportsField)) return false
+24 -54
View File
@@ -5,9 +5,10 @@
*
* The dsh family shares one version across its members and the workspace root:
* `major`, `minor`, `patch`, or an explicit `x.y.z` (including a prerelease such
* as `0.0.1-rc.1`). The vendored family has one version line per package and
* publishes only what changed since that package's own `vendor-<package>-v*`
* tag, which is the record of the commit it last published from.
* as `0.0.1-rc.1`). The vendored family has one version line per package, but
* every release advances and publishes the complete family so the next release
* never reuses an unchanged member's existing version from a different
* repository state.
*
* The version lands in the manifests, the lockfile follows, and a human creates
* the tag after the commit merges. CI never writes to the repository.
@@ -17,7 +18,7 @@ import { readFileSync, writeFileSync } from 'node:fs'
import { join, matchesGlob } from 'node:path'
import { parseArgs } from 'node:util'
import { releaseFamily, type ReleaseFamily, type ReleaseMember } from './families.ts'
import { attempt, capture, isEntry } from './process.ts'
import { capture, isEntry } from './process.ts'
/** Files npm publishes whether or not `files` lists them. */
const ALWAYS_PUBLISHED = ['package.json', 'README*', 'LICENSE*', 'LICENCE*'] as const
@@ -144,30 +145,33 @@ function nextSharedVersion(current: string, request: string): string {
/**
* The version a vendored package publishes next.
*
* The baseline is the higher of the manifest version and the last published
* The baseline is the higher of the manifest version and the last tagged
* version: a vendor re-sync restores upstream's version, which is lower than
* what this repository already published, and incrementing that would name a
* version the registry already carries.
* the release version this repository already reserved, and incrementing that
* would reuse an existing version.
*
* A prerelease does not consume its own release numbers. Publishing
* `4.0.1-rc.1` leaves `4.0.1` free, so the next stable version is `4.0.1`
* rather than `4.0.2`, and a second prerelease keeps those numbers too.
* @param current - the package's manifest version.
* @param published - the version its newest tag names, when it has one.
* @param tagged - the version its newest tag names, when it has one.
* @param prerelease - prerelease identifier to append, for a rehearsal publication.
* @returns The target version.
*/
export function nextVendorVersion(
current: string,
published: string | undefined,
tagged: string | undefined,
prerelease?: string,
): string {
const ahead = published !== undefined && compareReleaseNumbers(published, current) > 0
const baseline = ahead ? published : current
const taggedOrder = tagged === undefined ? undefined : compareReleaseNumbers(tagged, current)
const ahead = taggedOrder !== undefined && taggedOrder > 0
const baseline = ahead && tagged !== undefined ? tagged : current
const [major, minor, patch] = releaseNumbers(baseline)
// Reuse the numbers when the published version that set them is a prerelease
// Reuse the numbers when the tagged version that set them is a prerelease
// of them; increment when a stable release already holds them.
const reuse = ahead && published.includes('-')
const taggedPrerelease = tagged !== undefined && prereleaseOf(tagged) !== undefined
const sameReleasePrereleases = taggedOrder === 0 && prereleaseOf(current) !== undefined
const reuse = taggedPrerelease && (ahead || sameReleasePrereleases)
const numbers = reuse
? `${String(major)}.${String(minor)}.${String(patch)}`
: `${String(major)}.${String(minor)}.${String(patch + 1)}`
@@ -191,12 +195,12 @@ export function reachesPayload(member: ReleaseMember, path: string): boolean {
}
/**
* The newest version a member published, read from its tags.
* The newest version a member tagged.
* @param family - the member's family.
* @param member - the member.
* @returns The version, or undefined when the member never published.
* @returns The version, or undefined when the member has no release tag.
*/
function lastPublishedVersion(family: ReleaseFamily, member: ReleaseMember): string | undefined {
function lastTaggedVersion(family: ReleaseFamily, member: ReleaseMember): string | undefined {
const prefix = family.tagPrefixFor(member)
const versions = capture('git', ['tag', '--list', `${prefix}*`])
.split('\n').filter(line => line !== '').map(tag => tag.slice(prefix.length))
@@ -204,33 +208,6 @@ function lastPublishedVersion(family: ReleaseFamily, member: ReleaseMember): str
return versions.reduce((newest, candidate) => compareVersions(candidate, newest) > 0 ? candidate : newest)
}
/**
* Confirm the registry carries the version a tag names.
*
* A tag is a commit pointer, not proof of publication: a tag pushed for a
* publication that then failed would otherwise read as "already published" and
* skip the package indefinitely. Querying a private package needs credentials,
* so an unauthenticated machine reports the gap instead of failing.
* @param name - package name.
* @param version - the version the tag names.
*/
function confirmPublished(name: string, version: string): void {
const result = attempt('npm', ['view', `${name}@${version}`, 'version'])
if (result.status === 0) return
const output = `${result.stdout}${result.stderr}`
if (output.includes('ENEEDAUTH') || output.includes('E401') || output.includes('E403')) {
console.log(`release bump: cannot reach the registry for ${name}@${version}; skipping the tag check`)
return
}
if (output.includes('E404') || output.includes('404 Not Found')) {
throw new Error(
`${name}@${version} is tagged but absent from the registry.`
+ '\nThe tag was pushed for a publication that did not complete: re-run that publish, or delete the tag.',
)
}
throw new Error(`npm view ${name}@${version} failed:\n${output}`)
}
/**
* Write a version into a manifest, preserving formatting and key order.
* @param root - repository root.
@@ -293,8 +270,8 @@ function planShared(
}
/**
* Plan the vendored family's rewrite: every package whose payload changed since
* it last published.
* Plan the vendored family's rewrite: every package advances together while
* retaining its own version line and tag.
* @param family - the vendored family.
* @param members - the family's members.
* @param prerelease - prerelease identifier to append, for a rehearsal publication.
@@ -307,15 +284,8 @@ function planPerPackage(
): PlannedVersion[] {
const planned: PlannedVersion[] = []
for (const member of members) {
const published = lastPublishedVersion(family, member)
if (published !== undefined) {
confirmPublished(member.name, published)
const since = `${family.tagPrefixFor(member)}${published}`
const changed = capture('git', ['diff', '--name-only', `${since}..HEAD`, '--', member.directory])
.split('\n').filter(line => line !== '')
if (!changed.some(path => reachesPayload(member, path))) continue
}
const to = nextVendorVersion(member.version, published, prerelease)
const tagged = lastTaggedVersion(family, member)
const to = nextVendorVersion(member.version, tagged, prerelease)
planned.push({
manifestPath: join(member.directory, 'package.json'),
label: member.directory,
+1 -1
View File
@@ -32,7 +32,7 @@ describe('release families', () => {
it('rejects a family whose members disagree on the shared version', () => {
const dsh = releaseFamily('dsh')
const members = [member('apps/cli', '@deepseek-ai/dsh'), { ...member('apps/web', '@deepseek-ai/dsh-frontend'), version: '0.0.2' }]
const members = [member('apps/cli', '@deepseek-ai/dsh'), { ...member('apps/web', '@deepseek-ai/dsh-web-frontend'), version: '0.0.2' }]
expect(() => { dsh.verifyVersions(members) }).toThrow(/must share one version/)
expect(() => { dsh.verifyVersions([members[0]!]) }).not.toThrow()
+2 -2
View File
@@ -409,9 +409,9 @@ const VENDORED_LIBRARY = /^@deepseek-ai\\/(cosmokit|schemastery)(\\/|$)/
// repository's vendored copies; cosmokit comes along as cordis's own dependency.
id: 'packed-install-vendored-peer',
file: 'packages/sandbox/sandbox-local/tests/packed-install.e2e.ts',
find: ` 'packages/support/invariants',
find: ` 'packages/runtime-diagnostics/invariants',
]`,
replace: ` 'packages/support/invariants',
replace: ` 'packages/runtime-diagnostics/invariants',
// The framework and the vendored packages the closure declares outright:
// rescoped into @deepseek-ai, so the consumer installs this repository's
// copies. Schemastery is a hard dependency of three members above, not a
+1 -1
View File
@@ -150,7 +150,7 @@ describe('Oxlint gate', () => {
})
})
describe('TypeRT contract preparation', () => {
describe('Typert contract preparation', () => {
it('prepares primary source consumers once before they run', () => {
const subject = withEnv('DSH_OXLINT_THREADS', undefined, () =>
withPnpmEntrypoint(() => gatesForMode('ci-primary')))
+6 -5
View File
@@ -307,7 +307,7 @@ function nodeCompatSmokeGates(options: { cliSmoke?: boolean } = {}): Gate[] {
pnpmExec('source-worker-smoke', [
'vitest',
'run',
'packages/workflow/workflow-workerthread/tests/source-worker.compat.spec.ts',
'packages/workflow/workflow-worker-thread/tests/source-worker.compat.spec.ts',
], { label: 'source worker smoke' }),
pnpmExec('jsonl-zstd-smoke', [
'vitest',
@@ -455,7 +455,7 @@ function ciWindowsObservationalGates(): Gate[] {
}
function typertContractsGate(): Gate {
return pnpmScript('typert-contracts', 'build:lib:host', { label: 'TypeRT contracts' })
return pnpmScript('typert-contracts', 'build:lib:host', { label: 'Typert contracts' })
}
function lintGate(options: { needs?: string[] } = {}): Gate {
@@ -581,6 +581,7 @@ function docSyncLeafGates(options: {
? []
: [pnpmScript('doc-typecheck', options.docTypecheckScript ?? 'doc-typecheck', docTypecheckOptions)],
pnpmScript('cordis-catalog', 'verify-cordis-catalog', { label: 'cordis catalog' }),
pnpmScript('client-catalog', 'verify-client-catalog', { label: 'client catalog' }),
pnpmScript('export-jsdoc', 'verify-export-jsdoc', { label: 'export jsdoc' }),
pnpmScript('tool-catalog', 'verify-tool-catalog', { label: 'tool catalog' }),
pnpmScript('config-catalog', 'verify-config-catalog', { label: 'config catalog' }),
@@ -629,9 +630,9 @@ function builtBinSmokeGate(needs: string[] = ['build']): Gate {
// Built execution consumers: the only automated proof that package-name
// imports reach their lib/ entrypoints under plain Node. The e2e lane runs
// unbuilt, so these files self-skip there.
'packages/workflow/workflow-workerthread/tests/built-worker.e2e.ts',
'packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts',
'packages/lsp/lsp-local/tests/built-lib.e2e.ts',
'packages/workflow/workflow-worker-thread/tests/built-worker.e2e.ts',
'packages/code-runtime/code-runtime-worker-thread/tests/built-lib.e2e.ts',
'packages/lsp/lsp-stdio/tests/built-lib.e2e.ts',
], {
label: 'built-bin smoke',
needs,
+429
View File
@@ -0,0 +1,429 @@
/**
* AST helpers for the client slot surface: the `SlotMap` declaration merges
* that type every slot, and the `slots.register` call sites that say who
* already occupies one. Both readings are lexical (no type-checker program):
* the client catalog generator consumes them, and the same scan doubles as its
* own exhaustiveness backstop because it reads every source file rather than a
* reachable-export closure.
*/
import { globSync, readFileSync } from 'node:fs'
import { dirname, join, resolve, sep } from 'node:path'
import ts from 'typescript'
/** The module whose `SlotMap` / standard-kit interfaces every slot owner merges into. */
const SLOTS_MODULE = '@deepseek-ai/dsh-client-ui-slots'
/** Cheap textual prefilter for a slot-contract merge, quote-style agnostic. */
const MERGE_HEAD = /declare module ['"]@deepseek-ai\/dsh-client-ui-slots['"]/
/** Cheap textual prefilter for a registration call site. */
const REGISTER_HEAD = /\.register\(/
/** One `SlotMap` member: the slot's contract as its owning package declares it. */
export interface SlotDeclaration {
/** SlotMap key, e.g. `settings.section`. */
key: string
/** Cardinality literal (`single` / `list` / `keyed` / `chain`), or '' when not a literal. */
kind: string
/** Data-scope literal (`root` / `session` / `session-maybe`), or '' when not a literal. */
scope: string
/** Type name of the owner-supplied props share, absent when the slot declares none. */
ownerType?: string
/** Source text of the `keyProps` member (keyed slots), absent otherwise. */
keyProps?: string
/** Source text of the `hookContext` member, absent otherwise. */
hookContext?: string
/** Type name of the slot-level inject face, absent when the slot declares none. */
injectType?: string
/** The member's JSDoc with container indentation removed, '' when undocumented. */
jsDoc: string
/** Workspace package that declares the contract. */
package: string
/** Source pointer `packages/…/file.ts:line`. */
source: string
}
/** One `slots.register({ name, … }, Component)` call site. */
export interface SlotRegistration {
/** Target SlotMap key the entry contributes into. */
key: string
/** Workspace package that registers the entry. */
package: string
/** Component argument as written (identifier, or a trimmed expression). */
component: string
/** `id` literal of a list entry, absent otherwise. */
id?: string
/** `key` literal of a keyed entry, absent otherwise. */
entryKey?: string
/** SlotMap keys this registration declares as children (they exist while it is mounted). */
children: string[]
/** Source pointer `packages/…/file.ts:line`. */
source: string
}
/** One exported type declaration, retained with its JSDoc for catalog projection. */
export interface TypeDeclaration {
/** Declared name. */
name: string
/** Full declaration text INCLUDING its JSDoc (member docs are the teaching text). */
text: string
/** Source pointer `packages/…/file.ts:line`. */
source: string
}
/** One scanned source file with the artifacts the catalog reads from it. */
export interface ScannedFile {
/** Repo-relative, `/`-normalized path. */
rel: string
/** Workspace package name that owns the file. */
package: string
/** Parsed source file. */
sf: ts.SourceFile
}
/**
* Parse every file matching `patterns`, keeping the ones that carry a slot
* contract merge or a registration call. Files without either are skipped so
* the scan stays cheap over the whole workspace.
* @param scanRoot - repository root the patterns resolve against.
* @param patterns - glob(s) selecting the TypeScript/TSX files to scan.
* @returns one entry per interesting file, in path order.
*/
export function scanSlotFiles(scanRoot: string, patterns: readonly string[]): ScannedFile[] {
const out: ScannedFile[] = []
const names = new Map<string, string>()
const rels = [...new Set(globSync(patterns as string[], { cwd: scanRoot })
.map(path => path.split(sep).join('/')))].sort()
for (const rel of rels) {
const abs = resolve(scanRoot, rel)
const text = readFileSync(abs, 'utf8')
if (!MERGE_HEAD.test(text) && !REGISTER_HEAD.test(text)) continue
out.push({
rel,
package: packageNameOf(scanRoot, rel, names),
sf: ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true, scriptKindOf(rel)),
})
}
return out
}
/**
* Index every exported type declaration of the scanned packages, keeping JSDoc.
* The catalog resolves owner-props and inject-face shapes through this index
* instead of a type-checker program: the declaration text with its member
* documentation IS the teaching material a registrant needs.
* @param scanRoot - repository root the patterns resolve against.
* @param patterns - glob(s) selecting the TypeScript/TSX files to index.
* @returns name → declaration, with names declared more than once dropped as ambiguous.
*/
export function indexExportedTypes(scanRoot: string, patterns: readonly string[]): Map<string, TypeDeclaration> {
const index = new Map<string, TypeDeclaration>()
const ambiguous = new Set<string>()
const rels = [...new Set(globSync(patterns as string[], { cwd: scanRoot })
.map(path => path.split(sep).join('/')))].sort()
for (const rel of rels) {
const abs = resolve(scanRoot, rel)
const sf = ts.createSourceFile(abs, readFileSync(abs, 'utf8'), ts.ScriptTarget.Latest, true, scriptKindOf(rel))
for (const statement of sf.statements) {
if (!ts.isInterfaceDeclaration(statement) && !ts.isTypeAliasDeclaration(statement)) continue
if (!statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) continue
const name = statement.name.text
if (index.has(name)) {
ambiguous.add(name)
continue
}
index.set(name, {
name,
text: declarationText(statement, sf),
source: `${rel}:${String(lineOf(sf, statement))}`,
})
}
}
for (const name of ambiguous) index.delete(name)
return index
}
/**
* Read every `SlotMap` member declared in one scanned file.
* @param file - a file returned by {@link scanSlotFiles}.
* @returns the declared slots, in source order.
*/
export function slotDeclarations(file: ScannedFile): SlotDeclaration[] {
const out: SlotDeclaration[] = []
for (const body of slotModuleBodies(file.sf)) {
for (const statement of body.statements) {
if (!ts.isInterfaceDeclaration(statement) || statement.name.text !== 'SlotMap') continue
for (const member of statement.members) {
if (!ts.isPropertySignature(member) || member.type === undefined) continue
const key = ts.isStringLiteral(member.name) || ts.isIdentifier(member.name)
? member.name.text
: member.name.getText(file.sf)
const entry = ts.isTypeLiteralNode(member.type) ? member.type : undefined
const ownerType = memberTypeText(entry, 'owner', file.sf)
const keyProps = memberTypeText(entry, 'keyProps', file.sf)
const hookContext = memberTypeText(entry, 'hookContext', file.sf)
const injectType = memberTypeText(entry, 'inject', file.sf)
out.push({
key,
kind: literalMember(entry, 'kind'),
scope: literalMember(entry, 'scope'),
...ownerType === undefined ? {} : { ownerType },
...keyProps === undefined ? {} : { keyProps },
...hookContext === undefined ? {} : { hookContext },
...injectType === undefined ? {} : { injectType },
jsDoc: jsDocOf(member, file.sf),
package: file.package,
source: `${file.rel}:${String(lineOf(file.sf, member))}`,
})
}
}
}
return out
}
/**
* Read every registration call site in one scanned file: which slot it
* occupies, with which component and cell identity, and which child slots it
* declares. A call whose `name` is not a string literal is skipped — the
* shipped composition always names its target literally, and a computed name
* carries no catalog fact.
* @param file - a file returned by {@link scanSlotFiles}.
* @returns the registrations, in source order.
*/
export function slotRegistrations(file: ScannedFile): SlotRegistration[] {
const out: SlotRegistration[] = []
const visit = (node: ts.Node): void => {
if (ts.isCallExpression(node)
&& ts.isPropertyAccessExpression(node.expression)
&& node.expression.name.text === 'register'
&& isSlotsReceiver(node.expression.expression, file.sf)
&& node.arguments.length >= 1) {
const options = node.arguments[0]
if (options !== undefined && ts.isObjectLiteralExpression(options)) {
const key = stringProperty(options, 'name')
if (key !== undefined) {
const id = stringProperty(options, 'id')
const entryKey = stringProperty(options, 'key')
out.push({
key,
package: file.package,
component: componentText(node.arguments[1], file.sf),
...id === undefined ? {} : { id },
...entryKey === undefined ? {} : { entryKey },
children: childKeys(options),
source: `${file.rel}:${String(lineOf(file.sf, node))}`,
})
}
}
}
ts.forEachChild(node, visit)
}
visit(file.sf)
return out
}
/**
* Read one standard-kit interface's members from the scanned files: the props
* a slot component receives for free from the framework at a given scope.
* @param files - scanned files to search.
* @param interfaceName - `GlobalStandardProps`, `SessionStandardProps`, or `SessionMaybeStandardProps`.
* @returns `member: type` texts in declaration order, merged across declaring files.
*/
export function standardKitMembers(files: readonly ScannedFile[], interfaceName: string): string[] {
const out: string[] = []
for (const file of files) {
for (const body of slotModuleBodies(file.sf)) {
for (const statement of body.statements) {
if (!ts.isInterfaceDeclaration(statement) || statement.name.text !== interfaceName) continue
for (const member of statement.members) {
if (!ts.isPropertySignature(member)) continue
const type = member.type === undefined ? 'unknown' : member.type.getText(file.sf)
out.push(`${member.name.getText(file.sf)}${member.questionToken === undefined ? '' : '?'}: ${collapse(type)}`)
}
}
}
}
return out
}
/**
* Names in the type index that seed texts mention, word-bounded — ONE level, not
* a transitive closure. The catalog expands an owner-props contract exactly one
* step: the owner interface carries the interaction protocol in its own member
* documentation, while the shapes its fields reference belong to the subsystems
* that own them and would otherwise drag the entire session model into a single
* slot's report.
* @param seeds - declaration or signature texts to search.
* @param index - the type index from {@link indexExportedTypes}.
* @returns the mentioned names, sorted.
*/
export function referencedTypeNames(
seeds: readonly string[],
index: ReadonlyMap<string, TypeDeclaration>,
): string[] {
const found: string[] = []
for (const name of index.keys()) {
const pattern = new RegExp(`\\b${name}\\b`)
if (seeds.some(text => pattern.test(text))) found.push(name)
}
return found.sort()
}
/**
* Resolve declarations by name, dropping names the index does not hold.
* @param names - type names to resolve.
* @param index - the type index from {@link indexExportedTypes}.
* @returns the resolved declarations, sorted by name.
*/
export function declaredTypes(
names: readonly string[],
index: ReadonlyMap<string, TypeDeclaration>,
): TypeDeclaration[] {
return [...names]
.flatMap(name => index.get(name) ?? [])
.sort((left, right) => left.name.localeCompare(right.name))
}
/** Every slot-contract module block in one file, in source order. */
function slotModuleBodies(sf: ts.SourceFile): ts.ModuleBlock[] {
const bodies: ts.ModuleBlock[] = []
for (const statement of sf.statements) {
if (!ts.isModuleDeclaration(statement) || !ts.isStringLiteral(statement.name)) continue
if (statement.name.text !== SLOTS_MODULE) continue
if (statement.body !== undefined && ts.isModuleBlock(statement.body)) bodies.push(statement.body)
}
return bodies
}
/**
* Whether a `X.register(...)` receiver is the slots service. Every other
* registry in the repo (`ctx.tools`, `ctx.commands`, `ctx.settings`, …) also
* takes an options object with a `name`, so the receiver is what separates a
* slot occupancy fact from an unrelated registration.
*/
function isSlotsReceiver(receiver: ts.Expression, sf: ts.SourceFile): boolean {
const text = receiver.getText(sf)
return text === 'slots' || text.endsWith('.slots')
}
/** The workspace package name owning a repo-relative file, memoized per package root. */
function packageNameOf(scanRoot: string, rel: string, cache: Map<string, string>): string {
let dir = dirname(resolve(scanRoot, rel))
while (dir.length > scanRoot.length) {
const cached = cache.get(dir)
if (cached !== undefined) return cached
try {
const manifest = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as { name?: unknown }
if (typeof manifest.name === 'string') {
cache.set(dir, manifest.name)
return manifest.name
}
} catch {
// No manifest at this level: keep walking up to the owning package root.
}
dir = dirname(dir)
}
return '(unknown package)'
}
/** TSX must parse as TSX; a `.ts` file with JSX-looking generics must not. */
function scriptKindOf(rel: string): ts.ScriptKind {
return rel.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS
}
/** 1-based line of a node's first character. */
function lineOf(sf: ts.SourceFile, node: ts.Node): number {
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1
}
/** Declaration text including leading JSDoc, with container indentation removed. */
function declarationText(statement: ts.Node, sf: ts.SourceFile): string {
return dedent(sf.text.slice(statement.getStart(sf, true), statement.getEnd()))
}
/** One member's JSDoc comment text, '' when the member has none. */
function jsDocOf(member: ts.Node, sf: ts.SourceFile): string {
// getStart(includeJsDoc) brackets exactly the doc comment: with it the range
// opens at `/**`, without it at the member itself.
const withDoc = member.getStart(sf, true)
const withoutDoc = member.getStart(sf, false)
if (withDoc >= withoutDoc) return ''
return dedent(sf.text.slice(withDoc, withoutDoc).trimEnd())
}
/** Strip the shared leading indentation of a multi-line source slice. */
function dedent(text: string): string {
const lines = text.split('\n')
const indents = lines.slice(1).filter(line => line.trim() !== '')
.map(line => (/^\s*/.exec(line) as RegExpExecArray)[0].length)
const shared = indents.length === 0 ? 0 : Math.min(...indents)
return [lines[0] ?? '', ...lines.slice(1).map(line => line.slice(shared))].join('\n').trimEnd()
}
/** Collapse a type text to one line so catalog rows stay one row. */
function collapse(text: string): string {
return text.replace(/\s+/g, ' ').trim()
}
/** A type-literal member's string-literal type text, '' when absent or computed. */
function literalMember(entry: ts.TypeLiteralNode | undefined, name: string): string {
const member = namedMember(entry, name)
if (member?.type === undefined) return ''
return ts.isLiteralTypeNode(member.type) && ts.isStringLiteral(member.type.literal)
? member.type.literal.text
: ''
}
/** A type-literal member's type text on one line, absent when the member is. */
function memberTypeText(
entry: ts.TypeLiteralNode | undefined,
name: string,
sf: ts.SourceFile,
): string | undefined {
const member = namedMember(entry, name)
return member?.type === undefined ? undefined : collapse(member.type.getText(sf))
}
/** One named property signature of a type literal. */
function namedMember(entry: ts.TypeLiteralNode | undefined, name: string): ts.PropertySignature | undefined {
if (entry === undefined) return undefined
for (const member of entry.members) {
if (ts.isPropertySignature(member) && memberName(member.name) === name) return member
}
return undefined
}
/** A property name's text, quotes removed. */
function memberName(name: ts.PropertyName): string {
return ts.isStringLiteral(name) || ts.isIdentifier(name) ? name.text : name.getText()
}
/** One string-literal property of an options object literal. */
function stringProperty(options: ts.ObjectLiteralExpression, name: string): string | undefined {
for (const property of options.properties) {
if (!ts.isPropertyAssignment(property)) continue
if (memberName(property.name) !== name) continue
if (ts.isStringLiteral(property.initializer)) return property.initializer.text
}
return undefined
}
/** The SlotMap keys a registration's `children` table declares. */
function childKeys(options: ts.ObjectLiteralExpression): string[] {
for (const property of options.properties) {
if (!ts.isPropertyAssignment(property)) continue
if (memberName(property.name) !== 'children') continue
if (!ts.isObjectLiteralExpression(property.initializer)) return []
return property.initializer.properties
.flatMap(child => (child.name === undefined ? [] : [memberName(child.name)]))
}
return []
}
/** The component argument as written; a non-identifier expression is collapsed. */
function componentText(argument: ts.Expression | undefined, sf: ts.SourceFile): string {
if (argument === undefined) return '(none)'
const text = collapse(argument.getText(sf))
return text.length > 60 ? `${text.slice(0, 57)}` : text
}
+40 -22
View File
@@ -42,7 +42,7 @@ SNAPSHOT_SESSION_ID = "advanced-executable"
SNAPSHOT_DIRECT_CHILD_PROMPT = "Reply with exactly DIRECT_CHILD_OK and nothing else."
SNAPSHOT_WORKFLOW_CHILD_PROMPT = "Reply with exactly WORKFLOW_CHILD_OK and nothing else."
SNAPSHOT_FINAL_TEXT = "ADVANCED_EXECUTABLE_OK"
SNAPSHOT_MOUNT_CODE = """\
SNAPSHOT_PLUGIN_CODE = """\
return (ctx) => {
harness.registerTool(ctx, harness.defineTool({
name: 'snapshot_double',
@@ -70,8 +70,8 @@ SNAPSHOT_DIRECTORY = (
)
SNAPSHOT_FILENAMES = ("result.json", "session.jsonl", "session.1.jsonl", "session.2.jsonl")
CUSTOM_CORDIS = """\
- id: jsonrpc
name: '@deepseek-ai/dsh-jsonrpc'
- id: sdk-jsonrpc-server
name: '@deepseek-ai/dsh-sdk-jsonrpc-server'
- id: agent-core
name: '@deepseek-ai/dsh-agent-spine-demo'
config:
@@ -87,11 +87,11 @@ CUSTOM_CORDIS = """\
root: !!js process.env.DSH_SESSION_ROOT
compression: 'none'
- id: code-runtime
name: '@deepseek-ai/dsh-code-runtime-worker'
name: '@deepseek-ai/dsh-code-runtime-worker-thread'
- id: subagents
name: '@deepseek-ai/dsh-subagent'
- id: subagent-spawn
name: '@deepseek-ai/dsh-subagent-spawn'
- id: subagent-spawn-in-process
name: '@deepseek-ai/dsh-subagent-spawn-in-process'
config:
providerName: spawn
- id: subagent-tool
@@ -99,11 +99,13 @@ CUSTOM_CORDIS = """\
config:
provider: spawn
- id: workflow-engine
name: '@deepseek-ai/dsh-workflow-workerthread'
name: '@deepseek-ai/dsh-workflow-worker-thread'
config:
provider: spawn
- id: workflow-tool
name: '@deepseek-ai/dsh-tool-workflow'
- id: cordis-host-runner
name: '@deepseek-ai/dsh-cordis-host-runner'
- id: cordis-tool
name: '@deepseek-ai/dsh-tool-cordis'
"""
@@ -200,11 +202,16 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
if prompt == SNAPSHOT_WORKFLOW_CHILD_PROMPT:
return text_chunks("WORKFLOW_CHILD_OK")
if prompt == SNAPSHOT_PROMPT:
assert_advertised_tool(body, "cordis_mount")
assert_advertised_tool(body, "cordis_define")
return tool_call_chunks(
"advanced-mount",
"cordis_mount",
{"code": SNAPSHOT_MOUNT_CODE},
"advanced-define",
"cordis_define",
{
"plugin": {"kind": "new", "idPrefix": "snap"},
"name": "Snapshot Double",
"purpose": "Expose a deterministic doubling tool for executable snapshot verification.",
"code": {"host": SNAPSHOT_PLUGIN_CODE},
},
)
if prompt == CODE_PROMPT:
assert_advertised_tool(body, "run_code")
@@ -289,9 +296,20 @@ def advanced_tool_followup(
"""Advance the executable snapshot's deterministic parent tool chain."""
if not call_id.startswith("advanced-"):
return None
if call_id == "advanced-mount" and tool_name == "cordis_mount":
if "Temporary Plugin dyn-1 is running" not in tool_text:
raise AssertionError(f"cordis_mount returned no temporary Plugin id: {tool_text}")
if call_id == "advanced-define" and tool_name == "cordis_define":
if "Defined snap-1/pkg-1 (Snapshot Double)" not in tool_text:
raise AssertionError(f"cordis_define returned no dynamic Package ids: {tool_text}")
if "snapshot_double" in advertised_tool_names(body):
raise AssertionError("snapshot_double was advertised before cordis_run")
assert_advertised_tool(body, "cordis_run")
return tool_call_chunks(
"advanced-run",
"cordis_run",
{"pluginId": "snap-1", "packageId": "pkg-1", "mode": "run"},
)
if call_id == "advanced-run" and tool_name == "cordis_run":
if "snap-1/pkg-1 is running (run-1)" not in tool_text:
raise AssertionError(f"cordis_run returned no running Package ids: {tool_text}")
assert_advertised_tool(body, "run_code")
assert_advertised_tool(body, "snapshot_double")
return tool_call_chunks(
@@ -332,17 +350,17 @@ def advanced_tool_followup(
if call_id == "advanced-workflow" and tool_name == "workflow":
if "WORKFLOW_CHILD_OK" not in tool_text:
raise AssertionError(f"workflow returned no expected child value: {tool_text}")
assert_advertised_tool(body, "cordis_unmount")
assert_advertised_tool(body, "cordis_undefine")
return tool_call_chunks(
"advanced-unmount",
"cordis_unmount",
{"id": "dyn-1"},
"advanced-undefine",
"cordis_undefine",
{"pluginId": "snap-1"},
)
if call_id == "advanced-unmount" and tool_name == "cordis_unmount":
if "Temporary Plugin dyn-1 was unmounted and removed." not in tool_text:
raise AssertionError(f"cordis_unmount returned no unmount result: {tool_text}")
if call_id == "advanced-undefine" and tool_name == "cordis_undefine":
if "Removed dynamic Plugin snap-1 and all of its Packages." not in tool_text:
raise AssertionError(f"cordis_undefine returned no removal result: {tool_text}")
if "snapshot_double" in advertised_tool_names(body):
raise AssertionError("snapshot_double remained advertised after cordis_unmount")
raise AssertionError("snapshot_double remained advertised after cordis_undefine")
return text_chunks(SNAPSHOT_FINAL_TEXT)
raise AssertionError(f"unexpected advanced tool follow-up: {call_id} {tool_name}: {tool_text}")
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@
{"type":"user/message","seq":5,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"}
{"type":"user/message","seq":6,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"}
{"type":"session/title","seq":7,"time":0,"data":{"title":"Reply with exactly DIRECT_CHILD_OK and","messageSeqs":[5],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":8,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","snapshot_double","subagent","task_kill","task_list","task_output","workflow"]},"reason":"initial"}}
{"type":"request/header","seq":8,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_define","cordis_inspect_list","cordis_inspect_query","cordis_inspect_self","cordis_run","cordis_stop","cordis_undefine","job_kill","job_list","job_output","run_code","snapshot_double","subagent","workflow"]},"reason":"initial"}}
{"type":"request/context","seq":9,"time":0,"data":{"provider":"deepseek-official","model":"smoke-model","contextWindow":1000000}}
{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":11,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"DIRECT_CHILD_OK"}}}
@@ -7,7 +7,7 @@
{"type":"user/message","seq":5,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"}
{"type":"user/message","seq":6,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"}
{"type":"session/title","seq":7,"time":0,"data":{"title":"Reply with exactly WORKFLOW_CHILD_OK and","messageSeqs":[5],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":8,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","snapshot_double","subagent","task_kill","task_list","task_output","workflow"]},"reason":"initial"}}
{"type":"request/header","seq":8,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_define","cordis_inspect_list","cordis_inspect_query","cordis_inspect_self","cordis_run","cordis_stop","cordis_undefine","job_kill","job_list","job_output","run_code","snapshot_double","subagent","workflow"]},"reason":"initial"}}
{"type":"request/context","seq":9,"time":0,"data":{"provider":"deepseek-official","model":"smoke-model","contextWindow":1000000}}
{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":11,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"WORKFLOW_CHILD_OK"}}}
@@ -5,71 +5,81 @@
{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}
{"type":"user/message","seq":4,"time":0,"data":{"content":[{"type":"text","text":"Run the advanced packaged-runtime snapshot scenario."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"}
{"type":"session/title","seq":5,"time":0,"data":{"title":"Run the advanced packaged-runtime snapsh","messageSeqs":[4],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":6,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","subagent","task_kill","task_list","task_output","workflow"]},"reason":"initial"}}
{"type":"request/header","seq":6,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_define","cordis_inspect_list","cordis_inspect_query","cordis_inspect_self","cordis_run","cordis_stop","cordis_undefine","job_kill","job_list","job_output","run_code","subagent","workflow"]},"reason":"initial"}}
{"type":"request/context","seq":7,"time":0,"data":{"provider":"deepseek-official","model":"smoke-model","contextWindow":1000000}}
{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-mount","name":"cordis_mount","argumentsDelta":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}}}
{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}}}}
{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-define","name":"cordis_define","argumentsDelta":"{\"plugin\": {\"kind\": \"new\", \"idPrefix\": \"snap\"}, \"name\": \"Snapshot Double\", \"purpose\": \"Expose a deterministic doubling tool for executable snapshot verification.\", \"code\": {\"host\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}}"}}}
{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-define","name":"cordis_define","arguments":"{\"plugin\": {\"kind\": \"new\", \"idPrefix\": \"snap\"}, \"name\": \"Snapshot Double\", \"purpose\": \"Expose a deterministic doubling tool for executable snapshot verification.\", \"code\": {\"host\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}}"}}}}
{"type":"assistant/chunk","seq":11,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":12,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[8,9,10,11,12],"surfaceOp":"append"}
{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}}
{"type":"tool/result","seq":15,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"advanced-mount"},"content":[{"type":"tool-result","toolCallId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until unmounted or DSH restarts)."}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-define","name":"cordis_define","arguments":"{\"plugin\": {\"kind\": \"new\", \"idPrefix\": \"snap\"}, \"name\": \"Snapshot Double\", \"purpose\": \"Expose a deterministic doubling tool for executable snapshot verification.\", \"code\": {\"host\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[8,9,10,11,12],"surfaceOp":"append"}
{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-define","name":"cordis_define","arguments":"{\"plugin\": {\"kind\": \"new\", \"idPrefix\": \"snap\"}, \"name\": \"Snapshot Double\", \"purpose\": \"Expose a deterministic doubling tool for executable snapshot verification.\", \"code\": {\"host\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}}"}}
{"type":"tool/result","seq":15,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"advanced-define"},"content":[{"type":"tool-result","toolCallId":"advanced-define","content":[{"type":"text","text":"Defined snap-1/pkg-1 (Snapshot Double); it is not running yet. Use cordis_run to activate this Package."}],"isError":false}],"role":"user","id":"{{messageId}}"},"meta":{"pluginId":"snap-1","packageId":"pkg-1"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","seq":16,"time":0,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":17,"time":0,"data":{"turn":1,"step":2}}
{"type":"request/header","seq":18,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","snapshot_double","subagent","task_kill","task_list","task_output","workflow"]},"reason":"change"}}
{"type":"assistant/chunk","seq":19,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":20,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-code","name":"run_code","argumentsDelta":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}}}
{"type":"assistant/chunk","seq":21,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}}}}
{"type":"assistant/chunk","seq":22,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":23,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":24,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"}
{"type":"tool/call","seq":25,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}}
{"type":"tool/code-dispatch-start","seq":26,"time":0,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"snapshot_double","arguments":{"value":21}}}
{"type":"tool/code-dispatch","seq":27,"time":0,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"snapshot_double","arguments":{"value":21},"isError":false,"content":[{"type":"text","text":"42"}]}}
{"type":"tool/result","seq":28,"time":0,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"advanced-code"},"content":[{"type":"tool-result","toolCallId":"advanced-code","content":[{"type":"text","text":"42"}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[25],"surfaceOp":"append"}
{"type":"step/end","seq":29,"time":0,"data":{"turn":1,"step":2}}
{"type":"step/start","seq":30,"time":0,"data":{"turn":1,"step":3}}
{"type":"assistant/chunk","seq":31,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":32,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-direct-child","name":"subagent","argumentsDelta":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}}}
{"type":"assistant/chunk","seq":33,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}}}}
{"type":"assistant/chunk","seq":34,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":35,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":36,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[31,32,33,34,35],"surfaceOp":"append"}
{"type":"tool/call","seq":37,"time":0,"data":{"turn":1,"step":3,"callId":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}}
{"type":"tool/result","seq":38,"time":0,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"advanced-direct-child"},"content":[{"type":"tool-result","toolCallId":"advanced-direct-child","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[37],"surfaceOp":"append"}
{"type":"assistant/chunk","seq":18,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":19,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-run","name":"cordis_run","argumentsDelta":"{\"pluginId\": \"snap-1\", \"packageId\": \"pkg-1\", \"mode\": \"run\"}"}}}
{"type":"assistant/chunk","seq":20,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-run","name":"cordis_run","arguments":"{\"pluginId\": \"snap-1\", \"packageId\": \"pkg-1\", \"mode\": \"run\"}"}}}}
{"type":"assistant/chunk","seq":21,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":22,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":23,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-run","name":"cordis_run","arguments":"{\"pluginId\": \"snap-1\", \"packageId\": \"pkg-1\", \"mode\": \"run\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[18,19,20,21,22],"surfaceOp":"append"}
{"type":"tool/call","seq":24,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-run","name":"cordis_run","arguments":"{\"pluginId\": \"snap-1\", \"packageId\": \"pkg-1\", \"mode\": \"run\"}"}}
{"type":"tool/result","seq":25,"time":0,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"advanced-run"},"content":[{"type":"tool-result","toolCallId":"advanced-run","content":[{"type":"text","text":"snap-1/pkg-1 is running (run-1)."}],"isError":false}],"role":"user","id":"{{messageId}}"},"meta":{"pluginId":"snap-1","packageId":"pkg-1","pluginRunId":"run-1"}},"sourceEventSeqs":[24],"surfaceOp":"append"}
{"type":"step/end","seq":26,"time":0,"data":{"turn":1,"step":2}}
{"type":"step/start","seq":27,"time":0,"data":{"turn":1,"step":3}}
{"type":"request/header","seq":28,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_define","cordis_inspect_list","cordis_inspect_query","cordis_inspect_self","cordis_run","cordis_stop","cordis_undefine","job_kill","job_list","job_output","run_code","snapshot_double","subagent","workflow"]},"reason":"change"}}
{"type":"assistant/chunk","seq":29,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":30,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-code","name":"run_code","argumentsDelta":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}}}
{"type":"assistant/chunk","seq":31,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}}}}
{"type":"assistant/chunk","seq":32,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":33,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":34,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[29,30,31,32,33],"surfaceOp":"append"}
{"type":"tool/call","seq":35,"time":0,"data":{"turn":1,"step":3,"callId":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}}
{"type":"tool/code-dispatch-start","seq":36,"time":0,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"snapshot_double","arguments":{"value":21}}}
{"type":"tool/code-dispatch","seq":37,"time":0,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"snapshot_double","arguments":{"value":21},"isError":false,"content":[{"type":"text","text":"42"}]}}
{"type":"tool/result","seq":38,"time":0,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"advanced-code"},"content":[{"type":"tool-result","toolCallId":"advanced-code","content":[{"type":"text","text":"42"}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[35],"surfaceOp":"append"}
{"type":"step/end","seq":39,"time":0,"data":{"turn":1,"step":3}}
{"type":"step/start","seq":40,"time":0,"data":{"turn":1,"step":4}}
{"type":"assistant/chunk","seq":41,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":42,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-workflow","name":"workflow","argumentsDelta":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}}}
{"type":"assistant/chunk","seq":43,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}}}}
{"type":"assistant/chunk","seq":42,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-direct-child","name":"subagent","argumentsDelta":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}}}
{"type":"assistant/chunk","seq":43,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}}}}
{"type":"assistant/chunk","seq":44,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":45,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":46,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[41,42,43,44,45],"surfaceOp":"append"}
{"type":"tool/call","seq":47,"time":0,"data":{"turn":1,"step":4,"callId":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}}
{"type":"tool-workflow/run-start","seq":48,"time":0,"data":{"runId":"{{workflow-run}}","name":"advanced-exe-snapshot"}}
{"type":"tool-workflow/agent-start","seq":49,"time":0,"data":{"runId":"{{workflow-run}}","seq":1,"label":"workflow-child","phase":"Delegate","childId":"{{child-2}}"}}
{"type":"tool-workflow/agent-end","seq":50,"time":0,"data":{"runId":"{{workflow-run}}","seq":1,"outcome":"completed"}}
{"type":"tool-workflow/run-end","seq":51,"time":0,"data":{"runId":"{{workflow-run}}","stopReason":"completed"}}
{"type":"tool/result","seq":52,"time":0,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"advanced-workflow"},"content":[{"type":"tool-result","toolCallId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-exe-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[47],"surfaceOp":"append"}
{"type":"step/end","seq":53,"time":0,"data":{"turn":1,"step":4}}
{"type":"step/start","seq":54,"time":0,"data":{"turn":1,"step":5}}
{"type":"assistant/chunk","seq":55,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":56,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-unmount","name":"cordis_unmount","argumentsDelta":"{\"id\": \"dyn-1\"}"}}}
{"type":"assistant/chunk","seq":57,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}}}
{"type":"assistant/chunk","seq":58,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":59,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":60,"time":0,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[55,56,57,58,59],"surfaceOp":"append"}
{"type":"tool/call","seq":61,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}
{"type":"tool/result","seq":62,"time":0,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"advanced-unmount"},"content":[{"type":"tool-result","toolCallId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[61],"surfaceOp":"append"}
{"type":"assistant/message","seq":46,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[41,42,43,44,45],"surfaceOp":"append"}
{"type":"tool/call","seq":47,"time":0,"data":{"turn":1,"step":4,"callId":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}}
{"type":"tool/result","seq":48,"time":0,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"advanced-direct-child"},"content":[{"type":"tool-result","toolCallId":"advanced-direct-child","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[47],"surfaceOp":"append"}
{"type":"step/end","seq":49,"time":0,"data":{"turn":1,"step":4}}
{"type":"step/start","seq":50,"time":0,"data":{"turn":1,"step":5}}
{"type":"assistant/chunk","seq":51,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":52,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-workflow","name":"workflow","argumentsDelta":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}}}
{"type":"assistant/chunk","seq":53,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}}}}
{"type":"assistant/chunk","seq":54,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":55,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":56,"time":0,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[51,52,53,54,55],"surfaceOp":"append"}
{"type":"tool/call","seq":57,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}}
{"type":"tool-workflow/run-start","seq":58,"time":0,"data":{"runId":"{{workflow-run}}","name":"advanced-exe-snapshot"}}
{"type":"tool-workflow/agent-start","seq":59,"time":0,"data":{"runId":"{{workflow-run}}","seq":1,"label":"workflow-child","phase":"Delegate","childId":"{{child-2}}"}}
{"type":"tool-workflow/agent-end","seq":60,"time":0,"data":{"runId":"{{workflow-run}}","seq":1,"outcome":"completed"}}
{"type":"tool-workflow/run-end","seq":61,"time":0,"data":{"runId":"{{workflow-run}}","stopReason":"completed"}}
{"type":"tool/result","seq":62,"time":0,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"advanced-workflow"},"content":[{"type":"tool-result","toolCallId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-exe-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[57],"surfaceOp":"append"}
{"type":"step/end","seq":63,"time":0,"data":{"turn":1,"step":5}}
{"type":"step/start","seq":64,"time":0,"data":{"turn":1,"step":6}}
{"type":"request/header","seq":65,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","subagent","task_kill","task_list","task_output","workflow"]},"reason":"change"}}
{"type":"assistant/chunk","seq":66,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":67,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"text-delta","index":0,"text":"ADVANCED_EXECUTABLE_OK"}}}
{"type":"assistant/chunk","seq":68,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}}}}
{"type":"assistant/chunk","seq":69,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":70,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":71,"time":0,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[66,67,68,69,70],"surfaceOp":"append"}
{"type":"step/end","seq":72,"time":0,"data":{"turn":1,"step":6}}
{"type":"turn/end","seq":73,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
{"type":"assistant/chunk","seq":65,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":66,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-undefine","name":"cordis_undefine","argumentsDelta":"{\"pluginId\": \"snap-1\"}"}}}
{"type":"assistant/chunk","seq":67,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-undefine","name":"cordis_undefine","arguments":"{\"pluginId\": \"snap-1\"}"}}}}
{"type":"assistant/chunk","seq":68,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":69,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":70,"time":0,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-undefine","name":"cordis_undefine","arguments":"{\"pluginId\": \"snap-1\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[65,66,67,68,69],"surfaceOp":"append"}
{"type":"tool/call","seq":71,"time":0,"data":{"turn":1,"step":6,"callId":"advanced-undefine","name":"cordis_undefine","arguments":"{\"pluginId\": \"snap-1\"}"}}
{"type":"tool/result","seq":72,"time":0,"data":{"turn":1,"step":6,"message":{"source":{"kind":"tool","callId":"advanced-undefine"},"content":[{"type":"tool-result","toolCallId":"advanced-undefine","content":[{"type":"text","text":"Removed dynamic Plugin snap-1 and all of its Packages."}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[71],"surfaceOp":"append"}
{"type":"step/end","seq":73,"time":0,"data":{"turn":1,"step":6}}
{"type":"step/start","seq":74,"time":0,"data":{"turn":1,"step":7}}
{"type":"request/header","seq":75,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_define","cordis_inspect_list","cordis_inspect_query","cordis_inspect_self","cordis_run","cordis_stop","cordis_undefine","job_kill","job_list","job_output","run_code","subagent","workflow"]},"reason":"change"}}
{"type":"assistant/chunk","seq":76,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":77,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"text-delta","index":0,"text":"ADVANCED_EXECUTABLE_OK"}}}
{"type":"assistant/chunk","seq":78,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}}}}
{"type":"assistant/chunk","seq":79,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":80,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":81,"time":0,"data":{"turn":1,"step":7,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[76,77,78,79,80],"surfaceOp":"append"}
{"type":"step/end","seq":82,"time":0,"data":{"turn":1,"step":7}}
{"type":"turn/end","seq":83,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest'
import { Context, FiberState, Service, ValidationError } from '@deepseek-ai/cordis'
import Loader from '@deepseek-ai/cordis-plugin-loader'
import z from '@deepseek-ai/schemastery'
import InvariantService from '@deepseek-ai/dsh-invariants'
import InvariantRegistry from '@deepseek-ai/dsh-invariants'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
import { packageInvariantOwners } from './package-invariants.ts'
import {
@@ -154,7 +154,7 @@ describe('global test invariant host', () => {
it('recognizes focused invariant suites without a package inventory', () => {
expect(usesManualInvariantTree('/repo/packages/core/session/tests/invariant.spec.ts')).toBe(true)
expect(usesManualInvariantTree('/repo/packages/core/session/tests/request-invariant-hmr.spec.ts')).toBe(true)
expect(usesManualInvariantTree('C:\\repo\\packages\\support\\invariants\\tests\\service.spec.ts')).toBe(true)
expect(usesManualInvariantTree('C:\\repo\\packages\\runtime-diagnostics\\invariants\\tests\\service.spec.ts')).toBe(true)
expect(usesManualInvariantTree('/repo/packages/examples/agent-spine-demo/tests/agent-core.spec.ts')).toBe(true)
expect(usesManualInvariantTree('/repo/packages/core/session/tests/session.spec.ts')).toBe(false)
})
@@ -297,9 +297,9 @@ describe('global test invariant host', () => {
expect(order.at(-1)).toBe('nested')
if (delayedCompanion === undefined) throw new Error('delayed companion did not load')
await ctx.plugin(InvariantService, { enabled: true })
await ctx.plugin(InvariantRegistry, { enabled: true })
await ctx.plugin(delayedCompanion)
expect(ctx.registry.get(InvariantService)?.fibers).toHaveLength(1)
expect(ctx.registry.get(InvariantRegistry)?.fibers).toHaveLength(1)
expect(ctx.registry.get(delayedCompanion)?.fibers).toHaveLength(1)
},
)
+3 -3
View File
@@ -15,7 +15,7 @@ import type {
SaveImageAttachment,
StoredImageAttachment,
} from '@deepseek-ai/dsh-attachment'
import InvariantService from '@deepseek-ai/dsh-invariants'
import InvariantRegistry from '@deepseek-ai/dsh-invariants'
declare global {
interface ImportMeta {
@@ -47,7 +47,7 @@ export const testInvariantCompanions: Readonly<Record<string, () => Promise<Test
/** Manual-topology suites whose names cannot follow the focused invariant convention. */
const MANUAL_INVARIANT_TEST_EXCEPTIONS = [
'/packages/support/invariants/tests/service.spec.ts',
'/packages/runtime-diagnostics/invariants/tests/service.spec.ts',
'/packages/examples/agent-spine-demo/tests/agent-core.spec.ts',
] as const
@@ -174,7 +174,7 @@ function startInvariantHost(root: Context): InvariantHost {
// awaits ready, so none starts ahead of its package checks. Tests plugging
// a companion directly must await an earlier root plugin first — the
// duplicate-mount failure otherwise is loud (owner name already reserved).
const serviceFiber = mount(InvariantService, { enabled: true })
const serviceFiber = mount(InvariantRegistry, { enabled: true })
const testPath = expect.getState().testPath ?? ''
const companionPaths = testInvariantCompanionPaths(testPath)
const ready = requireActive(serviceFiber, 'invariant service').then(async () => {
+131 -131
View File
@@ -224,82 +224,82 @@
{
"doc": "docs/subsystems/schedule.md",
"symbol": "AfterScheduleRecord",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "AtScheduleRecord",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "EveryScheduleRecord",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "LocalAtInput",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "AtInput",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "OneShotScheduleRecord",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "ScheduleRecord",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "ScheduleCreateChange",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "ScheduleDeleteChange",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "OneShotScheduleDispatchChange",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "EveryScheduleDispatchChange",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "ScheduleDispatchChange",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "ScheduleChange",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "ScheduleState",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "ScheduleDeliveryMode",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/schedule.md",
"symbol": "ScheduleView",
"source": "packages/schedule/tool-schedule/src/types.ts"
"source": "packages/schedule/schedule/src/types.ts"
},
{
"doc": "docs/subsystems/commands.md",
@@ -806,44 +806,44 @@
"source": "packages/core/tools/src/json-schema.ts"
},
{
"doc": "docs/subsystems/user-interaction.md",
"doc": "docs/subsystems/user-questions.md",
"symbol": "AskUserQuestionOption",
"source": "packages/interaction/user-interaction/src/types.ts"
"source": "packages/interaction/user-questions/src/types.ts"
},
{
"doc": "docs/subsystems/user-interaction.md",
"doc": "docs/subsystems/user-questions.md",
"symbol": "AskUserQuestionIntent",
"source": "packages/interaction/user-interaction/src/types.ts"
"source": "packages/interaction/user-questions/src/types.ts"
},
{
"doc": "docs/subsystems/user-interaction.md",
"doc": "docs/subsystems/user-questions.md",
"symbol": "AskUserQuestionItem",
"source": "packages/interaction/user-interaction/src/types.ts"
"source": "packages/interaction/user-questions/src/types.ts"
},
{
"doc": "docs/subsystems/user-interaction.md",
"doc": "docs/subsystems/user-questions.md",
"symbol": "AskUserQuestionRequest",
"source": "packages/interaction/user-interaction/src/index.ts"
"source": "packages/interaction/user-questions/src/index.ts"
},
{
"doc": "docs/subsystems/user-interaction.md",
"doc": "docs/subsystems/user-questions.md",
"symbol": "AskUserQuestionAnswerItem",
"source": "packages/interaction/user-interaction/src/types.ts"
"source": "packages/interaction/user-questions/src/types.ts"
},
{
"doc": "docs/subsystems/user-interaction.md",
"doc": "docs/subsystems/user-questions.md",
"symbol": "AskUserQuestionAnswer",
"source": "packages/interaction/user-interaction/src/types.ts"
"source": "packages/interaction/user-questions/src/types.ts"
},
{
"doc": "docs/subsystems/user-interaction.md",
"symbol": "UserInteractionProvider",
"source": "packages/interaction/user-interaction/src/index.ts"
"doc": "docs/subsystems/user-questions.md",
"symbol": "UserQuestionProvider",
"source": "packages/interaction/user-questions/src/index.ts"
},
{
"doc": "docs/subsystems/user-interaction.md",
"symbol": "UserInteractionError",
"source": "packages/interaction/user-interaction/src/index.ts"
"doc": "docs/subsystems/user-questions.md",
"symbol": "UserQuestionError",
"source": "packages/interaction/user-questions/src/index.ts"
},
{
"doc": "docs/subsystems/approval.md",
@@ -891,94 +891,94 @@
"source": "packages/attachment/attachment/src/types.ts"
},
{
"doc": "docs/subsystems/bash.md",
"symbol": "BashExecRequest",
"source": "packages/bash/bash/src/types.ts"
"doc": "docs/subsystems/shell.md",
"symbol": "ShellExecRequest",
"source": "packages/shell/shell/src/types.ts"
},
{
"doc": "docs/subsystems/bash.md",
"symbol": "BashExecSpec",
"source": "packages/bash/bash/src/types.ts"
"doc": "docs/subsystems/shell.md",
"symbol": "ShellExecSpec",
"source": "packages/shell/shell/src/types.ts"
},
{
"doc": "docs/subsystems/bash.md",
"symbol": "BashRunResult",
"source": "packages/bash/bash/src/types.ts"
"doc": "docs/subsystems/shell.md",
"symbol": "ShellRunResult",
"source": "packages/shell/shell/src/types.ts"
},
{
"doc": "docs/subsystems/bash.md",
"symbol": "BashSandboxInfo",
"source": "packages/bash/bash/src/types.ts"
"doc": "docs/subsystems/shell.md",
"symbol": "ShellSandboxInfo",
"source": "packages/shell/shell/src/types.ts"
},
{
"doc": "docs/subsystems/bash.md",
"symbol": "BashProcess",
"source": "packages/bash/bash/src/types.ts"
"doc": "docs/subsystems/shell.md",
"symbol": "ShellProcess",
"source": "packages/shell/shell/src/types.ts"
},
{
"doc": "docs/subsystems/bash.md",
"symbol": "BashProcessRead",
"source": "packages/bash/bash/src/types.ts"
"doc": "docs/subsystems/shell.md",
"symbol": "ShellProcessRead",
"source": "packages/shell/shell/src/types.ts"
},
{
"doc": "docs/subsystems/tasks.md",
"symbol": "TaskKindMap",
"source": "packages/tasks/tasks/src/types.ts"
"doc": "docs/subsystems/jobs.md",
"symbol": "JobKindMap",
"source": "packages/jobs/jobs/src/types.ts"
},
{
"doc": "docs/subsystems/tasks.md",
"symbol": "TaskStart",
"source": "packages/tasks/tasks/src/types.ts"
"doc": "docs/subsystems/jobs.md",
"symbol": "JobStart",
"source": "packages/jobs/jobs/src/types.ts"
},
{
"doc": "docs/subsystems/tasks.md",
"symbol": "TaskHooks",
"source": "packages/tasks/tasks/src/types.ts"
"doc": "docs/subsystems/jobs.md",
"symbol": "JobHooks",
"source": "packages/jobs/jobs/src/types.ts"
},
{
"doc": "docs/subsystems/tasks.md",
"symbol": "TaskOutcome",
"source": "packages/tasks/tasks/src/types.ts"
"doc": "docs/subsystems/jobs.md",
"symbol": "JobOutcome",
"source": "packages/jobs/jobs/src/types.ts"
},
{
"doc": "docs/subsystems/tasks.md",
"symbol": "TaskSnapshot",
"source": "packages/tasks/tasks/src/types.ts"
"doc": "docs/subsystems/jobs.md",
"symbol": "JobSnapshot",
"source": "packages/jobs/jobs/src/types.ts"
},
{
"doc": "docs/subsystems/tasks.md",
"symbol": "TaskRead",
"source": "packages/tasks/tasks/src/types.ts"
"doc": "docs/subsystems/jobs.md",
"symbol": "JobRead",
"source": "packages/jobs/jobs/src/types.ts"
},
{
"doc": "docs/subsystems/pty.md",
"symbol": "PtyWaitReason",
"source": "packages/pty/pty/src/types.ts"
"doc": "docs/subsystems/terminal.md",
"symbol": "TerminalWaitReason",
"source": "packages/terminal/terminal/src/types.ts"
},
{
"doc": "docs/subsystems/pty.md",
"symbol": "PtySessionStatus",
"source": "packages/pty/pty/src/types.ts"
"doc": "docs/subsystems/terminal.md",
"symbol": "TerminalSessionStatus",
"source": "packages/terminal/terminal/src/types.ts"
},
{
"doc": "docs/subsystems/pty.md",
"symbol": "PtyBackend",
"source": "packages/pty/pty/src/types.ts"
"doc": "docs/subsystems/terminal.md",
"symbol": "TerminalBackend",
"source": "packages/terminal/terminal/src/types.ts"
},
{
"doc": "docs/subsystems/pty.md",
"symbol": "PtyBackendSession",
"source": "packages/pty/pty/src/types.ts"
"doc": "docs/subsystems/terminal.md",
"symbol": "TerminalBackendSession",
"source": "packages/terminal/terminal/src/types.ts"
},
{
"doc": "docs/subsystems/pty.md",
"symbol": "PtySendOperation",
"source": "packages/pty/pty/src/types.ts"
"doc": "docs/subsystems/terminal.md",
"symbol": "TerminalSendOperation",
"source": "packages/terminal/terminal/src/types.ts"
},
{
"doc": "docs/subsystems/pty.md",
"symbol": "PtySendResult",
"source": "packages/pty/pty/src/types.ts"
"doc": "docs/subsystems/terminal.md",
"symbol": "TerminalSendResult",
"source": "packages/terminal/terminal/src/types.ts"
},
{
"doc": "docs/subsystems/sandbox.md",
@@ -1117,8 +1117,8 @@
},
{
"doc": "docs/subsystems/filesystem.md",
"symbol": "FsPolicyExec",
"source": "packages/fs/fs-policy/src/types.ts"
"symbol": "FsObservationActor",
"source": "packages/fs/fs-observation-policy/src/types.ts"
},
{
"doc": "docs/subsystems/filesystem.md",
@@ -1198,27 +1198,27 @@
{
"doc": "docs/subsystems/compaction.md",
"symbol": "CompactionResult",
"source": "packages/compact/compact/src/types.ts"
"source": "packages/compaction/compaction/src/types.ts"
},
{
"doc": "docs/subsystems/compaction.md",
"symbol": "CompactionTrigger",
"source": "packages/compact/compact/src/index.ts"
"source": "packages/compaction/compaction/src/index.ts"
},
{
"doc": "docs/subsystems/compaction.md",
"symbol": "ManualCompactionErrorCode",
"source": "packages/compact/compact/src/index.ts"
"source": "packages/compaction/compaction/src/index.ts"
},
{
"doc": "docs/subsystems/compaction.md",
"symbol": "PrunedEntry",
"source": "packages/compact/compact-tool-result-prune/src/types.ts"
"source": "packages/compaction/compaction-tool-result-pruner/src/types.ts"
},
{
"doc": "docs/subsystems/compaction.md",
"symbol": "PruneResult",
"source": "packages/compact/compact-tool-result-prune/src/types.ts"
"source": "packages/compaction/compaction-tool-result-pruner/src/types.ts"
},
{
"doc": "docs/subsystems/subagent.md",
@@ -1616,14 +1616,14 @@
"source": "packages/settings/settings/src/index.ts"
},
{
"doc": "docs/subsystems/permission.md",
"doc": "docs/subsystems/permission-presets.md",
"symbol": "Config",
"source": "packages/interaction/permission/src/index.ts"
"source": "packages/interaction/permission-presets/src/index.ts"
},
{
"doc": "docs/subsystems/permission.md",
"doc": "docs/subsystems/permission-presets.md",
"symbol": "PresetOption",
"source": "packages/interaction/permission/src/types.ts"
"source": "packages/interaction/permission-presets/src/types.ts"
},
{
"doc": "docs/subsystems/plan.md",
@@ -1633,30 +1633,30 @@
{
"doc": "docs/subsystems/invariants.md",
"symbol": "Config",
"source": "packages/support/invariants/src/index.ts"
"source": "packages/runtime-diagnostics/invariants/src/index.ts"
},
{
"doc": "docs/subsystems/invariants.md",
"symbol": "InvariantFailure",
"source": "packages/support/invariants/src/index.ts"
"source": "packages/runtime-diagnostics/invariants/src/index.ts"
},
{
"doc": "docs/subsystems/invariants.md",
"symbol": "InvariantInstaller",
"source": "packages/support/invariants/src/index.ts"
"source": "packages/runtime-diagnostics/invariants/src/index.ts"
},
{
"doc": "docs/subsystems/http-server.md",
"doc": "docs/subsystems/web-server.md",
"symbol": "WebRouteKind",
"source": "packages/host/webserver/src/index.ts"
},
{
"doc": "docs/subsystems/http-server.md",
"doc": "docs/subsystems/web-server.md",
"symbol": "WebRoute",
"source": "packages/host/webserver/src/index.ts"
},
{
"doc": "docs/subsystems/http-server.md",
"doc": "docs/subsystems/web-server.md",
"symbol": "Config",
"source": "packages/host/webserver/src/index.ts"
},
@@ -1711,64 +1711,64 @@
"source": "packages/client/modules/src/client/manifest.ts"
},
{
"doc": "docs/subsystems/telemetry.md",
"symbol": "TelemetrySharingStatus",
"doc": "docs/subsystems/session-telemetry.md",
"symbol": "SessionTelemetrySharingStatus",
"source": "packages/session/session-telemetry/src/index.ts"
},
{
"doc": "docs/subsystems/telemetry.md",
"symbol": "TelemetrySeverity",
"doc": "docs/subsystems/session-telemetry.md",
"symbol": "SessionTelemetrySeverity",
"source": "packages/session/session-telemetry/src/index.ts"
},
{
"doc": "docs/subsystems/telemetry.md",
"symbol": "TelemetryRecord",
"doc": "docs/subsystems/session-telemetry.md",
"symbol": "SessionTelemetryRecord",
"source": "packages/session/session-telemetry/src/index.ts"
},
{
"doc": "docs/subsystems/telemetry.md",
"symbol": "TelemetryBackend",
"doc": "docs/subsystems/session-telemetry.md",
"symbol": "SessionTelemetrySink",
"source": "packages/session/session-telemetry/src/index.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "TypeRTLookupMap",
"source": "packages/typert/type-meta/src/types.ts"
"symbol": "TypertLookupMap",
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "TypeRTContextMap",
"source": "packages/typert/type-meta/src/types.ts"
"symbol": "TypertContextMap",
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "TypeRTLookupDefinition",
"source": "packages/typert/type-meta/src/types.ts"
"symbol": "TypertLookupDefinition",
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "TypeRTCodec",
"source": "packages/typert/type-meta/src/types.ts"
"symbol": "TypertCodec",
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "InvocationParameterDescriptor",
"source": "packages/typert/type-meta/src/types.ts"
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "InvocationDescriptor",
"source": "packages/typert/type-meta/src/types.ts"
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "TypeRTService",
"source": "packages/typert/type-meta/src/types.ts"
"symbol": "TypertRegistryContract",
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "TypeRTRemoteNamespaceMap",
"source": "packages/typert/type-meta/src/types.ts"
"symbol": "TypertRemoteNamespaceMap",
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/typert.md",
@@ -1787,8 +1787,8 @@
},
{
"doc": "docs/subsystems/typert.md",
"symbol": "TypeRTClientRemote",
"source": "packages/typert/type-meta/src/types.ts"
"symbol": "TypertClientRemote",
"source": "packages/typert/protocol/src/types.ts"
},
{
"doc": "docs/subsystems/credentials.md",
@@ -1796,9 +1796,9 @@
"source": "packages/credentials/credentials/src/types.ts"
},
{
"doc": "docs/subsystems/permission.md",
"doc": "docs/subsystems/permission-presets.md",
"symbol": "PresetSpec",
"source": "packages/interaction/permission/src/index.ts"
"source": "packages/interaction/permission-presets/src/index.ts"
},
{
"doc": "docs/subsystems/session-projection.md",
+2 -2
View File
@@ -53,7 +53,7 @@ const CHOOSER_PACKAGE = '@deepseek-ai/dsh-host-directory-picker-auto'
const CHOOSER_BACKEND_PACKAGES = [
'@deepseek-ai/dsh-host-directory-picker-native',
'@deepseek-ai/dsh-host-directory-picker-browse',
'@deepseek-ai/dsh-client-ui-directory-picker',
'@deepseek-ai/dsh-client-ui-directory-picker-browse',
'@deepseek-ai/dsh-client-ui-directory-picker-native',
]
const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
@@ -137,7 +137,7 @@ function validateClientHalvesDeclared(): string[] {
* contributor to that service reaches nobody; a row that registers into a host
* singleton registers once per live session, so the second one collides.
*
* Both have happened. `bash-env` in a preset realm left `DSH_WEB_URL` reaching
* Both have happened. `shell-env` in a preset realm left `DSH_WEB_URL` reaching
* no shell, and `tool-subagent-report` handed every child `report` once per live
* session until the second registration threw. Neither changes a tool catalog,
* so no catalog assertion can see them — and the shipped presets are near-copies
@@ -32,8 +32,8 @@ interface SentenceContract {
const NO_MODEL_EXPERIENCE_SECTION: Readonly<Record<string, string>> = {
'packages/core/scope': 'The package is a model-agnostic registration and lifecycle primitive; model-facing consumers own any context selection.',
'packages/util/brand': 'The package is a type-only primitive erased at compile time.',
'packages/util/paths': 'The package only resolves harness-owned host paths; model-facing consumers own any rendered use.',
'packages/util/environment': 'The package only resolves host environment values; model-facing consumers own any rendered use.',
'packages/util/home-paths': 'The package only resolves harness-owned host paths; model-facing consumers own any rendered use.',
'packages/util/launch-environment': 'The package only resolves host environment values; model-facing consumers own any rendered use.',
}
/**
@@ -44,13 +44,13 @@ const NO_MODEL_EXPERIENCE_SECTION: Readonly<Record<string, string>> = {
const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/attachment/attachment': { kind: 'indirect', reason: 'The storage seam delegates model request rendering to provider adapters.' },
'packages/attachment/attachment-local': { kind: 'indirect', reason: 'The local backend delegates model request rendering to provider adapters.' },
'packages/bash/bash': { kind: 'indirect', reason: 'The service interface delegates all model rendering to dsh-tool-bash.' },
'packages/bash/bash-env': { kind: 'indirect', reason: 'The env service exposes managed DSH_* facts through the shell tools (dsh-tool-bash/dsh-tool-pwsh); it registers no prompt or schema of its own.' },
'packages/bash/bash-local': { kind: 'indirect', reason: 'The executor backend delegates model rendering to dsh-tool-bash.' },
'packages/bash/pwsh-local': { kind: 'indirect', reason: 'The executor backend delegates model rendering to dsh-tool-pwsh.' },
'packages/shell/shell': { kind: 'indirect', reason: 'The service interface delegates all model rendering to dsh-tool-bash.' },
'packages/shell/shell-env': { kind: 'indirect', reason: 'The env service exposes managed DSH_* facts through the shell tools (dsh-tool-bash/dsh-tool-pwsh); it registers no prompt or schema of its own.' },
'packages/shell/bash-local': { kind: 'indirect', reason: 'The executor backend delegates model rendering to dsh-tool-bash.' },
'packages/shell/pwsh-local': { kind: 'indirect', reason: 'The executor backend delegates model rendering to dsh-tool-pwsh.' },
'packages/code-runtime/code-runtime': { kind: 'indirect', reason: 'The service interface delegates model rendering to Code Mode in dsh-tools.' },
'packages/core/agent-tool-mode': { kind: 'indirect', reason: 'The row only selects between the two projections dsh-tools owns; it registers no prompt, schema, or result of its own.' },
'packages/code-runtime/code-runtime-worker': { kind: 'indirect', reason: 'The worker backend delegates model rendering to Code Mode in dsh-tools.' },
'packages/core/agent-tool-presentation': { kind: 'indirect', reason: 'The row only selects between the two projections dsh-tools owns; it registers no prompt, schema, or result of its own.' },
'packages/code-runtime/code-runtime-worker-thread': { kind: 'indirect', reason: 'The worker backend delegates model rendering to Code Mode in dsh-tools.' },
'packages/client/ui-agent-preset': { kind: 'indirect', reason: 'Browser-side settings row; the preset it selects owns every model-facing effect.' },
'packages/core/agent-default-model': { kind: 'indirect', reason: 'The service supplies a ModelSelection; request assembly and adapters own the model-visible request.' },
'packages/preset/agent-presets': { kind: 'indirect', reason: 'The mount installs a preset\'s own plugins, which own every model-facing registration it makes visible.' },
@@ -59,7 +59,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/e2b/e2b': { kind: 'none', reason: 'The shared remote-runtime owner registers no model context; provider adapters and consumers own rendered effects.' },
'packages/client/hmr': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/modules': { kind: 'none', reason: 'Browser-side module-loading kernel machinery; registers nothing model-facing.' },
'packages/client/test-runtime': { kind: 'none', reason: 'Browser-side test infrastructure (jsdom bench); registers nothing model-facing.' },
'packages/test-support/client-runtime': { kind: 'none', reason: 'Browser-side test infrastructure (jsdom bench); registers nothing model-facing.' },
'packages/client/ui-slots': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-attachment': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-primitives': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
@@ -71,27 +71,28 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/client/ui-layout': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-sidebar': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-conversation': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-feedback': { kind: 'none', reason: 'Browser-side controls over the message-feedback sidecar; ratings and notes never enter the Session log, model context, or telemetry.' },
'packages/client/ui-message-feedback': { kind: 'none', reason: 'Browser-side controls over the message-feedback sidecar; ratings and notes never enter the Session log, model context, or telemetry.' },
'packages/client/ui-tool': { kind: 'none', reason: 'Browser-side Tool presentation layer; renders logged calls without changing model context.' },
'packages/client/ui-task': { kind: 'none', reason: 'Browser-side read-only projection of ctx.tasks records; dsh-tool-tasks owns the model-facing behavior.' },
'packages/client/ui-jobs': { kind: 'none', reason: 'Browser-side read-only projection of ctx.jobs records; dsh-tool-jobs owns the model-facing behavior.' },
'packages/client/ui-workflow-run': { kind: 'none', reason: 'Browser-side UI plugin layer; renders durable workflow records without changing model context.' },
'packages/client/ui-slash': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-command': { kind: 'indirect', reason: 'The dispatch paths trigger the host command.execute RPC; each command handler\'s host package owns any model-visible effect.' },
'packages/client/ui-model': { kind: 'indirect', reason: 'Selection routes session.selectModel; the Host snapshots the selection at the next prompt-assembly boundary and owns the model-visible effect.' },
'packages/client/ui-input-trigger': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-commands': { kind: 'indirect', reason: 'The dispatch paths trigger the host command.execute RPC; each command handler\'s host package owns any model-visible effect.' },
'packages/client/ui-model-selection': { kind: 'indirect', reason: 'Selection routes session.selectModel; the Host snapshots the selection at the next prompt-assembly boundary and owns the model-visible effect.' },
'packages/client/ui-goal': { kind: 'indirect', reason: 'The strip verbs route goal.* mutations; the host GoalService owns the model-visible goal/change context message.' },
'packages/client/ui-permission': { kind: 'indirect', reason: 'The picker submits the host /permission command; the knob events it appends own the model-visible effect through the sandbox/approval consumers.' },
'packages/client/ui-plugin-config': { kind: 'none', reason: 'Browser-side settings surface; registers no model surface.' },
'packages/extensions/ui-cordis': { kind: 'indirect', reason: 'The definition card drives the host dynamic run/stop verbs that the model\'s cordis_run/cordis_stop tools also reach; the runner owns any model-visible effect.' },
'packages/client/ui-permission-presets': { kind: 'indirect', reason: 'The picker submits the host /permission command; the knob events it appends own the model-visible effect through the sandbox/approval consumers.' },
'packages/client/ui-settings-plugins': { kind: 'none', reason: 'Browser-side settings surface; registers no model surface.' },
'packages/client/ui-plan': { kind: 'indirect', reason: 'The chip dispatches /plan off; dsh-plan-mode owns the model-visible policy, exit tool, and logged state.' },
'packages/client/ui-question': { kind: 'indirect', reason: 'The package mounts dsh-tool-ask-user; that tool owns the model-visible schema and answer rendering.' },
'packages/client/ui-user-questions': { kind: 'indirect', reason: 'The package mounts dsh-tool-ask-user; that tool owns the model-visible schema and answer rendering.' },
'packages/client/ui-trajectory': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-workspace': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-directory-picker': { kind: 'none', reason: 'Browser-side directory-browsing surface; registers nothing model-facing.' },
'packages/client/ui-directory-picker-browse': { kind: 'none', reason: 'Browser-side directory-browsing surface; registers nothing model-facing.' },
'packages/client/ui-directory-picker-native': { kind: 'none', reason: 'Browser-side surface driving the host OS chooser; registers nothing model-facing.' },
'packages/client/ui-theme': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-settings': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-settings-general': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-models': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-plugins': { kind: 'none', reason: 'Browser-side inventory projection; registers nothing model-facing.' },
'packages/client/ui-settings-models': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-settings-plugin-inventory': { kind: 'none', reason: 'Browser-side inventory projection; registers nothing model-facing.' },
'packages/client/locale': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/web': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/examples/agent-spine-demo': { kind: 'indirect', reason: 'The bundle only mounts model-facing child plugins.' },
@@ -112,12 +113,12 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/llm/llm': { kind: 'none', reason: 'The adapter registry forwards already-assembled requests unchanged.' },
'packages/llm/token-meter': { kind: 'indirect', reason: 'The measurement service leaves model-visible changes to its consumers.' },
'packages/lsp/lsp': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-lsp.' },
'packages/lsp/lsp-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-lsp.' },
'packages/lsp/lsp-stdio': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-lsp.' },
'packages/subprocess/subprocess': { kind: 'indirect', reason: 'The seam delegates all model rendering to consumer seams such as the bash executor family.' },
'packages/e2b/subprocess-e2b': { kind: 'indirect', reason: 'The remote spawn backend delegates model rendering to consumer seams such as the bash executor family.' },
'packages/subprocess/subprocess-local': { kind: 'indirect', reason: 'The spawn backend delegates model rendering to consumer seams such as the bash executor family.' },
'packages/sandbox/sandbox-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-bash-sandbox and dsh-tool-bash.' },
'packages/sandbox/sandbox-windows-acl': { kind: 'indirect', reason: 'The provider backend delegates model rendering to the bash/pwsh sandbox executors and their tools.' },
'packages/sandbox/sandbox-windows-acl': { kind: 'indirect', reason: 'The provider backend delegates model rendering to the shell/pwsh sandbox executors and their tools.' },
'packages/sdk/client': { kind: 'none', reason: 'Client-process library; model-facing behavior lives in the spawned runtime\'s composed plugins.' },
'packages/sdk/protocol': { kind: 'none', reason: 'Client-facing wire library; the runtime plugins behind the serving entry own model-facing behavior.' },
'packages/session/session-projection': { kind: 'none', reason: 'The projection registry serves client-facing read models of already-logged session state and registers nothing model-facing.' },
@@ -126,40 +127,40 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/session-query/session-query': { kind: 'none', reason: 'The trusted query service exposes cloned records only to callers and registers nothing model-facing.' },
'packages/session-query/session-query-sqlite': { kind: 'none', reason: 'The search backend returns hits only to callers and registers nothing model-facing.' },
'packages/settings/settings': { kind: 'indirect', reason: 'The seam stores and resolves user settings; consumer plugins own any model-facing content fed by a value.' },
'packages/settings/settings-local': { kind: 'indirect', reason: 'The file provider stores and publishes namespace sections; consumers of ctx.settings own any model-facing behavior.' },
'packages/settings/settings-file': { kind: 'indirect', reason: 'The file provider stores and publishes namespace sections; consumers of ctx.settings own any model-facing behavior.' },
'packages/credentials/credentials': { kind: 'indirect', reason: 'The seam resolves credential references; the consuming adapter owns every model-facing use a value authorizes.' },
'packages/credentials/credentials-local': { kind: 'indirect', reason: 'The file/environment provider stores credential values; consumers of ctx.credentials own any model-facing behavior.' },
'packages/util/atomic-write': { kind: 'none', reason: 'Pure filesystem write primitive; registers nothing model-facing.' },
'packages/session/session-telemetry': { kind: 'none', reason: 'The seam observes the session stream and hands redacted copies outward; it registers nothing model-facing.' },
'packages/session/session-telemetry-otel': { kind: 'none', reason: 'The backend forwards seam records into the OTel SDK pipeline and registers nothing model-facing.' },
'packages/session/user-id': { kind: 'none', reason: 'The shared identifier reaches DeepSeek only as model-hidden HTTP metadata; it registers nothing model-facing.' },
'packages/identity/anonymous-user-id': { kind: 'none', reason: 'The shared identifier reaches DeepSeek only as model-hidden HTTP metadata; it registers nothing model-facing.' },
'packages/skill/skill': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-skill.' },
'packages/skill/skill-badge': { kind: 'indirect', reason: 'The bundled provider delegates model rendering to dsh-tool-skill.' },
'packages/skill/skill-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-skill.' },
'packages/skill/skill-filesystem': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-skill.' },
'packages/spill/spill': { kind: 'indirect', reason: 'The storage seam delegates model rendering to spill consumers.' },
'packages/spill/spill-local': { kind: 'indirect', reason: 'The storage backend delegates model rendering to spill consumers.' },
'packages/support/acp-snapshot': { kind: 'none', reason: 'The test harness observes and normalizes transcripts without changing live requests.' },
'packages/support/agent-loop-testkit': { kind: 'none', reason: 'The test helper mounts services but neither drives nor modifies model requests.' },
'packages/support/invariants': { kind: 'none', reason: 'The observer validates requests but never rewrites their context.' },
'packages/support/loader-smoke': { kind: 'none', reason: 'The test harness submits an ordinary user task but delegates prompt and tool composition to the loaded tree.' },
'packages/support/llm-mock-server': { kind: 'none', reason: 'The test server substitutes provider wire behavior without invoking a real model.' },
'packages/support/llm-replay': { kind: 'none', reason: 'The keyless adapter invokes no provider model.' },
'packages/test-support/acp-snapshot': { kind: 'none', reason: 'The test harness observes and normalizes transcripts without changing live requests.' },
'packages/test-support/agent-loop-testkit': { kind: 'none', reason: 'The test helper mounts services but neither drives nor modifies model requests.' },
'packages/runtime-diagnostics/invariants': { kind: 'none', reason: 'The observer validates requests but never rewrites their context.' },
'packages/test-support/loader-smoke': { kind: 'none', reason: 'The test harness submits an ordinary user task but delegates prompt and tool composition to the loaded tree.' },
'packages/test-support/llm-mock-server': { kind: 'none', reason: 'The test server substitutes provider wire behavior without invoking a real model.' },
'packages/test-support/llm-replay': { kind: 'none', reason: 'The keyless adapter invokes no provider model.' },
'packages/api/gateway': { kind: 'none', reason: 'Remote dispatch infrastructure; invoked business methods own any model-visible effect.' },
'packages/typert/type-meta': { kind: 'none', reason: 'Compiler-independent Remote protocol declarations; registers nothing model-facing.' },
'packages/typert/protocol': { kind: 'none', reason: 'Compiler-independent Remote protocol declarations; registers nothing model-facing.' },
'packages/typert/generator': { kind: 'none', reason: 'The build-time generator runs outside any agent runtime and touches no model request.' },
'packages/tasks/tasks': { kind: 'indirect', reason: 'Producer and controller plugins own all model rendering over the task registry.' },
'packages/tasks/tasks-local': { kind: 'indirect', reason: 'The registry backend delegates model rendering to producer plugins and dsh-tool-tasks.' },
'packages/jobs/jobs': { kind: 'indirect', reason: 'Producer and controller plugins own all model rendering over the job registry.' },
'packages/jobs/jobs-local': { kind: 'indirect', reason: 'The registry backend delegates model rendering to producer plugins and dsh-tool-jobs.' },
'packages/examples/acp-demo': { kind: 'indirect', reason: 'The app bundle delegates request composition to dsh-agent-spine-demo and dsh-acp.' },
'packages/boot/app-boot': { kind: 'indirect', reason: 'Only the loaded plugin tree contributes model context.' },
'packages/boot/cmdline': { kind: 'none', reason: 'Resolves the process command line before any session exists; configured rows own every model-visible consequence.' },
'packages/examples/jsonrpc-demo': { kind: 'indirect', reason: 'Only the externally configured plugin tree contributes model context.' },
'packages/interaction/permission': { kind: 'indirect', reason: 'The service writes mechanism events rendered by dsh-user-approval and dsh-tool-bash.' },
'packages/interaction/user-interaction': { kind: 'indirect', reason: 'Model-facing consumers render provider answers and seam errors.' },
'packages/interaction/permission-presets': { kind: 'indirect', reason: 'The service writes mechanism events rendered by dsh-user-approval and dsh-tool-bash.' },
'packages/interaction/user-questions': { kind: 'indirect', reason: 'Model-facing consumers render provider answers and seam errors.' },
'packages/util/timeout': { kind: 'indirect', reason: 'Only timeout consumers render timeout outcomes.' },
'packages/util/retention': { kind: 'indirect', reason: 'Only retention consumers render retained content and omission metadata.' },
'packages/util/output-retention': { kind: 'indirect', reason: 'Only retention consumers render retained content and omission metadata.' },
'packages/util/native-command': { kind: 'none', reason: 'The host-side subprocess runner registers nothing model-facing.' },
'packages/web/web': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-web.' },
'packages/web/web-fetch-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-web.' },
'packages/web/web-fetch-http': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-web.' },
'packages/web/web-search-exa': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-web.' },
'packages/workflow/workflow': { kind: 'indirect', reason: 'The service delegates parent and child model rendering to its consumer and engine.' },
}