Merge pull request #2234 from deepseek-harness/feat/loader-entry-disabled-interpolation

feat(loader): interpolate the entry disabled field
This commit is contained in:
Huanqi Cao
2026-08-12 11:40:02 +08:00
committed by GitHub
42 changed files with 479 additions and 314 deletions
+2 -3
View File
@@ -131,9 +131,8 @@ function workspaceManifests(): WorkspaceManifest[] {
}
const packageFileExtras: Readonly<Record<string, readonly string[]>> = {
// Profile bundles publish their dsh.bundle.patch layer beside the lib;
// dsh-base also ships the win32 shell platform layer the launcher reads.
'@deepseek-ai/dsh-base': ['cordis.patch.yml', 'windows.cordis.patch.yml'],
// Profile bundles publish their dsh.bundle.patch layer beside the lib.
'@deepseek-ai/dsh-base': ['cordis.patch.yml'],
'@deepseek-ai/dsh-web-app': ['cordis.patch.yml'],
'@deepseek-ai/dsh-headless': ['cordis.patch.yml'],
'@deepseek-ai/dsh-client-ui-theme': ['lib/styles'],
+2
View File
@@ -99,6 +99,8 @@ const GENERIC_SKIPS: readonly GenericSkip[] = [
// the preset a model mounts, so the scoped name would send the model after an
// id no roster reports.
{ file: 'apps/cli/config/agent-presets/cordis/agent.cordis.yml', upstream: ['cordis'] },
// The preset-roster loop names the `cordis` preset id, not a package.
{ file: 'apps/cli/tests/windows-shell.spec.ts', upstream: ['cordis'] },
// GROUP_ORDER holds `packages/<group>/` directory names, not package names.
{ file: 'scripts/gen-module-graph.ts', upstream: ['cordis'] },
{ file: 'scripts/gen-doc-graphs.ts', upstream: ['cordis'] },
+39
View File
@@ -0,0 +1,39 @@
/**
* The verify-cordis-config metadata contract: `disabled` is the one entry
* metadata field whose `!!js` expression the Loader interpolates; every other
* metadata field must stay static, and a disabled expression must parse.
*/
import { describe, expect, it } from 'vitest'
import { metadataExpressionErrors } from './verify-cordis-config.ts'
describe('verify-cordis-config metadata expressions', () => {
it('accepts a disabled !!js expression', () => {
const problems = metadataExpressionErrors(
{ id: 'tool-bash', name: '@deepseek-ai/dsh-tool-bash', disabled: { __jsExpr: "process.platform === 'win32'" } },
'[0]',
)
expect(problems).toEqual([])
})
it('rejects an expression in a static metadata field', () => {
const problems = metadataExpressionErrors({ id: { __jsExpr: 'process.platform' }, name: 'pkg' }, '[0]')
expect(problems).toContain('[0].id: !!js is not interpolated here')
})
it('rejects an expression nested below disabled (only the field itself interpolates)', () => {
const problems = metadataExpressionErrors(
{ id: 'tool-bash', name: 'pkg', disabled: { when: { __jsExpr: 'process.platform' } } },
'[0]',
)
expect(problems).toContain('[0].disabled.when: !!js is not interpolated here')
})
it('rejects a disabled expression that does not parse (the loader would fail the boot)', () => {
const problems = metadataExpressionErrors(
{ id: 'tool-bash', name: 'pkg', disabled: { __jsExpr: 'process.platform ===' } },
'[0]',
)
expect(problems.some(problem => problem.includes('[0].disabled: disabled expression does not parse'))).toBe(true)
})
})
+83 -29
View File
@@ -1,11 +1,13 @@
/**
* Validate Cordis Loader entry metadata and package resolution.
*
* The Loader interpolates only a plugin entry's `config`; expression objects in
* fields such as `disabled` remain truthy data and silently change composition.
* Example configs and the dsh Web composition resolve named plugins from their
* owning workspace manifests. Local example packages must also be in the root
* TypeScript project graph.
* The Loader interpolates a plugin entry's `config` (after declared injections
* activate, against that plugin context) and the entry `disabled` field (at
* every mount decision, against the loader context). Every other entry
* metadata field stays static, so an expression there remains truthy data and
* silently changes composition. Example configs and the dsh Web composition
* resolve named plugins from their owning workspace manifests. Local example
* packages must also be in the root TypeScript project graph.
*/
import { globSync, readFileSync } from 'node:fs'
@@ -36,7 +38,7 @@ const appOverlayFiles = new Set([
'examples/web-schedule/cordis.yml',
...globSync('examples/mcp-memory/*.cordis.yml', { cwd: root }),
])
const metadataFields = ['id', 'name', 'group', 'disabled', 'inject', 'intercept', 'isolate'] as const
const metadataFields = ['id', 'name', 'group', 'inject', 'intercept', 'isolate'] as const
/** The adaptive directory-picker chooser package (mounts a backend row at boot). */
const CHOOSER_PACKAGE = '@deepseek-ai/dsh-host-directory-picker-auto'
@@ -64,33 +66,36 @@ const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
})
const schema = yaml.JSON_SCHEMA.extend(jsExprType)
const files = cordisConfigFiles(root)
const errors: string[] = []
const pluginReferences: PluginReference[] = []
for (const file of files) {
const document: unknown = yaml.load(readFileSync(resolve(root, file), 'utf8'), { schema })
if (!isUnknownArray(document)) {
errors.push(`${file}: root must be a Loader entry array`)
continue
}
for (let index = 0; index < document.length; index++) {
validateEntry(document[index], file, `[${index}]`)
}
}
if (import.meta.main) {
const files = cordisConfigFiles(root)
errors.push(...validateExampleResolution())
errors.push(...validateAppResolution())
errors.push(...validateSourcePlaneResolution())
errors.push(...validatePresetPlaneSeparation())
errors.push(...validateClientHalvesDeclared())
for (const file of files) {
const document: unknown = yaml.load(readFileSync(resolve(root, file), 'utf8'), { schema })
if (!isUnknownArray(document)) {
errors.push(`${file}: root must be a Loader entry array`)
continue
}
for (let index = 0; index < document.length; index++) {
validateEntry(document[index], file, `[${index}]`)
}
}
if (errors.length > 0) {
console.error('verify-cordis-config: invalid Loader metadata or plugin package resolution:')
for (const error of errors) console.error(`- ${error}`)
process.exitCode = 1
} else {
console.log(`verify-cordis-config: ${files.length} config files passed.`)
errors.push(...validateExampleResolution())
errors.push(...validateAppResolution())
errors.push(...validateSourcePlaneResolution())
errors.push(...validatePresetPlaneSeparation())
errors.push(...validateClientHalvesDeclared())
if (errors.length > 0) {
console.error('verify-cordis-config: invalid Loader metadata or plugin package resolution:')
for (const error of errors) console.error(`- ${error}`)
process.exitCode = 1
} else {
console.log(`verify-cordis-config: ${files.length} config files passed.`)
}
}
/**
@@ -409,11 +414,60 @@ function packageNameFromSpecifier(specifier: string): string | undefined {
}
function validateMetadata(entry: Record<string, unknown>, file: string, path: string): void {
for (const problem of metadataExpressionErrors(entry, path)) {
errors.push(`${file}${problem}`)
}
}
/**
* Expression-node diagnostics for one entry. `disabled` is the single
* interpolated metadata field: its own `!!js` expression node is allowed and
* must parse, while expressions nested below it stay truthy data; every other
* metadata field must stay fully static.
* @param entry - one loader entry (or patch row).
* @param path - the entry's diagnostic path prefix.
* @returns one diagnostic per offending expression.
*/
export function metadataExpressionErrors(entry: Record<string, unknown>, path: string): string[] {
const problems: string[] = []
for (const field of metadataFields) {
if (!(field in entry)) continue
const expressionPaths: string[] = []
collectExpressionPaths(entry[field], `${path}.${field}`, expressionPaths)
for (const expressionPath of expressionPaths) errors.push(`${file}${expressionPath}: !!js is not interpolated here`)
for (const expressionPath of expressionPaths) problems.push(`${expressionPath}: !!js is not interpolated here`)
}
const disabled = entry.disabled
if (disabled !== undefined) {
if (isJsExpr(disabled)) {
const detail = disabledExpressionProblem(disabled.__jsExpr)
if (detail !== undefined) problems.push(`${path}.disabled${detail}`)
} else {
// A non-expression value gates on Boolean() at mount; an expression
// nested anywhere below it never evaluates, so it must stay literal.
const expressionPaths: string[] = []
collectExpressionPaths(disabled, `${path}.disabled`, expressionPaths)
for (const expressionPath of expressionPaths) problems.push(`${expressionPath}: !!js is not interpolated here`)
}
}
return problems
}
/**
* Parse-only validation of a `disabled` expression: the Loader evaluates it
* at every mount decision, and a syntax error would fail the boot — rejecting
* it here moves that failure to the earliest resolvable point.
* @param expression - the `!!js` expression text.
* @returns the diagnostic suffix, or `undefined` when the expression parses.
*/
function disabledExpressionProblem(expression: string): string | undefined {
try {
// Compilation only — the constructor never executes the body.
// oxlint-disable-next-line typescript/no-implied-eval
new Function(`return (${expression})`)
return undefined
} catch (error) {
const detail = error instanceof Error ? error.message : String(error)
return `: disabled expression does not parse: ${detail}`
}
}