Add composer shortcuts for local Continue and OpenCode terminals and the official DeepSeek Harness Web client, using the current project and model bridge. Manage browser service reuse, stop actions and terminal cleanup. Upgrade DeepSeek Harness to 0.1.7-rc.2 and Cordis to 4.0.4. Include the coordinated IPC handler extraction and related tests and documentation. Remote native-client coordinator wiring, managed Node consolidation, Ask MCP mappings and full desktop/package acceptance remain unfinished; this commit records the implemented local workflow and supporting SSH groundwork. Validation: typecheck and lint passed; 267 focused tests passed and 5 skipped. No new real-provider calls were made for this commit. Prior real-model evidence and remaining acceptance work are documented in the feature progress files. Release note: 本地项目选择 Continue 或 OpenCode 后,可从输入框下方打开原生终端;选择 DS 后,可打开使用当前项目和模型的官方 Web 客户端。客户端使用独立会话,DS 网页关闭后服务继续运行,可在 GoodBuddy 中停止;远程入口尚未完成。
66 lines
3.7 KiB
JavaScript
66 lines
3.7 KiB
JavaScript
const { cp, mkdir, readFile, rm } = require('node:fs/promises');
|
|
const { existsSync } = require('node:fs');
|
|
const { dirname, join, relative } = require('node:path');
|
|
const tar = require('tar');
|
|
|
|
// Preserve npm's installed layout (including nested versions) and every package
|
|
// asset. The official loader discovers plugins and browser assets at runtime.
|
|
async function prepareNativeDshResources(projectDir, platform, architecture, downloadPackage, lockedIntegrity) {
|
|
const target = join(projectDir, '.runtime-resources', `dsh-${architecture}`);
|
|
await rm(target, { recursive: true, force: true });
|
|
await mkdir(target, { recursive: true });
|
|
const visited = new Set();
|
|
function locate(name, from) {
|
|
for (let directory = from; ; directory = dirname(directory)) {
|
|
const candidate = join(directory, 'node_modules', name);
|
|
if (existsSync(join(candidate, 'package.json'))) return candidate;
|
|
if (dirname(directory) === directory) return undefined;
|
|
}
|
|
}
|
|
async function copyPackage(directory) {
|
|
if (visited.has(directory)) return;
|
|
visited.add(directory);
|
|
const manifest = JSON.parse(await readFile(join(directory, 'package.json'), 'utf8'));
|
|
await cp(directory, join(target, relative(projectDir, directory)), { recursive: true });
|
|
const optional = manifest.optionalDependencies ?? {};
|
|
for (const name of Object.keys({ ...manifest.dependencies, ...manifest.peerDependencies, ...optional })) {
|
|
const dependency = locate(name, directory);
|
|
if (dependency) await copyPackage(dependency);
|
|
else if (!(name in optional) && !manifest.peerDependenciesMeta?.[name]?.optional) {
|
|
throw new Error(`DS Web bundle is missing ${name} required by ${manifest.name}`);
|
|
}
|
|
}
|
|
}
|
|
await copyPackage(join(projectDir, 'node_modules', '@deepseek-ai', 'dsh'));
|
|
// Cross-architecture packaging cannot rely on the build host's npm optional
|
|
// selection. Fetch the exact target Sharp packages from the existing lock.
|
|
const sharp = JSON.parse(await readFile(join(projectDir, 'node_modules', 'sharp', 'package.json'), 'utf8'));
|
|
const lock = JSON.parse(await readFile(join(projectDir, 'package-lock.json'), 'utf8'));
|
|
const nativePackages = [`@img/sharp-${platform}-${architecture}`];
|
|
if (platform !== 'win32') nativePackages.push(`@img/sharp-libvips-${platform}-${architecture}`);
|
|
for (const name of nativePackages) {
|
|
const version = sharp.optionalDependencies[name];
|
|
if (!version) throw new Error(`DS Web Sharp does not support ${platform}/${architecture}`);
|
|
const destination = join(target, 'node_modules', 'sharp', 'node_modules', name);
|
|
if (existsSync(join(destination, 'package.json'))) continue;
|
|
const locked = lock.packages[`node_modules/sharp/node_modules/${name}`] ?? lock.packages[`node_modules/${name}`];
|
|
if (locked?.version !== version || !locked.integrity) throw new Error(`Missing locked ${name}@${version}`);
|
|
const integrity = locked.integrity;
|
|
const archive = await downloadPackage(projectDir, name, version, integrity);
|
|
await mkdir(destination, { recursive: true });
|
|
await tar.x({ file: archive, cwd: destination, strip: 1 });
|
|
}
|
|
const koffi = JSON.parse(await readFile(join(projectDir, 'node_modules', 'koffi', 'package.json'), 'utf8'));
|
|
const koffiName = `@koromix/koffi-${platform}-${architecture}`;
|
|
const koffiDestination = join(target, 'node_modules', koffiName);
|
|
if (!existsSync(join(koffiDestination, 'package.json'))) {
|
|
const integrity = await lockedIntegrity(projectDir, koffiName, koffi.version);
|
|
const archive = await downloadPackage(projectDir, koffiName, koffi.version, integrity);
|
|
await mkdir(koffiDestination, { recursive: true });
|
|
await tar.x({ file: archive, cwd: koffiDestination, strip: 1 });
|
|
}
|
|
return target;
|
|
}
|
|
|
|
module.exports = { prepareNativeDshResources };
|