From 8dc3b0380e96a5b1a7c9e2d8b7641a1af8b07e99 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 24 Aug 2026 14:50:41 +0800 Subject: [PATCH] feat(bundle): ship the standalone sdk-minimal profile Add a startup-only sdk-minimal template whose sole bundle inserts the complete JSON-RPC agent tree over the empty profile root. The roster is an explicit composition allowlist: it contains one DeepSeek adapter, the minimal agent spine, persistent Bash, the string-replace editor, local execution, and JSONL persistence, while dsh-base and Web remain absent. Reuse the SDK app startup provider so the new profile retains help, stdin EOF, and bounded launcher shutdown semantics. Make that provider render its configured profile name, which keeps both sdk and sdk-minimal help truthful without duplicating process lifecycle code. Register the package in the CLI closure, TypeScript graph, lockfile, Knip policy, and bilingual bundle references. Exact manifest, row-roster, profile-template, config-dump, and HMR tests make later additions visible instead of relying on a blacklist. --- apps/cli/package.json | 1 + apps/cli/tests/built-bin.e2e.ts | 33 +++++++ apps/cli/tests/profile-hmr.spec.ts | 6 +- knip.json | 5 + packages/boot/app-boot/src/profile.ts | 4 + packages/boot/app-boot/tests/profile.spec.ts | 4 + packages/bundle/README.i18n.yaml | 4 +- packages/bundle/README.md | 1 + packages/bundle/README.zh.md | 1 + packages/bundle/sdk-app/README.i18n.yaml | 4 +- packages/bundle/sdk-app/README.md | 6 +- packages/bundle/sdk-app/README.zh.md | 6 +- packages/bundle/sdk-app/cordis.patch.yml | 2 + packages/bundle/sdk-app/package.json | 1 + packages/bundle/sdk-app/src/index.ts | 24 ++++- packages/bundle/sdk-app/tests/startup.spec.ts | 10 +- packages/bundle/sdk-app/tsconfig.json | 3 + packages/bundle/sdk-minimal/README.i18n.yaml | 6 ++ packages/bundle/sdk-minimal/README.md | 31 ++++++ packages/bundle/sdk-minimal/README.zh.md | 31 ++++++ packages/bundle/sdk-minimal/cordis.patch.yml | 97 +++++++++++++++++++ packages/bundle/sdk-minimal/package.json | 67 +++++++++++++ packages/bundle/sdk-minimal/src/index.ts | 9 ++ packages/bundle/sdk-minimal/src/invariant.ts | 26 +++++ .../sdk-minimal/tests/sdk-minimal.spec.ts | 59 +++++++++++ packages/bundle/sdk-minimal/tsconfig.json | 18 ++++ pnpm-lock.yaml | 64 ++++++++++++ tsconfig.host.json | 1 + 28 files changed, 510 insertions(+), 14 deletions(-) create mode 100644 packages/bundle/sdk-minimal/README.i18n.yaml create mode 100644 packages/bundle/sdk-minimal/README.md create mode 100644 packages/bundle/sdk-minimal/README.zh.md create mode 100644 packages/bundle/sdk-minimal/cordis.patch.yml create mode 100644 packages/bundle/sdk-minimal/package.json create mode 100644 packages/bundle/sdk-minimal/src/index.ts create mode 100644 packages/bundle/sdk-minimal/src/invariant.ts create mode 100644 packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts create mode 100644 packages/bundle/sdk-minimal/tsconfig.json diff --git a/apps/cli/package.json b/apps/cli/package.json index 49f7fc84d1..72e81a8208 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -58,6 +58,7 @@ "@deepseek-ai/dsh-session-projection": "workspace:^", "@deepseek-ai/dsh-session-reference": "workspace:^", "@deepseek-ai/dsh-sdk-app": "workspace:^", + "@deepseek-ai/dsh-sdk-minimal": "workspace:^", "@deepseek-ai/dsh-time-context": "workspace:^", "@deepseek-ai/dsh-skill": "workspace:^", "@deepseek-ai/dsh-skill-filesystem": "workspace:^", diff --git a/apps/cli/tests/built-bin.e2e.ts b/apps/cli/tests/built-bin.e2e.ts index 2fffff6700..397e960075 100644 --- a/apps/cli/tests/built-bin.e2e.ts +++ b/apps/cli/tests/built-bin.e2e.ts @@ -12,7 +12,9 @@ import { type SessionNotification, } from '@agentclientprotocol/sdk' import { startMockLlmServer } from '@deepseek-ai/dsh-llm-mock-server' +import { entryListSchema } from '@deepseek-ai/cordis-plugin-include' import { execa } from 'execa' +import * as yaml from 'js-yaml' import { afterEach, beforeEach, describe, expect, it } from 'vitest' /** Published-entry acceptance for argument errors, profile lifecycle, and boot-free config dumps. */ @@ -924,6 +926,37 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)', expect(stdout).not.toMatch(/name: '@deepseek-ai\/dsh-client-/) }, 30_000) + it('prints the exact standalone sdk-minimal tree without dsh-base', async () => { + const { stdout, code, stderr } = await runBuiltBin( + ['--profile', 'sdk-minimal', '--dump-default-config'], + { DSH_HOME: home }, + ) + expect(code).toBe(0) + expect(stderr).toBe('') + const rows = yaml.load(stdout, { schema: entryListSchema }) as Array<{ id?: string; name?: string }> + expect(rows.map(row => [row.id, row.name])).toEqual([ + ['sdk-app-startup', '@deepseek-ai/dsh-sdk-app'], + ['sdk-jsonrpc-server', '@deepseek-ai/dsh-sdk-jsonrpc-server'], + ['deepseek-llm-api-extensions', '@deepseek-ai/dsh-deepseek-llm-api-extensions'], + ['session-log-deepseek', '@deepseek-ai/dsh-session-log-deepseek'], + ['plugin-package-inventory-deepseek', '@deepseek-ai/dsh-plugin-package-inventory-deepseek'], + ['llm-deepseek', '@deepseek-ai/dsh-llm-deepseek'], + ['sandbox', '@deepseek-ai/dsh-sandbox-local'], + ['sandbox-policy', '@deepseek-ai/dsh-sandbox-policy'], + ['subprocess', '@deepseek-ai/dsh-subprocess-local'], + ['pty', '@deepseek-ai/dsh-terminal'], + ['terminal-bash', '@deepseek-ai/dsh-terminal-bash'], + ['fs-local', '@deepseek-ai/dsh-fs-local'], + ['agent-spine', '@deepseek-ai/dsh-agent-spine-demo'], + ['persistent-bash', '@deepseek-ai/dsh-tool-bash-persistent'], + ['str-replace-editor', '@deepseek-ai/dsh-tool-str-replace-editor'], + ['sessions', '@deepseek-ai/dsh-session-persistence-jsonl'], + ]) + expect(stdout).toContain('# == @deepseek-ai/dsh-sdk-minimal') + expect(stdout).not.toContain('@deepseek-ai/dsh-base') + expect(stdout).not.toContain('@deepseek-ai/dsh-web-app') + }, 30_000) + it('composes the profile user layer and a --patch overlay in order', async () => { // Auto-init the web profile first, then write its user layer. const init = await runBuiltBin(['--profile', 'web', '--dump-default-config'], { DSH_HOME: home }) diff --git a/apps/cli/tests/profile-hmr.spec.ts b/apps/cli/tests/profile-hmr.spec.ts index 6fed867d92..306bfe1458 100644 --- a/apps/cli/tests/profile-hmr.spec.ts +++ b/apps/cli/tests/profile-hmr.spec.ts @@ -9,7 +9,7 @@ import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include' const REPOSITORY_ROOT = fileURLToPath(new URL('../../../', import.meta.url)) /** Load one shipped bundle patch through the same parser as profile boot. */ -function bundle(name: 'acp-app' | 'base' | 'headless' | 'sdk-app' | 'web-app'): PatchOptions[] { +function bundle(name: 'acp-app' | 'base' | 'headless' | 'sdk-app' | 'sdk-minimal' | 'web-app'): PatchOptions[] { return loadOverlayPatches('profile-hmr test', join(REPOSITORY_ROOT, 'packages', 'bundle', name, 'cordis.patch.yml')) } @@ -39,4 +39,8 @@ describe('profile module-HMR policy', () => { config: { root: ['.'] }, }) }) + + it('keeps the standalone sdk-minimal tree free of module HMR', () => { + expect(composeEntries([bundle('sdk-minimal')]).find(entry => entry.id === 'hmr')).toBeUndefined() + }) }) diff --git a/knip.json b/knip.json index 6e03ebf7da..e8dc6139ee 100644 --- a/knip.json +++ b/knip.json @@ -699,6 +699,11 @@ "@deepseek-ai/dsh-sdk-jsonrpc-server" ] }, + "packages/bundle/sdk-minimal": { + "ignoreDependencies": [ + "@deepseek-ai/.+" + ] + }, "packages/bundle/web-app": { "ignoreDependencies": [ "@deepseek-ai/.+" diff --git a/packages/boot/app-boot/src/profile.ts b/packages/boot/app-boot/src/profile.ts index 17c6cf65ab..b99dca29c7 100644 --- a/packages/boot/app-boot/src/profile.ts +++ b/packages/boot/app-boot/src/profile.ts @@ -146,6 +146,10 @@ export const PROFILE_TEMPLATES: Record = { bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-sdk-app'], patchReload: 'startup', }, + 'sdk-minimal': { + bundles: ['@deepseek-ai/dsh-sdk-minimal'], + patchReload: 'startup', + }, } /** Installation-owned bundle tuples normalized to the shipped template. */ diff --git a/packages/boot/app-boot/tests/profile.spec.ts b/packages/boot/app-boot/tests/profile.spec.ts index 2e3b4e6187..4e7a2c5ddf 100644 --- a/packages/boot/app-boot/tests/profile.spec.ts +++ b/packages/boot/app-boot/tests/profile.spec.ts @@ -173,6 +173,10 @@ describe('loadProfile', () => { bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-sdk-app'], patchReload: 'startup', }) + expect(PROFILE_TEMPLATES['sdk-minimal']).toEqual({ + bundles: ['@deepseek-ai/dsh-sdk-minimal'], + patchReload: 'startup', + }) try { loadProfile('t', 'web', anchor, home) } catch { diff --git a/packages/bundle/README.i18n.yaml b/packages/bundle/README.i18n.yaml index 5f7fbd40f4..1ca618d528 100644 --- a/packages/bundle/README.i18n.yaml +++ b/packages/bundle/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/bundle/README.md -README.md: d6b24a276fa64bb2eb80c2aad1783795e351ebc4 -README.zh.md: 36acc510cfab7979d28052ab26687dce58175155 +README.md: 44cf46a217e078e4d71d838afb6525f2bc908bd2 +README.zh.md: f2987b72c526698e2df949e2edfe55e1375f2d8e diff --git a/packages/bundle/README.md b/packages/bundle/README.md index d6b24a276f..44cf46a217 100644 --- a/packages/bundle/README.md +++ b/packages/bundle/README.md @@ -13,5 +13,6 @@ The manifest declaration, not this directory, defines Bundle identity. Domain pa | [`web-app/`](web-app/README.md) | Browser surface: web patch layer + runtime glue plugin | mounts rows | | [`headless/`](headless/README.md) | Direct one-shot task mode over base, with no Host or Web layer | mounts `headless-runner` | | [`sdk-app/`](sdk-app/README.md) | SDK stdio JSON-RPC application over base | mounts the SDK server | +| [`sdk-minimal/`](sdk-minimal/README.md) | Standalone minimal SDK application without base or Web | — (complete patch tree) | In-box bundles resolve from the dsh installation; out-of-tree bundles install into a profile through `dsh plugin --profile add `. diff --git a/packages/bundle/README.zh.md b/packages/bundle/README.zh.md index 36acc510cf..f2987b72c5 100644 --- a/packages/bundle/README.zh.md +++ b/packages/bundle/README.zh.md @@ -13,5 +13,6 @@ Bundle 身份由 manifest 声明决定,而不是由本目录决定。领域包 | [`web-app/`](web-app/README.zh.md) | 浏览器表层:web patch 层 + 运行时粘合插件 | 挂载多条配置行 | | [`headless/`](headless/README.zh.md) | 直接运行在 base 之上的一次性任务模式,不含 Host 或 Web 层 | 挂载 `headless-runner` | | [`sdk-app/`](sdk-app/README.zh.md) | 运行在 base 之上的 SDK stdio JSON-RPC 应用 | 挂载 SDK server | +| [`sdk-minimal/`](sdk-minimal/README.zh.md) | 不含 base 或 Web 的独立极简 SDK 应用 | 无(完整 patch 树) | 内置组合包从 dsh 安装目录解析;树外(out-of-tree)组合包通过 `dsh plugin --profile add ` 安装进 profile。 diff --git a/packages/bundle/sdk-app/README.i18n.yaml b/packages/bundle/sdk-app/README.i18n.yaml index 8deaf213fa..43e0f90976 100644 --- a/packages/bundle/sdk-app/README.i18n.yaml +++ b/packages/bundle/sdk-app/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/bundle/sdk-app/README.md -README.md: 0356d6f4a99d7baef6ff7619d505392ff7f7f1d2 -README.zh.md: c70eb685954ebff42bca6c3d289ab58e46298d50 +README.md: c5022bd2096fae931fff48944bfc167280e62476 +README.zh.md: 687a19dc09679d11696a207ad17db3cf463ee901 diff --git a/packages/bundle/sdk-app/README.md b/packages/bundle/sdk-app/README.md index 0356d6f4a9..c5022bd209 100644 --- a/packages/bundle/sdk-app/README.md +++ b/packages/bundle/sdk-app/README.md @@ -2,10 +2,14 @@ English | [中文](README.zh.md) -The SDK stdio application as a `dsh` profile bundle over [`dsh-base`](../base/README.md). It inherits the base's disabled module-HMR policy; its patch sets the coding-agent persona, mounts an app-owned zero-option command provider, and starts [`dsh-sdk-jsonrpc-server`](../../sdk/server/README.md) only after that provider accepts the invocation. `dsh --profile sdk --help` therefore writes help and exits without claiming stdin or stdout. +The SDK stdio application as a `dsh` profile bundle over [`dsh-base`](../base/README.md). It inherits the base's disabled module-HMR policy; its patch sets the coding-agent persona, mounts an app-owned zero-option command provider, and starts [`dsh-sdk-jsonrpc-server`](../../sdk/server/README.md) only after that provider accepts the invocation. `dsh --profile sdk --help` therefore writes help and exits without claiming stdin or stdout. The standalone [`sdk-minimal`](../sdk-minimal/README.md) bundle reuses the same startup provider and supplies its own profile name. The startup provider binds stdin EOF to the launcher's bounded successful shutdown. SDK protocol `shutdown`, SIGINT, and SIGTERM retain their owning server or launcher paths; disposal drains the root profile tree and persistence. Stdout is reserved for newline-delimited JSON-RPC frames. The bundle disables model-generated session titles because the SDK exposes no title surface; deterministic fallback titles remain durable without an auxiliary model request. A deployment selects a different complete composition through profile bundles and patch files, not another app bin. +| Config | Default | Behavior | +|---|---|---| +| `profile` | `sdk` | Profile name rendered in command help; a bundle mounting this provider sets its own shipped profile name. | + `DSH_MAX_TOKENS_AS_SUCCESS` retains the SDK deployment mapping: unset or JSON `true` reports token-limited subagent completion as accepted, while JSON `false` reports it as an error. Provider/model and workspace cwd arrive through the SDK initialization request; the base profile owns adapters, tools, persistence, policy, settings, and credentials. ## Model Experience diff --git a/packages/bundle/sdk-app/README.zh.md b/packages/bundle/sdk-app/README.zh.md index c70eb68595..687a19dc09 100644 --- a/packages/bundle/sdk-app/README.zh.md +++ b/packages/bundle/sdk-app/README.zh.md @@ -2,10 +2,14 @@ [English](README.md) | 中文 -以 [`dsh-base`](../base/README.zh.md) 为基础的 SDK stdio 应用 `dsh` profile 组合包。它继承 base 默认禁用模块 HMR(热模块替换)的策略;其 patch 设置 coding agent(编程智能体)persona、挂载应用自有的零选项命令提供方,并且只在该提供方接受调用后启动 [`dsh-sdk-jsonrpc-server`](../../sdk/server/README.zh.md)。因此,`dsh --profile sdk --help` 会写出 help 并退出,不会占用 stdin 或 stdout。 +以 [`dsh-base`](../base/README.zh.md) 为基础的 SDK stdio 应用 `dsh` profile 组合包。它继承 base 默认禁用模块 HMR(热模块替换)的策略;其 patch 设置 coding agent(编程智能体)persona、挂载应用自有的零选项命令提供方,并且只在该提供方接受调用后启动 [`dsh-sdk-jsonrpc-server`](../../sdk/server/README.zh.md)。因此,`dsh --profile sdk --help` 会写出 help 并退出,不会占用 stdin 或 stdout。独立的 [`sdk-minimal`](../sdk-minimal/README.zh.md) 组合包复用同一个启动提供方,并提供自己的 profile 名称。 启动提供方把 stdin EOF 接到启动器的有界成功关闭流程。SDK 协议 `shutdown`、SIGINT 与 SIGTERM 继续使用各自所属的 server 或启动器路径;dispose(资源释放)会排空根 profile 配置树与持久化。stdout 专用于按换行分隔的 JSON-RPC 帧。SDK 不提供 title 表层,因此本组合包禁用模型生成的 session title;确定性的 fallback title 仍会持久化,但不发起辅助模型请求。部署通过 profile 组合包与 patch 文件选择另一套完整组合,而不是使用另一个应用 bin。 +| 配置 | 默认值 | 行为 | +|---|---|---| +| `profile` | `sdk` | 命令 help 中呈现的 profile 名称;挂载此提供方的组合包会设置自己的随附 profile 名称。 | + `DSH_MAX_TOKENS_AS_SUCCESS` 保留 SDK 部署映射:未设置或 JSON `true` 把 token 达限的 subagent 完成报告为已接受,JSON `false` 则报告为错误。模型提供方/模型与工作区 cwd 通过 SDK 初始化请求传入;base profile 拥有适配器、工具、持久化、策略、settings 与 credentials。 ## 模型体验 diff --git a/packages/bundle/sdk-app/cordis.patch.yml b/packages/bundle/sdk-app/cordis.patch.yml index aa1795168c..373e7aeb63 100644 --- a/packages/bundle/sdk-app/cordis.patch.yml +++ b/packages/bundle/sdk-app/cordis.patch.yml @@ -11,6 +11,8 @@ - insert: - id: sdk-app-startup name: '@deepseek-ai/dsh-sdk-app' + config: + profile: sdk - id: sdk-jsonrpc-server name: '@deepseek-ai/dsh-sdk-jsonrpc-server' diff --git a/packages/bundle/sdk-app/package.json b/packages/bundle/sdk-app/package.json index 87214882fb..6ab4167d8d 100644 --- a/packages/bundle/sdk-app/package.json +++ b/packages/bundle/sdk-app/package.json @@ -41,6 +41,7 @@ "dependencies": { "@deepseek-ai/dsh-cmdline": "workspace:^", "@deepseek-ai/dsh-sdk-jsonrpc-server": "workspace:^", + "@deepseek-ai/schemastery": "workspace:^", "commander": "^15.0.0" }, "peerDependencies": { diff --git a/packages/bundle/sdk-app/src/index.ts b/packages/bundle/sdk-app/src/index.ts index 9ade81a965..fec847af53 100644 --- a/packages/bundle/sdk-app/src/index.ts +++ b/packages/bundle/sdk-app/src/index.ts @@ -7,6 +7,7 @@ import { Command } from 'commander' import type { Context } from '@deepseek-ai/cordis' +import z from '@deepseek-ai/schemastery' import { exitOnStdinEnd, parseCmdline } from '@deepseek-ai/dsh-cmdline' /** Stable Cordis plugin name. */ @@ -18,18 +19,30 @@ export const inject = ['cmdlineArgs'] /** Service the JSON-RPC server row waits for before claiming stdio. */ export const SDK_APP_STARTUP_SERVICE = 'sdkAppStartup' +/** SDK stdio startup configuration. */ +export interface Config { + /** Profile name rendered in help and diagnostics (default `sdk`). */ + profile?: string +} + +/** Validate and default SDK stdio startup configuration. */ +export const Config: z = z.object({ + profile: z.string().default('sdk'), +}) + /** * Build this app's zero-option command and help. + * @param profile - selected profile name rendered in the command grammar. * @returns a fresh program for one invocation. */ -function sdkCommand(): Command { +function sdkCommand(profile: string): Command { return new Command() - .name('dsh --profile sdk') + .name(`dsh --profile ${profile}`) .description('Serve DeepSeek Harness SDK clients over stdio JSON-RPC.') .helpOption('-h, --help', 'show this help') .addHelpText('after', ` Example: - dsh --profile sdk serve one SDK runtime until its client disconnects + dsh --profile ${profile} serve one SDK runtime until its client disconnects `) } @@ -37,9 +50,10 @@ Example: * Accept an SDK profile invocation, publish readiness, and bind EOF to the * launcher's bounded shutdown. * @param ctx - plugin context carrying command-line and exit launcher values. + * @param config - selected profile identity for command help. */ -export function apply(ctx: Context): void { - const program = sdkCommand() +export function apply(ctx: Context, config: Config = {}): void { + const program = sdkCommand(config.profile ?? 'sdk') program.action(() => { exitOnStdinEnd(ctx, 'sdk-app.stdin') ctx.provide(SDK_APP_STARTUP_SERVICE, { accepted: true }) diff --git a/packages/bundle/sdk-app/tests/startup.spec.ts b/packages/bundle/sdk-app/tests/startup.spec.ts index 65f2b76c5d..5f0ec6524a 100644 --- a/packages/bundle/sdk-app/tests/startup.spec.ts +++ b/packages/bundle/sdk-app/tests/startup.spec.ts @@ -27,7 +27,7 @@ afterEach(() => { }) /** Run the provider with captured command output and exit requests. */ -function start(args: string[]): { ctx: Context; exits: number[]; out: () => string; stdin: TestStdin } { +function start(args: string[], profile = 'sdk'): { ctx: Context; exits: number[]; out: () => string; stdin: TestStdin } { const ctx = new Context() const exits: number[] = [] const stdin = new TestStdin() @@ -41,7 +41,7 @@ function start(args: string[]): { ctx: Context; exits: number[]; out: () => stri exit: code => void exits.push(code), ready: { onReady: (listener) => { listener(); return () => {} } }, }) - apply(ctx) + apply(ctx, { profile }) return { ctx, exits, out: () => out, stdin } } @@ -62,4 +62,10 @@ describe('SDK app startup', () => { stdin.end() expect(exits).toEqual([0]) }) + + it('renders the selected SDK profile name in help', () => { + const { out } = start(['--help'], 'sdk-minimal') + expect(out()).toContain('Usage: dsh --profile sdk-minimal') + expect(out()).toContain('dsh --profile sdk-minimal') + }) }) diff --git a/packages/bundle/sdk-app/tsconfig.json b/packages/bundle/sdk-app/tsconfig.json index 1d644141bd..0a98d3117c 100644 --- a/packages/bundle/sdk-app/tsconfig.json +++ b/packages/bundle/sdk-app/tsconfig.json @@ -11,6 +11,9 @@ { "path": "../../../vendor/cordis" }, + { + "path": "../../../vendor/schemastery" + }, { "path": "../../runtime-diagnostics/invariants" }, diff --git a/packages/bundle/sdk-minimal/README.i18n.yaml b/packages/bundle/sdk-minimal/README.i18n.yaml new file mode 100644 index 0000000000..51cf2a832d --- /dev/null +++ b/packages/bundle/sdk-minimal/README.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write packages/bundle/sdk-minimal/README.md +README.md: 5bc60991624ab52b3d55acac4851b6af2b198218 +README.zh.md: 7e11140bcb8a81cf3e22db88530bfcb364436f96 diff --git a/packages/bundle/sdk-minimal/README.md b/packages/bundle/sdk-minimal/README.md new file mode 100644 index 0000000000..5bc6099162 --- /dev/null +++ b/packages/bundle/sdk-minimal/README.md @@ -0,0 +1,31 @@ +# `@deepseek-ai/dsh-sdk-minimal` + +English | [中文](README.zh.md) + +Standalone minimal SDK application bundle for `dsh --profile sdk-minimal`. Its single insert is the complete Cordis tree: SDK stdio startup and JSON-RPC serving, one environment-configured DeepSeek adapter, the executor-less agent spine, local subprocess and unrestricted filesystem providers, a persistent Bash PTY, the string-replace editor, and uncompressed JSONL session persistence under `$DSH_HOME/sessions`. It deliberately does not include [`dsh-base`](../base/README.md), Web, settings, managed credentials, telemetry, compaction, workspace instructions, skills, jobs tools, subagents, or any other model-facing tool. + +The profile remains part of the ordinary launcher and layering model. The bundle supplies the complete default tree; the profile patch, home patch, and ordered `--patch` files can replace rows or insert external bundles above it. `dsh plugin --profile sdk-minimal` manages persistent dependencies. The shipped template uses startup-only patches so one stdio connection never observes replacement of its server or agent dependencies. + +`DEEPSEEK_API_KEY` supplies the adapter credential. `DSH_MODEL` selects the sole configured model, `DSH_CONTEXT_WINDOW` sets its context window, and `DSH_SYSTEM_PROMPT` replaces the default persona. The process working directory is the sandbox-policy workspace and local-filesystem root. The bundle sets `danger-full-access`; its persistent shell and editor can modify any path available to the process. + +## Model Experience + +### Minimal coding-agent composition + +#### What the model sees + +The system prompt is `DSH_SYSTEM_PROMPT` or `You are a helpful software engineer assistant.`. The only advertised tools are owner-scoped persistent `bash` and `str_replace_editor`; runtime context, workspace instructions, skills, jobs controls, compaction, and Harness identity are absent. + +#### Token effect + +One stable persona plus the two tool schemas. Tool results and ordinary conversation history grow with the session. + +#### KV Cache effect + +Stable for a fixed persona, platform, provider, model, and bundle patch stack. Profile changes take effect on the next process. + +## Known Limitations and Deferred Work + +- **The profile is POSIX-only** — this layer uses a Bash PTY; Windows support belongs to the platform runtime layer above it. +- **The composition intentionally omits shared product services** — select `dsh --profile sdk` when settings, managed credentials, policy presets, telemetry, Web tools, or the full default tool roster are required. +- **User patches can expand the tree and corrupt stdout** — profile customization is trusted application composition; a plugin that writes ordinary text to stdout can break JSON-RPC framing. diff --git a/packages/bundle/sdk-minimal/README.zh.md b/packages/bundle/sdk-minimal/README.zh.md new file mode 100644 index 0000000000..7e11140bcb --- /dev/null +++ b/packages/bundle/sdk-minimal/README.zh.md @@ -0,0 +1,31 @@ +# `@deepseek-ai/dsh-sdk-minimal` + +[English](README.md) | 中文 + +供 `dsh --profile sdk-minimal` 使用的独立极简 SDK 应用组合包。它的单个 insert 构成完整 Cordis 树:SDK stdio 启动与 JSON-RPC 对外服务、一个由环境配置的 DeepSeek 适配器、无执行器的 agent 主干、本地子进程与不受限文件系统提供方、持久 Bash PTY、字符串替换编辑器,以及位于 `$DSH_HOME/sessions` 的未压缩 JSONL 会话持久化。它刻意不包含 [`dsh-base`](../base/README.zh.md)、Web、settings、托管凭据、遥测、压缩(compaction)、workspace 指令、skills、jobs 工具、subagent 或任何其他面向模型的工具。 + +该 profile 仍遵循普通 launcher 与分层模型。组合包提供完整默认树;profile patch、home patch 与有序 `--patch` 文件可以在其上替换配置项或插入外部组合包。`dsh plugin --profile sdk-minimal` 管理持久依赖。随附模板仅在启动时应用 patch,因此一个 stdio 连接不会观察到服务器或 agent 依赖在运行中被替换。 + +`DEEPSEEK_API_KEY` 提供适配器凭据。`DSH_MODEL` 选择唯一配置的模型,`DSH_CONTEXT_WINDOW` 设置其上下文窗口,`DSH_SYSTEM_PROMPT` 替换默认 persona。进程工作目录同时作为沙箱策略 workspace 与本地文件系统根目录。该组合包设置 `danger-full-access`;其持久 shell 与编辑器可以修改进程可访问的任何路径。 + +## 模型体验 + +### 极简 coding agent 组合 + +#### 模型看到的内容 + +系统提示词取 `DSH_SYSTEM_PROMPT`,未设置时使用 `You are a helpful software engineer assistant.`。对外公布的工具只有 agent 所有的持久 `bash` 与 `str_replace_editor`;运行时上下文、workspace 指令、skills、jobs 控制、compaction 与 Harness 身份均不存在。 + +#### Token 影响 + +一个稳定 persona 加两个工具 schema。工具结果与普通对话历史随会话增长。 + +#### KV Cache 影响 + +当 persona、平台、提供方、模型与组合包 patch 栈固定时保持稳定。Profile 变更在下一个进程生效。 + +## 已知限制与待办工作 + +- **该 profile 仅支持 POSIX** — 此层使用 Bash PTY;Windows 支持属于其上的平台运行时层。 +- **该组合刻意省略共享产品服务** — 需要 settings、托管凭据、权限策略预设、遥测、Web 工具或完整默认工具清单时,请选择 `dsh --profile sdk`。 +- **用户 patch 可以扩展配置树并破坏 stdout** — profile 自定义属于受信任的应用组合;向 stdout 写入普通文本的插件会破坏 JSON-RPC 分帧。 diff --git a/packages/bundle/sdk-minimal/cordis.patch.yml b/packages/bundle/sdk-minimal/cordis.patch.yml new file mode 100644 index 0000000000..e43f3e2b03 --- /dev/null +++ b/packages/bundle/sdk-minimal/cordis.patch.yml @@ -0,0 +1,97 @@ +# Standalone minimal SDK application. Unlike the ordinary SDK profile, this +# bundle does not layer over dsh-base: this insert is the complete Cordis tree. +# User profile, home, and invocation patches still apply above it. + +- insert: + - id: sdk-app-startup + name: '@deepseek-ai/dsh-sdk-app' + config: + profile: sdk-minimal + + - id: sdk-jsonrpc-server + name: '@deepseek-ai/dsh-sdk-jsonrpc-server' + inject: [sdkAppStartup, loader] + config: + maxTokensAsSuccess: false + + - id: deepseek-llm-api-extensions + name: '@deepseek-ai/dsh-deepseek-llm-api-extensions' + + - id: session-log-deepseek + name: '@deepseek-ai/dsh-session-log-deepseek' + + - id: plugin-package-inventory-deepseek + name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek' + + - id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + config: + apiKeyEnv: DEEPSEEK_API_KEY + streamIdleTimeoutMs: 172800000 + models: + - id: !!js process.env.DSH_MODEL ?? 'deepseek-v4-flash' + contextWindow: !!js Number(process.env.DSH_CONTEXT_WINDOW ?? 1000000) + + - id: sandbox + name: '@deepseek-ai/dsh-sandbox-local' + + - id: sandbox-policy + name: '@deepseek-ai/dsh-sandbox-policy' + config: + mode: danger-full-access + workspaceRoot: !!js process.cwd() + + - id: subprocess + name: '@deepseek-ai/dsh-subprocess-local' + + - id: pty + name: '@deepseek-ai/dsh-terminal' + + - id: terminal-bash + name: '@deepseek-ai/dsh-terminal-bash' + config: + timeoutMs: 300000 + + # The editor uses the bare local filesystem; persistent Bash still consumes + # the shared danger-full-access sandbox policy above. + - id: fs-local + name: '@deepseek-ai/dsh-fs-local' + config: + cwd: !!js process.cwd() + + - id: agent-spine + name: '@deepseek-ai/dsh-agent-spine-demo' + config: + includeHarnessIdentity: false + includeRuntimeContext: false + persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer assistant.' + workspaceContext: false + skills: + enabled: false + toolBash: false + toolJobs: false + + - id: persistent-bash + name: '@deepseek-ai/dsh-tool-bash-persistent' + config: + timeoutMs: 300000 + description: |- + Run commands in a bash shell + * When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped. + * You don't have access to the internet via this tool. + * You do have access to a mirror of common linux and python packages via apt and pip. + * State is persistent across command calls and discussions with the user. + * To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'. + * Please avoid commands that may produce a very large amount of output. + * Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background. + + - id: str-replace-editor + name: '@deepseek-ai/dsh-tool-str-replace-editor' + config: + maxOutputChars: 16000 + + - id: sessions + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js dshHomePath('sessions') + compression: none diff --git a/packages/bundle/sdk-minimal/package.json b/packages/bundle/sdk-minimal/package.json new file mode 100644 index 0000000000..1b3d5da5d2 --- /dev/null +++ b/packages/bundle/sdk-minimal/package.json @@ -0,0 +1,67 @@ +{ + "name": "@deepseek-ai/dsh-sdk-minimal", + "description": "The standalone minimal SDK profile bundle: JSON-RPC, one DeepSeek adapter, persistent shell, editor, and JSONL sessions", + "version": "0.1.1-rc.2", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", + "directory": "packages/bundle/sdk-minimal" + }, + "type": "module", + "main": "lib/index.js", + "types": "lib/types/index.d.ts", + "exports": { + ".": { + "types": "./lib/types/index.d.ts", + "default": "./lib/index.js" + }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, + "./cordis.patch.yml": "./cordis.patch.yml", + "./src/*": "./src/*", + "./package.json": "./package.json" + }, + "files": [ + "lib/index.js", + "lib/invariant.js", + "cordis.patch.yml", + "lib/types/**/*.d.ts" + ], + "license": "MIT", + "dsh": { + "bundle": { + "patch": "./cordis.patch.yml" + } + }, + "dependencies": { + "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", + "@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:^", + "@deepseek-ai/dsh-fs-local": "workspace:^", + "@deepseek-ai/dsh-llm-deepseek": "workspace:^", + "@deepseek-ai/dsh-plugin-package-inventory-deepseek": "workspace:^", + "@deepseek-ai/dsh-sandbox-local": "workspace:^", + "@deepseek-ai/dsh-sandbox-policy": "workspace:^", + "@deepseek-ai/dsh-sdk-app": "workspace:^", + "@deepseek-ai/dsh-sdk-jsonrpc-server": "workspace:^", + "@deepseek-ai/dsh-session-log-deepseek": "workspace:^", + "@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^", + "@deepseek-ai/dsh-subprocess-local": "workspace:^", + "@deepseek-ai/dsh-terminal": "workspace:^", + "@deepseek-ai/dsh-terminal-bash": "workspace:^", + "@deepseek-ai/dsh-tool-bash-persistent": "workspace:^", + "@deepseek-ai/dsh-tool-str-replace-editor": "workspace:^" + }, + "peerDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", + "@deepseek-ai/cordis": "workspace:^" + }, + "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", + "@deepseek-ai/cordis": "workspace:^" + } +} diff --git a/packages/bundle/sdk-minimal/src/index.ts b/packages/bundle/sdk-minimal/src/index.ts new file mode 100644 index 0000000000..a5f162161e --- /dev/null +++ b/packages/bundle/sdk-minimal/src/index.ts @@ -0,0 +1,9 @@ +/** + * @deepseek-ai/dsh-sdk-minimal — the standalone minimal SDK profile bundle. + * The package's substance is `cordis.patch.yml`, declared by the + * `dsh.bundle.patch` manifest field and resolved by the profile composer; + * this module carries no runtime interface. + * @module @deepseek-ai/dsh-sdk-minimal + */ + +export {} diff --git a/packages/bundle/sdk-minimal/src/invariant.ts b/packages/bundle/sdk-minimal/src/invariant.ts new file mode 100644 index 0000000000..e2f480504a --- /dev/null +++ b/packages/bundle/sdk-minimal/src/invariant.ts @@ -0,0 +1,26 @@ +/** + * Package-owned invariant companion for `@deepseek-ai/dsh-sdk-minimal`. + * @module @deepseek-ai/dsh-sdk-minimal/invariant + */ + +import type { Context } from '@deepseek-ai/cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-sdk-minimal' + +/** Cordis companion plugin name. */ +export const name = 'sdk-minimal-bundle-invariant' +/** Service required before the companion can register. */ +export const inject = ['invariants'] + +// No runtime invariant: the package is a static patch-list carrier whose +// inserted rows own their runtime relationships and invariant companions. +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) diff --git a/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts b/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts new file mode 100644 index 0000000000..4e9fbbcc6a --- /dev/null +++ b/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts @@ -0,0 +1,59 @@ +/** The standalone SDK-minimal bundle's complete declared Cordis tree. */ + +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import * as yaml from 'js-yaml' +import { describe, expect, it } from 'vitest' +import { entryListSchema } from '@deepseek-ai/cordis-plugin-include' + +describe('dsh-sdk-minimal bundle', () => { + it('declares one standalone allowlisted tree with every row dependency', () => { + const root = fileURLToPath(new URL('..', import.meta.url)) + const manifest = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8')) as { + dependencies?: Record + dsh?: { bundle?: { patch?: string } } + } + expect(manifest.dsh?.bundle?.patch).toBe('./cordis.patch.yml') + const patches = yaml.load( + readFileSync(resolve(root, manifest.dsh!.bundle!.patch!), 'utf8'), + { schema: entryListSchema }, + ) as Array<{ insert?: Array<{ id?: string; inject?: string[]; name?: string; config?: Record }> }> + expect(patches).toHaveLength(1) + const rows = patches[0]?.insert ?? [] + expect(rows.map(row => [row.id, row.name])).toEqual([ + ['sdk-app-startup', '@deepseek-ai/dsh-sdk-app'], + ['sdk-jsonrpc-server', '@deepseek-ai/dsh-sdk-jsonrpc-server'], + ['deepseek-llm-api-extensions', '@deepseek-ai/dsh-deepseek-llm-api-extensions'], + ['session-log-deepseek', '@deepseek-ai/dsh-session-log-deepseek'], + ['plugin-package-inventory-deepseek', '@deepseek-ai/dsh-plugin-package-inventory-deepseek'], + ['llm-deepseek', '@deepseek-ai/dsh-llm-deepseek'], + ['sandbox', '@deepseek-ai/dsh-sandbox-local'], + ['sandbox-policy', '@deepseek-ai/dsh-sandbox-policy'], + ['subprocess', '@deepseek-ai/dsh-subprocess-local'], + ['pty', '@deepseek-ai/dsh-terminal'], + ['terminal-bash', '@deepseek-ai/dsh-terminal-bash'], + ['fs-local', '@deepseek-ai/dsh-fs-local'], + ['agent-spine', '@deepseek-ai/dsh-agent-spine-demo'], + ['persistent-bash', '@deepseek-ai/dsh-tool-bash-persistent'], + ['str-replace-editor', '@deepseek-ai/dsh-tool-str-replace-editor'], + ['sessions', '@deepseek-ai/dsh-session-persistence-jsonl'], + ]) + expect(rows.find(row => row.id === 'sdk-app-startup')?.config).toEqual({ profile: 'sdk-minimal' }) + expect(rows.find(row => row.id === 'sdk-jsonrpc-server')).toMatchObject({ + inject: ['sdkAppStartup', 'loader'], + config: { maxTokensAsSuccess: false }, + }) + expect(rows.find(row => row.id === 'agent-spine')?.config).toMatchObject({ + includeHarnessIdentity: false, + includeRuntimeContext: false, + workspaceContext: false, + skills: { enabled: false }, + toolBash: false, + toolJobs: false, + }) + expect(Object.keys(manifest.dependencies ?? {}).sort()).toEqual( + [...new Set(rows.map(row => row.name).filter((name): name is string => name !== undefined))].sort(), + ) + }) +}) diff --git a/packages/bundle/sdk-minimal/tsconfig.json b/packages/bundle/sdk-minimal/tsconfig.json new file mode 100644 index 0000000000..8f58ed6e28 --- /dev/null +++ b/packages/bundle/sdk-minimal/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../runtime-diagnostics/invariants" + } + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index daec43dbff..a6b3e12d45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,6 +225,9 @@ importers: '@deepseek-ai/dsh-sdk-app': specifier: workspace:^ version: link:../../packages/bundle/sdk-app + '@deepseek-ai/dsh-sdk-minimal': + specifier: workspace:^ + version: link:../../packages/bundle/sdk-minimal '@deepseek-ai/dsh-session-projection': specifier: workspace:^ version: link:../../packages/session/session-projection @@ -1524,6 +1527,9 @@ importers: '@deepseek-ai/dsh-sdk-jsonrpc-server': specifier: workspace:^ version: link:../../sdk/server + '@deepseek-ai/schemastery': + specifier: link:../../../vendor/schemastery + version: link:../../../vendor/schemastery commander: specifier: ^15.0.0 version: 15.0.0 @@ -1538,6 +1544,64 @@ importers: specifier: workspace:^ version: link:../../runtime-diagnostics/invariants + packages/bundle/sdk-minimal: + dependencies: + '@deepseek-ai/dsh-agent-spine-demo': + specifier: workspace:^ + version: link:../../examples/agent-spine-demo + '@deepseek-ai/dsh-deepseek-llm-api-extensions': + specifier: workspace:^ + version: link:../../llm/deepseek-llm-api-extensions + '@deepseek-ai/dsh-fs-local': + specifier: workspace:^ + version: link:../../fs/fs-local + '@deepseek-ai/dsh-llm-deepseek': + specifier: workspace:^ + version: link:../../llm/llm-deepseek + '@deepseek-ai/dsh-plugin-package-inventory-deepseek': + specifier: workspace:^ + version: link:../../llm/plugin-package-inventory-deepseek + '@deepseek-ai/dsh-sandbox-local': + specifier: workspace:^ + version: link:../../sandbox/sandbox-local + '@deepseek-ai/dsh-sandbox-policy': + specifier: workspace:^ + version: link:../../sandbox/sandbox-policy + '@deepseek-ai/dsh-sdk-app': + specifier: workspace:^ + version: link:../sdk-app + '@deepseek-ai/dsh-sdk-jsonrpc-server': + specifier: workspace:^ + version: link:../../sdk/server + '@deepseek-ai/dsh-session-log-deepseek': + specifier: workspace:^ + version: link:../../session/session-log-deepseek + '@deepseek-ai/dsh-session-persistence-jsonl': + specifier: workspace:^ + version: link:../../session/session-persistence-jsonl + '@deepseek-ai/dsh-subprocess-local': + specifier: workspace:^ + version: link:../../subprocess/subprocess-local + '@deepseek-ai/dsh-terminal': + specifier: workspace:^ + version: link:../../terminal/terminal + '@deepseek-ai/dsh-terminal-bash': + specifier: workspace:^ + version: link:../../terminal/terminal-bash + '@deepseek-ai/dsh-tool-bash-persistent': + specifier: workspace:^ + version: link:../../shell/tool-bash-persistent + '@deepseek-ai/dsh-tool-str-replace-editor': + specifier: workspace:^ + version: link:../../fs/tool-str-replace-editor + devDependencies: + '@deepseek-ai/cordis': + specifier: workspace:^ + version: link:../../../vendor/cordis + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../runtime-diagnostics/invariants + packages/bundle/web-app: dependencies: '@deepseek-ai/dsh-agent-presets': diff --git a/tsconfig.host.json b/tsconfig.host.json index 91fbfd03f7..0bcbe50df3 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -263,6 +263,7 @@ { "path": "./packages/bundle/base" }, { "path": "./packages/bundle/headless" }, { "path": "./packages/bundle/sdk-app" }, + { "path": "./packages/bundle/sdk-minimal" }, { "path": "./packages/bundle/web-app" }, { "path": "./packages/boot/app-boot" }, { "path": "./packages/boot/cmdline" },