fix(agent-presets): repair Windows preset CI
This commit is contained in:
@@ -65,20 +65,23 @@ describe('copying a preset', () => {
|
||||
expect(listed.find(preset => preset.id === 'mine')?.trust).toBe('user')
|
||||
})
|
||||
|
||||
it('copies the whole directory, execute bits kept and group/other stripped', async () => {
|
||||
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' },
|
||||
})
|
||||
await chmod(join(userRoot, 'source', 'skills', 'demo', 'run.sh'), 0o755)
|
||||
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')
|
||||
// A preset may ship runnable helpers; the copy keeps them runnable for the
|
||||
// owner while withdrawing the world-readability of the install.
|
||||
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)
|
||||
// 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 () => {
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
import { chmod, mkdtemp, mkdir, writeFile } from 'node:fs/promises'
|
||||
import { mkdtemp, mkdir, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { COMPOSITION_FILE, discoverPresets, scanRoot } from '@deepseek-ai/dsh-agent-presets'
|
||||
|
||||
const fsHarness = vi.hoisted(() => ({
|
||||
nextReadError: undefined as NodeJS.ErrnoException | undefined,
|
||||
}))
|
||||
|
||||
vi.mock('node:fs/promises', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('node:fs/promises')>()
|
||||
return {
|
||||
...actual,
|
||||
readFile: (async (path: unknown, ...rest: never[]) => {
|
||||
const error = fsHarness.nextReadError
|
||||
if (error !== undefined) {
|
||||
fsHarness.nextReadError = undefined
|
||||
throw error
|
||||
}
|
||||
return (actual.readFile as (path: unknown, ...args: never[]) => Promise<unknown>)(path, ...rest)
|
||||
}) as typeof actual.readFile,
|
||||
}
|
||||
})
|
||||
|
||||
const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), 'fixtures')
|
||||
const SYSTEM = { path: join(FIXTURES, 'system'), trust: 'system' as const }
|
||||
const USER = { path: join(FIXTURES, 'user'), trust: 'user' as const }
|
||||
|
||||
beforeEach(() => {
|
||||
fsHarness.nextReadError = undefined
|
||||
})
|
||||
|
||||
describe('display order', () => {
|
||||
it('puts declared order first, then everything else by id', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-order-'))
|
||||
@@ -177,10 +200,11 @@ describe('composition health', () => {
|
||||
await mkdir(join(root, 'sealed'))
|
||||
const path = join(root, 'sealed', COMPOSITION_FILE)
|
||||
await writeFile(path, '[]\n')
|
||||
await chmod(path, 0o000)
|
||||
fsHarness.nextReadError = Object.assign(new Error('EACCES: injected read failure'), { code: 'EACCES' })
|
||||
|
||||
const [preset] = await scanRoot({ path: root, trust: 'user' })
|
||||
|
||||
expect(fsHarness.nextReadError).toBeUndefined()
|
||||
expect(preset?.broken).toMatch(/cannot be read/)
|
||||
})
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { assembleContextFor, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import AgentPresets, {
|
||||
COMPOSITION_FILE, leakedServices, livePresetMounts, mountPreset, PresetMountError, serviceForAgent,
|
||||
} from '@deepseek-ai/dsh-agent-presets'
|
||||
@@ -84,6 +84,23 @@ beforeEach(async () => {
|
||||
})
|
||||
|
||||
describe('composing an agent from a preset', () => {
|
||||
it('hands an absolute plugin path to Node as a file URL', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-preset-absolute-plugin-'))
|
||||
const presetDir = join(root, 'absolute')
|
||||
const plugin = join(FIXTURES, 'plugins', 'contribute.js')
|
||||
await mkdir(presetDir)
|
||||
await writeFile(
|
||||
join(presetDir, COMPOSITION_FILE),
|
||||
`- id: only\n name: ${plugin}\n config:\n tool: absolute\n`,
|
||||
)
|
||||
const scoped = await harness({ default: 'absolute', roots: [{ path: root, trust: 'user' }] })
|
||||
const imported = vi.spyOn(scoped.loader.internal!, 'import')
|
||||
|
||||
await agentOn(scoped, 'sess-absolute-plugin')
|
||||
|
||||
expect(imported).toHaveBeenCalledWith(pathToFileURL(plugin).href, expect.any(String), {})
|
||||
})
|
||||
|
||||
it('gives each session only its own preset\'s tools', async () => {
|
||||
const alpha = await agentOn(ctx, 'sess-alpha', 'standard')
|
||||
const beta = await agentOn(ctx, 'sess-beta', 'minimal')
|
||||
|
||||
Reference in New Issue
Block a user