147 lines
4.6 KiB
JavaScript
147 lines
4.6 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
import {
|
|
createToolGateway,
|
|
extractToolInstruction,
|
|
hardenAiderLaunchPlan,
|
|
prepareAiderReceiptFile,
|
|
resolveAiderReceiptPath,
|
|
} from './tool-gateway.mjs';
|
|
|
|
test('tool gateway extracts text instructions from H5 message content', () => {
|
|
assert.equal(
|
|
extractToolInstruction({
|
|
content: [
|
|
{ type: 'text', text: '第一步' },
|
|
{ type: 'image_url', image_url: { url: 'https://example.test/a.png' } },
|
|
{ type: 'text', text: '第二步' },
|
|
],
|
|
}),
|
|
'第一步\n第二步',
|
|
);
|
|
});
|
|
|
|
test('tool gateway is disabled by default and reports protocol', () => {
|
|
const gateway = createToolGateway({ env: {} });
|
|
assert.deepEqual(gateway.getStatus(), {
|
|
enabled: false,
|
|
dryRun: false,
|
|
protocol: 'agent-run-v1',
|
|
executors: ['aider', 'openhands'],
|
|
defaultExecutor: 'aider',
|
|
openhandsTaskTypes: ['repo_refactor', 'multi_file', 'complex_repo', 'page_data_dev_complex'],
|
|
});
|
|
});
|
|
|
|
test('tool gateway selects openhands for configured task types', () => {
|
|
const gateway = createToolGateway({
|
|
env: {
|
|
MEMIND_TOOL_GATEWAY_DEFAULT_EXECUTOR: 'aider',
|
|
MEMIND_TOOL_GATEWAY_OPENHANDS_TASK_TYPES: 'repo_refactor',
|
|
},
|
|
});
|
|
assert.equal(gateway.selectExecutor({ taskType: 'repo_refactor' }), 'openhands');
|
|
assert.equal(gateway.selectExecutor({ taskType: 'small_patch' }), 'aider');
|
|
});
|
|
|
|
test('tool gateway selects openhands for page_data_dev_complex by default', () => {
|
|
const gateway = createToolGateway({ env: {} });
|
|
assert.equal(gateway.selectExecutor({ taskType: 'page_data_dev_complex' }), 'openhands');
|
|
});
|
|
|
|
test('tool gateway honors an explicitly required Aider executor over task defaults', () => {
|
|
const gateway = createToolGateway({ env: {} });
|
|
assert.equal(gateway.selectExecutor({
|
|
taskType: 'page_data_dev_complex',
|
|
userMessage: {
|
|
metadata: {
|
|
memindRun: {
|
|
executor: 'aider',
|
|
selectedChatSkill: 'aider-development',
|
|
},
|
|
},
|
|
},
|
|
}), 'aider');
|
|
});
|
|
|
|
test('tool gateway resolves and prepares the required Aider receipt without pre-validating it', async () => {
|
|
const requestId = 'req-receipt';
|
|
const relativePath = `.memind/agent-runs/${requestId}.json`;
|
|
const userMessage = {
|
|
metadata: {
|
|
memindRun: {
|
|
validation: {
|
|
expectedFile: { path: relativePath, contains: requestId },
|
|
},
|
|
},
|
|
},
|
|
};
|
|
assert.equal(resolveAiderReceiptPath(userMessage, requestId), relativePath);
|
|
const cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-aider-receipt-'));
|
|
try {
|
|
const target = await prepareAiderReceiptFile(cwd, relativePath);
|
|
const pending = JSON.parse(await fs.readFile(target, 'utf8'));
|
|
assert.deepEqual(pending, { status: 'pending', executor: 'aider' });
|
|
assert.equal((await fs.readFile(target, 'utf8')).includes(requestId), false);
|
|
} finally {
|
|
await fs.rm(cwd, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('tool gateway hardens Aider against repository commits and includes the receipt', () => {
|
|
const plan = hardenAiderLaunchPlan(
|
|
{ ok: true, args: ['--model', 'test-model'] },
|
|
'/tmp/work/.memind/agent-runs/req.json',
|
|
['/tmp/work/public/order.html'],
|
|
);
|
|
assert.ok(plan.args.includes('--no-git'));
|
|
assert.ok(plan.args.includes('--no-auto-commits'));
|
|
assert.ok(plan.args.includes('--no-dirty-commits'));
|
|
assert.deepEqual(
|
|
plan.args.slice(-2),
|
|
['--file', '/tmp/work/.memind/agent-runs/req.json'],
|
|
);
|
|
assert.ok(plan.args.includes('/tmp/work/public/order.html'));
|
|
});
|
|
|
|
test('tool gateway dry run builds executor launch plan without spawning', async () => {
|
|
const plans = [];
|
|
const gateway = createToolGateway({
|
|
env: {
|
|
MEMIND_TOOL_GATEWAY_ENABLED: '1',
|
|
MEMIND_TOOL_GATEWAY_DRY_RUN: '1',
|
|
MEMIND_TOOL_GATEWAY_DEFAULT_EXECUTOR: 'aider',
|
|
},
|
|
llmProviderService: {
|
|
async getExecutorLaunchPlan(executor, options) {
|
|
plans.push({ executor, options });
|
|
return {
|
|
ok: true,
|
|
executor,
|
|
cwd: options.cwd,
|
|
command: '/usr/bin/true',
|
|
args: ['--noop'],
|
|
};
|
|
},
|
|
},
|
|
});
|
|
|
|
const result = await gateway.executeJob({
|
|
runId: 'run-1',
|
|
requestId: 'req-1',
|
|
userId: 'user-1',
|
|
cwd: '/tmp/work',
|
|
taskType: 'small_patch',
|
|
userMessage: { content: [{ type: 'text', text: 'fix it' }] },
|
|
});
|
|
|
|
assert.equal(result.dryRun, true);
|
|
assert.equal(result.executor, 'aider');
|
|
assert.equal(result.command, '/usr/bin/true');
|
|
assert.equal(plans.length, 1);
|
|
assert.equal(plans[0].options.instruction, 'fix it');
|
|
});
|