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" },