fix(cli): boot the composition test the way the profile boot now does

The shipped surface stopped being two yml files: `base.cordis.yml` and
`web.cordis.yml` are bundle patch layers now, applied over an empty preset
root. This test still opened the old paths, so it failed before asserting
anything. It composes the same two layers the profile boot composes, over the
same empty root, and heals the flat module fallback the way the boot does —
the root lives outside this workspace, so bare plugin names have no other way
to resolve.

The web bundle's runtime row is disabled beside the webserver: it injects
`httpServer`, so a disabled port leaves it pending forever. It owns dist
serving and the URL prompt line, neither of which decides an agent's
capabilities.

`apps/cli` declares the packages the shipped agent presets name again. The
bundle split emptied its plugin dependencies, and the flat fallback links only
the app's dependency closure — so a preset row naming `dsh-persona` resolved
to nothing, and every preset mount failed. Which packages the shipped
presets compose is not implied by any bundle: the presets live beside this
app's config, so this app is what has to declare them.
This commit is contained in:
Yichen Jiang
2026-08-07 00:44:49 +08:00
parent 1123a128d9
commit 3a37ec4974
3 changed files with 135 additions and 7 deletions
+27
View File
@@ -21,17 +21,44 @@
"@deepseek-ai/dsh-app-boot": "workspace:^",
"@deepseek-ai/dsh-base": "workspace:^",
"@deepseek-ai/dsh-client-ui-agent-preset": "workspace:^",
"@deepseek-ai/dsh-command-compact": "workspace:^",
"@deepseek-ai/dsh-command-goal": "workspace:^",
"@deepseek-ai/dsh-compact-basic": "workspace:^",
"@deepseek-ai/dsh-compact-tool-result-prune": "workspace:^",
"@deepseek-ai/dsh-goal": "workspace:^",
"@deepseek-ai/dsh-goal-session": "workspace:^",
"@deepseek-ai/dsh-headless": "workspace:^",
"@deepseek-ai/dsh-mcp-client": "workspace:^",
"@deepseek-ai/dsh-paths": "workspace:^",
"@deepseek-ai/dsh-persona": "workspace:^",
"@deepseek-ai/dsh-plan-mode": "workspace:^",
"@deepseek-ai/dsh-pty": "workspace:^",
"@deepseek-ai/dsh-pty-local": "workspace:^",
"@deepseek-ai/dsh-session-reference": "workspace:^",
"@deepseek-ai/dsh-skill": "workspace:^",
"@deepseek-ai/dsh-skill-local": "workspace:^",
"@deepseek-ai/dsh-tasks-local": "workspace:^",
"@deepseek-ai/dsh-tmux-context": "workspace:^",
"@deepseek-ai/dsh-token-meter": "workspace:^",
"@deepseek-ai/dsh-tool-ask-user": "workspace:^",
"@deepseek-ai/dsh-tool-bash": "workspace:^",
"@deepseek-ai/dsh-tool-bash-persistent": "workspace:^",
"@deepseek-ai/dsh-tool-cordis": "workspace:^",
"@deepseek-ai/dsh-tool-fs": "workspace:^",
"@deepseek-ai/dsh-tool-fs-search": "workspace:^",
"@deepseek-ai/dsh-tool-goal": "workspace:^",
"@deepseek-ai/dsh-tool-ralph": "workspace:^",
"@deepseek-ai/dsh-tool-skill": "workspace:^",
"@deepseek-ai/dsh-tool-str-replace-editor": "workspace:^",
"@deepseek-ai/dsh-tool-subagent": "workspace:^",
"@deepseek-ai/dsh-tool-subagent-control": "workspace:^",
"@deepseek-ai/dsh-tool-tasks": "workspace:^",
"@deepseek-ai/dsh-tool-todo": "workspace:^",
"@deepseek-ai/dsh-tool-web": "workspace:^",
"@deepseek-ai/dsh-tool-workflow": "workspace:^",
"@deepseek-ai/dsh-web-app": "workspace:^",
"@deepseek-ai/dsh-workflow-workerthread": "workspace:^",
"@deepseek-ai/dsh-workspace-context": "workspace:^",
"commander": "^15.0.0",
"cordis": "^4.0.0-rc.7",
"js-yaml": "^4.2.0",
+27 -7
View File
@@ -1,9 +1,9 @@
import { mkdtemp, readFile, stat, writeFile } from 'node:fs/promises'
import { mkdir, mkdtemp, readFile, stat, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
import { Context } from 'cordis'
import { boot, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
import { boot, healProfilesModuleFallback, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
import { SessionId } from '@deepseek-ai/dsh-session'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type { PatchOptions } from '@cordisjs/plugin-include'
@@ -13,8 +13,12 @@ import { resolveSessionPreset, SETTINGS_NAMESPACE } from '@deepseek-ai/dsh-agent
import type {} from '@deepseek-ai/dsh-tools'
const CONFIG_DIR = fileURLToPath(new URL('../config/', import.meta.url))
const BASE_CONFIG = join(CONFIG_DIR, 'base.cordis.yml')
const WEB_OVERLAY = join(CONFIG_DIR, 'web.cordis.yml')
const REPO_ROOT = fileURLToPath(new URL('../../..', import.meta.url))
/** The shipped Web surface: the dsh-base and dsh-web-app bundle patches over an empty preset root. */
const BASE_PATCH = join(REPO_ROOT, 'packages/bundle/base/cordis.patch.yml')
const WEB_PATCH = join(REPO_ROOT, 'packages/bundle/web-app/cordis.patch.yml')
/** The installation anchor whose dependency surface the preset module fallback mirrors. */
const INSTALL_ANCHOR = join(REPO_ROOT, 'apps/cli/package.json')
/**
* Boot the shipped Web composition, minus the rows that would bind a port,
@@ -24,7 +28,8 @@ const WEB_OVERLAY = join(CONFIG_DIR, 'web.cordis.yml')
async function bootWeb(settingsFile: string, extra: PatchOptions[] = []): Promise<Context> {
const storageRoot = join(dirname(settingsFile), 'storages')
const patches: PatchOptions[] = [
...loadOverlayPatches('dsh-test', WEB_OVERLAY),
...loadOverlayPatches('dsh-test', BASE_PATCH),
...loadOverlayPatches('dsh-test', WEB_PATCH),
// The settings row defaults to `$DSH_HOME/settings.yaml`. Left alone it
// reads the developer's own document — and since the default preset is a
// setting, a stored `agent-presets.default` would decide this file's
@@ -44,6 +49,11 @@ async function bootWeb(settingsFile: string, extra: PatchOptions[] = []): Promis
// moved into the presets that a host row still waits for. The boot audit
// is that assertion.
{ id: 'webserver', disabled: true },
// The web bundle's runtime row injects `httpServer`, so it cannot
// activate without the bound port disabled above. It owns dist serving
// and the URL prompt line — surface glue, not anything that decides an
// agent's capabilities, which is all this file asserts.
{ id: 'web-runtime', disabled: true },
{ id: 'telemetry-otel', disabled: true },
{ id: 'modules', disabled: true },
{ id: 'connection', disabled: true },
@@ -62,7 +72,17 @@ async function bootWeb(settingsFile: string, extra: PatchOptions[] = []): Promis
},
...extra,
]
return await boot('dsh-test', BASE_CONFIG, patches)
// The surface is patch layers over an empty preset root, so the root sits
// outside this workspace and bare plugin names cannot resolve by Node's
// upward walk. The flat fallback the preset boot maintains is what makes
// them resolvable — the same mechanism, not a test-only shim.
const home = dirname(settingsFile)
healProfilesModuleFallback(INSTALL_ANCHOR, home)
const profileDir = join(home, 'profiles', 'spec')
await mkdir(profileDir, { recursive: true })
const rootConfig = join(profileDir, 'cordis.yml')
await writeFile(rootConfig, '[]\n')
return await boot('dsh-test', rootConfig, patches)
}
const toolNames = (ctx: Context, agent?: Agent): string[] =>
@@ -339,7 +359,7 @@ describe('authoring a preset on the shipped composition', () => {
let userRoot: string
beforeAll(async () => {
userRoot = join(await mkdtemp(join(tmpdir(), 'dsh-preset-authoring-')), 'presets')
userRoot = join(await mkdtemp(join(tmpdir(), 'dsh-preset-authoring-')), 'profiles')
const settingsFile = join(await mkdtemp(join(tmpdir(), 'dsh-preset-authoring-settings-')), 'settings.yaml')
await writeFile(settingsFile, '{}\n')
authorCtx = await bootWeb(settingsFile, [{
+81
View File
@@ -152,6 +152,24 @@ importers:
'@deepseek-ai/dsh-client-ui-agent-preset':
specifier: workspace:^
version: link:../../packages/client/ui-agent-preset
'@deepseek-ai/dsh-command-compact':
specifier: workspace:^
version: link:../../packages/compact/command-compact
'@deepseek-ai/dsh-command-goal':
specifier: workspace:^
version: link:../../packages/goal/command-goal
'@deepseek-ai/dsh-compact-basic':
specifier: workspace:^
version: link:../../packages/compact/compact-basic
'@deepseek-ai/dsh-compact-tool-result-prune':
specifier: workspace:^
version: link:../../packages/compact/compact-tool-result-prune
'@deepseek-ai/dsh-goal':
specifier: workspace:^
version: link:../../packages/goal/goal
'@deepseek-ai/dsh-goal-session':
specifier: workspace:^
version: link:../../packages/goal/goal-session
'@deepseek-ai/dsh-headless':
specifier: workspace:^
version: link:../../packages/bundle/headless
@@ -161,6 +179,12 @@ importers:
'@deepseek-ai/dsh-paths':
specifier: workspace:^
version: link:../../packages/util/paths
'@deepseek-ai/dsh-persona':
specifier: workspace:^
version: link:../../packages/preset/persona
'@deepseek-ai/dsh-plan-mode':
specifier: workspace:^
version: link:../../packages/plan/plan-mode
'@deepseek-ai/dsh-pty':
specifier: workspace:^
version: link:../../packages/pty/pty
@@ -170,21 +194,78 @@ importers:
'@deepseek-ai/dsh-session-reference':
specifier: workspace:^
version: link:../../packages/context/session-reference
'@deepseek-ai/dsh-skill':
specifier: workspace:^
version: link:../../packages/skill/skill
'@deepseek-ai/dsh-skill-local':
specifier: workspace:^
version: link:../../packages/skill/skill-local
'@deepseek-ai/dsh-tasks-local':
specifier: workspace:^
version: link:../../packages/tasks/tasks-local
'@deepseek-ai/dsh-tmux-context':
specifier: workspace:^
version: link:../../packages/context/tmux-context
'@deepseek-ai/dsh-token-meter':
specifier: workspace:^
version: link:../../packages/llm/token-meter
'@deepseek-ai/dsh-tool-ask-user':
specifier: workspace:^
version: link:../../packages/ui/tool-ask-user
'@deepseek-ai/dsh-tool-bash':
specifier: workspace:^
version: link:../../packages/bash/tool-bash
'@deepseek-ai/dsh-tool-bash-persistent':
specifier: workspace:^
version: link:../../packages/pty/tool-bash-persistent
'@deepseek-ai/dsh-tool-cordis':
specifier: workspace:^
version: link:../../packages/cordis/tool-cordis
'@deepseek-ai/dsh-tool-fs':
specifier: workspace:^
version: link:../../packages/fs/tool-fs
'@deepseek-ai/dsh-tool-fs-search':
specifier: workspace:^
version: link:../../packages/fs/tool-fs-search
'@deepseek-ai/dsh-tool-goal':
specifier: workspace:^
version: link:../../packages/goal/tool-goal
'@deepseek-ai/dsh-tool-ralph':
specifier: workspace:^
version: link:../../packages/workflow/tool-ralph
'@deepseek-ai/dsh-tool-skill':
specifier: workspace:^
version: link:../../packages/skill/tool-skill
'@deepseek-ai/dsh-tool-str-replace-editor':
specifier: workspace:^
version: link:../../packages/fs/tool-str-replace-editor
'@deepseek-ai/dsh-tool-subagent':
specifier: workspace:^
version: link:../../packages/subagent/tool-subagent
'@deepseek-ai/dsh-tool-subagent-control':
specifier: workspace:^
version: link:../../packages/subagent/tool-subagent-control
'@deepseek-ai/dsh-tool-tasks':
specifier: workspace:^
version: link:../../packages/tasks/tool-tasks
'@deepseek-ai/dsh-tool-todo':
specifier: workspace:^
version: link:../../packages/todo/tool-todo
'@deepseek-ai/dsh-tool-web':
specifier: workspace:^
version: link:../../packages/web/tool-web
'@deepseek-ai/dsh-tool-workflow':
specifier: workspace:^
version: link:../../packages/workflow/tool-workflow
'@deepseek-ai/dsh-web-app':
specifier: workspace:^
version: link:../../packages/bundle/web-app
'@deepseek-ai/dsh-workflow-workerthread':
specifier: workspace:^
version: link:../../packages/workflow/workflow-workerthread
'@deepseek-ai/dsh-workspace-context':
specifier: workspace:^
version: link:../../packages/context/workspace-context
commander:
specifier: ^15.0.0
version: 15.0.0