Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
164 lines
6.5 KiB
TypeScript
164 lines
6.5 KiB
TypeScript
/**
|
|
* The default preset is a user setting. `config.default` is the deployment's
|
|
* engineering default; the settings document overrides it and is hot-reloaded,
|
|
* so a person can change which preset new sessions get without a restart.
|
|
*/
|
|
|
|
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { dirname, join } from 'node:path'
|
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
import { Context } from '@deepseek-ai/cordis'
|
|
import Loader from '@deepseek-ai/cordis-plugin-loader'
|
|
import Include from '@deepseek-ai/cordis-plugin-include'
|
|
import LlmService from '@deepseek-ai/dsh-llm'
|
|
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
|
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
|
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
|
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
|
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
|
import SettingsLocal from '@deepseek-ai/dsh-settings-local'
|
|
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
import { describe, expect, it } from 'vitest'
|
|
import AgentPresets, { COMPOSITION_FILE, SETTINGS_NAMESPACE } from '@deepseek-ai/dsh-agent-presets'
|
|
|
|
const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), 'fixtures')
|
|
const ROOTS = [{ path: join(FIXTURES, 'system'), trust: 'system' as const }]
|
|
const NS = settingsNamespace(SETTINGS_NAMESPACE)
|
|
|
|
/**
|
|
* A composition with a real file-backed settings provider. `settingsFiber` is
|
|
* the provider's own handle, so a test can take it away the way a reload does.
|
|
*/
|
|
async function harness(
|
|
extraRoots: readonly { path: string; trust: 'system' | 'user' }[] = [],
|
|
): Promise<{ ctx: Context; settingsFile: string; settingsFiber: { dispose: () => unknown } }> {
|
|
const home = await mkdtemp(join(tmpdir(), 'dsh-preset-settings-'))
|
|
const settingsFile = join(home, 'settings.yaml')
|
|
await writeFile(settingsFile, '{}\n')
|
|
|
|
const ctx = new Context()
|
|
ctx.baseUrl = pathToFileURL(FIXTURES).href + '/'
|
|
await ctx.plugin(Loader)
|
|
ctx.loader.builtins.include = Include
|
|
await ctx.plugin(LlmService)
|
|
await ctx.plugin(SessionStore)
|
|
await ctx.plugin(SystemPrompt, { persona: '' })
|
|
await ctx.plugin(ToolRegistry)
|
|
await ctx.plugin(AgentRegistry)
|
|
await ctx.plugin(AgentLoop, { agents: [] })
|
|
const settingsFiber = ctx.plugin(SettingsLocal, { path: settingsFile, watch: false })
|
|
await settingsFiber
|
|
await ctx.plugin(AgentPresets, { default: 'standard', roots: [...ROOTS, ...extraRoots] })
|
|
return { ctx, settingsFile, settingsFiber }
|
|
}
|
|
|
|
const toolNames = (ctx: Context, agent?: unknown): string[] =>
|
|
ctx.tools.schemas(agent as never).map(schema => schema.name).sort()
|
|
|
|
describe('the default preset as a user setting', () => {
|
|
it('falls back to the composition default while the user set none', async () => {
|
|
const { ctx } = await harness()
|
|
|
|
expect(ctx.agentPresets.defaultId).toBe('standard')
|
|
})
|
|
|
|
it('takes the user default over the composition default', async () => {
|
|
const { ctx } = await harness()
|
|
|
|
await ctx.settings.update(NS, { default: 'minimal' })
|
|
|
|
expect(ctx.agentPresets.defaultId).toBe('minimal')
|
|
})
|
|
|
|
it('composes a new session from the user default', async () => {
|
|
const { ctx } = await harness()
|
|
await ctx.settings.update(NS, { default: 'minimal' })
|
|
|
|
const handle = await ctx.agents.create({
|
|
sessionId: SessionId('settings-default'),
|
|
setup: async (agentCtx: Context) => void await ctx.agentPresets.mount(agentCtx),
|
|
})
|
|
try {
|
|
expect(toolNames(ctx, handle.agent)).toEqual(['beta'])
|
|
} finally {
|
|
await handle.dispose()
|
|
}
|
|
})
|
|
|
|
it('leaves a running session on the preset it was composed from', async () => {
|
|
const { ctx } = await harness()
|
|
const running = await ctx.agents.create({
|
|
sessionId: SessionId('settings-running'),
|
|
setup: async (agentCtx: Context) => void await ctx.agentPresets.mount(agentCtx),
|
|
})
|
|
try {
|
|
expect(toolNames(ctx, running.agent)).toEqual(['alpha'])
|
|
|
|
// Changing the default mid-flight must not reach an agent that already
|
|
// composed: its history was produced under `standard`'s tools.
|
|
await ctx.settings.update(NS, { default: 'minimal' })
|
|
|
|
expect(ctx.agentPresets.defaultId).toBe('minimal')
|
|
expect(toolNames(ctx, running.agent)).toEqual(['alpha'])
|
|
} finally {
|
|
await running.dispose()
|
|
}
|
|
})
|
|
|
|
it('re-inherits the composition default when the user setting is cleared', async () => {
|
|
const { ctx } = await harness()
|
|
await ctx.settings.update(NS, { default: 'minimal' })
|
|
expect(ctx.agentPresets.defaultId).toBe('minimal')
|
|
|
|
await ctx.settings.replace(NS, {})
|
|
|
|
expect(ctx.agentPresets.defaultId).toBe('standard')
|
|
})
|
|
|
|
it('clears a user default it has just deleted', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'dsh-preset-authored-'))
|
|
await mkdir(join(root, 'mine'))
|
|
await writeFile(
|
|
join(root, 'mine', COMPOSITION_FILE),
|
|
`- id: only\n name: ${join(FIXTURES, 'plugins', 'contribute.js')}\n config:\n tool: only\n`,
|
|
)
|
|
const { ctx } = await harness([{ path: root, trust: 'user' as const }])
|
|
await ctx.settings.update(NS, { default: 'mine' })
|
|
expect(ctx.agentPresets.defaultId).toBe('mine')
|
|
|
|
await ctx.agentPresets.remove('mine')
|
|
|
|
// Nothing will ever supply that id again, so leaving the setting pointed at
|
|
// it would fail every session created without an explicit pick. Clearing it
|
|
// exposes the deployment's own default underneath.
|
|
expect(ctx.agentPresets.defaultId).toBe('standard')
|
|
expect((await ctx.agentPresets.resolve()).id).toBe('standard')
|
|
})
|
|
|
|
it('reports an unknown user default only when a session tries to use it', async () => {
|
|
const { ctx } = await harness()
|
|
|
|
// Storing it succeeds — the roster is a live directory, so a name that is
|
|
// absent now may exist by the time a session asks for it.
|
|
await ctx.settings.update(NS, { default: 'no-such-preset' })
|
|
|
|
await expect(ctx.agentPresets.resolve())
|
|
.rejects.toThrow(/preset "no-such-preset" not found/)
|
|
})
|
|
})
|
|
|
|
describe('a settings provider that goes away', () => {
|
|
it('falls back to the composition default when the provider unloads', async () => {
|
|
const { ctx, settingsFiber } = await harness()
|
|
await ctx.settings.update(NS, { default: 'minimal' })
|
|
expect(ctx.agentPresets.defaultId).toBe('minimal')
|
|
|
|
// Unloading the provider takes the user layer with it; the roster keeps
|
|
// working on its composition default rather than holding a stale override.
|
|
await settingsFiber.dispose()
|
|
|
|
expect(ctx.agentPresets.defaultId).toBe('standard')
|
|
})
|
|
})
|