diff --git a/.changeset/deepseek-install-pnpm-path.md b/.changeset/deepseek-install-pnpm-path.md new file mode 100644 index 00000000..412d1264 --- /dev/null +++ b/.changeset/deepseek-install-pnpm-path.md @@ -0,0 +1,5 @@ +--- +'aicodeman': patch +--- + +Installing DeepSeek from CLI management no longer leaves it "not installed". The `dsh` terminal profile install ran `dsh plugin`, which needs `pnpm` on its PATH, but the server's own PATH usually lacks the user prefix (`~/.local/bin`) where CLI management puts `dsh`. The profile install now puts `dsh`'s directory first on PATH, and the DeepSeek install command installs `pnpm` alongside `dsh`. diff --git a/config/clis.stock.json b/config/clis.stock.json index 5a86af0e..d1d3e609 100644 --- a/config/clis.stock.json +++ b/config/clis.stock.json @@ -238,8 +238,8 @@ }, "install": { "command": { - "linux": "npm install -g @deepseek-ai/dsh", - "darwin": "npm install -g @deepseek-ai/dsh" + "linux": "npm install -g @deepseek-ai/dsh pnpm", + "darwin": "npm install -g @deepseek-ai/dsh pnpm" }, "npmPackage": "@deepseek-ai/dsh", "docsUrl": "https://github.com/deepseek-ai/deepseek-harness", diff --git a/src/config/cli-registry/stock.ts b/src/config/cli-registry/stock.ts index ae497207..6a0b0af6 100644 --- a/src/config/cli-registry/stock.ts +++ b/src/config/cli-registry/stock.ts @@ -1259,8 +1259,10 @@ const DEEPSEEK: CliEntry = { launcherTargetParam: 'profile', install: { command: { - linux: 'npm install -g @deepseek-ai/dsh', - darwin: 'npm install -g @deepseek-ai/dsh', + // pnpm rides along: `dsh plugin` spawns a literal `pnpm` with no npm fallback, so the + // terminal profile this CLI needs before it can run cannot be installed without it (#352). + linux: 'npm install -g @deepseek-ai/dsh pnpm', + darwin: 'npm install -g @deepseek-ai/dsh pnpm', }, npmPackage: '@deepseek-ai/dsh', docsUrl: 'https://github.com/deepseek-ai/deepseek-harness', diff --git a/src/utils/deepseek-cli-resolver.ts b/src/utils/deepseek-cli-resolver.ts index b815a761..81a69584 100644 --- a/src/utils/deepseek-cli-resolver.ts +++ b/src/utils/deepseek-cli-resolver.ts @@ -84,7 +84,7 @@ export const DEEPSEEK_VERSION_REGEX = /(?:^|\s)v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z][0 */ export const DEEPSEEK_IDENTITY_REGEX = /DeepSeek\s+Harness/i; -const DEEPSEEK_NOT_FOUND = 'DeepSeek Harness CLI (dsh) not found. Install with: npm install -g @deepseek-ai/dsh'; +const DEEPSEEK_NOT_FOUND = 'DeepSeek Harness CLI (dsh) not found. Install with: npm install -g @deepseek-ai/dsh pnpm'; /** Where profiles live: `$DSH_HOME/profiles`, defaulting to `~/.dsh/profiles`. */ export function resolveDshHome(): string { diff --git a/src/web/public/session-ui.js b/src/web/public/session-ui.js index 3ec17fe0..91771570 100644 --- a/src/web/public/session-ui.js +++ b/src/web/public/session-ui.js @@ -92,7 +92,7 @@ const RUN_MODE_LAUNCH = { }, deepseek: { label: 'DeepSeek', - installHint: 'DeepSeek Harness CLI (dsh) not found. Install with: npm install -g @deepseek-ai/dsh', + installHint: 'DeepSeek Harness CLI (dsh) not found. Install with: npm install -g @deepseek-ai/dsh pnpm', // The two-part availability check is deliberate. `dsh` being installed is // not enough — DeepSeek ships no terminal front door, so a box can have a // perfect binary and nothing a pane can run. diff --git a/src/web/routes/system-routes.ts b/src/web/routes/system-routes.ts index e2251c24..91f49b04 100644 --- a/src/web/routes/system-routes.ts +++ b/src/web/routes/system-routes.ts @@ -617,13 +617,16 @@ export function registerSystemRoutes( // synchronous-spawn precedent this endpoint is modelled on. detached: true, // Inherit the environment: this needs a HOME to resolve $DSH_HOME - // against, and a PATH carrying `pnpm`. ⚠️ `dsh plugin` does NOT bundle a + // against, and a PATH carrying `pnpm` (see the PATH below). ⚠️ `dsh plugin` does NOT bundle a // package manager — it `spawnSync`s a literal `pnpm` with no npm // fallback, so on a host without one this exits 127 and dsh's own // stderr ("pnpm not found on PATH") is what reaches the caller through // the OPERATION_FAILED detail below. That is the same missing // dependency that broke the docker agent image in issue #352. - env: process.env, + // PATH leads with the directory `dsh` itself resolved to: CLI management installs + // `dsh` and `pnpm` into the user prefix (`~/.local/bin`), which a service's own PATH + // usually lacks, so `dsh plugin` could not find the `pnpm` sitting next to it. + env: { ...process.env, PATH: [dir, process.env.PATH].filter(Boolean).join(':') }, }); } catch (err) { resolve({ code: null, output: `spawn failed: ${getErrorMessage(err)}`, timedOut: false }); diff --git a/test/deepseek-install-profile-path.test.ts b/test/deepseek-install-profile-path.test.ts new file mode 100644 index 00000000..db88264d --- /dev/null +++ b/test/deepseek-install-profile-path.test.ts @@ -0,0 +1,18 @@ +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { STOCK_CLIS } from '../src/config/cli-registry/stock.js'; + +const root = resolve(import.meta.dirname, '..'); + +describe('DeepSeek install', () => { + it('installs pnpm with dsh, since `dsh plugin` spawns a literal pnpm', () => { + const ds = STOCK_CLIS.find((c) => (c.id as string) === 'deepseek')!; + expect(ds.discovery.install.command?.linux).toBe('npm install -g @deepseek-ai/dsh pnpm'); + }); + + it("puts dsh's own directory first on the profile install's PATH", () => { + const src = readFileSync(resolve(root, 'src/web/routes/system-routes.ts'), 'utf8'); + expect(src).toMatch(/env: \{ \.\.\.process\.env, PATH: \[dir, process\.env\.PATH\]/); + }); +});