feat(context): add dsh executor and headroom active loopback probe

Wire DeepSeek Harness as an optional fourth code executor (default off)
and add a loopback observation script that compares prompt tokens through
headroom proxy with OPENAI_TARGET_API_URL pointed at the compat proxy.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-09-09 21:32:57 +08:00
parent 40e4a527fe
commit fc4ac98e9f
13 changed files with 525 additions and 8 deletions
+1 -1
View File
@@ -599,7 +599,7 @@ export function resolveRequiredCodeExecutor(userMessage) {
const executor = String(
runMetadata?.executor ?? runMetadata?.requiredExecutor ?? '',
).trim().toLowerCase();
return ['aider', 'openhands', 'cursor'].includes(executor) ? executor : null;
return ['aider', 'openhands', 'cursor', 'dsh'].includes(executor) ? executor : null;
}
export function assertRequiredCodeExecutorAvailable(requiredExecutor, toolGatewayStatus) {
+7
View File
@@ -28,7 +28,14 @@ MEMORY_CANDIDATE_ENABLED=0
# MEMIND_HEADROOM_MODE=off
# MEMIND_HEADROOM_PROXY_BASE_URL=http://127.0.0.1:8787/v1
# MEMIND_HEADROOM_UPSTREAM_BASE_URL=http://127.0.0.1:18036/v1
# OPENAI_TARGET_API_URL=http://127.0.0.1:18036/v1
# HEADROOM_OUTPUT_SHAPER=0
# MEMIND_HEADROOM_OBS_MODEL=deepseek-chat
# DeepSeek Harness code executor (developer preview, default off)
# MEMIND_TOOL_GATEWAY_DSH_ENABLED=0
# MEMIND_TOOL_GATEWAY_DSH_TASK_TYPES=repo_refactor,multi_file
# MEMIND_DSH_BIN=dsh
# Context injection budget (Phase 2, default off — does not open memory injection)
# MEMIND_CONTEXT_BUDGET_MODE=off
+117
View File
@@ -0,0 +1,117 @@
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.',
],
};
}
+44
View File
@@ -0,0 +1,44 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
buildDshExecutorLaunchPlan,
dshExecutorEnabled,
resolveDshCommand,
} from './dsh-agent-launch.mjs';
test('dshExecutorEnabled defaults to false', () => {
assert.equal(dshExecutorEnabled({}), false);
assert.equal(dshExecutorEnabled({ MEMIND_TOOL_GATEWAY_DSH_ENABLED: '1' }), true);
});
test('resolveDshCommand falls back to npx package when forced', () => {
const resolved = resolveDshCommand({
MEMIND_DSH_FORCE_NPX: '1',
MEMIND_DSH_NPX_BIN: 'npx',
});
assert.equal(resolved.viaNpx, true);
assert.equal(resolved.command, 'npx');
});
test('buildDshExecutorLaunchPlan requires enable flag', () => {
const plan = buildDshExecutorLaunchPlan({
cwd: '/tmp/work',
instruction: 'fix tests',
env: {},
});
assert.equal(plan.ok, false);
});
test('buildDshExecutorLaunchPlan builds headless args when enabled', () => {
const plan = buildDshExecutorLaunchPlan({
cwd: '/tmp/work',
instruction: 'fix tests',
env: { MEMIND_TOOL_GATEWAY_DSH_ENABLED: '1', MEMIND_DSH_NPX_BIN: 'npx' },
runtimeEnv: { DEEPSEEK_API_KEY: 'test-key' },
});
assert.equal(plan.ok, true);
assert.equal(plan.executor, 'dsh');
assert.ok(plan.args.includes('headless'));
assert.ok(plan.args.at(-1).includes('fix tests'));
assert.equal(plan.env.DEEPSEEK_API_KEY, 'test-key');
});
+1
View File
@@ -2,6 +2,7 @@ const EXECUTOR_DISPLAY_LABELS = Object.freeze({
cursor: 'TKMind 智趣',
aider: 'Aider',
openhands: 'OpenHands',
dsh: 'DeepSeek Harness',
goose: 'TKMind',
});
+43 -1
View File
@@ -20,6 +20,7 @@ import {
buildCursorExecutorLaunchPlan,
cursorExecutorEnabled,
} from './cursor-agent-launch.mjs';
import { buildDshExecutorLaunchPlan } from './dsh-agent-launch.mjs';
import {
probeHeadroomProxyReachable,
resolveGoosedApiUrlWithHeadroom,
@@ -116,6 +117,12 @@ export const LLM_EXECUTOR_CATALOG = [
description: 'MindSpace 页面与代码任务,通过 TKMind 智趣执行器落盘',
purposes: ['default'],
},
{
id: 'dsh',
label: 'DeepSeek Harness',
description: 'Cordis 插件化 code harnessdeveloper previewheadless one-shot',
purposes: ['default'],
},
];
const catalogById = Object.fromEntries(LLM_PROVIDER_CATALOG.map((item) => [item.id, item]));
@@ -498,6 +505,25 @@ function executorEnvForProfile(executor, profile, model, { includeSecret = false
};
}
if (executor === 'dsh') {
const env = {
...common,
LLM_MODEL: model,
};
if (includeSecret) {
env.DEEPSEEK_API_KEY = profile.apiKey;
env.OPENAI_API_KEY = profile.apiKey;
if (providerId === 'openrouter') env.OPENROUTER_API_KEY = profile.apiKey;
} else {
env.DEEPSEEK_API_KEY = '[hidden]';
}
if (baseUrl) {
env.OPENAI_BASE_URL = baseUrl;
env.DEEPSEEK_API_BASE = baseUrl;
}
return env;
}
return {
...common,
GOOSE_PROVIDER: profile.providerKind === 'custom'
@@ -545,7 +571,14 @@ function resolveExecutorCommand(executor) {
'~/.local/bin/agent',
'agent',
]
: [executor];
: executor === 'dsh'
? [
process.env.MEMIND_DSH_BIN,
process.env.DSH_BIN,
'~/.local/bin/dsh',
'dsh',
]
: [executor];
const resolved = candidates.map((item) => String(item ?? '').trim()).find(Boolean) ?? executor;
if (resolved.startsWith('~/')) {
return path.join(os.homedir(), resolved.slice(2));
@@ -732,6 +765,15 @@ export function buildExecutorLaunchPlan(runtime, options = {}) {
});
}
if (runtime.executor === 'dsh') {
return buildDshExecutorLaunchPlan({
cwd,
instruction,
env: process.env,
runtimeEnv: env,
});
}
return {
ok: false,
executor: runtime.executor,
+29 -1
View File
@@ -320,7 +320,7 @@ test('listExecutorBindings returns all executor placeholders', async () => {
const bindings = await service.listExecutorBindings();
assert.deepEqual(
bindings.map((binding) => binding.executor),
['goose', 'aider', 'openhands', 'cursor'],
['goose', 'aider', 'openhands', 'cursor', 'dsh'],
);
assert.equal(bindings[0].enabled, false);
});
@@ -649,6 +649,34 @@ test('buildExecutorLaunchPlan rejects openhands headless without instruction', (
assert.match(plan.message ?? '', /instruction/);
});
test('buildExecutorLaunchPlan creates dsh headless command when enabled', () => {
const previous = process.env.MEMIND_TOOL_GATEWAY_DSH_ENABLED;
process.env.MEMIND_TOOL_GATEWAY_DSH_ENABLED = '1';
process.env.MEMIND_DSH_FORCE_NPX = '1';
try {
const plan = buildExecutorLaunchPlan({
ok: true,
executor: 'dsh',
executorLabel: 'DeepSeek Harness',
purpose: 'default',
env: { DEEPSEEK_API_KEY: 'test-key' },
}, {
cwd: '/tmp/mindspace/user-1',
instruction: 'run unit tests',
});
assert.equal(plan.ok, true);
assert.equal(plan.executor, 'dsh');
assert.equal(plan.command, 'npx');
assert.ok(plan.args.includes('@deepseek-ai/dsh'));
assert.ok(plan.args.includes('headless'));
assert.match(plan.args.at(-1) ?? '', /unit tests/);
} finally {
if (previous === undefined) delete process.env.MEMIND_TOOL_GATEWAY_DSH_ENABLED;
else process.env.MEMIND_TOOL_GATEWAY_DSH_ENABLED = previous;
delete process.env.MEMIND_DSH_FORCE_NPX;
}
});
test('buildExecutorLaunchPlan creates cursor headless command when enabled', () => {
const previous = process.env.MEMIND_CURSOR_EXECUTOR_ENABLED;
process.env.MEMIND_CURSOR_EXECUTOR_ENABLED = '1';
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env node
/**
* Local DeepSeek Harness executor dry-run probe.
*/
import { buildDshExecutorLaunchPlan, dshExecutorEnabled } from '../dsh-agent-launch.mjs';
if (!dshExecutorEnabled(process.env)) {
console.log('DSH_EXECUTOR_SKIP: MEMIND_TOOL_GATEWAY_DSH_ENABLED is off');
process.exit(0);
}
const plan = buildDshExecutorLaunchPlan({
cwd: process.cwd(),
instruction: process.env.MEMIND_DSH_SMOKE_TASK ?? 'List three files in the workspace root and stop.',
env: process.env,
runtimeEnv: {
DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY ?? process.env.OPENAI_API_KEY ?? '',
},
});
console.log('DSH_EXECUTOR_PLAN:');
console.log(JSON.stringify({
ok: plan.ok,
executor: plan.executor,
command: plan.command,
argsPreview: plan.args?.slice?.(0, 4),
cwd: plan.cwd,
message: plan.message ?? null,
}, null, 2));
if (!plan.ok) {
console.error('DSH_EXECUTOR_FAIL: launch plan unavailable');
process.exit(1);
}
console.log('DSH_EXECUTOR_OK: dry-run plan ready (use tool-gateway dry-run to execute)');
+1 -1
View File
@@ -4,7 +4,7 @@
* Requires: headroom proxy running with upstream pointing at DeepSeek compat proxy.
*
* Example:
* OPENAI_BASE_URL=http://127.0.0.1:18036/v1 HEADROOM_OUTPUT_SHAPER=0 headroom proxy --port 8787
* OPENAI_TARGET_API_URL=http://127.0.0.1:18036/v1 HEADROOM_OUTPUT_SHAPER=0 headroom proxy --port 8787
*/
import {
buildHeadroomRunObservation,
+169
View File
@@ -0,0 +1,169 @@
#!/usr/bin/env node
/**
* Compare direct compat proxy vs headroom proxy on a tool-heavy request.
* Loopback only; does not mutate goosed config unless MEMIND_HEADROOM_APPLY_GOosed=1.
*/
import { spawn } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { prepareGooseV149CheckEnv } from './goose-v149-canary.mjs';
import {
resolveDeepseekNoThinkProxyBaseUrl,
} from '../deepseek-no-think-proxy.mjs';
import {
probeHeadroomProxyReachable,
resolveGoosedApiUrlWithHeadroom,
resolveHeadroomMode,
resolveHeadroomProxyBaseUrl,
} from '../memind-headroom-policy.mjs';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
prepareGooseV149CheckEnv(process.env, root);
const upstreamBase = resolveDeepseekNoThinkProxyBaseUrl();
const headroomBase = resolveHeadroomProxyBaseUrl();
const model = process.env.MEMIND_HEADROOM_OBS_MODEL ?? 'deepseek-chat';
const apiKey = process.env.DEEPSEEK_API_KEY
?? process.env.OPENAI_API_KEY
?? process.env.TKMIND_EXECUTOR_API_KEY
?? 'local-dev';
function buildToolHeavyMessages() {
const toolPayload = [
'TKMind search result chunk',
...Array.from({ length: 400 }, (_, index) => (
`line-${index}: portal resume smoke validates session round-trip with harness bootstrap`
)),
].join('\n');
return [
{ role: 'system', content: 'You compress long tool output into one short sentence.' },
{ role: 'user', content: 'Summarize the tool result in under 30 Chinese characters.' },
{
role: 'assistant',
content: null,
tool_calls: [{
id: 'call_headroom_probe',
type: 'function',
function: { name: 'tkmind_read', arguments: '{"path":"docs/plan.md"}' },
}],
},
{ role: 'tool', tool_call_id: 'call_headroom_probe', content: toolPayload },
];
}
async function chatCompletion(baseUrl, messages, { viaHeadroom = false } = {}) {
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
};
if (viaHeadroom) {
headers['x-headroom-base-url'] = upstreamBase;
}
const response = await fetch(`${baseUrl.replace(/\/$/, '')}/chat/completions`, {
method: 'POST',
headers,
body: JSON.stringify({
model,
messages,
stream: false,
max_tokens: 64,
temperature: 0,
}),
});
const text = await response.text();
let payload = {};
try {
payload = JSON.parse(text);
} catch {
payload = { raw: text.slice(0, 400) };
}
if (!response.ok) {
throw new Error(`${baseUrl} ${response.status}: ${text.slice(0, 400)}`);
}
const usage = payload?.usage ?? {};
return {
reply: payload?.choices?.[0]?.message?.content ?? '',
promptTokens: Number(usage.prompt_tokens ?? usage.input_tokens ?? 0),
completionTokens: Number(usage.completion_tokens ?? usage.output_tokens ?? 0),
totalTokens: Number(usage.total_tokens ?? 0),
};
}
async function ensureHeadroomProxy() {
if (await probeHeadroomProxyReachable({ baseUrl: headroomBase })) return false;
const port = new URL(`${headroomBase}/`).port || '8787';
console.log(`HEADROOM_ACTIVE_START: spawning headroom proxy on :${port}`);
const child = spawn(
'headroom',
['proxy', '--port', port, '--openai-api-url', upstreamBase.replace(/\/v1\/?$/, '')],
{
detached: true,
stdio: 'ignore',
env: {
...process.env,
OPENAI_TARGET_API_URL: upstreamBase,
HEADROOM_OUTPUT_SHAPER: '0',
},
},
);
child.unref();
for (let attempt = 0; attempt < 20; attempt += 1) {
await new Promise((resolve) => setTimeout(resolve, 1000));
if (await probeHeadroomProxyReachable({ baseUrl: headroomBase })) return true;
}
throw new Error('headroom proxy failed to become reachable');
}
async function main() {
const previousMode = resolveHeadroomMode();
process.env.MEMIND_HEADROOM_MODE = 'active';
await ensureHeadroomProxy();
const reachable = await probeHeadroomProxyReachable({ baseUrl: headroomBase });
if (!reachable) {
throw new Error(`headroom proxy unreachable at ${headroomBase}`);
}
const routing = resolveGoosedApiUrlWithHeadroom({
apiUrl: upstreamBase,
mode: 'active',
headroomReachable: true,
eligible: true,
});
if (!routing.routed) {
throw new Error(`headroom routing not active: ${JSON.stringify(routing)}`);
}
const messages = buildToolHeavyMessages();
const direct = await chatCompletion(upstreamBase, messages);
const viaHeadroom = await chatCompletion(headroomBase, messages, { viaHeadroom: true });
const savedPromptTokens = direct.promptTokens - viaHeadroom.promptTokens;
const savedRatio = direct.promptTokens > 0
? savedPromptTokens / direct.promptTokens
: 0;
console.log('HEADROOM_ACTIVE_OBSERVATION:');
console.log(` mode=${previousMode}->active`);
console.log(` upstream=${upstreamBase}`);
console.log(` headroom=${headroomBase}`);
console.log(` model=${model}`);
console.log(` direct_prompt_tokens=${direct.promptTokens}`);
console.log(` headroom_prompt_tokens=${viaHeadroom.promptTokens}`);
console.log(` saved_prompt_tokens=${savedPromptTokens}`);
console.log(` saved_prompt_ratio=${savedRatio.toFixed(4)}`);
console.log(` direct_reply=${JSON.stringify(direct.reply).slice(0, 120)}`);
console.log(` headroom_reply=${JSON.stringify(viaHeadroom.reply).slice(0, 120)}`);
if (viaHeadroom.promptTokens >= direct.promptTokens) {
console.warn('HEADROOM_ACTIVE_WARN: no prompt token reduction observed on this sample');
} else {
console.log('HEADROOM_ACTIVE_OK: prompt tokens reduced through headroom proxy');
}
}
main().catch((error) => {
console.error(`HEADROOM_ACTIVE_FAIL: ${error.message}`);
process.exit(1);
});
+1 -1
View File
@@ -45,7 +45,7 @@ async function main() {
stdio: 'inherit',
env: {
...process.env,
OPENAI_BASE_URL: upstream,
OPENAI_TARGET_API_URL: upstream,
HEADROOM_OUTPUT_SHAPER: '0',
MEMIND_HEADROOM_MODE: process.env.MEMIND_HEADROOM_MODE ?? 'shadow',
},
+24 -3
View File
@@ -3,6 +3,7 @@ import { EventEmitter } from 'node:events';
import fs from 'node:fs/promises';
import path from 'node:path';
import { buildCursorExecutorLaunchPlan } from './cursor-agent-launch.mjs';
import { dshExecutorEnabled } from './dsh-agent-launch.mjs';
import {
extractCursorAgentDisplayText,
parseCursorAgentUsage,
@@ -22,9 +23,10 @@ function cursorExecutorEnabled(env = process.env) {
}
function codeExecutorsForEnv(env = process.env) {
return cursorExecutorEnabled(env)
? ['cursor', ...BASE_CODE_EXECUTORS]
: [...BASE_CODE_EXECUTORS];
const executors = [...BASE_CODE_EXECUTORS];
if (dshExecutorEnabled(env)) executors.unshift('dsh');
if (cursorExecutorEnabled(env)) executors.unshift('cursor');
return executors;
}
const DEFAULT_STDIO_LIMIT = 64 * 1024;
@@ -183,6 +185,11 @@ export function createToolGateway({
env.MEMIND_TOOL_GATEWAY_OPENHANDS_TASK_TYPES
?? 'repo_refactor,multi_file,complex_repo,page_data_dev_complex',
);
const dshEnabled = dshExecutorEnabled(env);
const dshTaskTypes = csvSet(
env.MEMIND_TOOL_GATEWAY_DSH_TASK_TYPES
?? 'repo_refactor,multi_file',
);
const stdioLimit = positiveInteger(env.MEMIND_TOOL_GATEWAY_STDIO_LIMIT, DEFAULT_STDIO_LIMIT);
function getStatus() {
@@ -193,7 +200,9 @@ export function createToolGateway({
executors: codeExecutorsForEnv(env),
defaultExecutor,
cursorEnabled,
dshEnabled,
cursorTaskTypes: [...cursorTaskTypes],
dshTaskTypes: [...dshTaskTypes],
openhandsTaskTypes: [...openhandsTaskTypes],
};
}
@@ -209,6 +218,7 @@ export function createToolGateway({
if (requested) return requested;
const normalizedTaskType = String(taskType ?? runMetadata.taskType ?? '').trim().toLowerCase();
if (cursorEnabled && cursorTaskTypes.has(normalizedTaskType)) return 'cursor';
if (dshEnabled && dshTaskTypes.has(normalizedTaskType)) return 'dsh';
if (openhandsTaskTypes.has(normalizedTaskType)) return 'openhands';
return defaultExecutor;
}
@@ -259,6 +269,17 @@ export function createToolGateway({
instruction: executorInstruction,
env,
});
} else if (executor === 'dsh') {
if (!llmProviderService?.getExecutorLaunchPlan) {
throw new Error('Tool Gateway missing llm provider service');
}
plan = await llmProviderService.getExecutorLaunchPlan('dsh', {
cwd,
mode: 'headless',
instruction: executorInstruction,
purpose: 'default',
includeSecret: true,
});
} else {
if (!llmProviderService?.getExecutorLaunchPlan) {
throw new Error('Tool Gateway missing llm provider service');
+52
View File
@@ -33,7 +33,9 @@ test('tool gateway is disabled by default and reports protocol', () => {
executors: ['aider', 'openhands'],
defaultExecutor: 'aider',
cursorEnabled: false,
dshEnabled: false,
cursorTaskTypes: ['h5_chat_code_task', 'mindspace_page', 'mindspace_html_page'],
dshTaskTypes: ['repo_refactor', 'multi_file'],
openhandsTaskTypes: ['repo_refactor', 'multi_file', 'complex_repo', 'page_data_dev_complex'],
});
});
@@ -194,6 +196,56 @@ test('tool gateway dry run builds executor launch plan without spawning', async
assert.equal(plans[0].options.instruction, 'fix it');
});
test('tool gateway exposes dsh executor when enabled', () => {
const gateway = createToolGateway({
env: {
MEMIND_TOOL_GATEWAY_DSH_ENABLED: '1',
MEMIND_TOOL_GATEWAY_DSH_TASK_TYPES: 'repo_refactor',
},
});
assert.deepEqual(gateway.getStatus().executors, ['dsh', 'aider', 'openhands']);
assert.equal(gateway.selectExecutor({ taskType: 'repo_refactor' }), 'dsh');
assert.equal(gateway.selectExecutor({ taskType: 'small_patch' }), 'aider');
});
test('tool gateway dsh dry run uses llm provider launch plan', async () => {
const plans = [];
const gateway = createToolGateway({
env: {
MEMIND_TOOL_GATEWAY_ENABLED: '1',
MEMIND_TOOL_GATEWAY_DRY_RUN: '1',
MEMIND_TOOL_GATEWAY_DSH_ENABLED: '1',
MEMIND_TOOL_GATEWAY_DSH_TASK_TYPES: 'repo_refactor',
},
llmProviderService: {
async getExecutorLaunchPlan(executor, options) {
plans.push({ executor, options });
return {
ok: true,
executor,
cwd: options.cwd,
command: 'npx',
args: ['-y', '@deepseek-ai/dsh', '--profile', 'headless', options.instruction],
};
},
},
});
const result = await gateway.executeJob({
runId: 'run-dsh',
requestId: 'req-dsh',
userId: 'user-1',
cwd: '/tmp/work',
taskType: 'repo_refactor',
userMessage: { content: [{ type: 'text', text: 'refactor auth module' }] },
});
assert.equal(result.dryRun, true);
assert.equal(result.executor, 'dsh');
assert.equal(plans.length, 1);
assert.equal(plans[0].executor, 'dsh');
});
test('tool gateway cursor dry run does not require llm provider service', async () => {
const gateway = createToolGateway({
env: {