From 3a37ec4974515fe4fa431bc4de37e5b4bd61a295 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Thu, 6 Aug 2026 21:20:56 +0800 Subject: [PATCH] fix(cli): boot the composition test the way the profile boot now does MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- apps/cli/package.json | 27 ++++++++ apps/cli/tests/web-agent-presets.spec.ts | 34 ++++++++-- pnpm-lock.yaml | 81 ++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 7 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 1553f02ed5..263edce734 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -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", diff --git a/apps/cli/tests/web-agent-presets.spec.ts b/apps/cli/tests/web-agent-presets.spec.ts index 4b6e6b0b5b..28b35d67e1 100644 --- a/apps/cli/tests/web-agent-presets.spec.ts +++ b/apps/cli/tests/web-agent-presets.spec.ts @@ -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 { 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, [{ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44ed3a2620..072987794f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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