feat(agent-presets): own the writable preset root instead of awaiting an app

`Config.roots` defaulted to `[]`, so a deployment that did not patch both roots
in got a roster with none — `dsh run` once booted exactly that and failed
resolving `standard`. The user root never needed an app: `<dshHome>/.agent-presets`
is the same place in every deployment, resolvable here the way
`dsh-skill-local` resolves `<dshHome>/skills`.

The roster now derives that root itself unless `includeUserRoot` is false, and
`apps/cli` supplies only the SHIPPED root, whose path an installed app alone
can resolve. The derived root is appended after every configured root, so a
shipped id still shadows a home directory claiming it and `writableRoot()`
still prefers an explicitly configured `user` root; the set is resolved once at
construction, because a root set that changed between a `list()` and the
`copy()` acting on its answer would author into a directory the caller never
saw.

Every test that pins an exact roster now says `includeUserRoot: false` — the
machine's real harness home must not decide what a golden or an assertion
counts.
This commit is contained in:
Yichen Jiang
2026-08-11 20:20:16 +08:00
parent ac266ed2de
commit 5c7dd6f8eb
21 changed files with 253 additions and 38 deletions

View File

@@ -385,7 +385,11 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
// able to change a golden.
{
id: 'agent-presets',
config: { default: 'standard', roots: [{ path: SHIPPED_PRESET_DIR, trust: 'system' }] },
config: {
default: 'standard',
roots: [{ path: SHIPPED_PRESET_DIR, trust: 'system' }],
includeUserRoot: false,
},
},
{ id: 'session-persistence-jsonl', config: { root: persistenceRoot } },
{ id: 'session-query-sqlite', config: { path: ':memory:', openAt: 'first-search' } },
@@ -442,7 +446,9 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
{ insert: [{ id: 'directory-picker-browse', name: '@deepseek-ai/dsh-host-directory-picker-browse' }] },
...options.agentPresets === undefined
? []
: [{ id: 'agent-presets', config: options.agentPresets }],
// Never the derived harness-home root: a developer's own presets must not
// be able to change a golden, whatever roots a scenario asks for.
: [{ id: 'agent-presets', config: { ...options.agentPresets, includeUserRoot: false } }],
...options.toolsMode === undefined ? [] : [{ id: 'tools', config: { mode: options.toolsMode } }],
...options.cordisTools === true
? [{ insert: [{ id: 'tool-cordis', name: 'cordis:tool-cordis' }] }]