From 7dad06b7710603620cf04e45e107acb79613bd01 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Sat, 8 Aug 2026 23:20:55 +0800 Subject: [PATCH] fix(cli): patch the agent-preset roots for every dsh launcher The roots are an assembly fact (the shipped set beside this app's config, the user's own under $DSH_HOME) but only `dsh web` patched them in, so the merged `dsh run` booted the roster with no roots and failed resolving `standard`. The shared profile boot now owns the patch for every launcher; the one-shot transcript header consequently records its composing preset, and master's interrupt_agent tool joins the standard composition's exact catalog. --- apps/cli/src/profile-boot.ts | 25 +++++++++++++++++++ apps/cli/src/web.ts | 20 +++------------ apps/cli/tests/web-agent-presets.e2e.ts | 2 +- .../snapshots/dsh-run/session.expected.jsonl | 2 +- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/apps/cli/src/profile-boot.ts b/apps/cli/src/profile-boot.ts index 4730ec7073..c62c2f140d 100644 --- a/apps/cli/src/profile-boot.ts +++ b/apps/cli/src/profile-boot.ts @@ -12,6 +12,7 @@ import { join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import type { Context } from 'cordis' import type { PatchOptions } from '@cordisjs/plugin-include' +import { dshHomePath } from '@deepseek-ai/dsh-paths' import { boot, composeEntries, @@ -25,6 +26,12 @@ import { type Profile, } from '@deepseek-ai/dsh-app-boot' import { resolveDshHome } from '@deepseek-ai/dsh-paths' + +/** Shipped agent-preset root: beside this app's own config, in both source and built layouts. */ +const SHIPPED_PRESET_ROOT = fileURLToPath(new URL('../config/agent-presets/', import.meta.url)) + +/** Harness-home directory holding locally authored agent presets. */ +const USER_PRESET_DIR = '.agent-presets' import { DSH_ENVIRONMENT_KEY, type EnvironmentSnapshot } from '@deepseek-ai/dsh-environment' import type { HeadlessIo } from '@deepseek-ai/dsh-headless' import { createProcessShutdown, type ProcessShutdown } from './process-shutdown.ts' @@ -147,6 +154,24 @@ function composeProfile( if (typeof row.id === 'string') rows.set(row.id, row) } const overlayAndFlags = [...overlays, ...deriveFlagPatches(rows)] + // The agent-preset roots are an assembly fact of every dsh launcher, not a + // patch author's choice: the shipped set sits beside this app's config and + // the user's own under the Harness home. Resolved per boot ($DSH_HOME may + // differ per run) and only patched when the composed tree actually mounts + // the roster — a one-shot `dsh run` composes agents from the same roster + // `dsh web` offers. + if (rows.has('agent-presets')) { + overlayAndFlags.push({ + id: 'agent-presets', + config: { + ...(rows.get('agent-presets')?.config ?? {}) as Record, + roots: [ + { path: SHIPPED_PRESET_ROOT, trust: 'system' }, + { path: dshHomePath(USER_PRESET_DIR), trust: 'user' }, + ], + }, + }) + } const telemetryPatch = resolveTelemetryPatch(process.env.DSH_TELEMETRY_DISABLED, rows.has(TELEMETRY_ROW_ID)) if (telemetryPatch !== undefined) overlayAndFlags.push(telemetryPatch) return { profile, bundlePatches, homePatches, overlayAndFlags, rows } diff --git a/apps/cli/src/web.ts b/apps/cli/src/web.ts index 2cf9b8229a..bdf301e2ae 100644 --- a/apps/cli/src/web.ts +++ b/apps/cli/src/web.ts @@ -10,7 +10,6 @@ import { networkInterfaces } from 'node:os' import { fileURLToPath } from 'node:url' -import { dshHomePath } from '@deepseek-ai/dsh-paths' import type { Context } from 'cordis' import type { PatchOptions } from '@cordisjs/plugin-include' import { addHarnessSourceSection } from '@deepseek-ai/dsh-app-boot' @@ -19,12 +18,6 @@ import { runProfile, type ProfileRows } from './profile-boot.ts' const SOURCE_ROOT = fileURLToPath(new URL('../../..', import.meta.url)) -/** Shipped agent-preset root: beside this app's own config, in both source and built layouts. */ -const SHIPPED_PRESET_ROOT = fileURLToPath(new URL('../config/agent-presets/', import.meta.url)) - -/** Harness-home directory holding locally authored agent presets. */ -const USER_PRESET_DIR = '.agent-presets' - /** The webserver schema's all-interfaces bind literal: gates LAN-authority derivation. */ const ALL_INTERFACES_HOST = '0.0.0.0' @@ -104,16 +97,9 @@ function deriveWebFlagPatches( // inserts the client-hmr row), never pass-throughs of composed values. put('web-runtime', 'mode', flags.dev ? 'development' : 'production') put('web-runtime', 'lanAddresses', lanAddresses) - // The agent-preset roots are an assembly fact, like the values above: the - // shipped set sits beside this app's config and the user's own under the - // Harness home, and neither location is something a patch author chooses. - // Only patched when the composed tree actually mounts the roster. - if (rows.has('agent-presets')) { - put('agent-presets', 'roots', [ - { path: SHIPPED_PRESET_ROOT, trust: 'system' }, - { path: dshHomePath(USER_PRESET_DIR), trust: 'user' }, - ]) - } + // The agent-preset roots are patched by the shared profile boot: they are + // an assembly fact of every dsh launcher, and `dsh run` composes agents + // from the same roster this alias offers. const patches = [...overrides.entries()].map(([id, bag]): PatchOptions => { const composed = rows.get(id) if (composed === undefined) throw new Error(`dsh: patch target row "${id}" not found in the web profile composition`) diff --git a/apps/cli/tests/web-agent-presets.e2e.ts b/apps/cli/tests/web-agent-presets.e2e.ts index 04ad03099d..c9392bf182 100644 --- a/apps/cli/tests/web-agent-presets.e2e.ts +++ b/apps/cli/tests/web-agent-presets.e2e.ts @@ -126,7 +126,7 @@ describe('the shipped Web composition', () => { // depend on ripgrep being present on the machine. expect(toolNames(ctx, handle.agent).filter(name => name !== 'glob' && name !== 'grep')).toEqual([ 'ask_user_question', 'bash', 'create_goal', 'edit', 'exit_plan_mode', - 'get_goal', 'list_agents', 'ralph', 'read', 'send_message', 'skill', + 'get_goal', 'interrupt_agent', 'list_agents', 'ralph', 'read', 'send_message', 'skill', 'str_replace_editor', 'subagent', 'subagent_fork', 'task_kill', 'task_list', 'task_output', 'todo_write', 'update_goal', 'web_search', 'workflow', 'write', diff --git a/examples/headless-agent/tests/snapshots/dsh-run/session.expected.jsonl b/examples/headless-agent/tests/snapshots/dsh-run/session.expected.jsonl index 1312eb6511..8b751b3823 100644 --- a/examples/headless-agent/tests/snapshots/dsh-run/session.expected.jsonl +++ b/examples/headless-agent/tests/snapshots/dsh-run/session.expected.jsonl @@ -1,4 +1,4 @@ -{"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0} +{"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0,"agentPreset":"standard"} {"type":"permission/preset","seq":0,"time":0,"data":{"preset":"danger-full-access"}} {"type":"sandbox/mode","seq":1,"time":0,"data":{"mode":"danger-full-access"}} {"type":"approval/policy","seq":2,"time":0,"data":{"policy":"never"}}