feat(plugin): compose optional bundles inside agent presets

This commit is contained in:
Tianyi Cui committed 2026-10-06 23:48:58 +08:00
1 parent 4da62dc8f4
commit 3aa1cab43a
109 files changed
+3442 -130

No files matched your search

+75 -6
View File
@@ -4,6 +4,7 @@ import { globSync, readFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import { describe, expect, it } from 'vitest'
import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
import { loadOverlayPatches } from '../packages/boot/app-boot/src/index.ts'
import { readPluginMeta } from '../packages/boot/app-boot/src/package-meta.ts'
import { OPTIONAL_BUNDLES, PROFILE_TEMPLATES, bundlePatchPaths, composeEntries } from '../packages/boot/app-boot/src/profile.ts'
@@ -27,6 +28,26 @@ function bundle(name: string): { dir: string; patches: ReturnType<typeof loadOve
return { dir: entry.dir, patches: bundlePatchPaths(entry.dir, entry.manifest.dsh.bundle).flatMap(path => loadOverlayPatches('test', path)) }
}
const lightweight = [
'@deepseek-ai/dsh-session-search',
'@deepseek-ai/dsh-string-editor',
'@deepseek-ai/dsh-tmux-location',
'@deepseek-ai/dsh-experimental-ralph-bundle',
'@deepseek-ai/dsh-experimental-terminal-bundle',
'@deepseek-ai/dsh-experimental-badge-skill-bundle',
'@deepseek-ai/dsh-experimental-session-titles-bundle',
]
function presetRows(entries: EntryOptions[], id: string): EntryOptions[] {
const preset = entries.find(entry => entry.id === id)
if (preset === undefined) throw new Error(`missing preset ${id}`)
return (preset.config as { plugins: EntryOptions[] }).plugins
}
function flattenRows(entries: EntryOptions[]): EntryOptions[] {
return entries.flatMap(entry => [entry, ...entry.group && Array.isArray(entry.config) ? flattenRows(entry.config as EntryOptions[]) : []])
}
describe('optional bundles', () => {
const shipped = ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'].map(name => bundle(name).patches)
@@ -42,12 +63,15 @@ describe('optional bundles', () => {
const { patches } = bundle(name)
const warnings: string[] = []
const composed = composeEntries([...shipped, patches], message => warnings.push(message))
const ids = new Set(composed.map(entry => entry.id))
expect(warnings).toEqual([])
// Inserted rows carry stable ids at the profile root, so a later profile patch can configure or disable them.
for (const row of patches.flatMap(patch => patch.insert ?? [])) {
expect(typeof row.id).toBe('string')
expect(ids.has(row.id)).toBe(true)
// Inserted ids remain addressable inside their selected profile or preset tree.
for (const patch of patches) {
const rows = typeof patch.preset === 'string' ? presetRows(composed, patch.preset) : composed
const ids = new Set(flattenRows(rows).map(entry => entry.id))
for (const row of patch.insert ?? []) {
expect(typeof row.id).toBe('string')
expect(ids.has(row.id)).toBe(true)
}
}
// One top-level row per id: a duplicate declaration leaves the Loader with the last one, silently
// replacing the layer that declared the id first.
@@ -57,7 +81,7 @@ describe('optional bundles', () => {
// row, and the override keeps the package the shipped layer declared on it.
const shippedComposed = composeEntries([...shipped])
for (const patch of patches) {
if (patch.insert !== undefined || typeof patch.id !== 'string') continue
if (patch.insert !== undefined || typeof patch.id !== 'string' || patch.preset !== undefined) continue
const matches = composed.filter(entry => entry.id === patch.id)
expect(matches).toHaveLength(1)
expect(matches[0]?.name).toBe(shippedComposed.find(entry => entry.id === patch.id)?.name)
@@ -75,6 +99,51 @@ describe('optional bundles', () => {
}
})
it('ships all seven lightweight bundles without selecting them in a default template', () => {
const defaults = Object.values(PROFILE_TEMPLATES).flatMap(template => template.bundles)
for (const name of lightweight) {
expect(OPTIONAL_BUNDLES).toContain(name)
expect(defaults).not.toContain(name)
}
})
it('composes every lightweight selection without changing minimal or adding host tools', () => {
const baseline = composeEntries(shipped)
const layers = lightweight.map(name => bundle(name).patches)
for (let mask = 0; mask < 2 ** lightweight.length; mask += 1) {
const selected = layers.filter((_layer, index) => (mask & 2 ** index) !== 0)
const warnings: string[] = []
const composed = composeEntries([...shipped, ...selected], warning => warnings.push(warning))
expect(warnings).toEqual([])
expect(presetRows(composed, 'preset-minimal')).toEqual(presetRows(baseline, 'preset-minimal'))
const addedHost = composed.filter(row => !baseline.some(existing => existing.id === row.id))
expect(addedHost.every(row => ['optional-skill-badge', 'optional-session-title-all-prompts'].includes(row.id ?? ''))).toBe(true)
for (const id of ['preset-standard', 'preset-cordis', 'preset-ptc']) {
const rows = flattenRows(presetRows(composed, id))
const ids = rows.map(row => row.id)
expect(new Set(ids).size).toBe(ids.length)
}
}
})
it('contributes tools inside all full presets with independently isolated services', () => {
const composed = composeEntries([...shipped, ...lightweight.map(name => bundle(name).patches)])
for (const id of ['preset-standard', 'preset-cordis', 'preset-ptc']) {
const roots = presetRows(composed, id)
const names = flattenRows(roots).map(row => row.name)
for (const name of [
'@deepseek-ai/dsh-tool-session-query', '@deepseek-ai/dsh-tool-str-replace-editor', '@deepseek-ai/dsh-tmux-context',
'@deepseek-ai/dsh-experimental-tool-ralph', '@deepseek-ai/dsh-experimental-tool-terminal',
]) expect(names).toContain(name)
expect(roots.find(row => row.id === 'optional-ralph')?.isolate).toEqual({ workflowEngine: true })
expect(roots.find(row => row.id === 'optional-persistent-terminals')?.isolate).toEqual({ terminals: true })
}
expect(composed.find(row => row.id === 'session-query-sqlite')?.config).toEqual({ path: ':memory:', openAt: 'first-search' })
expect(composed.find(row => row.id === 'session-title-llm')?.disabled).toBe(true)
expect(composed.find(row => row.id === 'optional-session-title-all-prompts')?.name)
.toBe('@deepseek-ai/dsh-experimental-session-title-all-prompts-llm')
})
it('delivers the Schedule service and task page without a Host clock row', () => {
const composed = composeEntries(shipped)
// The delivered composition carries the Host Schedule service and its task
@@ -138,6 +138,31 @@ describe('default product isolation', () => {
expect(verifyDefaultProductIsolation(root).failures.join('\n')).toContain(`optional bundle ${layer} must not be a default bundle`)
})
it.each(['dependencies', 'source', 'patch'])(
'checks a nonexperimental optional bundle for experimental %s', (kind) => {
const root = fixture()
const layer = '@deepseek-ai/dsh-optional-layer'
write(root, 'packages/bundle/optional-layer/package.json', {
name: layer, icon: './icon.svg', exports: { './locale/*.json': './locale/*.json' },
dsh: { bundle: { patch: './cordis.patch.yml' } },
})
write(root, 'packages/bundle/optional-layer/src/index.ts', 'export {}')
write(root, 'packages/bundle/optional-layer/cordis.patch.yml', [])
manifest(root, 'apps/cli/package.json', { dependencies: { [core]: 'workspace:*', [layer]: 'workspace:*' } })
write(root, profile, `export const PROFILE_TEMPLATES = { web: { bundles: ['${base}'] } }\n`
+ `export const DEFAULT_PROFILE_BUNDLES = ['${base}']\nexport const OPTIONAL_BUNDLES = ['${layer}']\n`)
expect(verifyDefaultProductIsolation(root).failures).toEqual([])
if (kind === 'dependencies') {
manifest(root, 'packages/bundle/optional-layer/package.json', { dependencies: { [experimental]: 'workspace:*' } })
} else if (kind === 'source') {
write(root, 'packages/bundle/optional-layer/src/index.ts', `import '${experimental}'`)
} else {
write(root, 'packages/bundle/optional-layer/cordis.patch.yml', [{ preset: 'preset-standard', insert: [{ name: experimental }] }])
}
expect(verifyDefaultProductIsolation(root).failures.join('\n')).toContain('must not include experimental packages')
},
)
it('requires each optional bundle to be a runtime dependency that declares a bundle patch, an icon, and locale metadata', () => {
const root = fixture()
write(root, profile, `export const PROFILE_TEMPLATES = { web: { bundles: ['${base}'] } }\n`
+2 -1
View File
@@ -293,7 +293,8 @@ export function verifyDefaultProductIsolation(root: string): ProductIsolationRes
const { manifest } = pkg
for (const section of RUNTIME_SECTIONS) {
for (const [name, range] of Object.entries(manifest[section] ?? {})) {
if (pkg === cli && section === 'dependencies' && optionalBundles.has(name)) continue
const target = packages.get(name)
if (pkg === cli && section === 'dependencies' && optionalBundles.has(name) && target !== undefined && isExperimental(target)) continue
dependency(name, range, pkg, `${manifest.name} ${section}`)
}
}
@@ -175,6 +175,13 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/host/plugin-inventory': { kind: 'none', reason: 'Host-side read-only Loader projection; registers nothing model-facing.' },
'packages/host/open-in-app': { kind: 'none', reason: 'Host routes opening desktop applications for a human; registers nothing model-facing.' },
'packages/client/ui-open-in-app': { kind: 'none', reason: 'Browser-side split button opening the workspace directory for a human; registers nothing model-facing.' },
'packages/bundle/session-search': { kind: 'indirect', reason: 'The profile layer delegates model-visible behavior to its selected capability.' },
'packages/bundle/string-editor': { kind: 'indirect', reason: 'The profile layer delegates model-visible behavior to its selected capability.' },
'packages/bundle/tmux-location': { kind: 'indirect', reason: 'The profile layer delegates model-visible behavior to its selected capability.' },
'packages/experimental/ralph-bundle': { kind: 'indirect', reason: 'The profile layer delegates model-visible behavior to its selected capability.' },
'packages/experimental/terminal-bundle': { kind: 'indirect', reason: 'The profile layer delegates model-visible behavior to its selected capability.' },
'packages/experimental/badge-skill-bundle': { kind: 'indirect', reason: 'The profile layer delegates model-visible behavior to its selected capability.' },
'packages/experimental/session-titles-bundle': { kind: 'indirect', reason: 'The profile layer delegates model-visible behavior to its selected capability.' },
'packages/bundle/base': { kind: 'indirect', reason: 'The bundle is a patch-list carrier; each inserted row\'s package owns its model-facing behavior.' },
'packages/bundle/headless': { kind: 'none', reason: 'The one-shot runner submits the task as an ordinary user message; prompts and tools belong to the composed base and headless bundles.' },
'packages/llm/llm': { kind: 'none', reason: 'The adapter registry forwards already-assembled requests unchanged.' },