mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
refactor(agent-presets,web): copy-only preset authoring with a path to the files
The web YAML editor is gone. agentPreset.write (arbitrary composition
text) became agentPreset.copy { from, agentPreset, name? }: a host-side
whole-directory copy of ids the host resolves itself — symlinks
dereferenced, modes re-tightened to owner-only with owner-execute kept,
metadata rewritten to keep the source's description but never its name or
roster order. No composition text or path crosses the wire in either
authoring direction, and the entryListSchema/!!js concern dissolves with
assertComposition itself.
The settings section becomes: a read-only viewer over shipped
compositions, a copy dialog (id + optional display name) as the only
create entry, delete for custom rows, and a location action leading into
the preset's own files — agentPreset.openDocument { agentPreset } resolves
the directory host-side and opens it natively, or answers
{ opened: false, path } for the row to show as text where the deployment
has no desktop. agentPreset.list reports hasDocument beside authorable;
the gateway's nativeOpen config pins the capability where
canOpenNativePath platform detection would mislead. The privileged set is
now read/copy/openDocument/remove.
With files as the only composition editor, standing mounts grew
stamp-keyed generations: ensureStanding compares the composition file's
mtime+size and starts the next generation for later sessions, while every
joined session keeps the generation it runs on.
New keyless web lane (agent-preset-authoring, overlay pins
nativeOpen: false so goldens render one branch on every platform) drives
view/copy/reveal/delete end to end; the real-composition CLI e2e switches
to copy semantics.
This commit is contained in:
@@ -376,32 +376,28 @@ describe('authoring a preset on the shipped composition', () => {
|
||||
}])
|
||||
})
|
||||
|
||||
it('refuses to overwrite or delete a shipped preset', async () => {
|
||||
await expect(authorCtx.agentPresets.write('standard', '- id: x\n')).rejects.toThrow(/ships with the deployment/)
|
||||
it('refuses to copy over or delete a shipped preset', async () => {
|
||||
await expect(authorCtx.agentPresets.copy('minimal', 'standard')).rejects.toThrow(/already exists/)
|
||||
await expect(authorCtx.agentPresets.remove('standard')).rejects.toThrow(/ships with the deployment/)
|
||||
})
|
||||
|
||||
it.each(['../escape', 'a/b', '/abs', 'Upper'])('refuses the uncontainable id %j', async (id) => {
|
||||
// The id becomes a directory name under the user root, so containment is
|
||||
// checked on the id rather than on the joined path afterwards.
|
||||
await expect(authorCtx.agentPresets.write(id, '- id: x\n')).rejects.toThrow()
|
||||
await expect(authorCtx.agentPresets.copy('minimal', id)).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('refuses text that is not a Cordis entry list', async () => {
|
||||
await expect(authorCtx.agentPresets.write('bad-shape', 'tools: []\n')).rejects.toThrow()
|
||||
await expect(authorCtx.agentPresets.resolve('bad-shape')).rejects.toThrow()
|
||||
})
|
||||
it('copies a shipped preset a session then really composes from', async () => {
|
||||
await authorCtx.agentPresets.copy('minimal', 'my-agent', '我的模式')
|
||||
|
||||
it('writes a preset a session then really composes from', async () => {
|
||||
const copied = await authorCtx.agentPresets.read('minimal')
|
||||
|
||||
await authorCtx.agentPresets.write('my-agent', copied)
|
||||
|
||||
// Round-trips through the roster as a `user` row, and the composition the
|
||||
// editor saved is one the mount actually accepts.
|
||||
// Round-trips through the roster as a `user` row carrying the given name
|
||||
// and the source's description, over the source's own composition text.
|
||||
const preset = await authorCtx.agentPresets.resolve('my-agent')
|
||||
const source = await authorCtx.agentPresets.resolve('minimal')
|
||||
expect(preset.trust).toBe('user')
|
||||
expect(await authorCtx.agentPresets.read('my-agent')).toBe(copied)
|
||||
expect(preset.name).toBe('我的模式')
|
||||
expect(preset.description).toBe(source.description)
|
||||
expect(await authorCtx.agentPresets.read('my-agent')).toBe(await authorCtx.agentPresets.read('minimal'))
|
||||
// Owner-only, in an owner-only directory: a composition is executable
|
||||
// configuration on a machine that may have other users.
|
||||
expect((await stat(preset.path)).mode & 0o777).toBe(0o600)
|
||||
@@ -410,7 +406,7 @@ describe('authoring a preset on the shipped composition', () => {
|
||||
setup: agentCtx => authorCtx.agentPresets.mount(agentCtx, 'my-agent').then(() => undefined),
|
||||
})
|
||||
try {
|
||||
// The same tools the shipped `minimal` composes, from a file written
|
||||
// The same tools the shipped `minimal` composes, from a directory copied
|
||||
// through the service into a root outside the installed harness.
|
||||
expect(toolNames(authorCtx, handle.agent)).toEqual(['bash', 'str_replace_editor'])
|
||||
} finally {
|
||||
@@ -418,8 +414,8 @@ describe('authoring a preset on the shipped composition', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('deletes what it wrote', async () => {
|
||||
await authorCtx.agentPresets.write('doomed', '- id: tool-web-search\n name: \'@deepseek-ai/dsh-tool-web-search\'\n')
|
||||
it('deletes what it copied', async () => {
|
||||
await authorCtx.agentPresets.copy('minimal', 'doomed')
|
||||
|
||||
await authorCtx.agentPresets.remove('doomed')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user