118 lines
3.1 KiB
JavaScript
118 lines
3.1 KiB
JavaScript
|
|
import fs from 'node:fs';
|
|||
|
|
import os from 'node:os';
|
|||
|
|
import path from 'node:path';
|
|||
|
|
|
|||
|
|
function envFlag(value, fallback = false) {
|
|||
|
|
const raw = String(value ?? '').trim().toLowerCase();
|
|||
|
|
if (!raw) return fallback;
|
|||
|
|
return ['1', 'true', 'yes', 'on'].includes(raw);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function dshExecutorEnabled(env = process.env) {
|
|||
|
|
return envFlag(env.MEMIND_TOOL_GATEWAY_DSH_ENABLED, false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function expandHome(value) {
|
|||
|
|
const text = String(value ?? '').trim();
|
|||
|
|
if (!text) return text;
|
|||
|
|
if (text.startsWith('~/')) return path.join(os.homedir(), text.slice(2));
|
|||
|
|
if (text === '~') return os.homedir();
|
|||
|
|
return text;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function resolveDshCommand(env = process.env) {
|
|||
|
|
if (envFlag(env.MEMIND_DSH_FORCE_NPX, false)) {
|
|||
|
|
return {
|
|||
|
|
command: String(env.MEMIND_DSH_NPX_BIN ?? 'npx').trim() || 'npx',
|
|||
|
|
viaNpx: true,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const fileCandidates = [
|
|||
|
|
env.MEMIND_DSH_BIN,
|
|||
|
|
env.DSH_BIN,
|
|||
|
|
'~/.local/bin/dsh',
|
|||
|
|
].map((item) => expandHome(String(item ?? '').trim())).filter(Boolean);
|
|||
|
|
|
|||
|
|
for (const candidate of fileCandidates) {
|
|||
|
|
if (fs.existsSync(candidate)) {
|
|||
|
|
return { command: candidate, viaNpx: false };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const bare = String(env.DSH_BIN ?? 'dsh').trim();
|
|||
|
|
if (bare && !bare.includes('/')) {
|
|||
|
|
return { command: bare, viaNpx: false };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
command: String(env.MEMIND_DSH_NPX_BIN ?? 'npx').trim() || 'npx',
|
|||
|
|
viaNpx: true,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function buildDshExecutorInstruction(instruction) {
|
|||
|
|
const base = String(instruction ?? '').trim();
|
|||
|
|
if (!base) return base;
|
|||
|
|
return [
|
|||
|
|
base,
|
|||
|
|
'',
|
|||
|
|
'Execution constraints:',
|
|||
|
|
'- Work only inside the provided workspace cwd',
|
|||
|
|
'- Do not run git commit/push or production deploy commands',
|
|||
|
|
'- Prefer minimal, reviewable diffs',
|
|||
|
|
'- Summarize changed files in the final answer',
|
|||
|
|
].join('\n');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function buildDshExecutorLaunchPlan({
|
|||
|
|
cwd,
|
|||
|
|
instruction,
|
|||
|
|
env = process.env,
|
|||
|
|
runtimeEnv = {},
|
|||
|
|
} = {}) {
|
|||
|
|
if (!dshExecutorEnabled(env)) {
|
|||
|
|
return {
|
|||
|
|
ok: false,
|
|||
|
|
executor: 'dsh',
|
|||
|
|
message: 'DeepSeek Harness 执行器未启用(MEMIND_TOOL_GATEWAY_DSH_ENABLED)',
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const workspace = path.resolve(String(cwd ?? env.MEMIND_DSH_EXECUTOR_WORKSPACE ?? process.cwd()));
|
|||
|
|
const prompt = buildDshExecutorInstruction(instruction);
|
|||
|
|
if (!prompt) {
|
|||
|
|
return {
|
|||
|
|
ok: false,
|
|||
|
|
executor: 'dsh',
|
|||
|
|
message: 'DeepSeek Harness 执行器需要 instruction',
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const resolved = resolveDshCommand(env);
|
|||
|
|
const args = resolved.viaNpx
|
|||
|
|
? ['-y', '@deepseek-ai/dsh', '--profile', 'headless', prompt]
|
|||
|
|
: ['--profile', 'headless', prompt];
|
|||
|
|
|
|||
|
|
const launchEnv = {
|
|||
|
|
...runtimeEnv,
|
|||
|
|
};
|
|||
|
|
if (!launchEnv.DEEPSEEK_API_KEY && env.DEEPSEEK_API_KEY) {
|
|||
|
|
launchEnv.DEEPSEEK_API_KEY = env.DEEPSEEK_API_KEY;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
ok: true,
|
|||
|
|
executor: 'dsh',
|
|||
|
|
executorLabel: 'DeepSeek Harness',
|
|||
|
|
cwd: workspace,
|
|||
|
|
command: resolved.command,
|
|||
|
|
args,
|
|||
|
|
env: launchEnv,
|
|||
|
|
notes: [
|
|||
|
|
'DeepSeek Harness headless one-shot task via dsh --profile headless.',
|
|||
|
|
'Developer preview: keep disabled by default and route only whitelisted task types.',
|
|||
|
|
],
|
|||
|
|
};
|
|||
|
|
}
|