Reading the repo
Reading the repo
How to install on Linux
DeepSeek Harness: Everything is a Plugin.
Saved setup from 4 h ago
Will it work on my device?
docs/development.mdEnglish | 中文
The setup tutorial takes a new contributor from prerequisites to a checked checkout. The contributor reference that follows covers repository layout, daily workflow, and CI organization. Design rationale and implementation details belong to the linked Agent Notes and scripts.
tsdown.config.ts with tsdown's native config loader, so they fail when --no-experimental-strip-types is in NODE_OPTIONS or the Node.js build lacks TypeScript support; pnpm run build checks this first and names the cause.pnpm@11.28.5 in package.json; run corepack enable if pnpm --version does not resolve through Corepack.On Windows, you can develop with native tools or use WSL 2 for a Linux environment. WSL 2 is useful for verifying Linux behavior and for using Linux toolchains when native dependency compilation or filesystem permissions obstruct Windows development. Each environment needs its own runtime, build tools, and permissions; WSL is optional.
Keep the checkout, installed dependencies, and toolchain in the same operating system environment. For WSL 2, store the checkout in the Linux filesystem; for native Windows tools, use the Windows filesystem. Accessing files across the two filesystems adds overhead to I/O-intensive operations such as Git, dependency installation, and builds. See Microsoft's file storage and performance guidance.
Install dependencies separately in each environment because native binaries and links can differ between operating systems. Test results apply to the environment where the tests ran; Windows-specific behavior still needs native Windows validation.
pnpm uses its strict symlinked linker so undeclared dependencies fail instead of relying on hoisting. Keep dependency build scripts explicitly allowlisted in pnpm-workspace.yaml.
Install dependencies from the repo root:
pnpm install
The install also configures worktree-local Lefthook hooks through scripts/install-lefthook.mjs. The worktree-local hooks Agent Note owns the hook-path safety contract.
The DevTools frontend is compiled locally from a pinned npm source package and Vite. After dependencies are installed, its build needs no network access or browser installation. Workspace and published-package installation run no DevTools resource-download hook; published Inspector packages contain the built frontend.
If the hooks are missing because dependencies were restored from cache or postinstall was skipped, install them manually:
node scripts/install-lefthook.mjs
If the wrapper rejects existing Git configuration or reports a stale lock, follow its diagnostic and the linked Agent Note rather than editing worktree metadata speculatively. After moving a checkout, rerun the wrapper to regenerate the owned path.
Run typecheck once after a fresh clone:
pnpm run typecheck
Setup is complete when pnpm run typecheck exits successfully.
The repository uses isolated Host and Client aggregates. An ordinary package is registered in exactly one aggregate: Host packages in tsconfig.host.json and Client packages in tsconfig.client.json; three packages (host/webserver, compaction/compaction, typert/registry) are referenced by both aggregates as shared leaves so each side type-checks the same source.
| File | Role | Forms a program? |
|---|---|---|
| tsconfig.json | Solution root: extends base, files: [], and references to the two aggregates. It is the tsserver discovery entry and the entry for explicitly running the complete Project Reference graph; through the inherited paths, it is also the resolution config for tsx running scripts/. | No |
| tsconfig.host.json | Host aggregate: Host packages, examples, tests, scripts, website, and the exceptional Host project of api/remotes. | Yes |
| tsconfig.client.json | Client aggregate: packages/client/* packages and their tests, apps/web, and the exceptional Client project of api/remotes. | Yes |
| tsconfig.base.json | Shared compilerOptions and the source paths map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no include, so its paths apply to every importer. | No |
| tsconfig.base.client.json | Browser compiler settings (jsx, DOM libs, types: []) extended by the Client aggregate and every packages/client/* package. | No |
Host and Client aggregate type checks run with an 8 GiB Node old-space limit. This is a per-compiler limit; concurrent build steps need additional memory. pnpm run build:official --serial prevents build steps from overlapping, even with DSH_GATE_CONCURRENCY set; the DSH publication workflow uses this option.
Host and Client stay two aggregate programs because both sides declaration-merge the cordis Context interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a ts.Program — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Three disciplines follow:
tsconfig.base.json never gains include or files: they would leak into every extending package project and narrow the facade's match-all scope.ts.Program seeds tsconfig.host.json or tsconfig.client.json explicitly — never the root solution, because flattening both aggregates into one program collides the Context merges.Seven packages split Host and Client tsconfigs: api/remotes, api/gateway, api/session-controller, api/workspace-controller, client/connection, session-query/session-log-export, and experimental/liveshare-protocol. api/remotes' Host entry participates in the Host Typert graph while its Client entry imports generated /remote declarations; session-log-export keeps Node archive production out of its browser controller; liveshare-protocol keeps the Node wire codecs on its Host leaf and the browser viewer carrier on its Client leaf. Each split package-root tsconfig.json is therefore only a solution, and the two aggregates and direct consumers reference tsconfig.host.json or tsconfig.client.json respectively. The workspace constraints gate walks the reachable Project Reference graph and checks each referencing project's own compiler face: a single-config target remains valid from either face, while a split target must name the matching leaf rather than its solution root or opposite leaf; it discovers split packages from the presence of both leaf configs, so a new split joins the gate automatically. The api-remotes README and session-log-export README explain their splits.
pnpm run build:artifacts emits every artifact, checking and compiling only the package references of each compiler face. Client compilation waits for Host and Desktop bundling to finish: split compiler faces rewrite shared lib/types files read by those bundles. Client bundling follows Client compilation. The artifact order is:
pnpm run build:native-system
pnpm exec tsx scripts/generate-typert.ts
tsc -b <tsconfig.host.json references>
tsdown --env.DSH_BUILD_FACE host
pnpm --filter @deepseek-ai/dsh-desktop run bundle
tsc -b <tsconfig.client.json references>
tsdown --env.DSH_BUILD_FACE client
pnpm run build:web
pnpm run build:check runs only tsc -p tsconfig.host.json and tsc -p tsconfig.client.json concurrently. Project mode checks each aggregate's repository-wide test and script program against the package declarations already on disk and never compiles a reference, so build:check neither detects nor rebuilds missing or stale build:artifacts output; missing output surfaces as compiler errors. pnpm run build runs both step sets as one scheduled dependency graph: each aggregate check starts once its own face's package compilation finishes and overlaps the remaining artifact steps. In all three commands DSH_GATE_CONCURRENCY bounds concurrent steps (1 runs them in dependency order) and DSH_GATE_FAIL_FAST=1 stops the remaining steps at the first failure; otherwise a failure skips only its dependents, so independent errors are all reported. A failed step prints its own output, and any failure exits non-zero without writing the client build record. Host TypeScript compiler processes receive an 8 GiB V8 old-space limit for the package graph and cold aggregate checks.
Choose the command by what the change needs:
| Situation | Command |
|---|---|
| Iterating on package sources and needing runnable artifacts | pnpm run build:artifacts; dev:web and dev:desktop run it before launching |
| Type-checking tests, scripts, benchmarks, or website code | pnpm run build:check after the latest build:artifacts; rerun build:artifacts first when package sources changed |
| Before declaring TypeScript work complete | pnpm run build:check after the latest build:artifacts, or pnpm run build |
| Complete verification, as the required CI build lanes run it | pnpm run build |
| Type checks without the Web and Desktop artifacts | pnpm run typecheck |
| Lint or documentation code-block checks | pnpm run lint or pnpm run doc-typecheck; each prepares host-contracts itself |
PR artifact builders run build:artifacts; required Linux and Windows normal builds and public typecheck also check the repository-wide test and script programs. CI benchmark preparation additionally uses --noCheck for its disposable library outputs, relying on those required builds for diagnostics; other artifact builders keep package checking (rationale).
Both tsdown passes match vendor/*, packages/*/*, and apps/cli; the Host pass also matches apps/desktop-host. They neither scan build artifacts to discover Client packages nor maintain a Host/Client package filter list. Package-local tsdown configs select entries for the current phase through DSH_BUILD_FACE: an ordinary Client plugin produces both its Node loader and browser bundle during the Client phase; api-remotes uses hostPhase: true to produce its Host entry early and only its browser bundle during the Client phase. Tsdown consumes only the JavaScript emitted to lib/types by the preceding tsc phase. Tsdown builds the matched workspace members concurrently, so apps/desktop, whose main bundle inlines workspace devDependencies from their lib/ output, bundles in its own step after the Host pass (Desktop README).
Typert analyzes Host types through tsconfig.host.json and generates Host reflection and Host-for-Client Remote declarations before package compilation. The source-only generate-typert.ts pass rejects workspace imports that require built output, including metadata dependencies that return to generated declarations. Host tsdown lowers source decorators without generating metadata again; Client tsdown also consumes the existing metadata. pnpm run typecheck generates metadata, compiles Host and Client packages in reference order, and checks each aggregate with tsc -b after its package compilation. It skips the native addon, bundles, Web, and the client build record. lint, lint:fix, and doc-typecheck prepare metadata and Host package declarations through compile-referenced-projects.ts host-contracts; generation failure stops dependent compilation. These declaration-only commands do not update runtime bundles: run build:artifacts before launching or testing built output.
pnpm run build and build:artifacts embed the root package version, the seven-character source commit, and a dirty marker when Git reports local changes; they also inherit other caller-supplied DSH_CLIENT_* values. pnpm run build:official is the cross-platform local equivalent of the CI and release artifact build and omits the local dirty marker. Each successful build or build:artifacts writes a gitignored record that binds the exact public values to the Vite output and dynamic client bundles; release packing and built Web tests reject a missing record or artifacts changed by a later partial build. pnpm run dev:web runs build:artifacts first (--skip-build reuses an existing artifact tree instead), then samples the current version and Git state once and shares that environment across every watcher stage for the session; it does not validate the complete-build record because the watcher stages rewrite its recorded artifacts.
pnpm run build --profile local explicitly selects local client branding, preserves repository version, commit and dirty metadata, and discards inherited client title and other public overrides. build:official retains its exact official values. Desktop packaging selects the profile for each signing mode.
Static analysis and tests resolve workspace imports through the base paths map to src and must pass on a clean tree; gates that consume built lib/ output declare that dependency explicitly. Generated Host-for-Client Remote declarations are the deliberate exception: the public typecheck, lint, and doc-typecheck commands generate them first, while internal *:contracts-ready scripts assume that an invoking public command or scheduler gate already depends on the Typert contract-generation pass or the complete build. See the Typert Remote note for the gate-preparation contract.
Business services declare callable methods on the Host with @Remote or @RemoteScope; the Host build generates Host-for-Client types and runtime contributions, and the Client's api-remotes composition loads those contributions under ctx.remote and scoped agentCtx.remote namespaces. See API Gateway for the generated artifacts on both sides, their assembly relationships, the SRC development fallback, and the Web build order.
If a relevant local check consumes built package output, build once first:
pnpm run build
pnpm run hygiene includes publint, which validates package entrypoints against the built lib/*.js files, and verify-node-next-types, which validates built declarations against a temporary NodeNext consumer. A fresh worktree has no bundled JS or declarations until pnpm run build runs; ordinary commits and pushes do not require that build unless their selected checks consume it.
The real DeepSeek adapter and key-backed agent demos read credentials from the environment or from a gitignored .env at the repo root:
DEEPSEEK_API_KEY=sk-...
DEEPSEEK_BASE_URL=https://... # optional
DEEPSEEK_BASE_URL is optional and defaults to the public API. Never commit real credentials. The real-API e2e suites self-skip when DEEPSEEK_API_KEY is not set.
.i18n.yaml records use Git's default text merge. A record conflicts only when both branches changed language-specific content in the same heading section; resolve the Markdown, then rerun pnpm run verify-translation-pairing --write <pair>. If pre-merge-commit rejects an otherwise clean merge, Git leaves the complete result staged without a commit; repair the failure and run git commit, or run git merge --abort.
lefthook is configured in lefthook.yml as a fast local checkpoint:
pre-commit verifies staged pairing records against the staged owner blobs, validates staged files with the project-free .oxlintrc.staged.json profile and applies Oxlint fixes with one bounded retry, checks the staged diff for whitespace errors, checks pi-ai provider metadata when its inputs or generated file are staged, and runs the vendor manifest guard.pre-merge-commit performs the same index-backed pairing check before Git creates an automatic merge commit.pre-push runs pnpm run typecheck, which generates Typert contracts without bundling before the Client TypeScript check and checks both aggregates.The vendor manifest guard checks that changes under vendor/*/src are staged with the matching vendor/README.md manifest update. See vendor/README.md before editing vendored code.
The pi-ai check regenerates expected metadata in memory. It rejects stale data without rewriting or staging files. After a pi-ai dependency or generator change, follow the package's provider metadata update steps.
Apart from the scoped pairing and provider metadata checks, the hooks intentionally do not run tests, snapshots, documentation checks, builds, or hygiene. Contributors run the checks relevant to the changed behavior once; CI owns exhaustive coverage, built-artifact smokes, and the Node 24.9, latest 24, and 26 compatibility matrix.
Contributors can opt into the comprehensive local gate set with pnpm run check:all. The command is independent of the Git hooks and is not an agent instruction.
Outside CI (CI other than true), default worker counts follow the local worker budget: 12 for gate schedulers, build, typecheck, Oxlint, and publint, and 48 for Vitest forks and local coverage. doc-sync, test:docs, hygiene, and check:all run at most 8 gates at once, in CI as well. os.availableParallelism() reports host CPUs rather than a container's CPU quota, so uncapped defaults oversubscribe shared hosts. CI keeps the runner's CPU count. DSH_GATE_CONCURRENCY, DSH_OXLINT_THREADS, DSH_COVERAGE_MAX_WORKERS, and Vitest --maxWorkers override the defaults.
run-gates gives the CLI help generator one child at a time; direct pnpm run verify-cli-help uses the standalone worker budget. DSH_CLI_HELP_CONCURRENCY overrides that child limit with a positive integer validated before startup. Each invocation retains its private DSH_HOME and DSH_AGENTS_HOME and 30-second deadline.
The keyless CI workflow groups independent gates into broad lanes and runs a smaller compatibility signal across supported Node versions. Artifact consumers wait for one build within their lane. Coverage partitions reuse file timings scoped to their platform and runner pool to balance work within the configured worker limit. Native Windows lanes run in the separate CI Windows workflow and
Then run bash setup.sh. It stops at the first error and is safe to run again. Servers and tests aren't started; it lists them at the end. Read it first.
git clone https://github.com/deepseek-ai/deepseek-harness.git cd deepseek-harness
Node.js >=24.9.0
# needs nvm: https://github.com/nvm-sh/nvm nvm install --lts
corepack enable pnpm install
pnpm test
npmInstall Node.js, then run:
npx @deepseek-ai/dsh web
The command starts the Web UI at http://127.0.0.1:3080 by default and opens it in the default browser for a local launch. An SSH launch only prints the host URL because the SSH client or editor owns the local forwarded address. Pass --no-open to run the server without opening a browser. See Web UI guide.
To run from a repository checkout:
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web
pnpm run build prepares the repository artifacts. pnpm dsh web uses those built artifacts without rebuilding.
No reports for Linux yet.
Opens a chat with this manual already filled in, for Linux. It uses your own account; gitmanual sends nothing.