fix(setup): resolve DeepSeek Harness home overrides (#2461)
This commit is contained in:
parent
c95315d2ce
commit
1dd6c2dff9
|
|
@ -209,8 +209,15 @@ replaces) the REA entry in the home patch `$DSH_HOME/cordis.patch.yml`
|
|||
patches and comments. `rea doctor --client dsh` checks that same entry id.
|
||||
`rea uninstall` removes it by scanning supported clients; uninstall takes no
|
||||
`--client` flag. The home patch applies to every Harness profile, so the
|
||||
registration is written once and no per-profile edit is needed. The written
|
||||
entry is:
|
||||
registration is written once and no per-profile edit is needed.
|
||||
|
||||
Setup, doctor and uninstall resolve `DSH_HOME` and `DSH_AGENTS_HOME` as Harness
|
||||
does: unset, empty or whitespace-only values use `~/.dsh` and `~/.agents`,
|
||||
respectively. Bare `~`, `~/` and `~\` expand to the user home; other relative
|
||||
values resolve against the command's working directory. Nonblank values retain
|
||||
their original spaces, and the agents root is independent of the Harness home.
|
||||
Run REA and Harness from the same working directory when using relative overrides.
|
||||
The written entry is:
|
||||
|
||||
```yaml
|
||||
- insert:
|
||||
|
|
|
|||
|
|
@ -157,8 +157,27 @@ const qwenCodeDirectory = ({ home, env }: ClientPathContext): string => {
|
|||
const grokDirectory = ({ home, env }: ClientPathContext): string =>
|
||||
env.GROK_HOME || join(home, ".grok");
|
||||
|
||||
/** Match Harness home-paths for both independent roots; trim only to detect blanks. */
|
||||
const dshDirectory = (
|
||||
home: string,
|
||||
override: string | undefined,
|
||||
fallback: string,
|
||||
): string => {
|
||||
const selected =
|
||||
override !== undefined && override.trim().length > 0
|
||||
? override
|
||||
: join(home, fallback);
|
||||
const expanded =
|
||||
selected === "~"
|
||||
? home
|
||||
: selected.startsWith("~/") || selected.startsWith("~\\")
|
||||
? join(home, selected.slice(2))
|
||||
: selected;
|
||||
return resolve(expanded);
|
||||
};
|
||||
|
||||
const dshHomeDirectory = ({ home, env }: ClientPathContext): string =>
|
||||
env.DSH_HOME ?? join(home, ".dsh");
|
||||
dshDirectory(home, env.DSH_HOME, ".dsh");
|
||||
|
||||
const dshPatchDirectory = (context: ClientPathContext): string =>
|
||||
join(dshHomeDirectory(context), "cordis.patch.yml");
|
||||
|
|
@ -555,7 +574,7 @@ export const SUPPORTED_CLIENT_DEFINITIONS = [
|
|||
// The home Cordis patch applies to every profile; the skill directory is
|
||||
// shared agent storage Harness discovers.
|
||||
skillPath: ({ home, env }: ClientPathContext) =>
|
||||
join(env.DSH_AGENTS_HOME ?? join(home, ".agents"), "skills"),
|
||||
join(dshDirectory(home, env.DSH_AGENTS_HOME, ".agents"), "skills"),
|
||||
configPath: dshPatchDirectory,
|
||||
markerPath: dshHomeDirectory,
|
||||
format: "dsh",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { dirname, join } from "node:path";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { expect } from "vitest";
|
||||
|
|
@ -8,6 +8,111 @@ import { z } from "zod";
|
|||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest.for([
|
||||
{
|
||||
label: "blank defaults",
|
||||
harness: "",
|
||||
agents: " \t ",
|
||||
dshRoot: "home/.dsh",
|
||||
agentsRoot: "home/.agents",
|
||||
},
|
||||
{
|
||||
label: "tilde roots",
|
||||
harness: "~/selected harness",
|
||||
agents: "~\\shared agents",
|
||||
dshRoot: "home/selected harness",
|
||||
agentsRoot: "home/shared agents",
|
||||
},
|
||||
{
|
||||
label: "relative roots",
|
||||
harness: "./selected harness",
|
||||
agents: "../shared agents",
|
||||
dshRoot: "workspace/selected harness",
|
||||
agentsRoot: "shared agents",
|
||||
},
|
||||
])(
|
||||
"DSH CLI uses Harness path semantics through setup, doctor and uninstall for $label",
|
||||
async ({ harness, agents, dshRoot, agentsRoot }, { cli }) => {
|
||||
const root = await createTestTempDirectory("rea-dsh-paths-");
|
||||
const home = join(root, "home");
|
||||
const cwd = join(root, "workspace");
|
||||
const config = join(root, dshRoot, "cordis.patch.yml");
|
||||
const skillRoot = join(
|
||||
root,
|
||||
agentsRoot,
|
||||
"skills",
|
||||
"reverse-engineer-anything",
|
||||
);
|
||||
const skill = join(skillRoot, "SKILL.md");
|
||||
await mkdir(home);
|
||||
await mkdir(cwd);
|
||||
await mkdir(dirname(config), { recursive: true });
|
||||
const original =
|
||||
"# caller patch\n- insert: [{id: caller, name: caller-plugin}]\n";
|
||||
await writeFile(config, original);
|
||||
const run = (args: readonly string[]) =>
|
||||
cli.run({
|
||||
arguments: [...args, "--json"],
|
||||
cwd,
|
||||
environment: {
|
||||
HOME: home,
|
||||
USERPROFILE: home,
|
||||
DSH_HOME: harness,
|
||||
DSH_AGENTS_HOME: agents,
|
||||
},
|
||||
});
|
||||
const planned = await run(["setup", "--client", "dsh", "--dry-run"]);
|
||||
expect(planned.exitCode).toBe(0);
|
||||
expect(planned.json).toMatchObject({
|
||||
status: "planned",
|
||||
appliedActions: [],
|
||||
plannedActions: expect.arrayContaining([
|
||||
expect.objectContaining({ id: "configure_client:dsh", target: config }),
|
||||
expect.objectContaining({ kind: "install_skill", target: skillRoot }),
|
||||
]),
|
||||
});
|
||||
expect(await readFile(config, "utf8")).toBe(original);
|
||||
await expect(readFile(skill)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
const configured = await run(["setup", "--client", "dsh", "--yes"]);
|
||||
expect(configured.exitCode).toBe(0);
|
||||
expect(configured.json).toMatchObject({
|
||||
status: "ready",
|
||||
clients: { dsh: { status: "configured" } },
|
||||
});
|
||||
const text = await readFile(config, "utf8");
|
||||
expect(text).toContain("# caller patch");
|
||||
expect(text).toContain("rea-mcp");
|
||||
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original);
|
||||
expect(await readFile(skill, "utf8")).toContain(
|
||||
"name: reverse-engineer-anything",
|
||||
);
|
||||
const doctor = await run(["doctor", "--client", "dsh", "--skill"]);
|
||||
expect(doctor.exitCode).toBe(0);
|
||||
expect(doctor.json).toMatchObject({ healthy: true });
|
||||
const repeated = await run(["setup", "--client", "dsh", "--yes"]);
|
||||
expect(repeated.exitCode).toBe(0);
|
||||
expect(repeated.json).toMatchObject({
|
||||
status: "ready",
|
||||
plannedActions: [],
|
||||
appliedActions: [],
|
||||
});
|
||||
expect(await readFile(config, "utf8")).toBe(text);
|
||||
const removed = await run(["uninstall"]);
|
||||
expect(removed.exitCode).toBe(0);
|
||||
expect(removed.json).toMatchObject({
|
||||
status: "complete",
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({ name: "dsh", status: "removed" }),
|
||||
]),
|
||||
});
|
||||
expect(parse(await readFile(config, "utf8"))).toEqual([
|
||||
{ insert: [{ id: "caller", name: "caller-plugin" }] },
|
||||
]);
|
||||
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original);
|
||||
await expect(readFile(skill)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
},
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"DSH CLI previews, approves, repairs, launches MCP, repeats and uninstalls while preserving caller YAML",
|
||||
async ({ cli }) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { join, resolve } from "node:path";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
|
|
@ -37,28 +37,76 @@ const dshClient = (
|
|||
env: Parameters<typeof supportedClients>[2],
|
||||
platform: NodeJS.Platform = "linux",
|
||||
) =>
|
||||
supportedClients(join("/home/a"), platform, env).find(
|
||||
supportedClients(resolve("/home/a"), platform, env).find(
|
||||
({ name }) => name === "dsh",
|
||||
);
|
||||
|
||||
describe("DeepSeek Harness configuration paths", () => {
|
||||
it.each([
|
||||
[undefined, ".dsh", ".agents", "shared"],
|
||||
["", ".dsh", ".agents", "shared"],
|
||||
[" \t ", ".dsh", ".agents", "shared"],
|
||||
["~", "", "", "dsh"],
|
||||
["~/selected home", "selected home", "selected home", "dsh"],
|
||||
["~\\selected home", "selected home", "selected home", "dsh"],
|
||||
] as const)(
|
||||
"resolves Harness and agents roots for %j",
|
||||
(override, dshRoot, agentsRoot, client) => {
|
||||
const home = resolve("/home/a");
|
||||
const environment = { DSH_HOME: override, DSH_AGENTS_HOME: override };
|
||||
expect(
|
||||
supportedClients(home, process.platform, environment).find(
|
||||
({ name }) => name === "dsh",
|
||||
),
|
||||
).toMatchObject({
|
||||
configPath: join(home, dshRoot, "cordis.patch.yml"),
|
||||
markerPath: join(home, dshRoot),
|
||||
});
|
||||
expect(clientSkillDirectories(home, ["dsh"], environment)).toEqual([
|
||||
{ client, directory: join(home, agentsRoot, "skills") },
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["relative home", " literal home ", "~someone"])(
|
||||
"resolves a literal root %j relative to cwd without trimming it",
|
||||
(override) => {
|
||||
const home = resolve("/home/a");
|
||||
const root = resolve(override);
|
||||
const environment = { DSH_HOME: override, DSH_AGENTS_HOME: override };
|
||||
expect(
|
||||
supportedClients(home, process.platform, environment).find(
|
||||
({ name }) => name === "dsh",
|
||||
),
|
||||
).toMatchObject({
|
||||
configPath: join(root, "cordis.patch.yml"),
|
||||
markerPath: root,
|
||||
});
|
||||
expect(clientSkillDirectories(home, ["dsh"], environment)).toEqual([
|
||||
{ client: "dsh", directory: join(root, "skills") },
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
it("resolves the home Cordis patch under the Harness home", () => {
|
||||
expect(dshClient({})).toEqual({
|
||||
name: "dsh",
|
||||
displayName: "DeepSeek Harness",
|
||||
configPath: join("/home/a", ".dsh", "cordis.patch.yml"),
|
||||
markerPath: join("/home/a", ".dsh"),
|
||||
configPath: resolve("/home/a", ".dsh", "cordis.patch.yml"),
|
||||
markerPath: resolve("/home/a", ".dsh"),
|
||||
format: "dsh",
|
||||
});
|
||||
// The home patch applies to every profile; DSH_HOME relocates it.
|
||||
expect(dshClient({ DSH_HOME: "/custom/harness" })?.configPath).toBe(
|
||||
join("/custom/harness", "cordis.patch.yml"),
|
||||
resolve("/custom/harness", "cordis.patch.yml"),
|
||||
);
|
||||
expect(
|
||||
clientSkillDirectories(join("/home/a"), ["dsh"], {
|
||||
DSH_AGENTS_HOME: "/custom/agents",
|
||||
}),
|
||||
).toEqual([{ client: "dsh", directory: join("/custom/agents", "skills") }]);
|
||||
).toEqual([
|
||||
{ client: "dsh", directory: resolve("/custom/agents", "skills") },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue