Skip to content

Commit 90dc5c7

Browse files
fix: install DSH companion from a shell-safe profile path (#6905) (#6911)
(cherry picked from commit e11616d) Co-authored-by: lefarcen <935902669@qq.com>
1 parent f84e69b commit 90dc5c7

3 files changed

Lines changed: 75 additions & 8 deletions

File tree

apps/daemon/src/agent-companion-setup.ts

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import { spawnEnvForAgent } from './runtimes/env.js';
1616
import { execAgentFile } from './runtimes/invocation.js';
1717
import { applyAgentLaunchEnv, resolveAgentLaunch } from './runtimes/launch.js';
1818
import { getAgentDef } from './runtimes/registry.js';
19-
import { hasOpenDesignProfile } from './runtimes/defs/deepseek-harness.js';
19+
import {
20+
hasOpenDesignProfile,
21+
resolveOpenDesignProfileDir,
22+
} from './runtimes/defs/deepseek-harness.js';
2023

2124
const execFileAsync = promisify(execFile);
2225
const DSH_AGENT_ID = 'deepseek-harness';
@@ -111,7 +114,7 @@ async function resolveVerifiedBundle(options: {
111114
projectRoot: string;
112115
resourceRoot: string;
113116
runtimeDataDir: string;
114-
}): Promise<{ manifest: RuntimeManifest; tarballPath: string }> {
117+
}): Promise<{ bytes: Buffer; manifest: RuntimeManifest }> {
115118
let directory = path.join(options.resourceRoot, DSH_RUNTIME_RESOURCE_DIRECTORY);
116119
if (!(await fileExists(path.join(directory, 'manifest.json')))) {
117120
directory = await materializeDevelopmentBundle(options.projectRoot, options.runtimeDataDir);
@@ -135,11 +138,48 @@ async function resolveVerifiedBundle(options: {
135138
throw new AgentCompanionSetupError('BUNDLED_COMPANION_INVALID', 'Invalid connection package manifest.');
136139
}
137140
const tarballPath = path.join(directory, manifest.file);
138-
const actualHash = createHash('sha256').update(await readFile(tarballPath)).digest('hex');
141+
const bytes = await readFile(tarballPath);
142+
const actualHash = createHash('sha256').update(bytes).digest('hex');
139143
if (actualHash !== manifest.sha256) {
140144
throw new AgentCompanionSetupError('BUNDLED_COMPANION_INVALID', 'Connection package integrity check failed.');
141145
}
142-
return { manifest, tarballPath };
146+
return { bytes, manifest };
147+
}
148+
149+
async function stageVerifiedBundleInProfile(
150+
env: NodeJS.ProcessEnv,
151+
manifest: RuntimeManifest,
152+
bytes: Buffer,
153+
): Promise<string> {
154+
const relativeDirectory = '.open-design';
155+
const file = `${manifest.sha256}.tgz`;
156+
const profileDirectory = resolveOpenDesignProfileDir(env);
157+
const bundleDirectory = path.join(profileDirectory, relativeDirectory);
158+
await mkdir(bundleDirectory, { recursive: true });
159+
await writeFile(path.join(bundleDirectory, file), bytes);
160+
// dsh runs pnpm with the profile directory as cwd. Keeping this spec
161+
// relative avoids rc.6's Windows shell forwarder splitting an absolute
162+
// packaged-app path such as "Open Design" at its spaces.
163+
return `${relativeDirectory}/${file}`;
164+
}
165+
166+
function commandFailureDetail(error: unknown): string | null {
167+
if (!error || typeof error !== 'object') return null;
168+
const candidate = error as { code?: unknown; signal?: unknown; stderr?: unknown };
169+
let stderr = '';
170+
if (typeof candidate.stderr === 'string') {
171+
stderr = candidate.stderr.trim();
172+
} else if (Buffer.isBuffer(candidate.stderr)) {
173+
stderr = candidate.stderr.toString('utf8').trim();
174+
}
175+
const parts = [
176+
typeof candidate.code === 'string' || typeof candidate.code === 'number'
177+
? `code=${candidate.code}`
178+
: null,
179+
typeof candidate.signal === 'string' ? `signal=${candidate.signal}` : null,
180+
stderr ? `stderr=${stderr.slice(-4_000)}` : null,
181+
].filter((part): part is string => part !== null);
182+
return parts.length > 0 ? parts.join(' ') : null;
143183
}
144184

145185
let activeSetup: Promise<AgentCompanionSetupResponse> | null = null;
@@ -168,7 +208,7 @@ async function installDeepSeekHarnessCompanionOnce(options: {
168208
const appConfig = await readAppConfig(options.runtimeDataDir);
169209
const configuredEnv = agentCliEnvForAgent(appConfig.agentCliEnv, DSH_AGENT_ID);
170210
const before = await detectAgent(def, configuredEnv);
171-
const { manifest, tarballPath } = await resolveVerifiedBundle(options);
211+
const { bytes, manifest } = await resolveVerifiedBundle(options);
172212
if (before.available) {
173213
return { action: 'already-compatible', agent: before, ok: true, packageVersion: manifest.version };
174214
}
@@ -185,13 +225,18 @@ async function installDeepSeekHarnessCompanionOnce(options: {
185225
launch,
186226
);
187227
const profileWasPresent = hasOpenDesignProfile(childEnv);
228+
const packageSpec = await stageVerifiedBundleInProfile(childEnv, manifest, bytes);
188229
try {
189230
await execAgentFile(
190231
launch.launchPath,
191-
['plugin', '--profile', 'open-design', 'add', tarballPath],
232+
['plugin', '--profile', 'open-design', 'add', packageSpec],
192233
{ env: childEnv, timeout: 120_000, maxBuffer: 2 * 1024 * 1024 },
193234
);
194-
} catch {
235+
} catch (error) {
236+
console.error(
237+
'[od] DeepSeek Harness connection component install failed',
238+
commandFailureDetail(error) ?? 'no command diagnostics were available',
239+
);
195240
throw new AgentCompanionSetupError(
196241
'COMPANION_INSTALL_FAILED',
197242
'DeepSeek Harness could not install the Open Design connection component. No agent selection was changed.',

apps/daemon/src/runtimes/defs/deepseek-harness.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,15 @@ function parseModels(stdout: string) {
3030
}
3131

3232
export function hasOpenDesignProfile(env: NodeJS.ProcessEnv): boolean {
33+
return existsSync(path.join(resolveOpenDesignProfileDir(env), 'package.json'));
34+
}
35+
36+
export function resolveOpenDesignProfileDir(env: NodeJS.ProcessEnv): string {
3337
const configuredHome = env.DSH_HOME?.trim();
3438
const dshHome = configuredHome
3539
? path.resolve(configuredHome)
3640
: path.join(homedir(), '.dsh');
37-
return existsSync(path.join(dshHome, 'profiles', 'open-design', 'package.json'));
41+
return path.join(dshHome, 'profiles', 'open-design');
3842
}
3943

4044
const DSH_VERSION_RE = /^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/u;

apps/daemon/tests/agent-companion-setup.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ if (args[0] === '--version') {
7676
}
7777
} else if (args[0] === 'plugin' && args[1] === '--profile' && args[2] === 'open-design' && args[3] === 'add') {
7878
if (process.env.OD_DSH_SETUP_FAKE_MODE === 'install-fail') process.exit(7);
79+
if (process.env.OD_DSH_SETUP_FAKE_MODE === 'require-profile-bundle') {
80+
const [directory, filename, extra] = args[4].split('/');
81+
const digest = filename?.endsWith('.tgz') ? filename.slice(0, -4) : '';
82+
const digestIsHex = digest.length === 64 && digest.replace(/[a-f0-9]/g, '') === '';
83+
if (directory !== '.open-design' || extra !== undefined || !digestIsHex) process.exit(8);
84+
const bundle = await readFile(path.join(profileRoot, args[4]), 'utf8');
85+
if (bundle !== 'fixture runtime package') process.exit(9);
86+
}
7987
await mkdir(profileRoot, { recursive: true });
8088
let count = 0;
8189
try { count = Number(await readFile(${JSON.stringify(stateFile)}, 'utf8')); } catch {}
@@ -166,6 +174,16 @@ describe('DeepSeek Harness companion setup', () => {
166174
);
167175
});
168176

177+
it('keeps the bundled package path out of the dsh Windows shell boundary', async () => {
178+
const test = await fixture();
179+
process.env.OD_DSH_SETUP_FAKE_MODE = 'require-profile-bundle';
180+
181+
await expect(installDeepSeekHarnessCompanion(test.options)).resolves.toMatchObject({
182+
action: 'installed',
183+
ok: true,
184+
});
185+
});
186+
169187
it('deduplicates concurrent setup requests', async () => {
170188
const test = await fixture();
171189
const first = installDeepSeekHarnessCompanion(test.options);

0 commit comments

Comments
 (0)