feat(plugin): compose optional bundles inside agent presets
This commit is contained in:
1 parent
4da62dc8f4
commit
3aa1cab43a
109 files changed
+3442
-130
No files matched your search
@@ -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`
|
||||
|
||||
@@ -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.' },
|
||||
|
||||
Reference in new issue
Block a user