/** * Authoring a preset copies an existing one's directory into the deployment's * `user` root — copy is the only authoring write, so no caller ever supplies * composition text. The id is a directory name, so its pattern is a * containment boundary rather than a style rule; the shipped `.system` set * stays read-only. */ import { chmod, mkdtemp, mkdir, readFile, stat, writeFile } from 'node:fs/promises' import { existsSync } from 'node:fs' 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 { beforeEach, describe, expect, it } from 'vitest' import AgentPresets, { COMPOSITION_FILE, copyComposition, METADATA_FILE, } from '@deepseek-ai/dsh-agent-presets' const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), 'fixtures') const VALID = '- id: tool-alpha\n name: ../../plugins/contribute.js\n config:\n tool: alpha\n' let ctx: Context let userRoot: string /** Hand-craft a preset directory (tests cannot author text through the service). */ async function seedPreset( root: string, id: string, options: { composition?: string; metadata?: string; extras?: Record } = {}, ): Promise { await mkdir(join(root, id), { recursive: true }) await writeFile(join(root, id, COMPOSITION_FILE), options.composition ?? VALID) if (options.metadata !== undefined) { await writeFile(join(root, id, METADATA_FILE), options.metadata) } for (const [name, content] of Object.entries(options.extras ?? {})) { await mkdir(dirname(join(root, id, name)), { recursive: true }) await writeFile(join(root, id, name), content) } } beforeEach(async () => { userRoot = await mkdtemp(join(tmpdir(), 'dsh-preset-authoring-')) ctx = new Context() ctx.baseUrl = pathToFileURL(FIXTURES).href + '/' await ctx.plugin(Loader) ctx.loader.builtins.include = Include await ctx.plugin(AgentPresets, { default: 'standard', roots: [ { path: join(FIXTURES, 'system'), trust: 'system' as const }, { path: userRoot, trust: 'user' as const }, ], // Every roster in this file pins its own roots: the derived harness-home // root would add the developer's real presets to what these assertions // count, and `copy` would write into it. includeUserRoot: false, }) }) describe('copying a preset', () => { it('copies a shipped preset into the user root and lists it', async () => { await ctx.agentPresets.copy('standard', 'mine') expect(await readFile(join(userRoot, 'mine', COMPOSITION_FILE), 'utf8')) .toBe(await ctx.agentPresets.read('standard')) const listed = await ctx.agentPresets.list() expect(listed.find(preset => preset.id === 'mine')?.trust).toBe('user') }) it('copies the whole directory and tightens POSIX modes', async () => { await seedPreset(userRoot, 'source', { extras: { 'skills/demo/SKILL.md': '# demo\n', 'skills/demo/run.sh': '#!/bin/sh\n' }, }) if (process.platform !== 'win32') { await chmod(join(userRoot, 'source', 'skills', 'demo', 'run.sh'), 0o755) } await ctx.agentPresets.copy('source', 'mine') expect(await readFile(join(userRoot, 'mine', 'skills', 'demo', 'SKILL.md'), 'utf8')).toBe('# demo\n') // Windows mode bits are synthetic and cannot represent the inherited DACL. if (process.platform !== 'win32') { expect((await stat(join(userRoot, 'mine', 'skills', 'demo', 'run.sh'))).mode & 0o777).toBe(0o700) expect((await stat(join(userRoot, 'mine', 'skills', 'demo', 'SKILL.md'))).mode & 0o777).toBe(0o600) expect((await stat(join(userRoot, 'mine'))).mode & 0o777).toBe(0o700) } }) it('keeps the source description but never its name or order', async () => { await seedPreset(userRoot, 'source', { metadata: 'name: 源模式\ndescription: 只做检索。\norder: 1\n' }) await ctx.agentPresets.copy('source', 'mine') // Two rows presenting identically is how a roster stops being a chooser, // and the shipped set's declared order is not the copy's to claim. const metadata = await readFile(join(userRoot, 'mine', METADATA_FILE), 'utf8') expect(metadata).toContain('description: 只做检索。') expect(metadata).not.toContain('name:') expect(metadata).not.toContain('order:') expect((await ctx.agentPresets.list()).find(preset => preset.id === 'mine')) .toMatchObject({ description: '只做检索。' }) }) it('stores the display name the author supplied', async () => { await ctx.agentPresets.copy('standard', 'mine', '我的模式') expect(await readFile(join(userRoot, 'mine', METADATA_FILE), 'utf8')).toContain('name: 我的模式') expect((await ctx.agentPresets.list()).find(preset => preset.id === 'mine')) .toMatchObject({ name: '我的模式' }) }) it('publishes no metadata file when there is nothing to publish', async () => { await seedPreset(userRoot, 'source') await ctx.agentPresets.copy('source', 'mine') // An empty metadata document would read as an intentional blank name; // absence is what "this preset publishes no display text" looks like. expect(existsSync(join(userRoot, 'mine', METADATA_FILE))).toBe(false) expect((await ctx.agentPresets.list()).find(preset => preset.id === 'mine')?.name).toBeUndefined() }) it('refuses an id that could escape the preset root', async () => { for (const id of ['../escape', 'a/b', '/abs', '..', 'Upper']) { await expect(ctx.agentPresets.copy('standard', id)).rejects.toThrow(/must match/) } // Nothing was created for any of them. expect(existsSync(join(userRoot, 'escape'))).toBe(false) }) it('refuses an id the roster already supplies, shipped ones included', async () => { await ctx.agentPresets.copy('standard', 'mine') await expect(ctx.agentPresets.copy('standard', 'mine')).rejects.toThrow(/already exists/) // A user directory named like a shipped preset would be shadowed by it. await expect(ctx.agentPresets.copy('standard', 'minimal')).rejects.toThrow(/already exists/) }) it('refuses a directory that occupies the name without being a preset', async () => { await mkdir(join(userRoot, 'occupied'), { recursive: true }) await writeFile(join(userRoot, 'occupied', 'README.txt'), 'nope\n') // Discovery does not list it (no composition file), so only the disk // check can refuse it with a readable error instead of a filesystem code. await expect(ctx.agentPresets.copy('standard', 'occupied')).rejects.toThrow(/already exists/) expect(await readFile(join(userRoot, 'occupied', 'README.txt'), 'utf8')).toBe('nope\n') }) it('reports an unknown source rather than creating anything', async () => { await expect(ctx.agentPresets.copy('never-existed', 'mine')).rejects.toThrow(/not found/) expect(existsSync(join(userRoot, 'mine'))).toBe(false) }) it('leaves nothing behind when the copy itself fails', async () => { const source = { id: 'gone', trust: 'user' as const, path: join(userRoot, 'gone', COMPOSITION_FILE), } // The source vanished between resolve and copy: the half-made target is // rolled back rather than left invisible to discovery. await expect(copyComposition( [{ path: userRoot, trust: 'user' as const }], source, 'mine', )).rejects.toThrow() expect(existsSync(join(userRoot, 'mine'))).toBe(false) }) }) describe('deleting a preset', () => { it('removes a locally authored one', async () => { await ctx.agentPresets.copy('standard', 'mine') await ctx.agentPresets.remove('mine') expect(existsSync(join(userRoot, 'mine'))).toBe(false) expect((await ctx.agentPresets.list()).some(preset => preset.id === 'mine')).toBe(false) }) it('refuses to delete a shipped one', async () => { await expect(ctx.agentPresets.remove('standard')) .rejects.toThrow(/ships with the deployment/) }) it('reports an unknown id rather than silently succeeding', async () => { await expect(ctx.agentPresets.remove('never-existed')).rejects.toThrow(/not found/) }) }) describe('a deployment with more than one user root', () => { it('refuses to delete a preset the writable root does not own', async () => { const second = await mkdtemp(join(tmpdir(), 'dsh-preset-second-')) await seedPreset(second, 'elsewhere') const layered = new Context() layered.baseUrl = pathToFileURL(FIXTURES).href + '/' await layered.plugin(Loader) layered.loader.builtins.include = Include await layered.plugin(AgentPresets, { default: 'standard', roots: [ { path: userRoot, trust: 'user' as const }, { path: second, trust: 'user' as const }, ], includeUserRoot: false, }) // Writes go to the first user root, so a preset discovered from a later // one is `user` trust yet outside what deletion is allowed to touch — // `rm -r` on a directory this root does not own is the failure to avoid. await expect(layered.agentPresets.remove('elsewhere')) .rejects.toThrow(/does not live under the writable preset root/) expect(existsSync(join(second, 'elsewhere'))).toBe(true) }) }) describe('a deployment with no writable root', () => { it('says authoring is unavailable rather than guessing a directory', async () => { const readOnly = new Context() readOnly.baseUrl = pathToFileURL(FIXTURES).href + '/' await readOnly.plugin(Loader) readOnly.loader.builtins.include = Include await readOnly.plugin(AgentPresets, { default: 'standard', roots: [{ path: join(FIXTURES, 'system'), trust: 'system' as const }], includeUserRoot: false, }) expect(readOnly.agentPresets.authorable).toBe(false) await expect(readOnly.agentPresets.copy('standard', 'mine')) .rejects.toThrow(/no user-writable preset root/) }) }) describe('a user root that does not exist yet', () => { it('is created by the first copy', async () => { const absent = join(await mkdtemp(join(tmpdir(), 'dsh-preset-absent-')), 'nested', 'preset') const fresh = new Context() fresh.baseUrl = pathToFileURL(FIXTURES).href + '/' await fresh.plugin(Loader) fresh.loader.builtins.include = Include await fresh.plugin(AgentPresets, { default: 'standard', roots: [ { path: join(FIXTURES, 'system'), trust: 'system' as const }, { path: absent, trust: 'user' as const }, ], includeUserRoot: false, }) await fresh.agentPresets.copy('standard', 'mine') expect(await readFile(join(absent, 'mine', COMPOSITION_FILE), 'utf8')) .toBe(await fresh.agentPresets.read('standard')) }) }) describe('display metadata beside a composition', () => { it('keeps a composition mountable when its metadata is unreadable', async () => { await ctx.agentPresets.copy('standard', 'mine') await writeFile(join(userRoot, 'mine', METADATA_FILE), 'name: [unclosed\n') // Presentation is not capability: discovery still yields the preset. const listed = (await ctx.agentPresets.list()).find(preset => preset.id === 'mine') expect(listed?.name).toBeUndefined() expect(await ctx.agentPresets.resolve('mine')).toMatchObject({ id: 'mine' }) }) }) describe('the on-disk occupancy backstop', () => { it('refuses a directory the roster cannot see', async () => { // The service's roster check sees every id-shaped directory now, so this // is the race backstop: a directory appearing between the roster read and // the copy still gets the readable refusal, not a filesystem error code. await mkdir(join(userRoot, 'raced'), { recursive: true }) const source = await ctx.agentPresets.resolve('standard') await expect(copyComposition( [{ path: userRoot, trust: 'user' as const }], source, 'raced', )).rejects.toThrow(/already exists/) }) }) describe('a ghost directory under the user root', () => { it('lists broken, blocks its id, and clears through remove', async () => { // The classic hand-edit: the composition file was deleted, the directory // stayed. It must not vanish from the roster — its id is still taken, so // there has to be something to see and delete. await mkdir(join(userRoot, 'ghost'), { recursive: true }) await writeFile(join(userRoot, 'ghost', 'README.txt'), 'composition deleted by hand\n') const ghost = (await ctx.agentPresets.list()).find(preset => preset.id === 'ghost') expect(ghost?.broken).toMatch(/agent\.cordis\.yml is missing/) await expect(ctx.agentPresets.copy('standard', 'ghost')).rejects.toThrow(/already exists/) // remove is the way out the roster row offers; the id is claimable again. await ctx.agentPresets.remove('ghost') expect(existsSync(join(userRoot, 'ghost'))).toBe(false) await ctx.agentPresets.copy('standard', 'ghost') expect((await ctx.agentPresets.list()).find(preset => preset.id === 'ghost')?.broken).toBeUndefined() }) })