fix(cli): keep source launch output clean

This commit is contained in:
Turtle
2026-08-10 19:29:13 +08:00
parent 2765de87c8
commit e59af75554
9 changed files with 75 additions and 13 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # 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: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-10-source-run-without-managed-installer.md # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-10-source-run-without-managed-installer.md
2026-08-10-source-run-without-managed-installer.md: 35f0c705731147ce8bb64c2e985789aff341996c 2026-08-10-source-run-without-managed-installer.md: ecc4530b90a495f210476f100e894a7ce77cdb63
2026-08-10-source-run-without-managed-installer.zh.md: 51b336f4c399ebfd24f499288eee477bed9f23cb 2026-08-10-source-run-without-managed-installer.zh.md: 940bc6245bacc91abea4e2c746d81dbce4a50105

View File

@@ -12,7 +12,7 @@ That lifecycle is not required to run or develop DeepSeek Harness from a source
## Decision ## Decision
The repository supports source execution through its root `pnpm` scripts. The `pnpm dsh <args...>` script runs the complete repository build before launching the source CLI; users select Web with `pnpm dsh web` and headless execution with `pnpm dsh run`. The independent ACP example remains available through `pnpm run demo:acp`. The repository supports source execution through its root `pnpm` scripts. The `pnpm dsh <args...>` launcher runs the complete repository build before launching the source CLI. It discards successful build output so CLI stdout remains machine-readable, reports failed build diagnostics on stderr, and sets `NODE_USE_ENV_PROXY=1` for the CLI process. Users select Web with `pnpm dsh web` and headless execution with `pnpm dsh run`. The independent ACP example remains available through `pnpm run demo:acp`.
The repository does not distribute a source installer, an installer test suite, or skills that assume a managed `current` symlink and timestamped staging worktrees. Users own source checkout placement, Git updates, and any launcher they create outside the repository. The repository does not distribute a source installer, an installer test suite, or skills that assume a managed `current` symlink and timestamped staging worktrees. Users own source checkout placement, Git updates, and any launcher they create outside the repository.
@@ -28,4 +28,4 @@ The repository does not distribute a source installer, an installer test suite,
Source users invoke repository scripts rather than an installed `dsh` command. The repository provides no atomic upgrade cutover or preserved staging rollback checkout, and it does not automate the integration or upstream publication of personal source modifications. A future distribution mechanism must justify its ownership of installation and upgrade state, define recovery behavior, and add tests and user documentation without making the source-run path depend on it. Any future publication workflow must isolate one approved feature and obtain explicit approval before its first push and draft PR. Source users invoke repository scripts rather than an installed `dsh` command. The repository provides no atomic upgrade cutover or preserved staging rollback checkout, and it does not automate the integration or upstream publication of personal source modifications. A future distribution mechanism must justify its ownership of installation and upgrade state, define recovery behavior, and add tests and user documentation without making the source-run path depend on it. Any future publication workflow must isolate one approved feature and obtain explicit approval before its first push and draft PR.
Verification covers repository-wide references to the removed entry points, documentation links, generated third-party-notice freshness, and a clean-artifact source CLI smoke through `pnpm dsh`. Verification covers repository-wide references to the removed entry points, documentation links, generated third-party-notice freshness, and source CLI smokes that verify the build-first launch and absence of build logs on stdout through `pnpm dsh`.

View File

@@ -12,7 +12,7 @@ Status: implemented
## 决策 ## 决策
仓库通过根目录的 `pnpm` 脚本支持从源码运行。`pnpm dsh <args...>` 脚本会先完成整个仓库的构建,再启动源码 CLI(命令行界面);用户使用 `pnpm dsh web` 选择 Web,使用 `pnpm dsh run` 选择无头执行。独立的 ACP(Agent Client Protocol)示例仍可通过 `pnpm run demo:acp` 运行。 仓库通过根目录的 `pnpm` 脚本支持从源码运行。`pnpm dsh <args...>` 启动器会先完成整个仓库的构建,再启动源码 CLI(命令行界面)。它会丢弃成功构建的输出,使 CLI stdout 保持机器可读;在 stderr 中报告构建失败的诊断信息;并为 CLI 进程设置 `NODE_USE_ENV_PROXY=1`。用户使用 `pnpm dsh web` 选择 Web,使用 `pnpm dsh run` 选择无头执行。独立的 ACP(Agent Client Protocol)示例仍可通过 `pnpm run demo:acp` 运行。
仓库不分发源码安装器、安装器测试套件,也不分发依赖受管理的 `current` 符号链接和带时间戳 staging worktree 的 skill。源码检出的存放位置、Git 更新,以及用户在仓库外创建的任何启动器均由用户负责。 仓库不分发源码安装器、安装器测试套件,也不分发依赖受管理的 `current` 符号链接和带时间戳 staging worktree 的 skill。源码检出的存放位置、Git 更新,以及用户在仓库外创建的任何启动器均由用户负责。
@@ -28,4 +28,4 @@ Status: implemented
源码用户通过仓库脚本运行程序,而非使用已安装的 `dsh` 命令。仓库不提供原子升级切换,也不保留 staging 回滚检出;仓库同样不会自动集成个人源码修改或将其发布到上游。未来的分发机制必须说明为何应由其管理安装和升级状态,定义恢复行为,并补充测试与用户文档,同时不得让源码运行路径依赖该机制。未来任何发布工作流都必须隔离出一项获批功能,并在首次推送和创建草稿 PR(Pull Request)前取得明确批准。 源码用户通过仓库脚本运行程序,而非使用已安装的 `dsh` 命令。仓库不提供原子升级切换,也不保留 staging 回滚检出;仓库同样不会自动集成个人源码修改或将其发布到上游。未来的分发机制必须说明为何应由其管理安装和升级状态,定义恢复行为,并补充测试与用户文档,同时不得让源码运行路径依赖该机制。未来任何发布工作流都必须隔离出一项获批功能,并在首次推送和创建草稿 PR(Pull Request)前取得明确批准。
验证范围包括仓库内对已移除入口点的所有引用、文档链接、生成的第三方声明文件的新鲜度,以及在无构建产物状态下通过 `pnpm dsh` 启动源码 CLI 的冒烟测试。 验证范围包括仓库内对已移除入口点的所有引用、文档链接、生成的第三方声明文件的新鲜度,以及通过 `pnpm dsh` 对源码 CLI 进行冒烟测试,验证先构建后启动且 stdout 不含构建日志。

View File

@@ -16,6 +16,22 @@ const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
const dshSourceBin = 'apps/cli/src/bin.ts' const dshSourceBin = 'apps/cli/src/bin.ts'
describe('dsh SOURCE launcher (node --import tsx/esm)', () => { describe('dsh SOURCE launcher (node --import tsx/esm)', () => {
it('builds without mixing build logs into CLI stdout', async () => {
const result = await execa('pnpm', ['dsh', '--help'], {
cwd: repoRoot,
timeout: 120_000,
killSignal: 'SIGKILL',
reject: false,
})
if (result.timedOut) {
throw new Error(`pnpm dsh --help did not exit within 120s. stdout:\n${result.stdout}\nstderr:\n${result.stderr}`)
}
expect(result.exitCode).toBe(0)
expect(result.stdout).toMatch(/^Usage: dsh /)
expect(result.stdout).not.toContain('tsdown')
expect(result.stdout).not.toContain('build:lib')
}, 125_000)
it('boots the source entry and requires a profile', async () => { it('boots the source entry and requires a profile', async () => {
const result = await execa(process.execPath, ['--import', 'tsx/esm', dshSourceBin], { const result = await execa(process.execPath, ['--import', 'tsx/esm', dshSourceBin], {
cwd: repoRoot, cwd: repoRoot,

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # 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: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/user/guide/quickstart.md # pnpm run verify-translation-pairing --write docs/user/guide/quickstart.md
quickstart.md: 540eed86bc83e70f3a9724f601fd71170996ce0b quickstart.md: f6537c71fc94096b2daf49827d041979346632bb
quickstart.zh.md: ab529333eb3c62add8cf422328b0ba838efc82e6 quickstart.zh.md: fbecabb964e3786929b3b942f49b5b44a4437e05

View File

@@ -19,10 +19,9 @@ pnpm -v
## Step 1: install and configure the API key ## Step 1: install and configure the API key
```sh ```sh
git clone https://github.com/deepseek-ai/deepseek-harness-sdk.git git clone https://github.com/deepseek-ai/deepseek-harness-sdk.git deepseek-harness
cd deepseek-harness cd deepseek-harness
pnpm install pnpm install
pnpm run build
``` ```
Create the gitignored repository-root `.env`: Create the gitignored repository-root `.env`:

View File

@@ -19,10 +19,9 @@ pnpm -v
## 第一步:安装并配置 API 密钥 ## 第一步:安装并配置 API 密钥
```sh ```sh
git clone https://github.com/deepseek-ai/deepseek-harness-sdk.git git clone https://github.com/deepseek-ai/deepseek-harness-sdk.git deepseek-harness
cd deepseek-harness cd deepseek-harness
pnpm install pnpm install
pnpm run build
``` ```
在仓库根目录创建已被 Git 忽略的 `.env`: 在仓库根目录创建已被 Git 忽略的 `.env`:

View File

@@ -127,7 +127,7 @@
"release:pack": "tsx scripts/release/pack.ts", "release:pack": "tsx scripts/release/pack.ts",
"release:verify-packed-install": "tsx scripts/release/verify-packed-install.ts", "release:verify-packed-install": "tsx scripts/release/verify-packed-install.ts",
"release:publish": "tsx scripts/release/publish.ts", "release:publish": "tsx scripts/release/publish.ts",
"dsh": "npm run build && node --import tsx/esm apps/cli/src/bin.ts", "dsh": "tsx scripts/run-source-dsh.ts",
"demo:code-mode": "node scripts/demo-code-mode.mjs", "demo:code-mode": "node scripts/demo-code-mode.mjs",
"demo:cordis": "node scripts/demo-cordis.mjs", "demo:cordis": "node scripts/demo-cordis.mjs",
"demo:acp": "node --import tsx packages/examples/acp-demo/src/bin.ts --config examples/acp-agent/cordis.yml", "demo:acp": "node --import tsx packages/examples/acp-demo/src/bin.ts --config examples/acp-agent/cordis.yml",

48
scripts/run-source-dsh.ts Normal file
View File

@@ -0,0 +1,48 @@
/**
* Source-checkout launcher. Successful build output stays out of CLI stdout;
* build failures report their captured diagnostics on stderr before exiting.
*/
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { execa } from 'execa'
const repoRoot = fileURLToPath(new URL('..', import.meta.url))
const sourceBin = fileURLToPath(new URL('../apps/cli/src/bin.ts', import.meta.url))
function completeFrom(result: { readonly exitCode?: number; readonly signal?: string }): void {
if (result.signal !== undefined) {
process.kill(process.pid, result.signal)
return
}
process.exitCode = result.exitCode ?? 1
}
function reportBuildFailure(result: {
readonly all: string | undefined
readonly shortMessage: string | undefined
}): void {
const diagnostic = result.all === undefined || result.all.length === 0
? result.shortMessage ?? 'Source build failed without diagnostics.'
: result.all
process.stderr.write(diagnostic.endsWith('\n') ? diagnostic : `${diagnostic}\n`)
}
const build = await execa('pnpm', ['run', 'build'], {
all: true,
cwd: repoRoot,
reject: false,
stripFinalNewline: false,
})
if (build.failed) {
reportBuildFailure(build)
completeFrom(build)
} else {
const cli = await execa(process.execPath, ['--import', 'tsx/esm', sourceBin, ...process.argv.slice(2)], {
cwd: repoRoot,
env: { ...process.env, NODE_USE_ENV_PROXY: '1' },
reject: false,
stdio: 'inherit',
})
completeFrom(cli)
}