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:
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.',
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
@@ -2,6 +2,7 @@ const EXECUTOR_DISPLAY_LABELS = Object.freeze({
|
||||
cursor: 'TKMind 智趣',
|
||||
aider: 'Aider',
|
||||
openhands: 'OpenHands',
|
||||
dsh: 'DeepSeek Harness',
|
||||
goose: 'TKMind',
|
||||
});
|
||||
|
||||
|
||||
+43
-1
@@ -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 harness(developer preview,headless 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
@@ -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';
|
||||
|
||||
@@ -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)');
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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
@@ -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');
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user